diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5fde95babb..3f2218a3f9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,6 +15,8 @@ jobs: build: name: Build runs-on: ubuntu-latest + permissions: + pull-requests: write strategy: matrix: node-version: [18, 20] @@ -22,9 +24,7 @@ jobs: env: NODE_VERSION: ${{ matrix.node-version }} PG_VERSION: ${{ matrix.pg-version }} - TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: 'medplum' - TURBO_REMOTE_ONLY: true + SECRETS_TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} services: postgres: image: postgres:${{ matrix.pg-version }} @@ -40,15 +40,15 @@ jobs: ports: - 5432/tcp steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} registry-url: 'https://registry.npmjs.org' - name: Cache node modules - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: @@ -58,6 +58,16 @@ jobs: ${{ runner.os }}-build-${{ env.cache-name }}- ${{ runner.os }}-build- ${{ runner.os }}- + - name: Setup TurboRepo + # Conditionally setup turborepo + # In the past, turborepo would silently ignore empty environment variables + # This is no longer the case, so we need to check if the secret is set + # You cannot use `if: ${{ secrets.TURBO_TOKEN != '' }}` because secrets are not available in the `if` condition + if: ${{ env.SECRETS_TURBO_TOKEN != '' }} + run: | + echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV + echo "TURBO_TEAM=${{ secrets.TURBO_TEAM }}" >> $GITHUB_ENV + echo "TURBO_REMOTE_ONLY=${{ secrets.TURBO_REMOTE_ONLY }}" >> $GITHUB_ENV - name: Build Project run: ./scripts/build.sh env: @@ -72,7 +82,8 @@ jobs: POSTGRES_HOST: localhost POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }} - name: Upload code coverage - uses: actions/upload-artifact@v3 + if: ${{ matrix.node-version == 20 && matrix.pg-version == 14 }} + uses: actions/upload-artifact@v4 with: name: medplum-code-coverage path: coverage/lcov.info diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e8294223bd..b17c7a403a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -44,7 +44,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/coveralls.yml b/.github/workflows/coveralls.yml index 5968ea83cf..3b93d7becc 100644 --- a/.github/workflows/coveralls.yml +++ b/.github/workflows/coveralls.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest if: github.event.workflow_run.conclusion == 'success' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: repository: ${{ github.event.workflow_run.head_repository.full_name }} ref: ${{ github.event.workflow_run.head_branch }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ca0ee0898e..25e8f21310 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -13,18 +13,18 @@ jobs: env: NODE_VERSION: '20' TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: 'medplum' - TURBO_REMOTE_ONLY: true + TURBO_TEAM: ${{ secrets.TURBO_TEAM }} + TURBO_REMOTE_ONLY: ${{ secrets.TURBO_REMOTE_ONLY }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 20 registry-url: 'https://registry.npmjs.org' - name: Cache node modules - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: diff --git a/.github/workflows/madge.yml b/.github/workflows/madge.yml index 71dabc1ba4..917f8ff338 100644 --- a/.github/workflows/madge.yml +++ b/.github/workflows/madge.yml @@ -16,15 +16,15 @@ jobs: outputs: madge_check_errs: ${{ steps.madge.outputs.madge_check_errs }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 20 registry-url: 'https://registry.npmjs.org' - name: Cache node modules - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index f3b554e205..d85dcd7860 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -7,11 +7,11 @@ jobs: name: Prepare release runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 token: ${{ secrets.MEDPLUM_BOT_GITHUB_ACCESS_TOKEN }} - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 20 registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/prettier-fmt.yml b/.github/workflows/prettier-fmt.yml index 2841ae6004..b471ef509c 100644 --- a/.github/workflows/prettier-fmt.yml +++ b/.github/workflows/prettier-fmt.yml @@ -15,18 +15,20 @@ jobs: prettier-fmt: name: prettier runs-on: ubuntu-latest + permissions: + pull-requests: write outputs: prettier_fmt_errs: ${{ steps.fmt.outputs.prettier_fmt_errs }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 20 registry-url: 'https://registry.npmjs.org' - name: Cache node modules - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: @@ -97,5 +99,5 @@ jobs: echo "See: https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode" echo "" echo "https://github.com/medplum/medplum/commits/${{github.sha}}" - + exit 1 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6b2ee28ff0..831e07018f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,24 +13,24 @@ jobs: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: 'medplum' - TURBO_REMOTE_ONLY: true + TURBO_TEAM: ${{ secrets.TURBO_TEAM }} + TURBO_REMOTE_ONLY: ${{ secrets.TURBO_REMOTE_ONLY }} permissions: actions: read contents: write pull-requests: write steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 20 registry-url: 'https://registry.npmjs.org' - name: Cache node modules - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: @@ -96,14 +96,14 @@ jobs: env: NODE_VERSION: '20' TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: 'medplum' - TURBO_REMOTE_ONLY: true + TURBO_TEAM: ${{ secrets.TURBO_TEAM }} + TURBO_REMOTE_ONLY: ${{ secrets.TURBO_REMOTE_ONLY }} permissions: actions: read contents: write steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install NSIS run: choco install nsis @@ -118,7 +118,7 @@ jobs: java-version: '17' - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 20 registry-url: 'https://registry.npmjs.org' @@ -171,17 +171,17 @@ jobs: env: NODE_VERSION: '20' TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: 'medplum' - TURBO_REMOTE_ONLY: true + TURBO_TEAM: ${{ secrets.TURBO_TEAM }} + TURBO_REMOTE_ONLY: ${{ secrets.TURBO_REMOTE_ONLY }} permissions: actions: read contents: write steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 20 registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 02c665780b..90415c714b 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest if: github.event.workflow_run.conclusion == 'success' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: repository: ${{ github.event.workflow_run.head_repository.full_name }} ref: ${{ github.event.workflow_run.head_branch }} diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index b6ec15068c..2567455bb4 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -13,18 +13,18 @@ jobs: env: NODE_VERSION: '20' TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: 'medplum' - TURBO_REMOTE_ONLY: true + TURBO_TEAM: ${{ secrets.TURBO_TEAM }} + TURBO_REMOTE_ONLY: ${{ secrets.TURBO_REMOTE_ONLY }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 20 registry-url: 'https://registry.npmjs.org' - name: Cache node modules - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: diff --git a/.github/workflows/upgrade-dependencies.yml b/.github/workflows/upgrade-dependencies.yml index 0802f0cfe4..90df5def78 100644 --- a/.github/workflows/upgrade-dependencies.yml +++ b/.github/workflows/upgrade-dependencies.yml @@ -1,17 +1,22 @@ name: Upgrade dependencies -on: workflow_dispatch +on: + workflow_dispatch: + schedule: + # Every Monday at 9:00 AM UTC + # Every Monday at 1:00 AM PST (2:00 AM PDT) + - cron: '0 9 * * 1' jobs: upgrade-dependencies: name: Upgrade dependencies runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 token: ${{ secrets.MEDPLUM_BOT_GITHUB_ACCESS_TOKEN }} - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 20 registry-url: 'https://registry.npmjs.org' diff --git a/examples/foomedical/package.json b/examples/foomedical/package.json index 8282f3a0c9..a7b613a90d 100644 --- a/examples/foomedical/package.json +++ b/examples/foomedical/package.json @@ -1,6 +1,6 @@ { "name": "foomedical", - "version": "3.0.2", + "version": "3.0.4", "type": "module", "scripts": { "build": "tsc && vite build", @@ -25,21 +25,21 @@ "@babel/preset-env": "7.23.9", "@babel/preset-react": "7.23.3", "@babel/preset-typescript": "7.23.3", - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/mock": "3.0.2", - "@medplum/react": "3.0.2", - "@tabler/icons-react": "2.46.0", - "@testing-library/jest-dom": "6.3.0", - "@testing-library/react": "14.1.2", - "@types/jest": "29.5.11", - "@types/node": "20.11.8", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", + "@medplum/react": "3.0.4", + "@tabler/icons-react": "2.47.0", + "@testing-library/jest-dom": "6.4.2", + "@testing-library/react": "14.2.1", + "@types/jest": "29.5.12", + "@types/node": "20.11.19", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "babel-jest": "29.7.0", "c8": "9.1.0", @@ -48,13 +48,13 @@ "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", "jest-transform-stub": "2.0.0", - "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-chartjs-2": "5.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.3" } } diff --git a/examples/medplum-chart-demo/package.json b/examples/medplum-chart-demo/package.json index f155473540..963efab1a0 100644 --- a/examples/medplum-chart-demo/package.json +++ b/examples/medplum-chart-demo/package.json @@ -1,6 +1,6 @@ { "name": "medplum-chart-demo", - "version": "3.0.2", + "version": "3.0.4", "private": true, "type": "module", "scripts": { @@ -19,24 +19,24 @@ ] }, "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/react": "3.0.2", - "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.8", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.19", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.3" } } diff --git a/examples/medplum-chart-demo/src/pages/SearchPage.tsx b/examples/medplum-chart-demo/src/pages/SearchPage.tsx index 0777d6803b..5fc1469526 100644 --- a/examples/medplum-chart-demo/src/pages/SearchPage.tsx +++ b/examples/medplum-chart-demo/src/pages/SearchPage.tsx @@ -3,7 +3,7 @@ import { DEFAULT_SEARCH_COUNT, Filter, formatSearchQuery, - parseSearchDefinition, + parseSearchRequest, SearchRequest, SortRule, } from '@medplum/core'; @@ -20,7 +20,7 @@ export function SearchPage(): JSX.Element { const [search, setSearch] = useState(); useEffect(() => { - const parsedSearch = parseSearchDefinition(location.pathname + location.search); + const parsedSearch = parseSearchRequest(location.pathname + location.search); const populatedSearch = addSearchValues(parsedSearch, medplum.getUserConfiguration()); diff --git a/examples/medplum-demo-bots/package.json b/examples/medplum-demo-bots/package.json index 3e0ac86c35..6a8d5a5a71 100644 --- a/examples/medplum-demo-bots/package.json +++ b/examples/medplum-demo-bots/package.json @@ -1,6 +1,6 @@ { "name": "medplum-demo-bots", - "version": "3.0.2", + "version": "3.0.4", "description": "Medplum Demo Bots", "license": "Apache-2.0", "author": "Medplum ", @@ -25,24 +25,24 @@ ] }, "devDependencies": { - "@medplum/cli": "3.0.2", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/mock": "3.0.2", - "@types/node": "20.11.8", + "@medplum/cli": "3.0.4", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", + "@types/node": "20.11.19", "@types/node-fetch": "2.6.11", "@types/ssh2-sftp-client": "9.0.3", - "@vitest/coverage-v8": "1.2.2", - "@vitest/ui": "1.2.2", - "esbuild": "0.20.0", + "@vitest/coverage-v8": "1.3.0", + "@vitest/ui": "1.3.0", + "esbuild": "0.20.1", "form-data": "4.0.0", "glob": "^10.3.10", "node-fetch": "2.7.0", "pdfmake": "0.2.9", "ssh2-sftp-client": "10.0.3", - "stripe": "14.14.0", + "stripe": "14.17.0", "typescript": "5.3.3", - "vitest": "1.2.2" + "vitest": "1.3.0" } } diff --git a/examples/medplum-fhircast-demo/package.json b/examples/medplum-fhircast-demo/package.json index d423539171..73662c1d45 100644 --- a/examples/medplum-fhircast-demo/package.json +++ b/examples/medplum-fhircast-demo/package.json @@ -1,6 +1,6 @@ { "name": "medplum-fhircast-demo", - "version": "3.0.2", + "version": "3.0.4", "private": true, "type": "module", "scripts": { @@ -15,23 +15,23 @@ ] }, "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/react": "3.0.2", - "@tabler/icons-react": "2.46.0", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", + "@tabler/icons-react": "2.47.0", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.3" } } diff --git a/examples/medplum-hello-world/package.json b/examples/medplum-hello-world/package.json index a4f4873eb1..ffffb31275 100644 --- a/examples/medplum-hello-world/package.json +++ b/examples/medplum-hello-world/package.json @@ -1,6 +1,6 @@ { "name": "medplum-hello-world", - "version": "3.0.2", + "version": "3.0.4", "private": true, "type": "module", "scripts": { @@ -19,24 +19,24 @@ ] }, "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/react": "3.0.2", - "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.8", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.19", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.3" } } diff --git a/examples/medplum-live-chat-demo/package.json b/examples/medplum-live-chat-demo/package.json index c7751e2e4c..ce0df630af 100644 --- a/examples/medplum-live-chat-demo/package.json +++ b/examples/medplum-live-chat-demo/package.json @@ -1,6 +1,6 @@ { "name": "medplum-live-chat-demo", - "version": "3.0.2", + "version": "3.0.4", "private": true, "type": "module", "scripts": { @@ -19,24 +19,24 @@ ] }, "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/react": "3.0.2", - "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.8", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.19", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.3" } } diff --git a/examples/medplum-live-chat-demo/src/components/Chat.tsx b/examples/medplum-live-chat-demo/src/components/Chat.tsx index 0b3ec32dd6..1e4fe5108a 100644 --- a/examples/medplum-live-chat-demo/src/components/Chat.tsx +++ b/examples/medplum-live-chat-demo/src/components/Chat.tsx @@ -1,15 +1,9 @@ import { ActionIcon, Avatar, Group, Paper, ScrollArea, Stack, TextInput } from '@mantine/core'; import { showNotification } from '@mantine/notifications'; -import { - MedplumClient, - ProfileResource, - createReference, - getReferenceString, - normalizeErrorString, -} from '@medplum/core'; -import { Bundle, Communication, Parameters, Practitioner, Reference, Subscription } from '@medplum/fhirtypes'; +import { ProfileResource, createReference, getReferenceString, normalizeErrorString } from '@medplum/core'; +import { Bundle, Communication, Practitioner, Reference } from '@medplum/fhirtypes'; import { DrAliceSmith } from '@medplum/mock'; -import { Form, useMedplum } from '@medplum/react'; +import { Form, useMedplum, useSubscription } from '@medplum/react'; import { IconArrowRight, IconChevronDown, IconMessage } from '@tabler/icons-react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import classes from './Chat.module.css'; @@ -49,44 +43,6 @@ function upsertCommunications( setCommunications(newCommunications); } -async function listenForSub( - medplum: MedplumClient, - subscription: Subscription, - setWebSocket: (ws: WebSocket) => void, - onNewMessage: (c: Communication) => void -): Promise { - const { parameter } = (await medplum.get( - `/fhir/R4/Subscription/${subscription.id}/$get-ws-binding-token` - )) as Parameters; - const token = parameter?.find((param) => param.name === 'token')?.valueString; - const url = parameter?.find((param) => param.name === 'websocket-url')?.valueUrl; - if (!token) { - throw new Error('Failed to get token!'); - } - - if (!url) { - throw new Error('Failed to get URL from $get-ws-binding-token!'); - } - - const ws = new WebSocket(url); - - ws.addEventListener('open', () => { - ws.send(JSON.stringify({ type: 'bind-with-token', payload: { token } })); - }); - - ws.addEventListener('message', (event: MessageEvent) => { - const bundle = JSON.parse(event.data) as Bundle; - const communication = bundle.entry?.[1]?.resource; - if (!communication || communication.resourceType !== 'Communication') { - console.error('Invalid chat bundle!'); - return; - } - onNewMessage(communication); - }); - - setWebSocket(ws); -} - export function Chat(): JSX.Element | null { const medplum = useMedplum(); const [open, setOpen] = useState(false); @@ -94,22 +50,36 @@ export function Chat(): JSX.Element | null { const inputRef = useRef(null); const scrollAreaRef = useRef(null); const [profile, setProfile] = useState(medplum.getProfile()); - const [subscription, setSubscription] = useState(undefined); - const [webSocket, setWebSocket] = useState(); - - const creatingSubRef = useRef(false); - const deleteSubTimerRef = useRef(); const profileRefStr = useMemo( () => (profile ? getReferenceString(medplum.getProfile() as ProfileResource) : ''), [profile, medplum] ); + useSubscription( + `Communication?sender=${profileRefStr},${DR_ALICE_SMITH.reference}&recipient=${DR_ALICE_SMITH.reference},${profileRefStr}`, + (bundle: Bundle) => { + const communication = bundle.entry?.[1]?.resource as Communication; + upsertCommunications(communicationsRef.current, [communication], setCommunications); + if (!(communication.received && communication.status === 'completed')) { + medplum + .updateResource({ + ...communication, + received: communication.received ?? new Date().toISOString(), // Mark as received if needed + status: 'completed', // Mark as read + // See: https://www.medplum.com/docs/communications/organizing-communications#:~:text=THE%20Communication%20LIFECYCLE + // for more info about recommended `Communication` lifecycle + }) + .catch(console.error); + } + } + ); + // Disabled because we can make sure this will trigger an update when local profile !== medplum.getProfile() // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => { const latestProfile = medplum.getProfile(); - if (profile !== latestProfile) { + if (profile?.id !== latestProfile?.id) { setProfile(latestProfile); } }); @@ -118,9 +88,6 @@ export function Chat(): JSX.Element | null { communicationsRef.current = communications; const prevCommunicationsRef = useRef(communications); - const openRef = useRef(); - openRef.current = open; - const scrollToBottomRef = useRef(false); const searchMessages = useCallback(async (): Promise => { @@ -136,66 +103,6 @@ export function Chat(): JSX.Element | null { upsertCommunications(communicationsRef.current, searchResult, setCommunications); }, [medplum, profileRefStr]); - useEffect(() => { - // Create subscription... - // Check for creatingSubRef - if (!(profile && profileRefStr) || creatingSubRef.current) { - return () => undefined; - } - if (!subscription) { - creatingSubRef.current = true; - medplum - .createResource({ - resourceType: 'Subscription', - criteria: `Communication?sender=${profileRefStr},${DR_ALICE_SMITH.reference}&recipient=${DR_ALICE_SMITH.reference},${profileRefStr}`, - status: 'active', - reason: `Watch for Communications between ${profileRefStr} and ${DR_ALICE_SMITH.reference}.`, - channel: { - type: 'websocket', - }, - }) - .then((subscription) => { - setSubscription(subscription); - listenForSub(medplum, subscription, setWebSocket, (communication) => { - upsertCommunications(communicationsRef.current, [communication], setCommunications); - // NOTE: We may normally want to do a guard like this to prevent our client from updating messages that we have sent ourselves, but in this case - // We allow them to be updated anyways so that we have received timestamps on our sent messages - // const senderId = resolveId(communication.sender); - // if (senderId === profile.id) { - // return; - // } - // You may want to update received time and "completed" status independently, but for the purposes of this demo we are updating them together - if (!(communication.received && communication.status === 'completed')) { - medplum - .updateResource({ - ...communication, - received: new Date().toISOString(), // Mark as received - status: 'completed', // Mark as read - // See: https://www.medplum.com/docs/communications/organizing-communications#:~:text=THE%20Communication%20LIFECYCLE - // for more info about recommended `Communication` lifecycle - }) - .catch(console.error); - } - }) - .then(() => { - creatingSubRef.current = false; - }) - .catch(console.error); - }) - .catch(console.error); - } - if (deleteSubTimerRef.current) { - clearTimeout(deleteSubTimerRef.current); - deleteSubTimerRef.current = undefined; - } - return () => { - if (deleteSubTimerRef.current) { - return; - } - deleteSubTimerRef.current = setTimeout(() => {}, 1000); - }; - }, [medplum, profile, profileRefStr, subscription]); - const sendMessage = useCallback( async (formData: Record) => { if (inputRef.current) { @@ -254,7 +161,7 @@ export function Chat(): JSX.Element | null { return null; } - if (open && webSocket) { + if (open) { return ( <>
@@ -272,7 +179,10 @@ export function Chat(): JSX.Element | null { (currCommTime !== prevCommTime &&
{currCommTime}
)} {c.sender?.reference === profileRefStr ? ( - + ) : ( @@ -341,7 +251,7 @@ export function Chat(): JSX.Element | null { interface ChatBubbleProps { communication: Communication; - showSeen?: boolean; + showDelivered?: boolean; } function ChatBubble(props: ChatBubbleProps): JSX.Element { @@ -350,9 +260,9 @@ function ChatBubble(props: ChatBubbleProps): JSX.Element { return (
{content}
- {props.showSeen && ( + {props.showDelivered && (
- Seen {seenTime.getHours()}:{seenTime.getMinutes().toString().length === 1 ? '0' : ''} + Delivered {seenTime.getHours()}:{seenTime.getMinutes().toString().length === 1 ? '0' : ''} {seenTime.getMinutes()}
)} diff --git a/examples/medplum-live-chat-demo/tsconfig.json b/examples/medplum-live-chat-demo/tsconfig.json index 79240f45dd..e963f801eb 100644 --- a/examples/medplum-live-chat-demo/tsconfig.json +++ b/examples/medplum-live-chat-demo/tsconfig.json @@ -13,7 +13,7 @@ "moduleResolution": "Node", "resolveJsonModule": true, "noEmit": true, - "jsx": "react-jsx" + "jsx": "react-jsx", }, - "include": ["src"] + "include": ["src"], } diff --git a/examples/medplum-nextjs-demo/package.json b/examples/medplum-nextjs-demo/package.json index 5aba00f179..e70623fb19 100644 --- a/examples/medplum-nextjs-demo/package.json +++ b/examples/medplum-nextjs-demo/package.json @@ -1,6 +1,6 @@ { "name": "medplum-nextjs-demo", - "version": "3.0.2", + "version": "3.0.4", "private": true, "type": "module", "scripts": { @@ -10,25 +10,25 @@ "start": "next start" }, "dependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", - "@medplum/core": "3.0.2", - "@medplum/react": "3.0.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/react": "3.0.4", "next": "14.1.0", "react": "18.2.0", "react-dom": "18.2.0", "rfc6902": "5.1.1" }, "devDependencies": { - "@medplum/fhirtypes": "3.0.2", - "@types/node": "20.11.8", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", + "@medplum/fhirtypes": "3.0.4", + "@types/node": "20.11.19", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", "eslint": "8.56.0", "eslint-config-next": "14.1.0", - "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", "typescript": "5.3.3" } } diff --git a/examples/medplum-provider/.gitignore b/examples/medplum-provider/.gitignore new file mode 100644 index 0000000000..b02a1ff770 --- /dev/null +++ b/examples/medplum-provider/.gitignore @@ -0,0 +1,25 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local +package-lock.json + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/examples/medplum-provider/README.md b/examples/medplum-provider/README.md new file mode 100644 index 0000000000..59a132c76f --- /dev/null +++ b/examples/medplum-provider/README.md @@ -0,0 +1,70 @@ +

Medplum Charting Demo

+

A starter application for building a charting app on Medplum.

+

+ + + +

+ +This example app demonstrates the following: + +- Using [Medplum React Components](https://storybook.medplum.com/?path=/docs/medplum-introduction--docs) to display a chart that provides visibility on a patient + - More information on a [charting experience](https://www.medplum.com/docs/charting) +- Using [Medplum GraphQL](https://graphiql.medplum.com/) queries to fetch linked resources + +### Components of the Patient Chart + +The Patient Chart has 3 distinct panels + +1. Clinical Chart + The left panel shows the patient history and their status. Notable information in the clinical chart includes the following Resources: + - Patient Information + - Upcoming Appointments + - Documented Visits + - List of Allergies + - List of Problems + - Medication Requests + - Smoking Status + - Vitals + +2. Tasks + The center panel shows list of the Task resource with a different focus resource. See our [Tasks Guide](https://www.medplum.com/docs/careplans/tasks) for more details. + - Each focus is interactive to either review or fill out + - This example project demonstrates interactions of the following resources: + - Questionnaire + - QuestionnaireResponse + - DiagnosticReport + - CarePlan + +3. SOAP Note + The right most panel documents an enounter with the patient through a questionnaire. Filling out and submitting the questionnaire automatically creates a task, with the response as the focus to be reviewed. + +### Getting Started + +If you haven't already done so, follow the instructions in [this tutorial](https://www.medplum.com/docs/tutorials/app/register) to register a Medplum project to store your data. + +[Fork](https://github.com/medplum/medplum-hello-world/fork) and clone the repo. + +Next, install the dependencies. + +```bash +npm install +``` + +Then, run the app + +```bash +npm run dev +``` + +This app should run on `http://localhost:3000/` + +### About Medplum + +[Medplum](https://www.medplum.com/) is an open-source, API-first EHR. Medplum makes it easy to build healthcare apps quickly with less code. + +Medplum supports self-hosting and provides a [hosted service](https://app.medplum.com/). Medplum Hello World uses the hosted service as a backend. + +- Read our [documentation](https://www.medplum.com/docs) +- Browse our [react component library](https://docs.medplum.com/storybook/index.html?) +- Join our [Discord](https://discord.gg/medplum) diff --git a/examples/medplum-provider/index.html b/examples/medplum-provider/index.html new file mode 100644 index 0000000000..039c0a01d5 --- /dev/null +++ b/examples/medplum-provider/index.html @@ -0,0 +1,13 @@ + + + + + + Medplum Provider + + +
+ + + + diff --git a/examples/medplum-provider/package.json b/examples/medplum-provider/package.json new file mode 100644 index 0000000000..3ab551a9f4 --- /dev/null +++ b/examples/medplum-provider/package.json @@ -0,0 +1,42 @@ +{ + "name": "medplum-provider", + "version": "3.0.4", + "private": true, + "type": "module", + "scripts": { + "build": "tsc && vite build", + "dev": "vite", + "preview": "vite preview" + }, + "prettier": { + "printWidth": 120, + "singleQuote": true, + "trailingComma": "es5" + }, + "eslintConfig": { + "extends": [ + "@medplum/eslint-config" + ] + }, + "devDependencies": { + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.19", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", + "@vitejs/plugin-react": "4.2.1", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-router-dom": "6.22.1", + "typescript": "5.3.3", + "vite": "5.1.3" + } +} diff --git a/examples/medplum-provider/postcss.config.mjs b/examples/medplum-provider/postcss.config.mjs new file mode 100644 index 0000000000..feba649756 --- /dev/null +++ b/examples/medplum-provider/postcss.config.mjs @@ -0,0 +1,19 @@ +import mantinePreset from 'postcss-preset-mantine'; +import simpleVars from 'postcss-simple-vars'; + +const config = { + plugins: [ + mantinePreset(), + simpleVars({ + variables: { + 'mantine-breakpoint-xs': '36em', + 'mantine-breakpoint-sm': '48em', + 'mantine-breakpoint-md': '62em', + 'mantine-breakpoint-lg': '75em', + 'mantine-breakpoint-xl': '88em', + }, + }), + ], +}; + +export default config; diff --git a/examples/medplum-provider/src/App.tsx b/examples/medplum-provider/src/App.tsx new file mode 100644 index 0000000000..a42d792ade --- /dev/null +++ b/examples/medplum-provider/src/App.tsx @@ -0,0 +1,68 @@ +import { AppShell, ErrorBoundary, Loading, Logo, useMedplum, useMedplumProfile } from '@medplum/react'; +import { IconUser } from '@tabler/icons-react'; +import { Suspense } from 'react'; +import { Navigate, Route, Routes } from 'react-router-dom'; +import { HomePage } from './pages/HomePage'; +import { ResourcePage } from './pages/ResourcePage'; +import { SearchPage } from './pages/SearchPage'; +import { SignInPage } from './pages/SignInPage'; +import { EditTab } from './pages/patient/EditTab'; +import { EncounterTab } from './pages/patient/EncounterTab'; +import { LabsTab } from './pages/patient/LabsTab'; +import { MedsTab } from './pages/patient/MedsTab'; +import { PatientPage } from './pages/patient/PatientPage'; +import { PatientSearchPage } from './pages/patient/PatientSearchPage'; +import { TasksTab } from './pages/patient/TasksTab'; +import { TimelineTab } from './pages/patient/TimelineTab'; + +export function App(): JSX.Element | null { + const medplum = useMedplum(); + const profile = useMedplumProfile(); + + if (medplum.isLoading()) { + return null; + } + + return ( + } + menus={[ + { + title: 'Charts', + links: [{ icon: , label: 'Patients', href: '/' }], + }, + ]} + > + + }> + + {profile ? ( + <> + } /> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + } /> + } /> + } /> + + ) : ( + <> + } /> + } /> + + )} + + + + + ); +} diff --git a/examples/medplum-provider/src/components/soapnote/SoapNote.questionnaire.ts b/examples/medplum-provider/src/components/soapnote/SoapNote.questionnaire.ts new file mode 100644 index 0000000000..e801e28c80 --- /dev/null +++ b/examples/medplum-provider/src/components/soapnote/SoapNote.questionnaire.ts @@ -0,0 +1,201 @@ +import { Questionnaire } from '@medplum/fhirtypes'; + +export const defaultSoapNoteQuestionnaire: Questionnaire = { + resourceType: 'Questionnaire', + name: 'SOAP Note - Menopause', + title: 'SOAP Note - Menopause Service', + status: 'active', + id: 'b0166b1b-d4b8-4d09-b6bc-2f094cf7de9e', + item: [ + { + id: 'id-55', + linkId: 'date', + type: 'date', + text: 'Date of Visit', + initial: [ + { + valueDate: '2023-10-19', + }, + ], + }, + { + id: 'id-64', + linkId: 'g10', + type: 'group', + text: 'Subjective Evaluation', + item: [ + { + id: 'id-94', + linkId: 'q29', + type: 'boolean', + text: 'Hot flashes', + }, + { + id: 'id-96', + linkId: 'q31', + type: 'boolean', + text: 'Mood swings', + }, + { + id: 'id-95', + linkId: 'q30', + type: 'boolean', + text: 'Vaginal dryness', + }, + { + id: 'id-97', + linkId: 'q32', + type: 'boolean', + text: 'Sleep Disturbance', + }, + { + id: 'id-67', + linkId: 'q13', + type: 'open-choice', + text: 'Self-reported history', + answerOption: [ + { + id: 'id-76', + valueString: 'Blood clots', + }, + { + id: 'id-77', + valueString: 'Stroke', + }, + { + id: 'id-78', + valueString: 'Breast cancer', + }, + { + id: 'id-79', + valueString: 'Endometrial cancer', + }, + { + id: 'id-80', + valueString: 'Irregular bleeding', + }, + { + id: 'id-81', + valueString: 'BMI > 30', + }, + ], + }, + { + id: 'id-100', + linkId: 'q34', + type: 'text', + text: 'Details', + }, + ], + }, + { + id: 'id-68', + linkId: 'g14', + type: 'group', + text: 'Objective Evaluation', + item: [ + { + id: 'id-92', + linkId: 'q27', + type: 'boolean', + text: 'Confirmed patient age > 45 y as of today', + }, + ], + }, + { + id: 'id-71', + linkId: 'g17', + type: 'group', + text: 'Assessment', + item: [ + { + id: 'id-72', + linkId: 'q18', + type: 'choice', + text: 'Based on data, submit the SNOMED diagnosis codes for this patient.', + answerOption: [ + { + id: 'id-83', + valueString: 'No current problems (160245001)', + }, + { + id: 'id-84', + valueString: 'Early menopause (160397006)', + }, + { + id: 'id-85', + valueString: 'Late menopause (160398001)', + }, + ], + }, + { + id: 'id-98', + linkId: 'q33', + type: 'string', + text: 'Other Diagnosis Code', + }, + ], + }, + { + id: 'id-86', + linkId: 'g21', + type: 'group', + text: 'Plan', + item: [ + { + id: 'id-87', + linkId: 'q22', + type: 'open-choice', + text: 'Outline next steps', + answerOption: [ + { + id: 'id-88', + valueString: 'Order cholesterol test', + }, + { + id: 'id-89', + valueString: 'Order imaging', + }, + { + id: 'id-90', + valueString: 'Refer to specialist', + }, + { + id: 'id-99', + valueString: 'Systemic HRT', + }, + { + id: 'id-100', + valueString: 'SSRI/SNRI', + }, + { + id: 'id-101', + valueString: 'Local HRT', + }, + { + id: 'id-102', + valueString: 'OTC treatment', + }, + ], + }, + ], + }, + { + id: 'id-103', + linkId: 'q37', + type: 'choice', + text: 'Choose a code for this visit', + answerOption: [ + { + id: 'id-104', + valueString: '99204 (Moderate Complexity)', + }, + { + id: 'id-105', + valueString: '99205 (High Complexity)', + }, + ], + }, + ], + subjectType: ['Patient'], +}; diff --git a/examples/medplum-provider/src/components/soapnote/SoapNote.tsx b/examples/medplum-provider/src/components/soapnote/SoapNote.tsx new file mode 100644 index 0000000000..f656c05ac8 --- /dev/null +++ b/examples/medplum-provider/src/components/soapnote/SoapNote.tsx @@ -0,0 +1,56 @@ +import { Box, Flex, Text } from '@mantine/core'; +import { createReference } from '@medplum/core'; +import { QuestionnaireResponse, Task } from '@medplum/fhirtypes'; +import { Document, QuestionnaireForm, useMedplum } from '@medplum/react'; +import { IconCircleCheck } from '@tabler/icons-react'; +import { useState } from 'react'; +import { usePatient } from '../../hooks/usePatient'; +import { defaultSoapNoteQuestionnaire } from './SoapNote.questionnaire'; + +export function SoapNote(): JSX.Element { + const medplum = useMedplum(); + const patient = usePatient(); + const [submitted, setSubmitted] = useState(false); + + async function handleSubmit(questionnaireResponse: QuestionnaireResponse): Promise { + const response = await medplum.createResource(questionnaireResponse); + + const newTask: Task = { + resourceType: 'Task', + status: 'ready', + intent: 'order', + code: { + coding: [ + { + system: 'http://loinc.org', + code: 'LL1474-7', + display: 'Physician menopause management note', + }, + ], + }, + focus: { + reference: `QuestionnaireResponse/${response.id}`, + }, + for: patient ? createReference(patient) : undefined, + }; + await medplum.createResource(newTask); + setSubmitted(true); + } + + return ( + + + + {submitted ? ( + + Submitted + + + ) : null} + + + ); +} diff --git a/examples/medplum-provider/src/components/tasks/DiagnosticReportTask.tsx b/examples/medplum-provider/src/components/tasks/DiagnosticReportTask.tsx new file mode 100644 index 0000000000..ca59ead096 --- /dev/null +++ b/examples/medplum-provider/src/components/tasks/DiagnosticReportTask.tsx @@ -0,0 +1,36 @@ +import { Button, Flex, Modal } from '@mantine/core'; +import { DiagnosticReport, Task } from '@medplum/fhirtypes'; +import { CodeableConceptDisplay, DiagnosticReportDisplay, useMedplum } from '@medplum/react'; +import { IconCircleCheck } from '@tabler/icons-react'; +import { useState } from 'react'; +import { TaskCellProps } from './TaskList'; + +export function DiagnosticReportModal(props: TaskCellProps): JSX.Element { + const [open, setOpen] = useState(false); + const [reviewed, setReviewed] = useState(false); + const medplum = useMedplum(); + const report = props.resource as DiagnosticReport; + + async function handleClick(): Promise { + await medplum.updateResource({ ...props.task, status: 'completed' }); + setReviewed(true); + } + if (reviewed) { + return ; + } + return ( + <> + + setOpen(false)} size="xl"> + + + + + + + ); +} diff --git a/examples/medplum-provider/src/components/tasks/QuestionnaireTask.tsx b/examples/medplum-provider/src/components/tasks/QuestionnaireTask.tsx new file mode 100644 index 0000000000..e4c4866734 --- /dev/null +++ b/examples/medplum-provider/src/components/tasks/QuestionnaireTask.tsx @@ -0,0 +1,151 @@ +import { Anchor, Button, Collapse, Flex, Modal, Text } from '@mantine/core'; +import { useDisclosure } from '@mantine/hooks'; +import { TypedValue, getTypedPropertyValue } from '@medplum/core'; +import { Questionnaire, QuestionnaireResponse, QuestionnaireResponseItem, Task } from '@medplum/fhirtypes'; +import { QuestionnaireForm, ResourcePropertyDisplay, useMedplum } from '@medplum/react'; +import { IconCircleCheck } from '@tabler/icons-react'; +import { useEffect, useState } from 'react'; +import { TaskCellProps } from './TaskList'; + +export function QuestionnaireTask(props: TaskCellProps): JSX.Element { + const [submitted, setSubmitted] = useState(false); + const questionnaire = props.resource as Questionnaire; + const medplum = useMedplum(); + + if (submitted) { + return ; + } + + async function handleSubmit(questionnaireResponse: QuestionnaireResponse): Promise { + await medplum.createResource(questionnaireResponse); + await medplum.updateResource({ ...props.task, status: 'completed' }); + setSubmitted(true); + } + return (questionnaire.item ?? []).length <= 2 ? ( + + ) : ( + + ); +} + +function QuestionnaireModal(props: { + readonly task: Task; + readonly questionnaire: Questionnaire; + readonly setSubmitted: (submit: boolean) => void; + readonly handleSubmit: (questionnaireResponse: QuestionnaireResponse) => Promise; +}): JSX.Element { + const [open, setOpen] = useState(false); + + async function handleModalSubmit(questionnaireResponse: QuestionnaireResponse): Promise { + await props.handleSubmit(questionnaireResponse); + setOpen(false); + } + + return ( + <> + + setOpen(false)} size="xl"> + + + + ); +} + +function QuestionnaireQuickAction(props: { + readonly task: Task; + readonly questionnaire: Questionnaire; + readonly setSubmitted: (submit: boolean) => void; + readonly handleSubmit: (questionnaireResponse: QuestionnaireResponse) => Promise; +}): JSX.Element { + return ; +} + +export function ResponseDisplay(props: TaskCellProps): JSX.Element | null { + const resource = props.resource as QuestionnaireResponse; + const items = resource.item ?? []; + const [schemaLoaded, setSchemaLoaded] = useState(false); + const [reviewed, setReviewed] = useState(false); + const [opened, { toggle }] = useDisclosure(false); + const medplum = useMedplum(); + + useEffect(() => { + medplum + .requestSchema('QuestionnaireResponse') + .then(() => setSchemaLoaded(true)) + .catch(console.error); + }, [medplum]); + + async function handleClick(): Promise { + await medplum.updateResource({ ...props.task, status: 'completed' }); + setReviewed(true); + } + + const visibleItems = items.slice(0, 3); + const collapsedItems = items.slice(3, items.length); + + if (!schemaLoaded) { + return null; + } + + return ( + <> + {visibleItems.map((item) => ( + + ))} + + {collapsedItems.map((item) => ( + + ))} + + {showCollapsibleButton(collapsedItems) && {opened ? 'Show less' : 'Show more'}} + + {reviewed ? ( + + ) : ( + + )} + + + ); +} + +function ItemRow(props: { item: QuestionnaireResponseItem }): JSX.Element | null { + const item = props.item; + const itemValue = getTypedPropertyValue( + { type: 'QuestionnaireResponseItemAnswer', value: item?.answer?.[0] }, + 'value' + ) as TypedValue; + if (!itemValue) { + return null; + } + const propertyName = itemValue.type; + return ( + + {item.text} + + + + + ); +} + +function showCollapsibleButton(items: QuestionnaireResponseItem[]): boolean { + if (items.length === 0) { + return false; + } + return items.some((item) => item.answer?.length); +} diff --git a/examples/medplum-provider/src/components/tasks/TaskList.tsx b/examples/medplum-provider/src/components/tasks/TaskList.tsx new file mode 100644 index 0000000000..de7956bc75 --- /dev/null +++ b/examples/medplum-provider/src/components/tasks/TaskList.tsx @@ -0,0 +1,182 @@ +import { Box, Card, Divider, Flex, Group, Text, Title } from '@mantine/core'; +import { formatDate } from '@medplum/core'; +import { CodeableConcept, Questionnaire, Reference, Resource, Task } from '@medplum/fhirtypes'; +import { + CodeableConceptDisplay, + ErrorBoundary, + ResourceName, + StatusBadge, + Timeline, + useMedplum, + useResource, +} from '@medplum/react'; +import { IconFilePencil, IconHeart, IconListCheck, IconReportMedical } from '@tabler/icons-react'; +import { Fragment, ReactNode, useEffect, useState } from 'react'; +import { usePatient } from '../../hooks/usePatient'; +import { DiagnosticReportModal } from './DiagnosticReportTask'; +import { QuestionnaireTask, ResponseDisplay } from './QuestionnaireTask'; + +const focusIcons: Record = { + Questionnaire: , + QuestionnaireResponse: , + DiagnosticReport: , + CarePlan: , +}; + +export interface TaskCellProps { + readonly task: Task; + readonly resource: Resource; +} + +interface TaskItemProps { + readonly task: Task; + readonly resource: Resource; + readonly profile?: Reference; + readonly children?: ReactNode; +} + +export function TaskList(): JSX.Element | null { + const medplum = useMedplum(); + const patient = usePatient(); + const [tasks, setTasks] = useState([]); + + useEffect(() => { + medplum + .searchResources( + 'Task', + `patient=${patient?.id}&status:not=completed&status:not=failed&status:not=rejected&focus:missing=false` + ) + .then((response) => { + setTasks(response); + }) + .catch(console.error); + }); + + if (!patient) { + return null; + } + + return ( + + {`Required Action (${tasks.length})`} + + + {tasks.map((task, idx) => ( + + + {idx !== tasks.length - 1 ? : null} + + ))} + + + + ); +} + +function FocusTimeline(props: { task: Task }): JSX.Element | null { + const task = props.task; + + const focused = useResource(task.focus); + if (!focused) { + return null; + } + return ( + + + + + + ); +} + +function ResourceFocus(props: TaskCellProps): JSX.Element { + const resource = props.resource; + + function renderResourceContent(resource: Resource): JSX.Element { + switch (resource.resourceType) { + case 'Questionnaire': + return ; + case 'QuestionnaireResponse': + return ; + case 'DiagnosticReport': + return ; + case 'CarePlan': + return ; + default: + return
; + } + } + + if (!resource) { + return
; + } + return {renderResourceContent(resource)}; +} + +function TaskItem(props: TaskItemProps): JSX.Element { + const { task, resource, profile } = props; + const author = profile ?? resource.meta?.author; + const dateTime = resource.meta?.lastUpdated; + return ( + <> + + {focusIcons[resource.resourceType]} + + + + + + + + {'status' in props.resource && } + + + + + + + · + + + {formatDate(dateTime)} + + + + + + <>{props.children} + + + ); +} + +function TaskTitle(props: TaskCellProps): JSX.Element { + const [title, setTitle] = useState(); + const medplum = useMedplum(); + + useEffect(() => { + async function fetchQuestionnaireTitle(): Promise { + if (props.resource.resourceType === 'QuestionnaireResponse') { + const questionnaireId = props.resource.questionnaire?.split('/')[1]; + try { + const questionnaire = await medplum.readResource('Questionnaire', questionnaireId as string); + setTitle(<>{questionnaire?.title} Response); + } catch (err) { + setTitle(<>Response); + } + } + } + + if ('code' in props.resource && props.resource.code) { + setTitle(); + } else if (props.resource.resourceType === 'Questionnaire') { + setTitle(<>{props.resource.title}); + } else if (props.resource.resourceType === 'QuestionnaireResponse') { + fetchQuestionnaireTitle().catch(console.error); + } else { + setTitle(<>{props.task.code}); + } + }, [props.resource, props.task, medplum]); + + return title ?? <>; +} diff --git a/examples/medplum-provider/src/hooks/usePatient.ts b/examples/medplum-provider/src/hooks/usePatient.ts new file mode 100644 index 0000000000..9d9cfd4b0a --- /dev/null +++ b/examples/medplum-provider/src/hooks/usePatient.ts @@ -0,0 +1,11 @@ +import { Patient } from '@medplum/fhirtypes'; +import { useResource } from '@medplum/react'; +import { useParams } from 'react-router-dom'; + +export function usePatient(): Patient | undefined { + const { patientId } = useParams(); + if (!patientId) { + throw new Error('Patient ID not found'); + } + return useResource({ reference: `Patient/${patientId}` }); +} diff --git a/examples/medplum-provider/src/main.tsx b/examples/medplum-provider/src/main.tsx new file mode 100644 index 0000000000..544c09acd8 --- /dev/null +++ b/examples/medplum-provider/src/main.tsx @@ -0,0 +1,47 @@ +import { MantineProvider, createTheme } from '@mantine/core'; +import '@mantine/core/styles.css'; +import { MedplumClient } from '@medplum/core'; +import { MedplumProvider } from '@medplum/react'; +// import '@medplum/react/styles.css'; +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import { App } from './App'; + +const medplum = new MedplumClient({ + onUnauthenticated: () => (window.location.href = '/'), + // baseUrl: 'http://localhost:8103/', // Uncomment this to run against the server on your localhost +}); + +const theme = createTheme({ + headings: { + sizes: { + h1: { + fontSize: '1.125rem', + fontWeight: '500', + lineHeight: '2.0', + }, + }, + }, + fontSizes: { + xs: '0.6875rem', + sm: '0.875rem', + md: '0.875rem', + lg: '1.0rem', + xl: '1.125rem', + }, +}); + +const container = document.getElementById('root') as HTMLDivElement; +const root = createRoot(container); +root.render( + + + + + + + + + +); diff --git a/examples/medplum-provider/src/pages/HomePage.tsx b/examples/medplum-provider/src/pages/HomePage.tsx new file mode 100644 index 0000000000..6acc2372b5 --- /dev/null +++ b/examples/medplum-provider/src/pages/HomePage.tsx @@ -0,0 +1,33 @@ +import { Title } from '@mantine/core'; +import { getReferenceString } from '@medplum/core'; +import { Practitioner } from '@medplum/fhirtypes'; +import { Document, ResourceName, SearchControl, useMedplumNavigate, useMedplumProfile } from '@medplum/react'; +import { Outlet } from 'react-router-dom'; + +/** + * Home page that greets the user and displays a list of patients. + * @returns A React component that displays the home page. + */ +export function HomePage(): JSX.Element { + // useMedplumProfile() returns the "profile resource" associated with the user. + // This can be a Practitioner, Patient, or RelatedPerson depending on the user's role in the project. + // See the "Register" tutorial for more detail + // https://www.medplum.com/docs/tutorials/register + const profile = useMedplumProfile() as Practitioner; + + const navigate = useMedplumNavigate(); + + return ( + + + Welcome <ResourceName value={profile} link /> + + navigate(`/${getReferenceString(e.resource)}`)} + hideToolbar + /> + + + ); +} diff --git a/examples/medplum-provider/src/pages/ResourcePage.tsx b/examples/medplum-provider/src/pages/ResourcePage.tsx new file mode 100644 index 0000000000..59f9a6b759 --- /dev/null +++ b/examples/medplum-provider/src/pages/ResourcePage.tsx @@ -0,0 +1,37 @@ +import { Title } from '@mantine/core'; +import { getDisplayString, getReferenceString } from '@medplum/core'; +import { Resource, ResourceType } from '@medplum/fhirtypes'; +import { Document, ResourceTable, useMedplum } from '@medplum/react'; +import { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; + +/** + * This is an example of a generic "Resource Display" page. + * It uses the Medplum `` component to display a resource. + * @returns A React component that displays a resource. + */ +export function ResourcePage(): JSX.Element | null { + const medplum = useMedplum(); + const { resourceType, id } = useParams(); + const [resource, setResource] = useState(undefined); + + useEffect(() => { + if (resourceType && id) { + medplum + .readResource(resourceType as ResourceType, id) + .then(setResource) + .catch(console.error); + } + }, [medplum, resourceType, id]); + + if (!resource) { + return null; + } + + return ( + + {getDisplayString(resource)} + + + ); +} diff --git a/examples/medplum-provider/src/pages/SearchPage.module.css b/examples/medplum-provider/src/pages/SearchPage.module.css new file mode 100644 index 0000000000..f80eca6060 --- /dev/null +++ b/examples/medplum-provider/src/pages/SearchPage.module.css @@ -0,0 +1,6 @@ +.paper { + @mixin smaller-than $mantine-breakpoint-sm { + margin: 2px; + padding: 4px; + } +} diff --git a/examples/medplum-provider/src/pages/SearchPage.tsx b/examples/medplum-provider/src/pages/SearchPage.tsx new file mode 100644 index 0000000000..5fc1469526 --- /dev/null +++ b/examples/medplum-provider/src/pages/SearchPage.tsx @@ -0,0 +1,104 @@ +import { Paper } from '@mantine/core'; +import { + DEFAULT_SEARCH_COUNT, + Filter, + formatSearchQuery, + parseSearchRequest, + SearchRequest, + SortRule, +} from '@medplum/core'; +import { UserConfiguration } from '@medplum/fhirtypes'; +import { Loading, MemoizedSearchControl, useMedplum } from '@medplum/react'; +import { useEffect, useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import classes from './SearchPage.module.css'; + +export function SearchPage(): JSX.Element { + const medplum = useMedplum(); + const navigate = useNavigate(); + const location = useLocation(); + const [search, setSearch] = useState(); + + useEffect(() => { + const parsedSearch = parseSearchRequest(location.pathname + location.search); + + const populatedSearch = addSearchValues(parsedSearch, medplum.getUserConfiguration()); + + if ( + location.pathname === `/${populatedSearch.resourceType}` && + location.search === formatSearchQuery(populatedSearch) + ) { + saveLastSearch(populatedSearch); + setSearch(populatedSearch); + } else { + navigate(`/${populatedSearch.resourceType}${formatSearchQuery(populatedSearch)}`); + } + }, [medplum, navigate, location]); + + if (!search?.resourceType || !search.fields || search.fields.length === 0) { + return ; + } + + return ( + + navigate(`/${e.resource.resourceType}/${e.resource.id}`)} + onAuxClick={(e) => window.open(`/${e.resource.resourceType}/${e.resource.id}`, '_blank')} + onChange={(e) => { + navigate(`/${search.resourceType}${formatSearchQuery(e.definition)}`); + }} + /> + + ); +} + +function addSearchValues(search: SearchRequest, config: UserConfiguration | undefined): SearchRequest { + const resourceType = search.resourceType || getDefaultResourceType(config); + const fields = search.fields ?? ['_id', '_lastUpdated']; + const filters = search.filters ?? (!search.resourceType ? getDefaultFilters(resourceType) : undefined); + const sortRules = search.sortRules ?? getDefaultSortRules(resourceType); + const offset = search.offset ?? 0; + const count = search.count ?? DEFAULT_SEARCH_COUNT; + + return { + ...search, + resourceType, + fields, + filters, + sortRules, + offset, + count, + }; +} + +function getDefaultResourceType(config: UserConfiguration | undefined): string { + return ( + localStorage.getItem('defaultResourceType') ?? + config?.option?.find((o) => o.id === 'defaultResourceType')?.valueString ?? + 'Task' + ); +} + +function getDefaultFilters(resourceType: string): Filter[] | undefined { + return getLastSearch(resourceType)?.filters; +} + +function getDefaultSortRules(resourceType: string): SortRule[] { + const lastSearch = getLastSearch(resourceType); + if (lastSearch?.sortRules) { + return lastSearch.sortRules; + } + return [{ code: '_lastUpdated', descending: true }]; +} + +function getLastSearch(resourceType: string): SearchRequest | undefined { + const value = localStorage.getItem(resourceType + '-defaultSearch'); + return value ? (JSON.parse(value) as SearchRequest) : undefined; +} + +function saveLastSearch(search: SearchRequest): void { + localStorage.setItem('defaultResourceType', search.resourceType); + localStorage.setItem(search.resourceType + '-defaultSearch', JSON.stringify(search)); +} diff --git a/examples/medplum-provider/src/pages/SignInPage.tsx b/examples/medplum-provider/src/pages/SignInPage.tsx new file mode 100644 index 0000000000..d7770ed8a2 --- /dev/null +++ b/examples/medplum-provider/src/pages/SignInPage.tsx @@ -0,0 +1,17 @@ +import { Title } from '@mantine/core'; +import { Logo, SignInForm } from '@medplum/react'; +import { useNavigate } from 'react-router-dom'; + +export function SignInPage(): JSX.Element { + const navigate = useNavigate(); + return ( + navigate('/')} + > + + Sign in to Medplum + + ); +} diff --git a/examples/medplum-provider/src/pages/patient/EditTab.tsx b/examples/medplum-provider/src/pages/patient/EditTab.tsx new file mode 100644 index 0000000000..961cb0dd70 --- /dev/null +++ b/examples/medplum-provider/src/pages/patient/EditTab.tsx @@ -0,0 +1,51 @@ +import { showNotification } from '@mantine/notifications'; +import { deepClone, normalizeErrorString, normalizeOperationOutcome } from '@medplum/core'; +import { OperationOutcome, Resource } from '@medplum/fhirtypes'; +import { Document, ResourceForm, useMedplum } from '@medplum/react'; +import { useCallback, useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; + +export function EditTab(): JSX.Element | null { + const medplum = useMedplum(); + const { patientId } = useParams() as { patientId: string }; + const [value, setValue] = useState(); + const navigate = useNavigate(); + const [outcome, setOutcome] = useState(); + + useEffect(() => { + medplum + .readResource('Patient', patientId) + .then((resource) => setValue(deepClone(resource))) + .catch((err) => { + setOutcome(normalizeOperationOutcome(err)); + showNotification({ color: 'red', message: normalizeErrorString(err), autoClose: false }); + }); + }, [medplum, patientId]); + + const handleSubmit = useCallback( + (newResource: Resource): void => { + setOutcome(undefined); + medplum + .updateResource(newResource) + .then(() => { + navigate(`/Patient/${patientId}/timeline`); + showNotification({ color: 'green', message: 'Success' }); + }) + .catch((err) => { + setOutcome(normalizeOperationOutcome(err)); + showNotification({ color: 'red', message: normalizeErrorString(err), autoClose: false }); + }); + }, + [medplum, patientId, navigate] + ); + + if (!value) { + return null; + } + + return ( + + + + ); +} diff --git a/examples/medplum-provider/src/pages/patient/EncounterTab.tsx b/examples/medplum-provider/src/pages/patient/EncounterTab.tsx new file mode 100644 index 0000000000..b808269a7f --- /dev/null +++ b/examples/medplum-provider/src/pages/patient/EncounterTab.tsx @@ -0,0 +1,12 @@ +import { Group } from '@mantine/core'; +import { SoapNote } from '../../components/soapnote/SoapNote'; +import { TaskList } from '../../components/tasks/TaskList'; + +export function EncounterTab(): JSX.Element { + return ( + + + + + ); +} diff --git a/examples/medplum-provider/src/pages/patient/LabsTab.tsx b/examples/medplum-provider/src/pages/patient/LabsTab.tsx new file mode 100644 index 0000000000..1f65971444 --- /dev/null +++ b/examples/medplum-provider/src/pages/patient/LabsTab.tsx @@ -0,0 +1,5 @@ +import { Document } from '@medplum/react'; + +export function LabsTab(): JSX.Element { + return Labs; +} diff --git a/examples/medplum-provider/src/pages/patient/MedsTab.tsx b/examples/medplum-provider/src/pages/patient/MedsTab.tsx new file mode 100644 index 0000000000..e8aacf1759 --- /dev/null +++ b/examples/medplum-provider/src/pages/patient/MedsTab.tsx @@ -0,0 +1,5 @@ +import { Document } from '@medplum/react'; + +export function MedsTab(): JSX.Element { + return Meds; +} diff --git a/examples/medplum-provider/src/pages/patient/PatientPage.module.css b/examples/medplum-provider/src/pages/patient/PatientPage.module.css new file mode 100644 index 0000000000..45466f220a --- /dev/null +++ b/examples/medplum-provider/src/pages/patient/PatientPage.module.css @@ -0,0 +1,15 @@ +.container { + display: flex; + flex-direction: row; + align-items: stretch; +} + +.sidebar { + flex: 1; + width: 350px; + height: 100%; +} + +.content { + width: 100%; +} diff --git a/examples/medplum-provider/src/pages/patient/PatientPage.tsx b/examples/medplum-provider/src/pages/patient/PatientPage.tsx new file mode 100644 index 0000000000..c60a2f095b --- /dev/null +++ b/examples/medplum-provider/src/pages/patient/PatientPage.tsx @@ -0,0 +1,72 @@ +import { Loader, Paper, ScrollArea, Tabs } from '@mantine/core'; +import { getReferenceString } from '@medplum/core'; +import { PatientSummary } from '@medplum/react'; +import { Fragment, useState } from 'react'; +import { Outlet, useNavigate } from 'react-router-dom'; +import { usePatient } from '../../hooks/usePatient'; +import classes from './PatientPage.module.css'; + +const tabs = [ + { id: 'timeline', url: '', label: 'Timeline' }, + { id: 'edit', url: 'edit', label: 'Edit' }, + { id: 'encounter', url: 'encounter', label: 'Encounter' }, + { id: 'tasks', url: 'Task?patient=%patient.id', label: 'Tasks' }, + { id: 'meds', url: 'MedicationRequest?patient=%patient.id', label: 'Meds' }, + { id: 'labs', url: 'ServiceRequest?patient=%patient.id', label: 'Labs' }, + { id: 'devices', url: 'Device?patient=%patient.id', label: 'Devices' }, +]; + +export function PatientPage(): JSX.Element { + const navigate = useNavigate(); + const patient = usePatient(); + const [currentTab, setCurrentTab] = useState(() => { + const tabId = window.location.pathname.split('/')[3] ?? ''; + return tabId && tabs.find((t) => t.id === tabId) ? tabId : tabs[0].id; + }); + + if (!patient) { + return ; + } + + /** + * Handles a tab change event. + * @param newTabName - The new tab name. + */ + function onTabChange(newTabName: string | null): void { + if (!newTabName) { + newTabName = tabs[0].id; + } + + const tab = tabs.find((t) => t.id === newTabName); + if (tab) { + setCurrentTab(tab.id); + navigate(`/Patient/${patient?.id}/${tab.url.replace('%patient.id', patient?.id as string)}`); + } + } + + return ( + +
+
+ +
+
+ + + + + {tabs.map((t) => ( + + {t.label} + + ))} + + + + + +
+
+
+ ); +} diff --git a/examples/medplum-provider/src/pages/patient/PatientSearchPage.tsx b/examples/medplum-provider/src/pages/patient/PatientSearchPage.tsx new file mode 100644 index 0000000000..725ae1f93d --- /dev/null +++ b/examples/medplum-provider/src/pages/patient/PatientSearchPage.tsx @@ -0,0 +1,69 @@ +import { Paper } from '@mantine/core'; +import { DEFAULT_SEARCH_COUNT, formatSearchQuery, parseSearchRequest, SearchRequest } from '@medplum/core'; +import { Patient } from '@medplum/fhirtypes'; +import { Loading, MemoizedSearchControl, useMedplum } from '@medplum/react'; +import { useEffect, useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { usePatient } from '../../hooks/usePatient'; + +export function PatientSearchPage(): JSX.Element { + const medplum = useMedplum(); + const patient = usePatient(); + const navigate = useNavigate(); + const location = useLocation(); + const [search, setSearch] = useState(); + + useEffect(() => { + if (!patient) { + return; + } + + const parsedSearch = parseSearchRequest(location.pathname + location.search); + const populatedSearch = addSearchValues(patient, parsedSearch); + + if ( + location.pathname === `/Patient/${patient.id}/${populatedSearch.resourceType}` && + location.search === formatSearchQuery(populatedSearch) + ) { + setSearch(populatedSearch); + } else { + navigate(`/Patient/${patient.id}/${populatedSearch.resourceType}${formatSearchQuery(populatedSearch)}`); + } + }, [medplum, patient, navigate, location]); + + if (!patient || !search?.resourceType || !search.fields || search.fields.length === 0) { + return ; + } + + return ( + + navigate(`/Patient/${patient.id}/${e.resource.resourceType}/${e.resource.id}`)} + onAuxClick={(e) => window.open(`/Patient/${patient.id}/${e.resource.resourceType}/${e.resource.id}`, '_blank')} + onChange={(e) => { + navigate(`/Patient/${patient.id}/${search.resourceType}${formatSearchQuery(e.definition)}`); + }} + /> + + ); +} + +function addSearchValues(patient: Patient, search: SearchRequest): SearchRequest { + const resourceType = search.resourceType; + const fields = search.fields ?? ['_id', '_lastUpdated']; + const filters = search.filters ?? []; + const sortRules = search.sortRules; + const offset = search.offset ?? 0; + const count = search.count ?? DEFAULT_SEARCH_COUNT; + return { + ...search, + resourceType, + fields, + filters, + sortRules, + offset, + count, + }; +} diff --git a/examples/medplum-provider/src/pages/patient/TasksTab.tsx b/examples/medplum-provider/src/pages/patient/TasksTab.tsx new file mode 100644 index 0000000000..66237dacd0 --- /dev/null +++ b/examples/medplum-provider/src/pages/patient/TasksTab.tsx @@ -0,0 +1,5 @@ +import { Document } from '@medplum/react'; + +export function TasksTab(): JSX.Element { + return Tasks; +} diff --git a/examples/medplum-provider/src/pages/patient/TimelineTab.tsx b/examples/medplum-provider/src/pages/patient/TimelineTab.tsx new file mode 100644 index 0000000000..a236133af3 --- /dev/null +++ b/examples/medplum-provider/src/pages/patient/TimelineTab.tsx @@ -0,0 +1,11 @@ +import { Loader } from '@mantine/core'; +import { PatientTimeline } from '@medplum/react'; +import { usePatient } from '../../hooks/usePatient'; + +export function TimelineTab(): JSX.Element { + const patient = usePatient(); + if (!patient) { + return ; + } + return ; +} diff --git a/examples/medplum-provider/src/vite-env.d.ts b/examples/medplum-provider/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/examples/medplum-provider/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/medplum-provider/tsconfig.json b/examples/medplum-provider/tsconfig.json new file mode 100644 index 0000000000..79240f45dd --- /dev/null +++ b/examples/medplum-provider/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"] +} diff --git a/examples/medplum-provider/vercel.json b/examples/medplum-provider/vercel.json new file mode 100644 index 0000000000..00e7eccdce --- /dev/null +++ b/examples/medplum-provider/vercel.json @@ -0,0 +1,3 @@ +{ + "routes": [{ "src": "/[^.]+", "dest": "/", "status": 200 }] +} diff --git a/examples/medplum-provider/vite.config.ts b/examples/medplum-provider/vite.config.ts new file mode 100644 index 0000000000..f21161a976 --- /dev/null +++ b/examples/medplum-provider/vite.config.ts @@ -0,0 +1,21 @@ +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; +import dns from 'dns'; +import path from 'path'; + +dns.setDefaultResultOrder('verbatim'); + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + host: 'localhost', + port: 3000, + }, + resolve: { + alias: { + '@medplum/core': path.resolve(__dirname, '../../packages/core/src'), + '@medplum/react': path.resolve(__dirname, '../../packages/react/src'), + }, + }, +}); diff --git a/examples/medplum-react-native-example/package.json b/examples/medplum-react-native-example/package.json index d56b71442c..37b91c30f5 100644 --- a/examples/medplum-react-native-example/package.json +++ b/examples/medplum-react-native-example/package.json @@ -1,6 +1,6 @@ { "name": "medplum-react-native-example", - "version": "3.0.2", + "version": "3.0.4", "main": "src/main.ts", "scripts": { "android": "expo start --android", @@ -20,14 +20,14 @@ }, "dependencies": { "@expo/webpack-config": "19.0.1", - "@medplum/core": "3.0.2", - "@medplum/expo-polyfills": "3.0.2", - "@medplum/react-hooks": "3.0.2", - "expo": "50.0.4", + "@medplum/core": "3.0.4", + "@medplum/expo-polyfills": "3.0.4", + "@medplum/react-hooks": "3.0.4", + "expo": "50.0.7", "expo-status-bar": "1.11.1", "react": "18.2.0", "react-dom": "18.2.0", - "react-native": "0.73.2", + "react-native": "0.73.4", "react-native-web": "0.19.10" }, "devDependencies": { diff --git a/examples/medplum-task-demo/package.json b/examples/medplum-task-demo/package.json index 4cd48951ce..be69fa3334 100644 --- a/examples/medplum-task-demo/package.json +++ b/examples/medplum-task-demo/package.json @@ -1,6 +1,6 @@ { "name": "medplum-task-demo", - "version": "3.0.2", + "version": "3.0.4", "private": true, "type": "module", "scripts": { @@ -22,25 +22,25 @@ ] }, "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", - "@medplum/core": "3.0.2", - "@medplum/definitions": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/react": "3.0.2", - "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.8", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.19", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.3" } } diff --git a/examples/medplum-task-demo/src/pages/SearchPage.tsx b/examples/medplum-task-demo/src/pages/SearchPage.tsx index a1ec1af412..a1ba054821 100644 --- a/examples/medplum-task-demo/src/pages/SearchPage.tsx +++ b/examples/medplum-task-demo/src/pages/SearchPage.tsx @@ -1,5 +1,5 @@ import { Tabs } from '@mantine/core'; -import { formatSearchQuery, getReferenceString, Operator, parseSearchDefinition, SearchRequest } from '@medplum/core'; +import { formatSearchQuery, getReferenceString, Operator, parseSearchRequest, SearchRequest } from '@medplum/core'; import { Document, Loading, SearchControl, useMedplum } from '@medplum/react'; import { useEffect, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; @@ -14,24 +14,24 @@ export function SearchPage(): JSX.Element { const [isNewOpen, setIsNewOpen] = useState(false); const [showTabs, setShowTabs] = useState(() => { - const search = parseSearchDefinition(window.location.pathname + window.location.search); + const search = parseSearchRequest(window.location.pathname + window.location.search); return shouldShowTabs(search); }); const tabs = ['Active', 'Completed']; const searchQuery = window.location.search; - const currentSearch = parseSearchDefinition(searchQuery); + const currentSearch = parseSearchRequest(searchQuery); const currentTab = handleInitialTab(currentSearch); useEffect(() => { - const searchQuery = parseSearchDefinition(location.pathname + location.search); + const searchQuery = parseSearchRequest(location.pathname + location.search); setShowTabs(shouldShowTabs(searchQuery)); }, [location]); useEffect(() => { // Parse the search definition from the url and get the correct fields for the resource type - const parsedSearch = parseSearchDefinition(location.pathname + location.search); + const parsedSearch = parseSearchRequest(location.pathname + location.search); if (!parsedSearch.resourceType) { navigate('/Task'); return; diff --git a/examples/medplum-websocket-subscriptions-demo/package.json b/examples/medplum-websocket-subscriptions-demo/package.json index ced1eec3df..318a4c8d4f 100644 --- a/examples/medplum-websocket-subscriptions-demo/package.json +++ b/examples/medplum-websocket-subscriptions-demo/package.json @@ -1,6 +1,6 @@ { "name": "medplum-websocket-subscriptions-demo", - "version": "3.0.2", + "version": "3.0.4", "private": true, "type": "module", "scripts": { @@ -20,24 +20,24 @@ }, "devDependencies": { "@emotion/react": "11.11.3", - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhir-router": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/mock": "3.0.2", - "@medplum/react": "3.0.2", - "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.8", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhir-router": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", + "@medplum/react": "3.0.4", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.19", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.3" } } diff --git a/examples/medplum-websocket-subscriptions-demo/src/App.tsx b/examples/medplum-websocket-subscriptions-demo/src/App.tsx index 5c976d0b8b..3fd5ce7d3b 100644 --- a/examples/medplum-websocket-subscriptions-demo/src/App.tsx +++ b/examples/medplum-websocket-subscriptions-demo/src/App.tsx @@ -1,5 +1,14 @@ -import { AppShell, ErrorBoundary, Loading, Logo, useMedplum, useMedplumProfile } from '@medplum/react'; -import { IconHome } from '@tabler/icons-react'; +import { ProfileResource, getReferenceString } from '@medplum/core'; +import { + AppShell, + ErrorBoundary, + Loading, + Logo, + NotificationIcon, + useMedplum, + useMedplumProfile, +} from '@medplum/react'; +import { IconClipboardCheck, IconHome, IconMail } from '@tabler/icons-react'; import { Suspense } from 'react'; import { Route, Routes } from 'react-router-dom'; import { HomePage } from './pages/HomePage'; @@ -20,6 +29,28 @@ export function App(): JSX.Element | null { menus={[{ links: [{ label: 'Home', href: '/', icon: }] }]} resourceTypeSearchDisabled={true} headerSearchDisabled={true} + notifications={ + profile && ( + <> + } + onClick={() => console.log('foo')} + /> + } + onClick={() => console.log('foo')} + /> + + ) + } > }> diff --git a/examples/medplum-websocket-subscriptions-demo/src/components/BundleDisplay.tsx b/examples/medplum-websocket-subscriptions-demo/src/components/BundleDisplay.tsx new file mode 100644 index 0000000000..cc3b97f916 --- /dev/null +++ b/examples/medplum-websocket-subscriptions-demo/src/components/BundleDisplay.tsx @@ -0,0 +1,73 @@ +import { Accordion, ActionIcon, Chip, Group } from '@mantine/core'; +import { Bundle, Communication, Reference } from '@medplum/fhirtypes'; +import { useMedplum } from '@medplum/react'; +import { IconArrowNarrowRight, IconCheck } from '@tabler/icons-react'; +import { useCallback } from 'react'; + +export interface BundleDisplayProps { + readonly bundle: Bundle; +} + +export function BundleDisplay(props: BundleDisplayProps): JSX.Element { + const medplum = useMedplum(); + const { bundle } = props; + const communication = bundle?.entry?.[1].resource as Communication; + const [senderType, senderId] = ((communication.sender as Reference).reference as string).split('/'); + const [recipientType, recipientId] = ((communication.recipient?.[0] as Reference).reference as string).split('/'); + + const markAsCompleted = useCallback( + (e: React.SyntheticEvent) => { + e.stopPropagation(); + e.preventDefault(); + medplum + .updateResource({ + ...communication, + received: new Date().toISOString(), // Mark as received + status: 'completed', // Mark as read + // See: https://www.medplum.com/docs/communications/organizing-communications#:~:text=THE%20Communication%20LIFECYCLE + // for more info about recommended `Communication` lifecycle + }) + .catch(console.error); + }, + [medplum, communication] + ); + + return ( + + + + {bundle.timestamp}{' '} + + {senderType}/{senderId.slice(0, 8)} + + + + {recipientType}/{recipientId.slice(0, 8)} + + + {communication.status} + + {communication.status !== 'completed' && ( + + + + )} + + + +
+
{JSON.stringify(bundle, null, 2)}
+
+
+
+ ); +} diff --git a/examples/medplum-websocket-subscriptions-demo/src/main.tsx b/examples/medplum-websocket-subscriptions-demo/src/main.tsx index 4b930bd0d6..9b72e3f021 100644 --- a/examples/medplum-websocket-subscriptions-demo/src/main.tsx +++ b/examples/medplum-websocket-subscriptions-demo/src/main.tsx @@ -1,6 +1,8 @@ import { MantineProvider, MantineThemeOverride } from '@mantine/core'; +import '@mantine/core/styles.css'; import { MedplumClient } from '@medplum/core'; import { MedplumProvider } from '@medplum/react'; +import '@medplum/react/styles.css'; import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; @@ -8,7 +10,7 @@ import { App } from './App'; const medplum = new MedplumClient({ onUnauthenticated: () => (window.location.href = '/'), - baseUrl: 'http://localhost:8103/', //Uncomment this to run against the server on your localhost; also change `googleClientId` in `./pages/SignInPage.tsx` + // baseUrl: 'http://localhost:8103/', // Uncomment this to run against the server on your localhost; also change `googleClientId` in `./pages/SignInPage.tsx` }); const theme: MantineThemeOverride = { diff --git a/examples/medplum-websocket-subscriptions-demo/src/pages/HomePage.tsx b/examples/medplum-websocket-subscriptions-demo/src/pages/HomePage.tsx index 8d187d805f..093943873c 100644 --- a/examples/medplum-websocket-subscriptions-demo/src/pages/HomePage.tsx +++ b/examples/medplum-websocket-subscriptions-demo/src/pages/HomePage.tsx @@ -1,63 +1,10 @@ -import { Accordion, Button, Chip, Group, Title } from '@mantine/core'; -import { createReference } from '@medplum/core'; -import { - Bundle, - BundleEntry, - Communication, - Parameters, - Patient, - Practitioner, - Reference, - Subscription, -} from '@medplum/fhirtypes'; -import { DrAliceSmith, HomerSimpson, MargeSimpson } from '@medplum/mock'; +import { Accordion, Button, Group, Title } from '@mantine/core'; +import { createReference, getReferenceString } from '@medplum/core'; +import { Bundle, Communication, Parameters, Patient, Practitioner, Subscription } from '@medplum/fhirtypes'; +import { HomerSimpson, MargeSimpson } from '@medplum/mock'; import { Document, ResourceName, useMedplum, useMedplumProfile } from '@medplum/react'; -import { IconArrowNarrowRight } from '@tabler/icons-react'; import { useState } from 'react'; - -interface BundleDisplayProps { - readonly bundle: Bundle; -} - -function BundleDisplay(props: BundleDisplayProps): JSX.Element { - const { bundle } = props; - const communication = bundle?.entry?.[1].resource as Communication; - const [senderType, senderId] = ((communication.sender as Reference).reference as string).split('/'); - const [recipientType, recipientId] = ((communication.recipient?.[0] as Reference).reference as string).split('/'); - return ( - - - - {bundle.timestamp}{' '} - - {senderType}/{senderId.slice(0, 8)} - - - - {recipientType}/{recipientId.slice(0, 8)} - - - {communication.status === 'in-progress' ? 'Sent' : 'Received'} - - - - -
-
{JSON.stringify(bundle, null, 2)}
-
-
-
- ); -} +import { BundleDisplay } from '../components/BundleDisplay'; /** * Home page that greets the user and displays a list of patients. @@ -77,7 +24,6 @@ export function HomePage(): JSX.Element { const [bundles, setBundles] = useState([]); const [patient, setPatient] = useState(); const [anotherPatient, setAnotherPatient] = useState(); - const [practitioner, setPractitioner] = useState(); async function createSubscriptions(): Promise { if (working) { @@ -88,25 +34,24 @@ export function HomePage(): JSX.Element { const homer = await medplum.createResourceIfNoneExist(HomerSimpson, 'name="Homer Simpson"'); const marge = await medplum.createResourceIfNoneExist(MargeSimpson, 'name="Marge Simpson"'); - const drAlice = await medplum.createResourceIfNoneExist(DrAliceSmith, 'name="Alice Smith"'); - const drRefString = `${drAlice.resourceType}/${drAlice.id as string}`; - const homerRefString = `${homer.resourceType}/${homer.id as string}`; + const meRefString = getReferenceString(profile); + const homerRefString = getReferenceString(homer); const subscription1 = await medplum.createResource({ resourceType: 'Subscription', - criteria: `Communication?_compartment=${homerRefString}&recipient=${drRefString}`, + criteria: `Communication?_compartment=${homerRefString}&recipient=${meRefString}`, status: 'active', - reason: `Watch for outgoing Communications for ${homerRefString} to ${drRefString}.`, + reason: `Watch for outgoing Communications for ${homerRefString} to ${meRefString}.`, channel: { type: 'websocket', }, }); const subscription2 = await medplum.createResource({ resourceType: 'Subscription', - criteria: `Communication?_compartment=${homerRefString}&sender=${drRefString}`, + criteria: `Communication?_compartment=${homerRefString}&sender=${meRefString}`, status: 'active', - reason: `Watch for incoming Communications from ${drRefString} to ${homerRefString}.`, + reason: `Watch for incoming Communications from ${meRefString} to ${homerRefString}.`, channel: { type: 'websocket', }, @@ -115,7 +60,6 @@ export function HomePage(): JSX.Element { setSubscriptions([subscription1, subscription2]); setPatient(homer); setAnotherPatient(marge); - setPractitioner(drAlice); setWorking(false); } @@ -157,23 +101,13 @@ export function HomePage(): JSX.Element { ws.addEventListener('message', (event: MessageEvent) => { const bundle = JSON.parse(event.data) as Bundle; - for (const entry of bundle.entry as BundleEntry[]) { - const entryResource = entry?.resource; - if ( - entryResource?.resourceType === 'Communication' && - !(entryResource.received && entryResource.status === 'completed') - ) { - medplum - .updateResource({ - ...entryResource, - received: new Date().toISOString(), // Mark as received - status: 'completed', // Mark as read - // See: https://www.medplum.com/docs/communications/organizing-communications#:~:text=THE%20Communication%20LIFECYCLE - // for more info about recommended `Communication` lifecycle - }) - .catch(console.error); - } + + const firstResource = bundle.entry?.[0]?.resource; + if (firstResource?.resourceType === 'SubscriptionStatus' && firstResource.type === 'heartbeat') { + // Ignore heartbeat bundles + return; } + setBundles((s) => [bundle, ...s]); }); @@ -182,29 +116,29 @@ export function HomePage(): JSX.Element { } async function createOutgoingMessage(): Promise { - if (!(patient && practitioner)) { + if (!patient) { return; } await medplum.createResource({ resourceType: 'Communication', status: 'in-progress', - sender: createReference({ resourceType: 'Patient', id: patient.id as string }), - recipient: [createReference({ resourceType: 'Practitioner', id: practitioner.id as string })], - payload: [{ contentString: "I'm not feeling great, and not sure if the medicine is working" }], + sender: createReference(profile), + recipient: [createReference(patient)], + payload: [{ contentString: 'Can you come in tomorrow for a follow-up?' }], sent: new Date().toISOString(), }); } async function createIncomingMessage(): Promise { - if (!(patient && practitioner)) { + if (!patient) { return; } await medplum.createResource({ resourceType: 'Communication', status: 'in-progress', - sender: createReference({ resourceType: 'Practitioner', id: practitioner.id as string }), - recipient: [createReference({ resourceType: 'Patient', id: patient.id as string })], - payload: [{ contentString: 'Can you come in tomorrow for a follow-up?' }], + sender: createReference(patient), + recipient: [createReference(profile)], + payload: [{ contentString: "I'm not feeling great, and not sure if the medicine is working" }], sent: new Date().toISOString(), }); } @@ -213,14 +147,14 @@ export function HomePage(): JSX.Element { // Important when tweaking criteria with advanced queries, such as the inclusion of `_filter` expressions // How can we make this more natural in this example? async function createMessageForAnotherPatient(): Promise { - if (!(anotherPatient && practitioner)) { + if (!anotherPatient) { return; } await medplum.createResource({ resourceType: 'Communication', status: 'in-progress', - sender: createReference({ resourceType: 'Practitioner', id: practitioner.id as string }), - recipient: [createReference({ resourceType: 'Patient', id: anotherPatient.id as string })], + sender: createReference(profile), + recipient: [createReference(anotherPatient)], payload: [{ contentString: 'Are you going to be able to make it to your appointment today?' }], sent: new Date().toISOString(), }); @@ -245,7 +179,6 @@ export function HomePage(): JSX.Element { } setSubscriptions(undefined); setPatient(undefined); - setPractitioner(undefined); setWorking(false); } diff --git a/package-lock.json b/package-lock.json index 63456582a5..0c56ebe706 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "root", - "version": "3.0.2", + "version": "3.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "root", - "version": "3.0.2", + "version": "3.0.4", "workspaces": [ "packages/*", "examples/*" @@ -17,27 +17,27 @@ "@babel/preset-react": "7.23.3", "@babel/preset-typescript": "7.23.3", "@cyclonedx/cyclonedx-npm": "1.16.1", - "@microsoft/api-documenter": "7.23.20", - "@microsoft/api-extractor": "7.39.4", - "@types/jest": "29.5.11", - "@types/node": "20.11.8", + "@microsoft/api-documenter": "7.23.24", + "@microsoft/api-extractor": "7.40.2", + "@types/jest": "29.5.12", + "@types/node": "20.11.19", "babel-jest": "29.7.0", "babel-preset-vite": "1.1.3", "cross-env": "7.0.3", "danger": "11.3.1", - "esbuild": "0.20.0", + "esbuild": "0.20.1", "identity-obj-proxy": "3.0.0", "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", - "npm-check-updates": "16.14.14", + "npm-check-updates": "16.14.15", "nyc": "15.1.0", - "prettier": "3.2.4", + "prettier": "3.2.5", "rimraf": "5.0.5", - "sort-package-json": "2.6.0", + "sort-package-json": "2.7.0", "source-map-explorer": "2.5.3", "ts-node": "10.9.2", "tslib": "2.6.2", - "turbo": "1.11.3", + "turbo": "1.12.4", "typescript": "5.3.3" }, "engines": { @@ -45,27 +45,27 @@ } }, "examples/foomedical": { - "version": "3.0.2", + "version": "3.0.4", "devDependencies": { "@babel/core": "7.23.9", "@babel/preset-env": "7.23.9", "@babel/preset-react": "7.23.3", "@babel/preset-typescript": "7.23.3", - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/mock": "3.0.2", - "@medplum/react": "3.0.2", - "@tabler/icons-react": "2.46.0", - "@testing-library/jest-dom": "6.3.0", - "@testing-library/react": "14.1.2", - "@types/jest": "29.5.11", - "@types/node": "20.11.8", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", + "@medplum/react": "3.0.4", + "@tabler/icons-react": "2.47.0", + "@testing-library/jest-dom": "6.4.2", + "@testing-library/react": "14.2.1", + "@types/jest": "29.5.12", + "@types/node": "20.11.19", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "babel-jest": "29.7.0", "c8": "9.1.0", @@ -74,20 +74,20 @@ "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", "jest-transform-stub": "2.0.0", - "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-chartjs-2": "5.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.3" } }, "examples/foomedical/node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -100,30 +100,30 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, "examples/foomedical/node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -172,33 +172,33 @@ } }, "examples/medplum-chart-demo": { - "version": "3.0.2", + "version": "3.0.4", "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/react": "3.0.2", - "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.8", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.19", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.3" } }, "examples/medplum-chart-demo/node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -211,30 +211,30 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, "examples/medplum-chart-demo/node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -283,57 +283,57 @@ } }, "examples/medplum-demo-bots": { - "version": "3.0.2", + "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { - "@medplum/cli": "3.0.2", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/mock": "3.0.2", - "@types/node": "20.11.8", + "@medplum/cli": "3.0.4", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", + "@types/node": "20.11.19", "@types/node-fetch": "2.6.11", "@types/ssh2-sftp-client": "9.0.3", - "@vitest/coverage-v8": "1.2.2", - "@vitest/ui": "1.2.2", - "esbuild": "0.20.0", + "@vitest/coverage-v8": "1.3.0", + "@vitest/ui": "1.3.0", + "esbuild": "0.20.1", "form-data": "4.0.0", "glob": "^10.3.10", "node-fetch": "2.7.0", "pdfmake": "0.2.9", "ssh2-sftp-client": "10.0.3", - "stripe": "14.14.0", + "stripe": "14.17.0", "typescript": "5.3.3", - "vitest": "1.2.2" + "vitest": "1.3.0" } }, "examples/medplum-fhircast-demo": { - "version": "3.0.2", + "version": "3.0.4", "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/react": "3.0.2", - "@tabler/icons-react": "2.46.0", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", + "@tabler/icons-react": "2.47.0", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.3" } }, "examples/medplum-fhircast-demo/node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -346,30 +346,30 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, "examples/medplum-fhircast-demo/node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -418,33 +418,33 @@ } }, "examples/medplum-hello-world": { - "version": "3.0.2", + "version": "3.0.4", "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/react": "3.0.2", - "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.8", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.19", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.3" } }, "examples/medplum-hello-world/node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -457,30 +457,30 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, "examples/medplum-hello-world/node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -529,33 +529,33 @@ } }, "examples/medplum-live-chat-demo": { - "version": "3.0.2", + "version": "3.0.4", "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/react": "3.0.2", - "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.8", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.19", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.3" } }, "examples/medplum-live-chat-demo/node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -568,30 +568,30 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, "examples/medplum-live-chat-demo/node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -640,42 +640,153 @@ } }, "examples/medplum-nextjs-demo": { - "version": "3.0.2", + "version": "3.0.4", "dependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", - "@medplum/core": "3.0.2", - "@medplum/react": "3.0.2", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/react": "3.0.4", "next": "14.1.0", "react": "18.2.0", "react-dom": "18.2.0", "rfc6902": "5.1.1" }, "devDependencies": { - "@medplum/fhirtypes": "3.0.2", - "@types/node": "20.11.8", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", + "@medplum/fhirtypes": "3.0.4", + "@types/node": "20.11.19", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", "eslint": "8.56.0", "eslint-config-next": "14.1.0", - "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", "typescript": "5.3.3" } }, + "examples/medplum-provider": { + "version": "3.0.4", + "devDependencies": { + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.19", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", + "@vitejs/plugin-react": "4.2.1", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-router-dom": "6.22.1", + "typescript": "5.3.3", + "vite": "5.1.3" + } + }, + "examples/medplum-provider/node_modules/rollup": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", + "fsevents": "~2.3.2" + } + }, + "examples/medplum-provider/node_modules/vite": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", + "dev": true, + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.35", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, "examples/medplum-react-native-example": { - "version": "3.0.2", + "version": "3.0.4", "dependencies": { "@expo/webpack-config": "19.0.1", - "@medplum/core": "3.0.2", - "@medplum/expo-polyfills": "3.0.2", - "@medplum/react-hooks": "3.0.2", - "expo": "50.0.4", + "@medplum/core": "3.0.4", + "@medplum/expo-polyfills": "3.0.4", + "@medplum/react-hooks": "3.0.4", + "expo": "50.0.7", "expo-status-bar": "1.11.1", "react": "18.2.0", "react-dom": "18.2.0", - "react-native": "0.73.2", + "react-native": "0.73.4", "react-native-web": "0.19.10" }, "devDependencies": { @@ -684,34 +795,34 @@ } }, "examples/medplum-task-demo": { - "version": "3.0.2", + "version": "3.0.4", "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", - "@medplum/core": "3.0.2", - "@medplum/definitions": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/react": "3.0.2", - "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.8", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.19", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.3" } }, "examples/medplum-task-demo/node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -724,30 +835,30 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, "examples/medplum-task-demo/node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -796,34 +907,34 @@ } }, "examples/medplum-websocket-subscriptions-demo": { - "version": "3.0.2", + "version": "3.0.4", "devDependencies": { "@emotion/react": "11.11.3", - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", - "@medplum/core": "3.0.2", - "@medplum/eslint-config": "3.0.2", - "@medplum/fhir-router": "3.0.2", - "@medplum/fhirtypes": "3.0.2", - "@medplum/mock": "3.0.2", - "@medplum/react": "3.0.2", - "@tabler/icons-react": "2.46.0", - "@types/node": "20.11.8", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/eslint-config": "3.0.4", + "@medplum/fhir-router": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", + "@medplum/react": "3.0.4", + "@tabler/icons-react": "2.47.0", + "@types/node": "20.11.19", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.1", "typescript": "5.3.3", - "vite": "5.0.12" + "vite": "5.1.3" } }, "examples/medplum-websocket-subscriptions-demo/node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -836,30 +947,30 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, "examples/medplum-websocket-subscriptions-demo/node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -1267,27 +1378,26 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@aws-sdk/client-acm": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-acm/-/client-acm-3.501.0.tgz", - "integrity": "sha512-2F5hD6mdEOSIfMm/YJfqc/0hLx2i6OyF4rEP77RZPZjFdxFsQskISQ/w8JK3TljyNJSYiXkH5Oovjv0BlU7SHA==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-acm/-/client-acm-3.515.0.tgz", + "integrity": "sha512-HXRh2mxrYD092b5Uuoe7UiVBLF5k310UpJAb1A7pj0snQTJYjr3qSrwkrRJmlbh1b7mo4pdHydKMU3NwMhIuow==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.501.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.501.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-signing": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/credential-provider-node": "3.515.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/fetch-http-handler": "^2.4.1", "@smithy/hash-node": "^2.1.1", "@smithy/invalid-dependency": "^2.1.1", @@ -1306,8 +1416,9 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-utf8": "^2.1.1", "@smithy/util-waiter": "^2.1.1", @@ -1318,27 +1429,26 @@ } }, "node_modules/@aws-sdk/client-cloudformation": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.501.0.tgz", - "integrity": "sha512-aDYafR4iJorrmftwVbNjUIKH0AM6RwNyyxBGN34R0xfLpP2fq6IPASulP56Eah2Fv14KVbYYFFDirKw4b8WPeA==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.515.0.tgz", + "integrity": "sha512-wRZTIGfKeuSlPPPb5aj3PahdDKNfLfz27VS8rAcICzRgryLg7HmTKwhxXLb6jG+AFylODedxWtpq+QflM2RghA==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.501.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.501.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-signing": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/credential-provider-node": "3.515.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/fetch-http-handler": "^2.4.1", "@smithy/hash-node": "^2.1.1", "@smithy/invalid-dependency": "^2.1.1", @@ -1357,42 +1467,42 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-utf8": "^2.1.1", "@smithy/util-waiter": "^2.1.1", "fast-xml-parser": "4.2.5", "tslib": "^2.5.0", - "uuid": "^8.3.2" + "uuid": "^9.0.1" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/client-cloudfront": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudfront/-/client-cloudfront-3.501.0.tgz", - "integrity": "sha512-VewcZ9hmzxAHiZZ44mg1hGNeDkGo+5F4lNOguFgxp6IwRDQaR3zySNzrPMZkfjI0TQHJ0f1bx7Os3T4FNyy9lw==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudfront/-/client-cloudfront-3.515.0.tgz", + "integrity": "sha512-aDiTeB2QEX6M9I3yqchCce4z78wRuDOh3oZq2eiBueJqk3R3RGm8zDdsiJ+U9N6NVSmcm7Xs55Ws8NUJZGwizw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.501.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.501.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-signing": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/credential-provider-node": "3.515.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@aws-sdk/xml-builder": "3.496.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/fetch-http-handler": "^2.4.1", "@smithy/hash-node": "^2.1.1", "@smithy/invalid-dependency": "^2.1.1", @@ -1411,8 +1521,9 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-stream": "^2.1.1", "@smithy/util-utf8": "^2.1.1", @@ -1425,27 +1536,26 @@ } }, "node_modules/@aws-sdk/client-cloudwatch-logs": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.501.0.tgz", - "integrity": "sha512-Lad4FHqTut9ZM8VKW48cnd8OBYekkVbYxWb6uWnf05NA9JMuy/zRBD4MGpv3MjhhRe4iX159G1406yK39N2GDg==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.515.0.tgz", + "integrity": "sha512-kjZU8iyi8xlN9lBAlnkWr7TCRNoiWQwmmQMSMUYUCBcQvlZCyylnFDADMRa/PR4+HIdnlFeSlPDjfsmazQ5Qig==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.501.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.501.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-signing": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/credential-provider-node": "3.515.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/eventstream-serde-browser": "^2.1.1", "@smithy/eventstream-serde-config-resolver": "^2.1.1", "@smithy/eventstream-serde-node": "^2.1.1", @@ -1467,39 +1577,39 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-utf8": "^2.1.1", "tslib": "^2.5.0", - "uuid": "^8.3.2" + "uuid": "^9.0.1" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/client-ecs": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecs/-/client-ecs-3.501.0.tgz", - "integrity": "sha512-UAOd7GKWFY6puxMYcVeZAfdfbmY2CEEsmn7sB4UKIc5pSax7xVnOpdYxZyHsHV1Xq85Vkfm/65I6r8XBFu++0w==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecs/-/client-ecs-3.515.0.tgz", + "integrity": "sha512-OVdH6Q5eHBfwOL+y4XpLoeclkk1rd4+3GTxFasb6X8rUb6CAflzW3rqO8x4g04+XMPLv01UrIla1/NwNwljTjA==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.501.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.501.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-signing": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/credential-provider-node": "3.515.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/fetch-http-handler": "^2.4.1", "@smithy/hash-node": "^2.1.1", "@smithy/invalid-dependency": "^2.1.1", @@ -1518,40 +1628,40 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-utf8": "^2.1.1", "@smithy/util-waiter": "^2.1.1", "tslib": "^2.5.0", - "uuid": "^8.3.2" + "uuid": "^9.0.1" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/client-lambda": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.501.0.tgz", - "integrity": "sha512-RkuNt5JalmPGxy3Nz73U9B0ArqVqkLLIP5BMvMlR9ghmrZWFaY/5AiIDUmz8xtfJjL9FHzdtt2/JcIYtAbrpeA==", + "version": "3.516.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.516.0.tgz", + "integrity": "sha512-VF47RCJc5q03nRjlTmMmvDrtIK7NcutzdOiUzQFKH+UcHP3XqyvgdPsN/LOiu1DqRz46bvTVT8gGUGRgrGhEFw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.501.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.501.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-signing": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/credential-provider-node": "3.515.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/eventstream-serde-browser": "^2.1.1", "@smithy/eventstream-serde-config-resolver": "^2.1.1", "@smithy/eventstream-serde-node": "^2.1.1", @@ -1573,8 +1683,9 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-stream": "^2.1.1", "@smithy/util-utf8": "^2.1.1", @@ -1586,36 +1697,36 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.501.0.tgz", - "integrity": "sha512-ovxYSGdnEdr4UrNiT+9e3ov2XULFr0bcyoXJkYxnkXPDg9Y65nuZgZAIZQMS6wnJVmNrUprhqTSQB3KHXvaEuQ==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.515.0.tgz", + "integrity": "sha512-K527n83hrMUdosxOYTzL63wtlJtmN5SUJZnGY1sUR6UyOrnOr9lS6t3AB6BgHqLFRFZJqSqmhflv2cOD7P1UPg==", "dependencies": { "@aws-crypto/sha1-browser": "3.0.0", "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.501.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.501.0", - "@aws-sdk/middleware-bucket-endpoint": "3.496.0", - "@aws-sdk/middleware-expect-continue": "3.496.0", - "@aws-sdk/middleware-flexible-checksums": "3.496.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-location-constraint": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-sdk-s3": "3.499.0", - "@aws-sdk/middleware-signing": "3.496.0", - "@aws-sdk/middleware-ssec": "3.498.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/signature-v4-multi-region": "3.499.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/credential-provider-node": "3.515.0", + "@aws-sdk/middleware-bucket-endpoint": "3.515.0", + "@aws-sdk/middleware-expect-continue": "3.515.0", + "@aws-sdk/middleware-flexible-checksums": "3.515.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-location-constraint": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-sdk-s3": "3.515.0", + "@aws-sdk/middleware-signing": "3.515.0", + "@aws-sdk/middleware-ssec": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/signature-v4-multi-region": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@aws-sdk/xml-builder": "3.496.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/eventstream-serde-browser": "^2.1.1", "@smithy/eventstream-serde-config-resolver": "^2.1.1", "@smithy/eventstream-serde-node": "^2.1.1", @@ -1640,7 +1751,7 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-stream": "^2.1.1", @@ -1654,27 +1765,26 @@ } }, "node_modules/@aws-sdk/client-secrets-manager": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.501.0.tgz", - "integrity": "sha512-/8IMGC1kakHYvHH7kv1gSZaTAoNZdga8q4IUijNH9QHaufEf8j00Pp289n2g3ML5+ACVC3Y2CuWVrrYsKHfxXw==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.515.0.tgz", + "integrity": "sha512-YO7SVh0mQ55COP5LcKsXE+o6wsOBBn3beAqKDZIBdjXho5rNhTdvbtaGTiaZmv0ALtu7TxtPVixhbDE0y1QseA==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.501.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.501.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-signing": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/credential-provider-node": "3.515.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/fetch-http-handler": "^2.4.1", "@smithy/hash-node": "^2.1.1", "@smithy/invalid-dependency": "^2.1.1", @@ -1693,39 +1803,39 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-utf8": "^2.1.1", "tslib": "^2.5.0", - "uuid": "^8.3.2" + "uuid": "^9.0.1" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/client-sesv2": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.501.0.tgz", - "integrity": "sha512-QxhDSR/x+Vcehd9NORGm+wk7m6OtFiPH1ZyM3S4CuJd0PdLSfrGpUYWyR5YczTewEVmAZdXdUYd6q4ZaH6PO1Q==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.515.0.tgz", + "integrity": "sha512-WHx1X+Qm+IHOQ5JCDUKbPh7Q+5Q26myXKFb0yF7ApvrGd+IxjDhYM/X02YQH47KEEvhcuIPvIBwIfCTgOaMonw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.501.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.501.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-signing": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/credential-provider-node": "3.515.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/fetch-http-handler": "^2.4.1", "@smithy/hash-node": "^2.1.1", "@smithy/invalid-dependency": "^2.1.1", @@ -1744,8 +1854,9 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-utf8": "^2.1.1", "tslib": "^2.5.0" @@ -1755,27 +1866,26 @@ } }, "node_modules/@aws-sdk/client-ssm": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.501.0.tgz", - "integrity": "sha512-XmG9ZwAbInsUbn8dS5iPfDs/VO7U6jOcuERrlyWpKxs+iyHUCi3dwexCCkgMvbxE4NQ4isr6DR17mk8pXpbJ2w==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.515.0.tgz", + "integrity": "sha512-Ifvy6J3BbypLTbw9x25T9c2huKh4kTZLaAGqOWOjCyl0/0ySgJqDUuvvdxWl7dPn0sMBRq3OiXyjQLbVP/gAqg==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.501.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.501.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-signing": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/credential-provider-node": "3.515.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/fetch-http-handler": "^2.4.1", "@smithy/hash-node": "^2.1.1", "@smithy/invalid-dependency": "^2.1.1", @@ -1794,37 +1904,38 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-utf8": "^2.1.1", "@smithy/util-waiter": "^2.1.1", "tslib": "^2.5.0", - "uuid": "^8.3.2" + "uuid": "^9.0.1" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.496.0.tgz", - "integrity": "sha512-fuaMuxKg7CMUsP9l3kxYWCOxFsBjdA0xj5nlikaDm1661/gB4KkAiGqRY8LsQkpNXvXU8Nj+f7oCFADFyGYzyw==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.515.0.tgz", + "integrity": "sha512-4oGBLW476zmkdN98lAns3bObRNO+DLOfg4MDUSR6l6GYBV/zGAtoy2O/FhwYKgA2L5h2ZtElGopLlk/1Q0ePLw==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/fetch-http-handler": "^2.4.1", "@smithy/hash-node": "^2.1.1", "@smithy/invalid-dependency": "^2.1.1", @@ -1843,8 +1954,9 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", "@smithy/util-utf8": "^2.1.1", "tslib": "^2.5.0" @@ -1853,26 +1965,77 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.515.0.tgz", + "integrity": "sha512-zACa8LNlPUdlNUBqQRf5a3MfouLNtcBfm84v2c8M976DwJrMGONPe1QjyLLsD38uESQiXiVQRruj/b000iMXNw==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", + "@smithy/config-resolver": "^2.1.1", + "@smithy/core": "^1.3.2", + "@smithy/fetch-http-handler": "^2.4.1", + "@smithy/hash-node": "^2.1.1", + "@smithy/invalid-dependency": "^2.1.1", + "@smithy/middleware-content-length": "^2.1.1", + "@smithy/middleware-endpoint": "^2.4.1", + "@smithy/middleware-retry": "^2.1.1", + "@smithy/middleware-serde": "^2.1.1", + "@smithy/middleware-stack": "^2.1.1", + "@smithy/node-config-provider": "^2.2.1", + "@smithy/node-http-handler": "^2.3.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "@smithy/url-parser": "^2.1.1", + "@smithy/util-base64": "^2.1.1", + "@smithy/util-body-length-browser": "^2.1.1", + "@smithy/util-body-length-node": "^2.2.1", + "@smithy/util-defaults-mode-browser": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", + "@smithy/util-endpoints": "^1.1.1", + "@smithy/util-middleware": "^2.1.1", + "@smithy/util-retry": "^2.1.1", + "@smithy/util-utf8": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "^3.515.0" + } + }, "node_modules/@aws-sdk/client-sts": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.501.0.tgz", - "integrity": "sha512-Uwc/xuxsA46dZS5s+4U703LBNDrGpWF7RB4XYEEMD21BLfGuqntxLLQux8xxKt3Pcur0CsXNja5jXt3uLnE5MA==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.515.0.tgz", + "integrity": "sha512-ScYuvaIDgip3atOJIA1FU2n0gJkEdveu1KrrCPathoUCV5zpK8qQmO/n+Fj/7hKFxeKdFbB+4W4CsJWYH94nlg==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.501.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", + "@aws-sdk/core": "3.513.0", + "@aws-sdk/middleware-host-header": "3.515.0", + "@aws-sdk/middleware-logger": "3.515.0", + "@aws-sdk/middleware-recursion-detection": "3.515.0", + "@aws-sdk/middleware-user-agent": "3.515.0", + "@aws-sdk/region-config-resolver": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", + "@aws-sdk/util-user-agent-browser": "3.515.0", + "@aws-sdk/util-user-agent-node": "3.515.0", "@smithy/config-resolver": "^2.1.1", - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/fetch-http-handler": "^2.4.1", "@smithy/hash-node": "^2.1.1", "@smithy/invalid-dependency": "^2.1.1", @@ -1891,7 +2054,7 @@ "@smithy/util-body-length-browser": "^2.1.1", "@smithy/util-body-length-node": "^2.2.1", "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", + "@smithy/util-defaults-mode-node": "^2.2.0", "@smithy/util-endpoints": "^1.1.1", "@smithy/util-middleware": "^2.1.1", "@smithy/util-retry": "^2.1.1", @@ -1901,6 +2064,9 @@ }, "engines": { "node": ">=14.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": "^3.515.0" } }, "node_modules/@aws-sdk/cloudfront-signer": { @@ -1916,11 +2082,11 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.496.0.tgz", - "integrity": "sha512-yT+ug7Cw/3eJi7x2es0+46x12+cIJm5Xv+GPWsrTFD1TKgqO/VPEgfDtHFagDNbFmjNQA65Ygc/kEdIX9ICX/A==", + "version": "3.513.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.513.0.tgz", + "integrity": "sha512-L+9DL4apWuqNKVOMJ8siAuWoRM9rZf9w1iPv8S2o83WO2jVK7E/m+rNW1dFo9HsA5V1ccDl2H2qLXx24HiHmOw==", "dependencies": { - "@smithy/core": "^1.3.1", + "@smithy/core": "^1.3.2", "@smithy/protocol-http": "^3.1.1", "@smithy/signature-v4": "^2.1.1", "@smithy/smithy-client": "^2.3.1", @@ -1932,11 +2098,11 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.496.0.tgz", - "integrity": "sha512-lukQMJ8SWWP5RqkRNOHi/H+WMhRvSWa3Fc5Jf/VP6xHiPLfF1XafcvthtV91e0VwPCiseI+HqChrcGq8pvnxHw==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.515.0.tgz", + "integrity": "sha512-45vxdyqhTAaUMERYVWOziG3K8L2TV9G4ryQS/KZ84o7NAybE9GMdoZRVmGHAO7mJJ1wQiYCM/E+i5b3NW9JfNA==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.515.0", "@smithy/property-provider": "^2.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -1945,16 +2111,36 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.515.0.tgz", + "integrity": "sha512-Ba6FXK77vU4WyheiamNjEuTFmir0eAXuJGPO27lBaA8g+V/seXGHScsbOG14aQGDOr2P02OPwKGZrWWA7BFpfQ==", + "dependencies": { + "@aws-sdk/types": "3.515.0", + "@smithy/fetch-http-handler": "^2.4.1", + "@smithy/node-http-handler": "^2.3.1", + "@smithy/property-provider": "^2.1.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/smithy-client": "^2.3.1", + "@smithy/types": "^2.9.1", + "@smithy/util-stream": "^2.1.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.501.0.tgz", - "integrity": "sha512-6UXnwLtYIr298ljveumCVXsH+x7csGscK5ylY+veRFy514NqyloRdJt8JY26hhh5SF9MYnkW+JyWSJ2Ls3tOjQ==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.496.0", - "@aws-sdk/credential-provider-process": "3.496.0", - "@aws-sdk/credential-provider-sso": "3.501.0", - "@aws-sdk/credential-provider-web-identity": "3.496.0", - "@aws-sdk/types": "3.496.0", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.515.0.tgz", + "integrity": "sha512-ouDlNZdv2TKeVEA/YZk2+XklTXyAAGdbWnl4IgN9ItaodWI+lZjdIoNC8BAooVH+atIV/cZgoGTGQL7j2TxJ9A==", + "dependencies": { + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/credential-provider-env": "3.515.0", + "@aws-sdk/credential-provider-process": "3.515.0", + "@aws-sdk/credential-provider-sso": "3.515.0", + "@aws-sdk/credential-provider-web-identity": "3.515.0", + "@aws-sdk/types": "3.515.0", "@smithy/credential-provider-imds": "^2.2.1", "@smithy/property-provider": "^2.1.1", "@smithy/shared-ini-file-loader": "^2.3.1", @@ -1966,16 +2152,17 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.501.0.tgz", - "integrity": "sha512-NM62D8gYrQ1nyLYwW4k48B2/lMHDzHDcQccS1wJakr6bg5sdtG06CumwlVcY+LAa0o1xRnhHmh/yiwj/nN4avw==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.496.0", - "@aws-sdk/credential-provider-ini": "3.501.0", - "@aws-sdk/credential-provider-process": "3.496.0", - "@aws-sdk/credential-provider-sso": "3.501.0", - "@aws-sdk/credential-provider-web-identity": "3.496.0", - "@aws-sdk/types": "3.496.0", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.515.0.tgz", + "integrity": "sha512-Y4kHSpbxksiCZZNcvsiKUd8Fb2XlyUuONEwqWFNL82ZH6TCCjBGS31wJQCSxBHqYcOL3tiORUEJkoO7uS30uQA==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.515.0", + "@aws-sdk/credential-provider-http": "3.515.0", + "@aws-sdk/credential-provider-ini": "3.515.0", + "@aws-sdk/credential-provider-process": "3.515.0", + "@aws-sdk/credential-provider-sso": "3.515.0", + "@aws-sdk/credential-provider-web-identity": "3.515.0", + "@aws-sdk/types": "3.515.0", "@smithy/credential-provider-imds": "^2.2.1", "@smithy/property-provider": "^2.1.1", "@smithy/shared-ini-file-loader": "^2.3.1", @@ -1987,11 +2174,11 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.496.0.tgz", - "integrity": "sha512-/YZscCTGOKVmGr916Th4XF8Sz6JDtZ/n2loHG9exok9iy/qIbACsTRNLP9zexPxhPoue/oZqecY5xbVljfY34A==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.515.0.tgz", + "integrity": "sha512-pSjiOA2FM63LHRKNDvEpBRp80FVGT0Mw/gzgbqFXP+sewk0WVonYbEcMDTJptH3VsLPGzqH/DQ1YL/aEIBuXFQ==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.515.0", "@smithy/property-provider": "^2.1.1", "@smithy/shared-ini-file-loader": "^2.3.1", "@smithy/types": "^2.9.1", @@ -2002,13 +2189,13 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.501.0.tgz", - "integrity": "sha512-y90dlvvZ55PwecODFdMx0NiNlJJfm7X6S61PKdLNCMRcu1YK+eWn0CmPHGHobBUQ4SEYhnFLcHSsf+VMim6BtQ==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.515.0.tgz", + "integrity": "sha512-j7vUkiSmuhpBvZYoPTRTI4ePnQbiZMFl6TNhg9b9DprC1zHkucsZnhRhqjOVlrw/H6J4jmcPGcHHTZ5WQNI5xQ==", "dependencies": { - "@aws-sdk/client-sso": "3.496.0", - "@aws-sdk/token-providers": "3.501.0", - "@aws-sdk/types": "3.496.0", + "@aws-sdk/client-sso": "3.515.0", + "@aws-sdk/token-providers": "3.515.0", + "@aws-sdk/types": "3.515.0", "@smithy/property-provider": "^2.1.1", "@smithy/shared-ini-file-loader": "^2.3.1", "@smithy/types": "^2.9.1", @@ -2019,11 +2206,12 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.496.0.tgz", - "integrity": "sha512-IbP+qLlvJSpNPj+zW6TtFuLRTK5Tf0hW+2pom4vFyi5YSH4pn8UOC136UdewX8vhXGS9BJQ5zBDMasIyl5VeGQ==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.515.0.tgz", + "integrity": "sha512-66+2g4z3fWwdoGReY8aUHvm6JrKZMTRxjuizljVmMyOBttKPeBYXvUTop/g3ZGUx1f8j+C5qsGK52viYBvtjuQ==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/types": "3.515.0", "@smithy/property-provider": "^2.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2033,9 +2221,9 @@ } }, "node_modules/@aws-sdk/lib-storage": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.501.0.tgz", - "integrity": "sha512-XZREd1O0S8AjM3RS85T2QCVJzXk+BSAGNOFvGP8t2al2Ti35O4+AvSHT75rmOGAZAsthtL2o9bt0h1VFnaIP+g==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.515.0.tgz", + "integrity": "sha512-/7z/3KnMs1ODNS9c8Skj/DFTsy6/v7n17clh1IGOcTYhhioCMA3MIzIZecWFeLjPYcUSkNQHIIjKFQt1nhZkwA==", "dependencies": { "@smithy/abort-controller": "^2.1.1", "@smithy/middleware-endpoint": "^2.4.1", @@ -2053,11 +2241,11 @@ } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.496.0.tgz", - "integrity": "sha512-B+ilBMSs3+LJuo2bl2KB8GFdu+8PPVtYEWtwhNkmnaU8iMisgMBp5uuM8sUDvJX7I4iSF0WbgnhguX4cJqfAew==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.515.0.tgz", + "integrity": "sha512-Vm423j3udFrhKPaKiXtie+6aF05efjX8lhAu5VOruIvbam7olvdWNdkH7sGWlz1ko3CVa7PwOYjGHiOOhxpEOA==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.515.0", "@aws-sdk/util-arn-parser": "3.495.0", "@smithy/node-config-provider": "^2.2.1", "@smithy/protocol-http": "^3.1.1", @@ -2070,11 +2258,11 @@ } }, "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.496.0.tgz", - "integrity": "sha512-+exo5DVc+BeDus2iI6Fz1thefHGDXxUhHZ+4VHQ6HkStMy3Y22HugyEGHSQZmtRL86Hjr7dFbEWFsC47a2ItGA==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.515.0.tgz", + "integrity": "sha512-TWCXulivab4reOMx/vxa/IwnPX78fLwI9NUoAxjsqB6W9qjmSnPD43BSVeGvbbl/YNmgk7XfMbZb6IgxW7RyzA==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.515.0", "@smithy/protocol-http": "^3.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2084,13 +2272,13 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.496.0.tgz", - "integrity": "sha512-yQIWfjEMvgsAJ7ku224vXDjXPD+f9zfKZFialJva8VUlEr7hQp4CQ0rxV3YThSaixKEDDs5k6kOjWAd2BPGr2A==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.515.0.tgz", + "integrity": "sha512-ydGjnqNeYlJaAkmQeQnS4pZRAAvzefdm8c234Qh0Fg55xRwHTNLp7uYsdfkTjrdAlj6YIO3Zr6vK6VJ6MGCwug==", "dependencies": { "@aws-crypto/crc32": "3.0.0", "@aws-crypto/crc32c": "3.0.0", - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.515.0", "@smithy/is-array-buffer": "^2.1.1", "@smithy/protocol-http": "^3.1.1", "@smithy/types": "^2.9.1", @@ -2102,11 +2290,11 @@ } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.496.0.tgz", - "integrity": "sha512-jUdPpSJeqCYXf6hSjfwsfHway7peIV8Vz51w/BN91bF4vB/bYwAC5o9/iJiK/EoByp5asxA8fg9wFOyGjzdbLg==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.515.0.tgz", + "integrity": "sha512-I1MwWPzdRKM1luvdDdjdGsDjNVPhj9zaIytEchjTY40NcKOg+p2evLD2y69ozzg8pyXK63r8DdvDGOo9QPuh0A==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.515.0", "@smithy/protocol-http": "^3.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2116,11 +2304,11 @@ } }, "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.496.0.tgz", - "integrity": "sha512-i4ocJ2Zs86OtPREbB18InFukhqg2qtBxb5gywv79IHDPVmpOYE4m/3v3yGUrkjfF2GTlUL0k5FskNNqw41yfng==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.515.0.tgz", + "integrity": "sha512-ORFC5oijjTJsHhUXy9o52/vl5Irf6e83bE/8tBp+sVVx81+E8zTTWZbysoa41c0B5Ycd0H3wCWutvjdXT16ydQ==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.515.0", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, @@ -2129,11 +2317,11 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.496.0.tgz", - "integrity": "sha512-EwMVSY6iBMeGbVnvwdaFl/ClMS/YWtxCAo+bcEtgk8ltRuo7qgbJem8Km/fvWC1vdWvIbe4ArdJ8iGzq62ffAw==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.515.0.tgz", + "integrity": "sha512-qXomJzg2m/5seQOxHi/yOXOKfSjwrrJSmEmfwJKJyQgdMbBcjz3Cz0H/1LyC6c5hHm6a/SZgSTzDAbAoUmyL+Q==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.515.0", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, @@ -2142,11 +2330,11 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.496.0.tgz", - "integrity": "sha512-+IuOcFsfqg2WAnaEzH6KhVbicqCxtOq9w3DH2jwTpddRlCx2Kqf6wCzg8luhHRGyjBZdsbIS+OXwyMevoppawA==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.515.0.tgz", + "integrity": "sha512-dokHLbTV3IHRIBrw9mGoxcNTnQsjlm7TpkJhPdGT9T4Mq399EyQo51u6IsVMm07RXLl2Zw7u+u9p+qWBFzmFRA==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.515.0", "@smithy/protocol-http": "^3.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2156,11 +2344,11 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.499.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.499.0.tgz", - "integrity": "sha512-thTb47U1hYHk5ei+yO0D0aehbgQXeAcgvyyxOID9/HDuRfWuTvKdclWh/goIeDfvSS87VBukEAjnCa5JYBwzug==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.515.0.tgz", + "integrity": "sha512-vB8JwiTEAqm1UT9xfugnCgl0H0dtBLUQQK99JwQEWjHPZmQ3HQuVkykmJRY3X0hzKMEgqXodz0hZOvf3Hq1mvQ==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.515.0", "@aws-sdk/util-arn-parser": "3.495.0", "@smithy/node-config-provider": "^2.2.1", "@smithy/protocol-http": "^3.1.1", @@ -2175,11 +2363,11 @@ } }, "node_modules/@aws-sdk/middleware-signing": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.496.0.tgz", - "integrity": "sha512-Oq73Brs4IConvWnRlh8jM1V7LHoTw9SVQklu/QW2FPlNrB3B8fuTdWHHYIWv7ybw1bykXoCY99v865Mmq/Or/g==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.515.0.tgz", + "integrity": "sha512-SdjCyQCL702I07KhCiBFcoh6+NYtnruHJQIzWwMpBteuYHnCHW1k9uZ6pqacsS+Y6qpAKfTVNpQx2zP2s6QoHA==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.515.0", "@smithy/property-provider": "^2.1.1", "@smithy/protocol-http": "^3.1.1", "@smithy/signature-v4": "^2.1.1", @@ -2192,11 +2380,11 @@ } }, "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.498.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.498.0.tgz", - "integrity": "sha512-sWujXgzeTqMZzj/pRYEnnEbSzhBosqw9DXHOY1Mg2igI9NEfGlB7lPARp6aKmCaYlP3Bcj2X86vKCqF53mbyig==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.515.0.tgz", + "integrity": "sha512-0qLjKiorosVBzzaV/o7MEyS9xqLLu02qGbP564Z/FZY74JUQEpBNedgveMUbb6lqr85RnOuwZ0GZ0cBRfH2brQ==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.515.0", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" }, @@ -2205,12 +2393,12 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.496.0.tgz", - "integrity": "sha512-+iMtRxFk0GmFWNUF4ilxylOQd9PZdR4ZC9jkcPIh1PZlvKtpCyFywKlk5RRZKklSoJ/CttcqwhMvOXTNbWm/0w==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.515.0.tgz", + "integrity": "sha512-nOqZjGA/GkjuJ5fUshec9Fv6HFd7ovOTxMJbw3MfAhqXuVZ6dKF41lpVJ4imNsgyFt3shUg9WDY8zGFjlYMB3g==", "dependencies": { - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", + "@aws-sdk/types": "3.515.0", + "@aws-sdk/util-endpoints": "3.515.0", "@smithy/protocol-http": "^3.1.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2220,11 +2408,11 @@ } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.496.0.tgz", - "integrity": "sha512-URrNVOPHPgEDm6QFu6lDC2cUFs+Jx23mA3jEwCvoKlXiEY/ZoWjH8wlX3OMUlLrF1qoUTuD03jjrJzF6zoCgug==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.515.0.tgz", + "integrity": "sha512-RIRx9loxMgEAc/r1wPfnfShOuzn4RBi8pPPv6/jhhITEeMnJe6enAh2k5y9DdiVDDgCWZgVFSv0YkAIfzAFsnQ==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.515.0", "@smithy/node-config-provider": "^2.2.1", "@smithy/types": "^2.9.1", "@smithy/util-config-provider": "^2.2.1", @@ -2236,12 +2424,12 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.499.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.499.0.tgz", - "integrity": "sha512-8HSFnZErRm7lAfk+Epxrf4QNdQEamg1CnbLybtKQQEjmvxLuXYvj16KlpYEZIwEENOMEvnCqMc7syTPkmjVhJA==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.515.0.tgz", + "integrity": "sha512-5lrCn4DSE0zL41k0L6moqcdExZhWdAnV0/oMEagrISzQYoia+aNTEeyVD3xqJhRbEW4gCj3Uoyis6c8muf7b9g==", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.499.0", - "@aws-sdk/types": "3.496.0", + "@aws-sdk/middleware-sdk-s3": "3.515.0", + "@aws-sdk/types": "3.515.0", "@smithy/protocol-http": "^3.1.1", "@smithy/signature-v4": "^2.1.1", "@smithy/types": "^2.9.1", @@ -2252,46 +2440,15 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.501.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.501.0.tgz", - "integrity": "sha512-MvLPhNxlStmQqVm2crGLUqYWvK/AbMmI9j4FbEfJ15oG/I+730zjSJQEy2MvdiqbJRDPZ/tRCL89bUedOrmi0g==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.515.0.tgz", + "integrity": "sha512-MQuf04rIcTXqwDzmyHSpFPF1fKEzRl64oXtCRUF3ddxTdK6wxXkePfK6wNCuL+GEbEcJAoCtIGIRpzGPJvQjHA==", "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/middleware-host-header": "3.496.0", - "@aws-sdk/middleware-logger": "3.496.0", - "@aws-sdk/middleware-recursion-detection": "3.496.0", - "@aws-sdk/middleware-user-agent": "3.496.0", - "@aws-sdk/region-config-resolver": "3.496.0", - "@aws-sdk/types": "3.496.0", - "@aws-sdk/util-endpoints": "3.496.0", - "@aws-sdk/util-user-agent-browser": "3.496.0", - "@aws-sdk/util-user-agent-node": "3.496.0", - "@smithy/config-resolver": "^2.1.1", - "@smithy/fetch-http-handler": "^2.4.1", - "@smithy/hash-node": "^2.1.1", - "@smithy/invalid-dependency": "^2.1.1", - "@smithy/middleware-content-length": "^2.1.1", - "@smithy/middleware-endpoint": "^2.4.1", - "@smithy/middleware-retry": "^2.1.1", - "@smithy/middleware-serde": "^2.1.1", - "@smithy/middleware-stack": "^2.1.1", - "@smithy/node-config-provider": "^2.2.1", - "@smithy/node-http-handler": "^2.3.1", + "@aws-sdk/client-sso-oidc": "3.515.0", + "@aws-sdk/types": "3.515.0", "@smithy/property-provider": "^2.1.1", - "@smithy/protocol-http": "^3.1.1", "@smithy/shared-ini-file-loader": "^2.3.1", - "@smithy/smithy-client": "^2.3.1", "@smithy/types": "^2.9.1", - "@smithy/url-parser": "^2.1.1", - "@smithy/util-base64": "^2.1.1", - "@smithy/util-body-length-browser": "^2.1.1", - "@smithy/util-body-length-node": "^2.2.1", - "@smithy/util-defaults-mode-browser": "^2.1.1", - "@smithy/util-defaults-mode-node": "^2.1.1", - "@smithy/util-endpoints": "^1.1.1", - "@smithy/util-retry": "^2.1.1", - "@smithy/util-utf8": "^2.1.1", "tslib": "^2.5.0" }, "engines": { @@ -2299,9 +2456,9 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.496.0.tgz", - "integrity": "sha512-umkGadK4QuNQaMoDICMm7NKRI/mYSXiyPjcn3d53BhsuArYU/52CebGQKdt4At7SwwsiVJZw9RNBHyN5Mm0HVw==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.515.0.tgz", + "integrity": "sha512-B3gUpiMlpT6ERaLvZZ61D0RyrQPsFYDkCncLPVkZOKkCOoFU46zi1o6T5JcYiz8vkx1q9RGloQ5exh79s5pU/w==", "dependencies": { "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2322,11 +2479,11 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.496.0.tgz", - "integrity": "sha512-1QzOiWHi383ZwqSi/R2KgKCd7M+6DxkxI5acqLPm8mvDRDP2jRjrnVaC0g9/tlttWousGEemDUWStwrD2mVYSw==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.515.0.tgz", + "integrity": "sha512-UJi+jdwcGFV/F7d3+e2aQn5yZOVpDiAgfgNhPnEtgV0WozJ5/ZUeZBgWvSc/K415N4A4D/9cbBc7+I+35qzcDQ==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.515.0", "@smithy/types": "^2.9.1", "@smithy/util-endpoints": "^1.1.1", "tslib": "^2.5.0" @@ -2347,22 +2504,22 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.496.0.tgz", - "integrity": "sha512-4j2spN+h0I0qfSMsGvJXTfQBu1e18rPdekKvzsGJxhaAE1tNgUfUT4nbvc5uVn0sNjZmirskmJ3kfbzVOrqIFg==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.515.0.tgz", + "integrity": "sha512-pTWQb0JCafTmLHLDv3Qqs/nAAJghcPdGQIBpsCStb0YEzg3At/dOi2AIQ683yYnXmeOxLXJDzmlsovfVObJScw==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.515.0", "@smithy/types": "^2.9.1", "bowser": "^2.11.0", "tslib": "^2.5.0" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.496.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.496.0.tgz", - "integrity": "sha512-h0Ax0jlDc7UIo3KoSI4C4tVLBFoiAdx3+DhTVfgLS7x93d41dMlziPoBX2RgdcFn37qnzw6AQKTVTMwDbRCGpg==", + "version": "3.515.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.515.0.tgz", + "integrity": "sha512-A/KJ+/HTohHyVXLH+t/bO0Z2mPrQgELbQO8tX+B2nElo8uklj70r5cT7F8ETsI9oOy+HDVpiL5/v45ZgpUOiPg==", "dependencies": { - "@aws-sdk/types": "3.496.0", + "@aws-sdk/types": "3.515.0", "@smithy/node-config-provider": "^2.2.1", "@smithy/types": "^2.9.1", "tslib": "^2.5.0" @@ -2500,9 +2657,9 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.9.tgz", - "integrity": "sha512-B2L9neXTIyPQoXDm+NtovPvG6VOLWnaXu3BIeVDWwdKFgG30oNa6CqVGiJPDWQwIAK49t9gnQI9c6K6RzabiKw==", + "version": "7.23.10", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.10.tgz", + "integrity": "sha512-2XpP2XhkXzgxecPNEEK8Vz8Asj9aRxt08oKOqtiZoqV2UGZ5T+EkyP9sXQ9nwMxBIG34a7jmasVqoMop7VdPUw==", "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.20", @@ -4551,9 +4708,9 @@ "peer": true }, "node_modules/@codemirror/view": { - "version": "6.23.1", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.23.1.tgz", - "integrity": "sha512-J2Xnn5lFYT1ZN/5ewEoMBCmLlL71lZ3mBdb7cUEuHhX2ESoSrNEucpsDXpX22EuTGm9LOgC9v4Z0wx+Ez8QmGA==", + "version": "6.24.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.24.0.tgz", + "integrity": "sha512-zK6m5pNkdhdJl8idPP1gA4N8JKTiSsOz8U/Iw+C1ChMwyLG7+MLiNXnH/wFuAk6KeGEe33/adOiAh5jMqee03w==", "dev": true, "peer": true, "dependencies": { @@ -5240,9 +5397,9 @@ } }, "node_modules/@docusaurus/core/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -6829,9 +6986,9 @@ "dev": true }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.41.0.tgz", - "integrity": "sha512-aKUhyn1QI5Ksbqcr3fFJj16p99QdjUxXAEuFst1Z47DRyoiMwivIH9MV/ARcJOCXVjPfjITciej8ZD2O/6qUmw==", + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.42.0.tgz", + "integrity": "sha512-R1w57YlVA6+YE01wch3GPYn6bCsrOV3YW/5oGGE2tmX6JcL9Nr+b5IikrjMPF+v9CV3ay+obImEdsDhovhJrzw==", "dev": true, "dependencies": { "comment-parser": "1.4.1", @@ -6843,9 +7000,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.0.tgz", - "integrity": "sha512-fGFDEctNh0CcSwsiRPxiaqX0P5rq+AqE0SRhYGZ4PX46Lg1FNR6oCxJghf8YgY0WQEgQuh3lErUFE4KxLeRmmw==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz", + "integrity": "sha512-m55cpeupQ2DbuRGQMMZDzbv9J9PgVelPjlcmM5kxHnrBdBx6REaEd7LamYV7Dm8N7rCyR/XwU6rVP8ploKtIkA==", "cpu": [ "ppc64" ], @@ -6859,9 +7016,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.0.tgz", - "integrity": "sha512-3bMAfInvByLHfJwYPJRlpTeaQA75n8C/QKpEaiS4HrFWFiJlNI0vzq/zCjBrhAYcPyVPG7Eo9dMrcQXuqmNk5g==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.1.tgz", + "integrity": "sha512-4j0+G27/2ZXGWR5okcJi7pQYhmkVgb4D7UKwxcqrjhvp5TKWx3cUjgB1CGj1mfdmJBQ9VnUGgUhign+FPF2Zgw==", "cpu": [ "arm" ], @@ -6875,9 +7032,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.0.tgz", - "integrity": "sha512-aVpnM4lURNkp0D3qPoAzSG92VXStYmoVPOgXveAUoQBWRSuQzt51yvSju29J6AHPmwY1BjH49uR29oyfH1ra8Q==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.1.tgz", + "integrity": "sha512-hCnXNF0HM6AjowP+Zou0ZJMWWa1VkD77BXe959zERgGJBBxB+sV+J9f/rcjeg2c5bsukD/n17RKWXGFCO5dD5A==", "cpu": [ "arm64" ], @@ -6891,9 +7048,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.0.tgz", - "integrity": "sha512-uK7wAnlRvjkCPzh8jJ+QejFyrP8ObKuR5cBIsQZ+qbMunwR8sbd8krmMbxTLSrDhiPZaJYKQAU5Y3iMDcZPhyQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.1.tgz", + "integrity": "sha512-MSfZMBoAsnhpS+2yMFYIQUPs8Z19ajwfuaSZx+tSl09xrHZCjbeXXMsUF/0oq7ojxYEpsSo4c0SfjxOYXRbpaA==", "cpu": [ "x64" ], @@ -6907,9 +7064,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.0.tgz", - "integrity": "sha512-AjEcivGAlPs3UAcJedMa9qYg9eSfU6FnGHJjT8s346HSKkrcWlYezGE8VaO2xKfvvlZkgAhyvl06OJOxiMgOYQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.1.tgz", + "integrity": "sha512-Ylk6rzgMD8klUklGPzS414UQLa5NPXZD5tf8JmQU8GQrj6BrFA/Ic9tb2zRe1kOZyCbGl+e8VMbDRazCEBqPvA==", "cpu": [ "arm64" ], @@ -6923,9 +7080,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.0.tgz", - "integrity": "sha512-bsgTPoyYDnPv8ER0HqnJggXK6RyFy4PH4rtsId0V7Efa90u2+EifxytE9pZnsDgExgkARy24WUQGv9irVbTvIw==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.1.tgz", + "integrity": "sha512-pFIfj7U2w5sMp52wTY1XVOdoxw+GDwy9FsK3OFz4BpMAjvZVs0dT1VXs8aQm22nhwoIWUmIRaE+4xow8xfIDZA==", "cpu": [ "x64" ], @@ -6939,9 +7096,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.0.tgz", - "integrity": "sha512-kQ7jYdlKS335mpGbMW5tEe3IrQFIok9r84EM3PXB8qBFJPSc6dpWfrtsC/y1pyrz82xfUIn5ZrnSHQQsd6jebQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.1.tgz", + "integrity": "sha512-UyW1WZvHDuM4xDz0jWun4qtQFauNdXjXOtIy7SYdf7pbxSWWVlqhnR/T2TpX6LX5NI62spt0a3ldIIEkPM6RHw==", "cpu": [ "arm64" ], @@ -6955,9 +7112,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.0.tgz", - "integrity": "sha512-uG8B0WSepMRsBNVXAQcHf9+Ko/Tr+XqmK7Ptel9HVmnykupXdS4J7ovSQUIi0tQGIndhbqWLaIL/qO/cWhXKyQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.1.tgz", + "integrity": "sha512-itPwCw5C+Jh/c624vcDd9kRCCZVpzpQn8dtwoYIt2TJF3S9xJLiRohnnNrKwREvcZYx0n8sCSbvGH349XkcQeg==", "cpu": [ "x64" ], @@ -6971,9 +7128,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.0.tgz", - "integrity": "sha512-2ezuhdiZw8vuHf1HKSf4TIk80naTbP9At7sOqZmdVwvvMyuoDiZB49YZKLsLOfKIr77+I40dWpHVeY5JHpIEIg==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.1.tgz", + "integrity": "sha512-LojC28v3+IhIbfQ+Vu4Ut5n3wKcgTu6POKIHN9Wpt0HnfgUGlBuyDDQR4jWZUZFyYLiz4RBBBmfU6sNfn6RhLw==", "cpu": [ "arm" ], @@ -6987,9 +7144,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.0.tgz", - "integrity": "sha512-uTtyYAP5veqi2z9b6Gr0NUoNv9F/rOzI8tOD5jKcCvRUn7T60Bb+42NDBCWNhMjkQzI0qqwXkQGo1SY41G52nw==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.1.tgz", + "integrity": "sha512-cX8WdlF6Cnvw/DO9/X7XLH2J6CkBnz7Twjpk56cshk9sjYVcuh4sXQBy5bmTwzBjNVZze2yaV1vtcJS04LbN8w==", "cpu": [ "arm64" ], @@ -7003,9 +7160,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.0.tgz", - "integrity": "sha512-c88wwtfs8tTffPaoJ+SQn3y+lKtgTzyjkD8NgsyCtCmtoIC8RDL7PrJU05an/e9VuAke6eJqGkoMhJK1RY6z4w==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.1.tgz", + "integrity": "sha512-4H/sQCy1mnnGkUt/xszaLlYJVTz3W9ep52xEefGtd6yXDQbz/5fZE5dFLUgsPdbUOQANcVUa5iO6g3nyy5BJiw==", "cpu": [ "ia32" ], @@ -7019,9 +7176,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.0.tgz", - "integrity": "sha512-lR2rr/128/6svngnVta6JN4gxSXle/yZEZL3o4XZ6esOqhyR4wsKyfu6qXAL04S4S5CgGfG+GYZnjFd4YiG3Aw==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.1.tgz", + "integrity": "sha512-c0jgtB+sRHCciVXlyjDcWb2FUuzlGVRwGXgI+3WqKOIuoo8AmZAddzeOHeYLtD+dmtHw3B4Xo9wAUdjlfW5yYA==", "cpu": [ "loong64" ], @@ -7035,9 +7192,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.0.tgz", - "integrity": "sha512-9Sycc+1uUsDnJCelDf6ZNqgZQoK1mJvFtqf2MUz4ujTxGhvCWw+4chYfDLPepMEvVL9PDwn6HrXad5yOrNzIsQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.1.tgz", + "integrity": "sha512-TgFyCfIxSujyuqdZKDZ3yTwWiGv+KnlOeXXitCQ+trDODJ+ZtGOzLkSWngynP0HZnTsDyBbPy7GWVXWaEl6lhA==", "cpu": [ "mips64el" ], @@ -7051,9 +7208,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.0.tgz", - "integrity": "sha512-CoWSaaAXOZd+CjbUTdXIJE/t7Oz+4g90A3VBCHLbfuc5yUQU/nFDLOzQsN0cdxgXd97lYW/psIIBdjzQIwTBGw==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.1.tgz", + "integrity": "sha512-b+yuD1IUeL+Y93PmFZDZFIElwbmFfIKLKlYI8M6tRyzE6u7oEP7onGk0vZRh8wfVGC2dZoy0EqX1V8qok4qHaw==", "cpu": [ "ppc64" ], @@ -7067,9 +7224,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.0.tgz", - "integrity": "sha512-mlb1hg/eYRJUpv8h/x+4ShgoNLL8wgZ64SUr26KwglTYnwAWjkhR2GpoKftDbPOCnodA9t4Y/b68H4J9XmmPzA==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.1.tgz", + "integrity": "sha512-wpDlpE0oRKZwX+GfomcALcouqjjV8MIX8DyTrxfyCfXxoKQSDm45CZr9fanJ4F6ckD4yDEPT98SrjvLwIqUCgg==", "cpu": [ "riscv64" ], @@ -7083,9 +7240,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.0.tgz", - "integrity": "sha512-fgf9ubb53xSnOBqyvWEY6ukBNRl1mVX1srPNu06B6mNsNK20JfH6xV6jECzrQ69/VMiTLvHMicQR/PgTOgqJUQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.1.tgz", + "integrity": "sha512-5BepC2Au80EohQ2dBpyTquqGCES7++p7G+7lXe1bAIvMdXm4YYcEfZtQrP4gaoZ96Wv1Ute61CEHFU7h4FMueQ==", "cpu": [ "s390x" ], @@ -7099,9 +7256,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.0.tgz", - "integrity": "sha512-H9Eu6MGse++204XZcYsse1yFHmRXEWgadk2N58O/xd50P9EvFMLJTQLg+lB4E1cF2xhLZU5luSWtGTb0l9UeSg==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.1.tgz", + "integrity": "sha512-5gRPk7pKuaIB+tmH+yKd2aQTRpqlf1E4f/mC+tawIm/CGJemZcHZpp2ic8oD83nKgUPMEd0fNanrnFljiruuyA==", "cpu": [ "x64" ], @@ -7115,9 +7272,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.0.tgz", - "integrity": "sha512-lCT675rTN1v8Fo+RGrE5KjSnfY0x9Og4RN7t7lVrN3vMSjy34/+3na0q7RIfWDAj0e0rCh0OL+P88lu3Rt21MQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.1.tgz", + "integrity": "sha512-4fL68JdrLV2nVW2AaWZBv3XEm3Ae3NZn/7qy2KGAt3dexAgSVT+Hc97JKSZnqezgMlv9x6KV0ZkZY7UO5cNLCg==", "cpu": [ "x64" ], @@ -7131,9 +7288,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.0.tgz", - "integrity": "sha512-HKoUGXz/TOVXKQ+67NhxyHv+aDSZf44QpWLa3I1lLvAwGq8x1k0T+e2HHSRvxWhfJrFxaaqre1+YyzQ99KixoA==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.1.tgz", + "integrity": "sha512-GhRuXlvRE+twf2ES+8REbeCb/zeikNqwD3+6S5y5/x+DYbAQUNl0HNBs4RQJqrechS4v4MruEr8ZtAin/hK5iw==", "cpu": [ "x64" ], @@ -7147,9 +7304,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.0.tgz", - "integrity": "sha512-GDwAqgHQm1mVoPppGsoq4WJwT3vhnz/2N62CzhvApFD1eJyTroob30FPpOZabN+FgCjhG+AgcZyOPIkR8dfD7g==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.1.tgz", + "integrity": "sha512-ZnWEyCM0G1Ex6JtsygvC3KUUrlDXqOihw8RicRuQAzw+c4f1D66YlPNNV3rkjVW90zXVsHwZYWbJh3v+oQFM9Q==", "cpu": [ "x64" ], @@ -7163,9 +7320,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.0.tgz", - "integrity": "sha512-0vYsP8aC4TvMlOQYozoksiaxjlvUcQrac+muDqj1Fxy6jh9l9CZJzj7zmh8JGfiV49cYLTorFLxg7593pGldwQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.1.tgz", + "integrity": "sha512-QZ6gXue0vVQY2Oon9WyLFCdSuYbXSoxaZrPuJ4c20j6ICedfsDilNPYfHLlMH7vGfU5DQR0czHLmJvH4Nzis/A==", "cpu": [ "arm64" ], @@ -7179,9 +7336,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.0.tgz", - "integrity": "sha512-p98u4rIgfh4gdpV00IqknBD5pC84LCub+4a3MO+zjqvU5MVXOc3hqR2UgT2jI2nh3h8s9EQxmOsVI3tyzv1iFg==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.1.tgz", + "integrity": "sha512-HzcJa1NcSWTAU0MJIxOho8JftNp9YALui3o+Ny7hCh0v5f90nprly1U3Sj1Ldj/CvKKdvvFsCRvDkpsEMp4DNw==", "cpu": [ "ia32" ], @@ -7195,9 +7352,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.0.tgz", - "integrity": "sha512-NgJnesu1RtWihtTtXGFMU5YSE6JyyHPMxCwBZK7a6/8d31GuSo9l0Ss7w1Jw5QnKUawG6UEehs883kcXf5fYwg==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.1.tgz", + "integrity": "sha512-0MBh53o6XtI6ctDnRMeQ+xoCN8kD2qI1rY1KgF/xdWQwoFeKou7puvDfV8/Wv4Ctx2rRpET/gGdz3YlNtNACSA==", "cpu": [ "x64" ], @@ -7370,10 +7527,18 @@ "safe-json-stringify": "~1" } }, + "node_modules/@expo/bunyan/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@expo/cli": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.17.3.tgz", - "integrity": "sha512-lIK8igsEQxTh4WuDlcEhE0wAJcDrAyjWDF00phdmwuSCpE5SaEXNlddOXvGxEVKPhUxHZUFo9NbfoQC+JVmkfA==", + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.17.5.tgz", + "integrity": "sha512-9cMquL/5bBfV73CbZcWipk3KZSo8mBqdgvkoWCtEtnnlm/879ayxzMWjVIgT5yV4w+X7+N6KkBSUIIj4t9Xqew==", "dependencies": { "@babel/runtime": "^7.20.0", "@expo/code-signing-certificates": "0.0.5", @@ -7441,6 +7606,7 @@ "send": "^0.18.0", "slugify": "^1.3.4", "source-map-support": "~0.5.21", + "stacktrace-parser": "^0.1.10", "structured-headers": "^0.4.1", "tar": "^6.0.5", "temp-dir": "^2.0.0", @@ -7948,9 +8114,9 @@ } }, "node_modules/@expo/cli/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -8272,9 +8438,9 @@ } }, "node_modules/@expo/config-plugins/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -8857,9 +9023,9 @@ } }, "node_modules/@expo/metro-config": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-0.17.3.tgz", - "integrity": "sha512-YW8ixbaz6yL7/Mg1rJJejiAAVQQKjGY1wXvT2Dh487r/r9/j1yE1YRS/oRY1yItYzbnHvO0p0jMnEGfiFYL3Tg==", + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-0.17.4.tgz", + "integrity": "sha512-PxqDMuVjXQeboa6Aj8kNj4iTxIpwpfoYlF803qOjf1LE1ePlREnWNwqy65ESCBnCmekYIO5hhm1Nksa+xCvuyg==", "dependencies": { "@babel/core": "^7.20.0", "@babel/generator": "^7.20.5", @@ -9491,6 +9657,14 @@ "resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-4.1.1.tgz", "integrity": "sha512-e6eB7zN6UBSwGVwrbWVH+gdLnkW9WwHhmq2YDK1Sh30pzx1onRVGBvogTlUeWxwTa+L86NYdo4hFkh7O8ZjSnA==" }, + "node_modules/@expo/rudder-sdk-node/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@expo/sdk-runtime-versions": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@expo/sdk-runtime-versions/-/sdk-runtime-versions-1.0.0.tgz", @@ -10022,12 +10196,12 @@ } }, "node_modules/@floating-ui/dom": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.0.tgz", - "integrity": "sha512-SZ0BEXzsaaS6THZfZJUcAobbZTD+MvfGM42bxgeg0Tnkp4/an/avqwAXiVLsFtIBZtfsx3Ymvwx0+KnnhdA/9g==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", "dependencies": { - "@floating-ui/core": "^1.6.0", - "@floating-ui/utils": "^0.2.1" + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" } }, "node_modules/@floating-ui/react": { @@ -10045,11 +10219,11 @@ } }, "node_modules/@floating-ui/react-dom": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.7.tgz", - "integrity": "sha512-B5GJxKUyPcGsvE1vua+Abvw0t6zVMyTbtG+Jk7BoI4hfc5Ahv50dstRIAn0nS0274kR9gnKwxIXyGA8EzBZJrA==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", "dependencies": { - "@floating-ui/dom": "^1.6.0" + "@floating-ui/dom": "^1.6.1" }, "peerDependencies": { "react": ">=16.8.0", @@ -10182,9 +10356,9 @@ } }, "node_modules/@graphiql/react": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@graphiql/react/-/react-0.20.2.tgz", - "integrity": "sha512-/crAUlM+4iVHyNHVdiZjsTEqfMXBHfjEvrMwCwTVig6YXmCAVuaxqkD7NlDtrrPQArLGkABmf1Nw7ObRpby5lg==", + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/@graphiql/react/-/react-0.20.3.tgz", + "integrity": "sha512-LHEiWQPABflTyRJZBZB50WSlrWER4RtlWg9XV1+D4yZQ3+6GbLM7X1zYf4D/TQ6AJB/vLZQHEnbhS0LuKcNqfA==", "dev": true, "dependencies": { "@graphiql/toolkit": "^0.9.1", @@ -10246,9 +10420,9 @@ } }, "node_modules/@grpc/grpc-js": { - "version": "1.9.14", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.14.tgz", - "integrity": "sha512-nOpuzZ2G3IuMFN+UPPpKrC6NsLmWsTqSsm66IRfnBt1D4pwTqE27lmbpcPM+l2Ua4gE7PfjRHI6uedAy7hoXUw==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.1.tgz", + "integrity": "sha512-55ONqFytZExfOIjF1RjXPcVmT/jJqFzbbDqxK9jmRV4nxiYWtL9hENSW1Jfx0SdZfrvoqd44YJ/GJTqfRrawSQ==", "dependencies": { "@grpc/proto-loader": "^0.7.8", "@types/node": ">=12.12.47" @@ -10973,9 +11147,9 @@ } }, "node_modules/@jest/reporters/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -11322,9 +11496,9 @@ } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "engines": { "node": ">=6.0.0" } @@ -11405,9 +11579,9 @@ } }, "node_modules/@lhncbc/ucum-lhc": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@lhncbc/ucum-lhc/-/ucum-lhc-5.0.0.tgz", - "integrity": "sha512-r1LRxZG/JVZV6daWQZWyg8EiykLYndMuK39/TbUx71b09bjp09He2aNHfBSBMlxj3r0CKT8nnuFIRrznvSjJYw==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@lhncbc/ucum-lhc/-/ucum-lhc-5.0.3.tgz", + "integrity": "sha512-FlWyCOE6+Oc73zwRiFaiNSYQD8xpMYe9f4Qzy/tvnM3j5tXUwM3U5W/aXh/znJmHZr+lu3Hx697Sefp/3efOog==", "dev": true, "dependencies": { "coffeescript": "^2.7.0", @@ -11423,9 +11597,9 @@ } }, "node_modules/@mantine/core": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.5.0.tgz", - "integrity": "sha512-0Qfn4oLCs6Qrli+JK6Q325xhNblVEPSKOB4sDMUkvKYUlCt/2lsIhwUXarVBgiIV3X+rKccf0/LcEWmpn/dYuw==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.5.3.tgz", + "integrity": "sha512-Wvv6DJXI+GX9mmKG5HITTh/24sCZ0RoYQHdTHh0tOfGnEy+RleyhA82UjnMsp0n2NjfCISBwbiKgfya6b2iaFw==", "dependencies": { "@floating-ui/react": "^0.24.8", "clsx": "2.0.0", @@ -11435,53 +11609,53 @@ "type-fest": "^3.13.1" }, "peerDependencies": { - "@mantine/hooks": "7.5.0", + "@mantine/hooks": "7.5.3", "react": "^18.2.0", "react-dom": "^18.2.0" } }, "node_modules/@mantine/dropzone": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-7.5.0.tgz", - "integrity": "sha512-vjYwVrdRiCK0TpPPYliUMNIeCrOhWiFwP6DlmO0Wnb8X+GpyW2+cuWCxdpcFQHZZGzbNUT3QB8ulIAuwFP9jKw==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-7.5.3.tgz", + "integrity": "sha512-NbMnLN9B07WHkdrpxNTC0k0vzR6prONF+/awFPidphsXGAgRSZhiZXv06l9Wp9wVp6xPhVMTqGphh0i4Ey+AYg==", "dev": true, "dependencies": { "react-dropzone-esm": "15.0.1" }, "peerDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", "react": "^18.2.0", "react-dom": "^18.2.0" } }, "node_modules/@mantine/hooks": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.5.0.tgz", - "integrity": "sha512-KCL/RRMO+9HRIaNww3RIykifWL9XHovnANAyaCU2YUHOPyGCLSXs1UfFxsKNU71HaZ7cHwqSd7J0rR8JpVYLxw==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.5.3.tgz", + "integrity": "sha512-mFI448mAs12v8FrgSVhytqlhTVrEjIfd/PqPEfwJu5YcZIq4YZdqpzJIUbANnRrFSvmoQpDb1PssdKx7Ds35hw==", "peerDependencies": { "react": "^18.2.0" } }, "node_modules/@mantine/notifications": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.5.0.tgz", - "integrity": "sha512-43+PwX1WTTffmRjAAH/42Zk0ffXny5DMIry+WGfyNQdoN1JPL1w9eulpWhPFMUn2fm7wHyrKTRpiZdfp8ry2Ow==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.5.3.tgz", + "integrity": "sha512-08mWoGBfc8sGDTRthBg/HYPD8dRHyugZpeUH1U7RjWQmYD4ktdkT8bdBocStTSJkCQIvtP7OPJ1MiKln1idt5w==", "dependencies": { - "@mantine/store": "7.5.0", + "@mantine/store": "7.5.3", "react-transition-group": "4.4.5" }, "peerDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", "react": "^18.2.0", "react-dom": "^18.2.0" } }, "node_modules/@mantine/store": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@mantine/store/-/store-7.5.0.tgz", - "integrity": "sha512-NvPS6ERKxHGnkIEY8ip/v3ySYYPQlJq6KtSSXALlJ/BdyezBTsBrkEkEgJxJcYK5H4TYr9jDjLYNL3PQy4GHxg==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@mantine/store/-/store-7.5.3.tgz", + "integrity": "sha512-jLTIaChJr9rWtzSRp2HQGHfuoFcr3ylD0JW/vsE5gpFvhoZFfkrxx5QsM1kn5wV34rZo0nQNMIJ8hvBVvfJtLQ==", "peerDependencies": { "react": "^18.2.0" } @@ -11582,9 +11756,9 @@ } }, "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "optional": true, "dependencies": { @@ -11605,9 +11779,9 @@ "optional": true }, "node_modules/@mdx-js/mdx": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.0.0.tgz", - "integrity": "sha512-Icm0TBKBLYqroYbNW3BPnzMGn+7mwpQOK310aZ7+fkCtiU3aqv2cdcX+nd0Ydo3wI5Rx8bX2Z2QmGb/XcAClCw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.0.1.tgz", + "integrity": "sha512-eIQ4QTrOWyL3LWEe/bu6Taqzq2HQvHcyTMaOrI95P2/LmJE7AsfPfgJGuFLPVqBUE1BC1rik3VIhU+s9u72arA==", "dev": true, "dependencies": { "@types/estree": "^1.0.0", @@ -11649,9 +11823,9 @@ } }, "node_modules/@mdx-js/react": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.0.0.tgz", - "integrity": "sha512-nDctevR9KyYFyV+m+/+S4cpzCWHqj+iHDHq3QrsWezcC+B17uZdIWgCguESUkwFhM3n/56KxWVE3V6EokrmONQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.0.1.tgz", + "integrity": "sha512-9ZrPIU4MGf6et1m1ov3zKf+q9+deetI51zprKB1D/z3NOb+rUxxtEl3mCjW5wTGh6VhRdwPueh1oRzi6ezkA8A==", "dev": true, "dependencies": { "@types/mdx": "^2.0.0" @@ -11750,15 +11924,15 @@ "link": true }, "node_modules/@microsoft/api-documenter": { - "version": "7.23.20", - "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.23.20.tgz", - "integrity": "sha512-61V6sukyYZ5jQEdyvDFzInaIRTd0wgT2ECKPanr2ba0fc+Mien+KIr5shz9EAqJMZz0GifTnw9HmJqsfR688xA==", + "version": "7.23.24", + "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.23.24.tgz", + "integrity": "sha512-mig/L2g3geTN+rkJynyu2yFBvS6s/v5Q5OqAjwzYE/tNiBDHj+81qKTB/3ib94FNU0g5U4l+DsVkDtHwoN37Hw==", "dev": true, "dependencies": { - "@microsoft/api-extractor-model": "7.28.7", + "@microsoft/api-extractor-model": "7.28.10", "@microsoft/tsdoc": "0.14.2", - "@rushstack/node-core-library": "3.64.2", - "@rushstack/ts-command-line": "4.17.1", + "@rushstack/node-core-library": "3.66.1", + "@rushstack/ts-command-line": "4.17.2", "colors": "~1.2.1", "js-yaml": "~3.13.1", "resolve": "~1.22.1" @@ -11768,17 +11942,17 @@ } }, "node_modules/@microsoft/api-extractor": { - "version": "7.39.4", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.39.4.tgz", - "integrity": "sha512-6YvfkpbEqRQ0UPdVBc+lOiq7VlXi9kw8U3w+RcXCFDVc/UljlXU5l9fHEyuBAW1GGO2opUe+yf9OscWhoHANhg==", + "version": "7.40.2", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.40.2.tgz", + "integrity": "sha512-BCK+a9r0Nl/fd9fGhotaXJBt9IHBtuvEf/a8YS2UXwcqI4lnGcrvT3pAt3rrziS/dc5+0W/7TDZorULSj6N1Aw==", "dev": true, "dependencies": { - "@microsoft/api-extractor-model": "7.28.7", + "@microsoft/api-extractor-model": "7.28.10", "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "3.64.2", - "@rushstack/rig-package": "0.5.1", - "@rushstack/ts-command-line": "4.17.1", + "@rushstack/node-core-library": "3.66.1", + "@rushstack/rig-package": "0.5.2", + "@rushstack/ts-command-line": "4.17.2", "colors": "~1.2.1", "lodash": "~4.17.15", "resolve": "~1.22.1", @@ -11791,14 +11965,14 @@ } }, "node_modules/@microsoft/api-extractor-model": { - "version": "7.28.7", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.28.7.tgz", - "integrity": "sha512-4gCGGEQGHmbQmarnDcEWS2cjj0LtNuD3D6rh3ZcAyAYTkceAugAk2eyQHGdTcGX8w3qMjWCTU1TPb8xHnMM+Kg==", + "version": "7.28.10", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.28.10.tgz", + "integrity": "sha512-5ThitnV04Jbo0337Q0/VOjeGdx0OiduGgx4aGzfD6gsTSppYCPQjm2eIygRfc7w+XIP33osAZsWHAOo419PGQg==", "dev": true, "dependencies": { "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "3.64.2" + "@rushstack/node-core-library": "3.66.1" } }, "node_modules/@microsoft/api-extractor/node_modules/lru-cache": { @@ -12249,9 +12423,9 @@ } }, "node_modules/@npmcli/fs/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -12298,9 +12472,9 @@ } }, "node_modules/@npmcli/git/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -12700,9 +12874,9 @@ } }, "node_modules/@opentelemetry/api-logs": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.46.0.tgz", - "integrity": "sha512-+9BcqfiEDGPXEIo+o3tso/aqGM5dGbGwAkGVp3FPpZ8GlkK1YlaKRd9gMVyPaeRATwvO5wYGGnCsAc/sMMM9Qw==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.48.0.tgz", + "integrity": "sha512-1/aMiU4Eqo3Zzpfwu51uXssp5pzvHFObk8S9pKAiXb1ne8pvg1qxBQitYL1XUiAMEXFzgjaidYG2V6624DRhhw==", "dependencies": { "@opentelemetry/api": "^1.0.0" }, @@ -12711,54 +12885,54 @@ } }, "node_modules/@opentelemetry/auto-instrumentations-node": { - "version": "0.40.3", - "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.40.3.tgz", - "integrity": "sha512-MgBCzpFU4FBQEsXPgt5driYzxErf2JGntQ8Amtc94sqrnvq+FKdEBa6vxOpZlPM+c4Xr6tPpAT1ecTBV8U87hw==", - "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", - "@opentelemetry/instrumentation-amqplib": "^0.33.5", - "@opentelemetry/instrumentation-aws-lambda": "^0.37.4", - "@opentelemetry/instrumentation-aws-sdk": "^0.37.2", - "@opentelemetry/instrumentation-bunyan": "^0.34.1", - "@opentelemetry/instrumentation-cassandra-driver": "^0.34.2", - "@opentelemetry/instrumentation-connect": "^0.32.4", - "@opentelemetry/instrumentation-cucumber": "^0.2.1", - "@opentelemetry/instrumentation-dataloader": "^0.5.4", - "@opentelemetry/instrumentation-dns": "^0.32.5", - "@opentelemetry/instrumentation-express": "^0.34.1", - "@opentelemetry/instrumentation-fastify": "^0.32.6", - "@opentelemetry/instrumentation-fs": "^0.8.4", - "@opentelemetry/instrumentation-generic-pool": "^0.32.5", - "@opentelemetry/instrumentation-graphql": "^0.36.1", - "@opentelemetry/instrumentation-grpc": "^0.46.0", - "@opentelemetry/instrumentation-hapi": "^0.33.3", - "@opentelemetry/instrumentation-http": "^0.46.0", - "@opentelemetry/instrumentation-ioredis": "^0.36.1", - "@opentelemetry/instrumentation-knex": "^0.32.4", - "@opentelemetry/instrumentation-koa": "^0.36.4", - "@opentelemetry/instrumentation-lru-memoizer": "^0.33.5", - "@opentelemetry/instrumentation-memcached": "^0.32.5", - "@opentelemetry/instrumentation-mongodb": "^0.38.1", - "@opentelemetry/instrumentation-mongoose": "^0.34.0", - "@opentelemetry/instrumentation-mysql": "^0.34.5", - "@opentelemetry/instrumentation-mysql2": "^0.34.5", - "@opentelemetry/instrumentation-nestjs-core": "^0.33.4", - "@opentelemetry/instrumentation-net": "^0.32.5", - "@opentelemetry/instrumentation-pg": "^0.37.2", - "@opentelemetry/instrumentation-pino": "^0.34.5", - "@opentelemetry/instrumentation-redis": "^0.35.5", - "@opentelemetry/instrumentation-redis-4": "^0.35.6", - "@opentelemetry/instrumentation-restify": "^0.34.3", - "@opentelemetry/instrumentation-router": "^0.33.4", - "@opentelemetry/instrumentation-socket.io": "^0.35.0", - "@opentelemetry/instrumentation-tedious": "^0.6.5", - "@opentelemetry/instrumentation-winston": "^0.33.1", - "@opentelemetry/resource-detector-alibaba-cloud": "^0.28.5", - "@opentelemetry/resource-detector-aws": "^1.3.5", - "@opentelemetry/resource-detector-container": "^0.3.5", - "@opentelemetry/resource-detector-gcp": "^0.29.5", + "version": "0.41.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.41.1.tgz", + "integrity": "sha512-gQG0mHlPVBQveuemqNYkrL4IB3T6v2e5sMiDI6FQzFWeLzzFrC04R4fY6AE1YmkhOyteCDpHL/U6CULP2mkt8w==", + "dependencies": { + "@opentelemetry/instrumentation": "^0.48.0", + "@opentelemetry/instrumentation-amqplib": "^0.34.0", + "@opentelemetry/instrumentation-aws-lambda": "^0.38.0", + "@opentelemetry/instrumentation-aws-sdk": "^0.38.1", + "@opentelemetry/instrumentation-bunyan": "^0.35.0", + "@opentelemetry/instrumentation-cassandra-driver": "^0.35.0", + "@opentelemetry/instrumentation-connect": "^0.33.0", + "@opentelemetry/instrumentation-cucumber": "^0.3.0", + "@opentelemetry/instrumentation-dataloader": "^0.6.0", + "@opentelemetry/instrumentation-dns": "^0.33.0", + "@opentelemetry/instrumentation-express": "^0.35.0", + "@opentelemetry/instrumentation-fastify": "^0.33.0", + "@opentelemetry/instrumentation-fs": "^0.9.0", + "@opentelemetry/instrumentation-generic-pool": "^0.33.0", + "@opentelemetry/instrumentation-graphql": "^0.37.0", + "@opentelemetry/instrumentation-grpc": "^0.48.0", + "@opentelemetry/instrumentation-hapi": "^0.34.0", + "@opentelemetry/instrumentation-http": "^0.48.0", + "@opentelemetry/instrumentation-ioredis": "^0.37.0", + "@opentelemetry/instrumentation-knex": "^0.33.0", + "@opentelemetry/instrumentation-koa": "^0.37.0", + "@opentelemetry/instrumentation-lru-memoizer": "^0.34.0", + "@opentelemetry/instrumentation-memcached": "^0.33.0", + "@opentelemetry/instrumentation-mongodb": "^0.39.0", + "@opentelemetry/instrumentation-mongoose": "^0.35.0", + "@opentelemetry/instrumentation-mysql": "^0.35.0", + "@opentelemetry/instrumentation-mysql2": "^0.35.0", + "@opentelemetry/instrumentation-nestjs-core": "^0.34.0", + "@opentelemetry/instrumentation-net": "^0.33.0", + "@opentelemetry/instrumentation-pg": "^0.38.0", + "@opentelemetry/instrumentation-pino": "^0.35.0", + "@opentelemetry/instrumentation-redis": "^0.36.0", + "@opentelemetry/instrumentation-redis-4": "^0.36.0", + "@opentelemetry/instrumentation-restify": "^0.35.0", + "@opentelemetry/instrumentation-router": "^0.34.0", + "@opentelemetry/instrumentation-socket.io": "^0.36.0", + "@opentelemetry/instrumentation-tedious": "^0.7.0", + "@opentelemetry/instrumentation-winston": "^0.34.0", + "@opentelemetry/resource-detector-alibaba-cloud": "^0.28.6", + "@opentelemetry/resource-detector-aws": "^1.3.6", + "@opentelemetry/resource-detector-container": "^0.3.6", + "@opentelemetry/resource-detector-gcp": "^0.29.6", "@opentelemetry/resources": "^1.12.0", - "@opentelemetry/sdk-node": "^0.46.0" + "@opentelemetry/sdk-node": "^0.48.0" }, "engines": { "node": ">=14" @@ -12767,344 +12941,6 @@ "@opentelemetry/api": "^1.4.1" } }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/context-async-hooks": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.19.0.tgz", - "integrity": "sha512-0i1ECOc9daKK3rjUgDDXf0GDD5XfCou5lXnt2DALIc2qKoruPPcesobNKE54laSVUWnC3jX26RzuOa31g0V32A==", - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.8.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/core": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.19.0.tgz", - "integrity": "sha512-w42AukJh3TP8R0IZZOVJVM/kMWu8g+lm4LzT70WtuKqhwq7KVhcDzZZuZinWZa6TtQCl7Smt2wolEYzpHabOgw==", - "dependencies": { - "@opentelemetry/semantic-conventions": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.8.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/exporter-trace-otlp-grpc": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.46.0.tgz", - "integrity": "sha512-kR4kehnfIhv7v/2MuNYfrlh9A/ZtQofwCzurTIplornUjdzhKDGgjui1NkNTqTfM1QkqfCiavGsf5hwocx29bA==", - "dependencies": { - "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.19.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.46.0", - "@opentelemetry/otlp-transformer": "0.46.0", - "@opentelemetry/resources": "1.19.0", - "@opentelemetry/sdk-trace-base": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/exporter-trace-otlp-http": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.46.0.tgz", - "integrity": "sha512-vZ2pYOB+qrQ+jnKPY6Gnd58y1k/Ti//Ny6/XsSX7/jED0X77crtSVgC6N5UA0JiGJOh6QB2KE9gaH99010XHzg==", - "dependencies": { - "@opentelemetry/core": "1.19.0", - "@opentelemetry/otlp-exporter-base": "0.46.0", - "@opentelemetry/otlp-transformer": "0.46.0", - "@opentelemetry/resources": "1.19.0", - "@opentelemetry/sdk-trace-base": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/exporter-trace-otlp-proto": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.46.0.tgz", - "integrity": "sha512-A7PftDM57w1TLiirrhi8ceAnCpYkpUBObELdn239IyYF67zwngImGfBLf5Yo3TTAOA2Oj1TL76L8zWVL8W+Suw==", - "dependencies": { - "@opentelemetry/core": "1.19.0", - "@opentelemetry/otlp-exporter-base": "0.46.0", - "@opentelemetry/otlp-proto-exporter-base": "0.46.0", - "@opentelemetry/otlp-transformer": "0.46.0", - "@opentelemetry/resources": "1.19.0", - "@opentelemetry/sdk-trace-base": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/exporter-zipkin": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-1.19.0.tgz", - "integrity": "sha512-TY1fy4JiOBN5a8T9fknqTMcz0DXIeFBr6sklaLCgwtj+G699a5R4CekNwpeM7DHSwC44UMX7gljO2I6dYsTS3A==", - "dependencies": { - "@opentelemetry/core": "1.19.0", - "@opentelemetry/resources": "1.19.0", - "@opentelemetry/sdk-trace-base": "1.19.0", - "@opentelemetry/semantic-conventions": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/otlp-exporter-base": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.46.0.tgz", - "integrity": "sha512-hfkh7cG17l77ZSLRAogz19SIJzr0KeC7xv5PDyTFbHFpwwoxV/bEViO49CqUFH6ckXB63NrltASP9R7po+ahTQ==", - "dependencies": { - "@opentelemetry/core": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/otlp-grpc-exporter-base": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.46.0.tgz", - "integrity": "sha512-/KB/xfZZiWIY2JknvCoT/e9paIzQO3QCBN5gR6RyxpXM/AGx3YTAOKvB/Ts9Va19jo5aE74gB7emhFaCNy4Rmw==", - "dependencies": { - "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.19.0", - "@opentelemetry/otlp-exporter-base": "0.46.0", - "protobufjs": "^7.2.3" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/otlp-proto-exporter-base": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-proto-exporter-base/-/otlp-proto-exporter-base-0.46.0.tgz", - "integrity": "sha512-rEJBA8U2AxfEzrdIUcyyjOweyVFkO6V1XAxwP161JkxpvNuVDdULHAfRVnGtoZhiVA1XsJKcpIIq2MEKAqq4cg==", - "dependencies": { - "@opentelemetry/core": "1.19.0", - "@opentelemetry/otlp-exporter-base": "0.46.0", - "protobufjs": "^7.2.3" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/otlp-transformer": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.46.0.tgz", - "integrity": "sha512-Fj9hZwr6xuqgsaERn667Uf6kuDG884puWhyrai2Jen2Fq+bGf4/5BzEJp/8xvty0VSU4EfXOto/ys3KpSz2UHg==", - "dependencies": { - "@opentelemetry/api-logs": "0.46.0", - "@opentelemetry/core": "1.19.0", - "@opentelemetry/resources": "1.19.0", - "@opentelemetry/sdk-logs": "0.46.0", - "@opentelemetry/sdk-metrics": "1.19.0", - "@opentelemetry/sdk-trace-base": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.8.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/propagator-b3": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-1.19.0.tgz", - "integrity": "sha512-v7y5IBOKBm0vP3yf0DHzlw4L2gL6tZ0KeeMTaxfO5IuomMffDbrGWcvYFp0Dt4LdZctTSK523rVLBB9FBHBciQ==", - "dependencies": { - "@opentelemetry/core": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.8.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/propagator-jaeger": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.19.0.tgz", - "integrity": "sha512-dedkOoTzKg+nYoLWCMp0Im+wo+XkTRW6aXhi8VQRtMW/9SNJGOllCJSu8llToLxMDF0+6zu7OCrKkevAof2tew==", - "dependencies": { - "@opentelemetry/core": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.8.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/resources": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.19.0.tgz", - "integrity": "sha512-RgxvKuuMOf7nctOeOvpDjt2BpZvZGr9Y0vf7eGtY5XYZPkh2p7e2qub1S2IArdBMf9kEbz0SfycqCviOu9isqg==", - "dependencies": { - "@opentelemetry/core": "1.19.0", - "@opentelemetry/semantic-conventions": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.8.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/sdk-logs": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.46.0.tgz", - "integrity": "sha512-Knlyk4+G72uEzNh6GRN1Fhmrj+/rkATI5/lOrevN7zRDLgp4kfyZBGGoWk7w+qQjlYvwhIIdPVxlIcipivdZIg==", - "dependencies": { - "@opentelemetry/core": "1.19.0", - "@opentelemetry/resources": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.4.0 <1.8.0", - "@opentelemetry/api-logs": ">=0.39.1" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/sdk-metrics": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.19.0.tgz", - "integrity": "sha512-FiMii40zr0Fmys4F1i8gmuCvbinBnBsDeGBr4FQemOf0iPCLytYQm5AZJ/nn4xSc71IgKBQwTFQRAGJI7JvZ4Q==", - "dependencies": { - "@opentelemetry/core": "1.19.0", - "@opentelemetry/resources": "1.19.0", - "lodash.merge": "^4.6.2" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.8.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/sdk-node": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.46.0.tgz", - "integrity": "sha512-BQhzdCRZXchhKjZaFkgxlgoowjOt/QXekJ1CZgfvFO9Yg5GV15LyJFUEyQkDyD8XbshGo3Cnj0WZMBnDWtWY1A==", - "dependencies": { - "@opentelemetry/api-logs": "0.46.0", - "@opentelemetry/core": "1.19.0", - "@opentelemetry/exporter-trace-otlp-grpc": "0.46.0", - "@opentelemetry/exporter-trace-otlp-http": "0.46.0", - "@opentelemetry/exporter-trace-otlp-proto": "0.46.0", - "@opentelemetry/exporter-zipkin": "1.19.0", - "@opentelemetry/instrumentation": "0.46.0", - "@opentelemetry/resources": "1.19.0", - "@opentelemetry/sdk-logs": "0.46.0", - "@opentelemetry/sdk-metrics": "1.19.0", - "@opentelemetry/sdk-trace-base": "1.19.0", - "@opentelemetry/sdk-trace-node": "1.19.0", - "@opentelemetry/semantic-conventions": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.8.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/sdk-trace-base": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.19.0.tgz", - "integrity": "sha512-+IRvUm+huJn2KqfFW3yW/cjvRwJ8Q7FzYHoUNx5Fr0Lws0LxjMJG1uVB8HDpLwm7mg5XXH2M5MF+0jj5cM8BpQ==", - "dependencies": { - "@opentelemetry/core": "1.19.0", - "@opentelemetry/resources": "1.19.0", - "@opentelemetry/semantic-conventions": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.8.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/sdk-trace-node": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.19.0.tgz", - "integrity": "sha512-TCiEq/cUjM15RFqBRwWomTVbOqzndWL4ILa7ZCu0zbjU1/XY6AgHkgrgAc7vGP6TjRqH4Xryuglol8tcIfbBUQ==", - "dependencies": { - "@opentelemetry/context-async-hooks": "1.19.0", - "@opentelemetry/core": "1.19.0", - "@opentelemetry/propagator-b3": "1.19.0", - "@opentelemetry/propagator-jaeger": "1.19.0", - "@opentelemetry/sdk-trace-base": "1.19.0", - "semver": "^7.5.2" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.8.0" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.19.0.tgz", - "integrity": "sha512-14jRpC8f5c0gPSwoZ7SbEJni1PqI+AhAE8m1bMz6v+RPM4OlP1PT2UHBJj5Qh/ALLPjhVU/aZUK3YyjTUqqQVg==", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/@opentelemetry/context-async-hooks": { "version": "1.21.0", "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.21.0.tgz", @@ -13242,9 +13078,9 @@ } }, "node_modules/@opentelemetry/instrumentation": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.46.0.tgz", - "integrity": "sha512-a9TijXZZbk0vI5TGLZl+0kxyFfrXHhX6Svtz7Pp2/VBlCSKrazuULEyoJQrOknJyFWNMEmbbJgOciHCCpQcisw==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.48.0.tgz", + "integrity": "sha512-sjtZQB5PStIdCw5ovVTDGwnmQC+GGYArJNgIcydrDSqUTdYBnMrN9P4pwQZgS3vTGIp+TU1L8vMXGe51NVmIKQ==", "dependencies": { "@types/shimmer": "^1.0.2", "import-in-the-middle": "1.7.1", @@ -13260,12 +13096,12 @@ } }, "node_modules/@opentelemetry/instrumentation-amqplib": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.33.5.tgz", - "integrity": "sha512-WQ/XPzNLOHL3fpsmgoQUkiKCkJ09hvPN8wGrGzzOHMiJ5/3LqvfvxsJ4Rcd6aWkA4il3hEfpl+V0VF0t/DP65A==", + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.34.0.tgz", + "integrity": "sha512-lVGRkyGnjFJv9O8oO/+uT40nrNj4UO+UN0k8708guy/toVgxsVpv4PtdWJTjbtu89UDk9gUxq62jpHxqrVaNnw==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13276,11 +13112,11 @@ } }, "node_modules/@opentelemetry/instrumentation-aws-lambda": { - "version": "0.37.4", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.37.4.tgz", - "integrity": "sha512-/wdZwUalIWAbxeycvmE+25c1xCMhe5EUuj8bN0MWWN3L8N2SYvfv6DmiRgwrTIPXRgIyFugh2udNiF4MezZN4Q==", + "version": "0.38.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.38.0.tgz", + "integrity": "sha512-MIPvM8S4LqGKE+IAnYVCRUnEjaWbPsbqL4p2BnGcox08e6+JQe+0d16DI0cKVSFZOzV5T/or3ewQ/bB0lPm8yg==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/propagator-aws-xray": "^1.3.1", "@opentelemetry/resources": "^1.8.0", "@opentelemetry/semantic-conventions": "^1.0.0", @@ -13294,13 +13130,13 @@ } }, "node_modules/@opentelemetry/instrumentation-aws-sdk": { - "version": "0.37.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.37.2.tgz", - "integrity": "sha512-eFHHOk0P9EFQ9Ho+2w7KH9R8cH7hut0/ePSsrk0nAM6Tiq2lBPeHn8ialCWESAA9jSjy+iuDelwmqAEXEe+jrg==", + "version": "0.38.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.38.1.tgz", + "integrity": "sha512-/Tupb4UfVVkmcopq2H2nr2D/pHLF0riVw2biQQUJ/jGIkfrOgUMMKbShi2RQE4Zy8NFv3xaDn4/pNxzodLBy3w==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.46.0", - "@opentelemetry/propagation-utils": "^0.30.5", + "@opentelemetry/instrumentation": "^0.48.0", + "@opentelemetry/propagation-utils": "^0.30.6", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13311,12 +13147,12 @@ } }, "node_modules/@opentelemetry/instrumentation-bunyan": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-bunyan/-/instrumentation-bunyan-0.34.1.tgz", - "integrity": "sha512-+eshbCFr2dkUYO2jCpbYGFC5hs94UCOsQRK1XqNOjeiNvQRtqvKYqk8ARwJBYBX+aW4J02jOliAHQUh/d7gYPg==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-bunyan/-/instrumentation-bunyan-0.35.0.tgz", + "integrity": "sha512-bQ8OzV7nVTA+oGiTzLjUmRFAbnXi0U/Z4VJCpj+1DRsaAaMT17eRpAOh22LQR0JBnv2vBm8CvIQl4CcAnsB46g==", "dependencies": { - "@opentelemetry/api-logs": "^0.46.0", - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/api-logs": "^0.48.0", + "@opentelemetry/instrumentation": "^0.48.0", "@types/bunyan": "1.8.9" }, "engines": { @@ -13327,11 +13163,11 @@ } }, "node_modules/@opentelemetry/instrumentation-cassandra-driver": { - "version": "0.34.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cassandra-driver/-/instrumentation-cassandra-driver-0.34.2.tgz", - "integrity": "sha512-wuzq7QQ9o7PJnzseblNfBcURtM+9AwO6e1m644QYtAb/6YRR6qg6gAmAipVeQu01H5BuHBFC/92svaAkdIV2WQ==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cassandra-driver/-/instrumentation-cassandra-driver-0.35.0.tgz", + "integrity": "sha512-NlJkEiP37/WQvtSyYe4zxaBcaoweO/2+UtDssldk9NFmFutLHyMT/P5q5fe8i73ylmkPOAZnN8P48oHOhZHM1g==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13342,12 +13178,12 @@ } }, "node_modules/@opentelemetry/instrumentation-connect": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.32.4.tgz", - "integrity": "sha512-oUGph9idncdnHlQMwdIs6m6mii7Kdp9PpHkM9roOZy71h+2vvf6+cVn45bs2unBbE2Vxho2i/049QQbaYKDYlw==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.33.0.tgz", + "integrity": "sha512-EAMmUC2/KfeZl4qNgUsiVqT5Jti0jDl4GHi4TpDg41VBEJkRX/0+JcPBWgdFUgEfeiZr0GPVQud4i8jAwJ+ORw==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@types/connect": "3.4.36" }, @@ -13358,12 +13194,20 @@ "@opentelemetry/api": "^1.3.0" } }, + "node_modules/@opentelemetry/instrumentation-connect/node_modules/@types/connect": { + "version": "3.4.36", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", + "integrity": "sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@opentelemetry/instrumentation-cucumber": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cucumber/-/instrumentation-cucumber-0.2.1.tgz", - "integrity": "sha512-ydF0DpmE0D6wccAbxx1F+6kokzcSSRy3X78Bvgok/3fHUSSshGLErqNiQL1HV44OIcV6392P3tu/jtXtUq3UDQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cucumber/-/instrumentation-cucumber-0.3.0.tgz", + "integrity": "sha512-nM9BL0t2Nxwbb41MXxNXTDL0zq7FXhOX9F3OiAqYUJHqb7BHyzV9KoQ+Ao1BjqJR91hUm1OFNgHAk3y8uiuq4w==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13374,11 +13218,11 @@ } }, "node_modules/@opentelemetry/instrumentation-dataloader": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.5.4.tgz", - "integrity": "sha512-l1qQvvygxZJw+S+4hgYgzvT4GArqBrar42wzB5LVsOy04+gmbDw/4y7IqxZYepFyXKuBownGS8pR4huRL/Tj/A==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.6.0.tgz", + "integrity": "sha512-jkPdn83WV/TcnhQ5bOIoYcJGvMxXyYlCzbqfuB6HsMqf3CqpdgBQYlMuKi6qIfD4QWYt2R992yglNxPLuJ7xeg==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0" + "@opentelemetry/instrumentation": "^0.48.0" }, "engines": { "node": ">=14" @@ -13388,11 +13232,11 @@ } }, "node_modules/@opentelemetry/instrumentation-dns": { - "version": "0.32.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dns/-/instrumentation-dns-0.32.5.tgz", - "integrity": "sha512-fHJqrbezpZSipo4O9nF9yGq6R8oyr1W2gSlyk1foJNXBaqdCODTlzIa7BP50vGtLBN/m+qO8pMOWCJmYSBX35g==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dns/-/instrumentation-dns-0.33.0.tgz", + "integrity": "sha512-QDJadJOQg9CLqMC79r4T5ugN4C4lb6eJYLmHgnLg3fh1JUGfyjQHtD3T7lH0P8251Mnt5m8zjDDbPKcqK2aGcw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0", "semver": "^7.5.4" }, @@ -13415,9 +13259,9 @@ } }, "node_modules/@opentelemetry/instrumentation-dns/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -13434,12 +13278,12 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/@opentelemetry/instrumentation-express": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.34.1.tgz", - "integrity": "sha512-pQBQxXpkH6imvzwCdPcw2FKjB1cphoRpmWTiGi6vtBdKXCP0hpne613ycpwhGG7C17S+mbarVmukbJKR4rmh6Q==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.35.0.tgz", + "integrity": "sha512-ZmSB4WMd88sSecOL7DlghzdBl56/8ymb02n+xEJ/6zUgONuw/1uoTh1TAaNPKfEWdNLoLKXQm+Gd2zBrUVOX0w==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13450,12 +13294,12 @@ } }, "node_modules/@opentelemetry/instrumentation-fastify": { - "version": "0.32.6", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.32.6.tgz", - "integrity": "sha512-UkBu8rAqeVC034jsRMiEAmYhFQ03pvmE/MnoPKE9gAbgVtPILdekHYqAKM0MdqnEjW7pO45t4wWsbtIcN0eiBw==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.33.0.tgz", + "integrity": "sha512-sl3q9Mt+yM6GlZJKhfLUIRrVEYqfmI0hqYLha5OFG5rLrgnZCCZVy8ra4+Pa40ecH1409cvwwBPf7k9AHEQBTw==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13466,12 +13310,12 @@ } }, "node_modules/@opentelemetry/instrumentation-fs": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.8.4.tgz", - "integrity": "sha512-g99963nK8TuisUM3KH+ee5hOCHdCHSKiAdmy0RMdiKT7ITh3rXUct7fghQibViQA7FVPkdwM9+uRKkigJSFS9w==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.9.0.tgz", + "integrity": "sha512-Xp31lb2Sj50ppsJ393650HxSi5IJIgddXxrUeVljmsabdmECPUj0YAt/Wwb1oIHFn04iL9Tq4VkF/otlLaI9ww==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13482,11 +13326,11 @@ } }, "node_modules/@opentelemetry/instrumentation-generic-pool": { - "version": "0.32.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.32.5.tgz", - "integrity": "sha512-MNFloXa3SDg/rHh397JnWADr7iYQ6HHRwT7ZId5Vt0L1Xx+Qp3XSIILsXk5qTXVUIytEIVgn8iMT+kZILHzSqg==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.33.0.tgz", + "integrity": "sha512-QMSSOfIqMJhXqFryLVbAMsgRktNHdhMEpsOgEiHurLfvAJhyEOBcTTUuo6Laqt50IIzIm3YuHL9ZtEl9fve2ag==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13497,11 +13341,11 @@ } }, "node_modules/@opentelemetry/instrumentation-graphql": { - "version": "0.36.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.36.1.tgz", - "integrity": "sha512-EOvaS+d2909TOJJjNTsb0vHaLg8WdTLoQS8bRKdy3lMgdd7I4OL9/LSC7dp4M8CvJKz9B464Ix9PnARvhMkNOw==", + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.37.0.tgz", + "integrity": "sha512-WL5Qn1aRudJDxVN0Ao73/yzXBGBJAH1Fd2tteuGXku/Qw9hYQ936CgoO66GWmSiq2lyjsojAk1t5f+HF9j3NXw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0" + "@opentelemetry/instrumentation": "^0.48.0" }, "engines": { "node": ">=14" @@ -13511,12 +13355,12 @@ } }, "node_modules/@opentelemetry/instrumentation-grpc": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.46.0.tgz", - "integrity": "sha512-KemIpB4jmywQv/+MbVoUIMVp3vr+rzra37TYbN7kTsbrn213YlzdXVamf6nq/yChI6q+9JlUnCdSZf86D6NO6g==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.48.0.tgz", + "integrity": "sha512-MmJHkbqaulqfECjotRtco9AXOq+D1HLq53wI7UFeE8bl8kISP9iMkt+A7PrtPFpRGCglwFvSa0djId6EWsP0DQ==", "dependencies": { - "@opentelemetry/instrumentation": "0.46.0", - "@opentelemetry/semantic-conventions": "1.19.0" + "@opentelemetry/instrumentation": "0.48.0", + "@opentelemetry/semantic-conventions": "1.21.0" }, "engines": { "node": ">=14" @@ -13525,21 +13369,13 @@ "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/instrumentation-grpc/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.19.0.tgz", - "integrity": "sha512-14jRpC8f5c0gPSwoZ7SbEJni1PqI+AhAE8m1bMz6v+RPM4OlP1PT2UHBJj5Qh/ALLPjhVU/aZUK3YyjTUqqQVg==", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/instrumentation-hapi": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.33.3.tgz", - "integrity": "sha512-l14u1TFPXMUjfHqnrHtzuMyLq6V8BikwhYJO/hIgkbaPCxS38TloCtDLJvcs8S8AZlcQfkUqE/NFgXYETZRo+Q==", + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.34.0.tgz", + "integrity": "sha512-qUENVxwCYbRbJ8HBY54ZL1Z9q1guCEurW6tCFFJJKQFu/MKEw7GSFImy5DR2Mp8b5ggZO36lYFcx0QUxfy4GJg==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@types/hapi__hapi": "20.0.13" }, @@ -13551,13 +13387,13 @@ } }, "node_modules/@opentelemetry/instrumentation-http": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.46.0.tgz", - "integrity": "sha512-t5cxgqfV9AcxVP00/OL1ggkOSZM57VXDpvlWaOidYyyfLKcUJ9e2fGbNwoVsGFboRDeH0iFo7gLA3EEvX13wCA==", + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.48.0.tgz", + "integrity": "sha512-uXqOsLhW9WC3ZlGm6+PSX0xjSDTCfy4CMjfYj6TPWusOO8dtdx040trOriF24y+sZmS3M+5UQc6/3/ZxBJh4Mw==", "dependencies": { - "@opentelemetry/core": "1.19.0", - "@opentelemetry/instrumentation": "0.46.0", - "@opentelemetry/semantic-conventions": "1.19.0", + "@opentelemetry/core": "1.21.0", + "@opentelemetry/instrumentation": "0.48.0", + "@opentelemetry/semantic-conventions": "1.21.0", "semver": "^7.5.2" }, "engines": { @@ -13567,28 +13403,6 @@ "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/core": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.19.0.tgz", - "integrity": "sha512-w42AukJh3TP8R0IZZOVJVM/kMWu8g+lm4LzT70WtuKqhwq7KVhcDzZZuZinWZa6TtQCl7Smt2wolEYzpHabOgw==", - "dependencies": { - "@opentelemetry/semantic-conventions": "1.19.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.8.0" - } - }, - "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.19.0.tgz", - "integrity": "sha512-14jRpC8f5c0gPSwoZ7SbEJni1PqI+AhAE8m1bMz6v+RPM4OlP1PT2UHBJj5Qh/ALLPjhVU/aZUK3YyjTUqqQVg==", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/instrumentation-http/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -13601,9 +13415,9 @@ } }, "node_modules/@opentelemetry/instrumentation-http/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -13620,11 +13434,11 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/@opentelemetry/instrumentation-ioredis": { - "version": "0.36.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.36.1.tgz", - "integrity": "sha512-xxab7n4TD5igCFR5N0j5PJFYVeOXm54ZtCARVS32xavwGyGf+Sb6VtuVCLdl0re4JENCg18FO97Dyb1ql2EBUA==", + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.37.0.tgz", + "integrity": "sha512-xBPfu03IIG8x1pmt1Dx+XrBO4ZB4UjEcrouGbp6eV3dLQ7eJPYZgfNXmsqkpsxgNQuVCi2a3WEAwZ5Wl2hk7Vw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/redis-common": "^0.36.1", "@opentelemetry/semantic-conventions": "^1.0.0", "@types/ioredis4": "npm:@types/ioredis@^4.28.10" @@ -13637,11 +13451,11 @@ } }, "node_modules/@opentelemetry/instrumentation-knex": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.32.4.tgz", - "integrity": "sha512-vqxK1wqhktQnBA7nGr6nqfhV5irSXK9v0R29hqlyTr/cB/86Hn3Z98zH58QvHo131FcE+d70QdiXu3P9S5vq4g==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.33.0.tgz", + "integrity": "sha512-7L3Q8Yy5vY4W4zpRrjKEc0OpVPYyERtDz5dAumKjkJsEVPANr7E8Cc+No6VjZGeN+HgFFwEo+jcQCTWJzdxvRw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13652,14 +13466,14 @@ } }, "node_modules/@opentelemetry/instrumentation-koa": { - "version": "0.36.4", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.36.4.tgz", - "integrity": "sha512-Qt6IF0mo3FZZ+x4NQhv/pViDtPSw/h/QYDvyIqMCuqUibqta619WLUCwAcanKnZWvzMuYh6lT0TnZ8ktjXo6jQ==", + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.37.0.tgz", + "integrity": "sha512-EfuGv1RJCSZh77dDc3PtvZXGwcsTufn9tU6T9VOTFcxovpyJ6w0og73eD0D02syR8R+kzv6rg1TeS8+lj7pyrQ==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0", - "@types/koa": "2.13.9", + "@types/koa": "2.14.0", "@types/koa__router": "12.0.3" }, "engines": { @@ -13670,11 +13484,11 @@ } }, "node_modules/@opentelemetry/instrumentation-lru-memoizer": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.33.5.tgz", - "integrity": "sha512-vbm9QPEYD3FZNGSa+7xQ7uOkgbJtskIwp1KnsCpmdBBiulhBaqqgeNLFo7gd8UxMrI2Vu3LTlZil2D0dVt8g/A==", + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.34.0.tgz", + "integrity": "sha512-m1kXrc11XNj7cC6sfcsYqd+kuCcN2wI9LXpB2l2BZCogqxHCgjuVoiXvT6K9LfO4tLefcvhoK0N8XuVJ8xyyOw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0" + "@opentelemetry/instrumentation": "^0.48.0" }, "engines": { "node": ">=14" @@ -13684,11 +13498,11 @@ } }, "node_modules/@opentelemetry/instrumentation-memcached": { - "version": "0.32.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-memcached/-/instrumentation-memcached-0.32.5.tgz", - "integrity": "sha512-1MS9LfgZruAsWOHVDTI+/Wy9mFFivUlpGUwYC4D+ho1I4NUyE33XHwL1BKcjMupkuRnE0JmbhdBfTG9Ai1vFkw==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-memcached/-/instrumentation-memcached-0.33.0.tgz", + "integrity": "sha512-TdGT5ytt8o7FTIsQvx010ykYbqu+IfGoOKA+IXLHdX1+l7vFWyv3ZOzQbRDmA4rxujJAAPf/n4/D7QECyedE/g==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@types/memcached": "^2.2.6" }, @@ -13700,11 +13514,11 @@ } }, "node_modules/@opentelemetry/instrumentation-mongodb": { - "version": "0.38.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.38.1.tgz", - "integrity": "sha512-X6YjE8dOCf8lG8FGmoAvczZq7LtgYaRzZcLGthZSUJQ2rfp1JJRlJixc+COvhrn1HJj5ab+AsSdUQgTpfQgEHQ==", + "version": "0.39.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.39.0.tgz", + "integrity": "sha512-m9dMj39pcCshzlfCEn2lGrlNo7eV5fb9pGBnPyl/Am9Crh7Or8vOqvByCNd26Dgf5J978zTdLGF+6tM8j1WOew==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/sdk-metrics": "^1.9.1", "@opentelemetry/semantic-conventions": "^1.0.0" }, @@ -13716,12 +13530,12 @@ } }, "node_modules/@opentelemetry/instrumentation-mongoose": { - "version": "0.34.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.34.0.tgz", - "integrity": "sha512-/wGNK7qR/QXpcidXntKsnfACHETp8G/f2o/k8FPvJ2lukvdM9r7cHLys8QwkJkTN8Kt3qG1VIwarGKYtE/zOSw==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.35.0.tgz", + "integrity": "sha512-gReBMWD2Oa/wBGRWyg6B2dbPHhgkpOqDio31gE3DbC4JaqCsMByyeix75rZSzZ71RQmVh3d4jRLsqUtNVBzcyg==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13732,11 +13546,11 @@ } }, "node_modules/@opentelemetry/instrumentation-mysql": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.34.5.tgz", - "integrity": "sha512-cE8z1uJTeLcMj+R31t1pLkLqt3ryGMl1HApxsqqf8YCSHetrkVwGZOcyQ3phfgGSaNlC4/pdf3CQqfjhXbLWlA==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.35.0.tgz", + "integrity": "sha512-QKRHd3aFA2vKOPzIZ9Q3UIxYeNPweB62HGlX2l3shOKrUhrtTg2/BzaKpHQBy2f2nO2mxTF/mOFeVEDeANnhig==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@types/mysql": "2.15.22" }, @@ -13748,11 +13562,11 @@ } }, "node_modules/@opentelemetry/instrumentation-mysql2": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.34.5.tgz", - "integrity": "sha512-Ai3+cJ83b11kLypIVEPLHuYCiIQjf828hHJPpllr78EhmHiq4S1yTW34aP3BzCBY+5Adr5PS5Nnv7NLBI/YfaQ==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.35.0.tgz", + "integrity": "sha512-DI9NXYJBbQ72rjz1KCKerQFQE+Z4xRDoyYek6JpITv5BlhPboA8zKkltxyQLL06Y2RTFYslw1gvg+x9CWlRzJw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@opentelemetry/sql-common": "^0.40.0" }, @@ -13764,11 +13578,11 @@ } }, "node_modules/@opentelemetry/instrumentation-nestjs-core": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.33.4.tgz", - "integrity": "sha512-AynD6TesE0bZg9UzS4ZLG//6TmU8GkRrjubhxs7jYZ3JRAlJAq1In0zSwM082JOIs7wr286nhs1FmqK91cG1cg==", + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.34.0.tgz", + "integrity": "sha512-HvbcCVAMZEIFrJ0Si9AfjxOr14KcH5h/lq5zLQ8AjZJpW0WaeO/ox5UgFi3J73Br91WbZHRgbXxMeodNycJJuA==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13779,11 +13593,11 @@ } }, "node_modules/@opentelemetry/instrumentation-net": { - "version": "0.32.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-net/-/instrumentation-net-0.32.5.tgz", - "integrity": "sha512-omBLTXyJeCtBy1MzVi+IzUcpXiup90k0z3AGhNKbzro4RhpsdMpN9O+3zZCXCIHMuyifhy7z8w99wmvvFO/FCQ==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-net/-/instrumentation-net-0.33.0.tgz", + "integrity": "sha512-x6awrqF0YfEhGGNE2JtEWvB+zEls7mFvLDii54DnWxpQU69+AqKCW/ZwC91EDefOMGSYBckL0uEn6XNOgkTTbw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13794,12 +13608,11 @@ } }, "node_modules/@opentelemetry/instrumentation-pg": { - "version": "0.37.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.37.2.tgz", - "integrity": "sha512-MAiKqdtGItYjvD6rOCyGS27CdMaDnh2JuImIHXhrPjq/sb2JlBNm6m1e4BH4uik1VfcKt/I3pI3UkydSWIscCg==", + "version": "0.38.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.38.0.tgz", + "integrity": "sha512-Q7V/OJ1OZwaWYNOP/E9S6sfS03Z+PNU1SAjdAoXTj5j4u4iJSMSieLRWXFaHwsbefIOMkYvA00EBKF9IgbgbLA==", "dependencies": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@opentelemetry/sql-common": "^0.40.0", "@types/pg": "8.6.1", @@ -13873,11 +13686,11 @@ } }, "node_modules/@opentelemetry/instrumentation-pino": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pino/-/instrumentation-pino-0.34.5.tgz", - "integrity": "sha512-auYDSeFhkPFXayT/060mQRL7O88Bt5NKcV0qOfquNa9J5/qs5dlJYdTOraxPrjiGPM3tHaAJ+AvAhR0CPYgZew==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pino/-/instrumentation-pino-0.35.0.tgz", + "integrity": "sha512-gMfJ5Qy793mbaAGnQE3yp1Cb0y4np74rBPu20Oy/v8TTgPQOEV5PyNI0GNGggmZQIJSkdtYa8Ndb3huH3iZE5g==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0" + "@opentelemetry/instrumentation": "^0.48.0" }, "engines": { "node": ">=14" @@ -13887,11 +13700,11 @@ } }, "node_modules/@opentelemetry/instrumentation-redis": { - "version": "0.35.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.35.5.tgz", - "integrity": "sha512-UPYUncDlLqDPtyU11UhyZOUxAyPQS6yQGT0b96KjpqMhmuRb3b0WxzZh3SoIaAyprL5f9fxyeV2HfSulR0aWFQ==", + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.36.0.tgz", + "integrity": "sha512-rKFylIacEBwLxKFrPvxpVi8hHY9qXfQSybYnYNyF/VxUWMGYDPMpbCnTQkiVR5u+tIhwSvhSDG2YQEq6syHUIQ==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/redis-common": "^0.36.1", "@opentelemetry/semantic-conventions": "^1.0.0" }, @@ -13903,11 +13716,11 @@ } }, "node_modules/@opentelemetry/instrumentation-redis-4": { - "version": "0.35.6", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.35.6.tgz", - "integrity": "sha512-OVSUJZAuy6OX18X2TKPdPlpwM5t4FooJU9QXiUxezhdMvfIAu00Agchw+gRbszkM7nvQ9dkXFOZO3nTmJNcLcA==", + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.36.0.tgz", + "integrity": "sha512-XO0EV2TxUsaRdcp79blyLGG5JWWl7NWVd/XNbU8vY7CuYUfRhWiTXYoM4PI+lwkAnUPvPtyiOzYs9px23GnibA==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/redis-common": "^0.36.1", "@opentelemetry/semantic-conventions": "^1.0.0" }, @@ -13919,12 +13732,12 @@ } }, "node_modules/@opentelemetry/instrumentation-restify": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-restify/-/instrumentation-restify-0.34.3.tgz", - "integrity": "sha512-nUqf4RTcQyc/YTWMT0e/ZqvuQqhRH04MOoSwaH6ocmyrEhKdPDq9AbvSMerQ/AxNC9yju/PytgzFFWH45hh3KA==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-restify/-/instrumentation-restify-0.35.0.tgz", + "integrity": "sha512-0ghtxsGJxHEwJfIzxDN3FCbNiTXqwv2jV6ip716jyjWN3f6MuRHm7NPWI/KNvu+IjqDj16KRGZG7nUAEB1ojog==", "dependencies": { "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13935,11 +13748,11 @@ } }, "node_modules/@opentelemetry/instrumentation-router": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-router/-/instrumentation-router-0.33.4.tgz", - "integrity": "sha512-4140BgILsL0H8tA0BBN2tjzajgjxlDqd4xPatk2i5q9ofy8wlB0BlDJ4m36U7G1z0v+a+LshQSNqPP2VHZ9ORw==", + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-router/-/instrumentation-router-0.34.0.tgz", + "integrity": "sha512-7LsonkdnQi35eF7CWl8394QDgyd811gCawJ6QuS8GbWNIvZ3S2f9j+Zy0fWSmFgO28ruW1pUG51ql8xdXWa8TA==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13950,11 +13763,11 @@ } }, "node_modules/@opentelemetry/instrumentation-socket.io": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-socket.io/-/instrumentation-socket.io-0.35.0.tgz", - "integrity": "sha512-ED1/Wco05H9fepKqX7n2kONq9EXxkBZTKS29nUH9vVL7wSIT8sBIDqpHz94CnQ8xuicaQQ7c5h9TVuhjtzV43Q==", + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-socket.io/-/instrumentation-socket.io-0.36.0.tgz", + "integrity": "sha512-c9Zc6WKxTZtMaOj01kmJGLKabEj805YgTav4l9vgojHrf6MH1fTlw+SGvOKhfPfzmu2QhNORAfqPAB1poDwqEQ==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "engines": { @@ -13965,11 +13778,11 @@ } }, "node_modules/@opentelemetry/instrumentation-tedious": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.6.5.tgz", - "integrity": "sha512-jjdrDegLUodz9np0yKCAa7sLxlAGTsxM7wU7l1mQ2NM6PHAGv4X3eSFClUk3fOipLx4+r5jTLnlsgu7g9CW+Qw==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.7.0.tgz", + "integrity": "sha512-o/5my8ZOuxACPSzMaXdPnQiMpmOPIJoTj+DRcs4vEJxk+KwlVNucoafSMpWQEyTNNuq2JI87Ru6Di2mp5T20EQ==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0", + "@opentelemetry/instrumentation": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@types/tedious": "^4.0.10" }, @@ -13981,11 +13794,11 @@ } }, "node_modules/@opentelemetry/instrumentation-winston": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-winston/-/instrumentation-winston-0.33.1.tgz", - "integrity": "sha512-4tSORoAY9f+yzqNVGcGr/3GydPMfgSiKK1OESc+qBwVTz0bmz4cOrhCruCngGzoqDCmPYpwqwR/8j4wRKgcUpw==", + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-winston/-/instrumentation-winston-0.34.0.tgz", + "integrity": "sha512-Ejssv6Uih7ipoNGYQLXd+cKZdEfTfTJ/vzpUSeYiJZ36mVYER1f8fs9Kb7jTKjHD55g2s6cUJj9lifbDFI4EEw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.46.0" + "@opentelemetry/instrumentation": "^0.48.0" }, "engines": { "node": ">=14" @@ -14006,9 +13819,9 @@ } }, "node_modules/@opentelemetry/instrumentation/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -14090,21 +13903,10 @@ "@opentelemetry/api": ">=1.3.0 <1.8.0" } }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/api-logs": { - "version": "0.48.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.48.0.tgz", - "integrity": "sha512-1/aMiU4Eqo3Zzpfwu51uXssp5pzvHFObk8S9pKAiXb1ne8pvg1qxBQitYL1XUiAMEXFzgjaidYG2V6624DRhhw==", - "dependencies": { - "@opentelemetry/api": "^1.0.0" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/propagation-utils": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagation-utils/-/propagation-utils-0.30.5.tgz", - "integrity": "sha512-9ENyx6ptmjyYzL7le3FXk/lJc3cFFTrh9Y/ubO9velQZ64BdjpF9kOMJN3Z8KLJFVt66HYoWy9xlWoSIfS/ICg==", + "version": "0.30.6", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagation-utils/-/propagation-utils-0.30.6.tgz", + "integrity": "sha512-qvnYee5I/xrBY5XClUlOASIOdoTbnFGNI6+ViKqdoErF2xKmhysXcmxlJNzQFNDK0muTfjvJMZcFyEielARk7g==", "engines": { "node": ">=14" }, @@ -14163,9 +13965,9 @@ } }, "node_modules/@opentelemetry/resource-detector-alibaba-cloud": { - "version": "0.28.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-alibaba-cloud/-/resource-detector-alibaba-cloud-0.28.5.tgz", - "integrity": "sha512-cobe2I0c5a66d98nCBprEB5erN5S0PTRrs49qSOnuTT2dC90nwSo2WDcSBfeDSKZH/7sB686P7FyKefWjQzhoA==", + "version": "0.28.6", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-alibaba-cloud/-/resource-detector-alibaba-cloud-0.28.6.tgz", + "integrity": "sha512-VuJXc+oDQ/SYRHBCQbEshl0WJtEMvgfSkTDBq+WSxj6y9sKe0HCt55Sxeb5nLmBdtCYWMQFniHe2K4GLSjDZVw==", "dependencies": { "@opentelemetry/resources": "^1.0.0", "@opentelemetry/semantic-conventions": "^1.0.0" @@ -14178,9 +13980,9 @@ } }, "node_modules/@opentelemetry/resource-detector-aws": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-aws/-/resource-detector-aws-1.3.5.tgz", - "integrity": "sha512-0GZJi8m6czksDJwpndSYJpnaPaFe83nEQVg4UnTTwB0cxKtrjpaarWDI46X0BuCX4bGp0M8pvI7f0rBt+LsIhA==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-aws/-/resource-detector-aws-1.3.6.tgz", + "integrity": "sha512-hFJ19yFwChqGCv1uMkXtjZU9BG8GcChe8cRCAkGWg1RZADse5S2ausf3D8pYw1cR3ktJtuAmRrGZniT6TDUPDw==", "dependencies": { "@opentelemetry/core": "^1.0.0", "@opentelemetry/resources": "^1.0.0", @@ -14194,9 +13996,9 @@ } }, "node_modules/@opentelemetry/resource-detector-container": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-container/-/resource-detector-container-0.3.5.tgz", - "integrity": "sha512-yLGLueH63DE9/WmDrizleqtT0uCOsslNqW+AcwzMHW7t8c2MtoJ7msgIsmi7tz5kxJgG8o54CnvXxobcwBhLCQ==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-container/-/resource-detector-container-0.3.6.tgz", + "integrity": "sha512-psxtzQakVuZKFl/ukn+nPW4Ixn+KPHGsWJMYKndmXrsgdFri78X+MHR0wLOO41Zn79sc35DiSk+GdJ24cCylbg==", "dependencies": { "@opentelemetry/resources": "^1.0.0", "@opentelemetry/semantic-conventions": "^1.0.0" @@ -14209,9 +14011,9 @@ } }, "node_modules/@opentelemetry/resource-detector-gcp": { - "version": "0.29.5", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-gcp/-/resource-detector-gcp-0.29.5.tgz", - "integrity": "sha512-cYckfRDDNX/nhiMF7fFooOCb/EObydNXWi0HwuPELHYa7heBCkWgTtNxs/PzuP46+FqDjuBcJL6beS2Ef7MyWg==", + "version": "0.29.6", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-gcp/-/resource-detector-gcp-0.29.6.tgz", + "integrity": "sha512-cx03fXPknmiVW0hpWAJr0Nr8xwkwRB8VNWPvNrmP7UzJ8eEztY9lHnVke4ZVFaVGvm/4EFxk2y5hPNggbIezoA==", "dependencies": { "@opentelemetry/core": "^1.0.0", "@opentelemetry/resources": "^1.0.0", @@ -14298,65 +14100,6 @@ "@opentelemetry/api": ">=1.3.0 <1.8.0" } }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/api-logs": { - "version": "0.48.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.48.0.tgz", - "integrity": "sha512-1/aMiU4Eqo3Zzpfwu51uXssp5pzvHFObk8S9pKAiXb1ne8pvg1qxBQitYL1XUiAMEXFzgjaidYG2V6624DRhhw==", - "dependencies": { - "@opentelemetry/api": "^1.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/instrumentation": { - "version": "0.48.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.48.0.tgz", - "integrity": "sha512-sjtZQB5PStIdCw5ovVTDGwnmQC+GGYArJNgIcydrDSqUTdYBnMrN9P4pwQZgS3vTGIp+TU1L8vMXGe51NVmIKQ==", - "dependencies": { - "@types/shimmer": "^1.0.2", - "import-in-the-middle": "1.7.1", - "require-in-the-middle": "^7.1.1", - "semver": "^7.5.2", - "shimmer": "^1.2.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/sdk-node/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@opentelemetry/sdk-node/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@opentelemetry/sdk-node/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/@opentelemetry/sdk-trace-base": { "version": "1.21.0", "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.21.0.tgz", @@ -14404,9 +14147,9 @@ } }, "node_modules/@opentelemetry/sdk-trace-node/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -15778,19 +15521,19 @@ } }, "node_modules/@react-native-community/cli": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.0.tgz", - "integrity": "sha512-XeQohi2E+S2+MMSz97QcEZ/bWpi8sfKiQg35XuYeJkc32Til2g0b97jRpn0/+fV0BInHoG1CQYWwHA7opMsrHg==", - "dependencies": { - "@react-native-community/cli-clean": "12.3.0", - "@react-native-community/cli-config": "12.3.0", - "@react-native-community/cli-debugger-ui": "12.3.0", - "@react-native-community/cli-doctor": "12.3.0", - "@react-native-community/cli-hermes": "12.3.0", - "@react-native-community/cli-plugin-metro": "12.3.0", - "@react-native-community/cli-server-api": "12.3.0", - "@react-native-community/cli-tools": "12.3.0", - "@react-native-community/cli-types": "12.3.0", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.2.tgz", + "integrity": "sha512-WgoUWwLDcf/G1Su2COUUVs3RzAwnV/vUTdISSpAUGgSc57mPabaAoUctKTnfYEhCnE3j02k3VtaVPwCAFRO3TQ==", + "dependencies": { + "@react-native-community/cli-clean": "12.3.2", + "@react-native-community/cli-config": "12.3.2", + "@react-native-community/cli-debugger-ui": "12.3.2", + "@react-native-community/cli-doctor": "12.3.2", + "@react-native-community/cli-hermes": "12.3.2", + "@react-native-community/cli-plugin-metro": "12.3.2", + "@react-native-community/cli-server-api": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", + "@react-native-community/cli-types": "12.3.2", "chalk": "^4.1.2", "commander": "^9.4.1", "deepmerge": "^4.3.0", @@ -15809,11 +15552,11 @@ } }, "node_modules/@react-native-community/cli-clean": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-12.3.0.tgz", - "integrity": "sha512-iAgLCOWYRGh9ukr+eVQnhkV/OqN3V2EGd/in33Ggn/Mj4uO6+oUncXFwB+yjlyaUNz6FfjudhIz09yYGSF+9sg==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-12.3.2.tgz", + "integrity": "sha512-90k2hCX0ddSFPT7EN7h5SZj0XZPXP0+y/++v262hssoey3nhurwF57NGWN0XAR0o9BSW7+mBfeInfabzDraO6A==", "dependencies": { - "@react-native-community/cli-tools": "12.3.0", + "@react-native-community/cli-tools": "12.3.2", "chalk": "^4.1.2", "execa": "^5.0.0" } @@ -15867,11 +15610,11 @@ } }, "node_modules/@react-native-community/cli-config": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-12.3.0.tgz", - "integrity": "sha512-BrTn5ndFD9uOxO8kxBQ32EpbtOvAsQExGPI7SokdI4Zlve70FziLtTq91LTlTUgMq1InVZn/jJb3VIDk6BTInQ==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-12.3.2.tgz", + "integrity": "sha512-UUCzDjQgvAVL/57rL7eOuFUhd+d+6qfM7V8uOegQFeFEmSmvUUDLYoXpBa5vAK9JgQtSqMBJ1Shmwao+/oElxQ==", "dependencies": { - "@react-native-community/cli-tools": "12.3.0", + "@react-native-community/cli-tools": "12.3.2", "chalk": "^4.1.2", "cosmiconfig": "^5.1.0", "deepmerge": "^4.3.0", @@ -16013,22 +15756,22 @@ } }, "node_modules/@react-native-community/cli-debugger-ui": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.0.tgz", - "integrity": "sha512-w3b0iwjQlk47GhZWHaeTG8kKH09NCMUJO729xSdMBXE8rlbm4kHpKbxQY9qKb6NlfWSJN4noGY+FkNZS2rRwnQ==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.2.tgz", + "integrity": "sha512-nSWQUL+51J682DlfcC1bjkUbQbGvHCC25jpqTwHIjmmVjYCX1uHuhPSqQKgPNdvtfOkrkACxczd7kVMmetxY2Q==", "dependencies": { "serve-static": "^1.13.1" } }, "node_modules/@react-native-community/cli-doctor": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-12.3.0.tgz", - "integrity": "sha512-BPCwNNesoQMkKsxB08Ayy6URgGQ8Kndv6mMhIvJSNdST3J1+x3ehBHXzG9B9Vfi+DrTKRb8lmEl/b/7VkDlPkA==", - "dependencies": { - "@react-native-community/cli-config": "12.3.0", - "@react-native-community/cli-platform-android": "12.3.0", - "@react-native-community/cli-platform-ios": "12.3.0", - "@react-native-community/cli-tools": "12.3.0", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-12.3.2.tgz", + "integrity": "sha512-GrAabdY4qtBX49knHFvEAdLtCjkmndjTeqhYO6BhsbAeKOtspcLT/0WRgdLIaKODRa61ADNB3K5Zm4dU0QrZOg==", + "dependencies": { + "@react-native-community/cli-config": "12.3.2", + "@react-native-community/cli-platform-android": "12.3.2", + "@react-native-community/cli-platform-ios": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", "chalk": "^4.1.2", "command-exists": "^1.2.8", "deepmerge": "^4.3.0", @@ -16090,9 +15833,9 @@ } }, "node_modules/@react-native-community/cli-doctor/node_modules/ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", + "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==" }, "node_modules/@react-native-community/cli-doctor/node_modules/lru-cache": { "version": "6.0.0", @@ -16106,9 +15849,9 @@ } }, "node_modules/@react-native-community/cli-doctor/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -16155,12 +15898,12 @@ } }, "node_modules/@react-native-community/cli-hermes": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-12.3.0.tgz", - "integrity": "sha512-G6FxpeZBO4AimKZwtWR3dpXRqTvsmEqlIkkxgwthdzn3LbVjDVIXKpVYU9PkR5cnT+KuAUxO0WwthrJ6Nmrrlg==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-12.3.2.tgz", + "integrity": "sha512-SL6F9O8ghp4ESBFH2YAPLtIN39jdnvGBKnK4FGKpDCjtB3DnUmDsGFlH46S+GGt5M6VzfG2eeKEOKf3pZ6jUzA==", "dependencies": { - "@react-native-community/cli-platform-android": "12.3.0", - "@react-native-community/cli-tools": "12.3.0", + "@react-native-community/cli-platform-android": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", "chalk": "^4.1.2", "hermes-profile-transformer": "^0.0.6", "ip": "^1.1.5" @@ -16204,9 +15947,9 @@ } }, "node_modules/@react-native-community/cli-hermes/node_modules/ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", + "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==" }, "node_modules/@react-native-community/cli-hermes/node_modules/supports-color": { "version": "7.2.0", @@ -16220,11 +15963,11 @@ } }, "node_modules/@react-native-community/cli-platform-android": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-12.3.0.tgz", - "integrity": "sha512-VU1NZw63+GLU2TnyQ919bEMThpHQ/oMFju9MCfrd3pyPJz4Sn+vc3NfnTDUVA5Z5yfLijFOkHIHr4vo/C9bjnw==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-12.3.2.tgz", + "integrity": "sha512-MZ5nO8yi/N+Fj2i9BJcJ9C/ez+9/Ir7lQt49DWRo9YDmzye66mYLr/P2l/qxsixllbbDi7BXrlLpxaEhMrDopg==", "dependencies": { - "@react-native-community/cli-tools": "12.3.0", + "@react-native-community/cli-tools": "12.3.2", "chalk": "^4.1.2", "execa": "^5.0.0", "fast-xml-parser": "^4.2.4", @@ -16320,11 +16063,11 @@ } }, "node_modules/@react-native-community/cli-platform-ios": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.3.0.tgz", - "integrity": "sha512-H95Sgt3wT7L8V75V0syFJDtv4YgqK5zbu69ko4yrXGv8dv2EBi6qZP0VMmkqXDamoPm9/U7tDTdbcf26ctnLfg==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.3.2.tgz", + "integrity": "sha512-OcWEAbkev1IL6SUiQnM6DQdsvfsKZhRZtoBNSj9MfdmwotVZSOEZJ+IjZ1FR9ChvMWayO9ns/o8LgoQxr1ZXeg==", "dependencies": { - "@react-native-community/cli-tools": "12.3.0", + "@react-native-community/cli-tools": "12.3.2", "chalk": "^4.1.2", "execa": "^5.0.0", "fast-xml-parser": "^4.0.12", @@ -16420,17 +16163,17 @@ } }, "node_modules/@react-native-community/cli-plugin-metro": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.3.0.tgz", - "integrity": "sha512-tYNHIYnNmxrBcsqbE2dAnLMzlKI3Cp1p1xUgTrNaOMsGPDN1epzNfa34n6Nps3iwKElSL7Js91CzYNqgTalucA==" + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.3.2.tgz", + "integrity": "sha512-FpFBwu+d2E7KRhYPTkKvQsWb2/JKsJv+t1tcqgQkn+oByhp+qGyXBobFB8/R3yYvRRDCSDhS+atWTJzk9TjM8g==" }, "node_modules/@react-native-community/cli-server-api": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-12.3.0.tgz", - "integrity": "sha512-Rode8NrdyByC+lBKHHn+/W8Zu0c+DajJvLmOWbe2WY/ECvnwcd9MHHbu92hlT2EQaJ9LbLhGrSbQE3cQy9EOCw==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-12.3.2.tgz", + "integrity": "sha512-iwa7EO9XFA/OjI5pPLLpI/6mFVqv8L73kNck3CNOJIUCCveGXBKK0VMyOkXaf/BYnihgQrXh+x5cxbDbggr7+Q==", "dependencies": { - "@react-native-community/cli-debugger-ui": "12.3.0", - "@react-native-community/cli-tools": "12.3.0", + "@react-native-community/cli-debugger-ui": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", "compression": "^1.7.1", "connect": "^3.6.5", "errorhandler": "^1.5.1", @@ -16559,9 +16302,9 @@ } }, "node_modules/@react-native-community/cli-tools": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-12.3.0.tgz", - "integrity": "sha512-2GafnCr8D88VdClwnm9KZfkEb+lzVoFdr/7ybqhdeYM0Vnt/tr2N+fM1EQzwI1DpzXiBzTYemw8GjRq+Utcz2Q==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-12.3.2.tgz", + "integrity": "sha512-nDH7vuEicHI2TI0jac/DjT3fr977iWXRdgVAqPZFFczlbs7A8GQvEdGnZ1G8dqRUmg+kptw0e4hwczAOG89JzQ==", "dependencies": { "appdirsjs": "^1.2.4", "chalk": "^4.1.2", @@ -16654,9 +16397,9 @@ } }, "node_modules/@react-native-community/cli-tools/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -16689,9 +16432,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/@react-native-community/cli-types": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-12.3.0.tgz", - "integrity": "sha512-MgOkmrXH4zsGxhte4YqKL7d+N8ZNEd3w1wo56MZlhu5WabwCJh87wYpU5T8vyfujFLYOFuFK5jjlcbs8F4/WDw==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-12.3.2.tgz", + "integrity": "sha512-9D0UEFqLW8JmS16mjHJxUJWX8E+zJddrHILSH8AJHZ0NNHv4u2DXKdb0wFLMobFxGNxPT+VSOjc60fGvXzWHog==", "dependencies": { "joi": "^17.2.1" } @@ -16808,9 +16551,9 @@ } }, "node_modules/@react-native-community/cli/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -16846,20 +16589,20 @@ } }, "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.73.3", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.73.3.tgz", - "integrity": "sha512-+zQrDDbz6lB48LyzFHxNCgXDCBHH+oTRdXAjikRcBUdeG9St9ABbYFLtb799zSxLOrCqFVyXqhJR2vlgLLEbcg==", + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.73.4.tgz", + "integrity": "sha512-XzRd8MJGo4Zc5KsphDHBYJzS1ryOHg8I2gOZDAUCGcwLFhdyGu1zBNDJYH2GFyDrInn9TzAbRIf3d4O+eltXQQ==", "dependencies": { - "@react-native/codegen": "0.73.2" + "@react-native/codegen": "0.73.3" }, "engines": { "node": ">=18" } }, "node_modules/@react-native/babel-preset": { - "version": "0.73.20", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.20.tgz", - "integrity": "sha512-fU9NqkusbfFq71l4BWQfqqD/lLcLC0MZ++UYgieA3j8lIEppJTLVauv2RwtD2yltBkjebgYEC5Rwvt1l0MUBXw==", + "version": "0.73.21", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.21.tgz", + "integrity": "sha512-WlFttNnySKQMeujN09fRmrdWqh46QyJluM5jdtDNrkl/2Hx6N4XeDUGhABvConeK95OidVO7sFFf7sNebVXogA==", "dependencies": { "@babel/core": "^7.20.0", "@babel/plugin-proposal-async-generator-functions": "^7.0.0", @@ -16900,7 +16643,7 @@ "@babel/plugin-transform-typescript": "^7.5.0", "@babel/plugin-transform-unicode-regex": "^7.0.0", "@babel/template": "^7.0.0", - "@react-native/babel-plugin-codegen": "0.73.3", + "@react-native/babel-plugin-codegen": "0.73.4", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" }, @@ -16912,9 +16655,9 @@ } }, "node_modules/@react-native/codegen": { - "version": "0.73.2", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.73.2.tgz", - "integrity": "sha512-lfy8S7umhE3QLQG5ViC4wg5N1Z+E6RnaeIw8w1voroQsXXGPB72IBozh8dAHR3+ceTxIU0KX3A8OpJI8e1+HpQ==", + "version": "0.73.3", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.73.3.tgz", + "integrity": "sha512-sxslCAAb8kM06vGy9Jyh4TtvjhcP36k/rvj2QE2Jdhdm61KvfafCATSIsOfc0QvnduWFcpXUPvAVyYwuv7PYDg==", "dependencies": { "@babel/parser": "^7.20.0", "flow-parser": "^0.206.0", @@ -17132,14 +16875,14 @@ } }, "node_modules/@react-native/community-cli-plugin": { - "version": "0.73.12", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.12.tgz", - "integrity": "sha512-xWU06OkC1cX++Duh/cD/Wv+oZ0oSY3yqbtxAqQA2H3Q+MQltNNJM6MqIHt1VOZSabRf/LVlR1JL6U9TXJirkaw==", + "version": "0.73.16", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.16.tgz", + "integrity": "sha512-eNH3v3qJJF6f0n/Dck90qfC9gVOR4coAXMTdYECO33GfgjTi+73vf/SBqlXw9HICH/RNZYGPM3wca4FRF7TYeQ==", "dependencies": { - "@react-native-community/cli-server-api": "12.3.0", - "@react-native-community/cli-tools": "12.3.0", + "@react-native-community/cli-server-api": "12.3.2", + "@react-native-community/cli-tools": "12.3.2", "@react-native/dev-middleware": "0.73.7", - "@react-native/metro-babel-transformer": "0.73.13", + "@react-native/metro-babel-transformer": "0.73.15", "chalk": "^4.0.0", "execa": "^5.1.1", "metro": "^0.80.3", @@ -17273,12 +17016,12 @@ } }, "node_modules/@react-native/metro-babel-transformer": { - "version": "0.73.13", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.13.tgz", - "integrity": "sha512-k9AQifogQfgUXPlqQSoMtX2KUhniw4XvJl+nZ4hphCH7qiMDAwuP8OmkJbz5E/N+Ro9OFuLE7ax4GlwxaTsAWg==", + "version": "0.73.15", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.15.tgz", + "integrity": "sha512-LlkSGaXCz+xdxc9819plmpsl4P4gZndoFtpjN3GMBIu6f7TBV0GVbyJAU4GE8fuAWPVSVL5ArOcdkWKSbI1klw==", "dependencies": { "@babel/core": "^7.20.0", - "@react-native/babel-preset": "0.73.19", + "@react-native/babel-preset": "0.73.21", "hermes-parser": "0.15.0", "nullthrows": "^1.1.1" }, @@ -17289,72 +17032,6 @@ "@babel/core": "*" } }, - "node_modules/@react-native/metro-babel-transformer/node_modules/@react-native/babel-plugin-codegen": { - "version": "0.73.2", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.73.2.tgz", - "integrity": "sha512-PadyFZWVaWXIBP7Q5dgEL7eAd7tnsgsLjoHJB1hIRZZuVUg1Zqe3nULwC7RFAqOtr5Qx7KXChkFFcKQ3WnZzGw==", - "dependencies": { - "@react-native/codegen": "0.73.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/metro-babel-transformer/node_modules/@react-native/babel-preset": { - "version": "0.73.19", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.19.tgz", - "integrity": "sha512-ujon01uMOREZecIltQxPDmJ6xlVqAUFGI/JCSpeVYdxyXBoBH5dBb0ihj7h6LKH1q1jsnO9z4MxfddtypKkIbg==", - "dependencies": { - "@babel/core": "^7.20.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.18.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.20.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.18.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.20.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.20.0", - "@babel/plugin-transform-flow-strip-types": "^7.20.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.11", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "@react-native/babel-plugin-codegen": "0.73.2", - "babel-plugin-transform-flow-enums": "^0.0.2", - "react-refresh": "^0.14.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, "node_modules/@react-native/normalize-color": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@react-native/normalize-color/-/normalize-color-2.1.0.tgz", @@ -17381,9 +17058,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.2.tgz", - "integrity": "sha512-ACXpdMM9hmKZww21yEqWwiLws/UPLhNKvimN8RrYSqPSvB3ov7sLvAcfvaxePeLvccTQKGdkDIhLYApZVDFuKg==", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.1.tgz", + "integrity": "sha512-zcU0gM3z+3iqj8UX45AmWY810l3oUmXM7uH4dt5xtzvMhRtYVhKGOmgOd1877dOPPepfCjUv57w+syamWIYe7w==", "dev": true, "engines": { "node": ">=14.0.0" @@ -17469,9 +17146,9 @@ "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz", - "integrity": "sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", + "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==", "cpu": [ "arm" ], @@ -17482,9 +17159,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.6.tgz", - "integrity": "sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz", + "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==", "cpu": [ "arm64" ], @@ -17495,9 +17172,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.6.tgz", - "integrity": "sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz", + "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==", "cpu": [ "arm64" ], @@ -17508,9 +17185,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.6.tgz", - "integrity": "sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz", + "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==", "cpu": [ "x64" ], @@ -17521,9 +17198,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.6.tgz", - "integrity": "sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz", + "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==", "cpu": [ "arm" ], @@ -17534,9 +17211,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.6.tgz", - "integrity": "sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz", + "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==", "cpu": [ "arm64" ], @@ -17547,9 +17224,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.6.tgz", - "integrity": "sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz", + "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==", "cpu": [ "arm64" ], @@ -17560,9 +17237,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.6.tgz", - "integrity": "sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz", + "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==", "cpu": [ "riscv64" ], @@ -17573,9 +17250,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.6.tgz", - "integrity": "sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz", + "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==", "cpu": [ "x64" ], @@ -17586,9 +17263,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.6.tgz", - "integrity": "sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz", + "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==", "cpu": [ "x64" ], @@ -17599,9 +17276,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.6.tgz", - "integrity": "sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz", + "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==", "cpu": [ "arm64" ], @@ -17612,9 +17289,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.6.tgz", - "integrity": "sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz", + "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==", "cpu": [ "ia32" ], @@ -17625,9 +17302,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.6.tgz", - "integrity": "sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz", + "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==", "cpu": [ "x64" ], @@ -17644,9 +17321,9 @@ "dev": true }, "node_modules/@rushstack/node-core-library": { - "version": "3.64.2", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.64.2.tgz", - "integrity": "sha512-n1S2VYEklONiwKpUyBq/Fym6yAsfsCXrqFabuOMcCuj4C+zW+HyaspSHXJCKqkMxfjviwe/c9+DUqvRWIvSN9Q==", + "version": "3.66.1", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.66.1.tgz", + "integrity": "sha512-ker69cVKAoar7MMtDFZC4CzcDxjwqIhFzqEnYI5NRN/8M3om6saWCVx/A7vL2t/jFCJsnzQplRDqA7c78pytng==", "dev": true, "dependencies": { "colors": "~1.2.1", @@ -17700,9 +17377,9 @@ "dev": true }, "node_modules/@rushstack/rig-package": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.5.1.tgz", - "integrity": "sha512-pXRYSe29TjRw7rqxD4WS3HN/sRSbfr+tJs4a9uuaSIBAITbUggygdhuG0VrO0EO+QqH91GhYMN4S6KRtOEmGVA==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.5.2.tgz", + "integrity": "sha512-mUDecIJeH3yYGZs2a48k+pbhM6JYwWlgjs2Ca5f2n1G2/kgdgP9D/07oglEGf6mRyXEnazhEENeYTSNDRCwdqA==", "dev": true, "dependencies": { "resolve": "~1.22.1", @@ -17710,9 +17387,9 @@ } }, "node_modules/@rushstack/ts-command-line": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.17.1.tgz", - "integrity": "sha512-2jweO1O57BYP5qdBGl6apJLB+aRIn5ccIRTPDyULh0KMwVzFqWtw6IZWt1qtUoZD/pD2RNkIOosH6Cq45rIYeg==", + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.17.2.tgz", + "integrity": "sha512-QS2S2nJo9zXq/+9Dk10LmvIFugMizI9IeQUH4jnhIcoaeqYlsv2fK830U+/gMKpI5vomXz19XMXfkUfZzO4R3A==", "dev": true, "dependencies": { "@types/argparse": "1.0.38", @@ -17744,9 +17421,9 @@ } }, "node_modules/@sideway/address": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", - "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", "dependencies": { "@hapi/hoek": "^9.0.0" } @@ -17935,9 +17612,9 @@ } }, "node_modules/@smithy/core": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-1.3.1.tgz", - "integrity": "sha512-tf+NIu9FkOh312b6M9G4D68is4Xr7qptzaZGZUREELF8ysE1yLKphqt7nsomjKZVwW7WE5pDDex9idowNGRQ/Q==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-1.3.2.tgz", + "integrity": "sha512-tYDmTp0f2TZVE18jAOH1PnmkngLQ+dOGUlMd1u67s87ieueNeyqhja6z/Z4MxhybEiXKOWFOmGjfTZWFxljwJw==", "dependencies": { "@smithy/middleware-endpoint": "^2.4.1", "@smithy/middleware-retry": "^2.1.1", @@ -18158,6 +17835,14 @@ "node": ">=14.0.0" } }, + "node_modules/@smithy/middleware-retry/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@smithy/middleware-serde": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.1.1.tgz", @@ -18408,9 +18093,9 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.1.1.tgz", - "integrity": "sha512-tYVrc+w+jSBfBd267KDnvSGOh4NMz+wVH7v4CClDbkdPfnjvImBZsOURncT5jsFwR9KCuDyPoSZq4Pa6+eCUrA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.2.0.tgz", + "integrity": "sha512-iFJp/N4EtkanFpBUtSrrIbtOIBf69KNuve03ic1afhJ9/korDxdM0c6cCH4Ehj/smI9pDCfVv+bqT3xZjF2WaA==", "dependencies": { "@smithy/config-resolver": "^2.1.1", "@smithy/credential-provider-imds": "^2.2.1", @@ -18528,12 +18213,12 @@ } }, "node_modules/@storybook/addon-actions": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-7.6.10.tgz", - "integrity": "sha512-pcKmf0H/caGzKDy8cz1adNSjv+KOBWLJ11RzGExrWm+Ad5ACifwlsQPykJ3TQ/21sTd9IXVrE9uuq4LldEnPbg==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-7.6.16.tgz", + "integrity": "sha512-wCpZljLXnu08TZzp+qL5AXousfUBzY6TgHVwn4yoZkMhPg3WLxZTceKYnc+XAxoMmdTrDjwanEF7v/uQ9eu64Q==", "dev": true, "dependencies": { - "@storybook/core-events": "7.6.10", + "@storybook/core-events": "7.6.16", "@storybook/global": "^5.0.0", "@types/uuid": "^9.0.1", "dequal": "^2.0.2", @@ -18545,23 +18230,10 @@ "url": "https://opencollective.com/storybook" } }, - "node_modules/@storybook/addon-actions/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@storybook/addon-backgrounds": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-7.6.10.tgz", - "integrity": "sha512-kGzsN1QkfyI8Cz7TErEx9OCB3PMzpCFGLd/iy7FreXwbMbeAQ3/9fYgKUsNOYgOhuTz7S09koZUWjS/WJuZGFA==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-7.6.16.tgz", + "integrity": "sha512-q9985hjtoX3ytvReV2YC4UY0FVASXFq2fW6RNOrrivw81UbW2SWxVG01vh7ZXjMrWbQ6r3yC05X9vVAmCa7TdQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0", @@ -18574,12 +18246,12 @@ } }, "node_modules/@storybook/addon-controls": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-7.6.10.tgz", - "integrity": "sha512-LjwCQRMWq1apLtFwDi6U8MI6ITUr+KhxJucZ60tfc58RgB2v8ayozyDAonFEONsx9YSR1dNIJ2Z/e2rWTBJeYA==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-7.6.16.tgz", + "integrity": "sha512-WeIuwyGxaMMClWSHhSH0ibwPSarEFtxE6SPQxCTmGIeD11bn5vQ6UUrmm9A2xbFqHOJBoB60TJhw69alnI0AHA==", "dev": true, "dependencies": { - "@storybook/blocks": "7.6.10", + "@storybook/blocks": "7.6.16", "lodash": "^4.17.21", "ts-dedent": "^2.0.0" }, @@ -18589,26 +18261,26 @@ } }, "node_modules/@storybook/addon-docs": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-7.6.10.tgz", - "integrity": "sha512-GtyQ9bMx1AOOtl6ZS9vwK104HFRK+tqzxddRRxhXkpyeKu3olm9aMgXp35atE/3fJSqyyDm2vFtxxH8mzBA20A==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-7.6.16.tgz", + "integrity": "sha512-X4WLAwwxGq9ki49FtERT5VHstGeZYca+l+8lxVXW6NQYuQ1xCeSy5puwknDv5p5u4thIVW2Fa4Uvma7wCfddtg==", "dev": true, "dependencies": { "@jest/transform": "^29.3.1", "@mdx-js/react": "^2.1.5", - "@storybook/blocks": "7.6.10", - "@storybook/client-logger": "7.6.10", - "@storybook/components": "7.6.10", - "@storybook/csf-plugin": "7.6.10", - "@storybook/csf-tools": "7.6.10", + "@storybook/blocks": "7.6.16", + "@storybook/client-logger": "7.6.16", + "@storybook/components": "7.6.16", + "@storybook/csf-plugin": "7.6.16", + "@storybook/csf-tools": "7.6.16", "@storybook/global": "^5.0.0", "@storybook/mdx2-csf": "^1.0.0", - "@storybook/node-logger": "7.6.10", - "@storybook/postinstall": "7.6.10", - "@storybook/preview-api": "7.6.10", - "@storybook/react-dom-shim": "7.6.10", - "@storybook/theming": "7.6.10", - "@storybook/types": "7.6.10", + "@storybook/node-logger": "7.6.16", + "@storybook/postinstall": "7.6.16", + "@storybook/preview-api": "7.6.16", + "@storybook/react-dom-shim": "7.6.16", + "@storybook/theming": "7.6.16", + "@storybook/types": "7.6.16", "fs-extra": "^11.1.0", "remark-external-links": "^8.0.0", "remark-slug": "^6.0.0", @@ -18676,24 +18348,24 @@ } }, "node_modules/@storybook/addon-essentials": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-7.6.10.tgz", - "integrity": "sha512-cjbuCCK/3dtUity0Uqi5LwbkgfxqCCE5x5mXZIk9lTMeDz5vB9q6M5nzncVDy8F8przF3NbDLLgxKlt8wjiICg==", - "dev": true, - "dependencies": { - "@storybook/addon-actions": "7.6.10", - "@storybook/addon-backgrounds": "7.6.10", - "@storybook/addon-controls": "7.6.10", - "@storybook/addon-docs": "7.6.10", - "@storybook/addon-highlight": "7.6.10", - "@storybook/addon-measure": "7.6.10", - "@storybook/addon-outline": "7.6.10", - "@storybook/addon-toolbars": "7.6.10", - "@storybook/addon-viewport": "7.6.10", - "@storybook/core-common": "7.6.10", - "@storybook/manager-api": "7.6.10", - "@storybook/node-logger": "7.6.10", - "@storybook/preview-api": "7.6.10", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-7.6.16.tgz", + "integrity": "sha512-LTrsud7yphxA7dpbk8TvIsHXqk5Wkq3JAwby3yQDEOFakpgNeXj8b6rlr9CHJja2p13pB4LuXokLk8t+qJGnQQ==", + "dev": true, + "dependencies": { + "@storybook/addon-actions": "7.6.16", + "@storybook/addon-backgrounds": "7.6.16", + "@storybook/addon-controls": "7.6.16", + "@storybook/addon-docs": "7.6.16", + "@storybook/addon-highlight": "7.6.16", + "@storybook/addon-measure": "7.6.16", + "@storybook/addon-outline": "7.6.16", + "@storybook/addon-toolbars": "7.6.16", + "@storybook/addon-viewport": "7.6.16", + "@storybook/core-common": "7.6.16", + "@storybook/manager-api": "7.6.16", + "@storybook/node-logger": "7.6.16", + "@storybook/preview-api": "7.6.16", "ts-dedent": "^2.0.0" }, "funding": { @@ -18706,9 +18378,9 @@ } }, "node_modules/@storybook/addon-highlight": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-7.6.10.tgz", - "integrity": "sha512-dIuS5QmoT1R+gFOcf6CoBa6D9UR5/wHCfPqPRH8dNNcCLtIGSHWQ4v964mS5OCq1Huj7CghmR15lOUk7SaYwUA==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-7.6.16.tgz", + "integrity": "sha512-DJtUBiButx6cz55eaRe5JFVBORVtp3Htr9PnxWVGEy4Ki5aoYCYWxMcPOuXVFvtWgBmh6d3HO0pEd888qPr60g==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -18719,9 +18391,9 @@ } }, "node_modules/@storybook/addon-links": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-7.6.10.tgz", - "integrity": "sha512-s/WkSYHpr2pb9p57j6u/xDBg3TKJhBq55YMl0GB5gXgkRPIeuGbPhGJhm2yTGVFLvXgr/aHHnOxb/R/W8PiRhA==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-7.6.16.tgz", + "integrity": "sha512-+582ePJxvweYZB5s133Uou6YRzZtnXGMRtKMJVovy/P5cWtq8FS5wzyMJPeK4z6ioR6BQJQVF2NV5lfrjoxpKQ==", "dev": true, "dependencies": { "@storybook/csf": "^0.1.2", @@ -18742,9 +18414,9 @@ } }, "node_modules/@storybook/addon-measure": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-7.6.10.tgz", - "integrity": "sha512-OVfTI56+kc4hLWfZ/YPV3WKj/aA9e4iKXYxZyPdhfX4Z8TgZdD1wv9Z6e8DKS0H5kuybYrHKHaID5ki6t7qz3w==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-7.6.16.tgz", + "integrity": "sha512-lQw7WXEeLuvDe3bfi7699WnHMryLIRnoT/w7oHqvS19UHp2HR0TKqYiPPppI6Yy4RoWHx+qFhKZJlajFyKDGfg==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0", @@ -18756,9 +18428,9 @@ } }, "node_modules/@storybook/addon-outline": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-7.6.10.tgz", - "integrity": "sha512-RVJrEoPArhI6zAIMNl1Gz0zrj84BTfEWYYz0yDWOTVgvN411ugsoIk1hw0671MOneXJ2RcQ9MFIeV/v6AVDQYg==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-7.6.16.tgz", + "integrity": "sha512-bG9KN10ANLUDIsm4e6RXRsCZ++b8pyfYTyu0MlSNXf6KdYcuDvdTY59gj6RIeVGKqWeX5yYCYUm2oPLtkms1NQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0", @@ -18770,12 +18442,12 @@ } }, "node_modules/@storybook/addon-storysource": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-storysource/-/addon-storysource-7.6.10.tgz", - "integrity": "sha512-ZtMiO26Bqd2oEovEeJ5ulvIL/rsAuHHpjAgBRZd/Byw25DQKY3GTqGtV474Wjm5tzj7HWhfk69fqAv87HnveCw==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-storysource/-/addon-storysource-7.6.16.tgz", + "integrity": "sha512-F/t0Y8bDF0C5m0h9Tui0ODCVAhp4Qicq6wonsZovEoSdhUE+Fq+dBtQ9K+zUYpQYf2alnvFexhy6xdAwBXFZAw==", "dev": true, "dependencies": { - "@storybook/source-loader": "7.6.10", + "@storybook/source-loader": "7.6.16", "estraverse": "^5.2.0", "tiny-invariant": "^1.3.1" }, @@ -18785,9 +18457,9 @@ } }, "node_modules/@storybook/addon-toolbars": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-7.6.10.tgz", - "integrity": "sha512-PaXY/oj9yxF7/H0CNdQKcioincyCkfeHpISZriZbZqhyqsjn3vca7RFEmsB88Q+ou6rMeqyA9st+6e2cx/Ct6A==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-7.6.16.tgz", + "integrity": "sha512-6wSNXe50auEVwHCcupYPrJkpzQFugumEBfgYuQ6ICW9k2xJtGtahy7TyM9sZbYgnDkoTm2ba7UhML6Noy3JuUg==", "dev": true, "funding": { "type": "opencollective", @@ -18795,9 +18467,9 @@ } }, "node_modules/@storybook/addon-viewport": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-7.6.10.tgz", - "integrity": "sha512-+bA6juC/lH4vEhk+w0rXakaG8JgLG4MOYrIudk5vJKQaC6X58LIM9N4kzIS2KSExRhkExXBPrWsnMfCo7uxmKg==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-7.6.16.tgz", + "integrity": "sha512-WkvixYHncLXpAeEnktjfYIffJ3b6poymB+wDbHKK/tg7m3N8llLlys64nvyeb7DbZ/+1yJls3K1DVbk1AIEHrQ==", "dev": true, "dependencies": { "memoizerific": "^1.11.3" @@ -18808,22 +18480,22 @@ } }, "node_modules/@storybook/blocks": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-7.6.10.tgz", - "integrity": "sha512-oSIukGC3yuF8pojABC/HLu5tv2axZvf60TaUs8eDg7+NiiKhzYSPoMQxs5uMrKngl+EJDB92ESgWT9vvsfvIPg==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-7.6.16.tgz", + "integrity": "sha512-rWG9a7BbK0qYvge1oJTIpAbcQ4eOSxetKqgeZc7jxQGeJw0Xvq7C/CmkBY4ZrdP8nj7M7R1Yw49u6OV4aXlyOg==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.10", - "@storybook/client-logger": "7.6.10", - "@storybook/components": "7.6.10", - "@storybook/core-events": "7.6.10", + "@storybook/channels": "7.6.16", + "@storybook/client-logger": "7.6.16", + "@storybook/components": "7.6.16", + "@storybook/core-events": "7.6.16", "@storybook/csf": "^0.1.2", - "@storybook/docs-tools": "7.6.10", + "@storybook/docs-tools": "7.6.16", "@storybook/global": "^5.0.0", - "@storybook/manager-api": "7.6.10", - "@storybook/preview-api": "7.6.10", - "@storybook/theming": "7.6.10", - "@storybook/types": "7.6.10", + "@storybook/manager-api": "7.6.16", + "@storybook/preview-api": "7.6.16", + "@storybook/theming": "7.6.16", + "@storybook/types": "7.6.16", "@types/lodash": "^4.14.167", "color-convert": "^2.0.1", "dequal": "^2.0.2", @@ -18847,15 +18519,15 @@ } }, "node_modules/@storybook/builder-manager": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-7.6.10.tgz", - "integrity": "sha512-f+YrjZwohGzvfDtH8BHzqM3xW0p4vjjg9u7uzRorqUiNIAAKHpfNrZ/WvwPlPYmrpAHt4xX/nXRJae4rFSygPw==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-7.6.16.tgz", + "integrity": "sha512-QTmvjmk49tpPe5IFM3SwHvRb1P6G0PTip4mCO7ab/zKiWaXlg9QZF5su+2e3KSil4ATssr3ybUlKlkqSubaCyQ==", "dev": true, "dependencies": { "@fal-works/esbuild-plugin-global-externals": "^2.1.2", - "@storybook/core-common": "7.6.10", - "@storybook/manager": "7.6.10", - "@storybook/node-logger": "7.6.10", + "@storybook/core-common": "7.6.16", + "@storybook/manager": "7.6.16", + "@storybook/node-logger": "7.6.16", "@types/ejs": "^3.1.1", "@types/find-cache-dir": "^3.2.1", "@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.10", @@ -18910,19 +18582,19 @@ } }, "node_modules/@storybook/builder-vite": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-7.6.10.tgz", - "integrity": "sha512-qxe19axiNJVdIKj943e1ucAmADwU42fTGgMSdBzzrvfH3pSOmx2057aIxRzd8YtBRnj327eeqpgCHYIDTunMYQ==", - "dev": true, - "dependencies": { - "@storybook/channels": "7.6.10", - "@storybook/client-logger": "7.6.10", - "@storybook/core-common": "7.6.10", - "@storybook/csf-plugin": "7.6.10", - "@storybook/node-logger": "7.6.10", - "@storybook/preview": "7.6.10", - "@storybook/preview-api": "7.6.10", - "@storybook/types": "7.6.10", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-7.6.16.tgz", + "integrity": "sha512-i0nL6ajWpcThnTP4ndUXdIKdOatYC6vWE4HxMjuRjfEgKBXn4pJbDBbQZ+L5jFau7aLGOTXiSQ69ONEhSjhllg==", + "dev": true, + "dependencies": { + "@storybook/channels": "7.6.16", + "@storybook/client-logger": "7.6.16", + "@storybook/core-common": "7.6.16", + "@storybook/csf-plugin": "7.6.16", + "@storybook/node-logger": "7.6.16", + "@storybook/preview": "7.6.16", + "@storybook/preview-api": "7.6.16", + "@storybook/types": "7.6.16", "@types/find-cache-dir": "^3.2.1", "browser-assert": "^1.2.1", "es-module-lexer": "^0.9.3", @@ -18990,13 +18662,13 @@ } }, "node_modules/@storybook/channels": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.10.tgz", - "integrity": "sha512-ITCLhFuDBKgxetuKnWwYqMUWlU7zsfH3gEKZltTb+9/2OAWR7ez0iqU7H6bXP1ridm0DCKkt2UMWj2mmr9iQqg==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.16.tgz", + "integrity": "sha512-LKB0t4OGISez1O4TRJ/CDPxlb2wAW7gg8YRL91VVUHeffVyr4bnpklvMbLbuEcYrysM82Q2UMB9ipQdyK6Issg==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.10", - "@storybook/core-events": "7.6.10", + "@storybook/client-logger": "7.6.16", + "@storybook/core-events": "7.6.16", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -19008,23 +18680,23 @@ } }, "node_modules/@storybook/cli": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-7.6.10.tgz", - "integrity": "sha512-pK1MEseMm73OMO2OVoSz79QWX8ymxgIGM8IeZTCo9gImiVRChMNDFYcv8yPWkjuyesY8c15CoO48aR7pdA1OjQ==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-7.6.16.tgz", + "integrity": "sha512-bFEiAXv69ZLqFnxAMCEBTxZqLnPG0GAEpGqwpPbt2lk6lLtro8g+//OR9RiztZt0YFHpp0YK5WCy6Xq0gwXcPw==", "dev": true, "dependencies": { "@babel/core": "^7.23.2", "@babel/preset-env": "^7.23.2", "@babel/types": "^7.23.0", "@ndelangen/get-tarball": "^3.0.7", - "@storybook/codemod": "7.6.10", - "@storybook/core-common": "7.6.10", - "@storybook/core-events": "7.6.10", - "@storybook/core-server": "7.6.10", - "@storybook/csf-tools": "7.6.10", - "@storybook/node-logger": "7.6.10", - "@storybook/telemetry": "7.6.10", - "@storybook/types": "7.6.10", + "@storybook/codemod": "7.6.16", + "@storybook/core-common": "7.6.16", + "@storybook/core-events": "7.6.16", + "@storybook/core-server": "7.6.16", + "@storybook/csf-tools": "7.6.16", + "@storybook/node-logger": "7.6.16", + "@storybook/telemetry": "7.6.16", + "@storybook/types": "7.6.16", "@types/semver": "^7.3.4", "@yarnpkg/fslib": "2.10.3", "@yarnpkg/libzip": "2.3.0", @@ -19175,9 +18847,9 @@ } }, "node_modules/@storybook/cli/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -19217,9 +18889,9 @@ "dev": true }, "node_modules/@storybook/client-logger": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.10.tgz", - "integrity": "sha512-U7bbpu21ntgePMz/mKM18qvCSWCUGCUlYru8mgVlXLCKqFqfTeP887+CsPEQf29aoE3cLgDrxqbRJ1wxX9kL9A==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.16.tgz", + "integrity": "sha512-Vquhmgk/SO0VeAkojcA1juuicBHoTST+f4XwBvyUNiebOSOdGIkxHVxpDFXu2kS0aKflFBEutX2IgoysDup+fQ==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -19230,18 +18902,18 @@ } }, "node_modules/@storybook/codemod": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-7.6.10.tgz", - "integrity": "sha512-pzFR0nocBb94vN9QCJLC3C3dP734ZigqyPmd0ZCDj9Xce2ytfHK3v1lKB6TZWzKAZT8zztauECYxrbo4LVuagw==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-7.6.16.tgz", + "integrity": "sha512-RlL2I7UV+ef3j+6NaFa1Y6j/hU9KDKssync1GfKypUKlFAP76ozfpRWdDVEkc/29JruEEkbvMiUxQdP7CE3PMQ==", "dev": true, "dependencies": { "@babel/core": "^7.23.2", "@babel/preset-env": "^7.23.2", "@babel/types": "^7.23.0", "@storybook/csf": "^0.1.2", - "@storybook/csf-tools": "7.6.10", - "@storybook/node-logger": "7.6.10", - "@storybook/types": "7.6.10", + "@storybook/csf-tools": "7.6.16", + "@storybook/node-logger": "7.6.16", + "@storybook/types": "7.6.16", "@types/cross-spawn": "^6.0.2", "cross-spawn": "^7.0.3", "globby": "^11.0.2", @@ -19271,18 +18943,18 @@ } }, "node_modules/@storybook/components": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/components/-/components-7.6.10.tgz", - "integrity": "sha512-H5hF8pxwtbt0LxV24KMMsPlbYG9Oiui3ObvAQkvGu6q62EYxRPeNSrq3GBI5XEbI33OJY9bT24cVaZx18dXqwQ==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-7.6.16.tgz", + "integrity": "sha512-5KZQqxFiVEGM485ceF/7PmiNEkHgouEa8ZUJvDGrW9Ap5MfN0xqAuyTTveHvZzGrKp0YlOcOnpqwu/cSk0HQKA==", "dev": true, "dependencies": { "@radix-ui/react-select": "^1.2.2", "@radix-ui/react-toolbar": "^1.0.4", - "@storybook/client-logger": "7.6.10", + "@storybook/client-logger": "7.6.16", "@storybook/csf": "^0.1.2", "@storybook/global": "^5.0.0", - "@storybook/theming": "7.6.10", - "@storybook/types": "7.6.10", + "@storybook/theming": "7.6.16", + "@storybook/types": "7.6.16", "memoizerific": "^1.11.3", "use-resize-observer": "^9.1.0", "util-deprecate": "^1.0.2" @@ -19297,13 +18969,13 @@ } }, "node_modules/@storybook/core-client": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/core-client/-/core-client-7.6.10.tgz", - "integrity": "sha512-DjnzSzSNDmZyxyg6TxugzWQwOsW+n/iWVv6sHNEvEd5STr0mjuJjIEELmv58LIr5Lsre5+LEddqHsyuLyt8ubg==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/core-client/-/core-client-7.6.16.tgz", + "integrity": "sha512-ogVwvjpNPrcv8Lk9oSa38b7X4NNgYIgVnCjvvr9FCmhh4xAuA4XNUyB+vyAdK4JOT0/CoMKRpTp+VL5e2+BZbg==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.10", - "@storybook/preview-api": "7.6.10" + "@storybook/client-logger": "7.6.16", + "@storybook/preview-api": "7.6.16" }, "funding": { "type": "opencollective", @@ -19311,14 +18983,14 @@ } }, "node_modules/@storybook/core-common": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.10.tgz", - "integrity": "sha512-K3YWqjCKMnpvYsWNjOciwTH6zWbuuZzmOiipziZaVJ+sB1XYmH52Y3WGEm07TZI8AYK9DRgwA13dR/7W0nw72Q==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.16.tgz", + "integrity": "sha512-Xn3Fbo4k9RRKgYzOBx9CeJFpWgS9gkcdo3J9XMMzmUqdZ+MUGT74kl2sMmzSypcH5aI1AUl5vZIKvLwloliejw==", "dev": true, "dependencies": { - "@storybook/core-events": "7.6.10", - "@storybook/node-logger": "7.6.10", - "@storybook/types": "7.6.10", + "@storybook/core-events": "7.6.16", + "@storybook/node-logger": "7.6.16", + "@storybook/types": "7.6.16", "@types/find-cache-dir": "^3.2.1", "@types/node": "^18.0.0", "@types/node-fetch": "^2.6.4", @@ -19346,9 +19018,9 @@ } }, "node_modules/@storybook/core-common/node_modules/@types/node": { - "version": "18.19.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.10.tgz", - "integrity": "sha512-IZD8kAM02AW1HRDTPOlz3npFava678pr8Ie9Vp8uRhBROXAv8MXT2pCnGZZAKYdromsNQLHQcfWQ6EOatVLtqA==", + "version": "18.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.17.tgz", + "integrity": "sha512-SzyGKgwPzuWp2SHhlpXKzCX0pIOfcI4V2eF37nNBJOhwlegQ83omtVQ1XxZpDE06V/d6AQvfQdPfnw0tRC//Ng==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -19442,9 +19114,9 @@ } }, "node_modules/@storybook/core-events": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.10.tgz", - "integrity": "sha512-yccDH67KoROrdZbRKwxgTswFMAco5nlCyxszCDASCLygGSV2Q2e+YuywrhchQl3U6joiWi3Ps1qWu56NeNafag==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.16.tgz", + "integrity": "sha512-mkBqzrbp6vmdjo0fBZGrFQQ4YdvMFxF6AesdKTf8EzPa69FoxnhQLrmQ4aXF+9vXkxfXVJF2HfpoTEdfqqAo+w==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -19455,26 +19127,26 @@ } }, "node_modules/@storybook/core-server": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-7.6.10.tgz", - "integrity": "sha512-2icnqJkn3vwq0eJPP0rNaHd7IOvxYf5q4lSVl2AWTxo/Ae19KhokI6j/2vvS2XQJMGQszwshlIwrZUNsj5p0yw==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-7.6.16.tgz", + "integrity": "sha512-Sj8j45XMg1bI7ktMqj9gxXHsZ4d1KgR+2A2eaxR7Heho7253WkUltLYxhu3hdH01rRJXYFxn/zZBxYfEib94Vg==", "dev": true, "dependencies": { "@aw-web-design/x-default-browser": "1.4.126", "@discoveryjs/json-ext": "^0.5.3", - "@storybook/builder-manager": "7.6.10", - "@storybook/channels": "7.6.10", - "@storybook/core-common": "7.6.10", - "@storybook/core-events": "7.6.10", + "@storybook/builder-manager": "7.6.16", + "@storybook/channels": "7.6.16", + "@storybook/core-common": "7.6.16", + "@storybook/core-events": "7.6.16", "@storybook/csf": "^0.1.2", - "@storybook/csf-tools": "7.6.10", + "@storybook/csf-tools": "7.6.16", "@storybook/docs-mdx": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/manager": "7.6.10", - "@storybook/node-logger": "7.6.10", - "@storybook/preview-api": "7.6.10", - "@storybook/telemetry": "7.6.10", - "@storybook/types": "7.6.10", + "@storybook/manager": "7.6.16", + "@storybook/node-logger": "7.6.16", + "@storybook/preview-api": "7.6.16", + "@storybook/telemetry": "7.6.16", + "@storybook/types": "7.6.16", "@types/detect-port": "^1.3.0", "@types/node": "^18.0.0", "@types/pretty-hrtime": "^1.0.0", @@ -19508,9 +19180,9 @@ } }, "node_modules/@storybook/core-server/node_modules/@types/node": { - "version": "18.19.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.10.tgz", - "integrity": "sha512-IZD8kAM02AW1HRDTPOlz3npFava678pr8Ie9Vp8uRhBROXAv8MXT2pCnGZZAKYdromsNQLHQcfWQ6EOatVLtqA==", + "version": "18.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.17.tgz", + "integrity": "sha512-SzyGKgwPzuWp2SHhlpXKzCX0pIOfcI4V2eF37nNBJOhwlegQ83omtVQ1XxZpDE06V/d6AQvfQdPfnw0tRC//Ng==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -19595,9 +19267,9 @@ } }, "node_modules/@storybook/core-server/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -19646,12 +19318,12 @@ } }, "node_modules/@storybook/csf-plugin": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-7.6.10.tgz", - "integrity": "sha512-Sc+zZg/BnPH2X28tthNaQBnDiFfO0QmfjVoOx0fGYM9SvY3P5ehzWwp5hMRBim6a/twOTzePADtqYL+t6GMqqg==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-7.6.16.tgz", + "integrity": "sha512-hslhGtnijMpL7HAcYYgIuo6acVLP7BDptflMwIyGFWKK3MHjMxqWTZ3Sj+BV1yg/pYZdqC2NYyUypeuuSpivSA==", "dev": true, "dependencies": { - "@storybook/csf-tools": "7.6.10", + "@storybook/csf-tools": "7.6.16", "unplugin": "^1.3.1" }, "funding": { @@ -19660,9 +19332,9 @@ } }, "node_modules/@storybook/csf-tools": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.10.tgz", - "integrity": "sha512-TnDNAwIALcN6SA4l00Cb67G02XMOrYU38bIpFJk5VMDX2dvgPjUtJNBuLmEbybGcOt7nPyyFIHzKcY5FCVGoWA==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.16.tgz", + "integrity": "sha512-8kVBq3UKDrEQq7rTHlNMoe1TDOTdO8iL8Jtv/FMDu/Qzj6AoT8/bjrtPsGjGMfVjP7QwBDeiLn6rStT4TlVGog==", "dev": true, "dependencies": { "@babel/generator": "^7.23.0", @@ -19670,7 +19342,7 @@ "@babel/traverse": "^7.23.2", "@babel/types": "^7.23.0", "@storybook/csf": "^0.1.2", - "@storybook/types": "7.6.10", + "@storybook/types": "7.6.16", "fs-extra": "^11.1.0", "recast": "^0.23.1", "ts-dedent": "^2.0.0" @@ -19734,14 +19406,14 @@ "dev": true }, "node_modules/@storybook/docs-tools": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/docs-tools/-/docs-tools-7.6.10.tgz", - "integrity": "sha512-UgbikducoXzqQHf2TozO0f2rshaeBNnShVbL5Ai4oW7pDymBmrfzdjGbF/milO7yxNKcoIByeoNmu384eBamgQ==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/docs-tools/-/docs-tools-7.6.16.tgz", + "integrity": "sha512-meuq5uLGBLOSJXKeCt9iEH0uVKgGqwfEBi2T4E2w3BcubC/6oQ3VeZl25/KO+l1XcLmOg9LkN2ZOtLV9TEiVLQ==", "dev": true, "dependencies": { - "@storybook/core-common": "7.6.10", - "@storybook/preview-api": "7.6.10", - "@storybook/types": "7.6.10", + "@storybook/core-common": "7.6.16", + "@storybook/preview-api": "7.6.16", + "@storybook/types": "7.6.16", "@types/doctrine": "^0.0.3", "assert": "^2.1.0", "doctrine": "^3.0.0", @@ -19759,9 +19431,9 @@ "dev": true }, "node_modules/@storybook/manager": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.6.10.tgz", - "integrity": "sha512-Co3sLCbNYY6O4iH2ggmRDLCPWLj03JE5s/DOG8OVoXc6vBwTc/Qgiyrsxxp6BHQnPpM0mxL6aKAxE3UjsW/Nog==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.6.16.tgz", + "integrity": "sha512-CPDhgT4jjF0CDgLDxT/R+amMJXpXxSsVp+XzahPbEB9Yu4v0W0HW3f2vSuNJXwpfofrPSkbJweO/oC4ioOtavw==", "dev": true, "funding": { "type": "opencollective", @@ -19769,19 +19441,19 @@ } }, "node_modules/@storybook/manager-api": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.6.10.tgz", - "integrity": "sha512-8eGVpRlpunuFScDtc7nxpPJf/4kJBAAZlNdlhmX09j8M3voX6GpcxabBamSEX5pXZqhwxQCshD4IbqBmjvadlw==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.6.16.tgz", + "integrity": "sha512-pX3xw4DsPhYTWEDspsnJiZSoakn0z3Rdt9YmHU0/NaFBLn64EClzd9XMDnGXnZzW1DtdG6T6l2CwDNDCNIVkWg==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.10", - "@storybook/client-logger": "7.6.10", - "@storybook/core-events": "7.6.10", + "@storybook/channels": "7.6.16", + "@storybook/client-logger": "7.6.16", + "@storybook/core-events": "7.6.16", "@storybook/csf": "^0.1.2", "@storybook/global": "^5.0.0", - "@storybook/router": "7.6.10", - "@storybook/theming": "7.6.10", - "@storybook/types": "7.6.10", + "@storybook/router": "7.6.16", + "@storybook/theming": "7.6.16", + "@storybook/types": "7.6.16", "dequal": "^2.0.2", "lodash": "^4.17.21", "memoizerific": "^1.11.3", @@ -19801,9 +19473,9 @@ "dev": true }, "node_modules/@storybook/node-logger": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.10.tgz", - "integrity": "sha512-ZBuqrv4bjJzKXyfRGFkVIi+z6ekn6rOPoQao4KmsfLNQAUUsEdR8Baw/zMnnU417zw5dSEaZdpuwx75SCQAeOA==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.16.tgz", + "integrity": "sha512-s18wgtLynLWnunz47lkVIpjk8J6LxT/OmfzkggieU8cG2XYRbf//t7/EOUpOqK77+Xqm3epSwgDAxOXGfjOjAA==", "dev": true, "funding": { "type": "opencollective", @@ -19811,9 +19483,9 @@ } }, "node_modules/@storybook/postinstall": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/postinstall/-/postinstall-7.6.10.tgz", - "integrity": "sha512-SMdXtednPCy3+SRJ7oN1OPN1oVFhj3ih+ChOEX8/kZ5J3nfmV3wLPtsZvFGUCf0KWQEP1xL+1Urv48mzMKcV/w==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/postinstall/-/postinstall-7.6.16.tgz", + "integrity": "sha512-axWxj8e90+iLUZPGU9Zvn2Jc/GQrWspu8DpwRCS7N23epTVW6n6OWp31GAShdSx8Oh5lmCMXGegTd1v2Mwc61A==", "dev": true, "funding": { "type": "opencollective", @@ -19821,9 +19493,9 @@ } }, "node_modules/@storybook/preview": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/preview/-/preview-7.6.10.tgz", - "integrity": "sha512-F07BzVXTD3byq+KTWtvsw3pUu3fQbyiBNLFr2CnfU4XSdLKja5lDt8VqDQq70TayVQOf5qfUTzRd4M6pQkjw1w==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/preview/-/preview-7.6.16.tgz", + "integrity": "sha512-q4DbLn9kEK8JM9s+2oIjXBPHQhY0tQzsZ5hFeq833vNFcmuHnXS+WYk20b+UkmzL6j+E8pLm8WpI7rdbi0ZUVA==", "dev": true, "funding": { "type": "opencollective", @@ -19831,17 +19503,17 @@ } }, "node_modules/@storybook/preview-api": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.10.tgz", - "integrity": "sha512-5A3etoIwZCx05yuv3KSTv1wynN4SR4rrzaIs/CTBp3BC4q1RBL+Or/tClk0IJPXQMlx/4Y134GtNIBbkiDofpw==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.16.tgz", + "integrity": "sha512-V9x9HOhi4CJuiX+0a7GU0JlfRAp6txStGMkV0DrCATbxSWpK+6d5x2Te521z16V3RIMMmYn33aEyarOp5WjTqw==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.10", - "@storybook/client-logger": "7.6.10", - "@storybook/core-events": "7.6.10", + "@storybook/channels": "7.6.16", + "@storybook/client-logger": "7.6.16", + "@storybook/core-events": "7.6.16", "@storybook/csf": "^0.1.2", "@storybook/global": "^5.0.0", - "@storybook/types": "7.6.10", + "@storybook/types": "7.6.16", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -19857,18 +19529,18 @@ } }, "node_modules/@storybook/react": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-7.6.10.tgz", - "integrity": "sha512-wwBn1cg2uZWW4peqqBjjU7XGmFq8HdkVUtWwh6dpfgmlY1Aopi+vPgZt7pY9KkWcTOq5+DerMdSfwxukpc3ajQ==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-7.6.16.tgz", + "integrity": "sha512-3vzjtEHu9xXLz827JiwC448ZVattzAR5qkfVg3dVOD1MtLH8LTJ/gOqv/8Kq0fOtEgOdlcAF4jQV/XAL6pEAkQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.10", - "@storybook/core-client": "7.6.10", - "@storybook/docs-tools": "7.6.10", + "@storybook/client-logger": "7.6.16", + "@storybook/core-client": "7.6.16", + "@storybook/docs-tools": "7.6.16", "@storybook/global": "^5.0.0", - "@storybook/preview-api": "7.6.10", - "@storybook/react-dom-shim": "7.6.10", - "@storybook/types": "7.6.10", + "@storybook/preview-api": "7.6.16", + "@storybook/react-dom-shim": "7.6.16", + "@storybook/types": "7.6.16", "@types/escodegen": "^0.0.6", "@types/estree": "^0.0.51", "@types/node": "^18.0.0", @@ -19903,9 +19575,9 @@ } }, "node_modules/@storybook/react-dom-shim": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-7.6.10.tgz", - "integrity": "sha512-M+N/h6ximacaFdIDjMN2waNoWwApeVYTpFeoDppiFTvdBTXChyIuiPgYX9QSg7gDz92OaA52myGOot4wGvXVzg==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-7.6.16.tgz", + "integrity": "sha512-F6pGgL2pWy5utn6m2YAVz1PYZO3pdlNHfT85g5Om3q7CR4msWpMQ1O/oEVYgqfJ9UfOqCV/mHeDWICzUa7pv6g==", "dev": true, "funding": { "type": "opencollective", @@ -19917,15 +19589,15 @@ } }, "node_modules/@storybook/react-vite": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-7.6.10.tgz", - "integrity": "sha512-YE2+J1wy8nO+c6Nv/hBMu91Edew3K184L1KSnfoZV8vtq2074k1Me/8pfe0QNuq631AncpfCYNb37yBAXQ/80w==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-7.6.16.tgz", + "integrity": "sha512-6Qu04cnKtpHyIWHt/1pjcRYS3ROoiF9gV1jgE8bubMODbFuT38w0Hu6JIcyH2FdAvhujjt4gQT5oXJRvXX1cqQ==", "dev": true, "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "0.3.0", "@rollup/pluginutils": "^5.0.2", - "@storybook/builder-vite": "7.6.10", - "@storybook/react": "7.6.10", + "@storybook/builder-vite": "7.6.16", + "@storybook/react": "7.6.16", "@vitejs/plugin-react": "^3.0.1", "magic-string": "^0.30.0", "react-docgen": "^7.0.0" @@ -19981,9 +19653,9 @@ "dev": true }, "node_modules/@storybook/react/node_modules/@types/node": { - "version": "18.19.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.10.tgz", - "integrity": "sha512-IZD8kAM02AW1HRDTPOlz3npFava678pr8Ie9Vp8uRhBROXAv8MXT2pCnGZZAKYdromsNQLHQcfWQ6EOatVLtqA==", + "version": "18.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.17.tgz", + "integrity": "sha512-SzyGKgwPzuWp2SHhlpXKzCX0pIOfcI4V2eF37nNBJOhwlegQ83omtVQ1XxZpDE06V/d6AQvfQdPfnw0tRC//Ng==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -20002,12 +19674,12 @@ } }, "node_modules/@storybook/router": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.6.10.tgz", - "integrity": "sha512-G/H4Jn2+y8PDe8Zbq4DVxF/TPn0/goSItdILts39JENucHiuGBCjKjSWGBe1rkwKi1tUbB3yhxJVrLagxFEPpQ==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.6.16.tgz", + "integrity": "sha512-PgVuzs83g4dq2r1qdcc0wvS1Pe1UpKdq54uy4TkBrrei7hBzB/+POztPXs0rVXXBXdCQT/jomLmRo/yC45bsGg==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.10", + "@storybook/client-logger": "7.6.16", "memoizerific": "^1.11.3", "qs": "^6.10.0" }, @@ -20017,13 +19689,13 @@ } }, "node_modules/@storybook/source-loader": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/source-loader/-/source-loader-7.6.10.tgz", - "integrity": "sha512-S3nOWyj+sdpsqJqKGIN3DKE1q+Q0KYxEyPlPCawMFazozUH7tOodTIqmHBqJZCSNqdC4M1S/qcL8vpP4PfXhuA==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/source-loader/-/source-loader-7.6.16.tgz", + "integrity": "sha512-HPZOFw0eAi5l5nXfewo7jMC2IRqhwF2TQfXvceNKk9ohr1bZXWz6+/Axs7ZGmdNjT0izgiOnq3vUTDsGLURHYQ==", "dev": true, "dependencies": { "@storybook/csf": "^0.1.2", - "@storybook/types": "7.6.10", + "@storybook/types": "7.6.16", "estraverse": "^5.2.0", "lodash": "^4.17.21", "prettier": "^2.8.0" @@ -20049,14 +19721,14 @@ } }, "node_modules/@storybook/telemetry": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.6.10.tgz", - "integrity": "sha512-p3mOSUtIyy2tF1z6pQXxNh1JzYFcAm97nUgkwLzF07GfEdVAPM+ftRSLFbD93zVvLEkmLTlsTiiKaDvOY/lQWg==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.6.16.tgz", + "integrity": "sha512-5Uaz6zSRBEio89ScrAN7KKz+mBTJ5Jc/8Uf0uUHIhAxiHprs16PhIBo6MtBeWPQoiNwytN884sAtiUFAP4zFQQ==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.10", - "@storybook/core-common": "7.6.10", - "@storybook/csf-tools": "7.6.10", + "@storybook/client-logger": "7.6.16", + "@storybook/core-common": "7.6.16", + "@storybook/csf-tools": "7.6.16", "chalk": "^4.1.0", "detect-package-manager": "^2.0.1", "fetch-retry": "^5.0.2", @@ -20156,13 +19828,13 @@ } }, "node_modules/@storybook/theming": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.6.10.tgz", - "integrity": "sha512-f5tuy7yV3TOP3fIboSqpgLHy0wKayAw/M8HxX0jVET4Z4fWlFK0BiHJabQ+XEdAfQM97XhPFHB2IPbwsqhCEcQ==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.6.16.tgz", + "integrity": "sha512-ZiUyakApTzAiAR28JwqbqY426U1OlJPG/Y7ddQgYgTsdoRFR1iMewAxWW1LId1q3B1dtiIHAccqhocEMNcYkLA==", "dev": true, "dependencies": { "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@storybook/client-logger": "7.6.10", + "@storybook/client-logger": "7.6.16", "@storybook/global": "^5.0.0", "memoizerific": "^1.11.3" }, @@ -20176,12 +19848,12 @@ } }, "node_modules/@storybook/types": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.10.tgz", - "integrity": "sha512-hcS2HloJblaMpCAj2axgGV+53kgSRYPT0a1PG1IHsZaYQILfHSMmBqM8XzXXYTsgf9250kz3dqFX1l0n3EqMlQ==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.16.tgz", + "integrity": "sha512-Ld4dKbgSbvqThdBNwNlOxQu5AiS6U9DXI5evf/j83eWs6skO3OBdQp+GWa6sUCI9eRqH8tFsw/YmMcIZ4uZrBQ==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.10", + "@storybook/channels": "7.6.16", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -20469,9 +20141,9 @@ } }, "node_modules/@tabler/icons": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-2.46.0.tgz", - "integrity": "sha512-Q5G8Pj5IO+Uhc6pszpu5/hGYY018JwEzzvmuqr+gKJtfIvAHA3umpwUilMRLEy89p+WCP+YsDhicMhfBCCv1qA==", + "version": "2.47.0", + "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-2.47.0.tgz", + "integrity": "sha512-4w5evLh+7FUUiA1GucvGj2ReX2TvOjEr4ejXdwL/bsjoSkof6r1gQmzqI+VHrE2CpJpB3al7bCTulOkFa/RcyA==", "dev": true, "funding": { "type": "github", @@ -20479,12 +20151,12 @@ } }, "node_modules/@tabler/icons-react": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-2.46.0.tgz", - "integrity": "sha512-X8MRxuslIOFqMjAo+GvUZDpjlOwNYNJTuOsHXf/NBvVI6ygqUf0FUNsDLLA5fQ6k6KtRwxMlgGB+eR8ZG1UP0g==", + "version": "2.47.0", + "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-2.47.0.tgz", + "integrity": "sha512-iqly2FvCF/qUbgmvS8E40rVeYY7laltc5GUjRxQj59DuX0x/6CpKHTXt86YlI2whg4czvd/c8Ce8YR08uEku0g==", "dev": true, "dependencies": { - "@tabler/icons": "2.46.0", + "@tabler/icons": "2.47.0", "prop-types": "^15.7.2" }, "funding": { @@ -20496,12 +20168,12 @@ } }, "node_modules/@tanstack/react-virtual": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.0.2.tgz", - "integrity": "sha512-9XbRLPKgnhMwwmuQMnJMv+5a9sitGNCSEtf/AZXzmJdesYk7XsjYHaEDny+IrJzvPNwZliIIDwCRiaUqR3zzCA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.1.1.tgz", + "integrity": "sha512-9tW9xwEW7exSa/8bxu29IPCcB5c9Xlq+whETixIIgYZYKuUY4ZOr000q3oLpL4bkOkolQbB4WXM0MoQGgJXqDg==", "dev": true, "dependencies": { - "@tanstack/virtual-core": "3.0.0" + "@tanstack/virtual-core": "3.1.1" }, "funding": { "type": "github", @@ -20513,9 +20185,9 @@ } }, "node_modules/@tanstack/virtual-core": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.0.0.tgz", - "integrity": "sha512-SYXOBTjJb05rXa2vl55TTwO40A6wKu0R5i1qQwhJYNDIqaIGF7D0HsLw+pJAyi2OvntlEIVusx3xtbbgSUi6zg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.1.1.tgz", + "integrity": "sha512-I5lerX+RWxLM+zw35gwwQIoLvtkOm0ecuQUlEjNey+Ga6TnR66WKLBnSHre59onugxhpDLT2nofRYzxf+izDFQ==", "dev": true, "funding": { "type": "github", @@ -20635,9 +20307,9 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.3.0.tgz", - "integrity": "sha512-hJVIrkFizEQxoWsGBlycTcQhrpoCH4DhXfrnHFFXgkx3Xdm15zycsq5Ep+vpw4W8S0NJa8cxDHcuJib+1tEbhg==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.2.tgz", + "integrity": "sha512-CzqH0AFymEMG48CpzXFriYYkOjk6ZGPCLMhW9e9jg3KMCn5OfJecF8GtGW7yGfR/IgCe3SX8BSwjdzI6BBbZLw==", "dev": true, "dependencies": { "@adobe/css-tools": "^4.3.2", @@ -20735,9 +20407,9 @@ } }, "node_modules/@testing-library/react": { - "version": "14.1.2", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.1.2.tgz", - "integrity": "sha512-z4p7DVBTPjKM5qDZ0t5ZjzkpSNb+fZy1u6bzO7kk8oeGagpPCAtgh4cx1syrfp7a+QWkM021jGqjJaxJJnXAZg==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.2.1.tgz", + "integrity": "sha512-sGdjws32ai5TLerhvzThYFbpnF9XtL65Cjf+gB0Dhr29BGqK+mAeN7SURSdu+eqgET4ANcWoC7FQpkaiGvBr+A==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", @@ -20986,9 +20658,9 @@ } }, "node_modules/@types/connect": { - "version": "3.4.36", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", - "integrity": "sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==", + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dependencies": { "@types/node": "*" } @@ -21135,9 +20807,9 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" }, "node_modules/@types/estree-jsx": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.3.tgz", - "integrity": "sha512-pvQ+TKeRHeiUGRhvYwRrQ/ISnohKkSJR14fT2yqyZ4e9K5vqc7hrtY2Y1Dw0ZwAzQ6DQsxsaCUuSIIi8v0Cq6w==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.4.tgz", + "integrity": "sha512-5idy3hvI9lAMqsyilBM+N+boaCf1MgoefbDxN6KEO5aK17TOHwFAYT9sjxzeKAiIWRUBgLxmZ9mPcnzZXtTcRQ==", "dev": true, "dependencies": { "@types/estree": "*" @@ -21170,9 +20842,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.42", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.42.tgz", - "integrity": "sha512-ckM3jm2bf/MfB3+spLPWYPUH573plBFwpOhqQ2WottxYV85j1HQFlxmnTq57X1yHY9awZPig06hL/cLMgNWHIQ==", + "version": "4.17.43", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", + "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -21247,9 +20919,9 @@ } }, "node_modules/@types/hast": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.3.tgz", - "integrity": "sha512-2fYGlaDy/qyLlhidX42wAH0KBi2TCjKMH8CHmBXgRlJ3Y+OXTiqsPQ6IWarZKwF1JoUcAJdPogv1d4b0COTpmQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", "dev": true, "dependencies": { "@types/unist": "*" @@ -21330,9 +21002,9 @@ } }, "node_modules/@types/jest": { - "version": "29.5.11", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.11.tgz", - "integrity": "sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ==", + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -21376,9 +21048,9 @@ } }, "node_modules/@types/koa": { - "version": "2.13.9", - "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.13.9.tgz", - "integrity": "sha512-tPX3cN1dGrMn+sjCDEiQqXH2AqlPoPd594S/8zxwUm/ZbPsQXKqHPUypr2gjCPhHUc+nDJLduhh5lXI/1olnGQ==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.14.0.tgz", + "integrity": "sha512-DTDUyznHGNHAl+wd1n0z1jxNajduyTh8R53xoewuerdBzGo6Ogj6F2299BFtrexJw4NtgjsI5SMPCmV9gZwGXA==", "dependencies": { "@types/accepts": "*", "@types/content-disposition": "*", @@ -21432,9 +21104,9 @@ } }, "node_modules/@types/mdx": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.10.tgz", - "integrity": "sha512-Rllzc5KHk0Al5/WANwgSPl1/CwjqCy+AZrGd78zuK+jO9aDM6ffblZ+zIjgPNAaEBmlO0RYDvLNh7wD0zKVgEg==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.11.tgz", + "integrity": "sha512-HM5bwOaIQJIQbAYfax35HCKxx7a3KrK3nBtIqJgSOitivTD1y3oW9P3rxY9RkXYPUk7y/AjAohfHKmFpGE79zw==", "dev": true }, "node_modules/@types/memcached": { @@ -21487,9 +21159,9 @@ } }, "node_modules/@types/node": { - "version": "20.11.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.8.tgz", - "integrity": "sha512-i7omyekpPTNdv4Jb/Rgqg0RU8YqLcNsI12quKSDkRXNfx7Wxdm6HhK1awT3xTgEkgxPn3bvnSpiEAc7a7Lpyow==", + "version": "20.11.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", + "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==", "dependencies": { "undici-types": "~5.26.4" } @@ -21543,18 +21215,18 @@ "dev": true }, "node_modules/@types/pdfkit": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@types/pdfkit/-/pdfkit-0.13.3.tgz", - "integrity": "sha512-CN0prCV0n1HssBg34izaTAcWRmq0916SnsmpTsRqIxlbdS6QkDYsZ5/cm6/a5V2MO3501fbZHkv9DLjJCh9W4Q==", + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/@types/pdfkit/-/pdfkit-0.13.4.tgz", + "integrity": "sha512-ixGNDHYJCCKuamY305wbfYSphZ2WPe8FPkjn8oF4fHV+PgPV4V+hecPh2VOS2h4RNtpSB3zQcR4sCpNvvrEb1A==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/pdfmake": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@types/pdfmake/-/pdfmake-0.2.8.tgz", - "integrity": "sha512-9HavCBXKri7lhfwnM4qK012ru2qGYXvV1BVgYuNwa+vX6KFfI2Pfd0YoJ2l8m2UhE2yd8d1KuIBku6+9igDr+Q==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@types/pdfmake/-/pdfmake-0.2.9.tgz", + "integrity": "sha512-uLDKEH3A1fnCd/qXYJB2OnKkkjfdC1oc6HYVYBKxsyN1UsJL/8Lt67T6WFo3umkL+5Zd74M2IYcOf5kwwd9x9w==", "dev": true, "dependencies": { "@types/node": "*", @@ -21618,9 +21290,9 @@ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, "node_modules/@types/react": { - "version": "18.2.48", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.48.tgz", - "integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==", + "version": "18.2.56", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.56.tgz", + "integrity": "sha512-NpwHDMkS/EFZF2dONFQHgkPRwhvgq/OAvIaGQzxGSBmaeR++kTg6njr15Vatz0/2VcCEwJQFi6Jf4Q0qBu0rLA==", "devOptional": true, "dependencies": { "@types/prop-types": "*", @@ -21629,9 +21301,9 @@ } }, "node_modules/@types/react-dom": { - "version": "18.2.18", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz", - "integrity": "sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.19.tgz", + "integrity": "sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA==", "dev": true, "dependencies": { "@types/react": "*" @@ -21705,9 +21377,9 @@ "devOptional": true }, "node_modules/@types/semver": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz", + "integrity": "sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==", "dev": true }, "node_modules/@types/send": { @@ -21791,9 +21463,9 @@ } }, "node_modules/@types/ssh2/node_modules/@types/node": { - "version": "18.19.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.10.tgz", - "integrity": "sha512-IZD8kAM02AW1HRDTPOlz3npFava678pr8Ie9Vp8uRhBROXAv8MXT2pCnGZZAKYdromsNQLHQcfWQ6EOatVLtqA==", + "version": "18.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.17.tgz", + "integrity": "sha512-SzyGKgwPzuWp2SHhlpXKzCX0pIOfcI4V2eF37nNBJOhwlegQ83omtVQ1XxZpDE06V/d6AQvfQdPfnw0tRC//Ng==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -21909,9 +21581,9 @@ "dev": true }, "node_modules/@types/validator": { - "version": "13.11.8", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.8.tgz", - "integrity": "sha512-c/hzNDBh7eRF+KbCf+OoZxKbnkpaK/cKp9iLQWqB7muXtM+MtL9SUUH8vCFcLn6dH1Qm05jiexK0ofWY7TfOhQ==", + "version": "13.11.9", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.9.tgz", + "integrity": "sha512-FCTsikRozryfayPuiI46QzH3fnrOoctTjvOYZkho9BTFLCOZ2rgZJHMOVgCOfttjPJcgOx52EpkY0CMfy87MIw==", "dev": true }, "node_modules/@types/ws": { @@ -21936,16 +21608,16 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.1.tgz", - "integrity": "sha512-roQScUGFruWod9CEyoV5KlCYrubC/fvG8/1zXuT0WTcxX87GnMMmnksMwSg99lo1xiKrBzw2icsJPMAw1OtKxg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.0.1.tgz", + "integrity": "sha512-OLvgeBv3vXlnnJGIAgCLYKjgMEU+wBGj07MQ/nxAaON+3mLzX7mJbhRYrVGiVvFiXtwFlkcBa/TtmglHy0UbzQ==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.19.1", - "@typescript-eslint/type-utils": "6.19.1", - "@typescript-eslint/utils": "6.19.1", - "@typescript-eslint/visitor-keys": "6.19.1", + "@typescript-eslint/scope-manager": "7.0.1", + "@typescript-eslint/type-utils": "7.0.1", + "@typescript-eslint/utils": "7.0.1", + "@typescript-eslint/visitor-keys": "7.0.1", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -21961,8 +21633,8 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -21983,9 +21655,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -22004,15 +21676,15 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.19.1.tgz", - "integrity": "sha512-WEfX22ziAh6pRE9jnbkkLGp/4RhTpffr2ZK5bJ18M8mIfA8A+k97U9ZyaXCEJRlmMHh7R9MJZWXp/r73DzINVQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.0.1.tgz", + "integrity": "sha512-8GcRRZNzaHxKzBPU3tKtFNing571/GwPBeCvmAUw0yBtfE2XVd0zFKJIMSWkHJcPQi0ekxjIts6L/rrZq5cxGQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.19.1", - "@typescript-eslint/types": "6.19.1", - "@typescript-eslint/typescript-estree": "6.19.1", - "@typescript-eslint/visitor-keys": "6.19.1", + "@typescript-eslint/scope-manager": "7.0.1", + "@typescript-eslint/types": "7.0.1", + "@typescript-eslint/typescript-estree": "7.0.1", + "@typescript-eslint/visitor-keys": "7.0.1", "debug": "^4.3.4" }, "engines": { @@ -22023,7 +21695,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -22032,13 +21704,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.1.tgz", - "integrity": "sha512-4CdXYjKf6/6aKNMSly/BP4iCSOpvMmqtDzRtqFyyAae3z5kkqEjKndR5vDHL8rSuMIIWP8u4Mw4VxLyxZW6D5w==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.0.1.tgz", + "integrity": "sha512-v7/T7As10g3bcWOOPAcbnMDuvctHzCFYCG/8R4bK4iYzdFqsZTbXGln0cZNVcwQcwewsYU2BJLay8j0/4zOk4w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.19.1", - "@typescript-eslint/visitor-keys": "6.19.1" + "@typescript-eslint/types": "7.0.1", + "@typescript-eslint/visitor-keys": "7.0.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -22049,13 +21721,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.19.1.tgz", - "integrity": "sha512-0vdyld3ecfxJuddDjACUvlAeYNrHP/pDeQk2pWBR2ESeEzQhg52DF53AbI9QCBkYE23lgkhLCZNkHn2hEXXYIg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.0.1.tgz", + "integrity": "sha512-YtT9UcstTG5Yqy4xtLiClm1ZpM/pWVGFnkAa90UfdkkZsR1eP2mR/1jbHeYp8Ay1l1JHPyGvoUYR6o3On5Nhmw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.19.1", - "@typescript-eslint/utils": "6.19.1", + "@typescript-eslint/typescript-estree": "7.0.1", + "@typescript-eslint/utils": "7.0.1", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -22067,7 +21739,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -22076,9 +21748,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.1.tgz", - "integrity": "sha512-6+bk6FEtBhvfYvpHsDgAL3uo4BfvnTnoge5LrrCj2eJN8g3IJdLTD4B/jK3Q6vo4Ql/Hoip9I8aB6fF+6RfDqg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.0.1.tgz", + "integrity": "sha512-uJDfmirz4FHib6ENju/7cz9SdMSkeVvJDK3VcMFvf/hAShg8C74FW+06MaQPODHfDJp/z/zHfgawIJRjlu0RLg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -22089,13 +21761,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.1.tgz", - "integrity": "sha512-aFdAxuhzBFRWhy+H20nYu19+Km+gFfwNO4TEqyszkMcgBDYQjmPJ61erHxuT2ESJXhlhrO7I5EFIlZ+qGR8oVA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.0.1.tgz", + "integrity": "sha512-SO9wHb6ph0/FN5OJxH4MiPscGah5wjOd0RRpaLvuBv9g8565Fgu0uMySFEPqwPHiQU90yzJ2FjRYKGrAhS1xig==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.19.1", - "@typescript-eslint/visitor-keys": "6.19.1", + "@typescript-eslint/types": "7.0.1", + "@typescript-eslint/visitor-keys": "7.0.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -22129,9 +21801,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -22150,17 +21822,17 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.19.1.tgz", - "integrity": "sha512-JvjfEZuP5WoMqwh9SPAPDSHSg9FBHHGhjPugSRxu5jMfjvBpq5/sGTD+9M9aQ5sh6iJ8AY/Kk/oUYVEMAPwi7w==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.0.1.tgz", + "integrity": "sha512-oe4his30JgPbnv+9Vef1h48jm0S6ft4mNwi9wj7bX10joGn07QRfqIqFHoMiajrtoU88cIhXf8ahwgrcbNLgPA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.19.1", - "@typescript-eslint/types": "6.19.1", - "@typescript-eslint/typescript-estree": "6.19.1", + "@typescript-eslint/scope-manager": "7.0.1", + "@typescript-eslint/types": "7.0.1", + "@typescript-eslint/typescript-estree": "7.0.1", "semver": "^7.5.4" }, "engines": { @@ -22171,7 +21843,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" } }, "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { @@ -22187,9 +21859,9 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -22208,12 +21880,12 @@ "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.1.tgz", - "integrity": "sha512-gkdtIO+xSO/SmI0W68DBg4u1KElmIUo3vXzgHyGPs6cxgB0sa3TlptRAAE0hUY1hM6FcDKEv7aIwiTGm76cXfQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.0.1.tgz", + "integrity": "sha512-hwAgrOyk++RTXrP4KzCg7zB2U0xt7RUU0ZdMSCsqF3eKUwkdXUMyTb0qdCuji7VIbcpG62kKTU9M1J1c9UpFBw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/types": "7.0.1", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -22262,9 +21934,9 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.2.2.tgz", - "integrity": "sha512-IHyKnDz18SFclIEEAHb9Y4Uxx0sPKC2VO1kdDCs1BF6Ip4S8rQprs971zIsooLUn7Afs71GRxWMWpkCGZpRMhw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.3.0.tgz", + "integrity": "sha512-e5Y5uK5NNoQMQaNitGQQjo9FoA5ZNcu7Bn6pH+dxUf48u6po1cX38kFBYUHZ9GNVkF4JLbncE0WeWwTw+nLrxg==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.1", @@ -22285,17 +21957,17 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "^1.0.0" + "vitest": "1.3.0" } }, "node_modules/@vitest/expect": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.2.2.tgz", - "integrity": "sha512-3jpcdPAD7LwHUUiT2pZTj2U82I2Tcgg2oVPvKxhn6mDI2On6tfvPQTjAI4628GUGDZrCm4Zna9iQHm5cEexOAg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.3.0.tgz", + "integrity": "sha512-7bWt0vBTZj08B+Ikv70AnLRicohYwFgzNjFqo9SxxqHHxSlUJGSXmCRORhOnRMisiUryKMdvsi1n27Bc6jL9DQ==", "dev": true, "dependencies": { - "@vitest/spy": "1.2.2", - "@vitest/utils": "1.2.2", + "@vitest/spy": "1.3.0", + "@vitest/utils": "1.3.0", "chai": "^4.3.10" }, "funding": { @@ -22303,12 +21975,12 @@ } }, "node_modules/@vitest/runner": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.2.2.tgz", - "integrity": "sha512-JctG7QZ4LSDXr5CsUweFgcpEvrcxOV1Gft7uHrvkQ+fsAVylmWQvnaAr/HDp3LAH1fztGMQZugIheTWjaGzYIg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.3.0.tgz", + "integrity": "sha512-1Jb15Vo/Oy7mwZ5bXi7zbgszsdIBNjc4IqP8Jpr/8RdBC4nF1CTzIAn2dxYvpF1nGSseeL39lfLQ2uvs5u1Y9A==", "dev": true, "dependencies": { - "@vitest/utils": "1.2.2", + "@vitest/utils": "1.3.0", "p-limit": "^5.0.0", "pathe": "^1.1.1" }, @@ -22332,9 +22004,9 @@ } }, "node_modules/@vitest/snapshot": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.2.2.tgz", - "integrity": "sha512-SmGY4saEw1+bwE1th6S/cZmPxz/Q4JWsl7LvbQIky2tKE35US4gd0Mjzqfr84/4OD0tikGWaWdMja/nWL5NIPA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.3.0.tgz", + "integrity": "sha512-swmktcviVVPYx9U4SEQXLV6AEY51Y6bZ14jA2yo6TgMxQ3h+ZYiO0YhAHGJNp0ohCFbPAis1R9kK0cvN6lDPQA==", "dev": true, "dependencies": { "magic-string": "^0.30.5", @@ -22346,9 +22018,9 @@ } }, "node_modules/@vitest/spy": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.2.2.tgz", - "integrity": "sha512-k9Gcahssw8d7X3pSLq3e3XEu/0L78mUkCjivUqCQeXJm9clfXR/Td8+AP+VC1O6fKPIDLcHDTAmBOINVuv6+7g==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.3.0.tgz", + "integrity": "sha512-AkCU0ThZunMvblDpPKgjIi025UxR8V7MZ/g/EwmAGpjIujLVV2X6rGYGmxE2D4FJbAy0/ijdROHMWa2M/6JVMw==", "dev": true, "dependencies": { "tinyspy": "^2.2.0" @@ -22358,12 +22030,12 @@ } }, "node_modules/@vitest/ui": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-1.2.2.tgz", - "integrity": "sha512-CG+5fa8lyoBr+9i+UZGS31Qw81v33QlD10uecHxN2CLJVN+jLnqx4pGzGvFFeJ7jSnUCT0AlbmVWY6fU6NJZmw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-1.3.0.tgz", + "integrity": "sha512-gDGEBUddrPOJvF4e0nIeKBz1whiDthBxBB0OUAbUqnY0HxJwqlKg9R257Sxoeh1Q7ZDSzc7qY96n4hrEPy1NaQ==", "dev": true, "dependencies": { - "@vitest/utils": "1.2.2", + "@vitest/utils": "1.3.0", "fast-glob": "^3.3.2", "fflate": "^0.8.1", "flatted": "^3.2.9", @@ -22375,13 +22047,13 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "^1.0.0" + "vitest": "1.3.0" } }, "node_modules/@vitest/utils": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.2.2.tgz", - "integrity": "sha512-WKITBHLsBHlpjnDQahr+XK6RE7MiAsgrIkr0pGhQ9ygoxBfUeG0lUG5iLlzqjmKSlBv3+j5EGsriBzh+C3Tq9g==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.3.0.tgz", + "integrity": "sha512-/LibEY/fkaXQufi4GDlQZhikQsPO2entBKtfuyIpr1jV4DpaeasqkeHjhdOhU24vSHshcSuEyVlWdzvv2XmYCw==", "dev": true, "dependencies": { "diff-sequences": "^29.6.3", @@ -23162,13 +22834,16 @@ } }, "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -23219,17 +22894,36 @@ "node": ">=0.10.0" } }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", - "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "node_modules/array.prototype.filter": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz", + "integrity": "sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.2.1" + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz", + "integrity": "sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -23275,30 +22969,31 @@ } }, "node_modules/array.prototype.tosorted": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz", - "integrity": "sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz", + "integrity": "sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.2.1" + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.1.0", + "es-shim-unscopables": "^1.0.2" } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", - "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", "is-shared-array-buffer": "^1.0.2" }, "engines": { @@ -23543,9 +23238,9 @@ } }, "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.6.tgz", + "integrity": "sha512-j1QzY8iPNPG4o4xmO3ptzpRxTciqD3MgEHtifP/YnJpIo58Xu+ne4BejlbkuaLfXn/nz6HFiw29bLpj2PNMdGg==", "engines": { "node": ">= 0.4" }, @@ -23554,9 +23249,9 @@ } }, "node_modules/aws-cdk": { - "version": "2.124.0", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.124.0.tgz", - "integrity": "sha512-kUOfqwIAaTEx4ZozojZEhWa8G+O9KU+P0tERtDVmTw9ip4QXNMwTTkjj/IPtoH8qfXGdeibTQ9MJwRvHOR8kXQ==", + "version": "2.128.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.128.0.tgz", + "integrity": "sha512-epOAr/0WKqmyaKqBc7N0Ky5++93pu+v6yVN9jNOa4JYkAkGbeTS3vR9bj/W0o94jnlgWevG3HNHr83jtRvw/4A==", "bin": { "cdk": "bin/cdk" }, @@ -23568,9 +23263,9 @@ } }, "node_modules/aws-cdk-lib": { - "version": "2.124.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.124.0.tgz", - "integrity": "sha512-K/Tey8TMw30GO6UD0qb19CPhBMZhleGshz520ZnbDUJwNfFtejwZOnpmRMOdUP9f4tHc5BrXl1VGsZtXtUaGhg==", + "version": "2.128.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.128.0.tgz", + "integrity": "sha512-cAU1L4jtPXPQXpa9kS2/HhHdkg3xGc5GCqwRgivdoj/iLQF3dDwIouOkwDBY/S5pXMqOUC7IoVdIPPbIgfGlsQ==", "bundleDependencies": [ "@balena/dockerignore", "case", @@ -23590,7 +23285,7 @@ "@balena/dockerignore": "^1.0.2", "case": "1.6.3", "fs-extra": "^11.2.0", - "ignore": "^5.3.0", + "ignore": "^5.3.1", "jsonschema": "^1.4.1", "minimatch": "^3.1.2", "punycode": "^2.3.1", @@ -23727,7 +23422,7 @@ "license": "ISC" }, "node_modules/aws-cdk-lib/node_modules/ignore": { - "version": "5.3.0", + "version": "5.3.1", "inBundle": true, "license": "MIT", "engines": { @@ -24908,12 +24603,12 @@ "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==" }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.5", + "content-type": "~1.0.4", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -24921,7 +24616,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.2", + "raw-body": "2.5.1", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -25187,9 +24882,9 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.22.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", - "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "funding": [ { "type": "opencollective", @@ -25205,8 +24900,8 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001580", - "electron-to-chromium": "^1.4.648", + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, @@ -25374,9 +25069,9 @@ } }, "node_modules/builtins/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -25395,9 +25090,9 @@ "dev": true }, "node_modules/bullmq": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.1.5.tgz", - "integrity": "sha512-Rc9QGHrj/wJ8RMENKa839o1pJmdicg7KBTfmVU8YqYuEK2JcMSJaKMg2XrAi7sdYSawgOJgC/kiW9fCGYEj6Yg==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.2.1.tgz", + "integrity": "sha512-uImDp9k4hiitA1Ve5W/7AlOAaHMXwLtM2gPay7X7O6Do0LlXHW5fD4M0SthBtZLhECBQtZCCvzJcd2COoCi40g==", "dependencies": { "cron-parser": "^4.6.0", "glob": "^8.0.3", @@ -25451,9 +25146,9 @@ } }, "node_modules/bullmq/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -25464,18 +25159,6 @@ "node": ">=10" } }, - "node_modules/bullmq/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/bullmq/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -25636,13 +25319,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -25728,9 +25416,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001580", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001580.tgz", - "integrity": "sha512-mtj5ur2FFPZcCEpXFy8ADXbDACuNFXg6mxVDqp7tqooX6l3zwm+d8EPoeOSIFRDvHs8qu7/SLFOGniULkcH2iA==", + "version": "1.0.30001588", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001588.tgz", + "integrity": "sha512-+hVY9jE44uKLkH0SrUTqxjxqNTOWHsbnQDIKjwkZ3lNTzUUVdBLBGXtj/q5Mp5u98r3droaZAewQuEDzjQdZlQ==", "funding": [ { "type": "opencollective", @@ -25757,11 +25445,11 @@ } }, "node_modules/cdk": { - "version": "2.124.0", - "resolved": "https://registry.npmjs.org/cdk/-/cdk-2.124.0.tgz", - "integrity": "sha512-sX/6smDi41L+3Fl+KKhwfCDc5sso8OkctllOvMbmGRQ3uKgmSRTpiPIfV+BYGBHUPJ4u2mnSqacIq4sIccR+Mw==", + "version": "2.128.0", + "resolved": "https://registry.npmjs.org/cdk/-/cdk-2.128.0.tgz", + "integrity": "sha512-h0OOqV4GICpH9senjuKCbeYsEifa0hOyb3bLEpXGTdj73UWDHZo+YEvr9H+mt8mBrKxbrF/dS42Ook9/PZNQlA==", "dependencies": { - "aws-cdk": "2.124.0" + "aws-cdk": "2.128.0" }, "bin": { "cdk": "bin/cdk" @@ -25771,18 +25459,18 @@ } }, "node_modules/cdk-nag": { - "version": "2.28.22", - "resolved": "https://registry.npmjs.org/cdk-nag/-/cdk-nag-2.28.22.tgz", - "integrity": "sha512-xMdWVccpA0S5JAJ/I1KQg0Hrp7FxNxWP8DHj6ucVYpKy7e1R0XmZLBXO0bFGEgvZpFwOLO5GST741BauE4jaNg==", + "version": "2.28.39", + "resolved": "https://registry.npmjs.org/cdk-nag/-/cdk-nag-2.28.39.tgz", + "integrity": "sha512-eC+QDhuAYgvGTtOsTNKAmVvW/d1VA6I3cfOLmdTFlclLRJLscjvE2GhfBJqB5ogxvkLwQWb9U8PZtp5/zo/xHA==", "peerDependencies": { "aws-cdk-lib": "^2.116.0", "constructs": "^10.0.5" } }, "node_modules/cdk-serverless-clamscan": { - "version": "2.6.84", - "resolved": "https://registry.npmjs.org/cdk-serverless-clamscan/-/cdk-serverless-clamscan-2.6.84.tgz", - "integrity": "sha512-UMlmPo+nGVc1rC4Mx4Y8f3K6GfN3bsgI2qNulpUbhspPaFd6NMTqOUrom6ydfXcY8S19RfLCzjBD9OsU79e2Lw==", + "version": "2.6.100", + "resolved": "https://registry.npmjs.org/cdk-serverless-clamscan/-/cdk-serverless-clamscan-2.6.100.tgz", + "integrity": "sha512-ylJoUGxwz25sCTYhOhlnh3ktVS1pavW/9R9QwcpIjiV5nOGKxy+SOZQyfM1+WHyPsceJ5ysMk+nOW98+V6CnLg==", "bin": { "0": "assets" }, @@ -25962,15 +25650,9 @@ } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -25983,6 +25665,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -26123,9 +25808,9 @@ } }, "node_modules/citty": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.5.tgz", - "integrity": "sha512-AS7n5NSc0OQVMV9v6wt3ByujNIrne0/cTjiC2MYqhvao57VNfiuVksTSr2p17nVOhEr2KtqiAkGwHcgMC/qUuQ==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", "dev": true, "dependencies": { "consola": "^3.2.3" @@ -26796,6 +26481,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -27015,25 +26705,6 @@ "node": ">= 0.6" } }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", @@ -27048,9 +26719,9 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", "engines": { "node": ">= 0.6" } @@ -27067,6 +26738,14 @@ "node": ">= 0.8.0" } }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -27167,9 +26846,9 @@ } }, "node_modules/core-js": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.35.1.tgz", - "integrity": "sha512-IgdsbxNyMskrTFxa9lWHyMwAJU5gXOPP+1yO+K59d50VLVAIDAbs7gIv705KzALModfK3ZrSZTPNpC0PQgIZuw==", + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.0.tgz", + "integrity": "sha512-mt7+TUBbTFg5+GngsAxeKBTl5/VS0guFeJacYge9OmHb+m058UwwIm41SE9T4Den7ClatV57B6TYTuJ0CX1MAw==", "dev": true, "hasInstallScript": true, "funding": { @@ -27178,11 +26857,11 @@ } }, "node_modules/core-js-compat": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.1.tgz", - "integrity": "sha512-sftHa5qUJY3rs9Zht1WEnmkvXputCyDBczPnr7QDgL8n3qrF3CMXY4VPSYtOLLiOUJcah2WNXREd48iOl6mQIw==", + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.0.tgz", + "integrity": "sha512-iV9Pd/PsgjNWBXeq8XRtWVSgz2tKAfhfvBs7qxYty+RlRd+OCksaWmOnc4JKrTc1cToXL1N0s3l/vwlxPtdElw==", "dependencies": { - "browserslist": "^4.22.2" + "browserslist": "^4.22.3" }, "funding": { "type": "opencollective", @@ -27190,9 +26869,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.35.1.tgz", - "integrity": "sha512-zcIdi/CL3MWbBJYo5YCeVAAx+Sy9yJE9I3/u9LkFABwbeaPhTMRWraM8mYFp9jW5Z50hOy7FVzCc8dCrpZqtIQ==", + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.36.0.tgz", + "integrity": "sha512-cN28qmhRNgbMZZMc/RFu5w8pK9VJzpb2rJVR/lHuZJKwmXnoWOpXmMkxqBB514igkp1Hu8WGROsiOAzUcKdHOQ==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -27458,9 +27137,9 @@ } }, "node_modules/css-loader": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.9.1.tgz", - "integrity": "sha512-OzABOh0+26JKFdMzlK6PY1u5Zx8+Ck7CVRlcGNZoY9qwJjdfu2VWFuprTIpPW+Av5TZTVViYWcFQaEEQURLknQ==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.10.0.tgz", + "integrity": "sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==", "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.33", @@ -27479,7 +27158,16 @@ "url": "https://opencollective.com/webpack" }, "peerDependencies": { + "@rspack/core": "0.x || 1.x", "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/css-loader/node_modules/lru-cache": { @@ -27494,9 +27182,9 @@ } }, "node_modules/css-loader/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -27808,33 +27496,6 @@ "cytoscape": "^3.2.0" } }, - "node_modules/cytoscape-fcose": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", - "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", - "dev": true, - "dependencies": { - "cose-base": "^2.2.0" - }, - "peerDependencies": { - "cytoscape": "^3.2.0" - } - }, - "node_modules/cytoscape-fcose/node_modules/cose-base": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", - "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", - "dev": true, - "dependencies": { - "layout-base": "^2.0.0" - } - }, - "node_modules/cytoscape-fcose/node_modules/layout-base": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", - "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", - "dev": true - }, "node_modules/d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -28445,9 +28106,9 @@ } }, "node_modules/dcmjs-dimse": { - "version": "0.1.24", - "resolved": "https://registry.npmjs.org/dcmjs-dimse/-/dcmjs-dimse-0.1.24.tgz", - "integrity": "sha512-j+EG7GbOfHAkeSDmCrECo5iFctdjnwrMKeeLe9SKre+EX1McNe62XCO15EOzhB5uEiunPTKGE2yVa0auJZi6HQ==", + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/dcmjs-dimse/-/dcmjs-dimse-0.1.25.tgz", + "integrity": "sha512-QvSXJPzweSqc7x5SlLeEf+DQW7K6D5C+y2F7Ic2CI3TjBIu3PWvK6WAWCQVnQoFhHfcowOuxocff2Np9jGajlg==", "dependencies": { "async-eventemitter": "^0.2.4", "dcmjs": "^0.29.10", @@ -28691,16 +28352,19 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-lazy-prop": { @@ -29024,9 +28688,9 @@ "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==" }, "node_modules/diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "engines": { "node": ">=0.3.1" } @@ -29035,7 +28699,6 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -29211,14 +28874,14 @@ } }, "node_modules/dotenv": { - "version": "16.4.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.1.tgz", - "integrity": "sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ==", + "version": "16.4.4", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.4.tgz", + "integrity": "sha512-XvPXc8XAQThSjAbY6cQ/9PcBXmFoWuw1sQ3b8HqUCR6ziGXjkTi//kB9SWa2UwqlgdAIuRqAa/9hVljzPehbYg==", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" + "url": "https://dotenvx.com" } }, "node_modules/dotenv-expand": { @@ -29300,9 +28963,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.648", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.648.tgz", - "integrity": "sha512-EmFMarXeqJp9cUKu/QEciEApn0S/xRcpZWuAm32U7NgoZCimjsilKXHRO9saeEW55eHZagIDg6XTUOv32w9pjg==" + "version": "1.4.673", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.673.tgz", + "integrity": "sha512-zjqzx4N7xGdl5468G+vcgzDhaHkaYgVcf9MqgexcTqsl2UHSCmOj/Bi3HAprg4BZCpC7HyD8a6nZl6QAZf72gw==" }, "node_modules/elkjs": { "version": "0.9.1", @@ -29442,9 +29105,9 @@ } }, "node_modules/envinfo": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", - "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==", + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.1.tgz", + "integrity": "sha512-8PiZgZNIB4q/Lw4AhOvAfB/ityHAd2bli3lESSWmWSzSsl5dKpy5N1d1Rfkd2teq/g9xN90lc6o98DOjMeYHpg==", "bin": { "envinfo": "dist/cli.js" }, @@ -29491,50 +29154,52 @@ } }, "node_modules/es-abstract": { - "version": "1.22.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", - "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.2", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.5", - "es-set-tostringtag": "^2.0.1", + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.4.tgz", + "integrity": "sha512-vZYJlk2u6qHYxBOTjAeg7qUxHdNfih64Uu2J8QqWgXZ2cri0ZpJAkzDUK/q593+mvKwlxyaxr6F1Q+3LKoQRgg==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.7", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.2", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.2", - "get-symbol-description": "^1.0.0", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0", + "has-property-descriptors": "^1.0.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "hasown": "^2.0.0", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", + "hasown": "^2.0.1", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-typed-array": "^1.1.12", + "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "safe-array-concat": "^1.0.1", - "safe-regex-test": "^1.0.0", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.0", + "safe-regex-test": "^1.0.3", "string.prototype.trim": "^1.2.8", "string.prototype.trimend": "^1.0.7", "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.0", + "typed-array-buffer": "^1.0.1", "typed-array-byte-length": "^1.0.0", "typed-array-byte-offset": "^1.0.0", "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.13" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -29543,6 +29208,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-get-iterator": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", @@ -29564,25 +29254,29 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", - "integrity": "sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==", + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.17.tgz", + "integrity": "sha512-lh7BsUqelv4KUbR5a/ZTaGGIMLCjPGPqJ6q+Oq24YP0RdyptX1uzm4vvaqzk7Zx3bpl/76YLTTDj9L7uYQ92oQ==", "dev": true, "dependencies": { "asynciterator.prototype": "^1.0.0", - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.22.1", - "es-set-tostringtag": "^2.0.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.2.1", + "es-abstract": "^1.22.4", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.2", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", "globalthis": "^1.0.3", - "has-property-descriptors": "^1.0.0", + "has-property-descriptors": "^1.0.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", + "internal-slot": "^1.0.7", "iterator.prototype": "^1.1.2", - "safe-array-concat": "^1.0.1" + "safe-array-concat": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/es-module-lexer": { @@ -29632,17 +29326,13 @@ } }, "node_modules/es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", - "hasInstallScript": true, + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" } }, "node_modules/es6-error": { @@ -29705,9 +29395,9 @@ } }, "node_modules/esbuild": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.0.tgz", - "integrity": "sha512-6iwE3Y2RVYCME1jLpBqq7LQWK3MW6vjV2bZy6gt/WrqkY+WE74Spyc0ThAOYpMtITvnjX09CrC6ym7A/m9mebA==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.1.tgz", + "integrity": "sha512-OJwEgrpWm/PCMsLVWXKqvcjme3bHNpOgN7Tb6cQnR5n0TPbQx1/Xrn7rqM+wn17bYeT6MGB5sn1Bh5YiGi70nA==", "dev": true, "hasInstallScript": true, "bin": { @@ -29717,35 +29407,35 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.0", - "@esbuild/android-arm": "0.20.0", - "@esbuild/android-arm64": "0.20.0", - "@esbuild/android-x64": "0.20.0", - "@esbuild/darwin-arm64": "0.20.0", - "@esbuild/darwin-x64": "0.20.0", - "@esbuild/freebsd-arm64": "0.20.0", - "@esbuild/freebsd-x64": "0.20.0", - "@esbuild/linux-arm": "0.20.0", - "@esbuild/linux-arm64": "0.20.0", - "@esbuild/linux-ia32": "0.20.0", - "@esbuild/linux-loong64": "0.20.0", - "@esbuild/linux-mips64el": "0.20.0", - "@esbuild/linux-ppc64": "0.20.0", - "@esbuild/linux-riscv64": "0.20.0", - "@esbuild/linux-s390x": "0.20.0", - "@esbuild/linux-x64": "0.20.0", - "@esbuild/netbsd-x64": "0.20.0", - "@esbuild/openbsd-x64": "0.20.0", - "@esbuild/sunos-x64": "0.20.0", - "@esbuild/win32-arm64": "0.20.0", - "@esbuild/win32-ia32": "0.20.0", - "@esbuild/win32-x64": "0.20.0" + "@esbuild/aix-ppc64": "0.20.1", + "@esbuild/android-arm": "0.20.1", + "@esbuild/android-arm64": "0.20.1", + "@esbuild/android-x64": "0.20.1", + "@esbuild/darwin-arm64": "0.20.1", + "@esbuild/darwin-x64": "0.20.1", + "@esbuild/freebsd-arm64": "0.20.1", + "@esbuild/freebsd-x64": "0.20.1", + "@esbuild/linux-arm": "0.20.1", + "@esbuild/linux-arm64": "0.20.1", + "@esbuild/linux-ia32": "0.20.1", + "@esbuild/linux-loong64": "0.20.1", + "@esbuild/linux-mips64el": "0.20.1", + "@esbuild/linux-ppc64": "0.20.1", + "@esbuild/linux-riscv64": "0.20.1", + "@esbuild/linux-s390x": "0.20.1", + "@esbuild/linux-x64": "0.20.1", + "@esbuild/netbsd-x64": "0.20.1", + "@esbuild/openbsd-x64": "0.20.1", + "@esbuild/sunos-x64": "0.20.1", + "@esbuild/win32-arm64": "0.20.1", + "@esbuild/win32-ia32": "0.20.1", + "@esbuild/win32-x64": "0.20.1" } }, "node_modules/esbuild-node-externals": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/esbuild-node-externals/-/esbuild-node-externals-1.12.0.tgz", - "integrity": "sha512-0rQM4N9QZwnLetzkUCOHj7Dj+YkD2IlHJIO/+3bb/AOAyDPG4D4tSTdli4QjrXRfPIffS5zAUrhxSbeyqmXhAg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/esbuild-node-externals/-/esbuild-node-externals-1.13.0.tgz", + "integrity": "sha512-EAd32LMfUajIbLZphERyDVltTn/jir55B40xND5ro6VpCiv5/pum+s51cQf3LBFSVgEFznVJYMJtfVCJiSb32w==", "dev": true, "dependencies": { "find-up": "^5.0.0", @@ -29755,7 +29445,7 @@ "node": ">=12" }, "peerDependencies": { - "esbuild": "0.12 - 0.19" + "esbuild": "0.12 - 0.20" } }, "node_modules/esbuild-plugin-alias": { @@ -29777,9 +29467,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "engines": { "node": ">=6" } @@ -29913,6 +29603,142 @@ } } }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-next/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-config-next/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-config-next/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", @@ -30059,19 +29885,19 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "48.0.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.0.4.tgz", - "integrity": "sha512-A0cH+5svWPXzGZszBjXA1t0aAqVGS+/x3i02KFmb73rU0iMLnadEcVWcD/dGBZHIfAMKr3YpWh58f6wn4N909w==", + "version": "48.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.1.0.tgz", + "integrity": "sha512-g9S8ukmTd1DVcV/xeBYPPXOZ6rc8WJ4yi0+MVxJ1jBOrz5kmxV9gJJQ64ltCqIWFnBChLIhLVx3tbTSarqVyFA==", "dev": true, "dependencies": { - "@es-joy/jsdoccomment": "~0.41.0", + "@es-joy/jsdoccomment": "~0.42.0", "are-docs-informative": "^0.0.2", "comment-parser": "1.4.1", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "esquery": "^1.5.0", "is-builtin-module": "^3.2.1", - "semver": "^7.5.4", + "semver": "^7.6.0", "spdx-expression-parse": "^4.0.0" }, "engines": { @@ -30094,9 +29920,9 @@ } }, "node_modules/eslint-plugin-jsdoc/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -30234,9 +30060,9 @@ } }, "node_modules/eslint-plugin-json-files/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -30996,23 +30822,23 @@ } }, "node_modules/expo": { - "version": "50.0.4", - "resolved": "https://registry.npmjs.org/expo/-/expo-50.0.4.tgz", - "integrity": "sha512-8QWBvYZyKFd7pHxbtri8/ZITBR19QbrW2IkezAhs3ZOHR2kluSgNfyo9ojAe7GnOnE8hCB6Xe83Dbm0R3Ealhw==", + "version": "50.0.7", + "resolved": "https://registry.npmjs.org/expo/-/expo-50.0.7.tgz", + "integrity": "sha512-lTqIrKOUTKHLdTuAaJzZihi1v7F8Ix1dOXVWMpToDy9zPC/s+fet0fbyXdFUxYsCUyuEDIB9tvejrTYZk8Hm0Q==", "dependencies": { "@babel/runtime": "^7.20.0", - "@expo/cli": "0.17.3", + "@expo/cli": "0.17.5", "@expo/config": "8.5.4", "@expo/config-plugins": "7.8.4", - "@expo/metro-config": "0.17.3", + "@expo/metro-config": "0.17.4", "@expo/vector-icons": "^14.0.0", "babel-preset-expo": "~10.0.1", "expo-asset": "~9.0.2", - "expo-file-system": "~16.0.5", - "expo-font": "~11.10.2", + "expo-file-system": "~16.0.6", + "expo-font": "~11.10.3", "expo-keep-awake": "~12.8.2", - "expo-modules-autolinking": "1.10.2", - "expo-modules-core": "1.11.8", + "expo-modules-autolinking": "1.10.3", + "expo-modules-core": "1.11.9", "fbemitter": "^3.0.0", "whatwg-url-without-unicode": "8.0.0-3" }, @@ -31045,9 +30871,9 @@ } }, "node_modules/expo-crypto": { - "version": "12.8.0", - "resolved": "https://registry.npmjs.org/expo-crypto/-/expo-crypto-12.8.0.tgz", - "integrity": "sha512-67CoxXz+b4VU1zMo/2kUp+9t6TiVs8HvCvHsW8zoLLAZkVNa3YW1l0arLtQ4oR4HQpEr1i9rAZhP0/mvo+fg5A==", + "version": "12.8.1", + "resolved": "https://registry.npmjs.org/expo-crypto/-/expo-crypto-12.8.1.tgz", + "integrity": "sha512-EJEzmfBUSkGfALTlZRKUbh1RMKF7mWI12vkhO2w6bhGO4bjgGB8XzUHgLfrvSjphDFMx/lwaR6bAQDmXKO9UkQ==", "peer": true, "dependencies": { "base64-js": "^1.3.0" @@ -31057,17 +30883,17 @@ } }, "node_modules/expo-file-system": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-16.0.5.tgz", - "integrity": "sha512-JpKMbKfwTaMCbwUwq7MwcSbPR7r+IqZEL3RFam3ClPHDtKLnlEoywREeaDsWjSZb7dS25hG3WqXspfTuugCDvg==", + "version": "16.0.6", + "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-16.0.6.tgz", + "integrity": "sha512-ATCHL7nEg2WwKeamW/SDTR9jBEqM5wncFq594ftKS5QFmhKIrX48d9jyPFGnNq+6h8AGPg4QKh2KCA4OY49L4g==", "peerDependencies": { "expo": "*" } }, "node_modules/expo-font": { - "version": "11.10.2", - "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-11.10.2.tgz", - "integrity": "sha512-AE0Q0LiWiVosQ/jlKUPoWoob7p3GwYM2xmLoUkuopO9RYh9NL1hZKHiMKcWBZyDG8Gww1GtBQwh7ZREST8+jjQ==", + "version": "11.10.3", + "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-11.10.3.tgz", + "integrity": "sha512-q1Td2zUvmLbCA9GV4OG4nLPw5gJuNY1VrPycsnemN1m8XWTzzs8nyECQQqrcBhgulCgcKZZJJ6U0kC2iuSoQHQ==", "dependencies": { "fontfaceobserver": "^2.1.0" }, @@ -31084,9 +30910,9 @@ } }, "node_modules/expo-modules-autolinking": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-1.10.2.tgz", - "integrity": "sha512-OEeoz0+zGx5EJwGtDm9pSywCr+gUCaisZV0mNkK7V3fuRl+EVPBSsI+957JwAc4ZxVps95jy28eLcRRtQ33yVg==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-1.10.3.tgz", + "integrity": "sha512-pn4n2Dl4iRh/zUeiChjRIe1C7EqOw1qhccr85viQV7W6l5vgRpY0osE51ij5LKg/kJmGRcJfs12+PwbdTplbKw==", "dependencies": { "@expo/config": "~8.5.0", "chalk": "^4.1.0", @@ -31189,9 +31015,9 @@ } }, "node_modules/expo-modules-core": { - "version": "1.11.8", - "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-1.11.8.tgz", - "integrity": "sha512-rlctE3nCNLCGv3LosGQNaTuwGrr2SyQA+hOgci/0l+VRc0gFNtvl0gskph9C0tnN1jzBeb8rRZQYVj5ih1yxcA==", + "version": "1.11.9", + "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-1.11.9.tgz", + "integrity": "sha512-GTUb81vcPaF+5MtlBI1u9IjrZbGdF1ZUwz3u8Gc+rOLBblkZ7pYsj2mU/tu+k0khTckI9vcH4ZBksXWvE1ncjQ==", "dependencies": { "invariant": "^2.2.4" } @@ -31496,37 +31322,6 @@ "node": ">= 8.0.0" } }, - "node_modules/express/node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/express/node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -31535,17 +31330,6 @@ "ms": "2.0.0" } }, - "node_modules/express/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -31565,39 +31349,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/express/node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/ext": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", @@ -31760,9 +31511,9 @@ } }, "node_modules/fastq": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.0.tgz", - "integrity": "sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dependencies": { "reusify": "^1.0.4" } @@ -31859,15 +31610,15 @@ "dev": true }, "node_modules/fflate": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.1.tgz", - "integrity": "sha512-/exOvEuc+/iaUm105QIiOt4LpBdMTWsXxqR0HDF35vx3fmaKzw7354gTilCh5rkzEt8WYyG//ku3h3nRmd7CHQ==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", "dev": true }, "node_modules/fhirpath": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/fhirpath/-/fhirpath-3.10.0.tgz", - "integrity": "sha512-/QMLMuzljlhsx6XatrnOCild5ZNAu5/y0Kr8NeibUSMprHIY4pVZpN11Ah4mNLEOs2UuGKLQxiXX5NUJ1EUn7w==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/fhirpath/-/fhirpath-3.10.1.tgz", + "integrity": "sha512-VaiivIB77CF1ECm/jQ2Q/YSqDY0jSXDZYP/kG//p5utLbyoEFMBKfG5e7oZ2qBMvy39S2bx7AXn8RL9PFLQ95A==", "dev": true, "dependencies": { "@lhncbc/ucum-lhc": "^5.0.0", @@ -32284,9 +32035,9 @@ "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==" }, "node_modules/flow-parser": { - "version": "0.227.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.227.0.tgz", - "integrity": "sha512-nOygtGKcX/siZK/lFzpfdHEfOkfGcTW7rNroR1Zsz6T/JxSahPALXVt5qVHq/fgvMJuv096BTKbgxN3PzVBaDA==", + "version": "0.229.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.229.0.tgz", + "integrity": "sha512-mOYmMuvJwAo/CvnMFEq4SHftq7E5188hYMTTxJyQOXk2nh+sgslRdYMw3wTthH+FMcFaZLtmBPuMu6IwztdoUQ==", "dev": true, "engines": { "node": ">=0.4.0" @@ -32575,9 +32326,9 @@ } }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -33039,9 +32790,9 @@ } }, "node_modules/gaxios": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.1.1.tgz", - "integrity": "sha512-bw8smrX+XlAoo9o1JAksBwX+hi/RG15J+NTSxmNPIclKC3ZVK6C2afwY8OSdRvOK0+ZLecUJYtj2MmjOt3Dm0w==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.2.0.tgz", + "integrity": "sha512-H6+bHeoEAU5D6XNc6mPKeN5dLZqEDs9Gpk6I+SZBEzK5So58JVrHPmevNi35fRl1J9Y5TaeLW0kYx3pCJ1U2mQ==", "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", @@ -33064,9 +32815,9 @@ } }, "node_modules/gaxios/node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dependencies": { "agent-base": "^7.0.2", "debug": "4" @@ -33118,15 +32869,19 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -33200,13 +32955,14 @@ } }, "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { "node": ">= 0.4" @@ -33499,12 +33255,12 @@ "dev": true }, "node_modules/graphiql": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/graphiql/-/graphiql-3.1.0.tgz", - "integrity": "sha512-1l2PecYNvFYYNSYq+4vIJOACXkP60Kod0E0SnKu+2f0Ux/npFNr3TfwJLZs7eKqqSh0KODmorvHi/XBP46Ua7A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/graphiql/-/graphiql-3.1.1.tgz", + "integrity": "sha512-FMNa981Wj8JBJJRTdryNyrVteigS8B7q+Q1fh1rW4IsFPaXNIs1VMs8kwqIZ8zERj4Fc64Ea750g3n6r2w9Zcg==", "dev": true, "dependencies": { - "@graphiql/react": "^0.20.2", + "@graphiql/react": "^0.20.3", "@graphiql/toolkit": "^0.9.1", "graphql-language-service": "^5.2.0", "markdown-it": "^12.2.0" @@ -33554,9 +33310,9 @@ } }, "node_modules/graphql-ws": { - "version": "5.14.3", - "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.14.3.tgz", - "integrity": "sha512-F/i2xNIVbaEF2xWggID0X/UZQa2V8kqKDPO8hwmu53bVOcTL7uNkxnexeEgSCVxYBQUTUNEI8+e4LO1FOhKPKQ==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.15.0.tgz", + "integrity": "sha512-xWGAtm3fig9TIhSaNsg0FaDZ8Pyn/3re3RFlP4rhQcmjRDIPpk1EhRuNB+YSJtLzttyuToaDiNhwT1OMoGnJnw==", "dev": true, "engines": { "node": ">=10" @@ -33670,11 +33426,11 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -33703,11 +33459,11 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -33760,9 +33516,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", "dependencies": { "function-bind": "^1.1.2" }, @@ -34470,9 +34226,9 @@ ] }, "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "engines": { "node": ">= 4" } @@ -34856,12 +34612,12 @@ } }, "node_modules/internal-slot": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", - "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2", + "es-errors": "^1.3.0", "hasown": "^2.0.0", "side-channel": "^1.0.4" }, @@ -34940,9 +34696,28 @@ "integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==" }, "node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", + "dev": true + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "dev": true }, "node_modules/ip-regex": { @@ -35010,14 +34785,16 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -35619,11 +35396,11 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dependencies": { - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -35865,6 +35642,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/istanbul-lib-processinfo/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -35916,9 +35702,9 @@ } }, "node_modules/istanbul-lib-report/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -36503,7 +36289,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", @@ -36518,7 +36303,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -36533,7 +36317,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -36549,7 +36332,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -36558,7 +36340,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -36690,9 +36471,9 @@ } }, "node_modules/jest-expo": { - "version": "50.0.1", - "resolved": "https://registry.npmjs.org/jest-expo/-/jest-expo-50.0.1.tgz", - "integrity": "sha512-osvA63UDLJ/v7MG9UHjU7WJ0oZ0Krq9UhXxm2s6rdOlnt85ARocyMU57RC0T0yzPN47C9Ref45sEeOIxoV4Mzg==", + "version": "50.0.2", + "resolved": "https://registry.npmjs.org/jest-expo/-/jest-expo-50.0.2.tgz", + "integrity": "sha512-g9Vq4Cpndp6M+bWGNJfyxw+iiZm7o1XpaOEHgtyC1evdy4B9IsEWql1Y2xBH7uD79FwSKhaIz+xCQHZNhnSlAg==", "dev": true, "dependencies": { "@expo/config": "~8.5.0", @@ -37338,9 +37119,9 @@ } }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -37764,7 +37545,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/jest-websocket-mock/-/jest-websocket-mock-2.5.0.tgz", "integrity": "sha512-a+UJGfowNIWvtIKIQBHoEWIUqRxxQHFx4CXT+R5KxxKBtEQ5rS3pPOV/5299sHzqbmeCzxxY5qE4+yfXePePig==", - "dev": true, "dependencies": { "jest-diff": "^29.2.0", "mock-socket": "^9.3.0" @@ -37827,13 +37607,13 @@ "dev": true }, "node_modules/joi": { - "version": "17.12.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.0.tgz", - "integrity": "sha512-HSLsmSmXz+PV9PYoi3p7cgIbj06WnEBNT28n+bbBNcPZXZFqCzzvGqpTBPujx/Z0nh1+KNQPDrNgdmQ8dq0qYw==", + "version": "17.12.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.1.tgz", + "integrity": "sha512-vtxmq+Lsc5SlfqotnfVjlViWfOL9nt/avKNbKYizwf6gsCfq9NYY/ceYRMFD8XDdrjJ9abJyScWmhmIiy+XRtQ==", "dependencies": { "@hapi/hoek": "^9.3.0", "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.4", + "@sideway/address": "^4.1.5", "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" } @@ -37844,9 +37624,9 @@ "integrity": "sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==" }, "node_modules/jose": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.2.0.tgz", - "integrity": "sha512-oW3PCnvyrcm1HMvGTzqjxxfnEs9EoFOFWi2HsEGhlFVOXxTE3K9GKWVMFoFw06yPUqwpvEWic1BmtUZBI/tIjw==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.2.2.tgz", + "integrity": "sha512-/WByRr4jDcsKlvMd1dRJnPfS1GVO3WuKyaurJ/vvXcOaUQO8rnNObCQMlv/5uCceVQIq5Q4WLF44ohsdiTohdg==", "funding": { "url": "https://github.com/sponsors/panva" } @@ -37868,6 +37648,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true + }, "node_modules/jsc-android": { "version": "250231.0.0", "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-250231.0.0.tgz", @@ -38305,9 +38091,9 @@ } }, "node_modules/jsonwebtoken/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -39356,9 +39142,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "version": "0.30.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", + "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -39379,9 +39165,9 @@ } }, "node_modules/mailparser": { - "version": "3.6.6", - "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.6.6.tgz", - "integrity": "sha512-noCjBl3FToxmqTP2fp7z17hQsiCroWNntfTd8O+UejOAF59xeN5WGZK27ilexXV2e2X/cbUhG3L8sfEKaz0/sw==", + "version": "3.6.7", + "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.6.7.tgz", + "integrity": "sha512-/3x8HW70DNehw+3vdOPKdlLuxOHoWcGB5jfx5vJ5XUbY9/2jUJbrrhda5Si8Dj/3w08U0y5uGAkqs5+SPTPKoA==", "dev": true, "dependencies": { "encoding-japanese": "2.0.0", @@ -39391,7 +39177,7 @@ "libmime": "5.2.1", "linkify-it": "5.0.0", "mailsplit": "5.4.0", - "nodemailer": "6.9.8", + "nodemailer": "6.9.9", "tlds": "1.248.0" } }, @@ -39565,9 +39351,9 @@ } }, "node_modules/markdown-to-jsx": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.4.0.tgz", - "integrity": "sha512-zilc+MIkVVXPyTb4iIUTIz9yyqfcWjszGXnwF9K/aiBWcHXFcmdEMTkG01/oQhwSCH7SY1BnG6+ev5BzWmbPrg==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.4.1.tgz", + "integrity": "sha512-GbrbkTnHp9u6+HqbPRFJbObi369AgJNXi/sGqq5HRsoZW063xR1XDCaConqq+whfEIAlzB1YPnOgsPc7B7bc/A==", "dev": true, "engines": { "node": ">= 10" @@ -39823,9 +39609,9 @@ } }, "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -39959,9 +39745,9 @@ } }, "node_modules/mdast-util-mdx-jsx": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.0.0.tgz", - "integrity": "sha512-XZuPPzQNBPAlaqsTTgRrcJnyFbSOBovSadFgbFu8SnuNgm+6Bdx1K+IWoitsmj6Lq6MNtI+ytOqwN70n//NaBA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.0.tgz", + "integrity": "sha512-A8AJHlR7/wPQ3+Jre1+1rq040fX9A4Q1jG8JxmSNp/PLPHg80A6475wxTp3KzHpApFH6yWxFotHrJQA3dXP6/w==", "dev": true, "dependencies": { "@types/estree-jsx": "^1.0.0", @@ -40002,9 +39788,9 @@ } }, "node_modules/mdast-util-phrasing": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.0.0.tgz", - "integrity": "sha512-xadSsJayQIucJ9n053dfQwVu1kuXg7jCTdYsMK8rqzKZh52nLfSH/k0sAxE0u+pj/zKZX+o5wB+ML5mRayOxFA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", "dev": true, "dependencies": { "@types/mdast": "^4.0.0", @@ -40113,6 +39899,10 @@ "resolved": "examples/medplum-nextjs-demo", "link": true }, + "node_modules/medplum-provider": { + "resolved": "examples/medplum-provider", + "link": true + }, "node_modules/medplum-react-native-example": { "resolved": "examples/medplum-react-native-example", "link": true @@ -40199,17 +39989,16 @@ } }, "node_modules/mermaid": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.7.0.tgz", - "integrity": "sha512-PsvGupPCkN1vemAAjScyw4pw34p4/0dZkSrqvAB26hUvJulOWGIwt35FZWmT9wPIi4r0QLa5X0PB4YLIGn0/YQ==", + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.8.0.tgz", + "integrity": "sha512-9CzfSreRjdDJxX796+jW4zjEq0DVw5xVF0nWsqff8OTbrt+ml0TZ5PyYUjjUZJa2NYxYJZZXewEquxGiM8qZEA==", "dev": true, "dependencies": { "@braintree/sanitize-url": "^6.0.1", "@types/d3-scale": "^4.0.3", "@types/d3-scale-chromatic": "^3.0.0", - "cytoscape": "^3.23.0", + "cytoscape": "^3.28.1", "cytoscape-cose-bilkent": "^4.1.0", - "cytoscape-fcose": "^2.1.0", "d3": "^7.4.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.10", @@ -40677,19 +40466,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/mermaid/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/meros": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/meros/-/meros-1.3.0.tgz", @@ -40716,9 +40492,9 @@ } }, "node_modules/metro": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.80.5.tgz", - "integrity": "sha512-OE/CGbOgbi8BlTN1QqJgKOBaC27dS0JBQw473JcivrpgVnqIsluROA7AavEaTVUrB9wPUZvoNVDROn5uiM2jfw==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.80.6.tgz", + "integrity": "sha512-f6Nhnht9TxVRP6zdBq9J2jNdeDBxRmJFnjxhQS1GeCpokBvI6fTXq+wHTLz5jZA+75fwbkPSzBxBJzQa6xi0AQ==", "dependencies": { "@babel/code-frame": "^7.0.0", "@babel/core": "^7.20.0", @@ -40735,24 +40511,24 @@ "denodeify": "^1.2.1", "error-stack-parser": "^2.0.6", "graceful-fs": "^4.2.4", - "hermes-parser": "0.18.2", + "hermes-parser": "0.19.1", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.6.3", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.80.5", - "metro-cache": "0.80.5", - "metro-cache-key": "0.80.5", - "metro-config": "0.80.5", - "metro-core": "0.80.5", - "metro-file-map": "0.80.5", - "metro-resolver": "0.80.5", - "metro-runtime": "0.80.5", - "metro-source-map": "0.80.5", - "metro-symbolicate": "0.80.5", - "metro-transform-plugins": "0.80.5", - "metro-transform-worker": "0.80.5", + "metro-babel-transformer": "0.80.6", + "metro-cache": "0.80.6", + "metro-cache-key": "0.80.6", + "metro-config": "0.80.6", + "metro-core": "0.80.6", + "metro-file-map": "0.80.6", + "metro-resolver": "0.80.6", + "metro-runtime": "0.80.6", + "metro-source-map": "0.80.6", + "metro-symbolicate": "0.80.6", + "metro-transform-plugins": "0.80.6", + "metro-transform-worker": "0.80.6", "mime-types": "^2.1.27", "node-fetch": "^2.2.0", "nullthrows": "^1.1.1", @@ -40772,12 +40548,12 @@ } }, "node_modules/metro-babel-transformer": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.80.5.tgz", - "integrity": "sha512-sxH6hcWCorhTbk4kaShCWsadzu99WBL4Nvq4m/sDTbp32//iGuxtAnUK+ZV+6IEygr2u9Z0/4XoZ8Sbcl71MpA==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.80.6.tgz", + "integrity": "sha512-ssuoVC4OzqaOt3LpwfUbDfBlFGRu9v1Yf2JJnKPz0ROYHNjSBws4aUesqQQ/Ea8DbiH7TK4j4cJmm+XjdHmgqA==", "dependencies": { "@babel/core": "^7.20.0", - "hermes-parser": "0.18.2", + "hermes-parser": "0.19.1", "nullthrows": "^1.1.1" }, "engines": { @@ -40785,24 +40561,24 @@ } }, "node_modules/metro-babel-transformer/node_modules/hermes-estree": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.18.2.tgz", - "integrity": "sha512-KoLsoWXJ5o81nit1wSyEZnWUGy9cBna9iYMZBR7skKh7okYAYKqQ9/OczwpMHn/cH0hKDyblulGsJ7FknlfVxQ==" + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.19.1.tgz", + "integrity": "sha512-daLGV3Q2MKk8w4evNMKwS8zBE/rcpA800nu1Q5kM08IKijoSnPe9Uo1iIxzPKRkn95IxxsgBMPeYHt3VG4ej2g==" }, "node_modules/metro-babel-transformer/node_modules/hermes-parser": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.18.2.tgz", - "integrity": "sha512-1eQfvib+VPpgBZ2zYKQhpuOjw1tH+Emuib6QmjkJWJMhyjM8xnXMvA+76o9LhF0zOAJDZgPfQhg43cyXEyl5Ew==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.19.1.tgz", + "integrity": "sha512-Vp+bXzxYJWrpEuJ/vXxUsLnt0+y4q9zyi4zUlkLqD8FKv4LjIfOvP69R/9Lty3dCyKh0E2BU7Eypqr63/rKT/A==", "dependencies": { - "hermes-estree": "0.18.2" + "hermes-estree": "0.19.1" } }, "node_modules/metro-cache": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.80.5.tgz", - "integrity": "sha512-2u+dQ4PZwmC7eZo9uMBNhQQMig9f+w4QWBZwXCdVy/RYOHM0eObgGdMEOwODo73uxie82T9lWzxr3aZOZ+Nqtw==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.80.6.tgz", + "integrity": "sha512-NP81pHSPkzs+iNlpVkJqijrpcd6lfuDAunYH9/Rn8oLNz0yLfkl8lt+xOdUU4IkFt3oVcTBEFCnzAzv4B8YhyA==", "dependencies": { - "metro-core": "0.80.5", + "metro-core": "0.80.6", "rimraf": "^3.0.2" }, "engines": { @@ -40810,9 +40586,9 @@ } }, "node_modules/metro-cache-key": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.80.5.tgz", - "integrity": "sha512-fr3QLZUarsB3tRbVcmr34kCBsTHk0Sh9JXGvBY/w3b2lbre+Lq5gtgLyFElHPecGF7o4z1eK9r3ubxtScHWcbA==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.80.6.tgz", + "integrity": "sha512-DFmjQacC8m/S3HpELklLMWkPGP/fZPX3BSgjd0xQvwIvWyFwk8Nn/lfp/uWdEVDtDSIr64/anXU5uWohGwlWXw==", "engines": { "node": ">=18" } @@ -40871,17 +40647,17 @@ } }, "node_modules/metro-config": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.80.5.tgz", - "integrity": "sha512-elqo/lwvF+VjZ1OPyvmW/9hSiGlmcqu+rQvDKw5F5WMX48ZC+ySTD1WcaD7e97pkgAlJHVYqZ98FCjRAYOAFRQ==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.80.6.tgz", + "integrity": "sha512-vHYYvJpRTWYbmvqlR7i04xQpZCHJ6yfZ/xIcPdz2ssbdJGGJbiT1Aar9wr8RAhsccSxdJgfE5B1DB8Mo+DnhIg==", "dependencies": { "connect": "^3.6.5", "cosmiconfig": "^5.0.5", "jest-validate": "^29.6.3", - "metro": "0.80.5", - "metro-cache": "0.80.5", - "metro-core": "0.80.5", - "metro-runtime": "0.80.5" + "metro": "0.80.6", + "metro-cache": "0.80.6", + "metro-core": "0.80.6", + "metro-runtime": "0.80.6" }, "engines": { "node": ">=18" @@ -40934,21 +40710,21 @@ } }, "node_modules/metro-core": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.80.5.tgz", - "integrity": "sha512-vkLuaBhnZxTVpaZO8ZJVEHzjaqSXpOdpAiztSZ+NDaYM6jEFgle3/XIbLW91jTSf2+T8Pj5yB1G7KuOX+BcVwg==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.80.6.tgz", + "integrity": "sha512-fn4rryTUAwzFJWj7VIPDH4CcW/q7MV4oGobqR6NsuxZoIGYrVpK7pBasumu5YbCqifuErMs5s23BhmrDNeZURw==", "dependencies": { "lodash.throttle": "^4.1.1", - "metro-resolver": "0.80.5" + "metro-resolver": "0.80.6" }, "engines": { "node": ">=18" } }, "node_modules/metro-file-map": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.80.5.tgz", - "integrity": "sha512-bKCvJ05drjq6QhQxnDUt3I8x7bTcHo3IIKVobEr14BK++nmxFGn/BmFLRzVBlghM6an3gqwpNEYxS5qNc+VKcg==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.80.6.tgz", + "integrity": "sha512-S3CUqvpXpc+q3q+hCEWvFKhVqgq0VmXdZQDF6u7ue86E2elq1XLnfLOt9JSpwyhpMQRyysjSCnd/Yh6GZMNHoQ==", "dependencies": { "anymatch": "^3.0.3", "debug": "^2.2.0", @@ -40982,9 +40758,9 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/metro-minify-terser": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.80.5.tgz", - "integrity": "sha512-S7oZLLcab6YXUT6jYFX/ZDMN7Fq6xBGGAG8liMFU1UljX6cTcEC2u+UIafYgCLrdVexp/+ClxrIetVPZ5LtL/g==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.80.6.tgz", + "integrity": "sha512-83eZaH2+B+jP92KuodPqXknzwmiboKAuZY4doRfTEEXAG57pNVNN6cqSRJlwDnmaTBKRffxoncBXbYqHQgulgg==", "dependencies": { "terser": "^5.15.0" }, @@ -40993,17 +40769,17 @@ } }, "node_modules/metro-resolver": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.80.5.tgz", - "integrity": "sha512-haJ/Hveio3zv/Fr4eXVdKzjUeHHDogYok7OpRqPSXGhTXisNXB+sLN7CpcUrCddFRUDLnVaqQOYwhYsFndgUwA==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.80.6.tgz", + "integrity": "sha512-R7trfglG4zY4X9XyM9cvuffAhQ9W1reWoahr1jdEWa6rOI8PyM0qXjcsb8l+fsOQhdSiVlkKcYAmkyrs1S/zrA==", "engines": { "node": ">=18" } }, "node_modules/metro-runtime": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.80.5.tgz", - "integrity": "sha512-L0syTWJUdWzfUmKgkScr6fSBVTh6QDr8eKEkRtn40OBd8LPagrJGySBboWSgbyn9eIb4ayW3Y347HxgXBSAjmg==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.80.6.tgz", + "integrity": "sha512-21GQVd0pp2nACoK0C2PL8mBsEhIFUFFntYrWRlYNHtPQoqDzddrPEIgkyaABGXGued+dZoBlFQl+LASlmmfkvw==", "dependencies": { "@babel/runtime": "^7.0.0" }, @@ -41012,16 +40788,16 @@ } }, "node_modules/metro-source-map": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.80.5.tgz", - "integrity": "sha512-DwSF4l03mKPNqCtyQ6K23I43qzU1BViAXnuH81eYWdHglP+sDlPpY+/7rUahXEo6qXEHXfAJgVoo1sirbXbmsQ==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.80.6.tgz", + "integrity": "sha512-lqDuSLctWy9Qccu4Zl0YB1PzItpsqcKGb1nK0aDY+lzJ26X65OCib2VzHlj+xj7e4PiIKOfsvDCczCBz4cnxdg==", "dependencies": { "@babel/traverse": "^7.20.0", "@babel/types": "^7.20.0", "invariant": "^2.2.4", - "metro-symbolicate": "0.80.5", + "metro-symbolicate": "0.80.6", "nullthrows": "^1.1.1", - "ob1": "0.80.5", + "ob1": "0.80.6", "source-map": "^0.5.6", "vlq": "^1.0.0" }, @@ -41038,12 +40814,12 @@ } }, "node_modules/metro-symbolicate": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.80.5.tgz", - "integrity": "sha512-IsM4mTYvmo9JvIqwEkCZ5+YeDVPST78Q17ZgljfLdHLSpIivOHp9oVoiwQ/YGbLx0xRHRIS/tKiXueWBnj3UWA==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.80.6.tgz", + "integrity": "sha512-SGwKeBi+lK7NmM5+EcW6DyRRa9HmGSvH0LJtlT4XoRMbpxzsLYs0qUEA+olD96pOIP+ta7I8S30nQr2ttqgO8A==", "dependencies": { "invariant": "^2.2.4", - "metro-source-map": "0.80.5", + "metro-source-map": "0.80.6", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "through2": "^2.0.1", @@ -41065,9 +40841,9 @@ } }, "node_modules/metro-transform-plugins": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.80.5.tgz", - "integrity": "sha512-7IdlTqK/k5+qE3RvIU5QdCJUPk4tHWEqgVuYZu8exeW+s6qOJ66hGIJjXY/P7ccucqF+D4nsbAAW5unkoUdS6g==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.80.6.tgz", + "integrity": "sha512-e04tdTC5Fy1vOQrTTXb5biao0t7nR/h+b1IaBTlM5UaHaAJZr658uVOoZhkRxKjbhF2mIwJ/8DdorD2CA15BCg==", "dependencies": { "@babel/core": "^7.20.0", "@babel/generator": "^7.20.0", @@ -41080,21 +40856,21 @@ } }, "node_modules/metro-transform-worker": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.80.5.tgz", - "integrity": "sha512-Q1oM7hfP+RBgAtzRFBDjPhArELUJF8iRCZ8OidqCpYzQJVGuJZ7InSnIf3hn1JyqiUQwv2f1LXBO78i2rAjzyA==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.80.6.tgz", + "integrity": "sha512-jV+VgCLiCj5jQadW/h09qJaqDreL6XcBRY52STCoz2xWn6WWLLMB5nXzQtvFNPmnIOps+Xu8+d5hiPcBNOhYmA==", "dependencies": { "@babel/core": "^7.20.0", "@babel/generator": "^7.20.0", "@babel/parser": "^7.20.0", "@babel/types": "^7.20.0", - "metro": "0.80.5", - "metro-babel-transformer": "0.80.5", - "metro-cache": "0.80.5", - "metro-cache-key": "0.80.5", - "metro-minify-terser": "0.80.5", - "metro-source-map": "0.80.5", - "metro-transform-plugins": "0.80.5", + "metro": "0.80.6", + "metro-babel-transformer": "0.80.6", + "metro-cache": "0.80.6", + "metro-cache-key": "0.80.6", + "metro-minify-terser": "0.80.6", + "metro-source-map": "0.80.6", + "metro-transform-plugins": "0.80.6", "nullthrows": "^1.1.1" }, "engines": { @@ -41188,16 +40964,16 @@ } }, "node_modules/metro/node_modules/hermes-estree": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.18.2.tgz", - "integrity": "sha512-KoLsoWXJ5o81nit1wSyEZnWUGy9cBna9iYMZBR7skKh7okYAYKqQ9/OczwpMHn/cH0hKDyblulGsJ7FknlfVxQ==" + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.19.1.tgz", + "integrity": "sha512-daLGV3Q2MKk8w4evNMKwS8zBE/rcpA800nu1Q5kM08IKijoSnPe9Uo1iIxzPKRkn95IxxsgBMPeYHt3VG4ej2g==" }, "node_modules/metro/node_modules/hermes-parser": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.18.2.tgz", - "integrity": "sha512-1eQfvib+VPpgBZ2zYKQhpuOjw1tH+Emuib6QmjkJWJMhyjM8xnXMvA+76o9LhF0zOAJDZgPfQhg43cyXEyl5Ew==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.19.1.tgz", + "integrity": "sha512-Vp+bXzxYJWrpEuJ/vXxUsLnt0+y4q9zyi4zUlkLqD8FKv4LjIfOvP69R/9Lty3dCyKh0E2BU7Eypqr63/rKT/A==", "dependencies": { - "hermes-estree": "0.18.2" + "hermes-estree": "0.19.1" } }, "node_modules/metro/node_modules/minimatch": { @@ -41370,9 +41146,9 @@ } }, "node_modules/micromark-core-commonmark/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -41445,9 +41221,9 @@ } }, "node_modules/micromark-extension-directive/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -41497,9 +41273,9 @@ } }, "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -41569,9 +41345,9 @@ } }, "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -41645,9 +41421,9 @@ } }, "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -41752,9 +41528,9 @@ } }, "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -41838,9 +41614,9 @@ } }, "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -41920,9 +41696,9 @@ } }, "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -41998,9 +41774,9 @@ } }, "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -42088,9 +41864,9 @@ } }, "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -42157,9 +41933,9 @@ } }, "node_modules/micromark-factory-destination/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -42215,9 +41991,9 @@ } }, "node_modules/micromark-factory-label/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -42277,9 +42053,9 @@ } }, "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -42391,9 +42167,9 @@ } }, "node_modules/micromark-factory-title/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -42469,9 +42245,9 @@ } }, "node_modules/micromark-factory-whitespace/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -42597,9 +42373,9 @@ } }, "node_modules/micromark-util-classify-character/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -42710,9 +42486,9 @@ } }, "node_modules/micromark-util-decode-string/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -42895,9 +42671,9 @@ } }, "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -43021,9 +42797,9 @@ } }, "node_modules/micromark/node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -43126,11 +42902,12 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.7.7", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.7.tgz", - "integrity": "sha512-+0n11YGyRavUR3IlaOzJ0/4Il1avMvJ1VJfhWfCn24ITQXhRr1gghbhhrda6tgtNcpZaWKdSuwKq20Jb7fnlyw==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.0.tgz", + "integrity": "sha512-CxmUYPFcTgET1zImteG/LZOy/4T5rTojesQXkSNBiquhydn78tfbCE9sjIjnJ/UcjNjOC1bphTCCW5rrS7cXAg==", "dependencies": { - "schema-utils": "^4.0.0" + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" }, "engines": { "node": ">= 12.13.0" @@ -43422,7 +43199,6 @@ "version": "9.3.1", "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz", "integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==", - "dev": true, "engines": { "node": ">= 8" } @@ -43773,9 +43549,9 @@ } }, "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha512-mc/caHeUcdjnC/boPWJefDr4KUIWQNv+tlnFnJd38QMou86QtxQzBJfxgGRzvx8jazYRqrVlaHarfO72uNxPOg==" }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", @@ -43810,9 +43586,9 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "node_modules/nise": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.7.tgz", - "integrity": "sha512-wWtNUhkT7k58uvWTB/Gy26eA/EJKtPZFVAhEilN5UYVmmGRYOURbejRUyKm0Uu9XVEW7K5nBOZfR8VMB4QR2RQ==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", "dependencies": { "@sinonjs/commons": "^3.0.0", "@sinonjs/fake-timers": "^11.2.2", @@ -43876,9 +43652,9 @@ } }, "node_modules/node-abi/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -43973,9 +43749,9 @@ } }, "node_modules/node-fetch-native": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.1.tgz", - "integrity": "sha512-bW9T/uJDPAJB2YNYEpWzE54U5O3MQidXsOyTfnbKYtTtFexRvGzb1waphBN4ZwP6EcIvYYEOwW0b72BpAqydTw==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.2.tgz", + "integrity": "sha512-69mtXOFZ6hSkYiXAVB5SqaRvrbITC/NPyqv7yuu/qw0nmgPyYbIMYYNIDhNtwPrzk0ptrimrLz/hhjvm4w5Z+w==", "dev": true }, "node_modules/node-fetch/node_modules/tr46": { @@ -44361,9 +44137,9 @@ } }, "node_modules/node-gyp/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -44577,9 +44353,9 @@ } }, "node_modules/nodemailer": { - "version": "6.9.8", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.8.tgz", - "integrity": "sha512-cfrYUk16e67Ks051i4CntM9kshRYei1/o/Gi8K1d+R34OIs21xdFnW7Pt7EucmVKA0LKtqUGNcjMZ7ehjl49mQ==", + "version": "6.9.9", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.9.tgz", + "integrity": "sha512-dexTll8zqQoVJEZPwQAKzxxtFn0qTnjdQTchoU6Re9BUUGBJiOy3YMn/0ShTW6J5M0dfQ1NeDeRTTl4oIWgQMA==", "engines": { "node": ">=6.0.0" } @@ -44634,9 +44410,9 @@ } }, "node_modules/normalize-package-data/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -44695,9 +44471,9 @@ } }, "node_modules/npm-check-updates": { - "version": "16.14.14", - "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.14.14.tgz", - "integrity": "sha512-Y3ajS/Ep40jM489rLBdz9jehn/BMil5s9fA4PSr2ZJxxSmtLWCSmRqsI2IEZ9Nb3MTMu8a3s7kBs0l+JbjdkTA==", + "version": "16.14.15", + "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.14.15.tgz", + "integrity": "sha512-WH0wJ9j6CP7Azl+LLCxWAYqroT2IX02kRIzgK/fg0rPpMbETgHITWBdOPtrv521xmA3JMgeNsQ62zvVtS/nCmQ==", "dev": true, "dependencies": { "chalk": "^5.3.0", @@ -44805,9 +44581,9 @@ } }, "node_modules/npm-check-updates/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -44884,9 +44660,9 @@ } }, "node_modules/npm-install-checks/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -44950,9 +44726,9 @@ } }, "node_modules/npm-package-arg/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -45022,9 +44798,9 @@ } }, "node_modules/npm-pick-manifest/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -45580,9 +45356,9 @@ } }, "node_modules/ob1": { - "version": "0.80.5", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.5.tgz", - "integrity": "sha512-zYDMnnNrFi/1Tqh0vo3PE4p97Tpl9/4MP2k2ECvkbLOZzQuAYZJLTUYVLZb7hJhbhjT+JJxAwBGS8iu5hCSd1w==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.6.tgz", + "integrity": "sha512-nlLGZPMQ/kbmkdIb5yvVzep1jKUII2x6ehNsHpgy71jpnJMW7V+KsB3AjYI2Ajb7UqMAMNjlssg6FUodrEMYzg==", "engines": { "node": ">=18" } @@ -45676,15 +45452,16 @@ } }, "node_modules/object.groupby": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", - "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.2.tgz", + "integrity": "sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1" + "array.prototype.filter": "^1.0.3", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0" } }, "node_modules/object.hasown": { @@ -46134,9 +45911,9 @@ } }, "node_modules/package-json/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -46946,9 +46723,9 @@ } }, "node_modules/pkg-fetch/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -47309,9 +47086,9 @@ } }, "node_modules/polished": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/polished/-/polished-4.2.2.tgz", - "integrity": "sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz", + "integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==", "dev": true, "dependencies": { "@babel/runtime": "^7.17.8" @@ -47333,9 +47110,9 @@ } }, "node_modules/postcss": { - "version": "8.4.33", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", - "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", "funding": [ { "type": "opencollective", @@ -47516,9 +47293,9 @@ } }, "node_modules/postcss-loader/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -47881,9 +47658,9 @@ } }, "node_modules/postcss-preset-mantine": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/postcss-preset-mantine/-/postcss-preset-mantine-1.12.3.tgz", - "integrity": "sha512-cCwowf20mIyRXnV1cSVoMGfhYgy8ZqFJWsEJthdMZ3n7LijjucE9l/HO47gv5gAtr9nY1MkaEkpWS7ulhSTbSg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/postcss-preset-mantine/-/postcss-preset-mantine-1.13.0.tgz", + "integrity": "sha512-1bv/mQz2K+/FixIMxYd83BYH7PusDZaI7LpUtKbb1l/5N5w6t1p/V9ONHfRJeeAZyfa6Xc+AtR+95VKdFXRH1g==", "dev": true, "dependencies": { "postcss-mixins": "^9.0.4", @@ -48181,9 +47958,9 @@ } }, "node_modules/postgres-range": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.3.tgz", - "integrity": "sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.4.tgz", + "integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==" }, "node_modules/prebuild-install": { "version": "7.1.1", @@ -48221,9 +47998,9 @@ } }, "node_modules/prettier": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", - "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -48855,9 +48632,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -49296,12 +49073,18 @@ } }, "node_modules/react-intersection-observer": { - "version": "9.5.3", - "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.5.3.tgz", - "integrity": "sha512-NJzagSdUPS5rPhaLsHXYeJbsvdpbJwL6yCHtMk91hc0ufQ2BnXis+0QQ9NBh6n9n+Q3OyjR6OQLShYbaNBkThQ==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.8.0.tgz", + "integrity": "sha512-wXHvMQUsTagh3X0Z6jDtGkIXc3VVCd2tjDRYR9kII3GKrZr0XF0xtpfdamo2n8BSF+zzfeeBVOTjxZWpBp9X0g==", "dev": true, "peerDependencies": { - "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } } }, "node_modules/react-is": { @@ -49352,17 +49135,17 @@ } }, "node_modules/react-native": { - "version": "0.73.2", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.73.2.tgz", - "integrity": "sha512-7zj9tcUYpJUBdOdXY6cM8RcXYWkyql4kMyGZflW99E5EuFPoC7Ti+ZQSl7LP9ZPzGD0vMfslwyDW0I4tPWUCFw==", + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.73.4.tgz", + "integrity": "sha512-VtS+Yr6OOTIuJGDECIYWzNU8QpJjASQYvMtfa/Hvm/2/h5GdB6W9H9TOmh13x07Lj4AOhNMx3XSsz6TdrO4jIg==", "dependencies": { "@jest/create-cache-key-function": "^29.6.3", - "@react-native-community/cli": "12.3.0", - "@react-native-community/cli-platform-android": "12.3.0", - "@react-native-community/cli-platform-ios": "12.3.0", + "@react-native-community/cli": "12.3.2", + "@react-native-community/cli-platform-android": "12.3.2", + "@react-native-community/cli-platform-ios": "12.3.2", "@react-native/assets-registry": "0.73.1", - "@react-native/codegen": "0.73.2", - "@react-native/community-cli-plugin": "0.73.12", + "@react-native/codegen": "0.73.3", + "@react-native/community-cli-plugin": "0.73.16", "@react-native/gradle-plugin": "0.73.4", "@react-native/js-polyfills": "0.73.1", "@react-native/normalize-colors": "0.73.2", @@ -49371,6 +49154,7 @@ "anser": "^1.4.9", "ansi-regex": "^5.0.0", "base64-js": "^1.5.1", + "chalk": "^4.0.0", "deprecated-react-native-prop-types": "^5.0.0", "event-target-shim": "^5.0.1", "flow-enums-runtime": "^0.0.6", @@ -49676,13 +49460,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.21.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.21.3.tgz", - "integrity": "sha512-kNzubk7n4YHSrErzjLK72j0B5i969GsuCGazRl3G6j1zqZBLjuSlYBdVdkDOgzGdPIffUOc9nmgiadTEVoq91g==", + "version": "6.22.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.1.tgz", + "integrity": "sha512-iwMyyyrbL7zkKY7MRjOVRy+TMnS/OPusaFVxM2P11x9dzSzGmLsebkCvYirGq0DWB9K9hOspHYYtDz33gE5Duw==", "dev": true, "dependencies": { - "@remix-run/router": "1.14.2", - "react-router": "6.21.3" + "@remix-run/router": "1.15.1", + "react-router": "6.22.1" }, "engines": { "node": ">=14.0.0" @@ -49693,12 +49477,12 @@ } }, "node_modules/react-router-dom/node_modules/react-router": { - "version": "6.21.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.3.tgz", - "integrity": "sha512-a0H638ZXULv1OdkmiK6s6itNhoy33ywxmUFT/xtSoVyf9VnC7n7+VT4LjVzdIHSaF5TIh9ylUgxMXksHTgGrKg==", + "version": "6.22.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.1.tgz", + "integrity": "sha512-0pdoRGwLtemnJqn1K0XHUbnKiX0S4X8CgvVVmHGOWmofESj31msHo/1YiqcJWK7Wxfq2a4uvvtS01KAQyWK/CQ==", "dev": true, "dependencies": { - "@remix-run/router": "1.14.2" + "@remix-run/router": "1.15.1" }, "engines": { "node": ">=14.0.0" @@ -49890,9 +49674,9 @@ } }, "node_modules/read-package-json/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -50055,6 +49839,11 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -50193,15 +49982,16 @@ } }, "node_modules/reflect.getprototypeof": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", - "integrity": "sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.5.tgz", + "integrity": "sha512-62wgfC8dJWrmxv44CA36pLDnP6KKl3Vhxb7PL+8+qrrFMMoJij4vgiMP8zV4O8+CBMXY1mHxI5fITGHXFHVmQQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0", + "get-intrinsic": "^1.2.3", "globalthis": "^1.0.3", "which-builtin-type": "^1.1.3" }, @@ -50242,13 +50032,14 @@ } }, "node_modules/regexp.prototype.flags": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", - "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -50493,9 +50284,9 @@ } }, "node_modules/remark-mdx": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.0.0.tgz", - "integrity": "sha512-O7yfjuC6ra3NHPbRVxfflafAj3LTwx3b73aBvkEFU5z4PsD6FD4vrqJAkE5iNGLz71GdjXfgRqm3SQ0h0VuE7g==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.0.1.tgz", + "integrity": "sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA==", "dev": true, "dependencies": { "mdast-util-mdx": "^3.0.0", @@ -51077,9 +50868,23 @@ } }, "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/safe-json-stringify": { "version": "1.2.0", @@ -51088,13 +50893,13 @@ "optional": true }, "node_modules/safe-regex-test": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.2.tgz", - "integrity": "sha512-83S9w6eFq12BBIJYvjMux6/dkirb8+4zJRA9cxNBVb7Wq5fJBW+Xze48WqR8pxua7bDuAaaAxtVVd4Idjp1dBQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", "dev": true, "dependencies": { - "call-bind": "^1.0.5", - "get-intrinsic": "^1.2.2", + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", "is-regex": "^1.1.4" }, "engines": { @@ -51269,9 +51074,9 @@ } }, "node_modules/semver-diff/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -51551,13 +51356,14 @@ "dev": true }, "node_modules/set-function-length": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", - "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", "dependencies": { - "define-data-property": "^1.1.1", + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.2", + "get-intrinsic": "^1.2.3", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.1" }, @@ -51732,13 +51538,17 @@ "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", + "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -52040,17 +51850,25 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.3.tgz", + "integrity": "sha512-vfuYK48HXCTFD03G/1/zkIls3Ebr2YNa4qU9gHDZdblHLiqhJrJGkY3+0Nx0JpN9qBhJbVObc1CNciT1bIZJxw==", "dev": true, "dependencies": { - "ip": "^2.0.0", + "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 10.13.0", + "node": ">= 10.0.0", "npm": ">= 3.0.0" } }, @@ -52084,9 +51902,9 @@ "dev": true }, "node_modules/sort-package-json": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-2.6.0.tgz", - "integrity": "sha512-XSQ+lY9bAYA8ZsoChcEoPlgcSMaheziEp1beox1JVxy1SV4F2jSq9+h2rJ+3mC/Dhu9Ius1DLnInD5AWcsDXZw==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-2.7.0.tgz", + "integrity": "sha512-6AayF8bp6L+WROgpbhTMUtB9JSFmpGHjmW7DyaNPS1HwlTw2oSVlUUtlkHSEZmg5o89F3zvLBZNvMeZ1T4fjQg==", "dev": true, "dependencies": { "detect-indent": "^7.0.1", @@ -52456,10 +52274,10 @@ } }, "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead" + "name": "@jridgewell/sourcemap-codec", + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/space-separated-tokens": { "version": "2.0.2", @@ -52597,9 +52415,9 @@ } }, "node_modules/spdx-exceptions": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.4.0.tgz", - "integrity": "sha512-hcjppoJ68fhxA/cjbN4T8N6uCUejN8yFw69ttpqtBeCbF3u13n7mb31NB9jKwGTTWWnt9IbRA/mf1FprYS8wfw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", "dev": true }, "node_modules/spdx-expression-parse": { @@ -52613,9 +52431,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", - "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", "dev": true }, "node_modules/spdy": { @@ -53038,18 +52856,18 @@ } }, "node_modules/store2": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/store2/-/store2-2.14.2.tgz", - "integrity": "sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==", + "version": "2.14.3", + "resolved": "https://registry.npmjs.org/store2/-/store2-2.14.3.tgz", + "integrity": "sha512-4QcZ+yx7nzEFiV4BMLnr/pRa5HYzNITX2ri0Zh6sT9EyQHbBHacC6YigllUPU9X3D0f/22QCgfokpKs52YRrUg==", "dev": true }, "node_modules/storybook": { - "version": "7.6.10", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-7.6.10.tgz", - "integrity": "sha512-ypFeGhQTUBBfqSUVZYh7wS5ghn3O2wILCiQc4459SeUpvUn+skcqw/TlrwGSoF5EWjDA7gtRrWDxO3mnlPt5Cw==", + "version": "7.6.16", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-7.6.16.tgz", + "integrity": "sha512-VSfaYoV/iurMtLE/OcVtKvYe/Skkc+JGQYN0uni2djTlrDbZ1IbG2ig+E2MGOE6KlPmC3wCZhW6CevZyjdFhTQ==", "dev": true, "dependencies": { - "@storybook/cli": "7.6.10" + "@storybook/cli": "7.6.16" }, "bin": { "sb": "index.js", @@ -53145,6 +52963,11 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -53449,33 +53272,27 @@ } }, "node_modules/strip-literal": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", - "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.0.0.tgz", + "integrity": "sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==", "dev": true, "dependencies": { - "acorn": "^8.10.0" + "js-tokens": "^8.0.2" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, - "node_modules/strip-literal/node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.3.tgz", + "integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==", + "dev": true }, "node_modules/stripe": { - "version": "14.14.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-14.14.0.tgz", - "integrity": "sha512-P6lvKHxgDzZXto9VMstG1ucv4Ls0U9nOoQhVZABXJ33kRD7zMwkBy5Y4c3BO59O230uSTkOFPLh87YxVzkp0Mg==", + "version": "14.17.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-14.17.0.tgz", + "integrity": "sha512-iwV5SKoXuRIQFne4twGwiiczOkVW73eE2CKn6ltUKCacDy4SGHBX6kj1/xCV2bzzzQjcVtsh5F1aAbJTmf3tLw==", "dev": true, "dependencies": { "@types/node": ">=8.1.0", @@ -53719,9 +53536,9 @@ } }, "node_modules/superagent/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -54164,9 +53981,9 @@ } }, "node_modules/terser": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz", - "integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==", + "version": "5.27.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.1.tgz", + "integrity": "sha512-29wAr6UU/oQpnTw5HoadwjUZnFQXGdOfj0LjZ4sVxzqwHh/QVkvr7m8y9WoR4iN3FRitVduTc6KdjcW38Npsug==", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -54474,9 +54291,9 @@ } }, "node_modules/tinyspy": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", - "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", "dev": true, "engines": { "node": ">=14.0.0" @@ -54628,9 +54445,9 @@ } }, "node_modules/trough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", - "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", "dev": true, "funding": { "type": "github", @@ -54638,12 +54455,12 @@ } }, "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", + "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==", "dev": true, "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" @@ -54719,9 +54536,9 @@ } }, "node_modules/ts-jest/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -55018,26 +54835,26 @@ } }, "node_modules/turbo": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/turbo/-/turbo-1.11.3.tgz", - "integrity": "sha512-RCJOUFcFMQNIGKSjC9YmA5yVP1qtDiBA0Lv9VIgrXraI5Da1liVvl3VJPsoDNIR9eFMyA/aagx1iyj6UWem5hA==", + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/turbo/-/turbo-1.12.4.tgz", + "integrity": "sha512-yUJ7elEUSToiGwFZogXpYKJpQ0BvaMbkEuQECIWtkBLcmWzlMOt6bActsIm29oN83mRU0WbzGt4e8H1KHWedhg==", "dev": true, "bin": { "turbo": "bin/turbo" }, "optionalDependencies": { - "turbo-darwin-64": "1.11.3", - "turbo-darwin-arm64": "1.11.3", - "turbo-linux-64": "1.11.3", - "turbo-linux-arm64": "1.11.3", - "turbo-windows-64": "1.11.3", - "turbo-windows-arm64": "1.11.3" + "turbo-darwin-64": "1.12.4", + "turbo-darwin-arm64": "1.12.4", + "turbo-linux-64": "1.12.4", + "turbo-linux-arm64": "1.12.4", + "turbo-windows-64": "1.12.4", + "turbo-windows-arm64": "1.12.4" } }, "node_modules/turbo-darwin-64": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-1.11.3.tgz", - "integrity": "sha512-IsOOg2bVbIt3o/X8Ew9fbQp5t1hTHN3fGNQYrPQwMR2W1kIAC6RfbVD4A9OeibPGyEPUpwOH79hZ9ydFH5kifw==", + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-1.12.4.tgz", + "integrity": "sha512-dBwFxhp9isTa9RS/fz2gDVk5wWhKQsPQMozYhjM7TT4jTrnYn0ZJMzr7V3B/M/T8QF65TbniW7w1gtgxQgX5Zg==", "cpu": [ "x64" ], @@ -55048,9 +54865,9 @@ ] }, "node_modules/turbo-darwin-arm64": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-1.11.3.tgz", - "integrity": "sha512-FsJL7k0SaPbJzI/KCnrf/fi3PgCDCjTliMc/kEFkuWVA6Httc3Q4lxyLIIinz69q6JTx8wzh6yznUMzJRI3+dg==", + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-1.12.4.tgz", + "integrity": "sha512-1Uo5iI6xsJ1j9ObsqxYRsa3W26mEbUe6fnj4rQYV6kDaqYD54oAMJ6hM53q9rB8JvFxwdrUXGp3PwTw9A0qqkA==", "cpu": [ "arm64" ], @@ -55061,9 +54878,9 @@ ] }, "node_modules/turbo-linux-64": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-1.11.3.tgz", - "integrity": "sha512-SvW7pvTVRGsqtSkII5w+wriZXvxqkluw5FO/MNAdFw0qmoov+PZ237+37/NgArqE3zVn1GX9P6nUx9VO+xcQAg==", + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-1.12.4.tgz", + "integrity": "sha512-ONg2aSqKP7LAQOg7ysmU5WpEQp4DGNxSlAiR7um+LKtbmC/UxogbR5+T+Uuq6zGuQ5kJyKjWJ4NhtvUswOqBsA==", "cpu": [ "x64" ], @@ -55074,9 +54891,9 @@ ] }, "node_modules/turbo-linux-arm64": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-1.11.3.tgz", - "integrity": "sha512-YhUfBi1deB3m+3M55X458J6B7RsIS7UtM3P1z13cUIhF+pOt65BgnaSnkHLwETidmhRh8Dl3GelaQGrB3RdCDw==", + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-1.12.4.tgz", + "integrity": "sha512-9FPufkwdgfIKg/9jj87Cdtftw8o36y27/S2vLN7FTR2pp9c0MQiTBOLVYadUr1FlShupddmaMbTkXEhyt9SdrA==", "cpu": [ "arm64" ], @@ -55087,9 +54904,9 @@ ] }, "node_modules/turbo-windows-64": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-1.11.3.tgz", - "integrity": "sha512-s+vEnuM2TiZuAUUUpmBHDr6vnNbJgj+5JYfnYmVklYs16kXh+EppafYQOAkcRIMAh7GjV3pLq5/uGqc7seZeHA==", + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-1.12.4.tgz", + "integrity": "sha512-2mOtxHW5Vjh/5rDVu/aFwsMzI+chs8XcEuJHlY1sYOpEymYTz+u6AXbnzRvwZFMrLKr7J7fQOGl+v96sLKbNdA==", "cpu": [ "x64" ], @@ -55100,9 +54917,9 @@ ] }, "node_modules/turbo-windows-arm64": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-1.11.3.tgz", - "integrity": "sha512-ZR5z5Zpc7cASwfdRAV5yNScCZBsgGSbcwiA/u3farCacbPiXsfoWUkz28iyrx21/TRW0bi6dbsB2v17swa8bjw==", + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-1.12.4.tgz", + "integrity": "sha512-nOY5wae9qnxPOpT1fRuYO0ks6dTwpKMPV6++VkDkamFDLFHUDVM/9kmD2UTeh1yyrKnrZksbb9zmShhmfj1wog==", "cpu": [ "arm64" ], @@ -55166,14 +54983,14 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", - "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.1.tgz", + "integrity": "sha512-RSqu1UEuSlrBhHTWC8O9FnPjOduNs4M7rJ4pRKoEjtx1zUNOPN2sSXHLDX+Y2WPbHIxbvg4JFo2DNAEfPIKWoQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -55198,16 +55015,17 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", - "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.1.tgz", + "integrity": "sha512-tcqKMrTRXjqvHN9S3553NPCaGL0VPgFI92lXszmrE8DMhiDPLBYLlvo8Uu4WZAAX/aGqp/T1sbA4ph8EWjDF9Q==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.7", "for-each": "^0.3.3", + "gopd": "^1.0.1", "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -55286,9 +55104,9 @@ "dev": true }, "node_modules/ufo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", - "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.4.0.tgz", + "integrity": "sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==", "dev": true }, "node_modules/uglify-js": { @@ -55564,12 +55382,12 @@ } }, "node_modules/unplugin": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.6.0.tgz", - "integrity": "sha512-BfJEpWBu3aE/AyHx8VaNE/WgouoQxgH9baAiH82JjX8cqVyi3uJQstqwD5J+SZxIK326SZIhsSZlALXVBCknTQ==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.7.1.tgz", + "integrity": "sha512-JqzORDAPxxs8ErLV4x+LL7bk5pk3YlcWqpSNsIkAZj972KzFZLClc/ekppahKkOczGkwIG6ElFgdOgOlK4tXZw==", "dev": true, "dependencies": { - "acorn": "^8.11.2", + "acorn": "^8.11.3", "chokidar": "^3.5.3", "webpack-sources": "^3.2.3", "webpack-virtual-modules": "^0.6.1" @@ -55759,9 +55577,9 @@ } }, "node_modules/update-notifier/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -56021,9 +55839,13 @@ } }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } @@ -56234,9 +56056,9 @@ } }, "node_modules/vite-node": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.2.2.tgz", - "integrity": "sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.3.0.tgz", + "integrity": "sha512-D/oiDVBw75XMnjAXne/4feCkCEwcbr2SU1bjAhCcfI5Bq3VoOHji8/wCPAfUkDIeohJ5nSZ39fNxM3dNZ6OBOA==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -56467,9 +56289,9 @@ } }, "node_modules/vite-node/node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -56482,30 +56304,30 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, "node_modules/vite-node/node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -56560,18 +56382,17 @@ "dev": true }, "node_modules/vitest": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.2.2.tgz", - "integrity": "sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.3.0.tgz", + "integrity": "sha512-V9qb276J1jjSx9xb75T2VoYXdO1UKi+qfflY7V7w93jzX7oA/+RtYE6TcifxksxsZvygSSMwu2Uw6di7yqDMwg==", "dev": true, "dependencies": { - "@vitest/expect": "1.2.2", - "@vitest/runner": "1.2.2", - "@vitest/snapshot": "1.2.2", - "@vitest/spy": "1.2.2", - "@vitest/utils": "1.2.2", + "@vitest/expect": "1.3.0", + "@vitest/runner": "1.3.0", + "@vitest/snapshot": "1.3.0", + "@vitest/spy": "1.3.0", + "@vitest/utils": "1.3.0", "acorn-walk": "^8.3.2", - "cac": "^6.7.14", "chai": "^4.3.10", "debug": "^4.3.4", "execa": "^8.0.1", @@ -56580,11 +56401,11 @@ "pathe": "^1.1.1", "picocolors": "^1.0.0", "std-env": "^3.5.0", - "strip-literal": "^1.3.0", + "strip-literal": "^2.0.0", "tinybench": "^2.5.1", "tinypool": "^0.8.2", "vite": "^5.0.0", - "vite-node": "1.2.2", + "vite-node": "1.3.0", "why-is-node-running": "^2.2.2" }, "bin": { @@ -56599,8 +56420,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "^1.0.0", - "@vitest/ui": "^1.0.0", + "@vitest/browser": "1.3.0", + "@vitest/ui": "1.3.0", "happy-dom": "*", "jsdom": "*" }, @@ -56956,9 +56777,9 @@ } }, "node_modules/vitest/node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -56971,19 +56792,19 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, @@ -57000,13 +56821,13 @@ } }, "node_modules/vitest/node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -57142,9 +56963,9 @@ } }, "node_modules/web-streams-polyfill": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.2.tgz", - "integrity": "sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", "engines": { "node": ">= 8" } @@ -57165,9 +56986,9 @@ } }, "node_modules/webpack": { - "version": "5.90.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.0.tgz", - "integrity": "sha512-bdmyXRCXeeNIePv6R6tGPyy20aUobw4Zy8r0LUS2EWO+U+Ke/gYDgsCh7bl5rB6jPpr4r0SZa6dPxBxLooDT3w==", + "version": "5.90.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.2.tgz", + "integrity": "sha512-ziXu8ABGr0InCMEYFnHrYweinHK2PWrMqnwdHk2oK3rRhv/1B+2FnfwYv5oD+RrknK/Pp/Hmyvu+eAsaMYhzCw==", "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", @@ -57787,15 +57608,15 @@ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" }, "node_modules/which-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", - "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", + "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.4", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.5", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -57913,9 +57734,9 @@ } }, "node_modules/winston-transport": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.6.0.tgz", - "integrity": "sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.0.tgz", + "integrity": "sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==", "dependencies": { "logform": "^2.3.2", "readable-stream": "^3.6.0", @@ -58430,18 +58251,18 @@ }, "packages/agent": { "name": "@medplum/agent", - "version": "3.0.2", + "version": "3.0.4", "license": "Apache-2.0", "dependencies": { - "@medplum/core": "*", - "@medplum/hl7": "*", - "dcmjs-dimse": "0.1.24", + "@medplum/core": "3.0.4", + "@medplum/hl7": "3.0.4", + "dcmjs-dimse": "0.1.25", "node-windows": "1.0.0-beta.8", "ws": "8.16.0" }, "devDependencies": { - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", "@types/async-eventemitter": "0.2.4", "@types/node-windows": "0.1.6", "@types/ws": "8.5.10", @@ -58454,40 +58275,40 @@ }, "packages/app": { "name": "@medplum/app", - "version": "3.0.2", + "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/dropzone": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", - "@medplum/react": "*", - "@tabler/icons-react": "2.46.0", - "@testing-library/jest-dom": "6.3.0", - "@testing-library/react": "14.1.2", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", + "@mantine/core": "7.5.3", + "@mantine/dropzone": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", + "@medplum/react": "3.0.4", + "@tabler/icons-react": "2.47.0", + "@testing-library/jest-dom": "6.4.2", + "@testing-library/react": "14.2.1", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.1", "rfc6902": "5.1.1", - "vite": "5.0.12" + "vite": "5.1.3" }, "engines": { "node": ">=18.0.0" } }, "packages/app/node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -58500,30 +58321,30 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, "packages/app/node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -58573,12 +58394,12 @@ }, "packages/bot-layer": { "name": "@medplum/bot-layer", - "version": "3.0.2", + "version": "3.0.4", "license": "Apache-2.0", "dependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "form-data": "4.0.0", - "jose": "5.2.0", + "jose": "5.2.2", "node-fetch": "2.7.0", "pdfmake": "0.2.9", "ssh2": "1.15.0", @@ -58591,7 +58412,7 @@ "node": ">=18.0.0" }, "peerDependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "form-data": "^4.0.0", "node-fetch": "^2.7.0", "pdfmake": "^0.2.7", @@ -58601,15 +58422,15 @@ }, "packages/cdk": { "name": "@medplum/cdk", - "version": "3.0.2", + "version": "3.0.4", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.496.0", - "@medplum/core": "*", - "aws-cdk-lib": "2.124.0", - "cdk": "2.124.0", - "cdk-nag": "2.28.22", - "cdk-serverless-clamscan": "2.6.84", + "@aws-sdk/types": "3.515.0", + "@medplum/core": "3.0.4", + "aws-cdk-lib": "2.128.0", + "cdk": "2.128.0", + "cdk-nag": "2.28.39", + "cdk-serverless-clamscan": "2.6.100", "constructs": "10.3.0" }, "engines": { @@ -58618,22 +58439,22 @@ }, "packages/cli": { "name": "@medplum/cli", - "version": "3.0.2", + "version": "3.0.4", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-acm": "3.501.0", - "@aws-sdk/client-cloudformation": "3.501.0", - "@aws-sdk/client-cloudfront": "3.501.0", - "@aws-sdk/client-ecs": "3.501.0", - "@aws-sdk/client-s3": "3.501.0", - "@aws-sdk/client-ssm": "3.501.0", - "@aws-sdk/client-sts": "3.501.0", - "@aws-sdk/types": "3.496.0", - "@medplum/core": "*", - "@medplum/hl7": "*", + "@aws-sdk/client-acm": "3.515.0", + "@aws-sdk/client-cloudformation": "3.515.0", + "@aws-sdk/client-cloudfront": "3.515.0", + "@aws-sdk/client-ecs": "3.515.0", + "@aws-sdk/client-s3": "3.515.0", + "@aws-sdk/client-ssm": "3.515.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@medplum/core": "3.0.4", + "@medplum/hl7": "3.0.4", "aws-sdk-client-mock": "3.0.1", - "commander": "11.1.0", - "dotenv": "16.4.1", + "commander": "12.0.0", + "dotenv": "16.4.4", "fast-glob": "3.3.2", "node-fetch": "2.7.0", "tar": "6.2.0" @@ -58642,8 +58463,8 @@ "medplum": "dist/cjs/index.cjs" }, "devDependencies": { - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", "@types/node-fetch": "2.6.11", "@types/tar": "6.1.11" }, @@ -58652,20 +58473,20 @@ } }, "packages/cli/node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", + "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", "engines": { - "node": ">=16" + "node": ">=18" } }, "packages/core": { "name": "@medplum/core", - "version": "3.0.2", + "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", "jest-websocket-mock": "2.5.0" }, "engines": { @@ -58682,7 +58503,7 @@ }, "packages/definitions": { "name": "@medplum/definitions", - "version": "3.0.2", + "version": "3.0.4", "license": "Apache-2.0", "engines": { "node": ">=18.0.0" @@ -58690,7 +58511,7 @@ }, "packages/docs": { "name": "@medplum/docs", - "version": "3.0.2", + "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { "@docusaurus/core": "3.1.1", @@ -58699,10 +58520,10 @@ "@docusaurus/theme-mermaid": "3.1.1", "@docusaurus/tsconfig": "3.1.1", "@docusaurus/types": "3.1.1", - "@mdx-js/react": "3.0.0", - "@medplum/core": "*", - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", + "@mdx-js/react": "3.0.1", + "@medplum/core": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", "@svgr/webpack": "8.1.0", "clsx": "2.1.0", "file-loader": "6.2.0", @@ -58710,8 +58531,8 @@ "raw-loader": "4.0.2", "react": "18.2.0", "react-dom": "18.2.0", - "react-intersection-observer": "9.5.3", - "react-router-dom": "6.21.3", + "react-intersection-observer": "9.8.0", + "react-router-dom": "6.22.1", "typescript": "5.3.3", "url-loader": "4.1.1" }, @@ -58730,13 +58551,13 @@ }, "packages/eslint-config": { "name": "@medplum/eslint-config", - "version": "3.0.2", + "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { - "@typescript-eslint/eslint-plugin": "6.19.1", - "@typescript-eslint/parser": "6.19.1", + "@typescript-eslint/eslint-plugin": "7.0.1", + "@typescript-eslint/parser": "7.0.1", "eslint": "8.56.0", - "eslint-plugin-jsdoc": "48.0.4", + "eslint-plugin-jsdoc": "48.1.0", "eslint-plugin-json-files": "4.1.0", "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-refresh": "0.4.5" @@ -58745,23 +58566,24 @@ "node": ">=18.0.0" }, "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^6", - "@typescript-eslint/parser": "^6", + "@typescript-eslint/eslint-plugin": "^7", + "@typescript-eslint/parser": "^7", "eslint": "^8", - "eslint-plugin-jsdoc": "^46", - "eslint-plugin-json-files": "^3", - "eslint-plugin-react-hooks": "^4" + "eslint-plugin-jsdoc": "^48", + "eslint-plugin-json-files": "^4", + "eslint-plugin-react-hooks": "^4", + "eslint-plugin-react-refresh": "^0" } }, "packages/examples": { "name": "@medplum/examples", - "version": "3.0.2", + "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { "@jest/globals": "29.7.0", - "@medplum/core": "*", - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", + "@medplum/core": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", "jest": "29.7.0" }, "engines": { @@ -58770,7 +58592,7 @@ }, "packages/expo-polyfills": { "name": "@medplum/expo-polyfills", - "version": "3.0.2", + "version": "3.0.4", "license": "Apache-2.0", "dependencies": { "base-64": "1.0.0", @@ -58778,19 +58600,19 @@ "text-encoding": "0.7.0" }, "devDependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "@types/base-64": "1.0.2", - "@types/react": "18.2.48", + "@types/react": "18.2.56", "@types/text-encoding": "0.0.39", - "esbuild": "0.20.0", - "esbuild-node-externals": "1.12.0", + "esbuild": "0.20.1", + "esbuild-node-externals": "1.13.0", "jest": "29.7.0", - "jest-expo": "50.0.1", + "jest-expo": "50.0.2", "rimraf": "5.0.5", "ts-jest": "29.1.2" }, "peerDependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "expo": "*", "expo-crypto": "^12.6.0", "expo-secure-store": "^12.3.1", @@ -58801,12 +58623,12 @@ }, "packages/fhir-router": { "name": "@medplum/fhir-router", - "version": "3.0.2", + "version": "3.0.4", "license": "Apache-2.0", "dependencies": { - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", "dataloader": "2.2.2", "graphql": "16.8.1", "rfc6902": "5.1.1" @@ -58817,7 +58639,7 @@ }, "packages/fhirtypes": { "name": "@medplum/fhirtypes", - "version": "3.0.2", + "version": "3.0.4", "license": "Apache-2.0", "engines": { "node": ">=18.0.0" @@ -58825,17 +58647,17 @@ }, "packages/generator": { "name": "@medplum/generator", - "version": "3.0.2", + "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", "@types/json-schema": "7.0.15", "@types/pg": "8.11.0", "@types/unzipper": "0.10.9", - "fast-xml-parser": "4.3.3", - "fhirpath": "3.10.0", + "fast-xml-parser": "4.3.4", + "fhirpath": "3.10.1", "mkdirp": "3.0.1", "node-stream-zip": "1.15.0", "pg": "8.11.3", @@ -58847,9 +58669,9 @@ } }, "packages/generator/node_modules/fast-xml-parser": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.3.tgz", - "integrity": "sha512-coV/D1MhrShMvU6D0I+VAK3umz6hUaxxhL0yp/9RjfiYUfAv14rDhGQL+PLForhMdr0wq3PiV07WtkkNjJjNHg==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.4.tgz", + "integrity": "sha512-utnwm92SyozgA3hhH2I8qldf2lBqm6qHOICawRNRFu1qMe3+oqr+GcXjGqTmXTMGE5T4eC03kr/rlh5C1IRdZA==", "dev": true, "funding": [ { @@ -58870,35 +58692,35 @@ }, "packages/graphiql": { "name": "@medplum/graphiql", - "version": "3.0.2", + "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { - "@graphiql/react": "0.20.2", + "@graphiql/react": "0.20.3", "@graphiql/toolkit": "0.9.1", - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@medplum/core": "*", - "@medplum/fhirtypes": "*", - "@medplum/react": "*", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", - "graphiql": "3.1.0", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", + "graphiql": "3.1.1", "graphql": "16.8.1", - "graphql-ws": "5.14.3", - "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "graphql-ws": "5.15.0", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "vite": "5.0.12" + "vite": "5.1.3" }, "engines": { "node": ">=18.0.0" } }, "packages/graphiql/node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -58911,30 +58733,30 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", "fsevents": "~2.3.2" } }, "packages/graphiql/node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { @@ -58984,10 +58806,10 @@ }, "packages/health-gorilla": { "name": "@medplum/health-gorilla", - "version": "3.0.2", + "version": "3.0.4", "license": "Apache-2.0", "dependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "@medplum/fhirtypes": "*" }, "devDependencies": { @@ -58999,7 +58821,7 @@ }, "packages/hl7": { "name": "@medplum/hl7", - "version": "3.0.2", + "version": "3.0.4", "license": "Apache-2.0", "dependencies": { "@medplum/core": "*" @@ -59013,18 +58835,19 @@ }, "packages/mock": { "name": "@medplum/mock", - "version": "3.0.2", + "version": "3.0.4", "license": "Apache-2.0", "dependencies": { - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhir-router": "*", - "@medplum/fhirtypes": "*", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhir-router": "3.0.4", + "@medplum/fhirtypes": "3.0.4", "dataloader": "2.2.2", + "jest-websocket-mock": "2.5.0", "rfc6902": "5.1.1" }, "devDependencies": { - "@types/pdfmake": "0.2.8" + "@types/pdfmake": "0.2.9" }, "engines": { "node": ">=18.0.0" @@ -59032,45 +58855,45 @@ }, "packages/react": { "name": "@medplum/react", - "version": "3.0.2", + "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", - "@medplum/react-hooks": "*", - "@storybook/addon-actions": "7.6.10", - "@storybook/addon-essentials": "7.6.10", - "@storybook/addon-links": "7.6.10", - "@storybook/addon-storysource": "7.6.10", - "@storybook/builder-vite": "7.6.10", - "@storybook/react": "7.6.10", - "@storybook/react-vite": "7.6.10", - "@tabler/icons-react": "2.46.0", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", + "@medplum/react-hooks": "3.0.4", + "@storybook/addon-actions": "7.6.16", + "@storybook/addon-essentials": "7.6.16", + "@storybook/addon-links": "7.6.16", + "@storybook/addon-storysource": "7.6.16", + "@storybook/builder-vite": "7.6.16", + "@storybook/react": "7.6.16", + "@storybook/react-vite": "7.6.16", + "@tabler/icons-react": "2.47.0", "@testing-library/dom": "9.3.4", - "@testing-library/jest-dom": "6.3.0", - "@testing-library/react": "14.1.2", + "@testing-library/jest-dom": "6.4.2", + "@testing-library/react": "14.2.1", "@testing-library/user-event": "14.5.2", - "@types/jest": "29.5.11", - "@types/node": "20.11.8", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", + "@types/jest": "29.5.12", + "@types/node": "20.11.19", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "chromatic": "10.3.1", "jest": "29.7.0", "jest-each": "29.7.0", - "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", "rfc6902": "5.1.1", "rimraf": "5.0.5", "sinon": "17.0.1", - "storybook": "7.6.10", + "storybook": "7.6.16", "typescript": "5.3.3", "vite-plugin-turbosnap": "^1.0.3" }, @@ -59081,7 +58904,7 @@ "@mantine/core": "^7.0.0", "@mantine/hooks": "^7.0.0", "@mantine/notifications": "^7.0.0", - "@medplum/core": "*", + "@medplum/core": "3.0.4", "react": "^17.0.2 || ^18.0.0", "react-dom": "^17.0.2 || ^18.0.0", "rfc6902": "^5.0.1" @@ -59103,22 +58926,23 @@ }, "packages/react-hooks": { "name": "@medplum/react-hooks", - "version": "3.0.2", + "version": "3.0.4", "license": "Apache-2.0", "devDependencies": { - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", "@testing-library/dom": "9.3.4", - "@testing-library/jest-dom": "6.3.0", - "@testing-library/react": "14.1.2", - "@types/jest": "29.5.11", - "@types/node": "20.11.8", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", + "@testing-library/jest-dom": "6.4.2", + "@testing-library/react": "14.2.1", + "@types/jest": "29.5.12", + "@types/node": "20.11.19", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", "jest": "29.7.0", "jest-each": "29.7.0", + "jest-websocket-mock": "2.5.0", "react": "18.2.0", "react-dom": "18.2.0", "rimraf": "5.0.5", @@ -59128,7 +58952,7 @@ "node": ">=18.0.0" }, "peerDependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "react": "^17.0.2 || ^18.0.0", "react-dom": "^17.0.2 || ^18.0.0" } @@ -59183,22 +59007,22 @@ }, "packages/server": { "name": "@medplum/server", - "version": "3.0.2", + "version": "3.0.4", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-cloudwatch-logs": "3.501.0", - "@aws-sdk/client-lambda": "3.501.0", - "@aws-sdk/client-s3": "3.501.0", - "@aws-sdk/client-secrets-manager": "3.501.0", - "@aws-sdk/client-sesv2": "3.501.0", - "@aws-sdk/client-ssm": "3.501.0", + "@aws-sdk/client-cloudwatch-logs": "3.515.0", + "@aws-sdk/client-lambda": "3.516.0", + "@aws-sdk/client-s3": "3.515.0", + "@aws-sdk/client-secrets-manager": "3.515.0", + "@aws-sdk/client-sesv2": "3.515.0", + "@aws-sdk/client-ssm": "3.515.0", "@aws-sdk/cloudfront-signer": "3.496.0", - "@aws-sdk/lib-storage": "3.501.0", - "@aws-sdk/types": "3.496.0", - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhir-router": "*", - "@opentelemetry/auto-instrumentations-node": "0.40.3", + "@aws-sdk/lib-storage": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhir-router": "3.0.4", + "@opentelemetry/auto-instrumentations-node": "0.41.1", "@opentelemetry/exporter-metrics-otlp-proto": "0.48.0", "@opentelemetry/exporter-trace-otlp-proto": "0.48.0", "@opentelemetry/sdk-metrics": "1.21.0", @@ -59207,14 +59031,14 @@ "@smithy/util-stream": "2.1.1", "bcryptjs": "2.4.3", "body-parser": "1.20.2", - "bullmq": "5.1.5", + "bullmq": "5.2.1", "bytes": "3.1.2", "compression": "1.7.4", "cookie-parser": "1.4.6", "cors": "2.8.5", "cron-validator": "1.3.1", "dataloader": "2.2.2", - "dotenv": "16.4.1", + "dotenv": "16.4.4", "express": "4.18.2", "express-rate-limit": "7.1.5", "express-validator": "7.0.1", @@ -59222,10 +59046,10 @@ "hibp": "14.0.3", "http-graceful-shutdown": "^3.1.13", "ioredis": "5.3.2", - "jose": "5.2.0", + "jose": "5.2.2", "jszip": "3.10.1", "node-fetch": "2.7.0", - "nodemailer": "6.9.8", + "nodemailer": "6.9.9", "otplib": "12.0.1", "pg": "8.11.3", "pg-cursor": "2.10.3", @@ -59237,7 +59061,7 @@ }, "devDependencies": { "@jest/test-sequencer": "29.7.0", - "@medplum/fhirtypes": "*", + "@medplum/fhirtypes": "3.0.4", "@types/bcryptjs": "2.4.6", "@types/body-parser": "1.19.5", "@types/bytes": "3.1.4", @@ -59249,7 +59073,7 @@ "@types/ioredis": "4.28.10", "@types/json-schema": "7.0.15", "@types/mailparser": "3.4.4", - "@types/node": "20.11.8", + "@types/node": "20.11.19", "@types/node-fetch": "2.6.11", "@types/nodemailer": "6.4.14", "@types/pg": "8.11.0", @@ -59258,11 +59082,11 @@ "@types/supertest": "6.0.2", "@types/ua-parser-js": "0.7.39", "@types/uuid": "9.0.8", - "@types/validator": "13.11.8", + "@types/validator": "13.11.9", "@types/ws": "8.5.10", "aws-sdk-client-mock": "3.0.1", "aws-sdk-client-mock-jest": "3.0.1", - "mailparser": "3.6.6", + "mailparser": "3.6.7", "openapi3-ts": "4.2.1", "set-cookie-parser": "2.6.0", "supertest": "6.3.4", @@ -59273,16 +59097,79 @@ "node": ">=18.0.0" } }, - "packages/server/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" + "packages/server/node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "packages/server/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "packages/server/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "packages/server/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "packages/server/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "packages/server/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" } } } diff --git a/package.json b/package.json index 1c5c3c729b..1cee9f4727 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "3.0.2", + "version": "3.0.4", "private": true, "workspaces": [ "packages/*", @@ -37,27 +37,27 @@ "@babel/preset-react": "7.23.3", "@babel/preset-typescript": "7.23.3", "@cyclonedx/cyclonedx-npm": "1.16.1", - "@microsoft/api-documenter": "7.23.20", - "@microsoft/api-extractor": "7.39.4", - "@types/jest": "29.5.11", - "@types/node": "20.11.8", + "@microsoft/api-documenter": "7.23.24", + "@microsoft/api-extractor": "7.40.2", + "@types/jest": "29.5.12", + "@types/node": "20.11.19", "babel-jest": "29.7.0", "babel-preset-vite": "1.1.3", "cross-env": "7.0.3", "danger": "11.3.1", - "esbuild": "0.20.0", + "esbuild": "0.20.1", "identity-obj-proxy": "3.0.0", "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", - "npm-check-updates": "16.14.14", + "npm-check-updates": "16.14.15", "nyc": "15.1.0", - "prettier": "3.2.4", + "prettier": "3.2.5", "rimraf": "5.0.5", - "sort-package-json": "2.6.0", + "sort-package-json": "2.7.0", "source-map-explorer": "2.5.3", "ts-node": "10.9.2", "tslib": "2.6.2", - "turbo": "1.11.3", + "turbo": "1.12.4", "typescript": "5.3.3" }, "packageManager": "npm@9.8.1", @@ -65,10 +65,12 @@ "node": ">=18.0.0" }, "overrides": { - "esbuild": "0.20.0", + "es5-ext": "0.10.53", + "esbuild": "0.20.1", "got": "11.8.6", "react": "18.2.0", "react-dom": "18.2.0", + "sourcemap-codec": "npm:@jridgewell/sourcemap-codec@1.4.15", "trim": "0.0.3" } } diff --git a/packages/agent/.dockerignore b/packages/agent/.dockerignore new file mode 100644 index 0000000000..4de047158c --- /dev/null +++ b/packages/agent/.dockerignore @@ -0,0 +1,9 @@ +# Ignore everything +* + +# But not these files... +!.gitignore +!Dockerfile + +# ...even if they are in subdirectories +!*/ diff --git a/packages/agent/Dockerfile b/packages/agent/Dockerfile new file mode 100644 index 0000000000..e442330d9a --- /dev/null +++ b/packages/agent/Dockerfile @@ -0,0 +1,24 @@ +FROM --platform=linux/amd64 debian:bullseye-slim + +ARG GIT_SHA +ARG MEDPLUM_VERSION + +ENV GIT_SHA ${GIT_SHA} +ENV MEDPLUM_VERSION ${MEDPLUM_VERSION} +ENV MEDPLUM_LOG_LEVEL=${MEDPLUM_LOG_LEVEL:-"INFO"} + +RUN apt-get update && \ + apt-get install -y iputils-ping && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +RUN adduser -u 5678 --disabled-password --gecos "" app + +COPY bin/medplum-agent-${MEDPLUM_VERSION}-linux /srv/medplum-agent +RUN chmod +x /srv/medplum-agent + +WORKDIR /srv + +USER app + +CMD ./medplum-agent $MEDPLUM_BASE_URL $MEDPLUM_CLIENT_ID $MEDPLUM_CLIENT_SECRET $MEDPLUM_AGENT_ID $MEDPLUM_LOG_LEVEL diff --git a/packages/agent/README.md b/packages/agent/README.md index db88dc1c92..ffc04a427c 100644 --- a/packages/agent/README.md +++ b/packages/agent/README.md @@ -41,3 +41,27 @@ References: - [JSign](https://ebourg.github.io/jsign/) - [Shawl](https://github.com/mtkennerly/shawl) - [NSIS](https://nsis.sourceforge.io/) + +## Docker Image + +Build and run the docker image + +```bash +docker build -t medplum-agent:latest \ + --build-arg GIT_SHA=$(git log -1 --format=format:%H) \ + --build-arg MEDPLUM_VERSION=3.0.3 . +``` + +```bash +docker run --rm \ + -e MEDPLUM_BASE_URL="" \ + -e MEDPLUM_CLIENT_ID="" \ + -e MEDPLUM_CLIENT_SECRET="" \ + -e MEDPLUM_AGENT_ID="" \ + medplum-agent:latest +``` + +Optionally set the `MEDPLUM_LOG_LEVEL` environment variable +```bash + -e MEDPLUM_LOG_LEVEL="DEBUG" +``` diff --git a/packages/agent/package.json b/packages/agent/package.json index dba4b3fc12..d786d70c35 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/agent", - "version": "3.0.2", + "version": "3.0.4", "description": "Medplum Agent", "homepage": "https://www.medplum.com/", "bugs": { @@ -23,15 +23,15 @@ "test": "jest" }, "dependencies": { - "@medplum/core": "*", - "@medplum/hl7": "*", - "dcmjs-dimse": "0.1.24", + "@medplum/core": "3.0.4", + "@medplum/hl7": "3.0.4", + "dcmjs-dimse": "0.1.25", "node-windows": "1.0.0-beta.8", "ws": "8.16.0" }, "devDependencies": { - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", "@types/async-eventemitter": "0.2.4", "@types/node-windows": "0.1.6", "@types/ws": "8.5.10", diff --git a/packages/agent/src/app.ts b/packages/agent/src/app.ts index 95bf9b9a2d..512a08b934 100644 --- a/packages/agent/src/app.ts +++ b/packages/agent/src/app.ts @@ -1,6 +1,8 @@ import { + AgentError, AgentMessage, AgentTransmitRequest, + AgentTransmitResponse, ContentType, Hl7Message, LogLevel, @@ -10,11 +12,17 @@ import { } from '@medplum/core'; import { Endpoint, Reference } from '@medplum/fhirtypes'; import { Hl7Client } from '@medplum/hl7'; +import { exec as _exec } from 'node:child_process'; +import { isIPv4, isIPv6 } from 'node:net'; +import { promisify } from 'node:util'; +import { platform } from 'os'; import WebSocket from 'ws'; import { Channel } from './channel'; import { AgentDicomChannel } from './dicom'; import { AgentHl7Channel } from './hl7'; +const exec = promisify(_exec); + export class App { static instance: App; readonly log: Logger; @@ -137,7 +145,11 @@ export class App { // @ts-expect-error - Deprecated message type case 'push': case 'agent:transmit:request': - this.pushMessage(command); + if (command.contentType === ContentType.PING) { + await this.tryPingIp(command); + } else { + this.pushMessage(command); + } break; case 'agent:error': this.log.error(command.body); @@ -253,6 +265,45 @@ export class App { } } + private async tryPingIp(message: AgentTransmitRequest): Promise { + try { + if (message.body && message.body !== 'PING') { + const warnMsg = 'Message body present but unused. Body should be empty for a ping request.'; + this.log.warn(warnMsg); + } + if (!isIPv4(message.remote)) { + let errMsg = `Attempted to ping invalid IP: ${message.remote}`; + if (isIPv6(message.remote)) { + errMsg = `Attempted to ping an IPv6 address: ${message.remote}\n\nIPv6 is currently unsupported.`; + } + this.log.error(errMsg); + throw new Error(errMsg); + } + // This covers Windows, Linux, and Mac + const { stderr, stdout } = await exec( + platform() === 'win32' ? `ping ${message.remote}` : `ping -c 4 ${message.remote}` + ); + if (stderr) { + throw new Error(`Received on stderr:\n\n${stderr}`); + } + this.log.info(`Ping result for ${message.remote}:\n\n${stdout}`); + this.addToWebSocketQueue({ + type: 'agent:transmit:response', + channel: message.channel, + contentType: ContentType.PING, + remote: message.remote, + callback: message.callback, + body: stdout, + } satisfies AgentTransmitResponse); + } catch (err) { + this.log.error(`Error during ping attempt to ${message.remote ?? 'NO_IP_GIVEN'}: ${normalizeErrorString(err)}`); + this.addToWebSocketQueue({ + type: 'agent:error', + body: (err as Error).message, + } satisfies AgentError); + } + } + private async sendToWebSocket(message: AgentMessage): Promise { if (!this.webSocket) { throw new Error('WebSocket not connected'); diff --git a/packages/agent/src/net-utils.test.ts b/packages/agent/src/net-utils.test.ts new file mode 100644 index 0000000000..5c156e6ef8 --- /dev/null +++ b/packages/agent/src/net-utils.test.ts @@ -0,0 +1,128 @@ +import { AgentMessage, allOk, ContentType, LogLevel, sleep } from '@medplum/core'; +import { Agent, Resource } from '@medplum/fhirtypes'; +import { MockClient } from '@medplum/mock'; +import { Client, Server } from 'mock-socket'; +import { App } from './app'; + +jest.mock('node-windows'); + +const medplum = new MockClient(); + +describe('Agent Net Utils', () => { + let mockServer: Server; + let mySocket: Client | undefined = undefined; + let wsClient: Client; + let app: App; + let onMessage: (command: AgentMessage) => void; + + beforeAll(async () => { + console.log = jest.fn(); + + medplum.router.router.add('POST', ':resourceType/:id/$execute', async () => { + return [allOk, {} as Resource]; + }); + + mockServer = new Server('wss://example.com/ws/agent'); + + mockServer.on('connection', (socket) => { + mySocket = socket; + socket.on('message', (data) => { + const command = JSON.parse((data as Buffer).toString('utf8')) as AgentMessage; + if (command.type === 'agent:connect:request') { + socket.send( + Buffer.from( + JSON.stringify({ + type: 'agent:connect:response', + }) + ) + ); + } else { + onMessage(command); + } + }); + }); + + const agent = await medplum.createResource({ + resourceType: 'Agent', + } as Agent); + + // Start the app + app = new App(medplum, agent.id as string, LogLevel.INFO); + await app.start(); + + // Wait for the WebSocket to connect + // eslint-disable-next-line no-unmodified-loop-condition + while (!mySocket) { + await sleep(100); + } + + wsClient = mySocket as unknown as Client; + }); + + afterAll(() => { + app.stop(); + mockServer.stop(); + }); + + test('Ping -- valid', async () => { + let resolve: (value: AgentMessage) => void; + let reject: (error: Error) => void; + + const messageReceived = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + + onMessage = (command) => resolve(command); + + expect(wsClient).toBeDefined(); + wsClient.send( + Buffer.from( + JSON.stringify({ + type: 'agent:transmit:request', + contentType: ContentType.PING, + remote: '127.0.0.1', + body: 'PING', + }) + ) + ); + + const timer = setTimeout(() => { + reject(new Error('Timeout')); + }, 3500); + + await expect(messageReceived).resolves.toMatchObject({ type: 'agent:transmit:response', body: expect.any(String) }); + clearTimeout(timer); + }); + + test('Ping -- non-IP remote', async () => { + let resolve: (value: AgentMessage) => void; + let reject: (error: Error) => void; + + const messageReceived = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + + onMessage = (command) => resolve(command); + + expect(wsClient).toBeDefined(); + wsClient.send( + Buffer.from( + JSON.stringify({ + type: 'agent:transmit:request', + contentType: ContentType.PING, + remote: 'https://localhost:3001', + body: 'PING', + }) + ) + ); + + const timer = setTimeout(() => { + reject(new Error('Timeout')); + }, 3500); + + await expect(messageReceived).resolves.toMatchObject({ type: 'agent:error', body: expect.any(String) }); + clearTimeout(timer); + }); +}); diff --git a/packages/app/package.json b/packages/app/package.json index 6a3a992f03..f5f6b5bb3e 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/app", - "version": "3.0.2", + "version": "3.0.4", "description": "Medplum App", "homepage": "https://www.medplum.com/", "bugs": { @@ -29,28 +29,28 @@ "last 1 Chrome versions" ], "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/dropzone": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", - "@medplum/react": "*", - "@tabler/icons-react": "2.46.0", - "@testing-library/jest-dom": "6.3.0", - "@testing-library/react": "14.1.2", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", + "@mantine/core": "7.5.3", + "@mantine/dropzone": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", + "@medplum/react": "3.0.4", + "@tabler/icons-react": "2.47.0", + "@testing-library/jest-dom": "6.4.2", + "@testing-library/react": "14.2.1", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", - "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "6.21.3", + "react-router-dom": "6.22.1", "rfc6902": "5.1.1", - "vite": "5.0.12" + "vite": "5.1.3" }, "engines": { "node": ">=18.0.0" diff --git a/packages/app/src/AppRoutes.tsx b/packages/app/src/AppRoutes.tsx index d597386668..fb53ba7e69 100644 --- a/packages/app/src/AppRoutes.tsx +++ b/packages/app/src/AppRoutes.tsx @@ -21,12 +21,12 @@ import { CreateClientPage } from './admin/CreateClientPage'; import { EditMembershipPage } from './admin/EditMembershipPage'; import { InvitePage } from './admin/InvitePage'; import { PatientsPage } from './admin/PatientsPage'; +import { ProjectAdminConfigPage } from './admin/ProjectAdminConfigPage'; import { ProjectDetailsPage } from './admin/ProjectDetailsPage'; import { ProjectPage } from './admin/ProjectPage'; import { SecretsPage } from './admin/SecretsPage'; import { SitesPage } from './admin/SitesPage'; import { SuperAdminPage } from './admin/SuperAdminPage'; -import { ProjectAdminConfigPage } from './admin/ProjectAdminConfigPage'; import { UsersPage } from './admin/UsersPage'; import { AssaysPage } from './lab/AssaysPage'; import { PanelsPage } from './lab/PanelsPage'; @@ -40,9 +40,12 @@ import { ChecklistPage } from './resource/ChecklistPage'; import { DeletePage } from './resource/DeletePage'; import { DetailsPage } from './resource/DetailsPage'; import { EditPage } from './resource/EditPage'; +import { FormCreatePage } from './resource/FormCreatePage'; import { HistoryPage } from './resource/HistoryPage'; +import { JsonCreatePage } from './resource/JsonCreatePage'; import { JsonPage } from './resource/JsonPage'; import { PreviewPage } from './resource/PreviewPage'; +import { ProfilesPage } from './resource/ProfilesPage'; import { QuestionnaireBotsPage } from './resource/QuestionnaireBotsPage'; import { QuestionnaireResponsePage } from './resource/QuestionnaireResponsePage'; import { ReferenceRangesPage } from './resource/ReferenceRangesPage'; @@ -51,9 +54,7 @@ import { ResourcePage } from './resource/ResourcePage'; import { ResourceVersionPage } from './resource/ResourceVersionPage'; import { SubscriptionsPage } from './resource/SubscriptionsPage'; import { TimelinePage } from './resource/TimelinePage'; -import { FormCreatePage } from './resource/FormCreatePage'; -import { JsonCreatePage } from './resource/JsonCreatePage'; -import { ProfilesPage } from './resource/ProfilesPage'; +import { ToolsPage } from './resource/ToolsPage'; export function AppRoutes(): JSX.Element { return ( @@ -95,7 +96,9 @@ export function AppRoutes(): JSX.Element { } /> } /> } /> + } /> + } /> }> } /> } /> diff --git a/packages/app/src/CreateResourcePage.tsx b/packages/app/src/CreateResourcePage.tsx index 7c75b74411..5a6854d64f 100644 --- a/packages/app/src/CreateResourcePage.tsx +++ b/packages/app/src/CreateResourcePage.tsx @@ -1,15 +1,19 @@ -import { Paper, ScrollArea, Tabs, Text } from '@mantine/core'; +import { Badge, Group, Paper, ScrollArea, Tabs, Text, useMantineTheme } from '@mantine/core'; import { useState } from 'react'; import { Outlet, useNavigate, useParams } from 'react-router-dom'; -const tabs = ['Form', 'JSON']; +const tabs = ['Form', 'JSON', 'Profiles'] as const; +const BETA_TABS: (typeof tabs)[number][] = ['Profiles']; const defaultTab = tabs[0].toLowerCase(); export function CreateResourcePage(): JSX.Element { const navigate = useNavigate(); + const theme = useMantineTheme(); const { resourceType } = useParams(); - const tab = window.location.pathname.split('/').pop(); - const [currentTab, setCurrentTab] = useState(tab ?? defaultTab); + const [currentTab, setCurrentTab] = useState(() => { + const tab = window.location.pathname.split('/').pop(); + return tab && tabs.map((t) => t.toLowerCase()).includes(tab) ? tab : defaultTab; + }); /** * Handles a tab change event. @@ -34,7 +38,16 @@ export function CreateResourcePage(): JSX.Element { {tabs.map((t) => ( - {t} + {BETA_TABS.includes(t) ? ( + + {t} + + Beta + + + ) : ( + t + )} ))} diff --git a/packages/app/src/HomePage.tsx b/packages/app/src/HomePage.tsx index 8f53903c36..97f513f31f 100644 --- a/packages/app/src/HomePage.tsx +++ b/packages/app/src/HomePage.tsx @@ -1,6 +1,6 @@ import { Paper } from '@mantine/core'; import { showNotification } from '@mantine/notifications'; -import { formatSearchQuery, normalizeErrorString, parseSearchDefinition, SearchRequest } from '@medplum/core'; +import { formatSearchQuery, normalizeErrorString, parseSearchRequest, SearchRequest } from '@medplum/core'; import { ResourceType } from '@medplum/fhirtypes'; import { Loading, MemoizedSearchControl, useMedplum } from '@medplum/react'; import { useEffect, useState } from 'react'; @@ -17,7 +17,7 @@ export function HomePage(): JSX.Element { useEffect(() => { // Parse the search from the URL - const parsedSearch = parseSearchDefinition(location.pathname + location.search); + const parsedSearch = parseSearchRequest(location.pathname + location.search); // Fill in the search with default values const populatedSearch = addSearchValues(parsedSearch, medplum.getUserConfiguration()); diff --git a/packages/app/src/admin/SecretsPage.tsx b/packages/app/src/admin/SecretsPage.tsx index 8dd7b65d1d..4535d3f27a 100644 --- a/packages/app/src/admin/SecretsPage.tsx +++ b/packages/app/src/admin/SecretsPage.tsx @@ -48,6 +48,7 @@ export function SecretsPage(): JSX.Element { , + loading: false, autoClose: false, withCloseButton: true, }); @@ -198,6 +199,7 @@ function startAsyncJob(medplum: MedplumClient, title: string, url: string, body? title, message: normalizeErrorString(err), icon: , + loading: false, autoClose: false, withCloseButton: true, }); diff --git a/packages/app/src/resource/FormCreatePage.tsx b/packages/app/src/resource/FormCreatePage.tsx index fd24a72b9c..da0e27b1ce 100644 --- a/packages/app/src/resource/FormCreatePage.tsx +++ b/packages/app/src/resource/FormCreatePage.tsx @@ -1,17 +1,58 @@ -import { OperationOutcome } from '@medplum/fhirtypes'; -import { Document, ResourceForm } from '@medplum/react'; -import { useState } from 'react'; -import { useParams } from 'react-router-dom'; +import { Stack, Text } from '@mantine/core'; +import { OperationOutcome, Resource } from '@medplum/fhirtypes'; +import { Document, ResourceForm, SupportedProfileStructureDefinition } from '@medplum/react'; +import { useCallback, useState } from 'react'; +import { useLocation, useParams } from 'react-router-dom'; import { useCreateResource } from './useCreateResource'; +import { ProfileTabs } from './ProfileTabs'; +import { addProfileToResource, cleanResource } from './utils'; export function FormCreatePage(): JSX.Element { const { resourceType } = useParams(); + const location = useLocation(); const [outcome, setOutcome] = useState(); const { defaultValue, handleSubmit } = useCreateResource(resourceType, setOutcome); + const [currentProfile, setCurrentProfile] = useState(); + + const isProfilesPage = location.pathname.toLowerCase().endsWith('profiles'); + + const onProfileSubmit = useCallback( + (resource: Resource): void => { + const cleanedResource = cleanResource(resource); + if (currentProfile) { + addProfileToResource(cleanedResource, currentProfile.url); + } + handleSubmit(cleanedResource); + }, + [currentProfile, handleSubmit] + ); + + if (!isProfilesPage) { + return ( + + + + ); + } return ( - + + + {currentProfile ? ( + + ) : ( + + Select a profile above + + )} + ); } diff --git a/packages/app/src/resource/ProfileTabs.tsx b/packages/app/src/resource/ProfileTabs.tsx new file mode 100644 index 0000000000..30d1532b66 --- /dev/null +++ b/packages/app/src/resource/ProfileTabs.tsx @@ -0,0 +1,66 @@ +import { Tabs, ThemeIcon } from '@mantine/core'; +import { Resource } from '@medplum/fhirtypes'; +import { SupportedProfileStructureDefinition, isSupportedProfileStructureDefinition, useMedplum } from '@medplum/react'; +import { IconCircleFilled } from '@tabler/icons-react'; +import { useEffect, useState } from 'react'; +export interface ProfileTabsProps { + readonly resource: Resource; + readonly currentProfile: SupportedProfileStructureDefinition | undefined; + readonly onChange: (newProfile: SupportedProfileStructureDefinition) => void; +} + +export function ProfileTabs({ resource, currentProfile, onChange }: ProfileTabsProps): JSX.Element { + const resourceType = resource.resourceType; + + const medplum = useMedplum(); + const [availableProfiles, setAvailableProfiles] = useState(); + + // This is a bit inefficient since the entire structure definition + // for each available profile is being fetched. All that is really needed is the title & url + // The SD is useful for the time being to populate the Snapshot and JSON debugging tabs; + // but those will likely be removed before deploying + useEffect(() => { + medplum + .searchResources('StructureDefinition', { type: resourceType, derivation: 'constraint', _count: 50 }) + .then((results) => { + setAvailableProfiles(results.filter(isSupportedProfileStructureDefinition)); + }) + .catch(console.error); + }, [medplum, onChange, resourceType]); + return ( + { + const newProfile = availableProfiles?.find((p) => p.url === newProfileUrl); + if (newProfile) { + onChange(newProfile); + } + }} + > + + {availableProfiles?.map((profile) => { + const isActive = resource.meta?.profile?.includes(profile.url); + const title = isActive + ? `This profile is present in this resource's meta.profile property.` + : `This profile is not included in this resource's meta.profile property.`; + return ( + + + + ) + } + > + {profile.title || profile.name} + + ); + })} + + + ); +} diff --git a/packages/app/src/resource/ProfilesPage.test.tsx b/packages/app/src/resource/ProfilesPage.test.tsx index 1290c9bb65..ad280d1438 100644 --- a/packages/app/src/resource/ProfilesPage.test.tsx +++ b/packages/app/src/resource/ProfilesPage.test.tsx @@ -7,15 +7,26 @@ import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' import { Suspense } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { AppRoutes } from '../AppRoutes'; +import { loadDataType } from '@medplum/core'; const medplum = new MockClient(); describe('ProfilesPage', () => { const fishPatientProfile = FishPatientResources.getFishPatientProfileSD(); beforeAll(async () => { + const loadedProfileUrls: string[] = []; for (const profile of [fishPatientProfile, FishPatientResources.getFishSpeciesExtensionSD()]) { - await medplum.createResourceIfNoneExist(profile, `url:${profile.url}`); + const sd = await medplum.createResourceIfNoneExist(profile, `url:${profile.url}`); + loadedProfileUrls.push(sd.url); + loadDataType(sd, sd.url); } + medplum.requestProfileSchema = jest.fn((profileUrl) => { + if (loadedProfileUrls.includes(profileUrl)) { + return Promise.resolve([profileUrl]); + } else { + throw new Error('unexpected profileUrl'); + } + }); }); async function setup(url: string): Promise { diff --git a/packages/app/src/resource/ProfilesPage.tsx b/packages/app/src/resource/ProfilesPage.tsx index 4660b616fb..9ee6bc4f8b 100644 --- a/packages/app/src/resource/ProfilesPage.tsx +++ b/packages/app/src/resource/ProfilesPage.tsx @@ -1,25 +1,18 @@ -import { Button, Group, Stack, Switch, Tabs, Text, ThemeIcon, Title } from '@mantine/core'; +import { Button, Group, Stack, Switch, Text, Title } from '@mantine/core'; import { showNotification } from '@mantine/notifications'; import { deepClone, normalizeErrorString, normalizeOperationOutcome } from '@medplum/core'; import { OperationOutcome, Resource, ResourceType } from '@medplum/fhirtypes'; -import { - Document, - ResourceForm, - SupportedProfileStructureDefinition, - isSupportedProfileStructureDefinition, - useMedplum, -} from '@medplum/react'; -import { IconCircleFilled } from '@tabler/icons-react'; +import { Document, ResourceForm, SupportedProfileStructureDefinition, useMedplum } from '@medplum/react'; import React, { useCallback, useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import { addProfileToResource, cleanResource, removeProfileFromResource } from './utils'; +import { ProfileTabs } from './ProfileTabs'; export function ProfilesPage(): JSX.Element | null { const medplum = useMedplum(); const { resourceType, id } = useParams() as { resourceType: ResourceType; id: string }; const [resource, setResource] = useState(); const [currentProfile, setCurrentProfile] = useState(); - const [availableProfiles, setAvailableProfiles] = useState(); useEffect(() => { medplum @@ -30,19 +23,6 @@ export function ProfilesPage(): JSX.Element | null { }); }, [medplum, resourceType, id]); - // This is a bit inefficient since the entire structure definition - // for each available profile is being fetched. All that is really needed is the title & url - // The SD is useful for the time being to populate the Snapshot and JSON debugging tabs; - // but those will likely be removed before deploying - useEffect(() => { - medplum - .searchResources('StructureDefinition', { type: resourceType, derivation: 'constraint', _count: 50 }) - .then((results) => { - setAvailableProfiles(results.filter(isSupportedProfileStructureDefinition)); - }) - .catch(console.error); - }, [medplum, resourceType]); - if (!resource) { return null; } @@ -52,35 +32,7 @@ export function ProfilesPage(): JSX.Element | null { Available {resourceType} profiles <> - setCurrentProfile(availableProfiles?.find((p) => p.url === newProfileUrl))} - > - - {availableProfiles?.map((profile) => { - const isActive = resource.meta?.profile?.includes(profile.url); - const title = isActive - ? `This profile is present in this resource's meta.profile property.` - : `This profile is not included in this resource's meta.profile property.`; - return ( - - - - ) - } - > - {profile.title || profile.name} - - ); - })} - - + {currentProfile ? ( = ({ profile, resource, onReso }) .catch((err) => { setOutcome(normalizeOperationOutcome(err)); - showNotification({ color: 'red', message: normalizeErrorString(err) }); + showNotification({ color: 'red', message: normalizeErrorString(err), autoClose: false }); }); }, [medplum, profile.url, onResourceUpdated, active] diff --git a/packages/app/src/resource/ResourcePage.tsx b/packages/app/src/resource/ResourcePage.tsx index cd179f576c..d0b020b402 100644 --- a/packages/app/src/resource/ResourcePage.tsx +++ b/packages/app/src/resource/ResourcePage.tsx @@ -40,6 +40,10 @@ function getTabs(resourceType: string): string[] { result.push('Ranges'); } + if (resourceType === 'Agent') { + result.push('Tools'); + } + result.push('Details', 'Edit', 'Event', 'History', 'Blame', 'JSON', 'Apps', 'Profiles'); return result; } diff --git a/packages/app/src/resource/ToolsPage.test.tsx b/packages/app/src/resource/ToolsPage.test.tsx new file mode 100644 index 0000000000..596fb5b637 --- /dev/null +++ b/packages/app/src/resource/ToolsPage.test.tsx @@ -0,0 +1,96 @@ +import { Notifications } from '@mantine/notifications'; +import { getReferenceString } from '@medplum/core'; +import { Agent } from '@medplum/fhirtypes'; +import { MockClient } from '@medplum/mock'; +import { MedplumProvider } from '@medplum/react'; +import { MemoryRouter } from 'react-router-dom'; +import { AppRoutes } from '../AppRoutes'; +import { act, fireEvent, render, screen } from '../test-utils/render'; + +const medplum = new MockClient(); + +describe('ToolsPage', () => { + let agent: Agent; + + function setup(url: string): void { + render( + + + + + + + ); + } + + beforeAll(async () => { + agent = await medplum.createResource({ + resourceType: 'Agent', + name: 'Agente', + } as Agent); + }); + + test('Renders last ping', async () => { + // load agent page + await act(async () => { + setup(`/${getReferenceString(agent)}`); + }); + + const toolsTab = screen.getByRole('tab', { name: 'Tools' }); + + // click on Tools tab + await act(async () => { + fireEvent.click(toolsTab); + }); + + expect(screen.getByText(agent.name)).toBeInTheDocument(); + expect(screen.getByLabelText('IP')).toBeInTheDocument(); + + await act(async () => { + fireEvent.change(screen.getByLabelText('IP'), { target: { value: '8.8.8.8' } }); + fireEvent.click(screen.getByText('Ping')); + }); + + await expect(screen.findByText('statistics', { exact: false })).resolves.toBeInTheDocument(); + }); + + test('Displays error notification whenever invalid IP entered', async () => { + // load agent tools page + await act(async () => { + setup(`/${getReferenceString(agent)}/tools`); + }); + + expect(screen.getByText(agent.name)).toBeInTheDocument(); + expect(screen.getByLabelText('IP')).toBeInTheDocument(); + + await act(async () => { + fireEvent.change(screen.getByLabelText('IP'), { target: { value: 'abc123' } }); + fireEvent.click(screen.getByText('Ping')); + }); + + await expect(screen.findByText('Invalid IP entered')).resolves.toBeInTheDocument(); + }); + + test('Displays error notification whenever agent unreachable', async () => { + medplum.setAgentAvailable(false); + + // load agent tools page + await act(async () => { + setup(`/${getReferenceString(agent)}/tools`); + }); + + expect(screen.getByText(agent.name)).toBeInTheDocument(); + expect(screen.getByLabelText('IP')).toBeInTheDocument(); + + await act(async () => { + fireEvent.change(screen.getByLabelText('IP'), { target: { value: '8.8.8.8' } }); + fireEvent.click(screen.getByText('Ping')); + }); + + await expect( + screen.findByText('"$push" operation timed out. Agent may be unreachable') + ).resolves.toBeInTheDocument(); + + medplum.setAgentAvailable(true); + }); +}); diff --git a/packages/app/src/resource/ToolsPage.tsx b/packages/app/src/resource/ToolsPage.tsx new file mode 100644 index 0000000000..ee3e3e98bb --- /dev/null +++ b/packages/app/src/resource/ToolsPage.tsx @@ -0,0 +1,82 @@ +import { Button, Input, Title } from '@mantine/core'; +import { showNotification } from '@mantine/notifications'; +import { ContentType } from '@medplum/core'; +import { Agent, Reference } from '@medplum/fhirtypes'; +import { Document, Form, ResourceName, useMedplum } from '@medplum/react'; +import { IconRouter } from '@tabler/icons-react'; +import { useCallback, useMemo, useRef, useState } from 'react'; +import { useParams } from 'react-router-dom'; + +export function ToolsPage(): JSX.Element | null { + const medplum = useMedplum(); + const { id } = useParams() as { id: string }; + const reference = useMemo(() => ({ reference: 'Agent/' + id }) as Reference, [id]); + const [ip, setIp] = useState(''); + const [lastPing, setLastPing] = useState(); + const [pinging, setPinging] = useState(false); + + const ipRef = useRef(ip); + ipRef.current = ip; + + const onSubmit = useCallback(() => { + if (ipRef.current === '') { + return; + } + setPinging(true); + medplum + .pushToAgent(reference, ipRef.current, 'PING', ContentType.PING, true) + .then((pingResult: string) => { + setLastPing(pingResult); + setPinging(false); + }) + .catch((err: unknown) => { + setPinging(false); + if ((err as Error).message === 'Destination device not found') { + // Report error + showNotification({ + color: 'red', + title: 'Error', + message: 'Invalid IP entered', + autoClose: false, + }); + } else if ((err as Error).message === 'Timeout') { + showNotification({ + color: 'red', + title: 'Error', + message: '"$push" operation timed out. Agent may be unreachable', + autoClose: false, + }); + } + }); + }, [medplum, reference]); + + return ( + + Ping from Agent +
+ Agent: +
+
+ + setIp(e.target.value)} value={ip} /> + +
+ + {!pinging && lastPing && ( + <> + Last Ping +
{lastPing}
+ + )} +
+ ); +} diff --git a/packages/app/src/resource/useCreateResource.ts b/packages/app/src/resource/useCreateResource.ts index 4aebdabf43..6a700b5aca 100644 --- a/packages/app/src/resource/useCreateResource.ts +++ b/packages/app/src/resource/useCreateResource.ts @@ -1,4 +1,5 @@ -import { normalizeOperationOutcome } from '@medplum/core'; +import { showNotification } from '@mantine/notifications'; +import { normalizeErrorString, normalizeOperationOutcome } from '@medplum/core'; import { OperationOutcome, Resource } from '@medplum/fhirtypes'; import { useMedplum } from '@medplum/react'; import { useNavigate } from 'react-router-dom'; @@ -32,6 +33,12 @@ export function useCreateResource( if (setOutcome) { setOutcome(normalizeOperationOutcome(err)); } + showNotification({ + color: 'red', + message: normalizeErrorString(err), + autoClose: false, + styles: { description: { whiteSpace: 'pre-line' } }, + }); }); }; diff --git a/packages/bot-layer/package.json b/packages/bot-layer/package.json index 2256928aa7..6b3b0efe3a 100644 --- a/packages/bot-layer/package.json +++ b/packages/bot-layer/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/bot-layer", - "version": "3.0.2", + "version": "3.0.4", "description": "Medplum Bot Lambda Layer", "keywords": [ "medplum", @@ -22,9 +22,9 @@ "author": "Medplum ", "type": "module", "dependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "form-data": "4.0.0", - "jose": "5.2.0", + "jose": "5.2.2", "node-fetch": "2.7.0", "pdfmake": "0.2.9", "ssh2": "1.15.0", @@ -34,7 +34,7 @@ "@types/node-fetch": "2.6.11" }, "peerDependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "form-data": "^4.0.0", "node-fetch": "^2.7.0", "pdfmake": "^0.2.7", diff --git a/packages/cdk/package.json b/packages/cdk/package.json index 68dd81b1cd..c0ca259228 100644 --- a/packages/cdk/package.json +++ b/packages/cdk/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/cdk", - "version": "3.0.2", + "version": "3.0.4", "description": "Medplum CDK Infra as Code", "homepage": "https://www.medplum.com/", "bugs": { @@ -23,12 +23,12 @@ "test": "jest --runInBand" }, "dependencies": { - "@aws-sdk/types": "3.496.0", - "@medplum/core": "*", - "aws-cdk-lib": "2.124.0", - "cdk": "2.124.0", - "cdk-nag": "2.28.22", - "cdk-serverless-clamscan": "2.6.84", + "@aws-sdk/types": "3.515.0", + "@medplum/core": "3.0.4", + "aws-cdk-lib": "2.128.0", + "cdk": "2.128.0", + "cdk-nag": "2.28.39", + "cdk-serverless-clamscan": "2.6.100", "constructs": "10.3.0" }, "engines": { diff --git a/packages/cli/package.json b/packages/cli/package.json index f9a90d4e69..b4d4eefa88 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/cli", - "version": "3.0.2", + "version": "3.0.4", "description": "Medplum Command Line Interface", "keywords": [ "medplum", @@ -41,26 +41,26 @@ "test": "jest" }, "dependencies": { - "@aws-sdk/client-acm": "3.501.0", - "@aws-sdk/client-cloudformation": "3.501.0", - "@aws-sdk/client-cloudfront": "3.501.0", - "@aws-sdk/client-ecs": "3.501.0", - "@aws-sdk/client-s3": "3.501.0", - "@aws-sdk/client-ssm": "3.501.0", - "@aws-sdk/client-sts": "3.501.0", - "@aws-sdk/types": "3.496.0", - "@medplum/core": "*", - "@medplum/hl7": "*", + "@aws-sdk/client-acm": "3.515.0", + "@aws-sdk/client-cloudformation": "3.515.0", + "@aws-sdk/client-cloudfront": "3.515.0", + "@aws-sdk/client-ecs": "3.515.0", + "@aws-sdk/client-s3": "3.515.0", + "@aws-sdk/client-ssm": "3.515.0", + "@aws-sdk/client-sts": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@medplum/core": "3.0.4", + "@medplum/hl7": "3.0.4", "aws-sdk-client-mock": "3.0.1", - "commander": "11.1.0", - "dotenv": "16.4.1", + "commander": "12.0.0", + "dotenv": "16.4.4", "fast-glob": "3.3.2", "node-fetch": "2.7.0", "tar": "6.2.0" }, "devDependencies": { - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", "@types/node-fetch": "2.6.11", "@types/tar": "6.1.11" }, diff --git a/packages/cli/src/aws/update-server.test.ts b/packages/cli/src/aws/update-server.test.ts index dcc01f48c8..1e9c35f721 100644 --- a/packages/cli/src/aws/update-server.test.ts +++ b/packages/cli/src/aws/update-server.test.ts @@ -99,7 +99,7 @@ describe('update-server command', () => { medplum = { startAsyncRequest: jest.fn(), - get: jest.fn().mockResolvedValue(`{"version":"2.4.17-b27a9f"}`), + get: jest.fn().mockResolvedValue({ version: '2.4.17-b27a9f' }), } as unknown as MedplumClient; (createMedplumClient as unknown as jest.Mock).mockResolvedValue(medplum); }); diff --git a/packages/cli/src/aws/update-server.ts b/packages/cli/src/aws/update-server.ts index f65e128264..91070ab83f 100644 --- a/packages/cli/src/aws/update-server.ts +++ b/packages/cli/src/aws/update-server.ts @@ -20,7 +20,7 @@ export async function updateServerCommand(tag: string, options: any): Promise -1) { diff --git a/packages/cli/src/bulk.test.ts b/packages/cli/src/bulk.test.ts index a4542fc298..e038231cc7 100644 --- a/packages/cli/src/bulk.test.ts +++ b/packages/cli/src/bulk.test.ts @@ -51,7 +51,13 @@ describe('CLI Bulk Commands', () => { if (url.includes('/$export?_since=200')) { return { status: 200, - headers: { get: () => ContentType.FHIR_JSON }, + headers: { + get(name: string): string | undefined { + return { + 'content-type': ContentType.FHIR_JSON, + }[name]; + }, + }, json: jest.fn(async () => { return { resourceType: 'OperationOutcome', @@ -104,7 +110,13 @@ describe('CLI Bulk Commands', () => { count++; return { status: 202, - headers: { get: () => ContentType.FHIR_JSON }, + headers: { + get(name: string): string | undefined { + return { + 'content-type': ContentType.FHIR_JSON, + }[name]; + }, + }, json: jest.fn(async () => { return {}; }), @@ -114,7 +126,13 @@ describe('CLI Bulk Commands', () => { return { status: 200, - headers: { get: () => ContentType.FHIR_JSON }, + headers: { + get(name: string): string | undefined { + return { + 'content-type': ContentType.FHIR_JSON, + }[name]; + }, + }, json: jest.fn(async () => ({ transactionTime: '2023-05-18T22:55:31.280Z', request: 'https://api.medplum.com/fhir/R4/$export?_type=Observation', @@ -186,7 +204,13 @@ describe('CLI Bulk Commands', () => { fetch = jest.fn(async () => { return { status: 200, - headers: { get: () => ContentType.FHIR_JSON }, + headers: { + get(name: string): string | undefined { + return { + 'content-type': ContentType.FHIR_JSON, + }[name]; + }, + }, json: jest.fn(async () => ({ resourceType: 'Bundle', type: 'transaction-response', diff --git a/packages/cli/src/profiles.test.ts b/packages/cli/src/profiles.test.ts index 97e64c448c..47129e4237 100644 --- a/packages/cli/src/profiles.test.ts +++ b/packages/cli/src/profiles.test.ts @@ -32,7 +32,13 @@ describe('Profiles', () => { if (url.includes('/$export?_since=200')) { return { status: 200, - headers: { get: () => ContentType.FHIR_JSON }, + headers: { + get(name: string): string | undefined { + return { + 'content-type': ContentType.FHIR_JSON, + }[name]; + }, + }, json: jest.fn(async () => { return { resourceType: 'OperationOutcome', @@ -85,7 +91,13 @@ describe('Profiles', () => { count++; return { status: 202, - headers: { get: () => ContentType.FHIR_JSON }, + headers: { + get(name: string): string | undefined { + return { + 'content-type': ContentType.FHIR_JSON, + }[name]; + }, + }, json: jest.fn(async () => { return {}; }), @@ -95,7 +107,13 @@ describe('Profiles', () => { return { status: 200, - headers: { get: () => ContentType.FHIR_JSON }, + headers: { + get(name: string): string | undefined { + return { + 'content-type': ContentType.FHIR_JSON, + }[name]; + }, + }, json: jest.fn(async () => ({ transactionTime: '2023-05-18T22:55:31.280Z', request: 'https://api.medplum.com/fhir/R4/$export?_type=Observation', diff --git a/packages/cli/src/util/command.ts b/packages/cli/src/util/command.ts index 924312d7b6..adc4193276 100644 --- a/packages/cli/src/util/command.ts +++ b/packages/cli/src/util/command.ts @@ -15,7 +15,6 @@ export function createMedplumCommand(name: string): Command { .option('--audience ', 'Audience for JWT authentication') .option('--issuer ', 'Issuer for JWT authentication') .option('--private-key-path ', 'Private key path for JWT assertion') - .option('--audience ', 'Audience for JWT assertion') .option('-p, --profile ', 'Profile name') .option('-v --verbose', 'Verbose output') .addOption( diff --git a/packages/core/package.json b/packages/core/package.json index 36d5ac5549..f07563f1f1 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/core", - "version": "3.0.2", + "version": "3.0.4", "description": "Medplum TS/JS Library", "keywords": [ "medplum", @@ -55,8 +55,8 @@ "test": "jest" }, "devDependencies": { - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", "jest-websocket-mock": "2.5.0" }, "peerDependencies": { diff --git a/packages/core/src/access.ts b/packages/core/src/access.ts index f22a3b3555..8b1d67d0d9 100644 --- a/packages/core/src/access.ts +++ b/packages/core/src/access.ts @@ -1,6 +1,6 @@ import { AccessPolicy, AccessPolicyResource, Resource, ResourceType } from '@medplum/fhirtypes'; import { matchesSearchRequest } from './search/match'; -import { parseCriteriaAsSearchRequest } from './search/search'; +import { parseSearchRequest } from './search/search'; const universalAccessPolicy: AccessPolicyResource = { resourceType: '*', @@ -180,10 +180,7 @@ function matchesAccessPolicyResourcePolicy( // Deprecated - to be removed return false; } - if ( - resourcePolicy.criteria && - !matchesSearchRequest(resource, parseCriteriaAsSearchRequest(resourcePolicy.criteria)) - ) { + if (resourcePolicy.criteria && !matchesSearchRequest(resource, parseSearchRequest(resourcePolicy.criteria))) { return false; } return true; diff --git a/packages/core/src/bundle.test.ts b/packages/core/src/bundle.test.ts index 84ae7be53e..2a73d5859a 100644 --- a/packages/core/src/bundle.test.ts +++ b/packages/core/src/bundle.test.ts @@ -1,6 +1,7 @@ import { Bundle, BundleEntry, DiagnosticReport, Patient, Resource, Specimen } from '@medplum/fhirtypes'; import { convertContainedResourcesToBundle, convertToTransactionBundle } from './bundle'; -import { isUUID } from './utils'; +import { deepClone, isUUID } from './utils'; +import { getDataType } from './typeschema/types'; let jsonFile: any; @@ -214,6 +215,70 @@ describe('Bundle tests', () => { ], }); }); + + test('Preserve resource.meta', () => { + const patient: Patient = { + resourceType: 'Patient', + meta: { + account: { + reference: 'Organization/33333333-3333-3333-3333-333333333333', + display: 'Organization #3', + }, + author: { + reference: 'Practitioner/22222222-2222-2222-2222-222222222222', + display: 'Doctor', + }, + compartment: [ + { + reference: 'Project/11111111-2222-3333-4444-555555555555', + }, + { + reference: 'Patient/00000000-0000-0000-0000-000000000000', + }, + ], + extension: [{ url: 'https://example.com/Extension/meta-1', valueBoolean: true }], + id: 'some-id', + lastUpdated: '2024-02-14T21:47:11.777Z', + profile: ['http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient'], + project: '11111111-2222-3333-4444-555555555555', + security: [{ system: 'http://hl7.org/fhir/v3/Confidentiality', code: 'N' }], + source: 'https://example.com/source', + tag: [{ system: 'http://hl7.org/fhir/v3/ObservationValue', code: 'SUBSETTED' }], + versionId: '55555555-5555-5555-5555-555555555555', + }, + active: true, + }; + + const expected = deepClone(patient); + const meta = expected.meta; + if (meta === undefined) { + fail('Expected meta to be defined'); + } + + const removedKeys = ['project', 'versionId', 'lastUpdated', 'compartment', 'author']; + for (const key of Object.keys(getDataType('Meta').elements)) { + // make sure every possible element is defined in the test + expect((meta as any)[key]).toBeDefined(); + + if (removedKeys.includes(key)) { + delete (meta as any)[key]; + } + } + + const inputBundle: Bundle = { + resourceType: 'Bundle', + type: 'searchset', + entry: [ + { + fullUrl: 'https://example.com/Patient/00000000-0000-0000-0000-000000000000', + resource: patient, + }, + ], + }; + + const result = convertToTransactionBundle(inputBundle); + expect(result?.entry?.[0]?.resource).toEqual(expected); + }); }); describe('convertContainedResourcesToBundle', () => { diff --git a/packages/core/src/bundle.ts b/packages/core/src/bundle.ts index 5efae1ce93..b46ea6167c 100644 --- a/packages/core/src/bundle.ts +++ b/packages/core/src/bundle.ts @@ -17,7 +17,13 @@ export function convertToTransactionBundle(bundle: Bundle): Bundle { const idToUuid: Record = {}; bundle = deepClone(bundle); for (const entry of bundle.entry || []) { - delete entry.resource?.meta; + if (entry.resource?.meta !== undefined) { + delete entry.resource.meta.author; + delete entry.resource.meta.compartment; + delete entry.resource.meta.lastUpdated; + delete entry.resource.meta.project; + delete entry.resource.meta.versionId; + } const id = entry.resource?.id; if (id) { idToUuid[id] = generateId(); diff --git a/packages/core/src/client-subscriptions.test.ts b/packages/core/src/client-subscriptions.test.ts new file mode 100644 index 0000000000..22010ee331 --- /dev/null +++ b/packages/core/src/client-subscriptions.test.ts @@ -0,0 +1,129 @@ +import { Parameters, Patient } from '@medplum/fhirtypes'; +import WS from 'jest-websocket-mock'; +import { MedplumClient } from './client'; +import { createFakeJwt, mockFetchWithStatus } from './client-test-utils'; +import { SubscriptionEmitter, SubscriptionEventMap, SubscriptionManager } from './subscriptions'; + +const ONE_HOUR = 60 * 60 * 1000; +const MOCK_SUBSCRIPTION_ID = '7b081dd8-a2d2-40dd-9596-58a7305a73b0'; + +const fetch = mockFetchWithStatus((url: string, options?: { body: string }) => { + switch (url) { + // createResource + case 'https://api.medplum.com/fhir/R4/Subscription': + return [ + 201, + { + ...(options?.body ? JSON.parse(options?.body) : {}), + id: MOCK_SUBSCRIPTION_ID, + }, + ] as [number, string]; + // $get-ws-binding-token + case `https://api.medplum.com/fhir/R4/Subscription/${MOCK_SUBSCRIPTION_ID}/$get-ws-binding-token`: + return [ + 200, + { + resourceType: 'Parameters', + parameter: [ + { + name: 'token', + valueString: 'token-123', + }, + { + name: 'expiration', + valueDateTime: new Date(Date.now() + ONE_HOUR).toISOString(), + }, + { + name: 'websocket-url', + valueUrl: 'wss://example.com/ws/subscriptions-r4', + }, + ], + } as Parameters, + ]; + // Get profile + case 'https://api.medplum.com/auth/me': + return [200, { profile: { resourceType: 'Patient', id: '123' } as Patient }]; + default: + throw new Error('Invalid URL'); + } +}); + +describe('MedplumClient -- Subscriptions', () => { + let medplum: MedplumClient; + let warnMockFn: jest.Mock; + let originalWarn: typeof console.warn; + + beforeAll(async () => { + originalWarn = console.warn; + console.warn = warnMockFn = jest.fn(); + jest.useFakeTimers(); + + // @ts-expect-error Need this to be here even if we are not using it so that WS reqs don't fail + const _wsServer = new WS('wss://api.medplum.com/ws/subscriptions-r4', { jsonProtocol: true }); + }); + + afterAll(async () => { + console.warn = originalWarn; + jest.useRealTimers(); + }); + + beforeEach(async () => { + warnMockFn.mockClear(); + medplum = new MedplumClient({ fetch, accessToken: createFakeJwt({ client_id: '123', login_id: '123' }) }); + await medplum.getProfileAsync(); + }); + + // This should be a no-op + test('unsubscribeFromCriteria() -- no SubscriptionManager', async () => { + expect(() => medplum.unsubscribeFromCriteria('Communication')).not.toThrow(); + }); + + test('getSubscriptionManager()', () => { + expect(medplum.getSubscriptionManager()).toBeInstanceOf(SubscriptionManager); + }); + + test('getMasterSubscriptionEmitter()', () => { + expect(medplum.getMasterSubscriptionEmitter()).toBeInstanceOf(SubscriptionEmitter); + }); + + test('subscribeToCriteria()', async () => { + const emitter1 = medplum.subscribeToCriteria('Communication'); + expect(emitter1).toBeInstanceOf(SubscriptionEmitter); + + const emitter2 = medplum.subscribeToCriteria('Communication'); + expect(emitter2).toBeInstanceOf(SubscriptionEmitter); + expect(emitter1).toBe(emitter2); + + const connectEvent = await new Promise((resolve) => { + emitter1.addEventListener('connect', (event) => { + resolve(event); + }); + }); + expect(connectEvent?.type).toEqual('connect'); + expect(connectEvent?.payload?.subscriptionId).toEqual(MOCK_SUBSCRIPTION_ID); + }); + + test('unsubscribeFromCriteria() -- SubscriptionManager exists', async () => { + const emitter = medplum.subscribeToCriteria('Communication'); + + const connectEvent = await new Promise((resolve) => { + emitter.addEventListener('connect', (event) => { + resolve(event); + }); + }); + expect(connectEvent?.type).toEqual('connect'); + expect(connectEvent?.payload?.subscriptionId).toEqual(MOCK_SUBSCRIPTION_ID); + + const disconnectEvent = await new Promise((resolve) => { + emitter.addEventListener('disconnect', (event) => { + resolve(event); + }); + expect(() => medplum.unsubscribeFromCriteria('Communication')).not.toThrow(); + }); + expect(disconnectEvent?.type).toEqual('disconnect'); + expect(disconnectEvent?.payload?.subscriptionId).toEqual(MOCK_SUBSCRIPTION_ID); + + expect(() => medplum.unsubscribeFromCriteria('Communication')).not.toThrow(); + expect(console.warn).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/core/src/client-test-utils.ts b/packages/core/src/client-test-utils.ts index 765fd6294a..1e2c1eede6 100644 --- a/packages/core/src/client-test-utils.ts +++ b/packages/core/src/client-test-utils.ts @@ -1,7 +1,10 @@ -import { OperationOutcome } from '@medplum/fhirtypes'; -import { FetchLike } from './client'; +import { OperationOutcome, Practitioner, Resource } from '@medplum/fhirtypes'; +import { FetchLike, MedplumClient } from './client'; import { ContentType } from './contenttype'; -import { getStatus, isOperationOutcome } from './outcomes'; +import { generateId } from './crypto'; +import { OperationOutcomeError, badRequest, getStatus, isOperationOutcome } from './outcomes'; +import { ReadablePromise } from './readablepromise'; +import { ProfileResource } from './utils'; export function mockFetch( status: number, @@ -12,12 +15,105 @@ export function mockFetch( return jest.fn((url: string, options?: any) => { const response = bodyFn(url, options); const responseStatus = isOperationOutcome(response) ? getStatus(response) : status; - return Promise.resolve({ - ok: responseStatus < 400, - status: responseStatus, - headers: { get: () => contentType }, - blob: () => Promise.resolve(response), - json: () => Promise.resolve(response), - }); + return Promise.resolve(mockFetchResponse(responseStatus, response, { 'content-type': contentType })); }); } + +export function mockFetchWithStatus( + onFetch: (url: string, options?: any) => [number, any], + contentType = ContentType.FHIR_JSON +): FetchLike & jest.Mock { + return jest.fn((url: string, options?: any) => { + const [status, response] = onFetch(url, options); + const responseStatus = isOperationOutcome(response) ? getStatus(response) : status; + return Promise.resolve(mockFetchResponse(responseStatus, response, { 'content-type': contentType })); + }); +} + +export function mockFetchResponse(status: number, body: any, headers?: Record): Response { + const headersMap = new Map(); + if (headers) { + for (const [key, value] of Object.entries(headers)) { + headersMap.set(key, value); + } + } + if (!headersMap.has('content-type')) { + headersMap.set('content-type', ContentType.FHIR_JSON); + } + let streamRead = false; + const streamReader = async (): Promise => { + if (streamRead) { + throw new Error('Stream already read'); + } + streamRead = true; + return body; + }; + return { + ok: status < 400, + status, + headers: headersMap, + blob: streamReader, + json: streamReader, + } as unknown as Response; +} + +export class MockFhirRouter { + routes: Map Record>; + constructor() { + this.routes = new Map(); + } + + makeKey(method: 'GET' | 'POST', path: string): string { + return `${method} ${path}`; + } + + addRoute(method: 'GET' | 'POST', path: string, callback: () => Record): void { + this.routes.set(this.makeKey(method, path), callback); + } + + fetchRoute>(method: 'GET' | 'POST', path: string): T { + const key = this.makeKey(method, path); + if (!this.routes.has(key)) { + throw new OperationOutcomeError(badRequest('Invalid route')); + } + return (this.routes.get(key) as () => T)(); + } +} + +export interface MockClientOptions { + fetch?: FetchLike; +} + +export class MockMedplumClient extends MedplumClient { + router: MockFhirRouter; + profile: Practitioner; + nextResourceId: string; + + constructor(options?: MockClientOptions) { + // @ts-expect-error need to pass something for fetch otherwise MedplumClient ctor will complain + super({ fetch: options?.fetch ?? (() => undefined) }); + this.router = new MockFhirRouter(); + this.profile = { resourceType: 'Practitioner', id: generateId() } as Practitioner; + this.nextResourceId = 'DEFAULT_MOCK_ID'; + } + + get(url: string | URL, _options?: RequestInit): ReadablePromise { + return new ReadablePromise(Promise.resolve(this.router.fetchRoute('GET', url.toString()))); + } + + addNextResourceId(id: string): void { + this.nextResourceId = id; + } + + createResource(resource: T, _options?: RequestInit | undefined): Promise { + return Promise.resolve({ ...resource, id: this.nextResourceId }); + } + + getProfile(): ProfileResource | undefined { + return this.profile; + } +} + +export function createFakeJwt(claims: Record): string { + return 'header.' + window.btoa(JSON.stringify(claims)) + '.signature'; +} diff --git a/packages/core/src/client.test.ts b/packages/core/src/client.test.ts index 009affa6c8..02216385ef 100644 --- a/packages/core/src/client.test.ts +++ b/packages/core/src/client.test.ts @@ -13,9 +13,9 @@ import { NewProjectRequest, NewUserRequest, } from './client'; -import { mockFetch } from './client-test-utils'; +import { createFakeJwt, mockFetch, mockFetchResponse } from './client-test-utils'; import { ContentType } from './contenttype'; -import { OperationOutcomeError, notFound, unauthorized } from './outcomes'; +import { OperationOutcomeError, accepted, allOk, forbidden, notFound, unauthorized } from './outcomes'; import { MockAsyncClientStorage } from './storage'; import { getDataType, isDataTypeLoaded, isProfileLoaded } from './typeschema/types'; import { ProfileResource, createReference } from './utils'; @@ -66,8 +66,9 @@ const schemaResponse = { }; const patientProfileUrl = 'http://example.com/patient-profile'; +const patientProfileExtensionUrl = 'http://example.com/patient-profile-extension'; -const profileSchemaResponse = { +const profileSD = { resourceType: 'StructureDefinition', name: 'PatientProfile', url: patientProfileUrl, @@ -102,6 +103,31 @@ const profileSchemaResponse = { }, ], }, + { + path: 'Patient.extension', + sliceName: 'fancy', + type: [ + { + code: 'Extension', + profile: [patientProfileExtensionUrl], + }, + ], + }, + ], + }, +}; + +const profileExtensionSD = { + resourceType: 'StructureDefinition', + type: 'Extension', + derivation: 'constraint', + name: 'PatientProfile', + url: patientProfileExtensionUrl, + snapshot: { + element: [ + { + path: 'Extension', + }, ], }, }; @@ -1687,7 +1713,7 @@ describe('Client', () => { const result2 = await client.executeBot(bot.identifier?.[0] as Identifier, {}); expect(result2).toBeDefined(); expect(fetch).toHaveBeenCalledWith( - 'https://api.medplum.com/fhir/R4/Bot/$execute?identifier=https://example.com|123', + 'https://api.medplum.com/fhir/R4/Bot/$execute?identifier=https%3A%2F%2Fexample.com%7C123', expect.objectContaining({}) ); }); @@ -1709,7 +1735,7 @@ describe('Client', () => { test('requestProfileSchema', async () => { const fetch = mockFetch(200, { resourceType: 'Bundle', - entry: [{ resource: profileSchemaResponse }], + entry: [{ resource: profileSD }], }); const client = new MedplumClient({ fetch }); @@ -1721,7 +1747,28 @@ describe('Client', () => { await request1; expect(isProfileLoaded(patientProfileUrl)).toBe(true); - expect(getDataType(profileSchemaResponse.name, patientProfileUrl)).toBeDefined(); + expect(getDataType(profileSD.name, patientProfileUrl)).toBeDefined(); + }); + + test('requestProfileSchema expandProfile', async () => { + const fetch = mockFetch(200, { + resourceType: 'Bundle', + entry: [{ resource: profileSD }, { resource: profileExtensionSD }], + }); + + const client = new MedplumClient({ fetch }); + + // Issue two requests simultaneously + const request1 = client.requestProfileSchema(patientProfileUrl, { expandProfile: true }); + const request2 = client.requestProfileSchema(patientProfileUrl, { expandProfile: true }); + expect(request2).toBe(request1); + + await request1; + await request2; + expect(isProfileLoaded(patientProfileUrl)).toBe(true); + expect(isProfileLoaded(patientProfileExtensionUrl)).toBe(true); + expect(getDataType(profileSD.name, patientProfileUrl)).toBeDefined(); + expect(getDataType(profileExtensionSD.name, patientProfileExtensionUrl)).toBeDefined(); }); test('Search', async () => { @@ -2240,18 +2287,7 @@ describe('Client', () => { test('Auto batch single request error', async () => { const fetch = mockFetch(404, notFound); - (fetch as unknown as jest.Mock).mockImplementation(() => ({ - status: 404, - json: () => notFound, - headers: { - get(name: string): string | undefined { - return { - 'content-type': ContentType.FHIR_JSON, - }[name]; - }, - }, - })); - const medplum = new MedplumClient({ fetch: fetch, autoBatchTime: 100 }); + const medplum = new MedplumClient({ fetch, autoBatchTime: 100 }); try { await medplum.readResource('Patient', 'xyz-not-found'); @@ -2393,84 +2429,32 @@ describe('Client', () => { let count = 0; fetch = jest.fn(async (url) => { if (url.includes('/$export?_since=200')) { - return { - status: 200, - headers: { get: () => ContentType.FHIR_JSON }, - json: jest.fn(async () => { - return { - resourceType: 'OperationOutcome', - id: 'accepted', - issue: [ - { - severity: 'information', - code: 'informational', - details: { - text: 'Accepted', - }, - }, - ], - }; - }), - }; + return mockFetchResponse(200, accepted('bulkdata/id/status'), { 'content-location': 'bulkdata/id/status' }); } if (url.includes('/$export')) { - return { - status: 202, - json: jest.fn(async () => { - return { - resourceType: 'OperationOutcome', - id: 'accepted', - issue: [ - { - severity: 'information', - code: 'informational', - details: { - text: 'Accepted', - }, - }, - ], - }; - }), - headers: { - get(name: string): string | undefined { - return { - 'content-type': ContentType.FHIR_JSON, - 'content-location': 'bulkdata/id/status', - }[name]; - }, - }, - }; + return mockFetchResponse(202, accepted('bulkdata/id/status'), { 'content-location': 'bulkdata/id/status' }); } if (url.includes('bulkdata/id/status')) { if (count < 1) { count++; - return { - status: 202, - json: jest.fn(async () => { - return {}; - }), - }; + return mockFetchResponse(202, {}); } } - return { - status: 200, - headers: { get: () => ContentType.FHIR_JSON }, - json: jest.fn(async () => ({ - transactionTime: '2023-05-18T22:55:31.280Z', - request: 'https://api.medplum.com/fhir/R4/$export?_type=Observation', - requiresAccessToken: false, - output: [ - { - type: 'ProjectMembership', - url: 'https://api.medplum.com/storage/TEST', - }, - ], - error: [], - })), - }; + return mockFetchResponse(200, { + transactionTime: '2023-05-18T22:55:31.280Z', + request: 'https://api.medplum.com/fhir/R4/$export?_type=Observation', + requiresAccessToken: false, + output: [ + { + type: 'ProjectMembership', + url: 'https://api.medplum.com/storage/TEST', + }, + ], + error: [], + }); }); }); @@ -2524,29 +2508,7 @@ describe('Client', () => { }); test('Kick off missing content-location', async () => { - const fetch = jest.fn(async () => { - return { - status: 202, - json: jest.fn(async () => { - return { - resourceType: 'OperationOutcome', - id: 'accepted', - issue: [ - { - severity: 'information', - code: 'informational', - details: { - text: 'Accepted', - }, - }, - ], - }; - }), - headers: { - get: (key: string) => (key === 'content-type' ? ContentType.FHIR_JSON : null), - }, - }; - }); + const fetch = mockFetch(202, allOk); const medplum = new MedplumClient({ fetch }); const response = await medplum.bulkExport(); @@ -2573,11 +2535,88 @@ describe('Client', () => { expect((err as Error).message).toBe('Not found'); } }); + + test('Poll after token refresh', async () => { + const clientId = randomUUID(); + const clientSecret = randomUUID(); + const statusUrl = 'status-' + randomUUID(); + const locationUrl = 'location-' + randomUUID(); + + const mockTokens = { + access_token: createFakeJwt({ client_id: clientId, login_id: '123' }), + refresh_token: createFakeJwt({ client_id: clientId }), + profile: { reference: 'Patient/123' }, + }; + + const mockMe = { + project: { resourceType: 'Project', id: '123' }, + membership: { resourceType: 'ProjectMembership', id: '123' }, + profile: { resouceType: 'Practitioner', id: '123' }, + config: { resourceType: 'UserConfiguration', id: '123' }, + accessPolicy: { resourceType: 'AccessPolicy', id: '123' }, + }; + + let count = 0; + + const mockFetch = async (url: string, options: any): Promise => { + count++; + switch (count) { + case 1: + // First, handle the initial startClientLogin client credentials flow + expect(options.method).toBe('POST'); + expect(url).toBe('https://api.medplum.com/oauth2/token'); + return mockFetchResponse(200, mockTokens); + case 2: + // MedplumClient will automatically fetch the user profile after token refresh + expect(options.method).toBe('GET'); + expect(url).toBe('https://api.medplum.com/auth/me'); + return mockFetchResponse(200, mockMe); + case 3: + // Next, handle the initial bulk export - mock an expired token response + expect(options.method).toBe('POST'); + expect(url).toBe('https://api.medplum.com/fhir/R4/$export'); + return mockFetchResponse(401, forbidden); + case 4: + // Now MedplumClient will try to automatically refresh the token + expect(options.method).toBe('POST'); + expect(url).toBe('https://api.medplum.com/oauth2/token'); + return mockFetchResponse(200, mockTokens); + case 5: + // And then MedplumClient will automatically fetch the user profile again + expect(options.method).toBe('GET'); + expect(url).toBe('https://api.medplum.com/auth/me'); + return mockFetchResponse(200, mockMe); + case 6: + // Ok, whew, we are refreshed, so we can finally get the bulk export + // However, the bulk export isn't "done", so return "Accepted" + expect(options.method).toBe('POST'); + expect(url).toBe('https://api.medplum.com/fhir/R4/$export'); + return mockFetchResponse(202, accepted(statusUrl)); + case 7: + // Report status complete, and send the location of the bulk export + expect(options.method).toBe('GET'); + expect(url).toBe('https://api.medplum.com/' + statusUrl); + return mockFetchResponse(201, {}, { location: locationUrl }); + case 8: + // What a journey! Finally, we can get the contents of the bulk export + expect(options.method).toBe('GET'); + expect(url).toBe('https://api.medplum.com/' + locationUrl); + return mockFetchResponse(200, { resourceType: 'Bundle' }); + } + throw new Error('Unexpected fetch call: ' + url); + }; + + const medplum = new MedplumClient({ fetch: mockFetch }); + await medplum.startClientLogin(clientId, clientSecret); + const result = await medplum.bulkExport(); + expect(result).toMatchObject({ resourceType: 'Bundle' }); + }); }); describe('Downloading resources', () => { const baseUrl = 'https://api.medplum.com/'; const fhirUrlPath = 'fhir/R4/'; + const accessToken = 'fake'; let fetch: FetchLike; let client: MedplumClient; @@ -2586,6 +2625,7 @@ describe('Client', () => { text: () => Promise.resolve(url), })); client = new MedplumClient({ fetch, baseUrl, fhirUrlPath }); + client.setAccessToken(accessToken); }); test('Downloading resources via URL', async () => { @@ -2595,6 +2635,7 @@ describe('Client', () => { expect.objectContaining({ headers: { Accept: DEFAULT_ACCEPT, + Authorization: `Bearer ${accessToken}`, 'X-Medplum': 'extended', }, }) @@ -2609,6 +2650,7 @@ describe('Client', () => { expect.objectContaining({ headers: { Accept: DEFAULT_ACCEPT, + Authorization: `Bearer ${accessToken}`, 'X-Medplum': 'extended', }, }) @@ -2646,66 +2688,34 @@ describe('Client', () => { const fetch = jest.fn(); // First time, return 202 Accepted with Content-Location - fetch.mockImplementationOnce(async () => ({ - ok: true, - status: 202, - headers: { - get: (key: string) => { - if (key.toLowerCase() === 'content-location') { - return 'https://example.com/content-location/1'; - } - if (key.toLowerCase() === 'content-type') { - return ContentType.FHIR_JSON; - } - return null; - }, - }, - json: async () => ({}), - })); + fetch.mockImplementationOnce(async () => + mockFetchResponse( + 202, + {}, + { + 'content-location': 'https://example.com/content-location/1', + } + ) + ); // Second time, return 202 Accepted with Content-Location - fetch.mockImplementationOnce(async () => ({ - ok: true, - status: 202, - headers: { - get: (key: string) => { - if (key.toLowerCase() === 'content-location') { - return 'https://example.com/content-location/1'; - } - if (key.toLowerCase() === 'content-type') { - return ContentType.FHIR_JSON; - } - return null; - }, - }, - json: async () => ({}), - })); + fetch.mockImplementationOnce(async () => + mockFetchResponse( + 202, + {}, + { + 'content-location': 'https://example.com/content-location/1', + } + ) + ); // Third time, return 201 Created with Location - fetch.mockImplementationOnce(async () => ({ - ok: true, - status: 201, - headers: { - get: (key: string) => { - if (key.toLowerCase() === 'location') { - return 'https://example.com/location/1'; - } - if (key.toLowerCase() === 'content-type') { - return ContentType.FHIR_JSON; - } - return null; - }, - }, - json: async () => ({}), - })); + fetch.mockImplementationOnce(async () => + mockFetchResponse(201, {}, { location: 'https://example.com/location/1' }) + ); - // Fourth time, return 200 with JSON - fetch.mockImplementationOnce(async () => ({ - ok: true, - status: 201, - headers: { get: () => ContentType.FHIR_JSON }, - json: async () => ({ resourceType: 'Patient' }), - })); + // Fourth time, return 201 with JSON + fetch.mockImplementationOnce(async () => mockFetchResponse(201, { resourceType: 'Patient' })); const client = new MedplumClient({ fetch }); const response = await client.startAsyncRequest('/test', { method: 'POST', body: '{}' }); @@ -2782,10 +2792,6 @@ function createPdf( }); } -function createFakeJwt(claims: Record): string { - return 'header.' + window.btoa(JSON.stringify(claims)) + '.signature'; -} - function fail(message: string): never { throw new Error(message); } diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 7af60a484b..3b8537936e 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -66,14 +66,16 @@ import { } from './outcomes'; import { ReadablePromise } from './readablepromise'; import { ClientStorage, IClientStorage } from './storage'; +import { SubscriptionEmitter, SubscriptionManager } from './subscriptions'; import { indexSearchParameter } from './types'; -import { indexStructureDefinitionBundle, isDataTypeLoaded, isProfileLoaded } from './typeschema/types'; +import { indexStructureDefinitionBundle, isDataTypeLoaded, isProfileLoaded, loadDataType } from './typeschema/types'; import { CodeChallengeMethod, ProfileResource, arrayBufferToBase64, createReference, getReferenceString, + getWebSocketUrl, resolveId, sleep, } from './utils'; @@ -407,6 +409,7 @@ export interface BotEvent; + readonly traceId?: string; } export interface InviteRequest { @@ -518,6 +521,11 @@ interface AutoBatchEntry { readonly reject: (reason: any) => void; } +interface RequestState { + statusUrl?: string; + pollCount?: number; +} + /** * OAuth 2.0 Grant Type Identifiers * Standard identifiers: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07#name-grant-types @@ -590,6 +598,11 @@ export interface ValueSetExpandParams { count?: number; } +export interface RequestProfileSchemaOptions { + /** (optional) Whether to include nested profiles, e.g. from extensions. Defaults to false. */ + expandProfile?: boolean; +} + /** * The MedplumClient class provides a client for the Medplum FHIR server. * @@ -662,6 +675,7 @@ export class MedplumClient extends EventTarget { private readonly onUnauthenticated?: () => void; private readonly autoBatchTime: number; private readonly autoBatchQueue: AutoBatchEntry[] | undefined; + private subscriptionManager?: SubscriptionManager; private medplumServer?: boolean; private clientId?: string; private clientSecret?: string; @@ -1534,7 +1548,7 @@ export class MedplumClient extends EventTarget { * @param resourceType - The FHIR resource type. * @param id - The resource ID. * @param options - Optional fetch options. - * @returns The resource if available; undefined otherwise. + * @returns The resource if available. */ readResource( resourceType: K, @@ -1562,7 +1576,7 @@ export class MedplumClient extends EventTarget { * @category Read * @param reference - The FHIR reference object. * @param options - Optional fetch options. - * @returns The resource if available; undefined otherwise. + * @returns The resource if available. */ readReference(reference: Reference, options?: RequestInit): ReadablePromise { const refString = reference.reference; @@ -1658,32 +1672,45 @@ export class MedplumClient extends EventTarget { * If the schema is already cached, the promise is resolved immediately. * @category Schema * @param profileUrl - The FHIR URL of the profile - * @returns Promise to a schema with the requested profile. + * @param options - (optional) Additional options + * @returns Promise with an array of URLs of the profile(s) loaded. */ - requestProfileSchema(profileUrl: string): Promise { - if (isProfileLoaded(profileUrl)) { - return Promise.resolve(); + requestProfileSchema(profileUrl: string, options?: RequestProfileSchemaOptions): Promise { + if (!options?.expandProfile && isProfileLoaded(profileUrl)) { + return Promise.resolve([profileUrl]); } - const cacheKey = profileUrl + '-requestSchema'; + const cacheKey = profileUrl + '-requestSchema' + (options?.expandProfile ? '-nested' : ''); const cached = this.getCacheEntry(cacheKey, undefined); if (cached) { return cached.value; } - const promise = new ReadablePromise( + const promise = new ReadablePromise( (async () => { - // Just sort by lastUpdated. Ideally, it would also be based on a logical sort of version - // See https://hl7.org/fhir/references.html#canonical-matching for more discussion - const sd = await this.searchOne('StructureDefinition', { - url: profileUrl, - _sort: '-_lastUpdated', - }); - - if (!sd) { - console.warn(`No StructureDefinition found for ${profileUrl}!`); + if (options?.expandProfile) { + const url = this.fhirUrl('StructureDefinition', '$expand-profile'); + url.search = new URLSearchParams({ url: profileUrl }).toString(); + const sdBundle = await this.get>(url.toString()); + return bundleToResourceArray(sdBundle).map((sd) => { + loadDataType(sd, sd.url); + return sd.url; + }); } else { + // Just sort by lastUpdated. Ideally, it would also be based on a logical sort of version + // See https://hl7.org/fhir/references.html#canonical-matching for more discussion + const sd = await this.searchOne('StructureDefinition', { + url: profileUrl, + _sort: '-_lastUpdated', + }); + + if (!sd) { + console.warn(`No StructureDefinition found for ${profileUrl}!`); + return []; + } + indexStructureDefinitionBundle([sd], profileUrl); + return [profileUrl]; } })() ); @@ -1736,7 +1763,7 @@ export class MedplumClient extends EventTarget { * @param id - The resource ID. * @param vid - The version ID. * @param options - Optional fetch options. - * @returns The resource if available; undefined otherwise. + * @returns The resource if available. */ readVersion( resourceType: K, @@ -2204,13 +2231,14 @@ export class MedplumClient extends EventTarget { contentType?: string, options?: RequestInit ): Promise { - let url; + let url: URL; if (typeof idOrIdentifier === 'string') { const id = idOrIdentifier; url = this.fhirUrl('Bot', id, '$execute'); } else { const identifier = idOrIdentifier; - url = this.fhirUrl('Bot', '$execute') + `?identifier=${identifier.system}|${identifier.value}`; + url = this.fhirUrl('Bot', '$execute'); + url.searchParams.set('identifier', identifier.system + '|' + identifier.value); } return this.post(url, body, contentType, options); } @@ -2397,7 +2425,7 @@ export class MedplumClient extends EventTarget { */ pushToAgent( agent: Agent | Reference, - destination: Device | Reference, + destination: Device | Reference | string, body: any, contentType?: string, waitForResponse?: boolean, @@ -2406,7 +2434,7 @@ export class MedplumClient extends EventTarget { return this.post( this.fhirUrl('Agent', resolveId(agent) as string, '$push'), { - destination: getReferenceString(destination), + destination: typeof destination === 'string' ? destination : getReferenceString(destination), body, contentType, waitForResponse, @@ -2681,16 +2709,7 @@ export class MedplumClient extends EventTarget { const headers = options.headers as Record; headers['Prefer'] = 'respond-async'; - const response = await this.fetchWithRetry(url, options); - - if (response.status === 202) { - const contentLocation = await tryGetContentLocation(response); - if (contentLocation) { - return this.pollStatus(contentLocation); - } - } - - return this.parseResponse(response, 'POST', url); + return this.request('POST', url, options); } // @@ -2755,9 +2774,15 @@ export class MedplumClient extends EventTarget { * @param method - The HTTP method (GET, POST, etc). * @param url - The target URL. * @param options - Optional fetch request init options. + * @param state - Optional request state. * @returns The JSON content body if available. */ - private async request(method: string, url: string, options: RequestInit = {}): Promise { + private async request( + method: string, + url: string, + options: RequestInit = {}, + state: RequestState = {} + ): Promise { await this.refreshIfExpired(); options.method = method; @@ -2765,15 +2790,6 @@ export class MedplumClient extends EventTarget { const response = await this.fetchWithRetry(url, options); - return this.parseResponse(response, method, url, options); - } - - private async parseResponse( - response: Response, - method: string, - url: string, - options: RequestInit = {} - ): Promise { if (response.status === 401) { // Refresh and try again return this.handleUnauthenticated(method, url, options); @@ -2788,16 +2804,40 @@ export class MedplumClient extends EventTarget { const isJson = contentType?.includes('json'); if (response.status === 404 && !isJson) { + // Special case for non-JSON 404 responses + // In the common case, the 404 response will include an OperationOutcome in JSON with additional details. + // In the non-JSON case, we can't parse the response, so we'll just throw a generic "Not Found" error. throw new OperationOutcomeError(notFound); } - const contentLocation = response.headers.get('content-location'); + const obj = await this.parseBody(response, isJson); + const redirectMode = options.redirect ?? this.options.redirect; - if (response.status === 201 && contentLocation && redirectMode === 'follow') { - // Follow redirect - return this.request('GET', contentLocation, { ...options, body: undefined }); + if ((response.status === 200 || response.status === 201) && redirectMode === 'follow') { + const contentLocation = await tryGetContentLocation(response, obj); + if (contentLocation) { + // Follow redirect + return this.request('GET', contentLocation, { ...options, body: undefined }); + } + } + + const preferMode = (options.headers as Record | undefined)?.['Prefer']; + if (response.status === 202 && preferMode === 'respond-async') { + const contentLocation = await tryGetContentLocation(response, obj); + const statusUrl = contentLocation ?? state.statusUrl; + if (statusUrl) { + return this.pollStatus(statusUrl, options, state); + } + } + + if (response.status >= 400) { + throw new OperationOutcomeError(normalizeOperationOutcome(obj)); } + return obj; + } + + private async parseBody(response: Response, isJson: boolean | undefined): Promise { let obj: any = undefined; if (isJson) { try { @@ -2809,11 +2849,6 @@ export class MedplumClient extends EventTarget { } else { obj = await response.text(); } - - if (response.status >= 400) { - throw new OperationOutcomeError(normalizeOperationOutcome(obj)); - } - return obj; } @@ -2863,29 +2898,19 @@ export class MedplumClient extends EventTarget { } } - private async pollStatus(statusUrl: string): Promise { - let checkStatus = true; - let resultResponse; - const retryDelay = 2000; - - while (checkStatus) { - const fetchOptions = {}; - this.addFetchOptionsDefaults(fetchOptions); - const statusResponse = await this.fetchWithRetry(statusUrl, fetchOptions); - if (statusResponse.status !== 202) { - checkStatus = false; - resultResponse = statusResponse; - - if (statusResponse.status === 201) { - const contentLocation = await tryGetContentLocation(statusResponse); - if (contentLocation) { - resultResponse = await this.fetchWithRetry(contentLocation, fetchOptions); - } - } - } + private async pollStatus(statusUrl: string, options: RequestInit, state: RequestState): Promise { + if (state.pollCount === undefined) { + // First request - try request immediately + options.redirect = 'follow'; + state.statusUrl = statusUrl; + state.pollCount = 1; + } else { + // Subsequent requests - wait and retry + const retryDelay = 1000; await sleep(retryDelay); + state.pollCount++; } - return this.parseResponse(resultResponse as Response, 'POST', statusUrl); + return this.request('GET', statusUrl, options, state); } /** @@ -3485,6 +3510,91 @@ export class MedplumClient extends EventTarget { throw err; } } + + /** + * Gets the `SubscriptionManager` for WebSocket subscriptions. + * + * @category Subscriptions + * @returns the `SubscriptionManager` for this client. + */ + getSubscriptionManager(): SubscriptionManager { + if (!this.subscriptionManager) { + this.subscriptionManager = new SubscriptionManager(this, getWebSocketUrl('/ws/subscriptions-r4', this.baseUrl)); + } + return this.subscriptionManager; + } + + /** + * Subscribes to a given criteria, listening to notifications over WebSockets. + * + * This uses Medplum's `WebSocket Subscriptions` under the hood. + * + * A `SubscriptionEmitter` is returned from this function, which can be used to listen for updates to resources described by the given criteria. + * + * When subscribing to the same criteria multiple times, the same `SubscriptionEmitter` will be returned, and a reference count will be incremented. + * + * ----- + * @example + * ```ts + * const emitter = medplum.subscribeToCriteria('Communication'); + * + * emitter.addEventListener('message', (bundle: Bundle) => { + * // Called when a `Communication` resource is created or modified + * console.log(bundle?.entry?.[1]?.resource); // Logs the `Communication` resource that was updated + * }); + * ``` + * + * @category Subscriptions + * @param criteria - The criteria to subscribe to. + * @returns a `SubscriptionEmitter` that emits `Bundle` resources containing changes to resources based on the given criteria. + */ + subscribeToCriteria(criteria: string): SubscriptionEmitter { + return this.getSubscriptionManager().addCriteria(criteria); + } + + /** + * Unsubscribes from the given criteria. + * + * When called the same amount of times as proceeding calls to `subscribeToCriteria` on a given `criteria`, + * the criteria is fully removed from the `SubscriptionManager`. + * + * @category Subscriptions + * @param criteria - The criteria to unsubscribe from. + */ + unsubscribeFromCriteria(criteria: string): void { + if (!this.subscriptionManager) { + return; + } + this.subscriptionManager.removeCriteria(criteria); + if (this.subscriptionManager.getCriteriaCount() === 0) { + this.subscriptionManager.closeWebSocket(); + } + } + + /** + * Get the master `SubscriptionEmitter` for the `SubscriptionManager`. + * + * The master `SubscriptionEmitter` gets messages for all subscribed `criteria` as well as WebSocket errors, `connect` and `disconnect` events, and the `close` event. + * + * It can also be used to listen for `heartbeat` messages. + * + *------ + * @example + * ### Listening for `heartbeat`: + * ```ts + * const masterEmitter = medplum.getMasterSubscriptionEmitter(); + * + * masterEmitter.addEventListener('heartbeat', (bundle: Bundle) => { + * console.log(bundle?.entry?.[0]?.resource); // A `SubscriptionStatus` of type `heartbeat` + * }); + * + * ``` + * @category Subscriptions + * @returns the master `SubscriptionEmitter` from the `SubscriptionManager`. + */ + getMasterSubscriptionEmitter(): SubscriptionEmitter { + return this.getSubscriptionManager().getMasterEmitter(); + } } /** @@ -3541,15 +3651,28 @@ function concatUrls(baseUrl: string, url: string): string { * most authoritative source for the content location. If this header is * not present, it falls back to the "Location" HTTP header. * + * Note that the FHIR spec does not follow the traditional HTTP semantics of "Content-Location" and "Location". + * "Content-Location" is not typically used with HTTP 202 responses because the content itself isn't available at the time of the response. + * However, the FHIR spec explicitly recommends it: + * + * 3.2.6.1.2 Kick-off Request + * 3.2.6.1.2.0.3 Response - Success + * HTTP Status Code of 202 Accepted + * Content-Location header with the absolute URL of an endpoint for subsequent status requests (polling location) + * + * Source: https://hl7.org/fhir/async-bulk.html + * * In cases where neither of these headers are available (for instance, * due to CORS restrictions), it attempts to retrieve the content location * from the 'diagnostics' field of the first issue in an OperationOutcome object * present in the response body. If all attempts fail, the function returns 'undefined'. + * * @async * @param response - The HTTP response object from which to extract the content location. + * @param body - The response body. * @returns A Promise that resolves to the content location string if it is found, or 'undefined' if the content location cannot be determined from the response. */ -async function tryGetContentLocation(response: Response): Promise { +async function tryGetContentLocation(response: Response, body: any): Promise { // Accepted content location can come from multiple sources // The authoritative source is the "Content-Location" HTTP header. const contentLocation = response.headers.get('content-location'); @@ -3565,7 +3688,6 @@ async function tryGetContentLocation(response: Response): Promise(sd)) { + return false; + } + return sd.resourceType === 'StructureDefinition'; +} + +function getSlicedElement( + schema: InternalTypeSchema, + slicedElementKey: string +): InternalSchemaElement & { slicing: SlicingRules } { + const slicedElement = schema.elements[slicedElementKey]; + if (!isPopulated(slicedElement)) { + fail(`Expected ${slicedElementKey} element to be defined`); + } + if (!isPopulated(slicedElement.slicing)) { + fail(`Expected slicing to exist on ${slicedElementKey} element`); + } + + return slicedElement as InternalSchemaElement & { slicing: SlicingRules }; +} +function getSlice(schema: InternalTypeSchema, slicedElementKey: string, sliceName: string): SliceDefinition { + const slicedElement = getSlicedElement(schema, slicedElementKey); + const slice = slicedElement.slicing?.slices.find((s) => s.name === sliceName); + if (!isPopulated(slice)) { + fail(`Expected ${sliceName} slice to be defined`); + } + + return slice; +} + +describe('apply default values', () => { + let USCoreStructureDefinitions: StructureDefinition[]; + beforeAll(() => { + USCoreStructureDefinitions = readJson('fhir/r4/testing/uscore-v5.0.1-structuredefinitions.json'); + }); + + function loadProfiles(profileUrls: string[]): void { + const sds: StructureDefinition[] = profileUrls + .map((profileUrl) => { + return USCoreStructureDefinitions.find((sd) => sd.url === profileUrl); + }) + .filter(isStructureDefinition); + + expect(sds.length).toEqual(profileUrls.length); + + for (const sd of sds) { + loadDataType(sd, sd?.url); + } + } + + describe('US Blood Pressure', () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-blood-pressure`; + const profileUrls = [profileUrl]; + + let schema: InternalTypeSchema; + + beforeAll(() => { + loadProfiles(profileUrls); + schema = tryGetProfile(profileUrl) as InternalTypeSchema; + if (!schema) { + fail(`Failed to load schema for ${profileUrl}`); + } + }); + + test('new Blood Pressure observation', async () => { + // casting to avoid specifying any required (according to typescript) fields + // since populating them is the point of the code being tested + const resource: Observation = { resourceType: 'Observation' } as Observation; + const withDefaults = applyDefaultValuesToResource(resource, schema); + + // fixed values in Observation.component.value[x] excluded since value[x] itself is optional (min === 0) + // In other words, { valueQuantity: {code: "mm[Hg]", system: "http://unitsofmeasure.org"} } should NOT be included + // in either component + expect(withDefaults).toEqual({ + resourceType: 'Observation', + category: [ + { + coding: [ + { + code: 'vital-signs', + system: 'http://terminology.hl7.org/CodeSystem/observation-category', + }, + ], + }, + ], + code: { + coding: [ + { + system: 'http://loinc.org', + code: '85354-9', + }, + ], + }, + component: [ + { + code: { + coding: [ + { + system: 'http://loinc.org', + code: '8480-6', + }, + ], + }, + }, + { + code: { + coding: [ + { + system: 'http://loinc.org', + code: '8462-4', + }, + ], + }, + }, + ], + }); + }); + + describe('required values within optional element', () => { + test('value for Observation.component.value[x] in systolic slice', () => { + const slice = getSlice(schema, 'component', 'systolic'); + expect(slice.elements['value[x]'].min).toEqual(0); + + const result = applyDefaultValuesToElement(Object.create(null), slice.elements, 'value[x]'); + expect(result).toEqual({ code: 'mm[Hg]', system: 'http://unitsofmeasure.org' }); + }); + + test('value for Observation.component.value[x] in systolic slice with visitor', () => { + const slice = getSlice(schema, 'component', 'systolic'); + const result = applyDefaultValuesToElementWithVisitor( + undefined, + 'Observation.component.value[x]', + slice.elements['value[x]'], + slice.elements, + schema + ); + expect(result).toEqual({ code: 'mm[Hg]', system: 'http://unitsofmeasure.org' }); + }); + }); + }); + + describe('US Core Patient', () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`; + const raceExtensionUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-race`; + const ethnicityExtensionUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-ethnicity`; + const profileUrls = [ + profileUrl, + raceExtensionUrl, + ethnicityExtensionUrl, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-birthsex`, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-genderIdentity`, + ]; + + let schema: InternalTypeSchema; + + beforeAll(() => { + loadProfiles(profileUrls); + schema = tryGetProfile(profileUrl) as InternalTypeSchema; + if (!schema) { + fail(`Failed to load schema for ${profileUrl}`); + } + }); + + test('new Patient has no fixed/pattern values', async () => { + const resource: Patient = { resourceType: 'Patient' }; + const withDefaults = applyDefaultValuesToResource(resource, schema); + + expect(withDefaults).toEqual({ resourceType: 'Patient' }); + // For now, a different object is returned by design + expect(withDefaults).not.toBe(resource); + }); + + test('HomerSimpsonUSCorePatient', async () => { + const resource = getComplexUSCorePatient(); + const withDefaults = applyDefaultValuesToResource(resource, schema); + + const expected = getComplexUSCorePatient(); + + // Prepare expected value + // Expect stub values for a slice named 'text' to have been added for race and ethnicity extensions + [ethnicityExtensionUrl, raceExtensionUrl].forEach((extUrl) => { + const ext = expected.extension?.find((e) => e.url === extUrl); + if (ext?.extension === undefined) { + fail(`expected ${extUrl} extensions to exist`); + } + + const textExt = ext.extension?.find((e) => e.url === 'text'); + expect(textExt).toBeUndefined(); + ext.extension.push({ url: 'text' }); + }); + + expect(withDefaults).toEqual(expected); + }); + + describe('fixed/pattern values within non-required extension slice entry', () => { + test('new race extension entry', () => { + const sliceSchema = tryGetProfile(raceExtensionUrl) as InternalTypeSchema; + if (!sliceSchema) { + fail(`Failed to load schema for ${raceExtensionUrl}`); + } + const result = applyDefaultValuesToElement(Object.create(null), sliceSchema.elements); + expect(result).toEqual({ url: raceExtensionUrl }); + }); + + test('new race extension entry with visitor', () => { + const slicedElement = getSlicedElement(schema, 'extension'); + const slice = getSlice(schema, 'extension', 'race'); + const result = getDefaultValuesForNewSliceEntry('extension', slice, slicedElement.slicing, schema); + expect(result).toEqual({ url: raceExtensionUrl }); + }); + }); + + test('applyFixedOrPatternValue with intermediate elements undefined', () => { + const elem = schema.elements['identifier.value']; + expect(elem).toBeDefined(); + expect(elem.fixed).toBeUndefined(); + // define a fake fixed value + elem.fixed = { type: 'string', value: '42' }; + const result = applyFixedOrPatternValue({}, 'identifier.value', elem, schema.elements); + expect(result).toEqual({ identifier: [{ value: '42' }] }); + + delete elem.fixed; + expect(elem.fixed).toBeUndefined(); + }); + + test('applyFixedOrPatternValue with intermediate elements undefined', () => { + const elem = schema.elements['identifier.value']; + expect(elem).toBeDefined(); + expect(elem.fixed).toBeUndefined(); + // define a fake fixed value + elem.fixed = { type: 'string', value: '42' }; + const result = applyFixedOrPatternValue({}, 'identifier.value', elem, schema.elements); + expect(result).toEqual({ identifier: [{ value: '42' }] }); + + delete elem.fixed; + expect(elem.fixed).toBeUndefined(); + }); + + test('applyFixedOrPatternValue on choice of types', () => { + const key = 'multipleBirth[x]'; + const originalElem = schema.elements[key]; + expect(originalElem).toBeDefined(); + + schema.elements[key] = { + ...originalElem, + fixed: { + type: 'integer', + value: 2, + }, + type: [ + { + code: 'integer', + targetProfile: undefined, + profile: undefined, + }, + ], + }; + + const elem = schema.elements[key]; + const result = applyFixedOrPatternValue({}, key, elem, schema.elements); + expect(result).toEqual({ multipleBirthInteger: 2 }); + + schema.elements[key] = originalElem; + }); + + test('applyFixedOrPatternValue with non-empty array', () => { + const key = 'maritalStatus'; + const elem = schema.elements[key]; + expect(elem).toBeDefined(); + expect(elem.pattern).toBeUndefined(); + + // define a fake pattern value + elem.pattern = { + type: 'CodeableConcept', + value: { + coding: [ + { + system: 'http://terminology.hl7.org/CodeSystem/v3-MaritalStatus', + code: 'UNK', + }, + ], + }, + }; + + expect(applyFixedOrPatternValue({}, key, elem, schema.elements)).toEqual({ + maritalStatus: { coding: [{ system: 'http://terminology.hl7.org/CodeSystem/v3-MaritalStatus', code: 'UNK' }] }, + }); + + expect(applyFixedOrPatternValue({ maritalStatus: { coding: [] } }, key, elem, schema.elements)).toEqual({ + maritalStatus: { coding: [{ system: 'http://terminology.hl7.org/CodeSystem/v3-MaritalStatus', code: 'UNK' }] }, + }); + + expect(applyFixedOrPatternValue({ maritalStatus: { coding: [{}] } }, key, elem, schema.elements)).toEqual({ + maritalStatus: { coding: [{}] }, + }); + + // degenerate case; maritalStatus should NOT be an array + expect(applyFixedOrPatternValue({ maritalStatus: [] }, key, elem, schema.elements)).toEqual({ + maritalStatus: [], + }); + + // unexpected pattern value type + elem.pattern = { ...elem.pattern, value: 42 }; + expect(applyFixedOrPatternValue({}, key, elem, schema.elements)).toEqual({}); + + delete elem.pattern; + expect(elem.pattern).toBeUndefined(); + }); + }); +}); + +function getComplexUSCorePatient(): Patient { + return { + resourceType: 'Patient', + id: '123', + gender: 'male', + meta: { + versionId: '2', + lastUpdated: '2020-01-02T00:00:00.000Z', + author: { + reference: 'Practitioner/123', + }, + }, + identifier: [ + { system: 'abc', value: '123' }, + { system: 'def', value: '456' }, + ], + active: true, + birthDate: '1956-05-12', + name: [ + { + given: ['Homer'], + family: 'Simpson', + }, + ], + extension: [ + { + extension: [ + { + valueCoding: { + system: 'urn:oid:2.16.840.1.113883.6.238', + code: '2106-3', + display: 'White', + }, + url: 'ombCategory', + }, + ], + url: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-race`, + }, + { + extension: [ + { + valueCoding: { + system: 'urn:oid:2.16.840.1.113883.6.238', + code: '2186-5', + display: 'Not Hispanic or Latino', + }, + url: 'ombCategory', + }, + ], + url: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-ethnicity`, + }, + { + valueCode: 'M', + url: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-birthsex`, + }, + { + valueCode: 'M', + url: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-sex`, + }, + { + valueCodeableConcept: { + coding: [ + { + system: 'urn:oid:2.16.840.1.113762.1.4.1021.32', + code: 'M', + display: 'Male', + }, + ], + }, + url: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-genderIdentity`, + }, + ], + }; +} diff --git a/packages/core/src/default-values.ts b/packages/core/src/default-values.ts new file mode 100644 index 0000000000..4e1a60157e --- /dev/null +++ b/packages/core/src/default-values.ts @@ -0,0 +1,439 @@ +import { Resource } from '@medplum/fhirtypes'; +import { SchemaCrawler, SchemaVisitor, VisitorSlicingRules } from './schema-crawler'; +import { SliceDefinitionWithTypes, getValueSliceName } from './typeschema/slices'; +import { InternalSchemaElement, InternalTypeSchema, SliceDefinition, SlicingRules } from './typeschema/types'; +import { capitalize, deepClone, getPathDifference, isComplexTypeCode, isEmpty, isObject, isPopulated } from './utils'; +import { ElementsContextType } from './elements-context'; + +/** + * Used when an array entry, typically an empty one, needs to be assigned + * to a given slice even though it doesn't match the slice's discriminator. + */ +const SLICE_NAME_KEY = '__sliceName'; + +/** + * Adds default values to `resource` based on the supplied `schema`. Default values includes all required fixed and pattern + * values specified on elements in the schema. If an element has a fixed/pattern value but is optional, i.e. + * `element.min === 0`, the default value is not added. + * + * @param resource - The resource to which default values should be added. + * @param schema - The schema to use for adding default values. + * @returns A clone of `resource` with default values added. + */ +export function applyDefaultValuesToResource(resource: Resource, schema: InternalTypeSchema): Resource { + const visitor = new DefaultValueVisitor(resource, resource.resourceType, 'resource'); + const crawler = new SchemaCrawler(schema, visitor); + crawler.crawlResource(); + return visitor.getDefaultValue(); +} + +/** + * Adds default values to `existingValue` for the given `key` and its children. If `key` is undefined, + * default values are added to all elements in `elements`. Default values consist of all fixed and pattern + * values defined in the relevant elements. + * @param existingValue - The + * @param elements - The elements to which default values should be added. + * @param key - (optional) The key of the element(s) for which default values should be added. Elements with nested + * keys are also included. If undefined, default values for all elements are added. + * @returns `existingValue` with default values added + */ +export function applyDefaultValuesToElement( + existingValue: object, + elements: Record, + key?: string +): object { + for (const [elementKey, element] of Object.entries(elements)) { + if (key === undefined || key === elementKey) { + applyFixedOrPatternValue(existingValue, elementKey, element, elements); + continue; + } + + const keyDifference = getPathDifference(key, elementKey); + if (keyDifference !== undefined) { + applyFixedOrPatternValue(existingValue, keyDifference, element, elements); + } + } + + return existingValue; +} + +export function applyDefaultValuesToElementWithVisitor( + existingValue: any, + path: string, + element: InternalSchemaElement, + elements: Record, + schema: InternalTypeSchema +): any { + const inputValue: object = existingValue ?? Object.create(null); + + const [parentPath, key] = splitOnceRight(path, '.'); + const parent = Object.create(null); + setValueAtKey(parent, inputValue, key, element); + + const visitor = new DefaultValueVisitor(parent, parentPath, 'element'); + const crawler = new SchemaCrawler(schema, visitor, elements); + crawler.crawlElement(element, key, parentPath); + const modifiedContainer = visitor.getDefaultValue(); + + return getValueAtKey(modifiedContainer, key, element, elements); +} + +export function getDefaultValuesForNewSliceEntry( + key: string, + slice: SliceDefinition, + slicing: SlicingRules, + schema: InternalTypeSchema +): Resource { + const visitor = new DefaultValueVisitor([{ [SLICE_NAME_KEY]: slice.name }], slice.path, 'element'); + const crawler = new SchemaCrawler(schema, visitor); + crawler.crawlSlice(key, slice, slicing); + return visitor.getDefaultValue()[0]; +} + +type ValueContext = { + type: 'resource' | 'element' | 'slice'; + path: string; + values: any[]; +}; + +class DefaultValueVisitor implements SchemaVisitor { + private rootValue: any; + + private readonly schemaStack: InternalTypeSchema[]; + private readonly valueStack: ValueContext[]; + + constructor(rootValue: any, path: string, type: ValueContext['type']) { + this.schemaStack = []; + this.valueStack = []; + + this.rootValue = deepClone(rootValue); + this.valueStack.splice(0, this.valueStack.length, { + type, + path, + values: [this.rootValue], + }); + } + + private get schema(): InternalTypeSchema { + return this.schemaStack[this.schemaStack.length - 1]; + } + + private get value(): ValueContext { + return this.valueStack[this.valueStack.length - 1]; + } + + onEnterSchema(schema: InternalTypeSchema): void { + this.schemaStack.push(schema); + } + + onExitSchema(): void { + this.schemaStack.pop(); + } + + onEnterElement(path: string, element: InternalSchemaElement, elementsContext: ElementsContextType): void { + // eld-6: Fixed value may only be specified if there is one type + // eld-7: Pattern may only be specified if there is one type + // It may be possible to optimize this by checking element.type.length > 1 and short-circuiting + + const parentValues = this.value.values; + const parentPath = this.value.path; + const key = getPathDifference(parentPath, path); + if (key === undefined) { + throw new Error(`Expected ${path} to be prefixed by ${parentPath}`); + } + const elementValues: any[] = []; + + for (const parentValue of parentValues) { + if (parentValue === undefined) { + continue; + } + + const parentArray: any[] = Array.isArray(parentValue) ? parentValue : [parentValue]; + for (const parent of parentArray) { + applyMinimums(parent, key, element, elementsContext.elements); + applyFixedOrPatternValue(parent, key, element, elementsContext.elements); + const elementValue = getValueAtKey(parent, key, element, elementsContext.elements); + if (elementValue !== undefined) { + elementValues.push(elementValue); + } + } + } + + this.valueStack.push({ + type: 'element', + path: path, + values: elementValues, + }); + } + + onExitElement(path: string, element: InternalSchemaElement, elementsContext: ElementsContextType): void { + const elementValueContext = this.valueStack.pop(); + if (!elementValueContext) { + throw new Error('Expected value context to exist when exiting element'); + } + + const key = getPathDifference(this.value.path, path); + if (key === undefined) { + throw new Error(`Expected ${path} to be prefixed by ${this.value.path}`); + } + + for (const parentValue of this.value.values) { + const elementValue = getValueAtKey(parentValue, key, element, elementsContext.elements); + + // remove empty items from arrays + if (Array.isArray(elementValue)) { + for (let i = elementValue.length - 1; i >= 0; i--) { + const value = elementValue[i]; + if (!isPopulated(value)) { + elementValue.splice(i, 1); + } + } + } + + if (isEmpty(elementValue)) { + // setting undefined to delete the key + setValueAtKey(parentValue, undefined, key, element); + } + } + } + + onEnterSlice(path: string, slice: SliceDefinitionWithTypes, slicing: VisitorSlicingRules): void { + const elementValues = this.value.values; + const sliceValues: any[] = []; + + for (const elementValue of elementValues) { + if (elementValue === undefined) { + continue; + } + + if (!Array.isArray(elementValue)) { + throw new Error('Expected array value for sliced element'); + } + + const matchingItems: any[] = this.getMatchingSliceValues(elementValue, slice, slicing); + sliceValues.push(matchingItems); + } + + this.valueStack.push({ + type: 'slice', + path, + values: sliceValues, + }); + } + + getMatchingSliceValues(elementValue: any[], slice: SliceDefinitionWithTypes, slicing: VisitorSlicingRules): any[] { + const matchingItems: any[] = []; + for (const arrayItem of elementValue) { + const sliceName: string | undefined = + arrayItem[SLICE_NAME_KEY] ?? getValueSliceName(arrayItem, [slice], slicing.discriminator, this.schema.url); + + if (sliceName === slice.name) { + matchingItems.push(arrayItem); + } + } + + // Make sure at least slice.min values exist + for (let i = matchingItems.length; i < slice.min; i++) { + if (isComplexTypeCode(slice.type[0].code)) { + const emptySliceValue = Object.create(null); + matchingItems.push(emptySliceValue); + + // push onto input array so that it propagates upwards as well + elementValue.push(emptySliceValue); + } + } + + return matchingItems; + } + + onExitSlice(): void { + const sliceValuesContext = this.valueStack.pop(); + if (!sliceValuesContext) { + throw new Error('Expected value context to exist in onExitSlice'); + } + + for (const sliceValueArray of sliceValuesContext.values) { + for (let i = sliceValueArray.length - 1; i >= 0; i--) { + const sliceValue = sliceValueArray[i]; + if (SLICE_NAME_KEY in sliceValue) { + delete sliceValue[SLICE_NAME_KEY]; + } + } + } + } + + getDefaultValue(): any { + return this.rootValue; + } +} + +function applyMinimums( + parent: any, + key: string, + element: InternalSchemaElement, + elements: Record +): void { + const existingValue = getValueAtKey(parent, key, element, elements); + + if (element.min > 0 && existingValue === undefined) { + if (isComplexTypeCode(element.type[0].code)) { + if (element.isArray) { + setValueAtKey(parent, [Object.create(null)], key, element); + } else { + setValueAtKey(parent, Object.create(null), key, element); + } + } + } +} + +function setValueAtKey(parent: any, value: any, key: string, element: InternalSchemaElement): void { + if (key.includes('.')) { + throw new Error('key cannot be nested'); + } + + let resolvedKey = key; + + if (key.includes('[x]')) { + const code = element.type[0].code; + resolvedKey = key.replace('[x]', capitalize(code)); + } + + if (value === undefined) { + delete parent[resolvedKey]; + } else { + parent[resolvedKey] = value; + } +} + +function getValueAtKey( + value: object, + key: string, + element: InternalSchemaElement, + elements: Record +): any { + const keyParts = key.split('.'); + let last: any = value; + let answer: any; + for (let i = 0; i < keyParts.length; i++) { + let keyPart = keyParts[i]; + if (keyPart.includes('[x]')) { + const keyPartElem = elements[keyParts.slice(0, i + 1).join('.')]; + // should this loop through all possible types instead of using type[0]? + const code = keyPartElem.type[0].code; + keyPart = keyPart.replace('[x]', capitalize(code)); + } + + // final key part + if (i === keyParts.length - 1) { + if (Array.isArray(last)) { + answer = last.map((item) => item[keyPart]); + } else { + answer = last[keyPart]; + } + continue; + } + + // intermediate key part + if (Array.isArray(last)) { + last = last.map((lastItem) => lastItem[keyPart]); + } else if (isObject(last)) { + if (last[keyPart] === undefined) { + return undefined; + } + last = last[keyPart]; + } else { + return undefined; + } + } + + return answer; +} + +export function applyFixedOrPatternValue( + inputValue: any, + key: string, + element: InternalSchemaElement, + elements: Record +): any { + if (!(element.fixed || element.pattern)) { + return inputValue; + } + + if (Array.isArray(inputValue)) { + return inputValue.map((iv) => applyFixedOrPatternValue(iv, key, element, elements)); + } + + if (inputValue === undefined || inputValue === null) { + inputValue = Object.create(null); + } + + const outputValue = inputValue; + + const keyParts = key.split('.'); + let last: any = outputValue; + for (let i = 0; i < keyParts.length; i++) { + let keyPart = keyParts[i]; + if (keyPart.includes('[x]')) { + const keyPartElem = elements[keyParts.slice(0, i + 1).join('.')]; + const code = keyPartElem.type[0].code; + keyPart = keyPart.replace('[x]', capitalize(code)); + } + + if (i === keyParts.length - 1) { + const lastArray = Array.isArray(last) ? last : [last]; + for (const item of lastArray) { + if (element.fixed) { + item[keyPart] ??= element.fixed.value; + } else if (element.pattern) { + item[keyPart] = applyPattern(item[keyPart], element.pattern.value); + } + } + } else { + if (!(keyPart in last)) { + const elementKey = keyParts.slice(0, i + 1).join('.'); + last[keyPart] = elements[elementKey].isArray ? [Object.create(null)] : Object.create(null); + } + last = last[keyPart]; + } + } + return outputValue; +} + +function applyPattern(existingValue: any, pattern: any): any { + if (Array.isArray(pattern) && (Array.isArray(existingValue) || existingValue === undefined)) { + if ((existingValue?.length ?? 0) > 0) { + // Cannot yet apply a pattern to a non-empty array since that would require considering cardinality and slicing + return existingValue; + } + return deepClone(pattern); + } else if (isObject(pattern)) { + if ((isObject(existingValue) && !Array.isArray(existingValue)) || existingValue === undefined) { + const resultObj = (deepClone(existingValue) ?? Object.create(null)) as { [key: string]: any }; + for (const key of Object.keys(pattern)) { + resultObj[key] = applyPattern(resultObj[key], pattern[key]); + } + return resultObj; + } + } + + return existingValue; +} + +/** + * Splits a string on the last occurrence of the delimiter + * @param str - The string to split + * @param delim - The delimiter string + * @returns An array of two strings; the first consisting of the beginning of the + * string up to the last occurrence of the delimiter. the second is the remainder of the + * string after the last occurrence of the delimiter. If the delimiter is not present + * in the string, the first element is empty and the second is the input string. + */ +function splitOnceRight(str: string, delim: string): [string, string] { + const delimIndex = str.lastIndexOf(delim); + if (delimIndex === -1) { + return ['', str]; + } + const beginning = str.substring(0, delimIndex); + const last = str.substring(delimIndex + delim.length); + return [beginning, last]; +} diff --git a/packages/core/src/elements-context.test.ts b/packages/core/src/elements-context.test.ts new file mode 100644 index 0000000000..b2e346b6e0 --- /dev/null +++ b/packages/core/src/elements-context.test.ts @@ -0,0 +1,112 @@ +import { buildElementsContext } from './elements-context'; +import { HTTP_HL7_ORG } from './constants'; +import { isPopulated } from './utils'; +import { InternalTypeSchema, parseStructureDefinition } from './typeschema/types'; +import { StructureDefinition } from '@medplum/fhirtypes'; +import { readJson } from '@medplum/definitions'; + +describe('buildElementsContext', () => { + let USCoreStructureDefinitions: StructureDefinition[]; + + function getSchemaFromProfileUrl(url: string): InternalTypeSchema { + const sd = USCoreStructureDefinitions.find((sd) => sd.url === url); + if (!isPopulated(sd)) { + fail(`Expected structure definition for ${url} to be found`); + } + return parseStructureDefinition(sd); + } + beforeAll(() => { + USCoreStructureDefinitions = readJson('fhir/r4/testing/uscore-v5.0.1-structuredefinitions.json'); + }); + + test('deeply nested schema', () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-medicationrequest`; + const schema = getSchemaFromProfileUrl(profileUrl); + + const context = buildElementsContext({ + elements: schema.elements, + path: 'MedicationRequest', + parentContext: undefined, + profileUrl, + }); + + if (context === undefined) { + fail('Expected context to be defined'); + } + + expect(context.profileUrl).toEqual(profileUrl); + expect(context.elements['dosageInstruction.method']).toBeDefined(); + expect(context.elementsByPath['MedicationRequest.dosageInstruction.method']).toBeDefined(); + expect(context.elements['dosageInstruction.method']).toBe( + context.elementsByPath['MedicationRequest.dosageInstruction.method'] + ); + }); + + test('building context at same path returns undefined', () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`; + const schema = getSchemaFromProfileUrl(profileUrl); + const context = buildElementsContext({ + elements: schema.elements, + path: 'Patient', + parentContext: undefined, + profileUrl, + }); + + if (context === undefined) { + fail('Expected context to be defined'); + } + + const samePath = buildElementsContext({ + elements: schema.elements, + path: 'Patient', + parentContext: context, + profileUrl, + }); + + expect(samePath).toBeUndefined(); + }); + + test('nested context', () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`; + const profileSchema = getSchemaFromProfileUrl(profileUrl); + + const context = buildElementsContext({ + elements: profileSchema.elements, + path: 'Patient', + parentContext: undefined, + profileUrl, + }); + + if (context === undefined) { + fail('Expected context to be defined'); + } + + const extensionUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-race`; + const extensionSchema = getSchemaFromProfileUrl(extensionUrl); + + const extensionContext = buildElementsContext({ + elements: extensionSchema.elements, + path: 'Patient.extension', + parentContext: context, + profileUrl: extensionUrl, + debugMode: true, + }); + + if (extensionContext === undefined) { + fail('Expected extension context to be defined'); + } + + expect(extensionContext.profileUrl).toEqual(extensionUrl); + expect(Object.keys(extensionContext.elements)).toEqual( + expect.arrayContaining(['extension', 'id', 'url', 'value[x]']) + ); + + expect(extensionContext.elements['extension'].slicing?.slices.length).toBe(3); + expect(extensionContext.elements['extension']).toBe(extensionContext.elementsByPath['Patient.extension.extension']); + + expect(extensionContext.elements['url'].fixed).toEqual({ + type: 'uri', + value: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-race', + }); + }); +}); diff --git a/packages/core/src/elements-context.ts b/packages/core/src/elements-context.ts new file mode 100644 index 0000000000..e94bd0f601 --- /dev/null +++ b/packages/core/src/elements-context.ts @@ -0,0 +1,109 @@ +import { InternalSchemaElement } from './typeschema/types'; +import { getPathDifference } from './utils'; + +/** + * Information for the set of elements at a given path within in a resource. This mostly exists to + * normalize access to elements regardless of whether they are from a profile, extension, or slice. + */ +export type ElementsContextType = { + /** The FHIR path from the root resource to which the keys of `elements` are relative. */ + path: string; + /** + * The mapping of keys to `InternalSchemaElement` at the current `path` relative to the + * root resource. `elements` originate from either `InternalTypeSchema.elements` or + * `SliceDefinition.elements` when the elements context is created within a slice. + */ + elements: Record; + /** + * Similar mapping as `elements`, but with keys being the full path from the root resource rather + * than relative to `path`, in other words, the keys of the Record are `${path}.${key}`. + */ + elementsByPath: Record; + /** The URL, if any, of the resource profile or extension from which the `elements` collection originated. */ + profileUrl: string | undefined; + /** Whether debug logging is enabled */ + debugMode: boolean; +}; + +export function buildElementsContext({ + parentContext, + path, + elements, + profileUrl, + debugMode, +}: { + /** The most recent `ElementsContextType` in which this context is being built. */ + parentContext: ElementsContextType | undefined; + /** The FHIR path from the root resource to which the keys of `elements` are relative. */ + path: string; + /** + * The mapping of keys to `InternalSchemaElement` at the current `path` relative to the + * root resource. This should be either `InternalTypeSchema.elements` or `SliceDefinition.elements`. + */ + elements: Record; + /** The URL, if any, of the resource profile or extension from which the `elements` collection originated. */ + profileUrl?: string; + /** Whether debug logging is enabled */ + debugMode?: boolean; +}): ElementsContextType | undefined { + if (path === parentContext?.path) { + return undefined; + } + + const mergedElements: ElementsContextType['elements'] = mergeElementsForContext( + path, + elements, + parentContext, + Boolean(debugMode) + ); + const elementsByPath: Record = Object.create(null); + + for (const [key, property] of Object.entries(mergedElements)) { + elementsByPath[path + '.' + key] = property; + } + + return { + path: path, + elements: mergedElements, + elementsByPath, + profileUrl: profileUrl ?? parentContext?.profileUrl, + debugMode: debugMode ?? parentContext?.debugMode ?? false, + }; +} + +function mergeElementsForContext( + path: string, + elements: Record, + parentContext: ElementsContextType | undefined, + debugMode: boolean +): Record { + const result: Record = Object.create(null); + + if (parentContext) { + for (const [elementPath, element] of Object.entries(parentContext.elementsByPath)) { + const key = getPathDifference(path, elementPath); + if (key !== undefined) { + result[key] = element; + } + } + } + + let usedNewElements = false; + if (elements) { + for (const [key, element] of Object.entries(elements)) { + if (!(key in result)) { + result[key] = element; + usedNewElements = true; + } + } + } + + // if no new elements are used, the ElementsContext is unnecessary. + // We could add another guard against unnecessary contexts if usedNewElements is false, + // but unnecessary contexts **should** already be taken care before + // this is ever hit. Leaving the debug logging in for now. + if (debugMode) { + console.assert(usedNewElements, 'Unnecessary ElementsContext; not using any newly provided elements'); + } + return result; +} diff --git a/packages/core/src/fhirmapper/parse.ts b/packages/core/src/fhirmapper/parse.ts index 87f6019de6..ddc2a39c19 100644 --- a/packages/core/src/fhirmapper/parse.ts +++ b/packages/core/src/fhirmapper/parse.ts @@ -198,6 +198,12 @@ class StructureMapParser { } private parseRuleSources(): StructureMapGroupRuleSource[] { + if (this.parser.hasMore() && this.parser.peek()?.value === 'for') { + // The "for" keyword is optional + // It is not in the official grammar: https://build.fhir.org/mapping.g4 + // But it is used in the examples: https://build.fhir.org/mapping-tutorial.html + this.parser.consume('Symbol', 'for'); + } const sources = [this.parseRuleSource()]; while (this.parser.hasMore() && this.parser.peek()?.value === ',') { this.parser.consume(','); diff --git a/packages/core/src/fhirmapper/transform.test.ts b/packages/core/src/fhirmapper/transform.test.ts index 6590ffcffb..1a4dc3201a 100644 --- a/packages/core/src/fhirmapper/transform.test.ts +++ b/packages/core/src/fhirmapper/transform.test.ts @@ -321,4 +321,102 @@ describe('FHIR Mapper transform', () => { const actual = structureMapTransform(parseMappingLanguage(map), input); expect(actual).toMatchObject(expected); }); + + test('Co-dependency in translation', () => { + // https://build.fhir.org/mapping-tutorial.html#step9 + // Another common translation is where the target mapping for one element depends on the value of another element. + + const map = ` + uses "http://hl7.org/fhir/StructureDefinition/tutorial-left" as source + uses "http://hl7.org/fhir/StructureDefinition/tutorial-right" as target + + group tutorial(source src : TLeft, target tgt : TRight) { + src.i as i where m < 2 -> tgt.j = i; + src.i as i where m >= 2 -> tgt.k = i; + } + `; + + const input1 = [toTypedValue({ i: 'foo', m: 1 })]; + const expected1 = [toTypedValue({ j: 'foo' })]; + const actual1 = structureMapTransform(parseMappingLanguage(map), input1); + expect(actual1).toMatchObject(expected1); + + const input2 = [toTypedValue({ i: 'foo', m: 3 })]; + const expected2 = [toTypedValue({ k: 'foo' })]; + const actual2 = structureMapTransform(parseMappingLanguage(map), input2); + expect(actual2).toMatchObject(expected2); + }); + + test('Reworking Structure #1', () => { + // https://build.fhir.org/mapping-tutorial.html#step11 + // It's now time to start moving away from relatively simple cases to some of the harder ones to manage mappings for. + // The first mixes list management, and converting from a specific structure to a general structure: + + const map = ` + uses "http://hl7.org/fhir/StructureDefinition/tutorial-left" as source + uses "http://hl7.org/fhir/StructureDefinition/tutorial-right" as target + + group tutorial(source src : TLeft, target tgt : TRight) { + src.e as s_e -> tgt.e as t_e then { + for s_e -> t_e.f = s_e, t_e.g = 'g1'; + }; + + src.f as s_f -> tgt.e as t_e first then { + s_f -> t_e.f = s_f, t_e.g = 'g2'; + }; + } + `; + + const input = [toTypedValue({ e: ['foo', 'bar'], f: 'baz' }), toTypedValue({ e: [] })]; + const expected = [ + toTypedValue({ + e: [ + { f: 'foo', g: 'g1' }, + { f: 'bar', g: 'g1' }, + { f: 'baz', g: 'g2' }, + ], + }), + ]; + const actual = structureMapTransform(parseMappingLanguage(map), input); + expect(actual).toMatchObject(expected); + }); + + test('Reworking Structure #2', () => { + // https://build.fhir.org/mapping-tutorial.html#step12 + // The second example for reworking structure moves cardinality around the hierarchy. + // In this case, the source has an optional structure that contains a repeating structure, + // while the target puts the cardinality at the next level up: + + const map = ` + uses "http://hl7.org/fhir/StructureDefinition/tutorial-left" as source + uses "http://hl7.org/fhir/StructureDefinition/tutorial-right" as target + + group tutorial(source src : TLeft, target tgt : TRight) { + // setting up a variable for the parent + src.az1 as s_az1 then { + + // one tgt.az1 for each az3 + s_az1.az3 as s_az3 -> tgt.az1 as t_az1 then { + // value for az2. Note that this refers to a previous context in the source + s_az1.az2 as az2 -> t_az1.az2 = az2; + + // value for az3 + s_az3 -> t_az1.az3 = s_az3; + }; + }; + } + `; + + const input = [toTypedValue({ az1: { az2: 'foo', az3: ['bar', 'baz'] }, f: 'baz' }), toTypedValue({ az1: [] })]; + const expected = [ + toTypedValue({ + az1: [ + { az2: 'foo', az3: 'bar' }, + { az2: 'foo', az3: 'baz' }, + ], + }), + ]; + const actual = structureMapTransform(parseMappingLanguage(map), input); + expect(actual).toMatchObject(expected); + }); }); diff --git a/packages/core/src/fhirmapper/transform.ts b/packages/core/src/fhirmapper/transform.ts index 854978c5be..86b29db4cb 100644 --- a/packages/core/src/fhirmapper/transform.ts +++ b/packages/core/src/fhirmapper/transform.ts @@ -41,12 +41,29 @@ export function structureMapTransform( return evalStructureMap({ root: structureMap, loader }, structureMap, input); } +/** + * Evaluates a FHIR StructureMap. + * + * @param ctx - The transform context. + * @param structureMap - The FHIR StructureMap definition. + * @param input - The input values. + * @returns The transformed values. + * @internal + */ function evalStructureMap(ctx: TransformContext, structureMap: StructureMap, input: TypedValue[]): TypedValue[] { evalImports(ctx, structureMap); hoistGroups(ctx, structureMap); - return evalGroup(ctx, (structureMap.group as StructureMapGroup[])[0], input); + return evalGroup(ctx, structureMap.group[0], input); } +/** + * Evaluates the imports in a FHIR StructureMap. + * For each import statement, the loader function is called to load the imported StructureMap. + * The imported StructureMap is then hoisted into the current context. + * @param ctx - The transform context. + * @param structureMap - The FHIR StructureMap definition. + * @internal + */ function evalImports(ctx: TransformContext, structureMap: StructureMap): void { if (ctx.loader && structureMap.import) { for (const url of structureMap.import) { @@ -58,6 +75,14 @@ function evalImports(ctx: TransformContext, structureMap: StructureMap): void { } } +/** + * Hoists the groups in a FHIR StructureMap into the current context. + * This is necessary to allow groups to reference each other. + * + * @param ctx - The transform context. + * @param structureMap - The FHIR StructureMap definition. + * @internal + */ function hoistGroups(ctx: TransformContext, structureMap: StructureMap): void { if (structureMap.group) { for (const group of structureMap.group) { @@ -66,6 +91,17 @@ function hoistGroups(ctx: TransformContext, structureMap: StructureMap): void { } } +/** + * Evaluates a FHIR StructureMapGroup. + * + * A "group" is similar to a function in a programming language. + * + * @param ctx - The transform context. + * @param group - The FHIR StructureMapGroup definition. + * @param input - The input values. + * @returns The transformed values. + * @internal + */ function evalGroup(ctx: TransformContext, group: StructureMapGroup, input: TypedValue[]): TypedValue[] { const sourceDefinitions: StructureMapGroupInput[] = []; const targetDefinitions: StructureMapGroupInput[] = []; @@ -122,20 +158,66 @@ function evalGroup(ctx: TransformContext, group: StructureMapGroup, input: Typed return outputs; } +/** + * Entry point for evaluating a rule. + * Rule sources are evaluated first, followed by the rule target, child rules, and dependent groups. + * Rule sources are evaluated recursively to handle multiple source statements. + * + * @param ctx - The transform context. + * @param rule - The FHIR Mapping rule definition. + * @internal + */ function evalRule(ctx: TransformContext, rule: StructureMapGroupRule): void { - let haveSource = false; + // https://build.fhir.org/mapping-language.html#7.8.0.8.1 + // If there are multiple source statements, the rule applies for the permutation of the source elements from each source statement. + // E.g. if there are 2 source statements, each with 2 matching elements, the rule applies 4 times, one for each combination. + // Typically, if there is more than one source statement, only one of the elements would repeat. + // If any of the source data elements have no value, then the rule never applies; + // only existing permutations are executed: for multiple source statements, all of them need to match. if (rule.source) { - for (const source of rule.source) { - if (evalSource(ctx, source)) { - haveSource = true; - } - } + evalRuleSourceAt(ctx, rule, 0); } +} - if (!haveSource) { - return; +/** + * Recursively evaluates a rule at a specific source index. + * + * @param ctx - The transform context. + * @param rule - The FHIR Mapping rule definition. + * @param index - The source index to evaluate. + * @internal + */ +function evalRuleSourceAt( + ctx: TransformContext, + rule: StructureMapGroupRule & { source: StructureMapGroupRuleSource[] }, + index: number +): void { + const source = rule.source[index]; + for (const sourceValue of evalSource(ctx, source)) { + if (source.variable) { + setVariable(ctx, source.variable as string, sourceValue); + } + + if (index < rule.source.length - 1) { + // If there are more sources, evaluate the next source + evalRuleSourceAt(ctx, rule, index + 1); + } else { + // Otherwise, evaluate the rule after the sources + evalRuleAfterSources(ctx, rule); + } } +} +/** + * Evaluates a rule after the sources have been evaluated. + * + * This includes the rule targets, child rules, and dependent groups. + * + * @param ctx - The transform context. + * @param rule - The FHIR Mapping rule definition. + * @internal + */ +function evalRuleAfterSources(ctx: TransformContext, rule: StructureMapGroupRule): void { if (rule.target) { for (const target of rule.target) { evalTarget(ctx, target); @@ -153,25 +235,36 @@ function evalRule(ctx: TransformContext, rule: StructureMapGroupRule): void { } } -function evalSource(ctx: TransformContext, source: StructureMapGroupRuleSource): boolean { +/** + * Evaluates a FHIR Mapping source definition. + * + * If the source has a condition, the condition is evaluated. + * If the source has a check, the check is evaluated. + * + * @param ctx - The transform context. + * @param source - The FHIR Mapping source definition. + * @returns The evaluated source values. + * @internal + */ +function evalSource(ctx: TransformContext, source: StructureMapGroupRuleSource): TypedValue[] { const sourceContext = getVariable(ctx, source.context as string) as TypedValue | undefined; if (!sourceContext) { - return false; + return []; } const sourceElement = source.element; if (!sourceElement) { - return true; + return [sourceContext]; } let sourceValue = evalFhirPathTyped(sourceElement, [sourceContext]); if (!sourceValue || sourceValue.length === 0) { - return false; + return []; } if (source.condition) { if (!evalCondition(sourceContext, { [source.variable as string]: sourceValue[0] }, source.condition)) { - return false; + return []; } } @@ -185,17 +278,32 @@ function evalSource(ctx: TransformContext, source: StructureMapGroupRuleSource): sourceValue = evalListMode(source, sourceValue); } - if (source.variable) { - setVariable(ctx, source.variable, unarrayify(sourceValue)); - } - - return true; + return sourceValue; } +/** + * Evaluates a FHIRPath condition for a FHIR Mapping source. + * + * This is used for both the "condition" and "check" properties. + * + * @param input - The input value, typically the rule source. + * @param variables - The variables in scope for the FHIRPath expression. + * @param condition - The FHIRPath condition to evaluate. + * @returns True if the condition is true, false otherwise. + * @internal + */ function evalCondition(input: TypedValue, variables: Record, condition: string): boolean { return toJsBoolean(evalFhirPathTyped(condition, [input], variables)); } +/** + * Evaluates the list mode for a FHIR Mapping source. + * + * @param source - The FHIR Mapping source definition. + * @param sourceValue - The source values. + * @returns The evaluated source values. + * @internal + */ function evalListMode(source: StructureMapGroupRuleSource, sourceValue: TypedValue[]): TypedValue[] { // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check switch (source.listMode) { @@ -216,6 +324,13 @@ function evalListMode(source: StructureMapGroupRuleSource, sourceValue: TypedVal return sourceValue; } +/** + * Evaluates a FHIR Mapping target definition. + * + * @param ctx - The transform context. + * @param target - The FHIR Mapping target definition. + * @internal + */ function evalTarget(ctx: TransformContext, target: StructureMapGroupRuleTarget): void { const targetContext = getVariable(ctx, target.context as string) as TypedValue | undefined; if (!targetContext) { @@ -284,22 +399,69 @@ function evalTarget(ctx: TransformContext, target: StructureMapGroupRuleTarget): } } +/** + * Returns true if the target property is an array field. + * + * @param targetContext - The target context. + * @param element - The element to check (i.e., the property name). + * @returns True if the target property is an array field. + * @internal + */ function isArrayProperty(targetContext: TypedValue, element: string): boolean | undefined { const targetContextTypeDefinition = tryGetDataType(targetContext.type); const targetPropertyTypeDefinition = targetContextTypeDefinition?.elements?.[element]; return targetPropertyTypeDefinition?.isArray; } +/** + * Evaluates the "append" transform. + * + * "Source is element or string - just append them all together" + * + * See: https://build.fhir.org/mapping-language.html#7.8.0.8.2 + * + * @param ctx - The transform context. + * @param target - The FHIR Mapping target definition. + * @returns The evaluated target values. + * @internal + */ function evalAppend(ctx: TransformContext, target: StructureMapGroupRuleTarget): TypedValue[] { const arg1 = resolveParameter(ctx, target.parameter?.[0])?.[0]?.value; const arg2 = resolveParameter(ctx, target.parameter?.[1])?.[0]?.value; return [{ type: 'string', value: (arg1 ?? '').toString() + (arg2 ?? '').toString() }]; } +/** + * Evaluates the "copy" transform. + * + * "Simply copy the source to the target as is (only allowed when the types in source and target match- typically for primitive types). + * In the concrete syntax, this is simply represented as the source variable, e.g. src.a = tgt.b" + * + * See: https://build.fhir.org/mapping-language.html#7.8.0.8.2 + * + * @param ctx - The transform context. + * @param target - The FHIR Mapping target definition. + * @returns The evaluated target values. + * @internal + */ function evalCopy(ctx: TransformContext, target: StructureMapGroupRuleTarget): TypedValue[] { return (target.parameter as StructureMapGroupRuleTargetParameter[]).flatMap((p) => resolveParameter(ctx, p)); } +/** + * Evaluates the "create" transform. + * + * "Use the standard API to create a new instance of data. + * Where structure definitions have been provided, the type parameter must be a string which is a known type of a root element. + * Where they haven't, the application must know the name somehow."" + * + * See: https://build.fhir.org/mapping-language.html#7.8.0.8.2 + * + * @param ctx - The transform context. + * @param target - The FHIR Mapping target definition. + * @returns The evaluated target values. + * @internal + */ function evalCreate(ctx: TransformContext, target: StructureMapGroupRuleTarget): TypedValue[] { const result: Record = {}; if (target.parameter && target.parameter.length > 0) { @@ -308,12 +470,39 @@ function evalCreate(ctx: TransformContext, target: StructureMapGroupRuleTarget): return [toTypedValue(result)]; } +/** + * Evaluates the "evaluate" transform. + * + * "Execute the supplied FHIRPath expression and use the value returned by that." + * + * See: https://build.fhir.org/mapping-language.html#7.8.0.8.2 + * + * @param ctx - The transform context. + * @param target - The FHIR Mapping target definition. + * @returns The evaluated target values. + * @internal + */ function evalEvaluate(ctx: TransformContext, target: StructureMapGroupRuleTarget): TypedValue[] { const typedExpr = resolveParameter(ctx, target.parameter?.[0]); const expr = typedExpr[0].value as string; return evalFhirPathTyped(expr, [], buildFhirPathVariables(ctx) as Record); } +/** + * Evaluates the "translate" transform. + * + * "Use the translate operation. The source is some type of code or coded datatype, + * and the source and map_uri are passed to the translate operation. + * The output determines what value from the translate operation is used for the result of the operation + * (code, system, display, Coding, or CodeableConcept)" + * + * See: https://build.fhir.org/mapping-language.html#7.8.0.8.2 + * + * @param ctx - The transform context. + * @param target - The FHIR Mapping target definition. + * @returns The evaluated target values. + * @internal + */ function evalTranslate(ctx: TransformContext, target: StructureMapGroupRuleTarget): TypedValue[] { const args = (target.parameter as StructureMapGroupRuleTargetParameter[]).flatMap((p) => resolveParameter(ctx, p)); const sourceValue = args[0].value; @@ -326,6 +515,18 @@ function evalTranslate(ctx: TransformContext, target: StructureMapGroupRuleTarge return [toTypedValue(result.match?.[0]?.concept?.code)]; } +/** + * Evaluates the "truncate" transform. + * + * "Source must be some stringy type that has some meaningful length property" + * + * See: https://build.fhir.org/mapping-language.html#7.8.0.8.2 + * + * @param ctx - The transform context. + * @param target - The FHIR Mapping target definition. + * @returns The evaluated target values. + * @internal + */ function evalTruncate(ctx: TransformContext, target: StructureMapGroupRuleTarget): TypedValue[] { const targetValue = resolveParameter(ctx, target.parameter?.[0])?.[0]; const targetLength = resolveParameter(ctx, target.parameter?.[1])?.[0]?.value as number; @@ -335,6 +536,15 @@ function evalTruncate(ctx: TransformContext, target: StructureMapGroupRuleTarget return [targetValue]; } +/** + * Evaluates a rule dependent group. + * + * See: https://hl7.org/fhir/r4/structuremap-definitions.html#StructureMap.group.rule.dependent + * + * @param ctx - The transform context. + * @param dependent - The FHIR Mapping dependent definition. + * @internal + */ function evalDependent(ctx: TransformContext, dependent: StructureMapGroupRuleDependent): void { const dependentGroup = getVariable(ctx, dependent.name as string) as TypedValue | undefined; if (!dependentGroup) { @@ -355,6 +565,18 @@ function evalDependent(ctx: TransformContext, dependent: StructureMapGroupRuleDe evalGroup(newContext, dependentGroup.value as StructureMapGroup, args); } +/** + * Resolves the value of a FHIR Mapping target parameter. + * + * For literal values, the value is returned as-is. + * + * For variables, the value is looked up in the current context. + * + * @param ctx - The transform context. + * @param parameter - The FHIR Mapping target parameter definition. + * @returns The resolved parameter values. + * @internal + */ function resolveParameter( ctx: TransformContext, parameter: StructureMapGroupRuleTargetParameter | undefined @@ -378,6 +600,16 @@ function resolveParameter( return paramValue; } +/** + * Returns a variable value by name. + * + * Recursively searches the parent context if the variable is not found in the current context. + * + * @param ctx - The transform context. + * @param name - The variable name. + * @returns The variable value. + * @internal + */ function getVariable(ctx: TransformContext, name: string): TypedValue[] | TypedValue | undefined { const value = ctx.variables?.[name]; if (value) { @@ -389,6 +621,16 @@ function getVariable(ctx: TransformContext, name: string): TypedValue[] | TypedV return undefined; } +/** + * Builds a collection of FHIRPath variables from the current context. + * + * Recursively searches the parent context to build the complete set of variables. + * + * @param ctx - The transform context. + * @param result - The builder output. + * @returns The result with the FHIRPath variables. + * @internal + */ function buildFhirPathVariables( ctx: TransformContext, result: Record = {} @@ -405,6 +647,14 @@ function buildFhirPathVariables( return result; } +/** + * Sets a variable value in the current context. + * + * @param ctx - The transform context. + * @param name - The variable name. + * @param value - The variable value. + * @internal + */ function setVariable(ctx: TransformContext, name: string, value: TypedValue[] | TypedValue): void { if (!ctx.variables) { ctx.variables = {}; diff --git a/packages/core/src/fhirpath/functions.test.ts b/packages/core/src/fhirpath/functions.test.ts index 511cfb8c2d..4a067f8ff1 100644 --- a/packages/core/src/fhirpath/functions.test.ts +++ b/packages/core/src/fhirpath/functions.test.ts @@ -29,6 +29,7 @@ const TYPED_Y = toTypedValue('y'); const TYPED_Z = toTypedValue('z'); const TYPED_APPLE = toTypedValue('apple'); const TYPED_XYZ = toTypedValue('xyz'); +const TYPED_EMPTY = toTypedValue({}); const LITERAL_TRUE = new LiteralAtom(TYPED_TRUE); const LITERAL_FALSE = new LiteralAtom(TYPED_FALSE); @@ -40,6 +41,7 @@ describe('FHIRPath functions', () => { test('empty', () => { expect(functions.empty(context, [])).toEqual([TYPED_TRUE]); + expect(functions.empty(context, [TYPED_EMPTY])).toEqual([TYPED_TRUE]); expect(functions.empty(context, [TYPED_1])).toEqual([TYPED_FALSE]); expect(functions.empty(context, [TYPED_1, TYPED_2])).toEqual([TYPED_FALSE]); }); @@ -52,6 +54,7 @@ describe('FHIRPath functions', () => { test('exists', () => { expect(functions.exists(context, [])).toEqual([TYPED_FALSE]); + expect(functions.exists(context, [TYPED_EMPTY])).toEqual([TYPED_FALSE]); expect(functions.exists(context, [TYPED_1])).toEqual([TYPED_TRUE]); expect(functions.exists(context, [TYPED_1, TYPED_2])).toEqual([TYPED_TRUE]); expect(functions.exists(context, [], isEven)).toEqual([TYPED_FALSE]); diff --git a/packages/core/src/fhirpath/functions.ts b/packages/core/src/fhirpath/functions.ts index cc1674df1a..84011d6b76 100644 --- a/packages/core/src/fhirpath/functions.ts +++ b/packages/core/src/fhirpath/functions.ts @@ -1,7 +1,7 @@ import { Reference } from '@medplum/fhirtypes'; import { Atom, AtomContext } from '../fhirlexer/parse'; import { PropertyType, TypedValue, isResource } from '../types'; -import { calculateAge } from '../utils'; +import { calculateAge, isEmpty } from '../utils'; import { DotAtom, SymbolAtom } from './atoms'; import { parseDateString } from './date'; import { booleanToTypedValue, fhirPathIs, isQuantity, removeDuplicates, toJsBoolean, toTypedValue } from './utils'; @@ -34,7 +34,7 @@ export const functions: Record = { * @returns True if the input collection is empty ({ }) and false otherwise. */ empty: (_context: AtomContext, input: TypedValue[]): TypedValue[] => { - return booleanToTypedValue(input.length === 0); + return booleanToTypedValue(input.length === 0 || input.every((e) => isEmpty(e.value))); }, /** @@ -67,7 +67,7 @@ export const functions: Record = { if (criteria) { return booleanToTypedValue(input.filter((e) => toJsBoolean(criteria.eval(context, [e]))).length > 0); } else { - return booleanToTypedValue(input.length > 0); + return booleanToTypedValue(input.length > 0 && input.every((e) => !isEmpty(e.value))); } }, diff --git a/packages/core/src/fhirpath/parse.test.ts b/packages/core/src/fhirpath/parse.test.ts index 62fb696c5d..1f42f4bd03 100644 --- a/packages/core/src/fhirpath/parse.test.ts +++ b/packages/core/src/fhirpath/parse.test.ts @@ -479,6 +479,36 @@ describe('FHIRPath parser', () => { ]); }); + test('%previous.empty() returns true for an empty %previous value', () => { + const patient: Patient = { + resourceType: 'Patient', + }; + const result = evalFhirPathTyped('%previous.empty()', [toTypedValue(patient)], { '%previous': toTypedValue({}) }); + + expect(result).toEqual([ + { + type: PropertyType.boolean, + value: true, + }, + ]); + }); + + test('%previous.exists().not() returns true for an empty %previous value', () => { + const patient: Patient = { + resourceType: 'Patient', + }; + const result = evalFhirPathTyped('%previous.exists().not()', [toTypedValue(patient)], { + '%previous': toTypedValue({}), + }); + + expect(result).toEqual([ + { + type: PropertyType.boolean, + value: true, + }, + ]); + }); + test('Context type comparison false', () => { const patient: Patient = { resourceType: 'Patient', diff --git a/packages/core/src/fhirpath/utils.test.ts b/packages/core/src/fhirpath/utils.test.ts index 6fbeb3f91b..0e44adceff 100644 --- a/packages/core/src/fhirpath/utils.test.ts +++ b/packages/core/src/fhirpath/utils.test.ts @@ -230,7 +230,7 @@ describe('FHIRPath utils', () => { }); test('getTypedPropertyValueWithSchema', () => { - const value = { active: true }; + const typedValue: TypedValue = { type: 'Patient', value: { active: true } }; const path = 'active'; const goodElement: InternalSchemaElement = { description: '', @@ -239,8 +239,9 @@ describe('FHIRPath utils', () => { max: 0, type: [{ code: 'boolean' }], }; - expect(getTypedPropertyValueWithSchema(value, path, goodElement)).toEqual({ type: 'boolean', value: true }); + expect(getTypedPropertyValueWithSchema(typedValue, path, goodElement)).toEqual({ type: 'boolean', value: true }); + const choiceOfTypeTypedValue: TypedValue = { type: 'Extension', value: { valueBoolean: true } }; const extensionValueX: InternalSchemaElement = { description: '', path: 'Extension.value[x]', @@ -248,7 +249,7 @@ describe('FHIRPath utils', () => { max: 1, type: [{ code: 'boolean' }], }; - expect(getTypedPropertyValueWithSchema({ valueBoolean: true }, 'value[x]', extensionValueX)).toEqual({ + expect(getTypedPropertyValueWithSchema(choiceOfTypeTypedValue, 'value[x]', extensionValueX)).toEqual({ type: 'boolean', value: true, }); diff --git a/packages/core/src/fhirpath/utils.ts b/packages/core/src/fhirpath/utils.ts index 3e5cee831a..27eb6b9edb 100644 --- a/packages/core/src/fhirpath/utils.ts +++ b/packages/core/src/fhirpath/utils.ts @@ -59,6 +59,11 @@ export function singleton(collection: TypedValue[], type?: string): TypedValue | } } +export interface GetTypedPropertyValueOptions { + /** (optional) URL of a resource profile for type resolution */ + profileUrl?: string; +} + /** * Returns the value of the property and the property type. * Some property definitions support multiple types. @@ -67,16 +72,21 @@ export function singleton(collection: TypedValue[], type?: string): TypedValue | * This function returns the value and the type. * @param input - The base context (FHIR resource or backbone element). * @param path - The property path. + * @param options - (optional) Additional options * @returns The value of the property and the property type. */ -export function getTypedPropertyValue(input: TypedValue, path: string): TypedValue[] | TypedValue | undefined { +export function getTypedPropertyValue( + input: TypedValue, + path: string, + options?: GetTypedPropertyValueOptions +): TypedValue[] | TypedValue | undefined { if (!input.value) { return undefined; } - const elementDefinition = getElementDefinition(input.type, path); + const elementDefinition = getElementDefinition(input.type, path, options?.profileUrl); if (elementDefinition) { - return getTypedPropertyValueWithSchema(input.value, path, elementDefinition); + return getTypedPropertyValueWithSchema(input, path, elementDefinition); } return getTypedPropertyValueWithoutSchema(input, path); @@ -84,13 +94,13 @@ export function getTypedPropertyValue(input: TypedValue, path: string): TypedVal /** * Returns the value of the property and the property type using a type schema. - * @param value - The base context (FHIR resource or backbone element). + * @param typedValue - The base context (FHIR resource or backbone element). * @param path - The property path. * @param element - The property element definition. * @returns The value of the property and the property type. */ export function getTypedPropertyValueWithSchema( - value: TypedValue['value'], + typedValue: TypedValue, path: string, element: InternalSchemaElement ): TypedValue[] | TypedValue | undefined { @@ -115,6 +125,7 @@ export function getTypedPropertyValueWithSchema( // Therefore, cannot only check for endsWith('[x]') since FHIRPath uses this code path // with a path of 'value' and expects Choice of Types treatment + const value = typedValue.value; const types = element.type; if (!types || types.length === 0) { return undefined; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 2eba61bfac..cdd5970587 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -9,6 +9,8 @@ export * from './config'; export * from './constants'; export * from './contenttype'; export * from './crypto'; +export * from './default-values'; +export * from './elements-context'; export * from './eventtarget'; export * from './fhircast'; export * from './fhirlexer/parse'; @@ -33,8 +35,10 @@ export * from './search/match'; export * from './search/search'; export * from './sftp'; export * from './storage'; +export * from './subscriptions'; export * from './types'; export * from './typeschema/crawler'; export * from './typeschema/types'; export * from './typeschema/validation'; +export * from './typeschema/slices'; export * from './utils'; diff --git a/packages/core/src/schema-crawler.ts b/packages/core/src/schema-crawler.ts new file mode 100644 index 0000000000..d41c77fdc5 --- /dev/null +++ b/packages/core/src/schema-crawler.ts @@ -0,0 +1,343 @@ +import { ElementsContextType, buildElementsContext } from './elements-context'; +import { SliceDefinitionWithTypes, isSliceDefinitionWithTypes } from './typeschema/slices'; +import { + InternalSchemaElement, + InternalTypeSchema, + SliceDefinition, + SlicingRules, + tryGetProfile, +} from './typeschema/types'; +import { isPopulated } from './utils'; + +export type VisitorSlicingRules = Omit & { + slices: SliceDefinitionWithTypes[]; +}; + +export interface SchemaVisitor { + /** + * Called when entering a schema. This is called once for the root profile and once for each + * extension with a profile associated with it. + * @param schema - The schema being entered. + */ + onEnterSchema?: (schema: InternalTypeSchema) => void; + /** + * Called when exiting a schema. See `onEnterSchema` for more information. + * @param schema - The schema being exited. + */ + onExitSchema?: (schema: InternalTypeSchema) => void; + + /** + * Called when entering an element. This is called for every element in the schema in a + * tree-like fashion. If the element has slices, the slices are crawled after `onEnterElement` + * but before `onExitElement`. + * + * @example + * Example of tree-like method invocation ordering: + * '''typescript + * onEnterElement('Patient.name') + * onEnterElement('Patient.name.given') + * onExitElement('Patient.name.given') + * onEnterElement('Patient.name.family') + * onExitElement('Patient.name.family') + * onExitElement('Patient.name') + * ''' + * + * + * @param path - The full path of the element being entered, even if within an extension. e.g The + * path of the ombCategory extension within the US Core Race extension will be + * 'Patient.extension.extension.value[x]' rather than 'Extension.extension.value[x]'. The latter is + * accessible on the element parameter. + * @param element - The element being entered. + * @param elementsContext - The context of the elements currently being crawled. + */ + onEnterElement?: (path: string, element: InternalSchemaElement, elementsContext: ElementsContextType) => void; + + /** + * Called when exiting an element. See `onEnterElement` for more information. + * @param path - The full path of the element being exited. + * @param element - The element being exited. + * @param elementsContext - The context of the elements currently being crawled. + */ + onExitElement?: (path: string, element: InternalSchemaElement, elementsContext: ElementsContextType) => void; + + /** + * Called when entering a slice. Called for every slice in a given sliced element. `onEnterElement` and `onExitElement` + * will be called in a tree-like fashion for elements within the slice followed by `onExitSlice`. + * + * @example + * Example of a sliced element being crawled with some elements excluded for brevity: + * '''typescript + * onEnterElement ('Observation.component') + * + * // systolic + * onEnterSlice ('Observation.component', systolicSlice, slicingRules) + * onEnterElement ('Observation.component.code') + * onExitElement ('Observation.component.code') + * onEnterElement ('Observation.component.value[x]') + * onEnterElement ('Observation.component.value[x].code') + * onExitElement ('Observation.component.value[x].code') + * onEnterElement ('Observation.component.value[x].system') + * onExitElement ('Observation.component.value[x].system') + * onExitElement ('Observation.component.value[x]') + * onExitSlice ('Observation.component', systolicSlice, slicingRules) + * + * // similar set of invocations for diastolic slice + * + * onExitElement ('Observation.component') + * ''' + * + * @param path - The full path of the sliced element being entered. See `onEnterElement` for more information. + * @param slice - The slice being entered. + * @param slicing - The slicing rules related to the slice being entered. + */ + onEnterSlice?: (path: string, slice: SliceDefinitionWithTypes, slicing: VisitorSlicingRules) => void; + + /** + * Called when exiting a slice. See `onEnterSlice` for more information. + * @param path - The full path of the sliced element being exited. See `onEnterElement` for more information. + * @param slice - The slice being exited. + * @param slicing - The slicing rules related to the slice. + */ + onExitSlice?: (path: string, slice: SliceDefinitionWithTypes, slicing: VisitorSlicingRules) => void; +} + +export class SchemaCrawler { + private readonly rootSchema: InternalTypeSchema & { type: string }; + private readonly visitor: SchemaVisitor; + private readonly elementsContextStack: ElementsContextType[]; + private sliceAllowList: SliceDefinition[] | undefined; + + constructor(schema: InternalTypeSchema, visitor: SchemaVisitor, elements?: InternalTypeSchema['elements']) { + if (schema.type === undefined) { + throw new Error('schema must include a type'); + } + this.rootSchema = schema as InternalTypeSchema & { type: string }; + + const rootContext = buildElementsContext({ + parentContext: undefined, + path: this.rootSchema.type, + elements: elements ?? this.rootSchema.elements, + profileUrl: this.rootSchema.name === this.rootSchema.type ? undefined : this.rootSchema.url, + }); + if (rootContext === undefined) { + throw new Error('Could not create root elements context'); + } + + this.elementsContextStack = [rootContext]; + this.visitor = visitor; + } + + private get elementsContext(): ElementsContextType { + return this.elementsContextStack[this.elementsContextStack.length - 1]; + } + + crawlElement(element: InternalSchemaElement, key: string, path: string): void { + if (this.visitor.onEnterSchema) { + this.visitor.onEnterSchema(this.rootSchema); + } + + const allowedElements = Object.fromEntries( + Object.entries(this.elementsContext.elements).filter(([elementKey]) => { + return elementKey.startsWith(key); + }) + ); + + this.crawlElementsImpl(allowedElements, path); + + if (this.visitor.onExitSchema) { + this.visitor.onExitSchema(this.rootSchema); + } + } + + crawlSlice(key: string, slice: SliceDefinition, slicing: SlicingRules): void { + const visitorSlicing = this.prepareSlices(slicing.slices, slicing); + + if (!isPopulated(visitorSlicing.slices)) { + throw new Error(`cannot crawl slice ${slice.name} since it has no type information`); + } + + if (this.visitor.onEnterSchema) { + this.visitor.onEnterSchema(this.rootSchema); + } + + this.sliceAllowList = [slice]; + + this.crawlSliceImpl(visitorSlicing.slices[0], slice.path, visitorSlicing); + this.sliceAllowList = undefined; + + if (this.visitor.onExitSchema) { + this.visitor.onExitSchema(this.rootSchema); + } + } + + crawlResource(): void { + if (this.visitor.onEnterSchema) { + this.visitor.onEnterSchema(this.rootSchema); + } + + this.crawlElementsImpl(this.rootSchema.elements, this.rootSchema.type); + + if (this.visitor.onExitSchema) { + this.visitor.onExitSchema(this.rootSchema); + } + } + + private crawlElementsImpl(elements: InternalTypeSchema['elements'], path: string): void { + const elementTree = createElementTree(elements); + for (const node of elementTree) { + this.crawlElementNode(node, path); + } + } + + private crawlElementNode(node: ElementNode, path: string): void { + const nodePath = path + '.' + node.key; + if (this.visitor.onEnterElement) { + this.visitor.onEnterElement(nodePath, node.element, this.elementsContext); + } + + for (const child of node.children) { + this.crawlElementNode(child, path); + } + + if (isPopulated(node.element?.slicing?.slices)) { + this.crawlSlicingImpl(node.element.slicing, nodePath); + } + + if (this.visitor.onExitElement) { + this.visitor.onExitElement(nodePath, node.element, this.elementsContext); + } + } + + private prepareSlices(slices: SliceDefinition[], slicing: SlicingRules): VisitorSlicingRules { + const slicesToVisit: SliceDefinitionWithTypes[] = []; + for (const slice of slices) { + if (!isSliceDefinitionWithTypes(slice)) { + continue; + } + const profileUrl = slice.type.find((t) => isPopulated(t.profile))?.profile?.[0]; + if (isPopulated(profileUrl)) { + const schema = tryGetProfile(profileUrl); + if (schema) { + slice.typeSchema = schema; + } + } + slicesToVisit.push(slice); + } + + const visitorSlicing = { ...slicing, slices: slicesToVisit } as VisitorSlicingRules; + return visitorSlicing; + } + + private crawlSlicingImpl(slicing: SlicingRules, path: string): void { + const visitorSlicing = this.prepareSlices(slicing.slices, slicing); + + for (const slice of visitorSlicing.slices) { + if (this.sliceAllowList === undefined || this.sliceAllowList.includes(slice)) { + this.crawlSliceImpl(slice, path, visitorSlicing); + } + } + } + + private crawlSliceImpl(slice: SliceDefinitionWithTypes, path: string, slicing: VisitorSlicingRules): void { + const sliceSchema = slice.typeSchema; + if (sliceSchema) { + if (this.visitor.onEnterSchema) { + this.visitor.onEnterSchema(sliceSchema); + } + } + + if (this.visitor.onEnterSlice) { + this.visitor.onEnterSlice(path, slice, slicing); + } + + let elementsContext: ElementsContextType | undefined; + + const sliceElements = sliceSchema?.elements ?? slice.elements; + if (isPopulated(sliceElements)) { + elementsContext = buildElementsContext({ + path, + parentContext: this.elementsContext, + elements: sliceElements, + }); + } + if (elementsContext) { + this.elementsContextStack.push(elementsContext); + } + + this.crawlElementsImpl(sliceElements, path); + + if (elementsContext) { + this.elementsContextStack.pop(); + } + + if (this.visitor.onExitSlice) { + this.visitor.onExitSlice(path, slice, slicing); + } + + if (sliceSchema) { + if (this.visitor.onExitSchema) { + this.visitor.onExitSchema(sliceSchema); + } + } + } +} + +type ElementNode = { + key: string; + element: InternalSchemaElement; + children: ElementNode[]; +}; + +/** + * Creates a tree of InternalSchemaElements nested by their key hierarchy: + * + * @param elements - + * @returns The list of root nodes of the tree + */ +function createElementTree(elements: Record): ElementNode[] { + const rootNodes: ElementNode[] = []; + + function isChildKey(parentKey: string, childKey: string): boolean { + return childKey.startsWith(parentKey + '.'); + } + + function addNode(currentNode: ElementNode, newNode: ElementNode): void { + for (const child of currentNode.children) { + // If the new node is a child of an existing child, recurse deeper + if (isChildKey(child.key, newNode.key)) { + addNode(child, newNode); + return; + } + } + // Otherwise, add it here + currentNode.children.push(newNode); + } + + const elementEntries = Object.entries(elements); + /* + By sorting beforehand, we guarantee that no false root nodes are created. + e.g. if 'a.b' were to be added to the tree before 'a', 'a.b' would be made a + root node when it should be a child of 'a'. + */ + elementEntries.sort((a, b) => a[0].localeCompare(b[0])); + + for (const [key, element] of elementEntries) { + const newNode: ElementNode = { key, element, children: [] }; + + let added = false; + for (const rootNode of rootNodes) { + if (isChildKey(rootNode.key, key)) { + addNode(rootNode, newNode); + added = true; + break; + } + } + + // If the string is not a child of any existing node, add it as a new root + if (!added) { + rootNodes.push(newNode); + } + } + + return rootNodes; +} diff --git a/packages/core/src/search/match.test.ts b/packages/core/src/search/match.test.ts index 2634957a3f..44597b1c7d 100644 --- a/packages/core/src/search/match.test.ts +++ b/packages/core/src/search/match.test.ts @@ -13,7 +13,7 @@ import { import { indexSearchParameterBundle } from '../types'; import { indexStructureDefinitionBundle } from '../typeschema/types'; import { matchesSearchRequest } from './match'; -import { Operator, SearchRequest, parseSearchDefinition } from './search'; +import { Operator, SearchRequest, parseSearchRequest } from './search'; // Dimensions: // 1. Search parameter type @@ -308,25 +308,25 @@ describe('Search matching', () => { expect( matchesSearchRequest( { resourceType: 'DiagnosticReport', status: 'preliminary' } as DiagnosticReport, - parseSearchDefinition('DiagnosticReport?status=cancelled') + parseSearchRequest('DiagnosticReport?status=cancelled') ) ).toBe(false); expect( matchesSearchRequest( { resourceType: 'DiagnosticReport', status: 'preliminary' } as DiagnosticReport, - parseSearchDefinition('DiagnosticReport?status:not=cancelled') + parseSearchRequest('DiagnosticReport?status:not=cancelled') ) ).toBe(true); expect( matchesSearchRequest( { resourceType: 'DiagnosticReport', status: 'cancelled' } as DiagnosticReport, - parseSearchDefinition('DiagnosticReport?status=cancelled') + parseSearchRequest('DiagnosticReport?status=cancelled') ) ).toBe(true); expect( matchesSearchRequest( { resourceType: 'DiagnosticReport', status: 'cancelled' } as DiagnosticReport, - parseSearchDefinition('DiagnosticReport?status:not=cancelled') + parseSearchRequest('DiagnosticReport?status:not=cancelled') ) ).toBe(false); @@ -335,25 +335,25 @@ describe('Search matching', () => { expect( matchesSearchRequest( { resourceType: 'ServiceRequest', orderDetail: [{ text: 'ORDERED' }] } as ServiceRequest, - parseSearchDefinition('ServiceRequest?order-detail=VOIDED,CANCELLED') + parseSearchRequest('ServiceRequest?order-detail=VOIDED,CANCELLED') ) ).toBe(false); expect( matchesSearchRequest( { resourceType: 'ServiceRequest', orderDetail: [{ text: 'ORDERED' }] } as ServiceRequest, - parseSearchDefinition('ServiceRequest?order-detail:not=VOIDED,CANCELLED') + parseSearchRequest('ServiceRequest?order-detail:not=VOIDED,CANCELLED') ) ).toBe(true); expect( matchesSearchRequest( { resourceType: 'ServiceRequest', orderDetail: [{ text: 'VOIDED' }] } as ServiceRequest, - parseSearchDefinition('ServiceRequest?order-detail=VOIDED,CANCELLED') + parseSearchRequest('ServiceRequest?order-detail=VOIDED,CANCELLED') ) ).toBe(true); expect( matchesSearchRequest( { resourceType: 'ServiceRequest', orderDetail: [{ text: 'VOIDED' }] } as ServiceRequest, - parseSearchDefinition('ServiceRequest?order-detail:not=VOIDED,CANCELLED') + parseSearchRequest('ServiceRequest?order-detail:not=VOIDED,CANCELLED') ) ).toBe(false); }); diff --git a/packages/core/src/search/parse.test.ts b/packages/core/src/search/parse.test.ts index f36f7cbcb9..3a34d5fa35 100644 --- a/packages/core/src/search/parse.test.ts +++ b/packages/core/src/search/parse.test.ts @@ -2,7 +2,7 @@ import { readJson } from '@medplum/definitions'; import { Bundle, SearchParameter } from '@medplum/fhirtypes'; import { indexSearchParameterBundle } from '../types'; import { indexStructureDefinitionBundle } from '../typeschema/types'; -import { Operator, parseSearchRequest, parseSearchUrl, SearchRequest } from './search'; +import { Operator, SearchRequest, parseSearchRequest, parseSearchUrl } from './search'; describe('Search parser', () => { beforeAll(() => { @@ -653,4 +653,25 @@ describe('Search parser', () => { parseSearchRequest('Patient', { _revinclude: 'Patient:*' }); }).toThrow(); }); + + test('_format', () => { + expect(parseSearchRequest('Patient', { _format: 'json' })).toMatchObject({ + resourceType: 'Patient', + format: 'json', + }); + }); + + test('_pretty=true', () => { + expect(parseSearchRequest('Patient', { _pretty: 'true' })).toMatchObject({ + resourceType: 'Patient', + pretty: true, + }); + }); + + test('_pretty=false', () => { + expect(parseSearchRequest('Patient', { _pretty: 'false' })).toMatchObject({ + resourceType: 'Patient', + pretty: false, + }); + }); }); diff --git a/packages/core/src/search/search.test.ts b/packages/core/src/search/search.test.ts index 5a8fc65ced..c41ee3639e 100644 --- a/packages/core/src/search/search.test.ts +++ b/packages/core/src/search/search.test.ts @@ -2,7 +2,15 @@ import { readJson } from '@medplum/definitions'; import { Bundle, Patient, SearchParameter } from '@medplum/fhirtypes'; import { indexSearchParameterBundle } from '../types'; import { indexStructureDefinitionBundle } from '../typeschema/types'; -import { Operator, SearchRequest, formatSearchQuery, parseSearchDefinition, parseXFhirQuery } from './search'; +import { + Operator, + SearchRequest, + formatSearchQuery, + parseSearchDefinition, + parseSearchRequest, + parseSearchUrl, + parseXFhirQuery, +} from './search'; describe('Search Utils', () => { beforeAll(() => { @@ -11,6 +19,61 @@ describe('Search Utils', () => { indexSearchParameterBundle(readJson('fhir/r4/search-parameters-medplum.json') as Bundle); }); + test('parseSearchRequest', () => { + expect(() => parseSearchRequest(null as unknown as string)).toThrow('Invalid search URL'); + expect(() => parseSearchRequest(undefined as unknown as string)).toThrow('Invalid search URL'); + expect(() => parseSearchRequest('')).toThrow('Invalid search URL'); + + expect(parseSearchRequest('Patient')).toMatchObject({ resourceType: 'Patient' }); + expect(parseSearchRequest('Patient?name=alice')).toMatchObject({ + resourceType: 'Patient', + filters: [{ code: 'name', operator: Operator.EQUALS, value: 'alice' }], + }); + expect(parseSearchRequest('Patient?_fields=id,name,birthDate')).toMatchObject({ + resourceType: 'Patient', + fields: ['id', 'name', 'birthDate'], + }); + + expect(parseSearchRequest('Patient')).toMatchObject({ resourceType: 'Patient' }); + expect(parseSearchRequest('Patient', { name: 'alice' })).toMatchObject({ + resourceType: 'Patient', + filters: [{ code: 'name', operator: Operator.EQUALS, value: 'alice' }], + }); + expect(parseSearchRequest('Patient', { name: ['alice'] })).toMatchObject({ + resourceType: 'Patient', + filters: [{ code: 'name', operator: Operator.EQUALS, value: 'alice' }], + }); + expect(parseSearchRequest('Patient', { _fields: 'id,name,birthDate' })).toMatchObject({ + resourceType: 'Patient', + fields: ['id', 'name', 'birthDate'], + }); + + expect(parseSearchRequest(new URL('https://example.com/Patient'))).toMatchObject({ resourceType: 'Patient' }); + expect(parseSearchRequest(new URL('https://example.com/Patient?name=alice'))).toMatchObject({ + resourceType: 'Patient', + filters: [{ code: 'name', operator: Operator.EQUALS, value: 'alice' }], + }); + expect(parseSearchRequest(new URL('https://example.com/Patient?_fields=id,name,birthDate'))).toMatchObject({ + resourceType: 'Patient', + fields: ['id', 'name', 'birthDate'], + }); + }); + + test('parseSearchUrl', () => { + expect(() => parseSearchUrl(null as unknown as URL)).toThrow('Invalid search URL'); + expect(() => parseSearchUrl(undefined as unknown as URL)).toThrow('Invalid search URL'); + + expect(parseSearchUrl(new URL('https://example.com/Patient'))).toMatchObject({ resourceType: 'Patient' }); + expect(parseSearchUrl(new URL('https://example.com/Patient?name=alice'))).toMatchObject({ + resourceType: 'Patient', + filters: [{ code: 'name', operator: Operator.EQUALS, value: 'alice' }], + }); + expect(parseSearchUrl(new URL('https://example.com/Patient?_fields=id,name,birthDate'))).toMatchObject({ + resourceType: 'Patient', + fields: ['id', 'name', 'birthDate'], + }); + }); + test('Parse Patient search', () => { const result = parseSearchDefinition('/x/y/z/Patient'); expect(result.resourceType).toBe('Patient'); diff --git a/packages/core/src/search/search.ts b/packages/core/src/search/search.ts index 7d6f16b3e1..b8c96e420f 100644 --- a/packages/core/src/search/search.ts +++ b/packages/core/src/search/search.ts @@ -18,6 +18,8 @@ export interface SearchRequest { include?: IncludeTarget[]; revInclude?: IncludeTarget[]; summary?: 'true' | 'text' | 'data'; + format?: string; + pretty?: boolean; } export interface Filter { @@ -121,24 +123,66 @@ const PREFIX_OPERATORS: Record = { /** * Parses a search URL into a search request. - * @param resourceType - The FHIR resource type. - * @param query - The collection of query string parameters. + * @param url - The original search URL or the FHIR resource type. + * @param query - Optional collection of additional query string parameters. * @returns A parsed SearchRequest. */ export function parseSearchRequest( - resourceType: T['resourceType'], - query: Record + url: T['resourceType'] | URL | string, + query?: Record ): SearchRequest { + if (!url) { + throw new Error('Invalid search URL'); + } + + // Parse the input into path and search parameters + let pathname: string = ''; + let searchParams: URLSearchParams | undefined = undefined; + if (typeof url === 'string') { + if (url.includes('?')) { + const [path, search] = url.split('?'); + pathname = path; + searchParams = new URLSearchParams(search); + } else { + pathname = url; + } + } else if (typeof url === 'object') { + pathname = url.pathname; + searchParams = url.searchParams; + } + + // Next, parse out the resource type from the URL + // By convention, the resource type is the last non-empty part of the path + let resourceType: ResourceType; + if (pathname.includes('/')) { + resourceType = pathname.split('/').filter(Boolean).pop() as ResourceType; + } else { + resourceType = pathname as ResourceType; + } + + // Next, parse out the search parameters + // First, we convert the URLSearchParams to an array of key-value pairs const queryArray: [string, string][] = []; - for (const [key, value] of Object.entries(query)) { - if (Array.isArray(value)) { - for (const v of value) { - queryArray.push([key, v]); + if (searchParams) { + queryArray.push(...searchParams.entries()); + } + + // Next, we merge in the query object + // This is an optional set of additional query parameters + // which should be added to the URL + if (query) { + for (const [key, value] of Object.entries(query)) { + if (Array.isArray(value)) { + for (const v of value) { + queryArray.push([key, v]); + } + } else { + queryArray.push([key, value ?? '']); } - } else { - queryArray.push([key, value ?? '']); } } + + // Finally we can move on to the actual parsing return parseSearchImpl(resourceType, queryArray); } @@ -146,24 +190,20 @@ export function parseSearchRequest( * Parses a search URL into a search request. * @param url - The search URL. * @returns A parsed SearchRequest. + * @deprecated Use parseSearchRequest instead. */ export function parseSearchUrl(url: URL): SearchRequest { - let resourceType; - for (const part of url.pathname.split('/')) { - if (part) { - resourceType = part; - } - } - return parseSearchImpl(resourceType as ResourceType, url.searchParams.entries()); + return parseSearchRequest(url); } /** * Parses a URL string into a SearchRequest. * @param url - The URL to parse. * @returns Parsed search definition. + * @deprecated Use parseSearchRequest instead. */ export function parseSearchDefinition(url: string): SearchRequest { - return parseSearchUrl(new URL(url, 'https://example.com/')); + return parseSearchRequest(url); } /** @@ -171,9 +211,10 @@ export function parseSearchDefinition(url: string * FHIR criteria strings are found on resources such as Subscription. * @param criteria - The FHIR criteria string. * @returns Parsed search definition. + * @deprecated Use parseSearchRequest instead. */ -export function parseCriteriaAsSearchRequest(criteria: string): SearchRequest { - return parseSearchUrl(new URL(criteria, 'https://api.medplum.com/')); +export function parseCriteriaAsSearchRequest(criteria: string): SearchRequest { + return parseSearchRequest(criteria); } function parseSearchImpl( @@ -258,6 +299,14 @@ function parseKeyValue(searchRequest: SearchRequest, key: string, value: string) searchRequest.fields = value.split(','); break; + case '_format': + searchRequest.format = value; + break; + + case '_pretty': + searchRequest.pretty = value === 'true'; + break; + default: { const param = globalSchema.types[searchRequest.resourceType]?.searchParams?.[code]; if (param) { @@ -410,7 +459,7 @@ export function parseXFhirQuery(query: string, variables: Record { + let wsServer: WS; + + beforeEach(() => { + wsServer = new WS('wss://example.com/ws/subscriptions-r4', { jsonProtocol: true }); + }); + + afterEach(() => { + WS.clean(); + }); + + test('.close()', async () => { + const robustWebSocket = new RobustWebSocket('wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + expect(robustWebSocket.readyState).toEqual(WebSocket.OPEN); + + robustWebSocket.close(); + expect(robustWebSocket.readyState).not.toEqual(WebSocket.OPEN); + }); + + test('Getting readyState of underlying WebSocket', async () => { + const robustWebSocket = new RobustWebSocket('wss://example.com/ws/subscriptions-r4'); + expect(robustWebSocket.readyState).toEqual(WebSocket.CONNECTING); + + await wsServer.connected; + expect(robustWebSocket.readyState).toEqual(WebSocket.OPEN); + + robustWebSocket.close(); + expect(robustWebSocket.readyState).toEqual(WebSocket.CLOSING); + + await wsServer.closed; + expect(robustWebSocket.readyState).toEqual(WebSocket.CLOSED); + }); + + test('Sending before WebSocket is connected', async () => { + const robustWebSocket = new RobustWebSocket('wss://example.com/ws/subscriptions-r4'); + expect(() => robustWebSocket.send(JSON.stringify({ hello: 'medplum' }))).not.toThrow(); + expect(() => robustWebSocket.send(JSON.stringify({ med: 'plum' }))).not.toThrow(); + + // Test that open is fired + await new Promise((resolve) => { + robustWebSocket.addEventListener('open', () => { + resolve(); + }); + }); + + await expect(wsServer).toReceiveMessage({ hello: 'medplum' }); + await expect(wsServer).toReceiveMessage({ med: 'plum' }); + }); + + test('Wait for `open` before sending', async () => { + const robustWebSocket = new RobustWebSocket('wss://example.com/ws/subscriptions-r4'); + // Test that open is fired + await new Promise((resolve) => { + robustWebSocket.addEventListener('open', () => { + resolve(); + }); + }); + + expect(() => robustWebSocket.send(JSON.stringify({ hello: 'medplum' }))).not.toThrow(); + await expect(wsServer).toReceiveMessage({ hello: 'medplum' }); + expect(() => robustWebSocket.send(JSON.stringify({ med: 'plum' }))).not.toThrow(); + await expect(wsServer).toReceiveMessage({ med: 'plum' }); + }); + + test('Should emit `message` when message received from WebSocket', async () => { + const robustWebSocket = new RobustWebSocket('wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + const receivedEvent = await new Promise((resolve) => { + robustWebSocket.addEventListener('message', (event) => { + resolve(event as MessageEvent); + }); + wsServer.send({ med: 'plum' }); + }); + expect(receivedEvent?.type).toEqual('message'); + expect(receivedEvent?.data).toBeDefined(); + expect(JSON.parse(receivedEvent.data)).toEqual({ med: 'plum' }); + }); + + test('Should emit `error` when error received from WebSocket', async () => { + const robustWebSocket = new RobustWebSocket('wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + const receivedEvent = await new Promise((resolve) => { + robustWebSocket.addEventListener('error', (event) => { + resolve(event as MessageEvent); + }); + wsServer.error(); + }); + expect(receivedEvent?.type).toEqual('error'); + }); + + test('Should emit `close` when server closes connection', async () => { + const robustWebSocket = new RobustWebSocket('wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + const receivedEvent = await new Promise((resolve) => { + robustWebSocket.addEventListener('close', (event) => { + resolve(event as CloseEvent); + }); + wsServer.close(); + }); + expect(receivedEvent?.type).toEqual('close'); + }); +}); + +describe('SubscriptionEmitter', () => { + test('getCriteria()', () => { + const emitter = new SubscriptionEmitter(); + expect(emitter.getCriteria().size).toEqual(0); + emitter._addCriteria('Communication'); + expect(emitter.getCriteria().size).toEqual(1); + + // Should be able to add again without changing count + emitter._addCriteria('Communication'); + expect(emitter.getCriteria().size).toEqual(1); + + emitter._addCriteria('DiagnosticReport'); + expect(emitter.getCriteria().size).toEqual(2); + + emitter._removeCriteria('DiagnosticReport'); + expect(emitter.getCriteria().size).toEqual(1); + }); +}); + +describe('SubscriptionManager', () => { + describe('Constructor', () => { + let wsServer: WS; + + beforeAll(async () => { + wsServer = new WS('wss://example.com/ws/subscriptions-r4', { jsonProtocol: true }); + }); + + afterAll(() => { + WS.clean(); + }); + + test('should throw if not passed a `MedplumClient`', () => { + // @ts-expect-error Invalid value for `medplum` + expect(() => new SubscriptionManager(undefined, 'wss://example.com/ws/subscriptions-r4')).toThrow( + OperationOutcomeError + ); + }); + + test('should throw if `wsUrl` is not a URL or URL string', async () => { + // @ts-expect-error Invalid value for `wsUrl` + expect(() => new SubscriptionManager(medplum, undefined)).toThrow(OperationOutcomeError); + // @ts-expect-error Invalid value for `wsUrl` + expect(() => new SubscriptionManager(medplum, new WebSocket('wss://example.com/ws/subscriptions-r4'))).toThrow( + OperationOutcomeError + ); + }); + + test('should NOT throw if `wsUrl` is a VALID URL or URL string', async () => { + const manager1 = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + expect(manager1).toBeDefined(); + await wsServer.connected; + + const manager2 = new SubscriptionManager(medplum, new URL('wss://example.com/ws/subscriptions-r4')); + expect(manager2).toBeDefined(); + await wsServer.connected; + }); + + test('should throw if `wsUrl` is an INVALID URL string', () => { + expect(() => new SubscriptionManager(medplum, 'abc123')).toThrow(OperationOutcomeError); + }); + }); + + describe('addCriteria()', () => { + let wsServer: WS; + + beforeAll(() => { + medplum.router.addRoute('GET', `/fhir/R4/Subscription/${MOCK_SUBSCRIPTION_ID}/$get-ws-binding-token`, () => { + return { + resourceType: 'Parameters', + parameter: [ + { + name: 'token', + valueString: 'token-123', + }, + { + name: 'expiration', + valueDateTime: new Date(Date.now() + ONE_HOUR).toISOString(), + }, + { + name: 'websocket-url', + valueUrl: 'wss://example.com/ws/subscriptions-r4', + }, + ], + } as Parameters; + }); + wsServer = new WS('wss://example.com/ws/subscriptions-r4', { jsonProtocol: true }); + }); + + afterAll(() => { + WS.clean(); + }); + + test('should add a criteria and receive messages for that criteria', async () => { + const manager = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + + const emitter = manager.addCriteria('Communication'); + expect(emitter).toBeInstanceOf(SubscriptionEmitter); + + const subscriptionId = await new Promise((resolve) => { + const handler = (event: SubscriptionEventMap['connect']): void => { + emitter.removeEventListener('connect', handler); + resolve(event.payload.subscriptionId); + }; + emitter.addEventListener('connect', handler); + }); + + expect(typeof subscriptionId).toEqual('string'); + await expect(wsServer).toReceiveMessage({ type: 'bind-with-token', payload: { token: 'token-123' } }); + + const timestamp = new Date().toISOString(); + const resource = { resourceType: 'Communication', id: generateId() } as Communication; + const sentBundle = { + resourceType: 'Bundle', + timestamp, + type: 'history', + entry: [ + { + resource: { + resourceType: 'SubscriptionStatus', + type: 'event-notification', + subscription: { reference: `Subscription/${subscriptionId}` }, + notificationEvent: [{ eventNumber: '0', timestamp, focus: createReference(resource) }], + } as SubscriptionStatus, + }, + { + resource, + fullUrl: `https://example.com/fhir/R4/Communication/${resource.id}`, + }, + ], + } as Bundle; + + const receivedBundle = await new Promise((resolve) => { + const handler = (event: SubscriptionEventMap['message']): void => { + resolve(event.payload); + emitter.removeEventListener('message', handler); + }; + emitter.addEventListener('message', handler); + + wsServer.send(sentBundle); + }); + expect(receivedBundle).toEqual(sentBundle); + }); + + test('should emit `error` when token or url missing from `Subscription/$get-ws-binding-token` operation', async () => { + const originalError = console.error; + console.error = jest.fn(); + + const manager1 = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + + medplum.router.addRoute('GET', `/fhir/R4/Subscription/${MOCK_SUBSCRIPTION_ID}/$get-ws-binding-token`, () => { + return { + resourceType: 'Parameters', + parameter: [ + { + name: 'expiration', + valueDateTime: new Date(Date.now() + ONE_HOUR).toISOString(), + }, + { + name: 'websocket-url', + valueUrl: 'wss://example.com/ws/subscriptions-r4', + }, + ], + } as Parameters; + }); + + const criteriaEmitter1 = manager1.addCriteria('Communication'); + + const [masterEvent1, criteriaEvent1] = await new Promise((resolve, reject) => { + const promises = []; + promises.push( + new Promise((resolve) => { + manager1.getMasterEmitter().addEventListener('error', (event) => { + resolve(event); + }); + }) + ); + promises.push( + new Promise((resolve) => { + criteriaEmitter1.addEventListener('error', (event) => { + resolve(event); + }); + }) + ); + + Promise.all(promises).then(resolve).catch(reject); + }); + + expect(masterEvent1?.type).toEqual('error'); + expect(masterEvent1?.payload).toBeInstanceOf(OperationOutcomeError); + expect(criteriaEvent1?.type).toEqual('error'); + expect(criteriaEvent1?.payload).toBeInstanceOf(OperationOutcomeError); + + medplum.router.addRoute('GET', `/fhir/R4/Subscription/${MOCK_SUBSCRIPTION_ID}/$get-ws-binding-token`, () => { + return { + resourceType: 'Parameters', + parameter: [ + { + name: 'token', + valueString: 'token-123', + }, + { + name: 'expiration', + valueDateTime: new Date(Date.now() + ONE_HOUR).toISOString(), + }, + ], + } as Parameters; + }); + + const manager2 = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + + const criteriaEmitter2 = manager2.addCriteria('Communication'); + + const [masterEvent2, criteriaEvent2] = await new Promise((resolve, reject) => { + const promises = []; + promises.push( + new Promise((resolve) => { + manager2.getMasterEmitter().addEventListener('error', (event) => { + resolve(event); + }); + }) + ); + promises.push( + new Promise((resolve) => { + criteriaEmitter2.addEventListener('error', (event) => { + resolve(event); + }); + }) + ); + + Promise.all(promises).then(resolve).catch(reject); + }); + + expect(masterEvent2?.type).toEqual('error'); + expect(masterEvent2?.payload).toBeInstanceOf(OperationOutcomeError); + expect(criteriaEvent2?.type).toEqual('error'); + expect(criteriaEvent2?.payload).toBeInstanceOf(OperationOutcomeError); + + expect(console.error).toHaveBeenCalledTimes(2); + console.error = originalError; + }); + }); + + describe('removeCriteria()', () => { + let wsServer: WS; + let manager: SubscriptionManager; + let emitter: SubscriptionEmitter; + + beforeAll(async () => { + medplum.router.addRoute('GET', `/fhir/R4/Subscription/${MOCK_SUBSCRIPTION_ID}/$get-ws-binding-token`, () => { + return { + resourceType: 'Parameters', + parameter: [ + { + name: 'token', + valueString: 'token-123', + }, + { + name: 'expiration', + valueDateTime: new Date(Date.now() + ONE_HOUR).toISOString(), + }, + { + name: 'websocket-url', + valueUrl: 'wss://example.com/ws/subscriptions-r4', + }, + ], + } as Parameters; + }); + wsServer = new WS('wss://example.com/ws/subscriptions-r4', { jsonProtocol: true }); + + manager = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + + emitter = manager.addCriteria('Communication'); + expect(emitter).toBeInstanceOf(SubscriptionEmitter); + + const subscriptionId = await new Promise((resolve) => { + const handler = (event: SubscriptionEventMap['connect']): void => { + emitter.removeEventListener('connect', handler); + resolve(event.payload.subscriptionId); + }; + emitter.addEventListener('connect', handler); + }); + + expect(typeof subscriptionId).toEqual('string'); + await expect(wsServer).toReceiveMessage({ type: 'bind-with-token', payload: { token: 'token-123' } }); + }); + + afterAll(() => { + WS.clean(); + }); + + test('should not throw when remove has been called on a criteria that is not known', () => { + const originalWarn = console.warn; + console.warn = jest.fn(); + expect(() => manager.removeCriteria('DiagnosticReport')).not.toThrow(); + expect(console.warn).toHaveBeenCalledTimes(1); + console.warn = originalWarn; + }); + + test('should not clean up a criteria if there are outstanding listeners', (done) => { + let success = false; + const emitter = manager.addCriteria('Communication'); + expect(emitter).toBeInstanceOf(SubscriptionEmitter); + + const handler = (): void => { + emitter.removeEventListener('disconnect', handler); + if (!success) { + done(new Error('Received `disconnect` when not expected')); + } + }; + emitter.addEventListener('disconnect', handler); + + manager.removeCriteria('Communication'); + + sleep(200) + .then(() => { + emitter.removeEventListener('disconnect', handler); + success = true; + done(); + }) + .catch(console.error); + }); + + test('should clean up for a criteria if we are the last subscriber', (done) => { + let success = false; + const handler = (): void => { + emitter.removeEventListener('disconnect', handler); + expect(true).toBeTruthy(); + success = true; + done(); + }; + emitter.addEventListener('disconnect', handler); + + manager.removeCriteria('Communication'); + + sleep(200) + .then(() => { + emitter.removeEventListener('disconnect', handler); + if (!success) { + done(new Error('Expected to receive `disconnect` message')); + } + }) + .catch(console.error); + }); + }); + + describe('getCriteriaCount()', () => { + let wsServer: WS; + + beforeAll(() => { + medplum.router.addRoute('GET', `/fhir/R4/Subscription/${MOCK_SUBSCRIPTION_ID}/$get-ws-binding-token`, () => { + return { + resourceType: 'Parameters', + parameter: [ + { + name: 'token', + valueString: 'token-123', + }, + { + name: 'expiration', + valueDateTime: new Date(Date.now() + ONE_HOUR).toISOString(), + }, + { + name: 'websocket-url', + valueUrl: 'wss://example.com/ws/subscriptions-r4', + }, + ], + } as Parameters; + }); + wsServer = new WS('wss://example.com/ws/subscriptions-r4', { jsonProtocol: true }); + }); + + afterAll(() => { + WS.clean(); + }); + + test('should return the correct amount of criteria', async () => { + const manager = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + + expect(manager.getCriteriaCount()).toEqual(0); + manager.addCriteria('Communication'); + expect(manager.getCriteriaCount()).toEqual(1); + manager.removeCriteria('Communication'); + expect(manager.getCriteriaCount()).toEqual(0); + }); + }); + + describe('closeWebSocket()', () => { + let wsServer: WS; + + beforeAll(() => { + wsServer = new WS('wss://example.com/ws/subscriptions-r4', { jsonProtocol: true }); + }); + + afterAll(() => { + WS.clean(); + }); + + test('should close websocket and emit `close` when called', async () => { + const manager = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + + const criteriaEmitter = manager.addCriteria('Communication'); + + const [masterEvent, criteriaEvent] = await new Promise((resolve, reject) => { + const promises = []; + promises.push( + new Promise((resolve) => { + manager.getMasterEmitter().addEventListener('close', (event) => { + resolve(event); + }); + }) + ); + promises.push( + new Promise((resolve) => { + criteriaEmitter.addEventListener('close', (event) => { + resolve(event); + }); + }) + ); + + expect(() => manager.closeWebSocket()).not.toThrow(); + Promise.all(promises).then(resolve).catch(reject); + }); + + await wsServer.closed; + expect(masterEvent?.type).toEqual('close'); + expect(criteriaEvent?.type).toEqual('close'); + }); + + test('should not emit close twice', async () => { + const manager = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + + const event = await new Promise((resolve) => { + manager.getMasterEmitter().addEventListener('close', (event) => { + resolve(event); + }); + expect(() => manager.closeWebSocket()).not.toThrow(); + }); + expect(event?.type).toEqual('close'); + + await wsServer.closed; + + await new Promise((resolve, reject) => { + manager.getMasterEmitter().addEventListener('close', () => { + reject(new Error('Expected not to call')); + }); + expect(() => manager.closeWebSocket()).not.toThrow(); + setTimeout(() => resolve(), 250); + }); + + await wsServer.closed; + }); + }); + + describe('getMasterEmitter()', () => { + let wsServer: WS; + + beforeAll(async () => { + wsServer = new WS('wss://example.com/ws/subscriptions-r4', { jsonProtocol: true }); + }); + + afterAll(() => { + WS.clean(); + }); + + test('should always get the same emitter', async () => { + const manager = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + expect(manager.getMasterEmitter()).toEqual(manager.getMasterEmitter()); + }); + }); + + describe('Scenarios', () => { + let wsServer: WS; + + beforeAll(() => { + medplum.router.addRoute('GET', `/fhir/R4/Subscription/${MOCK_SUBSCRIPTION_ID}/$get-ws-binding-token`, () => { + return { + resourceType: 'Parameters', + parameter: [ + { + name: 'token', + valueString: 'token-123', + }, + { + name: 'expiration', + valueDateTime: new Date(Date.now() + ONE_HOUR).toISOString(), + }, + { + name: 'websocket-url', + valueUrl: 'wss://example.com/ws/subscriptions-r4', + }, + ], + } as Parameters; + }); + wsServer = new WS('wss://example.com/ws/subscriptions-r4', { jsonProtocol: true }); + }); + + afterAll(() => { + WS.clean(); + }); + + test("should warn when receiving notification for subscription we aren't expecting", async () => { + const originalWarn = console.warn; + console.warn = jest.fn(); + + // @ts-expect-error We don't use manager + const _manager = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + + const timestamp = new Date().toISOString(); + const resource = { resourceType: 'Communication', id: generateId() } as Communication; + const sentBundle = { + resourceType: 'Bundle', + timestamp, + type: 'history', + entry: [ + { + resource: { + resourceType: 'SubscriptionStatus', + type: 'event-notification', + subscription: { reference: `Subscription/${MOCK_SUBSCRIPTION_ID}` }, + notificationEvent: [{ eventNumber: '0', timestamp, focus: createReference(resource) }], + } as SubscriptionStatus, + }, + { + resource, + fullUrl: `https://example.com/fhir/R4/Communication/${resource.id}`, + }, + ], + } as Bundle; + + wsServer.send(sentBundle); + + expect(console.warn).toHaveBeenCalled(); + console.warn = originalWarn; + }); + + test('should emit `heartbeat` event when heartbeat received', async () => { + const manager = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + + const timestamp = new Date().toISOString(); + const sentBundle = { + resourceType: 'Bundle', + timestamp, + type: 'history', + entry: [ + { + resource: { + resourceType: 'SubscriptionStatus', + status: 'active', + type: 'heartbeat', + subscription: { reference: `Subscription/${MOCK_SUBSCRIPTION_ID}` }, + } as SubscriptionStatus, + }, + ], + } as Bundle; + + const receivedBundle = await new Promise((resolve) => { + const emitter = manager.getMasterEmitter(); + emitter.addEventListener('heartbeat', (event) => { + resolve(event.payload); + }); + wsServer.send(sentBundle); + }); + + expect(receivedBundle).toEqual(sentBundle); + }); + + test('should emit `error` event when error received from WS', async () => { + const manager = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + + const receivedEvent = await new Promise((resolve) => { + manager.getMasterEmitter().addEventListener('error', (event) => { + resolve(event); + }); + wsServer.error(); + }); + + expect(receivedEvent).toMatchObject({ type: 'error', payload: expect.any(Error) }); + }); + + test('should emit `error` event when invalid message comes in over WebSocket', async () => { + const originalError = console.error; + console.error = jest.fn(); + + const wsServer = new WS('wss://example.com/ws/subscriptions-r4'); + const manager = new SubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + await wsServer.connected; + + const emitter = manager.addCriteria('Communication'); + const [receivedEvent1, receivedEvent2] = await new Promise((resolve, reject) => { + const promises = []; + promises.push( + new Promise((resolve) => { + manager.getMasterEmitter().addEventListener('error', (event) => { + resolve(event); + }); + }) + ); + promises.push( + new Promise((resolve) => { + emitter.addEventListener('error', (event) => { + resolve(event); + }); + }) + ); + + wsServer.send('invalid_json'); + Promise.all(promises).then(resolve).catch(reject); + }); + + expect(receivedEvent1?.type).toEqual('error'); + expect(receivedEvent1?.payload).toBeInstanceOf(SyntaxError); + expect(receivedEvent1?.payload?.message).toMatch(/^Unexpected token/); + + expect(receivedEvent2?.type).toEqual('error'); + expect(receivedEvent2?.payload).toBeInstanceOf(SyntaxError); + expect(receivedEvent2?.payload?.message).toMatch(/^Unexpected token/); + + expect(console.error).toHaveBeenCalledTimes(1); + console.error = originalError; + }); + }); +}); diff --git a/packages/core/src/subscriptions/index.ts b/packages/core/src/subscriptions/index.ts new file mode 100644 index 0000000000..51fc6dc4f7 --- /dev/null +++ b/packages/core/src/subscriptions/index.ts @@ -0,0 +1,342 @@ +import { Bundle, Parameters, Subscription, SubscriptionStatus } from '@medplum/fhirtypes'; +import { MedplumClient } from '../client'; +import { TypedEventTarget } from '../eventtarget'; +import { OperationOutcomeError, serverError, validationError } from '../outcomes'; +import { ProfileResource, getReferenceString, resolveId } from '../utils'; + +export type SubscriptionEventMap = { + connect: { type: 'connect'; payload: { subscriptionId: string } }; + disconnect: { type: 'disconnect'; payload: { subscriptionId: string } }; + error: { type: 'error'; payload: Error }; + message: { type: 'message'; payload: Bundle }; + close: { type: 'close' }; + heartbeat: { type: 'heartbeat'; payload: Bundle }; +}; + +export type RobustWebSocketEventMap = { + open: { type: 'open' }; + message: MessageEvent; + error: Event; + close: CloseEvent; +}; + +export class RobustWebSocket extends TypedEventTarget { + private ws: WebSocket; + private messageBuffer: string[]; + bufferedAmount = -Infinity; + extensions = 'NOT_IMPLEMENTED'; + + constructor(url: string) { + super(); + this.messageBuffer = []; + + const ws = new WebSocket(url); + + ws.addEventListener('open', () => { + if (this.messageBuffer.length) { + const buffer = this.messageBuffer; + for (const msg of buffer) { + ws.send(msg); + } + } + this.dispatchEvent(new Event('open')); + }); + + ws.addEventListener('error', (event) => { + this.dispatchEvent(event); + }); + + ws.addEventListener('message', (event) => { + this.dispatchEvent(event); + }); + + ws.addEventListener('close', (event) => { + this.dispatchEvent(event); + }); + + this.ws = ws; + } + + get readyState(): number { + return this.ws.readyState; + } + + close(): void { + this.ws.close(); + } + + send(message: string): void { + if (this.ws.readyState !== WebSocket.OPEN) { + this.messageBuffer.push(message); + return; + } + + try { + this.ws.send(message); + } catch (err: unknown) { + this.dispatchEvent(new ErrorEvent('error', { error: err as Error, message: (err as Error).message })); + this.messageBuffer.push(message); + } + } +} + +/** + * An `EventTarget` that emits events when new subscription notifications come in over WebSockets. + * + * ----- + * + * ### Events emitted: + * + * - `connect` - A new subscription is connected to the `SubscriptionManager` and `message` events for this subscription can be expected. + * - `disconnect` - The specified subscription is no longer being monitored by the `SubscriptionManager`. + * - `error` - An error has occurred. + * - `message` - A message containing a notification `Bundle` has been received. + * - `close` - The WebSocket has been closed. + * - `heartbeat` - A `heartbeat` message has been received. + */ +export class SubscriptionEmitter extends TypedEventTarget { + private criteria: Set; + constructor(...criteria: string[]) { + super(); + this.criteria = new Set(criteria); + } + getCriteria(): Set { + return this.criteria; + } + /** + * @internal + * @param criteria - The criteria to add to this `SubscriptionEmitter`. + */ + _addCriteria(criteria: string): void { + this.criteria.add(criteria); + } + /** + * @internal + * @param criteria - The criteria to remove from this `SubscriptionEmitter`. + */ + _removeCriteria(criteria: string): void { + this.criteria.delete(criteria); + } +} + +class CriteriaEntry { + readonly criteria: string; + readonly emitter: SubscriptionEmitter; + refCount: number; + subscriptionId?: string; + + constructor(criteria: string) { + this.criteria = criteria; + this.emitter = new SubscriptionEmitter(criteria); + this.refCount = 1; + } +} + +export class SubscriptionManager { + private readonly medplum: MedplumClient; + private ws: RobustWebSocket; + private masterSubEmitter?: SubscriptionEmitter; + private criteriaEntries: Map; // Map + private criteriaEntriesBySubscriptionId: Map; // Map + private wsClosed: boolean; + + constructor(medplum: MedplumClient, wsUrl: URL | string) { + if (!(medplum instanceof MedplumClient)) { + throw new OperationOutcomeError(validationError('First arg of constructor should be a `MedplumClient`')); + } + let url: string; + try { + url = new URL(wsUrl).toString(); + } catch (_err) { + throw new OperationOutcomeError(validationError('Not a valid URL')); + } + const ws = new RobustWebSocket(url); + + this.medplum = medplum; + this.ws = ws; + this.masterSubEmitter = new SubscriptionEmitter(); + this.criteriaEntries = new Map(); + this.criteriaEntriesBySubscriptionId = new Map(); + this.wsClosed = false; + + this.setupWebSocketListeners(); + } + + private setupWebSocketListeners(): void { + const ws = this.ws; + + ws.addEventListener('message', (event) => { + try { + const bundle = JSON.parse(event.data) as Bundle; + // Get criteria for event + const status = bundle?.entry?.[0]?.resource as SubscriptionStatus; + // Handle heartbeat + if (status.type === 'heartbeat') { + this.masterSubEmitter?.dispatchEvent({ type: 'heartbeat', payload: bundle }); + return; + } + this.masterSubEmitter?.dispatchEvent({ type: 'message', payload: bundle }); + const criteriaEntry = this.criteriaEntriesBySubscriptionId.get(resolveId(status.subscription) as string); + if (!criteriaEntry) { + console.warn('Received notification for criteria the SubscriptionManager is not listening for'); + return; + } + // Emit event for criteria + criteriaEntry.emitter.dispatchEvent({ type: 'message', payload: bundle }); + } catch (err: unknown) { + console.error(err); + const errorEvent = { type: 'error', payload: err as Error } as SubscriptionEventMap['error']; + this.masterSubEmitter?.dispatchEvent(errorEvent); + for (const { emitter } of this.criteriaEntries.values()) { + emitter.dispatchEvent(errorEvent); + } + } + }); + + ws.addEventListener('error', () => { + const errorEvent = { + type: 'error', + payload: new OperationOutcomeError(serverError(new Error('WebSocket error'))), + } as SubscriptionEventMap['error']; + this.masterSubEmitter?.dispatchEvent(errorEvent); + for (const { emitter } of this.criteriaEntries.values()) { + emitter.dispatchEvent(errorEvent); + } + }); + + ws.addEventListener('close', () => { + const closeEvent = { type: 'close' } as SubscriptionEventMap['close']; + if (this.wsClosed) { + this.masterSubEmitter?.dispatchEvent(closeEvent); + } + for (const { emitter } of this.criteriaEntries.values()) { + emitter.dispatchEvent(closeEvent); + } + }); + } + + private emitConnect(subscriptionId: string): void { + const connectEvent = { type: 'connect', payload: { subscriptionId } } as SubscriptionEventMap['connect']; + this.masterSubEmitter?.dispatchEvent(connectEvent); + for (const { emitter } of this.criteriaEntries.values()) { + emitter.dispatchEvent(connectEvent); + } + } + + private emitError(criteria: string, error: Error): void { + const errorEvent = { type: 'error', payload: error } as SubscriptionEventMap['error']; + this.masterSubEmitter?.dispatchEvent(errorEvent); + this.criteriaEntries.get(criteria)?.emitter?.dispatchEvent(errorEvent); + } + + private async getTokenForCriteria(criteriaEntry: CriteriaEntry): Promise<[string, string]> { + let subscriptionId = criteriaEntry?.subscriptionId; + if (!subscriptionId) { + // Make a new subscription + const subscription = await this.medplum.createResource({ + resourceType: 'Subscription', + status: 'active', + reason: `WebSocket subscription for ${getReferenceString(this.medplum.getProfile() as ProfileResource)}`, + criteria: criteriaEntry.criteria, + channel: { type: 'websocket' }, + }); + subscriptionId = subscription.id as string; + } + + // Get binding token + const { parameter } = (await this.medplum.get( + `/fhir/R4/Subscription/${subscriptionId}/$get-ws-binding-token` + )) as Parameters; + const token = parameter?.find((param) => param.name === 'token')?.valueString; + const url = parameter?.find((param) => param.name === 'websocket-url')?.valueUrl; + + if (!token) { + throw new OperationOutcomeError(validationError('Failed to get token')); + } + if (!url) { + throw new OperationOutcomeError(validationError('Failed to get URL from $get-ws-binding-token')); + } + + return [subscriptionId, token]; + } + + addCriteria(criteria: string): SubscriptionEmitter { + if (this.masterSubEmitter) { + this.masterSubEmitter._addCriteria(criteria); + } + const criteriaEntry = this.criteriaEntries.get(criteria); + if (criteriaEntry) { + criteriaEntry.refCount += 1; + return criteriaEntry.emitter; + } + const newCriteriaEntry = new CriteriaEntry(criteria); + this.criteriaEntries.set(criteria, newCriteriaEntry); + + this.getTokenForCriteria(newCriteriaEntry) + .then(([subscriptionId, token]) => { + newCriteriaEntry.subscriptionId = subscriptionId; + this.criteriaEntriesBySubscriptionId.set(subscriptionId, newCriteriaEntry); + + // Emit connect event + this.emitConnect(subscriptionId); + // Send binding message + this.ws.send(JSON.stringify({ type: 'bind-with-token', payload: { token } })); + }) + .catch((err) => { + console.error(err.message); + this.emitError(criteria, err); + this.criteriaEntries.delete(criteria); + }); + + return newCriteriaEntry.emitter; + } + + removeCriteria(criteria: string): void { + const criteriaEntry = this.criteriaEntries.get(criteria); + if (!criteriaEntry) { + console.warn('Criteria not known to `SubscriptionManager`. Possibly called remove too many times.'); + return; + } + + criteriaEntry.refCount -= 1; + if (criteriaEntry.refCount > 0) { + return; + } + + // If actually removing + const subscriptionId = this.criteriaEntries.get(criteria)?.subscriptionId; + const disconnectEvent = { type: 'disconnect', payload: { subscriptionId } } as SubscriptionEventMap['disconnect']; + // Remove from master + if (this.masterSubEmitter) { + this.masterSubEmitter._removeCriteria(criteria); + + // Emit disconnect on master + this.masterSubEmitter.dispatchEvent(disconnectEvent); + } + // Emit disconnect on criteria emitter + this.criteriaEntries.get(criteria)?.emitter?.dispatchEvent(disconnectEvent); + this.criteriaEntries.delete(criteria); + if (subscriptionId) { + this.criteriaEntriesBySubscriptionId.delete(subscriptionId); + } + } + + closeWebSocket(): void { + if (this.wsClosed) { + return; + } + this.wsClosed = true; + this.ws.close(); + } + + getCriteriaCount(): number { + return this.criteriaEntries.size; + } + + getMasterEmitter(): SubscriptionEmitter { + if (!this.masterSubEmitter) { + this.masterSubEmitter = new SubscriptionEmitter(...Array.from(this.criteriaEntries.keys())); + } + return this.masterSubEmitter; + } +} diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 84dc72bbb8..ebdb98f9e5 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -349,10 +349,15 @@ function capitalizeDisplayWord(word: string): string { * Returns an element definition by type and property name. * @param typeName - The type name. * @param propertyName - The property name. + * @param profileUrl - (optional) The URL of the current resource profile * @returns The element definition if found. */ -export function getElementDefinition(typeName: string, propertyName: string): InternalSchemaElement | undefined { - const typeSchema = tryGetDataType(typeName); +export function getElementDefinition( + typeName: string, + propertyName: string, + profileUrl?: string +): InternalSchemaElement | undefined { + const typeSchema = tryGetDataType(typeName, profileUrl); if (!typeSchema) { return undefined; } diff --git a/packages/core/src/typeschema/crawler.ts b/packages/core/src/typeschema/crawler.ts index b433aa8f36..47293aceb9 100644 --- a/packages/core/src/typeschema/crawler.ts +++ b/packages/core/src/typeschema/crawler.ts @@ -102,21 +102,25 @@ class ResourceCrawler { } } -export function getNestedProperty(value: TypedValue, key: string): (TypedValue | TypedValue[] | undefined)[] { +export function getNestedProperty( + value: TypedValue, + key: string, + options?: { profileUrl?: string } +): (TypedValue | TypedValue[] | undefined)[] { if (key === '$this') { return [value]; } const [firstProp, ...nestedProps] = key.split('.'); - let propertyValues = [getTypedPropertyValue(value, firstProp)]; + let propertyValues = [getTypedPropertyValue(value, firstProp, options)]; for (const prop of nestedProps) { const next = []; for (const current of propertyValues) { if (Array.isArray(current)) { for (const element of current) { - next.push(getTypedPropertyValue(element, prop)); + next.push(getTypedPropertyValue(element, prop, options)); } } else if (current !== undefined) { - next.push(getTypedPropertyValue(current, prop)); + next.push(getTypedPropertyValue(current, prop, options)); } } propertyValues = next; diff --git a/packages/core/src/typeschema/slices.ts b/packages/core/src/typeschema/slices.ts new file mode 100644 index 0000000000..f5647258e2 --- /dev/null +++ b/packages/core/src/typeschema/slices.ts @@ -0,0 +1,56 @@ +import { TypedValue } from '../types'; +import { getNestedProperty } from './crawler'; +import { InternalTypeSchema, SliceDefinition, SliceDiscriminator } from './types'; +import { matchDiscriminant } from './validation'; + +export type SliceDefinitionWithTypes = SliceDefinition & { + type: NonNullable; + typeSchema?: InternalTypeSchema; +}; + +export function isSliceDefinitionWithTypes(slice: SliceDefinition): slice is SliceDefinitionWithTypes { + return slice.type !== undefined && slice.type.length > 0; +} + +function isDiscriminatorComponentMatch( + typedValue: TypedValue, + discriminator: SliceDiscriminator, + slice: SliceDefinitionWithTypes, + profileUrl: string | undefined +): boolean { + const nestedProp = getNestedProperty(typedValue, discriminator.path, { profileUrl }); + + if (nestedProp) { + const elements = slice.typeSchema?.elements ?? slice.elements; + return nestedProp.some((v: any) => matchDiscriminant(v, discriminator, slice, elements)) ?? false; + } + + console.assert(false, 'getNestedProperty[%s] in isDiscriminatorComponentMatch missed', discriminator.path); + return false; +} + +export function getValueSliceName( + value: any, + slices: SliceDefinitionWithTypes[], + discriminators: SliceDiscriminator[], + profileUrl: string | undefined +): string | undefined { + if (!value) { + return undefined; + } + + for (const slice of slices) { + const typedValue: TypedValue = { + value, + type: slice.typeSchema?.name ?? slice.type?.[0].code, + }; + if ( + discriminators.every((d) => + isDiscriminatorComponentMatch(typedValue, d, slice, slice.typeSchema?.url ?? profileUrl) + ) + ) { + return slice.name; + } + } + return undefined; +} diff --git a/packages/core/src/typeschema/types.ts b/packages/core/src/typeschema/types.ts index 1097739dfa..72501198ea 100644 --- a/packages/core/src/typeschema/types.ts +++ b/packages/core/src/typeschema/types.ts @@ -74,6 +74,7 @@ export interface SliceDefinition { elements: Record; min: number; max: number; + binding?: ElementDefinitionBinding; } export interface SliceDiscriminator { @@ -163,11 +164,16 @@ export function isDataTypeLoaded(type: string): boolean { } export function tryGetDataType(type: string, profileUrl?: string): InternalTypeSchema | undefined { - return getDataTypesMap(profileUrl)[type]; + let result: InternalTypeSchema | undefined = getDataTypesMap(profileUrl)[type]; + if (!result && profileUrl) { + // Fallback to base schema if no result found in profileUrl namespace + result = getDataTypesMap()[type]; + } + return result; } export function getDataType(type: string, profileUrl?: string): InternalTypeSchema { - const schema = getDataTypesMap(profileUrl)[type]; + const schema = tryGetDataType(type, profileUrl); if (!schema) { throw new OperationOutcomeError(badRequest('Unknown data type: ' + type)); } @@ -436,6 +442,7 @@ class StructureDefinitionParser { elements: {}, min: element.min ?? 0, max: element.max === '*' ? Number.POSITIVE_INFINITY : Number.parseInt(element.max as string, 10), + binding: element.binding, }; } diff --git a/packages/core/src/utils.test.ts b/packages/core/src/utils.test.ts index 594abc8d76..bf8cea045c 100644 --- a/packages/core/src/utils.test.ts +++ b/packages/core/src/utils.test.ts @@ -32,8 +32,10 @@ import { getExtensionValue, getIdentifier, getImageSrc, + getPathDifference, getQuestionnaireAnswers, getReferenceString, + isComplexTypeCode, isEmpty, isLowerCase, isPopulated, @@ -53,6 +55,7 @@ import { splitN, stringify, } from './utils'; +import { PropertyType } from './types'; if (typeof btoa === 'undefined') { global.btoa = function (str) { @@ -635,6 +638,8 @@ describe('Core Utils', () => { const output = deepClone(input); expect(output).toEqual(input); expect(output).not.toBe(input); + + expect(deepClone(undefined)).toBeUndefined(); }); test('Capitalize', () => { @@ -652,6 +657,25 @@ describe('Core Utils', () => { expect(isLowerCase('3')).toEqual(false); }); + test('isComplexTypeCode', () => { + expect(isComplexTypeCode('url')).toEqual(false); + expect(isComplexTypeCode(PropertyType.SystemString)).toEqual(false); + expect(isComplexTypeCode('')).toEqual(false); + }); + + test('getPathDifference', () => { + expect(getPathDifference('a', 'b')).toEqual(undefined); + expect(getPathDifference('a', 'a')).toEqual(undefined); + expect(getPathDifference('A', 'A')).toEqual(undefined); + expect(getPathDifference('a.b', 'a')).toEqual(undefined); + + expect(getPathDifference('a', 'a.b')).toEqual('b'); + expect(getPathDifference('A.b', 'A.b.c.d')).toEqual('c.d'); + expect(getPathDifference('Patient.extension', 'Patient.extension.extension.value[x]')).toEqual( + 'extension.value[x]' + ); + }); + test('isUUID', () => { expect(isUUID('')).toBe(false); expect(isUUID('asdf')).toBe(false); @@ -1088,6 +1112,7 @@ describe('Core Utils', () => { splitN('_has:Observation:subject:encounter:Encounter._has:DiagnosticReport:encounter:result.status', ':', 3) ).toEqual(['_has', 'Observation', 'subject:encounter:Encounter._has:DiagnosticReport:encounter:result.status']); expect(splitN('organization', ':', 2)).toEqual(['organization']); + expect(splitN('system|', '|', 2)).toEqual(['system', '']); }); test('lazy', () => { diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index a4d6ebfea4..c4fb00e7d2 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -606,7 +606,7 @@ function deepIncludesObject(value: { [key: string]: unknown }, pattern: { [key: * @returns A deep clone of the input. */ export function deepClone(input: T): T { - return JSON.parse(JSON.stringify(input)) as T; + return input === undefined ? input : (JSON.parse(JSON.stringify(input)) as T); } /** @@ -683,6 +683,24 @@ export function isLowerCase(c: string): boolean { return c === c.toLowerCase() && c !== c.toUpperCase(); } +export function isComplexTypeCode(code: string): boolean { + return code.length > 0 && code.startsWith(code[0].toUpperCase()); +} + +/** + * Returns the difference between two paths which is often suitable to use as a key in a `Record` + * @param parentPath - The parent path that will be removed from `path`. + * @param path - The element path that should be a child of `parentPath`. + * @returns - The difference between `path` and `parentPath` or `undefined` if `path` is not a child of `parentPath`. + */ +export function getPathDifference(parentPath: string, path: string): string | undefined { + const parentPathPrefix = parentPath + '.'; + if (path.startsWith(parentPathPrefix)) { + return path.slice(parentPathPrefix.length); + } + return undefined; +} + /** * Tries to find a code string for a given system within a given codeable concept. * @param concept - The codeable concept. @@ -930,19 +948,27 @@ export const sleep = (ms: number): Promise => setTimeout(resolve, ms); }); +/** + * Splits a string into an array of strings using the specified delimiter. + * Unlike the built-in split function, this function will split the string into a maximum of exactly n parts. + * Trailing empty strings are included in the result. + * @param str - The string to split. + * @param delim - The delimiter. + * @param n - The maximum number of parts to split the string into. + * @returns The resulting array of strings. + */ export function splitN(str: string, delim: string, n: number): string[] { const result: string[] = []; for (let i = 0; i < n - 1; i++) { - let delimIndex = str.indexOf(delim); + const delimIndex = str.indexOf(delim); if (delimIndex < 0) { - delimIndex = str.length; + break; + } else { + result.push(str.slice(0, delimIndex)); + str = str.slice(delimIndex + delim.length); } - result.push(str.slice(0, delimIndex)); - str = str.slice(delimIndex + delim.length); - } - if (str) { - result.push(str); } + result.push(str); return result; } @@ -971,3 +997,7 @@ export function append(array: T[] | undefined, value: T): T[] { array.push(value); return array; } + +export function getWebSocketUrl(path: string, baseUrl: URL | string): string { + return new URL(path, baseUrl).toString().replace('http://', 'ws://').replace('https://', 'wss://'); +} diff --git a/packages/core/test.setup.cjs b/packages/core/test.setup.cjs index 7d6793ba41..3b0e32dce9 100644 --- a/packages/core/test.setup.cjs +++ b/packages/core/test.setup.cjs @@ -1,6 +1,6 @@ /* globals module require globalThis */ // eslint-disable-next-line @typescript-eslint/no-var-requires -const { MemoryStorage } = require('@medplum/core'); +const { MemoryStorage } = require('./src/storage'); module.exports = () => { Object.defineProperty(globalThis.window, 'sessionStorage', { value: new MemoryStorage() }); diff --git a/packages/definitions/dist/fhir/r4/profiles-medplum.json b/packages/definitions/dist/fhir/r4/profiles-medplum.json index 788f35e71b..d1ed01186d 100644 --- a/packages/definitions/dist/fhir/r4/profiles-medplum.json +++ b/packages/definitions/dist/fhir/r4/profiles-medplum.json @@ -94,6 +94,22 @@ "code" : "code" }] }, + { + "id" : "Project.identifier", + "path" : "Project.identifier", + "short" : "An identifier for this project", + "definition" : "An identifier for this project.", + "min" : 0, + "max" : "*", + "type" : [{ + "code" : "Identifier" + }], + "base" : { + "path" : "Project.identifier", + "min" : 0, + "max" : "*" + } + }, { "id" : "Project.name", "path" : "Project.name", @@ -204,7 +220,7 @@ }, "binding" : { "strength" : "required", - "valueSet" : "https://medplum.com/fhir/ValueSet/project-feature|4.0.1" + "valueSet" : "https://medplum.com/fhir/ValueSet/project-feature" } }, { @@ -381,6 +397,32 @@ "min" : 0, "max" : "1" } + }, + { + "id": "Project.link", + "path": "Project.link", + "definition": "Linked Projects whose contents are made available to this one", + "min": 0, + "max": "*", + "type": [{"code": "BackboneElement"}], + "base": { + "path": "Project.link", + "min": 0, + "max": "*" + } + }, + { + "id": "Project.link.project", + "path": "Project.link.project", + "definition": "A reference to the Project to be linked into this one", + "min": 1, + "max": "1", + "type": [{"code": "Reference", "targetProfile": ["Project"]}], + "base": { + "path": "Project.link.project", + "min": 1, + "max": "1" + } } ] } @@ -708,6 +750,22 @@ "code" : "code" }] }, + { + "id" : "User.identifier", + "path" : "User.identifier", + "short" : "An identifier for this user", + "definition" : "An identifier for this user.", + "min" : 0, + "max" : "*", + "type" : [{ + "code" : "Identifier" + }], + "base" : { + "path" : "User.identifier", + "min" : 0, + "max" : "*" + } + }, { "id" : "User.firstName", "path" : "User.firstName", diff --git a/packages/definitions/dist/fhir/r4/search-parameters-medplum.json b/packages/definitions/dist/fhir/r4/search-parameters-medplum.json index ecabbb1037..31e0b65d49 100644 --- a/packages/definitions/dist/fhir/r4/search-parameters-medplum.json +++ b/packages/definitions/dist/fhir/r4/search-parameters-medplum.json @@ -3,6 +3,23 @@ "id": "medplumSearchParams", "type": "collection", "entry": [ + { + "fullUrl": "https://medplum.com/fhir/SearchParameter/Project-identifier", + "resource": { + "resourceType": "SearchParameter", + "id": "Project-identifier", + "url": "https://medplum.com/fhir/SearchParameter/Project-identifier", + "version": "4.0.1", + "name": "identifier", + "status": "draft", + "publisher": "Medplum", + "description": "The identifier of the project", + "code": "identifier", + "base": ["Project"], + "type": "token", + "expression": "Project.identifier" + } + }, { "fullUrl": "https://medplum.com/fhir/SearchParameter/Project-name", "resource": { @@ -212,6 +229,23 @@ "expression": "JsonWebKey.active" } }, + { + "fullUrl": "https://medplum.com/fhir/SearchParameter/User-identifier", + "resource": { + "resourceType": "SearchParameter", + "id": "User-identifier", + "url": "https://medplum.com/fhir/SearchParameter/User-identifier", + "version": "4.0.1", + "name": "identifier", + "status": "draft", + "publisher": "Medplum", + "description": "The identifier of the user", + "code": "identifier", + "base": ["User"], + "type": "token", + "expression": "User.identifier" + } + }, { "fullUrl": "https://medplum.com/fhir/SearchParameter/User-email", "resource": { @@ -573,6 +607,40 @@ "type": "number", "expression": "iif(priority = 'stat', 50, iif(priority = 'asap', 40, iif(priority = 'urgent', 30, iif(priority = 'routine', 20, 10))))" } + }, + { + "fullUrl": "https://medplum.com/fhir/SearchParameter/CareTeam-name", + "resource": { + "resourceType": "SearchParameter", + "id": "CareTeam-name", + "url": "https://medplum.com/fhir/SearchParameter/CareTeam-name", + "version": "4.0.1", + "name": "name", + "status": "draft", + "publisher": "Medplum", + "description": "The name of the care team", + "code": "name", + "base": ["CareTeam"], + "type": "string", + "expression": "CareTeam.name" + } + }, + { + "fullUrl": "https://medplum.com/fhir/SearchParameter/Group-name", + "resource": { + "resourceType": "SearchParameter", + "id": "Group-name", + "url": "https://medplum.com/fhir/SearchParameter/Group-name", + "version": "4.0.1", + "name": "name", + "status": "draft", + "publisher": "Medplum", + "description": "The name of the group", + "code": "name", + "base": ["Group"], + "type": "string", + "expression": "Group.name" + } } ] } diff --git a/packages/definitions/dist/fhir/r4/testing/uscore-v5.0.1-structuredefinitions.json b/packages/definitions/dist/fhir/r4/testing/uscore-v5.0.1-structuredefinitions.json new file mode 100644 index 0000000000..41e4cba781 --- /dev/null +++ b/packages/definitions/dist/fhir/r4/testing/uscore-v5.0.1-structuredefinitions.json @@ -0,0 +1 @@ +[{"resourceType":"StructureDefinition","id":"us-core-patient","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient","version":"5.0.1","name":"USCorePatientProfile","title":"US Core Patient Profile","status":"active","experimental":false,"date":"2022-04-20T15:02:49-07:00","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"The US Core Patient Profile meets the U.S. Core Data for Interoperability (USCDI) v2 'Patient Demographics' requirements. This profile sets minimum expectations for the Patient resource to record, search, and fetch basic demographics and other administrative information about an individual patient. It identifies which core elements, extensions, vocabularies and value sets **SHALL** be present in the resource when using this profile to promote interoperability and adoption through common implementation. It identifies which core elements, extensions, vocabularies and value sets **SHALL** be present in the resource when using this profile. It provides the floor for standards development for specific uses cases.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"resource","abstract":false,"type":"Patient","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Patient","derivation":"constraint","snapshot":{"element":[{"id":"Patient","path":"Patient","short":"Information about an individual or animal receiving health care services","definition":"\\-","comment":"\\-","alias":["SubjectOfCare Client Resident"],"min":0,"max":"*","base":{"path":"Patient","min":0,"max":"*"},"constraint":[{"key":"dom-2","severity":"error","human":"If the resource is contained in another resource, it SHALL NOT contain nested Resources","expression":"contained.contained.empty()","xpath":"not(parent::f:contained and f:contained)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-3","severity":"error","human":"If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource","expression":"contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()","xpath":"not(exists(for $id in f:contained/*/f:id/@value return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-4","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated","expression":"contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()","xpath":"not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-5","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a security label","expression":"contained.meta.security.empty()","xpath":"not(exists(f:contained/*/f:meta/f:security))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice","valueBoolean":true},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice-explanation","valueMarkdown":"When a resource has no narrative, only systems that fully understand the data can display the resource to a human safely. Including a human readable representation in the resource makes for a much more robust eco-system and cheaper handling of resources by intermediary systems. Some ecosystems restrict distribution of resources to only those systems that do fully understand the resources, and as a consequence implementers may believe that the narrative is superfluous. However experience shows that such eco-systems often open up to new participants over time."}],"key":"dom-6","severity":"warning","human":"A resource should have narrative for robust management","expression":"text.`div`.exists()","xpath":"exists(f:text/h:div)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"us-core-6","severity":"error","human":"Either Patient.name.given and/or Patient.name.family SHALL be present or a Data Absent Reason Extension SHALL be present.","expression":"(name.family.exists() or name.given.exists()) xor extension.where(url='http://hl7.org/fhir/StructureDefinition/data-absent-reason').exists()","xpath":"(/f:name/f:extension/@url='http://hl7.org/fhir/StructureDefinition/data-absent-reason' and not(/f:name/f:family or /f:name/f:given)) or (not(/f:name/f:extension/@url='http://hl7.org/fhir/StructureDefinition/data-absent-reason') and (/f:name/f:family or /f:name/f:given))"}],"mustSupport":false,"isModifier":false,"isSummary":false},{"id":"Patient.id","path":"Patient.id","short":"Logical id of this artifact","definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.","comment":"The only time that a resource does not have an id is when it is being submitted to the server using a create operation.","min":0,"max":"1","base":{"path":"Resource.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":true},{"id":"Patient.meta","path":"Patient.meta","short":"Metadata about the resource","definition":"The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.","min":0,"max":"1","base":{"path":"Resource.meta","min":0,"max":"1"},"type":[{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.implicitRules","path":"Patient.implicitRules","short":"A set of rules under which this content was created","definition":"A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.","comment":"Asserting this rule set restricts the content to be only understood by a limited set of trading partners. This inherently limits the usefulness of the data in the long term. However, the existing health eco-system is highly fractured, and not yet ready to define, collect, and exchange data in a generally computable sense. Wherever possible, implementers and/or specification writers should avoid using this element. Often, when used, the URL is a reference to an implementation guide that defines these special rules as part of it's narrative along with other profiles, value sets, etc.","min":0,"max":"1","base":{"path":"Resource.implicitRules","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because the implicit rules may provide additional knowledge about the resource that modifies it's meaning or interpretation","isSummary":true},{"id":"Patient.language","path":"Patient.language","short":"Language of the resource content","definition":"The base language in which the resource is written.","comment":"Language is provided to support indexing and accessibility (typically, services such as text to speech use the language tag). The html language tag in the narrative applies to the narrative. The language tag on the resource may be used to specify the language of other presentations generated from the data in the resource. Not all the content has to be in the base language. The Resource.language should not be assumed to apply to the narrative automatically. If a language is specified, it should it also be specified on the div element in the html (see rules in HTML5 for information about the relationship between xml:lang and the html lang attribute).","min":0,"max":"1","base":{"path":"Resource.language","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/all-languages"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Language"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"preferred","description":"A human language.","valueSet":"http://hl7.org/fhir/ValueSet/languages"}},{"id":"Patient.text","path":"Patient.text","short":"Text summary of the resource, for human interpretation","definition":"A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it \"clinically safe\" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety.","comment":"Contained resources do not have narrative. Resources that are not contained SHOULD have a narrative. In some cases, a resource may only have text with little or no additional discrete data (as long as all minOccurs=1 elements are satisfied). This may be necessary for data from legacy systems where information is captured as a \"text blob\" or where text is additionally entered raw or narrated and encoded information is added later.","alias":["narrative","html","xhtml","display"],"min":0,"max":"1","base":{"path":"DomainResource.text","min":0,"max":"1"},"type":[{"code":"Narrative"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contained","path":"Patient.contained","short":"Contained, inline Resources","definition":"These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope.","comment":"This should never be done when the content can be identified properly, as once identification is lost, it is extremely difficult (and context dependent) to restore it again. Contained resources may have profiles and tags In their meta elements, but SHALL NOT have security labels.","alias":["inline resources","anonymous resources","contained resources"],"min":0,"max":"*","base":{"path":"DomainResource.contained","min":0,"max":"*"},"type":[{"code":"Resource"}],"isModifier":false,"isSummary":false},{"id":"Patient.extension","path":"Patient.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"ordered":false,"rules":"open"},"short":"Extension","definition":"An Extension","min":0,"max":"*","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.extension:race","path":"Patient.extension","sliceName":"race","short":"US Core Race Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The race codes used to represent these concepts are based upon the [CDC Race and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 921 reference race. The race concepts are grouped by and pre-mapped to the 5 OMB race categories:\n\n - American Indian or Alaska Native\n - Asian\n - Black or African American\n - Native Hawaiian or Other Pacific Islander\n - White.","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-race"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.extension:ethnicity","path":"Patient.extension","sliceName":"ethnicity","short":"US Core ethnicity Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The ethnicity codes used to represent these concepts are based upon the [CDC ethnicity and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 43 reference ethnicity. The ethnicity concepts are grouped by and pre-mapped to the 2 OMB ethnicity categories: - Hispanic or Latino - Not Hispanic or Latino.","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.extension:birthsex","path":"Patient.extension","sliceName":"birthsex","short":"Extension","definition":"A code classifying the person's sex assigned at birth as specified by the [Office of the National Coordinator for Health IT (ONC)](https://www.healthit.gov/newsroom/about-onc).","comment":"The codes required are intended to present birth sex (i.e., the sex recorded on the patient’s birth certificate) and not gender identity or reassigned sex.","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.extension:genderIdentity","path":"Patient.extension","sliceName":"genderIdentity","short":"Extension","definition":"An Extension","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.modifierExtension","path":"Patient.modifierExtension","short":"Extensions that cannot be ignored","definition":"May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the resource that contains them","isSummary":false},{"id":"Patient.identifier","path":"Patient.identifier","short":"An identifier for this patient","definition":"An identifier for this patient.","requirements":"Patients are almost always assigned specific numerical identifiers.","min":1,"max":"*","base":{"path":"Patient.identifier","min":0,"max":"*"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.identifier.id","path":"Patient.identifier.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.identifier.extension","path":"Patient.identifier.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.identifier.use","path":"Patient.identifier.use","short":"usual | official | temp | secondary | old (If known)","definition":"The purpose of this identifier.","comment":"Applications can assume that an identifier is permanent unless it explicitly says that it is temporary.","requirements":"Allows the appropriate identifier for a particular context of use to be selected from among a set of identifiers.","min":0,"max":"1","base":{"path":"Identifier.use","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary id for a permanent one.","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"IdentifierUse"}],"strength":"required","description":"Identifies the purpose for this identifier, if known .","valueSet":"http://hl7.org/fhir/ValueSet/identifier-use|4.0.1"}},{"id":"Patient.identifier.type","path":"Patient.identifier.type","short":"Description of identifier","definition":"A coded type for the identifier that can be used to determine which identifier to use for a specific purpose.","comment":"This element deals only with general categories of identifiers. It SHOULD not be used for codes that correspond 1..1 with the Identifier.system. Some identifiers may fall into multiple categories due to common usage. Where the system is known, a type is unnecessary because the type is always part of the system definition. However systems often need to handle identifiers where the system is not known. There is not a 1:1 relationship between type and system, since many different systems have the same type.","requirements":"Allows users to make use of identifiers when the identifier system is not known.","min":0,"max":"1","base":{"path":"Identifier.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"IdentifierType"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"extensible","description":"A coded type for an identifier that can be used to determine which identifier to use for a specific purpose.","valueSet":"http://hl7.org/fhir/ValueSet/identifier-type"}},{"id":"Patient.identifier.system","path":"Patient.identifier.system","short":"The namespace for the identifier value","definition":"Establishes the namespace for the value - that is, a URL that describes a set values that are unique.","comment":"Identifier.system is always case sensitive.","requirements":"There are many sets of identifiers. To perform matching of two identifiers, we need to know what set we're dealing with. The system identifies a particular set of unique identifiers.","min":1,"max":"1","base":{"path":"Identifier.system","min":0,"max":"1"},"type":[{"code":"uri"}],"example":[{"label":"General","valueUri":"http://www.acme.com/identifiers/patient"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.identifier.value","path":"Patient.identifier.value","short":"The value that is unique within the system.","definition":"The portion of the identifier typically relevant to the user and which is unique within the context of the system.","comment":"If the value is a full URI, then the system SHALL be urn:ietf:rfc:3986. The value's primary purpose is computational mapping. As a result, it may be normalized for comparison purposes (e.g. removing non-significant whitespace, dashes, etc.) A value formatted for human display can be conveyed using the [Rendered Value extension](http://hl7.org/fhir/R4/extension-rendered-value.html). Identifier.value is to be treated as case sensitive unless knowledge of the Identifier.system allows the processer to be confident that non-case-sensitive processing is safe.","min":1,"max":"1","base":{"path":"Identifier.value","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"123456"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.identifier.period","path":"Patient.identifier.period","short":"Time period when id is/was valid for use","definition":"Time period during which identifier is/was valid for use.","min":0,"max":"1","base":{"path":"Identifier.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.identifier.assigner","path":"Patient.identifier.assigner","short":"Organization that issued id (may be just text)","definition":"Organization that issued/manages the identifier.","comment":"The Identifier.assigner may omit the .reference element and only contain a .display element reflecting the name or other textual information about the assigning organization.","min":0,"max":"1","base":{"path":"Identifier.assigner","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.active","path":"Patient.active","short":"Whether this patient's record is in active use","definition":"Whether this patient record is in active use. \nMany systems use this property to mark as non-current patients, such as those that have not been seen for a period of time based on an organization's business rules.\n\nIt is often used to filter patient lists to exclude inactive patients\n\nDeceased patients may also be marked as inactive for the same reasons, but may be active for some time after death.","comment":"If a record is inactive, and linked to an active record, then future patient/record updates should occur on the other patient.","requirements":"Need to be able to mark a patient record as not to be used because it was created in error.","min":0,"max":"1","base":{"path":"Patient.active","min":0,"max":"1"},"type":[{"code":"boolean"}],"meaningWhenMissing":"This resource is generally assumed to be active if no value is provided for the active element","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labelled as a modifier because it is a status element that can indicate that a record should not be treated as valid","isSummary":true},{"id":"Patient.name","path":"Patient.name","short":"A name associated with the patient","definition":"A name associated with the individual.","comment":"A patient may have multiple names with different uses or applicable periods. For animals, the name is a \"HumanName\" in the sense that is assigned and used by humans and has the same patterns.","requirements":"Need to be able to track the patient by multiple names. Examples are your official name and a partner name.","min":1,"max":"*","base":{"path":"Patient.name","min":0,"max":"*"},"type":[{"code":"HumanName"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.name.id","path":"Patient.name.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.name.extension","path":"Patient.name.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.name.use","path":"Patient.name.use","short":"usual | official | temp | nickname | anonymous | old | maiden","definition":"Identifies the purpose for this name.","comment":"Applications can assume that a name is current unless it explicitly says that it is temporary or old.","requirements":"Allows the appropriate name for a particular context of use to be selected from among a set of names.","min":0,"max":"1","base":{"path":"HumanName.use","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary or old name etc.for a current/permanent one","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"NameUse"}],"strength":"required","description":"The use of a human name.","valueSet":"http://hl7.org/fhir/ValueSet/name-use|4.0.1"}},{"id":"Patient.name.text","path":"Patient.name.text","short":"Text representation of the full name","definition":"Specifies the entire name as it should be displayed e.g. on an application UI. This may be provided instead of or as well as the specific parts.","comment":"Can provide both a text representation and parts. Applications updating a name SHALL ensure that when both text and parts are present, no content is included in the text that isn't found in a part.","requirements":"A renderable, unencoded form.","min":0,"max":"1","base":{"path":"HumanName.text","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.name.family","path":"Patient.name.family","short":"Family name (often called 'Surname')","definition":"The part of a name that links to the genealogy. In some cultures (e.g. Eritrea) the family name of a son is the first name of his father.","comment":"Family Name may be decomposed into specific parts using extensions (de, nl, es related cultures).","alias":["surname"],"min":0,"max":"1","base":{"path":"HumanName.family","min":0,"max":"1"},"type":[{"code":"string"}],"condition":["us-core-6"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.name.given","path":"Patient.name.given","short":"Given names (not always 'first'). Includes middle names","definition":"Given name.","comment":"If only initials are recorded, they may be used in place of the full name parts. Initials may be separated into multiple given names but often aren't due to paractical limitations. This element is not called \"first name\" since given names do not always come first.","alias":["first name","middle name"],"min":0,"max":"*","base":{"path":"HumanName.given","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"Given Names appear in the correct order for presenting the name","condition":["us-core-6"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.name.prefix","path":"Patient.name.prefix","short":"Parts that come before the name","definition":"Part of the name that is acquired as a title due to academic, legal, employment or nobility status, etc. and that appears at the start of the name.","min":0,"max":"*","base":{"path":"HumanName.prefix","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"Prefixes appear in the correct order for presenting the name","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.name.suffix","path":"Patient.name.suffix","short":"Parts that come after the name","definition":"Part of the name that is acquired as a title due to academic, legal, employment or nobility status, etc. and that appears at the end of the name.","min":0,"max":"*","base":{"path":"HumanName.suffix","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"Suffixes appear in the correct order for presenting the name","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":true},{"id":"Patient.name.period","path":"Patient.name.period","short":"Time period when name was/is in use","definition":"Indicates the period of time when this name was valid for the named person.","requirements":"Allows names to be placed in historical context.","min":0,"max":"1","base":{"path":"HumanName.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":true},{"id":"Patient.telecom","path":"Patient.telecom","short":"A contact detail for the individual","definition":"A contact detail (e.g. a telephone number or an email address) by which the individual may be contacted.","comment":"A Patient may have multiple ways to be contacted with different uses or applicable periods. May need to have options for contacting the person urgently and also to help with identification. The address might not go directly to the individual, but may reach another party that is able to proxy for the patient (i.e. home phone, or pet owner's phone).","requirements":"People have (primary) ways to contact them in some way such as phone, email.","min":0,"max":"*","base":{"path":"Patient.telecom","min":0,"max":"*"},"type":[{"code":"ContactPoint"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":true},{"id":"Patient.telecom.id","path":"Patient.telecom.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.telecom.extension","path":"Patient.telecom.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.telecom.system","path":"Patient.telecom.system","short":"phone | fax | email | pager | url | sms | other","definition":"Telecommunications form for contact point - what communications system is required to make use of the contact.","min":1,"max":"1","base":{"path":"ContactPoint.system","min":0,"max":"1"},"type":[{"code":"code"}],"condition":["cpt-2"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"required","description":"Telecommunications form for contact point.","valueSet":"http://hl7.org/fhir/ValueSet/contact-point-system"}},{"id":"Patient.telecom.value","path":"Patient.telecom.value","short":"The actual contact point details","definition":"The actual contact point details, in a form that is meaningful to the designated communication system (i.e. phone number or email address).","comment":"Additional text data such as phone extension numbers, or notes about use of the contact are sometimes included in the value.","requirements":"Need to support legacy numbers that are not in a tightly controlled format.","min":1,"max":"1","base":{"path":"ContactPoint.value","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.telecom.use","path":"Patient.telecom.use","short":"home | work | temp | old | mobile - purpose of this contact point","definition":"Identifies the purpose for the contact point.","comment":"Applications can assume that a contact is current unless it explicitly says that it is temporary or old.","requirements":"Need to track the way a person uses this contact, so a user can choose which is appropriate for their purpose.","min":0,"max":"1","base":{"path":"ContactPoint.use","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary or old contact etc.for a current/permanent one","isSummary":true,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/ValueSet/contact-point-use"}},{"id":"Patient.telecom.rank","path":"Patient.telecom.rank","short":"Specify preferred order of use (1 = highest)","definition":"Specifies a preferred order in which to use a set of contacts. ContactPoints with lower rank values are more preferred than those with higher rank values.","comment":"Note that rank does not necessarily follow the order in which the contacts are represented in the instance.","min":0,"max":"1","base":{"path":"ContactPoint.rank","min":0,"max":"1"},"type":[{"code":"positiveInt"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.telecom.period","path":"Patient.telecom.period","short":"Time period when the contact point was/is in use","definition":"Time period when the contact point was/is in use.","min":0,"max":"1","base":{"path":"ContactPoint.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.gender","path":"Patient.gender","short":"male | female | other | unknown","definition":"Administrative Gender - the gender that the patient is considered to have for administration and record keeping purposes.","comment":"The gender might not match the biological sex as determined by genetics or the individual's preferred identification. Note that for both humans and particularly animals, there are other legitimate possibilities than male and female, though the vast majority of systems and contexts only support male and female. Systems providing decision support or enforcing business rules should ideally do this on the basis of Observations dealing with the specific sex or gender aspect of interest (anatomical, chromosomal, social, etc.) However, because these observations are infrequently recorded, defaulting to the administrative gender is common practice. Where such defaulting occurs, rule enforcement should allow for the variation between administrative and biological, chromosomal and other gender aspects. For example, an alert about a hysterectomy on a male should be handled as a warning or overridable error, not a \"hard\" error. See the Patient Gender and Sex section for additional information about communicating patient gender and sex.","requirements":"Needed for identification of the individual, in combination with (at least) name and birth date.","min":1,"max":"1","base":{"path":"Patient.gender","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/ValueSet/administrative-gender"}},{"id":"Patient.birthDate","path":"Patient.birthDate","short":"The date of birth for the individual","definition":"The date of birth for the individual.","comment":"At least an estimated year should be provided as a guess if the real DOB is unknown There is a standard extension \"patient-birthTime\" available that should be used where Time is required (such as in maternity/infant care systems).","requirements":"Age of the individual drives many clinical processes.","min":0,"max":"1","base":{"path":"Patient.birthDate","min":0,"max":"1"},"type":[{"code":"date"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.deceased[x]","path":"Patient.deceased[x]","short":"Indicates if the individual is deceased or not","definition":"Indicates if the individual is deceased or not.","comment":"If there's no value in the instance, it means there is no statement on whether or not the individual is deceased. Most systems will interpret the absence of a value as a sign of the person being alive.","requirements":"The fact that a patient is deceased influences the clinical process. Also, in human communication and relation management it is necessary to know whether the person is alive.","min":0,"max":"1","base":{"path":"Patient.deceased[x]","min":0,"max":"1"},"type":[{"code":"boolean"},{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because once a patient is marked as deceased, the actions that are appropriate to perform on the patient may be significantly different.","isSummary":true},{"id":"Patient.address","path":"Patient.address","short":"An address for the individual","definition":"An address for the individual.","comment":"Patient may have multiple addresses with different uses or applicable periods.","requirements":"May need to keep track of patient addresses for contacting, billing or reporting requirements and also to help with identification.","min":0,"max":"*","base":{"path":"Patient.address","min":0,"max":"*"},"type":[{"code":"Address"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.id","path":"Patient.address.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.address.extension","path":"Patient.address.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.address.use","path":"Patient.address.use","short":"home | work | temp | old | billing - purpose of this address","definition":"The purpose of this address.","comment":"Applications can assume that an address is current unless it explicitly says that it is temporary or old.","requirements":"Allows an appropriate address to be chosen from a list of many.","min":0,"max":"1","base":{"path":"Address.use","min":0,"max":"1"},"type":[{"code":"code"}],"example":[{"label":"General","valueCode":"home"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary or old address etc.for a current/permanent one","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AddressUse"}],"strength":"required","description":"The use of an address.","valueSet":"http://hl7.org/fhir/ValueSet/address-use|4.0.1"}},{"id":"Patient.address.type","path":"Patient.address.type","short":"postal | physical | both","definition":"Distinguishes between physical addresses (those you can visit) and mailing addresses (e.g. PO Boxes and care-of addresses). Most addresses are both.","comment":"The definition of Address states that \"address is intended to describe postal addresses, not physical locations\". However, many applications track whether an address has a dual purpose of being a location that can be visited as well as being a valid delivery destination, and Postal addresses are often used as proxies for physical locations (also see the [Location](http://hl7.org/fhir/R4/location.html#) resource).","min":0,"max":"1","base":{"path":"Address.type","min":0,"max":"1"},"type":[{"code":"code"}],"example":[{"label":"General","valueCode":"both"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AddressType"}],"strength":"required","description":"The type of an address (physical / postal).","valueSet":"http://hl7.org/fhir/ValueSet/address-type|4.0.1"}},{"id":"Patient.address.text","path":"Patient.address.text","short":"Text representation of the address","definition":"Specifies the entire address as it should be displayed e.g. on a postal label. This may be provided instead of or as well as the specific parts.","comment":"Can provide both a text representation and parts. Applications updating an address SHALL ensure that when both text and parts are present, no content is included in the text that isn't found in a part.","requirements":"A renderable, unencoded form.","min":0,"max":"1","base":{"path":"Address.text","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"137 Nowhere Street, Erewhon 9132"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.address.line","path":"Patient.address.line","short":"Street name, number, direction & P.O. Box etc.","definition":"This component contains the house number, apartment number, street name, street direction, P.O. Box number, delivery hints, and similar address information.","min":0,"max":"*","base":{"path":"Address.line","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"The order in which lines should appear in an address label","example":[{"label":"General","valueString":"137 Nowhere Street"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.city","path":"Patient.address.city","short":"Name of city, town etc.","definition":"The name of the city, town, suburb, village or other community or delivery center.","alias":["Municpality"],"min":0,"max":"1","base":{"path":"Address.city","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"Erewhon"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.district","path":"Patient.address.district","short":"District name (aka county)","definition":"The name of the administrative area (county).","comment":"District is sometimes known as county, but in some regions 'county' is used in place of city (municipality), so county name should be conveyed in city instead.","alias":["County"],"min":0,"max":"1","base":{"path":"Address.district","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"Madison"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.address.state","path":"Patient.address.state","short":"Sub-unit of country (abbreviations ok)","definition":"Sub-unit of a country with limited sovereignty in a federally organized country. A code may be used if codes are in common use (e.g. US 2 letter state codes).","alias":["Province","Territory"],"min":0,"max":"1","base":{"path":"Address.state","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"Two Letter USPS alphabetic codes.","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-usps-state"}},{"id":"Patient.address.postalCode","path":"Patient.address.postalCode","short":"US Zip Codes","definition":"A postal code designating a region defined by the postal service.","alias":["Zip","Zip Code"],"min":0,"max":"1","base":{"path":"Address.postalCode","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"9132"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.country","path":"Patient.address.country","short":"Country (e.g. can be ISO 3166 2 or 3 letter code)","definition":"Country - a nation as commonly understood or generally accepted.","comment":"ISO 3166 3 letter codes can be used in place of a human readable country name.","min":0,"max":"1","base":{"path":"Address.country","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.address.period","path":"Patient.address.period","short":"Time period when address was/is in use","definition":"Time period when address was/is in use.","requirements":"Allows addresses to be placed in historical context.","min":0,"max":"1","base":{"path":"Address.period","min":0,"max":"1"},"type":[{"code":"Period"}],"example":[{"label":"General","valuePeriod":{"start":"2010-03-23","end":"2010-07-01"}}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.maritalStatus","path":"Patient.maritalStatus","short":"Marital (civil) status of a patient","definition":"This field contains a patient's most recent marital (civil) status.","requirements":"Most, if not all systems capture it.","min":0,"max":"1","base":{"path":"Patient.maritalStatus","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MaritalStatus"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"extensible","description":"The domestic partnership status of a person.","valueSet":"http://hl7.org/fhir/ValueSet/marital-status"}},{"id":"Patient.multipleBirth[x]","path":"Patient.multipleBirth[x]","short":"Whether patient is part of a multiple birth","definition":"Indicates whether the patient is part of a multiple (boolean) or indicates the actual birth order (integer).","comment":"Where the valueInteger is provided, the number is the birth number in the sequence. E.g. The middle birth in triplets would be valueInteger=2 and the third born would have valueInteger=3 If a boolean value was provided for this triplets example, then all 3 patient records would have valueBoolean=true (the ordering is not indicated).","requirements":"For disambiguation of multiple-birth children, especially relevant where the care provider doesn't meet the patient, such as labs.","min":0,"max":"1","base":{"path":"Patient.multipleBirth[x]","min":0,"max":"1"},"type":[{"code":"boolean"},{"code":"integer"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.photo","path":"Patient.photo","short":"Image of the patient","definition":"Image of the patient.","comment":"Guidelines:\n* Use id photos, not clinical photos.\n* Limit dimensions to thumbnail.\n* Keep byte count low to ease resource updates.","requirements":"Many EHR systems have the capability to capture an image of the patient. Fits with newer social media usage too.","min":0,"max":"*","base":{"path":"Patient.photo","min":0,"max":"*"},"type":[{"code":"Attachment"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name","valueString":"Contact"}],"path":"Patient.contact","short":"A contact party (e.g. guardian, partner, friend) for the patient","definition":"A contact party (e.g. guardian, partner, friend) for the patient.","comment":"Contact covers all kinds of contact parties: family members, business contacts, guardians, caregivers. Not applicable to register pedigree and family ties beyond use of having contact.","requirements":"Need to track people you can contact about the patient.","min":0,"max":"*","base":{"path":"Patient.contact","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"pat-1","severity":"error","human":"SHALL at least contain a contact's details or a reference to an organization","expression":"name.exists() or telecom.exists() or address.exists() or organization.exists()","xpath":"exists(f:name) or exists(f:telecom) or exists(f:address) or exists(f:organization)","source":"http://hl7.org/fhir/StructureDefinition/Patient"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.id","path":"Patient.contact.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.extension","path":"Patient.contact.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.modifierExtension","path":"Patient.contact.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Patient.contact.relationship","path":"Patient.contact.relationship","short":"The kind of relationship","definition":"The nature of the relationship between the patient and the contact person.","requirements":"Used to determine which contact person is the most relevant to approach, depending on circumstances.","min":0,"max":"*","base":{"path":"Patient.contact.relationship","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ContactRelationship"}],"strength":"extensible","description":"The nature of the relationship between a patient and a contact person for that patient.","valueSet":"http://hl7.org/fhir/ValueSet/patient-contactrelationship"}},{"id":"Patient.contact.name","path":"Patient.contact.name","short":"A name associated with the contact person","definition":"A name associated with the contact person.","requirements":"Contact persons need to be identified by name, but it is uncommon to need details about multiple other names for that contact person.","min":0,"max":"1","base":{"path":"Patient.contact.name","min":0,"max":"1"},"type":[{"code":"HumanName"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.telecom","path":"Patient.contact.telecom","short":"A contact detail for the person","definition":"A contact detail for the person, e.g. a telephone number or an email address.","comment":"Contact may have multiple ways to be contacted with different uses or applicable periods. May need to have options for contacting the person urgently, and also to help with identification.","requirements":"People have (primary) ways to contact them in some way such as phone, email.","min":0,"max":"*","base":{"path":"Patient.contact.telecom","min":0,"max":"*"},"type":[{"code":"ContactPoint"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.address","path":"Patient.contact.address","short":"Address for the contact person","definition":"Address for the contact person.","requirements":"Need to keep track where the contact person can be contacted per postal mail or visited.","min":0,"max":"1","base":{"path":"Patient.contact.address","min":0,"max":"1"},"type":[{"code":"Address"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.gender","path":"Patient.contact.gender","short":"male | female | other | unknown","definition":"Administrative Gender - the gender that the contact person is considered to have for administration and record keeping purposes.","requirements":"Needed to address the person correctly.","min":0,"max":"1","base":{"path":"Patient.contact.gender","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AdministrativeGender"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"required","description":"The gender of a person used for administrative purposes.","valueSet":"http://hl7.org/fhir/ValueSet/administrative-gender|4.0.1"}},{"id":"Patient.contact.organization","path":"Patient.contact.organization","short":"Organization that is associated with the contact","definition":"Organization on behalf of which the contact is acting or for which the contact is working.","requirements":"For guardians or business related contacts, the organization is relevant.","min":0,"max":"1","base":{"path":"Patient.contact.organization","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"condition":["pat-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.period","path":"Patient.contact.period","short":"The period during which this contact person or organization is valid to be contacted relating to this patient","definition":"The period during which this contact person or organization is valid to be contacted relating to this patient.","min":0,"max":"1","base":{"path":"Patient.contact.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.communication","path":"Patient.communication","short":"A language which may be used to communicate with the patient about his or her health","definition":"A language which may be used to communicate with the patient about his or her health.","comment":"If no language is specified, this *implies* that the default local language is spoken. If you need to convey proficiency for multiple modes, then you need multiple Patient.Communication associations. For animals, language is not a relevant field, and should be absent from the instance. If the Patient does not speak the default local language, then the Interpreter Required Standard can be used to explicitly declare that an interpreter is required.","requirements":"If a patient does not speak the local language, interpreters may be required, so languages spoken and proficiency are important things to keep track of both for patient and other persons of interest.","min":0,"max":"*","base":{"path":"Patient.communication","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":false},{"id":"Patient.communication.id","path":"Patient.communication.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.communication.extension","path":"Patient.communication.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.communication.modifierExtension","path":"Patient.communication.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Patient.communication.language","path":"Patient.communication.language","short":"The language which can be used to communicate with the patient about his or her health","definition":"The ISO-639-1 alpha 2 code in lower case for the language, optionally followed by a hyphen and the ISO-3166-1 alpha 2 code for the region in upper case; e.g. \"en\" for English, or \"en-US\" for American English versus \"en-EN\" for England English.","comment":"The structure aa-BB with this exact casing is one the most widely used notations for locale. However not all systems actually code this but instead have it as free text. Hence CodeableConcept instead of code as the data type.","requirements":"Most systems in multilingual countries will want to convey language. Not all systems actually need the regional dialect.","min":1,"max":"1","base":{"path":"Patient.communication.language","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"strength":"extensible","valueSet":"http://hl7.org/fhir/us/core/ValueSet/simple-language"}},{"id":"Patient.communication.preferred","path":"Patient.communication.preferred","short":"Language preference indicator","definition":"Indicates whether or not the patient prefers this language (over other languages he masters up a certain level).","comment":"This language is specifically identified for communicating healthcare information.","requirements":"People that master multiple languages up to certain level may prefer one or more, i.e. feel more confident in communicating in a particular language making other languages sort of a fall back method.","min":0,"max":"1","base":{"path":"Patient.communication.preferred","min":0,"max":"1"},"type":[{"code":"boolean"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.generalPractitioner","path":"Patient.generalPractitioner","short":"Patient's nominated primary care provider","definition":"Patient's nominated care provider.","comment":"This may be the primary care provider (in a GP context), or it may be a patient nominated care manager in a community/disability setting, or even organization that will provide people to perform the care provider roles. It is not to be used to record Care Teams, these should be in a CareTeam resource that may be linked to the CarePlan or EpisodeOfCare resources.\nMultiple GPs may be recorded against the patient for various reasons, such as a student that has his home GP listed along with the GP at university during the school semesters, or a \"fly-in/fly-out\" worker that has the onsite GP also included with his home GP to remain aware of medical issues.\n\nJurisdictions may decide that they can profile this down to 1 if desired, or 1 per type.","alias":["careProvider"],"min":0,"max":"*","base":{"path":"Patient.generalPractitioner","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization","http://hl7.org/fhir/StructureDefinition/Practitioner","http://hl7.org/fhir/StructureDefinition/PractitionerRole"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.managingOrganization","path":"Patient.managingOrganization","short":"Organization that is the custodian of the patient record","definition":"Organization that is the custodian of the patient record.","comment":"There is only one managing organization for a specific patient record. Other organizations will have their own Patient record, and may use the Link property to join the records together (or a Person resource which can include confidence ratings for the association).","requirements":"Need to know who recognizes this patient record, manages and updates it.","min":0,"max":"1","base":{"path":"Patient.managingOrganization","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.link","path":"Patient.link","short":"Link to another patient resource that concerns the same actual person","definition":"Link to another patient resource that concerns the same actual patient.","comment":"There is no assumption that linked patient records have mutual links.","requirements":"There are multiple use cases: \n\n* Duplicate patient records due to the clerical errors associated with the difficulties of identifying humans consistently, and \n* Distribution of patient information across multiple servers.","min":0,"max":"*","base":{"path":"Patient.link","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because it might not be the main Patient resource, and the referenced patient should be used instead of this Patient record. This is when the link.type value is 'replaced-by'","isSummary":true},{"id":"Patient.link.id","path":"Patient.link.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.link.extension","path":"Patient.link.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.link.modifierExtension","path":"Patient.link.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Patient.link.other","path":"Patient.link.other","short":"The other patient or related person resource that the link refers to","definition":"The other patient resource that the link refers to.","comment":"Referencing a RelatedPerson here removes the need to use a Person record to associate a Patient and RelatedPerson as the same individual.","min":1,"max":"1","base":{"path":"Patient.link.other","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-hierarchy","valueBoolean":false}],"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Patient","http://hl7.org/fhir/StructureDefinition/RelatedPerson"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.link.type","path":"Patient.link.type","short":"replaced-by | replaces | refer | seealso","definition":"The type of link between this patient resource and another patient resource.","min":1,"max":"1","base":{"path":"Patient.link.type","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"LinkType"}],"strength":"required","description":"The type of link between this patient resource and another patient resource.","valueSet":"http://hl7.org/fhir/ValueSet/link-type|4.0.1"}}]}},{"resourceType":"StructureDefinition","id":"us-core-race","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","version":"5.0.1","name":"USCoreRaceExtension","title":"US Core Race Extension","status":"active","date":"2019-05-21","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The race codes used to represent these concepts are based upon the [CDC Race and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 921 reference race. The race concepts are grouped by and pre-mapped to the 5 OMB race categories:\n\n - American Indian or Alaska Native\n - Asian\n - Black or African American\n - Native Hawaiian or Other Pacific Islander\n - White.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"purpose":"Complies with 2015 Edition Common Clinical Data Set for patient race.","copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"},{"type":"element","expression":"RelatedPerson"},{"type":"element","expression":"Person"},{"type":"element","expression":"Practitioner"},{"type":"element","expression":"FamilyMemberHistory"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Extension","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"US Core Race Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The race codes used to represent these concepts are based upon the [CDC Race and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 921 reference race. The race concepts are grouped by and pre-mapped to the 5 OMB race categories:\n\n - American Indian or Alaska Native\n - Asian\n - Black or African American\n - Native Hawaiian or Other Pacific Islander\n - White.","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory","path":"Extension.extension","sliceName":"ombCategory","short":"American Indian or Alaska Native|Asian|Black or African American|Native Hawaiian or Other Pacific Islander|White","definition":"The 5 race category codes according to the [OMB Standards for Maintaining, Collecting, and Presenting Federal Data on Race and Ethnicity, Statistical Policy Directive No. 15, as revised, October 30, 1997](https://www.govinfo.gov/content/pkg/FR-1997-10-30/pdf/97-28653.pdf).","min":0,"max":"5","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"ombCategory","isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","description":"The 5 race category codes according to the [OMB Standards for Maintaining, Collecting, and Presenting Federal Data on Race and Ethnicity, Statistical Policy Directive No. 15, as revised, October 30, 1997](https://www.govinfo.gov/content/pkg/FR-1997-10-30/pdf/97-28653.pdf).","valueSet":"http://hl7.org/fhir/us/core/ValueSet/omb-race-category"}},{"id":"Extension.extension:detailed","path":"Extension.extension","sliceName":"detailed","short":"Extended race codes","definition":"The 900+ CDC race codes that are grouped under one of the 5 OMB race category codes:.","min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"detailed","isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/us/core/ValueSet/detailed-race"}},{"id":"Extension.extension:text","path":"Extension.extension","sliceName":"text","short":"Race Text","definition":"Plain text representation of the race concept(s).","min":1,"max":"1","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"text","isModifier":false,"isSummary":false},{"id":"Extension.extension:text.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":0,"max":"0","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"base64Binary"},{"code":"boolean"},{"code":"canonical"},{"code":"code"},{"code":"date"},{"code":"dateTime"},{"code":"decimal"},{"code":"id"},{"code":"instant"},{"code":"integer"},{"code":"markdown"},{"code":"oid"},{"code":"positiveInt"},{"code":"string"},{"code":"time"},{"code":"unsignedInt"},{"code":"uri"},{"code":"url"},{"code":"uuid"},{"code":"Address"},{"code":"Age"},{"code":"Annotation"},{"code":"Attachment"},{"code":"CodeableConcept"},{"code":"Coding"},{"code":"ContactPoint"},{"code":"Count"},{"code":"Distance"},{"code":"Duration"},{"code":"HumanName"},{"code":"Identifier"},{"code":"Money"},{"code":"Period"},{"code":"Quantity"},{"code":"Range"},{"code":"Ratio"},{"code":"Reference"},{"code":"SampledData"},{"code":"Signature"},{"code":"Timing"},{"code":"ContactDetail"},{"code":"Contributor"},{"code":"DataRequirement"},{"code":"Expression"},{"code":"ParameterDefinition"},{"code":"RelatedArtifact"},{"code":"TriggerDefinition"},{"code":"UsageContext"},{"code":"Dosage"},{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}},{"resourceType":"StructureDefinition","id":"us-core-ethnicity","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","version":"5.0.1","name":"USCoreEthnicityExtension","title":"US Core Ethnicity Extension","status":"active","date":"2019-05-21T00:00:00-04:00","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The ethnicity codes used to represent these concepts are based upon the [CDC ethnicity and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 43 reference ethnicity. The ethnicity concepts are grouped by and pre-mapped to the 2 OMB ethnicity categories: - Hispanic or Latino - Not Hispanic or Latino.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"purpose":"Complies with 2015 Edition Common Clinical Data Set for patient race.","copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"},{"type":"element","expression":"RelatedPerson"},{"type":"element","expression":"Person"},{"type":"element","expression":"Practitioner"},{"type":"element","expression":"FamilyMemberHistory"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Extension","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"US Core ethnicity Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The ethnicity codes used to represent these concepts are based upon the [CDC ethnicity and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 43 reference ethnicity. The ethnicity concepts are grouped by and pre-mapped to the 2 OMB ethnicity categories: - Hispanic or Latino - Not Hispanic or Latino.","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory","path":"Extension.extension","sliceName":"ombCategory","short":"Hispanic or Latino|Not Hispanic or Latino","definition":"The 2 ethnicity category codes according to the [OMB Standards for Maintaining, Collecting, and Presenting Federal Data on Race and Ethnicity, Statistical Policy Directive No. 15, as revised, October 30, 1997](https://www.govinfo.gov/content/pkg/FR-1997-10-30/pdf/97-28653.pdf).","min":0,"max":"1","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"ombCategory","isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/us/core/ValueSet/omb-ethnicity-category"}},{"id":"Extension.extension:detailed","path":"Extension.extension","sliceName":"detailed","short":"Extended ethnicity codes","definition":"The 41 CDC ethnicity codes that are grouped under one of the 2 OMB ethnicity category codes.","min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"detailed","isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/us/core/ValueSet/detailed-ethnicity"}},{"id":"Extension.extension:text","path":"Extension.extension","sliceName":"text","short":"ethnicity Text","definition":"Plain text representation of the ethnicity concept(s).","min":1,"max":"1","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"text","isModifier":false,"isSummary":false},{"id":"Extension.extension:text.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":0,"max":"0","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"base64Binary"},{"code":"boolean"},{"code":"canonical"},{"code":"code"},{"code":"date"},{"code":"dateTime"},{"code":"decimal"},{"code":"id"},{"code":"instant"},{"code":"integer"},{"code":"markdown"},{"code":"oid"},{"code":"positiveInt"},{"code":"string"},{"code":"time"},{"code":"unsignedInt"},{"code":"uri"},{"code":"url"},{"code":"uuid"},{"code":"Address"},{"code":"Age"},{"code":"Annotation"},{"code":"Attachment"},{"code":"CodeableConcept"},{"code":"Coding"},{"code":"ContactPoint"},{"code":"Count"},{"code":"Distance"},{"code":"Duration"},{"code":"HumanName"},{"code":"Identifier"},{"code":"Money"},{"code":"Period"},{"code":"Quantity"},{"code":"Range"},{"code":"Ratio"},{"code":"Reference"},{"code":"SampledData"},{"code":"Signature"},{"code":"Timing"},{"code":"ContactDetail"},{"code":"Contributor"},{"code":"DataRequirement"},{"code":"Expression"},{"code":"ParameterDefinition"},{"code":"RelatedArtifact"},{"code":"TriggerDefinition"},{"code":"UsageContext"},{"code":"Dosage"},{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}},{"resourceType":"StructureDefinition","id":"us-core-birthsex","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","version":"5.0.1","name":"USCoreBirthSexExtension","title":"US Core Birth Sex Extension","status":"active","date":"2019-05-21","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"A code classifying the person's sex assigned at birth as specified by the [Office of the National Coordinator for Health IT (ONC)](https://www.healthit.gov/newsroom/about-onc). This extension aligns with the C-CDA Birth Sex Observation (LOINC 76689-9).","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Extension","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"Extension","definition":"A code classifying the person's sex assigned at birth as specified by the [Office of the National Coordinator for Health IT (ONC)](https://www.healthit.gov/newsroom/about-onc).","comment":"The codes required are intended to present birth sex (i.e., the sex recorded on the patient’s birth certificate) and not gender identity or reassigned sex.","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","description":"Code for sex assigned at birth","valueSet":"http://hl7.org/fhir/us/core/ValueSet/birthsex"}}]}},{"resourceType":"StructureDefinition","id":"us-core-genderIdentity","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity","version":"5.0.1","name":"USCoreGenderIdentityExtension","title":"US Core Gender Identity Extension","status":"active","date":"2022-01-22","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"This extension provides concepts to describe the gender a person identifies as.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"purpose":"Complies with USCDI v2","copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"},{"type":"element","expression":"RelatedPerson"},{"type":"element","expression":"Person"},{"type":"element","expression":"Practitioner"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/patient-genderIdentity","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"Extension","definition":"An Extension","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Extension","definition":"An Extension","min":0,"max":"0","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"extensible","valueSet":"http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1021.32"}}]}},{"resourceType":"StructureDefinition","id":"us-core-implantable-device","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-implantable-device","version":"5.0.1","name":"USCoreImplantableDeviceProfile","title":"US Core Implantable Device Profile","status":"active","experimental":false,"date":"2022-04-20","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"The US Core Implantable Device Profile is based upon the core FHIR Device Resource and meets the U.S. Core Data for Interoperability (USCDI) v2 ‘Unique Device Identifier(s) for a Patient’s Implantable Device(s)’ requirements. To promote interoperability and adoption through common implementation, this profile sets minimum expectations for the Device resource to record, search, and fetch UDI information associated with a patient's implantable device(s). It identifies which core elements, extensions, vocabularies and value sets **SHALL** be present in the resource when using this profile. It provides the floor for standards development for specific uses cases.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"resource","abstract":false,"type":"Device","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Device","derivation":"constraint","snapshot":{"element":[{"id":"Device","path":"Device","short":"Item used in healthcare","definition":"\\-","comment":"\\-","min":0,"max":"*","base":{"path":"Device","min":0,"max":"*"},"constraint":[{"key":"dom-2","severity":"error","human":"If the resource is contained in another resource, it SHALL NOT contain nested Resources","expression":"contained.contained.empty()","xpath":"not(parent::f:contained and f:contained)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-3","severity":"error","human":"If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource","expression":"contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()","xpath":"not(exists(for $id in f:contained/*/f:id/@value return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-4","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated","expression":"contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()","xpath":"not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-5","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a security label","expression":"contained.meta.security.empty()","xpath":"not(exists(f:contained/*/f:meta/f:security))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice","valueBoolean":true},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice-explanation","valueMarkdown":"When a resource has no narrative, only systems that fully understand the data can display the resource to a human safely. Including a human readable representation in the resource makes for a much more robust eco-system and cheaper handling of resources by intermediary systems. Some ecosystems restrict distribution of resources to only those systems that do fully understand the resources, and as a consequence implementers may believe that the narrative is superfluous. However experience shows that such eco-systems often open up to new participants over time."}],"key":"dom-6","severity":"warning","human":"A resource should have narrative for robust management","expression":"text.`div`.exists()","xpath":"exists(f:text/h:div)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"}],"mustSupport":false,"isModifier":false,"isSummary":false},{"id":"Device.id","path":"Device.id","short":"Logical id of this artifact","definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.","comment":"The only time that a resource does not have an id is when it is being submitted to the server using a create operation.","min":0,"max":"1","base":{"path":"Resource.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":true},{"id":"Device.meta","path":"Device.meta","short":"Metadata about the resource","definition":"The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.","min":0,"max":"1","base":{"path":"Resource.meta","min":0,"max":"1"},"type":[{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Device.implicitRules","path":"Device.implicitRules","short":"A set of rules under which this content was created","definition":"A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.","comment":"Asserting this rule set restricts the content to be only understood by a limited set of trading partners. This inherently limits the usefulness of the data in the long term. However, the existing health eco-system is highly fractured, and not yet ready to define, collect, and exchange data in a generally computable sense. Wherever possible, implementers and/or specification writers should avoid using this element. Often, when used, the URL is a reference to an implementation guide that defines these special rules as part of it's narrative along with other profiles, value sets, etc.","min":0,"max":"1","base":{"path":"Resource.implicitRules","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because the implicit rules may provide additional knowledge about the resource that modifies it's meaning or interpretation","isSummary":true},{"id":"Device.language","path":"Device.language","short":"Language of the resource content","definition":"The base language in which the resource is written.","comment":"Language is provided to support indexing and accessibility (typically, services such as text to speech use the language tag). The html language tag in the narrative applies to the narrative. The language tag on the resource may be used to specify the language of other presentations generated from the data in the resource. Not all the content has to be in the base language. The Resource.language should not be assumed to apply to the narrative automatically. If a language is specified, it should it also be specified on the div element in the html (see rules in HTML5 for information about the relationship between xml:lang and the html lang attribute).","min":0,"max":"1","base":{"path":"Resource.language","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/all-languages"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Language"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"preferred","description":"A human language.","valueSet":"http://hl7.org/fhir/ValueSet/languages"}},{"id":"Device.text","path":"Device.text","short":"Text summary of the resource, for human interpretation","definition":"A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it \"clinically safe\" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety.","comment":"Contained resources do not have narrative. Resources that are not contained SHOULD have a narrative. In some cases, a resource may only have text with little or no additional discrete data (as long as all minOccurs=1 elements are satisfied). This may be necessary for data from legacy systems where information is captured as a \"text blob\" or where text is additionally entered raw or narrated and encoded information is added later.","alias":["narrative","html","xhtml","display"],"min":0,"max":"1","base":{"path":"DomainResource.text","min":0,"max":"1"},"type":[{"code":"Narrative"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.contained","path":"Device.contained","short":"Contained, inline Resources","definition":"These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope.","comment":"This should never be done when the content can be identified properly, as once identification is lost, it is extremely difficult (and context dependent) to restore it again. Contained resources may have profiles and tags In their meta elements, but SHALL NOT have security labels.","alias":["inline resources","anonymous resources","contained resources"],"min":0,"max":"*","base":{"path":"DomainResource.contained","min":0,"max":"*"},"type":[{"code":"Resource"}],"isModifier":false,"isSummary":false},{"id":"Device.extension","path":"Device.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the resource. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.modifierExtension","path":"Device.modifierExtension","short":"Extensions that cannot be ignored","definition":"May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the resource that contains them","isSummary":false},{"id":"Device.identifier","path":"Device.identifier","short":"Instance identifier","definition":"Unique instance identifiers assigned to a device by manufacturers other organizations or owners.","comment":"The barcode string from a barcode present on a device label or package may identify the instance, include names given to the device in local usage, or may identify the type of device. If the identifier identifies the type of device, Device.type element should be used.","min":0,"max":"*","base":{"path":"Device.identifier","min":0,"max":"*"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.definition","path":"Device.definition","short":"The reference to the definition for the device","definition":"The reference to the definition for the device.","min":0,"max":"1","base":{"path":"Device.definition","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/DeviceDefinition"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier","path":"Device.udiCarrier","short":"Unique Device Identifier (UDI) Barcode string","definition":"Unique device identifier (UDI) assigned to device label or package. Note that the Device may include multiple udiCarriers as it either may include just the udiCarrier for the jurisdiction it is sold, or for multiple jurisdictions it could have been sold.","comment":"Some devices may not have UDI information (for example. historical data or patient reported data).","min":0,"max":"1","base":{"path":"Device.udiCarrier","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.id","path":"Device.udiCarrier.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.extension","path":"Device.udiCarrier.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.modifierExtension","path":"Device.udiCarrier.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.udiCarrier.deviceIdentifier","path":"Device.udiCarrier.deviceIdentifier","short":"Mandatory fixed portion of UDI","definition":"The device identifier (DI) is a mandatory, fixed portion of a UDI that identifies the labeler and the specific version or model of a device.","alias":["DI"],"min":1,"max":"1","base":{"path":"Device.udiCarrier.deviceIdentifier","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.issuer","path":"Device.udiCarrier.issuer","short":"UDI Issuing Organization","definition":"Organization that is charged with issuing UDIs for devices. For example, the US FDA issuers include :\n1) GS1: \nhttp://hl7.org/fhir/NamingSystem/gs1-di, \n2) HIBCC:\nhttp://hl7.org/fhir/NamingSystem/hibcc-dI, \n3) ICCBBA for blood containers:\nhttp://hl7.org/fhir/NamingSystem/iccbba-blood-di, \n4) ICCBA for other devices:\nhttp://hl7.org/fhir/NamingSystem/iccbba-other-di.","alias":["Barcode System"],"min":0,"max":"1","base":{"path":"Device.udiCarrier.issuer","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.jurisdiction","path":"Device.udiCarrier.jurisdiction","short":"Regional UDI authority","definition":"The identity of the authoritative source for UDI generation within a jurisdiction. All UDIs are globally unique within a single namespace with the appropriate repository uri as the system. For example, UDIs of devices managed in the U.S. by the FDA, the value is http://hl7.org/fhir/NamingSystem/fda-udi.","requirements":"Allows a recipient of a UDI to know which database will contain the UDI-associated metadata.","min":0,"max":"1","base":{"path":"Device.udiCarrier.jurisdiction","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.carrierAIDC","path":"Device.udiCarrier.carrierAIDC","short":"UDI Machine Readable Barcode String","definition":"The full UDI carrier of the Automatic Identification and Data Capture (AIDC) technology representation of the barcode string as printed on the packaging of the device - e.g., a barcode or RFID. Because of limitations on character sets in XML and the need to round-trip JSON data through XML, AIDC Formats *SHALL* be base64 encoded.","comment":"The AIDC form of UDIs should be scanned or otherwise used for the identification of the device whenever possible to minimize errors in records resulting from manual transcriptions. If separate barcodes for DI and PI are present, concatenate the string with DI first and in order of human readable expression on label.","alias":["Automatic Identification and Data Capture"],"min":0,"max":"1","base":{"path":"Device.udiCarrier.carrierAIDC","min":0,"max":"1"},"type":[{"code":"base64Binary"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.carrierHRF","path":"Device.udiCarrier.carrierHRF","short":"UDI Human Readable Barcode String","definition":"The full UDI carrier as the human readable form (HRF) representation of the barcode string as printed on the packaging of the device.","comment":"If separate barcodes for DI and PI are present, concatenate the string with DI first and in order of human readable expression on label.","alias":["Human Readable Form","UDI","Barcode String"],"min":0,"max":"1","base":{"path":"Device.udiCarrier.carrierHRF","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.entryType","path":"Device.udiCarrier.entryType","short":"barcode | rfid | manual +","definition":"A coded entry to indicate how the data was entered.","requirements":"Supports a way to distinguish hand entered from machine read data.","min":0,"max":"1","base":{"path":"Device.udiCarrier.entryType","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"UDIEntryType"}],"strength":"required","description":"Codes to identify how UDI data was entered.","valueSet":"http://hl7.org/fhir/ValueSet/udi-entry-type|4.0.1"}},{"id":"Device.status","path":"Device.status","short":"active | inactive | entered-in-error | unknown","definition":"Status of the Device availability.","comment":"This element is labeled as a modifier because the status contains the codes inactive and entered-in-error that mark the device (record)as not currently valid.","min":0,"max":"1","base":{"path":"Device.status","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labelled as a modifier because it is a status element that contains status entered-in-error which means that the resource should not be treated as valid","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"FHIRDeviceStatus"}],"strength":"required","description":"The availability status of the device.","valueSet":"http://hl7.org/fhir/ValueSet/device-status|4.0.1"}},{"id":"Device.statusReason","path":"Device.statusReason","short":"online | paused | standby | offline | not-ready | transduc-discon | hw-discon | off","definition":"Reason for the dtatus of the Device availability.","min":0,"max":"*","base":{"path":"Device.statusReason","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"FHIRDeviceStatusReason"}],"strength":"extensible","description":"The availability status reason of the device.","valueSet":"http://hl7.org/fhir/ValueSet/device-status-reason"}},{"id":"Device.distinctIdentifier","path":"Device.distinctIdentifier","short":"The distinct identification string","definition":"The distinct identification string as required by regulation for a human cell, tissue, or cellular and tissue-based product.","comment":"For example, this applies to devices in the United States regulated under *Code of Federal Regulation 21CFR§1271.290(c)*.","alias":["Distinct Identification Code (DIC)"],"min":0,"max":"1","base":{"path":"Device.distinctIdentifier","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.manufacturer","path":"Device.manufacturer","short":"Name of device manufacturer","definition":"A name of the manufacturer.","min":0,"max":"1","base":{"path":"Device.manufacturer","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.manufactureDate","path":"Device.manufactureDate","short":"Date when the device was made","definition":"The date and time when the device was manufactured.","min":0,"max":"1","base":{"path":"Device.manufactureDate","min":0,"max":"1"},"type":[{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.expirationDate","path":"Device.expirationDate","short":"Date and time of expiry of this device (if applicable)","definition":"The date and time beyond which this device is no longer valid or should not be used (if applicable).","min":0,"max":"1","base":{"path":"Device.expirationDate","min":0,"max":"1"},"type":[{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.lotNumber","path":"Device.lotNumber","short":"Lot number of manufacture","definition":"Lot number assigned by the manufacturer.","min":0,"max":"1","base":{"path":"Device.lotNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.serialNumber","path":"Device.serialNumber","short":"Serial number assigned by the manufacturer","definition":"The serial number assigned by the organization when the device was manufactured.","comment":"Alphanumeric Maximum 20.","min":0,"max":"1","base":{"path":"Device.serialNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.deviceName","path":"Device.deviceName","short":"The name of the device as given by the manufacturer","definition":"This represents the manufacturer's name of the device as provided by the device, from a UDI label, or by a person describing the Device. This typically would be used when a person provides the name(s) or when the device represents one of the names available from DeviceDefinition.","min":0,"max":"*","base":{"path":"Device.deviceName","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.id","path":"Device.deviceName.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.extension","path":"Device.deviceName.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.modifierExtension","path":"Device.deviceName.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.deviceName.name","path":"Device.deviceName.name","short":"The name of the device","definition":"The name of the device.","alias":["Σ"],"min":1,"max":"1","base":{"path":"Device.deviceName.name","min":1,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.type","path":"Device.deviceName.type","short":"udi-label-name | user-friendly-name | patient-reported-name | manufacturer-name | model-name | other","definition":"The type of deviceName.\nUDILabelName | UserFriendlyName | PatientReportedName | ManufactureDeviceName | ModelName.","min":1,"max":"1","base":{"path":"Device.deviceName.type","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"DeviceNameType"}],"strength":"required","description":"The type of name the device is referred by.","valueSet":"http://hl7.org/fhir/ValueSet/device-nametype|4.0.1"}},{"id":"Device.modelNumber","path":"Device.modelNumber","short":"The model number for the device","definition":"The model number for the device.","min":0,"max":"1","base":{"path":"Device.modelNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.partNumber","path":"Device.partNumber","short":"The part number of the device","definition":"The part number of the device.","comment":"Alphanumeric Maximum 20.","min":0,"max":"1","base":{"path":"Device.partNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.type","path":"Device.type","short":"The kind or type of device","definition":"The kind or type of device.","min":1,"max":"1","base":{"path":"Device.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"strength":"extensible","description":"Codes to identify medical devices","valueSet":"http://hl7.org/fhir/ValueSet/device-kind"}},{"id":"Device.specialization","path":"Device.specialization","short":"The capabilities supported on a device, the standards to which the device conforms for a particular purpose, and used for the communication","definition":"The capabilities supported on a device, the standards to which the device conforms for a particular purpose, and used for the communication.","min":0,"max":"*","base":{"path":"Device.specialization","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.id","path":"Device.specialization.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.extension","path":"Device.specialization.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.modifierExtension","path":"Device.specialization.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.specialization.systemType","path":"Device.specialization.systemType","short":"The standard that is used to operate and communicate","definition":"The standard that is used to operate and communicate.","alias":["Σ"],"min":1,"max":"1","base":{"path":"Device.specialization.systemType","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.version","path":"Device.specialization.version","short":"The version of the standard that is used to operate and communicate","definition":"The version of the standard that is used to operate and communicate.","min":0,"max":"1","base":{"path":"Device.specialization.version","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version","path":"Device.version","short":"The actual design of the device or software version running on the device","definition":"The actual design of the device or software version running on the device.","min":0,"max":"*","base":{"path":"Device.version","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version.id","path":"Device.version.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.version.extension","path":"Device.version.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.version.modifierExtension","path":"Device.version.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.version.type","path":"Device.version.type","short":"The type of the device version","definition":"The type of the device version.","alias":["Σ"],"min":0,"max":"1","base":{"path":"Device.version.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version.component","path":"Device.version.component","short":"A single component of the device version","definition":"A single component of the device version.","min":0,"max":"1","base":{"path":"Device.version.component","min":0,"max":"1"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version.value","path":"Device.version.value","short":"The version text","definition":"The version text.","min":1,"max":"1","base":{"path":"Device.version.value","min":1,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property","path":"Device.property","short":"The actual configuration settings of a device as it actually operates, e.g., regulation status, time properties","definition":"The actual configuration settings of a device as it actually operates, e.g., regulation status, time properties.","min":0,"max":"*","base":{"path":"Device.property","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property.id","path":"Device.property.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.property.extension","path":"Device.property.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.property.modifierExtension","path":"Device.property.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.property.type","path":"Device.property.type","short":"Code that specifies the property DeviceDefinitionPropetyCode (Extensible)","definition":"Code that specifies the property DeviceDefinitionPropetyCode (Extensible).","min":1,"max":"1","base":{"path":"Device.property.type","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property.valueQuantity","path":"Device.property.valueQuantity","short":"Property value as a quantity","definition":"Property value as a quantity.","min":0,"max":"*","base":{"path":"Device.property.valueQuantity","min":0,"max":"*"},"type":[{"code":"Quantity"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property.valueCode","path":"Device.property.valueCode","short":"Property value as a code, e.g., NTP4 (synced to NTP)","definition":"Property value as a code, e.g., NTP4 (synced to NTP).","min":0,"max":"*","base":{"path":"Device.property.valueCode","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.patient","path":"Device.patient","short":"Patient to whom Device is affixed","definition":"Patient information, If the device is affixed to a person.","requirements":"If the device is implanted in a patient, then need to associate the device to the patient.","min":1,"max":"1","base":{"path":"Device.patient","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.owner","path":"Device.owner","short":"Organization responsible for device","definition":"An organization that is responsible for the provision and ongoing maintenance of the device.","min":0,"max":"1","base":{"path":"Device.owner","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.contact","path":"Device.contact","short":"Details for human/organization for support","definition":"Contact details for an organization or a particular human that is responsible for the device.","comment":"used for troubleshooting etc.","min":0,"max":"*","base":{"path":"Device.contact","min":0,"max":"*"},"type":[{"code":"ContactPoint"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.location","path":"Device.location","short":"Where the device is found","definition":"The place where the device can be found.","requirements":"Device.location can be used to track device location.","min":0,"max":"1","base":{"path":"Device.location","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Location"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.url","path":"Device.url","short":"Network address to contact device","definition":"A network address on which the device may be contacted directly.","comment":"If the device is running a FHIR server, the network address should be the Base URL from which a conformance statement may be retrieved.","min":0,"max":"1","base":{"path":"Device.url","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.note","path":"Device.note","short":"Device notes and comments","definition":"Descriptive information, usage information or implantation information that is not captured in an existing element.","min":0,"max":"*","base":{"path":"Device.note","min":0,"max":"*"},"type":[{"code":"Annotation"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.safety","path":"Device.safety","short":"Safety Characteristics of Device","definition":"Provides additional safety characteristics about a medical device. For example devices containing latex.","min":0,"max":"*","base":{"path":"Device.safety","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Device.parent","path":"Device.parent","short":"The parent device","definition":"The parent device.","min":0,"max":"1","base":{"path":"Device.parent","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Device"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}},{"resourceType":"StructureDefinition","id":"us-core-blood-pressure","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-blood-pressure","version":"5.0.1","name":"USCoreBloodPressureProfile","title":"US Core Blood Pressure Profile","status":"active","experimental":false,"date":"2022-04-20","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"To promote interoperability and adoption through common implementation, this profile sets minimum expectations for the Observation resource to record, search, and fetch diastolic and systolic blood pressure observations with standard LOINC codes and UCUM units of measure. It is based on the US Core Vital Signs Profile and identifies the *additional* mandatory core elements, extensions, vocabularies and value sets which **SHALL** be present in the Observation resource when using this profile. It provides the floor for standards development for specific uses cases.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"resource","abstract":false,"type":"Observation","baseDefinition":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-vital-signs","derivation":"constraint","snapshot":{"element":[{"id":"Observation","path":"Observation","short":"US Core Blood Pressure Profile","definition":"\\-","comment":"\\-","alias":["Vital Signs","Measurement","Results","Tests"],"min":0,"max":"*","base":{"path":"Observation","min":0,"max":"*"},"constraint":[{"key":"dom-2","severity":"error","human":"If the resource is contained in another resource, it SHALL NOT contain nested Resources","expression":"contained.contained.empty()","xpath":"not(parent::f:contained and f:contained)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-3","severity":"error","human":"If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource","expression":"contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()","xpath":"not(exists(for $id in f:contained/*/f:id/@value return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-4","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated","expression":"contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()","xpath":"not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-5","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a security label","expression":"contained.meta.security.empty()","xpath":"not(exists(f:contained/*/f:meta/f:security))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice","valueBoolean":true},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice-explanation","valueMarkdown":"When a resource has no narrative, only systems that fully understand the data can display the resource to a human safely. Including a human readable representation in the resource makes for a much more robust eco-system and cheaper handling of resources by intermediary systems. Some ecosystems restrict distribution of resources to only those systems that do fully understand the resources, and as a consequence implementers may believe that the narrative is superfluous. However experience shows that such eco-systems often open up to new participants over time."}],"key":"dom-6","severity":"warning","human":"A resource should have narrative for robust management","expression":"text.`div`.exists()","xpath":"exists(f:text/h:div)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"obs-6","severity":"error","human":"dataAbsentReason SHALL only be present if Observation.value[x] is not present","expression":"dataAbsentReason.empty() or value.empty()","xpath":"not(exists(f:dataAbsentReason)) or (not(exists(*[starts-with(local-name(.), 'value')])))","source":"http://hl7.org/fhir/StructureDefinition/Observation"},{"key":"obs-7","severity":"error","human":"If Observation.code is the same as an Observation.component.code then the value element associated with the code SHALL NOT be present","expression":"value.empty() or component.code.where(coding.intersect(%resource.code.coding).exists()).empty()","xpath":"not(f:*[starts-with(local-name(.), 'value')] and (for $coding in f:code/f:coding return f:component/f:code/f:coding[f:code/@value=$coding/f:code/@value] [f:system/@value=$coding/f:system/@value]))","source":"http://hl7.org/fhir/StructureDefinition/Observation"},{"key":"vs-2","severity":"error","human":"If there is no component or hasMember element then either a value[x] or a data absent reason must be present.","expression":"(component.empty() and hasMember.empty()) implies (dataAbsentReason.exists() or value.exists())","xpath":"f:component or f:memberOF or f:*[starts-with(local-name(.), 'value')] or f:dataAbsentReason","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"isModifier":false,"isSummary":false},{"id":"Observation.id","path":"Observation.id","short":"Logical id of this artifact","definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.","comment":"The only time that a resource does not have an id is when it is being submitted to the server using a create operation.","min":0,"max":"1","base":{"path":"Resource.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":true},{"id":"Observation.meta","path":"Observation.meta","short":"Metadata about the resource","definition":"The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.","min":0,"max":"1","base":{"path":"Resource.meta","min":0,"max":"1"},"type":[{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.implicitRules","path":"Observation.implicitRules","short":"A set of rules under which this content was created","definition":"A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.","comment":"Asserting this rule set restricts the content to be only understood by a limited set of trading partners. This inherently limits the usefulness of the data in the long term. However, the existing health eco-system is highly fractured, and not yet ready to define, collect, and exchange data in a generally computable sense. Wherever possible, implementers and/or specification writers should avoid using this element. Often, when used, the URL is a reference to an implementation guide that defines these special rules as part of it's narrative along with other profiles, value sets, etc.","min":0,"max":"1","base":{"path":"Resource.implicitRules","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because the implicit rules may provide additional knowledge about the resource that modifies it's meaning or interpretation","isSummary":true},{"id":"Observation.language","path":"Observation.language","short":"Language of the resource content","definition":"The base language in which the resource is written.","comment":"Language is provided to support indexing and accessibility (typically, services such as text to speech use the language tag). The html language tag in the narrative applies to the narrative. The language tag on the resource may be used to specify the language of other presentations generated from the data in the resource. Not all the content has to be in the base language. The Resource.language should not be assumed to apply to the narrative automatically. If a language is specified, it should it also be specified on the div element in the html (see rules in HTML5 for information about the relationship between xml:lang and the html lang attribute).","min":0,"max":"1","base":{"path":"Resource.language","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/all-languages"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Language"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"preferred","description":"A human language.","valueSet":"http://hl7.org/fhir/ValueSet/languages"}},{"id":"Observation.text","path":"Observation.text","short":"Text summary of the resource, for human interpretation","definition":"A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it \"clinically safe\" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety.","comment":"Contained resources do not have narrative. Resources that are not contained SHOULD have a narrative. In some cases, a resource may only have text with little or no additional discrete data (as long as all minOccurs=1 elements are satisfied). This may be necessary for data from legacy systems where information is captured as a \"text blob\" or where text is additionally entered raw or narrated and encoded information is added later.","alias":["narrative","html","xhtml","display"],"min":0,"max":"1","base":{"path":"DomainResource.text","min":0,"max":"1"},"type":[{"code":"Narrative"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.contained","path":"Observation.contained","short":"Contained, inline Resources","definition":"These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope.","comment":"This should never be done when the content can be identified properly, as once identification is lost, it is extremely difficult (and context dependent) to restore it again. Contained resources may have profiles and tags In their meta elements, but SHALL NOT have security labels.","alias":["inline resources","anonymous resources","contained resources"],"min":0,"max":"*","base":{"path":"DomainResource.contained","min":0,"max":"*"},"type":[{"code":"Resource"}],"isModifier":false,"isSummary":false},{"id":"Observation.extension","path":"Observation.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the resource. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.modifierExtension","path":"Observation.modifierExtension","short":"Extensions that cannot be ignored","definition":"May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/extensibility.html#modifierExtension).","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the resource that contains them","isSummary":false},{"id":"Observation.identifier","path":"Observation.identifier","short":"Business Identifier for observation","definition":"A unique identifier assigned to this observation.","requirements":"Allows observations to be distinguished and referenced.","min":0,"max":"*","base":{"path":"Observation.identifier","min":0,"max":"*"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.basedOn","path":"Observation.basedOn","short":"Fulfills plan, proposal or order","definition":"A plan, proposal or order that is fulfilled in whole or in part by this event. For example, a MedicationRequest may require a patient to have laboratory test performed before it is dispensed.","requirements":"Allows tracing of authorization for the event and tracking whether proposals/recommendations were acted upon.","alias":["Fulfills"],"min":0,"max":"*","base":{"path":"Observation.basedOn","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/CarePlan","http://hl7.org/fhir/StructureDefinition/DeviceRequest","http://hl7.org/fhir/StructureDefinition/ImmunizationRecommendation","http://hl7.org/fhir/StructureDefinition/MedicationRequest","http://hl7.org/fhir/StructureDefinition/NutritionOrder","http://hl7.org/fhir/StructureDefinition/ServiceRequest"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.partOf","path":"Observation.partOf","short":"Part of referenced event","definition":"A larger event of which this particular Observation is a component or step. For example, an observation as part of a procedure.","comment":"To link an Observation to an Encounter use `encounter`. See the [Notes](http://hl7.org/fhir/observation.html#obsgrouping) below for guidance on referencing another Observation.","alias":["Container"],"min":0,"max":"*","base":{"path":"Observation.partOf","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/MedicationAdministration","http://hl7.org/fhir/StructureDefinition/MedicationDispense","http://hl7.org/fhir/StructureDefinition/MedicationStatement","http://hl7.org/fhir/StructureDefinition/Procedure","http://hl7.org/fhir/StructureDefinition/Immunization","http://hl7.org/fhir/StructureDefinition/ImagingStudy"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.status","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-display-hint","valueString":"default: final"}],"path":"Observation.status","short":"registered | preliminary | final | amended +","definition":"The status of the result value.","comment":"This element is labeled as a modifier because the status contains codes that mark the resource as not currently valid.","requirements":"Need to track the status of individual results. Some results are finalized before the whole report is finalized.","min":1,"max":"1","base":{"path":"Observation.status","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":true,"isModifierReason":"This element is labeled as a modifier because it is a status element that contains status entered-in-error which means that the resource should not be treated as valid","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Status"}],"strength":"required","valueSet":"http://hl7.org/fhir/ValueSet/observation-status|4.0.1"}},{"id":"Observation.category","path":"Observation.category","slicing":{"discriminator":[{"type":"value","path":"coding.code"},{"type":"value","path":"coding.system"}],"ordered":false,"rules":"open"},"short":"Classification of type of observation","definition":"A code that classifies the general type of observation being made.","comment":"In addition to the required category valueset, this element allows various categorization schemes based on the owner’s definition of the category and effectively multiple categories can be used at once. The level of granularity is defined by the category concepts in the value set.","requirements":"Used for filtering what observations are retrieved and displayed.","min":1,"max":"*","base":{"path":"Observation.category","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationCategory"}],"strength":"preferred","description":"Codes for high level observation categories.","valueSet":"http://hl7.org/fhir/ValueSet/observation-category"}},{"id":"Observation.category:VSCat","path":"Observation.category","sliceName":"VSCat","short":"Classification of type of observation","definition":"A code that classifies the general type of observation being made.","comment":"In addition to the required category valueset, this element allows various categorization schemes based on the owner’s definition of the category and effectively multiple categories can be used at once. The level of granularity is defined by the category concepts in the value set.","requirements":"Used for filtering what observations are retrieved and displayed.","min":1,"max":"1","base":{"path":"Observation.category","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationCategory"}],"strength":"preferred","description":"Codes for high level observation categories.","valueSet":"http://hl7.org/fhir/ValueSet/observation-category"}},{"id":"Observation.category:VSCat.id","path":"Observation.category.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.category:VSCat.extension","path":"Observation.category.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.category:VSCat.coding","path":"Observation.category.coding","short":"Code defined by a terminology system","definition":"A reference to a code defined by a terminology system.","comment":"Codes may be defined very casually in enumerations, or code lists, up to very formal definitions such as SNOMED CT - see the HL7 v3 Core Principles for more information. Ordering of codings is undefined and SHALL NOT be used to infer meaning. Generally, at most only one of the coding values will be labeled as UserSelected = true.","requirements":"Allows for alternative encodings within a code system, and translations to other code systems.","min":1,"max":"*","base":{"path":"CodeableConcept.coding","min":0,"max":"*"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.coding.id","path":"Observation.category.coding.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.category:VSCat.coding.extension","path":"Observation.category.coding.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.category:VSCat.coding.system","path":"Observation.category.coding.system","short":"Identity of the terminology system","definition":"The identification of the code system that defines the meaning of the symbol in the code.","comment":"The URI may be an OID (urn:oid:...) or a UUID (urn:uuid:...). OIDs and UUIDs SHALL be references to the HL7 OID registry. Otherwise, the URI should come from HL7's list of FHIR defined special URIs or it should reference to some definition that establishes the system clearly and unambiguously.","requirements":"Need to be unambiguous about the source of the definition of the symbol.","min":1,"max":"1","base":{"path":"Coding.system","min":0,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"http://terminology.hl7.org/CodeSystem/observation-category","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.coding.version","path":"Observation.category.coding.version","short":"Version of the system - if relevant","definition":"The version of the code system which was used when choosing this code. Note that a well-maintained code system does not need the version reported, because the meaning of codes is consistent across versions. However this cannot consistently be assured, and when the meaning is not guaranteed to be consistent, the version SHOULD be exchanged.","comment":"Where the terminology does not clearly define what string should be used to identify code system versions, the recommendation is to use the date (expressed in FHIR date format) on which that version was officially published as the version date.","min":0,"max":"1","base":{"path":"Coding.version","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.coding.code","path":"Observation.category.coding.code","short":"Symbol in syntax defined by the system","definition":"A symbol in syntax defined by the system. The symbol may be a predefined code or an expression in a syntax defined by the coding system (e.g. post-coordination).","requirements":"Need to refer to a particular code in the system.","min":1,"max":"1","base":{"path":"Coding.code","min":0,"max":"1"},"type":[{"code":"code"}],"fixedCode":"vital-signs","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.coding.display","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable","valueBoolean":true}],"path":"Observation.category.coding.display","short":"Representation defined by the system","definition":"A representation of the meaning of the code in the system, following the rules of the system.","requirements":"Need to be able to carry a human-readable meaning of the code for readers that do not know the system.","min":0,"max":"1","base":{"path":"Coding.display","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.coding.userSelected","path":"Observation.category.coding.userSelected","short":"If this coding was chosen directly by the user","definition":"Indicates that this coding was chosen by a user directly - e.g. off a pick list of available items (codes or displays).","comment":"Amongst a set of alternatives, a directly chosen code is the most appropriate starting point for new translations. There is some ambiguity about what exactly 'directly chosen' implies, and trading partner agreement may be needed to clarify the use of this element and its consequences more completely.","requirements":"This has been identified as a clinical safety criterium - that this exact system/code pair was chosen explicitly, rather than inferred by the system based on some rules or language processing.","min":0,"max":"1","base":{"path":"Coding.userSelected","min":0,"max":"1"},"type":[{"code":"boolean"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.text","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable","valueBoolean":true}],"path":"Observation.category.text","short":"Plain text representation of the concept","definition":"A human language representation of the concept as seen/selected/uttered by the user who entered the data and/or which represents the intended meaning of the user.","comment":"Very often the text is the same as a displayName of one of the codings.","requirements":"The codes from the terminologies do not always capture the correct meaning with all the nuances of the human using them, or sometimes there is no appropriate code at all. In these cases, the text is used to capture the full meaning of the source.","min":0,"max":"1","base":{"path":"CodeableConcept.text","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.code","path":"Observation.code","short":"Blood Pressure","definition":"Coded Responses from C-CDA Vital Sign Results.","comment":"*All* code-value and, if present, component.code-component.value pairs need to be taken into account to correctly understand the meaning of the observation.","requirements":"5. SHALL contain exactly one [1..1] code, where the @code SHOULD be selected from ValueSet HITSP Vital Sign Result Type 2.16.840.1.113883.3.88.12.80.62 DYNAMIC (CONF:7301).","alias":["Name"],"min":1,"max":"1","base":{"path":"Observation.code","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"patternCodeableConcept":{"coding":[{"system":"http://loinc.org","code":"85354-9"}]},"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"The vital sign codes from the base FHIR and US Core Vital Signs.","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-vital-signs"}},{"id":"Observation.subject","path":"Observation.subject","short":"Who and/or what the observation is about","definition":"The patient, or group of patients, location, or device this observation is about and into whose record the observation is placed. If the actual focus of the observation is different from the subject (or a sample of, part, or region of the subject), the `focus` element or the `code` itself specifies the actual focus of the observation.","comment":"One would expect this element to be a cardinality of 1..1. The only circumstance in which the subject can be missing is when the observation is made by a device that does not know the patient. In this case, the observation SHALL be matched to a patient through some context/channel matching technique, and at this point, the observation should be updated.","requirements":"Observations have no value if you don't know who or what they're about.","min":1,"max":"1","base":{"path":"Observation.subject","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.focus","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status","valueCode":"trial-use"}],"path":"Observation.focus","short":"What the observation is about, when it is not about the subject of record","definition":"The actual focus of an observation when it is not the patient of record representing something or someone associated with the patient such as a spouse, parent, fetus, or donor. For example, fetus observations in a mother's record. The focus of an observation could also be an existing condition, an intervention, the subject's diet, another observation of the subject, or a body structure such as tumor or implanted device. An example use case would be using the Observation resource to capture whether the mother is trained to change her child's tracheostomy tube. In this example, the child is the patient of record and the mother is the focus.","comment":"Typically, an observation is made about the subject - a patient, or group of patients, location, or device - and the distinction between the subject and what is directly measured for an observation is specified in the observation code itself ( e.g., \"Blood Glucose\") and does not need to be represented separately using this element. Use `specimen` if a reference to a specimen is required. If a code is required instead of a resource use either `bodysite` for bodysites or the standard extension [focusCode](http://hl7.org/fhir/extension-observation-focuscode.html).","min":0,"max":"*","base":{"path":"Observation.focus","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Resource"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.encounter","path":"Observation.encounter","short":"Healthcare event during which this observation is made","definition":"The healthcare event (e.g. a patient and healthcare provider interaction) during which this observation is made.","comment":"This will typically be the encounter the event occurred within, but some events may be initiated prior to or after the official completion of an encounter but still be tied to the context of the encounter (e.g. pre-admission laboratory tests).","requirements":"For some observations it may be important to know the link between an observation and a particular encounter.","alias":["Context"],"min":0,"max":"1","base":{"path":"Observation.encounter","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Encounter"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.effective[x]","path":"Observation.effective[x]","short":"Often just a dateTime for Vital Signs","definition":"Often just a dateTime for Vital Signs.","comment":"At least a date should be present unless this observation is a historical report. For recording imprecise or \"fuzzy\" times (For example, a blood glucose measurement taken \"after breakfast\") use the [Timing](http://hl7.org/fhir/datatypes.html#timing) datatype which allow the measurement to be tied to regular life events.","requirements":"Knowing when an observation was deemed true is important to its relevance as well as determining trends.","alias":["Occurrence"],"min":1,"max":"1","base":{"path":"Observation.effective[x]","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"dateTime"},{"code":"Period"}],"condition":["vs-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"vs-1","severity":"error","human":"if Observation.effective[x] is dateTime and has a value then that value shall be precise to the day","expression":"($this as dateTime).toString().length() >= 8","xpath":"f:effectiveDateTime[matches(@value, '^\\d{4}-\\d{2}-\\d{2}')]","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.issued","path":"Observation.issued","short":"Date/Time this version was made available","definition":"The date and time this version of the observation was made available to providers, typically after the results have been reviewed and verified.","comment":"For Observations that don’t require review and verification, it may be the same as the [`lastUpdated` ](http://hl7.org/fhir/resource-definitions.html#Meta.lastUpdated) time of the resource itself. For Observations that do require review and verification for certain updates, it might not be the same as the `lastUpdated` time of the resource itself due to a non-clinically significant update that doesn’t require the new version to be reviewed and verified again.","min":0,"max":"1","base":{"path":"Observation.issued","min":0,"max":"1"},"type":[{"code":"instant"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.performer","path":"Observation.performer","short":"Who is responsible for the observation","definition":"Who was responsible for asserting the observed value as \"true\".","requirements":"May give a degree of confidence in the observation and also indicates where follow-up questions should be directed.","min":0,"max":"*","base":{"path":"Observation.performer","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Practitioner","http://hl7.org/fhir/StructureDefinition/PractitionerRole","http://hl7.org/fhir/StructureDefinition/Organization","http://hl7.org/fhir/StructureDefinition/CareTeam","http://hl7.org/fhir/StructureDefinition/Patient","http://hl7.org/fhir/StructureDefinition/RelatedPerson"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.value[x]","path":"Observation.value[x]","short":"Vital Signs Value","definition":"Vital Signs value are typically recorded using the Quantity data type.","comment":"An observation may have; 1) a single value here, 2) both a value and a set of related or component values, or 3) only a set of related or component values. If a value is present, the datatype for this element should be determined by Observation.code. A CodeableConcept with just a text would be used instead of a string if the field was usually coded, or if the type associated with the Observation.code defines a coded value. For additional guidance, see the [Notes section](http://hl7.org/fhir/observation.html#notes) below.","requirements":"9. SHALL contain exactly one [1..1] value with @xsi:type=\"PQ\" (CONF:7305).","min":0,"max":"1","base":{"path":"Observation.value[x]","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"Quantity"},{"code":"CodeableConcept"},{"code":"string"},{"code":"boolean"},{"code":"integer"},{"code":"Range"},{"code":"Ratio"},{"code":"SampledData"},{"code":"time"},{"code":"dateTime"},{"code":"Period"}],"condition":["obs-7","vs-2"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"Common UCUM units for recording Vital Signs.","valueSet":"http://hl7.org/fhir/ValueSet/ucum-vitals-common|4.0.1"}},{"id":"Observation.dataAbsentReason","path":"Observation.dataAbsentReason","short":"Why the result is missing","definition":"Provides a reason why the expected value in the element Observation.value[x] is missing.","comment":"Null or exceptional values can be represented two ways in FHIR Observations. One way is to simply include them in the value set and represent the exceptions in the value. For example, measurement values for a serology test could be \"detected\", \"not detected\", \"inconclusive\", or \"specimen unsatisfactory\". \n\nThe alternate way is to use the value element for actual observations and use the explicit dataAbsentReason element to record exceptional values. For example, the dataAbsentReason code \"error\" could be used when the measurement was not completed. Note that an observation may only be reported if there are values to report. For example differential cell counts values may be reported only when > 0. Because of these options, use-case agreements are required to interpret general observations for null or exceptional values.","requirements":"For many results it is necessary to handle exceptional values in measurements.","min":0,"max":"1","base":{"path":"Observation.dataAbsentReason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"condition":["obs-6","vs-2"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationValueAbsentReason"}],"strength":"extensible","description":"Codes specifying why the result (`Observation.value[x]`) is missing.","valueSet":"http://hl7.org/fhir/ValueSet/data-absent-reason"}},{"id":"Observation.interpretation","path":"Observation.interpretation","short":"High, low, normal, etc.","definition":"A categorical assessment of an observation value. For example, high, low, normal.","comment":"Historically used for laboratory results (known as 'abnormal flag' ), its use extends to other use cases where coded interpretations are relevant. Often reported as one or more simple compact codes this element is often placed adjacent to the result value in reports and flow sheets to signal the meaning/normalcy status of the result.","requirements":"For some results, particularly numeric results, an interpretation is necessary to fully understand the significance of a result.","alias":["Abnormal Flag"],"min":0,"max":"*","base":{"path":"Observation.interpretation","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationInterpretation"}],"strength":"extensible","description":"Codes identifying interpretations of observations.","valueSet":"http://hl7.org/fhir/ValueSet/observation-interpretation"}},{"id":"Observation.note","path":"Observation.note","short":"Comments about the observation","definition":"Comments about the observation or the results.","comment":"May include general statements about the observation, or statements about significant, unexpected or unreliable results values, or information about its source when relevant to its interpretation.","requirements":"Need to be able to provide free text additional information.","min":0,"max":"*","base":{"path":"Observation.note","min":0,"max":"*"},"type":[{"code":"Annotation"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.bodySite","path":"Observation.bodySite","short":"Observed body part","definition":"Indicates the site on the subject's body where the observation was made (i.e. the target site).","comment":"Only used if not implicit in code found in Observation.code. In many systems, this may be represented as a related observation instead of an inline component. \n\nIf the use case requires BodySite to be handled as a separate resource (e.g. to identify and track separately) then use the standard extension[ bodySite](http://hl7.org/fhir/extension-bodysite.html).","min":0,"max":"1","base":{"path":"Observation.bodySite","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"BodySite"}],"strength":"example","description":"Codes describing anatomical locations. May include laterality.","valueSet":"http://hl7.org/fhir/ValueSet/body-site"}},{"id":"Observation.method","path":"Observation.method","short":"How it was done","definition":"Indicates the mechanism used to perform the observation.","comment":"Only used if not implicit in code for Observation.code.","requirements":"In some cases, method can impact results and is thus used for determining whether results can be compared or determining significance of results.","min":0,"max":"1","base":{"path":"Observation.method","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationMethod"}],"strength":"example","description":"Methods for simple observations.","valueSet":"http://hl7.org/fhir/ValueSet/observation-methods"}},{"id":"Observation.specimen","path":"Observation.specimen","short":"Specimen used for this observation","definition":"The specimen that was used when this observation was made.","comment":"Should only be used if not implicit in code found in `Observation.code`. Observations are not made on specimens themselves; they are made on a subject, but in many cases by the means of a specimen. Note that although specimens are often involved, they are not always tracked and reported explicitly. Also note that observation resources may be used in contexts that track the specimen explicitly (e.g. Diagnostic Report).","min":0,"max":"1","base":{"path":"Observation.specimen","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Specimen"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.device","path":"Observation.device","short":"(Measurement) Device","definition":"The device used to generate the observation data.","comment":"Note that this is not meant to represent a device involved in the transmission of the result, e.g., a gateway. Such devices may be documented using the Provenance resource where relevant.","min":0,"max":"1","base":{"path":"Observation.device","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Device","http://hl7.org/fhir/StructureDefinition/DeviceMetric"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange","path":"Observation.referenceRange","short":"Provides guide for interpretation","definition":"Guidance on how to interpret the value by comparison to a normal or recommended range. Multiple reference ranges are interpreted as an \"OR\". In other words, to represent two distinct target populations, two `referenceRange` elements would be used.","comment":"Most observations only have one generic reference range. Systems MAY choose to restrict to only supplying the relevant reference range based on knowledge about the patient (e.g., specific to the patient's age, gender, weight and other factors), but this might not be possible or appropriate. Whenever more than one reference range is supplied, the differences between them SHOULD be provided in the reference range and/or age properties.","requirements":"Knowing what values are considered \"normal\" can help evaluate the significance of a particular result. Need to be able to provide multiple reference ranges for different contexts.","min":0,"max":"*","base":{"path":"Observation.referenceRange","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"obs-3","severity":"error","human":"Must have at least a low or a high or text","expression":"low.exists() or high.exists() or text.exists()","xpath":"(exists(f:low) or exists(f:high)or exists(f:text))","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.id","path":"Observation.referenceRange.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.extension","path":"Observation.referenceRange.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.modifierExtension","path":"Observation.referenceRange.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Observation.referenceRange.low","path":"Observation.referenceRange.low","short":"Low Range, if relevant","definition":"The value of the low bound of the reference range. The low bound of the reference range endpoint is inclusive of the value (e.g. reference range is >=5 - <=9). If the low bound is omitted, it is assumed to be meaningless (e.g. reference range is <=2.3).","min":0,"max":"1","base":{"path":"Observation.referenceRange.low","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"condition":["obs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.high","path":"Observation.referenceRange.high","short":"High Range, if relevant","definition":"The value of the high bound of the reference range. The high bound of the reference range endpoint is inclusive of the value (e.g. reference range is >=5 - <=9). If the high bound is omitted, it is assumed to be meaningless (e.g. reference range is >= 2.3).","min":0,"max":"1","base":{"path":"Observation.referenceRange.high","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"condition":["obs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.type","path":"Observation.referenceRange.type","short":"Reference range qualifier","definition":"Codes to indicate the what part of the targeted reference population it applies to. For example, the normal or therapeutic range.","comment":"This SHOULD be populated if there is more than one range. If this element is not present then the normal range is assumed.","requirements":"Need to be able to say what kind of reference range this is - normal, recommended, therapeutic, etc., - for proper interpretation.","min":0,"max":"1","base":{"path":"Observation.referenceRange.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationRangeMeaning"}],"strength":"preferred","description":"Code for the meaning of a reference range.","valueSet":"http://hl7.org/fhir/ValueSet/referencerange-meaning"}},{"id":"Observation.referenceRange.appliesTo","path":"Observation.referenceRange.appliesTo","short":"Reference range population","definition":"Codes to indicate the target population this reference range applies to. For example, a reference range may be based on the normal population or a particular sex or race. Multiple `appliesTo` are interpreted as an \"AND\" of the target populations. For example, to represent a target population of African American females, both a code of female and a code for African American would be used.","comment":"This SHOULD be populated if there is more than one range. If this element is not present then the normal population is assumed.","requirements":"Need to be able to identify the target population for proper interpretation.","min":0,"max":"*","base":{"path":"Observation.referenceRange.appliesTo","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationRangeType"}],"strength":"example","description":"Codes identifying the population the reference range applies to.","valueSet":"http://hl7.org/fhir/ValueSet/referencerange-appliesto"}},{"id":"Observation.referenceRange.age","path":"Observation.referenceRange.age","short":"Applicable age range, if relevant","definition":"The age at which this reference range is applicable. This is a neonatal age (e.g. number of weeks at term) if the meaning says so.","requirements":"Some analytes vary greatly over age.","min":0,"max":"1","base":{"path":"Observation.referenceRange.age","min":0,"max":"1"},"type":[{"code":"Range"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.text","path":"Observation.referenceRange.text","short":"Text based reference range in an observation","definition":"Text based reference range in an observation which may be used when a quantitative range is not appropriate for an observation. An example would be a reference value of \"Negative\" or a list or table of \"normals\".","min":0,"max":"1","base":{"path":"Observation.referenceRange.text","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.hasMember","path":"Observation.hasMember","short":"Used when reporting vital signs panel components","definition":"Used when reporting vital signs panel components.","comment":"When using this element, an observation will typically have either a value or a set of related resources, although both may be present in some cases. For a discussion on the ways Observations can assembled in groups together, see [Notes](http://hl7.org/fhir/observation.html#obsgrouping) below. Note that a system may calculate results from [QuestionnaireResponse](http://hl7.org/fhir/questionnaireresponse.html) into a final score and represent the score as an Observation.","min":0,"max":"*","base":{"path":"Observation.hasMember","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse","http://hl7.org/fhir/StructureDefinition/MolecularSequence","http://hl7.org/fhir/StructureDefinition/vitalsigns"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.derivedFrom","path":"Observation.derivedFrom","short":"Related measurements the observation is made from","definition":"The target resource that represents a measurement from which this observation value is derived. For example, a calculated anion gap or a fetal measurement based on an ultrasound image.","comment":"All the reference choices that are listed in this element can represent clinical observations and other measurements that may be the source for a derived value. The most common reference will be another Observation. For a discussion on the ways Observations can assembled in groups together, see [Notes](http://hl7.org/fhir/observation.html#obsgrouping) below.","min":0,"max":"*","base":{"path":"Observation.derivedFrom","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/DocumentReference","http://hl7.org/fhir/StructureDefinition/ImagingStudy","http://hl7.org/fhir/StructureDefinition/Media","http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse","http://hl7.org/fhir/StructureDefinition/MolecularSequence","http://hl7.org/fhir/StructureDefinition/vitalsigns"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.component","path":"Observation.component","slicing":{"discriminator":[{"type":"pattern","path":"code"}],"ordered":false,"rules":"open"},"short":"Component observations","definition":"Used when reporting component observation such as systolic and diastolic blood pressure.","comment":"For a discussion on the ways Observations can be assembled in groups together see [Notes](http://hl7.org/fhir/observation.html#notes) below.","requirements":"Component observations share the same attributes in the Observation resource as the primary observation and are always treated a part of a single observation (they are not separable). However, the reference range for the primary observation value is not inherited by the component values and is required when appropriate for each component observation.","min":2,"max":"*","base":{"path":"Observation.component","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"vs-3","severity":"error","human":"If there is no a value a data absent reason must be present","expression":"value.exists() or dataAbsentReason.exists()","xpath":"f:*[starts-with(local-name(.), 'value')] or f:dataAbsentReason","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component.id","path":"Observation.component.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.component.extension","path":"Observation.component.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.component.modifierExtension","path":"Observation.component.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Observation.component.code","path":"Observation.component.code","short":"Type of component observation (code / type)","definition":"Describes what was observed. Sometimes this is called the observation \"code\".","comment":"*All* code-value and component.code-component.value pairs need to be taken into account to correctly understand the meaning of the observation.","requirements":"Knowing what kind of observation is being made is essential to understanding the observation.","min":1,"max":"1","base":{"path":"Observation.component.code","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"The vital sign codes from the base FHIR and US Core Vital Signs.","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-vital-signs"}},{"id":"Observation.component.value[x]","path":"Observation.component.value[x]","short":"Vital Sign Component Value","definition":"Vital Signs value are typically recorded using the Quantity data type. For supporting observations such as cuff size could use other datatypes such as CodeableConcept.","comment":"Used when observation has a set of component observations. An observation may have both a value (e.g. an Apgar score) and component observations (the observations from which the Apgar score was derived). If a value is present, the datatype for this element should be determined by Observation.code. A CodeableConcept with just a text would be used instead of a string if the field was usually coded, or if the type associated with the Observation.code defines a coded value. For additional guidance, see the [Notes section](http://hl7.org/fhir/observation.html#notes) below.","requirements":"9. SHALL contain exactly one [1..1] value with @xsi:type=\"PQ\" (CONF:7305).","min":0,"max":"1","base":{"path":"Observation.component.value[x]","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"Quantity"},{"code":"CodeableConcept"},{"code":"string"},{"code":"boolean"},{"code":"integer"},{"code":"Range"},{"code":"Ratio"},{"code":"SampledData"},{"code":"time"},{"code":"dateTime"},{"code":"Period"}],"condition":["vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"Common UCUM units for recording Vital Signs.","valueSet":"http://hl7.org/fhir/ValueSet/ucum-vitals-common|4.0.1"}},{"id":"Observation.component.dataAbsentReason","path":"Observation.component.dataAbsentReason","short":"Why the component result is missing","definition":"Provides a reason why the expected value in the element Observation.component.value[x] is missing.","comment":"\"Null\" or exceptional values can be represented two ways in FHIR Observations. One way is to simply include them in the value set and represent the exceptions in the value. For example, measurement values for a serology test could be \"detected\", \"not detected\", \"inconclusive\", or \"test not done\". \n\nThe alternate way is to use the value element for actual observations and use the explicit dataAbsentReason element to record exceptional values. For example, the dataAbsentReason code \"error\" could be used when the measurement was not completed. Because of these options, use-case agreements are required to interpret general observations for exceptional values.","requirements":"For many results it is necessary to handle exceptional values in measurements.","min":0,"max":"1","base":{"path":"Observation.component.dataAbsentReason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"condition":["obs-6","vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationValueAbsentReason"}],"strength":"extensible","description":"Codes specifying why the result (`Observation.value[x]`) is missing.","valueSet":"http://hl7.org/fhir/ValueSet/data-absent-reason"}},{"id":"Observation.component.interpretation","path":"Observation.component.interpretation","short":"High, low, normal, etc.","definition":"A categorical assessment of an observation value. For example, high, low, normal.","comment":"Historically used for laboratory results (known as 'abnormal flag' ), its use extends to other use cases where coded interpretations are relevant. Often reported as one or more simple compact codes this element is often placed adjacent to the result value in reports and flow sheets to signal the meaning/normalcy status of the result.","requirements":"For some results, particularly numeric results, an interpretation is necessary to fully understand the significance of a result.","alias":["Abnormal Flag"],"min":0,"max":"*","base":{"path":"Observation.component.interpretation","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationInterpretation"}],"strength":"extensible","description":"Codes identifying interpretations of observations.","valueSet":"http://hl7.org/fhir/ValueSet/observation-interpretation"}},{"id":"Observation.component.referenceRange","path":"Observation.component.referenceRange","short":"Provides guide for interpretation of component result","definition":"Guidance on how to interpret the value by comparison to a normal or recommended range.","comment":"Most observations only have one generic reference range. Systems MAY choose to restrict to only supplying the relevant reference range based on knowledge about the patient (e.g., specific to the patient's age, gender, weight and other factors), but this might not be possible or appropriate. Whenever more than one reference range is supplied, the differences between them SHOULD be provided in the reference range and/or age properties.","requirements":"Knowing what values are considered \"normal\" can help evaluate the significance of a particular result. Need to be able to provide multiple reference ranges for different contexts.","min":0,"max":"*","base":{"path":"Observation.component.referenceRange","min":0,"max":"*"},"contentReference":"http://hl7.org/fhir/StructureDefinition/Observation#Observation.referenceRange","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:systolic","path":"Observation.component","sliceName":"systolic","short":"Systolic Blood Pressure","definition":"Used when reporting component observation such as systolic and diastolic blood pressure.","comment":"For a discussion on the ways Observations can be assembled in groups together see [Notes](http://hl7.org/fhir/observation.html#notes) below.","requirements":"Component observations share the same attributes in the Observation resource as the primary observation and are always treated a part of a single observation (they are not separable). However, the reference range for the primary observation value is not inherited by the component values and is required when appropriate for each component observation.","min":1,"max":"1","base":{"path":"Observation.component","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"vs-3","severity":"error","human":"If there is no a value a data absent reason must be present","expression":"value.exists() or dataAbsentReason.exists()","xpath":"f:*[starts-with(local-name(.), 'value')] or f:dataAbsentReason","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:systolic.id","path":"Observation.component.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:systolic.extension","path":"Observation.component.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:systolic.modifierExtension","path":"Observation.component.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Observation.component:systolic.code","path":"Observation.component.code","short":"Systolic Blood Pressure Code","definition":"Describes what was observed. Sometimes this is called the observation \"code\".","comment":"*All* code-value and component.code-component.value pairs need to be taken into account to correctly understand the meaning of the observation.","requirements":"Knowing what kind of observation is being made is essential to understanding the observation.","min":1,"max":"1","base":{"path":"Observation.component.code","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"patternCodeableConcept":{"coding":[{"system":"http://loinc.org","code":"8480-6"}]},"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"The vital sign codes from the base FHIR and US Core Vital Signs.","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-vital-signs"}},{"id":"Observation.component:systolic.value[x]","path":"Observation.component.value[x]","short":"Vital Sign Component Value","definition":"Vital Signs value are typically recorded using the Quantity data type. For supporting observations such as cuff size could use other datatypes such as CodeableConcept.","comment":"Used when observation has a set of component observations. An observation may have both a value (e.g. an Apgar score) and component observations (the observations from which the Apgar score was derived). If a value is present, the datatype for this element should be determined by Observation.code. A CodeableConcept with just a text would be used instead of a string if the field was usually coded, or if the type associated with the Observation.code defines a coded value. For additional guidance, see the [Notes section](http://hl7.org/fhir/observation.html#notes) below.","requirements":"9. SHALL contain exactly one [1..1] value with @xsi:type=\"PQ\" (CONF:7305).","min":0,"max":"1","base":{"path":"Observation.component.value[x]","min":0,"max":"1"},"type":[{"code":"Quantity"}],"condition":["vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"Common UCUM units for recording Vital Signs.","valueSet":"http://hl7.org/fhir/ValueSet/ucum-vitals-common|4.0.1"}},{"id":"Observation.component:systolic.value[x].id","path":"Observation.component.value[x].id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:systolic.value[x].extension","path":"Observation.component.value[x].extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:systolic.value[x].value","path":"Observation.component.value[x].value","short":"Numerical value (with implicit precision)","definition":"The value of the measured amount. The value includes an implicit precision in the presentation of the value.","comment":"The implicit precision in the value should always be honored. Monetary values have their own rules for handling precision (refer to standard accounting text books).","requirements":"Precision is handled implicitly in almost all cases of measurement.","min":1,"max":"1","base":{"path":"Quantity.value","min":0,"max":"1"},"type":[{"code":"decimal"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:systolic.value[x].comparator","path":"Observation.component.value[x].comparator","short":"< | <= | >= | > - how to understand the value","definition":"How the value should be understood and represented - whether the actual value is greater or less than the stated value due to measurement issues; e.g. if the comparator is \"<\" , then the real value is < stated value.","requirements":"Need a framework for handling measures where the value is <5ug/L or >400mg/L due to the limitations of measuring methodology.","min":0,"max":"1","base":{"path":"Quantity.comparator","min":0,"max":"1"},"type":[{"code":"code"}],"meaningWhenMissing":"If there is no comparator, then there is no modification of the value","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because the comparator modifies the interpretation of the value significantly. If there is no comparator, then there is no modification of the value","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"QuantityComparator"}],"strength":"required","description":"How the Quantity should be understood and represented.","valueSet":"http://hl7.org/fhir/ValueSet/quantity-comparator|4.0.1"}},{"id":"Observation.component:systolic.value[x].unit","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable","valueBoolean":true}],"path":"Observation.component.value[x].unit","short":"Unit representation","definition":"A human-readable form of the unit.","requirements":"There are many representations for units of measure and in many contexts, particular representations are fixed and required. I.e. mcg for micrograms.","min":1,"max":"1","base":{"path":"Quantity.unit","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:systolic.value[x].system","path":"Observation.component.value[x].system","short":"System that defines coded unit form","definition":"The identification of the system that provides the coded form of the unit.","requirements":"Need to know the system that defines the coded form of the unit.","min":1,"max":"1","base":{"path":"Quantity.system","min":0,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"http://unitsofmeasure.org","condition":["qty-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:systolic.value[x].code","path":"Observation.component.value[x].code","short":"Coded form of the unit","definition":"A computer processable form of the unit in some unit representation system.","comment":"The preferred system is UCUM, but SNOMED CT can also be used (for customary units) or ISO 4217 for currency. The context of use may additionally require a code from a particular system.","requirements":"Need a computable form of the unit that is fixed across all forms. UCUM provides this for quantities, but SNOMED CT provides many units of interest.","min":1,"max":"1","base":{"path":"Quantity.code","min":0,"max":"1"},"type":[{"code":"code"}],"fixedCode":"mm[Hg]","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:systolic.dataAbsentReason","path":"Observation.component.dataAbsentReason","short":"Why the component result is missing","definition":"Provides a reason why the expected value in the element Observation.component.value[x] is missing.","comment":"\"Null\" or exceptional values can be represented two ways in FHIR Observations. One way is to simply include them in the value set and represent the exceptions in the value. For example, measurement values for a serology test could be \"detected\", \"not detected\", \"inconclusive\", or \"test not done\". \n\nThe alternate way is to use the value element for actual observations and use the explicit dataAbsentReason element to record exceptional values. For example, the dataAbsentReason code \"error\" could be used when the measurement was not completed. Because of these options, use-case agreements are required to interpret general observations for exceptional values.","requirements":"For many results it is necessary to handle exceptional values in measurements.","min":0,"max":"1","base":{"path":"Observation.component.dataAbsentReason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"condition":["obs-6","vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationValueAbsentReason"}],"strength":"extensible","description":"Codes specifying why the result (`Observation.value[x]`) is missing.","valueSet":"http://hl7.org/fhir/ValueSet/data-absent-reason"}},{"id":"Observation.component:systolic.interpretation","path":"Observation.component.interpretation","short":"High, low, normal, etc.","definition":"A categorical assessment of an observation value. For example, high, low, normal.","comment":"Historically used for laboratory results (known as 'abnormal flag' ), its use extends to other use cases where coded interpretations are relevant. Often reported as one or more simple compact codes this element is often placed adjacent to the result value in reports and flow sheets to signal the meaning/normalcy status of the result.","requirements":"For some results, particularly numeric results, an interpretation is necessary to fully understand the significance of a result.","alias":["Abnormal Flag"],"min":0,"max":"*","base":{"path":"Observation.component.interpretation","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationInterpretation"}],"strength":"extensible","description":"Codes identifying interpretations of observations.","valueSet":"http://hl7.org/fhir/ValueSet/observation-interpretation"}},{"id":"Observation.component:systolic.referenceRange","path":"Observation.component.referenceRange","short":"Provides guide for interpretation of component result","definition":"Guidance on how to interpret the value by comparison to a normal or recommended range.","comment":"Most observations only have one generic reference range. Systems MAY choose to restrict to only supplying the relevant reference range based on knowledge about the patient (e.g., specific to the patient's age, gender, weight and other factors), but this might not be possible or appropriate. Whenever more than one reference range is supplied, the differences between them SHOULD be provided in the reference range and/or age properties.","requirements":"Knowing what values are considered \"normal\" can help evaluate the significance of a particular result. Need to be able to provide multiple reference ranges for different contexts.","min":0,"max":"*","base":{"path":"Observation.component.referenceRange","min":0,"max":"*"},"contentReference":"http://hl7.org/fhir/StructureDefinition/Observation#Observation.referenceRange","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:diastolic","path":"Observation.component","sliceName":"diastolic","short":"Diastolic Blood Pressure","definition":"Used when reporting component observation such as systolic and diastolic blood pressure.","comment":"For a discussion on the ways Observations can be assembled in groups together see [Notes](http://hl7.org/fhir/observation.html#notes) below.","requirements":"Component observations share the same attributes in the Observation resource as the primary observation and are always treated a part of a single observation (they are not separable). However, the reference range for the primary observation value is not inherited by the component values and is required when appropriate for each component observation.","min":1,"max":"1","base":{"path":"Observation.component","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"vs-3","severity":"error","human":"If there is no a value a data absent reason must be present","expression":"value.exists() or dataAbsentReason.exists()","xpath":"f:*[starts-with(local-name(.), 'value')] or f:dataAbsentReason","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:diastolic.id","path":"Observation.component.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:diastolic.extension","path":"Observation.component.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:diastolic.modifierExtension","path":"Observation.component.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Observation.component:diastolic.code","path":"Observation.component.code","short":"Diastolic Blood Pressure Code","definition":"Describes what was observed. Sometimes this is called the observation \"code\".","comment":"*All* code-value and component.code-component.value pairs need to be taken into account to correctly understand the meaning of the observation.","requirements":"Knowing what kind of observation is being made is essential to understanding the observation.","min":1,"max":"1","base":{"path":"Observation.component.code","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"patternCodeableConcept":{"coding":[{"system":"http://loinc.org","code":"8462-4"}]},"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"The vital sign codes from the base FHIR and US Core Vital Signs.","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-vital-signs"}},{"id":"Observation.component:diastolic.value[x]","path":"Observation.component.value[x]","short":"Vital Sign Component Value","definition":"Vital Signs value are typically recorded using the Quantity data type. For supporting observations such as cuff size could use other datatypes such as CodeableConcept.","comment":"Used when observation has a set of component observations. An observation may have both a value (e.g. an Apgar score) and component observations (the observations from which the Apgar score was derived). If a value is present, the datatype for this element should be determined by Observation.code. A CodeableConcept with just a text would be used instead of a string if the field was usually coded, or if the type associated with the Observation.code defines a coded value. For additional guidance, see the [Notes section](http://hl7.org/fhir/observation.html#notes) below.","requirements":"9. SHALL contain exactly one [1..1] value with @xsi:type=\"PQ\" (CONF:7305).","min":0,"max":"1","base":{"path":"Observation.component.value[x]","min":0,"max":"1"},"type":[{"code":"Quantity"}],"condition":["vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"Common UCUM units for recording Vital Signs.","valueSet":"http://hl7.org/fhir/ValueSet/ucum-vitals-common|4.0.1"}},{"id":"Observation.component:diastolic.value[x].id","path":"Observation.component.value[x].id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:diastolic.value[x].extension","path":"Observation.component.value[x].extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:diastolic.value[x].value","path":"Observation.component.value[x].value","short":"Numerical value (with implicit precision)","definition":"The value of the measured amount. The value includes an implicit precision in the presentation of the value.","comment":"The implicit precision in the value should always be honored. Monetary values have their own rules for handling precision (refer to standard accounting text books).","requirements":"Precision is handled implicitly in almost all cases of measurement.","min":1,"max":"1","base":{"path":"Quantity.value","min":0,"max":"1"},"type":[{"code":"decimal"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:diastolic.value[x].comparator","path":"Observation.component.value[x].comparator","short":"< | <= | >= | > - how to understand the value","definition":"How the value should be understood and represented - whether the actual value is greater or less than the stated value due to measurement issues; e.g. if the comparator is \"<\" , then the real value is < stated value.","requirements":"Need a framework for handling measures where the value is <5ug/L or >400mg/L due to the limitations of measuring methodology.","min":0,"max":"1","base":{"path":"Quantity.comparator","min":0,"max":"1"},"type":[{"code":"code"}],"meaningWhenMissing":"If there is no comparator, then there is no modification of the value","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because the comparator modifies the interpretation of the value significantly. If there is no comparator, then there is no modification of the value","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"QuantityComparator"}],"strength":"required","description":"How the Quantity should be understood and represented.","valueSet":"http://hl7.org/fhir/ValueSet/quantity-comparator|4.0.1"}},{"id":"Observation.component:diastolic.value[x].unit","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable","valueBoolean":true}],"path":"Observation.component.value[x].unit","short":"Unit representation","definition":"A human-readable form of the unit.","requirements":"There are many representations for units of measure and in many contexts, particular representations are fixed and required. I.e. mcg for micrograms.","min":1,"max":"1","base":{"path":"Quantity.unit","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:diastolic.value[x].system","path":"Observation.component.value[x].system","short":"System that defines coded unit form","definition":"The identification of the system that provides the coded form of the unit.","requirements":"Need to know the system that defines the coded form of the unit.","min":1,"max":"1","base":{"path":"Quantity.system","min":0,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"http://unitsofmeasure.org","condition":["qty-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:diastolic.value[x].code","path":"Observation.component.value[x].code","short":"Coded form of the unit","definition":"A computer processable form of the unit in some unit representation system.","comment":"The preferred system is UCUM, but SNOMED CT can also be used (for customary units) or ISO 4217 for currency. The context of use may additionally require a code from a particular system.","requirements":"Need a computable form of the unit that is fixed across all forms. UCUM provides this for quantities, but SNOMED CT provides many units of interest.","min":1,"max":"1","base":{"path":"Quantity.code","min":0,"max":"1"},"type":[{"code":"code"}],"fixedCode":"mm[Hg]","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:diastolic.dataAbsentReason","path":"Observation.component.dataAbsentReason","short":"Why the component result is missing","definition":"Provides a reason why the expected value in the element Observation.component.value[x] is missing.","comment":"\"Null\" or exceptional values can be represented two ways in FHIR Observations. One way is to simply include them in the value set and represent the exceptions in the value. For example, measurement values for a serology test could be \"detected\", \"not detected\", \"inconclusive\", or \"test not done\". \n\nThe alternate way is to use the value element for actual observations and use the explicit dataAbsentReason element to record exceptional values. For example, the dataAbsentReason code \"error\" could be used when the measurement was not completed. Because of these options, use-case agreements are required to interpret general observations for exceptional values.","requirements":"For many results it is necessary to handle exceptional values in measurements.","min":0,"max":"1","base":{"path":"Observation.component.dataAbsentReason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"condition":["obs-6","vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationValueAbsentReason"}],"strength":"extensible","description":"Codes specifying why the result (`Observation.value[x]`) is missing.","valueSet":"http://hl7.org/fhir/ValueSet/data-absent-reason"}},{"id":"Observation.component:diastolic.interpretation","path":"Observation.component.interpretation","short":"High, low, normal, etc.","definition":"A categorical assessment of an observation value. For example, high, low, normal.","comment":"Historically used for laboratory results (known as 'abnormal flag' ), its use extends to other use cases where coded interpretations are relevant. Often reported as one or more simple compact codes this element is often placed adjacent to the result value in reports and flow sheets to signal the meaning/normalcy status of the result.","requirements":"For some results, particularly numeric results, an interpretation is necessary to fully understand the significance of a result.","alias":["Abnormal Flag"],"min":0,"max":"*","base":{"path":"Observation.component.interpretation","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationInterpretation"}],"strength":"extensible","description":"Codes identifying interpretations of observations.","valueSet":"http://hl7.org/fhir/ValueSet/observation-interpretation"}},{"id":"Observation.component:diastolic.referenceRange","path":"Observation.component.referenceRange","short":"Provides guide for interpretation of component result","definition":"Guidance on how to interpret the value by comparison to a normal or recommended range.","comment":"Most observations only have one generic reference range. Systems MAY choose to restrict to only supplying the relevant reference range based on knowledge about the patient (e.g., specific to the patient's age, gender, weight and other factors), but this might not be possible or appropriate. Whenever more than one reference range is supplied, the differences between them SHOULD be provided in the reference range and/or age properties.","requirements":"Knowing what values are considered \"normal\" can help evaluate the significance of a particular result. Need to be able to provide multiple reference ranges for different contexts.","min":0,"max":"*","base":{"path":"Observation.component.referenceRange","min":0,"max":"*"},"contentReference":"http://hl7.org/fhir/StructureDefinition/Observation#Observation.referenceRange","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}},{"resourceType":"StructureDefinition","id":"us-core-medicationrequest","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-medicationrequest","version":"5.0.1","name":"USCoreMedicationRequestProfile","title":"US Core MedicationRequest Profile","status":"active","experimental":false,"date":"2020-06-26","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"The US Core Medication Request Profile is based upon the core FHIR MedicationRequest Resource and meets the U.S. Core Data for Interoperability (USCDI) v2 'Medications' requirements. The MedicationRequest resource can be used to record a patient’s medication prescription or order. This profile sets minimum expectations for the MedicationRequest resource to record, search, and fetch a patient's medication to promote interoperability and adoption through common implementation. It identifies which core elements, extensions, vocabularies and value sets **SHALL** be present in the resource when using this profile. It provides the floor for standards development for specific uses cases.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"resource","abstract":false,"type":"MedicationRequest","baseDefinition":"http://hl7.org/fhir/StructureDefinition/MedicationRequest","derivation":"constraint","snapshot":{"element":[{"id":"MedicationRequest","path":"MedicationRequest","short":"Ordering of medication for patient or group","definition":"\\-","comment":"\\-","alias":["Prescription","Order"],"min":0,"max":"*","base":{"path":"MedicationRequest","min":0,"max":"*"},"constraint":[{"key":"dom-2","severity":"error","human":"If the resource is contained in another resource, it SHALL NOT contain nested Resources","expression":"contained.contained.empty()","xpath":"not(parent::f:contained and f:contained)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-3","severity":"error","human":"If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource","expression":"contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()","xpath":"not(exists(for $id in f:contained/*/f:id/@value return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-4","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated","expression":"contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()","xpath":"not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-5","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a security label","expression":"contained.meta.security.empty()","xpath":"not(exists(f:contained/*/f:meta/f:security))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice","valueBoolean":true},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice-explanation","valueMarkdown":"When a resource has no narrative, only systems that fully understand the data can display the resource to a human safely. Including a human readable representation in the resource makes for a much more robust eco-system and cheaper handling of resources by intermediary systems. Some ecosystems restrict distribution of resources to only those systems that do fully understand the resources, and as a consequence implementers may believe that the narrative is superfluous. However experience shows that such eco-systems often open up to new participants over time."}],"key":"dom-6","severity":"warning","human":"A resource should have narrative for robust management","expression":"text.`div`.exists()","xpath":"exists(f:text/h:div)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"}],"mustSupport":false,"isModifier":false,"isSummary":false},{"id":"MedicationRequest.id","path":"MedicationRequest.id","short":"Logical id of this artifact","definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.","comment":"The only time that a resource does not have an id is when it is being submitted to the server using a create operation.","min":0,"max":"1","base":{"path":"Resource.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.meta","path":"MedicationRequest.meta","short":"Metadata about the resource","definition":"The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.","min":0,"max":"1","base":{"path":"Resource.meta","min":0,"max":"1"},"type":[{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.implicitRules","path":"MedicationRequest.implicitRules","short":"A set of rules under which this content was created","definition":"A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.","comment":"Asserting this rule set restricts the content to be only understood by a limited set of trading partners. This inherently limits the usefulness of the data in the long term. However, the existing health eco-system is highly fractured, and not yet ready to define, collect, and exchange data in a generally computable sense. Wherever possible, implementers and/or specification writers should avoid using this element. Often, when used, the URL is a reference to an implementation guide that defines these special rules as part of it's narrative along with other profiles, value sets, etc.","min":0,"max":"1","base":{"path":"Resource.implicitRules","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because the implicit rules may provide additional knowledge about the resource that modifies it's meaning or interpretation","isSummary":true},{"id":"MedicationRequest.language","path":"MedicationRequest.language","short":"Language of the resource content","definition":"The base language in which the resource is written.","comment":"Language is provided to support indexing and accessibility (typically, services such as text to speech use the language tag). The html language tag in the narrative applies to the narrative. The language tag on the resource may be used to specify the language of other presentations generated from the data in the resource. Not all the content has to be in the base language. The Resource.language should not be assumed to apply to the narrative automatically. If a language is specified, it should it also be specified on the div element in the html (see rules in HTML5 for information about the relationship between xml:lang and the html lang attribute).","min":0,"max":"1","base":{"path":"Resource.language","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/all-languages"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Language"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"preferred","description":"A human language.","valueSet":"http://hl7.org/fhir/ValueSet/languages"}},{"id":"MedicationRequest.text","path":"MedicationRequest.text","short":"Text summary of the resource, for human interpretation","definition":"A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it \"clinically safe\" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety.","comment":"Contained resources do not have narrative. Resources that are not contained SHOULD have a narrative. In some cases, a resource may only have text with little or no additional discrete data (as long as all minOccurs=1 elements are satisfied). This may be necessary for data from legacy systems where information is captured as a \"text blob\" or where text is additionally entered raw or narrated and encoded information is added later.","alias":["narrative","html","xhtml","display"],"min":0,"max":"1","base":{"path":"DomainResource.text","min":0,"max":"1"},"type":[{"code":"Narrative"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.contained","path":"MedicationRequest.contained","short":"Contained, inline Resources","definition":"These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope.","comment":"This should never be done when the content can be identified properly, as once identification is lost, it is extremely difficult (and context dependent) to restore it again. Contained resources may have profiles and tags In their meta elements, but SHALL NOT have security labels.","alias":["inline resources","anonymous resources","contained resources"],"min":0,"max":"*","base":{"path":"DomainResource.contained","min":0,"max":"*"},"type":[{"code":"Resource"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.extension","path":"MedicationRequest.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the resource. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.modifierExtension","path":"MedicationRequest.modifierExtension","short":"Extensions that cannot be ignored","definition":"May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the resource that contains them","isSummary":false},{"id":"MedicationRequest.identifier","path":"MedicationRequest.identifier","short":"External ids for this request","definition":"Identifiers associated with this medication request that are defined by business processes and/or used to refer to it when a direct URL reference to the resource itself is not appropriate. They are business identifiers assigned to this resource by the performer or other systems and remain constant as the resource is updated and propagates from server to server.","comment":"This is a business identifier, not a resource identifier.","min":0,"max":"*","base":{"path":"MedicationRequest.identifier","min":0,"max":"*"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.status","path":"MedicationRequest.status","short":"active | on-hold | cancelled | completed | entered-in-error | stopped | draft | unknown","definition":"A code specifying the current state of the order. Generally, this will be active or completed state.","comment":"This element is labeled as a modifier because the status contains codes that mark the resource as not currently valid.","min":1,"max":"1","base":{"path":"MedicationRequest.status","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":true,"isModifierReason":"This element is labeled as a modifier because it is a status element that contains status entered-in-error which means that the resource should not be treated as valid","isSummary":true,"binding":{"strength":"required","description":"A code specifying the state of the prescribing event. Describes the lifecycle of the prescription.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-status"}},{"id":"MedicationRequest.statusReason","path":"MedicationRequest.statusReason","short":"Reason for current status","definition":"Captures the reason for the current state of the MedicationRequest.","comment":"This is generally only used for \"exception\" statuses such as \"suspended\" or \"cancelled\". The reason why the MedicationRequest was created at all is captured in reasonCode, not here.","min":0,"max":"1","base":{"path":"MedicationRequest.statusReason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestStatusReason"}],"strength":"example","description":"Identifies the reasons for a given status.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-status-reason"}},{"id":"MedicationRequest.intent","path":"MedicationRequest.intent","short":"proposal | plan | order | original-order | reflex-order | filler-order | instance-order | option","definition":"Whether the request is a proposal, plan, or an original order.","comment":"It is expected that the type of requester will be restricted for different stages of a MedicationRequest. For example, Proposals can be created by a patient, relatedPerson, Practitioner or Device. Plans can be created by Practitioners, Patients, RelatedPersons and Devices. Original orders can be created by a Practitioner only.\r\rAn instance-order is an instantiation of a request or order and may be used to populate Medication Administration Record.\r\rThis element is labeled as a modifier because the intent alters when and how the resource is actually applicable.","min":1,"max":"1","base":{"path":"MedicationRequest.intent","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":true,"isModifierReason":"This element changes the interpretation of all descriptive attributes. For example \"the time the request is recommended to occur\" vs. \"the time the request is authorized to occur\" or \"who is recommended to perform the request\" vs. \"who is authorized to perform the request","isSummary":true,"binding":{"strength":"required","description":"The kind of medication order.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-intent"}},{"id":"MedicationRequest.category","path":"MedicationRequest.category","slicing":{"discriminator":[{"type":"pattern","path":"$this"}],"rules":"open"},"short":"Type of medication usage","definition":"Indicates the type of medication request (for example, where the medication is expected to be consumed or administered (i.e. inpatient or outpatient)).","comment":"The category can be used to include where the medication is expected to be consumed or other types of requests.","min":0,"max":"*","base":{"path":"MedicationRequest.category","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestCategory"}],"strength":"example","description":"A coded concept identifying the category of medication request. For example, where the medication is to be consumed or administered, or the type of medication treatment.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-category"}},{"id":"MedicationRequest.category:us-core","path":"MedicationRequest.category","sliceName":"us-core","short":"Type of medication usage","definition":"Indicates the type of medication request (for example, where the medication is expected to be consumed or administered (i.e. inpatient or outpatient)).","comment":"The category can be used to include where the medication is expected to be consumed or other types of requests.","min":0,"max":"*","base":{"path":"MedicationRequest.category","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"strength":"required","description":"The type of medication order.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-category"}},{"id":"MedicationRequest.priority","path":"MedicationRequest.priority","short":"routine | urgent | asap | stat","definition":"Indicates how quickly the Medication Request should be addressed with respect to other requests.","min":0,"max":"1","base":{"path":"MedicationRequest.priority","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestPriority"}],"strength":"required","description":"Identifies the level of importance to be assigned to actioning the request.","valueSet":"http://hl7.org/fhir/ValueSet/request-priority|4.0.1"}},{"id":"MedicationRequest.doNotPerform","path":"MedicationRequest.doNotPerform","short":"True if request is prohibiting action","definition":"If true indicates that the provider is asking for the medication request not to occur.","comment":"If do not perform is not specified, the request is a positive request e.g. \"do perform\".","min":0,"max":"1","base":{"path":"MedicationRequest.doNotPerform","min":0,"max":"1"},"type":[{"code":"boolean"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because this element negates the request to occur (ie, this is a request for the medication not to be ordered or prescribed, etc)","isSummary":true},{"id":"MedicationRequest.reported[x]","path":"MedicationRequest.reported[x]","short":"Reported rather than primary record","definition":"Indicates if this record was captured as a secondary 'reported' record rather than as an original primary source-of-truth record. It may also indicate the source of the report.","min":0,"max":"1","base":{"path":"MedicationRequest.reported[x]","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"boolean"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner","http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization","http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient","http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole","http://hl7.org/fhir/us/core/StructureDefinition/us-core-relatedperson"],"_targetProfile":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]}]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"MedicationRequest.medication[x]","path":"MedicationRequest.medication[x]","short":"Medication to be taken","definition":"Identifies the medication being requested. This is a link to a resource that represents the medication which may be the details of the medication or simply an attribute carrying a code that identifies the medication from a known list of medications.","comment":"If only a code is specified, then it needs to be a code for a specific product. If more information is required, then the use of the Medication resource is recommended. For example, if you require form or lot number or if the medication is compounded or extemporaneously prepared, then you must reference the Medication resource.","min":1,"max":"1","base":{"path":"MedicationRequest.medication[x]","min":1,"max":"1"},"type":[{"code":"CodeableConcept"},{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-medication"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","valueSet":"http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1010.4"}},{"id":"MedicationRequest.subject","path":"MedicationRequest.subject","short":"Who or group medication request is for","definition":"A link to a resource representing the person or set of individuals to whom the medication will be given.","comment":"The subject on a medication request is mandatory. For the secondary use case where the actual subject is not provided, there still must be an anonymized subject specified.","min":1,"max":"1","base":{"path":"MedicationRequest.subject","min":1,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"MedicationRequest.encounter","path":"MedicationRequest.encounter","short":"Encounter created as part of encounter/admission/stay","definition":"The Encounter during which this [x] was created or to which the creation of this record is tightly associated.","comment":"This will typically be the encounter the event occurred within, but some activities may be initiated prior to or after the official completion of an encounter but still be tied to the context of the encounter.\" If there is a need to link to episodes of care they will be handled with an extension.","min":0,"max":"1","base":{"path":"MedicationRequest.encounter","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-encounter"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"MedicationRequest.supportingInformation","path":"MedicationRequest.supportingInformation","short":"Information to support ordering of the medication","definition":"Include additional information (for example, patient height and weight) that supports the ordering of the medication.","min":0,"max":"*","base":{"path":"MedicationRequest.supportingInformation","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Resource"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.authoredOn","path":"MedicationRequest.authoredOn","short":"When request was initially authored","definition":"The date (and perhaps time) when the prescription was initially written or authored on.","min":0,"max":"1","base":{"path":"MedicationRequest.authoredOn","min":0,"max":"1"},"type":[{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"MedicationRequest.requester","path":"MedicationRequest.requester","short":"Who/What requested the Request","definition":"The individual, organization, or device that initiated the request and has responsibility for its activation.","min":1,"max":"1","base":{"path":"MedicationRequest.requester","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner","http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient","http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization","http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole","http://hl7.org/fhir/us/core/StructureDefinition/us-core-relatedperson","http://hl7.org/fhir/StructureDefinition/Device"],"_targetProfile":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]}]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"MedicationRequest.performer","path":"MedicationRequest.performer","short":"Intended performer of administration","definition":"The specified desired performer of the medication treatment (e.g. the performer of the medication administration).","min":0,"max":"1","base":{"path":"MedicationRequest.performer","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Practitioner","http://hl7.org/fhir/StructureDefinition/PractitionerRole","http://hl7.org/fhir/StructureDefinition/Organization","http://hl7.org/fhir/StructureDefinition/Patient","http://hl7.org/fhir/StructureDefinition/Device","http://hl7.org/fhir/StructureDefinition/RelatedPerson","http://hl7.org/fhir/StructureDefinition/CareTeam"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.performerType","path":"MedicationRequest.performerType","short":"Desired kind of performer of the medication administration","definition":"Indicates the type of performer of the administration of the medication.","comment":"If specified without indicating a performer, this indicates that the performer must be of the specified type. If specified with a performer then it indicates the requirements of the performer if the designated performer is not available.","min":0,"max":"1","base":{"path":"MedicationRequest.performerType","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestPerformerType"}],"strength":"example","description":"Identifies the type of individual that is desired to administer the medication.","valueSet":"http://hl7.org/fhir/ValueSet/performer-role"}},{"id":"MedicationRequest.recorder","path":"MedicationRequest.recorder","short":"Person who entered the request","definition":"The person who entered the order on behalf of another individual for example in the case of a verbal or a telephone order.","min":0,"max":"1","base":{"path":"MedicationRequest.recorder","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Practitioner","http://hl7.org/fhir/StructureDefinition/PractitionerRole"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.reasonCode","path":"MedicationRequest.reasonCode","short":"Reason or indication for ordering or not ordering the medication","definition":"The reason or the indication for ordering or not ordering the medication.","comment":"This could be a diagnosis code. If a full condition record exists or additional detail is needed, use reasonReference.","min":0,"max":"*","base":{"path":"MedicationRequest.reasonCode","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestReason"}],"strength":"example","description":"A coded concept indicating why the medication was ordered.","valueSet":"http://hl7.org/fhir/ValueSet/condition-code"}},{"id":"MedicationRequest.reasonReference","path":"MedicationRequest.reasonReference","short":"Condition or observation that supports why the prescription is being written","definition":"Condition or observation that supports why the medication was ordered.","comment":"This is a reference to a condition or observation that is the reason for the medication order. If only a code exists, use reasonCode.","min":0,"max":"*","base":{"path":"MedicationRequest.reasonReference","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Condition","http://hl7.org/fhir/StructureDefinition/Observation"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.instantiatesCanonical","path":"MedicationRequest.instantiatesCanonical","short":"Instantiates FHIR protocol or definition","definition":"The URL pointing to a protocol, guideline, orderset, or other definition that is adhered to in whole or in part by this MedicationRequest.","min":0,"max":"*","base":{"path":"MedicationRequest.instantiatesCanonical","min":0,"max":"*"},"type":[{"code":"canonical"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.instantiatesUri","path":"MedicationRequest.instantiatesUri","short":"Instantiates external protocol or definition","definition":"The URL pointing to an externally maintained protocol, guideline, orderset or other definition that is adhered to in whole or in part by this MedicationRequest.","min":0,"max":"*","base":{"path":"MedicationRequest.instantiatesUri","min":0,"max":"*"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.basedOn","path":"MedicationRequest.basedOn","short":"What request fulfills","definition":"A plan or request that is fulfilled in whole or in part by this medication request.","min":0,"max":"*","base":{"path":"MedicationRequest.basedOn","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/CarePlan","http://hl7.org/fhir/StructureDefinition/MedicationRequest","http://hl7.org/fhir/StructureDefinition/ServiceRequest","http://hl7.org/fhir/StructureDefinition/ImmunizationRecommendation"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.groupIdentifier","path":"MedicationRequest.groupIdentifier","short":"Composite request this is part of","definition":"A shared identifier common to all requests that were authorized more or less simultaneously by a single author, representing the identifier of the requisition or prescription.","requirements":"Requests are linked either by a \"basedOn\" relationship (i.e. one request is fulfilling another) or by having a common requisition. Requests that are part of the same requisition are generally treated independently from the perspective of changing their state or maintaining them after initial creation.","min":0,"max":"1","base":{"path":"MedicationRequest.groupIdentifier","min":0,"max":"1"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.courseOfTherapyType","path":"MedicationRequest.courseOfTherapyType","short":"Overall pattern of medication administration","definition":"The description of the overall patte3rn of the administration of the medication to the patient.","comment":"This attribute should not be confused with the protocol of the medication.","min":0,"max":"1","base":{"path":"MedicationRequest.courseOfTherapyType","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestCourseOfTherapy"}],"strength":"example","description":"Identifies the overall pattern of medication administratio.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-course-of-therapy"}},{"id":"MedicationRequest.insurance","path":"MedicationRequest.insurance","short":"Associated insurance coverage","definition":"Insurance plans, coverage extensions, pre-authorizations and/or pre-determinations that may be required for delivering the requested service.","min":0,"max":"*","base":{"path":"MedicationRequest.insurance","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Coverage","http://hl7.org/fhir/StructureDefinition/ClaimResponse"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.note","path":"MedicationRequest.note","short":"Information about the prescription","definition":"Extra information about the prescription that could not be conveyed by the other attributes.","min":0,"max":"*","base":{"path":"MedicationRequest.note","min":0,"max":"*"},"type":[{"code":"Annotation"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction","path":"MedicationRequest.dosageInstruction","short":"How the medication should be taken","definition":"Indicates how the medication is to be used by the patient.","comment":"There are examples where a medication request may include the option of an oral dose or an Intravenous or Intramuscular dose. For example, \"Ondansetron 8mg orally or IV twice a day as needed for nausea\" or \"Compazine® (prochlorperazine) 5-10mg PO or 25mg PR bid prn nausea or vomiting\". In these cases, two medication requests would be created that could be grouped together. The decision on which dose and route of administration to use is based on the patient's condition at the time the dose is needed.","min":0,"max":"*","base":{"path":"MedicationRequest.dosageInstruction","min":0,"max":"*"},"type":[{"code":"Dosage"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction.id","path":"MedicationRequest.dosageInstruction.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction.extension","path":"MedicationRequest.dosageInstruction.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction.modifierExtension","path":"MedicationRequest.dosageInstruction.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"MedicationRequest.dosageInstruction.sequence","path":"MedicationRequest.dosageInstruction.sequence","short":"The order of the dosage instructions","definition":"Indicates the order in which the dosage instructions should be applied or interpreted.","requirements":"If the sequence number of multiple Dosages is the same, then it is implied that the instructions are to be treated as concurrent. If the sequence number is different, then the Dosages are intended to be sequential.","min":0,"max":"1","base":{"path":"Dosage.sequence","min":0,"max":"1"},"type":[{"code":"integer"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.text","path":"MedicationRequest.dosageInstruction.text","short":"Free text dosage instructions e.g. SIG","definition":"Free text dosage instructions e.g. SIG.","requirements":"Free text dosage instructions can be used for cases where the instructions are too complex to code. The content of this attribute does not include the name or description of the medication. When coded instructions are present, the free text instructions may still be present for display to humans taking or administering the medication. It is expected that the text instructions will always be populated. If the dosage.timing attribute is also populated, then the dosage.text should reflect the same information as the timing. Additional information about administration or preparation of the medication should be included as text.","min":0,"max":"1","base":{"path":"Dosage.text","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.additionalInstruction","path":"MedicationRequest.dosageInstruction.additionalInstruction","short":"Supplemental instruction or warnings to the patient - e.g. \"with meals\", \"may cause drowsiness\"","definition":"Supplemental instructions to the patient on how to take the medication (e.g. \"with meals\" or\"take half to one hour before food\") or warnings for the patient about the medication (e.g. \"may cause drowsiness\" or \"avoid exposure of skin to direct sunlight or sunlamps\").","comment":"Information about administration or preparation of the medication (e.g. \"infuse as rapidly as possibly via intraperitoneal port\" or \"immediately following drug x\") should be populated in dosage.text.","requirements":"Additional instruction is intended to be coded, but where no code exists, the element could include text. For example, \"Swallow with plenty of water\" which might or might not be coded.","min":0,"max":"*","base":{"path":"Dosage.additionalInstruction","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AdditionalInstruction"}],"strength":"example","description":"A coded concept identifying additional instructions such as \"take with water\" or \"avoid operating heavy machinery\".","valueSet":"http://hl7.org/fhir/ValueSet/additional-instruction-codes"}},{"id":"MedicationRequest.dosageInstruction.patientInstruction","path":"MedicationRequest.dosageInstruction.patientInstruction","short":"Patient or consumer oriented instructions","definition":"Instructions in terms that are understood by the patient or consumer.","min":0,"max":"1","base":{"path":"Dosage.patientInstruction","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.timing","path":"MedicationRequest.dosageInstruction.timing","short":"When medication should be administered","definition":"When medication should be administered.","comment":"This attribute might not always be populated while the Dosage.text is expected to be populated. If both are populated, then the Dosage.text should reflect the content of the Dosage.timing.","requirements":"The timing schedule for giving the medication to the patient. This data type allows many different expressions. For example: \"Every 8 hours\"; \"Three times a day\"; \"1/2 an hour before breakfast for 10 days from 23-Dec 2011:\"; \"15 Oct 2013, 17 Oct 2013 and 1 Nov 2013\". Sometimes, a rate can imply duration when expressed as total volume / duration (e.g. 500mL/2 hours implies a duration of 2 hours). However, when rate doesn't imply duration (e.g. 250mL/hour), then the timing.repeat.duration is needed to convey the infuse over time period.","min":0,"max":"1","base":{"path":"Dosage.timing","min":0,"max":"1"},"type":[{"code":"Timing"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.asNeeded[x]","path":"MedicationRequest.dosageInstruction.asNeeded[x]","short":"Take \"as needed\" (for x)","definition":"Indicates whether the Medication is only taken when needed within a specific dosing schedule (Boolean option), or it indicates the precondition for taking the Medication (CodeableConcept).","comment":"Can express \"as needed\" without a reason by setting the Boolean = True. In this case the CodeableConcept is not populated. Or you can express \"as needed\" with a reason by including the CodeableConcept. In this case the Boolean is assumed to be True. If you set the Boolean to False, then the dose is given according to the schedule and is not \"prn\" or \"as needed\".","min":0,"max":"1","base":{"path":"Dosage.asNeeded[x]","min":0,"max":"1"},"type":[{"code":"boolean"},{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationAsNeededReason"}],"strength":"example","description":"A coded concept identifying the precondition that should be met or evaluated prior to consuming or administering a medication dose. For example \"pain\", \"30 minutes prior to sexual intercourse\", \"on flare-up\" etc.","valueSet":"http://hl7.org/fhir/ValueSet/medication-as-needed-reason"}},{"id":"MedicationRequest.dosageInstruction.site","path":"MedicationRequest.dosageInstruction.site","short":"Body site to administer to","definition":"Body site to administer to.","comment":"If the use case requires attributes from the BodySite resource (e.g. to identify and track separately) then use the standard extension [bodySite](http://hl7.org/fhir/R4/extension-bodysite.html). May be a summary code, or a reference to a very precise definition of the location, or both.","requirements":"A coded specification of the anatomic site where the medication first enters the body.","min":0,"max":"1","base":{"path":"Dosage.site","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationAdministrationSite"}],"strength":"example","description":"A coded concept describing the site location the medicine enters into or onto the body.","valueSet":"http://hl7.org/fhir/ValueSet/approach-site-codes"}},{"id":"MedicationRequest.dosageInstruction.route","path":"MedicationRequest.dosageInstruction.route","short":"How drug should enter body","definition":"How drug should enter body.","requirements":"A code specifying the route or physiological path of administration of a therapeutic agent into or onto a patient's body.","min":0,"max":"1","base":{"path":"Dosage.route","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"RouteOfAdministration"}],"strength":"example","description":"A coded concept describing the route or physiological path of administration of a therapeutic agent into or onto the body of a subject.","valueSet":"http://hl7.org/fhir/ValueSet/route-codes"}},{"id":"MedicationRequest.dosageInstruction.method","path":"MedicationRequest.dosageInstruction.method","short":"Technique for administering medication","definition":"Technique for administering medication.","comment":"Terminologies used often pre-coordinate this term with the route and or form of administration.","requirements":"A coded value indicating the method by which the medication is introduced into or onto the body. Most commonly used for injections. For examples, Slow Push; Deep IV.","min":0,"max":"1","base":{"path":"Dosage.method","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationAdministrationMethod"}],"strength":"example","description":"A coded concept describing the technique by which the medicine is administered.","valueSet":"http://hl7.org/fhir/ValueSet/administration-method-codes"}},{"id":"MedicationRequest.dosageInstruction.doseAndRate","path":"MedicationRequest.dosageInstruction.doseAndRate","short":"Amount of medication administered","definition":"The amount of medication administered.","min":0,"max":"*","base":{"path":"Dosage.doseAndRate","min":0,"max":"*"},"type":[{"code":"Element"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.doseAndRate.id","path":"MedicationRequest.dosageInstruction.doseAndRate.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction.doseAndRate.extension","path":"MedicationRequest.dosageInstruction.doseAndRate.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction.doseAndRate.type","path":"MedicationRequest.dosageInstruction.doseAndRate.type","short":"The kind of dose or rate specified","definition":"The kind of dose or rate specified, for example, ordered or calculated.","requirements":"If the type is not populated, assume to be \"ordered\".","min":0,"max":"1","base":{"path":"Dosage.doseAndRate.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"DoseAndRateType"}],"strength":"example","description":"The kind of dose or rate specified.","valueSet":"http://hl7.org/fhir/ValueSet/dose-rate-type"}},{"id":"MedicationRequest.dosageInstruction.doseAndRate.dose[x]","path":"MedicationRequest.dosageInstruction.doseAndRate.dose[x]","short":"Amount of medication per dose","definition":"Amount of medication per dose.","comment":"Note that this specifies the quantity of the specified medication, not the quantity for each active ingredient(s). Each ingredient amount can be communicated in the Medication resource. For example, if one wants to communicate that a tablet was 375 mg, where the dose was one tablet, you can use the Medication resource to document that the tablet was comprised of 375 mg of drug XYZ. Alternatively if the dose was 375 mg, then you may only need to use the Medication resource to indicate this was a tablet. If the example were an IV such as dopamine and you wanted to communicate that 400mg of dopamine was mixed in 500 ml of some IV solution, then this would all be communicated in the Medication resource. If the administration is not intended to be instantaneous (rate is present or timing has a duration), this can be specified to convey the total amount to be administered over the period of time as indicated by the schedule e.g. 500 ml in dose, with timing used to convey that this should be done over 4 hours.","requirements":"The amount of therapeutic or other substance given at one administration event.","min":0,"max":"1","base":{"path":"Dosage.doseAndRate.dose[x]","min":0,"max":"1"},"type":[{"code":"Range"},{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.doseAndRate.rate[x]","path":"MedicationRequest.dosageInstruction.doseAndRate.rate[x]","short":"Amount of medication per unit of time","definition":"Amount of medication per unit of time.","comment":"It is possible to supply both a rate and a doseQuantity to provide full details about how the medication is to be administered and supplied. If the rate is intended to change over time, depending on local rules/regulations, each change should be captured as a new version of the MedicationRequest with an updated rate, or captured with a new MedicationRequest with the new rate.\r\rIt is possible to specify a rate over time (for example, 100 ml/hour) using either the rateRatio and rateQuantity. The rateQuantity approach requires systems to have the capability to parse UCUM grammer where ml/hour is included rather than a specific ratio where the time is specified as the denominator. Where a rate such as 500ml over 2 hours is specified, the use of rateRatio may be more semantically correct than specifying using a rateQuantity of 250 mg/hour.","requirements":"Identifies the speed with which the medication was or will be introduced into the patient. Typically the rate for an infusion e.g. 100 ml per 1 hour or 100 ml/hr. May also be expressed as a rate per unit of time e.g. 500 ml per 2 hours. Other examples: 200 mcg/min or 200 mcg/1 minute; 1 liter/8 hours. Sometimes, a rate can imply duration when expressed as total volume / duration (e.g. 500mL/2 hours implies a duration of 2 hours). However, when rate doesn't imply duration (e.g. 250mL/hour), then the timing.repeat.duration is needed to convey the infuse over time period.","min":0,"max":"1","base":{"path":"Dosage.doseAndRate.rate[x]","min":0,"max":"1"},"type":[{"code":"Ratio"},{"code":"Range"},{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.maxDosePerPeriod","path":"MedicationRequest.dosageInstruction.maxDosePerPeriod","short":"Upper limit on medication per unit of time","definition":"Upper limit on medication per unit of time.","comment":"This is intended for use as an adjunct to the dosage when there is an upper cap. For example \"2 tablets every 4 hours to a maximum of 8/day\".","requirements":"The maximum total quantity of a therapeutic substance that may be administered to a subject over the period of time. For example, 1000mg in 24 hours.","min":0,"max":"1","base":{"path":"Dosage.maxDosePerPeriod","min":0,"max":"1"},"type":[{"code":"Ratio"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.maxDosePerAdministration","path":"MedicationRequest.dosageInstruction.maxDosePerAdministration","short":"Upper limit on medication per administration","definition":"Upper limit on medication per administration.","comment":"This is intended for use as an adjunct to the dosage when there is an upper cap. For example, a body surface area related dose with a maximum amount, such as 1.5 mg/m2 (maximum 2 mg) IV over 5 – 10 minutes would have doseQuantity of 1.5 mg/m2 and maxDosePerAdministration of 2 mg.","requirements":"The maximum total quantity of a therapeutic substance that may be administered to a subject per administration.","min":0,"max":"1","base":{"path":"Dosage.maxDosePerAdministration","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.maxDosePerLifetime","path":"MedicationRequest.dosageInstruction.maxDosePerLifetime","short":"Upper limit on medication per lifetime of the patient","definition":"Upper limit on medication per lifetime of the patient.","requirements":"The maximum total quantity of a therapeutic substance that may be administered per lifetime of the subject.","min":0,"max":"1","base":{"path":"Dosage.maxDosePerLifetime","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dispenseRequest","path":"MedicationRequest.dispenseRequest","short":"Medication supply authorization","definition":"Indicates the specific details for the dispense or medication supply part of a medication request (also known as a Medication Prescription or Medication Order). Note that this information is not always sent with the order. There may be in some settings (e.g. hospitals) institutional or system support for completing the dispense details in the pharmacy department.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest","min":0,"max":"1"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.id","path":"MedicationRequest.dispenseRequest.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.extension","path":"MedicationRequest.dispenseRequest.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.modifierExtension","path":"MedicationRequest.dispenseRequest.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"MedicationRequest.dispenseRequest.initialFill","path":"MedicationRequest.dispenseRequest.initialFill","short":"First fill details","definition":"Indicates the quantity or duration for the first dispense of the medication.","comment":"If populating this element, either the quantity or the duration must be included.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.initialFill","min":0,"max":"1"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.initialFill.id","path":"MedicationRequest.dispenseRequest.initialFill.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.initialFill.extension","path":"MedicationRequest.dispenseRequest.initialFill.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.initialFill.modifierExtension","path":"MedicationRequest.dispenseRequest.initialFill.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"MedicationRequest.dispenseRequest.initialFill.quantity","path":"MedicationRequest.dispenseRequest.initialFill.quantity","short":"First fill quantity","definition":"The amount or quantity to provide as part of the first dispense.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.initialFill.quantity","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.initialFill.duration","path":"MedicationRequest.dispenseRequest.initialFill.duration","short":"First fill duration","definition":"The length of time that the first dispense is expected to last.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.initialFill.duration","min":0,"max":"1"},"type":[{"code":"Duration"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.dispenseInterval","path":"MedicationRequest.dispenseRequest.dispenseInterval","short":"Minimum period of time between dispenses","definition":"The minimum period of time that must occur between dispenses of the medication.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.dispenseInterval","min":0,"max":"1"},"type":[{"code":"Duration"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.validityPeriod","path":"MedicationRequest.dispenseRequest.validityPeriod","short":"Time period supply is authorized for","definition":"This indicates the validity period of a prescription (stale dating the Prescription).","comment":"It reflects the prescribers' perspective for the validity of the prescription. Dispenses must not be made against the prescription outside of this period. The lower-bound of the Dispensing Window signifies the earliest date that the prescription can be filled for the first time. If an upper-bound is not specified then the Prescription is open-ended or will default to a stale-date based on regulations.","requirements":"Indicates when the Prescription becomes valid, and when it ceases to be a dispensable Prescription.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.validityPeriod","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.numberOfRepeatsAllowed","path":"MedicationRequest.dispenseRequest.numberOfRepeatsAllowed","short":"Number of refills authorized","definition":"An integer indicating the number of times, in addition to the original dispense, (aka refills or repeats) that the patient can receive the prescribed medication. Usage Notes: This integer does not include the original order dispense. This means that if an order indicates dispense 30 tablets plus \"3 repeats\", then the order can be dispensed a total of 4 times and the patient can receive a total of 120 tablets. A prescriber may explicitly say that zero refills are permitted after the initial dispense.","comment":"If displaying \"number of authorized fills\", add 1 to this number.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.numberOfRepeatsAllowed","min":0,"max":"1"},"type":[{"code":"unsignedInt"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.quantity","path":"MedicationRequest.dispenseRequest.quantity","short":"Amount of medication to supply per dispense","definition":"The amount that is to be dispensed for one fill.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.quantity","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.expectedSupplyDuration","path":"MedicationRequest.dispenseRequest.expectedSupplyDuration","short":"Number of days supply per dispense","definition":"Identifies the period time over which the supplied product is expected to be used, or the length of time the dispense is expected to last.","comment":"In some situations, this attribute may be used instead of quantity to identify the amount supplied by how long it is expected to last, rather than the physical quantity issued, e.g. 90 days supply of medication (based on an ordered dosage). When possible, it is always better to specify quantity, as this tends to be more precise. expectedSupplyDuration will always be an estimate that can be influenced by external factors.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.expectedSupplyDuration","min":0,"max":"1"},"type":[{"code":"Duration"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.performer","path":"MedicationRequest.dispenseRequest.performer","short":"Intended dispenser","definition":"Indicates the intended dispensing Organization specified by the prescriber.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.performer","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.substitution","path":"MedicationRequest.substitution","short":"Any restrictions on medication substitution","definition":"Indicates whether or not substitution can or should be part of the dispense. In some cases, substitution must happen, in other cases substitution must not happen. This block explains the prescriber's intent. If nothing is specified substitution may be done.","min":0,"max":"1","base":{"path":"MedicationRequest.substitution","min":0,"max":"1"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.substitution.id","path":"MedicationRequest.substitution.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.substitution.extension","path":"MedicationRequest.substitution.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.substitution.modifierExtension","path":"MedicationRequest.substitution.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"MedicationRequest.substitution.allowed[x]","path":"MedicationRequest.substitution.allowed[x]","short":"Whether substitution is allowed or not","definition":"True if the prescriber allows a different drug to be dispensed from what was prescribed.","comment":"This element is labeled as a modifier because whether substitution is allow or not, it cannot be ignored.","min":1,"max":"1","base":{"path":"MedicationRequest.substitution.allowed[x]","min":1,"max":"1"},"type":[{"code":"boolean"},{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestSubstitution"}],"strength":"example","description":"Identifies the type of substitution allowed.","valueSet":"http://terminology.hl7.org/ValueSet/v3-ActSubstanceAdminSubstitutionCode"}},{"id":"MedicationRequest.substitution.reason","path":"MedicationRequest.substitution.reason","short":"Why should (not) substitution be made","definition":"Indicates the reason for the substitution, or why substitution must or must not be performed.","min":0,"max":"1","base":{"path":"MedicationRequest.substitution.reason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationIntendedSubstitutionReason"}],"strength":"example","description":"A coded concept describing the reason that a different medication should (or should not) be substituted from what was prescribed.","valueSet":"http://terminology.hl7.org/ValueSet/v3-SubstanceAdminSubstitutionReason"}},{"id":"MedicationRequest.priorPrescription","path":"MedicationRequest.priorPrescription","short":"An order/prescription that is being replaced","definition":"A link to a resource representing an earlier order related order or prescription.","min":0,"max":"1","base":{"path":"MedicationRequest.priorPrescription","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/MedicationRequest"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.detectedIssue","path":"MedicationRequest.detectedIssue","short":"Clinical Issue with action","definition":"Indicates an actual or potential clinical issue with or between one or more active or proposed clinical actions for a patient; e.g. Drug-drug interaction, duplicate therapy, dosage alert etc.","comment":"This element can include a detected issue that has been identified either by a decision support system or by a clinician and may include information on the steps that were taken to address the issue.","alias":["Contraindication","Drug Utilization Review (DUR)","Alert"],"min":0,"max":"*","base":{"path":"MedicationRequest.detectedIssue","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/DetectedIssue"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.eventHistory","path":"MedicationRequest.eventHistory","short":"A list of events of interest in the lifecycle","definition":"Links to Provenance records for past versions of this resource or fulfilling request or event resources that identify key state transitions or updates that are likely to be relevant to a user looking at the current version of the resource.","comment":"This might not include provenances for all versions of the request – only those deemed “relevant” or important. This SHALL NOT include the provenance associated with this current version of the resource. (If that provenance is deemed to be a “relevant” change, it will need to be added as part of a later update. Until then, it can be queried directly as the provenance that points to this version using _revinclude All Provenances should have some historical version of this Request as their subject.).","min":0,"max":"*","base":{"path":"MedicationRequest.eventHistory","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Provenance"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}}] \ No newline at end of file diff --git a/packages/definitions/dist/fhir/r4/valuesets-medplum.json b/packages/definitions/dist/fhir/r4/valuesets-medplum.json index 0f511500a9..9d1d11426d 100644 --- a/packages/definitions/dist/fhir/r4/valuesets-medplum.json +++ b/packages/definitions/dist/fhir/r4/valuesets-medplum.json @@ -148,6 +148,16 @@ "code": "graphql-introspection", "display": "GraphQL Introspection", "definition": "GraphQL Introspection" + }, + { + "code": "terminology", + "display": "Terminology Service", + "definition": "Updated implementation of Terminology Service functionality" + }, + { + "code": "websocket-subscriptions", + "display": "WebSocket Subscriptions", + "definition": "WebSocket Subscriptions" } ] } @@ -696,6 +706,59 @@ ] } } + }, + { + "fullUrl": "https://medplum.com/fhir/CodeSystem/loinc-stub", + "resource": { + "resourceType": "CodeSystem", + "url": "http://loinc.org", + "version": "0.0.0", + "status": "active", + "hierarchyMeaning": "is-a", + "content": "example", + "concept": [ + { + "code": "LA28865-6", + "display": "Longitudinal care-coordination focused care team" + }, + { + "code": "86645-9", + "display": "Pregnancy intention in the next year - Reported" + }, + { + "code": "82810-3", + "display": "Pregnancy status" + }, + { + "code": "76690-7", + "display": "Sexual orientation" + }, + { + "code": "77606-2", + "display": "Weight-for-length Per age and sex" + }, + { + "code": "8480-6", + "display": "Systolic blood pressure" + }, + { + "code": "8462-4", + "display": "Diastolic blood pressure" + }, + { + "code": "8867-4", + "display": "Heart rate" + }, + { + "code": "2708-6", + "display": "Oxygen saturation in Arterial blood" + }, + { + "code": "3151-8", + "display": "Inhaled oxygen flow rate" + } + ] + } } ] } diff --git a/packages/definitions/package.json b/packages/definitions/package.json index f858a7051d..185fc748dd 100644 --- a/packages/definitions/package.json +++ b/packages/definitions/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/definitions", - "version": "3.0.2", + "version": "3.0.4", "description": "Medplum Data Definitions", "keywords": [ "medplum", diff --git a/packages/docs/blog/2023-03-08-patient-deduplication.md b/packages/docs/blog/2023-03-08-patient-deduplication.md index d8766afec9..97c99c6cb8 100644 --- a/packages/docs/blog/2023-03-08-patient-deduplication.md +++ b/packages/docs/blog/2023-03-08-patient-deduplication.md @@ -31,7 +31,7 @@ A sample (skeleton) deduplication bot can be found in the [Medplum Demo Bot](htt ## Maintaining Identifiers -Many systems issue patient identifiers, like payors (e.g. United Healthcare), pharmacy and medication systems (e.g. Surescripts or DoseSpot) and even payment providers like Stripe. FHIR support maintaining multiple identifiers from different systems. If you maintain records with patient identifiers from different systems, this can be the basis for detecting duplicates with high accuracy. +Many systems issue patient identifiers, like payors (e.g. United Healthcare), pharmacy and medication systems (e.g. DoseSpot) and even payment providers like Stripe. FHIR support maintaining multiple identifiers from different systems. If you maintain records with patient identifiers from different systems, this can be the basis for detecting duplicates with high accuracy. The sample deduplication bot test shows a patient with multiple identifiers for reference. diff --git a/packages/docs/blog/2024-01-26-develo-case-study.md b/packages/docs/blog/2024-01-26-develo-case-study.md index a7e36c5584..19ef10f3cf 100644 --- a/packages/docs/blog/2024-01-26-develo-case-study.md +++ b/packages/docs/blog/2024-01-26-develo-case-study.md @@ -6,7 +6,7 @@ authors: title: Medplum Core Team url: https://github.com/reshmakh image_url: https://github.com/reshmakh.png -tags: [pediatrics, fhir-datastore, self-host] +tags: [pediatrics, fhir-datastore, self-host, ai] --- # Develo Pediatric EHR diff --git a/packages/docs/blog/2024-02-22-flexpa-case-study.md b/packages/docs/blog/2024-02-22-flexpa-case-study.md new file mode 100644 index 0000000000..9bd4c260e5 --- /dev/null +++ b/packages/docs/blog/2024-02-22-flexpa-case-study.md @@ -0,0 +1,51 @@ +--- +slug: flexpa-case-study +title: Flexpa - sync health history to apps +authors: + name: Joshua Kelly + title: Flexpa CTO + url: https://github.com/jdjkelly + image_url: https://github.com/jdjkelly.png +tags: [billing, fhir-datastore, self-host] +--- + +# Flexpa - sync health history to apps + +Claims data is a uniquely rich source of financial and clinical data important to many healthcare workflows. The EDI 837 Health Care Claim transaction is one of the oldest forms of electronic data exchange, stemming from being defined as a required data transmission specification by HIPAA. + +Today, we are showcasing [Flexpa](https://www.flexpa.com/) which connects applications to claims data via direct patient consent and a modern FHIR API powered by Medplum. + + + +## How does it work? +Flexpa aggregates and standardizes Patient Access APIs created by payers as required by CMS-9115-F. First, patients authenticate and consent to a data-sharing request from an application. + +Then, Flexpa extracts, transforms, and loads payer responses into a normalized FHIR dataset. Flexpa stores data in a temporary FHIR server cache during the period for which a patient has granted access. + +Finally, applications receive a patient-specific authorization response which can be used to retrieve data from a FHIR API provided by Flexpa – powered by Medplum. + +![Flexpa](/img/blog/flexpa.png) + +## What problems does Flexpa solve? + +Payer FHIR servers offer an extremely variable API experience and implementing against 200+ of them is painful. Using Medplum as a data cache for their own FHIR API allows for a uniform developer experience on top of the underlying network access. Flexpa allows developers to use claims data to deliver risk factor adjustment scoring to value-based care providers, help patients navigate care, join clinical trials, negotiate bills, and more. + +## How does Flexpa use Medplum? + +Flexpa takes advantage of several important features of Medplum’s FHIR implementation: + +- [Self-hosting](/docs/self-hosting) +- [Multi-tenant through Projects](/docs/auth/user-management-guide#background-user-model) +- [Update as Create](/docs/sdk/core.medplumclient.createresourceifnoneexist) +- Client assigned IDs +- [Batch](/docs/fhir-datastore/fhir-batch-requests) transactions +- [Medplum App](/docs/app) +- FHIR Operations such as [$validate](/docs/api/fhir/operations/validate-a-resource), [$everything](/docs/api/fhir/operations/patient-everything) and [$expunge](/docs/fhir-datastore/deleting-data#expunge-operation) + +Medplum’s open source implementation provides Flexpa with the ability to contribute back to the project when improvements or changes are required. Additionally, Medplum’s technology choices and stack align perfectly with Flexpa’s making working with Medplum easy for Flexpa’s development team. + +## Related Resources + +- [Flexpa](https://www.flexpa.com/) website +- [Flexpa Blog](https://www.flexpa.com/blog) +- [CMS FHIR](/docs/compliance/cms-fhir) compliance documentation diff --git a/packages/docs/docs/access/admin.md b/packages/docs/docs/access/admin.md index 33fbe10109..d178f3dd21 100644 --- a/packages/docs/docs/access/admin.md +++ b/packages/docs/docs/access/admin.md @@ -21,6 +21,12 @@ Project Admins have the following privileges: - [`PasswordChangeRequest`](/docs/api/fhir/medplum/passwordchangerequest) - used to [send custom emails](/docs/auth/custom-emails#password-change-request-bot) - [`User`](/docs/api/fhir/medplum/user) - only for [project scoped users](/docs/auth/user-management-guide#project-scoped-users) +:::note Applying Access Policies to Admins + +If you want to limit these privileges, you can apply Access Policies to your Admin users. See the [Access Policies docs](/docs/access/access-policies) for more details. + +::: + ## Super Admin A super admin user has an increased level privileges for performing server-level operations. **This level of privilege can cause irreparable data changes, and should be limited to system administrators.** diff --git a/packages/docs/docs/access/project-switcher.png b/packages/docs/docs/access/project-switcher.png new file mode 100644 index 0000000000..fab8007e19 Binary files /dev/null and b/packages/docs/docs/access/project-switcher.png differ diff --git a/packages/docs/docs/access/projects.md b/packages/docs/docs/access/projects.md index 62731e8c7c..15ef630534 100644 --- a/packages/docs/docs/access/projects.md +++ b/packages/docs/docs/access/projects.md @@ -45,6 +45,13 @@ Logging into the Super Admin project allows for potential dangerous operations a ::: +:::note Checking If You Are In The SuperAdmin Project + +To switch to the SuperAdmin project or check if you are already in it, you can use the [**profile selector**](/docs/app/app-introduction/index.md#profile-selector). + +![project switcher](project-switcher.png) +::: + ## Creating a Project #### Medplum App @@ -57,13 +64,13 @@ Logging into the Super Admin project allows for potential dangerous operations a You can find the full `Project` resource schema [here](/docs/api/fhir/medplum/project) -| Setting | Description | Default | -| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -| `superAdmin` | Whether this project is the super administrator project ([see above](#superadmin)). | `false` | -| `strictMode` | Whether this project uses strict FHIR validation, based on [FHIR profiles](/docs/fhir-datastore/profiles). **Strongly recommend setting this to `true`.** | `true` | -| `checkReferencesOnWrite` | If `true`, the the server will reject any create or write operations to a FHIR resource with invalid references. | `false` | +| Setting | Description | Default | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `superAdmin` | Whether this project is the super administrator project ([see above](#superadmin)). | `false` | +| `strictMode` | Whether this project uses strict FHIR validation, based on [FHIR profiles](/docs/fhir-datastore/profiles). **Strongly recommend setting this to `true`.** | `true` | +| `checkReferencesOnWrite` | If `true`, the the server will reject any create or write operations to a FHIR resource with invalid references. | `false` | | `features` | A list of optional features that are enabled for the project. Allowed values are:
  • `bots`: This [`Project`](/docs/api/fhir/medplum/project) is allowed to create and run [Bots](/docs/bots/bot-basics).
  • `email`: Bots in this project can [send emails](/docs/sdk/core.medplumclient.sendemail).
  • `cron`: This [`Project`](/docs/api/fhir/medplum/project) can run Bots on [CRON timers](https://www.medplum.com/docs/bots/bot-cron-job)
  • `google-auth-required`: [Google authentication](/docs/auth/methods/google-auth) is the only method allowed for this [`Project`](/docs/api/fhir/medplum/project)
| | -| `defaultPatientAccessPolicy` | The default [`AccessPolicy`](/docs/access/access-policies) applied to all [Patient Users](/docs/auth/user-management-guide#project-scoped-users) invited to this [`Project`](/docs/api/fhir/medplum/project). This is required to enable [open patient registration](/docs/auth/open-patient-registration). | | +| `defaultPatientAccessPolicy` | The default [`AccessPolicy`](/docs/access/access-policies) applied to all [Patient Users](/docs/auth/user-management-guide#project-scoped-users) invited to this [`Project`](/docs/api/fhir/medplum/project). This is required to enable [open patient registration](/docs/auth/open-patient-registration). | | ## Project Secrets diff --git a/packages/docs/docs/api/oauth/token.md b/packages/docs/docs/api/oauth/token.md index 2294086fe6..6d8023a2dc 100644 --- a/packages/docs/docs/api/oauth/token.md +++ b/packages/docs/docs/api/oauth/token.md @@ -94,7 +94,7 @@ Content-Type: application/json ``` **Note** -The token endpoint returns `refresh_token` only when the `grant_type` is `authorization_code`. +The token endpoint returns `refresh_token` only when the `grant_type` is `authorization_code` and the requested scopes during login included `offline` or `offline_access`. ### Exchanging client credentials for an access token diff --git a/packages/docs/docs/app/admin-page/admin-page.md b/packages/docs/docs/app/admin-page/admin-page.md new file mode 100644 index 0000000000..aa3cd60cf8 --- /dev/null +++ b/packages/docs/docs/app/admin-page/admin-page.md @@ -0,0 +1,35 @@ +# The Admin Page + +The [Admin Page](https://app.medplum.com/admin/project) of the Medplum App allows admin users to view and edit details of their project that normal users do not have access to. + +At the top of the page, there is an array of tabs as shown below. In this guide, we will briefly go over the content of each tab as well as any functionality they provide. + +![App Admin Page](./admin-page.png) + +## Details + +The Details tab displays all of the populated elements of the current [`Project`](/docs/api/fhir/medplum/project) resource. For more details on the [`Project`](/docs/api/fhir/medplum/project) resource, see the [User Management Guide](/docs/auth/user-management-guide). + +## Users + +The Users tab displays all of the [`Practitioner`](/docs/api/fhir/resources/practitioner) resources that are also a [`User`](/docs/api/fhir/medplum/user) in your project. It also allows you to invite new users. For more details on the [`User`](/docs/api/fhir/medplum/user) resource, see the [User Management Guide](/docs/auth/user-management-guide). + +## Patients + +The Patients tab displays all of the [`Patient`](/docs/api/fhir/resources/patient) resources that are also a [`User`](/docs/api/fhir/medplum/user) in your project. It also allows you to invite new patients. For more details on the [`User`](/docs/api/fhir/medplum/user) and [`Patient`](/docs/api/fhir/resources/patient) resources see the [User Management Guide](/docs/auth/user-management-guide#project-scoped-users). + +## Clients + +The Clients tab displays all of the [`ClientApplication`](/docs/api/fhir/medplum/clientapplication) resources that are associated with your project. It also allows you to create new [`ClientApplication`](/docs/api/fhir/medplum/clientapplication). For more details, see the [Authentication Method docs](/docs/auth/methods/token-exchange#set-up-your-clientapplication). + +## Bots + +The Bots tab dispalys all of the [`Bots`](/docs/api/fhir/medplum/bot) that are a part of your project and allows you to create new ones. For more details on [`Bots`](/docs/api/fhir/medplum/bot) see the [Bot Basics docs](/docs/bots/bot-basics). + +## Secrets + +The Secrets tab displays all of your project secrets as well as allowing you to create new ones. Secrets are used to store sensitive information and as access controls. For example, API keys, [`Bot`](/docs/api/fhir/medplum/bot) secrets, and reCAPTCHA secrets would all appear here. For more details see the [Project Secrets docs](/docs/access/projects#project-secrets). + +## Sites + +The Sites tab allows you to view and edit any custom domains that your project is configured for. diff --git a/packages/docs/docs/app/admin-page/admin-page.png b/packages/docs/docs/app/admin-page/admin-page.png new file mode 100644 index 0000000000..362f2cbd40 Binary files /dev/null and b/packages/docs/docs/app/admin-page/admin-page.png differ diff --git a/packages/docs/docs/fhir-datastore/resource-history.md b/packages/docs/docs/fhir-datastore/resource-history.md index fabc9a1226..86595f84e0 100644 --- a/packages/docs/docs/fhir-datastore/resource-history.md +++ b/packages/docs/docs/fhir-datastore/resource-history.md @@ -40,7 +40,7 @@ The history of a resouce can also be viewed by accessing the `/_history` endpoin To access the `/_history` endpoint, make a GET request to the url of the desired resource or resource type. -The Mepdlum SDK also provides the `readHistory` helper function to access the `/_history` endpoint. +The Medplum SDK also provides the `readHistory` helper function to access the `/_history` endpoint. diff --git a/packages/docs/docs/integration/dosespot.md b/packages/docs/docs/integration/dosespot.md new file mode 100644 index 0000000000..3f2cc2921d --- /dev/null +++ b/packages/docs/docs/integration/dosespot.md @@ -0,0 +1,67 @@ +# DoseSpot + +Medplum has partnered with [DoseSpot](https://www.dosespot.com/), a leader in e-prescription (eRx) technology, to offer prescription ordering services through the Medplum platform. + +This collaboration enables healthcare professionals to seamlessly place medication orders using DoseSpot's eRx interface, which Medplum integrates through an embedded iFrame. This approach ensures a secure, safe, and efficient, experience for clinicians, and offers functionality such as: + +- Clinician identity proofing +- Drug-drug interactions +- Patient coverage benefits checks + +## DoseSpot FAQ + +### Who is qualified to use the eRx feature at Medplum? + +The eRx feature at Medplum is available to professionals authorized to prescribe medication in the United States, contingent upon integration with our partner platform, DoseSpot. Approval from DoseSpot is required for access. + +The feature is user-specific; hence, only approved users can issue prescriptions. Proxy access can be granted to other team members upon designation. + +### Why does DoseSpot request credit card information? + +DoseSpot partners with Experian to verify prescribers' identities. This is a one time check that is performed during the prescriber's first prescription in Medplum. + +Credit card details may be requested by Medplum's eRx partner for identity verification purposes, to ensure the security and integrity of prescribing privileges within the platform. + +This procedure is integral to the DoseSpot's identity proofing protocol. Such identity verification measures are widely adopted to prevent fraud and confirm the authenticity of an individual's identity. To accomplish this, the prescriber's credit card details are cross-referenced with Experian data to ensure a match between the prescriber's provided information and that associated with the credit card and report. + +**Importantly, the credit card is neither charged nor stored in Medplum.** Moreover, this process involves a soft credit check, which does not impact the prescriber's credit rating or borrowing capabilities + +### What distinguishes a "Proxy" user from a "Prescriber" ? + +A "Prescriber" is a user authorized to directly issue prescriptions, holding the necessary credentials and permissions. + +A "Proxy" user, however, acts as an assistant or delegate, performing tasks on behalf of a Prescriber but does not have the authority to finalize prescriptions without review and approval by a Prescriber. + +### How do "Refill" and "Reorder" differ within Medplum? + +A "Refill" refers to authorizing additional quantities of a medication under an existing prescription, is initiated by the pharmacy. + +A "Reorder" involves creating a new prescription order for a medication previously prescribed, and is initiated by the prescriber. + +### How does one initiate an Electronic Prior Authorization (ePA) with Medplum's eRx service? + +When formulary data indicates a need for ePA based on insurance, the clinician can start the process within the eRx interface. This option becomes available after a prescription is marked as pending, accessible through the medication's action menu. Note that this process is independent of the patient's coverage details, which are auto-populated from external sources when available. + +### Does Medplum support prescription submissions to pharmacies across all 50 states? + +Yes. Medplum Medplum's integration with DoseSpot allows ordering prescriptions to any pharmacy across 50 states. + +### How does Medplum collect and manage patient insurance details? + +Patient insurance details within Medplum's eRx system are pulled by DoseSpot, which matches insurance information based on patient demographics: name, gender, and date of birth. + +It's important to note that insurance information stored directly in Medplum **does not integrate with DoseSpot**, nor can it be manually entered into the eRx system. + +### What constitutes a transmission error in Medplum's eRx service? + +A transmission error occurs when there's a failure in sending a prescription from Medplum's eRx system to a pharmacy, due to issues like incorrect pharmacy details, network problems, or data mismatches. + +### Does Medplum offer drug references or educational materials for patients? + +The DoseSpot eRx interface, provides access to drug monographs when a prescription is placed. These monographs can be accessed by clicking on the drug name when viewing pending prescriptions. + +### Can custom instructions be added to prescription notes in Medplum? + +Drop-down fields within the Medplum eRx interface cannot be customized. However, prescribers can use the free text instructions field for adding notes. + +Similarly, the patient notes field can not be auto-populated at this time. diff --git a/packages/docs/docs/integration/log-streaming.md b/packages/docs/docs/integration/log-streaming.md index 34c0b3f96b..7908571269 100644 --- a/packages/docs/docs/integration/log-streaming.md +++ b/packages/docs/docs/integration/log-streaming.md @@ -35,7 +35,7 @@ Medplum uses includes both request IDs and trace IDs to aid in log correlation, The request ID is automatically generated by the Medplum server for each unique HTTP request. -Clients can pass in their own trace id in their request headers. Medplum supports both the headers `X-TRACE-ID` or `traceParent`. +Clients can pass in their own trace id in their request headers. Medplum supports both the headers `X-TRACE-ID` or [`traceparent`](https://www.w3.org/TR/trace-context/#traceparent-header).
diff --git a/packages/docs/docs/medications/e-prescibe.md b/packages/docs/docs/medications/e-prescibe.md new file mode 100644 index 0000000000..7d68013597 --- /dev/null +++ b/packages/docs/docs/medications/e-prescibe.md @@ -0,0 +1,41 @@ +# E-Prescribe (eRx) + +Medplum's flexible architecture allows for integration with any e-prescribe(eRx) provider through the use of [Bots](/docs/bots), offering customizable e-prescription solutions. + +To provide an integrated prescribing experience out of the box, Medplum offers DoseSpot as its default, first-party eRx provider . While DoseSpot serves as our default solution, [Medplum Bots](/docs/bots) can be used to integrate other prescribing solutions. + +Visit our [DoseSpot integrations page](/docs/integration/dosespot) to learn more about our DoseSpot integration. + +## E-Prescribe FAQ + +### Who is qualified to use the eRx feature at Medplum? + +The eRx feature at Medplum is available to professionals authorized to prescribe medication in the United States. The feature is user-specific; hence, only approved users can issue prescriptions. + +### Is it possible to prescribe controlled substances through Medplum? + +No. Currently, Medplum's eRx integration does not allow prescription of controlled substances. If controlled substances are important for your care model, please contact our support team at [support@medplum.com](mailto:support@medplum.com). + +### Are there other limits on which medications can be prescribed? + +No. Beyond restrictions on controlled substances, prescriber has free choice of medication when prescribing. + +### What guidelines exist for prescribing to minors through Medplum? + +It is up to the prescriber to ensure dosages are within standard guidelines for for patient's height and weight. + +:::danger + +E-prescription providers often impose additional validation requirements when prescribing medication for minors. Please check the requirements of your eRx vendor. + +::: + +### Can prescription cost information be retrieved through the API? + +At this time, prescription cost retrieval is not available via the Medplum API. However, Some eRx vendors may display cost information at the time a prescription is written. Check with your specific eRX vendor for more details. + +### How does Medplum collect and manage patient insurance details? + +It is best practice to maintain update date coverage information for each patient in Medplum, as it is important for both clinical and administrative workflows in the core EHR. See our [Guide on Patient Insurance](/docs/billing/patient-insurance) for more details. + +However, many eRx vendors do not allow users to directly input patient coverage details. Rather, they depend on clearinghouses to supply patient coverage data. Check with your eRx vendor for details on how patient coverage is managed. diff --git a/packages/docs/docs/search/basic-search.mdx b/packages/docs/docs/search/basic-search.mdx index a4f260c199..7269d1db84 100644 --- a/packages/docs/docs/search/basic-search.mdx +++ b/packages/docs/docs/search/basic-search.mdx @@ -324,7 +324,7 @@ You can search an exclusive range using an OR search You can sort your search results by using the special search parameter `_sort`. -The `_sort` parameter allows you specify a list of search parameters to sort by, in order. +The `_sort` parameter allows you specify a list of [search parameters](#search-parameters) to sort by, in order. The example below searches for all [`RiskAssessments`](/docs/api/fhir/resources/riskassessment), sorted by their `probability`, then by `date`. diff --git a/packages/docs/docs/self-hosting/config-settings.md b/packages/docs/docs/self-hosting/config-settings.md index d1d278100f..0b4c191756 100644 --- a/packages/docs/docs/self-hosting/config-settings.md +++ b/packages/docs/docs/self-hosting/config-settings.md @@ -109,45 +109,47 @@ For example, if your environment name is "prod", then the "baseUrl" parameter na You will also be prompted for a parameter "Type". The default option is "String". Medplum supports both "String" and "SecureString". "SecureString" is recommended for security and compliance purposes. -| Key | Description | Required | Created by | Default | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------- | ----------------------------------- | -| `port` | The port number that the API server binds to inside the Docker image. By default, you should use `8103`. In some cases, you may need to use `5000`. | yes | `init` | `8103` | -| `baseUrl` | The fully qualified base URL of the API server including a trailing slash. For example, `https://api.example.com/`. | yes | `init` | | -| `issuer` | The JWK issuer. By default, Medplum server uses built in OAuth, so `issuer` should be the same as `baseUrl`. | | | `baseUrl` | -| `jwksUrl` | The JWKS URL. By default, Medplum server uses built in OAuth, so `jwksUrl` should be `baseUrl` + `.well-known/jwks.json`. | | | `baseUrl` + `.well-known/jwks.json` | -| `authorizeUrl` | The OAuth authorize URL. By default, Medplum server uses built in OAuth, so `authorizeUrl` should be `baseUrl` + `oauth2/authorize`. | | | `baseUrl` + `oauth2/authorize` | -| `tokenUrl` | The OAuth token URL. By default, Medplum server uses built in OAuth, so `tokenUrl` should be `baseUrl` + `oauth2/token`. | | | `baseUrl` + `oauth2/token` | -| `userInfoUrl` | The OAuth userinfo URL. By default, Medplum server uses built in OAuth, so `userInfoUrl` should be `baseUrl` + `oauth2/userinfo`. | | | `baseUrl` + `oauth2/userinfo` | -| `appBaseUrl` | The fully qualified URL of the user-facing app. This is used for CORS and system generated emails. For example, `https://app.example.com/`. | yes | `init` | | -| `logLevel` | Verbosity of logging: `'NONE'`, `'ERROR'`, `'WARN'`, `'INFO'`, `'DEBUG'` | | | `'INFO'` | -| `binaryStorage` | Where to store binary contents. This should be the CDK config `storageBucketName` with `s3:` prefix. For example, `s3:medplum-storage`. | yes | `init` | | -| `storageBaseUrl` | The fully qualified base URL of the binary storage. This should be the CDK config `storageDomainName` with `https://` prefix. For example, `https://storage.medplum.com/binary/`. | yes | `init` | | -| `signingKeyId` | The AWS key ID of the CloudFront signing key that you created before. | yes | `cdk` | | -| `signingKey` | The private key of the CloudFront signing key. | yes | `init` | | -| `signingKeyPassphrase` | The passphrase of the CloudFront signing key. | yes | `init` | | -| `allowedOrigins` | Optional comma separated list of allowed origins for [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) requests. `appBaseUrl` is included automatically. See [Setting Up CORS](/docs/self-hosting/setting-up-cors) for more details. | | | | -| `supportEmail` | The email address to use when sending system generated messages. This email address must be registered in AWS SES. | yes | `init` | | -| `googleClientId` | If using Google Authentication, this is the Google Client ID. | | | | -| `googleClientSecret` | If using Google Authentication, this is the Google Client Secret. | | | | -| `recaptchaSiteKey` | If using reCAPTCHA, this is the reCAPTCHA site key. | | | | -| `recaptchaSecretKey` | If using reCAPTCHA, this is the reCAPTCHA secret key. | | | | -| `botLambdaRoleArn` | If using Medplum Bots, this is the ARN of the [Lambda execution role](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html). | | `cdk` | | -| `botLambdaLayerName` | If using Medplum Bots, this is the name of the [Lambda layer](https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html). For example, `medplum-bot-layer`. | | | `medplum-bot-layer` | -| `database` | The database connection details as a JSON object. Only available when using JSON config file. See [AWS Secrets](#aws-secrets). | | | | -| `DatabaseSecrets` | The AWS Secret ID containing database connection details (created automatically by CDK). Only available when using AWS Parameter Store config. See [AWS Secrets](#aws-secrets). | | `cdk` | | -| `redis` | The redis connection details as a JSON object. Only available when using JSON config file. See [AWS Secrets](#aws-secrets). | | | | -| `RedisSecrets` | The AWS Secret ID containing Redis connection details (created automatically by CDK). Only available when using AWS Parameter Store config. See [AWS Secrets](#aws-secrets). | | `cdk` | | -| `logRequests` | Optional flag to log individual HTTP requests. | | | `false` | -| `saveAuditEvents` | Optional flag to save `AuditEvent` resources for all auth and RESTful operations in the database. | | | `false` | -| `logAuditEvents` | Optional flag to log `AuditEvent` resources for all auth and RESTful operations to the logger. | | | `false` | -| `auditEventLogGroup` | Optional AWS CloudWatch Log Group name for `AuditEvent` logs. If not specified, `AuditEvent` logs use the default logger. | | | | -| `auditEventLogStream` | Optional AWS CloudWatch Log Stream name for `AuditEvent` logs. Only applies if `auditEventLogGroup` is set. Uses `os.hostname()` as the default. | | | `os.hostname()` | -| `registerEnabled` | Optional flag whether new user registration is enabled. | | | `true` | -| `maxJsonSize` | Maximum JSON size for API calls. String is parsed with the [bytes](https://www.npmjs.com/package/bytes) library. Default is `1mb`. | | | `1mb` | -| `smtp` | Optional SMTP email settings to use SMTP for email. See [Sending SMTP Emails](/docs/self-hosting/sendgrid) for more details. | | | | -| `awsRegion` | The AWS Region identifier. | | `cdk` | `us-east-1` | -| `otlpMetricsEndpoint` | Optional OTLP metrics endpoint for OpenTelemetry. For example, `http://localhost:4318/v1/metrics`. See [OpenTelemetry](/docs/self-hosting/opentelemetry) for more details. | | | | -| `otlpTraceEndpoint` | Optional OTLP trace endpoint for OpenTelemetry. For example, `http://localhost:4318/v1/traces`. See [OpenTelemetry](/docs/self-hosting/opentelemetry) for more details. | | | | +| Key | Description | Required | Created by | Default | +| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------- | ----------------------------------- | +| `port` | The port number that the API server binds to inside the Docker image. By default, you should use `8103`. In some cases, you may need to use `5000`. | yes | `init` | `8103` | +| `baseUrl` | The fully qualified base URL of the API server including a trailing slash. For example, `https://api.example.com/`. | yes | `init` | | +| `issuer` | The JWK issuer. By default, Medplum server uses built in OAuth, so `issuer` should be the same as `baseUrl`. | | | `baseUrl` | +| `jwksUrl` | The JWKS URL. By default, Medplum server uses built in OAuth, so `jwksUrl` should be `baseUrl` + `.well-known/jwks.json`. | | | `baseUrl` + `.well-known/jwks.json` | +| `authorizeUrl` | The OAuth authorize URL. By default, Medplum server uses built in OAuth, so `authorizeUrl` should be `baseUrl` + `oauth2/authorize`. | | | `baseUrl` + `oauth2/authorize` | +| `tokenUrl` | The OAuth token URL. By default, Medplum server uses built in OAuth, so `tokenUrl` should be `baseUrl` + `oauth2/token`. | | | `baseUrl` + `oauth2/token` | +| `userInfoUrl` | The OAuth userinfo URL. By default, Medplum server uses built in OAuth, so `userInfoUrl` should be `baseUrl` + `oauth2/userinfo`. | | | `baseUrl` + `oauth2/userinfo` | +| `appBaseUrl` | The fully qualified URL of the user-facing app. This is used for CORS and system generated emails. For example, `https://app.example.com/`. | yes | `init` | | +| `logLevel` | Verbosity of logging: `'NONE'`, `'ERROR'`, `'WARN'`, `'INFO'`, `'DEBUG'` | | | `'INFO'` | +| `binaryStorage` | Where to store binary contents. This should be the CDK config `storageBucketName` with `s3:` prefix. For example, `s3:medplum-storage`. | yes | `init` | | +| `storageBaseUrl` | The fully qualified base URL of the binary storage. This should be the CDK config `storageDomainName` with `https://` prefix. For example, `https://storage.medplum.com/binary/`. | yes | `init` | | +| `signingKeyId` | The AWS key ID of the CloudFront signing key that you created before. | yes | `cdk` | | +| `signingKey` | The private key of the CloudFront signing key. | yes | `init` | | +| `signingKeyPassphrase` | The passphrase of the CloudFront signing key. | yes | `init` | | +| `allowedOrigins` | Optional comma separated list of allowed origins for [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) requests. `appBaseUrl` is included automatically. See [Setting Up CORS](/docs/self-hosting/setting-up-cors) for more details. | | | | +| `supportEmail` | The email address to use when sending system generated messages. This email address must be registered in AWS SES. | yes | `init` | | +| `googleClientId` | If using Google Authentication, this is the Google Client ID. | | | | +| `googleClientSecret` | If using Google Authentication, this is the Google Client Secret. | | | | +| `recaptchaSiteKey` | If using reCAPTCHA, this is the reCAPTCHA site key. | | | | +| `recaptchaSecretKey` | If using reCAPTCHA, this is the reCAPTCHA secret key. | | | | +| `botLambdaRoleArn` | If using Medplum Bots, this is the ARN of the [Lambda execution role](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html). | | `cdk` | | +| `botLambdaLayerName` | If using Medplum Bots, this is the name of the [Lambda layer](https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html). For example, `medplum-bot-layer`. | | | `medplum-bot-layer` | +| `database` | The database connection details as a JSON object. Only available when using JSON config file. See [AWS Secrets](#aws-secrets). | | | | +| `DatabaseSecrets` | The AWS Secret ID containing database connection details (created automatically by CDK). Only available when using AWS Parameter Store config. See [AWS Secrets](#aws-secrets). | | `cdk` | | +| `redis` | The redis connection details as a JSON object. Only available when using JSON config file. See [AWS Secrets](#aws-secrets). | | | | +| `RedisSecrets` | The AWS Secret ID containing Redis connection details (created automatically by CDK). Only available when using AWS Parameter Store config. See [AWS Secrets](#aws-secrets). | | `cdk` | | +| `logRequests` | Optional flag to log individual HTTP requests. | | | `false` | +| `saveAuditEvents` | Optional flag to save `AuditEvent` resources for all auth and RESTful operations in the database. | | | `false` | +| `logAuditEvents` | Optional flag to log `AuditEvent` resources for all auth and RESTful operations to the logger. | | | `false` | +| `auditEventLogGroup` | Optional AWS CloudWatch Log Group name for `AuditEvent` logs. If not specified, `AuditEvent` logs use the default logger. | | | | +| `auditEventLogStream` | Optional AWS CloudWatch Log Stream name for `AuditEvent` logs. Only applies if `auditEventLogGroup` is set. Uses `os.hostname()` as the default. | | | `os.hostname()` | +| `registerEnabled` | Optional flag whether new user registration is enabled. | | | `true` | +| `maxJsonSize` | Maximum JSON size for API calls. String is parsed with the [bytes](https://www.npmjs.com/package/bytes) library. Default is `1mb`. | | | `1mb` | +| `smtp` | Optional SMTP email settings to use SMTP for email. See [Sending SMTP Emails](/docs/self-hosting/sendgrid) for more details. | | | | +| `awsRegion` | The AWS Region identifier. | | `cdk` | `us-east-1` | +| `otlpMetricsEndpoint` | Optional OTLP metrics endpoint for OpenTelemetry. For example, `http://localhost:4318/v1/metrics`. See [OpenTelemetry](/docs/self-hosting/opentelemetry) for more details. | | | | +| `otlpTraceEndpoint` | Optional OTLP trace endpoint for OpenTelemetry. For example, `http://localhost:4318/v1/traces`. See [OpenTelemetry](/docs/self-hosting/opentelemetry) for more details. | | | | +| `accurateCountThreshold` | Optional threshold for accurate count queries. The server will always perform an estimate count first (to protect database performance), and an accurate count if the estimate is below this threshold. | | | `1000000` | +| `defaultBotRuntimeVersion` | Optional default bot runtime version. See [Bot runtime version](/docs/api/fhir/medplum/bot) for more details. | | | `awslambda` | :::tip Local Config To make changes to the server config after your first deploy, you must the edit parameter values _directly in AWS parameter store_ diff --git a/packages/docs/package.json b/packages/docs/package.json index e4d6316e0f..d8aeb627ae 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/docs", - "version": "3.0.2", + "version": "3.0.4", "description": "Medplum Docs", "homepage": "https://www.medplum.com/", "bugs": { @@ -46,10 +46,10 @@ "@docusaurus/theme-mermaid": "3.1.1", "@docusaurus/tsconfig": "3.1.1", "@docusaurus/types": "3.1.1", - "@mdx-js/react": "3.0.0", - "@medplum/core": "*", - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", + "@mdx-js/react": "3.0.1", + "@medplum/core": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", "@svgr/webpack": "8.1.0", "clsx": "2.1.0", "file-loader": "6.2.0", @@ -57,8 +57,8 @@ "raw-loader": "4.0.2", "react": "18.2.0", "react-dom": "18.2.0", - "react-intersection-observer": "9.5.3", - "react-router-dom": "6.21.3", + "react-intersection-observer": "9.8.0", + "react-router-dom": "6.22.1", "typescript": "5.3.3", "url-loader": "4.1.1" }, diff --git a/packages/docs/src/pages/case-studies.tsx b/packages/docs/src/pages/case-studies.tsx index 9dbf944bb6..4e0a1df04a 100644 --- a/packages/docs/src/pages/case-studies.tsx +++ b/packages/docs/src/pages/case-studies.tsx @@ -82,18 +82,18 @@ export default function CaseStudiesPage(): JSX.Element { youtubeUrl="https://youtu.be/q-22Y7Ox2jY" /> @@ -117,6 +117,31 @@ export default function CaseStudiesPage(): JSX.Element { >
+
+ + + + + +
); diff --git a/packages/docs/src/pages/products/integration.md b/packages/docs/src/pages/products/integration.md index 411f2f20c8..9c6b3d7fa8 100644 --- a/packages/docs/src/pages/products/integration.md +++ b/packages/docs/src/pages/products/integration.md @@ -70,7 +70,7 @@ Below are some of the classes of applications indexed by common integration meth | PACS | HL7 | HL7 ORU, OBX message types common | | Vaccine Registry | SFTP | Often operated by public health departments, government | | Payor | FHIR | Regulatory changes have increased payor FHIR adoption | -| Pharmacy | REST | Pharmacy API such as DoseSpot, Surescripts | +| Pharmacy | REST | Pharmacy API such as DoseSpot | | Logging and Analytics | REST | Splunk, Freshpaint, Amplitude, Segment and related | | CRM | REST, CSV | CRM often have API or file based import | | Forms | REST | Qualtrics, Jotforms, Formstack have REST API with webhooks | diff --git a/packages/docs/static/img/blog/codex-logo.jpeg b/packages/docs/static/img/blog/codex-logo.jpeg new file mode 100644 index 0000000000..f47fdbcdc5 Binary files /dev/null and b/packages/docs/static/img/blog/codex-logo.jpeg differ diff --git a/packages/docs/static/img/blog/develo.jpeg b/packages/docs/static/img/blog/develo.jpeg new file mode 100644 index 0000000000..203234012d Binary files /dev/null and b/packages/docs/static/img/blog/develo.jpeg differ diff --git a/packages/docs/static/img/blog/flexpa-logo.png b/packages/docs/static/img/blog/flexpa-logo.png new file mode 100644 index 0000000000..05d5399f84 Binary files /dev/null and b/packages/docs/static/img/blog/flexpa-logo.png differ diff --git a/packages/docs/static/img/blog/flexpa.png b/packages/docs/static/img/blog/flexpa.png new file mode 100644 index 0000000000..afba9e593e Binary files /dev/null and b/packages/docs/static/img/blog/flexpa.png differ diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 0bd17f63d8..776a6107bb 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/eslint-config", - "version": "3.0.2", + "version": "3.0.4", "description": "Shared ESLint configuration for Medplum projects", "keywords": [ "eslint", @@ -19,21 +19,22 @@ "author": "Medplum ", "main": "index.cjs", "devDependencies": { - "@typescript-eslint/eslint-plugin": "6.19.1", - "@typescript-eslint/parser": "6.19.1", + "@typescript-eslint/eslint-plugin": "7.0.1", + "@typescript-eslint/parser": "7.0.1", "eslint": "8.56.0", - "eslint-plugin-jsdoc": "48.0.4", + "eslint-plugin-jsdoc": "48.1.0", "eslint-plugin-json-files": "4.1.0", "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-refresh": "0.4.5" }, "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^6", - "@typescript-eslint/parser": "^6", + "@typescript-eslint/eslint-plugin": "^7", + "@typescript-eslint/parser": "^7", "eslint": "^8", - "eslint-plugin-jsdoc": "^46", - "eslint-plugin-json-files": "^3", - "eslint-plugin-react-hooks": "^4" + "eslint-plugin-jsdoc": "^48", + "eslint-plugin-json-files": "^4", + "eslint-plugin-react-hooks": "^4", + "eslint-plugin-react-refresh": "^0" }, "engines": { "node": ">=18.0.0" diff --git a/packages/examples/package.json b/packages/examples/package.json index 91b3733f56..316c54b74d 100644 --- a/packages/examples/package.json +++ b/packages/examples/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/examples", - "version": "3.0.2", + "version": "3.0.4", "description": "Medplum Code Examples", "homepage": "https://www.medplum.com/", "bugs": { @@ -19,9 +19,9 @@ }, "devDependencies": { "@jest/globals": "29.7.0", - "@medplum/core": "*", - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", + "@medplum/core": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", "jest": "29.7.0" }, "engines": { diff --git a/packages/expo-polyfills/package.json b/packages/expo-polyfills/package.json index 1111f19f2f..6b8e17b528 100644 --- a/packages/expo-polyfills/package.json +++ b/packages/expo-polyfills/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/expo-polyfills", - "version": "3.0.2", + "version": "3.0.4", "description": "A module for polyfilling the minimum necessary web APIs for using the Medplum client on React Native", "keywords": [ "react-native", @@ -47,19 +47,19 @@ "text-encoding": "0.7.0" }, "devDependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "@types/base-64": "1.0.2", - "@types/react": "18.2.48", + "@types/react": "18.2.56", "@types/text-encoding": "0.0.39", - "esbuild": "0.20.0", - "esbuild-node-externals": "1.12.0", + "esbuild": "0.20.1", + "esbuild-node-externals": "1.13.0", "jest": "29.7.0", - "jest-expo": "50.0.1", + "jest-expo": "50.0.2", "rimraf": "5.0.5", "ts-jest": "29.1.2" }, "peerDependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "expo": "*", "expo-crypto": "^12.6.0", "expo-secure-store": "^12.3.1", diff --git a/packages/fhir-router/package.json b/packages/fhir-router/package.json index 5ea93ef63f..767be19be0 100644 --- a/packages/fhir-router/package.json +++ b/packages/fhir-router/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/fhir-router", - "version": "3.0.2", + "version": "3.0.4", "description": "Medplum FHIR Router", "keywords": [ "medplum", @@ -53,9 +53,9 @@ "test": "jest" }, "dependencies": { - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", "dataloader": "2.2.2", "graphql": "16.8.1", "rfc6902": "5.1.1" diff --git a/packages/fhir-router/src/batch.ts b/packages/fhir-router/src/batch.ts index 9951931712..da5eea6feb 100644 --- a/packages/fhir-router/src/batch.ts +++ b/packages/fhir-router/src/batch.ts @@ -6,7 +6,7 @@ import { isOk, normalizeOperationOutcome, OperationOutcomeError, - parseSearchUrl, + parseSearchRequest, resolveId, } from '@medplum/core'; import { Bundle, BundleEntry, BundleEntryRequest, OperationOutcome, Resource } from '@medplum/fhirtypes'; @@ -101,9 +101,9 @@ class BatchProcessor { const request = entry.request as BundleEntryRequest; if (entry.resource?.resourceType && request.ifNoneExist) { - const baseUrl = `https://example.com/${entry.resource.resourceType}`; - const searchUrl = new URL('?' + request.ifNoneExist, baseUrl); - const searchBundle = await this.repo.search(parseSearchUrl(searchUrl)); + const searchBundle = await this.repo.search( + parseSearchRequest(`${entry.resource.resourceType}?${request.ifNoneExist}`) + ); const entries = searchBundle.entry as BundleEntry[]; if (entries.length > 1) { return buildBundleResponse(badRequest('Multiple matches')); diff --git a/packages/fhir-router/src/repo.test.ts b/packages/fhir-router/src/repo.test.ts index 93eb084bb2..59bf71df1a 100644 --- a/packages/fhir-router/src/repo.test.ts +++ b/packages/fhir-router/src/repo.test.ts @@ -4,7 +4,7 @@ import { indexStructureDefinitionBundle, notFound, OperationOutcomeError, - parseSearchDefinition, + parseSearchRequest, } from '@medplum/core'; import { readJson } from '@medplum/definitions'; import { Bundle, Observation, Patient, ResourceType, SearchParameter } from '@medplum/fhirtypes'; @@ -83,21 +83,21 @@ describe('MemoryRepository', () => { test('searchResources helper', async () => { const family = randomUUID(); const patient = await repo.createResource({ resourceType: 'Patient', name: [{ family }] }); - const resources = await repo.searchResources(parseSearchDefinition('Patient?family=' + family)); + const resources = await repo.searchResources(parseSearchRequest('Patient?family=' + family)); expect(resources).toHaveLength(1); expect(resources[0].id).toBe(patient.id); - const emptyResources = await repo.searchResources(parseSearchDefinition('Patient?family=' + randomUUID())); + const emptyResources = await repo.searchResources(parseSearchRequest('Patient?family=' + randomUUID())); expect(emptyResources).toHaveLength(0); }); test('searchOne helper', async () => { const family = randomUUID(); const patient = await repo.createResource({ resourceType: 'Patient', name: [{ family }] }); - const resource = await repo.searchOne(parseSearchDefinition('Patient?family=' + family)); + const resource = await repo.searchOne(parseSearchRequest('Patient?family=' + family)); expect(resource?.id).toBe(patient.id); - const emptyResource = await repo.searchOne(parseSearchDefinition('Patient?family=' + randomUUID())); + const emptyResource = await repo.searchOne(parseSearchRequest('Patient?family=' + randomUUID())); expect(emptyResource).toBeUndefined(); }); diff --git a/packages/fhirtypes/dist/Project.d.ts b/packages/fhirtypes/dist/Project.d.ts index f4c0cce598..6b3e2fd52e 100644 --- a/packages/fhirtypes/dist/Project.d.ts +++ b/packages/fhirtypes/dist/Project.d.ts @@ -4,6 +4,7 @@ */ import { AccessPolicy } from './AccessPolicy'; +import { Identifier } from './Identifier'; import { Meta } from './Meta'; import { Reference } from './Reference'; import { User } from './User'; @@ -44,6 +45,11 @@ export interface Project { */ language?: string; + /** + * An identifier for this project. + */ + identifier?: Identifier[]; + /** * A name associated with the Project. */ @@ -80,7 +86,7 @@ export interface Project { /** * A list of optional features that are enabled for the project. */ - features?: ('bots' | 'cron' | 'email' | 'google-auth-required' | 'graphql-introspection')[]; + features?: ('bots' | 'cron' | 'email' | 'google-auth-required' | 'graphql-introspection' | 'terminology' | 'websocket-subscriptions')[]; /** * The default access policy for patients using open registration. @@ -97,6 +103,22 @@ export interface Project { * Web application or web site that is associated with the project. */ site?: ProjectSite[]; + + /** + * Linked Projects whose contents are made available to this one + */ + link?: ProjectLink[]; +} + +/** + * Linked Projects whose contents are made available to this one + */ +export interface ProjectLink { + + /** + * A reference to the Project to be linked into this one + */ + project: Reference; } /** diff --git a/packages/fhirtypes/dist/User.d.ts b/packages/fhirtypes/dist/User.d.ts index 0987b05198..fde151c585 100644 --- a/packages/fhirtypes/dist/User.d.ts +++ b/packages/fhirtypes/dist/User.d.ts @@ -3,6 +3,7 @@ * Do not edit manually. */ +import { Identifier } from './Identifier'; import { Meta } from './Meta'; import { Project } from './Project'; import { Reference } from './Reference'; @@ -43,6 +44,11 @@ export interface User { */ language?: string; + /** + * An identifier for this user. + */ + identifier?: Identifier[]; + /** * The first name or given name of the user. This is the value as entered * when the user is created. It is used to populate the profile resource. diff --git a/packages/fhirtypes/package.json b/packages/fhirtypes/package.json index e3ad91c34e..baefb32c3c 100644 --- a/packages/fhirtypes/package.json +++ b/packages/fhirtypes/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/fhirtypes", - "version": "3.0.2", + "version": "3.0.4", "description": "Medplum FHIR Type Definitions", "keywords": [ "medplum", diff --git a/packages/generator/package.json b/packages/generator/package.json index e5e787d9e6..ef40366dd1 100644 --- a/packages/generator/package.json +++ b/packages/generator/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/generator", - "version": "3.0.2", + "version": "3.0.4", "description": "Medplum Code Generator", "homepage": "https://www.medplum.com/", "repository": { @@ -21,14 +21,14 @@ "test": "jest" }, "devDependencies": { - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", "@types/json-schema": "7.0.15", "@types/pg": "8.11.0", "@types/unzipper": "0.10.9", - "fast-xml-parser": "4.3.3", - "fhirpath": "3.10.0", + "fast-xml-parser": "4.3.4", + "fhirpath": "3.10.1", "mkdirp": "3.0.1", "node-stream-zip": "1.15.0", "pg": "8.11.3", diff --git a/packages/generator/src/storybook.ts b/packages/generator/src/storybook.ts index ecd6b869b0..90a6f4d353 100644 --- a/packages/generator/src/storybook.ts +++ b/packages/generator/src/storybook.ts @@ -83,6 +83,8 @@ const USCoreStructureDefinitionFiles = [ 'StructureDefinition-us-core-birthsex.json', 'StructureDefinition-us-core-genderIdentity.json', 'StructureDefinition-us-core-implantable-device.json', + 'StructureDefinition-us-core-blood-pressure.json', + 'StructureDefinition-us-core-medicationrequest.json', ]; const BUILD_USCORE = false; @@ -95,10 +97,10 @@ export function main(): void { // To build USCore, download and expand a USCore Implementation Guide package file, // such as https://hl7.org/fhir/us/core/STU5.0.1/package.tgz which is linked to // from https://hl7.org/fhir/us/core/STU5.0.1/downloads.html - buildUSCoreStructureDefinitions( - '/absolute/path/to/expanded/package-file', - resolve(__dirname, '../../mock/src/mocks/uscore/uscore-v5.0.1-structuredefinitions.json') - ); + buildUSCoreStructureDefinitions('/absolute/path/to/expanded/package-file', [ + resolve(__dirname, '../../mock/src/mocks/uscore/uscore-v5.0.1-structuredefinitions.json'), + resolve(__dirname, '../../definitions/dist/fhir/r4/testing/uscore-v5.0.1-structuredefinitions.json'), + ]); } } @@ -167,12 +169,14 @@ function cleanStructureDefinition(sd: StructureDefinition): void { } } -function buildUSCoreStructureDefinitions(inputDirectory: string, outputFilename: string): void { +function buildUSCoreStructureDefinitions(inputDirectory: string, outputFilenames: string[]): void { const sds = []; for (const file of USCoreStructureDefinitionFiles) { const sd = JSON.parse(readFileSync(resolve(inputDirectory, file), 'utf8')); cleanStructureDefinition(sd); sds.push(sd); } - writeFileSync(outputFilename, JSON.stringify(sds)); + for (const outputFilename of outputFilenames) { + writeFileSync(outputFilename, JSON.stringify(sds)); + } } diff --git a/packages/graphiql/package.json b/packages/graphiql/package.json index bf8389c367..adfdeab027 100644 --- a/packages/graphiql/package.json +++ b/packages/graphiql/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/graphiql", - "version": "3.0.2", + "version": "3.0.4", "description": "Medplum GraphiQL", "homepage": "https://www.medplum.com/", "bugs": { @@ -23,23 +23,23 @@ "last 1 Chrome versions" ], "devDependencies": { - "@graphiql/react": "0.20.2", + "@graphiql/react": "0.20.3", "@graphiql/toolkit": "0.9.1", - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@medplum/core": "*", - "@medplum/fhirtypes": "*", - "@medplum/react": "*", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", - "graphiql": "3.1.0", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/react": "3.0.4", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", + "graphiql": "3.1.1", "graphql": "16.8.1", - "graphql-ws": "5.14.3", - "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "graphql-ws": "5.15.0", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", - "vite": "5.0.12" + "vite": "5.1.3" }, "engines": { "node": ">=18.0.0" diff --git a/packages/health-gorilla/package.json b/packages/health-gorilla/package.json index 0f0a235785..61d9d6027d 100644 --- a/packages/health-gorilla/package.json +++ b/packages/health-gorilla/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/health-gorilla", - "version": "3.0.2", + "version": "3.0.4", "description": "Medplum Health Gorilla SDK", "homepage": "https://www.medplum.com/", "bugs": { @@ -39,7 +39,7 @@ "test": "jest" }, "dependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "@medplum/fhirtypes": "*" }, "devDependencies": { diff --git a/packages/hl7/package.json b/packages/hl7/package.json index 5c199c8c48..e69bf8bb48 100644 --- a/packages/hl7/package.json +++ b/packages/hl7/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/hl7", - "version": "3.0.2", + "version": "3.0.4", "description": "Medplum HL7 Utilities", "keywords": [ "medplum", diff --git a/packages/mock/package.json b/packages/mock/package.json index a9729dc7f9..7418bfbad4 100644 --- a/packages/mock/package.json +++ b/packages/mock/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/mock", - "version": "3.0.2", + "version": "3.0.4", "description": "Medplum Mock Client", "keywords": [ "medplum", @@ -53,15 +53,16 @@ "test": "jest" }, "dependencies": { - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhir-router": "*", - "@medplum/fhirtypes": "*", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhir-router": "3.0.4", + "@medplum/fhirtypes": "3.0.4", "dataloader": "2.2.2", + "jest-websocket-mock": "2.5.0", "rfc6902": "5.1.1" }, "devDependencies": { - "@types/pdfmake": "0.2.8" + "@types/pdfmake": "0.2.9" }, "engines": { "node": ">=18.0.0" diff --git a/packages/mock/src/client.test.ts b/packages/mock/src/client.test.ts index 353506e859..63a620a0bc 100644 --- a/packages/mock/src/client.test.ts +++ b/packages/mock/src/client.test.ts @@ -7,17 +7,19 @@ import { NewProjectRequest, NewUserRequest, OperationOutcomeError, + SubscriptionEmitter, allOk, getReferenceString, indexSearchParameterBundle, indexStructureDefinitionBundle, } from '@medplum/core'; import { readJson } from '@medplum/definitions'; -import { Bundle, CodeableConcept, Patient, SearchParameter, ServiceRequest } from '@medplum/fhirtypes'; +import { Agent, Bundle, CodeableConcept, Patient, SearchParameter, ServiceRequest } from '@medplum/fhirtypes'; import { randomUUID, webcrypto } from 'crypto'; import { TextEncoder } from 'util'; import { MockClient } from './client'; import { DrAliceSmith, DrAliceSmithSchedule, HomerSimpson } from './mocks'; +import { MockSubscriptionManager } from './subscription-manager'; describe('MockClient', () => { beforeAll(() => { @@ -669,6 +671,76 @@ describe('MockClient', () => { expect(homer.name[0].given[0]).toEqual('Homer'); expect(homer.name[0].family).toEqual('Simpson'); }); + + test('pushToAgent() -- Valid IP', async () => { + const medplum = new MockClient(); + const agent = await medplum.createResource({ resourceType: 'Agent', status: 'active', name: 'Agente' }); + await expect(medplum.pushToAgent(agent, '8.8.8.8', 'PING', ContentType.PING, true)).resolves.toMatch( + /8.8.8.8 ping statistics/ + ); + }); + + test('pushToAgent() - Valid IP other than 8.8.8.8', async () => { + const medplum = new MockClient(); + const agent = await medplum.createResource({ resourceType: 'Agent', status: 'active', name: 'Agente' }); + const oldWarn = console.warn; + console.warn = jest.fn(); + await expect(medplum.pushToAgent(agent, '127.0.0.1', 'PING', ContentType.PING, true)).rejects.toThrow( + OperationOutcomeError + ); + expect(console.warn).toHaveBeenCalled(); + console.warn = oldWarn; + }); + + test('pushToAgent() -- Invalid IP', async () => { + const medplum = new MockClient(); + const agent = await medplum.createResource({ resourceType: 'Agent', status: 'active', name: 'Agente' }); + await expect(medplum.pushToAgent(agent, 'abc123', 'PING', ContentType.PING, true)).rejects.toThrow( + OperationOutcomeError + ); + }); + + test('pushToAgent() -- Agent Timeout', async () => { + const medplum = new MockClient(); + const agent = await medplum.createResource({ resourceType: 'Agent', status: 'active', name: 'Agente' }); + await expect(medplum.pushToAgent(agent, '8.8.8.8', 'PING', ContentType.PING, true)).resolves.toBeDefined(); + medplum.setAgentAvailable(false); + await expect(medplum.pushToAgent(agent, '8.8.8.8', 'PING', ContentType.PING, true)).rejects.toThrow( + OperationOutcomeError + ); + medplum.setAgentAvailable(true); + await expect(medplum.pushToAgent(agent, '8.8.8.8', 'PING', ContentType.PING, true)).resolves.toBeDefined(); + }); + + test('getSubscriptionManager()', () => { + const medplum = new MockClient(); + expect(medplum.getSubscriptionManager()).toBeInstanceOf(MockSubscriptionManager); + }); + + test('getMasterSubscriptionEmitter()', () => { + const medplum = new MockClient(); + expect(medplum.getMasterSubscriptionEmitter()).toBeInstanceOf(SubscriptionEmitter); + }); + + test('subscribeToCriteria()', () => { + const medplum = new MockClient(); + const emitter1 = medplum.subscribeToCriteria('Communication'); + expect(emitter1).toBeInstanceOf(SubscriptionEmitter); + const emitter2 = medplum.subscribeToCriteria('Communication'); + expect(emitter1).toEqual(emitter2); + }); + + test('unsubscribeFromCriteria()', () => { + const medplum = new MockClient(); + + medplum.subscribeToCriteria('Communication'); + medplum.subscribeToCriteria('Communication'); + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(1); + + medplum.unsubscribeFromCriteria('Communication'); + medplum.unsubscribeFromCriteria('Communication'); + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(0); + }); }); describe('MockAsyncClientStorage', () => { diff --git a/packages/mock/src/client.ts b/packages/mock/src/client.ts index 72f436e17f..a7d9088b9d 100644 --- a/packages/mock/src/client.ts +++ b/packages/mock/src/client.ts @@ -8,10 +8,21 @@ import { LoginState, MedplumClient, MedplumClientOptions, + OperationOutcomeError, ProfileResource, + SubscriptionEmitter, } from '@medplum/core'; import { FhirRequest, FhirRouter, HttpMethod, MemoryRepository } from '@medplum/fhir-router'; -import { Binary, Resource, SearchParameter, StructureDefinition, UserConfiguration } from '@medplum/fhirtypes'; +import { + Agent, + Binary, + Device, + Reference, + Resource, + SearchParameter, + StructureDefinition, + UserConfiguration, +} from '@medplum/fhirtypes'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment /** @ts-ignore */ import type { CustomTableLayout, TDocumentDefinitions, TFontDictionary } from 'pdfmake/interfaces'; @@ -21,7 +32,6 @@ import { DrAliceSmith, DrAliceSmithPreviousVersion, DrAliceSmithSchedule, - makeDrAliceSmithSlots, ExampleBot, ExampleClient, ExampleQuestionnaire, @@ -44,6 +54,7 @@ import { HomerSimpson, HomerSimpsonPreviousVersion, HomerSimpsonSpecimen, + makeDrAliceSmithSlots, TestOrganization, } from './mocks'; import { ExampleAccessPolicy, ExampleStatusValueSet, ExampleUserConfiguration } from './mocks/accesspolicy'; @@ -62,6 +73,7 @@ import { ExampleWorkflowTask2, ExampleWorkflowTask3, } from './mocks/workflow'; +import { MockSubscriptionManager } from './subscription-manager'; export interface MockClientOptions extends MedplumClientOptions { readonly debug?: boolean; @@ -78,7 +90,9 @@ export class MockClient extends MedplumClient { readonly client: MockFetchClient; readonly debug: boolean; activeLoginOverride?: LoginState; + private agentAvailable = true; private readonly profile: ReturnType; + subManager: MockSubscriptionManager | undefined; constructor(clientOptions?: MockClientOptions) { const router = new FhirRouter(); @@ -179,6 +193,62 @@ export class MockClient extends MedplumClient { url: 'https://example.com/binary/123', }; } + + async pushToAgent( + agent: Agent | Reference, + destination: Device | Reference | string, + body: any, + contentType?: string | undefined, + _waitForResponse?: boolean | undefined, + _options?: RequestInit | undefined + ): Promise { + if (contentType === ContentType.PING) { + if (!this.agentAvailable) { + throw new OperationOutcomeError(badRequest('Timeout')); + } + if (typeof destination === 'string' && destination !== '8.8.8.8') { + // Exception for test case + if (destination !== 'abc123') { + console.warn('IPs other than 8.8.8.8 will always throw an error in MockClient'); + } + throw new OperationOutcomeError(badRequest('Destination device not found')); + } + return `PING 8.8.8.8 (8.8.8.8): 56 data bytes +64 bytes from 8.8.8.8: icmp_seq=0 ttl=115 time=10.977 ms +64 bytes from 8.8.8.8: icmp_seq=1 ttl=115 time=13.037 ms +64 bytes from 8.8.8.8: icmp_seq=2 ttl=115 time=23.159 ms +64 bytes from 8.8.8.8: icmp_seq=3 ttl=115 time=12.725 ms + +--- 8.8.8.8 ping statistics --- +4 packets transmitted, 4 packets received, 0.0% packet loss +round-trip min/avg/max/stddev = 10.977/14.975/23.159/4.790 ms +`; + } + return undefined; + } + + setAgentAvailable(value: boolean): void { + this.agentAvailable = value; + } + + getSubscriptionManager(): MockSubscriptionManager { + if (!this.subManager) { + this.subManager = new MockSubscriptionManager(this, 'wss://example.com/ws/subscriptions-r4'); + } + return this.subManager; + } + + subscribeToCriteria(criteria: string): SubscriptionEmitter { + return this.getSubscriptionManager().addCriteria(criteria); + } + + unsubscribeFromCriteria(criteria: string): void { + this.getSubscriptionManager().removeCriteria(criteria); + } + + getMasterSubscriptionEmitter(): SubscriptionEmitter { + return this.getSubscriptionManager().getMasterEmitter(); + } } export class MockFetchClient { @@ -221,7 +291,11 @@ export class MockFetchClient { ok: true, status: response?.resourceType === 'OperationOutcome' ? getStatus(response) : 200, headers: { - get: () => ContentType.FHIR_JSON, + get(name: string): string | undefined { + return { + 'content-type': ContentType.FHIR_JSON, + }[name]; + }, } as unknown as Headers, blob: () => Promise.resolve(response), json: () => Promise.resolve(response), diff --git a/packages/mock/src/index.ts b/packages/mock/src/index.ts index 89a92906c0..c920c7dce7 100644 --- a/packages/mock/src/index.ts +++ b/packages/mock/src/index.ts @@ -1,3 +1,4 @@ export * from './client'; export * from './mocks'; +export * from './subscription-manager'; export * from './test-resources/fish-patient'; diff --git a/packages/mock/src/mocks/uscore/index.ts b/packages/mock/src/mocks/uscore/index.ts index a53c29d53b..3961fded92 100644 --- a/packages/mock/src/mocks/uscore/index.ts +++ b/packages/mock/src/mocks/uscore/index.ts @@ -1,9 +1,9 @@ -import { Device, Patient } from '@medplum/fhirtypes'; +import { Device, Patient, StructureDefinition } from '@medplum/fhirtypes'; import StructureDefinitionList from './uscore-v5.0.1-structuredefinitions.json'; import { HTTP_HL7_ORG, deepClone } from '@medplum/core'; import { HomerSimpson } from '../simpsons'; -export const USCoreStructureDefinitionList = StructureDefinitionList; +export const USCoreStructureDefinitionList = StructureDefinitionList as StructureDefinition[]; export const HomerSimpsonUSCorePatient: Patient = { ...deepClone(HomerSimpson), diff --git a/packages/mock/src/mocks/uscore/uscore-v5.0.1-structuredefinitions.json b/packages/mock/src/mocks/uscore/uscore-v5.0.1-structuredefinitions.json index a800f60960..41e4cba781 100644 --- a/packages/mock/src/mocks/uscore/uscore-v5.0.1-structuredefinitions.json +++ b/packages/mock/src/mocks/uscore/uscore-v5.0.1-structuredefinitions.json @@ -1 +1 @@ -[{"resourceType":"StructureDefinition","id":"us-core-patient","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient","version":"5.0.1","name":"USCorePatientProfile","title":"US Core Patient Profile","status":"active","experimental":false,"date":"2022-04-20T15:02:49-07:00","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"The US Core Patient Profile meets the U.S. Core Data for Interoperability (USCDI) v2 'Patient Demographics' requirements. This profile sets minimum expectations for the Patient resource to record, search, and fetch basic demographics and other administrative information about an individual patient. It identifies which core elements, extensions, vocabularies and value sets **SHALL** be present in the resource when using this profile to promote interoperability and adoption through common implementation. It identifies which core elements, extensions, vocabularies and value sets **SHALL** be present in the resource when using this profile. It provides the floor for standards development for specific uses cases.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"resource","abstract":false,"type":"Patient","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Patient","derivation":"constraint","snapshot":{"element":[{"id":"Patient","path":"Patient","short":"Information about an individual or animal receiving health care services","definition":"\\-","comment":"\\-","alias":["SubjectOfCare Client Resident"],"min":0,"max":"*","base":{"path":"Patient","min":0,"max":"*"},"constraint":[{"key":"dom-2","severity":"error","human":"If the resource is contained in another resource, it SHALL NOT contain nested Resources","expression":"contained.contained.empty()","xpath":"not(parent::f:contained and f:contained)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-3","severity":"error","human":"If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource","expression":"contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()","xpath":"not(exists(for $id in f:contained/*/f:id/@value return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-4","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated","expression":"contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()","xpath":"not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-5","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a security label","expression":"contained.meta.security.empty()","xpath":"not(exists(f:contained/*/f:meta/f:security))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice","valueBoolean":true},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice-explanation","valueMarkdown":"When a resource has no narrative, only systems that fully understand the data can display the resource to a human safely. Including a human readable representation in the resource makes for a much more robust eco-system and cheaper handling of resources by intermediary systems. Some ecosystems restrict distribution of resources to only those systems that do fully understand the resources, and as a consequence implementers may believe that the narrative is superfluous. However experience shows that such eco-systems often open up to new participants over time."}],"key":"dom-6","severity":"warning","human":"A resource should have narrative for robust management","expression":"text.`div`.exists()","xpath":"exists(f:text/h:div)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"us-core-6","severity":"error","human":"Either Patient.name.given and/or Patient.name.family SHALL be present or a Data Absent Reason Extension SHALL be present.","expression":"(name.family.exists() or name.given.exists()) xor extension.where(url='http://hl7.org/fhir/StructureDefinition/data-absent-reason').exists()","xpath":"(/f:name/f:extension/@url='http://hl7.org/fhir/StructureDefinition/data-absent-reason' and not(/f:name/f:family or /f:name/f:given)) or (not(/f:name/f:extension/@url='http://hl7.org/fhir/StructureDefinition/data-absent-reason') and (/f:name/f:family or /f:name/f:given))"}],"mustSupport":false,"isModifier":false,"isSummary":false},{"id":"Patient.id","path":"Patient.id","short":"Logical id of this artifact","definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.","comment":"The only time that a resource does not have an id is when it is being submitted to the server using a create operation.","min":0,"max":"1","base":{"path":"Resource.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":true},{"id":"Patient.meta","path":"Patient.meta","short":"Metadata about the resource","definition":"The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.","min":0,"max":"1","base":{"path":"Resource.meta","min":0,"max":"1"},"type":[{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.implicitRules","path":"Patient.implicitRules","short":"A set of rules under which this content was created","definition":"A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.","comment":"Asserting this rule set restricts the content to be only understood by a limited set of trading partners. This inherently limits the usefulness of the data in the long term. However, the existing health eco-system is highly fractured, and not yet ready to define, collect, and exchange data in a generally computable sense. Wherever possible, implementers and/or specification writers should avoid using this element. Often, when used, the URL is a reference to an implementation guide that defines these special rules as part of it's narrative along with other profiles, value sets, etc.","min":0,"max":"1","base":{"path":"Resource.implicitRules","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because the implicit rules may provide additional knowledge about the resource that modifies it's meaning or interpretation","isSummary":true},{"id":"Patient.language","path":"Patient.language","short":"Language of the resource content","definition":"The base language in which the resource is written.","comment":"Language is provided to support indexing and accessibility (typically, services such as text to speech use the language tag). The html language tag in the narrative applies to the narrative. The language tag on the resource may be used to specify the language of other presentations generated from the data in the resource. Not all the content has to be in the base language. The Resource.language should not be assumed to apply to the narrative automatically. If a language is specified, it should it also be specified on the div element in the html (see rules in HTML5 for information about the relationship between xml:lang and the html lang attribute).","min":0,"max":"1","base":{"path":"Resource.language","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/all-languages"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Language"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"preferred","description":"A human language.","valueSet":"http://hl7.org/fhir/ValueSet/languages"}},{"id":"Patient.text","path":"Patient.text","short":"Text summary of the resource, for human interpretation","definition":"A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it \"clinically safe\" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety.","comment":"Contained resources do not have narrative. Resources that are not contained SHOULD have a narrative. In some cases, a resource may only have text with little or no additional discrete data (as long as all minOccurs=1 elements are satisfied). This may be necessary for data from legacy systems where information is captured as a \"text blob\" or where text is additionally entered raw or narrated and encoded information is added later.","alias":["narrative","html","xhtml","display"],"min":0,"max":"1","base":{"path":"DomainResource.text","min":0,"max":"1"},"type":[{"code":"Narrative"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contained","path":"Patient.contained","short":"Contained, inline Resources","definition":"These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope.","comment":"This should never be done when the content can be identified properly, as once identification is lost, it is extremely difficult (and context dependent) to restore it again. Contained resources may have profiles and tags In their meta elements, but SHALL NOT have security labels.","alias":["inline resources","anonymous resources","contained resources"],"min":0,"max":"*","base":{"path":"DomainResource.contained","min":0,"max":"*"},"type":[{"code":"Resource"}],"isModifier":false,"isSummary":false},{"id":"Patient.extension","path":"Patient.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"ordered":false,"rules":"open"},"short":"Extension","definition":"An Extension","min":0,"max":"*","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.extension:race","path":"Patient.extension","sliceName":"race","short":"US Core Race Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The race codes used to represent these concepts are based upon the [CDC Race and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 921 reference race. The race concepts are grouped by and pre-mapped to the 5 OMB race categories:\n\n - American Indian or Alaska Native\n - Asian\n - Black or African American\n - Native Hawaiian or Other Pacific Islander\n - White.","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-race"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.extension:ethnicity","path":"Patient.extension","sliceName":"ethnicity","short":"US Core ethnicity Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The ethnicity codes used to represent these concepts are based upon the [CDC ethnicity and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 43 reference ethnicity. The ethnicity concepts are grouped by and pre-mapped to the 2 OMB ethnicity categories: - Hispanic or Latino - Not Hispanic or Latino.","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.extension:birthsex","path":"Patient.extension","sliceName":"birthsex","short":"Extension","definition":"A code classifying the person's sex assigned at birth as specified by the [Office of the National Coordinator for Health IT (ONC)](https://www.healthit.gov/newsroom/about-onc).","comment":"The codes required are intended to present birth sex (i.e., the sex recorded on the patient’s birth certificate) and not gender identity or reassigned sex.","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.extension:genderIdentity","path":"Patient.extension","sliceName":"genderIdentity","short":"Extension","definition":"An Extension","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.modifierExtension","path":"Patient.modifierExtension","short":"Extensions that cannot be ignored","definition":"May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the resource that contains them","isSummary":false},{"id":"Patient.identifier","path":"Patient.identifier","short":"An identifier for this patient","definition":"An identifier for this patient.","requirements":"Patients are almost always assigned specific numerical identifiers.","min":1,"max":"*","base":{"path":"Patient.identifier","min":0,"max":"*"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.identifier.id","path":"Patient.identifier.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.identifier.extension","path":"Patient.identifier.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.identifier.use","path":"Patient.identifier.use","short":"usual | official | temp | secondary | old (If known)","definition":"The purpose of this identifier.","comment":"Applications can assume that an identifier is permanent unless it explicitly says that it is temporary.","requirements":"Allows the appropriate identifier for a particular context of use to be selected from among a set of identifiers.","min":0,"max":"1","base":{"path":"Identifier.use","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary id for a permanent one.","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"IdentifierUse"}],"strength":"required","description":"Identifies the purpose for this identifier, if known .","valueSet":"http://hl7.org/fhir/ValueSet/identifier-use|4.0.1"}},{"id":"Patient.identifier.type","path":"Patient.identifier.type","short":"Description of identifier","definition":"A coded type for the identifier that can be used to determine which identifier to use for a specific purpose.","comment":"This element deals only with general categories of identifiers. It SHOULD not be used for codes that correspond 1..1 with the Identifier.system. Some identifiers may fall into multiple categories due to common usage. Where the system is known, a type is unnecessary because the type is always part of the system definition. However systems often need to handle identifiers where the system is not known. There is not a 1:1 relationship between type and system, since many different systems have the same type.","requirements":"Allows users to make use of identifiers when the identifier system is not known.","min":0,"max":"1","base":{"path":"Identifier.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"IdentifierType"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"extensible","description":"A coded type for an identifier that can be used to determine which identifier to use for a specific purpose.","valueSet":"http://hl7.org/fhir/ValueSet/identifier-type"}},{"id":"Patient.identifier.system","path":"Patient.identifier.system","short":"The namespace for the identifier value","definition":"Establishes the namespace for the value - that is, a URL that describes a set values that are unique.","comment":"Identifier.system is always case sensitive.","requirements":"There are many sets of identifiers. To perform matching of two identifiers, we need to know what set we're dealing with. The system identifies a particular set of unique identifiers.","min":1,"max":"1","base":{"path":"Identifier.system","min":0,"max":"1"},"type":[{"code":"uri"}],"example":[{"label":"General","valueUri":"http://www.acme.com/identifiers/patient"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.identifier.value","path":"Patient.identifier.value","short":"The value that is unique within the system.","definition":"The portion of the identifier typically relevant to the user and which is unique within the context of the system.","comment":"If the value is a full URI, then the system SHALL be urn:ietf:rfc:3986. The value's primary purpose is computational mapping. As a result, it may be normalized for comparison purposes (e.g. removing non-significant whitespace, dashes, etc.) A value formatted for human display can be conveyed using the [Rendered Value extension](http://hl7.org/fhir/R4/extension-rendered-value.html). Identifier.value is to be treated as case sensitive unless knowledge of the Identifier.system allows the processer to be confident that non-case-sensitive processing is safe.","min":1,"max":"1","base":{"path":"Identifier.value","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"123456"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.identifier.period","path":"Patient.identifier.period","short":"Time period when id is/was valid for use","definition":"Time period during which identifier is/was valid for use.","min":0,"max":"1","base":{"path":"Identifier.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.identifier.assigner","path":"Patient.identifier.assigner","short":"Organization that issued id (may be just text)","definition":"Organization that issued/manages the identifier.","comment":"The Identifier.assigner may omit the .reference element and only contain a .display element reflecting the name or other textual information about the assigning organization.","min":0,"max":"1","base":{"path":"Identifier.assigner","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.active","path":"Patient.active","short":"Whether this patient's record is in active use","definition":"Whether this patient record is in active use. \nMany systems use this property to mark as non-current patients, such as those that have not been seen for a period of time based on an organization's business rules.\n\nIt is often used to filter patient lists to exclude inactive patients\n\nDeceased patients may also be marked as inactive for the same reasons, but may be active for some time after death.","comment":"If a record is inactive, and linked to an active record, then future patient/record updates should occur on the other patient.","requirements":"Need to be able to mark a patient record as not to be used because it was created in error.","min":0,"max":"1","base":{"path":"Patient.active","min":0,"max":"1"},"type":[{"code":"boolean"}],"meaningWhenMissing":"This resource is generally assumed to be active if no value is provided for the active element","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labelled as a modifier because it is a status element that can indicate that a record should not be treated as valid","isSummary":true},{"id":"Patient.name","path":"Patient.name","short":"A name associated with the patient","definition":"A name associated with the individual.","comment":"A patient may have multiple names with different uses or applicable periods. For animals, the name is a \"HumanName\" in the sense that is assigned and used by humans and has the same patterns.","requirements":"Need to be able to track the patient by multiple names. Examples are your official name and a partner name.","min":1,"max":"*","base":{"path":"Patient.name","min":0,"max":"*"},"type":[{"code":"HumanName"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.name.id","path":"Patient.name.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.name.extension","path":"Patient.name.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.name.use","path":"Patient.name.use","short":"usual | official | temp | nickname | anonymous | old | maiden","definition":"Identifies the purpose for this name.","comment":"Applications can assume that a name is current unless it explicitly says that it is temporary or old.","requirements":"Allows the appropriate name for a particular context of use to be selected from among a set of names.","min":0,"max":"1","base":{"path":"HumanName.use","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary or old name etc.for a current/permanent one","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"NameUse"}],"strength":"required","description":"The use of a human name.","valueSet":"http://hl7.org/fhir/ValueSet/name-use|4.0.1"}},{"id":"Patient.name.text","path":"Patient.name.text","short":"Text representation of the full name","definition":"Specifies the entire name as it should be displayed e.g. on an application UI. This may be provided instead of or as well as the specific parts.","comment":"Can provide both a text representation and parts. Applications updating a name SHALL ensure that when both text and parts are present, no content is included in the text that isn't found in a part.","requirements":"A renderable, unencoded form.","min":0,"max":"1","base":{"path":"HumanName.text","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.name.family","path":"Patient.name.family","short":"Family name (often called 'Surname')","definition":"The part of a name that links to the genealogy. In some cultures (e.g. Eritrea) the family name of a son is the first name of his father.","comment":"Family Name may be decomposed into specific parts using extensions (de, nl, es related cultures).","alias":["surname"],"min":0,"max":"1","base":{"path":"HumanName.family","min":0,"max":"1"},"type":[{"code":"string"}],"condition":["us-core-6"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.name.given","path":"Patient.name.given","short":"Given names (not always 'first'). Includes middle names","definition":"Given name.","comment":"If only initials are recorded, they may be used in place of the full name parts. Initials may be separated into multiple given names but often aren't due to paractical limitations. This element is not called \"first name\" since given names do not always come first.","alias":["first name","middle name"],"min":0,"max":"*","base":{"path":"HumanName.given","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"Given Names appear in the correct order for presenting the name","condition":["us-core-6"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.name.prefix","path":"Patient.name.prefix","short":"Parts that come before the name","definition":"Part of the name that is acquired as a title due to academic, legal, employment or nobility status, etc. and that appears at the start of the name.","min":0,"max":"*","base":{"path":"HumanName.prefix","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"Prefixes appear in the correct order for presenting the name","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.name.suffix","path":"Patient.name.suffix","short":"Parts that come after the name","definition":"Part of the name that is acquired as a title due to academic, legal, employment or nobility status, etc. and that appears at the end of the name.","min":0,"max":"*","base":{"path":"HumanName.suffix","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"Suffixes appear in the correct order for presenting the name","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":true},{"id":"Patient.name.period","path":"Patient.name.period","short":"Time period when name was/is in use","definition":"Indicates the period of time when this name was valid for the named person.","requirements":"Allows names to be placed in historical context.","min":0,"max":"1","base":{"path":"HumanName.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":true},{"id":"Patient.telecom","path":"Patient.telecom","short":"A contact detail for the individual","definition":"A contact detail (e.g. a telephone number or an email address) by which the individual may be contacted.","comment":"A Patient may have multiple ways to be contacted with different uses or applicable periods. May need to have options for contacting the person urgently and also to help with identification. The address might not go directly to the individual, but may reach another party that is able to proxy for the patient (i.e. home phone, or pet owner's phone).","requirements":"People have (primary) ways to contact them in some way such as phone, email.","min":0,"max":"*","base":{"path":"Patient.telecom","min":0,"max":"*"},"type":[{"code":"ContactPoint"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":true},{"id":"Patient.telecom.id","path":"Patient.telecom.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.telecom.extension","path":"Patient.telecom.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.telecom.system","path":"Patient.telecom.system","short":"phone | fax | email | pager | url | sms | other","definition":"Telecommunications form for contact point - what communications system is required to make use of the contact.","min":1,"max":"1","base":{"path":"ContactPoint.system","min":0,"max":"1"},"type":[{"code":"code"}],"condition":["cpt-2"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"required","description":"Telecommunications form for contact point.","valueSet":"http://hl7.org/fhir/ValueSet/contact-point-system"}},{"id":"Patient.telecom.value","path":"Patient.telecom.value","short":"The actual contact point details","definition":"The actual contact point details, in a form that is meaningful to the designated communication system (i.e. phone number or email address).","comment":"Additional text data such as phone extension numbers, or notes about use of the contact are sometimes included in the value.","requirements":"Need to support legacy numbers that are not in a tightly controlled format.","min":1,"max":"1","base":{"path":"ContactPoint.value","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.telecom.use","path":"Patient.telecom.use","short":"home | work | temp | old | mobile - purpose of this contact point","definition":"Identifies the purpose for the contact point.","comment":"Applications can assume that a contact is current unless it explicitly says that it is temporary or old.","requirements":"Need to track the way a person uses this contact, so a user can choose which is appropriate for their purpose.","min":0,"max":"1","base":{"path":"ContactPoint.use","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary or old contact etc.for a current/permanent one","isSummary":true,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/ValueSet/contact-point-use"}},{"id":"Patient.telecom.rank","path":"Patient.telecom.rank","short":"Specify preferred order of use (1 = highest)","definition":"Specifies a preferred order in which to use a set of contacts. ContactPoints with lower rank values are more preferred than those with higher rank values.","comment":"Note that rank does not necessarily follow the order in which the contacts are represented in the instance.","min":0,"max":"1","base":{"path":"ContactPoint.rank","min":0,"max":"1"},"type":[{"code":"positiveInt"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.telecom.period","path":"Patient.telecom.period","short":"Time period when the contact point was/is in use","definition":"Time period when the contact point was/is in use.","min":0,"max":"1","base":{"path":"ContactPoint.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.gender","path":"Patient.gender","short":"male | female | other | unknown","definition":"Administrative Gender - the gender that the patient is considered to have for administration and record keeping purposes.","comment":"The gender might not match the biological sex as determined by genetics or the individual's preferred identification. Note that for both humans and particularly animals, there are other legitimate possibilities than male and female, though the vast majority of systems and contexts only support male and female. Systems providing decision support or enforcing business rules should ideally do this on the basis of Observations dealing with the specific sex or gender aspect of interest (anatomical, chromosomal, social, etc.) However, because these observations are infrequently recorded, defaulting to the administrative gender is common practice. Where such defaulting occurs, rule enforcement should allow for the variation between administrative and biological, chromosomal and other gender aspects. For example, an alert about a hysterectomy on a male should be handled as a warning or overridable error, not a \"hard\" error. See the Patient Gender and Sex section for additional information about communicating patient gender and sex.","requirements":"Needed for identification of the individual, in combination with (at least) name and birth date.","min":1,"max":"1","base":{"path":"Patient.gender","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/ValueSet/administrative-gender"}},{"id":"Patient.birthDate","path":"Patient.birthDate","short":"The date of birth for the individual","definition":"The date of birth for the individual.","comment":"At least an estimated year should be provided as a guess if the real DOB is unknown There is a standard extension \"patient-birthTime\" available that should be used where Time is required (such as in maternity/infant care systems).","requirements":"Age of the individual drives many clinical processes.","min":0,"max":"1","base":{"path":"Patient.birthDate","min":0,"max":"1"},"type":[{"code":"date"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.deceased[x]","path":"Patient.deceased[x]","short":"Indicates if the individual is deceased or not","definition":"Indicates if the individual is deceased or not.","comment":"If there's no value in the instance, it means there is no statement on whether or not the individual is deceased. Most systems will interpret the absence of a value as a sign of the person being alive.","requirements":"The fact that a patient is deceased influences the clinical process. Also, in human communication and relation management it is necessary to know whether the person is alive.","min":0,"max":"1","base":{"path":"Patient.deceased[x]","min":0,"max":"1"},"type":[{"code":"boolean"},{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because once a patient is marked as deceased, the actions that are appropriate to perform on the patient may be significantly different.","isSummary":true},{"id":"Patient.address","path":"Patient.address","short":"An address for the individual","definition":"An address for the individual.","comment":"Patient may have multiple addresses with different uses or applicable periods.","requirements":"May need to keep track of patient addresses for contacting, billing or reporting requirements and also to help with identification.","min":0,"max":"*","base":{"path":"Patient.address","min":0,"max":"*"},"type":[{"code":"Address"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.id","path":"Patient.address.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.address.extension","path":"Patient.address.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.address.use","path":"Patient.address.use","short":"home | work | temp | old | billing - purpose of this address","definition":"The purpose of this address.","comment":"Applications can assume that an address is current unless it explicitly says that it is temporary or old.","requirements":"Allows an appropriate address to be chosen from a list of many.","min":0,"max":"1","base":{"path":"Address.use","min":0,"max":"1"},"type":[{"code":"code"}],"example":[{"label":"General","valueCode":"home"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary or old address etc.for a current/permanent one","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AddressUse"}],"strength":"required","description":"The use of an address.","valueSet":"http://hl7.org/fhir/ValueSet/address-use|4.0.1"}},{"id":"Patient.address.type","path":"Patient.address.type","short":"postal | physical | both","definition":"Distinguishes between physical addresses (those you can visit) and mailing addresses (e.g. PO Boxes and care-of addresses). Most addresses are both.","comment":"The definition of Address states that \"address is intended to describe postal addresses, not physical locations\". However, many applications track whether an address has a dual purpose of being a location that can be visited as well as being a valid delivery destination, and Postal addresses are often used as proxies for physical locations (also see the [Location](http://hl7.org/fhir/R4/location.html#) resource).","min":0,"max":"1","base":{"path":"Address.type","min":0,"max":"1"},"type":[{"code":"code"}],"example":[{"label":"General","valueCode":"both"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AddressType"}],"strength":"required","description":"The type of an address (physical / postal).","valueSet":"http://hl7.org/fhir/ValueSet/address-type|4.0.1"}},{"id":"Patient.address.text","path":"Patient.address.text","short":"Text representation of the address","definition":"Specifies the entire address as it should be displayed e.g. on a postal label. This may be provided instead of or as well as the specific parts.","comment":"Can provide both a text representation and parts. Applications updating an address SHALL ensure that when both text and parts are present, no content is included in the text that isn't found in a part.","requirements":"A renderable, unencoded form.","min":0,"max":"1","base":{"path":"Address.text","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"137 Nowhere Street, Erewhon 9132"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.address.line","path":"Patient.address.line","short":"Street name, number, direction & P.O. Box etc.","definition":"This component contains the house number, apartment number, street name, street direction, P.O. Box number, delivery hints, and similar address information.","min":0,"max":"*","base":{"path":"Address.line","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"The order in which lines should appear in an address label","example":[{"label":"General","valueString":"137 Nowhere Street"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.city","path":"Patient.address.city","short":"Name of city, town etc.","definition":"The name of the city, town, suburb, village or other community or delivery center.","alias":["Municpality"],"min":0,"max":"1","base":{"path":"Address.city","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"Erewhon"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.district","path":"Patient.address.district","short":"District name (aka county)","definition":"The name of the administrative area (county).","comment":"District is sometimes known as county, but in some regions 'county' is used in place of city (municipality), so county name should be conveyed in city instead.","alias":["County"],"min":0,"max":"1","base":{"path":"Address.district","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"Madison"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.address.state","path":"Patient.address.state","short":"Sub-unit of country (abbreviations ok)","definition":"Sub-unit of a country with limited sovereignty in a federally organized country. A code may be used if codes are in common use (e.g. US 2 letter state codes).","alias":["Province","Territory"],"min":0,"max":"1","base":{"path":"Address.state","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"Two Letter USPS alphabetic codes.","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-usps-state"}},{"id":"Patient.address.postalCode","path":"Patient.address.postalCode","short":"US Zip Codes","definition":"A postal code designating a region defined by the postal service.","alias":["Zip","Zip Code"],"min":0,"max":"1","base":{"path":"Address.postalCode","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"9132"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.country","path":"Patient.address.country","short":"Country (e.g. can be ISO 3166 2 or 3 letter code)","definition":"Country - a nation as commonly understood or generally accepted.","comment":"ISO 3166 3 letter codes can be used in place of a human readable country name.","min":0,"max":"1","base":{"path":"Address.country","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.address.period","path":"Patient.address.period","short":"Time period when address was/is in use","definition":"Time period when address was/is in use.","requirements":"Allows addresses to be placed in historical context.","min":0,"max":"1","base":{"path":"Address.period","min":0,"max":"1"},"type":[{"code":"Period"}],"example":[{"label":"General","valuePeriod":{"start":"2010-03-23","end":"2010-07-01"}}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.maritalStatus","path":"Patient.maritalStatus","short":"Marital (civil) status of a patient","definition":"This field contains a patient's most recent marital (civil) status.","requirements":"Most, if not all systems capture it.","min":0,"max":"1","base":{"path":"Patient.maritalStatus","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MaritalStatus"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"extensible","description":"The domestic partnership status of a person.","valueSet":"http://hl7.org/fhir/ValueSet/marital-status"}},{"id":"Patient.multipleBirth[x]","path":"Patient.multipleBirth[x]","short":"Whether patient is part of a multiple birth","definition":"Indicates whether the patient is part of a multiple (boolean) or indicates the actual birth order (integer).","comment":"Where the valueInteger is provided, the number is the birth number in the sequence. E.g. The middle birth in triplets would be valueInteger=2 and the third born would have valueInteger=3 If a boolean value was provided for this triplets example, then all 3 patient records would have valueBoolean=true (the ordering is not indicated).","requirements":"For disambiguation of multiple-birth children, especially relevant where the care provider doesn't meet the patient, such as labs.","min":0,"max":"1","base":{"path":"Patient.multipleBirth[x]","min":0,"max":"1"},"type":[{"code":"boolean"},{"code":"integer"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.photo","path":"Patient.photo","short":"Image of the patient","definition":"Image of the patient.","comment":"Guidelines:\n* Use id photos, not clinical photos.\n* Limit dimensions to thumbnail.\n* Keep byte count low to ease resource updates.","requirements":"Many EHR systems have the capability to capture an image of the patient. Fits with newer social media usage too.","min":0,"max":"*","base":{"path":"Patient.photo","min":0,"max":"*"},"type":[{"code":"Attachment"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name","valueString":"Contact"}],"path":"Patient.contact","short":"A contact party (e.g. guardian, partner, friend) for the patient","definition":"A contact party (e.g. guardian, partner, friend) for the patient.","comment":"Contact covers all kinds of contact parties: family members, business contacts, guardians, caregivers. Not applicable to register pedigree and family ties beyond use of having contact.","requirements":"Need to track people you can contact about the patient.","min":0,"max":"*","base":{"path":"Patient.contact","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"pat-1","severity":"error","human":"SHALL at least contain a contact's details or a reference to an organization","expression":"name.exists() or telecom.exists() or address.exists() or organization.exists()","xpath":"exists(f:name) or exists(f:telecom) or exists(f:address) or exists(f:organization)","source":"http://hl7.org/fhir/StructureDefinition/Patient"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.id","path":"Patient.contact.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.extension","path":"Patient.contact.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.modifierExtension","path":"Patient.contact.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Patient.contact.relationship","path":"Patient.contact.relationship","short":"The kind of relationship","definition":"The nature of the relationship between the patient and the contact person.","requirements":"Used to determine which contact person is the most relevant to approach, depending on circumstances.","min":0,"max":"*","base":{"path":"Patient.contact.relationship","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ContactRelationship"}],"strength":"extensible","description":"The nature of the relationship between a patient and a contact person for that patient.","valueSet":"http://hl7.org/fhir/ValueSet/patient-contactrelationship"}},{"id":"Patient.contact.name","path":"Patient.contact.name","short":"A name associated with the contact person","definition":"A name associated with the contact person.","requirements":"Contact persons need to be identified by name, but it is uncommon to need details about multiple other names for that contact person.","min":0,"max":"1","base":{"path":"Patient.contact.name","min":0,"max":"1"},"type":[{"code":"HumanName"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.telecom","path":"Patient.contact.telecom","short":"A contact detail for the person","definition":"A contact detail for the person, e.g. a telephone number or an email address.","comment":"Contact may have multiple ways to be contacted with different uses or applicable periods. May need to have options for contacting the person urgently, and also to help with identification.","requirements":"People have (primary) ways to contact them in some way such as phone, email.","min":0,"max":"*","base":{"path":"Patient.contact.telecom","min":0,"max":"*"},"type":[{"code":"ContactPoint"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.address","path":"Patient.contact.address","short":"Address for the contact person","definition":"Address for the contact person.","requirements":"Need to keep track where the contact person can be contacted per postal mail or visited.","min":0,"max":"1","base":{"path":"Patient.contact.address","min":0,"max":"1"},"type":[{"code":"Address"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.gender","path":"Patient.contact.gender","short":"male | female | other | unknown","definition":"Administrative Gender - the gender that the contact person is considered to have for administration and record keeping purposes.","requirements":"Needed to address the person correctly.","min":0,"max":"1","base":{"path":"Patient.contact.gender","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AdministrativeGender"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"required","description":"The gender of a person used for administrative purposes.","valueSet":"http://hl7.org/fhir/ValueSet/administrative-gender|4.0.1"}},{"id":"Patient.contact.organization","path":"Patient.contact.organization","short":"Organization that is associated with the contact","definition":"Organization on behalf of which the contact is acting or for which the contact is working.","requirements":"For guardians or business related contacts, the organization is relevant.","min":0,"max":"1","base":{"path":"Patient.contact.organization","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"condition":["pat-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.period","path":"Patient.contact.period","short":"The period during which this contact person or organization is valid to be contacted relating to this patient","definition":"The period during which this contact person or organization is valid to be contacted relating to this patient.","min":0,"max":"1","base":{"path":"Patient.contact.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.communication","path":"Patient.communication","short":"A language which may be used to communicate with the patient about his or her health","definition":"A language which may be used to communicate with the patient about his or her health.","comment":"If no language is specified, this *implies* that the default local language is spoken. If you need to convey proficiency for multiple modes, then you need multiple Patient.Communication associations. For animals, language is not a relevant field, and should be absent from the instance. If the Patient does not speak the default local language, then the Interpreter Required Standard can be used to explicitly declare that an interpreter is required.","requirements":"If a patient does not speak the local language, interpreters may be required, so languages spoken and proficiency are important things to keep track of both for patient and other persons of interest.","min":0,"max":"*","base":{"path":"Patient.communication","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":false},{"id":"Patient.communication.id","path":"Patient.communication.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.communication.extension","path":"Patient.communication.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.communication.modifierExtension","path":"Patient.communication.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Patient.communication.language","path":"Patient.communication.language","short":"The language which can be used to communicate with the patient about his or her health","definition":"The ISO-639-1 alpha 2 code in lower case for the language, optionally followed by a hyphen and the ISO-3166-1 alpha 2 code for the region in upper case; e.g. \"en\" for English, or \"en-US\" for American English versus \"en-EN\" for England English.","comment":"The structure aa-BB with this exact casing is one the most widely used notations for locale. However not all systems actually code this but instead have it as free text. Hence CodeableConcept instead of code as the data type.","requirements":"Most systems in multilingual countries will want to convey language. Not all systems actually need the regional dialect.","min":1,"max":"1","base":{"path":"Patient.communication.language","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"strength":"extensible","valueSet":"http://hl7.org/fhir/us/core/ValueSet/simple-language"}},{"id":"Patient.communication.preferred","path":"Patient.communication.preferred","short":"Language preference indicator","definition":"Indicates whether or not the patient prefers this language (over other languages he masters up a certain level).","comment":"This language is specifically identified for communicating healthcare information.","requirements":"People that master multiple languages up to certain level may prefer one or more, i.e. feel more confident in communicating in a particular language making other languages sort of a fall back method.","min":0,"max":"1","base":{"path":"Patient.communication.preferred","min":0,"max":"1"},"type":[{"code":"boolean"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.generalPractitioner","path":"Patient.generalPractitioner","short":"Patient's nominated primary care provider","definition":"Patient's nominated care provider.","comment":"This may be the primary care provider (in a GP context), or it may be a patient nominated care manager in a community/disability setting, or even organization that will provide people to perform the care provider roles. It is not to be used to record Care Teams, these should be in a CareTeam resource that may be linked to the CarePlan or EpisodeOfCare resources.\nMultiple GPs may be recorded against the patient for various reasons, such as a student that has his home GP listed along with the GP at university during the school semesters, or a \"fly-in/fly-out\" worker that has the onsite GP also included with his home GP to remain aware of medical issues.\n\nJurisdictions may decide that they can profile this down to 1 if desired, or 1 per type.","alias":["careProvider"],"min":0,"max":"*","base":{"path":"Patient.generalPractitioner","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization","http://hl7.org/fhir/StructureDefinition/Practitioner","http://hl7.org/fhir/StructureDefinition/PractitionerRole"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.managingOrganization","path":"Patient.managingOrganization","short":"Organization that is the custodian of the patient record","definition":"Organization that is the custodian of the patient record.","comment":"There is only one managing organization for a specific patient record. Other organizations will have their own Patient record, and may use the Link property to join the records together (or a Person resource which can include confidence ratings for the association).","requirements":"Need to know who recognizes this patient record, manages and updates it.","min":0,"max":"1","base":{"path":"Patient.managingOrganization","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.link","path":"Patient.link","short":"Link to another patient resource that concerns the same actual person","definition":"Link to another patient resource that concerns the same actual patient.","comment":"There is no assumption that linked patient records have mutual links.","requirements":"There are multiple use cases: \n\n* Duplicate patient records due to the clerical errors associated with the difficulties of identifying humans consistently, and \n* Distribution of patient information across multiple servers.","min":0,"max":"*","base":{"path":"Patient.link","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because it might not be the main Patient resource, and the referenced patient should be used instead of this Patient record. This is when the link.type value is 'replaced-by'","isSummary":true},{"id":"Patient.link.id","path":"Patient.link.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.link.extension","path":"Patient.link.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.link.modifierExtension","path":"Patient.link.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Patient.link.other","path":"Patient.link.other","short":"The other patient or related person resource that the link refers to","definition":"The other patient resource that the link refers to.","comment":"Referencing a RelatedPerson here removes the need to use a Person record to associate a Patient and RelatedPerson as the same individual.","min":1,"max":"1","base":{"path":"Patient.link.other","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-hierarchy","valueBoolean":false}],"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Patient","http://hl7.org/fhir/StructureDefinition/RelatedPerson"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.link.type","path":"Patient.link.type","short":"replaced-by | replaces | refer | seealso","definition":"The type of link between this patient resource and another patient resource.","min":1,"max":"1","base":{"path":"Patient.link.type","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"LinkType"}],"strength":"required","description":"The type of link between this patient resource and another patient resource.","valueSet":"http://hl7.org/fhir/ValueSet/link-type|4.0.1"}}]}},{"resourceType":"StructureDefinition","id":"us-core-race","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","version":"5.0.1","name":"USCoreRaceExtension","title":"US Core Race Extension","status":"active","date":"2019-05-21","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The race codes used to represent these concepts are based upon the [CDC Race and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 921 reference race. The race concepts are grouped by and pre-mapped to the 5 OMB race categories:\n\n - American Indian or Alaska Native\n - Asian\n - Black or African American\n - Native Hawaiian or Other Pacific Islander\n - White.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"purpose":"Complies with 2015 Edition Common Clinical Data Set for patient race.","copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"},{"type":"element","expression":"RelatedPerson"},{"type":"element","expression":"Person"},{"type":"element","expression":"Practitioner"},{"type":"element","expression":"FamilyMemberHistory"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Extension","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"US Core Race Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The race codes used to represent these concepts are based upon the [CDC Race and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 921 reference race. The race concepts are grouped by and pre-mapped to the 5 OMB race categories:\n\n - American Indian or Alaska Native\n - Asian\n - Black or African American\n - Native Hawaiian or Other Pacific Islander\n - White.","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory","path":"Extension.extension","sliceName":"ombCategory","short":"American Indian or Alaska Native|Asian|Black or African American|Native Hawaiian or Other Pacific Islander|White","definition":"The 5 race category codes according to the [OMB Standards for Maintaining, Collecting, and Presenting Federal Data on Race and Ethnicity, Statistical Policy Directive No. 15, as revised, October 30, 1997](https://www.govinfo.gov/content/pkg/FR-1997-10-30/pdf/97-28653.pdf).","min":0,"max":"5","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"ombCategory","isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","description":"The 5 race category codes according to the [OMB Standards for Maintaining, Collecting, and Presenting Federal Data on Race and Ethnicity, Statistical Policy Directive No. 15, as revised, October 30, 1997](https://www.govinfo.gov/content/pkg/FR-1997-10-30/pdf/97-28653.pdf).","valueSet":"http://hl7.org/fhir/us/core/ValueSet/omb-race-category"}},{"id":"Extension.extension:detailed","path":"Extension.extension","sliceName":"detailed","short":"Extended race codes","definition":"The 900+ CDC race codes that are grouped under one of the 5 OMB race category codes:.","min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"detailed","isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/us/core/ValueSet/detailed-race"}},{"id":"Extension.extension:text","path":"Extension.extension","sliceName":"text","short":"Race Text","definition":"Plain text representation of the race concept(s).","min":1,"max":"1","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"text","isModifier":false,"isSummary":false},{"id":"Extension.extension:text.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":0,"max":"0","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"base64Binary"},{"code":"boolean"},{"code":"canonical"},{"code":"code"},{"code":"date"},{"code":"dateTime"},{"code":"decimal"},{"code":"id"},{"code":"instant"},{"code":"integer"},{"code":"markdown"},{"code":"oid"},{"code":"positiveInt"},{"code":"string"},{"code":"time"},{"code":"unsignedInt"},{"code":"uri"},{"code":"url"},{"code":"uuid"},{"code":"Address"},{"code":"Age"},{"code":"Annotation"},{"code":"Attachment"},{"code":"CodeableConcept"},{"code":"Coding"},{"code":"ContactPoint"},{"code":"Count"},{"code":"Distance"},{"code":"Duration"},{"code":"HumanName"},{"code":"Identifier"},{"code":"Money"},{"code":"Period"},{"code":"Quantity"},{"code":"Range"},{"code":"Ratio"},{"code":"Reference"},{"code":"SampledData"},{"code":"Signature"},{"code":"Timing"},{"code":"ContactDetail"},{"code":"Contributor"},{"code":"DataRequirement"},{"code":"Expression"},{"code":"ParameterDefinition"},{"code":"RelatedArtifact"},{"code":"TriggerDefinition"},{"code":"UsageContext"},{"code":"Dosage"},{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}},{"resourceType":"StructureDefinition","id":"us-core-ethnicity","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","version":"5.0.1","name":"USCoreEthnicityExtension","title":"US Core Ethnicity Extension","status":"active","date":"2019-05-21T00:00:00-04:00","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The ethnicity codes used to represent these concepts are based upon the [CDC ethnicity and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 43 reference ethnicity. The ethnicity concepts are grouped by and pre-mapped to the 2 OMB ethnicity categories: - Hispanic or Latino - Not Hispanic or Latino.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"purpose":"Complies with 2015 Edition Common Clinical Data Set for patient race.","copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"},{"type":"element","expression":"RelatedPerson"},{"type":"element","expression":"Person"},{"type":"element","expression":"Practitioner"},{"type":"element","expression":"FamilyMemberHistory"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Extension","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"US Core ethnicity Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The ethnicity codes used to represent these concepts are based upon the [CDC ethnicity and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 43 reference ethnicity. The ethnicity concepts are grouped by and pre-mapped to the 2 OMB ethnicity categories: - Hispanic or Latino - Not Hispanic or Latino.","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory","path":"Extension.extension","sliceName":"ombCategory","short":"Hispanic or Latino|Not Hispanic or Latino","definition":"The 2 ethnicity category codes according to the [OMB Standards for Maintaining, Collecting, and Presenting Federal Data on Race and Ethnicity, Statistical Policy Directive No. 15, as revised, October 30, 1997](https://www.govinfo.gov/content/pkg/FR-1997-10-30/pdf/97-28653.pdf).","min":0,"max":"1","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"ombCategory","isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/us/core/ValueSet/omb-ethnicity-category"}},{"id":"Extension.extension:detailed","path":"Extension.extension","sliceName":"detailed","short":"Extended ethnicity codes","definition":"The 41 CDC ethnicity codes that are grouped under one of the 2 OMB ethnicity category codes.","min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"detailed","isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/us/core/ValueSet/detailed-ethnicity"}},{"id":"Extension.extension:text","path":"Extension.extension","sliceName":"text","short":"ethnicity Text","definition":"Plain text representation of the ethnicity concept(s).","min":1,"max":"1","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"text","isModifier":false,"isSummary":false},{"id":"Extension.extension:text.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":0,"max":"0","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"base64Binary"},{"code":"boolean"},{"code":"canonical"},{"code":"code"},{"code":"date"},{"code":"dateTime"},{"code":"decimal"},{"code":"id"},{"code":"instant"},{"code":"integer"},{"code":"markdown"},{"code":"oid"},{"code":"positiveInt"},{"code":"string"},{"code":"time"},{"code":"unsignedInt"},{"code":"uri"},{"code":"url"},{"code":"uuid"},{"code":"Address"},{"code":"Age"},{"code":"Annotation"},{"code":"Attachment"},{"code":"CodeableConcept"},{"code":"Coding"},{"code":"ContactPoint"},{"code":"Count"},{"code":"Distance"},{"code":"Duration"},{"code":"HumanName"},{"code":"Identifier"},{"code":"Money"},{"code":"Period"},{"code":"Quantity"},{"code":"Range"},{"code":"Ratio"},{"code":"Reference"},{"code":"SampledData"},{"code":"Signature"},{"code":"Timing"},{"code":"ContactDetail"},{"code":"Contributor"},{"code":"DataRequirement"},{"code":"Expression"},{"code":"ParameterDefinition"},{"code":"RelatedArtifact"},{"code":"TriggerDefinition"},{"code":"UsageContext"},{"code":"Dosage"},{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}},{"resourceType":"StructureDefinition","id":"us-core-birthsex","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","version":"5.0.1","name":"USCoreBirthSexExtension","title":"US Core Birth Sex Extension","status":"active","date":"2019-05-21","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"A code classifying the person's sex assigned at birth as specified by the [Office of the National Coordinator for Health IT (ONC)](https://www.healthit.gov/newsroom/about-onc). This extension aligns with the C-CDA Birth Sex Observation (LOINC 76689-9).","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Extension","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"Extension","definition":"A code classifying the person's sex assigned at birth as specified by the [Office of the National Coordinator for Health IT (ONC)](https://www.healthit.gov/newsroom/about-onc).","comment":"The codes required are intended to present birth sex (i.e., the sex recorded on the patient’s birth certificate) and not gender identity or reassigned sex.","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","description":"Code for sex assigned at birth","valueSet":"http://hl7.org/fhir/us/core/ValueSet/birthsex"}}]}},{"resourceType":"StructureDefinition","id":"us-core-genderIdentity","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity","version":"5.0.1","name":"USCoreGenderIdentityExtension","title":"US Core Gender Identity Extension","status":"active","date":"2022-01-22","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"This extension provides concepts to describe the gender a person identifies as.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"purpose":"Complies with USCDI v2","copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"},{"type":"element","expression":"RelatedPerson"},{"type":"element","expression":"Person"},{"type":"element","expression":"Practitioner"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/patient-genderIdentity","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"Extension","definition":"An Extension","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Extension","definition":"An Extension","min":0,"max":"0","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"extensible","valueSet":"http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1021.32"}}]}},{"resourceType":"StructureDefinition","id":"us-core-implantable-device","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-implantable-device","version":"5.0.1","name":"USCoreImplantableDeviceProfile","title":"US Core Implantable Device Profile","status":"active","experimental":false,"date":"2022-04-20","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"The US Core Implantable Device Profile is based upon the core FHIR Device Resource and meets the U.S. Core Data for Interoperability (USCDI) v2 ‘Unique Device Identifier(s) for a Patient’s Implantable Device(s)’ requirements. To promote interoperability and adoption through common implementation, this profile sets minimum expectations for the Device resource to record, search, and fetch UDI information associated with a patient's implantable device(s). It identifies which core elements, extensions, vocabularies and value sets **SHALL** be present in the resource when using this profile. It provides the floor for standards development for specific uses cases.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"resource","abstract":false,"type":"Device","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Device","derivation":"constraint","snapshot":{"element":[{"id":"Device","path":"Device","short":"Item used in healthcare","definition":"\\-","comment":"\\-","min":0,"max":"*","base":{"path":"Device","min":0,"max":"*"},"constraint":[{"key":"dom-2","severity":"error","human":"If the resource is contained in another resource, it SHALL NOT contain nested Resources","expression":"contained.contained.empty()","xpath":"not(parent::f:contained and f:contained)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-3","severity":"error","human":"If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource","expression":"contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()","xpath":"not(exists(for $id in f:contained/*/f:id/@value return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-4","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated","expression":"contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()","xpath":"not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-5","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a security label","expression":"contained.meta.security.empty()","xpath":"not(exists(f:contained/*/f:meta/f:security))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice","valueBoolean":true},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice-explanation","valueMarkdown":"When a resource has no narrative, only systems that fully understand the data can display the resource to a human safely. Including a human readable representation in the resource makes for a much more robust eco-system and cheaper handling of resources by intermediary systems. Some ecosystems restrict distribution of resources to only those systems that do fully understand the resources, and as a consequence implementers may believe that the narrative is superfluous. However experience shows that such eco-systems often open up to new participants over time."}],"key":"dom-6","severity":"warning","human":"A resource should have narrative for robust management","expression":"text.`div`.exists()","xpath":"exists(f:text/h:div)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"}],"mustSupport":false,"isModifier":false,"isSummary":false},{"id":"Device.id","path":"Device.id","short":"Logical id of this artifact","definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.","comment":"The only time that a resource does not have an id is when it is being submitted to the server using a create operation.","min":0,"max":"1","base":{"path":"Resource.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":true},{"id":"Device.meta","path":"Device.meta","short":"Metadata about the resource","definition":"The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.","min":0,"max":"1","base":{"path":"Resource.meta","min":0,"max":"1"},"type":[{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Device.implicitRules","path":"Device.implicitRules","short":"A set of rules under which this content was created","definition":"A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.","comment":"Asserting this rule set restricts the content to be only understood by a limited set of trading partners. This inherently limits the usefulness of the data in the long term. However, the existing health eco-system is highly fractured, and not yet ready to define, collect, and exchange data in a generally computable sense. Wherever possible, implementers and/or specification writers should avoid using this element. Often, when used, the URL is a reference to an implementation guide that defines these special rules as part of it's narrative along with other profiles, value sets, etc.","min":0,"max":"1","base":{"path":"Resource.implicitRules","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because the implicit rules may provide additional knowledge about the resource that modifies it's meaning or interpretation","isSummary":true},{"id":"Device.language","path":"Device.language","short":"Language of the resource content","definition":"The base language in which the resource is written.","comment":"Language is provided to support indexing and accessibility (typically, services such as text to speech use the language tag). The html language tag in the narrative applies to the narrative. The language tag on the resource may be used to specify the language of other presentations generated from the data in the resource. Not all the content has to be in the base language. The Resource.language should not be assumed to apply to the narrative automatically. If a language is specified, it should it also be specified on the div element in the html (see rules in HTML5 for information about the relationship between xml:lang and the html lang attribute).","min":0,"max":"1","base":{"path":"Resource.language","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/all-languages"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Language"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"preferred","description":"A human language.","valueSet":"http://hl7.org/fhir/ValueSet/languages"}},{"id":"Device.text","path":"Device.text","short":"Text summary of the resource, for human interpretation","definition":"A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it \"clinically safe\" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety.","comment":"Contained resources do not have narrative. Resources that are not contained SHOULD have a narrative. In some cases, a resource may only have text with little or no additional discrete data (as long as all minOccurs=1 elements are satisfied). This may be necessary for data from legacy systems where information is captured as a \"text blob\" or where text is additionally entered raw or narrated and encoded information is added later.","alias":["narrative","html","xhtml","display"],"min":0,"max":"1","base":{"path":"DomainResource.text","min":0,"max":"1"},"type":[{"code":"Narrative"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.contained","path":"Device.contained","short":"Contained, inline Resources","definition":"These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope.","comment":"This should never be done when the content can be identified properly, as once identification is lost, it is extremely difficult (and context dependent) to restore it again. Contained resources may have profiles and tags In their meta elements, but SHALL NOT have security labels.","alias":["inline resources","anonymous resources","contained resources"],"min":0,"max":"*","base":{"path":"DomainResource.contained","min":0,"max":"*"},"type":[{"code":"Resource"}],"isModifier":false,"isSummary":false},{"id":"Device.extension","path":"Device.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the resource. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.modifierExtension","path":"Device.modifierExtension","short":"Extensions that cannot be ignored","definition":"May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the resource that contains them","isSummary":false},{"id":"Device.identifier","path":"Device.identifier","short":"Instance identifier","definition":"Unique instance identifiers assigned to a device by manufacturers other organizations or owners.","comment":"The barcode string from a barcode present on a device label or package may identify the instance, include names given to the device in local usage, or may identify the type of device. If the identifier identifies the type of device, Device.type element should be used.","min":0,"max":"*","base":{"path":"Device.identifier","min":0,"max":"*"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.definition","path":"Device.definition","short":"The reference to the definition for the device","definition":"The reference to the definition for the device.","min":0,"max":"1","base":{"path":"Device.definition","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/DeviceDefinition"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier","path":"Device.udiCarrier","short":"Unique Device Identifier (UDI) Barcode string","definition":"Unique device identifier (UDI) assigned to device label or package. Note that the Device may include multiple udiCarriers as it either may include just the udiCarrier for the jurisdiction it is sold, or for multiple jurisdictions it could have been sold.","comment":"Some devices may not have UDI information (for example. historical data or patient reported data).","min":0,"max":"1","base":{"path":"Device.udiCarrier","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.id","path":"Device.udiCarrier.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.extension","path":"Device.udiCarrier.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.modifierExtension","path":"Device.udiCarrier.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.udiCarrier.deviceIdentifier","path":"Device.udiCarrier.deviceIdentifier","short":"Mandatory fixed portion of UDI","definition":"The device identifier (DI) is a mandatory, fixed portion of a UDI that identifies the labeler and the specific version or model of a device.","alias":["DI"],"min":1,"max":"1","base":{"path":"Device.udiCarrier.deviceIdentifier","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.issuer","path":"Device.udiCarrier.issuer","short":"UDI Issuing Organization","definition":"Organization that is charged with issuing UDIs for devices. For example, the US FDA issuers include :\n1) GS1: \nhttp://hl7.org/fhir/NamingSystem/gs1-di, \n2) HIBCC:\nhttp://hl7.org/fhir/NamingSystem/hibcc-dI, \n3) ICCBBA for blood containers:\nhttp://hl7.org/fhir/NamingSystem/iccbba-blood-di, \n4) ICCBA for other devices:\nhttp://hl7.org/fhir/NamingSystem/iccbba-other-di.","alias":["Barcode System"],"min":0,"max":"1","base":{"path":"Device.udiCarrier.issuer","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.jurisdiction","path":"Device.udiCarrier.jurisdiction","short":"Regional UDI authority","definition":"The identity of the authoritative source for UDI generation within a jurisdiction. All UDIs are globally unique within a single namespace with the appropriate repository uri as the system. For example, UDIs of devices managed in the U.S. by the FDA, the value is http://hl7.org/fhir/NamingSystem/fda-udi.","requirements":"Allows a recipient of a UDI to know which database will contain the UDI-associated metadata.","min":0,"max":"1","base":{"path":"Device.udiCarrier.jurisdiction","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.carrierAIDC","path":"Device.udiCarrier.carrierAIDC","short":"UDI Machine Readable Barcode String","definition":"The full UDI carrier of the Automatic Identification and Data Capture (AIDC) technology representation of the barcode string as printed on the packaging of the device - e.g., a barcode or RFID. Because of limitations on character sets in XML and the need to round-trip JSON data through XML, AIDC Formats *SHALL* be base64 encoded.","comment":"The AIDC form of UDIs should be scanned or otherwise used for the identification of the device whenever possible to minimize errors in records resulting from manual transcriptions. If separate barcodes for DI and PI are present, concatenate the string with DI first and in order of human readable expression on label.","alias":["Automatic Identification and Data Capture"],"min":0,"max":"1","base":{"path":"Device.udiCarrier.carrierAIDC","min":0,"max":"1"},"type":[{"code":"base64Binary"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.carrierHRF","path":"Device.udiCarrier.carrierHRF","short":"UDI Human Readable Barcode String","definition":"The full UDI carrier as the human readable form (HRF) representation of the barcode string as printed on the packaging of the device.","comment":"If separate barcodes for DI and PI are present, concatenate the string with DI first and in order of human readable expression on label.","alias":["Human Readable Form","UDI","Barcode String"],"min":0,"max":"1","base":{"path":"Device.udiCarrier.carrierHRF","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.entryType","path":"Device.udiCarrier.entryType","short":"barcode | rfid | manual +","definition":"A coded entry to indicate how the data was entered.","requirements":"Supports a way to distinguish hand entered from machine read data.","min":0,"max":"1","base":{"path":"Device.udiCarrier.entryType","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"UDIEntryType"}],"strength":"required","description":"Codes to identify how UDI data was entered.","valueSet":"http://hl7.org/fhir/ValueSet/udi-entry-type|4.0.1"}},{"id":"Device.status","path":"Device.status","short":"active | inactive | entered-in-error | unknown","definition":"Status of the Device availability.","comment":"This element is labeled as a modifier because the status contains the codes inactive and entered-in-error that mark the device (record)as not currently valid.","min":0,"max":"1","base":{"path":"Device.status","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labelled as a modifier because it is a status element that contains status entered-in-error which means that the resource should not be treated as valid","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"FHIRDeviceStatus"}],"strength":"required","description":"The availability status of the device.","valueSet":"http://hl7.org/fhir/ValueSet/device-status|4.0.1"}},{"id":"Device.statusReason","path":"Device.statusReason","short":"online | paused | standby | offline | not-ready | transduc-discon | hw-discon | off","definition":"Reason for the dtatus of the Device availability.","min":0,"max":"*","base":{"path":"Device.statusReason","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"FHIRDeviceStatusReason"}],"strength":"extensible","description":"The availability status reason of the device.","valueSet":"http://hl7.org/fhir/ValueSet/device-status-reason"}},{"id":"Device.distinctIdentifier","path":"Device.distinctIdentifier","short":"The distinct identification string","definition":"The distinct identification string as required by regulation for a human cell, tissue, or cellular and tissue-based product.","comment":"For example, this applies to devices in the United States regulated under *Code of Federal Regulation 21CFR§1271.290(c)*.","alias":["Distinct Identification Code (DIC)"],"min":0,"max":"1","base":{"path":"Device.distinctIdentifier","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.manufacturer","path":"Device.manufacturer","short":"Name of device manufacturer","definition":"A name of the manufacturer.","min":0,"max":"1","base":{"path":"Device.manufacturer","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.manufactureDate","path":"Device.manufactureDate","short":"Date when the device was made","definition":"The date and time when the device was manufactured.","min":0,"max":"1","base":{"path":"Device.manufactureDate","min":0,"max":"1"},"type":[{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.expirationDate","path":"Device.expirationDate","short":"Date and time of expiry of this device (if applicable)","definition":"The date and time beyond which this device is no longer valid or should not be used (if applicable).","min":0,"max":"1","base":{"path":"Device.expirationDate","min":0,"max":"1"},"type":[{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.lotNumber","path":"Device.lotNumber","short":"Lot number of manufacture","definition":"Lot number assigned by the manufacturer.","min":0,"max":"1","base":{"path":"Device.lotNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.serialNumber","path":"Device.serialNumber","short":"Serial number assigned by the manufacturer","definition":"The serial number assigned by the organization when the device was manufactured.","comment":"Alphanumeric Maximum 20.","min":0,"max":"1","base":{"path":"Device.serialNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.deviceName","path":"Device.deviceName","short":"The name of the device as given by the manufacturer","definition":"This represents the manufacturer's name of the device as provided by the device, from a UDI label, or by a person describing the Device. This typically would be used when a person provides the name(s) or when the device represents one of the names available from DeviceDefinition.","min":0,"max":"*","base":{"path":"Device.deviceName","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.id","path":"Device.deviceName.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.extension","path":"Device.deviceName.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.modifierExtension","path":"Device.deviceName.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.deviceName.name","path":"Device.deviceName.name","short":"The name of the device","definition":"The name of the device.","alias":["Σ"],"min":1,"max":"1","base":{"path":"Device.deviceName.name","min":1,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.type","path":"Device.deviceName.type","short":"udi-label-name | user-friendly-name | patient-reported-name | manufacturer-name | model-name | other","definition":"The type of deviceName.\nUDILabelName | UserFriendlyName | PatientReportedName | ManufactureDeviceName | ModelName.","min":1,"max":"1","base":{"path":"Device.deviceName.type","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"DeviceNameType"}],"strength":"required","description":"The type of name the device is referred by.","valueSet":"http://hl7.org/fhir/ValueSet/device-nametype|4.0.1"}},{"id":"Device.modelNumber","path":"Device.modelNumber","short":"The model number for the device","definition":"The model number for the device.","min":0,"max":"1","base":{"path":"Device.modelNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.partNumber","path":"Device.partNumber","short":"The part number of the device","definition":"The part number of the device.","comment":"Alphanumeric Maximum 20.","min":0,"max":"1","base":{"path":"Device.partNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.type","path":"Device.type","short":"The kind or type of device","definition":"The kind or type of device.","min":1,"max":"1","base":{"path":"Device.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"strength":"extensible","description":"Codes to identify medical devices","valueSet":"http://hl7.org/fhir/ValueSet/device-kind"}},{"id":"Device.specialization","path":"Device.specialization","short":"The capabilities supported on a device, the standards to which the device conforms for a particular purpose, and used for the communication","definition":"The capabilities supported on a device, the standards to which the device conforms for a particular purpose, and used for the communication.","min":0,"max":"*","base":{"path":"Device.specialization","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.id","path":"Device.specialization.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.extension","path":"Device.specialization.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.modifierExtension","path":"Device.specialization.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.specialization.systemType","path":"Device.specialization.systemType","short":"The standard that is used to operate and communicate","definition":"The standard that is used to operate and communicate.","alias":["Σ"],"min":1,"max":"1","base":{"path":"Device.specialization.systemType","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.version","path":"Device.specialization.version","short":"The version of the standard that is used to operate and communicate","definition":"The version of the standard that is used to operate and communicate.","min":0,"max":"1","base":{"path":"Device.specialization.version","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version","path":"Device.version","short":"The actual design of the device or software version running on the device","definition":"The actual design of the device or software version running on the device.","min":0,"max":"*","base":{"path":"Device.version","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version.id","path":"Device.version.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.version.extension","path":"Device.version.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.version.modifierExtension","path":"Device.version.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.version.type","path":"Device.version.type","short":"The type of the device version","definition":"The type of the device version.","alias":["Σ"],"min":0,"max":"1","base":{"path":"Device.version.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version.component","path":"Device.version.component","short":"A single component of the device version","definition":"A single component of the device version.","min":0,"max":"1","base":{"path":"Device.version.component","min":0,"max":"1"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version.value","path":"Device.version.value","short":"The version text","definition":"The version text.","min":1,"max":"1","base":{"path":"Device.version.value","min":1,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property","path":"Device.property","short":"The actual configuration settings of a device as it actually operates, e.g., regulation status, time properties","definition":"The actual configuration settings of a device as it actually operates, e.g., regulation status, time properties.","min":0,"max":"*","base":{"path":"Device.property","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property.id","path":"Device.property.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.property.extension","path":"Device.property.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.property.modifierExtension","path":"Device.property.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.property.type","path":"Device.property.type","short":"Code that specifies the property DeviceDefinitionPropetyCode (Extensible)","definition":"Code that specifies the property DeviceDefinitionPropetyCode (Extensible).","min":1,"max":"1","base":{"path":"Device.property.type","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property.valueQuantity","path":"Device.property.valueQuantity","short":"Property value as a quantity","definition":"Property value as a quantity.","min":0,"max":"*","base":{"path":"Device.property.valueQuantity","min":0,"max":"*"},"type":[{"code":"Quantity"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property.valueCode","path":"Device.property.valueCode","short":"Property value as a code, e.g., NTP4 (synced to NTP)","definition":"Property value as a code, e.g., NTP4 (synced to NTP).","min":0,"max":"*","base":{"path":"Device.property.valueCode","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.patient","path":"Device.patient","short":"Patient to whom Device is affixed","definition":"Patient information, If the device is affixed to a person.","requirements":"If the device is implanted in a patient, then need to associate the device to the patient.","min":1,"max":"1","base":{"path":"Device.patient","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.owner","path":"Device.owner","short":"Organization responsible for device","definition":"An organization that is responsible for the provision and ongoing maintenance of the device.","min":0,"max":"1","base":{"path":"Device.owner","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.contact","path":"Device.contact","short":"Details for human/organization for support","definition":"Contact details for an organization or a particular human that is responsible for the device.","comment":"used for troubleshooting etc.","min":0,"max":"*","base":{"path":"Device.contact","min":0,"max":"*"},"type":[{"code":"ContactPoint"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.location","path":"Device.location","short":"Where the device is found","definition":"The place where the device can be found.","requirements":"Device.location can be used to track device location.","min":0,"max":"1","base":{"path":"Device.location","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Location"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.url","path":"Device.url","short":"Network address to contact device","definition":"A network address on which the device may be contacted directly.","comment":"If the device is running a FHIR server, the network address should be the Base URL from which a conformance statement may be retrieved.","min":0,"max":"1","base":{"path":"Device.url","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.note","path":"Device.note","short":"Device notes and comments","definition":"Descriptive information, usage information or implantation information that is not captured in an existing element.","min":0,"max":"*","base":{"path":"Device.note","min":0,"max":"*"},"type":[{"code":"Annotation"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.safety","path":"Device.safety","short":"Safety Characteristics of Device","definition":"Provides additional safety characteristics about a medical device. For example devices containing latex.","min":0,"max":"*","base":{"path":"Device.safety","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Device.parent","path":"Device.parent","short":"The parent device","definition":"The parent device.","min":0,"max":"1","base":{"path":"Device.parent","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Device"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}}] \ No newline at end of file +[{"resourceType":"StructureDefinition","id":"us-core-patient","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient","version":"5.0.1","name":"USCorePatientProfile","title":"US Core Patient Profile","status":"active","experimental":false,"date":"2022-04-20T15:02:49-07:00","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"The US Core Patient Profile meets the U.S. Core Data for Interoperability (USCDI) v2 'Patient Demographics' requirements. This profile sets minimum expectations for the Patient resource to record, search, and fetch basic demographics and other administrative information about an individual patient. It identifies which core elements, extensions, vocabularies and value sets **SHALL** be present in the resource when using this profile to promote interoperability and adoption through common implementation. It identifies which core elements, extensions, vocabularies and value sets **SHALL** be present in the resource when using this profile. It provides the floor for standards development for specific uses cases.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"resource","abstract":false,"type":"Patient","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Patient","derivation":"constraint","snapshot":{"element":[{"id":"Patient","path":"Patient","short":"Information about an individual or animal receiving health care services","definition":"\\-","comment":"\\-","alias":["SubjectOfCare Client Resident"],"min":0,"max":"*","base":{"path":"Patient","min":0,"max":"*"},"constraint":[{"key":"dom-2","severity":"error","human":"If the resource is contained in another resource, it SHALL NOT contain nested Resources","expression":"contained.contained.empty()","xpath":"not(parent::f:contained and f:contained)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-3","severity":"error","human":"If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource","expression":"contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()","xpath":"not(exists(for $id in f:contained/*/f:id/@value return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-4","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated","expression":"contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()","xpath":"not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-5","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a security label","expression":"contained.meta.security.empty()","xpath":"not(exists(f:contained/*/f:meta/f:security))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice","valueBoolean":true},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice-explanation","valueMarkdown":"When a resource has no narrative, only systems that fully understand the data can display the resource to a human safely. Including a human readable representation in the resource makes for a much more robust eco-system and cheaper handling of resources by intermediary systems. Some ecosystems restrict distribution of resources to only those systems that do fully understand the resources, and as a consequence implementers may believe that the narrative is superfluous. However experience shows that such eco-systems often open up to new participants over time."}],"key":"dom-6","severity":"warning","human":"A resource should have narrative for robust management","expression":"text.`div`.exists()","xpath":"exists(f:text/h:div)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"us-core-6","severity":"error","human":"Either Patient.name.given and/or Patient.name.family SHALL be present or a Data Absent Reason Extension SHALL be present.","expression":"(name.family.exists() or name.given.exists()) xor extension.where(url='http://hl7.org/fhir/StructureDefinition/data-absent-reason').exists()","xpath":"(/f:name/f:extension/@url='http://hl7.org/fhir/StructureDefinition/data-absent-reason' and not(/f:name/f:family or /f:name/f:given)) or (not(/f:name/f:extension/@url='http://hl7.org/fhir/StructureDefinition/data-absent-reason') and (/f:name/f:family or /f:name/f:given))"}],"mustSupport":false,"isModifier":false,"isSummary":false},{"id":"Patient.id","path":"Patient.id","short":"Logical id of this artifact","definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.","comment":"The only time that a resource does not have an id is when it is being submitted to the server using a create operation.","min":0,"max":"1","base":{"path":"Resource.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":true},{"id":"Patient.meta","path":"Patient.meta","short":"Metadata about the resource","definition":"The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.","min":0,"max":"1","base":{"path":"Resource.meta","min":0,"max":"1"},"type":[{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.implicitRules","path":"Patient.implicitRules","short":"A set of rules under which this content was created","definition":"A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.","comment":"Asserting this rule set restricts the content to be only understood by a limited set of trading partners. This inherently limits the usefulness of the data in the long term. However, the existing health eco-system is highly fractured, and not yet ready to define, collect, and exchange data in a generally computable sense. Wherever possible, implementers and/or specification writers should avoid using this element. Often, when used, the URL is a reference to an implementation guide that defines these special rules as part of it's narrative along with other profiles, value sets, etc.","min":0,"max":"1","base":{"path":"Resource.implicitRules","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because the implicit rules may provide additional knowledge about the resource that modifies it's meaning or interpretation","isSummary":true},{"id":"Patient.language","path":"Patient.language","short":"Language of the resource content","definition":"The base language in which the resource is written.","comment":"Language is provided to support indexing and accessibility (typically, services such as text to speech use the language tag). The html language tag in the narrative applies to the narrative. The language tag on the resource may be used to specify the language of other presentations generated from the data in the resource. Not all the content has to be in the base language. The Resource.language should not be assumed to apply to the narrative automatically. If a language is specified, it should it also be specified on the div element in the html (see rules in HTML5 for information about the relationship between xml:lang and the html lang attribute).","min":0,"max":"1","base":{"path":"Resource.language","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/all-languages"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Language"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"preferred","description":"A human language.","valueSet":"http://hl7.org/fhir/ValueSet/languages"}},{"id":"Patient.text","path":"Patient.text","short":"Text summary of the resource, for human interpretation","definition":"A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it \"clinically safe\" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety.","comment":"Contained resources do not have narrative. Resources that are not contained SHOULD have a narrative. In some cases, a resource may only have text with little or no additional discrete data (as long as all minOccurs=1 elements are satisfied). This may be necessary for data from legacy systems where information is captured as a \"text blob\" or where text is additionally entered raw or narrated and encoded information is added later.","alias":["narrative","html","xhtml","display"],"min":0,"max":"1","base":{"path":"DomainResource.text","min":0,"max":"1"},"type":[{"code":"Narrative"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contained","path":"Patient.contained","short":"Contained, inline Resources","definition":"These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope.","comment":"This should never be done when the content can be identified properly, as once identification is lost, it is extremely difficult (and context dependent) to restore it again. Contained resources may have profiles and tags In their meta elements, but SHALL NOT have security labels.","alias":["inline resources","anonymous resources","contained resources"],"min":0,"max":"*","base":{"path":"DomainResource.contained","min":0,"max":"*"},"type":[{"code":"Resource"}],"isModifier":false,"isSummary":false},{"id":"Patient.extension","path":"Patient.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"ordered":false,"rules":"open"},"short":"Extension","definition":"An Extension","min":0,"max":"*","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.extension:race","path":"Patient.extension","sliceName":"race","short":"US Core Race Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The race codes used to represent these concepts are based upon the [CDC Race and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 921 reference race. The race concepts are grouped by and pre-mapped to the 5 OMB race categories:\n\n - American Indian or Alaska Native\n - Asian\n - Black or African American\n - Native Hawaiian or Other Pacific Islander\n - White.","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-race"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.extension:ethnicity","path":"Patient.extension","sliceName":"ethnicity","short":"US Core ethnicity Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The ethnicity codes used to represent these concepts are based upon the [CDC ethnicity and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 43 reference ethnicity. The ethnicity concepts are grouped by and pre-mapped to the 2 OMB ethnicity categories: - Hispanic or Latino - Not Hispanic or Latino.","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.extension:birthsex","path":"Patient.extension","sliceName":"birthsex","short":"Extension","definition":"A code classifying the person's sex assigned at birth as specified by the [Office of the National Coordinator for Health IT (ONC)](https://www.healthit.gov/newsroom/about-onc).","comment":"The codes required are intended to present birth sex (i.e., the sex recorded on the patient’s birth certificate) and not gender identity or reassigned sex.","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.extension:genderIdentity","path":"Patient.extension","sliceName":"genderIdentity","short":"Extension","definition":"An Extension","min":0,"max":"1","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity"]}],"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":false,"isModifier":false},{"id":"Patient.modifierExtension","path":"Patient.modifierExtension","short":"Extensions that cannot be ignored","definition":"May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the resource that contains them","isSummary":false},{"id":"Patient.identifier","path":"Patient.identifier","short":"An identifier for this patient","definition":"An identifier for this patient.","requirements":"Patients are almost always assigned specific numerical identifiers.","min":1,"max":"*","base":{"path":"Patient.identifier","min":0,"max":"*"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.identifier.id","path":"Patient.identifier.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.identifier.extension","path":"Patient.identifier.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.identifier.use","path":"Patient.identifier.use","short":"usual | official | temp | secondary | old (If known)","definition":"The purpose of this identifier.","comment":"Applications can assume that an identifier is permanent unless it explicitly says that it is temporary.","requirements":"Allows the appropriate identifier for a particular context of use to be selected from among a set of identifiers.","min":0,"max":"1","base":{"path":"Identifier.use","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary id for a permanent one.","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"IdentifierUse"}],"strength":"required","description":"Identifies the purpose for this identifier, if known .","valueSet":"http://hl7.org/fhir/ValueSet/identifier-use|4.0.1"}},{"id":"Patient.identifier.type","path":"Patient.identifier.type","short":"Description of identifier","definition":"A coded type for the identifier that can be used to determine which identifier to use for a specific purpose.","comment":"This element deals only with general categories of identifiers. It SHOULD not be used for codes that correspond 1..1 with the Identifier.system. Some identifiers may fall into multiple categories due to common usage. Where the system is known, a type is unnecessary because the type is always part of the system definition. However systems often need to handle identifiers where the system is not known. There is not a 1:1 relationship between type and system, since many different systems have the same type.","requirements":"Allows users to make use of identifiers when the identifier system is not known.","min":0,"max":"1","base":{"path":"Identifier.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"IdentifierType"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"extensible","description":"A coded type for an identifier that can be used to determine which identifier to use for a specific purpose.","valueSet":"http://hl7.org/fhir/ValueSet/identifier-type"}},{"id":"Patient.identifier.system","path":"Patient.identifier.system","short":"The namespace for the identifier value","definition":"Establishes the namespace for the value - that is, a URL that describes a set values that are unique.","comment":"Identifier.system is always case sensitive.","requirements":"There are many sets of identifiers. To perform matching of two identifiers, we need to know what set we're dealing with. The system identifies a particular set of unique identifiers.","min":1,"max":"1","base":{"path":"Identifier.system","min":0,"max":"1"},"type":[{"code":"uri"}],"example":[{"label":"General","valueUri":"http://www.acme.com/identifiers/patient"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.identifier.value","path":"Patient.identifier.value","short":"The value that is unique within the system.","definition":"The portion of the identifier typically relevant to the user and which is unique within the context of the system.","comment":"If the value is a full URI, then the system SHALL be urn:ietf:rfc:3986. The value's primary purpose is computational mapping. As a result, it may be normalized for comparison purposes (e.g. removing non-significant whitespace, dashes, etc.) A value formatted for human display can be conveyed using the [Rendered Value extension](http://hl7.org/fhir/R4/extension-rendered-value.html). Identifier.value is to be treated as case sensitive unless knowledge of the Identifier.system allows the processer to be confident that non-case-sensitive processing is safe.","min":1,"max":"1","base":{"path":"Identifier.value","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"123456"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.identifier.period","path":"Patient.identifier.period","short":"Time period when id is/was valid for use","definition":"Time period during which identifier is/was valid for use.","min":0,"max":"1","base":{"path":"Identifier.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.identifier.assigner","path":"Patient.identifier.assigner","short":"Organization that issued id (may be just text)","definition":"Organization that issued/manages the identifier.","comment":"The Identifier.assigner may omit the .reference element and only contain a .display element reflecting the name or other textual information about the assigning organization.","min":0,"max":"1","base":{"path":"Identifier.assigner","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.active","path":"Patient.active","short":"Whether this patient's record is in active use","definition":"Whether this patient record is in active use. \nMany systems use this property to mark as non-current patients, such as those that have not been seen for a period of time based on an organization's business rules.\n\nIt is often used to filter patient lists to exclude inactive patients\n\nDeceased patients may also be marked as inactive for the same reasons, but may be active for some time after death.","comment":"If a record is inactive, and linked to an active record, then future patient/record updates should occur on the other patient.","requirements":"Need to be able to mark a patient record as not to be used because it was created in error.","min":0,"max":"1","base":{"path":"Patient.active","min":0,"max":"1"},"type":[{"code":"boolean"}],"meaningWhenMissing":"This resource is generally assumed to be active if no value is provided for the active element","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labelled as a modifier because it is a status element that can indicate that a record should not be treated as valid","isSummary":true},{"id":"Patient.name","path":"Patient.name","short":"A name associated with the patient","definition":"A name associated with the individual.","comment":"A patient may have multiple names with different uses or applicable periods. For animals, the name is a \"HumanName\" in the sense that is assigned and used by humans and has the same patterns.","requirements":"Need to be able to track the patient by multiple names. Examples are your official name and a partner name.","min":1,"max":"*","base":{"path":"Patient.name","min":0,"max":"*"},"type":[{"code":"HumanName"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.name.id","path":"Patient.name.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.name.extension","path":"Patient.name.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.name.use","path":"Patient.name.use","short":"usual | official | temp | nickname | anonymous | old | maiden","definition":"Identifies the purpose for this name.","comment":"Applications can assume that a name is current unless it explicitly says that it is temporary or old.","requirements":"Allows the appropriate name for a particular context of use to be selected from among a set of names.","min":0,"max":"1","base":{"path":"HumanName.use","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary or old name etc.for a current/permanent one","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"NameUse"}],"strength":"required","description":"The use of a human name.","valueSet":"http://hl7.org/fhir/ValueSet/name-use|4.0.1"}},{"id":"Patient.name.text","path":"Patient.name.text","short":"Text representation of the full name","definition":"Specifies the entire name as it should be displayed e.g. on an application UI. This may be provided instead of or as well as the specific parts.","comment":"Can provide both a text representation and parts. Applications updating a name SHALL ensure that when both text and parts are present, no content is included in the text that isn't found in a part.","requirements":"A renderable, unencoded form.","min":0,"max":"1","base":{"path":"HumanName.text","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.name.family","path":"Patient.name.family","short":"Family name (often called 'Surname')","definition":"The part of a name that links to the genealogy. In some cultures (e.g. Eritrea) the family name of a son is the first name of his father.","comment":"Family Name may be decomposed into specific parts using extensions (de, nl, es related cultures).","alias":["surname"],"min":0,"max":"1","base":{"path":"HumanName.family","min":0,"max":"1"},"type":[{"code":"string"}],"condition":["us-core-6"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.name.given","path":"Patient.name.given","short":"Given names (not always 'first'). Includes middle names","definition":"Given name.","comment":"If only initials are recorded, they may be used in place of the full name parts. Initials may be separated into multiple given names but often aren't due to paractical limitations. This element is not called \"first name\" since given names do not always come first.","alias":["first name","middle name"],"min":0,"max":"*","base":{"path":"HumanName.given","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"Given Names appear in the correct order for presenting the name","condition":["us-core-6"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.name.prefix","path":"Patient.name.prefix","short":"Parts that come before the name","definition":"Part of the name that is acquired as a title due to academic, legal, employment or nobility status, etc. and that appears at the start of the name.","min":0,"max":"*","base":{"path":"HumanName.prefix","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"Prefixes appear in the correct order for presenting the name","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.name.suffix","path":"Patient.name.suffix","short":"Parts that come after the name","definition":"Part of the name that is acquired as a title due to academic, legal, employment or nobility status, etc. and that appears at the end of the name.","min":0,"max":"*","base":{"path":"HumanName.suffix","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"Suffixes appear in the correct order for presenting the name","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":true},{"id":"Patient.name.period","path":"Patient.name.period","short":"Time period when name was/is in use","definition":"Indicates the period of time when this name was valid for the named person.","requirements":"Allows names to be placed in historical context.","min":0,"max":"1","base":{"path":"HumanName.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":true},{"id":"Patient.telecom","path":"Patient.telecom","short":"A contact detail for the individual","definition":"A contact detail (e.g. a telephone number or an email address) by which the individual may be contacted.","comment":"A Patient may have multiple ways to be contacted with different uses or applicable periods. May need to have options for contacting the person urgently and also to help with identification. The address might not go directly to the individual, but may reach another party that is able to proxy for the patient (i.e. home phone, or pet owner's phone).","requirements":"People have (primary) ways to contact them in some way such as phone, email.","min":0,"max":"*","base":{"path":"Patient.telecom","min":0,"max":"*"},"type":[{"code":"ContactPoint"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":true},{"id":"Patient.telecom.id","path":"Patient.telecom.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.telecom.extension","path":"Patient.telecom.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.telecom.system","path":"Patient.telecom.system","short":"phone | fax | email | pager | url | sms | other","definition":"Telecommunications form for contact point - what communications system is required to make use of the contact.","min":1,"max":"1","base":{"path":"ContactPoint.system","min":0,"max":"1"},"type":[{"code":"code"}],"condition":["cpt-2"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"required","description":"Telecommunications form for contact point.","valueSet":"http://hl7.org/fhir/ValueSet/contact-point-system"}},{"id":"Patient.telecom.value","path":"Patient.telecom.value","short":"The actual contact point details","definition":"The actual contact point details, in a form that is meaningful to the designated communication system (i.e. phone number or email address).","comment":"Additional text data such as phone extension numbers, or notes about use of the contact are sometimes included in the value.","requirements":"Need to support legacy numbers that are not in a tightly controlled format.","min":1,"max":"1","base":{"path":"ContactPoint.value","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.telecom.use","path":"Patient.telecom.use","short":"home | work | temp | old | mobile - purpose of this contact point","definition":"Identifies the purpose for the contact point.","comment":"Applications can assume that a contact is current unless it explicitly says that it is temporary or old.","requirements":"Need to track the way a person uses this contact, so a user can choose which is appropriate for their purpose.","min":0,"max":"1","base":{"path":"ContactPoint.use","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary or old contact etc.for a current/permanent one","isSummary":true,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/ValueSet/contact-point-use"}},{"id":"Patient.telecom.rank","path":"Patient.telecom.rank","short":"Specify preferred order of use (1 = highest)","definition":"Specifies a preferred order in which to use a set of contacts. ContactPoints with lower rank values are more preferred than those with higher rank values.","comment":"Note that rank does not necessarily follow the order in which the contacts are represented in the instance.","min":0,"max":"1","base":{"path":"ContactPoint.rank","min":0,"max":"1"},"type":[{"code":"positiveInt"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.telecom.period","path":"Patient.telecom.period","short":"Time period when the contact point was/is in use","definition":"Time period when the contact point was/is in use.","min":0,"max":"1","base":{"path":"ContactPoint.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.gender","path":"Patient.gender","short":"male | female | other | unknown","definition":"Administrative Gender - the gender that the patient is considered to have for administration and record keeping purposes.","comment":"The gender might not match the biological sex as determined by genetics or the individual's preferred identification. Note that for both humans and particularly animals, there are other legitimate possibilities than male and female, though the vast majority of systems and contexts only support male and female. Systems providing decision support or enforcing business rules should ideally do this on the basis of Observations dealing with the specific sex or gender aspect of interest (anatomical, chromosomal, social, etc.) However, because these observations are infrequently recorded, defaulting to the administrative gender is common practice. Where such defaulting occurs, rule enforcement should allow for the variation between administrative and biological, chromosomal and other gender aspects. For example, an alert about a hysterectomy on a male should be handled as a warning or overridable error, not a \"hard\" error. See the Patient Gender and Sex section for additional information about communicating patient gender and sex.","requirements":"Needed for identification of the individual, in combination with (at least) name and birth date.","min":1,"max":"1","base":{"path":"Patient.gender","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/ValueSet/administrative-gender"}},{"id":"Patient.birthDate","path":"Patient.birthDate","short":"The date of birth for the individual","definition":"The date of birth for the individual.","comment":"At least an estimated year should be provided as a guess if the real DOB is unknown There is a standard extension \"patient-birthTime\" available that should be used where Time is required (such as in maternity/infant care systems).","requirements":"Age of the individual drives many clinical processes.","min":0,"max":"1","base":{"path":"Patient.birthDate","min":0,"max":"1"},"type":[{"code":"date"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.deceased[x]","path":"Patient.deceased[x]","short":"Indicates if the individual is deceased or not","definition":"Indicates if the individual is deceased or not.","comment":"If there's no value in the instance, it means there is no statement on whether or not the individual is deceased. Most systems will interpret the absence of a value as a sign of the person being alive.","requirements":"The fact that a patient is deceased influences the clinical process. Also, in human communication and relation management it is necessary to know whether the person is alive.","min":0,"max":"1","base":{"path":"Patient.deceased[x]","min":0,"max":"1"},"type":[{"code":"boolean"},{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because once a patient is marked as deceased, the actions that are appropriate to perform on the patient may be significantly different.","isSummary":true},{"id":"Patient.address","path":"Patient.address","short":"An address for the individual","definition":"An address for the individual.","comment":"Patient may have multiple addresses with different uses or applicable periods.","requirements":"May need to keep track of patient addresses for contacting, billing or reporting requirements and also to help with identification.","min":0,"max":"*","base":{"path":"Patient.address","min":0,"max":"*"},"type":[{"code":"Address"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.id","path":"Patient.address.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.address.extension","path":"Patient.address.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.address.use","path":"Patient.address.use","short":"home | work | temp | old | billing - purpose of this address","definition":"The purpose of this address.","comment":"Applications can assume that an address is current unless it explicitly says that it is temporary or old.","requirements":"Allows an appropriate address to be chosen from a list of many.","min":0,"max":"1","base":{"path":"Address.use","min":0,"max":"1"},"type":[{"code":"code"}],"example":[{"label":"General","valueCode":"home"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because applications should not mistake a temporary or old address etc.for a current/permanent one","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AddressUse"}],"strength":"required","description":"The use of an address.","valueSet":"http://hl7.org/fhir/ValueSet/address-use|4.0.1"}},{"id":"Patient.address.type","path":"Patient.address.type","short":"postal | physical | both","definition":"Distinguishes between physical addresses (those you can visit) and mailing addresses (e.g. PO Boxes and care-of addresses). Most addresses are both.","comment":"The definition of Address states that \"address is intended to describe postal addresses, not physical locations\". However, many applications track whether an address has a dual purpose of being a location that can be visited as well as being a valid delivery destination, and Postal addresses are often used as proxies for physical locations (also see the [Location](http://hl7.org/fhir/R4/location.html#) resource).","min":0,"max":"1","base":{"path":"Address.type","min":0,"max":"1"},"type":[{"code":"code"}],"example":[{"label":"General","valueCode":"both"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AddressType"}],"strength":"required","description":"The type of an address (physical / postal).","valueSet":"http://hl7.org/fhir/ValueSet/address-type|4.0.1"}},{"id":"Patient.address.text","path":"Patient.address.text","short":"Text representation of the address","definition":"Specifies the entire address as it should be displayed e.g. on a postal label. This may be provided instead of or as well as the specific parts.","comment":"Can provide both a text representation and parts. Applications updating an address SHALL ensure that when both text and parts are present, no content is included in the text that isn't found in a part.","requirements":"A renderable, unencoded form.","min":0,"max":"1","base":{"path":"Address.text","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"137 Nowhere Street, Erewhon 9132"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.address.line","path":"Patient.address.line","short":"Street name, number, direction & P.O. Box etc.","definition":"This component contains the house number, apartment number, street name, street direction, P.O. Box number, delivery hints, and similar address information.","min":0,"max":"*","base":{"path":"Address.line","min":0,"max":"*"},"type":[{"code":"string"}],"orderMeaning":"The order in which lines should appear in an address label","example":[{"label":"General","valueString":"137 Nowhere Street"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.city","path":"Patient.address.city","short":"Name of city, town etc.","definition":"The name of the city, town, suburb, village or other community or delivery center.","alias":["Municpality"],"min":0,"max":"1","base":{"path":"Address.city","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"Erewhon"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.district","path":"Patient.address.district","short":"District name (aka county)","definition":"The name of the administrative area (county).","comment":"District is sometimes known as county, but in some regions 'county' is used in place of city (municipality), so county name should be conveyed in city instead.","alias":["County"],"min":0,"max":"1","base":{"path":"Address.district","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"Madison"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.address.state","path":"Patient.address.state","short":"Sub-unit of country (abbreviations ok)","definition":"Sub-unit of a country with limited sovereignty in a federally organized country. A code may be used if codes are in common use (e.g. US 2 letter state codes).","alias":["Province","Territory"],"min":0,"max":"1","base":{"path":"Address.state","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"Two Letter USPS alphabetic codes.","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-usps-state"}},{"id":"Patient.address.postalCode","path":"Patient.address.postalCode","short":"US Zip Codes","definition":"A postal code designating a region defined by the postal service.","alias":["Zip","Zip Code"],"min":0,"max":"1","base":{"path":"Address.postalCode","min":0,"max":"1"},"type":[{"code":"string"}],"example":[{"label":"General","valueString":"9132"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.address.country","path":"Patient.address.country","short":"Country (e.g. can be ISO 3166 2 or 3 letter code)","definition":"Country - a nation as commonly understood or generally accepted.","comment":"ISO 3166 3 letter codes can be used in place of a human readable country name.","min":0,"max":"1","base":{"path":"Address.country","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.address.period","path":"Patient.address.period","short":"Time period when address was/is in use","definition":"Time period when address was/is in use.","requirements":"Allows addresses to be placed in historical context.","min":0,"max":"1","base":{"path":"Address.period","min":0,"max":"1"},"type":[{"code":"Period"}],"example":[{"label":"General","valuePeriod":{"start":"2010-03-23","end":"2010-07-01"}}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Patient.maritalStatus","path":"Patient.maritalStatus","short":"Marital (civil) status of a patient","definition":"This field contains a patient's most recent marital (civil) status.","requirements":"Most, if not all systems capture it.","min":0,"max":"1","base":{"path":"Patient.maritalStatus","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MaritalStatus"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"extensible","description":"The domestic partnership status of a person.","valueSet":"http://hl7.org/fhir/ValueSet/marital-status"}},{"id":"Patient.multipleBirth[x]","path":"Patient.multipleBirth[x]","short":"Whether patient is part of a multiple birth","definition":"Indicates whether the patient is part of a multiple (boolean) or indicates the actual birth order (integer).","comment":"Where the valueInteger is provided, the number is the birth number in the sequence. E.g. The middle birth in triplets would be valueInteger=2 and the third born would have valueInteger=3 If a boolean value was provided for this triplets example, then all 3 patient records would have valueBoolean=true (the ordering is not indicated).","requirements":"For disambiguation of multiple-birth children, especially relevant where the care provider doesn't meet the patient, such as labs.","min":0,"max":"1","base":{"path":"Patient.multipleBirth[x]","min":0,"max":"1"},"type":[{"code":"boolean"},{"code":"integer"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.photo","path":"Patient.photo","short":"Image of the patient","definition":"Image of the patient.","comment":"Guidelines:\n* Use id photos, not clinical photos.\n* Limit dimensions to thumbnail.\n* Keep byte count low to ease resource updates.","requirements":"Many EHR systems have the capability to capture an image of the patient. Fits with newer social media usage too.","min":0,"max":"*","base":{"path":"Patient.photo","min":0,"max":"*"},"type":[{"code":"Attachment"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name","valueString":"Contact"}],"path":"Patient.contact","short":"A contact party (e.g. guardian, partner, friend) for the patient","definition":"A contact party (e.g. guardian, partner, friend) for the patient.","comment":"Contact covers all kinds of contact parties: family members, business contacts, guardians, caregivers. Not applicable to register pedigree and family ties beyond use of having contact.","requirements":"Need to track people you can contact about the patient.","min":0,"max":"*","base":{"path":"Patient.contact","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"pat-1","severity":"error","human":"SHALL at least contain a contact's details or a reference to an organization","expression":"name.exists() or telecom.exists() or address.exists() or organization.exists()","xpath":"exists(f:name) or exists(f:telecom) or exists(f:address) or exists(f:organization)","source":"http://hl7.org/fhir/StructureDefinition/Patient"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.id","path":"Patient.contact.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.extension","path":"Patient.contact.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.modifierExtension","path":"Patient.contact.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Patient.contact.relationship","path":"Patient.contact.relationship","short":"The kind of relationship","definition":"The nature of the relationship between the patient and the contact person.","requirements":"Used to determine which contact person is the most relevant to approach, depending on circumstances.","min":0,"max":"*","base":{"path":"Patient.contact.relationship","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ContactRelationship"}],"strength":"extensible","description":"The nature of the relationship between a patient and a contact person for that patient.","valueSet":"http://hl7.org/fhir/ValueSet/patient-contactrelationship"}},{"id":"Patient.contact.name","path":"Patient.contact.name","short":"A name associated with the contact person","definition":"A name associated with the contact person.","requirements":"Contact persons need to be identified by name, but it is uncommon to need details about multiple other names for that contact person.","min":0,"max":"1","base":{"path":"Patient.contact.name","min":0,"max":"1"},"type":[{"code":"HumanName"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.telecom","path":"Patient.contact.telecom","short":"A contact detail for the person","definition":"A contact detail for the person, e.g. a telephone number or an email address.","comment":"Contact may have multiple ways to be contacted with different uses or applicable periods. May need to have options for contacting the person urgently, and also to help with identification.","requirements":"People have (primary) ways to contact them in some way such as phone, email.","min":0,"max":"*","base":{"path":"Patient.contact.telecom","min":0,"max":"*"},"type":[{"code":"ContactPoint"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.address","path":"Patient.contact.address","short":"Address for the contact person","definition":"Address for the contact person.","requirements":"Need to keep track where the contact person can be contacted per postal mail or visited.","min":0,"max":"1","base":{"path":"Patient.contact.address","min":0,"max":"1"},"type":[{"code":"Address"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.gender","path":"Patient.contact.gender","short":"male | female | other | unknown","definition":"Administrative Gender - the gender that the contact person is considered to have for administration and record keeping purposes.","requirements":"Needed to address the person correctly.","min":0,"max":"1","base":{"path":"Patient.contact.gender","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AdministrativeGender"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"required","description":"The gender of a person used for administrative purposes.","valueSet":"http://hl7.org/fhir/ValueSet/administrative-gender|4.0.1"}},{"id":"Patient.contact.organization","path":"Patient.contact.organization","short":"Organization that is associated with the contact","definition":"Organization on behalf of which the contact is acting or for which the contact is working.","requirements":"For guardians or business related contacts, the organization is relevant.","min":0,"max":"1","base":{"path":"Patient.contact.organization","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"condition":["pat-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.contact.period","path":"Patient.contact.period","short":"The period during which this contact person or organization is valid to be contacted relating to this patient","definition":"The period during which this contact person or organization is valid to be contacted relating to this patient.","min":0,"max":"1","base":{"path":"Patient.contact.period","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.communication","path":"Patient.communication","short":"A language which may be used to communicate with the patient about his or her health","definition":"A language which may be used to communicate with the patient about his or her health.","comment":"If no language is specified, this *implies* that the default local language is spoken. If you need to convey proficiency for multiple modes, then you need multiple Patient.Communication associations. For animals, language is not a relevant field, and should be absent from the instance. If the Patient does not speak the default local language, then the Interpreter Required Standard can be used to explicitly declare that an interpreter is required.","requirements":"If a patient does not speak the local language, interpreters may be required, so languages spoken and proficiency are important things to keep track of both for patient and other persons of interest.","min":0,"max":"*","base":{"path":"Patient.communication","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":false,"isModifier":false,"isSummary":false},{"id":"Patient.communication.id","path":"Patient.communication.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.communication.extension","path":"Patient.communication.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.communication.modifierExtension","path":"Patient.communication.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Patient.communication.language","path":"Patient.communication.language","short":"The language which can be used to communicate with the patient about his or her health","definition":"The ISO-639-1 alpha 2 code in lower case for the language, optionally followed by a hyphen and the ISO-3166-1 alpha 2 code for the region in upper case; e.g. \"en\" for English, or \"en-US\" for American English versus \"en-EN\" for England English.","comment":"The structure aa-BB with this exact casing is one the most widely used notations for locale. However not all systems actually code this but instead have it as free text. Hence CodeableConcept instead of code as the data type.","requirements":"Most systems in multilingual countries will want to convey language. Not all systems actually need the regional dialect.","min":1,"max":"1","base":{"path":"Patient.communication.language","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"strength":"extensible","valueSet":"http://hl7.org/fhir/us/core/ValueSet/simple-language"}},{"id":"Patient.communication.preferred","path":"Patient.communication.preferred","short":"Language preference indicator","definition":"Indicates whether or not the patient prefers this language (over other languages he masters up a certain level).","comment":"This language is specifically identified for communicating healthcare information.","requirements":"People that master multiple languages up to certain level may prefer one or more, i.e. feel more confident in communicating in a particular language making other languages sort of a fall back method.","min":0,"max":"1","base":{"path":"Patient.communication.preferred","min":0,"max":"1"},"type":[{"code":"boolean"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.generalPractitioner","path":"Patient.generalPractitioner","short":"Patient's nominated primary care provider","definition":"Patient's nominated care provider.","comment":"This may be the primary care provider (in a GP context), or it may be a patient nominated care manager in a community/disability setting, or even organization that will provide people to perform the care provider roles. It is not to be used to record Care Teams, these should be in a CareTeam resource that may be linked to the CarePlan or EpisodeOfCare resources.\nMultiple GPs may be recorded against the patient for various reasons, such as a student that has his home GP listed along with the GP at university during the school semesters, or a \"fly-in/fly-out\" worker that has the onsite GP also included with his home GP to remain aware of medical issues.\n\nJurisdictions may decide that they can profile this down to 1 if desired, or 1 per type.","alias":["careProvider"],"min":0,"max":"*","base":{"path":"Patient.generalPractitioner","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization","http://hl7.org/fhir/StructureDefinition/Practitioner","http://hl7.org/fhir/StructureDefinition/PractitionerRole"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Patient.managingOrganization","path":"Patient.managingOrganization","short":"Organization that is the custodian of the patient record","definition":"Organization that is the custodian of the patient record.","comment":"There is only one managing organization for a specific patient record. Other organizations will have their own Patient record, and may use the Link property to join the records together (or a Person resource which can include confidence ratings for the association).","requirements":"Need to know who recognizes this patient record, manages and updates it.","min":0,"max":"1","base":{"path":"Patient.managingOrganization","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.link","path":"Patient.link","short":"Link to another patient resource that concerns the same actual person","definition":"Link to another patient resource that concerns the same actual patient.","comment":"There is no assumption that linked patient records have mutual links.","requirements":"There are multiple use cases: \n\n* Duplicate patient records due to the clerical errors associated with the difficulties of identifying humans consistently, and \n* Distribution of patient information across multiple servers.","min":0,"max":"*","base":{"path":"Patient.link","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because it might not be the main Patient resource, and the referenced patient should be used instead of this Patient record. This is when the link.type value is 'replaced-by'","isSummary":true},{"id":"Patient.link.id","path":"Patient.link.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Patient.link.extension","path":"Patient.link.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Patient.link.modifierExtension","path":"Patient.link.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Patient.link.other","path":"Patient.link.other","short":"The other patient or related person resource that the link refers to","definition":"The other patient resource that the link refers to.","comment":"Referencing a RelatedPerson here removes the need to use a Person record to associate a Patient and RelatedPerson as the same individual.","min":1,"max":"1","base":{"path":"Patient.link.other","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-hierarchy","valueBoolean":false}],"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Patient","http://hl7.org/fhir/StructureDefinition/RelatedPerson"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Patient.link.type","path":"Patient.link.type","short":"replaced-by | replaces | refer | seealso","definition":"The type of link between this patient resource and another patient resource.","min":1,"max":"1","base":{"path":"Patient.link.type","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"LinkType"}],"strength":"required","description":"The type of link between this patient resource and another patient resource.","valueSet":"http://hl7.org/fhir/ValueSet/link-type|4.0.1"}}]}},{"resourceType":"StructureDefinition","id":"us-core-race","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","version":"5.0.1","name":"USCoreRaceExtension","title":"US Core Race Extension","status":"active","date":"2019-05-21","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The race codes used to represent these concepts are based upon the [CDC Race and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 921 reference race. The race concepts are grouped by and pre-mapped to the 5 OMB race categories:\n\n - American Indian or Alaska Native\n - Asian\n - Black or African American\n - Native Hawaiian or Other Pacific Islander\n - White.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"purpose":"Complies with 2015 Edition Common Clinical Data Set for patient race.","copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"},{"type":"element","expression":"RelatedPerson"},{"type":"element","expression":"Person"},{"type":"element","expression":"Practitioner"},{"type":"element","expression":"FamilyMemberHistory"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Extension","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"US Core Race Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The race codes used to represent these concepts are based upon the [CDC Race and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 921 reference race. The race concepts are grouped by and pre-mapped to the 5 OMB race categories:\n\n - American Indian or Alaska Native\n - Asian\n - Black or African American\n - Native Hawaiian or Other Pacific Islander\n - White.","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory","path":"Extension.extension","sliceName":"ombCategory","short":"American Indian or Alaska Native|Asian|Black or African American|Native Hawaiian or Other Pacific Islander|White","definition":"The 5 race category codes according to the [OMB Standards for Maintaining, Collecting, and Presenting Federal Data on Race and Ethnicity, Statistical Policy Directive No. 15, as revised, October 30, 1997](https://www.govinfo.gov/content/pkg/FR-1997-10-30/pdf/97-28653.pdf).","min":0,"max":"5","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"ombCategory","isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","description":"The 5 race category codes according to the [OMB Standards for Maintaining, Collecting, and Presenting Federal Data on Race and Ethnicity, Statistical Policy Directive No. 15, as revised, October 30, 1997](https://www.govinfo.gov/content/pkg/FR-1997-10-30/pdf/97-28653.pdf).","valueSet":"http://hl7.org/fhir/us/core/ValueSet/omb-race-category"}},{"id":"Extension.extension:detailed","path":"Extension.extension","sliceName":"detailed","short":"Extended race codes","definition":"The 900+ CDC race codes that are grouped under one of the 5 OMB race category codes:.","min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"detailed","isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/us/core/ValueSet/detailed-race"}},{"id":"Extension.extension:text","path":"Extension.extension","sliceName":"text","short":"Race Text","definition":"Plain text representation of the race concept(s).","min":1,"max":"1","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"text","isModifier":false,"isSummary":false},{"id":"Extension.extension:text.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":0,"max":"0","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"base64Binary"},{"code":"boolean"},{"code":"canonical"},{"code":"code"},{"code":"date"},{"code":"dateTime"},{"code":"decimal"},{"code":"id"},{"code":"instant"},{"code":"integer"},{"code":"markdown"},{"code":"oid"},{"code":"positiveInt"},{"code":"string"},{"code":"time"},{"code":"unsignedInt"},{"code":"uri"},{"code":"url"},{"code":"uuid"},{"code":"Address"},{"code":"Age"},{"code":"Annotation"},{"code":"Attachment"},{"code":"CodeableConcept"},{"code":"Coding"},{"code":"ContactPoint"},{"code":"Count"},{"code":"Distance"},{"code":"Duration"},{"code":"HumanName"},{"code":"Identifier"},{"code":"Money"},{"code":"Period"},{"code":"Quantity"},{"code":"Range"},{"code":"Ratio"},{"code":"Reference"},{"code":"SampledData"},{"code":"Signature"},{"code":"Timing"},{"code":"ContactDetail"},{"code":"Contributor"},{"code":"DataRequirement"},{"code":"Expression"},{"code":"ParameterDefinition"},{"code":"RelatedArtifact"},{"code":"TriggerDefinition"},{"code":"UsageContext"},{"code":"Dosage"},{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}},{"resourceType":"StructureDefinition","id":"us-core-ethnicity","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","version":"5.0.1","name":"USCoreEthnicityExtension","title":"US Core Ethnicity Extension","status":"active","date":"2019-05-21T00:00:00-04:00","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The ethnicity codes used to represent these concepts are based upon the [CDC ethnicity and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 43 reference ethnicity. The ethnicity concepts are grouped by and pre-mapped to the 2 OMB ethnicity categories: - Hispanic or Latino - Not Hispanic or Latino.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"purpose":"Complies with 2015 Edition Common Clinical Data Set for patient race.","copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"},{"type":"element","expression":"RelatedPerson"},{"type":"element","expression":"Person"},{"type":"element","expression":"Practitioner"},{"type":"element","expression":"FamilyMemberHistory"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Extension","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"US Core ethnicity Extension","definition":"Concepts classifying the person into a named category of humans sharing common history, traits, geographical origin or nationality. The ethnicity codes used to represent these concepts are based upon the [CDC ethnicity and Ethnicity Code Set Version 1.0](http://www.cdc.gov/phin/resources/vocabulary/index.html) which includes over 900 concepts for representing race and ethnicity of which 43 reference ethnicity. The ethnicity concepts are grouped by and pre-mapped to the 2 OMB ethnicity categories: - Hispanic or Latino - Not Hispanic or Latino.","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory","path":"Extension.extension","sliceName":"ombCategory","short":"Hispanic or Latino|Not Hispanic or Latino","definition":"The 2 ethnicity category codes according to the [OMB Standards for Maintaining, Collecting, and Presenting Federal Data on Race and Ethnicity, Statistical Policy Directive No. 15, as revised, October 30, 1997](https://www.govinfo.gov/content/pkg/FR-1997-10-30/pdf/97-28653.pdf).","min":0,"max":"1","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"ombCategory","isModifier":false,"isSummary":false},{"id":"Extension.extension:ombCategory.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/us/core/ValueSet/omb-ethnicity-category"}},{"id":"Extension.extension:detailed","path":"Extension.extension","sliceName":"detailed","short":"Extended ethnicity codes","definition":"The 41 CDC ethnicity codes that are grouped under one of the 2 OMB ethnicity category codes.","min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"detailed","isModifier":false,"isSummary":false},{"id":"Extension.extension:detailed.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","valueSet":"http://hl7.org/fhir/us/core/ValueSet/detailed-ethnicity"}},{"id":"Extension.extension:text","path":"Extension.extension","sliceName":"text","short":"ethnicity Text","definition":"Plain text representation of the ethnicity concept(s).","min":1,"max":"1","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.id","path":"Extension.extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.extension","path":"Extension.extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension:text.url","path":"Extension.extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"text","isModifier":false,"isSummary":false},{"id":"Extension.extension:text.value[x]","path":"Extension.extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":0,"max":"0","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"base64Binary"},{"code":"boolean"},{"code":"canonical"},{"code":"code"},{"code":"date"},{"code":"dateTime"},{"code":"decimal"},{"code":"id"},{"code":"instant"},{"code":"integer"},{"code":"markdown"},{"code":"oid"},{"code":"positiveInt"},{"code":"string"},{"code":"time"},{"code":"unsignedInt"},{"code":"uri"},{"code":"url"},{"code":"uuid"},{"code":"Address"},{"code":"Age"},{"code":"Annotation"},{"code":"Attachment"},{"code":"CodeableConcept"},{"code":"Coding"},{"code":"ContactPoint"},{"code":"Count"},{"code":"Distance"},{"code":"Duration"},{"code":"HumanName"},{"code":"Identifier"},{"code":"Money"},{"code":"Period"},{"code":"Quantity"},{"code":"Range"},{"code":"Ratio"},{"code":"Reference"},{"code":"SampledData"},{"code":"Signature"},{"code":"Timing"},{"code":"ContactDetail"},{"code":"Contributor"},{"code":"DataRequirement"},{"code":"Expression"},{"code":"ParameterDefinition"},{"code":"RelatedArtifact"},{"code":"TriggerDefinition"},{"code":"UsageContext"},{"code":"Dosage"},{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}},{"resourceType":"StructureDefinition","id":"us-core-birthsex","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","version":"5.0.1","name":"USCoreBirthSexExtension","title":"US Core Birth Sex Extension","status":"active","date":"2019-05-21","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"A code classifying the person's sex assigned at birth as specified by the [Office of the National Coordinator for Health IT (ONC)](https://www.healthit.gov/newsroom/about-onc). This extension aligns with the C-CDA Birth Sex Observation (LOINC 76689-9).","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Extension","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"Extension","definition":"A code classifying the person's sex assigned at birth as specified by the [Office of the National Coordinator for Health IT (ONC)](https://www.healthit.gov/newsroom/about-onc).","comment":"The codes required are intended to present birth sex (i.e., the sex recorded on the patient’s birth certificate) and not gender identity or reassigned sex.","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R4/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"required","description":"Code for sex assigned at birth","valueSet":"http://hl7.org/fhir/us/core/ValueSet/birthsex"}}]}},{"resourceType":"StructureDefinition","id":"us-core-genderIdentity","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity","version":"5.0.1","name":"USCoreGenderIdentityExtension","title":"US Core Gender Identity Extension","status":"active","date":"2022-01-22","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"This extension provides concepts to describe the gender a person identifies as.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"purpose":"Complies with USCDI v2","copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"complex-type","abstract":false,"context":[{"type":"element","expression":"Patient"},{"type":"element","expression":"RelatedPerson"},{"type":"element","expression":"Person"},{"type":"element","expression":"Practitioner"}],"type":"Extension","baseDefinition":"http://hl7.org/fhir/StructureDefinition/patient-genderIdentity","derivation":"constraint","snapshot":{"element":[{"id":"Extension","path":"Extension","short":"Extension","definition":"An Extension","min":0,"max":"1","base":{"path":"Extension","min":0,"max":"*"},"condition":["ele-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false},{"id":"Extension.id","path":"Extension.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Extension.extension","path":"Extension.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Extension","definition":"An Extension","min":0,"max":"0","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Extension.url","path":"Extension.url","representation":["xmlAttr"],"short":"identifies the meaning of the extension","definition":"Source of the definition for the extension code - a logical name or a URL.","comment":"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.","min":1,"max":"1","base":{"path":"Extension.url","min":1,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"uri"}],"code":"http://hl7.org/fhirpath/System.String"}],"fixedUri":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity","isModifier":false,"isSummary":false},{"id":"Extension.value[x]","path":"Extension.value[x]","short":"Value of extension","definition":"Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/extensibility.html) for a list).","min":1,"max":"1","base":{"path":"Extension.value[x]","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"extensible","valueSet":"http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1021.32"}}]}},{"resourceType":"StructureDefinition","id":"us-core-implantable-device","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-implantable-device","version":"5.0.1","name":"USCoreImplantableDeviceProfile","title":"US Core Implantable Device Profile","status":"active","experimental":false,"date":"2022-04-20","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"The US Core Implantable Device Profile is based upon the core FHIR Device Resource and meets the U.S. Core Data for Interoperability (USCDI) v2 ‘Unique Device Identifier(s) for a Patient’s Implantable Device(s)’ requirements. To promote interoperability and adoption through common implementation, this profile sets minimum expectations for the Device resource to record, search, and fetch UDI information associated with a patient's implantable device(s). It identifies which core elements, extensions, vocabularies and value sets **SHALL** be present in the resource when using this profile. It provides the floor for standards development for specific uses cases.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"resource","abstract":false,"type":"Device","baseDefinition":"http://hl7.org/fhir/StructureDefinition/Device","derivation":"constraint","snapshot":{"element":[{"id":"Device","path":"Device","short":"Item used in healthcare","definition":"\\-","comment":"\\-","min":0,"max":"*","base":{"path":"Device","min":0,"max":"*"},"constraint":[{"key":"dom-2","severity":"error","human":"If the resource is contained in another resource, it SHALL NOT contain nested Resources","expression":"contained.contained.empty()","xpath":"not(parent::f:contained and f:contained)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-3","severity":"error","human":"If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource","expression":"contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()","xpath":"not(exists(for $id in f:contained/*/f:id/@value return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-4","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated","expression":"contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()","xpath":"not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-5","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a security label","expression":"contained.meta.security.empty()","xpath":"not(exists(f:contained/*/f:meta/f:security))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice","valueBoolean":true},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice-explanation","valueMarkdown":"When a resource has no narrative, only systems that fully understand the data can display the resource to a human safely. Including a human readable representation in the resource makes for a much more robust eco-system and cheaper handling of resources by intermediary systems. Some ecosystems restrict distribution of resources to only those systems that do fully understand the resources, and as a consequence implementers may believe that the narrative is superfluous. However experience shows that such eco-systems often open up to new participants over time."}],"key":"dom-6","severity":"warning","human":"A resource should have narrative for robust management","expression":"text.`div`.exists()","xpath":"exists(f:text/h:div)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"}],"mustSupport":false,"isModifier":false,"isSummary":false},{"id":"Device.id","path":"Device.id","short":"Logical id of this artifact","definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.","comment":"The only time that a resource does not have an id is when it is being submitted to the server using a create operation.","min":0,"max":"1","base":{"path":"Resource.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":true},{"id":"Device.meta","path":"Device.meta","short":"Metadata about the resource","definition":"The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.","min":0,"max":"1","base":{"path":"Resource.meta","min":0,"max":"1"},"type":[{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Device.implicitRules","path":"Device.implicitRules","short":"A set of rules under which this content was created","definition":"A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.","comment":"Asserting this rule set restricts the content to be only understood by a limited set of trading partners. This inherently limits the usefulness of the data in the long term. However, the existing health eco-system is highly fractured, and not yet ready to define, collect, and exchange data in a generally computable sense. Wherever possible, implementers and/or specification writers should avoid using this element. Often, when used, the URL is a reference to an implementation guide that defines these special rules as part of it's narrative along with other profiles, value sets, etc.","min":0,"max":"1","base":{"path":"Resource.implicitRules","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because the implicit rules may provide additional knowledge about the resource that modifies it's meaning or interpretation","isSummary":true},{"id":"Device.language","path":"Device.language","short":"Language of the resource content","definition":"The base language in which the resource is written.","comment":"Language is provided to support indexing and accessibility (typically, services such as text to speech use the language tag). The html language tag in the narrative applies to the narrative. The language tag on the resource may be used to specify the language of other presentations generated from the data in the resource. Not all the content has to be in the base language. The Resource.language should not be assumed to apply to the narrative automatically. If a language is specified, it should it also be specified on the div element in the html (see rules in HTML5 for information about the relationship between xml:lang and the html lang attribute).","min":0,"max":"1","base":{"path":"Resource.language","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/all-languages"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Language"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"preferred","description":"A human language.","valueSet":"http://hl7.org/fhir/ValueSet/languages"}},{"id":"Device.text","path":"Device.text","short":"Text summary of the resource, for human interpretation","definition":"A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it \"clinically safe\" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety.","comment":"Contained resources do not have narrative. Resources that are not contained SHOULD have a narrative. In some cases, a resource may only have text with little or no additional discrete data (as long as all minOccurs=1 elements are satisfied). This may be necessary for data from legacy systems where information is captured as a \"text blob\" or where text is additionally entered raw or narrated and encoded information is added later.","alias":["narrative","html","xhtml","display"],"min":0,"max":"1","base":{"path":"DomainResource.text","min":0,"max":"1"},"type":[{"code":"Narrative"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.contained","path":"Device.contained","short":"Contained, inline Resources","definition":"These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope.","comment":"This should never be done when the content can be identified properly, as once identification is lost, it is extremely difficult (and context dependent) to restore it again. Contained resources may have profiles and tags In their meta elements, but SHALL NOT have security labels.","alias":["inline resources","anonymous resources","contained resources"],"min":0,"max":"*","base":{"path":"DomainResource.contained","min":0,"max":"*"},"type":[{"code":"Resource"}],"isModifier":false,"isSummary":false},{"id":"Device.extension","path":"Device.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the resource. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.modifierExtension","path":"Device.modifierExtension","short":"Extensions that cannot be ignored","definition":"May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the resource that contains them","isSummary":false},{"id":"Device.identifier","path":"Device.identifier","short":"Instance identifier","definition":"Unique instance identifiers assigned to a device by manufacturers other organizations or owners.","comment":"The barcode string from a barcode present on a device label or package may identify the instance, include names given to the device in local usage, or may identify the type of device. If the identifier identifies the type of device, Device.type element should be used.","min":0,"max":"*","base":{"path":"Device.identifier","min":0,"max":"*"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.definition","path":"Device.definition","short":"The reference to the definition for the device","definition":"The reference to the definition for the device.","min":0,"max":"1","base":{"path":"Device.definition","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/DeviceDefinition"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier","path":"Device.udiCarrier","short":"Unique Device Identifier (UDI) Barcode string","definition":"Unique device identifier (UDI) assigned to device label or package. Note that the Device may include multiple udiCarriers as it either may include just the udiCarrier for the jurisdiction it is sold, or for multiple jurisdictions it could have been sold.","comment":"Some devices may not have UDI information (for example. historical data or patient reported data).","min":0,"max":"1","base":{"path":"Device.udiCarrier","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.id","path":"Device.udiCarrier.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.extension","path":"Device.udiCarrier.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.modifierExtension","path":"Device.udiCarrier.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.udiCarrier.deviceIdentifier","path":"Device.udiCarrier.deviceIdentifier","short":"Mandatory fixed portion of UDI","definition":"The device identifier (DI) is a mandatory, fixed portion of a UDI that identifies the labeler and the specific version or model of a device.","alias":["DI"],"min":1,"max":"1","base":{"path":"Device.udiCarrier.deviceIdentifier","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.issuer","path":"Device.udiCarrier.issuer","short":"UDI Issuing Organization","definition":"Organization that is charged with issuing UDIs for devices. For example, the US FDA issuers include :\n1) GS1: \nhttp://hl7.org/fhir/NamingSystem/gs1-di, \n2) HIBCC:\nhttp://hl7.org/fhir/NamingSystem/hibcc-dI, \n3) ICCBBA for blood containers:\nhttp://hl7.org/fhir/NamingSystem/iccbba-blood-di, \n4) ICCBA for other devices:\nhttp://hl7.org/fhir/NamingSystem/iccbba-other-di.","alias":["Barcode System"],"min":0,"max":"1","base":{"path":"Device.udiCarrier.issuer","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.jurisdiction","path":"Device.udiCarrier.jurisdiction","short":"Regional UDI authority","definition":"The identity of the authoritative source for UDI generation within a jurisdiction. All UDIs are globally unique within a single namespace with the appropriate repository uri as the system. For example, UDIs of devices managed in the U.S. by the FDA, the value is http://hl7.org/fhir/NamingSystem/fda-udi.","requirements":"Allows a recipient of a UDI to know which database will contain the UDI-associated metadata.","min":0,"max":"1","base":{"path":"Device.udiCarrier.jurisdiction","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.udiCarrier.carrierAIDC","path":"Device.udiCarrier.carrierAIDC","short":"UDI Machine Readable Barcode String","definition":"The full UDI carrier of the Automatic Identification and Data Capture (AIDC) technology representation of the barcode string as printed on the packaging of the device - e.g., a barcode or RFID. Because of limitations on character sets in XML and the need to round-trip JSON data through XML, AIDC Formats *SHALL* be base64 encoded.","comment":"The AIDC form of UDIs should be scanned or otherwise used for the identification of the device whenever possible to minimize errors in records resulting from manual transcriptions. If separate barcodes for DI and PI are present, concatenate the string with DI first and in order of human readable expression on label.","alias":["Automatic Identification and Data Capture"],"min":0,"max":"1","base":{"path":"Device.udiCarrier.carrierAIDC","min":0,"max":"1"},"type":[{"code":"base64Binary"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.carrierHRF","path":"Device.udiCarrier.carrierHRF","short":"UDI Human Readable Barcode String","definition":"The full UDI carrier as the human readable form (HRF) representation of the barcode string as printed on the packaging of the device.","comment":"If separate barcodes for DI and PI are present, concatenate the string with DI first and in order of human readable expression on label.","alias":["Human Readable Form","UDI","Barcode String"],"min":0,"max":"1","base":{"path":"Device.udiCarrier.carrierHRF","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Device.udiCarrier.entryType","path":"Device.udiCarrier.entryType","short":"barcode | rfid | manual +","definition":"A coded entry to indicate how the data was entered.","requirements":"Supports a way to distinguish hand entered from machine read data.","min":0,"max":"1","base":{"path":"Device.udiCarrier.entryType","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"UDIEntryType"}],"strength":"required","description":"Codes to identify how UDI data was entered.","valueSet":"http://hl7.org/fhir/ValueSet/udi-entry-type|4.0.1"}},{"id":"Device.status","path":"Device.status","short":"active | inactive | entered-in-error | unknown","definition":"Status of the Device availability.","comment":"This element is labeled as a modifier because the status contains the codes inactive and entered-in-error that mark the device (record)as not currently valid.","min":0,"max":"1","base":{"path":"Device.status","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labelled as a modifier because it is a status element that contains status entered-in-error which means that the resource should not be treated as valid","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"FHIRDeviceStatus"}],"strength":"required","description":"The availability status of the device.","valueSet":"http://hl7.org/fhir/ValueSet/device-status|4.0.1"}},{"id":"Device.statusReason","path":"Device.statusReason","short":"online | paused | standby | offline | not-ready | transduc-discon | hw-discon | off","definition":"Reason for the dtatus of the Device availability.","min":0,"max":"*","base":{"path":"Device.statusReason","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"FHIRDeviceStatusReason"}],"strength":"extensible","description":"The availability status reason of the device.","valueSet":"http://hl7.org/fhir/ValueSet/device-status-reason"}},{"id":"Device.distinctIdentifier","path":"Device.distinctIdentifier","short":"The distinct identification string","definition":"The distinct identification string as required by regulation for a human cell, tissue, or cellular and tissue-based product.","comment":"For example, this applies to devices in the United States regulated under *Code of Federal Regulation 21CFR§1271.290(c)*.","alias":["Distinct Identification Code (DIC)"],"min":0,"max":"1","base":{"path":"Device.distinctIdentifier","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.manufacturer","path":"Device.manufacturer","short":"Name of device manufacturer","definition":"A name of the manufacturer.","min":0,"max":"1","base":{"path":"Device.manufacturer","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.manufactureDate","path":"Device.manufactureDate","short":"Date when the device was made","definition":"The date and time when the device was manufactured.","min":0,"max":"1","base":{"path":"Device.manufactureDate","min":0,"max":"1"},"type":[{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.expirationDate","path":"Device.expirationDate","short":"Date and time of expiry of this device (if applicable)","definition":"The date and time beyond which this device is no longer valid or should not be used (if applicable).","min":0,"max":"1","base":{"path":"Device.expirationDate","min":0,"max":"1"},"type":[{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.lotNumber","path":"Device.lotNumber","short":"Lot number of manufacture","definition":"Lot number assigned by the manufacturer.","min":0,"max":"1","base":{"path":"Device.lotNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.serialNumber","path":"Device.serialNumber","short":"Serial number assigned by the manufacturer","definition":"The serial number assigned by the organization when the device was manufactured.","comment":"Alphanumeric Maximum 20.","min":0,"max":"1","base":{"path":"Device.serialNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.deviceName","path":"Device.deviceName","short":"The name of the device as given by the manufacturer","definition":"This represents the manufacturer's name of the device as provided by the device, from a UDI label, or by a person describing the Device. This typically would be used when a person provides the name(s) or when the device represents one of the names available from DeviceDefinition.","min":0,"max":"*","base":{"path":"Device.deviceName","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.id","path":"Device.deviceName.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.extension","path":"Device.deviceName.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.modifierExtension","path":"Device.deviceName.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.deviceName.name","path":"Device.deviceName.name","short":"The name of the device","definition":"The name of the device.","alias":["Σ"],"min":1,"max":"1","base":{"path":"Device.deviceName.name","min":1,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.deviceName.type","path":"Device.deviceName.type","short":"udi-label-name | user-friendly-name | patient-reported-name | manufacturer-name | model-name | other","definition":"The type of deviceName.\nUDILabelName | UserFriendlyName | PatientReportedName | ManufactureDeviceName | ModelName.","min":1,"max":"1","base":{"path":"Device.deviceName.type","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"DeviceNameType"}],"strength":"required","description":"The type of name the device is referred by.","valueSet":"http://hl7.org/fhir/ValueSet/device-nametype|4.0.1"}},{"id":"Device.modelNumber","path":"Device.modelNumber","short":"The model number for the device","definition":"The model number for the device.","min":0,"max":"1","base":{"path":"Device.modelNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.partNumber","path":"Device.partNumber","short":"The part number of the device","definition":"The part number of the device.","comment":"Alphanumeric Maximum 20.","min":0,"max":"1","base":{"path":"Device.partNumber","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.type","path":"Device.type","short":"The kind or type of device","definition":"The kind or type of device.","min":1,"max":"1","base":{"path":"Device.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"strength":"extensible","description":"Codes to identify medical devices","valueSet":"http://hl7.org/fhir/ValueSet/device-kind"}},{"id":"Device.specialization","path":"Device.specialization","short":"The capabilities supported on a device, the standards to which the device conforms for a particular purpose, and used for the communication","definition":"The capabilities supported on a device, the standards to which the device conforms for a particular purpose, and used for the communication.","min":0,"max":"*","base":{"path":"Device.specialization","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.id","path":"Device.specialization.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.extension","path":"Device.specialization.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.modifierExtension","path":"Device.specialization.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.specialization.systemType","path":"Device.specialization.systemType","short":"The standard that is used to operate and communicate","definition":"The standard that is used to operate and communicate.","alias":["Σ"],"min":1,"max":"1","base":{"path":"Device.specialization.systemType","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.specialization.version","path":"Device.specialization.version","short":"The version of the standard that is used to operate and communicate","definition":"The version of the standard that is used to operate and communicate.","min":0,"max":"1","base":{"path":"Device.specialization.version","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version","path":"Device.version","short":"The actual design of the device or software version running on the device","definition":"The actual design of the device or software version running on the device.","min":0,"max":"*","base":{"path":"Device.version","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version.id","path":"Device.version.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.version.extension","path":"Device.version.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.version.modifierExtension","path":"Device.version.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.version.type","path":"Device.version.type","short":"The type of the device version","definition":"The type of the device version.","alias":["Σ"],"min":0,"max":"1","base":{"path":"Device.version.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version.component","path":"Device.version.component","short":"A single component of the device version","definition":"A single component of the device version.","min":0,"max":"1","base":{"path":"Device.version.component","min":0,"max":"1"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.version.value","path":"Device.version.value","short":"The version text","definition":"The version text.","min":1,"max":"1","base":{"path":"Device.version.value","min":1,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property","path":"Device.property","short":"The actual configuration settings of a device as it actually operates, e.g., regulation status, time properties","definition":"The actual configuration settings of a device as it actually operates, e.g., regulation status, time properties.","min":0,"max":"*","base":{"path":"Device.property","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property.id","path":"Device.property.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Device.property.extension","path":"Device.property.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Device.property.modifierExtension","path":"Device.property.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Device.property.type","path":"Device.property.type","short":"Code that specifies the property DeviceDefinitionPropetyCode (Extensible)","definition":"Code that specifies the property DeviceDefinitionPropetyCode (Extensible).","min":1,"max":"1","base":{"path":"Device.property.type","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property.valueQuantity","path":"Device.property.valueQuantity","short":"Property value as a quantity","definition":"Property value as a quantity.","min":0,"max":"*","base":{"path":"Device.property.valueQuantity","min":0,"max":"*"},"type":[{"code":"Quantity"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.property.valueCode","path":"Device.property.valueCode","short":"Property value as a code, e.g., NTP4 (synced to NTP)","definition":"Property value as a code, e.g., NTP4 (synced to NTP).","min":0,"max":"*","base":{"path":"Device.property.valueCode","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.patient","path":"Device.patient","short":"Patient to whom Device is affixed","definition":"Patient information, If the device is affixed to a person.","requirements":"If the device is implanted in a patient, then need to associate the device to the patient.","min":1,"max":"1","base":{"path":"Device.patient","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"Device.owner","path":"Device.owner","short":"Organization responsible for device","definition":"An organization that is responsible for the provision and ongoing maintenance of the device.","min":0,"max":"1","base":{"path":"Device.owner","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.contact","path":"Device.contact","short":"Details for human/organization for support","definition":"Contact details for an organization or a particular human that is responsible for the device.","comment":"used for troubleshooting etc.","min":0,"max":"*","base":{"path":"Device.contact","min":0,"max":"*"},"type":[{"code":"ContactPoint"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.location","path":"Device.location","short":"Where the device is found","definition":"The place where the device can be found.","requirements":"Device.location can be used to track device location.","min":0,"max":"1","base":{"path":"Device.location","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Location"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.url","path":"Device.url","short":"Network address to contact device","definition":"A network address on which the device may be contacted directly.","comment":"If the device is running a FHIR server, the network address should be the Base URL from which a conformance statement may be retrieved.","min":0,"max":"1","base":{"path":"Device.url","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.note","path":"Device.note","short":"Device notes and comments","definition":"Descriptive information, usage information or implantation information that is not captured in an existing element.","min":0,"max":"*","base":{"path":"Device.note","min":0,"max":"*"},"type":[{"code":"Annotation"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Device.safety","path":"Device.safety","short":"Safety Characteristics of Device","definition":"Provides additional safety characteristics about a medical device. For example devices containing latex.","min":0,"max":"*","base":{"path":"Device.safety","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Device.parent","path":"Device.parent","short":"The parent device","definition":"The parent device.","min":0,"max":"1","base":{"path":"Device.parent","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Device"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}},{"resourceType":"StructureDefinition","id":"us-core-blood-pressure","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-blood-pressure","version":"5.0.1","name":"USCoreBloodPressureProfile","title":"US Core Blood Pressure Profile","status":"active","experimental":false,"date":"2022-04-20","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"To promote interoperability and adoption through common implementation, this profile sets minimum expectations for the Observation resource to record, search, and fetch diastolic and systolic blood pressure observations with standard LOINC codes and UCUM units of measure. It is based on the US Core Vital Signs Profile and identifies the *additional* mandatory core elements, extensions, vocabularies and value sets which **SHALL** be present in the Observation resource when using this profile. It provides the floor for standards development for specific uses cases.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"resource","abstract":false,"type":"Observation","baseDefinition":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-vital-signs","derivation":"constraint","snapshot":{"element":[{"id":"Observation","path":"Observation","short":"US Core Blood Pressure Profile","definition":"\\-","comment":"\\-","alias":["Vital Signs","Measurement","Results","Tests"],"min":0,"max":"*","base":{"path":"Observation","min":0,"max":"*"},"constraint":[{"key":"dom-2","severity":"error","human":"If the resource is contained in another resource, it SHALL NOT contain nested Resources","expression":"contained.contained.empty()","xpath":"not(parent::f:contained and f:contained)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-3","severity":"error","human":"If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource","expression":"contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()","xpath":"not(exists(for $id in f:contained/*/f:id/@value return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-4","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated","expression":"contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()","xpath":"not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-5","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a security label","expression":"contained.meta.security.empty()","xpath":"not(exists(f:contained/*/f:meta/f:security))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice","valueBoolean":true},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice-explanation","valueMarkdown":"When a resource has no narrative, only systems that fully understand the data can display the resource to a human safely. Including a human readable representation in the resource makes for a much more robust eco-system and cheaper handling of resources by intermediary systems. Some ecosystems restrict distribution of resources to only those systems that do fully understand the resources, and as a consequence implementers may believe that the narrative is superfluous. However experience shows that such eco-systems often open up to new participants over time."}],"key":"dom-6","severity":"warning","human":"A resource should have narrative for robust management","expression":"text.`div`.exists()","xpath":"exists(f:text/h:div)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"obs-6","severity":"error","human":"dataAbsentReason SHALL only be present if Observation.value[x] is not present","expression":"dataAbsentReason.empty() or value.empty()","xpath":"not(exists(f:dataAbsentReason)) or (not(exists(*[starts-with(local-name(.), 'value')])))","source":"http://hl7.org/fhir/StructureDefinition/Observation"},{"key":"obs-7","severity":"error","human":"If Observation.code is the same as an Observation.component.code then the value element associated with the code SHALL NOT be present","expression":"value.empty() or component.code.where(coding.intersect(%resource.code.coding).exists()).empty()","xpath":"not(f:*[starts-with(local-name(.), 'value')] and (for $coding in f:code/f:coding return f:component/f:code/f:coding[f:code/@value=$coding/f:code/@value] [f:system/@value=$coding/f:system/@value]))","source":"http://hl7.org/fhir/StructureDefinition/Observation"},{"key":"vs-2","severity":"error","human":"If there is no component or hasMember element then either a value[x] or a data absent reason must be present.","expression":"(component.empty() and hasMember.empty()) implies (dataAbsentReason.exists() or value.exists())","xpath":"f:component or f:memberOF or f:*[starts-with(local-name(.), 'value')] or f:dataAbsentReason","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"isModifier":false,"isSummary":false},{"id":"Observation.id","path":"Observation.id","short":"Logical id of this artifact","definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.","comment":"The only time that a resource does not have an id is when it is being submitted to the server using a create operation.","min":0,"max":"1","base":{"path":"Resource.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":true},{"id":"Observation.meta","path":"Observation.meta","short":"Metadata about the resource","definition":"The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.","min":0,"max":"1","base":{"path":"Resource.meta","min":0,"max":"1"},"type":[{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.implicitRules","path":"Observation.implicitRules","short":"A set of rules under which this content was created","definition":"A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.","comment":"Asserting this rule set restricts the content to be only understood by a limited set of trading partners. This inherently limits the usefulness of the data in the long term. However, the existing health eco-system is highly fractured, and not yet ready to define, collect, and exchange data in a generally computable sense. Wherever possible, implementers and/or specification writers should avoid using this element. Often, when used, the URL is a reference to an implementation guide that defines these special rules as part of it's narrative along with other profiles, value sets, etc.","min":0,"max":"1","base":{"path":"Resource.implicitRules","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because the implicit rules may provide additional knowledge about the resource that modifies it's meaning or interpretation","isSummary":true},{"id":"Observation.language","path":"Observation.language","short":"Language of the resource content","definition":"The base language in which the resource is written.","comment":"Language is provided to support indexing and accessibility (typically, services such as text to speech use the language tag). The html language tag in the narrative applies to the narrative. The language tag on the resource may be used to specify the language of other presentations generated from the data in the resource. Not all the content has to be in the base language. The Resource.language should not be assumed to apply to the narrative automatically. If a language is specified, it should it also be specified on the div element in the html (see rules in HTML5 for information about the relationship between xml:lang and the html lang attribute).","min":0,"max":"1","base":{"path":"Resource.language","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/all-languages"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Language"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"preferred","description":"A human language.","valueSet":"http://hl7.org/fhir/ValueSet/languages"}},{"id":"Observation.text","path":"Observation.text","short":"Text summary of the resource, for human interpretation","definition":"A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it \"clinically safe\" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety.","comment":"Contained resources do not have narrative. Resources that are not contained SHOULD have a narrative. In some cases, a resource may only have text with little or no additional discrete data (as long as all minOccurs=1 elements are satisfied). This may be necessary for data from legacy systems where information is captured as a \"text blob\" or where text is additionally entered raw or narrated and encoded information is added later.","alias":["narrative","html","xhtml","display"],"min":0,"max":"1","base":{"path":"DomainResource.text","min":0,"max":"1"},"type":[{"code":"Narrative"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.contained","path":"Observation.contained","short":"Contained, inline Resources","definition":"These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope.","comment":"This should never be done when the content can be identified properly, as once identification is lost, it is extremely difficult (and context dependent) to restore it again. Contained resources may have profiles and tags In their meta elements, but SHALL NOT have security labels.","alias":["inline resources","anonymous resources","contained resources"],"min":0,"max":"*","base":{"path":"DomainResource.contained","min":0,"max":"*"},"type":[{"code":"Resource"}],"isModifier":false,"isSummary":false},{"id":"Observation.extension","path":"Observation.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the resource. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.modifierExtension","path":"Observation.modifierExtension","short":"Extensions that cannot be ignored","definition":"May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/extensibility.html#modifierExtension).","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the resource that contains them","isSummary":false},{"id":"Observation.identifier","path":"Observation.identifier","short":"Business Identifier for observation","definition":"A unique identifier assigned to this observation.","requirements":"Allows observations to be distinguished and referenced.","min":0,"max":"*","base":{"path":"Observation.identifier","min":0,"max":"*"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.basedOn","path":"Observation.basedOn","short":"Fulfills plan, proposal or order","definition":"A plan, proposal or order that is fulfilled in whole or in part by this event. For example, a MedicationRequest may require a patient to have laboratory test performed before it is dispensed.","requirements":"Allows tracing of authorization for the event and tracking whether proposals/recommendations were acted upon.","alias":["Fulfills"],"min":0,"max":"*","base":{"path":"Observation.basedOn","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/CarePlan","http://hl7.org/fhir/StructureDefinition/DeviceRequest","http://hl7.org/fhir/StructureDefinition/ImmunizationRecommendation","http://hl7.org/fhir/StructureDefinition/MedicationRequest","http://hl7.org/fhir/StructureDefinition/NutritionOrder","http://hl7.org/fhir/StructureDefinition/ServiceRequest"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.partOf","path":"Observation.partOf","short":"Part of referenced event","definition":"A larger event of which this particular Observation is a component or step. For example, an observation as part of a procedure.","comment":"To link an Observation to an Encounter use `encounter`. See the [Notes](http://hl7.org/fhir/observation.html#obsgrouping) below for guidance on referencing another Observation.","alias":["Container"],"min":0,"max":"*","base":{"path":"Observation.partOf","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/MedicationAdministration","http://hl7.org/fhir/StructureDefinition/MedicationDispense","http://hl7.org/fhir/StructureDefinition/MedicationStatement","http://hl7.org/fhir/StructureDefinition/Procedure","http://hl7.org/fhir/StructureDefinition/Immunization","http://hl7.org/fhir/StructureDefinition/ImagingStudy"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.status","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-display-hint","valueString":"default: final"}],"path":"Observation.status","short":"registered | preliminary | final | amended +","definition":"The status of the result value.","comment":"This element is labeled as a modifier because the status contains codes that mark the resource as not currently valid.","requirements":"Need to track the status of individual results. Some results are finalized before the whole report is finalized.","min":1,"max":"1","base":{"path":"Observation.status","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":true,"isModifierReason":"This element is labeled as a modifier because it is a status element that contains status entered-in-error which means that the resource should not be treated as valid","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Status"}],"strength":"required","valueSet":"http://hl7.org/fhir/ValueSet/observation-status|4.0.1"}},{"id":"Observation.category","path":"Observation.category","slicing":{"discriminator":[{"type":"value","path":"coding.code"},{"type":"value","path":"coding.system"}],"ordered":false,"rules":"open"},"short":"Classification of type of observation","definition":"A code that classifies the general type of observation being made.","comment":"In addition to the required category valueset, this element allows various categorization schemes based on the owner’s definition of the category and effectively multiple categories can be used at once. The level of granularity is defined by the category concepts in the value set.","requirements":"Used for filtering what observations are retrieved and displayed.","min":1,"max":"*","base":{"path":"Observation.category","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationCategory"}],"strength":"preferred","description":"Codes for high level observation categories.","valueSet":"http://hl7.org/fhir/ValueSet/observation-category"}},{"id":"Observation.category:VSCat","path":"Observation.category","sliceName":"VSCat","short":"Classification of type of observation","definition":"A code that classifies the general type of observation being made.","comment":"In addition to the required category valueset, this element allows various categorization schemes based on the owner’s definition of the category and effectively multiple categories can be used at once. The level of granularity is defined by the category concepts in the value set.","requirements":"Used for filtering what observations are retrieved and displayed.","min":1,"max":"1","base":{"path":"Observation.category","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationCategory"}],"strength":"preferred","description":"Codes for high level observation categories.","valueSet":"http://hl7.org/fhir/ValueSet/observation-category"}},{"id":"Observation.category:VSCat.id","path":"Observation.category.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.category:VSCat.extension","path":"Observation.category.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.category:VSCat.coding","path":"Observation.category.coding","short":"Code defined by a terminology system","definition":"A reference to a code defined by a terminology system.","comment":"Codes may be defined very casually in enumerations, or code lists, up to very formal definitions such as SNOMED CT - see the HL7 v3 Core Principles for more information. Ordering of codings is undefined and SHALL NOT be used to infer meaning. Generally, at most only one of the coding values will be labeled as UserSelected = true.","requirements":"Allows for alternative encodings within a code system, and translations to other code systems.","min":1,"max":"*","base":{"path":"CodeableConcept.coding","min":0,"max":"*"},"type":[{"code":"Coding"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.coding.id","path":"Observation.category.coding.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.category:VSCat.coding.extension","path":"Observation.category.coding.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.category:VSCat.coding.system","path":"Observation.category.coding.system","short":"Identity of the terminology system","definition":"The identification of the code system that defines the meaning of the symbol in the code.","comment":"The URI may be an OID (urn:oid:...) or a UUID (urn:uuid:...). OIDs and UUIDs SHALL be references to the HL7 OID registry. Otherwise, the URI should come from HL7's list of FHIR defined special URIs or it should reference to some definition that establishes the system clearly and unambiguously.","requirements":"Need to be unambiguous about the source of the definition of the symbol.","min":1,"max":"1","base":{"path":"Coding.system","min":0,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"http://terminology.hl7.org/CodeSystem/observation-category","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.coding.version","path":"Observation.category.coding.version","short":"Version of the system - if relevant","definition":"The version of the code system which was used when choosing this code. Note that a well-maintained code system does not need the version reported, because the meaning of codes is consistent across versions. However this cannot consistently be assured, and when the meaning is not guaranteed to be consistent, the version SHOULD be exchanged.","comment":"Where the terminology does not clearly define what string should be used to identify code system versions, the recommendation is to use the date (expressed in FHIR date format) on which that version was officially published as the version date.","min":0,"max":"1","base":{"path":"Coding.version","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.coding.code","path":"Observation.category.coding.code","short":"Symbol in syntax defined by the system","definition":"A symbol in syntax defined by the system. The symbol may be a predefined code or an expression in a syntax defined by the coding system (e.g. post-coordination).","requirements":"Need to refer to a particular code in the system.","min":1,"max":"1","base":{"path":"Coding.code","min":0,"max":"1"},"type":[{"code":"code"}],"fixedCode":"vital-signs","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.coding.display","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable","valueBoolean":true}],"path":"Observation.category.coding.display","short":"Representation defined by the system","definition":"A representation of the meaning of the code in the system, following the rules of the system.","requirements":"Need to be able to carry a human-readable meaning of the code for readers that do not know the system.","min":0,"max":"1","base":{"path":"Coding.display","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.coding.userSelected","path":"Observation.category.coding.userSelected","short":"If this coding was chosen directly by the user","definition":"Indicates that this coding was chosen by a user directly - e.g. off a pick list of available items (codes or displays).","comment":"Amongst a set of alternatives, a directly chosen code is the most appropriate starting point for new translations. There is some ambiguity about what exactly 'directly chosen' implies, and trading partner agreement may be needed to clarify the use of this element and its consequences more completely.","requirements":"This has been identified as a clinical safety criterium - that this exact system/code pair was chosen explicitly, rather than inferred by the system based on some rules or language processing.","min":0,"max":"1","base":{"path":"Coding.userSelected","min":0,"max":"1"},"type":[{"code":"boolean"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.category:VSCat.text","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable","valueBoolean":true}],"path":"Observation.category.text","short":"Plain text representation of the concept","definition":"A human language representation of the concept as seen/selected/uttered by the user who entered the data and/or which represents the intended meaning of the user.","comment":"Very often the text is the same as a displayName of one of the codings.","requirements":"The codes from the terminologies do not always capture the correct meaning with all the nuances of the human using them, or sometimes there is no appropriate code at all. In these cases, the text is used to capture the full meaning of the source.","min":0,"max":"1","base":{"path":"CodeableConcept.text","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.code","path":"Observation.code","short":"Blood Pressure","definition":"Coded Responses from C-CDA Vital Sign Results.","comment":"*All* code-value and, if present, component.code-component.value pairs need to be taken into account to correctly understand the meaning of the observation.","requirements":"5. SHALL contain exactly one [1..1] code, where the @code SHOULD be selected from ValueSet HITSP Vital Sign Result Type 2.16.840.1.113883.3.88.12.80.62 DYNAMIC (CONF:7301).","alias":["Name"],"min":1,"max":"1","base":{"path":"Observation.code","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"patternCodeableConcept":{"coding":[{"system":"http://loinc.org","code":"85354-9"}]},"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"The vital sign codes from the base FHIR and US Core Vital Signs.","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-vital-signs"}},{"id":"Observation.subject","path":"Observation.subject","short":"Who and/or what the observation is about","definition":"The patient, or group of patients, location, or device this observation is about and into whose record the observation is placed. If the actual focus of the observation is different from the subject (or a sample of, part, or region of the subject), the `focus` element or the `code` itself specifies the actual focus of the observation.","comment":"One would expect this element to be a cardinality of 1..1. The only circumstance in which the subject can be missing is when the observation is made by a device that does not know the patient. In this case, the observation SHALL be matched to a patient through some context/channel matching technique, and at this point, the observation should be updated.","requirements":"Observations have no value if you don't know who or what they're about.","min":1,"max":"1","base":{"path":"Observation.subject","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.focus","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status","valueCode":"trial-use"}],"path":"Observation.focus","short":"What the observation is about, when it is not about the subject of record","definition":"The actual focus of an observation when it is not the patient of record representing something or someone associated with the patient such as a spouse, parent, fetus, or donor. For example, fetus observations in a mother's record. The focus of an observation could also be an existing condition, an intervention, the subject's diet, another observation of the subject, or a body structure such as tumor or implanted device. An example use case would be using the Observation resource to capture whether the mother is trained to change her child's tracheostomy tube. In this example, the child is the patient of record and the mother is the focus.","comment":"Typically, an observation is made about the subject - a patient, or group of patients, location, or device - and the distinction between the subject and what is directly measured for an observation is specified in the observation code itself ( e.g., \"Blood Glucose\") and does not need to be represented separately using this element. Use `specimen` if a reference to a specimen is required. If a code is required instead of a resource use either `bodysite` for bodysites or the standard extension [focusCode](http://hl7.org/fhir/extension-observation-focuscode.html).","min":0,"max":"*","base":{"path":"Observation.focus","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Resource"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.encounter","path":"Observation.encounter","short":"Healthcare event during which this observation is made","definition":"The healthcare event (e.g. a patient and healthcare provider interaction) during which this observation is made.","comment":"This will typically be the encounter the event occurred within, but some events may be initiated prior to or after the official completion of an encounter but still be tied to the context of the encounter (e.g. pre-admission laboratory tests).","requirements":"For some observations it may be important to know the link between an observation and a particular encounter.","alias":["Context"],"min":0,"max":"1","base":{"path":"Observation.encounter","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Encounter"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.effective[x]","path":"Observation.effective[x]","short":"Often just a dateTime for Vital Signs","definition":"Often just a dateTime for Vital Signs.","comment":"At least a date should be present unless this observation is a historical report. For recording imprecise or \"fuzzy\" times (For example, a blood glucose measurement taken \"after breakfast\") use the [Timing](http://hl7.org/fhir/datatypes.html#timing) datatype which allow the measurement to be tied to regular life events.","requirements":"Knowing when an observation was deemed true is important to its relevance as well as determining trends.","alias":["Occurrence"],"min":1,"max":"1","base":{"path":"Observation.effective[x]","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"dateTime"},{"code":"Period"}],"condition":["vs-1"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"vs-1","severity":"error","human":"if Observation.effective[x] is dateTime and has a value then that value shall be precise to the day","expression":"($this as dateTime).toString().length() >= 8","xpath":"f:effectiveDateTime[matches(@value, '^\\d{4}-\\d{2}-\\d{2}')]","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.issued","path":"Observation.issued","short":"Date/Time this version was made available","definition":"The date and time this version of the observation was made available to providers, typically after the results have been reviewed and verified.","comment":"For Observations that don’t require review and verification, it may be the same as the [`lastUpdated` ](http://hl7.org/fhir/resource-definitions.html#Meta.lastUpdated) time of the resource itself. For Observations that do require review and verification for certain updates, it might not be the same as the `lastUpdated` time of the resource itself due to a non-clinically significant update that doesn’t require the new version to be reviewed and verified again.","min":0,"max":"1","base":{"path":"Observation.issued","min":0,"max":"1"},"type":[{"code":"instant"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.performer","path":"Observation.performer","short":"Who is responsible for the observation","definition":"Who was responsible for asserting the observed value as \"true\".","requirements":"May give a degree of confidence in the observation and also indicates where follow-up questions should be directed.","min":0,"max":"*","base":{"path":"Observation.performer","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Practitioner","http://hl7.org/fhir/StructureDefinition/PractitionerRole","http://hl7.org/fhir/StructureDefinition/Organization","http://hl7.org/fhir/StructureDefinition/CareTeam","http://hl7.org/fhir/StructureDefinition/Patient","http://hl7.org/fhir/StructureDefinition/RelatedPerson"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.value[x]","path":"Observation.value[x]","short":"Vital Signs Value","definition":"Vital Signs value are typically recorded using the Quantity data type.","comment":"An observation may have; 1) a single value here, 2) both a value and a set of related or component values, or 3) only a set of related or component values. If a value is present, the datatype for this element should be determined by Observation.code. A CodeableConcept with just a text would be used instead of a string if the field was usually coded, or if the type associated with the Observation.code defines a coded value. For additional guidance, see the [Notes section](http://hl7.org/fhir/observation.html#notes) below.","requirements":"9. SHALL contain exactly one [1..1] value with @xsi:type=\"PQ\" (CONF:7305).","min":0,"max":"1","base":{"path":"Observation.value[x]","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"Quantity"},{"code":"CodeableConcept"},{"code":"string"},{"code":"boolean"},{"code":"integer"},{"code":"Range"},{"code":"Ratio"},{"code":"SampledData"},{"code":"time"},{"code":"dateTime"},{"code":"Period"}],"condition":["obs-7","vs-2"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"Common UCUM units for recording Vital Signs.","valueSet":"http://hl7.org/fhir/ValueSet/ucum-vitals-common|4.0.1"}},{"id":"Observation.dataAbsentReason","path":"Observation.dataAbsentReason","short":"Why the result is missing","definition":"Provides a reason why the expected value in the element Observation.value[x] is missing.","comment":"Null or exceptional values can be represented two ways in FHIR Observations. One way is to simply include them in the value set and represent the exceptions in the value. For example, measurement values for a serology test could be \"detected\", \"not detected\", \"inconclusive\", or \"specimen unsatisfactory\". \n\nThe alternate way is to use the value element for actual observations and use the explicit dataAbsentReason element to record exceptional values. For example, the dataAbsentReason code \"error\" could be used when the measurement was not completed. Note that an observation may only be reported if there are values to report. For example differential cell counts values may be reported only when > 0. Because of these options, use-case agreements are required to interpret general observations for null or exceptional values.","requirements":"For many results it is necessary to handle exceptional values in measurements.","min":0,"max":"1","base":{"path":"Observation.dataAbsentReason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"condition":["obs-6","vs-2"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationValueAbsentReason"}],"strength":"extensible","description":"Codes specifying why the result (`Observation.value[x]`) is missing.","valueSet":"http://hl7.org/fhir/ValueSet/data-absent-reason"}},{"id":"Observation.interpretation","path":"Observation.interpretation","short":"High, low, normal, etc.","definition":"A categorical assessment of an observation value. For example, high, low, normal.","comment":"Historically used for laboratory results (known as 'abnormal flag' ), its use extends to other use cases where coded interpretations are relevant. Often reported as one or more simple compact codes this element is often placed adjacent to the result value in reports and flow sheets to signal the meaning/normalcy status of the result.","requirements":"For some results, particularly numeric results, an interpretation is necessary to fully understand the significance of a result.","alias":["Abnormal Flag"],"min":0,"max":"*","base":{"path":"Observation.interpretation","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationInterpretation"}],"strength":"extensible","description":"Codes identifying interpretations of observations.","valueSet":"http://hl7.org/fhir/ValueSet/observation-interpretation"}},{"id":"Observation.note","path":"Observation.note","short":"Comments about the observation","definition":"Comments about the observation or the results.","comment":"May include general statements about the observation, or statements about significant, unexpected or unreliable results values, or information about its source when relevant to its interpretation.","requirements":"Need to be able to provide free text additional information.","min":0,"max":"*","base":{"path":"Observation.note","min":0,"max":"*"},"type":[{"code":"Annotation"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.bodySite","path":"Observation.bodySite","short":"Observed body part","definition":"Indicates the site on the subject's body where the observation was made (i.e. the target site).","comment":"Only used if not implicit in code found in Observation.code. In many systems, this may be represented as a related observation instead of an inline component. \n\nIf the use case requires BodySite to be handled as a separate resource (e.g. to identify and track separately) then use the standard extension[ bodySite](http://hl7.org/fhir/extension-bodysite.html).","min":0,"max":"1","base":{"path":"Observation.bodySite","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"BodySite"}],"strength":"example","description":"Codes describing anatomical locations. May include laterality.","valueSet":"http://hl7.org/fhir/ValueSet/body-site"}},{"id":"Observation.method","path":"Observation.method","short":"How it was done","definition":"Indicates the mechanism used to perform the observation.","comment":"Only used if not implicit in code for Observation.code.","requirements":"In some cases, method can impact results and is thus used for determining whether results can be compared or determining significance of results.","min":0,"max":"1","base":{"path":"Observation.method","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationMethod"}],"strength":"example","description":"Methods for simple observations.","valueSet":"http://hl7.org/fhir/ValueSet/observation-methods"}},{"id":"Observation.specimen","path":"Observation.specimen","short":"Specimen used for this observation","definition":"The specimen that was used when this observation was made.","comment":"Should only be used if not implicit in code found in `Observation.code`. Observations are not made on specimens themselves; they are made on a subject, but in many cases by the means of a specimen. Note that although specimens are often involved, they are not always tracked and reported explicitly. Also note that observation resources may be used in contexts that track the specimen explicitly (e.g. Diagnostic Report).","min":0,"max":"1","base":{"path":"Observation.specimen","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Specimen"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.device","path":"Observation.device","short":"(Measurement) Device","definition":"The device used to generate the observation data.","comment":"Note that this is not meant to represent a device involved in the transmission of the result, e.g., a gateway. Such devices may be documented using the Provenance resource where relevant.","min":0,"max":"1","base":{"path":"Observation.device","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Device","http://hl7.org/fhir/StructureDefinition/DeviceMetric"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange","path":"Observation.referenceRange","short":"Provides guide for interpretation","definition":"Guidance on how to interpret the value by comparison to a normal or recommended range. Multiple reference ranges are interpreted as an \"OR\". In other words, to represent two distinct target populations, two `referenceRange` elements would be used.","comment":"Most observations only have one generic reference range. Systems MAY choose to restrict to only supplying the relevant reference range based on knowledge about the patient (e.g., specific to the patient's age, gender, weight and other factors), but this might not be possible or appropriate. Whenever more than one reference range is supplied, the differences between them SHOULD be provided in the reference range and/or age properties.","requirements":"Knowing what values are considered \"normal\" can help evaluate the significance of a particular result. Need to be able to provide multiple reference ranges for different contexts.","min":0,"max":"*","base":{"path":"Observation.referenceRange","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"obs-3","severity":"error","human":"Must have at least a low or a high or text","expression":"low.exists() or high.exists() or text.exists()","xpath":"(exists(f:low) or exists(f:high)or exists(f:text))","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.id","path":"Observation.referenceRange.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.extension","path":"Observation.referenceRange.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.modifierExtension","path":"Observation.referenceRange.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Observation.referenceRange.low","path":"Observation.referenceRange.low","short":"Low Range, if relevant","definition":"The value of the low bound of the reference range. The low bound of the reference range endpoint is inclusive of the value (e.g. reference range is >=5 - <=9). If the low bound is omitted, it is assumed to be meaningless (e.g. reference range is <=2.3).","min":0,"max":"1","base":{"path":"Observation.referenceRange.low","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"condition":["obs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.high","path":"Observation.referenceRange.high","short":"High Range, if relevant","definition":"The value of the high bound of the reference range. The high bound of the reference range endpoint is inclusive of the value (e.g. reference range is >=5 - <=9). If the high bound is omitted, it is assumed to be meaningless (e.g. reference range is >= 2.3).","min":0,"max":"1","base":{"path":"Observation.referenceRange.high","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"condition":["obs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.type","path":"Observation.referenceRange.type","short":"Reference range qualifier","definition":"Codes to indicate the what part of the targeted reference population it applies to. For example, the normal or therapeutic range.","comment":"This SHOULD be populated if there is more than one range. If this element is not present then the normal range is assumed.","requirements":"Need to be able to say what kind of reference range this is - normal, recommended, therapeutic, etc., - for proper interpretation.","min":0,"max":"1","base":{"path":"Observation.referenceRange.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationRangeMeaning"}],"strength":"preferred","description":"Code for the meaning of a reference range.","valueSet":"http://hl7.org/fhir/ValueSet/referencerange-meaning"}},{"id":"Observation.referenceRange.appliesTo","path":"Observation.referenceRange.appliesTo","short":"Reference range population","definition":"Codes to indicate the target population this reference range applies to. For example, a reference range may be based on the normal population or a particular sex or race. Multiple `appliesTo` are interpreted as an \"AND\" of the target populations. For example, to represent a target population of African American females, both a code of female and a code for African American would be used.","comment":"This SHOULD be populated if there is more than one range. If this element is not present then the normal population is assumed.","requirements":"Need to be able to identify the target population for proper interpretation.","min":0,"max":"*","base":{"path":"Observation.referenceRange.appliesTo","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationRangeType"}],"strength":"example","description":"Codes identifying the population the reference range applies to.","valueSet":"http://hl7.org/fhir/ValueSet/referencerange-appliesto"}},{"id":"Observation.referenceRange.age","path":"Observation.referenceRange.age","short":"Applicable age range, if relevant","definition":"The age at which this reference range is applicable. This is a neonatal age (e.g. number of weeks at term) if the meaning says so.","requirements":"Some analytes vary greatly over age.","min":0,"max":"1","base":{"path":"Observation.referenceRange.age","min":0,"max":"1"},"type":[{"code":"Range"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.referenceRange.text","path":"Observation.referenceRange.text","short":"Text based reference range in an observation","definition":"Text based reference range in an observation which may be used when a quantitative range is not appropriate for an observation. An example would be a reference value of \"Negative\" or a list or table of \"normals\".","min":0,"max":"1","base":{"path":"Observation.referenceRange.text","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.hasMember","path":"Observation.hasMember","short":"Used when reporting vital signs panel components","definition":"Used when reporting vital signs panel components.","comment":"When using this element, an observation will typically have either a value or a set of related resources, although both may be present in some cases. For a discussion on the ways Observations can assembled in groups together, see [Notes](http://hl7.org/fhir/observation.html#obsgrouping) below. Note that a system may calculate results from [QuestionnaireResponse](http://hl7.org/fhir/questionnaireresponse.html) into a final score and represent the score as an Observation.","min":0,"max":"*","base":{"path":"Observation.hasMember","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse","http://hl7.org/fhir/StructureDefinition/MolecularSequence","http://hl7.org/fhir/StructureDefinition/vitalsigns"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.derivedFrom","path":"Observation.derivedFrom","short":"Related measurements the observation is made from","definition":"The target resource that represents a measurement from which this observation value is derived. For example, a calculated anion gap or a fetal measurement based on an ultrasound image.","comment":"All the reference choices that are listed in this element can represent clinical observations and other measurements that may be the source for a derived value. The most common reference will be another Observation. For a discussion on the ways Observations can assembled in groups together, see [Notes](http://hl7.org/fhir/observation.html#obsgrouping) below.","min":0,"max":"*","base":{"path":"Observation.derivedFrom","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/DocumentReference","http://hl7.org/fhir/StructureDefinition/ImagingStudy","http://hl7.org/fhir/StructureDefinition/Media","http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse","http://hl7.org/fhir/StructureDefinition/MolecularSequence","http://hl7.org/fhir/StructureDefinition/vitalsigns"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"Observation.component","path":"Observation.component","slicing":{"discriminator":[{"type":"pattern","path":"code"}],"ordered":false,"rules":"open"},"short":"Component observations","definition":"Used when reporting component observation such as systolic and diastolic blood pressure.","comment":"For a discussion on the ways Observations can be assembled in groups together see [Notes](http://hl7.org/fhir/observation.html#notes) below.","requirements":"Component observations share the same attributes in the Observation resource as the primary observation and are always treated a part of a single observation (they are not separable). However, the reference range for the primary observation value is not inherited by the component values and is required when appropriate for each component observation.","min":2,"max":"*","base":{"path":"Observation.component","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"vs-3","severity":"error","human":"If there is no a value a data absent reason must be present","expression":"value.exists() or dataAbsentReason.exists()","xpath":"f:*[starts-with(local-name(.), 'value')] or f:dataAbsentReason","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component.id","path":"Observation.component.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.component.extension","path":"Observation.component.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.component.modifierExtension","path":"Observation.component.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Observation.component.code","path":"Observation.component.code","short":"Type of component observation (code / type)","definition":"Describes what was observed. Sometimes this is called the observation \"code\".","comment":"*All* code-value and component.code-component.value pairs need to be taken into account to correctly understand the meaning of the observation.","requirements":"Knowing what kind of observation is being made is essential to understanding the observation.","min":1,"max":"1","base":{"path":"Observation.component.code","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"The vital sign codes from the base FHIR and US Core Vital Signs.","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-vital-signs"}},{"id":"Observation.component.value[x]","path":"Observation.component.value[x]","short":"Vital Sign Component Value","definition":"Vital Signs value are typically recorded using the Quantity data type. For supporting observations such as cuff size could use other datatypes such as CodeableConcept.","comment":"Used when observation has a set of component observations. An observation may have both a value (e.g. an Apgar score) and component observations (the observations from which the Apgar score was derived). If a value is present, the datatype for this element should be determined by Observation.code. A CodeableConcept with just a text would be used instead of a string if the field was usually coded, or if the type associated with the Observation.code defines a coded value. For additional guidance, see the [Notes section](http://hl7.org/fhir/observation.html#notes) below.","requirements":"9. SHALL contain exactly one [1..1] value with @xsi:type=\"PQ\" (CONF:7305).","min":0,"max":"1","base":{"path":"Observation.component.value[x]","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"Quantity"},{"code":"CodeableConcept"},{"code":"string"},{"code":"boolean"},{"code":"integer"},{"code":"Range"},{"code":"Ratio"},{"code":"SampledData"},{"code":"time"},{"code":"dateTime"},{"code":"Period"}],"condition":["vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"Common UCUM units for recording Vital Signs.","valueSet":"http://hl7.org/fhir/ValueSet/ucum-vitals-common|4.0.1"}},{"id":"Observation.component.dataAbsentReason","path":"Observation.component.dataAbsentReason","short":"Why the component result is missing","definition":"Provides a reason why the expected value in the element Observation.component.value[x] is missing.","comment":"\"Null\" or exceptional values can be represented two ways in FHIR Observations. One way is to simply include them in the value set and represent the exceptions in the value. For example, measurement values for a serology test could be \"detected\", \"not detected\", \"inconclusive\", or \"test not done\". \n\nThe alternate way is to use the value element for actual observations and use the explicit dataAbsentReason element to record exceptional values. For example, the dataAbsentReason code \"error\" could be used when the measurement was not completed. Because of these options, use-case agreements are required to interpret general observations for exceptional values.","requirements":"For many results it is necessary to handle exceptional values in measurements.","min":0,"max":"1","base":{"path":"Observation.component.dataAbsentReason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"condition":["obs-6","vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationValueAbsentReason"}],"strength":"extensible","description":"Codes specifying why the result (`Observation.value[x]`) is missing.","valueSet":"http://hl7.org/fhir/ValueSet/data-absent-reason"}},{"id":"Observation.component.interpretation","path":"Observation.component.interpretation","short":"High, low, normal, etc.","definition":"A categorical assessment of an observation value. For example, high, low, normal.","comment":"Historically used for laboratory results (known as 'abnormal flag' ), its use extends to other use cases where coded interpretations are relevant. Often reported as one or more simple compact codes this element is often placed adjacent to the result value in reports and flow sheets to signal the meaning/normalcy status of the result.","requirements":"For some results, particularly numeric results, an interpretation is necessary to fully understand the significance of a result.","alias":["Abnormal Flag"],"min":0,"max":"*","base":{"path":"Observation.component.interpretation","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationInterpretation"}],"strength":"extensible","description":"Codes identifying interpretations of observations.","valueSet":"http://hl7.org/fhir/ValueSet/observation-interpretation"}},{"id":"Observation.component.referenceRange","path":"Observation.component.referenceRange","short":"Provides guide for interpretation of component result","definition":"Guidance on how to interpret the value by comparison to a normal or recommended range.","comment":"Most observations only have one generic reference range. Systems MAY choose to restrict to only supplying the relevant reference range based on knowledge about the patient (e.g., specific to the patient's age, gender, weight and other factors), but this might not be possible or appropriate. Whenever more than one reference range is supplied, the differences between them SHOULD be provided in the reference range and/or age properties.","requirements":"Knowing what values are considered \"normal\" can help evaluate the significance of a particular result. Need to be able to provide multiple reference ranges for different contexts.","min":0,"max":"*","base":{"path":"Observation.component.referenceRange","min":0,"max":"*"},"contentReference":"http://hl7.org/fhir/StructureDefinition/Observation#Observation.referenceRange","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:systolic","path":"Observation.component","sliceName":"systolic","short":"Systolic Blood Pressure","definition":"Used when reporting component observation such as systolic and diastolic blood pressure.","comment":"For a discussion on the ways Observations can be assembled in groups together see [Notes](http://hl7.org/fhir/observation.html#notes) below.","requirements":"Component observations share the same attributes in the Observation resource as the primary observation and are always treated a part of a single observation (they are not separable). However, the reference range for the primary observation value is not inherited by the component values and is required when appropriate for each component observation.","min":1,"max":"1","base":{"path":"Observation.component","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"vs-3","severity":"error","human":"If there is no a value a data absent reason must be present","expression":"value.exists() or dataAbsentReason.exists()","xpath":"f:*[starts-with(local-name(.), 'value')] or f:dataAbsentReason","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:systolic.id","path":"Observation.component.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:systolic.extension","path":"Observation.component.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:systolic.modifierExtension","path":"Observation.component.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Observation.component:systolic.code","path":"Observation.component.code","short":"Systolic Blood Pressure Code","definition":"Describes what was observed. Sometimes this is called the observation \"code\".","comment":"*All* code-value and component.code-component.value pairs need to be taken into account to correctly understand the meaning of the observation.","requirements":"Knowing what kind of observation is being made is essential to understanding the observation.","min":1,"max":"1","base":{"path":"Observation.component.code","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"patternCodeableConcept":{"coding":[{"system":"http://loinc.org","code":"8480-6"}]},"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"The vital sign codes from the base FHIR and US Core Vital Signs.","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-vital-signs"}},{"id":"Observation.component:systolic.value[x]","path":"Observation.component.value[x]","short":"Vital Sign Component Value","definition":"Vital Signs value are typically recorded using the Quantity data type. For supporting observations such as cuff size could use other datatypes such as CodeableConcept.","comment":"Used when observation has a set of component observations. An observation may have both a value (e.g. an Apgar score) and component observations (the observations from which the Apgar score was derived). If a value is present, the datatype for this element should be determined by Observation.code. A CodeableConcept with just a text would be used instead of a string if the field was usually coded, or if the type associated with the Observation.code defines a coded value. For additional guidance, see the [Notes section](http://hl7.org/fhir/observation.html#notes) below.","requirements":"9. SHALL contain exactly one [1..1] value with @xsi:type=\"PQ\" (CONF:7305).","min":0,"max":"1","base":{"path":"Observation.component.value[x]","min":0,"max":"1"},"type":[{"code":"Quantity"}],"condition":["vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"Common UCUM units for recording Vital Signs.","valueSet":"http://hl7.org/fhir/ValueSet/ucum-vitals-common|4.0.1"}},{"id":"Observation.component:systolic.value[x].id","path":"Observation.component.value[x].id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:systolic.value[x].extension","path":"Observation.component.value[x].extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:systolic.value[x].value","path":"Observation.component.value[x].value","short":"Numerical value (with implicit precision)","definition":"The value of the measured amount. The value includes an implicit precision in the presentation of the value.","comment":"The implicit precision in the value should always be honored. Monetary values have their own rules for handling precision (refer to standard accounting text books).","requirements":"Precision is handled implicitly in almost all cases of measurement.","min":1,"max":"1","base":{"path":"Quantity.value","min":0,"max":"1"},"type":[{"code":"decimal"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:systolic.value[x].comparator","path":"Observation.component.value[x].comparator","short":"< | <= | >= | > - how to understand the value","definition":"How the value should be understood and represented - whether the actual value is greater or less than the stated value due to measurement issues; e.g. if the comparator is \"<\" , then the real value is < stated value.","requirements":"Need a framework for handling measures where the value is <5ug/L or >400mg/L due to the limitations of measuring methodology.","min":0,"max":"1","base":{"path":"Quantity.comparator","min":0,"max":"1"},"type":[{"code":"code"}],"meaningWhenMissing":"If there is no comparator, then there is no modification of the value","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because the comparator modifies the interpretation of the value significantly. If there is no comparator, then there is no modification of the value","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"QuantityComparator"}],"strength":"required","description":"How the Quantity should be understood and represented.","valueSet":"http://hl7.org/fhir/ValueSet/quantity-comparator|4.0.1"}},{"id":"Observation.component:systolic.value[x].unit","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable","valueBoolean":true}],"path":"Observation.component.value[x].unit","short":"Unit representation","definition":"A human-readable form of the unit.","requirements":"There are many representations for units of measure and in many contexts, particular representations are fixed and required. I.e. mcg for micrograms.","min":1,"max":"1","base":{"path":"Quantity.unit","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:systolic.value[x].system","path":"Observation.component.value[x].system","short":"System that defines coded unit form","definition":"The identification of the system that provides the coded form of the unit.","requirements":"Need to know the system that defines the coded form of the unit.","min":1,"max":"1","base":{"path":"Quantity.system","min":0,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"http://unitsofmeasure.org","condition":["qty-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:systolic.value[x].code","path":"Observation.component.value[x].code","short":"Coded form of the unit","definition":"A computer processable form of the unit in some unit representation system.","comment":"The preferred system is UCUM, but SNOMED CT can also be used (for customary units) or ISO 4217 for currency. The context of use may additionally require a code from a particular system.","requirements":"Need a computable form of the unit that is fixed across all forms. UCUM provides this for quantities, but SNOMED CT provides many units of interest.","min":1,"max":"1","base":{"path":"Quantity.code","min":0,"max":"1"},"type":[{"code":"code"}],"fixedCode":"mm[Hg]","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:systolic.dataAbsentReason","path":"Observation.component.dataAbsentReason","short":"Why the component result is missing","definition":"Provides a reason why the expected value in the element Observation.component.value[x] is missing.","comment":"\"Null\" or exceptional values can be represented two ways in FHIR Observations. One way is to simply include them in the value set and represent the exceptions in the value. For example, measurement values for a serology test could be \"detected\", \"not detected\", \"inconclusive\", or \"test not done\". \n\nThe alternate way is to use the value element for actual observations and use the explicit dataAbsentReason element to record exceptional values. For example, the dataAbsentReason code \"error\" could be used when the measurement was not completed. Because of these options, use-case agreements are required to interpret general observations for exceptional values.","requirements":"For many results it is necessary to handle exceptional values in measurements.","min":0,"max":"1","base":{"path":"Observation.component.dataAbsentReason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"condition":["obs-6","vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationValueAbsentReason"}],"strength":"extensible","description":"Codes specifying why the result (`Observation.value[x]`) is missing.","valueSet":"http://hl7.org/fhir/ValueSet/data-absent-reason"}},{"id":"Observation.component:systolic.interpretation","path":"Observation.component.interpretation","short":"High, low, normal, etc.","definition":"A categorical assessment of an observation value. For example, high, low, normal.","comment":"Historically used for laboratory results (known as 'abnormal flag' ), its use extends to other use cases where coded interpretations are relevant. Often reported as one or more simple compact codes this element is often placed adjacent to the result value in reports and flow sheets to signal the meaning/normalcy status of the result.","requirements":"For some results, particularly numeric results, an interpretation is necessary to fully understand the significance of a result.","alias":["Abnormal Flag"],"min":0,"max":"*","base":{"path":"Observation.component.interpretation","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationInterpretation"}],"strength":"extensible","description":"Codes identifying interpretations of observations.","valueSet":"http://hl7.org/fhir/ValueSet/observation-interpretation"}},{"id":"Observation.component:systolic.referenceRange","path":"Observation.component.referenceRange","short":"Provides guide for interpretation of component result","definition":"Guidance on how to interpret the value by comparison to a normal or recommended range.","comment":"Most observations only have one generic reference range. Systems MAY choose to restrict to only supplying the relevant reference range based on knowledge about the patient (e.g., specific to the patient's age, gender, weight and other factors), but this might not be possible or appropriate. Whenever more than one reference range is supplied, the differences between them SHOULD be provided in the reference range and/or age properties.","requirements":"Knowing what values are considered \"normal\" can help evaluate the significance of a particular result. Need to be able to provide multiple reference ranges for different contexts.","min":0,"max":"*","base":{"path":"Observation.component.referenceRange","min":0,"max":"*"},"contentReference":"http://hl7.org/fhir/StructureDefinition/Observation#Observation.referenceRange","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:diastolic","path":"Observation.component","sliceName":"diastolic","short":"Diastolic Blood Pressure","definition":"Used when reporting component observation such as systolic and diastolic blood pressure.","comment":"For a discussion on the ways Observations can be assembled in groups together see [Notes](http://hl7.org/fhir/observation.html#notes) below.","requirements":"Component observations share the same attributes in the Observation resource as the primary observation and are always treated a part of a single observation (they are not separable). However, the reference range for the primary observation value is not inherited by the component values and is required when appropriate for each component observation.","min":1,"max":"1","base":{"path":"Observation.component","min":0,"max":"*"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"vs-3","severity":"error","human":"If there is no a value a data absent reason must be present","expression":"value.exists() or dataAbsentReason.exists()","xpath":"f:*[starts-with(local-name(.), 'value')] or f:dataAbsentReason","source":"http://hl7.org/fhir/StructureDefinition/vitalsigns"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:diastolic.id","path":"Observation.component.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:diastolic.extension","path":"Observation.component.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:diastolic.modifierExtension","path":"Observation.component.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"Observation.component:diastolic.code","path":"Observation.component.code","short":"Diastolic Blood Pressure Code","definition":"Describes what was observed. Sometimes this is called the observation \"code\".","comment":"*All* code-value and component.code-component.value pairs need to be taken into account to correctly understand the meaning of the observation.","requirements":"Knowing what kind of observation is being made is essential to understanding the observation.","min":1,"max":"1","base":{"path":"Observation.component.code","min":1,"max":"1"},"type":[{"code":"CodeableConcept"}],"patternCodeableConcept":{"coding":[{"system":"http://loinc.org","code":"8462-4"}]},"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"The vital sign codes from the base FHIR and US Core Vital Signs.","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-vital-signs"}},{"id":"Observation.component:diastolic.value[x]","path":"Observation.component.value[x]","short":"Vital Sign Component Value","definition":"Vital Signs value are typically recorded using the Quantity data type. For supporting observations such as cuff size could use other datatypes such as CodeableConcept.","comment":"Used when observation has a set of component observations. An observation may have both a value (e.g. an Apgar score) and component observations (the observations from which the Apgar score was derived). If a value is present, the datatype for this element should be determined by Observation.code. A CodeableConcept with just a text would be used instead of a string if the field was usually coded, or if the type associated with the Observation.code defines a coded value. For additional guidance, see the [Notes section](http://hl7.org/fhir/observation.html#notes) below.","requirements":"9. SHALL contain exactly one [1..1] value with @xsi:type=\"PQ\" (CONF:7305).","min":0,"max":"1","base":{"path":"Observation.component.value[x]","min":0,"max":"1"},"type":[{"code":"Quantity"}],"condition":["vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","description":"Common UCUM units for recording Vital Signs.","valueSet":"http://hl7.org/fhir/ValueSet/ucum-vitals-common|4.0.1"}},{"id":"Observation.component:diastolic.value[x].id","path":"Observation.component.value[x].id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:diastolic.value[x].extension","path":"Observation.component.value[x].extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"Observation.component:diastolic.value[x].value","path":"Observation.component.value[x].value","short":"Numerical value (with implicit precision)","definition":"The value of the measured amount. The value includes an implicit precision in the presentation of the value.","comment":"The implicit precision in the value should always be honored. Monetary values have their own rules for handling precision (refer to standard accounting text books).","requirements":"Precision is handled implicitly in almost all cases of measurement.","min":1,"max":"1","base":{"path":"Quantity.value","min":0,"max":"1"},"type":[{"code":"decimal"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:diastolic.value[x].comparator","path":"Observation.component.value[x].comparator","short":"< | <= | >= | > - how to understand the value","definition":"How the value should be understood and represented - whether the actual value is greater or less than the stated value due to measurement issues; e.g. if the comparator is \"<\" , then the real value is < stated value.","requirements":"Need a framework for handling measures where the value is <5ug/L or >400mg/L due to the limitations of measuring methodology.","min":0,"max":"1","base":{"path":"Quantity.comparator","min":0,"max":"1"},"type":[{"code":"code"}],"meaningWhenMissing":"If there is no comparator, then there is no modification of the value","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This is labeled as \"Is Modifier\" because the comparator modifies the interpretation of the value significantly. If there is no comparator, then there is no modification of the value","isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"QuantityComparator"}],"strength":"required","description":"How the Quantity should be understood and represented.","valueSet":"http://hl7.org/fhir/ValueSet/quantity-comparator|4.0.1"}},{"id":"Observation.component:diastolic.value[x].unit","extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable","valueBoolean":true}],"path":"Observation.component.value[x].unit","short":"Unit representation","definition":"A human-readable form of the unit.","requirements":"There are many representations for units of measure and in many contexts, particular representations are fixed and required. I.e. mcg for micrograms.","min":1,"max":"1","base":{"path":"Quantity.unit","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:diastolic.value[x].system","path":"Observation.component.value[x].system","short":"System that defines coded unit form","definition":"The identification of the system that provides the coded form of the unit.","requirements":"Need to know the system that defines the coded form of the unit.","min":1,"max":"1","base":{"path":"Quantity.system","min":0,"max":"1"},"type":[{"code":"uri"}],"fixedUri":"http://unitsofmeasure.org","condition":["qty-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:diastolic.value[x].code","path":"Observation.component.value[x].code","short":"Coded form of the unit","definition":"A computer processable form of the unit in some unit representation system.","comment":"The preferred system is UCUM, but SNOMED CT can also be used (for customary units) or ISO 4217 for currency. The context of use may additionally require a code from a particular system.","requirements":"Need a computable form of the unit that is fixed across all forms. UCUM provides this for quantities, but SNOMED CT provides many units of interest.","min":1,"max":"1","base":{"path":"Quantity.code","min":0,"max":"1"},"type":[{"code":"code"}],"fixedCode":"mm[Hg]","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"Observation.component:diastolic.dataAbsentReason","path":"Observation.component.dataAbsentReason","short":"Why the component result is missing","definition":"Provides a reason why the expected value in the element Observation.component.value[x] is missing.","comment":"\"Null\" or exceptional values can be represented two ways in FHIR Observations. One way is to simply include them in the value set and represent the exceptions in the value. For example, measurement values for a serology test could be \"detected\", \"not detected\", \"inconclusive\", or \"test not done\". \n\nThe alternate way is to use the value element for actual observations and use the explicit dataAbsentReason element to record exceptional values. For example, the dataAbsentReason code \"error\" could be used when the measurement was not completed. Because of these options, use-case agreements are required to interpret general observations for exceptional values.","requirements":"For many results it is necessary to handle exceptional values in measurements.","min":0,"max":"1","base":{"path":"Observation.component.dataAbsentReason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"condition":["obs-6","vs-3"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationValueAbsentReason"}],"strength":"extensible","description":"Codes specifying why the result (`Observation.value[x]`) is missing.","valueSet":"http://hl7.org/fhir/ValueSet/data-absent-reason"}},{"id":"Observation.component:diastolic.interpretation","path":"Observation.component.interpretation","short":"High, low, normal, etc.","definition":"A categorical assessment of an observation value. For example, high, low, normal.","comment":"Historically used for laboratory results (known as 'abnormal flag' ), its use extends to other use cases where coded interpretations are relevant. Often reported as one or more simple compact codes this element is often placed adjacent to the result value in reports and flow sheets to signal the meaning/normalcy status of the result.","requirements":"For some results, particularly numeric results, an interpretation is necessary to fully understand the significance of a result.","alias":["Abnormal Flag"],"min":0,"max":"*","base":{"path":"Observation.component.interpretation","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"ObservationInterpretation"}],"strength":"extensible","description":"Codes identifying interpretations of observations.","valueSet":"http://hl7.org/fhir/ValueSet/observation-interpretation"}},{"id":"Observation.component:diastolic.referenceRange","path":"Observation.component.referenceRange","short":"Provides guide for interpretation of component result","definition":"Guidance on how to interpret the value by comparison to a normal or recommended range.","comment":"Most observations only have one generic reference range. Systems MAY choose to restrict to only supplying the relevant reference range based on knowledge about the patient (e.g., specific to the patient's age, gender, weight and other factors), but this might not be possible or appropriate. Whenever more than one reference range is supplied, the differences between them SHOULD be provided in the reference range and/or age properties.","requirements":"Knowing what values are considered \"normal\" can help evaluate the significance of a particular result. Need to be able to provide multiple reference ranges for different contexts.","min":0,"max":"*","base":{"path":"Observation.component.referenceRange","min":0,"max":"*"},"contentReference":"http://hl7.org/fhir/StructureDefinition/Observation#Observation.referenceRange","constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}},{"resourceType":"StructureDefinition","id":"us-core-medicationrequest","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-medicationrequest","version":"5.0.1","name":"USCoreMedicationRequestProfile","title":"US Core MedicationRequest Profile","status":"active","experimental":false,"date":"2020-06-26","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"The US Core Medication Request Profile is based upon the core FHIR MedicationRequest Resource and meets the U.S. Core Data for Interoperability (USCDI) v2 'Medications' requirements. The MedicationRequest resource can be used to record a patient’s medication prescription or order. This profile sets minimum expectations for the MedicationRequest resource to record, search, and fetch a patient's medication to promote interoperability and adoption through common implementation. It identifies which core elements, extensions, vocabularies and value sets **SHALL** be present in the resource when using this profile. It provides the floor for standards development for specific uses cases.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","kind":"resource","abstract":false,"type":"MedicationRequest","baseDefinition":"http://hl7.org/fhir/StructureDefinition/MedicationRequest","derivation":"constraint","snapshot":{"element":[{"id":"MedicationRequest","path":"MedicationRequest","short":"Ordering of medication for patient or group","definition":"\\-","comment":"\\-","alias":["Prescription","Order"],"min":0,"max":"*","base":{"path":"MedicationRequest","min":0,"max":"*"},"constraint":[{"key":"dom-2","severity":"error","human":"If the resource is contained in another resource, it SHALL NOT contain nested Resources","expression":"contained.contained.empty()","xpath":"not(parent::f:contained and f:contained)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-3","severity":"error","human":"If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource","expression":"contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()","xpath":"not(exists(for $id in f:contained/*/f:id/@value return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-4","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated","expression":"contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()","xpath":"not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-5","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a security label","expression":"contained.meta.security.empty()","xpath":"not(exists(f:contained/*/f:meta/f:security))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice","valueBoolean":true},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice-explanation","valueMarkdown":"When a resource has no narrative, only systems that fully understand the data can display the resource to a human safely. Including a human readable representation in the resource makes for a much more robust eco-system and cheaper handling of resources by intermediary systems. Some ecosystems restrict distribution of resources to only those systems that do fully understand the resources, and as a consequence implementers may believe that the narrative is superfluous. However experience shows that such eco-systems often open up to new participants over time."}],"key":"dom-6","severity":"warning","human":"A resource should have narrative for robust management","expression":"text.`div`.exists()","xpath":"exists(f:text/h:div)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"}],"mustSupport":false,"isModifier":false,"isSummary":false},{"id":"MedicationRequest.id","path":"MedicationRequest.id","short":"Logical id of this artifact","definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.","comment":"The only time that a resource does not have an id is when it is being submitted to the server using a create operation.","min":0,"max":"1","base":{"path":"Resource.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.meta","path":"MedicationRequest.meta","short":"Metadata about the resource","definition":"The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.","min":0,"max":"1","base":{"path":"Resource.meta","min":0,"max":"1"},"type":[{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.implicitRules","path":"MedicationRequest.implicitRules","short":"A set of rules under which this content was created","definition":"A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.","comment":"Asserting this rule set restricts the content to be only understood by a limited set of trading partners. This inherently limits the usefulness of the data in the long term. However, the existing health eco-system is highly fractured, and not yet ready to define, collect, and exchange data in a generally computable sense. Wherever possible, implementers and/or specification writers should avoid using this element. Often, when used, the URL is a reference to an implementation guide that defines these special rules as part of it's narrative along with other profiles, value sets, etc.","min":0,"max":"1","base":{"path":"Resource.implicitRules","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because the implicit rules may provide additional knowledge about the resource that modifies it's meaning or interpretation","isSummary":true},{"id":"MedicationRequest.language","path":"MedicationRequest.language","short":"Language of the resource content","definition":"The base language in which the resource is written.","comment":"Language is provided to support indexing and accessibility (typically, services such as text to speech use the language tag). The html language tag in the narrative applies to the narrative. The language tag on the resource may be used to specify the language of other presentations generated from the data in the resource. Not all the content has to be in the base language. The Resource.language should not be assumed to apply to the narrative automatically. If a language is specified, it should it also be specified on the div element in the html (see rules in HTML5 for information about the relationship between xml:lang and the html lang attribute).","min":0,"max":"1","base":{"path":"Resource.language","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/all-languages"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Language"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"preferred","description":"A human language.","valueSet":"http://hl7.org/fhir/ValueSet/languages"}},{"id":"MedicationRequest.text","path":"MedicationRequest.text","short":"Text summary of the resource, for human interpretation","definition":"A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it \"clinically safe\" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety.","comment":"Contained resources do not have narrative. Resources that are not contained SHOULD have a narrative. In some cases, a resource may only have text with little or no additional discrete data (as long as all minOccurs=1 elements are satisfied). This may be necessary for data from legacy systems where information is captured as a \"text blob\" or where text is additionally entered raw or narrated and encoded information is added later.","alias":["narrative","html","xhtml","display"],"min":0,"max":"1","base":{"path":"DomainResource.text","min":0,"max":"1"},"type":[{"code":"Narrative"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.contained","path":"MedicationRequest.contained","short":"Contained, inline Resources","definition":"These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope.","comment":"This should never be done when the content can be identified properly, as once identification is lost, it is extremely difficult (and context dependent) to restore it again. Contained resources may have profiles and tags In their meta elements, but SHALL NOT have security labels.","alias":["inline resources","anonymous resources","contained resources"],"min":0,"max":"*","base":{"path":"DomainResource.contained","min":0,"max":"*"},"type":[{"code":"Resource"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.extension","path":"MedicationRequest.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the resource. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.modifierExtension","path":"MedicationRequest.modifierExtension","short":"Extensions that cannot be ignored","definition":"May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the resource that contains them","isSummary":false},{"id":"MedicationRequest.identifier","path":"MedicationRequest.identifier","short":"External ids for this request","definition":"Identifiers associated with this medication request that are defined by business processes and/or used to refer to it when a direct URL reference to the resource itself is not appropriate. They are business identifiers assigned to this resource by the performer or other systems and remain constant as the resource is updated and propagates from server to server.","comment":"This is a business identifier, not a resource identifier.","min":0,"max":"*","base":{"path":"MedicationRequest.identifier","min":0,"max":"*"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.status","path":"MedicationRequest.status","short":"active | on-hold | cancelled | completed | entered-in-error | stopped | draft | unknown","definition":"A code specifying the current state of the order. Generally, this will be active or completed state.","comment":"This element is labeled as a modifier because the status contains codes that mark the resource as not currently valid.","min":1,"max":"1","base":{"path":"MedicationRequest.status","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":true,"isModifierReason":"This element is labeled as a modifier because it is a status element that contains status entered-in-error which means that the resource should not be treated as valid","isSummary":true,"binding":{"strength":"required","description":"A code specifying the state of the prescribing event. Describes the lifecycle of the prescription.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-status"}},{"id":"MedicationRequest.statusReason","path":"MedicationRequest.statusReason","short":"Reason for current status","definition":"Captures the reason for the current state of the MedicationRequest.","comment":"This is generally only used for \"exception\" statuses such as \"suspended\" or \"cancelled\". The reason why the MedicationRequest was created at all is captured in reasonCode, not here.","min":0,"max":"1","base":{"path":"MedicationRequest.statusReason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestStatusReason"}],"strength":"example","description":"Identifies the reasons for a given status.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-status-reason"}},{"id":"MedicationRequest.intent","path":"MedicationRequest.intent","short":"proposal | plan | order | original-order | reflex-order | filler-order | instance-order | option","definition":"Whether the request is a proposal, plan, or an original order.","comment":"It is expected that the type of requester will be restricted for different stages of a MedicationRequest. For example, Proposals can be created by a patient, relatedPerson, Practitioner or Device. Plans can be created by Practitioners, Patients, RelatedPersons and Devices. Original orders can be created by a Practitioner only.\r\rAn instance-order is an instantiation of a request or order and may be used to populate Medication Administration Record.\r\rThis element is labeled as a modifier because the intent alters when and how the resource is actually applicable.","min":1,"max":"1","base":{"path":"MedicationRequest.intent","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":true,"isModifierReason":"This element changes the interpretation of all descriptive attributes. For example \"the time the request is recommended to occur\" vs. \"the time the request is authorized to occur\" or \"who is recommended to perform the request\" vs. \"who is authorized to perform the request","isSummary":true,"binding":{"strength":"required","description":"The kind of medication order.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-intent"}},{"id":"MedicationRequest.category","path":"MedicationRequest.category","slicing":{"discriminator":[{"type":"pattern","path":"$this"}],"rules":"open"},"short":"Type of medication usage","definition":"Indicates the type of medication request (for example, where the medication is expected to be consumed or administered (i.e. inpatient or outpatient)).","comment":"The category can be used to include where the medication is expected to be consumed or other types of requests.","min":0,"max":"*","base":{"path":"MedicationRequest.category","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestCategory"}],"strength":"example","description":"A coded concept identifying the category of medication request. For example, where the medication is to be consumed or administered, or the type of medication treatment.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-category"}},{"id":"MedicationRequest.category:us-core","path":"MedicationRequest.category","sliceName":"us-core","short":"Type of medication usage","definition":"Indicates the type of medication request (for example, where the medication is expected to be consumed or administered (i.e. inpatient or outpatient)).","comment":"The category can be used to include where the medication is expected to be consumed or other types of requests.","min":0,"max":"*","base":{"path":"MedicationRequest.category","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"strength":"required","description":"The type of medication order.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-category"}},{"id":"MedicationRequest.priority","path":"MedicationRequest.priority","short":"routine | urgent | asap | stat","definition":"Indicates how quickly the Medication Request should be addressed with respect to other requests.","min":0,"max":"1","base":{"path":"MedicationRequest.priority","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestPriority"}],"strength":"required","description":"Identifies the level of importance to be assigned to actioning the request.","valueSet":"http://hl7.org/fhir/ValueSet/request-priority|4.0.1"}},{"id":"MedicationRequest.doNotPerform","path":"MedicationRequest.doNotPerform","short":"True if request is prohibiting action","definition":"If true indicates that the provider is asking for the medication request not to occur.","comment":"If do not perform is not specified, the request is a positive request e.g. \"do perform\".","min":0,"max":"1","base":{"path":"MedicationRequest.doNotPerform","min":0,"max":"1"},"type":[{"code":"boolean"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because this element negates the request to occur (ie, this is a request for the medication not to be ordered or prescribed, etc)","isSummary":true},{"id":"MedicationRequest.reported[x]","path":"MedicationRequest.reported[x]","short":"Reported rather than primary record","definition":"Indicates if this record was captured as a secondary 'reported' record rather than as an original primary source-of-truth record. It may also indicate the source of the report.","min":0,"max":"1","base":{"path":"MedicationRequest.reported[x]","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"boolean"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner","http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization","http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient","http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole","http://hl7.org/fhir/us/core/StructureDefinition/us-core-relatedperson"],"_targetProfile":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]}]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"MedicationRequest.medication[x]","path":"MedicationRequest.medication[x]","short":"Medication to be taken","definition":"Identifies the medication being requested. This is a link to a resource that represents the medication which may be the details of the medication or simply an attribute carrying a code that identifies the medication from a known list of medications.","comment":"If only a code is specified, then it needs to be a code for a specific product. If more information is required, then the use of the Medication resource is recommended. For example, if you require form or lot number or if the medication is compounded or extemporaneously prepared, then you must reference the Medication resource.","min":1,"max":"1","base":{"path":"MedicationRequest.medication[x]","min":1,"max":"1"},"type":[{"code":"CodeableConcept"},{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-medication"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","valueSet":"http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1010.4"}},{"id":"MedicationRequest.subject","path":"MedicationRequest.subject","short":"Who or group medication request is for","definition":"A link to a resource representing the person or set of individuals to whom the medication will be given.","comment":"The subject on a medication request is mandatory. For the secondary use case where the actual subject is not provided, there still must be an anonymized subject specified.","min":1,"max":"1","base":{"path":"MedicationRequest.subject","min":1,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"MedicationRequest.encounter","path":"MedicationRequest.encounter","short":"Encounter created as part of encounter/admission/stay","definition":"The Encounter during which this [x] was created or to which the creation of this record is tightly associated.","comment":"This will typically be the encounter the event occurred within, but some activities may be initiated prior to or after the official completion of an encounter but still be tied to the context of the encounter.\" If there is a need to link to episodes of care they will be handled with an extension.","min":0,"max":"1","base":{"path":"MedicationRequest.encounter","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-encounter"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"MedicationRequest.supportingInformation","path":"MedicationRequest.supportingInformation","short":"Information to support ordering of the medication","definition":"Include additional information (for example, patient height and weight) that supports the ordering of the medication.","min":0,"max":"*","base":{"path":"MedicationRequest.supportingInformation","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Resource"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.authoredOn","path":"MedicationRequest.authoredOn","short":"When request was initially authored","definition":"The date (and perhaps time) when the prescription was initially written or authored on.","min":0,"max":"1","base":{"path":"MedicationRequest.authoredOn","min":0,"max":"1"},"type":[{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"MedicationRequest.requester","path":"MedicationRequest.requester","short":"Who/What requested the Request","definition":"The individual, organization, or device that initiated the request and has responsibility for its activation.","min":1,"max":"1","base":{"path":"MedicationRequest.requester","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner","http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient","http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization","http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole","http://hl7.org/fhir/us/core/StructureDefinition/us-core-relatedperson","http://hl7.org/fhir/StructureDefinition/Device"],"_targetProfile":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]}]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"MedicationRequest.performer","path":"MedicationRequest.performer","short":"Intended performer of administration","definition":"The specified desired performer of the medication treatment (e.g. the performer of the medication administration).","min":0,"max":"1","base":{"path":"MedicationRequest.performer","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Practitioner","http://hl7.org/fhir/StructureDefinition/PractitionerRole","http://hl7.org/fhir/StructureDefinition/Organization","http://hl7.org/fhir/StructureDefinition/Patient","http://hl7.org/fhir/StructureDefinition/Device","http://hl7.org/fhir/StructureDefinition/RelatedPerson","http://hl7.org/fhir/StructureDefinition/CareTeam"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.performerType","path":"MedicationRequest.performerType","short":"Desired kind of performer of the medication administration","definition":"Indicates the type of performer of the administration of the medication.","comment":"If specified without indicating a performer, this indicates that the performer must be of the specified type. If specified with a performer then it indicates the requirements of the performer if the designated performer is not available.","min":0,"max":"1","base":{"path":"MedicationRequest.performerType","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestPerformerType"}],"strength":"example","description":"Identifies the type of individual that is desired to administer the medication.","valueSet":"http://hl7.org/fhir/ValueSet/performer-role"}},{"id":"MedicationRequest.recorder","path":"MedicationRequest.recorder","short":"Person who entered the request","definition":"The person who entered the order on behalf of another individual for example in the case of a verbal or a telephone order.","min":0,"max":"1","base":{"path":"MedicationRequest.recorder","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Practitioner","http://hl7.org/fhir/StructureDefinition/PractitionerRole"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.reasonCode","path":"MedicationRequest.reasonCode","short":"Reason or indication for ordering or not ordering the medication","definition":"The reason or the indication for ordering or not ordering the medication.","comment":"This could be a diagnosis code. If a full condition record exists or additional detail is needed, use reasonReference.","min":0,"max":"*","base":{"path":"MedicationRequest.reasonCode","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestReason"}],"strength":"example","description":"A coded concept indicating why the medication was ordered.","valueSet":"http://hl7.org/fhir/ValueSet/condition-code"}},{"id":"MedicationRequest.reasonReference","path":"MedicationRequest.reasonReference","short":"Condition or observation that supports why the prescription is being written","definition":"Condition or observation that supports why the medication was ordered.","comment":"This is a reference to a condition or observation that is the reason for the medication order. If only a code exists, use reasonCode.","min":0,"max":"*","base":{"path":"MedicationRequest.reasonReference","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Condition","http://hl7.org/fhir/StructureDefinition/Observation"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.instantiatesCanonical","path":"MedicationRequest.instantiatesCanonical","short":"Instantiates FHIR protocol or definition","definition":"The URL pointing to a protocol, guideline, orderset, or other definition that is adhered to in whole or in part by this MedicationRequest.","min":0,"max":"*","base":{"path":"MedicationRequest.instantiatesCanonical","min":0,"max":"*"},"type":[{"code":"canonical"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.instantiatesUri","path":"MedicationRequest.instantiatesUri","short":"Instantiates external protocol or definition","definition":"The URL pointing to an externally maintained protocol, guideline, orderset or other definition that is adhered to in whole or in part by this MedicationRequest.","min":0,"max":"*","base":{"path":"MedicationRequest.instantiatesUri","min":0,"max":"*"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.basedOn","path":"MedicationRequest.basedOn","short":"What request fulfills","definition":"A plan or request that is fulfilled in whole or in part by this medication request.","min":0,"max":"*","base":{"path":"MedicationRequest.basedOn","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/CarePlan","http://hl7.org/fhir/StructureDefinition/MedicationRequest","http://hl7.org/fhir/StructureDefinition/ServiceRequest","http://hl7.org/fhir/StructureDefinition/ImmunizationRecommendation"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.groupIdentifier","path":"MedicationRequest.groupIdentifier","short":"Composite request this is part of","definition":"A shared identifier common to all requests that were authorized more or less simultaneously by a single author, representing the identifier of the requisition or prescription.","requirements":"Requests are linked either by a \"basedOn\" relationship (i.e. one request is fulfilling another) or by having a common requisition. Requests that are part of the same requisition are generally treated independently from the perspective of changing their state or maintaining them after initial creation.","min":0,"max":"1","base":{"path":"MedicationRequest.groupIdentifier","min":0,"max":"1"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.courseOfTherapyType","path":"MedicationRequest.courseOfTherapyType","short":"Overall pattern of medication administration","definition":"The description of the overall patte3rn of the administration of the medication to the patient.","comment":"This attribute should not be confused with the protocol of the medication.","min":0,"max":"1","base":{"path":"MedicationRequest.courseOfTherapyType","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestCourseOfTherapy"}],"strength":"example","description":"Identifies the overall pattern of medication administratio.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-course-of-therapy"}},{"id":"MedicationRequest.insurance","path":"MedicationRequest.insurance","short":"Associated insurance coverage","definition":"Insurance plans, coverage extensions, pre-authorizations and/or pre-determinations that may be required for delivering the requested service.","min":0,"max":"*","base":{"path":"MedicationRequest.insurance","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Coverage","http://hl7.org/fhir/StructureDefinition/ClaimResponse"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.note","path":"MedicationRequest.note","short":"Information about the prescription","definition":"Extra information about the prescription that could not be conveyed by the other attributes.","min":0,"max":"*","base":{"path":"MedicationRequest.note","min":0,"max":"*"},"type":[{"code":"Annotation"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction","path":"MedicationRequest.dosageInstruction","short":"How the medication should be taken","definition":"Indicates how the medication is to be used by the patient.","comment":"There are examples where a medication request may include the option of an oral dose or an Intravenous or Intramuscular dose. For example, \"Ondansetron 8mg orally or IV twice a day as needed for nausea\" or \"Compazine® (prochlorperazine) 5-10mg PO or 25mg PR bid prn nausea or vomiting\". In these cases, two medication requests would be created that could be grouped together. The decision on which dose and route of administration to use is based on the patient's condition at the time the dose is needed.","min":0,"max":"*","base":{"path":"MedicationRequest.dosageInstruction","min":0,"max":"*"},"type":[{"code":"Dosage"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction.id","path":"MedicationRequest.dosageInstruction.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction.extension","path":"MedicationRequest.dosageInstruction.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction.modifierExtension","path":"MedicationRequest.dosageInstruction.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"MedicationRequest.dosageInstruction.sequence","path":"MedicationRequest.dosageInstruction.sequence","short":"The order of the dosage instructions","definition":"Indicates the order in which the dosage instructions should be applied or interpreted.","requirements":"If the sequence number of multiple Dosages is the same, then it is implied that the instructions are to be treated as concurrent. If the sequence number is different, then the Dosages are intended to be sequential.","min":0,"max":"1","base":{"path":"Dosage.sequence","min":0,"max":"1"},"type":[{"code":"integer"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.text","path":"MedicationRequest.dosageInstruction.text","short":"Free text dosage instructions e.g. SIG","definition":"Free text dosage instructions e.g. SIG.","requirements":"Free text dosage instructions can be used for cases where the instructions are too complex to code. The content of this attribute does not include the name or description of the medication. When coded instructions are present, the free text instructions may still be present for display to humans taking or administering the medication. It is expected that the text instructions will always be populated. If the dosage.timing attribute is also populated, then the dosage.text should reflect the same information as the timing. Additional information about administration or preparation of the medication should be included as text.","min":0,"max":"1","base":{"path":"Dosage.text","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.additionalInstruction","path":"MedicationRequest.dosageInstruction.additionalInstruction","short":"Supplemental instruction or warnings to the patient - e.g. \"with meals\", \"may cause drowsiness\"","definition":"Supplemental instructions to the patient on how to take the medication (e.g. \"with meals\" or\"take half to one hour before food\") or warnings for the patient about the medication (e.g. \"may cause drowsiness\" or \"avoid exposure of skin to direct sunlight or sunlamps\").","comment":"Information about administration or preparation of the medication (e.g. \"infuse as rapidly as possibly via intraperitoneal port\" or \"immediately following drug x\") should be populated in dosage.text.","requirements":"Additional instruction is intended to be coded, but where no code exists, the element could include text. For example, \"Swallow with plenty of water\" which might or might not be coded.","min":0,"max":"*","base":{"path":"Dosage.additionalInstruction","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AdditionalInstruction"}],"strength":"example","description":"A coded concept identifying additional instructions such as \"take with water\" or \"avoid operating heavy machinery\".","valueSet":"http://hl7.org/fhir/ValueSet/additional-instruction-codes"}},{"id":"MedicationRequest.dosageInstruction.patientInstruction","path":"MedicationRequest.dosageInstruction.patientInstruction","short":"Patient or consumer oriented instructions","definition":"Instructions in terms that are understood by the patient or consumer.","min":0,"max":"1","base":{"path":"Dosage.patientInstruction","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.timing","path":"MedicationRequest.dosageInstruction.timing","short":"When medication should be administered","definition":"When medication should be administered.","comment":"This attribute might not always be populated while the Dosage.text is expected to be populated. If both are populated, then the Dosage.text should reflect the content of the Dosage.timing.","requirements":"The timing schedule for giving the medication to the patient. This data type allows many different expressions. For example: \"Every 8 hours\"; \"Three times a day\"; \"1/2 an hour before breakfast for 10 days from 23-Dec 2011:\"; \"15 Oct 2013, 17 Oct 2013 and 1 Nov 2013\". Sometimes, a rate can imply duration when expressed as total volume / duration (e.g. 500mL/2 hours implies a duration of 2 hours). However, when rate doesn't imply duration (e.g. 250mL/hour), then the timing.repeat.duration is needed to convey the infuse over time period.","min":0,"max":"1","base":{"path":"Dosage.timing","min":0,"max":"1"},"type":[{"code":"Timing"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.asNeeded[x]","path":"MedicationRequest.dosageInstruction.asNeeded[x]","short":"Take \"as needed\" (for x)","definition":"Indicates whether the Medication is only taken when needed within a specific dosing schedule (Boolean option), or it indicates the precondition for taking the Medication (CodeableConcept).","comment":"Can express \"as needed\" without a reason by setting the Boolean = True. In this case the CodeableConcept is not populated. Or you can express \"as needed\" with a reason by including the CodeableConcept. In this case the Boolean is assumed to be True. If you set the Boolean to False, then the dose is given according to the schedule and is not \"prn\" or \"as needed\".","min":0,"max":"1","base":{"path":"Dosage.asNeeded[x]","min":0,"max":"1"},"type":[{"code":"boolean"},{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationAsNeededReason"}],"strength":"example","description":"A coded concept identifying the precondition that should be met or evaluated prior to consuming or administering a medication dose. For example \"pain\", \"30 minutes prior to sexual intercourse\", \"on flare-up\" etc.","valueSet":"http://hl7.org/fhir/ValueSet/medication-as-needed-reason"}},{"id":"MedicationRequest.dosageInstruction.site","path":"MedicationRequest.dosageInstruction.site","short":"Body site to administer to","definition":"Body site to administer to.","comment":"If the use case requires attributes from the BodySite resource (e.g. to identify and track separately) then use the standard extension [bodySite](http://hl7.org/fhir/R4/extension-bodysite.html). May be a summary code, or a reference to a very precise definition of the location, or both.","requirements":"A coded specification of the anatomic site where the medication first enters the body.","min":0,"max":"1","base":{"path":"Dosage.site","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationAdministrationSite"}],"strength":"example","description":"A coded concept describing the site location the medicine enters into or onto the body.","valueSet":"http://hl7.org/fhir/ValueSet/approach-site-codes"}},{"id":"MedicationRequest.dosageInstruction.route","path":"MedicationRequest.dosageInstruction.route","short":"How drug should enter body","definition":"How drug should enter body.","requirements":"A code specifying the route or physiological path of administration of a therapeutic agent into or onto a patient's body.","min":0,"max":"1","base":{"path":"Dosage.route","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"RouteOfAdministration"}],"strength":"example","description":"A coded concept describing the route or physiological path of administration of a therapeutic agent into or onto the body of a subject.","valueSet":"http://hl7.org/fhir/ValueSet/route-codes"}},{"id":"MedicationRequest.dosageInstruction.method","path":"MedicationRequest.dosageInstruction.method","short":"Technique for administering medication","definition":"Technique for administering medication.","comment":"Terminologies used often pre-coordinate this term with the route and or form of administration.","requirements":"A coded value indicating the method by which the medication is introduced into or onto the body. Most commonly used for injections. For examples, Slow Push; Deep IV.","min":0,"max":"1","base":{"path":"Dosage.method","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationAdministrationMethod"}],"strength":"example","description":"A coded concept describing the technique by which the medicine is administered.","valueSet":"http://hl7.org/fhir/ValueSet/administration-method-codes"}},{"id":"MedicationRequest.dosageInstruction.doseAndRate","path":"MedicationRequest.dosageInstruction.doseAndRate","short":"Amount of medication administered","definition":"The amount of medication administered.","min":0,"max":"*","base":{"path":"Dosage.doseAndRate","min":0,"max":"*"},"type":[{"code":"Element"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.doseAndRate.id","path":"MedicationRequest.dosageInstruction.doseAndRate.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction.doseAndRate.extension","path":"MedicationRequest.dosageInstruction.doseAndRate.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dosageInstruction.doseAndRate.type","path":"MedicationRequest.dosageInstruction.doseAndRate.type","short":"The kind of dose or rate specified","definition":"The kind of dose or rate specified, for example, ordered or calculated.","requirements":"If the type is not populated, assume to be \"ordered\".","min":0,"max":"1","base":{"path":"Dosage.doseAndRate.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"DoseAndRateType"}],"strength":"example","description":"The kind of dose or rate specified.","valueSet":"http://hl7.org/fhir/ValueSet/dose-rate-type"}},{"id":"MedicationRequest.dosageInstruction.doseAndRate.dose[x]","path":"MedicationRequest.dosageInstruction.doseAndRate.dose[x]","short":"Amount of medication per dose","definition":"Amount of medication per dose.","comment":"Note that this specifies the quantity of the specified medication, not the quantity for each active ingredient(s). Each ingredient amount can be communicated in the Medication resource. For example, if one wants to communicate that a tablet was 375 mg, where the dose was one tablet, you can use the Medication resource to document that the tablet was comprised of 375 mg of drug XYZ. Alternatively if the dose was 375 mg, then you may only need to use the Medication resource to indicate this was a tablet. If the example were an IV such as dopamine and you wanted to communicate that 400mg of dopamine was mixed in 500 ml of some IV solution, then this would all be communicated in the Medication resource. If the administration is not intended to be instantaneous (rate is present or timing has a duration), this can be specified to convey the total amount to be administered over the period of time as indicated by the schedule e.g. 500 ml in dose, with timing used to convey that this should be done over 4 hours.","requirements":"The amount of therapeutic or other substance given at one administration event.","min":0,"max":"1","base":{"path":"Dosage.doseAndRate.dose[x]","min":0,"max":"1"},"type":[{"code":"Range"},{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.doseAndRate.rate[x]","path":"MedicationRequest.dosageInstruction.doseAndRate.rate[x]","short":"Amount of medication per unit of time","definition":"Amount of medication per unit of time.","comment":"It is possible to supply both a rate and a doseQuantity to provide full details about how the medication is to be administered and supplied. If the rate is intended to change over time, depending on local rules/regulations, each change should be captured as a new version of the MedicationRequest with an updated rate, or captured with a new MedicationRequest with the new rate.\r\rIt is possible to specify a rate over time (for example, 100 ml/hour) using either the rateRatio and rateQuantity. The rateQuantity approach requires systems to have the capability to parse UCUM grammer where ml/hour is included rather than a specific ratio where the time is specified as the denominator. Where a rate such as 500ml over 2 hours is specified, the use of rateRatio may be more semantically correct than specifying using a rateQuantity of 250 mg/hour.","requirements":"Identifies the speed with which the medication was or will be introduced into the patient. Typically the rate for an infusion e.g. 100 ml per 1 hour or 100 ml/hr. May also be expressed as a rate per unit of time e.g. 500 ml per 2 hours. Other examples: 200 mcg/min or 200 mcg/1 minute; 1 liter/8 hours. Sometimes, a rate can imply duration when expressed as total volume / duration (e.g. 500mL/2 hours implies a duration of 2 hours). However, when rate doesn't imply duration (e.g. 250mL/hour), then the timing.repeat.duration is needed to convey the infuse over time period.","min":0,"max":"1","base":{"path":"Dosage.doseAndRate.rate[x]","min":0,"max":"1"},"type":[{"code":"Ratio"},{"code":"Range"},{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.maxDosePerPeriod","path":"MedicationRequest.dosageInstruction.maxDosePerPeriod","short":"Upper limit on medication per unit of time","definition":"Upper limit on medication per unit of time.","comment":"This is intended for use as an adjunct to the dosage when there is an upper cap. For example \"2 tablets every 4 hours to a maximum of 8/day\".","requirements":"The maximum total quantity of a therapeutic substance that may be administered to a subject over the period of time. For example, 1000mg in 24 hours.","min":0,"max":"1","base":{"path":"Dosage.maxDosePerPeriod","min":0,"max":"1"},"type":[{"code":"Ratio"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.maxDosePerAdministration","path":"MedicationRequest.dosageInstruction.maxDosePerAdministration","short":"Upper limit on medication per administration","definition":"Upper limit on medication per administration.","comment":"This is intended for use as an adjunct to the dosage when there is an upper cap. For example, a body surface area related dose with a maximum amount, such as 1.5 mg/m2 (maximum 2 mg) IV over 5 – 10 minutes would have doseQuantity of 1.5 mg/m2 and maxDosePerAdministration of 2 mg.","requirements":"The maximum total quantity of a therapeutic substance that may be administered to a subject per administration.","min":0,"max":"1","base":{"path":"Dosage.maxDosePerAdministration","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dosageInstruction.maxDosePerLifetime","path":"MedicationRequest.dosageInstruction.maxDosePerLifetime","short":"Upper limit on medication per lifetime of the patient","definition":"Upper limit on medication per lifetime of the patient.","requirements":"The maximum total quantity of a therapeutic substance that may be administered per lifetime of the subject.","min":0,"max":"1","base":{"path":"Dosage.maxDosePerLifetime","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.dispenseRequest","path":"MedicationRequest.dispenseRequest","short":"Medication supply authorization","definition":"Indicates the specific details for the dispense or medication supply part of a medication request (also known as a Medication Prescription or Medication Order). Note that this information is not always sent with the order. There may be in some settings (e.g. hospitals) institutional or system support for completing the dispense details in the pharmacy department.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest","min":0,"max":"1"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.id","path":"MedicationRequest.dispenseRequest.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.extension","path":"MedicationRequest.dispenseRequest.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.modifierExtension","path":"MedicationRequest.dispenseRequest.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"MedicationRequest.dispenseRequest.initialFill","path":"MedicationRequest.dispenseRequest.initialFill","short":"First fill details","definition":"Indicates the quantity or duration for the first dispense of the medication.","comment":"If populating this element, either the quantity or the duration must be included.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.initialFill","min":0,"max":"1"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.initialFill.id","path":"MedicationRequest.dispenseRequest.initialFill.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.initialFill.extension","path":"MedicationRequest.dispenseRequest.initialFill.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.initialFill.modifierExtension","path":"MedicationRequest.dispenseRequest.initialFill.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"MedicationRequest.dispenseRequest.initialFill.quantity","path":"MedicationRequest.dispenseRequest.initialFill.quantity","short":"First fill quantity","definition":"The amount or quantity to provide as part of the first dispense.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.initialFill.quantity","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.initialFill.duration","path":"MedicationRequest.dispenseRequest.initialFill.duration","short":"First fill duration","definition":"The length of time that the first dispense is expected to last.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.initialFill.duration","min":0,"max":"1"},"type":[{"code":"Duration"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.dispenseInterval","path":"MedicationRequest.dispenseRequest.dispenseInterval","short":"Minimum period of time between dispenses","definition":"The minimum period of time that must occur between dispenses of the medication.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.dispenseInterval","min":0,"max":"1"},"type":[{"code":"Duration"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.validityPeriod","path":"MedicationRequest.dispenseRequest.validityPeriod","short":"Time period supply is authorized for","definition":"This indicates the validity period of a prescription (stale dating the Prescription).","comment":"It reflects the prescribers' perspective for the validity of the prescription. Dispenses must not be made against the prescription outside of this period. The lower-bound of the Dispensing Window signifies the earliest date that the prescription can be filled for the first time. If an upper-bound is not specified then the Prescription is open-ended or will default to a stale-date based on regulations.","requirements":"Indicates when the Prescription becomes valid, and when it ceases to be a dispensable Prescription.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.validityPeriod","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.numberOfRepeatsAllowed","path":"MedicationRequest.dispenseRequest.numberOfRepeatsAllowed","short":"Number of refills authorized","definition":"An integer indicating the number of times, in addition to the original dispense, (aka refills or repeats) that the patient can receive the prescribed medication. Usage Notes: This integer does not include the original order dispense. This means that if an order indicates dispense 30 tablets plus \"3 repeats\", then the order can be dispensed a total of 4 times and the patient can receive a total of 120 tablets. A prescriber may explicitly say that zero refills are permitted after the initial dispense.","comment":"If displaying \"number of authorized fills\", add 1 to this number.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.numberOfRepeatsAllowed","min":0,"max":"1"},"type":[{"code":"unsignedInt"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.quantity","path":"MedicationRequest.dispenseRequest.quantity","short":"Amount of medication to supply per dispense","definition":"The amount that is to be dispensed for one fill.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.quantity","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.expectedSupplyDuration","path":"MedicationRequest.dispenseRequest.expectedSupplyDuration","short":"Number of days supply per dispense","definition":"Identifies the period time over which the supplied product is expected to be used, or the length of time the dispense is expected to last.","comment":"In some situations, this attribute may be used instead of quantity to identify the amount supplied by how long it is expected to last, rather than the physical quantity issued, e.g. 90 days supply of medication (based on an ordered dosage). When possible, it is always better to specify quantity, as this tends to be more precise. expectedSupplyDuration will always be an estimate that can be influenced by external factors.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.expectedSupplyDuration","min":0,"max":"1"},"type":[{"code":"Duration"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.dispenseRequest.performer","path":"MedicationRequest.dispenseRequest.performer","short":"Intended dispenser","definition":"Indicates the intended dispensing Organization specified by the prescriber.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.performer","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.substitution","path":"MedicationRequest.substitution","short":"Any restrictions on medication substitution","definition":"Indicates whether or not substitution can or should be part of the dispense. In some cases, substitution must happen, in other cases substitution must not happen. This block explains the prescriber's intent. If nothing is specified substitution may be done.","min":0,"max":"1","base":{"path":"MedicationRequest.substitution","min":0,"max":"1"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.substitution.id","path":"MedicationRequest.substitution.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.substitution.extension","path":"MedicationRequest.substitution.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.substitution.modifierExtension","path":"MedicationRequest.substitution.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true},{"id":"MedicationRequest.substitution.allowed[x]","path":"MedicationRequest.substitution.allowed[x]","short":"Whether substitution is allowed or not","definition":"True if the prescriber allows a different drug to be dispensed from what was prescribed.","comment":"This element is labeled as a modifier because whether substitution is allow or not, it cannot be ignored.","min":1,"max":"1","base":{"path":"MedicationRequest.substitution.allowed[x]","min":1,"max":"1"},"type":[{"code":"boolean"},{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestSubstitution"}],"strength":"example","description":"Identifies the type of substitution allowed.","valueSet":"http://terminology.hl7.org/ValueSet/v3-ActSubstanceAdminSubstitutionCode"}},{"id":"MedicationRequest.substitution.reason","path":"MedicationRequest.substitution.reason","short":"Why should (not) substitution be made","definition":"Indicates the reason for the substitution, or why substitution must or must not be performed.","min":0,"max":"1","base":{"path":"MedicationRequest.substitution.reason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationIntendedSubstitutionReason"}],"strength":"example","description":"A coded concept describing the reason that a different medication should (or should not) be substituted from what was prescribed.","valueSet":"http://terminology.hl7.org/ValueSet/v3-SubstanceAdminSubstitutionReason"}},{"id":"MedicationRequest.priorPrescription","path":"MedicationRequest.priorPrescription","short":"An order/prescription that is being replaced","definition":"A link to a resource representing an earlier order related order or prescription.","min":0,"max":"1","base":{"path":"MedicationRequest.priorPrescription","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/MedicationRequest"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.detectedIssue","path":"MedicationRequest.detectedIssue","short":"Clinical Issue with action","definition":"Indicates an actual or potential clinical issue with or between one or more active or proposed clinical actions for a patient; e.g. Drug-drug interaction, duplicate therapy, dosage alert etc.","comment":"This element can include a detected issue that has been identified either by a decision support system or by a clinician and may include information on the steps that were taken to address the issue.","alias":["Contraindication","Drug Utilization Review (DUR)","Alert"],"min":0,"max":"*","base":{"path":"MedicationRequest.detectedIssue","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/DetectedIssue"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false},{"id":"MedicationRequest.eventHistory","path":"MedicationRequest.eventHistory","short":"A list of events of interest in the lifecycle","definition":"Links to Provenance records for past versions of this resource or fulfilling request or event resources that identify key state transitions or updates that are likely to be relevant to a user looking at the current version of the resource.","comment":"This might not include provenances for all versions of the request – only those deemed “relevant” or important. This SHALL NOT include the provenance associated with this current version of the resource. (If that provenance is deemed to be a “relevant” change, it will need to be added as part of a later update. Until then, it can be queried directly as the provenance that points to this version using _revinclude All Provenances should have some historical version of this Request as their subject.).","min":0,"max":"*","base":{"path":"MedicationRequest.eventHistory","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Provenance"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false}]}}] \ No newline at end of file diff --git a/packages/mock/src/subscription-manager.test.ts b/packages/mock/src/subscription-manager.test.ts new file mode 100644 index 0000000000..247288a797 --- /dev/null +++ b/packages/mock/src/subscription-manager.test.ts @@ -0,0 +1,80 @@ +import { SubscriptionEmitter, SubscriptionEventMap, generateId } from '@medplum/core'; +import { Bundle } from '@medplum/fhirtypes'; +import 'jest-websocket-mock'; +import { MockClient } from './client'; +import { MockSubscriptionManager } from './subscription-manager'; + +describe('MockSubscriptionManager', () => { + let medplum: MockClient; + let manager: MockSubscriptionManager; + + beforeAll(() => { + medplum = new MockClient(); + manager = new MockSubscriptionManager(medplum, 'wss://example.com/ws/subscriptions-r4'); + }); + + test('addCriteria()', () => { + const emitter1 = manager.addCriteria('Communication'); + expect(emitter1).toBeInstanceOf(SubscriptionEmitter); + expect(manager.getCriteriaCount()).toEqual(1); + + const emitter2 = manager.addCriteria('Communication'); + expect(emitter2).toBeInstanceOf(SubscriptionEmitter); + expect(manager.getCriteriaCount()).toEqual(1); + + expect(emitter1).toBe(emitter2); + }); + + test('removeCriteria()', () => { + manager.removeCriteria('Communication'); + expect(manager.getCriteriaCount()).toEqual(1); + manager.removeCriteria('Communication'); + expect(manager.getCriteriaCount()).toEqual(0); + expect(() => manager.removeCriteria('Communucation')).not.toThrow(); + expect(manager.getCriteriaCount()).toEqual(0); + }); + + test('getMasterEmitter()', () => { + expect(manager.getMasterEmitter()).toBeInstanceOf(SubscriptionEmitter); + }); + + test('emitEventForCriteria()', async () => { + const emitter = manager.addCriteria('Communication'); + expect(emitter).toBeInstanceOf(SubscriptionEmitter); + expect(emitter.getCriteria().has('Communication')).toEqual(true); + + const bundleId = generateId(); + + const receivedEvent = await new Promise((resolve) => { + emitter.addEventListener('message', (event) => { + resolve(event); + }); + manager.emitEventForCriteria<'message'>('Communication', { + type: 'message', + payload: { resourceType: 'Bundle', id: bundleId } as Bundle, + }); + }); + + expect(receivedEvent).toMatchObject({ + type: 'message', + payload: { resourceType: 'Bundle', id: bundleId }, + } as SubscriptionEventMap['message']); + }); + + test('closeWebSockets()', async () => { + const receivedEvent = await new Promise((resolve) => { + manager.getMasterEmitter().addEventListener('close', (event) => { + resolve(event); + }); + manager.closeWebSocket(); + }); + expect(receivedEvent?.type).toEqual('close'); + }); + + test('getEmitter()', async () => { + expect(manager.getEmitter('Subscription')).toBeUndefined(); + const emitter = manager.addCriteria('Subscription'); + expect(emitter).toBeInstanceOf(SubscriptionEmitter); + expect(manager.getEmitter('Subscription')).toBe(emitter); + }); +}); diff --git a/packages/mock/src/subscription-manager.ts b/packages/mock/src/subscription-manager.ts new file mode 100644 index 0000000000..ba69b18607 --- /dev/null +++ b/packages/mock/src/subscription-manager.ts @@ -0,0 +1,59 @@ +import { MedplumClient, SubscriptionEmitter, SubscriptionEventMap, SubscriptionManager } from '@medplum/core'; + +export class MockSubscriptionManager extends SubscriptionManager { + emitters: Map; + counts: Map; + masterEmitter: SubscriptionEmitter; + + constructor(medplum: MedplumClient, _wsOrUrl: WebSocket | string) { + super(medplum, 'wss://example.com/ws/subscriptions-r4'); + this.emitters = new Map(); + this.counts = new Map(); + this.masterEmitter = new SubscriptionEmitter(); + } + + addCriteria(criteria: string): SubscriptionEmitter { + if (!this.emitters.has(criteria)) { + this.emitters.set(criteria, new SubscriptionEmitter(criteria)); + } + this.counts.set(criteria, (this.counts.get(criteria) ?? 0) + 1); + return this.emitters.get(criteria) as SubscriptionEmitter; + } + + removeCriteria(criteria: string): void { + if (!this.emitters.has(criteria)) { + return; + } + this.counts.set(criteria, (this.counts.get(criteria) as number) - 1); + if (this.counts.get(criteria) === 0) { + this.emitters.delete(criteria); + this.counts.delete(criteria); + } + } + + closeWebSocket(): void { + this.masterEmitter.dispatchEvent({ type: 'close' }); + for (const emitter of this.emitters.values()) { + emitter.dispatchEvent({ type: 'close' }); + } + } + + getCriteriaCount(): number { + return this.emitters.size; + } + + getMasterEmitter(): SubscriptionEmitter { + return this.masterEmitter; + } + + emitEventForCriteria( + criteria: string, + event: SubscriptionEventMap[K] + ): void { + this.emitters.get(criteria)?.dispatchEvent(event); + } + + getEmitter(criteria: string): SubscriptionEmitter | undefined { + return this.emitters.get(criteria); + } +} diff --git a/packages/react-hooks/README.md b/packages/react-hooks/README.md index b7bd4107b3..5a2707376f 100644 --- a/packages/react-hooks/README.md +++ b/packages/react-hooks/README.md @@ -63,7 +63,7 @@ export function MyComponent() { ```ts interface MedplumContext { medplum: MedplumClient; - navigate: MepdlumNavigateFunction; + navigate: MedplumNavigateFunction; profile?: ProfileResource; loading: boolean; } diff --git a/packages/react-hooks/package.json b/packages/react-hooks/package.json index b34aa16d16..7a56b7067d 100644 --- a/packages/react-hooks/package.json +++ b/packages/react-hooks/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/react-hooks", - "version": "3.0.2", + "version": "3.0.4", "description": "Medplum React Hooks Library", "keywords": [ "medplum", @@ -57,26 +57,27 @@ "test": "jest" }, "devDependencies": { - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", "@testing-library/dom": "9.3.4", - "@testing-library/jest-dom": "6.3.0", - "@testing-library/react": "14.1.2", - "@types/jest": "29.5.11", - "@types/node": "20.11.8", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", + "@testing-library/jest-dom": "6.4.2", + "@testing-library/react": "14.2.1", + "@types/jest": "29.5.12", + "@types/node": "20.11.19", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", "jest": "29.7.0", "jest-each": "29.7.0", + "jest-websocket-mock": "2.5.0", "react": "18.2.0", "react-dom": "18.2.0", "rimraf": "5.0.5", "typescript": "5.3.3" }, "peerDependencies": { - "@medplum/core": "*", + "@medplum/core": "3.0.4", "react": "^17.0.2 || ^18.0.0", "react-dom": "^17.0.2 || ^18.0.0" }, diff --git a/packages/react-hooks/src/MedplumProvider/MedplumProvider.context.ts b/packages/react-hooks/src/MedplumProvider/MedplumProvider.context.ts index 14112b0d28..02bf020146 100644 --- a/packages/react-hooks/src/MedplumProvider/MedplumProvider.context.ts +++ b/packages/react-hooks/src/MedplumProvider/MedplumProvider.context.ts @@ -3,11 +3,11 @@ import { createContext, useContext } from 'react'; export const reactContext = createContext(undefined as MedplumContext | undefined); -export type MepdlumNavigateFunction = (path: string) => void; +export type MedplumNavigateFunction = (path: string) => void; export interface MedplumContext { medplum: MedplumClient; - navigate: MepdlumNavigateFunction; + navigate: MedplumNavigateFunction; profile?: ProfileResource; loading: boolean; } @@ -33,7 +33,7 @@ export function useMedplum(): MedplumClient { * Returns the Medplum navigate function. * @returns The Medplum navigate function. */ -export function useMedplumNavigate(): MepdlumNavigateFunction { +export function useMedplumNavigate(): MedplumNavigateFunction { return useMedplumContext().navigate; } diff --git a/packages/react-hooks/src/MedplumProvider/MedplumProvider.tsx b/packages/react-hooks/src/MedplumProvider/MedplumProvider.tsx index 06672f238e..52e71dbb8c 100644 --- a/packages/react-hooks/src/MedplumProvider/MedplumProvider.tsx +++ b/packages/react-hooks/src/MedplumProvider/MedplumProvider.tsx @@ -1,10 +1,10 @@ import { MedplumClient } from '@medplum/core'; import { ReactNode, useEffect, useMemo, useState } from 'react'; -import { MepdlumNavigateFunction, reactContext } from './MedplumProvider.context'; +import { MedplumNavigateFunction, reactContext } from './MedplumProvider.context'; export interface MedplumProviderProps { readonly medplum: MedplumClient; - readonly navigate?: MepdlumNavigateFunction; + readonly navigate?: MedplumNavigateFunction; readonly children: ReactNode; } diff --git a/packages/react-hooks/src/index.ts b/packages/react-hooks/src/index.ts index 74af4dae34..461a2d4985 100644 --- a/packages/react-hooks/src/index.ts +++ b/packages/react-hooks/src/index.ts @@ -1,5 +1,6 @@ export * from './MedplumProvider/MedplumProvider'; export * from './MedplumProvider/MedplumProvider.context'; +export * from './useCachedBinaryUrl/useCachedBinaryUrl'; export * from './useResource/useResource'; export * from './useSearch/useSearch'; -export * from './useCachedBinaryUrl/useCachedBinaryUrl'; +export * from './useSubscription/useSubscription'; diff --git a/packages/react-hooks/src/useSearch/useSearch.ts b/packages/react-hooks/src/useSearch/useSearch.ts index d4afdb31fb..7ff43ecd06 100644 --- a/packages/react-hooks/src/useSearch/useSearch.ts +++ b/packages/react-hooks/src/useSearch/useSearch.ts @@ -80,7 +80,7 @@ function useSearchImpl( setOutcome(normalizeOperationOutcome(err)); }); } - }, [medplum, searchFn, resourceType, query, searchKey, setResult]); + }, [medplum, searchFn, resourceType, query, searchKey]); return [result, loading, outcome]; } diff --git a/packages/react-hooks/src/useSubscription/useSubscription.test.tsx b/packages/react-hooks/src/useSubscription/useSubscription.test.tsx new file mode 100644 index 0000000000..332b69963b --- /dev/null +++ b/packages/react-hooks/src/useSubscription/useSubscription.test.tsx @@ -0,0 +1,243 @@ +import { SubscriptionEmitter, generateId } from '@medplum/core'; +import { Bundle } from '@medplum/fhirtypes'; +import { MockClient } from '@medplum/mock'; +import { act, render, screen } from '@testing-library/react'; +import 'jest-websocket-mock'; +import { ReactNode, StrictMode, useState } from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { MedplumProvider } from '../MedplumProvider/MedplumProvider'; +import { useSubscription } from './useSubscription'; + +function TestComponent({ + criteria, + callback, +}: { + criteria?: string; + callback?: (bundle: Bundle) => void; +}): JSX.Element { + const [lastReceived, setLastReceived] = useState(); + useSubscription( + criteria ?? 'Communication', + callback ?? + ((bundle: Bundle) => { + setLastReceived(bundle); + }) + ); + return ( +
+
{JSON.stringify(lastReceived)}
+
+ ); +} + +function RenderToggleComponent({ render }: { render: boolean }): JSX.Element { + return <>{render ? : null}; +} + +describe('useSubscription()', () => { + let medplum: MockClient; + + beforeAll(() => { + medplum = new MockClient(); + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + function setup( + children: ReactNode, + strict = false + ): { + unmount: ReturnType['unmount']; + rerender: (element: JSX.Element) => void; + } { + const defaultWrapper = (children: ReactNode): JSX.Element => ( + + {children} + + ); + + const strictWrapper = (children: ReactNode): JSX.Element => { + return {defaultWrapper(children)}; + }; + + const wrapper = strict ? strictWrapper : defaultWrapper; + const { unmount, rerender } = render(wrapper(children)); + return { unmount, rerender: (element: JSX.Element) => rerender(wrapper(element)) }; + } + + test('Mount and unmount completely', async () => { + const { unmount } = setup(); + + act(() => { + medplum.getSubscriptionManager().emitEventForCriteria<'message'>('Communication', { + type: 'message', + payload: { resourceType: 'Bundle', id: generateId(), type: 'history' }, + }); + }); + + const el = await screen.findByTestId('bundle'); + expect(el).toBeInTheDocument(); + + const bundle = JSON.parse(el.innerHTML); + expect(bundle.resourceType).toBe('Bundle'); + expect(bundle.type).toBe('history'); + + // Make sure subscription is cleaned up + unmount(); + jest.advanceTimersByTime(5000); + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(0); + }); + + test('Mount and remount before debounce timeout', async () => { + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(0); + const { rerender } = setup(); + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(1); + + const emitter = medplum.getSubscriptionManager().getEmitter('Communication') as SubscriptionEmitter; + expect(emitter).toBeInstanceOf(SubscriptionEmitter); + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(1); + + rerender(); + jest.advanceTimersByTime(1000); + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(1); + + rerender(); + jest.advanceTimersByTime(5000); + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(1); + expect(medplum.getSubscriptionManager().getEmitter('Communication')).toBe(emitter); + + // Make sure we fully unmount later when actually unmounting + rerender(); + jest.advanceTimersByTime(5000); + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(0); + }); + + test('Debounces properly in StrictMode', async () => { + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(0); + const emitter = medplum.getSubscriptionManager().addCriteria('Communication'); + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(1); + + setup(, true); + jest.advanceTimersByTime(5000); + expect(medplum.getSubscriptionManager().getCriteriaCount()).toEqual(1); + expect(medplum.getSubscriptionManager().getEmitter('Communication')).toBe(emitter); + }); + + test('Callback changed', async () => { + let lastFromCb1: Bundle | undefined; + let lastFromCb2: Bundle | undefined; + const id1 = generateId(); + const id2 = generateId(); + + const { rerender } = setup( + { + lastFromCb1 = bundle; + }} + /> + ); + + act(() => { + medplum.getSubscriptionManager().emitEventForCriteria<'message'>('Communication', { + type: 'message', + payload: { resourceType: 'Bundle', id: id1, type: 'history' }, + }); + }); + + expect(lastFromCb1?.resourceType).toEqual('Bundle'); + expect(lastFromCb1?.type).toEqual('history'); + expect(lastFromCb1?.id).toEqual(id1); + expect(lastFromCb2).not.toBeDefined(); + + rerender( + { + lastFromCb2 = bundle; + }} + /> + ); + + act(() => { + medplum.getSubscriptionManager().emitEventForCriteria<'message'>('Communication', { + type: 'message', + payload: { resourceType: 'Bundle', id: id2, type: 'history' }, + }); + }); + + expect(lastFromCb1?.resourceType).toEqual('Bundle'); + expect(lastFromCb1?.type).toEqual('history'); + expect(lastFromCb1?.id).toEqual(id1); + + expect(lastFromCb2?.resourceType).toEqual('Bundle'); + expect(lastFromCb2?.type).toEqual('history'); + expect(lastFromCb2?.id).toEqual(id2); + }); + + test('Criteria changed', () => { + let lastFromCb1: Bundle | undefined; + let lastFromCb2: Bundle | undefined; + const id1 = generateId(); + const id2 = generateId(); + const id3 = generateId(); + + const { rerender } = setup( + { + lastFromCb1 = bundle; + }} + /> + ); + + act(() => { + medplum.getSubscriptionManager().emitEventForCriteria<'message'>('Communication', { + type: 'message', + payload: { resourceType: 'Bundle', id: id1, type: 'history' }, + }); + }); + + expect(lastFromCb1?.resourceType).toEqual('Bundle'); + expect(lastFromCb1?.type).toEqual('history'); + expect(lastFromCb1?.id).toEqual(id1); + expect(lastFromCb2).not.toBeDefined(); + + rerender( + { + lastFromCb2 = bundle; + }} + /> + ); + + act(() => { + medplum.getSubscriptionManager().emitEventForCriteria<'message'>('Communication', { + type: 'message', + payload: { resourceType: 'Bundle', id: id2, type: 'history' }, + }); + }); + + expect(lastFromCb1?.resourceType).toEqual('Bundle'); + expect(lastFromCb1?.type).toEqual('history'); + expect(lastFromCb1?.id).toEqual(id1); + expect(lastFromCb2).not.toBeDefined(); + + act(() => { + medplum.getSubscriptionManager().emitEventForCriteria<'message'>('DiagnosticReport', { + type: 'message', + payload: { resourceType: 'Bundle', id: id3, type: 'history' }, + }); + }); + + expect(lastFromCb1?.resourceType).toEqual('Bundle'); + expect(lastFromCb1?.type).toEqual('history'); + expect(lastFromCb1?.id).toEqual(id1); + + expect(lastFromCb2?.resourceType).toEqual('Bundle'); + expect(lastFromCb2?.type).toEqual('history'); + expect(lastFromCb2?.id).toEqual(id3); + }); +}); diff --git a/packages/react-hooks/src/useSubscription/useSubscription.ts b/packages/react-hooks/src/useSubscription/useSubscription.ts new file mode 100644 index 0000000000..5baeb74672 --- /dev/null +++ b/packages/react-hooks/src/useSubscription/useSubscription.ts @@ -0,0 +1,56 @@ +import { SubscriptionEmitter, SubscriptionEventMap } from '@medplum/core'; +import { Bundle } from '@medplum/fhirtypes'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useMedplum } from '../MedplumProvider/MedplumProvider.context'; + +const SUBSCRIPTION_DEBOUNCE_MS = 3000; + +export function useSubscription(criteria: string, callback: (bundle: Bundle) => void): void { + const medplum = useMedplum(); + const [emitter, setEmitter] = useState(); + + const listeningRef = useRef(false); + const unsubTimerRef = useRef>(); + const prevCriteriaRef = useRef(); + + const callbackRef = useRef(); + callbackRef.current = callback; + + useEffect(() => { + if (unsubTimerRef.current) { + clearTimeout(unsubTimerRef.current); + unsubTimerRef.current = undefined; + } + if (prevCriteriaRef.current !== criteria) { + setEmitter(medplum.subscribeToCriteria(criteria)); + } + + // Set prev criteria to latest + prevCriteriaRef.current = criteria; + + return () => { + unsubTimerRef.current = setTimeout(() => { + setEmitter(undefined); + medplum.unsubscribeFromCriteria(criteria); + }, SUBSCRIPTION_DEBOUNCE_MS); + }; + }, [medplum, criteria]); + + const emitterCallback = useCallback((event: SubscriptionEventMap['message']) => { + callbackRef.current?.(event.payload); + }, []); + + useEffect(() => { + if (!emitter) { + return () => undefined; + } + if (!listeningRef.current) { + emitter.addEventListener('message', emitterCallback); + listeningRef.current = true; + } + return () => { + listeningRef.current = false; + emitter.removeEventListener('message', emitterCallback); + }; + }, [emitter, emitterCallback]); +} diff --git a/packages/react/.storybook/main.ts b/packages/react/.storybook/main.ts index 20981071a1..b83b59e8ac 100644 --- a/packages/react/.storybook/main.ts +++ b/packages/react/.storybook/main.ts @@ -1,6 +1,7 @@ import turbosnap from 'vite-plugin-turbosnap'; import type { StorybookConfig } from '@storybook/react-vite'; import { mergeConfig } from 'vite'; +import path from 'path'; const config: StorybookConfig = { stories: [ @@ -28,16 +29,24 @@ const config: StorybookConfig = { docs: { autodocs: 'tag', }, - viteFinal(config, { configType }) { - let finalConfig = config; + async viteFinal(inputConfig, { configType }) { + let config = inputConfig; if (configType === 'PRODUCTION') { - finalConfig = mergeConfig(config, { + config = mergeConfig(config, { plugins: [turbosnap({ rootDir: config.root ?? process.cwd() })], }); + } else if (configType === 'DEVELOPMENT') { + config = mergeConfig(config, { + resolve: { + alias: { + '@medplum/core': path.resolve(__dirname, '../../core/src'), + }, + }, + }); } - return finalConfig; + return config; }, }; diff --git a/packages/react/package.json b/packages/react/package.json index eb82f3ad18..5df90198ef 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/react", - "version": "3.0.2", + "version": "3.0.4", "description": "Medplum React Component Library", "keywords": [ "medplum", @@ -67,42 +67,42 @@ "test": "jest" }, "devDependencies": { - "@mantine/core": "7.5.0", - "@mantine/hooks": "7.5.0", - "@mantine/notifications": "7.5.0", - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhirtypes": "*", - "@medplum/mock": "*", - "@medplum/react-hooks": "*", - "@storybook/addon-actions": "7.6.10", - "@storybook/addon-essentials": "7.6.10", - "@storybook/addon-links": "7.6.10", - "@storybook/addon-storysource": "7.6.10", - "@storybook/builder-vite": "7.6.10", - "@storybook/react": "7.6.10", - "@storybook/react-vite": "7.6.10", - "@tabler/icons-react": "2.46.0", + "@mantine/core": "7.5.3", + "@mantine/hooks": "7.5.3", + "@mantine/notifications": "7.5.3", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhirtypes": "3.0.4", + "@medplum/mock": "3.0.4", + "@medplum/react-hooks": "3.0.4", + "@storybook/addon-actions": "7.6.16", + "@storybook/addon-essentials": "7.6.16", + "@storybook/addon-links": "7.6.16", + "@storybook/addon-storysource": "7.6.16", + "@storybook/builder-vite": "7.6.16", + "@storybook/react": "7.6.16", + "@storybook/react-vite": "7.6.16", + "@tabler/icons-react": "2.47.0", "@testing-library/dom": "9.3.4", - "@testing-library/jest-dom": "6.3.0", - "@testing-library/react": "14.1.2", + "@testing-library/jest-dom": "6.4.2", + "@testing-library/react": "14.2.1", "@testing-library/user-event": "14.5.2", - "@types/jest": "29.5.11", - "@types/node": "20.11.8", - "@types/react": "18.2.48", - "@types/react-dom": "18.2.18", + "@types/jest": "29.5.12", + "@types/node": "20.11.19", + "@types/react": "18.2.56", + "@types/react-dom": "18.2.19", "@vitejs/plugin-react": "4.2.1", "chromatic": "10.3.1", "jest": "29.7.0", "jest-each": "29.7.0", - "postcss": "8.4.33", - "postcss-preset-mantine": "1.12.3", + "postcss": "8.4.35", + "postcss-preset-mantine": "1.13.0", "react": "18.2.0", "react-dom": "18.2.0", "rfc6902": "5.1.1", "rimraf": "5.0.5", "sinon": "17.0.1", - "storybook": "7.6.10", + "storybook": "7.6.16", "typescript": "5.3.3", "vite-plugin-turbosnap": "^1.0.3" }, @@ -110,7 +110,7 @@ "@mantine/core": "^7.0.0", "@mantine/hooks": "^7.0.0", "@mantine/notifications": "^7.0.0", - "@medplum/core": "*", + "@medplum/core": "3.0.4", "react": "^17.0.2 || ^18.0.0", "react-dom": "^17.0.2 || ^18.0.0", "rfc6902": "^5.0.1" diff --git a/packages/react/src/AddressInput/AddressInput.tsx b/packages/react/src/AddressInput/AddressInput.tsx index 8b2d268b89..96fdd7f2c3 100644 --- a/packages/react/src/AddressInput/AddressInput.tsx +++ b/packages/react/src/AddressInput/AddressInput.tsx @@ -24,10 +24,6 @@ export function AddressInput(props: AddressInputProps): JSX.Element { const valueRef = useRef
(); valueRef.current = value; - // const stateElement = useMemo( - // () => getModifiedNestedElement(props.path + '.state'), - // [getModifiedNestedElement, props.path] - // ); // TODO{profiles} is it worth the complexity of subbing in an autocomplete input when // a binding is defined in a profile? If so, it should go in a new wrapper around TextInput // e.g. US Core Patient Profile diff --git a/packages/react/src/AppShell/AppShell.stories.tsx b/packages/react/src/AppShell/AppShell.stories.tsx index dd54822108..e70a4c5888 100644 --- a/packages/react/src/AppShell/AppShell.stories.tsx +++ b/packages/react/src/AppShell/AppShell.stories.tsx @@ -1,14 +1,19 @@ +import { ProfileResource, getReferenceString } from '@medplum/core'; +import { useMedplumProfile } from '@medplum/react-hooks'; import { Meta } from '@storybook/react'; import { Icon2fa, IconBellRinging, + IconClipboardCheck, IconDatabaseImport, IconFingerprint, IconKey, + IconMail, IconReceipt2, IconSettings, } from '@tabler/icons-react'; import { Logo } from '../Logo/Logo'; +import { NotificationIcon } from '../NotificationIcon/NotificationIcon'; import { AppShell } from './AppShell'; import classes from './AppShell.stories.module.css'; @@ -124,3 +129,53 @@ export function DisabledResourceNavigator(): JSX.Element {
); } + +export function NotificationIcons(): JSX.Element { + const profile = useMedplumProfile(); + + return ( +
+ } + version="your.version" + menus={[ + { + title: 'My Menu', + links: [ + { href: '/notifications', label: 'Notifications', icon: }, + { href: '/billing', label: 'Billing', icon: }, + { href: '/security', label: 'Security', icon: }, + { href: '/sshkeys', label: 'SSH Keys', icon: }, + { href: '/databases', label: 'Databases', icon: }, + { href: '/auth', label: 'Authentication', icon: }, + { href: '/settings', label: 'Other Settings', icon: }, + ], + }, + ]} + displayAddBookmark={true} + notifications={ + <> + } + onClick={() => console.log('foo')} + /> + } + onClick={() => console.log('foo')} + /> + + } + > + Your application here + +
+ ); +} diff --git a/packages/react/src/AppShell/AppShell.tsx b/packages/react/src/AppShell/AppShell.tsx index 698b668d39..8dd8c8e7f5 100644 --- a/packages/react/src/AppShell/AppShell.tsx +++ b/packages/react/src/AppShell/AppShell.tsx @@ -18,6 +18,7 @@ export interface AppShellProps { readonly children: ReactNode; readonly displayAddBookmark?: boolean; readonly resourceTypeSearchDisabled?: boolean; + readonly notifications?: ReactNode; } export function AppShell(props: AppShellProps): JSX.Element { @@ -71,6 +72,7 @@ export function AppShell(props: AppShellProps): JSX.Element { logo={props.logo} version={props.version} navbarToggle={toggleNavbar} + notifications={props.notifications} /> )} {profile && navbarOpen ? ( diff --git a/packages/react/src/AppShell/Header.tsx b/packages/react/src/AppShell/Header.tsx index 829fcd903b..0d2ed338d9 100644 --- a/packages/react/src/AppShell/Header.tsx +++ b/packages/react/src/AppShell/Header.tsx @@ -1,24 +1,13 @@ -import { - Avatar, - Group, - AppShell as MantineAppShell, - MantineColorScheme, - Menu, - SegmentedControl, - Stack, - Text, - UnstyledButton, - useMantineColorScheme, -} from '@mantine/core'; -import { ProfileResource, formatHumanName, getReferenceString } from '@medplum/core'; +import { Group, AppShell as MantineAppShell, Menu, Text, UnstyledButton } from '@mantine/core'; +import { formatHumanName } from '@medplum/core'; import { HumanName } from '@medplum/fhirtypes'; -import { useMedplumContext } from '@medplum/react-hooks'; -import { IconChevronDown, IconLogout, IconSettings, IconSwitchHorizontal } from '@tabler/icons-react'; +import { useMedplumProfile } from '@medplum/react-hooks'; +import { IconChevronDown } from '@tabler/icons-react'; import cx from 'clsx'; import { ReactNode, useState } from 'react'; -import { HumanNameDisplay } from '../HumanNameDisplay/HumanNameDisplay'; import { ResourceAvatar } from '../ResourceAvatar/ResourceAvatar'; import classes from './Header.module.css'; +import { HeaderDropdown } from './HeaderDropdown'; import { HeaderSearchInput } from './HeaderSearchInput'; export interface HeaderProps { @@ -28,14 +17,12 @@ export interface HeaderProps { readonly logo: ReactNode; readonly version?: string; readonly navbarToggle: () => void; + readonly notifications?: ReactNode; } export function Header(props: HeaderProps): JSX.Element { - const context = useMedplumContext(); - const { medplum, profile, navigate } = context; - const logins = medplum.getLogins(); + const profile = useMedplumProfile(); const [userMenuOpened, setUserMenuOpened] = useState(false); - const { colorScheme, setColorScheme } = useMantineColorScheme(); return ( @@ -48,104 +35,35 @@ export function Header(props: HeaderProps): JSX.Element { )} - - setUserMenuOpened(false)} - > - - setUserMenuOpened((o) => !o)} - > - - - - {formatHumanName(profile?.name?.[0] as HumanName)} - - - - - - - - - - - {medplum.getActiveLogin()?.project.display} - - - {logins.length > 1 && } - {logins.map( - (login) => - login.profile.reference !== getReferenceString(context.profile as ProfileResource) && ( - { - medplum - .setActiveLogin(login) - .then(() => window.location.reload()) - .catch(console.log); - }} - > - - -
- - {login.profile.display} - - - {login.project.display} - -
-
-
- ) - )} - - - setColorScheme(newValue as MantineColorScheme)} - data={[ - { label: 'Light', value: 'light' }, - { label: 'Dark', value: 'dark' }, - { label: 'Auto', value: 'auto' }, - ]} - /> - - - } - onClick={() => navigate('/signin')} - > - Add another account - - } - onClick={() => navigate(`/${getReferenceString(profile as ProfileResource)}`)} - > - Account settings - - } - onClick={async () => { - await medplum.signOut(); - navigate('/signin'); - }} - > - Sign out - - - {props.version} - -
-
+ + {props.notifications} + setUserMenuOpened(false)} + > + + setUserMenuOpened((o) => !o)} + > + + + + {formatHumanName(profile?.name?.[0] as HumanName)} + + + + + + + + + +
); diff --git a/packages/react/src/AppShell/HeaderDropdown.tsx b/packages/react/src/AppShell/HeaderDropdown.tsx new file mode 100644 index 0000000000..af8a999f4d --- /dev/null +++ b/packages/react/src/AppShell/HeaderDropdown.tsx @@ -0,0 +1,101 @@ +import { + Avatar, + Group, + MantineColorScheme, + Menu, + SegmentedControl, + Stack, + Text, + useMantineColorScheme, +} from '@mantine/core'; +import { ProfileResource, getReferenceString } from '@medplum/core'; +import { HumanName } from '@medplum/fhirtypes'; +import { useMedplumContext } from '@medplum/react-hooks'; +import { IconLogout, IconSettings, IconSwitchHorizontal } from '@tabler/icons-react'; +import { HumanNameDisplay } from '../HumanNameDisplay/HumanNameDisplay'; +import { ResourceAvatar } from '../ResourceAvatar/ResourceAvatar'; + +export interface HeaderDropdownProps { + readonly version?: string; +} + +export function HeaderDropdown(props: HeaderDropdownProps): JSX.Element { + const context = useMedplumContext(); + const { medplum, profile, navigate } = context; + const logins = medplum.getLogins(); + const { colorScheme, setColorScheme } = useMantineColorScheme(); + + return ( + <> + + + + + {medplum.getActiveLogin()?.project.display} + + + {logins.length > 1 && } + {logins.map( + (login) => + login.profile.reference !== getReferenceString(context.profile as ProfileResource) && ( + { + medplum + .setActiveLogin(login) + .then(() => window.location.reload()) + .catch(console.log); + }} + > + + +
+ + {login.profile.display} + + + {login.project.display} + +
+
+
+ ) + )} + + + setColorScheme(newValue as MantineColorScheme)} + data={[ + { label: 'Light', value: 'light' }, + { label: 'Dark', value: 'dark' }, + { label: 'Auto', value: 'auto' }, + ]} + /> + + + } onClick={() => navigate('/signin')}> + Add another account + + } + onClick={() => navigate(`/${getReferenceString(profile as ProfileResource)}`)} + > + Account settings + + } + onClick={async () => { + await medplum.signOut(); + navigate('/signin'); + }} + > + Sign out + + + {props.version} + + + ); +} diff --git a/packages/react/src/AppShell/Navbar.tsx b/packages/react/src/AppShell/Navbar.tsx index f35dae3db9..234016efc6 100644 --- a/packages/react/src/AppShell/Navbar.tsx +++ b/packages/react/src/AppShell/Navbar.tsx @@ -58,6 +58,7 @@ export function Navbar(props: NavbarProps): JSX.Element { key={window.location.pathname} name="resourceType" placeholder="Resource Type" + maxValues={0} onChange={(newValue) => navigateResourceType(newValue)} /> diff --git a/packages/react/src/AsyncAutocomplete/AsyncAutocomplete.tsx b/packages/react/src/AsyncAutocomplete/AsyncAutocomplete.tsx index 7cae006ccc..13ba5fc36c 100644 --- a/packages/react/src/AsyncAutocomplete/AsyncAutocomplete.tsx +++ b/packages/react/src/AsyncAutocomplete/AsyncAutocomplete.tsx @@ -176,6 +176,7 @@ export function AsyncAutocomplete(props: AsyncAutocompleteProps): JSX.Elem const handleValueSelect = (val: string): void => { setSearch(''); setOptions([]); + combobox.closeDropdown(); lastValueRef.current = undefined; if (val === '$create') { addSelected(search); @@ -233,6 +234,7 @@ export function AsyncAutocomplete(props: AsyncAutocompleteProps): JSX.Elem setSearch(''); setSelected([]); onChange([]); + combobox.closeDropdown(); }} /> ); @@ -256,18 +258,20 @@ export function AsyncAutocomplete(props: AsyncAutocompleteProps): JSX.Elem ))} - - combobox.closeDropdown()} - onKeyDown={handleKeyDown} - onChange={handleSearchChange} - /> - + {(maxValues === undefined || maxValues === 0 || selected.length < maxValues) && ( + + combobox.closeDropdown()} + onKeyDown={handleKeyDown} + onChange={handleSearchChange} + /> + + )} diff --git a/packages/react/src/BackboneElementInput/BackboneElementInput.stories.tsx b/packages/react/src/BackboneElementInput/BackboneElementInput.stories.tsx index 2d20571791..35cbd86f76 100644 --- a/packages/react/src/BackboneElementInput/BackboneElementInput.stories.tsx +++ b/packages/react/src/BackboneElementInput/BackboneElementInput.stories.tsx @@ -9,6 +9,6 @@ export default { export const Basic = (): JSX.Element => ( - + ); diff --git a/packages/react/src/BackboneElementInput/BackboneElementInput.test.tsx b/packages/react/src/BackboneElementInput/BackboneElementInput.test.tsx index b47023c520..2a0c8a89e2 100644 --- a/packages/react/src/BackboneElementInput/BackboneElementInput.test.tsx +++ b/packages/react/src/BackboneElementInput/BackboneElementInput.test.tsx @@ -73,19 +73,19 @@ describe('BackboneElementInput', () => { test('Renders', async () => { await medplum.requestSchema('Patient'); - await setup({ typeName: 'PatientContact' }); + await setup({ typeName: 'PatientContact', path: 'Patient.contact' }); expect(screen.getByText('Name')).toBeDefined(); }); test('Handles content reference', async () => { await medplum.requestSchema('ValueSet'); - await setup({ typeName: 'ValueSetCompose' }); + await setup({ typeName: 'ValueSetCompose', path: 'ValueSet.compose' }); expect(screen.getByText('Locked Date')).toBeInTheDocument(); expect(screen.getByText('Exclude')).toBeInTheDocument(); }); test('Not implemented', async () => { - await setup({ typeName: 'Foo' }); + await setup({ typeName: 'Foo', path: 'Foo' }); expect(screen.getByText('Foo not implemented')).toBeInTheDocument(); }); @@ -98,6 +98,7 @@ describe('BackboneElementInput', () => { indexStructureDefinitionBundle([profile], profile.url); } await setup({ + path: fishPatientProfile.type, typeName: fishPatientProfile.name, profileUrl: fishPatientProfile.url, defaultValue: fishPatient, diff --git a/packages/react/src/BackboneElementInput/BackboneElementInput.tsx b/packages/react/src/BackboneElementInput/BackboneElementInput.tsx index b65a8a4383..60dcb21524 100644 --- a/packages/react/src/BackboneElementInput/BackboneElementInput.tsx +++ b/packages/react/src/BackboneElementInput/BackboneElementInput.tsx @@ -1,12 +1,15 @@ -import { tryGetDataType } from '@medplum/core'; +import { ElementsContextType, buildElementsContext, tryGetDataType } from '@medplum/core'; import { OperationOutcome } from '@medplum/fhirtypes'; import { useContext, useMemo, useState } from 'react'; import { ElementsInput } from '../ElementsInput/ElementsInput'; -import { BackboneElementContext, buildBackboneElementContext } from './BackboneElementInput.utils'; +import { ElementsContext } from '../ElementsInput/ElementsInput.utils'; +import { maybeWrapWithContext } from '../utils/maybeWrapWithContext'; export interface BackboneElementInputProps { /** Type name the backbone element represents */ readonly typeName: string; + /** The path identifies the element and is expressed as a "."-separated list of ancestor elements, beginning with the name of the resource or extension. */ + readonly path: string; /** (optional) The contents of the resource represented by the backbone element */ readonly defaultValue?: any; /** (optional) OperationOutcome from the last attempted system action*/ @@ -18,36 +21,37 @@ export interface BackboneElementInputProps { } export function BackboneElementInput(props: BackboneElementInputProps): JSX.Element { - const { typeName } = props; - const [value, setValue] = useState(props.defaultValue ?? {}); - const backboneContext = useContext(BackboneElementContext); - const profileUrl = props.profileUrl ?? backboneContext.profileUrl; - const typeSchema = useMemo(() => tryGetDataType(typeName, profileUrl), [typeName, profileUrl]); + const [defaultValue] = useState(() => props.defaultValue ?? {}); + const parentElementsContext = useContext(ElementsContext); + const profileUrl = props.profileUrl ?? parentElementsContext?.profileUrl; + const typeSchema = useMemo(() => tryGetDataType(props.typeName, profileUrl), [props.typeName, profileUrl]); + const type = typeSchema?.type ?? props.typeName; - const context = useMemo(() => { - return buildBackboneElementContext(typeSchema, profileUrl); - }, [typeSchema, profileUrl]); + const contextValue: ElementsContextType | undefined = useMemo(() => { + if (!typeSchema) { + return undefined; + } + return buildElementsContext({ + parentContext: parentElementsContext, + elements: typeSchema.elements, + path: props.path, + profileUrl: typeSchema.url, + }); + }, [typeSchema, props.path, parentElementsContext]); if (!typeSchema) { - return
{typeName} not implemented
; - } - - function setValueWrapper(newValue: any): void { - setValue(newValue); - if (props.onChange) { - props.onChange(newValue); - } + return
{type} not implemented
; } - return ( - - - + return maybeWrapWithContext( + ElementsContext.Provider, + contextValue, + ); } diff --git a/packages/react/src/BackboneElementInput/BackboneElementInput.utils.test.ts b/packages/react/src/BackboneElementInput/BackboneElementInput.utils.test.ts deleted file mode 100644 index 41646a5125..0000000000 --- a/packages/react/src/BackboneElementInput/BackboneElementInput.utils.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { parseStructureDefinition } from '@medplum/core'; -import { readFileSync } from 'fs'; -import { resolve } from 'path'; -import { buildBackboneElementContext } from './BackboneElementInput.utils'; - -describe('buildBackboneElementContext', () => { - test('deeply nested schema', () => { - const sd = JSON.parse( - readFileSync(resolve(__dirname, '__test__', 'StructureDefinition-us-core-medicationrequest.json'), 'utf8') - ); - const schema = parseStructureDefinition(sd); - - const context = buildBackboneElementContext(schema, sd.url); - - expect(context.profileUrl).toEqual(sd.url); - expect(context.getModifiedNestedElement('MedicationRequest.dosageInstruction.method')).toBeDefined(); - }); -}); diff --git a/packages/react/src/BackboneElementInput/BackboneElementInput.utils.ts b/packages/react/src/BackboneElementInput/BackboneElementInput.utils.ts deleted file mode 100644 index 5310b81ed8..0000000000 --- a/packages/react/src/BackboneElementInput/BackboneElementInput.utils.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { InternalSchemaElement, InternalTypeSchema } from '@medplum/core'; -import React from 'react'; - -/** - * Splits a string on the last occurrence of the delimiter - * @param str - The string to split - * @param delim - The delimiter string - * @returns An array of two strings; the first consisting of the beginning of the - * string up to the last occurrence of the delimiter. the second is the remainder of the - * string after the last occurrence of the delimiter. If the delimiter is not present - * in the string, the first element is empty and the second is the input string. - */ -function splitOnceRight(str: string, delim: string): [string, string] { - const delimIndex = str.lastIndexOf(delim); - if (delimIndex === -1) { - return ['', str]; - } - const beginning = str.substring(0, delimIndex); - const last = str.substring(delimIndex + delim.length); - return [beginning, last]; -} - -export type FlatWalkedPaths = { - [path: string]: InternalSchemaElement; -}; - -export type BackboneElementContextType = { - debugMode: boolean; - profileUrl: string | undefined; - /** - * Get the element definition for the specified path if it has been modified by a profile. - * @param nestedElementPath - The path of the nested element - * @returns The modified element definition if it has been modified by the active profile or undefined. If undefined, - * the element has the default definition for the given type. - */ - getModifiedNestedElement: (nestedElementPath: string) => InternalSchemaElement | undefined; -}; - -export const BackboneElementContext = React.createContext({ - profileUrl: undefined, - debugMode: false, - getModifiedNestedElement: () => undefined, -}); - -export function buildBackboneElementContext( - typeSchema: InternalTypeSchema | undefined, - profileUrl?: string | undefined, - debugMode?: boolean | undefined -): BackboneElementContextType { - const nestedPaths: FlatWalkedPaths = Object.create(null); - - function getModifiedNestedElement(nestedElementPath: string): InternalSchemaElement | undefined { - return nestedPaths[nestedElementPath]; - } - - const elements = typeSchema?.elements; - if (elements) { - const seenKeys = new Set(); - for (const [key, property] of Object.entries(elements)) { - const [beginning, _last] = splitOnceRight(key, '.'); - // assume paths are hierarchically sorted, e.g. identifier comes before identifier.id - if (seenKeys.has(beginning)) { - nestedPaths[typeSchema.type + '.' + key] = property; - } - seenKeys.add(key); - } - } - - return { debugMode: debugMode ?? false, profileUrl, getModifiedNestedElement }; -} diff --git a/packages/react/src/BackboneElementInput/__test__/StructureDefinition-us-core-medicationrequest.json b/packages/react/src/BackboneElementInput/__test__/StructureDefinition-us-core-medicationrequest.json deleted file mode 100644 index 0a037dd3fb..0000000000 --- a/packages/react/src/BackboneElementInput/__test__/StructureDefinition-us-core-medicationrequest.json +++ /dev/null @@ -1 +0,0 @@ -{"resourceType":"StructureDefinition","id":"us-core-medicationrequest","url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-medicationrequest","version":"6.1.0","name":"USCoreMedicationRequestProfile","title":"US Core MedicationRequest Profile","status":"active","experimental":false,"date":"2020-06-26","publisher":"HL7 International - Cross-Group Projects","contact":[{"name":"HL7 International - Cross-Group Projects","telecom":[{"system":"url","value":"http://www.hl7.org/Special/committees/cgp"},{"system":"email","value":"cgp@lists.HL7.org"}]}],"description":"The US Core Medication Request Profile is based upon the core FHIR MedicationRequest Resource and meets the U.S. Core Data for Interoperability (USCDI) v2 *Medications* requirements. The MedicationRequest resource can be used to record a patient's medication prescription or order. This profile sets minimum expectations for the MedicationRequest resource to record, search, and fetch a patient's medication to promote interoperability and adoption through common implementation. It identifies which core elements, extensions, vocabularies, and value sets **SHALL** be present in the resource and constrains the way the elements are used when using this profile. It provides the floor for standards development for specific use cases.","jurisdiction":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"US"}]}],"copyright":"Used by permission of HL7 International, all rights reserved Creative Commons License","fhirVersion":"4.0.1","mapping":[{"identity":"workflow","uri":"http://hl7.org/fhir/workflow","name":"Workflow Pattern"},{"identity":"script10.6","uri":"http://ncpdp.org/SCRIPT10_6","name":"Mapping to NCPDP SCRIPT 10.6"},{"identity":"rim","uri":"http://hl7.org/v3","name":"RIM Mapping"},{"identity":"w5","uri":"http://hl7.org/fhir/fivews","name":"FiveWs Pattern Mapping"},{"identity":"v2","uri":"http://hl7.org/v2","name":"HL7 v2 Mapping"}],"kind":"resource","abstract":false,"type":"MedicationRequest","baseDefinition":"http://hl7.org/fhir/StructureDefinition/MedicationRequest","derivation":"constraint","snapshot":{"element":[{"id":"MedicationRequest","path":"MedicationRequest","short":"Ordering of medication for patient or group","definition":"\\-","comment":"\\-","alias":["Prescription","Order"],"min":0,"max":"*","base":{"path":"MedicationRequest","min":0,"max":"*"},"constraint":[{"key":"dom-2","severity":"error","human":"If the resource is contained in another resource, it SHALL NOT contain nested Resources","expression":"contained.contained.empty()","xpath":"not(parent::f:contained and f:contained)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-3","severity":"error","human":"If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource","expression":"contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()","xpath":"not(exists(for $id in f:contained/*/f:id/@value return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-4","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated","expression":"contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()","xpath":"not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"dom-5","severity":"error","human":"If a resource is contained in another resource, it SHALL NOT have a security label","expression":"contained.meta.security.empty()","xpath":"not(exists(f:contained/*/f:meta/f:security))","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice","valueBoolean":true},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice-explanation","valueMarkdown":"When a resource has no narrative, only systems that fully understand the data can display the resource to a human safely. Including a human readable representation in the resource makes for a much more robust eco-system and cheaper handling of resources by intermediary systems. Some ecosystems restrict distribution of resources to only those systems that do fully understand the resources, and as a consequence implementers may believe that the narrative is superfluous. However experience shows that such eco-systems often open up to new participants over time."}],"key":"dom-6","severity":"warning","human":"A resource should have narrative for robust management","expression":"text.`div`.exists()","xpath":"exists(f:text/h:div)","source":"http://hl7.org/fhir/StructureDefinition/DomainResource"},{"key":"us-core-21","severity":"error","human":"requester SHALL be present if intent is \"order\"","expression":"(intent='order' or intent='original-order' or intent='reflex-order'or intent='filler-order' or intent='instance-order') implies requester.exists()"}],"mustSupport":false,"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"Entity. Role, or Act"},{"identity":"workflow","map":"Request"},{"identity":"script10.6","map":"Message/Body/NewRx"},{"identity":"rim","map":"CombinedMedicationRequest"},{"identity":"argonaut-dq-dstu2","map":"MedicationOrder"}]},{"id":"MedicationRequest.id","path":"MedicationRequest.id","short":"Logical id of this artifact","definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.","comment":"The only time that a resource does not have an id is when it is being submitted to the server using a create operation.","min":0,"max":"1","base":{"path":"Resource.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"id"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.meta","path":"MedicationRequest.meta","short":"Metadata about the resource","definition":"The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.","min":0,"max":"1","base":{"path":"Resource.meta","min":0,"max":"1"},"type":[{"code":"Meta"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true},{"id":"MedicationRequest.implicitRules","path":"MedicationRequest.implicitRules","short":"A set of rules under which this content was created","definition":"A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc.","comment":"Asserting this rule set restricts the content to be only understood by a limited set of trading partners. This inherently limits the usefulness of the data in the long term. However, the existing health eco-system is highly fractured, and not yet ready to define, collect, and exchange data in a generally computable sense. Wherever possible, implementers and/or specification writers should avoid using this element. Often, when used, the URL is a reference to an implementation guide that defines these special rules as part of it's narrative along with other profiles, value sets, etc.","min":0,"max":"1","base":{"path":"Resource.implicitRules","min":0,"max":"1"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because the implicit rules may provide additional knowledge about the resource that modifies it's meaning or interpretation","isSummary":true},{"id":"MedicationRequest.language","path":"MedicationRequest.language","short":"Language of the resource content","definition":"The base language in which the resource is written.","comment":"Language is provided to support indexing and accessibility (typically, services such as text to speech use the language tag). The html language tag in the narrative applies to the narrative. The language tag on the resource may be used to specify the language of other presentations generated from the data in the resource. Not all the content has to be in the base language. The Resource.language should not be assumed to apply to the narrative automatically. If a language is specified, it should it also be specified on the div element in the html (see rules in HTML5 for information about the relationship between xml:lang and the html lang attribute).","min":0,"max":"1","base":{"path":"Resource.language","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/all-languages"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"Language"},{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding","valueBoolean":true}],"strength":"preferred","description":"A human language.","valueSet":"http://hl7.org/fhir/ValueSet/languages"}},{"id":"MedicationRequest.text","path":"MedicationRequest.text","short":"Text summary of the resource, for human interpretation","definition":"A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it \"clinically safe\" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety.","comment":"Contained resources do not have narrative. Resources that are not contained SHOULD have a narrative. In some cases, a resource may only have text with little or no additional discrete data (as long as all minOccurs=1 elements are satisfied). This may be necessary for data from legacy systems where information is captured as a \"text blob\" or where text is additionally entered raw or narrated and encoded information is added later.","alias":["narrative","html","xhtml","display"],"min":0,"max":"1","base":{"path":"DomainResource.text","min":0,"max":"1"},"type":[{"code":"Narrative"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"Act.text?"}]},{"id":"MedicationRequest.contained","path":"MedicationRequest.contained","short":"Contained, inline Resources","definition":"These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope.","comment":"This should never be done when the content can be identified properly, as once identification is lost, it is extremely difficult (and context dependent) to restore it again. Contained resources may have profiles and tags In their meta elements, but SHALL NOT have security labels.","alias":["inline resources","anonymous resources","contained resources"],"min":0,"max":"*","base":{"path":"DomainResource.contained","min":0,"max":"*"},"type":[{"code":"Resource"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"N/A"}]},{"id":"MedicationRequest.extension","path":"MedicationRequest.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the resource. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"N/A"}]},{"id":"MedicationRequest.modifierExtension","path":"MedicationRequest.modifierExtension","short":"Extensions that cannot be ignored","definition":"May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"DomainResource.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the resource that contains them","isSummary":false,"mapping":[{"identity":"rim","map":"N/A"}]},{"id":"MedicationRequest.identifier","path":"MedicationRequest.identifier","short":"External ids for this request","definition":"Identifiers associated with this medication request that are defined by business processes and/or used to refer to it when a direct URL reference to the resource itself is not appropriate. They are business identifiers assigned to this resource by the performer or other systems and remain constant as the resource is updated and propagates from server to server.","comment":"This is a business identifier, not a resource identifier.","min":0,"max":"*","base":{"path":"MedicationRequest.identifier","min":0,"max":"*"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"workflow","map":"Request.identifier"},{"identity":"script10.6","map":"Message/Header/PrescriberOrderNumber"},{"identity":"w5","map":"FiveWs.identifier"},{"identity":"v2","map":"ORC-2-Placer Order Number / ORC-3-Filler Order Number"},{"identity":"rim","map":".id"}]},{"id":"MedicationRequest.status","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.status","short":"(USCDI) active | on-hold | cancelled | completed | entered-in-error | stopped | draft | unknown","definition":"A code specifying the current state of the order. Generally, this will be active or completed state.","comment":"This element is labeled as a modifier because the status contains codes that mark the resource as not currently valid.","min":1,"max":"1","base":{"path":"MedicationRequest.status","min":1,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":true,"isModifierReason":"This element is labeled as a modifier because it is a status element that contains status entered-in-error which means that the resource should not be treated as valid","isSummary":true,"binding":{"strength":"required","description":"A code specifying the state of the prescribing event. Describes the lifecycle of the prescription.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-status"},"mapping":[{"identity":"workflow","map":"Request.status"},{"identity":"script10.6","map":"no mapping"},{"identity":"w5","map":"FiveWs.status"},{"identity":"rim","map":".statusCode"},{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.status"}]},{"id":"MedicationRequest.statusReason","path":"MedicationRequest.statusReason","short":"Reason for current status","definition":"Captures the reason for the current state of the MedicationRequest.","comment":"This is generally only used for \"exception\" statuses such as \"suspended\" or \"cancelled\". The reason why the MedicationRequest was created at all is captured in reasonCode, not here.","min":0,"max":"1","base":{"path":"MedicationRequest.statusReason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestStatusReason"}],"strength":"example","description":"Identifies the reasons for a given status.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-status-reason"},"mapping":[{"identity":"workflow","map":"Request.statusReason"},{"identity":"rim","map":".inboundRelationship[typeCode=SUBJ].source[classCode=CACT, moodCode=EVN].reasonCOde"}]},{"id":"MedicationRequest.intent","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.intent","short":"(USCDI) proposal | plan | order | original-order | reflex-order | filler-order | instance-order | option","definition":"Whether the request is a proposal, plan, or an original order.","comment":"It is expected that the type of requester will be restricted for different stages of a MedicationRequest. For example, Proposals can be created by a patient, relatedPerson, Practitioner or Device. Plans can be created by Practitioners, Patients, RelatedPersons and Devices. Original orders can be created by a Practitioner only.\r\rAn instance-order is an instantiation of a request or order and may be used to populate Medication Administration Record.\r\rThis element is labeled as a modifier because the intent alters when and how the resource is actually applicable.","min":1,"max":"1","base":{"path":"MedicationRequest.intent","min":1,"max":"1"},"type":[{"code":"code"}],"condition":["us-core-21"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":true,"isModifierReason":"This element changes the interpretation of all descriptive attributes. For example \"the time the request is recommended to occur\" vs. \"the time the request is authorized to occur\" or \"who is recommended to perform the request\" vs. \"who is authorized to perform the request","isSummary":true,"binding":{"strength":"required","description":"The kind of medication order.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-intent"},"mapping":[{"identity":"workflow","map":"Request.intent"},{"identity":"w5","map":"FiveWs.class"},{"identity":"rim","map":".moodCode (nuances beyond PRP/PLAN/RQO would need to be elsewhere)"},{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.status"}]},{"id":"MedicationRequest.category","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.category","slicing":{"discriminator":[{"type":"pattern","path":"$this"}],"rules":"open"},"short":"(USCDI) Type of medication usage","definition":"Indicates the type of medication request (for example, where the medication is expected to be consumed or administered (i.e. inpatient or outpatient)).","comment":"The category can be used to include where the medication is expected to be consumed or other types of requests.","min":0,"max":"*","base":{"path":"MedicationRequest.category","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestCategory"}],"strength":"example","description":"A coded concept identifying the category of medication request. For example, where the medication is to be consumed or administered, or the type of medication treatment.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-category"},"mapping":[{"identity":"script10.6","map":"Message/Body/NewRx/MedicationPrescribed/Directions\r\ror \r\rMessage/Body/NewRx/MedicationPrescribed/StructuredSIG"},{"identity":"w5","map":"FiveWs.class"},{"identity":"rim","map":".inboundRelationship[typeCode=COMP].source[classCode=OBS, moodCode=EVN, code=\"type of medication usage\"].value"}]},{"id":"MedicationRequest.category:us-core","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.category","sliceName":"us-core","short":"(USCDI) Type of medication usage","definition":"Indicates the type of medication request (for example, where the medication is expected to be consumed or administered (i.e. inpatient or outpatient)).","comment":"The category can be used to include where the medication is expected to be consumed or other types of requests.","min":0,"max":"*","base":{"path":"MedicationRequest.category","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"binding":{"strength":"required","description":"The type of medication order. Note that other codes are permitted, see [Required Bindings When Slicing by Value Sets](http://hl7.org/fhir/us/core/general-requirements.html#required-bindings-when-slicing-by-valuesets)","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-category"},"mapping":[{"identity":"script10.6","map":"Message/Body/NewRx/MedicationPrescribed/Directions\r\ror \r\rMessage/Body/NewRx/MedicationPrescribed/StructuredSIG"},{"identity":"w5","map":"FiveWs.class"},{"identity":"rim","map":".inboundRelationship[typeCode=COMP].source[classCode=OBS, moodCode=EVN, code=\"type of medication usage\"].value"}]},{"id":"MedicationRequest.priority","path":"MedicationRequest.priority","short":"routine | urgent | asap | stat","definition":"Indicates how quickly the Medication Request should be addressed with respect to other requests.","min":0,"max":"1","base":{"path":"MedicationRequest.priority","min":0,"max":"1"},"type":[{"code":"code"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestPriority"}],"strength":"required","description":"Identifies the level of importance to be assigned to actioning the request.","valueSet":"http://hl7.org/fhir/ValueSet/request-priority|4.0.1"},"mapping":[{"identity":"workflow","map":"Request.priority"},{"identity":"w5","map":"FiveWs.grade"},{"identity":"rim","map":".priorityCode"}]},{"id":"MedicationRequest.doNotPerform","path":"MedicationRequest.doNotPerform","short":"True if request is prohibiting action","definition":"If true indicates that the provider is asking for the medication request not to occur.","comment":"If do not perform is not specified, the request is a positive request e.g. \"do perform\".","min":0,"max":"1","base":{"path":"MedicationRequest.doNotPerform","min":0,"max":"1"},"type":[{"code":"boolean"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":true,"isModifierReason":"This element is labeled as a modifier because this element negates the request to occur (ie, this is a request for the medication not to be ordered or prescribed, etc)","isSummary":true,"mapping":[{"identity":"rim","map":"SubstanceAdministration.actionNegationInd"}]},{"id":"MedicationRequest.reported[x]","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.reported[x]","short":"(USCDI) Reported rather than primary record","definition":"Indicates if this record was captured as a secondary 'reported' record rather than as an original primary source-of-truth record. It may also indicate the source of the report.","min":0,"max":"1","base":{"path":"MedicationRequest.reported[x]","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"boolean"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner","http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization","http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient","http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole","http://hl7.org/fhir/us/core/StructureDefinition/us-core-relatedperson"],"_targetProfile":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]}]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"mapping":[{"identity":"rim","map":".participation[typeCode=INF].role"},{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.status"}]},{"id":"MedicationRequest.medication[x]","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.medication[x]","short":"(USCDI) Medication to be taken","definition":"Identifies the medication being requested. This is a link to a resource that represents the medication which may be the details of the medication or simply an attribute carrying a code that identifies the medication from a known list of medications.","comment":"If only a code is specified, then it needs to be a code for a specific product. If more information is required, then the use of the Medication resource is recommended. For example, if you require form or lot number or if the medication is compounded or extemporaneously prepared, then you must reference the Medication resource.","min":1,"max":"1","base":{"path":"MedicationRequest.medication[x]","min":1,"max":"1"},"type":[{"code":"CodeableConcept"},{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-medication"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"strength":"extensible","valueSet":"http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1010.4"},"mapping":[{"identity":"workflow","map":"Request.code"},{"identity":"script10.6","map":"Message/Body/NewRx/MedicationPrescribed\r\rMedication.code.coding.code = Message/Body/NewRx/MedicationPrescribed/DrugCoded/ProductCode\r\rMedication.code.coding.system = Message/Body/NewRx/MedicationPrescribed/DrugCoded/ProductCodeQualifier\r\rMedication.code.coding.display = Message/Body/NewRx/MedicationPrescribed/DrugDescription"},{"identity":"w5","map":"FiveWs.what[x]"},{"identity":"v2","map":"RXE-2-Give Code / RXO-1-Requested Give Code / RXC-2-Component Code"},{"identity":"rim","map":"consumable.administrableMedication"},{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.medication[x]"}]},{"id":"MedicationRequest.subject","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.subject","short":"(USCDI) Who or group medication request is for","definition":"A link to a resource representing the person or set of individuals to whom the medication will be given.","comment":"The subject on a medication request is mandatory. For the secondary use case where the actual subject is not provided, there still must be an anonymized subject specified.","min":1,"max":"1","base":{"path":"MedicationRequest.subject","min":1,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"mapping":[{"identity":"workflow","map":"Request.subject"},{"identity":"script10.6","map":"Message/Body/NewRx/Patient\r\r(need detail to link to specific patient … Patient.Identification in SCRIPT)"},{"identity":"w5","map":"FiveWs.subject[x]"},{"identity":"v2","map":"PID-3-Patient ID List"},{"identity":"rim","map":".participation[typeCode=AUT].role"},{"identity":"w5","map":"FiveWs.subject"},{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.patient"}]},{"id":"MedicationRequest.encounter","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.encounter","short":"(USCDI) Encounter created as part of encounter/admission/stay","definition":"The Encounter during which this [x] was created or to which the creation of this record is tightly associated.","comment":"This will typically be the encounter the event occurred within, but some activities may be initiated prior to or after the official completion of an encounter but still be tied to the context of the encounter.\" If there is a need to link to episodes of care they will be handled with an extension.","min":0,"max":"1","base":{"path":"MedicationRequest.encounter","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-encounter"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"mapping":[{"identity":"workflow","map":"Request.context"},{"identity":"script10.6","map":"no mapping"},{"identity":"w5","map":"FiveWs.context"},{"identity":"v2","map":"PV1-19-Visit Number"},{"identity":"rim","map":".inboundRelationship[typeCode=COMP].source[classCode=ENC, moodCode=EVN, code=\"type of encounter or episode\"]"},{"identity":"argonaut-dq-dstu2","map":"NA"}]},{"id":"MedicationRequest.supportingInformation","path":"MedicationRequest.supportingInformation","short":"Information to support ordering of the medication","definition":"Include additional information (for example, patient height and weight) that supports the ordering of the medication.","min":0,"max":"*","base":{"path":"MedicationRequest.supportingInformation","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Resource"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"workflow","map":"Request.supportingInfo"},{"identity":"w5","map":"FiveWs.context"},{"identity":"rim","map":".outboundRelationship[typeCode=PERT].target[A_SupportingClinicalStatement CMET minimal with many different choices of classCodes(ORG, ENC, PROC, SPLY, SBADM, OBS) and each of the act class codes draws from one or more of the following moodCodes (EVN, DEF, INT PRMS, RQO, PRP, APT, ARQ, GOL)]"}]},{"id":"MedicationRequest.authoredOn","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.authoredOn","short":"(USCDI) When request was initially authored","definition":"The date (and perhaps time) when the prescription was initially written or authored on.","min":0,"max":"1","base":{"path":"MedicationRequest.authoredOn","min":0,"max":"1"},"type":[{"code":"dateTime"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"mapping":[{"identity":"workflow","map":"Request.authoredOn"},{"identity":"script10.6","map":"Message/Body/NewRx/MedicationPrescribed/WrittenDate"},{"identity":"w5","map":"FiveWs.recorded"},{"identity":"v2","map":"RXE-32-Original Order Date/Time / ORC-9-Date/Time of Transaction"},{"identity":"rim","map":"author.time"},{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.dateWritten"}]},{"id":"MedicationRequest.requester","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.requester","short":"(USCDI) Who/What requested the Request","definition":"The individual, organization, or device that initiated the request and has responsibility for its activation.","min":0,"max":"1","base":{"path":"MedicationRequest.requester","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner","http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient","http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization","http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole","http://hl7.org/fhir/us/core/StructureDefinition/us-core-relatedperson","http://hl7.org/fhir/StructureDefinition/Device"],"_targetProfile":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]}]}],"condition":["us-core-21"],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"mapping":[{"identity":"workflow","map":"Request.requester"},{"identity":"w5","map":"FiveWs.author"},{"identity":"rim","map":".participation[typeCode=AUT].role"},{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.prescriber"}]},{"id":"MedicationRequest.performer","path":"MedicationRequest.performer","short":"Intended performer of administration","definition":"The specified desired performer of the medication treatment (e.g. the performer of the medication administration).","min":0,"max":"1","base":{"path":"MedicationRequest.performer","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Practitioner","http://hl7.org/fhir/StructureDefinition/PractitionerRole","http://hl7.org/fhir/StructureDefinition/Organization","http://hl7.org/fhir/StructureDefinition/Patient","http://hl7.org/fhir/StructureDefinition/Device","http://hl7.org/fhir/StructureDefinition/RelatedPerson","http://hl7.org/fhir/StructureDefinition/CareTeam"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"workflow","map":"Request.performer"},{"identity":"w5","map":"FiveWs.actor"},{"identity":"rim","map":".participation[typeCode=PRF].role[scoper.determinerCode=INSTANCE]"}]},{"id":"MedicationRequest.performerType","path":"MedicationRequest.performerType","short":"Desired kind of performer of the medication administration","definition":"Indicates the type of performer of the administration of the medication.","comment":"If specified without indicating a performer, this indicates that the performer must be of the specified type. If specified with a performer then it indicates the requirements of the performer if the designated performer is not available.","min":0,"max":"1","base":{"path":"MedicationRequest.performerType","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestPerformerType"}],"strength":"example","description":"Identifies the type of individual that is desired to administer the medication.","valueSet":"http://hl7.org/fhir/ValueSet/performer-role"},"mapping":[{"identity":"workflow","map":"Request.performerType"},{"identity":"rim","map":".participation[typeCode=PRF].role[scoper.determinerCode=KIND].code"}]},{"id":"MedicationRequest.recorder","path":"MedicationRequest.recorder","short":"Person who entered the request","definition":"The person who entered the order on behalf of another individual for example in the case of a verbal or a telephone order.","min":0,"max":"1","base":{"path":"MedicationRequest.recorder","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Practitioner","http://hl7.org/fhir/StructureDefinition/PractitionerRole"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"w5","map":"FiveWs.who"},{"identity":"rim","map":".participation[typeCode=TRANS].role[classCode=ASSIGNED].code (HealthcareProviderType)"}]},{"id":"MedicationRequest.reasonCode","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.reasonCode","short":"(USCDI) Reason or indication for ordering or not ordering the medication","definition":"The reason or the indication for ordering or not ordering the medication.","comment":"This could be a diagnosis code. If a full condition record exists or additional detail is needed, use reasonReference.","min":0,"max":"*","base":{"path":"MedicationRequest.reasonCode","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"strength":"extensible","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-condition-code"},"mapping":[{"identity":"workflow","map":"Request.reasonCode"},{"identity":"script10.6","map":"Message/Body/NewRx/MedicationPrescribed/Diagnosis/Primary/Value"},{"identity":"w5","map":"FiveWs.why[x]"},{"identity":"v2","map":"ORC-16-Order Control Code Reason /RXE-27-Give Indication/RXO-20-Indication / RXD-21-Indication / RXG-22-Indication / RXA-19-Indication"},{"identity":"rim","map":"reason.observation.reasonCode"}]},{"id":"MedicationRequest.reasonReference","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.reasonReference","short":"(USCDI) US Core Condition or Observation that supports the prescription","definition":"Condition or observation that supports why the medication was ordered.","comment":"This is a reference to a condition or observation that is the reason for the medication order. If only a code exists, use reasonCode.","min":0,"max":"*","base":{"path":"MedicationRequest.reasonReference","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Condition","http://hl7.org/fhir/StructureDefinition/Observation"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"workflow","map":"Request.reasonReference"},{"identity":"script10.6","map":"no mapping"},{"identity":"w5","map":"FiveWs.why[x]"},{"identity":"rim","map":"reason.observation[code=ASSERTION].value"}]},{"id":"MedicationRequest.instantiatesCanonical","path":"MedicationRequest.instantiatesCanonical","short":"Instantiates FHIR protocol or definition","definition":"The URL pointing to a protocol, guideline, orderset, or other definition that is adhered to in whole or in part by this MedicationRequest.","min":0,"max":"*","base":{"path":"MedicationRequest.instantiatesCanonical","min":0,"max":"*"},"type":[{"code":"canonical"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"mapping":[{"identity":"workflow","map":"Request.instantiates"},{"identity":"rim","map":".outboundRelationship[typeCode=DEFN].target"}]},{"id":"MedicationRequest.instantiatesUri","path":"MedicationRequest.instantiatesUri","short":"Instantiates external protocol or definition","definition":"The URL pointing to an externally maintained protocol, guideline, orderset or other definition that is adhered to in whole or in part by this MedicationRequest.","min":0,"max":"*","base":{"path":"MedicationRequest.instantiatesUri","min":0,"max":"*"},"type":[{"code":"uri"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"mapping":[{"identity":"rim","map":".outboundRelationship[typeCode=DEFN].target"}]},{"id":"MedicationRequest.basedOn","path":"MedicationRequest.basedOn","short":"What request fulfills","definition":"A plan or request that is fulfilled in whole or in part by this medication request.","min":0,"max":"*","base":{"path":"MedicationRequest.basedOn","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/CarePlan","http://hl7.org/fhir/StructureDefinition/MedicationRequest","http://hl7.org/fhir/StructureDefinition/ServiceRequest","http://hl7.org/fhir/StructureDefinition/ImmunizationRecommendation"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"mapping":[{"identity":"workflow","map":"Request.basedOn"},{"identity":"rim","map":".outboundRelationship[typeCode=FLFS].target[classCode=SBADM or PROC or PCPR or OBS, moodCode=RQO orPLAN or PRP]"}]},{"id":"MedicationRequest.groupIdentifier","path":"MedicationRequest.groupIdentifier","short":"Composite request this is part of","definition":"A shared identifier common to all requests that were authorized more or less simultaneously by a single author, representing the identifier of the requisition or prescription.","requirements":"Requests are linked either by a \"basedOn\" relationship (i.e. one request is fulfilling another) or by having a common requisition. Requests that are part of the same requisition are generally treated independently from the perspective of changing their state or maintaining them after initial creation.","min":0,"max":"1","base":{"path":"MedicationRequest.groupIdentifier","min":0,"max":"1"},"type":[{"code":"Identifier"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"mapping":[{"identity":"workflow","map":"Request.groupIdentifier"},{"identity":"rim","map":".outboundRelationship(typeCode=COMP].target[classCode=SBADM, moodCode=INT].id"}]},{"id":"MedicationRequest.courseOfTherapyType","path":"MedicationRequest.courseOfTherapyType","short":"Overall pattern of medication administration","definition":"The description of the overall patte3rn of the administration of the medication to the patient.","comment":"This attribute should not be confused with the protocol of the medication.","min":0,"max":"1","base":{"path":"MedicationRequest.courseOfTherapyType","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestCourseOfTherapy"}],"strength":"example","description":"Identifies the overall pattern of medication administratio.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-course-of-therapy"},"mapping":[{"identity":"rim","map":"Act.code where classCode = LIST and moodCode = EVN"}]},{"id":"MedicationRequest.insurance","path":"MedicationRequest.insurance","short":"Associated insurance coverage","definition":"Insurance plans, coverage extensions, pre-authorizations and/or pre-determinations that may be required for delivering the requested service.","min":0,"max":"*","base":{"path":"MedicationRequest.insurance","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Coverage","http://hl7.org/fhir/StructureDefinition/ClaimResponse"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"workflow","map":"Request.insurance"},{"identity":"rim","map":".outboundRelationship[typeCode=COVBY].target"}]},{"id":"MedicationRequest.note","path":"MedicationRequest.note","short":"Information about the prescription","definition":"Extra information about the prescription that could not be conveyed by the other attributes.","min":0,"max":"*","base":{"path":"MedicationRequest.note","min":0,"max":"*"},"type":[{"code":"Annotation"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"workflow","map":"Request.note"},{"identity":"script10.6","map":"Message/Body/NewRx/MedicationPrescribed/Note"},{"identity":"rim","map":".inboundRelationship[typeCode=SUBJ]/source[classCode=OBS,moodCode=EVN,code=\"annotation\"].value"}]},{"id":"MedicationRequest.dosageInstruction","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dosageInstruction","short":"(USCDI) How the medication should be taken","definition":"Indicates how the medication is to be used by the patient.","comment":"There are examples where a medication request may include the option of an oral dose or an Intravenous or Intramuscular dose. For example, \"Ondansetron 8mg orally or IV twice a day as needed for nausea\" or \"Compazine® (prochlorperazine) 5-10mg PO or 25mg PR bid prn nausea or vomiting\". In these cases, two medication requests would be created that could be grouped together. The decision on which dose and route of administration to use is based on the patient's condition at the time the dose is needed.","min":0,"max":"*","base":{"path":"MedicationRequest.dosageInstruction","min":0,"max":"*"},"type":[{"code":"Dosage"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"mapping":[{"identity":"workflow","map":"Request.occurrence[x]"},{"identity":"rim","map":"see dosageInstruction mapping"}]},{"id":"MedicationRequest.dosageInstruction.id","path":"MedicationRequest.dosageInstruction.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"n/a"}]},{"id":"MedicationRequest.dosageInstruction.extension","path":"MedicationRequest.dosageInstruction.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"n/a"}]},{"id":"MedicationRequest.dosageInstruction.modifierExtension","path":"MedicationRequest.dosageInstruction.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true,"mapping":[{"identity":"rim","map":"N/A"}]},{"id":"MedicationRequest.dosageInstruction.sequence","path":"MedicationRequest.dosageInstruction.sequence","short":"The order of the dosage instructions","definition":"Indicates the order in which the dosage instructions should be applied or interpreted.","requirements":"If the sequence number of multiple Dosages is the same, then it is implied that the instructions are to be treated as concurrent. If the sequence number is different, then the Dosages are intended to be sequential.","min":0,"max":"1","base":{"path":"Dosage.sequence","min":0,"max":"1"},"type":[{"code":"integer"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"mapping":[{"identity":"v2","map":"TQ1-1"},{"identity":"rim","map":".text"}]},{"id":"MedicationRequest.dosageInstruction.text","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dosageInstruction.text","short":"(USCDI) Free text dosage instructions e.g. SIG","definition":"Free text dosage instructions e.g. SIG.","requirements":"Free text dosage instructions can be used for cases where the instructions are too complex to code. The content of this attribute does not include the name or description of the medication. When coded instructions are present, the free text instructions may still be present for display to humans taking or administering the medication. It is expected that the text instructions will always be populated. If the dosage.timing attribute is also populated, then the dosage.text should reflect the same information as the timing. Additional information about administration or preparation of the medication should be included as text.","min":0,"max":"1","base":{"path":"Dosage.text","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"mapping":[{"identity":"v2","map":"RXO-6; RXE-21"},{"identity":"rim","map":".text"}]},{"id":"MedicationRequest.dosageInstruction.additionalInstruction","path":"MedicationRequest.dosageInstruction.additionalInstruction","short":"Supplemental instruction or warnings to the patient - e.g. \"with meals\", \"may cause drowsiness\"","definition":"Supplemental instructions to the patient on how to take the medication (e.g. \"with meals\" or\"take half to one hour before food\") or warnings for the patient about the medication (e.g. \"may cause drowsiness\" or \"avoid exposure of skin to direct sunlight or sunlamps\").","comment":"Information about administration or preparation of the medication (e.g. \"infuse as rapidly as possibly via intraperitoneal port\" or \"immediately following drug x\") should be populated in dosage.text.","requirements":"Additional instruction is intended to be coded, but where no code exists, the element could include text. For example, \"Swallow with plenty of water\" which might or might not be coded.","min":0,"max":"*","base":{"path":"Dosage.additionalInstruction","min":0,"max":"*"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"AdditionalInstruction"}],"strength":"example","description":"A coded concept identifying additional instructions such as \"take with water\" or \"avoid operating heavy machinery\".","valueSet":"http://hl7.org/fhir/ValueSet/additional-instruction-codes"},"mapping":[{"identity":"v2","map":"RXO-7"},{"identity":"rim","map":".text"}]},{"id":"MedicationRequest.dosageInstruction.patientInstruction","path":"MedicationRequest.dosageInstruction.patientInstruction","short":"Patient or consumer oriented instructions","definition":"Instructions in terms that are understood by the patient or consumer.","min":0,"max":"1","base":{"path":"Dosage.patientInstruction","min":0,"max":"1"},"type":[{"code":"string"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"mapping":[{"identity":"v2","map":"RXO-7"},{"identity":"rim","map":".text"}]},{"id":"MedicationRequest.dosageInstruction.timing","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dosageInstruction.timing","short":"(USCDI) When medication should be administered","definition":"When medication should be administered.","comment":"This attribute might not always be populated while the Dosage.text is expected to be populated. If both are populated, then the Dosage.text should reflect the content of the Dosage.timing.","requirements":"The timing schedule for giving the medication to the patient. This data type allows many different expressions. For example: \"Every 8 hours\"; \"Three times a day\"; \"1/2 an hour before breakfast for 10 days from 23-Dec 2011:\"; \"15 Oct 2013, 17 Oct 2013 and 1 Nov 2013\". Sometimes, a rate can imply duration when expressed as total volume / duration (e.g. 500mL/2 hours implies a duration of 2 hours). However, when rate doesn't imply duration (e.g. 250mL/hour), then the timing.repeat.duration is needed to convey the infuse over time period.","min":0,"max":"1","base":{"path":"Dosage.timing","min":0,"max":"1"},"type":[{"code":"Timing"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"mapping":[{"identity":"rim","map":".effectiveTime"}]},{"id":"MedicationRequest.dosageInstruction.asNeeded[x]","path":"MedicationRequest.dosageInstruction.asNeeded[x]","short":"Take \"as needed\" (for x)","definition":"Indicates whether the Medication is only taken when needed within a specific dosing schedule (Boolean option), or it indicates the precondition for taking the Medication (CodeableConcept).","comment":"Can express \"as needed\" without a reason by setting the Boolean = True. In this case the CodeableConcept is not populated. Or you can express \"as needed\" with a reason by including the CodeableConcept. In this case the Boolean is assumed to be True. If you set the Boolean to False, then the dose is given according to the schedule and is not \"prn\" or \"as needed\".","min":0,"max":"1","base":{"path":"Dosage.asNeeded[x]","min":0,"max":"1"},"type":[{"code":"boolean"},{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationAsNeededReason"}],"strength":"example","description":"A coded concept identifying the precondition that should be met or evaluated prior to consuming or administering a medication dose. For example \"pain\", \"30 minutes prior to sexual intercourse\", \"on flare-up\" etc.","valueSet":"http://hl7.org/fhir/ValueSet/medication-as-needed-reason"},"mapping":[{"identity":"v2","map":"TQ1-9"},{"identity":"rim","map":".outboundRelationship[typeCode=PRCN].target[classCode=OBS, moodCode=EVN, code=\"as needed\"].value=boolean or codable concept"}]},{"id":"MedicationRequest.dosageInstruction.site","path":"MedicationRequest.dosageInstruction.site","short":"Body site to administer to","definition":"Body site to administer to.","comment":"If the use case requires attributes from the BodySite resource (e.g. to identify and track separately) then use the standard extension [bodySite](http://hl7.org/fhir/R4/extension-bodysite.html). May be a summary code, or a reference to a very precise definition of the location, or both.","requirements":"A coded specification of the anatomic site where the medication first enters the body.","min":0,"max":"1","base":{"path":"Dosage.site","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationAdministrationSite"}],"strength":"example","description":"A coded concept describing the site location the medicine enters into or onto the body.","valueSet":"http://hl7.org/fhir/ValueSet/approach-site-codes"},"mapping":[{"identity":"v2","map":"RXR-2"},{"identity":"rim","map":".approachSiteCode"}]},{"id":"MedicationRequest.dosageInstruction.route","path":"MedicationRequest.dosageInstruction.route","short":"How drug should enter body","definition":"How drug should enter body.","requirements":"A code specifying the route or physiological path of administration of a therapeutic agent into or onto a patient's body.","min":0,"max":"1","base":{"path":"Dosage.route","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"RouteOfAdministration"}],"strength":"example","description":"A coded concept describing the route or physiological path of administration of a therapeutic agent into or onto the body of a subject.","valueSet":"http://hl7.org/fhir/ValueSet/route-codes"},"mapping":[{"identity":"v2","map":"RXR-1"},{"identity":"rim","map":".routeCode"}]},{"id":"MedicationRequest.dosageInstruction.method","path":"MedicationRequest.dosageInstruction.method","short":"Technique for administering medication","definition":"Technique for administering medication.","comment":"Terminologies used often pre-coordinate this term with the route and or form of administration.","requirements":"A coded value indicating the method by which the medication is introduced into or onto the body. Most commonly used for injections. For examples, Slow Push; Deep IV.","min":0,"max":"1","base":{"path":"Dosage.method","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationAdministrationMethod"}],"strength":"example","description":"A coded concept describing the technique by which the medicine is administered.","valueSet":"http://hl7.org/fhir/ValueSet/administration-method-codes"},"mapping":[{"identity":"v2","map":"RXR-4"},{"identity":"rim","map":".doseQuantity"}]},{"id":"MedicationRequest.dosageInstruction.doseAndRate","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dosageInstruction.doseAndRate","short":"(USCDI) Amount of medication administered","definition":"The amount of medication administered.","min":0,"max":"*","base":{"path":"Dosage.doseAndRate","min":0,"max":"*"},"type":[{"code":"Element"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"mapping":[{"identity":"v2","map":"TQ1-2"}]},{"id":"MedicationRequest.dosageInstruction.doseAndRate.id","path":"MedicationRequest.dosageInstruction.doseAndRate.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"n/a"}]},{"id":"MedicationRequest.dosageInstruction.doseAndRate.extension","path":"MedicationRequest.dosageInstruction.doseAndRate.extension","slicing":{"discriminator":[{"type":"value","path":"url"}],"description":"Extensions are always sliced by (at least) url","rules":"open"},"short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"n/a"}]},{"id":"MedicationRequest.dosageInstruction.doseAndRate.type","path":"MedicationRequest.dosageInstruction.doseAndRate.type","short":"The kind of dose or rate specified","definition":"The kind of dose or rate specified, for example, ordered or calculated.","requirements":"If the type is not populated, assume to be \"ordered\".","min":0,"max":"1","base":{"path":"Dosage.doseAndRate.type","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"DoseAndRateType"}],"strength":"example","description":"The kind of dose or rate specified.","valueSet":"http://hl7.org/fhir/ValueSet/dose-rate-type"},"mapping":[{"identity":"v2","map":"RXO-21; RXE-23"}]},{"id":"MedicationRequest.dosageInstruction.doseAndRate.dose[x]","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dosageInstruction.doseAndRate.dose[x]","short":"(USCDI) Amount of medication per dose","definition":"Amount of medication per dose.","comment":"Note that this specifies the quantity of the specified medication, not the quantity for each active ingredient(s). Each ingredient amount can be communicated in the Medication resource. For example, if one wants to communicate that a tablet was 375 mg, where the dose was one tablet, you can use the Medication resource to document that the tablet was comprised of 375 mg of drug XYZ. Alternatively if the dose was 375 mg, then you may only need to use the Medication resource to indicate this was a tablet. If the example were an IV such as dopamine and you wanted to communicate that 400mg of dopamine was mixed in 500 ml of some IV solution, then this would all be communicated in the Medication resource. If the administration is not intended to be instantaneous (rate is present or timing has a duration), this can be specified to convey the total amount to be administered over the period of time as indicated by the schedule e.g. 500 ml in dose, with timing used to convey that this should be done over 4 hours.","requirements":"The amount of therapeutic or other substance given at one administration event.","min":0,"max":"1","base":{"path":"Dosage.doseAndRate.dose[x]","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"Quantity"},{"code":"Range"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/ucum-units"}],"strength":"preferred","valueSet":"http://hl7.org/fhir/ValueSet/ucum-common"},"mapping":[{"identity":"v2","map":"RXO-2, RXE-3"},{"identity":"rim","map":".doseQuantity"}]},{"id":"MedicationRequest.dosageInstruction.doseAndRate.rate[x]","path":"MedicationRequest.dosageInstruction.doseAndRate.rate[x]","short":"Amount of medication per unit of time","definition":"Amount of medication per unit of time.","comment":"It is possible to supply both a rate and a doseQuantity to provide full details about how the medication is to be administered and supplied. If the rate is intended to change over time, depending on local rules/regulations, each change should be captured as a new version of the MedicationRequest with an updated rate, or captured with a new MedicationRequest with the new rate.\r\rIt is possible to specify a rate over time (for example, 100 ml/hour) using either the rateRatio and rateQuantity. The rateQuantity approach requires systems to have the capability to parse UCUM grammer where ml/hour is included rather than a specific ratio where the time is specified as the denominator. Where a rate such as 500ml over 2 hours is specified, the use of rateRatio may be more semantically correct than specifying using a rateQuantity of 250 mg/hour.","requirements":"Identifies the speed with which the medication was or will be introduced into the patient. Typically the rate for an infusion e.g. 100 ml per 1 hour or 100 ml/hr. May also be expressed as a rate per unit of time e.g. 500 ml per 2 hours. Other examples: 200 mcg/min or 200 mcg/1 minute; 1 liter/8 hours. Sometimes, a rate can imply duration when expressed as total volume / duration (e.g. 500mL/2 hours implies a duration of 2 hours). However, when rate doesn't imply duration (e.g. 250mL/hour), then the timing.repeat.duration is needed to convey the infuse over time period.","min":0,"max":"1","base":{"path":"Dosage.doseAndRate.rate[x]","min":0,"max":"1"},"type":[{"code":"Ratio"},{"code":"Range"},{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"mapping":[{"identity":"v2","map":"RXE22, RXE23, RXE-24"},{"identity":"rim","map":".rateQuantity"}]},{"id":"MedicationRequest.dosageInstruction.maxDosePerPeriod","path":"MedicationRequest.dosageInstruction.maxDosePerPeriod","short":"Upper limit on medication per unit of time","definition":"Upper limit on medication per unit of time.","comment":"This is intended for use as an adjunct to the dosage when there is an upper cap. For example \"2 tablets every 4 hours to a maximum of 8/day\".","requirements":"The maximum total quantity of a therapeutic substance that may be administered to a subject over the period of time. For example, 1000mg in 24 hours.","min":0,"max":"1","base":{"path":"Dosage.maxDosePerPeriod","min":0,"max":"1"},"type":[{"code":"Ratio"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"mapping":[{"identity":"v2","map":"RXO-23, RXE-19"},{"identity":"rim","map":".maxDoseQuantity"}]},{"id":"MedicationRequest.dosageInstruction.maxDosePerAdministration","path":"MedicationRequest.dosageInstruction.maxDosePerAdministration","short":"Upper limit on medication per administration","definition":"Upper limit on medication per administration.","comment":"This is intended for use as an adjunct to the dosage when there is an upper cap. For example, a body surface area related dose with a maximum amount, such as 1.5 mg/m2 (maximum 2 mg) IV over 5 – 10 minutes would have doseQuantity of 1.5 mg/m2 and maxDosePerAdministration of 2 mg.","requirements":"The maximum total quantity of a therapeutic substance that may be administered to a subject per administration.","min":0,"max":"1","base":{"path":"Dosage.maxDosePerAdministration","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"mapping":[{"identity":"rim","map":"not supported"}]},{"id":"MedicationRequest.dosageInstruction.maxDosePerLifetime","path":"MedicationRequest.dosageInstruction.maxDosePerLifetime","short":"Upper limit on medication per lifetime of the patient","definition":"Upper limit on medication per lifetime of the patient.","requirements":"The maximum total quantity of a therapeutic substance that may be administered per lifetime of the subject.","min":0,"max":"1","base":{"path":"Dosage.maxDosePerLifetime","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":true,"mapping":[{"identity":"rim","map":"not supported"}]},{"id":"MedicationRequest.dispenseRequest","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dispenseRequest","short":"(USCDI) Medication supply authorization","definition":"Indicates the specific details for the dispense or medication supply part of a medication request (also known as a Medication Prescription or Medication Order). Note that this information is not always sent with the order. There may be in some settings (e.g. hospitals) institutional or system support for completing the dispense details in the pharmacy department.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest","min":0,"max":"1"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"mapping":[{"identity":"script10.6","map":"Message/Body/NewRx/MedicationPrescribed/ExpirationDate"},{"identity":"rim","map":"component.supplyEvent"}]},{"id":"MedicationRequest.dispenseRequest.id","path":"MedicationRequest.dispenseRequest.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"n/a"}]},{"id":"MedicationRequest.dispenseRequest.extension","path":"MedicationRequest.dispenseRequest.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"n/a"}]},{"id":"MedicationRequest.dispenseRequest.modifierExtension","path":"MedicationRequest.dispenseRequest.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true,"mapping":[{"identity":"rim","map":"N/A"}]},{"id":"MedicationRequest.dispenseRequest.initialFill","path":"MedicationRequest.dispenseRequest.initialFill","short":"First fill details","definition":"Indicates the quantity or duration for the first dispense of the medication.","comment":"If populating this element, either the quantity or the duration must be included.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.initialFill","min":0,"max":"1"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"SubstanceAdministration -> ActRelationship[sequenceNumber = '1'] -> Supply"}]},{"id":"MedicationRequest.dispenseRequest.initialFill.id","path":"MedicationRequest.dispenseRequest.initialFill.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"n/a"}]},{"id":"MedicationRequest.dispenseRequest.initialFill.extension","path":"MedicationRequest.dispenseRequest.initialFill.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"n/a"}]},{"id":"MedicationRequest.dispenseRequest.initialFill.modifierExtension","path":"MedicationRequest.dispenseRequest.initialFill.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true,"mapping":[{"identity":"rim","map":"N/A"}]},{"id":"MedicationRequest.dispenseRequest.initialFill.quantity","path":"MedicationRequest.dispenseRequest.initialFill.quantity","short":"First fill quantity","definition":"The amount or quantity to provide as part of the first dispense.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.initialFill.quantity","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"Supply.quantity[moodCode=RQO]"}]},{"id":"MedicationRequest.dispenseRequest.initialFill.duration","path":"MedicationRequest.dispenseRequest.initialFill.duration","short":"First fill duration","definition":"The length of time that the first dispense is expected to last.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.initialFill.duration","min":0,"max":"1"},"type":[{"code":"Duration"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"Supply.effectivetime[moodCode=RQO]"}]},{"id":"MedicationRequest.dispenseRequest.dispenseInterval","path":"MedicationRequest.dispenseRequest.dispenseInterval","short":"Minimum period of time between dispenses","definition":"The minimum period of time that must occur between dispenses of the medication.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.dispenseInterval","min":0,"max":"1"},"type":[{"code":"Duration"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"Supply.effectivetime[moodCode=RQO]"}]},{"id":"MedicationRequest.dispenseRequest.validityPeriod","path":"MedicationRequest.dispenseRequest.validityPeriod","short":"Time period supply is authorized for","definition":"This indicates the validity period of a prescription (stale dating the Prescription).","comment":"It reflects the prescribers' perspective for the validity of the prescription. Dispenses must not be made against the prescription outside of this period. The lower-bound of the Dispensing Window signifies the earliest date that the prescription can be filled for the first time. If an upper-bound is not specified then the Prescription is open-ended or will default to a stale-date based on regulations.","requirements":"Indicates when the Prescription becomes valid, and when it ceases to be a dispensable Prescription.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.validityPeriod","min":0,"max":"1"},"type":[{"code":"Period"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"script10.6","map":"Message/Body/NewRx/MedicationPrescribed/Refills"},{"identity":"rim","map":"effectiveTime"}]},{"id":"MedicationRequest.dispenseRequest.numberOfRepeatsAllowed","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dispenseRequest.numberOfRepeatsAllowed","short":"(USCDI) Number of refills authorized","definition":"An integer indicating the number of times, in addition to the original dispense, (aka refills or repeats) that the patient can receive the prescribed medication. Usage Notes: This integer does not include the original order dispense. This means that if an order indicates dispense 30 tablets plus \"3 repeats\", then the order can be dispensed a total of 4 times and the patient can receive a total of 120 tablets. A prescriber may explicitly say that zero refills are permitted after the initial dispense.","comment":"If displaying \"number of authorized fills\", add 1 to this number.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.numberOfRepeatsAllowed","min":0,"max":"1"},"type":[{"code":"unsignedInt"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"mapping":[{"identity":"script10.6","map":"Message/Body/NewRx/MedicationPrescribed/Quantity"},{"identity":"v2","map":"RXE-12-Number of Refills"},{"identity":"rim","map":"repeatNumber"}]},{"id":"MedicationRequest.dispenseRequest.quantity","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dispenseRequest.quantity","short":"(USCDI) Amount of medication to supply per dispense","definition":"The amount that is to be dispensed for one fill.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.quantity","min":0,"max":"1"},"type":[{"code":"Quantity","profile":["http://hl7.org/fhir/StructureDefinition/SimpleQuantity"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"mustSupport":true,"isModifier":false,"isSummary":false,"mapping":[{"identity":"script10.6","map":"Message/Body/NewRx/MedicationPrescribed/DaysSupply"},{"identity":"v2","map":"RXD-4-Actual Dispense Amount / RXD-5.1-Actual Dispense Units.code / RXD-5.3-Actual Dispense Units.name of coding system"},{"identity":"rim","map":"quantity"}]},{"id":"MedicationRequest.dispenseRequest.expectedSupplyDuration","path":"MedicationRequest.dispenseRequest.expectedSupplyDuration","short":"Number of days supply per dispense","definition":"Identifies the period time over which the supplied product is expected to be used, or the length of time the dispense is expected to last.","comment":"In some situations, this attribute may be used instead of quantity to identify the amount supplied by how long it is expected to last, rather than the physical quantity issued, e.g. 90 days supply of medication (based on an ordered dosage). When possible, it is always better to specify quantity, as this tends to be more precise. expectedSupplyDuration will always be an estimate that can be influenced by external factors.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.expectedSupplyDuration","min":0,"max":"1"},"type":[{"code":"Duration"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"script10.6","map":"Message/Body/NewRx/MedicationPrescribed/Substitutions"},{"identity":"rim","map":"expectedUseTime"}]},{"id":"MedicationRequest.dispenseRequest.performer","path":"MedicationRequest.dispenseRequest.performer","short":"Intended dispenser","definition":"Indicates the intended dispensing Organization specified by the prescriber.","min":0,"max":"1","base":{"path":"MedicationRequest.dispenseRequest.performer","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Organization"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"w5","map":"FiveWs.who"},{"identity":"rim","map":".outboundRelationship[typeCode=COMP].target[classCode=SPLY, moodCode=RQO] .participation[typeCode=PRF].role[scoper.determinerCode=INSTANCE]"}]},{"id":"MedicationRequest.substitution","path":"MedicationRequest.substitution","short":"Any restrictions on medication substitution","definition":"Indicates whether or not substitution can or should be part of the dispense. In some cases, substitution must happen, in other cases substitution must not happen. This block explains the prescriber's intent. If nothing is specified substitution may be done.","min":0,"max":"1","base":{"path":"MedicationRequest.substitution","min":0,"max":"1"},"type":[{"code":"BackboneElement"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"script10.6","map":"specific values within Message/Body/NewRx/MedicationPrescribed/Substitutions"},{"identity":"rim","map":"subjectOf.substitutionPersmission"}]},{"id":"MedicationRequest.substitution.id","path":"MedicationRequest.substitution.id","representation":["xmlAttr"],"short":"Unique id for inter-element referencing","definition":"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.","min":0,"max":"1","base":{"path":"Element.id","min":0,"max":"1"},"type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type","valueUrl":"string"}],"code":"http://hl7.org/fhirpath/System.String"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"n/a"}]},{"id":"MedicationRequest.substitution.extension","path":"MedicationRequest.substitution.extension","short":"Additional content defined by implementations","definition":"May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","alias":["extensions","user content"],"min":0,"max":"*","base":{"path":"Element.extension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":"n/a"}]},{"id":"MedicationRequest.substitution.modifierExtension","path":"MedicationRequest.substitution.modifierExtension","short":"Extensions that cannot be ignored even if unrecognized","definition":"May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).","comment":"There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.","requirements":"Modifier extensions allow for extensions that *cannot* be safely ignored to be clearly distinguished from the vast majority of extensions which can be safely ignored. This promotes interoperability by eliminating the need for implementers to prohibit the presence of extensions. For further information, see the [definition of modifier extensions](http://hl7.org/fhir/R4/extensibility.html#modifierExtension).","alias":["extensions","user content","modifiers"],"min":0,"max":"*","base":{"path":"BackboneElement.modifierExtension","min":0,"max":"*"},"type":[{"code":"Extension"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"},{"key":"ext-1","severity":"error","human":"Must have either extensions or value[x], not both","expression":"extension.exists() != value.exists()","xpath":"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \"value\")])","source":"http://hl7.org/fhir/StructureDefinition/Extension"}],"isModifier":true,"isModifierReason":"Modifier extensions are expected to modify the meaning or interpretation of the element that contains them","isSummary":true,"mapping":[{"identity":"rim","map":"N/A"}]},{"id":"MedicationRequest.substitution.allowed[x]","path":"MedicationRequest.substitution.allowed[x]","short":"Whether substitution is allowed or not","definition":"True if the prescriber allows a different drug to be dispensed from what was prescribed.","comment":"This element is labeled as a modifier because whether substitution is allow or not, it cannot be ignored.","min":1,"max":"1","base":{"path":"MedicationRequest.substitution.allowed[x]","min":1,"max":"1"},"type":[{"code":"boolean"},{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationRequestSubstitution"}],"strength":"example","description":"Identifies the type of substitution allowed.","valueSet":"http://terminology.hl7.org/ValueSet/v3-ActSubstanceAdminSubstitutionCode"},"mapping":[{"identity":"script10.6","map":"specific values within Message/Body/NewRx/MedicationPrescribed/Substitutions"},{"identity":"v2","map":"RXO-9-Allow Substitutions / RXE-9-Substitution Status"},{"identity":"rim","map":"code"}]},{"id":"MedicationRequest.substitution.reason","path":"MedicationRequest.substitution.reason","short":"Why should (not) substitution be made","definition":"Indicates the reason for the substitution, or why substitution must or must not be performed.","min":0,"max":"1","base":{"path":"MedicationRequest.substitution.reason","min":0,"max":"1"},"type":[{"code":"CodeableConcept"}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName","valueString":"MedicationIntendedSubstitutionReason"}],"strength":"example","description":"A coded concept describing the reason that a different medication should (or should not) be substituted from what was prescribed.","valueSet":"http://terminology.hl7.org/ValueSet/v3-SubstanceAdminSubstitutionReason"},"mapping":[{"identity":"script10.6","map":"not mapped"},{"identity":"v2","map":"RXE-9 Substition status"},{"identity":"rim","map":"reasonCode"}]},{"id":"MedicationRequest.priorPrescription","path":"MedicationRequest.priorPrescription","short":"An order/prescription that is being replaced","definition":"A link to a resource representing an earlier order related order or prescription.","min":0,"max":"1","base":{"path":"MedicationRequest.priorPrescription","min":0,"max":"1"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/MedicationRequest"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"workflow","map":"Request.replaces"},{"identity":"script10.6","map":"not mapped"},{"identity":"rim","map":".outboundRelationship[typeCode=?RPLC or ?SUCC]/target[classCode=SBADM,moodCode=RQO]"}]},{"id":"MedicationRequest.detectedIssue","path":"MedicationRequest.detectedIssue","short":"Clinical Issue with action","definition":"Indicates an actual or potential clinical issue with or between one or more active or proposed clinical actions for a patient; e.g. Drug-drug interaction, duplicate therapy, dosage alert etc.","comment":"This element can include a detected issue that has been identified either by a decision support system or by a clinician and may include information on the steps that were taken to address the issue.","alias":["Contraindication","Drug Utilization Review (DUR)","Alert"],"min":0,"max":"*","base":{"path":"MedicationRequest.detectedIssue","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/DetectedIssue"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"rim","map":".inboundRelationship[typeCode=SUBJ]/source[classCode=ALRT,moodCode=EVN].value"}]},{"id":"MedicationRequest.eventHistory","path":"MedicationRequest.eventHistory","short":"A list of events of interest in the lifecycle","definition":"Links to Provenance records for past versions of this resource or fulfilling request or event resources that identify key state transitions or updates that are likely to be relevant to a user looking at the current version of the resource.","comment":"This might not include provenances for all versions of the request – only those deemed “relevant” or important. This SHALL NOT include the provenance associated with this current version of the resource. (If that provenance is deemed to be a “relevant” change, it will need to be added as part of a later update. Until then, it can be queried directly as the provenance that points to this version using _revinclude All Provenances should have some historical version of this Request as their subject.).","min":0,"max":"*","base":{"path":"MedicationRequest.eventHistory","min":0,"max":"*"},"type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/StructureDefinition/Provenance"]}],"constraint":[{"key":"ele-1","severity":"error","human":"All FHIR elements must have a @value or children","expression":"hasValue() or (children().count() > id.count())","xpath":"@value|f:*|h:div","source":"http://hl7.org/fhir/StructureDefinition/Element"}],"isModifier":false,"isSummary":false,"mapping":[{"identity":"workflow","map":"Request.relevantHistory"},{"identity":"rim","map":".inboundRelationship(typeCode=SUBJ].source[classCode=CACT, moodCode=EVN]"}]}]},"differential":{"element":[{"id":"MedicationRequest","path":"MedicationRequest","definition":"\\-","comment":"\\-","constraint":[{"key":"us-core-21","severity":"error","human":"requester SHALL be present if intent is \"order\"","expression":"(intent='order' or intent='original-order' or intent='reflex-order'or intent='filler-order' or intent='instance-order') implies requester.exists()"}],"mustSupport":false,"mapping":[{"identity":"argonaut-dq-dstu2","map":"MedicationOrder"}]},{"id":"MedicationRequest.status","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.status","short":"(USCDI) active | on-hold | cancelled | completed | entered-in-error | stopped | draft | unknown","mustSupport":true,"binding":{"strength":"required","description":"A code specifying the state of the prescribing event. Describes the lifecycle of the prescription.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-status"},"mapping":[{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.status"}]},{"id":"MedicationRequest.intent","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.intent","short":"(USCDI) proposal | plan | order | original-order | reflex-order | filler-order | instance-order | option","condition":["us-core-21"],"mustSupport":true,"binding":{"strength":"required","description":"The kind of medication order.","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-intent"},"mapping":[{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.status"}]},{"id":"MedicationRequest.category","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.category","slicing":{"discriminator":[{"type":"pattern","path":"$this"}],"rules":"open"},"short":"(USCDI) Type of medication usage","mustSupport":true},{"id":"MedicationRequest.category:us-core","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.category","sliceName":"us-core","short":"(USCDI) Type of medication usage","mustSupport":true,"binding":{"strength":"required","description":"The type of medication order. Note that other codes are permitted, see [Required Bindings When Slicing by Value Sets](http://hl7.org/fhir/us/core/general-requirements.html#required-bindings-when-slicing-by-valuesets)","valueSet":"http://hl7.org/fhir/ValueSet/medicationrequest-category"}},{"id":"MedicationRequest.reported[x]","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.reported[x]","short":"(USCDI) Reported rather than primary record","type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"boolean"},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner","http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization","http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient","http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole","http://hl7.org/fhir/us/core/StructureDefinition/us-core-relatedperson"],"_targetProfile":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]}]}],"mustSupport":true,"mapping":[{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.status"}]},{"id":"MedicationRequest.medication[x]","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.medication[x]","short":"(USCDI) Medication to be taken","type":[{"code":"CodeableConcept"},{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-medication"]}],"mustSupport":true,"binding":{"strength":"extensible","valueSet":"http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1010.4"},"mapping":[{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.medication[x]"}]},{"id":"MedicationRequest.subject","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.subject","short":"(USCDI) Who or group medication request is for","type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]}],"mustSupport":true,"mapping":[{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.patient"}]},{"id":"MedicationRequest.encounter","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.encounter","short":"(USCDI) Encounter created as part of encounter/admission/stay","type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-encounter"]}],"mustSupport":true,"mapping":[{"identity":"argonaut-dq-dstu2","map":"NA"}]},{"id":"MedicationRequest.authoredOn","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.authoredOn","short":"(USCDI) When request was initially authored","mustSupport":true,"mapping":[{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.dateWritten"}]},{"id":"MedicationRequest.requester","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.requester","short":"(USCDI) Who/What requested the Request","type":[{"code":"Reference","targetProfile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner","http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient","http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization","http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitionerrole","http://hl7.org/fhir/us/core/StructureDefinition/us-core-relatedperson","http://hl7.org/fhir/StructureDefinition/Device"],"_targetProfile":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]},{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":false}]}]}],"condition":["us-core-21"],"mustSupport":true,"mapping":[{"identity":"argonaut-dq-dstu2","map":"MedicationOrder.prescriber"}]},{"id":"MedicationRequest.reasonCode","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.reasonCode","short":"(USCDI) Reason or indication for ordering or not ordering the medication","binding":{"strength":"extensible","valueSet":"http://hl7.org/fhir/us/core/ValueSet/us-core-condition-code"}},{"id":"MedicationRequest.reasonReference","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.reasonReference","short":"(USCDI) US Core Condition or Observation that supports the prescription"},{"id":"MedicationRequest.dosageInstruction","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dosageInstruction","short":"(USCDI) How the medication should be taken","mustSupport":true},{"id":"MedicationRequest.dosageInstruction.text","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dosageInstruction.text","short":"(USCDI) Free text dosage instructions e.g. SIG","mustSupport":true},{"id":"MedicationRequest.dosageInstruction.timing","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dosageInstruction.timing","short":"(USCDI) When medication should be administered","mustSupport":true},{"id":"MedicationRequest.dosageInstruction.doseAndRate","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dosageInstruction.doseAndRate","short":"(USCDI) Amount of medication administered","mustSupport":true},{"id":"MedicationRequest.dosageInstruction.doseAndRate.dose[x]","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dosageInstruction.doseAndRate.dose[x]","short":"(USCDI) Amount of medication per dose","type":[{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support","valueBoolean":true}],"code":"Quantity"},{"code":"Range"}],"mustSupport":true,"binding":{"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet","valueCanonical":"http://hl7.org/fhir/ValueSet/ucum-units"}],"strength":"preferred","valueSet":"http://hl7.org/fhir/ValueSet/ucum-common"}},{"id":"MedicationRequest.dispenseRequest","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dispenseRequest","short":"(USCDI) Medication supply authorization","mustSupport":true},{"id":"MedicationRequest.dispenseRequest.numberOfRepeatsAllowed","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dispenseRequest.numberOfRepeatsAllowed","short":"(USCDI) Number of refills authorized","mustSupport":true},{"id":"MedicationRequest.dispenseRequest.quantity","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement","valueBoolean":true}],"path":"MedicationRequest.dispenseRequest.quantity","short":"(USCDI) Amount of medication to supply per dispense","mustSupport":true}]}} diff --git a/packages/react/src/CheckboxFormSection/CheckboxFormSection.tsx b/packages/react/src/CheckboxFormSection/CheckboxFormSection.tsx index ecc2131419..6d985e7eee 100644 --- a/packages/react/src/CheckboxFormSection/CheckboxFormSection.tsx +++ b/packages/react/src/CheckboxFormSection/CheckboxFormSection.tsx @@ -1,6 +1,6 @@ import { Group, Input } from '@mantine/core'; import { ReactNode, useContext } from 'react'; -import { BackboneElementContext } from '../BackboneElementInput/BackboneElementInput.utils'; +import { ElementsContext } from '../ElementsInput/ElementsInput.utils'; export interface CheckboxFormSectionProps { readonly htmlFor?: string; @@ -13,7 +13,7 @@ export interface CheckboxFormSectionProps { } export function CheckboxFormSection(props: CheckboxFormSectionProps): JSX.Element { - const { debugMode } = useContext(BackboneElementContext); + const { debugMode } = useContext(ElementsContext); let label: React.ReactNode; if (debugMode && props.fhirPath) { diff --git a/packages/react/src/CodeInput/CodeInput.tsx b/packages/react/src/CodeInput/CodeInput.tsx index ea129af96f..cf89f4e4f3 100644 --- a/packages/react/src/CodeInput/CodeInput.tsx +++ b/packages/react/src/CodeInput/CodeInput.tsx @@ -8,7 +8,7 @@ export interface CodeInputProps extends Omit(defaultValue); function handleChange(newValues: ValueSetExpansionContains[]): void { @@ -20,7 +20,14 @@ export function CodeInput(props: CodeInputProps): JSX.Element { } } - return ; + return ( + + ); } function codeToValueSetElement(code: string | undefined): ValueSetExpansionContains | undefined { diff --git a/packages/react/src/CodeableConceptInput/CodeableConceptInput.stories.tsx b/packages/react/src/CodeableConceptInput/CodeableConceptInput.stories.tsx index 76a037516c..952b887bb8 100644 --- a/packages/react/src/CodeableConceptInput/CodeableConceptInput.stories.tsx +++ b/packages/react/src/CodeableConceptInput/CodeableConceptInput.stories.tsx @@ -11,7 +11,13 @@ const valueSet = 'http://hl7.org/fhir/ValueSet/marital-status'; export const Basic = (): JSX.Element => ( - + ); @@ -22,6 +28,8 @@ export const DefaultValue = (): JSX.Element => ( binding={valueSet} defaultValue={{ coding: [{ code: 'M', display: 'Married' }] }} onChange={console.log} + path={'Patient.maritalStatus'} + outcome={undefined} /> ); diff --git a/packages/react/src/CodeableConceptInput/CodeableConceptInput.test.tsx b/packages/react/src/CodeableConceptInput/CodeableConceptInput.test.tsx index 4ab4074813..8341c4a36e 100644 --- a/packages/react/src/CodeableConceptInput/CodeableConceptInput.test.tsx +++ b/packages/react/src/CodeableConceptInput/CodeableConceptInput.test.tsx @@ -1,9 +1,8 @@ import { CodeableConcept } from '@medplum/fhirtypes'; import { MockClient } from '@medplum/mock'; import { MedplumProvider } from '@medplum/react-hooks'; -import { ReactNode } from 'react'; import { act, fireEvent, render, screen, waitFor } from '../test-utils/render'; -import { CodeableConceptInput } from './CodeableConceptInput'; +import { CodeableConceptInput, CodeableConceptInputProps } from './CodeableConceptInput'; const medplum = new MockClient(); const binding = 'https://example.com/test'; @@ -20,27 +19,39 @@ describe('CodeableConceptInput', () => { jest.useRealTimers(); }); - async function setup(child: ReactNode): Promise { + async function setup(props?: Partial): Promise { + const finalProps: CodeableConceptInputProps = { + binding, + name: 'test', + path: 'Resource.test', + outcome: undefined, + onChange: jest.fn(), + ...props, + }; await act(async () => { - render({child}); + render( + + + + ); }); } test('Renders', async () => { - await setup(); + await setup(); expect(screen.getByRole('searchbox')).toBeInTheDocument(); }); test('Renders CodeableConcept default value', async () => { - await setup(); + await setup({ defaultValue: { coding: [{ code: 'abc' }] } }); expect(screen.getByRole('searchbox')).toBeInTheDocument(); expect(screen.getByText('abc')).toBeDefined(); }); test('Searches for results', async () => { - await setup(); + await setup(); const input = screen.getByRole('searchbox') as HTMLInputElement; @@ -70,7 +81,7 @@ describe('CodeableConceptInput', () => { test('Create unstructured value', async () => { let currValue: CodeableConcept | undefined; - await setup( (currValue = newValue)} />); + await setup({ onChange: (newValue) => (currValue = newValue) }); const input = screen.getByRole('searchbox') as HTMLInputElement; @@ -111,9 +122,7 @@ describe('CodeableConceptInput', () => { ], }; - await setup( - - ); + await setup({ defaultValue }); const input = screen.getByRole('searchbox') as HTMLInputElement; diff --git a/packages/react/src/CodeableConceptInput/CodeableConceptInput.tsx b/packages/react/src/CodeableConceptInput/CodeableConceptInput.tsx index c106624768..a15c03dbad 100644 --- a/packages/react/src/CodeableConceptInput/CodeableConceptInput.tsx +++ b/packages/react/src/CodeableConceptInput/CodeableConceptInput.tsx @@ -1,14 +1,16 @@ import { CodeableConcept, ValueSetExpansionContains } from '@medplum/fhirtypes'; import { useState } from 'react'; +import { ComplexTypeInputProps } from '../ResourcePropertyInput/ResourcePropertyInput.utils'; import { ValueSetAutocomplete, ValueSetAutocompleteProps } from '../ValueSetAutocomplete/ValueSetAutocomplete'; -export interface CodeableConceptInputProps extends Omit { - readonly defaultValue?: CodeableConcept; - readonly onChange?: (value: CodeableConcept | undefined) => void; +export interface CodeableConceptInputProps + extends Omit, + ComplexTypeInputProps { + readonly onChange: ((value: CodeableConcept | undefined) => void) | undefined; } export function CodeableConceptInput(props: CodeableConceptInputProps): JSX.Element { - const { defaultValue, onChange, ...rest } = props; + const { defaultValue, onChange, withHelpText, ...rest } = props; const [value, setValue] = useState(defaultValue); function handleChange(newValues: ValueSetExpansionContains[]): void { @@ -23,6 +25,7 @@ export function CodeableConceptInput(props: CodeableConceptInputProps): JSX.Elem ); diff --git a/packages/react/src/CodingInput/CodingInput.test.tsx b/packages/react/src/CodingInput/CodingInput.test.tsx index b3f79332da..18a486aab7 100644 --- a/packages/react/src/CodingInput/CodingInput.test.tsx +++ b/packages/react/src/CodingInput/CodingInput.test.tsx @@ -34,7 +34,7 @@ describe('CodingInput', () => { test('Renders Coding default value', async () => { await setup(); - expect(screen.getByRole('searchbox')).toBeInTheDocument(); + expect(screen.queryByRole('searchbox')).not.toBeInTheDocument(); expect(screen.getByText('abc')).toBeDefined(); }); diff --git a/packages/react/src/CodingInput/CodingInput.tsx b/packages/react/src/CodingInput/CodingInput.tsx index 27cd0c5ffa..f230a452b4 100644 --- a/packages/react/src/CodingInput/CodingInput.tsx +++ b/packages/react/src/CodingInput/CodingInput.tsx @@ -8,7 +8,7 @@ export interface CodingInputProps extends Omit(defaultValue); function handleChange(newValues: ValueSetExpansionContains[]): void { @@ -25,6 +25,7 @@ export function CodingInput(props: CodingInputProps): JSX.Element { defaultValue={value && codingToValueSetElement(value)} maxValues={1} onChange={handleChange} + withHelpText={withHelpText ?? true} {...rest} /> ); diff --git a/packages/react/src/ContactPointInput/ContactPointInput.tsx b/packages/react/src/ContactPointInput/ContactPointInput.tsx index 4bd92780c0..8b5b10a89a 100644 --- a/packages/react/src/ContactPointInput/ContactPointInput.tsx +++ b/packages/react/src/ContactPointInput/ContactPointInput.tsx @@ -2,7 +2,7 @@ import { Group, NativeSelect, TextInput } from '@mantine/core'; import { ContactPoint } from '@medplum/fhirtypes'; import { useContext, useMemo, useRef, useState } from 'react'; import { ComplexTypeInputProps } from '../ResourcePropertyInput/ResourcePropertyInput.utils'; -import { BackboneElementContext } from '../BackboneElementInput/BackboneElementInput.utils'; +import { ElementsContext } from '../ElementsInput/ElementsInput.utils'; import { getErrorsForInput } from '../utils/outcomes'; export type ContactPointInputProps = ComplexTypeInputProps & { @@ -11,15 +11,15 @@ export type ContactPointInputProps = ComplexTypeInputProps & { export function ContactPointInput(props: ContactPointInputProps): JSX.Element { const { path, outcome } = props; - const { getModifiedNestedElement } = useContext(BackboneElementContext); + const { elementsByPath } = useContext(ElementsContext); const [contactPoint, setContactPoint] = useState(props.defaultValue); const ref = useRef(); ref.current = contactPoint; const [systemElement, useElement, valueElement] = useMemo( - () => ['system', 'use', 'value'].map((field) => getModifiedNestedElement(path + '.' + field)), - [getModifiedNestedElement, path] + () => ['system', 'use', 'value'].map((field) => elementsByPath[path + '.' + field]), + [elementsByPath, path] ); function setContactPointWrapper(newValue: ContactPoint | undefined): void { diff --git a/packages/react/src/ElementsInput/ElementsInput.tsx b/packages/react/src/ElementsInput/ElementsInput.tsx index 8e21ef61d0..fd8f2fad95 100644 --- a/packages/react/src/ElementsInput/ElementsInput.tsx +++ b/packages/react/src/ElementsInput/ElementsInput.tsx @@ -1,20 +1,22 @@ import { Stack } from '@mantine/core'; import { InternalSchemaElement, TypedValue, getPathDisplayName, isPopulated } from '@medplum/core'; import { OperationOutcome } from '@medplum/fhirtypes'; -import { useMemo, useState } from 'react'; +import { useContext, useMemo, useState } from 'react'; import { CheckboxFormSection } from '../CheckboxFormSection/CheckboxFormSection'; import { FormSection } from '../FormSection/FormSection'; import { setPropertyValue } from '../ResourceForm/ResourceForm.utils'; import { getValueAndTypeFromElement } from '../ResourcePropertyDisplay/ResourcePropertyDisplay.utils'; import { ResourcePropertyInput } from '../ResourcePropertyInput/ResourcePropertyInput'; import { DEFAULT_IGNORED_NON_NESTED_PROPERTIES, DEFAULT_IGNORED_PROPERTIES } from '../constants'; +import { ElementsContext } from './ElementsInput.utils'; const EXTENSION_KEYS = new Set(['extension', 'modifierExtension']); const IGNORED_PROPERTIES = new Set(['id', ...DEFAULT_IGNORED_PROPERTIES].filter((prop) => !EXTENSION_KEYS.has(prop))); export interface ElementsInputProps { - readonly type: string | undefined; - readonly elements: { [key: string]: InternalSchemaElement }; + readonly type: string; + /** The path identifies the element and is expressed as a "."-separated list of ancestor elements, beginning with the name of the resource or extension. */ + readonly path: string; readonly defaultValue: any; readonly outcome: OperationOutcome | undefined; readonly onChange: ((value: any) => void) | undefined; @@ -22,70 +24,32 @@ export interface ElementsInputProps { } export function ElementsInput(props: ElementsInputProps): JSX.Element { - const { elements } = props; const [value, setValue] = useState(props.defaultValue ?? {}); - - const fixedProperties = useMemo(() => { - const result: { [key: string]: InternalSchemaElement & { fixed: TypedValue } } = Object.create(null); - for (const [key, property] of Object.entries(elements)) { - if (property.fixed) { - result[key] = property as any; - } - } - return result; - }, [elements]); + const elementsContext = useContext(ElementsContext); + const elementsToRender = useMemo(() => { + return getElementsToRender(elementsContext.elements); + }, [elementsContext.elements]); function setValueWrapper(newValue: any): void { - for (const [key, prop] of Object.entries(fixedProperties)) { - setPropertyValue(newValue, key, key, prop, prop.fixed.value); - } setValue(newValue); if (props.onChange) { props.onChange(newValue); } } + const typedValue: TypedValue = { type: props.type, value }; + return ( - {Object.entries(elements).map(([key, element]) => { - if (!element.type) { - return null; - } - - if (element.max === 0) { - return null; - } - - // mostly for Extension.url - if (key === 'url' && element.fixed) { - return null; - } - - if (EXTENSION_KEYS.has(key) && !isPopulated(element.slicing?.slices)) { - // an extension property without slices has no nested extensions - return null; - } else if (IGNORED_PROPERTIES.has(key)) { - return null; - } else if (DEFAULT_IGNORED_NON_NESTED_PROPERTIES.includes(key) && element.path.split('.').length === 2) { - return null; - } - - // Profiles can include nested elements in addition to their containing element, e.g.: - // identifier, identifier.use, identifier.system - // Skip nested elements, e.g. identifier.use, since they are handled by the containing element - if (key.includes('.')) { - return null; - } - - const [propertyValue, propertyType] = getValueAndTypeFromElement(value, key, element); - + {elementsToRender.map(([key, element]) => { + const [propertyValue, propertyType] = getValueAndTypeFromElement(typedValue, key, element); const required = element.min !== undefined && element.min > 0; - const resourcePropertyInput = ( { @@ -133,3 +97,40 @@ export function ElementsInput(props: ElementsInputProps): JSX.Element { ); } + +function getElementsToRender(inputElements: Record): [string, InternalSchemaElement][] { + const result = Object.entries(inputElements).filter(([key, element]) => { + if (!isPopulated(element.type)) { + return false; + } + + if (element.max === 0) { + return false; + } + + // toLowerCase to handle Extension.url as well as Extension.extension.url, etc. + if (element.path.toLowerCase().endsWith('extension.url') && element.fixed) { + return false; + } + + if (EXTENSION_KEYS.has(key) && !isPopulated(element.slicing?.slices)) { + // an extension property without slices has no nested extensions + return false; + } else if (IGNORED_PROPERTIES.has(key)) { + return false; + } else if (DEFAULT_IGNORED_NON_NESTED_PROPERTIES.includes(key) && element.path.split('.').length === 2) { + return false; + } + + // Profiles can include nested elements in addition to their containing element, e.g.: + // identifier, identifier.use, identifier.system + // Skip nested elements, e.g. identifier.use, since they are handled by the containing element + if (key.includes('.')) { + return false; + } + + return true; + }); + + return result; +} diff --git a/packages/react/src/ElementsInput/ElementsInput.utils.ts b/packages/react/src/ElementsInput/ElementsInput.utils.ts new file mode 100644 index 0000000000..bebb89d420 --- /dev/null +++ b/packages/react/src/ElementsInput/ElementsInput.utils.ts @@ -0,0 +1,11 @@ +import { ElementsContextType } from '@medplum/core'; +import React from 'react'; + +export const ElementsContext = React.createContext({ + path: '', + profileUrl: undefined, + elements: Object.create(null), + elementsByPath: Object.create(null), + debugMode: false, +}); +ElementsContext.displayName = 'ElementsContext'; diff --git a/packages/react/src/ExtensionInput/ExtensionInput.stories.tsx b/packages/react/src/ExtensionInput/ExtensionInput.stories.tsx index ff912d45b3..08c0efbe7e 100644 --- a/packages/react/src/ExtensionInput/ExtensionInput.stories.tsx +++ b/packages/react/src/ExtensionInput/ExtensionInput.stories.tsx @@ -16,7 +16,7 @@ export const Basic = (): JSX.Element => ( defaultValue={ { url: 'http://hl7.org/fhir/StructureDefinition/patient-interpreterRequired', valueBoolean: true } as Extension } - path={''} + path="Patient.interpreterRequired" onChange={undefined} outcome={undefined} propertyType={{ code: 'Extension' }} diff --git a/packages/react/src/ExtensionInput/ExtensionInput.test.tsx b/packages/react/src/ExtensionInput/ExtensionInput.test.tsx index 610b37d8b3..0666ed51ef 100644 --- a/packages/react/src/ExtensionInput/ExtensionInput.test.tsx +++ b/packages/react/src/ExtensionInput/ExtensionInput.test.tsx @@ -28,16 +28,23 @@ describe('ExtensionInput', () => { test('Renders', async () => { await setup({ ...defaultProps, - defaultValue: { url: 'https://example.com' }, + defaultValue: { url: 'https://example.com', valueBoolean: true }, }); - expect(screen.getByTestId('extension-json-input')).toBeDefined(); + expect(screen.getByTestId('url')).toBeInTheDocument(); + expect(screen.getByTestId('url')).toHaveValue('https://example.com'); + expect(screen.getByTestId('value[x]-selector')).toBeInTheDocument(); + expect(screen.getByTestId('value[x]')).toBeInTheDocument(); + expect(screen.getByTestId('value[x]')).toBeChecked(); }); test('Renders undefined value', async () => { await setup({ ...defaultProps, }); - expect(screen.getByTestId('extension-json-input')).toBeDefined(); + expect(screen.getByTestId('url')).toBeInTheDocument(); + expect(screen.getByTestId('url')).toHaveValue(''); + expect(screen.getByTestId('value[x]')).toBeInTheDocument(); + expect(screen.getByTestId('value[x]')).toHaveValue(''); }); test('Set value', async () => { @@ -49,8 +56,8 @@ describe('ExtensionInput', () => { }); await act(async () => { - fireEvent.change(screen.getByTestId('extension-json-input'), { - target: { value: '{"url":"https://foo.com"}' }, + fireEvent.change(screen.getByTestId('url'), { + target: { value: 'https://foo.com' }, }); }); diff --git a/packages/react/src/ExtensionInput/ExtensionInput.tsx b/packages/react/src/ExtensionInput/ExtensionInput.tsx index bd6d3ca6ac..0ca3ad9217 100644 --- a/packages/react/src/ExtensionInput/ExtensionInput.tsx +++ b/packages/react/src/ExtensionInput/ExtensionInput.tsx @@ -1,5 +1,4 @@ -import { JsonInput } from '@mantine/core'; -import { InternalTypeSchema, stringify, tryGetProfile, isProfileLoaded } from '@medplum/core'; +import { InternalTypeSchema, tryGetProfile, isProfileLoaded, isPopulated } from '@medplum/core'; import { ElementDefinitionType, Extension } from '@medplum/fhirtypes'; import { useMedplum } from '@medplum/react-hooks'; import { useEffect, useMemo, useState } from 'react'; @@ -14,15 +13,15 @@ export function ExtensionInput(props: ExtensionInputProps): JSX.Element | null { const { propertyType } = props; const medplum = useMedplum(); - const [loadingProfile, setLoadingProfile] = useState(false); const [typeSchema, setTypeSchema] = useState(); const profileUrl: string | undefined = useMemo(() => { - if (!propertyType.profile || propertyType.profile.length === 0) { + if (!isPopulated(propertyType.profile)) { return undefined; } return propertyType.profile[0] satisfies string; }, [propertyType]); + const [loadingProfile, setLoadingProfile] = useState(profileUrl !== undefined); useEffect(() => { if (profileUrl) { @@ -41,25 +40,10 @@ export function ExtensionInput(props: ExtensionInputProps): JSX.Element | null { } }, [medplum, profileUrl]); - function onChange(newValue: any): void { - if (props.onChange) { - console.log('Extension', newValue); - props.onChange(newValue); - } - } - - if (!profileUrl) { - return ; - } - if (profileUrl && (loadingProfile || !isProfileLoaded(profileUrl))) { return
Loading...
; } - if (!typeSchema) { - return
StructureDefinition for {profileUrl} not found
; - } - /* From the spec: An extension SHALL have either a value (i.e. a value[x] element) or sub-extensions, but not both. @@ -75,26 +59,10 @@ export function ExtensionInput(props: ExtensionInputProps): JSX.Element | null { return ( - ); -} - -function ExtensionJsonInput(props: ExtensionInputProps): JSX.Element { - return ( - { - if (props.onChange) { - props.onChange(JSON.parse(newValue)); - } - }} + onChange={props.onChange} /> ); } diff --git a/packages/react/src/FormSection/FormSection.tsx b/packages/react/src/FormSection/FormSection.tsx index 723d91d8ad..c209d1cfe5 100644 --- a/packages/react/src/FormSection/FormSection.tsx +++ b/packages/react/src/FormSection/FormSection.tsx @@ -2,7 +2,7 @@ import { Input } from '@mantine/core'; import { OperationOutcome } from '@medplum/fhirtypes'; import { ReactNode, useContext } from 'react'; import { getErrorsForInput } from '../utils/outcomes'; -import { BackboneElementContext } from '../BackboneElementInput/BackboneElementInput.utils'; +import { ElementsContext } from '../ElementsInput/ElementsInput.utils'; export interface FormSectionProps { readonly title?: string; @@ -16,7 +16,7 @@ export interface FormSectionProps { } export function FormSection(props: FormSectionProps): JSX.Element { - const { debugMode } = useContext(BackboneElementContext); + const { debugMode } = useContext(ElementsContext); let label: React.ReactNode; if (debugMode && props.fhirPath) { diff --git a/packages/react/src/IdentifierInput/IdentifierInput.tsx b/packages/react/src/IdentifierInput/IdentifierInput.tsx index dbb3f82c24..a0a6fb22f9 100644 --- a/packages/react/src/IdentifierInput/IdentifierInput.tsx +++ b/packages/react/src/IdentifierInput/IdentifierInput.tsx @@ -2,7 +2,7 @@ import { Group, TextInput } from '@mantine/core'; import { Identifier } from '@medplum/fhirtypes'; import { useContext, useMemo, useState } from 'react'; import { getErrorsForInput } from '../utils/outcomes'; -import { BackboneElementContext } from '../BackboneElementInput/BackboneElementInput.utils'; +import { ElementsContext } from '../ElementsInput/ElementsInput.utils'; import { ComplexTypeInputProps } from '../ResourcePropertyInput/ResourcePropertyInput.utils'; export type IdentifierInputProps = ComplexTypeInputProps; @@ -10,11 +10,11 @@ export type IdentifierInputProps = ComplexTypeInputProps; export function IdentifierInput(props: IdentifierInputProps): JSX.Element { const { path, outcome } = props; const [value, setValue] = useState(props.defaultValue); - const { getModifiedNestedElement } = useContext(BackboneElementContext); + const { elementsByPath } = useContext(ElementsContext); const [systemElement, valueElement] = useMemo( - () => ['system', 'value'].map((field) => getModifiedNestedElement(path + '.' + field)), - [getModifiedNestedElement, path] + () => ['system', 'value'].map((field) => elementsByPath[path + '.' + field]), + [elementsByPath, path] ); function setValueWrapper(newValue: Identifier): void { diff --git a/packages/react/src/NotificationIcon/NotificationIcon.test.tsx b/packages/react/src/NotificationIcon/NotificationIcon.test.tsx new file mode 100644 index 0000000000..f9f8b34b23 --- /dev/null +++ b/packages/react/src/NotificationIcon/NotificationIcon.test.tsx @@ -0,0 +1,59 @@ +import { Communication } from '@medplum/fhirtypes'; +import { MockClient } from '@medplum/mock'; +import { MedplumProvider } from '@medplum/react-hooks'; +import { IconMail } from '@tabler/icons-react'; +import 'jest-websocket-mock'; +import { act, render, screen, waitFor } from '../test-utils/render'; +import { NotificationIcon } from './NotificationIcon'; + +describe('NotificationIcon()', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + test('Criteria changed', async () => { + const medplum = new MockClient(); + + render( + + } + onClick={() => console.log('foo')} + /> + + ); + + // On first render, the count should be zero, so no indicator should be shown + expect(screen.queryByText('0')).not.toBeInTheDocument(); + expect(screen.queryByText('1')).not.toBeInTheDocument(); + + // Create a communication + const communication = await medplum.createResource({ + resourceType: 'Communication', + status: 'in-progress', + recipient: [{ reference: 'Practitioner/123' }], + }); + + // Emulate the server sending a message + await act(async () => { + medplum.getSubscriptionManager().emitEventForCriteria<'message'>('Communication?recipient=Practitioner/123', { + type: 'message', + payload: { resourceType: 'Bundle', id: communication.id, type: 'history' }, + }); + }); + + // Wait for the indicator to change + await waitFor(() => screen.getByText('1')); + + // After emitting a message, the count should be 1, and the indicator should be shown + expect(screen.getByText('1')).toBeInTheDocument(); + }); +}); diff --git a/packages/react/src/NotificationIcon/NotificationIcon.tsx b/packages/react/src/NotificationIcon/NotificationIcon.tsx new file mode 100644 index 0000000000..877fee35f4 --- /dev/null +++ b/packages/react/src/NotificationIcon/NotificationIcon.tsx @@ -0,0 +1,57 @@ +import { ActionIcon, Indicator, Tooltip } from '@mantine/core'; +import { ResourceType } from '@medplum/fhirtypes'; +import { useMedplum, useSubscription } from '@medplum/react-hooks'; +import { useCallback, useEffect, useState } from 'react'; + +export interface NotificationIconProps { + readonly iconComponent: JSX.Element; + readonly label: string; + readonly resourceType: ResourceType; + readonly countCriteria: string; + readonly subscriptionCriteria: string; + readonly onClick: () => void; +} + +export function NotificationIcon(props: NotificationIconProps): JSX.Element { + const medplum = useMedplum(); + const { label, resourceType, countCriteria, subscriptionCriteria, onClick } = props; + const [unreadCount, setUnreadCount] = useState(0); + + const updateCount = useCallback( + (cache: 'default' | 'reload') => { + medplum + .search(resourceType, countCriteria, { cache }) + .then((result) => setUnreadCount(result.total as number)) + .catch(console.error); + }, + [medplum, resourceType, countCriteria] + ); + + // Initial count + useEffect(() => { + // Cache=default to use the default cache policy, and accept most recent data + updateCount('default'); + }, [updateCount]); + + // Subscribe to the criteria + useSubscription(subscriptionCriteria, () => { + // Cache=reload to force a reload + updateCount('reload'); + }); + + const icon = ( + + + {props.iconComponent} + + + ); + + return unreadCount > 0 ? ( + + {icon} + + ) : ( + icon + ); +} diff --git a/packages/react/src/PatientSummary/Allergies.tsx b/packages/react/src/PatientSummary/Allergies.tsx index 0e409f1284..78384e15b1 100644 --- a/packages/react/src/PatientSummary/Allergies.tsx +++ b/packages/react/src/PatientSummary/Allergies.tsx @@ -74,9 +74,11 @@ export function Allergies(props: AllergiesProps): JSX.Element { setCode(allergy)} + outcome={undefined} /> diff --git a/packages/react/src/PatientSummary/Medications.tsx b/packages/react/src/PatientSummary/Medications.tsx index 1b2c653563..6a161536da 100644 --- a/packages/react/src/PatientSummary/Medications.tsx +++ b/packages/react/src/PatientSummary/Medications.tsx @@ -80,9 +80,11 @@ export function Medications(props: MedicationsProps): JSX.Element { setCode(request)} + outcome={undefined} /> diff --git a/packages/react/src/PlanDefinitionBuilder/PlanDefinitionBuilder.tsx b/packages/react/src/PlanDefinitionBuilder/PlanDefinitionBuilder.tsx index 168eb337b7..152316f3b5 100644 --- a/packages/react/src/PlanDefinitionBuilder/PlanDefinitionBuilder.tsx +++ b/packages/react/src/PlanDefinitionBuilder/PlanDefinitionBuilder.tsx @@ -397,6 +397,7 @@ function ActionTimingInput(props: ActionTimingInputProps): JSX.Element { { diff --git a/packages/react/src/QuestionnaireBuilder/QuestionnaireBuilder.tsx b/packages/react/src/QuestionnaireBuilder/QuestionnaireBuilder.tsx index 329dc79d8d..6fd195590a 100644 --- a/packages/react/src/QuestionnaireBuilder/QuestionnaireBuilder.tsx +++ b/packages/react/src/QuestionnaireBuilder/QuestionnaireBuilder.tsx @@ -484,6 +484,7 @@ function AnswerOptionsInput(props: AnswerOptionsInputProps): JSX.Element { = { +const defaultProps: Pick = { name: 'myProp', + path: 'Fake.myProp', property, outcome: undefined, }; @@ -194,31 +195,20 @@ describe('ResourceArrayInput', () => { onChange, }); - const shouldExist = [ + ['slice-chocolateVariety-add', 'slice-vanillaVariety-add', 'nonsliced-add'].forEach((testId) => { + expect(screen.getByTestId(testId)).toBeInTheDocument(); + }); + + [ 'slice-chocolateVariety-elements-0', 'slice-chocolateVariety-remove-0', - 'slice-chocolateVariety-add', 'slice-vanillaVariety-elements-0', 'slice-vanillaVariety-remove-0', - 'nonsliced-add', - ]; - shouldExist.forEach((testId) => { - expect(screen.getByTestId(testId)).toBeInTheDocument(); - }); - - const shouldNotExist = ['slice-vanillaVariety-add', 'nonsliced-remove-0']; - shouldNotExist.forEach((testId) => { + 'nonsliced-remove-0', + ].forEach((testId) => { expect(screen.queryByTestId(testId)).toBeNull(); }); - await act(async () => { - fireEvent.click(screen.getByTestId('slice-chocolateVariety-remove-0')); - }); - - expect(screen.queryByTestId('slice-chocolateVariety-add')).toBeInTheDocument(); - expect(screen.queryByTestId('slice-chocolateVariety-remove-0')).toBeNull(); - expect(screen.queryByTestId('slice-chocolateVariety-elements-0')).toBeNull(); - await act(async () => { fireEvent.click(screen.getByTestId('slice-chocolateVariety-add')); }); @@ -283,9 +273,8 @@ describe('ResourceArrayInput', () => { { url: 'greenVariety', valueString: 'Pistachio' }, ]; - expect(onChange.mock.calls.length).toBe(1); - expect(onChange.mock.calls[0][0].length).toBe(expectedValue.length); - expect(onChange).toHaveBeenCalledWith(expect.arrayContaining(expectedValue)); + expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenLastCalledWith(expectedValue); }); test('Hiding non-sliced values', async () => { diff --git a/packages/react/src/ResourceArrayInput/ResourceArrayInput.tsx b/packages/react/src/ResourceArrayInput/ResourceArrayInput.tsx index 56a4302ab7..a66c4ab7c4 100644 --- a/packages/react/src/ResourceArrayInput/ResourceArrayInput.tsx +++ b/packages/react/src/ResourceArrayInput/ResourceArrayInput.tsx @@ -1,29 +1,21 @@ -import { ActionIcon, Button, Group, Stack } from '@mantine/core'; -import { - InternalSchemaElement, - getPathDisplayName, - getPropertyDisplayName, - isEmpty, - tryGetProfile, -} from '@medplum/core'; +import { Group, Stack } from '@mantine/core'; +import { InternalSchemaElement, SliceDefinitionWithTypes, getPathDisplayName } from '@medplum/core'; import { OperationOutcome } from '@medplum/fhirtypes'; import { useMedplum } from '@medplum/react-hooks'; -import { IconCircleMinus, IconCirclePlus } from '@tabler/icons-react'; -import { MouseEvent, useEffect, useState } from 'react'; -import { ElementsInput } from '../ElementsInput/ElementsInput'; -import { FormSection } from '../FormSection/FormSection'; -import { ElementDefinitionTypeInput, ResourcePropertyInput } from '../ResourcePropertyInput/ResourcePropertyInput'; +import { MouseEvent, useContext, useEffect, useState } from 'react'; +import { ElementsContext } from '../ElementsInput/ElementsInput.utils'; +import { ResourcePropertyInput } from '../ResourcePropertyInput/ResourcePropertyInput'; +import { SliceInput } from '../SliceInput/SliceInput'; +import { ArrayAddButton } from '../buttons/ArrayAddButton'; +import { ArrayRemoveButton } from '../buttons/ArrayRemoveButton'; import { killEvent } from '../utils/dom'; import classes from './ResourceArrayInput.module.css'; -import { - SupportedSliceDefinition, - assignValuesIntoSlices, - isSupportedSliceDefinition, -} from './ResourceArrayInput.utils'; +import { assignValuesIntoSlices, prepareSlices } from './ResourceArrayInput.utils'; export interface ResourceArrayInputProps { readonly property: InternalSchemaElement; readonly name: string; + readonly path: string; readonly defaultValue?: any[]; readonly indent?: boolean; readonly outcome: OperationOutcome | undefined; @@ -35,68 +27,29 @@ export function ResourceArrayInput(props: ResourceArrayInputProps): JSX.Element const { property } = props; const medplum = useMedplum(); const [loading, setLoading] = useState(true); - const [slices, setSlices] = useState([]); + const [slices, setSlices] = useState([]); // props.defaultValue should NOT be used after this; prefer the defaultValue state const [defaultValue] = useState(() => (Array.isArray(props.defaultValue) ? props.defaultValue : [])); - const [slicedValues, setSlicedValues] = useState([[]]); + const [slicedValues, setSlicedValues] = useState(() => [defaultValue]); + const ctx = useContext(ElementsContext); const propertyTypeCode = property.type[0]?.code; useEffect(() => { - if (!property.slicing) { - const emptySlices: SupportedSliceDefinition[] = []; - setSlices(emptySlices); - const results = assignValuesIntoSlices(defaultValue, emptySlices, property.slicing); - setSlicedValues(results); - setLoading(false); - return; - } - - const supportedSlices: SupportedSliceDefinition[] = []; - const profileUrls: (string | undefined)[] = []; - const promises: Promise[] = []; - for (const slice of property.slicing.slices) { - if (!isSupportedSliceDefinition(slice)) { - continue; - } - - const sliceType = slice.type[0]; - let profileUrl: string | undefined; - if (isEmpty(slice.elements)) { - if (sliceType.profile) { - profileUrl = sliceType.profile[0]; - } - } - - // important to keep these three arrays the same length; - supportedSlices.push(slice); - profileUrls.push(profileUrl); - if (profileUrl) { - promises.push(medplum.requestProfileSchema(profileUrl)); - } else { - promises.push(Promise.resolve()); - } - } - - Promise.all(promises) - .then(() => { - for (let i = 0; i < supportedSlices.length; i++) { - const slice = supportedSlices[i]; - const profileUrl = profileUrls[i]; - if (profileUrl) { - const typeSchema = tryGetProfile(profileUrl); - slice.typeSchema = typeSchema; - } - } - setSlices(supportedSlices); - const results = assignValuesIntoSlices(defaultValue, supportedSlices, property.slicing); - setSlicedValues(results); + prepareSlices({ + medplum, + property, + }) + .then((slices) => { + setSlices(slices); + const slicedValues = assignValuesIntoSlices(defaultValue, slices, property.slicing, ctx.profileUrl); + setSlicedValues(slicedValues); setLoading(false); }) .catch((reason) => { console.error(reason); setLoading(false); }); - }, [medplum, property.slicing, propertyTypeCode, defaultValue]); + }, [medplum, property, defaultValue, ctx.profileUrl, setSlicedValues, props.path]); function setValuesWrapper(newValues: any[], sliceIndex: number): void { const newSlicedValues = [...slicedValues]; @@ -127,6 +80,7 @@ export function ResourceArrayInput(props: ResourceArrayInputProps): JSX.Element { @@ -145,6 +99,7 @@ export function ResourceArrayInput(props: ResourceArrayInputProps): JSX.Element arrayElement={true} property={props.property} name={props.name + '.' + valueIndex} + path={props.path} defaultValue={value} onChange={(newValue: any) => { const newNonSliceValues = [...nonSliceValues]; @@ -155,7 +110,7 @@ export function ResourceArrayInput(props: ResourceArrayInputProps): JSX.Element outcome={props.outcome} />
- { @@ -169,7 +124,7 @@ export function ResourceArrayInput(props: ResourceArrayInputProps): JSX.Element ))} {showNonSliceValues && slicedValues.flat().length < property.max && ( - { killEvent(e); @@ -184,152 +139,3 @@ export function ResourceArrayInput(props: ResourceArrayInputProps): JSX.Element ); } - -interface SliceInputProps { - readonly slice: SupportedSliceDefinition; - readonly property: InternalSchemaElement; - readonly defaultValue: any[]; - readonly onChange: (newValue: any[]) => void; - readonly outcome?: OperationOutcome; - readonly testId?: string; -} - -function SliceInput(props: SliceInputProps): JSX.Element | null { - const { slice, property } = props; - const [values, setValues] = useState(() => { - return props.defaultValue.map((v) => v ?? {}); - }); - - function setValuesWrapper(newValues: any[]): void { - setValues(newValues); - if (props.onChange) { - props.onChange(newValues); - } - } - - const required = slice.min > 0; - - // this is a bit of a hack targeted at nested extensions; indentation would ideally be controlled elsewhere - // e.g. USCorePatientProfile -> USCoreEthnicityExtension -> {ombCategory, detailed, text} - const indentedStack = isEmpty(slice.elements); - const propertyDisplayName = getPropertyDisplayName(slice.name); - return ( - - - {values.map((value, valueIndex) => { - return ( - -
- - {!isEmpty(slice.elements) ? ( - { - const newValues = [...values]; - newValues[valueIndex] = newValue; - setValuesWrapper(newValues); - }} - testId={props.testId && `${props.testId}-elements-${valueIndex}`} - /> - ) : ( - { - const newValues = [...values]; - newValues[valueIndex] = newValue; - setValuesWrapper(newValues); - }} - outcome={undefined} - min={slice.min} - max={slice.max} - binding={undefined} - path={slice.path} - /> - )} - -
- {values.length > slice.min && ( - { - killEvent(e); - const newValues = [...values]; - newValues.splice(valueIndex, 1); - setValuesWrapper(newValues); - }} - /> - )} -
- ); - })} - {values.length < slice.max && ( - - { - killEvent(e); - const newValues = [...values, undefined]; - setValuesWrapper(newValues); - }} - testId={props.testId && `${props.testId}-add`} - /> - - )} -
-
- ); -} - -interface ButtonProps { - readonly propertyDisplayName?: string; - readonly onClick: React.MouseEventHandler; - readonly testId?: string; -} - -function AddButton({ propertyDisplayName, onClick, testId }: ButtonProps): JSX.Element { - const text = propertyDisplayName ? `Add ${propertyDisplayName}` : 'Add'; - - return propertyDisplayName ? ( - - ) : ( - - - - ); -} - -function RemoveButton({ propertyDisplayName, onClick, testId }: ButtonProps): JSX.Element { - return ( - - - - ); -} diff --git a/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.test.ts b/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.test.ts new file mode 100644 index 0000000000..5887d5d79c --- /dev/null +++ b/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.test.ts @@ -0,0 +1,243 @@ +import { + HTTP_HL7_ORG, + InternalTypeSchema, + buildElementsContext, + isProfileLoaded, + loadDataType, + tryGetProfile, +} from '@medplum/core'; +import { assignValuesIntoSlices, prepareSlices } from './ResourceArrayInput.utils'; +import { MockClient } from '@medplum/mock'; +import { StructureDefinition } from '@medplum/fhirtypes'; +import { readJson } from '@medplum/definitions'; + +const medplum = new MockClient(); + +describe('assignValuesIntoSlices', () => { + let USCoreStructureDefinitions: StructureDefinition[]; + + beforeAll(() => { + USCoreStructureDefinitions = readJson('fhir/r4/testing/uscore-v5.0.1-structuredefinitions.json'); + }); + + describe('US Core Patient', () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`; + const profilesToLoad = [ + profileUrl, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-race`, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-ethnicity`, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-birthsex`, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-genderIdentity`, + ]; + let patientSD: StructureDefinition; + let patientSchema: InternalTypeSchema; + + beforeAll(() => { + patientSD = USCoreStructureDefinitions.find((sd) => sd.url === profileUrl) as StructureDefinition; + expect(patientSD).toBeDefined(); + for (const url of profilesToLoad) { + const sd = USCoreStructureDefinitions.find((sd) => sd.url === url); + if (!sd) { + fail(`could not find structure definition for ${url}`); + } + loadDataType(sd, sd.url); + } + expect(isProfileLoaded(profileUrl)).toBe(true); + patientSchema = tryGetProfile(profileUrl) as InternalTypeSchema; + expect(patientSchema).toBeDefined(); + }); + + test('Patient.extension (race, ethnicity, birthsex, genderIdentity)', async () => { + const patient = { + id: 'homer-simpson', + extension: [ + { + extension: [ + { + url: 'text', + valueString: 'Some text', + }, + ], + url: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-race', + }, + { + extension: [ + { + url: 'text', + valueString: 'Some text', + }, + ], + url: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity', + }, + { + url: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex', + valueCode: 'F', + }, + { + url: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity', + valueCodeableConcept: { + coding: [ + { + system: 'http://terminology.hl7.org/CodeSystem/v3-NullFlavor', + code: 'ASKU', + display: 'asked but unknown', + }, + ], + text: 'asked but unknown', + }, + }, + ], + }; + + const property = patientSchema.elements['extension']; + + const elementsContext = buildElementsContext({ + parentContext: undefined, + elements: patientSchema.elements, + path: 'Patient', + profileUrl, + }); + if (!elementsContext) { + fail('elementsContext should be defined'); + } + + const slices = await prepareSlices({ + medplum, + property, + }); + + expect(slices.length).toBe(4); + const slicedValues = assignValuesIntoSlices( + patient.extension, + slices, + property.slicing, + elementsContext.profileUrl + ); + expect(slicedValues.map((sliceValues) => sliceValues.length)).toEqual([1, 1, 1, 1, 0]); + }); + }); + + describe('US Core Blood Pressure', () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-blood-pressure`; + let bpSD: StructureDefinition; + let bpSchema: InternalTypeSchema; + + beforeAll(() => { + bpSD = USCoreStructureDefinitions.find((sd) => sd.url === profileUrl) as StructureDefinition; + expect(bpSD).toBeDefined(); + loadDataType(bpSD, bpSD.url); + expect(isProfileLoaded(profileUrl)).toBe(true); + bpSchema = tryGetProfile(profileUrl) as InternalTypeSchema; + expect(bpSchema).toBeDefined(); + }); + + test('Observation.category (VSCat)', async () => { + const resource = { + category: [ + { + coding: [ + { + system: 'http://terminology.hl7.org/CodeSystem/observation-category', + code: 'vital-signs', + }, + ], + }, + ], + }; + + const property = bpSchema.elements['category']; + + const elementsContext = buildElementsContext({ + parentContext: undefined, + elements: bpSchema.elements, + path: 'Observation', + profileUrl, + }); + if (!elementsContext) { + fail('elementsContext should be defined'); + } + + const slices = await prepareSlices({ + medplum, + property, + }); + + expect(slices.length).toBe(1); + const slicedValues = assignValuesIntoSlices( + resource.category, + slices, + property.slicing, + elementsContext.profileUrl + ); + expect(slicedValues.map((sliceValues) => sliceValues.length)).toEqual([1, 0]); + }); + + test('Observation.component (systolic and diastolic)', async () => { + const resource = { + component: [ + { + code: { + coding: [ + { + system: 'http://loinc.org', + code: '8480-6', + }, + ], + text: 'Systolic blood pressure', + }, + valueQuantity: { + value: 109, + unit: 'mmHg', + system: 'http://unitsofmeasure.org', + code: 'mm[Hg]', + }, + }, + { + code: { + coding: [ + { + system: 'http://loinc.org', + code: '8462-4', + }, + ], + text: 'Diastolic blood pressure', + }, + valueQuantity: { + value: 49, + unit: 'mmHg', + system: 'http://unitsofmeasure.org', + code: 'mm[Hg]', + }, + }, + ], + }; + + const property = bpSchema.elements['component']; + + const elementsContext = buildElementsContext({ + parentContext: undefined, + elements: bpSchema.elements, + path: 'Observation', + profileUrl, + }); + + if (!elementsContext) { + fail('elementsContext should be defined'); + } + + const slices = await prepareSlices({ + medplum, + property, + }); + const slicedValues = assignValuesIntoSlices( + resource.component, + slices, + property.slicing, + elementsContext.profileUrl + ); + + expect(slices.length).toBe(2); + expect(slicedValues.map((sliceValues) => sliceValues.length)).toEqual([1, 1, 0]); + }); + }); +}); diff --git a/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.ts b/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.ts index 2b3ddde527..3a36e18db5 100644 --- a/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.ts +++ b/packages/react/src/ResourceArrayInput/ResourceArrayInput.utils.ts @@ -1,64 +1,21 @@ import { - InternalTypeSchema, - SliceDefinition, - SliceDiscriminator, + InternalSchemaElement, + MedplumClient, + SliceDefinitionWithTypes, SlicingRules, - TypedValue, - arrayify, - getElementDefinitionFromElements, - getTypedPropertyValueWithSchema, + getValueSliceName, isPopulated, - matchDiscriminant, + isSliceDefinitionWithTypes, + tryGetProfile, } from '@medplum/core'; -function isDiscriminatorComponentMatch( - typedValue: TypedValue, - discriminator: SliceDiscriminator, - slice: SupportedSliceDefinition -): boolean { - for (const elementList of [slice.elements, slice.typeSchema?.elements]) { - let nestedProp: TypedValue | TypedValue[] | undefined; - if (isPopulated(elementList)) { - const ed = getElementDefinitionFromElements(elementList, discriminator.path); - if (ed) { - nestedProp = getTypedPropertyValueWithSchema(typedValue.value, discriminator.path, ed); - } - } - - if (nestedProp) { - return arrayify(nestedProp)?.some((v: any) => matchDiscriminant(v, discriminator, slice, elementList)) ?? false; - } - } - - return false; -} -function getValueSliceName( - value: any, - slices: SupportedSliceDefinition[], - discriminators: SliceDiscriminator[] -): string | undefined { - if (!value) { - return undefined; - } - - for (const slice of slices) { - const typedValue: TypedValue = { - value, - type: slice.typeSchema?.name ?? slice.type[0].code, - }; - if (discriminators.every((d) => isDiscriminatorComponentMatch(typedValue, d, slice))) { - return slice.name; - } - } - return undefined; -} - export function assignValuesIntoSlices( values: any[], - slices: SupportedSliceDefinition[], - slicing: SlicingRules | undefined + slices: SliceDefinitionWithTypes[], + slicing: SlicingRules | undefined, + profileUrl: string | undefined ): any[][] { - if (!slicing || slicing.slices.length === 0) { + if (!isPopulated(slicing?.slices)) { return [values]; } @@ -69,29 +26,79 @@ export function assignValuesIntoSlices( } for (const value of values) { - const sliceName = getValueSliceName(value, slices, slicing.discriminator); + const sliceName = getValueSliceName(value, slices, slicing.discriminator, profileUrl); + let sliceIndex = sliceName ? slices.findIndex((slice) => slice.name === sliceName) : -1; + // -1 can come from either findIndex or the ternary else if (sliceIndex === -1) { - sliceIndex = slices.length; // values not matched to a slice go in the last entry for non-slice + sliceIndex = slices.length; } slicedValues[sliceIndex].push(value); } - // for slices without existing values, add a placeholder empty value - for (let i = 0; i < slices.length; i++) { - if (slicedValues[i].length === 0) { - slicedValues[i].push(undefined); + // add placeholder empty values for required slices + for (let sliceIndex = 0; sliceIndex < slices.length; sliceIndex++) { + const slice = slices[sliceIndex]; + const sliceValues = slicedValues[sliceIndex]; + + while (sliceValues.length < slice.min) { + sliceValues.push(undefined); } } return slicedValues; } -export type SupportedSliceDefinition = SliceDefinition & { - type: NonNullable; - typeSchema?: InternalTypeSchema; -}; +export async function prepareSlices({ + medplum, + property, +}: { + medplum: MedplumClient; + property: InternalSchemaElement; +}): Promise { + return new Promise((resolve, reject) => { + if (!property.slicing) { + resolve([]); + return; + } + + const supportedSlices: SliceDefinitionWithTypes[] = []; + const profileUrls: (string | undefined)[] = []; + const promises: Promise[] = []; + for (const slice of property.slicing.slices) { + if (!isSliceDefinitionWithTypes(slice)) { + console.debug('Unsupported slice definition', slice); + continue; + } + + let profileUrl: string | undefined; + // If elements are not defined for the slice, look for a profile + if (!isPopulated(slice.elements)) { + profileUrl = slice.type[0]?.profile?.[0]; + } + + // important to keep these three arrays the same length; + supportedSlices.push(slice); + profileUrls.push(profileUrl); + if (profileUrl) { + promises.push(medplum.requestProfileSchema(profileUrl)); + } else { + promises.push(Promise.resolve([])); + } + } -export function isSupportedSliceDefinition(slice: SliceDefinition): slice is SupportedSliceDefinition { - return slice.type !== undefined && slice.type.length > 0; + Promise.all(promises) + .then(() => { + for (let i = 0; i < supportedSlices.length; i++) { + const slice = supportedSlices[i]; + const profileUrl = profileUrls[i]; + if (profileUrl) { + const typeSchema = tryGetProfile(profileUrl); + slice.typeSchema = typeSchema; + } + } + resolve(supportedSlices); + }) + .catch(reject); + }); } diff --git a/packages/react/src/ResourceAvatar/ResourceAvatar.test.tsx b/packages/react/src/ResourceAvatar/ResourceAvatar.test.tsx index 27ce46c5d8..9c9756220a 100644 --- a/packages/react/src/ResourceAvatar/ResourceAvatar.test.tsx +++ b/packages/react/src/ResourceAvatar/ResourceAvatar.test.tsx @@ -1,9 +1,10 @@ import { createReference } from '@medplum/core'; import { HomerSimpson, MockClient } from '@medplum/mock'; import { MedplumProvider } from '@medplum/react-hooks'; -import { act, render, screen, waitFor } from '../test-utils/render'; import { MemoryRouter } from 'react-router-dom'; +import { act, render, screen, waitFor } from '../test-utils/render'; import { ResourceAvatar, ResourceAvatarProps } from './ResourceAvatar'; +import { getInitials } from './ResourceAvatar.utils'; const medplum = new MockClient(); @@ -65,4 +66,11 @@ describe('ResourceAvatar', () => { expect(screen.getByAltText('Homer Simpson')).toBeDefined(); }); + + test('getInitials', () => { + expect(getInitials('Homer Simpson')).toEqual('HS'); + expect(getInitials('Homer')).toEqual('H'); + expect(getInitials('Homer J Simpson')).toEqual('HS'); + expect(getInitials('')).toEqual(''); + }); }); diff --git a/packages/react/src/ResourceAvatar/ResourceAvatar.tsx b/packages/react/src/ResourceAvatar/ResourceAvatar.tsx index 4bb3b8871d..a748f98c96 100644 --- a/packages/react/src/ResourceAvatar/ResourceAvatar.tsx +++ b/packages/react/src/ResourceAvatar/ResourceAvatar.tsx @@ -3,6 +3,7 @@ import { getDisplayString, getImageSrc } from '@medplum/core'; import { Reference, Resource } from '@medplum/fhirtypes'; import { useCachedBinaryUrl, useResource } from '@medplum/react-hooks'; import { MedplumLink } from '../MedplumLink/MedplumLink'; +import { getInitials } from './ResourceAvatar.utils'; export interface ResourceAvatarProps extends AvatarProps { readonly value?: Reference | Resource; @@ -12,6 +13,7 @@ export interface ResourceAvatarProps extends AvatarProps { export function ResourceAvatar(props: ResourceAvatarProps): JSX.Element { const resource = useResource(props.value); const text = resource ? getDisplayString(resource) : props.alt ?? ''; + const initials = getInitials(text); const uncachedImageUrl = (resource && getImageSrc(resource)) ?? props.src; const imageUrl = useCachedBinaryUrl(uncachedImageUrl ?? undefined); const radius = props.radius ?? 'xl'; @@ -23,10 +25,16 @@ export function ResourceAvatar(props: ResourceAvatarProps): JSX.Element { if (props.link) { return ( - + + {initials} + ); } - return ; + return ( + + {initials} + + ); } diff --git a/packages/react/src/ResourceAvatar/ResourceAvatar.utils.ts b/packages/react/src/ResourceAvatar/ResourceAvatar.utils.ts new file mode 100644 index 0000000000..f347e4e4f5 --- /dev/null +++ b/packages/react/src/ResourceAvatar/ResourceAvatar.utils.ts @@ -0,0 +1,10 @@ +export function getInitials(input: string): string { + const words = input.split(' ').filter(Boolean); + if (words.length > 1) { + return words[0][0] + words[words.length - 1][0]; + } + if (words.length === 1) { + return words[0][0]; + } + return ''; +} diff --git a/packages/react/src/ResourceDiffTable/ResourceDiffTable.test.tsx b/packages/react/src/ResourceDiffTable/ResourceDiffTable.test.tsx index daf46d3d71..d6349b89d0 100644 --- a/packages/react/src/ResourceDiffTable/ResourceDiffTable.test.tsx +++ b/packages/react/src/ResourceDiffTable/ResourceDiffTable.test.tsx @@ -20,6 +20,7 @@ describe('ResourceDiffTable', () => { resourceType: 'Patient', id: '123', meta: { + author: { reference: 'Practitioner/456' }, versionId: '456', }, birthDate: '1990-01-01', @@ -30,6 +31,7 @@ describe('ResourceDiffTable', () => { resourceType: 'Patient', id: '123', meta: { + author: { reference: 'Practitioner/457' }, versionId: '457', }, birthDate: '1990-01-01', @@ -56,5 +58,182 @@ describe('ResourceDiffTable', () => { // Birth date did not change, and therefore should not be shown expect(screen.queryByText('Birth Date')).toBeNull(); + + // Certain meta fields should not be shown + expect(screen.queryByText('Author')).toBeNull(); + expect(screen.queryByText('Version ID')).toBeNull(); + }); + + test('Array index operations', async () => { + const original: Patient = { + resourceType: 'Patient', + id: '123', + meta: { versionId: '456' }, + name: [{ family: 'Smith', given: ['John'] }], + identifier: [ + { system: 'http://example.com/foo', value: '123' }, + { system: 'http://example.com/bar', value: '456' }, + ], + }; + + const revised: Patient = { + ...original, + meta: { versionId: '457' }, + identifier: [ + { system: 'http://example.com/foo', value: '123x' }, + { system: 'http://example.com/bar', value: '456x' }, + ], + }; + + await act(async () => { + setup({ original, revised }); + }); + + await waitFor(() => screen.getByText('Replace identifier[0].value')); + expect(screen.getByText('Replace identifier[0].value')).toBeInTheDocument(); + expect(screen.getByText('123')).toBeInTheDocument(); + expect(screen.getByText('123x')).toBeInTheDocument(); + expect(screen.getByText('Replace identifier[1].value')).toBeInTheDocument(); + expect(screen.getByText('456')).toBeInTheDocument(); + expect(screen.getByText('456x')).toBeInTheDocument(); + }); + + test('Single array add', async () => { + const original: Patient = { + resourceType: 'Patient', + id: '123', + meta: { versionId: '456' }, + name: [{ family: 'Smith', given: ['John'] }], + identifier: [{ system: 'http://example.com/foo', value: '123' }], + }; + + const revised: Patient = { + ...original, + meta: { versionId: '457' }, + identifier: [ + { system: 'http://example.com/foo', value: '123' }, + { system: 'http://example.com/bar', value: '456' }, + ], + }; + + await act(async () => { + setup({ original, revised }); + }); + + await waitFor(() => screen.getByText('Add identifier.last()')); + + const operations = screen.getAllByText('Add identifier.last()'); + expect(operations).toHaveLength(1); + }); + + test('Combine patch operations on array add', async () => { + const original: Patient = { + resourceType: 'Patient', + id: '123', + meta: { versionId: '456' }, + name: [{ family: 'Smith', given: ['John'] }], + identifier: [{ system: 'http://example.com/foo', value: '123' }], + }; + + const revised: Patient = { + ...original, + meta: { versionId: '457' }, + identifier: [ + { system: 'http://example.com/foo', value: '123' }, + { system: 'http://example.com/bar', value: '456' }, + { system: 'http://example.com/baz', value: '789' }, + ], + }; + + await act(async () => { + setup({ original, revised }); + }); + + await waitFor(() => screen.getByText('Replace identifier')); + + const operations = screen.getAllByText('Replace identifier'); + expect(operations).toHaveLength(1); + }); + + test('Single array remove', async () => { + const original: Patient = { + resourceType: 'Patient', + id: '123', + meta: { versionId: '456' }, + name: [{ family: 'Smith', given: ['John'] }], + identifier: [ + { system: 'http://example.com/foo', value: '123' }, + { system: 'http://example.com/bar', value: '456' }, + ], + }; + + const revised: Patient = { + ...original, + meta: { versionId: '457' }, + identifier: [{ system: 'http://example.com/foo', value: '123' }], + }; + + await act(async () => { + setup({ original, revised }); + }); + + await waitFor(() => screen.getByText('Remove identifier[1]')); + + const operations = screen.getAllByText('Remove identifier[1]'); + expect(operations).toHaveLength(1); + }); + + test('Combine patch operations on array remove', async () => { + const original: Patient = { + resourceType: 'Patient', + id: '123', + meta: { versionId: '456' }, + name: [{ family: 'Smith', given: ['John'] }], + identifier: [ + { system: 'http://example.com/foo', value: '123' }, + { system: 'http://example.com/bar', value: '456' }, + { system: 'http://example.com/baz', value: '789' }, + ], + }; + + const revised: Patient = { + ...original, + meta: { versionId: '457' }, + identifier: [{ system: 'http://example.com/foo', value: '123' }], + }; + + await act(async () => { + setup({ original, revised }); + }); + + await waitFor(() => screen.getByText('Replace identifier')); + + const operations = screen.getAllByText('Replace identifier'); + expect(operations).toHaveLength(1); + }); + + test('Change attachment URL', async () => { + const original: Patient = { + resourceType: 'Patient', + id: '123', + meta: { versionId: '456' }, + name: [{ family: 'Smith', given: ['John'] }], + photo: [{ url: 'http://example.com/foo.jpg' }], + }; + + const revised: Patient = { + ...original, + meta: { versionId: '457' }, + photo: [{ url: 'http://example.com/bar.jpg' }], + }; + + await act(async () => { + setup({ original, revised }); + }); + + await waitFor(() => screen.getByText('Replace photo[0]')); + + // There should be 2 download links + expect(screen.getAllByText('Download')).toHaveLength(2); }); }); diff --git a/packages/react/src/ResourceDiffTable/ResourceDiffTable.tsx b/packages/react/src/ResourceDiffTable/ResourceDiffTable.tsx index 03f38da6c1..fe3f6fb783 100644 --- a/packages/react/src/ResourceDiffTable/ResourceDiffTable.tsx +++ b/packages/react/src/ResourceDiffTable/ResourceDiffTable.tsx @@ -1,9 +1,17 @@ import { Table } from '@mantine/core'; -import { capitalize, evalFhirPathTyped, getSearchParameterDetails, toTypedValue } from '@medplum/core'; +import { + InternalSchemaElement, + TypedValue, + arrayify, + capitalize, + evalFhirPathTyped, + getSearchParameterDetails, + toTypedValue, +} from '@medplum/core'; import { Resource, SearchParameter } from '@medplum/fhirtypes'; import { useMedplum } from '@medplum/react-hooks'; -import { useEffect, useState } from 'react'; -import { createPatch } from 'rfc6902'; +import { useEffect, useMemo, useState } from 'react'; +import { Operation, createPatch } from 'rfc6902'; import { ResourcePropertyDisplay } from '../ResourcePropertyDisplay/ResourcePropertyDisplay'; import classes from './ResourceDiffTable.module.css'; @@ -14,6 +22,7 @@ export interface ResourceDiffTableProps { export function ResourceDiffTable(props: ResourceDiffTableProps): JSX.Element | null { const medplum = useMedplum(); + const { original, revised } = props; const [schemaLoaded, setSchemaLoaded] = useState(false); useEffect(() => { @@ -23,14 +32,44 @@ export function ResourceDiffTable(props: ResourceDiffTableProps): JSX.Element | .catch(console.log); }, [medplum, props.original.resourceType]); - if (!schemaLoaded) { + const diffTable = useMemo(() => { + if (!schemaLoaded) { + return null; + } + + const typedOriginal = [toTypedValue(original)]; + const typedRevised = [toTypedValue(revised)]; + const result = []; + + // First, we filter and conslidate the patch operations + // We can do this because we do not use the "value" field in the patch operations + // Remove patch operations on meta elements such as "meta.lastUpdated" and "meta.versionId" + // Consolidate patch operations on arrays + const patch = mergePatchOperations(createPatch(original, revised)); + + // Next, convert the patch operations to a diff table + for (const op of patch) { + const path = op.path; + const fhirPath = jsonPathToFhirPath(path); + const property = tryGetElementDefinition(original.resourceType, fhirPath); + const originalValue = op.op === 'add' ? undefined : evalFhirPathTyped(fhirPath, typedOriginal); + const revisedValue = op.op === 'remove' ? undefined : evalFhirPathTyped(fhirPath, typedRevised); + result.push({ + key: `op-${op.op}-${op.path}`, + name: `${capitalize(op.op)} ${fhirPath}`, + property: property, + originalValue: touchUpValue(property, originalValue), + revisedValue: touchUpValue(property, revisedValue), + }); + } + + return result; + }, [schemaLoaded, original, revised]); + + if (!diffTable) { return null; } - const patch = createPatch(props.original, props.revised); - const typedOriginal = [toTypedValue(props.original)]; - const typedRevised = [toTypedValue(props.revised)]; - return ( @@ -41,57 +80,63 @@ export function ResourceDiffTable(props: ResourceDiffTableProps): JSX.Element | - {patch.map((op) => { - if (op.path.startsWith('/meta')) { - return null; - } - - const path = op.path; - const fhirPath = jsonPathToFhirPath(path); - const details = getSearchParameterDetails(props.original.resourceType, { - resourceType: 'SearchParameter', - base: [props.original.resourceType], - code: props.original.resourceType + '.' + fhirPath, - expression: props.original.resourceType + '.' + fhirPath, - } as SearchParameter); - const property = details?.elementDefinitions?.[0]; - const isArray = !!property?.isArray; - const originalValue = op.op === 'add' ? undefined : evalFhirPathTyped(fhirPath, typedOriginal)?.[0]; - const revisedValue = op.op === 'remove' ? undefined : evalFhirPathTyped(fhirPath, typedRevised)?.[0]; - - return ( - - - {capitalize(op.op)} {fhirPath} - - - {originalValue && ( - - )} - - - {revisedValue && ( - - )} - - - ); - })} + {diffTable.map((row) => ( + + {row.name} + + {row.originalValue && ( + + )} + + + {row.revisedValue && ( + + )} + + + ))}
); } +function mergePatchOperations(patch: Operation[]): Operation[] { + const result: Operation[] = []; + for (const patchOperation of patch) { + const { op, path } = patchOperation; + if ( + path.startsWith('/meta/author') || + path.startsWith('/meta/compartment') || + path.startsWith('/meta/lastUpdated') || + path.startsWith('/meta/versionId') + ) { + continue; + } + const count = patch.filter((el) => el.op === op && el.path === path).length; + const resultOperation = { op, path } as Operation; + if (count > 1 && (op === 'add' || op === 'remove') && /\/[0-9-]+$/.test(path)) { + // Remove everything after the last slash + resultOperation.op = 'replace'; + resultOperation.path = path.replace(/\/[^/]+$/, ''); + } + if (!result.some((el) => el.op === resultOperation.op && el.path === resultOperation.path)) { + // Only add the operation if it doesn't already exist + result.push(resultOperation); + } + } + return result; +} + function jsonPathToFhirPath(path: string): string { const parts = path.split('/').filter(Boolean); let result = ''; @@ -108,15 +153,42 @@ function jsonPathToFhirPath(path: string): string { result += part; } } + + // For attachments, remove the .url suffix + // Note that not all ".url" properties are attachments, but it is the common case. + // If the property is not an attachment, the diff will simply render the parent element, + // which is still fine. + if (result.endsWith('.url')) { + result = result.replace(/\.url$/, ''); + } + return result; } -function fixArray(inputValue: any, isArray: boolean): any { - if (Array.isArray(inputValue) && !isArray) { - return inputValue[0]; - } - if (!Array.isArray(inputValue) && isArray) { - return [inputValue]; +function tryGetElementDefinition(resourceType: string, fhirPath: string): InternalSchemaElement | undefined { + const details = getSearchParameterDetails(resourceType, { + resourceType: 'SearchParameter', + base: [resourceType], + code: resourceType + '.' + fhirPath, + expression: resourceType + '.' + fhirPath, + } as SearchParameter); + return details?.elementDefinitions?.[0]; +} + +function touchUpValue( + property: InternalSchemaElement | undefined, + input: TypedValue[] | TypedValue | undefined +): TypedValue | undefined { + if (!input) { + return input; } - return inputValue; + return { + type: Array.isArray(input) ? input[0].type : input.type, + value: fixArray(input, !!property?.isArray), + }; +} + +function fixArray(input: TypedValue[] | TypedValue, isArray: boolean): any { + const inputValue = (arrayify(input) as TypedValue[]).flatMap((v) => v.value); + return isArray ? inputValue : inputValue[0]; } diff --git a/packages/react/src/ResourceForm/ResourceForm.stories.tsx b/packages/react/src/ResourceForm/ResourceForm.stories.tsx index 2a3c0a79a6..8e9bc25d8d 100644 --- a/packages/react/src/ResourceForm/ResourceForm.stories.tsx +++ b/packages/react/src/ResourceForm/ResourceForm.stories.tsx @@ -10,8 +10,8 @@ import { Meta } from '@storybook/react'; import { Document } from '../Document/Document'; import { ResourceForm } from './ResourceForm'; import { useMedplum } from '@medplum/react-hooks'; -import { useEffect, useMemo, useState } from 'react'; -import { MedplumClient, deepClone, loadDataType } from '@medplum/core'; +import { useEffect, useLayoutEffect, useMemo, useState } from 'react'; +import { MedplumClient, RequestProfileSchemaOptions, deepClone, loadDataType } from '@medplum/core'; import { StructureDefinition } from '@medplum/fhirtypes'; export default { @@ -151,7 +151,7 @@ function useUSCoreDataTypes({ medplum }: { medplum: MedplumClient }): { loaded: const [loaded, setLoaded] = useState(false); useEffect(() => { (async (): Promise => { - for (const sd of USCoreStructureDefinitionList as StructureDefinition[]) { + for (const sd of USCoreStructureDefinitionList) { loadDataType(sd, sd.url); } return true; @@ -167,9 +167,32 @@ function useUSCoreDataTypes({ medplum }: { medplum: MedplumClient }): { loaded: return result; } -function useProfile(profileName: string): StructureDefinition { +function useFakeRequestProfileSchema(medplum: MedplumClient): void { + useLayoutEffect(() => { + const realRequestProfileSchema = medplum.requestProfileSchema; + async function fakeRequestProfileSchema( + profileUrl: string, + options?: RequestProfileSchemaOptions + ): Promise { + console.log( + 'Fake medplum.requestProfileSchema invoked but not doing anything; ensure expected profiles are already loaded', + profileUrl, + options + ); + return [profileUrl]; + } + + medplum.requestProfileSchema = fakeRequestProfileSchema; + + return () => { + medplum.requestProfileSchema = realRequestProfileSchema; + }; + }, [medplum]); +} + +function useUSCoreProfile(profileName: string): StructureDefinition { const profileSD = useMemo(() => { - const result = (USCoreStructureDefinitionList as StructureDefinition[]).find((sd) => sd.name === profileName); + const result = USCoreStructureDefinitionList.find((sd) => sd.name === profileName); if (!result) { throw new Error(`Could not find ${profileName}`); } @@ -181,8 +204,9 @@ function useProfile(profileName: string): StructureDefinition { export const USCorePatient = (): JSX.Element => { const medplum = useMedplum(); + useFakeRequestProfileSchema(medplum); const { loaded } = useUSCoreDataTypes({ medplum }); - const profileSD = useProfile('USCorePatientProfile'); + const profileSD = useUSCoreProfile('USCorePatientProfile'); const homerSimpsonUSCorePatient = useMemo(() => { return deepClone(HomerSimpsonUSCorePatient); @@ -207,8 +231,9 @@ export const USCorePatient = (): JSX.Element => { export const USCoreImplantableDevice = (): JSX.Element => { const medplum = useMedplum(); + useFakeRequestProfileSchema(medplum); const { loaded } = useUSCoreDataTypes({ medplum }); - const profileSD = useProfile('USCoreImplantableDeviceProfile'); + const profileSD = useUSCoreProfile('USCoreImplantableDeviceProfile'); const implantedKnee = useMemo(() => { return deepClone(ImplantableDeviceKnee); diff --git a/packages/react/src/ResourceForm/ResourceForm.test.tsx b/packages/react/src/ResourceForm/ResourceForm.test.tsx index ace1eee9b0..81d8da2c8f 100644 --- a/packages/react/src/ResourceForm/ResourceForm.test.tsx +++ b/packages/react/src/ResourceForm/ResourceForm.test.tsx @@ -1,18 +1,35 @@ -import { createReference } from '@medplum/core'; -import { Observation, Patient, Specimen } from '@medplum/fhirtypes'; +import { HTTP_HL7_ORG, createReference, deepClone, loadDataType } from '@medplum/core'; +import { Observation, Patient, Specimen, StructureDefinition } from '@medplum/fhirtypes'; import { HomerObservation1, MockClient } from '@medplum/mock'; import { MedplumProvider } from '@medplum/react-hooks'; import { convertIsoToLocal, convertLocalToIso } from '../DateTimeInput/DateTimeInput.utils'; -import { act, fireEvent, render, screen, waitFor } from '../test-utils/render'; +import { act, fireEvent, render, screen, waitFor, within } from '../test-utils/render'; import { ResourceForm, ResourceFormProps } from './ResourceForm'; +import { readJson } from '@medplum/definitions'; const medplum = new MockClient(); describe('ResourceForm', () => { - async function setup(props: ResourceFormProps): Promise { + let USCoreStructureDefinitions: StructureDefinition[]; + beforeAll(() => { + USCoreStructureDefinitions = readJson('fhir/r4/testing/uscore-v5.0.1-structuredefinitions.json'); + }); + + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(async () => { + await act(async () => { + jest.runOnlyPendingTimers(); + }); + jest.useRealTimers(); + }); + + async function setup(props: ResourceFormProps, medplumClient?: MockClient): Promise { await act(async () => { render( - + ); @@ -223,4 +240,139 @@ describe('ResourceForm', () => { expect(patient.resourceType).toBe('Patient'); expect(patient.active).toBe(true); }); + + test('With profileUrl specified', async () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-implantable-device`; + const profilesToLoad = [profileUrl, `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`]; + for (const url of profilesToLoad) { + const sd = USCoreStructureDefinitions.find((sd) => sd.url === url); + if (!sd) { + fail(`could not find structure definition for ${url}`); + } + loadDataType(sd, sd.url); + } + + const onSubmit = jest.fn(); + + const mockedMedplum = new MockClient(); + const fakeRequestProfileSchema = jest.fn(async (profileUrl: string) => { + return [profileUrl]; + }); + mockedMedplum.requestProfileSchema = fakeRequestProfileSchema; + await setup({ defaultValue: { resourceType: 'Device' }, profileUrl, onSubmit }, mockedMedplum); + + expect(fakeRequestProfileSchema).toHaveBeenCalledTimes(1); + }); + + test('US Core Patient add extensions', async () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`; + const raceExtensionUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-race`; + const ethnicityExtensionUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-ethnicity`; + const profileUrls = [ + profileUrl, + raceExtensionUrl, + ethnicityExtensionUrl, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-birthsex`, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-genderIdentity`, + ]; + for (const url of profileUrls) { + const sd = USCoreStructureDefinitions.find((sd) => sd.url === url); + if (!sd) { + fail(`could not find structure definition for ${url}`); + } + loadDataType(sd, sd.url); + } + + const onSubmit = jest.fn(); + const mockedMedplum = new MockClient(); + const fakeRequestProfileSchema = jest.fn(async (profileUrl: string) => { + return [profileUrl]; + }); + mockedMedplum.requestProfileSchema = fakeRequestProfileSchema; + + const initialValue: Patient = { + resourceType: 'Patient', + name: [ + { + given: ['Lisa'], + family: 'Simpson', + use: 'usual', + }, + ], + gender: 'female', + identifier: [ + { + system: 'http://name.ly', + value: 'lisa-123', + }, + ], + }; + const expectedValue = deepClone(initialValue); + expectedValue.extension = []; + + await setup({ defaultValue: initialValue, profileUrl, onSubmit }, mockedMedplum); + + const raceExtension = screen.getByTestId('slice-race'); + + await act(async () => { + fireEvent.click(within(raceExtension).getByText('Add Race')); + }); + + await act(async () => { + fireEvent.click(within(raceExtension).getByText('Add OMB Category')); + }); + + const ombCategoryInput = within(within(raceExtension).getByTestId('slice-ombCategory')).getByRole('searchbox'); + + await act(async () => { + fireEvent.focus(ombCategoryInput); + }); + + await act(async () => { + fireEvent.change(ombCategoryInput, { target: { value: 'custom-omb-category-value' } }); + }); + + await waitFor(() => screen.getByText('+ Create custom-omb-category-value')); + + await act(async () => { + fireEvent.click(screen.getByText('+ Create custom-omb-category-value')); + }); + + await waitFor(() => screen.getByText('custom-omb-category-value')); + + await act(async () => { + const textInput = within(within(raceExtension).getByTestId('slice-text')).getByTestId('value[x]'); + fireEvent.change(textInput, { + target: { value: 'This is a text value' }, + }); + }); + + // Just clicking add, but not filling in a value should not add it to the final value + await act(async () => { + fireEvent.click(within(raceExtension).getByText('Add Detailed')); + }); + + expectedValue.extension.push({ + extension: [ + { + url: 'ombCategory', + valueCoding: { + code: 'custom-omb-category-value', + display: 'custom-omb-category-value', + }, + }, + { + url: 'text', + valueString: 'This is a text value', + }, + ], + url: raceExtensionUrl, + }); + + await act(async () => { + fireEvent.click(screen.getByText('OK')); + }); + + expect(onSubmit).toHaveBeenCalledWith(expectedValue); + }); }); diff --git a/packages/react/src/ResourceForm/ResourceForm.tsx b/packages/react/src/ResourceForm/ResourceForm.tsx index f70f86d592..0f074c13fb 100644 --- a/packages/react/src/ResourceForm/ResourceForm.tsx +++ b/packages/react/src/ResourceForm/ResourceForm.tsx @@ -1,5 +1,5 @@ import { Button, Group, Stack, TextInput } from '@mantine/core'; -import { deepClone, tryGetProfile } from '@medplum/core'; +import { applyDefaultValuesToResource, tryGetProfile } from '@medplum/core'; import { OperationOutcome, Reference, Resource } from '@medplum/fhirtypes'; import { useMedplum, useResource } from '@medplum/react-hooks'; import { FormEvent, useEffect, useState } from 'react'; @@ -25,25 +25,31 @@ export function ResourceForm(props: ResourceFormProps): JSX.Element { useEffect(() => { if (defaultValue) { - setValue(deepClone(defaultValue)); if (props.profileUrl) { const profileUrl: string = props.profileUrl; medplum - .requestProfileSchema(props.profileUrl) + .requestProfileSchema(props.profileUrl, { expandProfile: true }) .then(() => { const profile = tryGetProfile(profileUrl); if (profile) { setSchemaLoaded(profile.name); + const modifiedDefaultValue = applyDefaultValuesToResource(defaultValue, profile); + setValue(modifiedDefaultValue); } else { - console.log(`Schema not found for ${profileUrl}`); + console.error(`Schema not found for ${profileUrl}`); } }) - .catch(console.log); + .catch((reason) => { + console.error('Error in requestProfileSchema', reason); + }); } else { const schemaName = props.schemaName ?? defaultValue?.resourceType; medplum .requestSchema(schemaName) - .then(() => setSchemaLoaded(schemaName)) + .then(() => { + setValue(defaultValue); + setSchemaLoaded(schemaName); + }) .catch(console.log); } } @@ -73,6 +79,7 @@ export function ResourceForm(props: ResourceFormProps): JSX.Element { (props: ResourceInpu [onChange] ); - if (props.defaultValue && !outcome && !defaultValue) { + if (isPopulated(props.defaultValue) && !outcome && !defaultValue) { // If a default value was specified, but the default resource is not loaded yet, // then return null to avoid rendering the input until the default resource is loaded. // The Mantine component does not reliably handle changes to defaultValue. @@ -142,21 +144,23 @@ export function ResourceInput(props: ResourceInpu ); } -const ItemComponent = forwardRef(({ label, resource, ...others }: any, ref) => { - return ( -
- - -
- {label} - - {(resource as Patient).birthDate} - -
-
-
- ); -}); +const ItemComponent = forwardRef>( + ({ label, resource, ...others }: AsyncAutocompleteOption, ref) => { + return ( +
+ + +
+ {label} + + {(resource as Patient).birthDate} + +
+
+
+ ); + } +); /** * Returns the search parameter to use for the given resource type. diff --git a/packages/react/src/ResourcePropertyDisplay/ResourcePropertyDisplay.utils.ts b/packages/react/src/ResourcePropertyDisplay/ResourcePropertyDisplay.utils.ts index 76c2a5d55a..1214245b90 100644 --- a/packages/react/src/ResourcePropertyDisplay/ResourcePropertyDisplay.utils.ts +++ b/packages/react/src/ResourcePropertyDisplay/ResourcePropertyDisplay.utils.ts @@ -34,17 +34,17 @@ export function getValueAndType(context: TypedValue, path: string): [any, string * For example, "Observation.value[x]" can be "valueString", "valueInteger", "valueQuantity", etc. * According to the spec, there can only be one property for a given element definition. * This function returns the value and the type. - * @param value - The base context (usually a FHIR resource). + * @param typedValue - The base context (usually a FHIR resource). * @param path - The property path. * @param element - The property element definition. * @returns The value of the property and the property type. */ export function getValueAndTypeFromElement( - value: TypedValue['value'], + typedValue: TypedValue, path: string, element: InternalSchemaElement ): [any, string] { - const typedResult = getTypedPropertyValueWithSchema(value, path, element); + const typedResult = getTypedPropertyValueWithSchema(typedValue, path, element); if (!typedResult) { return [undefined, 'undefined']; } diff --git a/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.stories.tsx b/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.stories.tsx index a93c026678..7716664105 100644 --- a/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.stories.tsx +++ b/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.stories.tsx @@ -1,8 +1,10 @@ -import { PropertyType } from '@medplum/core'; +import { InternalSchemaElement, PropertyType } from '@medplum/core'; import { HomerSimpson } from '@medplum/mock'; import { Meta } from '@storybook/react'; import { Document } from '../Document/Document'; import { ResourcePropertyInput } from './ResourcePropertyInput'; +import { Extension } from '@medplum/fhirtypes'; +import { useCallback } from 'react'; export default { title: 'Medplum/ResourcePropertyInput', @@ -13,6 +15,7 @@ export const AddressInput = (): JSX.Element => ( ( ( ( ( /> ); + +const defaultValue: Extension[] = [ + { + url: 'https://example.com', + valueString: 'foo', + }, +]; +const property: InternalSchemaElement = { + path: 'extension', + description: '', + min: 0, + max: 10, + type: [{ code: 'Extension' }], + isArray: false, +}; +export const ExtensionInput = (): JSX.Element => { + const onChange = useCallback((newValue: any): void => { + console.log('onChange', newValue); + }, []); + + return ( + + + + ); +}; diff --git a/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.test.tsx b/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.test.tsx index 5fae94ffaa..7e41c816e5 100644 --- a/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.test.tsx +++ b/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.test.tsx @@ -30,7 +30,8 @@ const baseProperty: Omit = { path: '', }; -const defaultProps: Pick = { +const defaultProps: Pick = { + path: 'Resource.path', defaultValue: undefined, outcome: undefined, onChange: undefined, @@ -283,14 +284,15 @@ describe('ResourcePropertyInput', () => { onChange, }); - const el = screen.getByDisplayValue('{"url":"https://example.com","valueString":"foo"}'); + expect(screen.getByDisplayValue('https://example.com')).toBeInTheDocument(); + const el = screen.getByDisplayValue('foo'); expect(el).toBeInTheDocument(); await act(async () => { - fireEvent.change(el, { target: { value: '{"url":"https://example.com","valueString":"bar"}' } }); + fireEvent.change(el, { target: { value: 'new value' } }); }); - expect(onChange).toHaveBeenCalledWith([{ url: 'https://example.com', valueString: 'bar' }]); + expect(onChange).toHaveBeenCalledWith([{ url: 'https://example.com', valueString: 'new value' }]); }); test('HumanName property', async () => { @@ -512,9 +514,8 @@ describe('ResourcePropertyInput', () => { expect(comboboxes).toHaveLength(1); expect(comboboxes[0]).toBeInstanceOf(HTMLSelectElement); - const searchBoxes = screen.getAllByRole('searchbox'); - expect(searchBoxes).toHaveLength(1); - expect(searchBoxes[0]).toBeInstanceOf(HTMLInputElement); + const searchBoxes = screen.queryAllByRole('searchbox'); + expect(searchBoxes).toHaveLength(0); }); test('Type selector', async () => { @@ -622,6 +623,7 @@ describe('ResourcePropertyInput', () => { await setup({ ...defaultProps, name: 'secret', + path: property.path, property, onChange, }); diff --git a/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.tsx b/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.tsx index a1338c976a..77a3160438 100644 --- a/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.tsx +++ b/packages/react/src/ResourcePropertyInput/ResourcePropertyInput.tsx @@ -1,7 +1,17 @@ import { Checkbox, Group, NativeSelect, Textarea, TextInput } from '@mantine/core'; -import { capitalize, HTTP_HL7_ORG, InternalSchemaElement, PropertyType } from '@medplum/core'; +import { + applyDefaultValuesToElement, + capitalize, + getPathDifference, + HTTP_HL7_ORG, + InternalSchemaElement, + isComplexTypeCode, + isEmpty, + isPopulated, + PropertyType, +} from '@medplum/core'; import { ElementDefinitionBinding, ElementDefinitionType, OperationOutcome } from '@medplum/fhirtypes'; -import { useState } from 'react'; +import { useContext, useMemo, useState } from 'react'; import { AddressInput } from '../AddressInput/AddressInput'; import { AnnotationInput } from '../AnnotationInput/AnnotationInput'; import { AttachmentArrayInput } from '../AttachmentArrayInput/AttachmentArrayInput'; @@ -13,6 +23,7 @@ import { CodingInput } from '../CodingInput/CodingInput'; import { ContactDetailInput } from '../ContactDetailInput/ContactDetailInput'; import { ContactPointInput } from '../ContactPointInput/ContactPointInput'; import { DateTimeInput } from '../DateTimeInput/DateTimeInput'; +import { ElementsContext } from '../ElementsInput/ElementsInput.utils'; import { ExtensionInput } from '../ExtensionInput/ExtensionInput'; import { HumanNameInput } from '../HumanNameInput/HumanNameInput'; import { IdentifierInput } from '../IdentifierInput/IdentifierInput'; @@ -31,6 +42,8 @@ import { ComplexTypeInputProps } from './ResourcePropertyInput.utils'; export interface ResourcePropertyInputProps { readonly property: InternalSchemaElement; readonly name: string; + /** The path identifies the element and is expressed as a "."-separated list of ancestor elements, beginning with the name of the resource or extension. */ + readonly path: string; readonly defaultPropertyType?: string | undefined; readonly defaultValue: any; readonly arrayElement?: boolean | undefined; @@ -39,12 +52,11 @@ export interface ResourcePropertyInputProps { } export function ResourcePropertyInput(props: ResourcePropertyInputProps): JSX.Element { - const { property, name, defaultValue, onChange } = props; + const { property, name, onChange, defaultValue } = props; const defaultPropertyType = props.defaultPropertyType && props.defaultPropertyType !== 'undefined' ? props.defaultPropertyType : property.type[0].code; - const propertyTypes = property.type as ElementDefinitionType[]; if ((property.isArray || property.max > 1) && !props.arrayElement) { @@ -58,15 +70,14 @@ export function ResourcePropertyInput(props: ResourcePropertyInputProps): JSX.El ); - } - - if (propertyTypes.length > 1) { + } else if (propertyTypes.length > 1) { return ; } else { return ( @@ -84,7 +95,7 @@ export function ResourcePropertyInput(props: ResourcePropertyInputProps): JSX.El min={property.min} max={property.min} binding={property.binding} - path={property.path} + path={props.path} /> ); } @@ -109,6 +120,7 @@ export function ElementDefinitionInputSelector(props: ElementDefinitionSelectorP { setSelectedType( propertyTypes.find( @@ -141,24 +153,48 @@ export function ElementDefinitionInputSelector(props: ElementDefinitionSelectorP } // Avoiding optional props on lower-level components like to make it more difficult to misuse -export type ElementDefinitionTypeInputProps = { - readonly name: ResourcePropertyInputProps['name']; - readonly path: string; - readonly defaultValue: ResourcePropertyInputProps['defaultValue']; - readonly onChange: ResourcePropertyInputProps['onChange']; - readonly outcome: ResourcePropertyInputProps['outcome']; +export interface ElementDefinitionTypeInputProps + extends Pick { readonly elementDefinitionType: ElementDefinitionType; readonly min: number; readonly max: number; readonly binding: ElementDefinitionBinding | undefined; -}; +} export function ElementDefinitionTypeInput(props: ElementDefinitionTypeInputProps): JSX.Element { - const { name, defaultValue, onChange, outcome, binding, path } = props; + const { name, onChange, outcome, binding, path } = props; const required = props.min !== undefined && props.min > 0; const propertyType = props.elementDefinitionType.code; + const elementsContext = useContext(ElementsContext); + const defaultValue = useMemo(() => { + if (!isComplexTypeCode(propertyType)) { + return props.defaultValue; + } + + if (!isEmpty(props.defaultValue)) { + return props.defaultValue; + } + + const withDefaults = Object.create(null); + if (elementsContext.path === props.path) { + applyDefaultValuesToElement(withDefaults, elementsContext.elements); + } else { + const key = getPathDifference(elementsContext.path, props.path); + if (key === undefined) { + return props.defaultValue; + } + applyDefaultValuesToElement(withDefaults, elementsContext.elements, key); + } + + if (isPopulated(withDefaults)) { + return withDefaults; + } + + return props.defaultValue; + }, [propertyType, elementsContext.path, elementsContext.elements, props.path, props.defaultValue]); + if (!propertyType) { return
Property type not specified
; } @@ -326,6 +362,7 @@ export function ElementDefinitionTypeInput(props: ElementDefinitionTypeInputProp return ( { name: string; + /** The path identifies the element and is expressed as a "."-separated list of ancestor elements, beginning with the name of the resource or extension. */ path: string; defaultValue?: ValueType; onChange: ((value: ValueType, propName?: string) => void) | undefined; diff --git a/packages/react/src/ResourceTypeInput/ResourceTypeInput.tsx b/packages/react/src/ResourceTypeInput/ResourceTypeInput.tsx index 3e5e762a02..816353cb03 100644 --- a/packages/react/src/ResourceTypeInput/ResourceTypeInput.tsx +++ b/packages/react/src/ResourceTypeInput/ResourceTypeInput.tsx @@ -8,6 +8,7 @@ export interface ResourceTypeInputProps { readonly defaultValue?: ResourceType; readonly autoFocus?: boolean; readonly testId?: string; + readonly maxValues?: number; readonly onChange?: (value: ResourceType | undefined) => void; } @@ -35,8 +36,9 @@ export function ResourceTypeInput(props: ResourceTypeInputProps): JSX.Element { placeholder={props.placeholder} binding="https://medplum.com/fhir/ValueSet/resource-types" creatable={false} - maxValues={0} + maxValues={props.maxValues ?? 1} clearable={false} + withHelpText={false} /> ); } diff --git a/packages/react/src/SearchControl/SearchControl.test.tsx b/packages/react/src/SearchControl/SearchControl.test.tsx index 3497a86ff4..7a0c50cdfe 100644 --- a/packages/react/src/SearchControl/SearchControl.test.tsx +++ b/packages/react/src/SearchControl/SearchControl.test.tsx @@ -1,8 +1,9 @@ -import { Operator } from '@medplum/core'; -import { MockClient } from '@medplum/mock'; -import { act, fireEvent, render, screen, waitFor } from '../test-utils/render'; -import { MemoryRouter } from 'react-router-dom'; +import { Operator, SearchRequest } from '@medplum/core'; +import { Bundle } from '@medplum/fhirtypes'; +import { HomerSimpson, MockClient } from '@medplum/mock'; import { MedplumProvider } from '@medplum/react-hooks'; +import { MemoryRouter } from 'react-router-dom'; +import { act, fireEvent, render, screen, waitFor } from '../test-utils/render'; import { SearchControl, SearchControlProps } from './SearchControl'; describe('SearchControl', () => { @@ -17,11 +18,15 @@ describe('SearchControl', () => { jest.useRealTimers(); }); - async function setup(args: SearchControlProps): Promise { + async function setup(args: SearchControlProps, returnVal?: Bundle): Promise { + const medplum = new MockClient(); + if (returnVal) { + medplum.search = jest.fn().mockResolvedValue(returnVal); + } await act(async () => { render( - + @@ -874,4 +879,91 @@ describe('SearchControl', () => { await waitFor(() => screen.getByText('Homer Simpson')); expect(onLoad).toHaveBeenCalled(); }); + + describe('Pagination', () => { + const onLoad = jest.fn(); + const search: SearchRequest = { + resourceType: 'Patient', + count: 20, + offset: 0, + filters: [ + { + code: 'name', + operator: Operator.EQUALS, + value: 'Simpson', + }, + ], + fields: ['id', '_lastUpdated', 'name'], + }; + test('Single Page', async () => { + const props: SearchControlProps = { + search, + onLoad, + }; + await setup(props, { + resourceType: 'Bundle', + type: 'searchset', + total: 5, + entry: [{ resource: HomerSimpson }, ...Array(4).fill({ resourceType: 'Patient' })], + }); + await waitFor(() => screen.getByText('Homer Simpson')); + const element = screen.getByTestId('count-display'); + expect(element.textContent).toBe('1-5 of 5'); + }); + + test('Multiple Pages', async () => { + const props: SearchControlProps = { + search, + onLoad, + }; + await setup(props, { + resourceType: 'Bundle', + type: 'searchset', + total: 40, + entry: [{ resource: HomerSimpson }, ...Array(19).fill({ resourceType: 'Patient' })], + }); + await waitFor(() => screen.getByText('Homer Simpson')); + const element = screen.getByTestId('count-display'); + expect(element.textContent).toBe('1-20 of 40'); + }); + + test('Large Estimated Count', async () => { + const props: SearchControlProps = { + search, + onLoad, + }; + + await setup(props, { + resourceType: 'Bundle', + type: 'searchset', + total: 403091, + entry: [{ resource: HomerSimpson }, ...Array(19).fill({ resourceType: 'Patient' })], + }); + await waitFor(() => screen.getByText('Homer Simpson')); + const element = screen.getByTestId('count-display'); + expect(element.textContent).toBe('1-20 of 403,091'); + }); + + test('Large Estimated Count w/ High Offset', async () => { + const props: SearchControlProps = { + search: { ...search, offset: 200000, count: 20 }, + onLoad, + }; + + await setup(props, { + resourceType: 'Bundle', + type: 'searchset', + total: 403091, + entry: [{ resource: HomerSimpson }, ...Array(19).fill({ resourceType: 'Patient' })], + link: [ + { + relation: 'next', + url: '', + }, + ], + }); + await waitFor(() => screen.getByText('Homer Simpson')); + expect(screen.getByTestId('count-display').textContent).toBe('200,001-200,020 of 403,091'); + }); + }); }); diff --git a/packages/react/src/SearchControl/SearchControl.tsx b/packages/react/src/SearchControl/SearchControl.tsx index 73ed42b903..8a6c3091f1 100644 --- a/packages/react/src/SearchControl/SearchControl.tsx +++ b/packages/react/src/SearchControl/SearchControl.tsx @@ -333,8 +333,8 @@ export function SearchControl(props: SearchControlProps): JSX.Element {
{lastResult && ( - - {getStart(search, lastResult)}-{getEnd(search, lastResult)} + + {getStart(search, lastResult).toLocaleString()}-{getEnd(search, lastResult).toLocaleString()} {lastResult.total !== undefined && ` of ${search.total === 'estimate' ? '~' : ''}${lastResult.total?.toLocaleString()}`} @@ -577,7 +577,8 @@ function getPage(search: SearchRequest): number { function getTotalPages(search: SearchRequest, lastResult: Bundle): number { const pageSize = search.count ?? DEFAULT_SEARCH_COUNT; - return Math.ceil(getTotal(search, lastResult) / pageSize); + const total = getTotal(search, lastResult); + return Math.ceil(total / pageSize); } function getStart(search: SearchRequest, lastResult: Bundle): number { @@ -585,7 +586,7 @@ function getStart(search: SearchRequest, lastResult: Bundle): number { } function getEnd(search: SearchRequest, lastResult: Bundle): number { - return Math.min(getTotal(search, lastResult), ((search.offset ?? 0) + 1) * (search.count ?? DEFAULT_SEARCH_COUNT)); + return getStart(search, lastResult) + (lastResult.entry?.length ?? 0) - 1; } function getTotal(search: SearchRequest, lastResult: Bundle): number { diff --git a/packages/react/src/SearchFilterEditor/SearchFilterEditor.test.tsx b/packages/react/src/SearchFilterEditor/SearchFilterEditor.test.tsx index d234de6615..75a96633f1 100644 --- a/packages/react/src/SearchFilterEditor/SearchFilterEditor.test.tsx +++ b/packages/react/src/SearchFilterEditor/SearchFilterEditor.test.tsx @@ -122,7 +122,13 @@ describe('SearchFilterEditor', () => { fireEvent.click(screen.getByText('Edit')); }); - const input = screen.getAllByRole('searchbox')[1] as HTMLInputElement; + // Clear the existing value + const clearButton = screen.getByTitle('Clear all'); + await act(async () => { + fireEvent.click(clearButton); + }); + + const input = screen.getAllByRole('searchbox')[0] as HTMLInputElement; await act(async () => { fireEvent.change(input, { target: { value: 'Different' } }); }); diff --git a/packages/react/src/SearchFilterEditor/SearchFilterEditor.tsx b/packages/react/src/SearchFilterEditor/SearchFilterEditor.tsx index 6c8885192f..7c43b857fd 100644 --- a/packages/react/src/SearchFilterEditor/SearchFilterEditor.tsx +++ b/packages/react/src/SearchFilterEditor/SearchFilterEditor.tsx @@ -129,7 +129,7 @@ function FilterRowDisplay(props: FilterRowDisplayProps): JSX.Element | null { - diff --git a/packages/react/src/SearchFilterValueInput/SearchFilterValueInput.test.tsx b/packages/react/src/SearchFilterValueInput/SearchFilterValueInput.test.tsx index 1125ebd63b..020273af97 100644 --- a/packages/react/src/SearchFilterValueInput/SearchFilterValueInput.test.tsx +++ b/packages/react/src/SearchFilterValueInput/SearchFilterValueInput.test.tsx @@ -145,7 +145,13 @@ describe('SearchFilterValueInput', () => { await waitFor(() => screen.getByText('Test Organization')); }); - const input = screen.getAllByRole('searchbox')[1] as HTMLInputElement; + // Clear the existing value + const clearButton = screen.getByTitle('Clear all'); + await act(async () => { + fireEvent.click(clearButton); + }); + + const input = screen.getAllByRole('searchbox')[0] as HTMLInputElement; await act(async () => { fireEvent.change(input, { target: { value: 'Different' } }); }); diff --git a/packages/react/src/SliceInput/SliceInput.tsx b/packages/react/src/SliceInput/SliceInput.tsx new file mode 100644 index 0000000000..bec86703a0 --- /dev/null +++ b/packages/react/src/SliceInput/SliceInput.tsx @@ -0,0 +1,128 @@ +import { Group, Stack } from '@mantine/core'; +import { + ElementsContextType, + InternalSchemaElement, + SliceDefinitionWithTypes, + buildElementsContext, + getPropertyDisplayName, + isEmpty, + isPopulated, +} from '@medplum/core'; +import { OperationOutcome } from '@medplum/fhirtypes'; +import { useContext, useMemo, useState } from 'react'; +import { ElementsContext } from '../ElementsInput/ElementsInput.utils'; +import { FormSection } from '../FormSection/FormSection'; +import { ElementDefinitionTypeInput } from '../ResourcePropertyInput/ResourcePropertyInput'; +import { ArrayAddButton } from '../buttons/ArrayAddButton'; +import { ArrayRemoveButton } from '../buttons/ArrayRemoveButton'; +import { killEvent } from '../utils/dom'; +import classes from '../ResourceArrayInput/ResourceArrayInput.module.css'; +import { maybeWrapWithContext } from '../utils/maybeWrapWithContext'; + +export interface SliceInputProps { + readonly path: string; + readonly slice: SliceDefinitionWithTypes; + readonly property: InternalSchemaElement; + readonly defaultValue: any[]; + readonly onChange: (newValue: any[]) => void; + readonly outcome?: OperationOutcome; + readonly testId?: string; +} + +export function SliceInput(props: SliceInputProps): JSX.Element | null { + const { slice, property } = props; + const [values, setValues] = useState(props.defaultValue); + + const sliceElements = slice.typeSchema?.elements ?? slice.elements; + + const parentElementsContextValue = useContext(ElementsContext); + + const contextValue: ElementsContextType | undefined = useMemo(() => { + if (isPopulated(sliceElements)) { + return buildElementsContext({ + parentContext: parentElementsContextValue, + elements: sliceElements, + path: props.path, + profileUrl: slice.typeSchema?.url, + }); + } + console.assert(false, 'Expected sliceElements to always be populated', props.path); + return undefined; + }, [parentElementsContextValue, props.path, slice.typeSchema?.url, sliceElements]); + + function setValuesWrapper(newValues: any[]): void { + setValues(newValues); + if (props.onChange) { + props.onChange(newValues); + } + } + + const required = slice.min > 0; + + // this is a bit of a hack targeted at nested extensions; indentation would ideally be controlled elsewhere + // e.g. USCorePatientProfile -> USCoreEthnicityExtension -> {ombCategory, detailed, text} + const indentedStack = isEmpty(slice.elements); + const propertyDisplayName = getPropertyDisplayName(slice.name); + return maybeWrapWithContext( + ElementsContext.Provider, + contextValue, + + + {values.map((value, valueIndex) => { + return ( + +
+ { + const newValues = [...values]; + newValues[valueIndex] = newValue; + setValuesWrapper(newValues); + }} + outcome={props.outcome} + min={slice.min} + max={slice.max} + binding={slice.binding} + path={props.path} + /> +
+ {values.length > slice.min && ( + { + killEvent(e); + const newValues = [...values]; + newValues.splice(valueIndex, 1); + setValuesWrapper(newValues); + }} + /> + )} +
+ ); + })} + {values.length < slice.max && ( + + { + killEvent(e); + const newValues = [...values, undefined]; + setValuesWrapper(newValues); + }} + testId={props.testId && `${props.testId}-add`} + /> + + )} +
+
+ ); +} diff --git a/packages/react/src/ValueSetAutocomplete/ValueSetAutocomplete.tsx b/packages/react/src/ValueSetAutocomplete/ValueSetAutocomplete.tsx index 61850fcff0..3dae42846e 100644 --- a/packages/react/src/ValueSetAutocomplete/ValueSetAutocomplete.tsx +++ b/packages/react/src/ValueSetAutocomplete/ValueSetAutocomplete.tsx @@ -1,7 +1,8 @@ +import { Group, Text } from '@mantine/core'; import { ValueSetExpandParams } from '@medplum/core'; import { ValueSetExpansionContains } from '@medplum/fhirtypes'; import { useMedplum } from '@medplum/react-hooks'; -import { useCallback } from 'react'; +import { forwardRef, useCallback } from 'react'; import { AsyncAutocomplete, AsyncAutocompleteOption, @@ -14,6 +15,7 @@ export interface ValueSetAutocompleteProps readonly creatable?: boolean; readonly clearable?: boolean; readonly expandParams?: Partial; + readonly withHelpText?: boolean; } function toKey(element: ValueSetExpansionContains): string { @@ -53,7 +55,7 @@ function createValue(input: string): ValueSetExpansionContains { */ export function ValueSetAutocomplete(props: ValueSetAutocompleteProps): JSX.Element { const medplum = useMedplum(); - const { binding, creatable, clearable, expandParams, ...rest } = props; + const { binding, creatable, clearable, expandParams, withHelpText, ...rest } = props; const loadValues = useCallback( async (input: string, signal: AbortSignal): Promise => { @@ -89,6 +91,24 @@ export function ValueSetAutocomplete(props: ValueSetAutocompleteProps): JSX.Elem toOption={toOption} loadOptions={loadValues} onCreate={createValue} + itemComponent={withHelpText ? ItemComponent : undefined} /> ); } + +const ItemComponent = forwardRef>( + ({ label, resource, ...others }: AsyncAutocompleteOption, ref) => { + return ( +
+ +
+ {label} + + {`${resource.system}#${resource.code}`} + +
+
+
+ ); + } +); diff --git a/packages/react/src/auth/ChooseProfileForm.stories.tsx b/packages/react/src/auth/ChooseProfileForm.stories.tsx new file mode 100644 index 0000000000..e9ec8306bc --- /dev/null +++ b/packages/react/src/auth/ChooseProfileForm.stories.tsx @@ -0,0 +1,46 @@ +import { ProjectMembership } from '@medplum/fhirtypes'; +import { Meta } from '@storybook/react'; +import { Document } from '../Document/Document'; +import { ChooseProfileForm } from './ChooseProfileForm'; + +export default { + title: 'Medplum/Auth/ChooseProfileForm', + component: ChooseProfileForm, +} as Meta; + +export function FewMemberships(): JSX.Element { + return ( + + + + ); +} + +export function ManyMemberships(): JSX.Element { + const memberships = []; + for (let i = 1; i <= 30; i++) { + memberships.push(makeMembership('membership' + i, 'Project ' + i, 'Profile ' + i)); + } + return ( + + + + ); +} + +function makeMembership(id: string, projectName: string, profileName: string): ProjectMembership { + return { + resourceType: 'ProjectMembership', + id, + project: { reference: 'Project/' + projectName, display: projectName }, + user: { reference: 'User/x', display: 'x' }, + profile: { reference: 'Practitioner/' + profileName, display: profileName }, + }; +} diff --git a/packages/react/src/auth/ChooseProfileForm.test.tsx b/packages/react/src/auth/ChooseProfileForm.test.tsx new file mode 100644 index 0000000000..e67dbb409c --- /dev/null +++ b/packages/react/src/auth/ChooseProfileForm.test.tsx @@ -0,0 +1,84 @@ +import { ProjectMembership } from '@medplum/fhirtypes'; +import { MockClient } from '@medplum/mock'; +import { MedplumProvider } from '@medplum/react-hooks'; +import { act } from 'react-dom/test-utils'; +import { fireEvent, render, screen } from '../test-utils/render'; +import { ChooseProfileForm } from './ChooseProfileForm'; + +describe('ChooseProfileForm', () => { + test('Renders', () => { + render( + + + + ); + + expect(screen.getByText('Choose profile')).toBeInTheDocument(); + expect(screen.getByText('Prod')).toBeInTheDocument(); + expect(screen.getByText('Staging')).toBeInTheDocument(); + }); + + test('Filters', async () => { + render( + + + + ); + + const input = screen.getByPlaceholderText('Search') as HTMLInputElement; + await act(async () => { + fireEvent.change(input, { target: { value: 'prod' } }); + }); + + expect(screen.getByText('Prod')).toBeInTheDocument(); + expect(screen.queryByText('Staging')).not.toBeInTheDocument(); + }); + + test('No matches', async () => { + render( + + + + ); + + const input = screen.getByPlaceholderText('Search') as HTMLInputElement; + await act(async () => { + fireEvent.change(input, { target: { value: 'xyz' } }); + }); + + expect(screen.queryByText('Prod')).not.toBeInTheDocument(); + expect(screen.queryByText('Staging')).not.toBeInTheDocument(); + expect(screen.getByText('Nothing found...')).toBeInTheDocument(); + }); +}); + +function makeMembership(id: string, projectName: string, profileName: string): ProjectMembership { + return { + resourceType: 'ProjectMembership', + id, + project: { reference: 'Project/' + projectName, display: projectName }, + user: { reference: 'User/x', display: 'x' }, + profile: { reference: 'Practitioner/' + profileName, display: profileName }, + }; +} diff --git a/packages/react/src/auth/ChooseProfileForm.tsx b/packages/react/src/auth/ChooseProfileForm.tsx index 7e97ef2563..2478a991c2 100644 --- a/packages/react/src/auth/ChooseProfileForm.tsx +++ b/packages/react/src/auth/ChooseProfileForm.tsx @@ -1,9 +1,9 @@ -import { Avatar, Center, Group, Stack, Text, Title, UnstyledButton } from '@mantine/core'; +import { Avatar, Combobox, Flex, Group, Stack, Text, TextInput, Title, useCombobox } from '@mantine/core'; import { LoginAuthenticationResponse, normalizeOperationOutcome } from '@medplum/core'; import { OperationOutcome, ProjectMembership } from '@medplum/fhirtypes'; +import { useMedplum } from '@medplum/react-hooks'; import { useState } from 'react'; import { Logo } from '../Logo/Logo'; -import { useMedplum } from '@medplum/react-hooks'; import { OperationOutcomeAlert } from '../OperationOutcomeAlert/OperationOutcomeAlert'; export interface ChooseProfileFormProps { @@ -14,40 +14,78 @@ export interface ChooseProfileFormProps { export function ChooseProfileForm(props: ChooseProfileFormProps): JSX.Element { const medplum = useMedplum(); + const combobox = useCombobox(); + const [search, setSearch] = useState(''); const [outcome, setOutcome] = useState(); + + function filterDisplay(display: string | undefined): boolean { + return !!display?.toLowerCase()?.includes(search.toLowerCase()); + } + + function filterMembership(membership: ProjectMembership): boolean { + return filterDisplay(membership.profile?.display) || filterDisplay(membership.project?.display); + } + + function handleValueSelect(membershipId: string): void { + medplum + .post('auth/profile', { + login: props.login, + profile: membershipId, + }) + .then(props.handleAuthResponse) + .catch((err) => setOutcome(normalizeOperationOutcome(err))); + } + + const options = props.memberships + .filter(filterMembership) + .slice(0, 10) + .map((item) => ( + + + + )); + return ( -
+ Choose profile -
+ - {props.memberships.map((membership: ProjectMembership) => ( - { - medplum - .post('auth/profile', { - login: props.login, - profile: membership.id, - }) - .then(props.handleAuthResponse) - .catch((err) => setOutcome(normalizeOperationOutcome(err))); - }} - > - - -
- - {membership.profile?.display} - - - {membership.project?.display} - -
-
-
- ))} + + + { + setSearch(event.currentTarget.value); + combobox.updateSelectedOptionIndex(); + }} + /> + + +
+ + {options.length > 0 ? options : Nothing found...} + +
+
); } + +function SelectOption(membership: ProjectMembership): JSX.Element { + return ( + + +
+ + {membership.profile?.display} + + + {membership.project?.display} + +
+
+ ); +} diff --git a/packages/react/src/auth/RegisterForm.stories.tsx b/packages/react/src/auth/RegisterForm.stories.tsx index 69f404870b..b56a677e94 100644 --- a/packages/react/src/auth/RegisterForm.stories.tsx +++ b/packages/react/src/auth/RegisterForm.stories.tsx @@ -4,7 +4,7 @@ import { RegisterForm } from '../auth/RegisterForm'; import { Logo } from '../Logo/Logo'; export default { - title: 'Medplum/RegisterForm', + title: 'Medplum/Auth/RegisterForm', component: RegisterForm, } as Meta; diff --git a/packages/react/src/auth/SignInForm.stories.tsx b/packages/react/src/auth/SignInForm.stories.tsx index f9b1e7c78d..b38b58c9e8 100644 --- a/packages/react/src/auth/SignInForm.stories.tsx +++ b/packages/react/src/auth/SignInForm.stories.tsx @@ -4,7 +4,7 @@ import { SignInForm } from './SignInForm'; import { Logo } from '../Logo/Logo'; export default { - title: 'Medplum/SignInForm', + title: 'Medplum/Auth/SignInForm', component: SignInForm, } as Meta; diff --git a/packages/react/src/buttons/ArrayAddButton.tsx b/packages/react/src/buttons/ArrayAddButton.tsx new file mode 100644 index 0000000000..254818d9b6 --- /dev/null +++ b/packages/react/src/buttons/ArrayAddButton.tsx @@ -0,0 +1,30 @@ +import { Button, ActionIcon } from '@mantine/core'; +import { IconCirclePlus } from '@tabler/icons-react'; + +export interface ArrayAddButtonProps { + readonly propertyDisplayName?: string; + readonly onClick: React.MouseEventHandler; + readonly testId?: string; +} + +export function ArrayAddButton({ propertyDisplayName, onClick, testId }: ArrayAddButtonProps): JSX.Element { + const text = propertyDisplayName ? `Add ${propertyDisplayName}` : 'Add'; + + return propertyDisplayName ? ( + + ) : ( + + + + ); +} diff --git a/packages/react/src/buttons/ArrayRemoveButton.tsx b/packages/react/src/buttons/ArrayRemoveButton.tsx new file mode 100644 index 0000000000..bc78478711 --- /dev/null +++ b/packages/react/src/buttons/ArrayRemoveButton.tsx @@ -0,0 +1,22 @@ +import { ActionIcon } from '@mantine/core'; +import { IconCircleMinus } from '@tabler/icons-react'; + +export interface ArrayRemoveButtonProps { + readonly propertyDisplayName?: string; + readonly onClick: React.MouseEventHandler; + readonly testId?: string; +} + +export function ArrayRemoveButton({ propertyDisplayName, onClick, testId }: ArrayRemoveButtonProps): JSX.Element { + return ( + + + + ); +} diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 7c717f8c3c..b2a992ce18 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -51,6 +51,7 @@ export * from './MedplumLink/MedplumLink'; export * from './MoneyDisplay/MoneyDisplay'; export * from './MoneyInput/MoneyInput'; export * from './NoteDisplay/NoteDisplay'; +export * from './NotificationIcon/NotificationIcon'; export * from './OperationOutcomeAlert/OperationOutcomeAlert'; export * from './Panel/Panel'; export * from './PatientSummary/PatientSummary'; diff --git a/packages/react/src/utils/maybeWrapWithContext.tsx b/packages/react/src/utils/maybeWrapWithContext.tsx new file mode 100644 index 0000000000..c21d70acbb --- /dev/null +++ b/packages/react/src/utils/maybeWrapWithContext.tsx @@ -0,0 +1,11 @@ +export function maybeWrapWithContext( + ContextProvider: React.Context['Provider'], + contextValue: T | undefined, + contents: JSX.Element +): JSX.Element { + if (contextValue !== undefined) { + return {contents}; + } + + return contents; +} diff --git a/packages/server/medplum.config.json b/packages/server/medplum.config.json index 9903513fc2..2ef2451f46 100644 --- a/packages/server/medplum.config.json +++ b/packages/server/medplum.config.json @@ -22,6 +22,8 @@ "maxJsonSize": "1mb", "botLambdaRoleArn": "", "botLambdaLayerName": "medplum-bot-layer", + "vmContextBotsEnabled": true, + "defaultBotRuntimeVersion": "vmcontext", "allowedOrigins": "*", "introspectionEnabled": true, "database": { diff --git a/packages/server/package.json b/packages/server/package.json index 1c14f50416..987ae7f9d2 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@medplum/server", - "version": "3.0.2", + "version": "3.0.4", "description": "Medplum Server", "homepage": "https://www.medplum.com/", "bugs": { @@ -21,19 +21,19 @@ "test": "jest --runInBand" }, "dependencies": { - "@aws-sdk/client-cloudwatch-logs": "3.501.0", - "@aws-sdk/client-lambda": "3.501.0", - "@aws-sdk/client-s3": "3.501.0", - "@aws-sdk/client-secrets-manager": "3.501.0", - "@aws-sdk/client-sesv2": "3.501.0", - "@aws-sdk/client-ssm": "3.501.0", + "@aws-sdk/client-cloudwatch-logs": "3.515.0", + "@aws-sdk/client-lambda": "3.516.0", + "@aws-sdk/client-s3": "3.515.0", + "@aws-sdk/client-secrets-manager": "3.515.0", + "@aws-sdk/client-sesv2": "3.515.0", + "@aws-sdk/client-ssm": "3.515.0", "@aws-sdk/cloudfront-signer": "3.496.0", - "@aws-sdk/lib-storage": "3.501.0", - "@aws-sdk/types": "3.496.0", - "@medplum/core": "*", - "@medplum/definitions": "*", - "@medplum/fhir-router": "*", - "@opentelemetry/auto-instrumentations-node": "0.40.3", + "@aws-sdk/lib-storage": "3.515.0", + "@aws-sdk/types": "3.515.0", + "@medplum/core": "3.0.4", + "@medplum/definitions": "3.0.4", + "@medplum/fhir-router": "3.0.4", + "@opentelemetry/auto-instrumentations-node": "0.41.1", "@opentelemetry/exporter-metrics-otlp-proto": "0.48.0", "@opentelemetry/exporter-trace-otlp-proto": "0.48.0", "@opentelemetry/sdk-metrics": "1.21.0", @@ -42,14 +42,14 @@ "@smithy/util-stream": "2.1.1", "bcryptjs": "2.4.3", "body-parser": "1.20.2", - "bullmq": "5.1.5", + "bullmq": "5.2.1", "bytes": "3.1.2", "compression": "1.7.4", "cookie-parser": "1.4.6", "cors": "2.8.5", "cron-validator": "1.3.1", "dataloader": "2.2.2", - "dotenv": "16.4.1", + "dotenv": "16.4.4", "express": "4.18.2", "express-rate-limit": "7.1.5", "express-validator": "7.0.1", @@ -57,10 +57,10 @@ "hibp": "14.0.3", "http-graceful-shutdown": "^3.1.13", "ioredis": "5.3.2", - "jose": "5.2.0", + "jose": "5.2.2", "jszip": "3.10.1", "node-fetch": "2.7.0", - "nodemailer": "6.9.8", + "nodemailer": "6.9.9", "otplib": "12.0.1", "pg": "8.11.3", "pg-cursor": "2.10.3", @@ -72,7 +72,7 @@ }, "devDependencies": { "@jest/test-sequencer": "29.7.0", - "@medplum/fhirtypes": "*", + "@medplum/fhirtypes": "3.0.4", "@types/bcryptjs": "2.4.6", "@types/body-parser": "1.19.5", "@types/bytes": "3.1.4", @@ -84,7 +84,7 @@ "@types/ioredis": "4.28.10", "@types/json-schema": "7.0.15", "@types/mailparser": "3.4.4", - "@types/node": "20.11.8", + "@types/node": "20.11.19", "@types/node-fetch": "2.6.11", "@types/nodemailer": "6.4.14", "@types/pg": "8.11.0", @@ -93,11 +93,11 @@ "@types/supertest": "6.0.2", "@types/ua-parser-js": "0.7.39", "@types/uuid": "9.0.8", - "@types/validator": "13.11.8", + "@types/validator": "13.11.9", "@types/ws": "8.5.10", "aws-sdk-client-mock": "3.0.1", "aws-sdk-client-mock-jest": "3.0.1", - "mailparser": "3.6.6", + "mailparser": "3.6.7", "openapi3-ts": "4.2.1", "set-cookie-parser": "2.6.0", "supertest": "6.3.4", diff --git a/packages/server/src/__mocks__/ioredis.ts b/packages/server/src/__mocks__/ioredis.ts index 665c1980fe..b1f455ec90 100644 --- a/packages/server/src/__mocks__/ioredis.ts +++ b/packages/server/src/__mocks__/ioredis.ts @@ -2,6 +2,44 @@ const values = new Map(); const sets = new Map>(); const subscribers = new Map>(); +class ReplyError extends Error {} + +type Command = { + command: 'srem' | 'del'; + args: string[]; +}; + +type Result = number | null; + +class MultiClient { + private redis: Redis; + private commandQueue: Command[]; + + constructor(redis: Redis) { + this.redis = redis; + this.commandQueue = []; + } + + srem(setKey: string, members: string[]): this { + this.commandQueue.push({ command: 'srem', args: [setKey, ...members] }); + return this; + } + + del(setKey: string | string[]): this { + this.commandQueue.push({ command: 'del', args: [...setKey] }); + return this; + } + + async exec(): Promise { + const results = [] as Result[]; + for (const cmd of this.commandQueue) { + results.push((await this.redis[cmd.command](cmd.args[0], cmd.args.slice(1))) ?? null); + } + this.commandQueue = []; + return results; + } +} + class Redis { private callback?: (...args: any[]) => void; @@ -18,12 +56,18 @@ class Redis { return 'PONG'; } - async get(key: string): Promise { - return values.get(key); + async get(key: string): Promise { + return values.get(key) ?? null; } - async mget(...keys: string[]): Promise<(string | undefined)[]> { - return keys.map((key) => values.get(key)); + async mget(...keys: string[] | string[][]): Promise<(string | null)[]> { + let normalizedKeys: string[]; + if (keys.length === 1 && Array.isArray(keys[0])) { + normalizedKeys = keys[0]; + } else { + normalizedKeys = keys as string[]; + } + return normalizedKeys.map((key) => values.get(key) ?? null); } async set(key: string, value: string, ...args: (string | number)[]): Promise { @@ -98,12 +142,16 @@ class Redis { } async sadd(setKey: string, ...members: string[]): Promise { + const existingValue = sets.get(setKey); let keySet: Set; - if (!sets.has(setKey)) { + if (existingValue) { + if (!(existingValue instanceof Set)) { + throw new ReplyError('WRONGTYPE Operation against a key holding the wrong kind of value'); + } + keySet = existingValue; + } else { keySet = new Set(); sets.set(setKey, keySet); - } else { - keySet = sets.get(setKey) as Set; } let added = 0; for (const member of members) { @@ -116,12 +164,53 @@ class Redis { return added; } + async srem(setKey: string, member: string[]): Promise; + async srem(setKey: string, ...members: string[] | string[][]): Promise { + const keySet = sets.get(setKey); + if (!keySet) { + return 0; + } + if (!(keySet instanceof Set)) { + throw new ReplyError('WRONGTYPE Operation against a key holding the wrong kind of value'); + } + let normalizedMembers: string[]; + if (Array.isArray(members[0])) { + normalizedMembers = members[0]; + } else { + normalizedMembers = members as string[]; + } + let removed = 0; + for (const member of normalizedMembers) { + if (keySet.has(member)) { + keySet.delete(member); + removed += 1; + } + } + return removed; + } + async smembers(setKey: string): Promise { - const set = sets.get(setKey); - if (!set) { + const keySet = sets.get(setKey); + if (!keySet) { return []; } - return Array.from(set.keys()); + if (!(keySet instanceof Set)) { + throw new ReplyError('WRONGTYPE Operation against a key holding the wrong kind of value'); + } + return Array.from(keySet.keys()); + } + + async smismember(setKey: string, ...members: string[]): Promise { + const keySet = sets.get(setKey); + if (!keySet) { + return new Array(members.length).fill(0); + } + if (!(keySet instanceof Set)) { + throw new ReplyError('WRONGTYPE Operation against a key holding the wrong kind of value'); + } + return members.map((member) => { + return keySet.has(member) ? 1 : 0; + }); } async scard(setKey: string): Promise { @@ -131,6 +220,10 @@ class Redis { } return set.size; } + + multi(): MultiClient { + return new MultiClient(this); + } } export default Redis; diff --git a/packages/server/src/admin/bot.ts b/packages/server/src/admin/bot.ts index 03c631f8e4..a458dee35b 100644 --- a/packages/server/src/admin/bot.ts +++ b/packages/server/src/admin/bot.ts @@ -3,9 +3,10 @@ import { AccessPolicy, Binary, Bot, Project, ProjectMembership, Reference } from import { Request, Response } from 'express'; import { body } from 'express-validator'; import { Readable } from 'stream'; -import { Repository, systemRepo } from '../fhir/repo'; -import { getBinaryStorage } from '../fhir/storage'; +import { getConfig } from '../config'; import { getAuthenticatedContext } from '../context'; +import { Repository, getSystemRepo } from '../fhir/repo'; +import { getBinaryStorage } from '../fhir/storage'; import { makeValidationMiddleware } from '../util/validator'; export const createBotValidator = makeValidationMiddleware([ @@ -54,7 +55,7 @@ export async function createBot(repo: Repository, request: CreateBotRequest): Pr resourceType: 'Bot', name: request.name, description: request.description, - runtimeVersion: request.runtimeVersion ?? 'awslambda', + runtimeVersion: request.runtimeVersion ?? getConfig().defaultBotRuntimeVersion, sourceCode: { contentType, title: filename, @@ -62,6 +63,7 @@ export async function createBot(repo: Repository, request: CreateBotRequest): Pr }, }); + const systemRepo = getSystemRepo(); await systemRepo.createResource({ meta: { project: request.project.id, diff --git a/packages/server/src/admin/client.ts b/packages/server/src/admin/client.ts index 723898af77..e8cc026764 100644 --- a/packages/server/src/admin/client.ts +++ b/packages/server/src/admin/client.ts @@ -9,9 +9,9 @@ import { } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { body } from 'express-validator'; -import { Repository, systemRepo } from '../fhir/repo'; -import { generateSecret } from '../oauth/keys'; import { getAuthenticatedContext } from '../context'; +import { Repository, getSystemRepo } from '../fhir/repo'; +import { generateSecret } from '../oauth/keys'; import { makeValidationMiddleware } from '../util/validator'; export const createClientValidator = makeValidationMiddleware([ @@ -57,6 +57,7 @@ export async function createClient(repo: Repository, request: CreateClientReques identityProvider: request.identityProvider, }); + const systemRepo = getSystemRepo(); await systemRepo.createResource({ meta: { project: request.project.id, diff --git a/packages/server/src/admin/invite.ts b/packages/server/src/admin/invite.ts index febeb75f76..cf7b5bf725 100644 --- a/packages/server/src/admin/invite.ts +++ b/packages/server/src/admin/invite.ts @@ -16,9 +16,9 @@ import Mail from 'nodemailer/lib/mailer'; import { resetPassword } from '../auth/resetpassword'; import { bcryptHashPassword, createProfile, createProjectMembership } from '../auth/utils'; import { getConfig } from '../config'; -import { getAuthenticatedContext } from '../context'; +import { getAuthenticatedContext, getLogger } from '../context'; import { sendEmail } from '../email/email'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo, Repository } from '../fhir/repo'; import { sendResponse } from '../fhir/response'; import { generateSecret } from '../oauth/keys'; import { getUserByEmailInProject, getUserByEmailWithoutProject } from '../oauth/utils'; @@ -43,13 +43,14 @@ export async function inviteHandler(req: Request, res: Response): Promise const inviteRequest = { ...req.body } as ServerInviteRequest; const { projectId } = req.params; if (ctx.project.superAdmin) { + const systemRepo = getSystemRepo(); inviteRequest.project = await systemRepo.readResource('Project', projectId as string); } else { inviteRequest.project = ctx.project; } const { membership } = await inviteUser(inviteRequest); - return sendResponse(res, allOk, membership); + return sendResponse(req, res, allOk, membership); } export interface ServerInviteRequest extends InviteRequest { @@ -63,7 +64,9 @@ export interface ServerInviteResponse { } export async function inviteUser(request: ServerInviteRequest): Promise { - const ctx = getAuthenticatedContext(); + const systemRepo = getSystemRepo(); + const logger = getLogger(); + if (request.email) { request.email = request.email.toLowerCase(); } @@ -84,15 +87,15 @@ export async function inviteUser(request: ServerInviteRequest): Promise { project = createReference(request.project); } + const systemRepo = getSystemRepo(); return systemRepo.createResource({ resourceType: 'User', firstName, @@ -164,6 +169,7 @@ async function createUser(request: ServerInviteRequest): Promise { async function searchForExistingProfile(request: ServerInviteRequest): Promise { const { project, resourceType, membership, email } = request; + const systemRepo = getSystemRepo(); if (membership?.profile) { const result = await systemRepo.readReference(membership.profile); @@ -198,13 +204,14 @@ async function searchForExistingProfile(request: ServerInviteRequest): Promise

, upsert: boolean ): Promise { - const existingMembership = await searchForExistingMembership(user, project); + const existingMembership = await searchForExistingMembership(systemRepo, user, project); if (existingMembership) { if (!upsert) { throw new OperationOutcomeError(badRequest('User is already a member of this project')); @@ -231,7 +238,11 @@ async function createOrUpdateProjectMembership( return createProjectMembership(user, project, profile, membershipTemplate); } -async function searchForExistingMembership(user: User, project: Project): Promise { +async function searchForExistingMembership( + systemRepo: Repository, + user: User, + project: Project +): Promise { return systemRepo.searchOne({ resourceType: 'ProjectMembership', filters: [ @@ -250,6 +261,7 @@ async function searchForExistingMembership(user: User, project: Project): Promis } async function sendInviteEmail( + systemRepo: Repository, request: ServerInviteRequest, user: User, existing: boolean, diff --git a/packages/server/src/admin/super.test.ts b/packages/server/src/admin/super.test.ts index 06604d5480..0b1ef7b6d3 100644 --- a/packages/server/src/admin/super.test.ts +++ b/packages/server/src/admin/super.test.ts @@ -6,13 +6,13 @@ import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { registerNew } from '../auth/register'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { AuthenticatedRequestContext, requestContextStore } from '../context'; +import { getSystemRepo } from '../fhir/repo'; import { generateAccessToken } from '../oauth/keys'; import { rebuildR4SearchParameters } from '../seeds/searchparameters'; import { rebuildR4StructureDefinitions } from '../seeds/structuredefinitions'; -import { createTestProject, waitForAsyncJob, withTestContext } from '../test.setup'; -import { AuthenticatedRequestContext, requestContextStore } from '../context'; import { rebuildR4ValueSets } from '../seeds/valuesets'; +import { createTestProject, waitForAsyncJob, withTestContext } from '../test.setup'; jest.mock('../seeds/valuesets'); jest.mock('../seeds/structuredefinitions'); @@ -30,10 +30,9 @@ describe('Super Admin routes', () => { await initApp(app, config); requestContextStore.enterWith(AuthenticatedRequestContext.system()); - ({ project, client } = await createTestProject()); + ({ project, client } = await createTestProject({ withClient: true, superAdmin: true })); - // Mark the project as a "Super Admin" project - await systemRepo.updateResource({ ...project, superAdmin: true }); + const systemRepo = getSystemRepo(); const practitioner1 = await systemRepo.createResource({ resourceType: 'Practitioner' }); @@ -142,24 +141,6 @@ describe('Super Admin routes', () => { await waitForAsyncJob(res.headers['content-location'], app, adminAccessToken); }); - test('Rebuild ValueSetElements as super admin with respond-async error', async () => { - const err = new Error('createvalueSet test error'); - (rebuildR4ValueSets as unknown as jest.Mock).mockImplementationOnce((): Promise => { - return Promise.reject(err); - }); - - const res = await request(app) - .post('/admin/super/valuesets') - .set('Authorization', 'Bearer ' + adminAccessToken) - .set('Prefer', 'respond-async') - .type('json') - .send({}); - - expect(res.status).toEqual(202); - const job = await waitForAsyncJob(res.headers['content-location'], app, adminAccessToken); - expect(job.status).toEqual('error'); - }); - test('Rebuild ValueSetElements access denied', async () => { const res = await request(app) .post('/admin/super/valuesets') @@ -334,26 +315,6 @@ describe('Super Admin routes', () => { await waitForAsyncJob(res.headers['content-location'], app, adminAccessToken); }); - test('Reindex with respond-async error', async () => { - const err = new Error('reindex test error'); - jest.spyOn(systemRepo, 'reindexResourceType').mockImplementationOnce((): Promise => { - return Promise.reject(err); - }); - - const res = await request(app) - .post('/admin/super/reindex') - .set('Authorization', 'Bearer ' + adminAccessToken) - .set('Prefer', 'respond-async') - .type('json') - .send({ - resourceType: 'PaymentNotice', - }); - - expect(res.status).toEqual(202); - const job = await waitForAsyncJob(res.headers['content-location'], app, adminAccessToken); - expect(job.status).toEqual('error'); - }); - test('Rebuild compartments access denied', async () => { const res = await request(app) .post('/admin/super/compartments') @@ -405,26 +366,6 @@ describe('Super Admin routes', () => { expect(res.headers['content-location']).toBeDefined(); }); - test('Rebuild compartments with respond-async error', async () => { - const err = new Error('rebuildCompartmentsForResourceType test error'); - jest.spyOn(systemRepo, 'rebuildCompartmentsForResourceType').mockImplementationOnce((): Promise => { - return Promise.reject(err); - }); - - const res = await request(app) - .post('/admin/super/compartments') - .set('Authorization', 'Bearer ' + adminAccessToken) - .set('Prefer', 'respond-async') - .type('json') - .send({ - resourceType: 'PaymentNotice', - }); - - expect(res.status).toEqual(202); - const job = await waitForAsyncJob(res.headers['content-location'], app, adminAccessToken); - expect(job.status).toEqual('error'); - }); - test('Set password access denied', async () => { const res = await request(app) .post('/admin/super/setpassword') diff --git a/packages/server/src/admin/super.ts b/packages/server/src/admin/super.ts index 5e689b3e70..707fde50f9 100644 --- a/packages/server/src/admin/super.ts +++ b/packages/server/src/admin/super.ts @@ -16,7 +16,7 @@ import { AuthenticatedRequestContext, getAuthenticatedContext } from '../context import { getDatabasePool } from '../database'; import { AsyncJobExecutor } from '../fhir/operations/utils/asyncjobexecutor'; import { invalidRequest, sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import * as dataMigrations from '../migrations/data'; import { authenticateRequest } from '../oauth/middleware'; import { getUserByEmail } from '../oauth/utils'; @@ -37,7 +37,10 @@ superAdminRouter.post( requireSuperAdmin(); requireAsync(req); - await sendAsyncResponse(req, res, () => rebuildR4ValueSets()); + await sendAsyncResponse(req, res, async () => { + await rebuildR4ValueSets(); + await getSystemRepo().reindexResourceType('CodeSystem'); + }); }) ); @@ -80,6 +83,7 @@ superAdminRouter.post( validateResourceType(resourceType); await sendAsyncResponse(req, res, async () => { + const systemRepo = getSystemRepo(); await systemRepo.reindexResourceType(resourceType); }); }) @@ -98,6 +102,7 @@ superAdminRouter.post( validateResourceType(resourceType); await sendAsyncResponse(req, res, async () => { + const systemRepo = getSystemRepo(); await systemRepo.rebuildCompartmentsForResourceType(resourceType); }); }) @@ -203,6 +208,7 @@ superAdminRouter.post( requireAsync(req); await sendAsyncResponse(req, res, async () => { + const systemRepo = getSystemRepo(); const client = getDatabasePool(); const result = await client.query('SELECT "dataVersion" FROM "DatabaseMigration"'); const version = result.rows[0]?.dataVersion as number; diff --git a/packages/server/src/agent/websockets.test.ts b/packages/server/src/agent/websockets.test.ts index 3154291de6..eec0385fc0 100644 --- a/packages/server/src/agent/websockets.test.ts +++ b/packages/server/src/agent/websockets.test.ts @@ -21,7 +21,7 @@ describe('Agent WebSockets', () => { config.vmContextBotsEnabled = true; server = await initApp(app, config); - accessToken = await initTestAuth({}, { admin: true }); + accessToken = await initTestAuth({ membership: { admin: true } }); await new Promise((resolve) => { server.listen(0, 'localhost', 511, resolve); @@ -371,7 +371,7 @@ describe('Agent WebSockets', () => { 'NK1|1|JONES^BARBARA^K|SPO|||||20011105\r' + 'PV1|1|I|2000^2012^01||||004777^LEBAUER^SIDNEY^J.|||SUR||-||1|A0-', }); - pushRequest.then(() => true); + pushRequest.then(() => undefined); }) .expectText((str) => { const message = JSON.parse(str); @@ -397,7 +397,7 @@ describe('Agent WebSockets', () => { .expectClosed(); }); - test('Ping', async () => { + test('Heartbeat', async () => { await request(server) .ws('/ws/agent') .sendText( @@ -417,6 +417,58 @@ describe('Agent WebSockets', () => { .expectClosed(); }); + test('Ping IP', async () => { + let pushRequest: any = undefined; + let pushResponse: any = undefined; + + await request(server) + .ws('/ws/agent') + .sendText( + JSON.stringify({ + type: 'agent:connect:request', + accessToken, + agentId: agent.id, + }) + ) + .expectText('{"type":"agent:connect:response"}') + .exec(async () => { + // Send the request but do not wait for the response + pushRequest = request(server) + .post(`/fhir/R4/Agent/${agent.id}/$push`) + .set('Content-Type', ContentType.JSON) + .set('Authorization', 'Bearer ' + accessToken) + .send({ + waitForResponse: true, + channel: 'test', + destination: '8.8.8.8', + contentType: ContentType.PING, + body: 'PING', + }); + pushRequest.then(() => undefined); + }) + .expectText((str) => { + const message = JSON.parse(str); + expect(message.type).toBe('agent:transmit:request'); + expect(message.remote).toBe('8.8.8.8'); + expect(message.callback).toBeDefined(); + pushResponse = JSON.stringify({ + type: 'agent:transmit:response', + callback: message.callback, + contentType: ContentType.PING, + body: 'PING', + }); + return true; + }) + .exec((ws) => ws.send(pushResponse)) + .exec(async () => { + const res = await pushRequest; + expect(res.status).toBe(200); + expect(res.headers['content-type']).toBe('x-application/ping; charset=utf-8'); + }) + .close() + .expectClosed(); + }); + test('Unknown message type', async () => { await request(server) .ws('/ws/agent') diff --git a/packages/server/src/agent/websockets.ts b/packages/server/src/agent/websockets.ts index 7531bf762e..482054a889 100644 --- a/packages/server/src/agent/websockets.ts +++ b/packages/server/src/agent/websockets.ts @@ -104,7 +104,7 @@ export async function handleAgentConnection(socket: ws.WebSocket, request: Incom agentId = command.agentId; const { login, project, membership } = await getLoginForAccessToken(command.accessToken); - const repo = await getRepoForLogin(login, membership, project.strictMode, true, project.checkReferencesOnWrite); + const repo = await getRepoForLogin(login, membership, project, true); const agent = await repo.readResource('Agent', agentId); // Connect to Redis @@ -149,7 +149,7 @@ export async function handleAgentConnection(socket: ws.WebSocket, request: Incom } const { login, project, membership } = await getLoginForAccessToken(command.accessToken); - const repo = await getRepoForLogin(login, membership, project.strictMode, true, project.checkReferencesOnWrite); + const repo = await getRepoForLogin(login, membership, project, true); const agent = await repo.readResource('Agent', agentId); const channel = agent?.channel?.find((c) => c.name === command.channel); if (!channel) { diff --git a/packages/server/src/app.ts b/packages/server/src/app.ts index 99f9ea4813..1e037a5cde 100644 --- a/packages/server/src/app.ts +++ b/packages/server/src/app.ts @@ -15,6 +15,7 @@ import { attachRequestContext, AuthenticatedRequestContext, closeRequestContext, + getLogger, getRequestContext, requestContextStore, } from './context'; @@ -31,7 +32,6 @@ import { fhircastSTU2Router, fhircastSTU3Router } from './fhircast/routes'; import { healthcheckHandler } from './healthcheck'; import { cleanupHeartbeat, initHeartbeat } from './heartbeat'; import { hl7BodyParser } from './hl7/parser'; -import { globalLogger } from './logger'; import { initKeys } from './oauth/keys'; import { oauthRouter } from './oauth/routes'; import { openApiHandler } from './openapi'; @@ -124,11 +124,7 @@ function errorHandler(err: any, req: Request, res: Response, next: NextFunction) sendOutcome(res, badRequest('File too large')); return; } - try { - getRequestContext().logger.error('Unhandled error', err); - } catch { - globalLogger.error('Unhandled error', err); - } + getLogger().error('Unhandled error', err); res.status(500).json({ msg: 'Internal Server Error' }); } @@ -245,7 +241,7 @@ const loggingMiddleware = (req: Request, res: Response, next: NextFunction): voi duration: `${duration} ms`, ip: req.ip, method: req.method, - path: req.path, + path: req.originalUrl, profile: userProfile, projectId, receivedAt: start, diff --git a/packages/server/src/auth/changepassword.ts b/packages/server/src/auth/changepassword.ts index 54ffff05ef..1172b47ab1 100644 --- a/packages/server/src/auth/changepassword.ts +++ b/packages/server/src/auth/changepassword.ts @@ -4,11 +4,11 @@ import bcrypt from 'bcryptjs'; import { Request, Response } from 'express'; import { body } from 'express-validator'; import { pwnedPassword } from 'hibp'; -import { sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; -import { bcryptHashPassword } from './utils'; import { getAuthenticatedContext } from '../context'; +import { sendOutcome } from '../fhir/outcomes'; +import { getSystemRepo } from '../fhir/repo'; import { makeValidationMiddleware } from '../util/validator'; +import { bcryptHashPassword } from './utils'; export const changePasswordValidator = makeValidationMiddleware([ body('oldPassword').notEmpty().withMessage('Missing oldPassword'), @@ -18,6 +18,7 @@ export const changePasswordValidator = makeValidationMiddleware([ export async function changePasswordHandler(req: Request, res: Response): Promise { const ctx = getAuthenticatedContext(); + const systemRepo = getSystemRepo(); const user = await systemRepo.readReference(ctx.membership.user as Reference); await changePassword({ @@ -35,7 +36,7 @@ export interface ChangePasswordRequest { newPassword: string; } -export async function changePassword(request: ChangePasswordRequest): Promise { +async function changePassword(request: ChangePasswordRequest): Promise { const oldPasswordHash = request.user.passwordHash as string; const bcryptResult = await bcrypt.compare(request.oldPassword, oldPasswordHash); if (!bcryptResult) { @@ -48,6 +49,7 @@ export async function changePassword(request: ChangePasswordRequest): Promise({ ...request.user, passwordHash: newPasswordHash, diff --git a/packages/server/src/auth/exchange.test.ts b/packages/server/src/auth/exchange.test.ts index 346e5a6b2c..eaddd8f57c 100644 --- a/packages/server/src/auth/exchange.test.ts +++ b/packages/server/src/auth/exchange.test.ts @@ -8,9 +8,9 @@ import { createClient } from '../admin/client'; import { inviteUser } from '../admin/invite'; import { initApp, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; -import { registerNew } from './register'; +import { getSystemRepo } from '../fhir/repo'; import { withTestContext } from '../test.setup'; +import { registerNew } from './register'; jest.mock('node-fetch'); @@ -51,6 +51,8 @@ describe('Token Exchange', () => { clientSecret: '456', }; + const systemRepo = getSystemRepo(); + // Create a new client application with external auth externalAuthClient = await createClient(systemRepo, { project, diff --git a/packages/server/src/auth/external.test.ts b/packages/server/src/auth/external.test.ts index 9b7502977a..3b7d193604 100644 --- a/packages/server/src/auth/external.test.ts +++ b/packages/server/src/auth/external.test.ts @@ -8,7 +8,7 @@ import { createClient } from '../admin/client'; import { inviteUser } from '../admin/invite'; import { initApp, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { withTestContext } from '../test.setup'; import { registerNew } from './register'; @@ -51,6 +51,8 @@ describe('External', () => { project = registerResult.project; defaultClient = registerResult.client; + const systemRepo = getSystemRepo(); + // Create a domain configuration with external identity provider await systemRepo.createResource({ resourceType: 'DomainConfiguration', @@ -321,6 +323,8 @@ describe('External', () => { test('Subject auth success', async () => { const subjectAuthClient = await withTestContext(async () => { + const systemRepo = getSystemRepo(); + // Create a new client application with external subject auth const client = await createClient(systemRepo, { project, @@ -377,6 +381,8 @@ describe('External', () => { test('Client secret post', async () => { const clientSecretPostClient = await withTestContext(async () => { + const systemRepo = getSystemRepo(); + // Create a new client application with external subject auth const client = await createClient(systemRepo, { project, @@ -437,6 +443,8 @@ describe('External', () => { const domain = `${randomUUID()}.example.com`; const redirectUri = `https://${domain}/auth/callback`; const client = await withTestContext(async () => { + const systemRepo = getSystemRepo(); + // Create a new project const { project, client } = await registerNew({ firstName: 'External', diff --git a/packages/server/src/auth/external.ts b/packages/server/src/auth/external.ts index 61f26c4d94..fc68b61887 100644 --- a/packages/server/src/auth/external.ts +++ b/packages/server/src/auth/external.ts @@ -13,7 +13,7 @@ import { Request, Response } from 'express'; import fetch from 'node-fetch'; import { getConfig } from '../config'; import { sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { globalLogger } from '../logger'; import { CodeChallengeMethod, tryLogin } from '../oauth/utils'; import { getDomainConfiguration } from './method'; @@ -130,6 +130,7 @@ async function getIdentityProvider( state: ExternalAuthState ): Promise<{ idp?: IdentityProvider; client?: ClientApplication }> { if (state.clientId) { + const systemRepo = getSystemRepo(); const client = await systemRepo.readResource('ClientApplication', state.clientId); if (client.identityProvider) { return { idp: client.identityProvider, client }; diff --git a/packages/server/src/auth/google.test.ts b/packages/server/src/auth/google.test.ts index eee041f1e1..1950d10be6 100644 --- a/packages/server/src/auth/google.test.ts +++ b/packages/server/src/auth/google.test.ts @@ -3,10 +3,10 @@ import express from 'express'; import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { getConfig, loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { getUserByEmail } from '../oauth/utils'; -import { registerNew } from './register'; import { withTestContext } from '../test.setup'; +import { registerNew } from './register'; jest.mock('jose', () => { const original = jest.requireActual('jose'); @@ -144,6 +144,7 @@ describe('Google Auth', () => { }); // As a super admin, update the project to require Google auth + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...project, features: ['google-auth-required'], @@ -179,6 +180,7 @@ describe('Google Auth', () => { }); // As a super admin, set the google client ID + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...project, site: [ @@ -221,6 +223,7 @@ describe('Google Auth', () => { }); // As a super admin, set the google client ID + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...project, site: [ @@ -266,6 +269,7 @@ describe('Google Auth', () => { }); // As a super admin, set the google client ID + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...project, site: [ @@ -308,6 +312,7 @@ describe('Google Auth', () => { }); // As a super admin, set the google client ID + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...project, site: [ diff --git a/packages/server/src/auth/google.ts b/packages/server/src/auth/google.ts index e6069febfd..03cd507a26 100644 --- a/packages/server/src/auth/google.ts +++ b/packages/server/src/auth/google.ts @@ -7,11 +7,11 @@ import { createRemoteJWKSet, jwtVerify, JWTVerifyOptions } from 'jose'; import { URL } from 'url'; import { getConfig } from '../config'; import { sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { getUserByEmail, GoogleCredentialClaims, tryLogin } from '../oauth/utils'; +import { makeValidationMiddleware } from '../util/validator'; import { isExternalAuth } from './method'; import { getProjectIdByClientId, sendLoginResult } from './utils'; -import { makeValidationMiddleware } from '../util/validator'; /* * Integrating Google Sign-In into your web app @@ -101,6 +101,7 @@ export async function googleHandler(req: Request, res: Response): Promise sendOutcome(res, badRequest('User not found')); return; } + const systemRepo = getSystemRepo(); await systemRepo.createResource({ resourceType: 'User', firstName: claims.given_name, @@ -146,5 +147,6 @@ function getProjectsByGoogleClientId(googleClientId: string, projectId: string | }); } + const systemRepo = getSystemRepo(); return systemRepo.searchResources({ resourceType: 'Project', filters }); } diff --git a/packages/server/src/auth/login.test.ts b/packages/server/src/auth/login.test.ts index ce926cd983..c3a5f01b88 100644 --- a/packages/server/src/auth/login.test.ts +++ b/packages/server/src/auth/login.test.ts @@ -10,7 +10,7 @@ import request from 'supertest'; import { inviteUser } from '../admin/invite'; import { initApp, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { createTestProject, setupPwnedPasswordMock, setupRecaptchaMock, withTestContext } from '../test.setup'; import { registerNew } from './register'; import { setPassword } from './setpassword'; @@ -32,7 +32,7 @@ describe('Login', () => { await initApp(app, config); // Create a test project - ({ project, client } = await createTestProject()); + ({ project, client } = await createTestProject({ withClient: true })); // Create a test user const { user } = await inviteUser({ @@ -343,6 +343,7 @@ describe('Login', () => { }); // As a super admin, update the project to require Google auth + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...project, features: ['google-auth-required'], diff --git a/packages/server/src/auth/login.ts b/packages/server/src/auth/login.ts index 1fbfbc704a..ceb623b72d 100644 --- a/packages/server/src/auth/login.ts +++ b/packages/server/src/auth/login.ts @@ -2,9 +2,10 @@ import { ResourceType } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; import { Request, Response } from 'express'; import { body } from 'express-validator'; +import { getLogger } from '../context'; import { tryLogin } from '../oauth/utils'; -import { getProjectIdByClientId, sendLoginResult } from './utils'; import { makeValidationMiddleware } from '../util/validator'; +import { getProjectIdByClientId, sendLoginResult } from './utils'; export const loginValidator = makeValidationMiddleware([ body('email').isEmail().withMessage('Valid email address is required'), @@ -41,5 +42,8 @@ export async function loginHandler(req: Request, res: Response): Promise { userAgent: req.get('User-Agent'), allowNoMembership: req.body.projectId === 'new', }); + + getLogger().info('Login success', { email: req.body.email, projectId }); + await sendLoginResult(res, login); } diff --git a/packages/server/src/auth/me.ts b/packages/server/src/auth/me.ts index 7b94810ff5..adab756c80 100644 --- a/packages/server/src/auth/me.ts +++ b/packages/server/src/auth/me.ts @@ -2,10 +2,10 @@ import { getReferenceString, Operator, ProfileResource } from '@medplum/core'; import { Login, Project, ProjectMembership, Reference, User, UserConfiguration } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { UAParser } from 'ua-parser-js'; +import { getAuthenticatedContext } from '../context'; import { getAccessPolicyForLogin } from '../fhir/accesspolicy'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo, Repository } from '../fhir/repo'; import { rewriteAttachments, RewriteMode } from '../fhir/rewrite'; -import { getAuthenticatedContext } from '../context'; interface UserSession { id: string; @@ -22,15 +22,17 @@ interface UserSecurity { } export async function meHandler(req: Request, res: Response): Promise { + const systemRepo = getSystemRepo(); + const { login, project, membership, profile: profileRef } = getAuthenticatedContext(); const profile = await systemRepo.readReference(profileRef); - const config = await getUserConfiguration(project, membership); + const config = await getUserConfiguration(systemRepo, project, membership); const accessPolicy = await getAccessPolicyForLogin(login, membership); let security: UserSecurity | undefined = undefined; if (membership.user?.reference?.startsWith('User/')) { const user = await systemRepo.readReference(membership.user as Reference); - const sessions = await getSessions(user); + const sessions = await getSessions(systemRepo, user); security = { mfaEnrolled: !!user.mfaEnrolled, sessions, @@ -62,7 +64,11 @@ export async function meHandler(req: Request, res: Response): Promise { res.status(200).json(await rewriteAttachments(RewriteMode.PRESIGNED_URL, systemRepo, result)); } -async function getUserConfiguration(project: Project, membership: ProjectMembership): Promise { +async function getUserConfiguration( + systemRepo: Repository, + project: Project, + membership: ProjectMembership +): Promise { if (membership.userConfiguration) { return systemRepo.readReference(membership.userConfiguration); } @@ -105,7 +111,7 @@ async function getUserConfiguration(project: Project, membership: ProjectMembers return result; } -async function getSessions(user: User): Promise { +async function getSessions(systemRepo: Repository, user: User): Promise { const logins = await systemRepo.searchResources({ resourceType: 'Login', filters: [ diff --git a/packages/server/src/auth/method.test.ts b/packages/server/src/auth/method.test.ts index 39136c6f01..8ca4167728 100644 --- a/packages/server/src/auth/method.test.ts +++ b/packages/server/src/auth/method.test.ts @@ -4,12 +4,13 @@ import express from 'express'; import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { withTestContext } from '../test.setup'; -const app = express(); - describe('Method', () => { + const app = express(); + const systemRepo = getSystemRepo(); + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); diff --git a/packages/server/src/auth/method.ts b/packages/server/src/auth/method.ts index 9eaeede3f5..68dcd660ca 100644 --- a/packages/server/src/auth/method.ts +++ b/packages/server/src/auth/method.ts @@ -1,11 +1,11 @@ -import { Operator, OperationOutcomeError, badRequest } from '@medplum/core'; +import { OperationOutcomeError, Operator, badRequest } from '@medplum/core'; import { DomainConfiguration } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { body } from 'express-validator'; import { getConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; -import { makeValidationMiddleware } from '../util/validator'; +import { getSystemRepo } from '../fhir/repo'; import { globalLogger } from '../logger'; +import { makeValidationMiddleware } from '../util/validator'; /* * The method handler is used to determine available login methods. @@ -68,6 +68,7 @@ export async function isExternalAuth(email: string): Promise<{ domain: string; a * @returns The domain configuration for the domain name if available. */ export async function getDomainConfiguration(domain: string): Promise { + const systemRepo = getSystemRepo(); const results = await systemRepo.search({ resourceType: 'DomainConfiguration', filters: [ diff --git a/packages/server/src/auth/mfa.ts b/packages/server/src/auth/mfa.ts index 821e92cc5a..1102452588 100644 --- a/packages/server/src/auth/mfa.ts +++ b/packages/server/src/auth/mfa.ts @@ -4,12 +4,12 @@ import { Request, Response, Router } from 'express'; import { body, validationResult } from 'express-validator'; import { authenticator } from 'otplib'; import { asyncWrap } from '../async'; +import { getAuthenticatedContext } from '../context'; import { invalidRequest, sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { authenticateRequest } from '../oauth/middleware'; import { verifyMfaToken } from '../oauth/utils'; import { sendLoginResult } from './utils'; -import { getAuthenticatedContext } from '../context'; authenticator.options = { window: 1, @@ -21,6 +21,7 @@ mfaRouter.get( '/status', authenticateRequest, asyncWrap(async (_req: Request, res: Response) => { + const systemRepo = getSystemRepo(); const ctx = getAuthenticatedContext(); let user = await systemRepo.readReference(ctx.membership.user as Reference); if (user.mfaEnrolled) { @@ -52,6 +53,7 @@ mfaRouter.post( authenticateRequest, [body('token').notEmpty().withMessage('Missing token')], asyncWrap(async (req: Request, res: Response) => { + const systemRepo = getSystemRepo(); const ctx = getAuthenticatedContext(); const user = await systemRepo.readReference(ctx.membership.user as Reference); @@ -90,6 +92,7 @@ mfaRouter.post( return Promise.resolve(); } + const systemRepo = getSystemRepo(); const login = await systemRepo.readResource('Login', req.body.login); const result = await verifyMfaToken(login, req.body.token); return sendLoginResult(res, result); diff --git a/packages/server/src/auth/newpatient.test.ts b/packages/server/src/auth/newpatient.test.ts index f486e1df2e..9ab17727f7 100644 --- a/packages/server/src/auth/newpatient.test.ts +++ b/packages/server/src/auth/newpatient.test.ts @@ -7,7 +7,7 @@ import fetch from 'node-fetch'; import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { setupPwnedPasswordMock, setupRecaptchaMock, withTestContext } from '../test.setup'; jest.mock('hibp'); @@ -33,6 +33,8 @@ describe('New patient', () => { }); test('Patient registration', async () => { + const systemRepo = getSystemRepo(); + // Register as Christina const res1 = await request(app) .post('/auth/newuser') diff --git a/packages/server/src/auth/newpatient.ts b/packages/server/src/auth/newpatient.ts index d01d532daa..b1359ef37c 100644 --- a/packages/server/src/auth/newpatient.ts +++ b/packages/server/src/auth/newpatient.ts @@ -3,10 +3,10 @@ import { Login, Patient, Project, ProjectMembership, Reference, User } from '@me import { Request, Response } from 'express'; import { body } from 'express-validator'; import { sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { setLoginMembership } from '../oauth/utils'; -import { createProfile, createProjectMembership } from './utils'; import { makeValidationMiddleware } from '../util/validator'; +import { createProfile, createProjectMembership } from './utils'; export const newPatientValidator = makeValidationMiddleware([ body('login').notEmpty().withMessage('Missing login'), @@ -20,6 +20,7 @@ export const newPatientValidator = makeValidationMiddleware([ * @param res - The HTTP response. */ export async function newPatientHandler(req: Request, res: Response): Promise { + const systemRepo = getSystemRepo(); const login = await systemRepo.readResource('Login', req.body.login); if (login.membership) { @@ -61,6 +62,7 @@ export async function createPatient( firstName: string, lastName: string ): Promise { + const systemRepo = getSystemRepo(); const user = await systemRepo.readReference(login.user as Reference); const project = await systemRepo.readResource('Project', projectId); diff --git a/packages/server/src/auth/newproject.ts b/packages/server/src/auth/newproject.ts index 7669844a79..24c3d531b9 100644 --- a/packages/server/src/auth/newproject.ts +++ b/packages/server/src/auth/newproject.ts @@ -2,10 +2,10 @@ import { badRequest, createReference } from '@medplum/core'; import { Login, Reference, User } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { body } from 'express-validator'; +import { createProject } from '../fhir/operations/projectinit'; import { sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { makeValidationMiddleware } from '../util/validator'; -import { createProject } from '../fhir/operations/projectinit'; export interface NewProjectRequest { readonly loginId: string; @@ -25,6 +25,7 @@ export const newProjectValidator = makeValidationMiddleware([ * @param res - The HTTP response. */ export async function newProjectHandler(req: Request, res: Response): Promise { + const systemRepo = getSystemRepo(); const login = await systemRepo.readResource('Login', req.body.login); if (login.membership) { diff --git a/packages/server/src/auth/newuser.test.ts b/packages/server/src/auth/newuser.test.ts index 5295c53ede..070bb78229 100644 --- a/packages/server/src/auth/newuser.test.ts +++ b/packages/server/src/auth/newuser.test.ts @@ -7,7 +7,7 @@ import fetch from 'node-fetch'; import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { getConfig, loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { setupPwnedPasswordMock, setupRecaptchaMock, withTestContext } from '../test.setup'; import { registerNew } from './register'; @@ -205,6 +205,7 @@ describe('New user', () => { }); // As a super admin, set the recaptcha site key // and the default access policy + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...project, site: [ @@ -257,6 +258,7 @@ describe('New user', () => { }); // As a super admin, set the recaptcha site key // and the default access policy + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...project, site: [ @@ -308,6 +310,7 @@ describe('New user', () => { }); // As a super admin, set the recaptcha site key // but *not* the access policy + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...project, site: [ @@ -370,6 +373,7 @@ describe('New user', () => { password, }); // As a super admin, set the recaptcha site key + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...project, site: [ diff --git a/packages/server/src/auth/newuser.ts b/packages/server/src/auth/newuser.ts index b912628c51..dbd05e90a7 100644 --- a/packages/server/src/auth/newuser.ts +++ b/packages/server/src/auth/newuser.ts @@ -6,7 +6,7 @@ import { body } from 'express-validator'; import { pwnedPassword } from 'hibp'; import { getConfig } from '../config'; import { sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { globalLogger } from '../logger'; import { getUserByEmailInProject, getUserByEmailWithoutProject, tryLogin } from '../oauth/utils'; import { makeValidationMiddleware } from '../util/validator'; @@ -36,6 +36,8 @@ export async function newUserHandler(req: Request, res: Response): Promise return; } + const systemRepo = getSystemRepo(); + let projectId = req.body.projectId as string | undefined; // If the user specifies a client ID, then make sure it is compatible with the project @@ -101,6 +103,8 @@ export async function createUser(request: Omit globalLogger.info('User creation request received', { email }); const passwordHash = await bcryptHashPassword(password); + + const systemRepo = getSystemRepo(); const result = await systemRepo.createResource({ resourceType: 'User', firstName, diff --git a/packages/server/src/auth/profile.test.ts b/packages/server/src/auth/profile.test.ts index 2af903cd16..f85275bbcd 100644 --- a/packages/server/src/auth/profile.test.ts +++ b/packages/server/src/auth/profile.test.ts @@ -5,7 +5,7 @@ import express from 'express'; import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { withTestContext } from '../test.setup'; import { registerNew } from './register'; @@ -91,6 +91,7 @@ describe('Profile', () => { expect(res1.status).toBe(200); expect(res1.body.login).toBeDefined(); + const systemRepo = getSystemRepo(); const login = await systemRepo.readResource('Login', res1.body.login); await withTestContext(() => systemRepo.updateResource({ @@ -120,6 +121,7 @@ describe('Profile', () => { expect(res1.status).toBe(200); expect(res1.body.login).toBeDefined(); + const systemRepo = getSystemRepo(); const login = await systemRepo.readResource('Login', res1.body.login); await withTestContext(() => systemRepo.updateResource({ @@ -149,6 +151,7 @@ describe('Profile', () => { expect(res1.status).toBe(200); expect(res1.body.login).toBeDefined(); + const systemRepo = getSystemRepo(); const login = await systemRepo.readResource('Login', res1.body.login); await withTestContext(() => systemRepo.updateResource({ @@ -196,6 +199,7 @@ describe('Profile', () => { test('Membership for different user', async () => { // Create a dummy ProjectMembership + const systemRepo = getSystemRepo(); const membership = await withTestContext(() => systemRepo.createResource({ resourceType: 'ProjectMembership', diff --git a/packages/server/src/auth/profile.ts b/packages/server/src/auth/profile.ts index d62c9a78c6..2a07460399 100644 --- a/packages/server/src/auth/profile.ts +++ b/packages/server/src/auth/profile.ts @@ -1,10 +1,10 @@ import { Login } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { body } from 'express-validator'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { setLoginMembership } from '../oauth/utils'; -import { sendLoginCookie } from './utils'; import { makeValidationMiddleware } from '../util/validator'; +import { sendLoginCookie } from './utils'; /* * The profile handler is used during login when a user has multiple profiles. @@ -17,6 +17,7 @@ export const profileValidator = makeValidationMiddleware([ ]); export async function profileHandler(req: Request, res: Response): Promise { + const systemRepo = getSystemRepo(); const login = await systemRepo.readResource('Login', req.body.login); // Update the login diff --git a/packages/server/src/auth/register.ts b/packages/server/src/auth/register.ts index be75e8befe..a745093f49 100644 --- a/packages/server/src/auth/register.ts +++ b/packages/server/src/auth/register.ts @@ -2,7 +2,7 @@ import { createReference, ProfileResource } from '@medplum/core'; import { ClientApplication, Login, Project, ProjectMembership, User } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; import { createProject } from '../fhir/operations/projectinit'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { getAuthTokens, getUserByEmailWithoutProject, tryLogin } from '../oauth/utils'; import { bcryptHashPassword } from './utils'; @@ -45,6 +45,7 @@ export async function registerNew(request: RegisterRequest): Promise({ resourceType: 'User', firstName, diff --git a/packages/server/src/auth/resetpassword.test.ts b/packages/server/src/auth/resetpassword.test.ts index 078f1b5cae..437d4f60f0 100644 --- a/packages/server/src/auth/resetpassword.test.ts +++ b/packages/server/src/auth/resetpassword.test.ts @@ -9,7 +9,7 @@ import fetch from 'node-fetch'; import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { getConfig, loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { setupPwnedPasswordMock, setupRecaptchaMock, withTestContext } from '../test.setup'; import { registerNew } from './register'; @@ -17,9 +17,9 @@ jest.mock('@aws-sdk/client-sesv2'); jest.mock('hibp'); jest.mock('node-fetch'); -const app = express(); - describe('Reset Password', () => { + const app = express(); + const systemRepo = getSystemRepo(); const testRecaptchaSecretKey = 'testrecaptchasecretkey'; beforeAll(async () => { diff --git a/packages/server/src/auth/resetpassword.ts b/packages/server/src/auth/resetpassword.ts index 70e4086802..a52590f82e 100644 --- a/packages/server/src/auth/resetpassword.ts +++ b/packages/server/src/auth/resetpassword.ts @@ -5,7 +5,7 @@ import { body } from 'express-validator'; import { getConfig } from '../config'; import { sendEmail } from '../email/email'; import { sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { generateSecret } from '../oauth/keys'; import { makeValidationMiddleware } from '../util/validator'; import { isExternalAuth } from './method'; @@ -51,6 +51,7 @@ export async function resetPasswordHandler(req: Request, res: Response): Promise } // Search for a user based on the defined filters + const systemRepo = getSystemRepo(); const user = await systemRepo.searchOne({ resourceType: 'User', filters, @@ -98,6 +99,7 @@ export async function resetPasswordHandler(req: Request, res: Response): Promise */ export async function resetPassword(user: User, type: 'invite' | 'reset', redirectUri?: string): Promise { // Create the password change request + const systemRepo = getSystemRepo(); const pcr = await systemRepo.createResource({ resourceType: 'PasswordChangeRequest', meta: { diff --git a/packages/server/src/auth/revoke.ts b/packages/server/src/auth/revoke.ts index c8af7f5371..8fdf8b763c 100644 --- a/packages/server/src/auth/revoke.ts +++ b/packages/server/src/auth/revoke.ts @@ -2,10 +2,10 @@ import { allOk, notFound } from '@medplum/core'; import { Login } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { body } from 'express-validator'; +import { getAuthenticatedContext } from '../context'; import { sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { revokeLogin } from '../oauth/utils'; -import { getAuthenticatedContext } from '../context'; import { makeValidationMiddleware } from '../util/validator'; export const revokeValidator = makeValidationMiddleware([ @@ -15,6 +15,7 @@ export const revokeValidator = makeValidationMiddleware([ export async function revokeHandler(req: Request, res: Response): Promise { const ctx = getAuthenticatedContext(); + const systemRepo = getSystemRepo(); const login = await systemRepo.readResource('Login', req.body.loginId); // Make sure the login belongs to the current user diff --git a/packages/server/src/auth/scope.test.ts b/packages/server/src/auth/scope.test.ts index 8ecb912f7b..bf7e151e12 100644 --- a/packages/server/src/auth/scope.test.ts +++ b/packages/server/src/auth/scope.test.ts @@ -4,15 +4,16 @@ import express from 'express'; import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; -import { registerNew } from './register'; +import { getSystemRepo } from '../fhir/repo'; import { withTestContext } from '../test.setup'; - -const app = express(); -const email = `multi${randomUUID()}@example.com`; -const password = randomUUID(); +import { registerNew } from './register'; describe('Scope', () => { + const app = express(); + const systemRepo = getSystemRepo(); + const email = `multi${randomUUID()}@example.com`; + const password = randomUUID(); + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); diff --git a/packages/server/src/auth/scope.ts b/packages/server/src/auth/scope.ts index 7244c5d632..1c93f67412 100644 --- a/packages/server/src/auth/scope.ts +++ b/packages/server/src/auth/scope.ts @@ -1,7 +1,7 @@ import { Login } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { body } from 'express-validator'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { setLoginScope } from '../oauth/utils'; import { makeValidationMiddleware } from '../util/validator'; @@ -16,6 +16,7 @@ export const scopeValidator = makeValidationMiddleware([ ]); export async function scopeHandler(req: Request, res: Response): Promise { + const systemRepo = getSystemRepo(); const login = await systemRepo.readResource('Login', req.body.login); // Update the login diff --git a/packages/server/src/auth/setpassword.ts b/packages/server/src/auth/setpassword.ts index b7c5f25ac2..216ebedfe1 100644 --- a/packages/server/src/auth/setpassword.ts +++ b/packages/server/src/auth/setpassword.ts @@ -4,7 +4,7 @@ import { Request, Response } from 'express'; import { body } from 'express-validator'; import { pwnedPassword } from 'hibp'; import { sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { timingSafeEqualStr } from '../oauth/utils'; import { makeValidationMiddleware } from '../util/validator'; import { bcryptHashPassword } from './utils'; @@ -16,6 +16,7 @@ export const setPasswordValidator = makeValidationMiddleware([ ]); export async function setPasswordHandler(req: Request, res: Response): Promise { + const systemRepo = getSystemRepo(); const pcr = await systemRepo.readResource('PasswordChangeRequest', req.body.id); if (pcr.used) { @@ -43,5 +44,6 @@ export async function setPasswordHandler(req: Request, res: Response): Promise { const passwordHash = await bcryptHashPassword(password); + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...user, passwordHash }); } diff --git a/packages/server/src/auth/status.test.ts b/packages/server/src/auth/status.test.ts index 085877d1e2..4f22c5524e 100644 --- a/packages/server/src/auth/status.test.ts +++ b/packages/server/src/auth/status.test.ts @@ -5,13 +5,14 @@ import express from 'express'; import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { withTestContext } from '../test.setup'; -const app = express(); -let user: User; - describe('Status', () => { + const app = express(); + const systemRepo = getSystemRepo(); + let user: User; + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); diff --git a/packages/server/src/auth/status.ts b/packages/server/src/auth/status.ts index 7f29b9f61a..113872199d 100644 --- a/packages/server/src/auth/status.ts +++ b/packages/server/src/auth/status.ts @@ -3,9 +3,9 @@ import { Login } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { param } from 'express-validator'; import { sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; -import { sendLoginResult } from './utils'; +import { getSystemRepo } from '../fhir/repo'; import { makeValidationMiddleware } from '../util/validator'; +import { sendLoginResult } from './utils'; /* * The status handler gets an in-process login status. @@ -15,6 +15,8 @@ import { makeValidationMiddleware } from '../util/validator'; export const statusValidator = makeValidationMiddleware([param('login').isUUID().withMessage('Login ID is required')]); export async function statusHandler(req: Request, res: Response): Promise { + const systemRepo = getSystemRepo(); + const loginId = req.params.login; const login = await systemRepo.readResource('Login', loginId); diff --git a/packages/server/src/auth/utils.ts b/packages/server/src/auth/utils.ts index 525748e325..b1cad56ab5 100644 --- a/packages/server/src/auth/utils.ts +++ b/packages/server/src/auth/utils.ts @@ -6,14 +6,24 @@ import { ProfileResource, resolveId, } from '@medplum/core'; -import { ContactPoint, Login, OperationOutcome, Project, ProjectMembership, Reference, User } from '@medplum/fhirtypes'; +import { + Attachment, + ContactPoint, + Login, + OperationOutcome, + Project, + ProjectMembership, + Reference, + User, +} from '@medplum/fhirtypes'; import bcrypt from 'bcryptjs'; import { Handler, NextFunction, Request, Response } from 'express'; import fetch from 'node-fetch'; +import { createHash } from 'node:crypto'; import { getConfig } from '../config'; -import { getRequestContext } from '../context'; +import { getLogger } from '../context'; import { sendOutcome } from '../fhir/outcomes'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { rewriteAttachments, RewriteMode } from '../fhir/rewrite'; import { getClientApplication, getMembershipsForLogin } from '../oauth/utils'; @@ -24,12 +34,16 @@ export async function createProfile( lastName: string, email: string | undefined ): Promise { - const ctx = getRequestContext(); - ctx.logger.info('Creating profile', { resourceType, firstName, lastName }); + const logger = getLogger(); + logger.info('Creating profile', { resourceType, firstName, lastName }); let telecom: ContactPoint[] | undefined = undefined; + let photo: Attachment[] | undefined = undefined; if (email) { telecom = [{ system: 'email', use: 'work', value: email }]; + photo = [{ url: getGravatar(email), contentType: 'image/png', title: 'profile.png' }]; } + + const systemRepo = getSystemRepo(); const result = await systemRepo.createResource({ resourceType, meta: { @@ -42,8 +56,9 @@ export async function createProfile( }, ], telecom, + photo, } as ProfileResource); - ctx.logger.info('Created profile', { id: result.id }); + logger.info('Created profile', { id: result.id }); return result; } @@ -53,8 +68,10 @@ export async function createProjectMembership( profile: ProfileResource, details?: Partial ): Promise { - const ctx = getRequestContext(); - ctx.logger.info('Creating project membership', { name: project.name }); + const logger = getLogger(); + logger.info('Creating project membership', { name: project.name }); + + const systemRepo = getSystemRepo(); const result = await systemRepo.createResource({ ...details, resourceType: 'ProjectMembership', @@ -62,7 +79,7 @@ export async function createProjectMembership( user: createReference(user), profile: createReference(profile), }); - ctx.logger.info('Created project memberships', { id: result.id }); + logger.info('Created project memberships', { id: result.id }); return result; } @@ -74,6 +91,7 @@ export async function createProjectMembership( * @param login - The login details. */ export async function sendLoginResult(res: Response, login: Login): Promise { + const systemRepo = getSystemRepo(); const user = await systemRepo.readReference(login.user as Reference); if (user.mfaEnrolled && login.authMethod === 'password' && !login.mfaVerified) { res.json({ login: login.id, mfaRequired: true }); @@ -199,6 +217,7 @@ export function getProjectByRecaptchaSiteKey( }); } + const systemRepo = getSystemRepo(); return systemRepo.searchOne({ resourceType: 'Project', filters }); } @@ -253,3 +272,13 @@ export function validateRecaptcha(projectValidation?: (p: Project) => OperationO next(); }; } + +/** + * Returns the Gravatar URL for the given email address. + * @param email - The email address. + * @returns The Gravatar URL. + */ +function getGravatar(email: string): string { + const hash = createHash('sha256').update(email.trim().toLowerCase()).digest('hex'); + return `https://gravatar.com/avatar/${hash}?s=256&r=pg&d=retro`; +} diff --git a/packages/server/src/config.ts b/packages/server/src/config.ts index 63021d7f9e..0c5f904b93 100644 --- a/packages/server/src/config.ts +++ b/packages/server/src/config.ts @@ -50,6 +50,8 @@ export interface MedplumServerConfig { shutdownTimeoutMilliseconds?: number; heartbeatMilliseconds?: number; heartbeatEnabled?: boolean; + accurateCountThreshold: number; + defaultBotRuntimeVersion: 'awslambda' | 'vmcontext'; /** @deprecated */ auditEventLogGroup?: string; @@ -189,7 +191,7 @@ function loadEnvConfig(): MedplumServerConfig { key = key.toLowerCase().replace(/_([a-z])/g, (g) => g[1].toUpperCase()); if (isIntegerConfig(key)) { - currConfig.port = parseInt(value ?? '', 10); + currConfig[key] = parseInt(value ?? '', 10); } else if (isBooleanConfig(key)) { currConfig[key] = value === 'true'; } else if (isObjectConfig(key)) { @@ -244,7 +246,7 @@ async function loadAwsConfig(path: string): Promise { } else if (key === 'RedisSecrets') { config['redis'] = await loadAwsSecrets(region, value); } else if (isIntegerConfig(key)) { - config.port = parseInt(value, 10); + config[key] = parseInt(value, 10); } else if (isBooleanConfig(key)) { config[key] = value === 'true'; } else { @@ -294,11 +296,13 @@ function addDefaults(config: MedplumServerConfig): MedplumServerConfig { config.bcryptHashSalt = config.bcryptHashSalt || 10; config.bullmq = { concurrency: 10, removeOnComplete: { count: 1 }, removeOnFail: { count: 1 }, ...config.bullmq }; config.shutdownTimeoutMilliseconds = config.shutdownTimeoutMilliseconds ?? 30000; + config.accurateCountThreshold = config.accurateCountThreshold ?? 1000000; + config.defaultBotRuntimeVersion = config.defaultBotRuntimeVersion ?? 'awslambda'; return config; } function isIntegerConfig(key: string): boolean { - return key === 'port'; + return key === 'port' || key === 'accurateCountThreshold'; } function isBooleanConfig(key: string): boolean { diff --git a/packages/server/src/context.test.ts b/packages/server/src/context.test.ts new file mode 100644 index 0000000000..dec5a68794 --- /dev/null +++ b/packages/server/src/context.test.ts @@ -0,0 +1,60 @@ +import { Request } from 'express'; +import { + RequestContext, + getAuthenticatedContext, + getRequestContext, + getTraceId, + requestContextStore, + tryGetRequestContext, + tryRunInRequestContext, +} from './context'; +import { withTestContext } from './test.setup'; + +describe('RequestContext', () => { + test('tryGetRequestContext', async () => { + expect(tryGetRequestContext()).toBeUndefined(); + withTestContext(() => expect(tryGetRequestContext()).toBeDefined()); + }); + + test('getRequestContext', () => { + expect(() => getRequestContext()).toThrow('No request context available'); + withTestContext(() => expect(getRequestContext()).toBeDefined()); + }); + + test('getAuthenticatedContext', () => { + expect(() => getAuthenticatedContext()).toThrow('No request context available'); + requestContextStore.run(new RequestContext('request', 'trace'), () => { + expect(() => getAuthenticatedContext()).toThrow('Request is not authenticated'); + }); + withTestContext(() => expect(getAuthenticatedContext()).toBeDefined()); + }); + + test('tryRunInRequestContext', () => { + tryRunInRequestContext(undefined, undefined, () => { + expect(tryGetRequestContext()).toBeUndefined(); + }); + tryRunInRequestContext('request', 'trace', () => { + expect(tryGetRequestContext()).toBeDefined(); + }); + }); + + test('getTraceId', () => { + expect(getTraceId(mockRequest({}))).toBeUndefined(); + expect(getTraceId(mockRequest({ 'x-trace-id': 'foo' }))).toBeUndefined(); + expect(getTraceId(mockRequest({ traceparent: 'foo' }))).toBeUndefined(); + + const uuid = '00000000-0000-0000-0000-000000000000'; + expect(getTraceId(mockRequest({ 'x-trace-id': uuid }))).toEqual(uuid); + + const tpid = '00-12345678901234567890123456789012-3456789012345678-01'; + expect(getTraceId(mockRequest({ traceparent: tpid }))).toEqual(tpid); + }); +}); + +function mockRequest(headers: Record): Request { + return { + header(name: string): string | undefined { + return headers[name]; + }, + } as unknown as Request; +} diff --git a/packages/server/src/context.ts b/packages/server/src/context.ts index 1d0c161ac4..890357221b 100644 --- a/packages/server/src/context.ts +++ b/packages/server/src/context.ts @@ -3,7 +3,8 @@ import { Login, Project, ProjectMembership, Reference } from '@medplum/fhirtypes import { AsyncLocalStorage } from 'async_hooks'; import { randomUUID } from 'crypto'; import { NextFunction, Request, Response } from 'express'; -import { Repository, systemRepo } from './fhir/repo'; +import { Repository, getSystemRepo } from './fhir/repo'; +import { parseTraceparent } from './traceparent'; export class RequestContext { readonly requestId: string; @@ -27,6 +28,8 @@ export class RequestContext { } } +const systemLogger = new Logger(write, undefined, LogLevel.ERROR); + export class AuthenticatedRequestContext extends RequestContext { readonly repo: Repository; readonly project: Project; @@ -58,14 +61,13 @@ export class AuthenticatedRequestContext extends RequestContext { this.repo.close(); } - static system(): AuthenticatedRequestContext { - const systemLogger = new Logger(write, undefined, LogLevel.ERROR); + static system(ctx?: { requestId?: string; traceId?: string }): AuthenticatedRequestContext { return new AuthenticatedRequestContext( - new RequestContext('', ''), + new RequestContext(ctx?.requestId ?? '', ctx?.traceId ?? ''), {} as unknown as Login, {} as unknown as Project, {} as unknown as ProjectMembership, - systemRepo, + getSystemRepo(), systemLogger ); } @@ -73,6 +75,10 @@ export class AuthenticatedRequestContext extends RequestContext { export const requestContextStore = new AsyncLocalStorage(); +export function tryGetRequestContext(): RequestContext | undefined { + return requestContextStore.getStore(); +} + export function getRequestContext(): RequestContext { const ctx = requestContextStore.getStore(); if (!ctx) { @@ -101,29 +107,37 @@ export function closeRequestContext(): void { } } -function requestIds(req: Request): { requestId: string; traceId: string } { - const requestId = randomUUID(); - const traceIdHeader = req.header('x-trace-id'); - const traceParentHeader = req.header('traceparent'); - let traceId: string | undefined; - if (traceIdHeader && isUUID(traceIdHeader)) { - traceId = traceIdHeader; - } else if (traceParentHeader?.startsWith('00-')) { - const id = traceParentHeader.split('-')[1]; - const uuid = [ - id.substring(0, 8), - id.substring(8, 12), - id.substring(12, 16), - id.substring(16, 20), - id.substring(20, 32), - ].join('-'); - if (isUUID(uuid)) { - traceId = uuid; - } +export function getLogger(): Logger { + const ctx = requestContextStore.getStore(); + return ctx ? ctx.logger : systemLogger; +} + +export function tryRunInRequestContext(requestId: string | undefined, traceId: string | undefined, fn: () => T): T { + if (requestId && traceId) { + return requestContextStore.run(new RequestContext(requestId, traceId), fn); + } else { + return fn(); + } +} + +export function getTraceId(req: Request): string | undefined { + const xTraceId = req.header('x-trace-id'); + if (xTraceId && isUUID(xTraceId)) { + return xTraceId; } - if (!traceId) { - traceId = randomUUID(); + + const traceparent = req.header('traceparent'); + if (traceparent && parseTraceparent(traceparent)) { + return traceparent; } + + return undefined; +} + +function requestIds(req: Request): { requestId: string; traceId: string } { + const requestId = randomUUID(); + const traceId = getTraceId(req) ?? randomUUID(); + return { requestId, traceId }; } diff --git a/packages/server/src/email/email.test.ts b/packages/server/src/email/email.test.ts index 7b69e5a46f..498e39a317 100644 --- a/packages/server/src/email/email.test.ts +++ b/packages/server/src/email/email.test.ts @@ -10,12 +10,13 @@ import Mail from 'nodemailer/lib/mailer'; import { Readable } from 'stream'; import { initAppServices, shutdownApp } from '../app'; import { getConfig, loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { getBinaryStorage } from '../fhir/storage'; -import { sendEmail } from './email'; import { withTestContext } from '../test.setup'; +import { sendEmail } from './email'; describe('Email', () => { + const systemRepo = getSystemRepo(); let mockSESv2Client: AwsClientStub; beforeAll(async () => { diff --git a/packages/server/src/fhir/accesspolicy.test.ts b/packages/server/src/fhir/accesspolicy.test.ts index cef86aa04f..4a91ab7e8d 100644 --- a/packages/server/src/fhir/accesspolicy.test.ts +++ b/packages/server/src/fhir/accesspolicy.test.ts @@ -29,16 +29,23 @@ import { inviteUser } from '../admin/invite'; import { initAppServices, shutdownApp } from '../app'; import { registerNew } from '../auth/register'; import { loadTestConfig } from '../config'; -import { withTestContext } from '../test.setup'; +import { createTestProject, withTestContext } from '../test.setup'; import { getRepoForLogin } from './accesspolicy'; -import { Repository, systemRepo } from './repo'; +import { getSystemRepo, Repository } from './repo'; describe('AccessPolicy', () => { + let testProject: Project; + const systemRepo = getSystemRepo(); + beforeAll(async () => { const config = await loadTestConfig(); await initAppServices(config); }); + beforeEach(async () => { + testProject = (await createTestProject()).project; + }); + afterAll(async () => { await shutdownApp(); }); @@ -598,27 +605,22 @@ describe('AccessPolicy', () => { test('Multiple entries per resource type', () => withTestContext(async () => { - const accessPolicy: AccessPolicy = { - resourceType: 'AccessPolicy', - resource: [ - { - resourceType: 'ServiceRequest', - criteria: `ServiceRequest?status=active`, - }, - { - resourceType: 'ServiceRequest', - criteria: `ServiceRequest?status=completed`, - readonly: true, - }, - ], - }; - - const repo = new Repository({ - extendedMode: true, - author: { - reference: 'Practitioner/123', + const { repo } = await createTestProject({ + withRepo: true, + accessPolicy: { + resourceType: 'AccessPolicy', + resource: [ + { + resourceType: 'ServiceRequest', + criteria: `ServiceRequest?status=active`, + }, + { + resourceType: 'ServiceRequest', + criteria: `ServiceRequest?status=completed`, + readonly: true, + }, + ], }, - accessPolicy: accessPolicy, }); // User can create a ServiceRequest with status=active @@ -653,7 +655,6 @@ describe('AccessPolicy', () => { test('ClientApplication with account restriction', () => withTestContext(async () => { - const project = randomUUID(); const account = 'Organization/' + randomUUID(); // Create the access policy @@ -703,12 +704,13 @@ describe('AccessPolicy', () => { { resourceType: 'ProjectMembership', project: { - reference: 'Project/' + project, + reference: 'Project/' + testProject.id, }, profile: createReference(clientApplication as ClientApplication), accessPolicy: createReference(accessPolicy), user: createReference(clientApplication), - } + }, + testProject ); // Create a Patient using the ClientApplication @@ -778,8 +780,6 @@ describe('AccessPolicy', () => { test('ClientApplication with access policy', () => withTestContext(async () => { - const project = randomUUID(); - // Create the access policy const accessPolicy = await systemRepo.createResource({ resourceType: 'AccessPolicy', @@ -809,12 +809,13 @@ describe('AccessPolicy', () => { { resourceType: 'ProjectMembership', project: { - reference: 'Project/' + project, + reference: 'Project/' + testProject.id, }, profile: createReference(clientApplication as ClientApplication), accessPolicy: createReference(accessPolicy), user: createReference(clientApplication), - } + }, + testProject ); // Create a Patient using the ClientApplication @@ -1437,10 +1438,9 @@ describe('AccessPolicy', () => { test('Compound parameterized access policy', () => withTestContext(async () => { - const project = randomUUID(); const adminRepo = new Repository({ author: { reference: 'Practitioner/' + randomUUID() }, - project, + projects: [testProject.id as string], strictMode: true, extendedMode: true, }); @@ -1465,7 +1465,7 @@ describe('AccessPolicy', () => { const membership = await systemRepo.createResource({ resourceType: 'ProjectMembership', user: { reference: 'User/' + randomUUID() }, - project: { reference: 'Project/' + project }, + project: { reference: 'Project/' + testProject.id }, profile: { reference: 'Practitioner/' + randomUUID() }, access: [ { @@ -1479,7 +1479,7 @@ describe('AccessPolicy', () => { ], }); - const repo2 = await getRepoForLogin({ resourceType: 'Login' } as Login, membership); + const repo2 = await getRepoForLogin({ resourceType: 'Login' } as Login, membership, testProject); const check1 = await repo2.readResource('Patient', p1.id as string); expect(check1.id).toBe(p1.id); @@ -1497,10 +1497,9 @@ describe('AccessPolicy', () => { test('String parameters', () => withTestContext(async () => { - const project = randomUUID(); const adminRepo = new Repository({ author: { reference: 'Practitioner/' + randomUUID() }, - project, + projects: [testProject.id as string], strictMode: true, extendedMode: true, }); @@ -1520,8 +1519,9 @@ describe('AccessPolicy', () => { const membership = await systemRepo.createResource({ resourceType: 'ProjectMembership', + meta: { project: testProject.id }, user: { reference: 'User/' + randomUUID() }, - project: { reference: 'Project/' + project }, + project: { reference: 'Project/' + testProject.id }, profile: { reference: 'Practitioner/' + randomUUID() }, access: [ { @@ -1531,7 +1531,7 @@ describe('AccessPolicy', () => { ], }); - const repo2 = await getRepoForLogin({ resourceType: 'Login' } as Login, membership); + const repo2 = await getRepoForLogin({ resourceType: 'Login' } as Login, membership, testProject); const check1 = await repo2.readResource('Task', t1.id as string); expect(check1.id).toBe(t1.id); @@ -1550,7 +1550,7 @@ describe('AccessPolicy', () => { const adminRepo = new Repository({ author: { reference: 'Practitioner/' + randomUUID() }, - project: project.id, + projects: [project.id as string], strictMode: true, extendedMode: true, }); @@ -1593,7 +1593,7 @@ describe('AccessPolicy', () => { sendEmail: false, }); - const repo2 = await getRepoForLogin({ resourceType: 'Login' } as Login, membership, true, true, true); + const repo2 = await getRepoForLogin({ resourceType: 'Login' } as Login, membership, project, true); const check1 = await repo2.readResource('Patient', patient.id as string); expect(check1.id).toBe(patient.id); @@ -1633,7 +1633,7 @@ describe('AccessPolicy', () => { admin: true, }); - const repo2 = await getRepoForLogin({ resourceType: 'Login' } as Login, membership, true, true); + const repo2 = await getRepoForLogin({ resourceType: 'Login' } as Login, membership, project, true); const check1 = await repo2.readResource('Project', project.id as string); expect(check1.id).toEqual(project.id); @@ -1707,8 +1707,8 @@ describe('AccessPolicy', () => { admin: false, }); - const adminRepo = await getRepoForLogin({ resourceType: 'Login' } as Login, adminMembership, true, true); - const nonAdminRepo = await getRepoForLogin({ resourceType: 'Login' } as Login, nonAdminMembership, true, true); + const adminRepo = await getRepoForLogin({ resourceType: 'Login' } as Login, adminMembership, project, true); + const nonAdminRepo = await getRepoForLogin({ resourceType: 'Login' } as Login, nonAdminMembership, project, true); const account1 = randomUUID(); const account2 = randomUUID(); @@ -1775,7 +1775,7 @@ describe('AccessPolicy', () => { }); const repo = new Repository({ author: { reference: 'Practitioner/' + randomUUID() }, - project: project.id, + projects: [project.id as string], projectAdmin: true, strictMode: true, extendedMode: true, @@ -1841,17 +1841,17 @@ describe('AccessPolicy', () => { const project1 = await systemRepo.createResource({ resourceType: 'Project', name: 'Test1' }); const repo1 = new Repository({ author: { reference: 'Practitioner/' + randomUUID() }, - project: project1.id, + projects: [project1.id as string], projectAdmin: true, strictMode: true, extendedMode: true, checkReferencesOnWrite: true, }); - const project2 = await systemRepo.createResource({ resourceType: 'Project', name: 'Test1' }); + const project2 = await systemRepo.createResource({ resourceType: 'Project', name: 'Test2' }); const repo2 = new Repository({ author: { reference: 'Practitioner/' + randomUUID() }, - project: project2.id, + projects: [project2.id as string], projectAdmin: true, strictMode: true, extendedMode: true, @@ -1905,7 +1905,7 @@ describe('AccessPolicy', () => { }); // Get a repo for the user - const repo = await getRepoForLogin(login, updatedMembership, true, true, true); + const repo = await getRepoForLogin(login, updatedMembership, project, true); // Try to search for StructureDefinitions, should succeed const bundle1 = await repo.search({ resourceType: 'StructureDefinition' }); @@ -1937,7 +1937,7 @@ describe('AccessPolicy', () => { withTestContext(async () => { const repo = new Repository({ author: { reference: 'Practitioner/' + randomUUID() }, - project: randomUUID(), + projects: [testProject.id as string], projectAdmin: true, strictMode: true, extendedMode: true, @@ -1969,4 +1969,57 @@ describe('AccessPolicy', () => { expect(outcome.issue?.[0]?.code).toEqual('forbidden'); } })); + + test('Repo with multiple Projects', async () => + withTestContext(async () => { + const patientData: Patient = { + resourceType: 'Patient', + }; + + const project1 = await systemRepo.createResource({ resourceType: 'Project', name: 'Test1' }); + const repo1 = new Repository({ + author: { reference: 'Practitioner/' + randomUUID() }, + projects: [project1.id as string], + projectAdmin: true, + strictMode: true, + extendedMode: true, + checkReferencesOnWrite: true, + }); + + const project2 = await systemRepo.createResource({ resourceType: 'Project', name: 'Test2' }); + const repo2 = new Repository({ + author: { reference: 'Practitioner/' + randomUUID() }, + projects: [project2.id as string, project1.id as string], + projectAdmin: true, + strictMode: true, + extendedMode: true, + checkReferencesOnWrite: true, + }); + + const patient1 = await repo1.createResource(patientData); + const patient2 = await repo2.createResource(patientData); + + await expect(repo1.readResource('Patient', patient1.id as string)).resolves.toEqual(patient1); + await expect(repo1.readResource('Patient', patient2.id as string)).rejects.toBeInstanceOf(Error); + await expect(repo2.readResource('Patient', patient1.id as string)).resolves.toEqual(patient1); + await expect(repo2.readResource('Patient', patient2.id as string)).resolves.toEqual(patient2); + })); + + test('Project Admin cannot link Projects', async () => + withTestContext(async () => { + const { project, membership, login } = await registerNew({ + firstName: 'Link', + lastName: 'Test', + projectName: 'Project link test', + email: randomUUID() + '@example.com', + password: randomUUID(), + }); + expect(project.link).toBeUndefined(); + const repo = await getRepoForLogin(login, membership, project, true); + + project.link = [{ project: { reference: 'Project/foo' } }, { project: { reference: 'Project/bar' } }]; + + const updatedProject = await repo.updateResource(project); + expect(updatedProject.link).toBeUndefined(); + })); }); diff --git a/packages/server/src/fhir/accesspolicy.ts b/packages/server/src/fhir/accesspolicy.ts index dfd31d192f..d6696982bf 100644 --- a/packages/server/src/fhir/accesspolicy.ts +++ b/packages/server/src/fhir/accesspolicy.ts @@ -4,11 +4,12 @@ import { AccessPolicyIpAccessRule, AccessPolicyResource, Login, + Project, ProjectMembership, ProjectMembershipAccess, Reference, } from '@medplum/fhirtypes'; -import { Repository, systemRepo } from './repo'; +import { Repository, getSystemRepo } from './repo'; import { applySmartScopes } from './smart'; /** @@ -18,30 +19,38 @@ import { applySmartScopes } from './smart'; * This method ensures that the repository is setup correctly. * @param login - The user login. * @param membership - The active project membership. - * @param strictMode - Optional flag to enable strict mode for in-depth FHIR schema validation. + * @param project - The Project the current user is a member of. * @param extendedMode - Optional flag to enable extended mode for custom Medplum properties. - * @param checkReferencesOnWrite - Optional flag to enable reference checking on write. * @returns A repository configured for the login details. */ export async function getRepoForLogin( login: Login, membership: ProjectMembership, - strictMode?: boolean, - extendedMode?: boolean, - checkReferencesOnWrite?: boolean + project: Project, + extendedMode?: boolean ): Promise { const accessPolicy = await getAccessPolicyForLogin(login, membership); + let allowedProjects: string[] | undefined; + if (project.id) { + allowedProjects = [project.id]; + } + if (project.link && allowedProjects?.length) { + for (const link of project.link) { + allowedProjects.push(resolveId(link.project) as string); + } + } + return new Repository({ - project: resolveId(membership.project) as string, + projects: allowedProjects, author: membership.profile as Reference, remoteAddress: login.remoteAddress, superAdmin: login.superAdmin, projectAdmin: membership.admin, accessPolicy, - strictMode, + strictMode: project.strictMode, extendedMode, - checkReferencesOnWrite, + checkReferencesOnWrite: project.checkReferencesOnWrite, }); } @@ -80,7 +89,7 @@ export async function getAccessPolicyForLogin( * @param membership - The user project membership. * @returns The parameterized compound access policy. */ -async function buildAccessPolicy(membership: ProjectMembership): Promise { +export async function buildAccessPolicy(membership: ProjectMembership): Promise { let access: ProjectMembershipAccess[] = []; if (membership.accessPolicy) { @@ -128,6 +137,7 @@ async function buildAccessPolicyResources( access: ProjectMembershipAccess, profile: Reference ): Promise { + const systemRepo = getSystemRepo(); const original = await systemRepo.readReference(access.policy as Reference); const params = access.parameter || []; params.push({ name: 'profile', valueReference: profile }); @@ -211,7 +221,7 @@ function applyProjectAdminAccessPolicy( accessPolicy.resource.push({ resourceType: 'Project', criteria: `Project?_id=${resolveId(membership.project)}`, - readonlyFields: ['superAdmin', 'features'], + readonlyFields: ['superAdmin', 'features', 'link'], }); accessPolicy.resource.push({ diff --git a/packages/server/src/fhir/job.test.ts b/packages/server/src/fhir/job.test.ts index a73c12d3dd..b391dfe45d 100644 --- a/packages/server/src/fhir/job.test.ts +++ b/packages/server/src/fhir/job.test.ts @@ -24,11 +24,11 @@ describe('Job status', () => { }); beforeEach(async () => { - const testProject = await createTestProject(); + const testProject = await createTestProject({ withAccessToken: true }); accessToken = testProject.accessToken; asyncJobManager = new AsyncJobExecutor( new Repository({ - project: testProject.project.id as string, + projects: [testProject.project.id as string], author: { reference: 'User/' + randomUUID() }, }) ); diff --git a/packages/server/src/fhir/job.ts b/packages/server/src/fhir/job.ts index d367100dbd..4ca1f121f1 100644 --- a/packages/server/src/fhir/job.ts +++ b/packages/server/src/fhir/job.ts @@ -24,7 +24,7 @@ jobRouter.get( return; } - await sendResponse(res, allOk, asyncJob); + await sendResponse(req, res, allOk, asyncJob); }) ); diff --git a/packages/server/src/fhir/lookups/address.test.ts b/packages/server/src/fhir/lookups/address.test.ts index 61ce833117..cc6bf68bd0 100644 --- a/packages/server/src/fhir/lookups/address.test.ts +++ b/packages/server/src/fhir/lookups/address.test.ts @@ -3,10 +3,12 @@ import { Patient } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; import { initAppServices, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config'; -import { systemRepo } from '../repo'; import { withTestContext } from '../../test.setup'; +import { getSystemRepo } from '../repo'; describe('Address Lookup Table', () => { + const systemRepo = getSystemRepo(); + beforeAll(async () => { const config = await loadTestConfig(); await initAppServices(config); diff --git a/packages/server/src/fhir/lookups/coding.test.ts b/packages/server/src/fhir/lookups/coding.test.ts index 94fe130b5a..37baa8ecd5 100644 --- a/packages/server/src/fhir/lookups/coding.test.ts +++ b/packages/server/src/fhir/lookups/coding.test.ts @@ -3,9 +3,11 @@ import { initAppServices, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config'; import { getDatabasePool } from '../../database'; import { withTestContext } from '../../test.setup'; -import { systemRepo } from '../repo'; +import { getSystemRepo } from '../repo'; describe('Coding lookup table', () => { + const systemRepo = getSystemRepo(); + beforeAll(async () => { const config = await loadTestConfig(); await initAppServices(config); diff --git a/packages/server/src/fhir/lookups/coding.ts b/packages/server/src/fhir/lookups/coding.ts index 3d537f258d..0a26f552bc 100644 --- a/packages/server/src/fhir/lookups/coding.ts +++ b/packages/server/src/fhir/lookups/coding.ts @@ -27,7 +27,7 @@ export class CodingTable extends LookupTable { } async indexResource(client: PoolClient, resource: Resource): Promise { - if (resource.resourceType === 'CodeSystem' && resource.content === 'complete') { + if (resource.resourceType === 'CodeSystem' && (resource.content === 'complete' || resource.content === 'example')) { await this.deleteValuesForResource(client, resource); const elements = this.getCodeSystemElements(resource); @@ -47,13 +47,15 @@ export class CodingTable extends LookupTable { .execute(client); await new DeleteQuery('CodeSystem_Property').where('system', '=', resource.id).execute(client); if (deletedCodes.length) { - await new DeleteQuery('Coding_Property') - .where( - 'coding', - 'IN', - deletedCodes.map((c) => c.id) - ) - .execute(client); + for (let i = 0; i < deletedCodes.length; i += 500) { + await new DeleteQuery('Coding_Property') + .where( + 'coding', + 'IN', + deletedCodes.slice(i, i + 500).map((c) => c.id) + ) + .execute(client); + } } } diff --git a/packages/server/src/fhir/lookups/humanname.test.ts b/packages/server/src/fhir/lookups/humanname.test.ts index 2c9498d114..926f3e3979 100644 --- a/packages/server/src/fhir/lookups/humanname.test.ts +++ b/packages/server/src/fhir/lookups/humanname.test.ts @@ -4,9 +4,11 @@ import { randomUUID } from 'crypto'; import { initAppServices, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config'; import { bundleContains, withTestContext } from '../../test.setup'; -import { systemRepo } from '../repo'; +import { getSystemRepo } from '../repo'; describe('HumanName Lookup Table', () => { + const systemRepo = getSystemRepo(); + beforeAll(async () => { const config = await loadTestConfig(); await initAppServices(config); diff --git a/packages/server/src/fhir/lookups/token.test.ts b/packages/server/src/fhir/lookups/token.test.ts index 747fe8ab12..3281c2748c 100644 --- a/packages/server/src/fhir/lookups/token.test.ts +++ b/packages/server/src/fhir/lookups/token.test.ts @@ -4,9 +4,11 @@ import { randomUUID } from 'crypto'; import { initAppServices, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config'; import { bundleContains, withTestContext } from '../../test.setup'; -import { systemRepo } from '../repo'; +import { getSystemRepo } from '../repo'; describe('Identifier Lookup Table', () => { + const systemRepo = getSystemRepo(); + beforeAll(async () => { const config = await loadTestConfig(); await initAppServices(config); @@ -472,4 +474,31 @@ describe('Identifier Lookup Table', () => { expect(bundleContains(searchResult1, sr1)).toBe(true); expect(bundleContains(searchResult1, sr2)).toBe(false); })); + + test('Identifier value with pipe', () => + withTestContext(async () => { + const system = randomUUID(); + const base = randomUUID(); + const id1 = base + '|1'; + const id2 = base + '|2'; + + const p1 = await systemRepo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + identifier: [{ system, value: id1 }], + }); + + await systemRepo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + identifier: [{ system, value: id2 }], + }); + + const r1 = await systemRepo.search({ + resourceType: 'Patient', + filters: [{ code: 'identifier', operator: Operator.EQUALS, value: `${system}|${id1}` }], + }); + expect(r1.entry?.length).toEqual(1); + expect(r1.entry?.[0]?.resource?.id).toEqual(p1.id); + })); }); diff --git a/packages/server/src/fhir/lookups/token.ts b/packages/server/src/fhir/lookups/token.ts index b5a055e31a..5ed1f49087 100644 --- a/packages/server/src/fhir/lookups/token.ts +++ b/packages/server/src/fhir/lookups/token.ts @@ -8,6 +8,7 @@ import { OperationOutcomeError, PropertyType, SortRule, + splitN, toTypedValue, TypedValue, } from '@medplum/core'; @@ -21,10 +22,10 @@ import { SearchParameter, } from '@medplum/fhirtypes'; import { PoolClient } from 'pg'; +import { getLogger } from '../../context'; import { Column, Condition, Conjunction, Disjunction, Exists, Expression, Negation, SelectQuery } from '../sql'; import { LookupTable } from './lookuptable'; import { compareArrays, deriveIdentifierSearchParameter } from './util'; -import { getRequestContext } from '../../context'; interface Token { readonly code: string; @@ -458,7 +459,7 @@ function buildWhereExpression(tableName: string, filter: Filter): Expression | u * @returns A WHERE Condition on the token table, if applicable, else undefined */ function buildWhereCondition(tableName: string, operator: FhirOperator, query: string): Expression | undefined { - const parts = query.split('|'); + const parts = splitN(query, '|', 2); // Handle the case where the query value is a system|value pair (e.g. token or identifier search) if (parts.length === 2) { const systemCondition = new Condition(new Column(tableName, 'system'), '=', parts[0]); @@ -482,17 +483,15 @@ function buildWhereCondition(tableName: string, operator: FhirOperator, query: s } function buildValueCondition(tableName: string, operator: FhirOperator, value: string): Expression { - const ctx = getRequestContext(); - const column = new Column(tableName, 'value'); if (operator === FhirOperator.TEXT) { - ctx.logger.warn('Potentially expensive token lookup query', { operator }); + getLogger().warn('Potentially expensive token lookup query', { operator }); return new Conjunction([ new Condition(new Column(tableName, 'system'), '=', 'text'), new Condition(column, 'TSVECTOR_SIMPLE', value.trim() + ':*'), ]); } else if (operator === FhirOperator.CONTAINS) { - ctx.logger.warn('Potentially expensive token lookup query', { operator }); + getLogger().warn('Potentially expensive token lookup query', { operator }); return new Condition(column, 'LIKE', value.trim() + '%'); } else { return new Condition(column, '=', value.trim()); diff --git a/packages/server/src/fhir/metadata.ts b/packages/server/src/fhir/metadata.ts index b284779209..b64f7ce248 100644 --- a/packages/server/src/fhir/metadata.ts +++ b/packages/server/src/fhir/metadata.ts @@ -113,6 +113,38 @@ const supportedOperations: Record /** * Returns the Agent for the execute request. - * If using "/Agent/:id/$execute", then the agent ID is read from the path parameter. - * If using "/Agent/$execute?identifier=...", then the agent is searched by identifier. + * If using "/Agent/:id/$push", then the agent ID is read from the path parameter. + * If using "/Agent/$push?identifier=...", then the agent is searched by identifier. * Otherwise, returns undefined. * @param req - The HTTP request. * @param repo - The repository. @@ -144,12 +145,15 @@ async function getDevice(repo: Repository, destination: string): Promise({ reference: destination }); - } catch (err) { + } catch (_err) { return undefined; } } if (destination.startsWith('Device?')) { - return repo.searchOne(parseSearchDefinition(destination)); + return repo.searchOne(parseSearchRequest(destination)); + } + if (isIPv4(destination)) { + return { resourceType: 'Device', url: destination }; } return undefined; } diff --git a/packages/server/src/fhir/operations/codesystemimport.test.ts b/packages/server/src/fhir/operations/codesystemimport.test.ts index 744b2937e2..fc8c2837c9 100644 --- a/packages/server/src/fhir/operations/codesystemimport.test.ts +++ b/packages/server/src/fhir/operations/codesystemimport.test.ts @@ -175,30 +175,6 @@ describe('CodeSystem $import', () => { expect(res.body.issue[0].code).toEqual('invalid'); }); - test('Returns error on ambiguous code system', async () => { - // Upload another copy of the CodeSystem - const res = await request(app) - .post(`/fhir/R4/CodeSystem`) - .set('Authorization', 'Bearer ' + accessToken) - .set('Content-Type', ContentType.FHIR_JSON) - .send(snomedJSON); - expect(res.status).toEqual(201); - - const res2 = await request(app) - .post(`/fhir/R4/CodeSystem/$import`) - .set('Authorization', 'Bearer ' + accessToken) - .set('Content-Type', ContentType.FHIR_JSON) - .send({ - resourceType: 'Parameters', - parameter: [ - { name: 'system', valueUri: snomed.url }, - { name: 'concept', valueCoding: { code: '1', display: 'Aspirin' } }, - ], - }); - expect(res2.status).toEqual(400); - expect(res2.body.issue[0].code).toEqual('invalid'); - }); - test('Returns error on unknown code for property', async () => { const res2 = await request(app) .post(`/fhir/R4/CodeSystem/$import`) diff --git a/packages/server/src/fhir/operations/codesystemimport.ts b/packages/server/src/fhir/operations/codesystemimport.ts index 010a48ec9a..c56bc2a8b1 100644 --- a/packages/server/src/fhir/operations/codesystemimport.ts +++ b/packages/server/src/fhir/operations/codesystemimport.ts @@ -1,4 +1,4 @@ -import { OperationOutcomeError, Operator, allOk, badRequest, normalizeOperationOutcome } from '@medplum/core'; +import { OperationOutcomeError, allOk, badRequest, normalizeOperationOutcome } from '@medplum/core'; import { CodeSystem, Coding, OperationDefinition } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { PoolClient } from 'pg'; @@ -6,6 +6,7 @@ import { requireSuperAdmin } from '../../admin/super'; import { sendOutcome } from '../outcomes'; import { InsertQuery, SelectQuery } from '../sql'; import { parseInputParameters, sendOutputParameters } from './utils/parameters'; +import { findCodeSystem } from './expand'; const operation: OperationDefinition = { resourceType: 'OperationDefinition', @@ -61,18 +62,7 @@ export async function codeSystemImportHandler(req: Request, res: Response): Prom const ctx = requireSuperAdmin(); const params = parseInputParameters(operation, req); - const codeSystems = await ctx.repo.searchResources({ - resourceType: 'CodeSystem', - filters: [{ code: 'url', operator: Operator.EQUALS, value: params.system }], - }); - if (codeSystems.length === 0) { - sendOutcome(res, badRequest('No CodeSystem found with URL ' + params.system)); - return; - } else if (codeSystems.length > 1) { - sendOutcome(res, badRequest('Ambiguous code system URI: ' + params.system)); - return; - } - const codeSystem = codeSystems[0]; + const codeSystem = await findCodeSystem(params.system); try { await ctx.repo.withTransaction(async (db) => { @@ -82,7 +72,7 @@ export async function codeSystemImportHandler(req: Request, res: Response): Prom sendOutcome(res, normalizeOperationOutcome(err)); return; } - await sendOutputParameters(operation, res, allOk, codeSystem); + await sendOutputParameters(req, res, operation, allOk, codeSystem); } export async function importCodeSystem( @@ -159,6 +149,7 @@ async function processProperties( } export const parentProperty = 'http://hl7.org/fhir/concept-properties#parent'; +export const childProperty = 'http://hl7.org/fhir/concept-properties#child'; async function resolveProperty(codeSystem: CodeSystem, code: string, db: PoolClient): Promise<[number, boolean]> { let prop = codeSystem.property?.find((p) => p.code === code); diff --git a/packages/server/src/fhir/operations/codesystemlookup.test.ts b/packages/server/src/fhir/operations/codesystemlookup.test.ts index 0c9b81c2d0..558aa5cab2 100644 --- a/packages/server/src/fhir/operations/codesystemlookup.test.ts +++ b/packages/server/src/fhir/operations/codesystemlookup.test.ts @@ -169,7 +169,7 @@ describe('CodeSystem lookup', () => { expect(res.status).toEqual(400); expect(res.body).toMatchObject({ resourceType: 'OperationOutcome', - issue: [{ severity: 'error', code: 'invalid', details: { text: 'CodeSystem not found' } }], + issue: [{ severity: 'error', code: 'invalid', details: { text: `Code system ${codeSystem.url} not found` } }], }); }); }); diff --git a/packages/server/src/fhir/operations/codesystemlookup.ts b/packages/server/src/fhir/operations/codesystemlookup.ts index 46ab13cd1d..bbd40368b6 100644 --- a/packages/server/src/fhir/operations/codesystemlookup.ts +++ b/packages/server/src/fhir/operations/codesystemlookup.ts @@ -1,12 +1,12 @@ -import { Operator, TypedValue, allOk, badRequest, notFound } from '@medplum/core'; -import { CodeSystem, Coding } from '@medplum/fhirtypes'; +import { TypedValue, allOk, badRequest, notFound } from '@medplum/core'; +import { Coding } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; -import { getAuthenticatedContext } from '../../context'; import { getDatabasePool } from '../../database'; import { sendOutcome } from '../outcomes'; import { Column, Condition, SelectQuery } from '../sql'; import { getOperationDefinition } from './definitions'; import { parseInputParameters, sendOutputParameters } from './utils/parameters'; +import { findCodeSystem } from './expand'; const operation = getOperationDefinition('CodeSystem', 'lookup'); @@ -18,7 +18,6 @@ type CodeSystemLookupParameters = { }; export async function codeSystemLookupHandler(req: Request, res: Response): Promise { - const ctx = getAuthenticatedContext(); const params = parseInputParameters(operation, req); let coding: Coding; @@ -31,14 +30,7 @@ export async function codeSystemLookupHandler(req: Request, res: Response): Prom return; } - const codeSystem = await ctx.repo.searchOne({ - resourceType: 'CodeSystem', - filters: [{ code: 'url', operator: Operator.EQUALS, value: coding.system as string }], - }); - if (!codeSystem) { - sendOutcome(res, badRequest('CodeSystem not found')); - return; - } + const codeSystem = await findCodeSystem(coding.system as string); const lookup = new SelectQuery('Coding'); const codeSystemTable = lookup.getNextJoinAlias(); @@ -95,5 +87,5 @@ export async function codeSystemLookupHandler(req: Request, res: Response): Prom } } - await sendOutputParameters(operation, res, allOk, output); + await sendOutputParameters(req, res, operation, allOk, output); } diff --git a/packages/server/src/fhir/operations/codesystemvalidatecode.test.ts b/packages/server/src/fhir/operations/codesystemvalidatecode.test.ts index b65b420e27..d3df957ffc 100644 --- a/packages/server/src/fhir/operations/codesystemvalidatecode.test.ts +++ b/packages/server/src/fhir/operations/codesystemvalidatecode.test.ts @@ -146,7 +146,7 @@ describe('CodeSystem validate-code', () => { expect(res.status).toEqual(400); expect(res.body).toMatchObject({ resourceType: 'OperationOutcome', - issue: [{ severity: 'error', code: 'invalid', details: { text: 'CodeSystem not found' } }], + issue: [{ severity: 'error', code: 'invalid', details: { text: `Code system ${codeSystem.url} not found` } }], }); }); }); diff --git a/packages/server/src/fhir/operations/codesystemvalidatecode.ts b/packages/server/src/fhir/operations/codesystemvalidatecode.ts index 2452fdcdfb..345667ea2c 100644 --- a/packages/server/src/fhir/operations/codesystemvalidatecode.ts +++ b/packages/server/src/fhir/operations/codesystemvalidatecode.ts @@ -1,12 +1,12 @@ -import { Operator, allOk, badRequest } from '@medplum/core'; +import { allOk, badRequest } from '@medplum/core'; import { CodeSystem, Coding } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; -import { getAuthenticatedContext } from '../../context'; import { getDatabasePool } from '../../database'; import { sendOutcome } from '../outcomes'; import { Column, Condition, SelectQuery } from '../sql'; import { getOperationDefinition } from './definitions'; import { parseInputParameters, sendOutputParameters } from './utils/parameters'; +import { findCodeSystem } from './expand'; const operation = getOperationDefinition('CodeSystem', 'validate-code'); @@ -26,7 +26,6 @@ type CodeSystemValidateCodeParameters = { * @param res - The HTTP response. */ export async function codeSystemValidateCodeHandler(req: Request, res: Response): Promise { - const ctx = getAuthenticatedContext(); const params = parseInputParameters(operation, req); let coding: Coding; @@ -39,15 +38,20 @@ export async function codeSystemValidateCodeHandler(req: Request, res: Response) return; } - const codeSystem = await ctx.repo.searchOne({ - resourceType: 'CodeSystem', - filters: [{ code: 'url', operator: Operator.EQUALS, value: coding.system as string }], - }); - if (!codeSystem) { - sendOutcome(res, badRequest('CodeSystem not found')); - return; + const codeSystem = await findCodeSystem(coding.system as string); + const result = await validateCode(codeSystem, coding.code as string); + + const output: Record = Object.create(null); + if (result) { + output.result = true; + output.display = result.display; + } else { + output.result = false; } + await sendOutputParameters(req, res, operation, allOk, output); +} +export async function validateCode(codeSystem: CodeSystem, code: string): Promise { const query = new SelectQuery('Coding'); const codeSystemTable = query.getNextJoinAlias(); query.innerJoin( @@ -55,16 +59,13 @@ export async function codeSystemValidateCodeHandler(req: Request, res: Response) codeSystemTable, new Condition(new Column('Coding', 'system'), '=', new Column(codeSystemTable, 'id')) ); - query.column('display').where(new Column(codeSystemTable, 'id'), '=', codeSystem.id).where('code', '=', coding.code); + query + .column('id') + .column('display') + .where(new Column(codeSystemTable, 'id'), '=', codeSystem.id) + .where('code', '=', code); const db = getDatabasePool(); const result = await query.execute(db); - const output: Record = Object.create(null); - if (result.length) { - output.result = true; - output.display = result[0].display; - } else { - output.result = false; - } - await sendOutputParameters(operation, res, allOk, output); + return result.length ? { id: result[0].id, system: codeSystem.url, code, display: result[0].display } : undefined; } diff --git a/packages/server/src/fhir/operations/conceptmaptranslate.ts b/packages/server/src/fhir/operations/conceptmaptranslate.ts index fab90a3d4d..9be64d2e9d 100644 --- a/packages/server/src/fhir/operations/conceptmaptranslate.ts +++ b/packages/server/src/fhir/operations/conceptmaptranslate.ts @@ -26,7 +26,7 @@ export async function conceptMapTranslateHandler(req: Request, res: Response): P } const output = conceptMapTranslate(map, params); - await sendOutputParameters(operation, res, allOk, output); + await sendOutputParameters(req, res, operation, allOk, output); } async function lookupConceptMap( diff --git a/packages/server/src/fhir/operations/csv.test.ts b/packages/server/src/fhir/operations/csv.test.ts index b2c2a2356d..0bd0cb8a19 100644 --- a/packages/server/src/fhir/operations/csv.test.ts +++ b/packages/server/src/fhir/operations/csv.test.ts @@ -14,7 +14,7 @@ describe('CSV Export', () => { beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); - accessToken = await initTestAuth({ strictMode: false }); + accessToken = await initTestAuth({ project: { strictMode: false } }); }); afterAll(async () => { diff --git a/packages/server/src/fhir/operations/deploy.ts b/packages/server/src/fhir/operations/deploy.ts index 76a0fd7234..d961432447 100644 --- a/packages/server/src/fhir/operations/deploy.ts +++ b/packages/server/src/fhir/operations/deploy.ts @@ -9,7 +9,7 @@ import { UpdateFunctionCodeCommand, UpdateFunctionConfigurationCommand, } from '@aws-sdk/client-lambda'; -import { allOk, badRequest, ContentType, getReferenceString, sleep } from '@medplum/core'; +import { ContentType, allOk, badRequest, getReferenceString, sleep } from '@medplum/core'; import { Binary, Bot } from '@medplum/fhirtypes'; import { ConfiguredRetryStrategy } from '@smithy/util-retry'; import { Request, Response } from 'express'; @@ -19,7 +19,7 @@ import { asyncWrap } from '../../async'; import { getConfig } from '../../config'; import { getAuthenticatedContext, getRequestContext } from '../../context'; import { sendOutcome } from '../outcomes'; -import { systemRepo } from '../repo'; +import { getSystemRepo } from '../repo'; import { getBinaryStorage } from '../storage'; import { isBotEnabled } from './execute'; @@ -35,10 +35,15 @@ const PdfPrinter = require("pdfmake"); const userCode = require("./user.js"); exports.handler = async (event, context) => { - const { baseUrl, accessToken, contentType, secrets } = event; + const { baseUrl, accessToken, contentType, secrets, traceId } = event; const medplum = new MedplumClient({ baseUrl, - fetch, + fetch: function(url, options = {}) { + options.headers ||= {}; + options.headers['X-Trace-Id'] = traceId; + options.headers['traceparent'] = traceId; + return fetch(url, options); + }, createPdf, }); medplum.setAccessToken(accessToken); @@ -47,7 +52,7 @@ exports.handler = async (event, context) => { if (contentType === ContentType.HL7_V2 && input) { input = Hl7Message.parse(input); } - let result = await userCode.handler(medplum, { input, contentType, secrets }); + let result = await userCode.handler(medplum, { input, contentType, secrets, traceId }); if (contentType === ContentType.HL7_V2 && result) { result = result.toString(); } @@ -111,6 +116,7 @@ export const deployHandler = asyncWrap(async (req: Request, res: Response) => { await ctx.repo.readResource('Bot', id); // Then read the bot as system user to load extended metadata + const systemRepo = getSystemRepo(); const bot = await systemRepo.readResource('Bot', id); if (!(await isBotEnabled(bot))) { diff --git a/packages/server/src/fhir/operations/evaluatemeasure.ts b/packages/server/src/fhir/operations/evaluatemeasure.ts index e92cb0e6de..7dcfa13dd3 100644 --- a/packages/server/src/fhir/operations/evaluatemeasure.ts +++ b/packages/server/src/fhir/operations/evaluatemeasure.ts @@ -1,4 +1,4 @@ -import { Operator, badRequest, created, parseSearchDefinition } from '@medplum/core'; +import { Operator, badRequest, created, parseSearchRequest } from '@medplum/core'; import { Measure, MeasureGroup, @@ -50,7 +50,7 @@ export async function evaluateMeasureHandler(req: Request, res: Response): Promi } const measureReport = await evaluateMeasure(ctx.repo, params, measure); - await sendResponse(res, created, measureReport); + await sendResponse(req, res, created, measureReport); } /** @@ -177,7 +177,7 @@ async function evaluateCount( criteria: string, params: EvaluateMeasureParameters ): Promise { - const searchDefinition = parseSearchDefinition(criteria); + const searchDefinition = parseSearchRequest(criteria); searchDefinition.total = 'accurate'; if (!searchDefinition.filters) { diff --git a/packages/server/src/fhir/operations/execute.ts b/packages/server/src/fhir/operations/execute.ts index 21cd38f981..e02b65e009 100644 --- a/packages/server/src/fhir/operations/execute.ts +++ b/packages/server/src/fhir/operations/execute.ts @@ -33,14 +33,14 @@ import vm from 'node:vm'; import { TextDecoder, TextEncoder } from 'util'; import { asyncWrap } from '../../async'; import { getConfig } from '../../config'; -import { getAuthenticatedContext, getRequestContext } from '../../context'; +import { getAuthenticatedContext, getLogger } from '../../context'; import { generateAccessToken } from '../../oauth/keys'; import { recordHistogramValue } from '../../otel/otel'; import { AuditEventOutcome, logAuditEvent } from '../../util/auditevent'; import { MockConsole } from '../../util/console'; -import { createAuditEventEntities } from '../../workers/utils'; +import { createAuditEventEntities, findProjectMembership } from '../../workers/utils'; import { sendOutcome } from '../outcomes'; -import { systemRepo } from '../repo'; +import { getSystemRepo } from '../repo'; import { getBinaryStorage } from '../storage'; export const EXECUTE_CONTENT_TYPES = [ContentType.JSON, ContentType.FHIR_JSON, ContentType.TEXT, ContentType.HL7_V2]; @@ -56,6 +56,7 @@ export interface BotExecutionRequest { readonly remoteAddress?: string; readonly forwardedFor?: string; readonly requestTime?: string; + readonly traceId?: string; } export interface BotExecutionResult { @@ -81,14 +82,26 @@ export const executeHandler = asyncWrap(async (req: Request, res: Response) => { } // Then read the bot as system user to load extended metadata + const systemRepo = getSystemRepo(); const bot = await systemRepo.readResource('Bot', userBot.id as string); + // Find the project membership + // If the bot is configured to run as the user, then use the current user's membership + // Otherwise, use the bot's project membership + const project = bot.meta?.project as string; + let runAs: ProjectMembership | undefined; + if (bot.runAsUser) { + runAs = ctx.membership; + } else { + runAs = (await findProjectMembership(project, createReference(bot))) ?? ctx.membership; + } + // Execute the bot // If the request is HTTP POST, then the body is the input // If the request is HTTP GET, then the query string is the input const result = await executeBot({ bot, - runAs: ctx.membership, + runAs, input: req.method === 'POST' ? req.body : req.query, contentType: req.header('content-type') as string, }); @@ -188,6 +201,7 @@ export async function executeBot(request: BotExecutionRequest): Promise { + const systemRepo = getSystemRepo(); const project = await systemRepo.readResource('Project', bot.meta?.project as string); return !!project.features?.includes('bots'); } @@ -234,7 +248,7 @@ async function writeBotInputToStorage(request: BotExecutionRequest): Promise { - const { bot, runAs, input, contentType } = request; + const { bot, runAs, input, contentType, traceId } = request; const config = getConfig(); const accessToken = await getBotAccessToken(runAs); const secrets = await getBotSecrets(bot); @@ -280,6 +294,7 @@ async function runInLambda(request: BotExecutionRequest): Promise { - const { bot, runAs, input, contentType } = request; + const { bot, runAs, input, contentType, traceId } = request; const config = getConfig(); if (!config.vmContextBotsEnabled) { @@ -385,6 +400,7 @@ async function runInVmContext(request: BotExecutionRequest): Promise({ reference: codeUrl } as Reference); const stream = await getBinaryStorage().readBinary(binary); const code = await readStreamToString(stream); @@ -406,6 +422,7 @@ async function runInVmContext(request: BotExecutionRequest): Promise { - const { baseUrl, accessToken, contentType, secrets } = event; + const { baseUrl, accessToken, contentType, secrets, traceId } = event; const medplum = new MedplumClient({ baseUrl, - fetch, + fetch: function(url, options = {}) { + options.headers ||= {}; + options.headers['X-Trace-Id'] = traceId; + options.headers['traceparent'] = traceId; + return fetch(url, options); + }, }); medplum.setAccessToken(accessToken); try { @@ -434,7 +456,7 @@ async function runInVmContext(request: BotExecutionRequest): Promise { + const systemRepo = getSystemRepo(); + // Create the Login resource const login = await systemRepo.createResource({ resourceType: 'Login', @@ -493,6 +517,7 @@ async function getBotAccessToken(runAs: ProjectMembership): Promise { } async function getBotSecrets(bot: Bot): Promise> { + const systemRepo = getSystemRepo(); const project = await systemRepo.readResource('Project', bot.meta?.project as string); const secrets = Object.fromEntries(project.secret?.map((secret) => [secret.name, secret]) || []); return secrets; @@ -568,6 +593,7 @@ async function createAuditEvent( const destination = bot.auditEventDestination ?? ['resource']; if (destination.includes('resource')) { + const systemRepo = getSystemRepo(); await systemRepo.createResource(auditEvent); } if (destination.includes('log')) { diff --git a/packages/server/src/fhir/operations/expand.test.ts b/packages/server/src/fhir/operations/expand.test.ts index 42975f4cf8..3605712c77 100644 --- a/packages/server/src/fhir/operations/expand.test.ts +++ b/packages/server/src/fhir/operations/expand.test.ts @@ -1,28 +1,36 @@ import { ContentType, LOINC, SNOMED } from '@medplum/core'; -import { OperationOutcome, ValueSet, ValueSetExpansionContains } from '@medplum/fhirtypes'; +import { + CodeSystem, + OperationOutcome, + Project, + ValueSet, + ValueSetExpansion, + ValueSetExpansionContains, +} from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; import express from 'express'; import request from 'supertest'; import { initApp, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config'; import { initTestAuth, withTestContext } from '../../test.setup'; -import { systemRepo } from '../repo'; +import { getSystemRepo } from '../repo'; -const app = express(); -let accessToken: string; +describe.each>([{ features: [] }, { features: ['terminology'] }])('Expand with %j', (projectProps) => { + const app = express(); + const systemRepo = getSystemRepo(); + let accessToken: string; -describe('Expand', () => { beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); - accessToken = await initTestAuth(); + accessToken = await initTestAuth(projectProps); }); afterAll(async () => { await shutdownApp(); }); - test('No system', async () => { + test('No ValueSet URL', async () => { const res = await request(app) .get(`/fhir/R4/ValueSet/$expand`) .set('Authorization', 'Bearer ' + accessToken); @@ -38,7 +46,7 @@ describe('Expand', () => { expect((res.body as OperationOutcome).issue?.[0].details?.text).toContain('ValueSet not found'); }); - test('No systems', async () => { + test('No logical definition', async () => { const url = 'https://example.com/ValueSet/' + randomUUID(); await withTestContext(() => systemRepo.createResource({ @@ -51,7 +59,9 @@ describe('Expand', () => { .get(`/fhir/R4/ValueSet/$expand?url=${encodeURIComponent(url)}`) .set('Authorization', 'Bearer ' + accessToken); expect(res.status).toBe(400); - expect((res.body as OperationOutcome).issue?.[0].details?.text).toContain('No systems found'); + expect((res.body as OperationOutcome).issue?.[0].details?.text).toMatch( + /(^Missing ValueSet definition$)|(^No systems found$)/ + ); }); test('No filter', async () => { @@ -80,12 +90,12 @@ describe('Expand', () => { .get( `/fhir/R4/ValueSet/$expand?url=${encodeURIComponent( 'http://hl7.org/fhir/ValueSet/observation-codes' - )}&filter=left` + )}&filter=rate` ) .set('Authorization', 'Bearer ' + accessToken); expect(res.status).toBe(200); expect(res.body.expansion.contains[0].system).toBe(LOINC); - expect(res.body.expansion.contains[0].display).toMatch(/left/i); + expect(res.body.expansion.contains[0].display).toMatch(/rate/i); }); test('Success with count and offset', async () => { @@ -93,36 +103,13 @@ describe('Expand', () => { .get( `/fhir/R4/ValueSet/$expand?url=${encodeURIComponent( 'http://hl7.org/fhir/ValueSet/observation-codes' - )}&filter=left&offset=1&count=1` + )}&filter=blood&offset=1&count=1` ) .set('Authorization', 'Bearer ' + accessToken); expect(res.status).toBe(200); - expect(res.body.expansion.offset).toBe(1); expect(res.body.expansion.contains.length).toBe(1); expect(res.body.expansion.contains[0].system).toBe(LOINC); - expect(res.body.expansion.contains[0].display).toMatch(/left/i); - }); - - test('Resource types', async () => { - const valueSet = 'http://hl7.org/fhir/ValueSet/resource-types|4.0.1'; - const res = await request(app) - .get(`/fhir/R4/ValueSet/$expand?url=${encodeURIComponent(valueSet)}&filter=Patient`) - .set('Authorization', 'Bearer ' + accessToken); - expect(res.status).toBe(200); - expect(res.body).toMatchObject({ - resourceType: 'ValueSet', - url: 'http://hl7.org/fhir/ValueSet/resource-types', - expansion: { - offset: 0, - contains: [ - { - system: 'http://hl7.org/fhir/resource-types', - code: 'Patient', - display: 'Patient', - }, - ], - }, - }); + expect(res.body.expansion.contains[0].display).toMatch(/blood/i); }); test('No duplicates', async () => { @@ -135,7 +122,6 @@ describe('Expand', () => { resourceType: 'ValueSet', url: 'http://hl7.org/fhir/ValueSet/subscription-status', expansion: { - offset: 0, contains: [ { system: 'http://hl7.org/fhir/subscription-status', @@ -148,29 +134,6 @@ describe('Expand', () => { expect(res.body.expansion.contains.length).toBe(1); }); - test('External system', async () => { - const valueSet = 'http://hl7.org/fhir/ValueSet/servicerequest-category'; - const filter = 'imaging'; - const res = await request(app) - .get(`/fhir/R4/ValueSet/$expand?url=${encodeURIComponent(valueSet)}&filter=${encodeURIComponent(filter)}`) - .set('Authorization', 'Bearer ' + accessToken); - expect(res.status).toBe(200); - expect(res.body).toMatchObject({ - resourceType: 'ValueSet', - url: valueSet, - expansion: { - offset: 0, - contains: [ - { - system: SNOMED, - code: '363679005', - display: 'Imaging', - }, - ], - }, - }); - }); - test('Marital status', async () => { // This is a good test, because it covers a bunch of edge cases. // Marital status is the combination of two code systems: http://hl7.org/fhir/v3/MaritalStatus and http://hl7.org/fhir/v3/NullFlavor @@ -186,7 +149,6 @@ describe('Expand', () => { resourceType: 'ValueSet', url: valueSet, expansion: { - offset: 0, contains: [ { system: 'http://terminology.hl7.org/CodeSystem/v3-MaritalStatus', @@ -208,19 +170,19 @@ describe('Expand', () => { .get( `/fhir/R4/ValueSet/$expand?url=${encodeURIComponent( 'http://hl7.org/fhir/ValueSet/observation-codes' - )}&filter=${encodeURIComponent('(left)')}` + )}&filter=${encodeURIComponent('intention - reported')}` ) .set('Authorization', 'Bearer ' + accessToken); expect(res.status).toBe(200); expect(res.body.expansion.contains[0].system).toBe(LOINC); - expect(res.body.expansion.contains[0].display).toMatch(/left/i); + expect(res.body.expansion.contains[0].display).toMatch(/pregnancy intention/i); }); test('Handle empty string after punctuation', async () => { const res = await request(app) .get( `/fhir/R4/ValueSet/$expand?url=${encodeURIComponent( - 'http://hl7.org/fhir/ValueSet/observation-codes' + 'http://hl7.org/fhir/ValueSet/care-plan-activity-kind' )}&filter=${encodeURIComponent('[')}` ) .set('Authorization', 'Bearer ' + accessToken); @@ -229,25 +191,23 @@ describe('Expand', () => { test('No null `display` field', async () => { const res = await request(app) - .get(`/fhir/R4/ValueSet/$expand?url=${encodeURIComponent('http://hl7.org/fhir/ValueSet/observation-codes')}`) + .get( + `/fhir/R4/ValueSet/$expand?url=${encodeURIComponent('http://hl7.org/fhir/ValueSet/care-plan-activity-kind')}` + ) .set('Authorization', 'Bearer ' + accessToken); expect(res.status).toBe(200); const body = res.body as ValueSet; expect(body).toBeDefined(); - let foundNullDisplay = false; const contains = body.expansion?.contains; expect(contains).toBeDefined(); expect(contains?.length).toBeGreaterThan(0); for (const code of contains as ValueSetExpansionContains[]) { - if (code.display && code.display === null) { - foundNullDisplay = true; - break; + if (code.display === null) { + fail(`Found null display value for coding ${code.system}|${code.code}`); } } - - expect(foundNullDisplay).toBe(false); }); test('User uploaded ValueSet', async () => { @@ -258,26 +218,197 @@ describe('Expand', () => { .send({ resourceType: 'ValueSet', status: 'active', - url: 'https://example.com/fhir/ValueSet/fruits', + url: 'https://example.com/fhir/ValueSet/clinical-resources' + randomUUID(), expansion: { timestamp: '2023-09-13T23:24:00.000Z', }, compose: { include: [ { - system: 'http://example.com/fruits', + system: 'http://hl7.org/fhir/resource-types', concept: [ { - code: 'apple', - display: 'Apple', + code: 'Patient', }, { - code: 'banana', - display: 'Banana', + code: 'Practitioner', }, { - code: 'cherry', - display: 'Cherry', + code: 'Observation', + }, + ], + }, + ], + }, + }); + expect(res1.status).toBe(201); + const url = res1.body.url; + + const res2 = await request(app) + .get(`/fhir/R4/ValueSet/$expand?url=${encodeURIComponent(url)}`) + .set('Authorization', 'Bearer ' + accessToken); + expect(res2.status).toBe(200); + expect(res2.body.expansion.contains).toContainEqual({ + system: 'http://hl7.org/fhir/resource-types', + code: 'Patient', + display: 'Patient', + }); + expect(res2.body.expansion.contains).toContainEqual({ + system: 'http://hl7.org/fhir/resource-types', + code: 'Practitioner', + display: 'Practitioner', + }); + expect(res2.body.expansion.contains).toContainEqual({ + system: 'http://hl7.org/fhir/resource-types', + code: 'Observation', + display: 'Observation', + }); + }); + + test('CodeSystem resolution', async () => { + const codeSystem: CodeSystem = { + resourceType: 'CodeSystem', + status: 'active', + url: 'http://example.com/CodeSystem/foo' + randomUUID(), + version: '1', + content: 'not-present', + }; + const superAdminAccessToken = await initTestAuth({ superAdmin: true }); + + // First version of code system + const res1 = await request(app) + .post('/fhir/R4/CodeSystem') + .set('Authorization', 'Bearer ' + accessToken) + .send(codeSystem); + expect(res1.status).toEqual(201); + const res2 = await request(app) + .post(`/fhir/R4/CodeSystem/$import`) + .set('Authorization', 'Bearer ' + superAdminAccessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send({ + resourceType: 'Parameters', + parameter: [ + { name: 'system', valueUri: codeSystem.url }, + { name: 'concept', valueCoding: { code: 'foo', display: 'Foo' } }, + { name: 'concept', valueCoding: { code: 'bar', display: 'Bar' } }, + ], + }); + expect(res2.status).toEqual(200); + + // Second version of code system + codeSystem.version = '2'; + const res3 = await request(app) + .post('/fhir/R4/CodeSystem') + .set('Authorization', 'Bearer ' + accessToken) + .send(codeSystem); + expect(res3.status).toEqual(201); + const res4 = await request(app) + .post(`/fhir/R4/CodeSystem/$import`) + .set('Authorization', 'Bearer ' + superAdminAccessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send({ + resourceType: 'Parameters', + parameter: [ + { name: 'system', valueUri: codeSystem.url }, + { name: 'concept', valueCoding: { code: 'baz', display: 'Baz' } }, + { name: 'concept', valueCoding: { code: 'quux', display: 'Quux' } }, + ], + }); + expect(res4.status).toEqual(200); + + // ValueSet containing all of target CodeSystem + const res5 = await request(app) + .post('/fhir/R4/ValueSet') + .set('Authorization', 'Bearer ' + accessToken) + .send({ + resourceType: 'ValueSet', + status: 'active', + url: 'http://example.com/ValueSet/bar' + randomUUID(), + compose: { + include: [{ system: codeSystem.url }], + }, + }); + expect(res5.status).toEqual(201); + const valueSet = res5.body as ValueSet; + + const res6 = await request(app) + .get(`/fhir/R4/ValueSet/$expand?url=${encodeURIComponent(valueSet.url as string)}`) + .set('Authorization', 'Bearer ' + accessToken); + expect(res6.status).toEqual(200); + }); +}); + +describe('Updated implementation', () => { + const app = express(); + let accessToken: string; + + beforeAll(async () => { + const config = await loadTestConfig(); + await initApp(app, config); + accessToken = await initTestAuth({ project: { features: ['terminology'] } }); + }); + + afterAll(async () => { + await shutdownApp(); + }); + + test('Returns error for recursive definition', async () => { + const valueSet: ValueSet = { + resourceType: 'ValueSet', + status: 'active', + url: 'https://example.com/fhir/ValueSet/recursive' + randomUUID(), + compose: { + include: [{ valueSet: ['http://example.com/ValueSet/recursive'] }], + }, + }; + const res1 = await request(app) + .post(`/fhir/R4/ValueSet`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send(valueSet); + expect(res1.status).toBe(201); + + const res2 = await request(app) + .get(`/fhir/R4/ValueSet/$expand?url=${encodeURIComponent(valueSet.url as string)}`) + .set('Authorization', 'Bearer ' + accessToken); + expect(res2.status).toEqual(400); + }); + + test('Subsumption', async () => { + const res = await request(app) + .get( + `/fhir/R4/ValueSet/$expand?url=${encodeURIComponent('http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype')}&count=100` + ) + .set('Authorization', 'Bearer ' + accessToken); + expect(res.status).toEqual(200); + const expansion = res.body.expansion as ValueSetExpansion; + + expect( + expansion.contains?.find( + (c) => c.system === 'http://terminology.hl7.org/CodeSystem/v3-RoleCode' && c.code === 'FRND' + )?.display + ).toEqual('unrelated friend'); + }); + + test('Returns error when CodeSystem not found', async () => { + const res1 = await request(app) + .post(`/fhir/R4/ValueSet`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send({ + resourceType: 'ValueSet', + status: 'active', + url: 'https://example.com/csdne' + randomUUID(), + expansion: { + timestamp: '2023-09-13T23:24:00.000Z', + }, + compose: { + include: [ + { + system: 'http://example.com/the-codesystem-does-not-exist', + concept: [ + { + code: '0', }, ], }, @@ -285,14 +416,129 @@ describe('Expand', () => { }, }); expect(res1.status).toBe(201); + const url = res1.body.url; + + const res2 = await request(app) + .get(`/fhir/R4/ValueSet/$expand?url=${encodeURIComponent(url)}`) + .set('Authorization', 'Bearer ' + accessToken); + expect(res2.status).toBe(400); + expect(res2.body.issue[0].details.text).toEqual( + 'Code system http://example.com/the-codesystem-does-not-exist not found' + ); + }); + + test('Prefers current Project version of common CodeSystem', async () => { + const res1 = await request(app) + .post(`/fhir/R4/CodeSystem`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send({ + resourceType: 'CodeSystem', + status: 'active', + url: SNOMED, + content: 'complete', + concept: [{ code: '314159265', display: 'Test SNOMED override' }], + }); + expect(res1.status).toEqual(201); + + const res2 = await request(app) + .post(`/fhir/R4/ValueSet`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send({ + resourceType: 'ValueSet', + status: 'active', + url: 'https://example.com/snomed-all' + randomUUID(), + compose: { + include: [ + { + system: SNOMED, + }, + ], + }, + }); + expect(res2.status).toBe(201); + + const res3 = await request(app) + .get(`/fhir/R4/ValueSet/$expand?url=${encodeURIComponent(res2.body.url)}`) + .set('Authorization', 'Bearer ' + accessToken); + expect(res3.status).toBe(200); + const coding = res3.body.expansion.contains[0]; + expect(coding.system).toBe(SNOMED); + expect(coding.code).toBe('314159265'); + expect(coding.display).toEqual('Test SNOMED override'); + }); + + test('Returns error when property filter is invalid for CodeSystem', async () => { + const res1 = await request(app) + .post(`/fhir/R4/CodeSystem`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send({ + resourceType: 'CodeSystem', + status: 'active', + url: 'http://example.com/custom-code-system', + content: 'complete', + hierarchyMeaning: 'grouped-by', + concept: [{ code: 'A', concept: [{ code: 'B' }] }], + }); + expect(res1.status).toEqual(201); const res2 = await request(app) + .post(`/fhir/R4/ValueSet`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send({ + resourceType: 'ValueSet', + status: 'active', + url: 'https://example.com/invalid-hierarchy' + randomUUID(), + compose: { + include: [ + { + system: 'http://example.com/custom-code-system', + filter: [{ property: 'concept', op: 'is-a', value: 'A' }], + }, + ], + }, + }); + expect(res2.status).toBe(201); + + const res3 = await request(app) + .get(`/fhir/R4/ValueSet/$expand?url=${encodeURIComponent(res2.body.url)}`) + .set('Authorization', 'Bearer ' + accessToken); + expect(res3.status).toBe(400); + expect(res3.body.issue[0].details.text).toMatch(/invalid filter/i); + }); + + test('Includes ancestor code in is-a filter', async () => { + const res = await request(app) + .get(`/fhir/R4/ValueSet/$expand?url=${encodeURIComponent('http://hl7.org/fhir/ValueSet/care-team-category')}`) + .set('Authorization', 'Bearer ' + accessToken); + expect(res.status).toEqual(200); + const expansion = res.body.expansion as ValueSetExpansion; + + expect(expansion.contains).toHaveLength(1); + expect(expansion.contains?.[0]).toEqual({ + system: LOINC, + code: 'LA28865-6', + display: expect.stringMatching(/care team/i), + }); + }); + + test('Recursive subsumption', async () => { + const res = await request(app) .get( - `/fhir/R4/ValueSet/$expand?url=${encodeURIComponent('https://example.com/fhir/ValueSet/fruits')}&filter=apple` + `/fhir/R4/ValueSet/$expand?url=${encodeURIComponent('http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype')}&count=200` ) .set('Authorization', 'Bearer ' + accessToken); - expect(res2.status).toBe(200); - expect(res2.body.expansion.contains[0].system).toBe('http://example.com/fruits'); - expect(res2.body.expansion.contains[0].display).toMatch(/apple/i); + expect(res.status).toEqual(200); + const expansion = res.body.expansion as ValueSetExpansion; + + expect( + expansion.contains?.filter((c) => c.system === 'http://terminology.hl7.org/CodeSystem/v2-0131') + ).toHaveLength(12); + expect( + expansion.contains?.filter((c) => c.system === 'http://terminology.hl7.org/CodeSystem/v3-RoleCode') + ).toHaveLength(110); }); }); diff --git a/packages/server/src/fhir/operations/expand.ts b/packages/server/src/fhir/operations/expand.ts index 270b8a7b95..5dba838b1f 100644 --- a/packages/server/src/fhir/operations/expand.ts +++ b/packages/server/src/fhir/operations/expand.ts @@ -1,11 +1,16 @@ -import { badRequest, Operator as SearchOperator } from '@medplum/core'; -import { ValueSet, ValueSetComposeInclude } from '@medplum/fhirtypes'; +import { badRequest, OperationOutcomeError, Operator, Operator as SearchOperator } from '@medplum/core'; +import { CodeSystem, Coding, ValueSet, ValueSetComposeInclude, ValueSetExpansionContains } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { asyncWrap } from '../../async'; -import { getDatabasePool } from '../../database'; import { sendOutcome } from '../outcomes'; -import { systemRepo } from '../repo'; -import { Condition, Conjunction, Disjunction, Expression, SelectQuery } from '../sql'; +import { getSystemRepo } from '../repo'; +import { Column, Condition, Conjunction, SelectQuery, Expression, Disjunction, Union } from '../sql'; +import { getAuthenticatedContext } from '../../context'; +import { parentProperty } from './codesystemimport'; +import { clamp } from './utils/parameters'; +import { validateCode } from './codesystemvalidatecode'; +import { getDatabasePool } from '../../database'; +import { r4ProjectId } from '../../seed'; // Implements FHIR "Value Set Expansion" // https://www.hl7.org/fhir/operation-valueset-expand.html @@ -14,7 +19,7 @@ import { Condition, Conjunction, Disjunction, Expression, SelectQuery } from '.. // 1) The "url" parameter to identify the value set // 2) The "filter" parameter for text search // 3) Optional offset for pagination (default is zero for beginning) -// 4) Optional count for pagination (default is 10, can be 1-20) +// 4) Optional count for pagination (default is 10, can be 1-1000) export const expandOperator = asyncWrap(async (req: Request, res: Response) => { let url = req.query.url as string | undefined; @@ -34,20 +39,12 @@ export const expandOperator = asyncWrap(async (req: Request, res: Response) => { url = url.substring(0, pipeIndex); } - // First, get the ValueSet resource - const valueSet = await getValueSetByUrl(url); + let valueSet = await getValueSetByUrl(url); if (!valueSet) { sendOutcome(res, badRequest('ValueSet not found')); return; } - // Build a collection of all systems to include - const systemExpressions = buildValueSetSystems(valueSet); - if (systemExpressions.length === 0) { - sendOutcome(res, badRequest('No systems found')); - return; - } - let offset = 0; if (req.query.offset) { offset = Math.max(0, parseInt(req.query.offset as string, 10)); @@ -55,7 +52,40 @@ export const expandOperator = asyncWrap(async (req: Request, res: Response) => { let count = 10; if (req.query.count) { - count = Math.max(1, Math.min(20, parseInt(req.query.count as string, 10))); + count = clamp(parseInt(req.query.count as string, 10), 1, 1000); + } + + if (shouldUseLegacyTable()) { + const elements = await queryValueSetElements(valueSet, offset, count, filter); + res.status(200).json({ + resourceType: 'ValueSet', + url, + expansion: { + offset, + contains: elements, + }, + } as ValueSet); + } else { + valueSet = await expandValueSet(valueSet, offset, count, filter); + res.status(200).json(valueSet); + } +}); + +function shouldUseLegacyTable(): boolean { + const ctx = getAuthenticatedContext(); + return !ctx.project.features?.includes('terminology'); +} + +async function queryValueSetElements( + valueSet: ValueSet, + offset: number, + count: number, + filter?: string +): Promise { + // Build a collection of all systems to include + const systemExpressions = buildValueSetSystems(valueSet); + if (systemExpressions.length === 0) { + throw new OperationOutcomeError(badRequest('No systems found')); } const client = getDatabasePool(); @@ -81,17 +111,10 @@ export const expandOperator = asyncWrap(async (req: Request, res: Response) => { system: row.system, code: row.code, display: row.display ?? undefined, // if display is NULL, we want to filter it out before sending this to the client - })); + })) as ValueSetExpansionContains[]; - res.status(200).json({ - resourceType: 'ValueSet', - url, - expansion: { - offset, - contains: elements, - }, - } as ValueSet); -}); + return elements; +} function filterToTsvectorQuery(filter: string | undefined): string | undefined { if (!filter) { @@ -110,6 +133,7 @@ function filterToTsvectorQuery(filter: string | undefined): string | undefined { } function getValueSetByUrl(url: string): Promise { + const systemRepo = getSystemRepo(); return systemRepo.searchOne({ resourceType: 'ValueSet', filters: [{ code: 'url', operator: SearchOperator.EQUALS, value: url }], @@ -131,15 +155,221 @@ function processInclude(systemExpressions: Expression[], include: ValueSetCompos return; } - const systemExpression = new Condition('system', '=', include.system as string); + const systemExpression = new Condition('system', '=', include.system); if (include.concept) { const codeExpressions: Expression[] = []; for (const concept of include.concept) { - codeExpressions.push(new Condition('code', '=', concept.code as string)); + codeExpressions.push(new Condition('code', '=', concept.code)); } systemExpressions.push(new Conjunction([systemExpression, new Disjunction(codeExpressions)])); } else { systemExpressions.push(systemExpression); } } + +const MAX_EXPANSION_SIZE = 1001; + +export async function expandValueSet( + valueSet: ValueSet, + offset: number, + count: number, + filter?: string +): Promise { + const expansion = valueSet.expansion; + if (expansion?.contains?.length && !expansion.parameter && expansion.total === expansion.contains.length) { + // Full expansion is already available, use that + return valueSet; + } + + // Compute expansion + const expandedSet = [] as ValueSetExpansionContains[]; + await computeExpansion(valueSet, expandedSet, offset, count, filter); + if (expandedSet.length >= MAX_EXPANSION_SIZE) { + valueSet.expansion = { + total: 1001, + timestamp: new Date().toISOString(), + contains: expandedSet.slice(0, 1000), + }; + } else { + valueSet.expansion = { + total: expandedSet.length, + timestamp: new Date().toISOString(), + contains: expandedSet.slice(0, count), + }; + } + return valueSet; +} + +async function computeExpansion( + valueSet: ValueSet, + expansion: ValueSetExpansionContains[], + offset: number, + count: number, + filter?: string +): Promise { + if (!valueSet.compose?.include.length) { + throw new OperationOutcomeError(badRequest('Missing ValueSet definition', 'ValueSet.compose.include')); + } + + const codeSystemCache: Record = Object.create(null); + for (const include of valueSet.compose.include) { + if (!include.system) { + throw new OperationOutcomeError( + badRequest('Missing system URL for ValueSet include', 'ValueSet.compose.include.system') + ); + } + + const codeSystem = codeSystemCache[include.system] ?? (await findCodeSystem(include.system)); + codeSystemCache[include.system] = codeSystem; + if (include.concept) { + const concepts = await Promise.all(include.concept.flatMap((c) => validateCode(codeSystem, c.code))); + for (const c of concepts) { + if (c && (!filter || c.display?.includes(filter))) { + c.id = undefined; + expansion.push(c); + } + } + } else { + await includeInExpansion(include, expansion, codeSystem, offset, count, filter); + } + + if (expansion.length > count) { + // Return partial expansion + return; + } + } +} + +export async function findCodeSystem(url: string): Promise { + const { repo, logger } = getAuthenticatedContext(); + const codeSystems = await repo.searchResources({ + resourceType: 'CodeSystem', + filters: [{ code: 'url', operator: Operator.EQUALS, value: url }], + sortRules: [ + // Select highest version (by lexical sort -- no version is assumed to be "current") + { code: 'version', descending: true }, + // Break ties by selecting more recently-updated resource (lexically -- no date is assumed to be current) + { code: 'date', descending: true }, + ], + }); + + if (!codeSystems.length) { + throw new OperationOutcomeError(badRequest(`Code system ${url} not found`, 'ValueSet.compose.include.system')); + } else if (codeSystems.length === 1) { + return codeSystems[0]; + } else { + codeSystems.sort((a: CodeSystem, b: CodeSystem) => { + // Select the non-base FHIR versions of resources before the base FHIR ones + // This is kind of a kludge, but is required to break ties because some CodeSystems (including SNOMED) + // don't have a version and the base spec version doesn't include a date (and so is always considered current) + if (a.meta?.project === r4ProjectId) { + return 1; + } else if (b.meta?.project === r4ProjectId) { + return -1; + } + return 0; + }); + logger.warn('Possibly ambiguous CodeSystem', { url, codeSystems: codeSystems.map((cs) => cs.id) }); + return codeSystems[0]; + } +} + +async function includeInExpansion( + include: ValueSetComposeInclude, + expansion: ValueSetExpansionContains[], + codeSystem: CodeSystem, + offset: number, + count: number, + filter?: string +): Promise { + const ctx = getAuthenticatedContext(); + + let query = new SelectQuery('Coding') + .column('id') + .column('code') + .column('display') + .where('system', '=', codeSystem.id) + .limit(count + 1) + .offset(offset); + if (filter) { + query.where('display', '!=', null).where('display', 'TSVECTOR_ENGLISH', filterToTsvectorQuery(filter)); + } + if (include.filter?.length) { + for (const condition of include.filter) { + switch (condition.op) { + case 'is-a': + { + const coding = await validateCode(codeSystem, condition.value); + if (!coding) { + ctx.logger.warn('Invalid parent code in ValueSet', { codeSystem: codeSystem.id, code: condition.value }); + return; // Invalid parent code, don't make DB query with incorrect filters + } + query = addParentCondition(query, codeSystem, coding); + } + break; + default: + ctx.logger.warn('Unknown filter type in ValueSet', { filter: condition }); + return; // Unknown filter type, don't make DB query with incorrect filters + } + } + } + + const results = await query.execute(ctx.repo.getDatabaseClient()); + const system = codeSystem.url; + for (const { code, display } of results) { + expansion.push({ system, code, display }); + } +} + +function addParentCondition(query: SelectQuery, codeSystem: CodeSystem, parent: Coding): SelectQuery { + if (codeSystem.hierarchyMeaning !== 'is-a') { + throw new OperationOutcomeError( + badRequest( + `Invalid filter: CodeSystem ${codeSystem.url} does not have an is-a hierarchy`, + 'ValueSet.compose.include.filter' + ) + ); + } + let property = codeSystem.property?.find((p) => p.uri === parentProperty); + if (!property) { + // Implicit parent property for hierarchical CodeSystems + property = { code: codeSystem.hierarchyMeaning ?? 'parent', uri: parentProperty, type: 'code' }; + } + + const base = new SelectQuery('Coding').column('id').column('code').column('display').where('id', '=', parent.id); + + const propertyTable = query.getNextJoinAlias(); + query.innerJoin( + 'Coding_Property', + propertyTable, + new Condition(new Column('Coding', 'id'), '=', new Column(propertyTable, 'coding')) + ); + + const csPropertyTable = query.getNextJoinAlias(); + query.innerJoin( + 'CodeSystem_Property', + csPropertyTable, + new Conjunction([ + new Condition(new Column(propertyTable, 'property'), '=', new Column(csPropertyTable, 'id')), + new Condition(new Column(csPropertyTable, 'code'), '=', property.code), + ]) + ); + + const recursiveCTE = 'cte_descendants'; + const recursiveTable = query.getNextJoinAlias(); + query.innerJoin( + recursiveCTE, + recursiveTable, + new Condition(new Column(propertyTable, 'target'), '=', new Column(recursiveTable, 'id')) + ); + const offset = query.offset_; + query.offset(0); + + return new SelectQuery('cte_descendants') + .column('code') + .column('display') + .withRecursive('cte_descendants', new Union(base, query)) + .limit(query.limit_) + .offset(offset); +} diff --git a/packages/server/src/fhir/operations/export.test.ts b/packages/server/src/fhir/operations/export.test.ts index 6e75840237..09c424eb7b 100644 --- a/packages/server/src/fhir/operations/export.test.ts +++ b/packages/server/src/fhir/operations/export.test.ts @@ -5,13 +5,14 @@ import request from 'supertest'; import { initApp, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config'; import { createTestProject, initTestAuth, waitForAsyncJob } from '../../test.setup'; -import { systemRepo } from '../repo'; +import { getSystemRepo } from '../repo'; import { exportResourceType } from './export'; import { BulkExporter } from './utils/bulkexporter'; -const app = express(); - describe('Export', () => { + const app = express(); + const systemRepo = getSystemRepo(); + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); diff --git a/packages/server/src/fhir/operations/expunge.test.ts b/packages/server/src/fhir/operations/expunge.test.ts index 9dd1431c02..5b9344eee2 100644 --- a/packages/server/src/fhir/operations/expunge.test.ts +++ b/packages/server/src/fhir/operations/expunge.test.ts @@ -8,14 +8,15 @@ import { loadTestConfig } from '../../config'; import { getDatabasePool } from '../../database'; import { getRedis } from '../../redis'; import { createTestProject, initTestAuth, waitForAsyncJob, withTestContext } from '../../test.setup'; -import { systemRepo } from '../repo'; +import { getSystemRepo } from '../repo'; import { SelectQuery } from '../sql'; import { Expunger } from './expunge'; -const app = express(); -let superAdminAccessToken: string; - describe('Expunge', () => { + const app = express(); + const systemRepo = getSystemRepo(); + let superAdminAccessToken: string; + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); @@ -65,7 +66,7 @@ describe('Expunge', () => { }); test('Expunge project compartment', async () => { - const { project, client, membership } = await createTestProject(); + const { project, client, membership } = await createTestProject({ withClient: true }); expect(project).toBeDefined(); expect(client).toBeDefined(); expect(membership).toBeDefined(); @@ -104,7 +105,7 @@ describe('Expunge', () => { test('Expunger.expunge() expunges all resource types', async () => { //setup - const { project, client, membership } = await createTestProject(); + const { project, client, membership } = await createTestProject({ withClient: true }); expect(project).toBeDefined(); expect(client).toBeDefined(); expect(membership).toBeDefined(); diff --git a/packages/server/src/fhir/operations/getwsbindingtoken.test.ts b/packages/server/src/fhir/operations/getwsbindingtoken.test.ts index a33c2a608d..b88c7f8205 100644 --- a/packages/server/src/fhir/operations/getwsbindingtoken.test.ts +++ b/packages/server/src/fhir/operations/getwsbindingtoken.test.ts @@ -1,10 +1,11 @@ import { ContentType } from '@medplum/core'; -import { Parameters, Subscription } from '@medplum/fhirtypes'; +import { OperationOutcome, Parameters, Subscription } from '@medplum/fhirtypes'; import express from 'express'; import request from 'supertest'; import { initApp, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config'; -import { initTestAuth } from '../../test.setup'; +import { verifyJwt } from '../../oauth/keys'; +import { initTestAuth, withTestContext } from '../../test.setup'; const app = express(); let accessToken: string; @@ -20,8 +21,102 @@ describe('Get WebSocket binding token', () => { await shutdownApp(); }); - test('Basic', async () => { - // Create Subscription + test('Basic', () => + withTestContext(async () => { + // Create Subscription + const res1 = await request(app) + .post(`/fhir/R4/Subscription`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send({ + resourceType: 'Subscription', + reason: 'test', + status: 'active', + criteria: 'Patient', + channel: { + type: 'websocket', + }, + } satisfies Subscription); + const createdSub = res1.body as Subscription; + expect(res1.status).toBe(201); + expect(createdSub).toBeDefined(); + expect(createdSub.id).toBeDefined(); + + // Start the export + const res2 = await request(app) + .get(`/fhir/R4/Subscription/${createdSub.id}/$get-ws-binding-token`) + .set('Authorization', 'Bearer ' + accessToken); + expect(res2.status).toBe(200); + expect(res2.body).toBeDefined(); + + const params = res2.body as Parameters; + expect(params.resourceType).toEqual('Parameters'); + expect(params.parameter?.length).toEqual(3); + expect(params.parameter?.[0]).toBeDefined(); + expect(params.parameter?.[0]?.name).toEqual('token'); + + const token = params.parameter?.[0]?.valueString as string; + expect(token).toBeDefined(); + + const { payload } = await verifyJwt(token); + expect(payload?.sub).toBeDefined(); + expect(payload?.exp).toBeDefined(); + expect(payload?.aud).toBeDefined(); + expect(payload?.username).toBeDefined(); + expect(payload?.subscription_id).toBeDefined(); + + expect(params.parameter?.[1]).toBeDefined(); + expect(params.parameter?.[1]?.name).toEqual('expiration'); + expect(params.parameter?.[1]?.valueDateTime).toBeDefined(); + expect(new Date(params.parameter?.[1]?.valueDateTime as string).getTime()).toBeGreaterThanOrEqual(Date.now()); + expect(params.parameter?.[2]).toBeDefined(); + expect(params.parameter?.[2]?.name).toEqual('websocket-url'); + expect(params.parameter?.[2]?.valueUrl).toBeDefined(); + })); + + test('should return OperationOutcome error if Subscription no longer exists', () => + withTestContext(async () => { + // Create subscription to watch patient + const res1 = await request(app) + .post(`/fhir/R4/Subscription`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send({ + resourceType: 'Subscription', + reason: 'test', + status: 'active', + criteria: 'Patient', + channel: { + type: 'websocket', + }, + } satisfies Subscription); + const createdSub = res1.body as Subscription; + expect(res1.status).toBe(201); + expect(createdSub).toBeDefined(); + expect(createdSub.id).toBeDefined(); + + const res2 = await request(app) + .delete(`/fhir/R4/Subscription/${createdSub.id as string}`) + .set('Authorization', 'Bearer ' + accessToken); + + expect(res2.body).toMatchObject({ + resourceType: 'OperationOutcome', + issue: [{ severity: 'information', code: 'informational' }], + }); + + // Call $get-ws-binding-token + const res3 = await request(app) + .get(`/fhir/R4/Subscription/${createdSub.id}/$get-ws-binding-token`) + .set('Authorization', 'Bearer ' + accessToken); + + expect(res3.body).toMatchObject({ + resourceType: 'OperationOutcome', + issue: [{ severity: 'error', code: 'invalid' }], + }); + })); + + test('should return OperationOutcome error if user does not have access to this Subscription', async () => { + // Create subscription to watch patient const res1 = await request(app) .post(`/fhir/R4/Subscription`) .set('Authorization', 'Bearer ' + accessToken) @@ -40,25 +135,16 @@ describe('Get WebSocket binding token', () => { expect(createdSub).toBeDefined(); expect(createdSub.id).toBeDefined(); - // Start the export + const anotherUserToken = await initTestAuth(); + + // Call $get-ws-binding-token const res2 = await request(app) .get(`/fhir/R4/Subscription/${createdSub.id}/$get-ws-binding-token`) - .set('Authorization', 'Bearer ' + accessToken); - expect(res2.status).toBe(200); - expect(res2.body).toBeDefined(); - - const params = res2.body as Parameters; - expect(params.resourceType).toEqual('Parameters'); - expect(params.parameter?.length).toEqual(3); - expect(params.parameter?.[0]).toBeDefined(); - expect(params.parameter?.[0].name).toEqual('token'); - expect(params.parameter?.[0].valueString).toBeDefined(); - expect(params.parameter?.[1]).toBeDefined(); - expect(params.parameter?.[1].name).toEqual('expiration'); - expect(params.parameter?.[1].valueDateTime).toBeDefined(); - expect(new Date(params.parameter?.[1].valueDateTime as string).getTime()).toBeGreaterThanOrEqual(Date.now()); - expect(params.parameter?.[2]).toBeDefined(); - expect(params.parameter?.[2].name).toEqual('websocket-url'); - expect(params.parameter?.[2].valueUrl).toBeDefined(); + .set('Authorization', 'Bearer ' + anotherUserToken); + + expect(res2.body).toMatchObject({ + resourceType: 'OperationOutcome', + issue: [{ severity: 'error', code: 'invalid' }], + }); }); }); diff --git a/packages/server/src/fhir/operations/getwsbindingtoken.ts b/packages/server/src/fhir/operations/getwsbindingtoken.ts index 3fd1aeee12..58f895ae2d 100644 --- a/packages/server/src/fhir/operations/getwsbindingtoken.ts +++ b/packages/server/src/fhir/operations/getwsbindingtoken.ts @@ -1,10 +1,9 @@ -import { allOk, badRequest, resolveId } from '@medplum/core'; -import { Parameters } from '@medplum/fhirtypes'; +import { allOk, badRequest, normalizeErrorString, resolveId } from '@medplum/core'; +import { Parameters, Subscription } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { getConfig } from '../../config'; import { getAuthenticatedContext } from '../../context'; import { generateAccessToken } from '../../oauth/keys'; -import { getRedis } from '../../redis'; import { sendOutcome } from '../outcomes'; import { sendResponse } from '../response'; @@ -28,21 +27,21 @@ export type AdditionalWsBindingClaims = { * @param res - The HTTP response. */ export async function getWsBindingTokenHandler(req: Request, res: Response): Promise { - const { login, profile } = getAuthenticatedContext(); + const { login, profile, repo } = getAuthenticatedContext(); const { baseUrl } = getConfig(); - const redis = getRedis(); const clientId = login.client && resolveId(login.client); const userId = resolveId(login.user); if (!userId) { - await sendOutcome(res, badRequest('Login missing user')); + sendOutcome(res, badRequest('Login missing user')); return; } const subscriptionId = req.params.id; - const subExists = await redis.exists(`Subscription/${subscriptionId}`); - if (!subExists) { - await sendOutcome(res, badRequest('Content could not be parsed')); + try { + await repo.readResource('Subscription', subscriptionId); + } catch (err: unknown) { + sendOutcome(res, badRequest(`Error reading subscription: ${normalizeErrorString(err)}`)); return; } @@ -79,5 +78,5 @@ export async function getWsBindingTokenHandler(req: Request, res: Response): Pro ], } satisfies Parameters; - await sendResponse(res, allOk, tokenParams); + await sendResponse(req, res, allOk, tokenParams); } diff --git a/packages/server/src/fhir/operations/graphql.test.ts b/packages/server/src/fhir/operations/graphql.test.ts index 45d9d10868..95c2749039 100644 --- a/packages/server/src/fhir/operations/graphql.test.ts +++ b/packages/server/src/fhir/operations/graphql.test.ts @@ -38,7 +38,7 @@ describe('GraphQL', () => { const aliceRepo = new Repository({ author: createReference(aliceRegistration.profile), - project: aliceRegistration.project.id as string, + projects: [aliceRegistration.project.id as string], }); // Create a profile picture diff --git a/packages/server/src/fhir/operations/groupexport.test.ts b/packages/server/src/fhir/operations/groupexport.test.ts index 758c7fbc1d..19ec16188c 100644 --- a/packages/server/src/fhir/operations/groupexport.test.ts +++ b/packages/server/src/fhir/operations/groupexport.test.ts @@ -5,14 +5,15 @@ import request from 'supertest'; import { initApp, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config'; import { createTestProject, initTestAuth, waitForAsyncJob, withTestContext } from '../../test.setup'; -import { systemRepo } from '../repo'; +import { getSystemRepo } from '../repo'; import { groupExportResources } from './groupexport'; import { BulkExporter } from './utils/bulkexporter'; -const app = express(); -let accessToken: string; - describe('Group Export', () => { + const app = express(); + const systemRepo = getSystemRepo(); + let accessToken: string; + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); diff --git a/packages/server/src/fhir/operations/groupexport.ts b/packages/server/src/fhir/operations/groupexport.ts index 4329bccb69..8d64c3a610 100644 --- a/packages/server/src/fhir/operations/groupexport.ts +++ b/packages/server/src/fhir/operations/groupexport.ts @@ -2,11 +2,11 @@ import { accepted } from '@medplum/core'; import { Group, Patient, Project } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; import { getConfig } from '../../config'; +import { getAuthenticatedContext, getLogger } from '../../context'; import { sendOutcome } from '../outcomes'; import { Repository } from '../repo'; import { getPatientEverything } from './patienteverything'; import { BulkExporter } from './utils/bulkexporter'; -import { getAuthenticatedContext, getRequestContext } from '../../context'; /** * Handles a Group export request. @@ -64,7 +64,7 @@ export async function groupExportResources( await exporter.writeResource(resource); } } catch (err) { - getRequestContext().logger.warn('Unable to read patient for group export', { + getLogger().warn('Unable to read patient for group export', { reference: member.entity.reference, }); } diff --git a/packages/server/src/fhir/operations/patienteverything.test.ts b/packages/server/src/fhir/operations/patienteverything.test.ts index 268dbcd7b8..1f5de73474 100644 --- a/packages/server/src/fhir/operations/patienteverything.test.ts +++ b/packages/server/src/fhir/operations/patienteverything.test.ts @@ -71,5 +71,25 @@ describe('Patient Everything Operation', () => { .set('Authorization', 'Bearer ' + accessToken); expect(res4.status).toBe(200); expect(res4.body.entry).toHaveLength(3); + + // Create another observation + const res5 = await request(app) + .post(`/fhir/R4/Observation`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send({ + resourceType: 'Observation', + status: 'final', + code: { coding: [{ system: LOINC, code: '12345-6' }] }, + subject: createReference(res1.body as Patient), + }); + expect(res5.status).toBe(201); + + // Execute the operation with _since + const res6 = await request(app) + .get(`/fhir/R4/Patient/${res1.body.id}/$everything?_since=${res5.body.meta?.lastUpdated}`) + .set('Authorization', 'Bearer ' + accessToken); + expect(res6.status).toBe(200); + expect(res6.body.entry).toHaveLength(1); }); }); diff --git a/packages/server/src/fhir/operations/patienteverything.ts b/packages/server/src/fhir/operations/patienteverything.ts index 8e9377e195..664ef6e527 100644 --- a/packages/server/src/fhir/operations/patienteverything.ts +++ b/packages/server/src/fhir/operations/patienteverything.ts @@ -8,11 +8,18 @@ import { ResourceType, } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; -import { getConfig } from '../../config'; import { getAuthenticatedContext } from '../../context'; import { getPatientCompartments } from '../patient'; import { Repository } from '../repo'; -import { sendResponse } from '../response'; +import { getFullUrl, sendResponse } from '../response'; +import { getOperationDefinition } from './definitions'; +import { parseInputParameters } from './utils/parameters'; + +const operation = getOperationDefinition('Patient', 'everything'); + +type PatientEverythingParameters = { + _since?: string; +}; // Patient everything operation. // https://hl7.org/fhir/operation-patient-everything.html @@ -26,14 +33,15 @@ import { sendResponse } from '../response'; export async function patientEverythingHandler(req: Request, res: Response): Promise { const ctx = getAuthenticatedContext(); const { id } = req.params; + const params = parseInputParameters(operation, req); // First read the patient to verify access const patient = await ctx.repo.readResource('Patient', id); // Then read all of the patient data - const bundle = await getPatientEverything(ctx.repo, patient); + const bundle = await getPatientEverything(ctx.repo, patient, params); - await sendResponse(res, allOk, bundle); + await sendResponse(req, res, allOk, bundle); } /** @@ -41,13 +49,28 @@ export async function patientEverythingHandler(req: Request, res: Response): Pro * Searches for all resources related to the patient. * @param repo - The repository. * @param patient - The root patient. + * @param params - The operation input parameters. * @returns The patient everything search result bundle. */ -export async function getPatientEverything(repo: Repository, patient: Patient): Promise { +export async function getPatientEverything( + repo: Repository, + patient: Patient, + params?: PatientEverythingParameters +): Promise { const patientRef = getReferenceString(patient); const resourceList = getPatientCompartments().resource as CompartmentDefinitionResource[]; const searches: SearchRequest[] = []; + // Build a list of filters to apply to the searches + const filters = []; + if (params?._since) { + filters.push({ + code: '_lastUpdated', + operator: Operator.GREATER_THAN_OR_EQUALS, + value: params._since, + }); + } + // Build a list of searches for (const resource of resourceList) { const searchParams = resource.param; @@ -64,6 +87,7 @@ export async function getPatientEverything(repo: Repository, patient: Patient): operator: Operator.EQUALS, value: patientRef, }, + ...filters, ], }); } @@ -75,12 +99,15 @@ export async function getPatientEverything(repo: Repository, patient: Patient): const searchResults = await Promise.all(promises); // Build the result bundle - const entry: BundleEntry[] = [ - { - fullUrl: `${getConfig().baseUrl}fhir/R4/Patient/${patient.id}`, + const entry: BundleEntry[] = []; + + if (!params?._since || (patient.meta?.lastUpdated as string) >= params?._since) { + entry.push({ + fullUrl: getFullUrl('Patient', patient.id as string), resource: patient, - }, - ]; + }); + } + const resourceSet = new Set([getReferenceString(patient)]); for (const searchResult of searchResults) { if (searchResult.entry) { diff --git a/packages/server/src/fhir/operations/plandefinitionapply.ts b/packages/server/src/fhir/operations/plandefinitionapply.ts index 5c9d621878..4f346ec533 100644 --- a/packages/server/src/fhir/operations/plandefinitionapply.ts +++ b/packages/server/src/fhir/operations/plandefinitionapply.ts @@ -73,7 +73,7 @@ export async function planDefinitionApplyHandler(req: Request, res: Response): P intent: 'order', action: actions, }); - await sendResponse(res, allOk, requestGroup); + await sendResponse(req, res, allOk, requestGroup); } /** diff --git a/packages/server/src/fhir/operations/projectclone.test.ts b/packages/server/src/fhir/operations/projectclone.test.ts index 428de26d99..76a05830ae 100644 --- a/packages/server/src/fhir/operations/projectclone.test.ts +++ b/packages/server/src/fhir/operations/projectclone.test.ts @@ -27,16 +27,17 @@ import { setupRecaptchaMock, withTestContext, } from '../../test.setup'; -import { systemRepo } from '../repo'; +import { getSystemRepo } from '../repo'; import { getBinaryStorage } from '../storage'; import { createProject } from './projectinit'; jest.mock('node-fetch'); jest.mock('hibp'); -const app = express(); - describe('Project clone', () => { + const app = express(); + const systemRepo = getSystemRepo(); + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); @@ -118,7 +119,7 @@ describe('Project clone', () => { }); test('Success with project name in body', async () => { - const { project } = await createTestProject(); + const { project } = await createTestProject({ withClient: true }); const newProjectName = 'A New Name for cloned project'; expect(project).toBeDefined(); @@ -211,7 +212,7 @@ describe('Project clone', () => { }); test('Success with resource type in body', async () => { - const { project } = await createTestProject(); + const { project } = await createTestProject({ withClient: true }); const resourceTypes = ['ProjectMembership']; expect(project).toBeDefined(); @@ -247,7 +248,7 @@ describe('Project clone', () => { }); test('Success with includeIds in body', async () => { - const { project, membership } = await createTestProject(); + const { project, membership } = await createTestProject({ withClient: true }); const includeIds = [membership.id]; expect(project).toBeDefined(); @@ -283,7 +284,7 @@ describe('Project clone', () => { }); test('Success with excludeIds in body', async () => { - const { project, membership } = await createTestProject(); + const { project, membership } = await createTestProject({ withClient: true }); const excludeIds = [membership.id]; expect(project).toBeDefined(); @@ -319,12 +320,11 @@ describe('Project clone', () => { }); test('Success with Bot attachments', async () => { - const { project } = await createTestProject(); + const { project, repo } = await createTestProject({ withRepo: true }); expect(project).toBeDefined(); - const sourceCodeBinary = await systemRepo.createResource({ + const sourceCodeBinary = await repo.createResource({ resourceType: 'Binary', - meta: { project: project.id }, contentType: ContentType.JAVASCRIPT, }); @@ -335,9 +335,8 @@ describe('Project clone', () => { Readable.from('console.log("Hello world");') ); - const bot = await systemRepo.createResource({ + const bot = await repo.createResource({ resourceType: 'Bot', - meta: { project: project.id }, name: 'Test Bot', sourceCode: { url: getReferenceString(sourceCodeBinary), diff --git a/packages/server/src/fhir/operations/projectclone.ts b/packages/server/src/fhir/operations/projectclone.ts index e3d6601ada..32c23ec67b 100644 --- a/packages/server/src/fhir/operations/projectclone.ts +++ b/packages/server/src/fhir/operations/projectclone.ts @@ -26,7 +26,7 @@ export async function projectCloneHandler(req: Request, res: Response): Promise< const { name, resourceTypes, includeIds, excludeIds } = req.body; const cloner = new ProjectCloner(ctx.repo, id, name, resourceTypes, includeIds, excludeIds); const result = await cloner.cloneProject(); - await sendResponse(res, created, result); + await sendResponse(req, res, created, result); } class ProjectCloner { @@ -46,7 +46,6 @@ class ProjectCloner { const resourceTypes = getResourceTypes(); const allResources: Resource[] = []; const maxResourcesPerResourceType = 1000; - let newProject: Project | undefined = undefined; for (const resourceType of resourceTypes) { const bundle = await repo.search({ @@ -58,25 +57,28 @@ class ProjectCloner { for (const entry of bundle.entry) { if (entry.resource && this.isResourceAllowed(entry.resource)) { this.idMap.set(entry.resource.id as string, randomUUID()); - allResources.push(entry.resource); + if (entry.resource.resourceType !== 'Project') { + allResources.push(entry.resource); + } } } } } + // Create the project first - otherwise project references will fail + const newProject = await repo.updateResource(this.rewriteIds(project)); + + // Then create all other resources for (const resource of allResources) { // Use updateResource to create with specified ID // That feature is only available to super admins const result = await repo.updateResource(this.rewriteIds(resource)); - if (result.resourceType === 'Project') { - newProject = result; - } if (resource.resourceType === 'Binary') { await getBinaryStorage().copyBinary(resource, result as Binary); } } - return newProject as Project; + return newProject; } isResourceAllowed(resource: Resource): boolean { @@ -108,7 +110,7 @@ class ProjectCloner { return true; } - rewriteIds(resource: Resource): Resource { + rewriteIds(resource: T): T { const resourceObj = JSON.parse(JSON.stringify(resource, (k, v) => this.rewriteKeyReplacer(k, v))); if (this.projectName) { diff --git a/packages/server/src/fhir/operations/projectinit.ts b/packages/server/src/fhir/operations/projectinit.ts index 846ba5cc37..4c811e38a7 100644 --- a/packages/server/src/fhir/operations/projectinit.ts +++ b/packages/server/src/fhir/operations/projectinit.ts @@ -1,3 +1,4 @@ +import { ProfileResource, badRequest, createReference, created } from '@medplum/core'; import { ClientApplication, OperationDefinition, @@ -6,17 +7,16 @@ import { Reference, User, } from '@medplum/fhirtypes'; -import { getAuthenticatedContext, getRequestContext } from '../../context'; -import { systemRepo } from '../repo'; -import { ProfileResource, badRequest, createReference, created } from '@medplum/core'; -import { parseInputParameters, sendOutputParameters } from './utils/parameters'; +import { randomUUID } from 'crypto'; import { Request, Response } from 'express'; -import { sendOutcome } from '../outcomes'; import { createClient } from '../../admin/client'; +import { createUser } from '../../auth/newuser'; import { createProfile, createProjectMembership } from '../../auth/utils'; +import { getAuthenticatedContext, getRequestContext } from '../../context'; import { getUserByEmailWithoutProject } from '../../oauth/utils'; -import { createUser } from '../../auth/newuser'; -import { randomUUID } from 'crypto'; +import { sendOutcome } from '../outcomes'; +import { getSystemRepo } from '../repo'; +import { parseInputParameters, sendOutputParameters } from './utils/parameters'; const projectInitOperation: OperationDefinition = { resourceType: 'OperationDefinition', @@ -98,6 +98,8 @@ export async function projectInitHandler(req: Request, res: Response): Promise { const ctx = getRequestContext(); + const systemRepo = getSystemRepo(); ctx.logger.info('Project creation request received', { name: projectName }); const project = await systemRepo.createResource({ diff --git a/packages/server/src/fhir/operations/resourcegraph.test.ts b/packages/server/src/fhir/operations/resourcegraph.test.ts index 4e66f4c8a7..9ea33eedf8 100644 --- a/packages/server/src/fhir/operations/resourcegraph.test.ts +++ b/packages/server/src/fhir/operations/resourcegraph.test.ts @@ -29,7 +29,7 @@ describe('Resource $graph', () => { beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); - defaultAccessToken = await initTestAuth({ strictMode: false }); + defaultAccessToken = await initTestAuth({ project: { strictMode: false } }); }); afterAll(async () => { diff --git a/packages/server/src/fhir/operations/resourcegraph.ts b/packages/server/src/fhir/operations/resourcegraph.ts index 8498360c79..39892d6422 100644 --- a/packages/server/src/fhir/operations/resourcegraph.ts +++ b/packages/server/src/fhir/operations/resourcegraph.ts @@ -7,7 +7,7 @@ import { notFound, OperationOutcomeError, Operator, - parseSearchDefinition, + parseSearchRequest, PropertyType, toTypedValue, TypedValue, @@ -21,7 +21,7 @@ import { ResourceType, } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; -import { getAuthenticatedContext, getRequestContext } from '../../context'; +import { getAuthenticatedContext, getLogger } from '../../context'; import { Repository } from '../repo'; import { sendResponse } from '../response'; @@ -51,7 +51,7 @@ export async function resourceGraphHandler(req: Request, res: Response): Promise } await followLinks(ctx.repo, rootResource, definition.link, results, resourceCache); - await sendResponse(res, allOk, { + await sendResponse(req, res, allOk, { resourceType: 'Bundle', entry: deduplicateResources(results).map((r) => ({ resource: r, @@ -214,7 +214,7 @@ async function followCanonicalElements( filters: [{ code: 'url', operator: Operator.EQUALS, value: url }], }); if (linkedResources.length > 1) { - getRequestContext().logger.warn('Found more than 1 resource with canonical URL', { url }); + getLogger().warn('Found more than 1 resource with canonical URL', { url }); } // Cache here to speed up subsequent loop iterations @@ -251,7 +251,7 @@ async function followSearchLink( const searchParams = params.replace('{ref}', getReferenceString(resource)); // Formulate the searchURL string - const searchRequest = parseSearchDefinition(`${searchResourceType}?${searchParams}`); + const searchRequest = parseSearchRequest(`${searchResourceType}?${searchParams}`); // Parse the max count from the link description, if available searchRequest.count = Math.min(parseCardinality(link.max), 5000); diff --git a/packages/server/src/fhir/operations/structuredefinitionexpandprofile.test.ts b/packages/server/src/fhir/operations/structuredefinitionexpandprofile.test.ts new file mode 100644 index 0000000000..2422e3bc2d --- /dev/null +++ b/packages/server/src/fhir/operations/structuredefinitionexpandprofile.test.ts @@ -0,0 +1,343 @@ +import { ContentType, HTTP_HL7_ORG } from '@medplum/core'; +import { readJson } from '@medplum/definitions'; +import { Bundle, ElementDefinition, StructureDefinition, StructureDefinitionSnapshot } from '@medplum/fhirtypes'; +import express from 'express'; +import request from 'supertest'; +import { initApp, shutdownApp } from '../../app'; +import { loadTestConfig } from '../../config'; +import { initTestAuth } from '../../test.setup'; + +jest.mock('node-fetch'); + +const app = express(); + +describe('StructureDefinition $expand-profile', () => { + let USCoreStructureDefinitions: StructureDefinition[]; + let accessToken: string; + + async function createSDs(profileUrls: string[], accessToken: string): Promise { + for (const profileUrl of profileUrls) { + const sd = USCoreStructureDefinitions.find((sd) => sd.url === profileUrl); + + if (!sd) { + fail(`could not find structure definition for ${profileUrl}`); + } + const res = await request(app) + .post(`/fhir/R4/StructureDefinition`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send(sd); + expect(res.status).toEqual(201); + } + } + + beforeAll(async () => { + USCoreStructureDefinitions = readJson('fhir/r4/testing/uscore-v5.0.1-structuredefinitions.json'); + const config = await loadTestConfig(); + await initApp(app, config); + }); + + beforeEach(async () => { + // A new project per test since tests are dependent on SDs being within search scope or not. + accessToken = await initTestAuth(); + }); + + afterAll(async () => { + await shutdownApp(); + }); + + test('Success with nested profiles', async () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`; + const expectedProfiles = [ + profileUrl, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-race`, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-ethnicity`, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-birthsex`, + `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-genderIdentity`, + ]; + await createSDs(expectedProfiles, accessToken); + + const res = await request(app) + .get(`/fhir/R4/StructureDefinition/$expand-profile?url=${profileUrl}`) + .set('Authorization', 'Bearer ' + accessToken) + .send(); + expect(res.status).toEqual(200); + expect(res.body.resourceType).toEqual('Bundle'); + + const bundle = res.body as Bundle; + expect(bundle.entry?.length).toEqual(expectedProfiles.length); + for (const entry of bundle.entry || []) { + expect(expectedProfiles.includes(entry.resource?.url ?? '')).toEqual(true); + } + }); + + test('Success with missing nested profiles', async () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`; + // us-core-patient references several other profiles, but they are not in the database + // so we expect to only get the profiles that are + const expectedProfiles = [profileUrl, `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-race`]; + await createSDs(expectedProfiles, accessToken); + + const res = await request(app) + .get(`/fhir/R4/StructureDefinition/$expand-profile?url=${profileUrl}`) + .set('Authorization', 'Bearer ' + accessToken) + .send(); + expect(res.status).toEqual(200); + expect(res.body.resourceType).toEqual('Bundle'); + + const bundle = res.body as Bundle; + expect(bundle.entry?.length).toEqual(expectedProfiles.length); + for (const entry of bundle.entry || []) { + expect(expectedProfiles.includes(entry.resource?.url ?? '')).toEqual(true); + } + }); + + test('Profile not found', async () => { + const profileUrl = `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-race`; + + // Note that nothing is created in the database, so we expect an empty bundle + + const res = await request(app) + .get(`/fhir/R4/StructureDefinition/$expand-profile?url=${profileUrl}`) + .set('Authorization', 'Bearer ' + accessToken) + .send(); + expect(res.status).toEqual(400); + }); + + test('Profile URL not specified', async () => { + const res = await request(app) + .get(`/fhir/R4/StructureDefinition/$expand-profile`) + .set('Authorization', 'Bearer ' + accessToken) + .send(); + expect(res.status).toEqual(400); + }); + + test('Circuit breaker for deeply nested profiles', async () => { + const extensionCount = 10; + const sds = await createNestedStructureDefinitions(accessToken, extensionCount); + const sdUrls = sds.map((sd) => sd.url); + expect(sds.length).toBe(extensionCount + 1); + const profileUrl = sds[0].url; + const res = await request(app) + .get(`/fhir/R4/StructureDefinition/$expand-profile?url=${profileUrl}`) + .set('Authorization', 'Bearer ' + accessToken) + .send(); + expect(res.status).toEqual(200); + const bundle = res.body as Bundle; + expect(bundle.entry?.length).toEqual(sds.length); + for (const entry of bundle.entry || []) { + expect(sdUrls.includes(entry.resource?.url ?? '')).toEqual(true); + } + }); + + test('Circuit breaker for too deeply nested profiles', async () => { + const extensionCount = 11; + const sds = await createNestedStructureDefinitions(accessToken, extensionCount); + const sdUrls = sds.map((sd) => sd.url); + expect(sds.length).toBe(extensionCount + 1); + const profileUrl = sds[0].url; + const res = await request(app) + .get(`/fhir/R4/StructureDefinition/$expand-profile?url=${profileUrl}`) + .set('Authorization', 'Bearer ' + accessToken) + .send(); + expect(res.status).toEqual(200); + const bundle = res.body as Bundle; + expect(bundle.entry?.length).toEqual(sds.length - 1); // -1 because the last extension was not recursed due to circuit breaker + + for (const sdUrl of sdUrls.slice(0, -1)) { + expect(bundle.entry?.some((entry) => entry.resource?.url === sdUrl)).toEqual(true); + } + + const missingSdUrl = sdUrls[sdUrls.length - 1]; + expect(bundle.entry?.some((entry) => entry.resource?.url === missingSdUrl)).toEqual(false); + }); +}); + +async function createNestedStructureDefinitions( + accessToken: string, + extensionDepthCount: number +): Promise { + if (extensionDepthCount < 1) { + throw new Error('extensionDepthCount must be at least one'); + } + + const sds: StructureDefinition[] = []; + const sd: StructureDefinition = { + resourceType: 'StructureDefinition', + id: 'deeply-nested', + url: 'http://hl7.org/fhir/StructureDefinition/deeply-nested-profile', + name: 'DeeplyNestedProfile', + experimental: true, + date: '2024-02-07', + description: '', + kind: 'resource', + abstract: false, + status: 'draft', + type: 'Patient', + baseDefinition: 'http://hl7.org/fhir/StructureDefinition/Patient', + derivation: 'constraint', + snapshot: { + element: [ + { + id: 'Patient', + path: 'Patient', + definition: '\\-', + min: 0, + max: '*', + base: { path: 'Patient', min: 0, max: '*' }, + isModifier: false, + isSummary: false, + }, + { + id: 'Patient.extension', + path: 'Patient.extension', + definition: '\\-', + slicing: { discriminator: [{ type: 'value', path: 'url' }], ordered: false, rules: 'open' }, + min: 0, + max: '*', + base: { path: 'DomainResource.extension', min: 0, max: '*' }, + type: [{ code: 'Extension' }], + isModifier: false, + isSummary: false, + }, + { + id: 'Patient.extension:someExtension', + path: 'Patient.extension', + definition: '\\-', + sliceName: 'someExtension', + min: 0, + max: '1', + base: { path: 'DomainResource.extension', min: 0, max: '*' }, + type: [{ code: 'Extension', profile: [getExtensionUrl(0)] }], + }, + ], + }, + }; + + sds.push(sd); + for (let i = 0; i < extensionDepthCount; i++) { + const extension = createExtension( + `nested-extension-${i}`, + getExtensionUrl(i), + i === extensionDepthCount - 1 ? undefined : getExtensionUrl(i + 1) + ); + + sds.push(extension); + } + + for (const sd of sds) { + const res = await request(app) + .post(`/fhir/R4/StructureDefinition`) + .set('Authorization', 'Bearer ' + accessToken) + .set('Content-Type', ContentType.FHIR_JSON) + .send(sd); + expect(res.status).toEqual(201); + } + + return sds; +} + +function getExtensionUrl(index: number): string { + return `${HTTP_HL7_ORG}/fhir/StructureDefinition/deeply-nested-extension-${index}`; +} + +function createExtension(id: string, url: string, nestedExtensionUrl: string | undefined): StructureDefinition { + let nestedExtensionElement: ElementDefinition | undefined; + if (nestedExtensionUrl) { + nestedExtensionElement = { + id: 'Extension.extension:nestedExtension', + path: 'Extension.extension', + definition: '\\-', + sliceName: 'nestedExtension', + min: 0, + max: '1', + base: { path: 'Extension.extension', min: 0, max: '*' }, + type: [{ code: 'Extension', profile: [nestedExtensionUrl] }], + mustSupport: true, + isModifier: false, + isSummary: false, + }; + } + + const extension: StructureDefinition & { snapshot: StructureDefinitionSnapshot } = { + resourceType: 'StructureDefinition', + id, + url, + name: 'DeeplyNestedExtension', + title: 'Deeply Nested Extension', + status: 'draft', + description: 'An arbitrarily deeply nested extension for testing purposes', + fhirVersion: '4.0.1', + kind: 'complex-type', + abstract: false, + type: 'Extension', + baseDefinition: 'http://hl7.org/fhir/StructureDefinition/Extension', + derivation: 'constraint', + context: [{ type: 'element', expression: 'Element' }], + snapshot: { + element: [ + { + id: 'Extension', + path: 'Extension', + definition: '\\-', + min: 0, + max: '*', + base: { path: 'Extension', min: 0, max: '*' }, + isModifier: false, + }, + { + id: 'Extension.id', + path: 'Extension.id', + definition: '\\-', + min: 0, + max: '1', + base: { path: 'Extension.id', min: 0, max: '*' }, + type: [ + { + extension: [ + { + url: 'http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type', + valueUrl: 'string', + }, + ], + code: 'http://hl7.org/fhirpath/System.String', + }, + ], + isModifier: false, + isSummary: false, + }, + { + id: 'Extension.extension', + path: 'Extension.extension', + definition: '\\-', + slicing: { + discriminator: [ + { + type: 'value', + path: 'url', + }, + ], + description: 'Extensions are always sliced by (at least) url', + rules: 'open', + }, + min: 0, + max: '*', + base: { path: 'Extension.extension', min: 0, max: '*' }, + type: [ + { + code: 'Extension', + }, + ], + isModifier: false, + isSummary: false, + }, + ], + }, + }; + + if (nestedExtensionElement) { + extension.snapshot.element.push(nestedExtensionElement); + } + + return extension; +} diff --git a/packages/server/src/fhir/operations/structuredefinitionexpandprofile.ts b/packages/server/src/fhir/operations/structuredefinitionexpandprofile.ts new file mode 100644 index 0000000000..01e4ef3c8e --- /dev/null +++ b/packages/server/src/fhir/operations/structuredefinitionexpandprofile.ts @@ -0,0 +1,122 @@ +import { allOk, badRequest, Operator } from '@medplum/core'; +import { Bundle, BundleEntry, StructureDefinition } from '@medplum/fhirtypes'; +import { Request, Response } from 'express'; +import { getAuthenticatedContext } from '../../context'; +import { Repository } from '../repo'; +import { getFullUrl, sendResponse } from '../response'; +import { sendOutcome } from '../outcomes'; + +/** + * Handles a StructureDefinition profile expansion request. + * Searches for all extensions related to the profile. + * @param req - The HTTP request. + * @param res - The HTTP response. + */ +export async function structureDefinitionExpandProfileHandler(req: Request, res: Response): Promise { + const ctx = getAuthenticatedContext(); + const { url } = req.query; + + if (!url || typeof url !== 'string') { + sendOutcome(res, badRequest('Profile url not specified')); + return; + } + + const profile = await fetchProfileByUrl(ctx.repo, url); + + if (!profile) { + sendOutcome(res, badRequest('Profile not found')); + return; + } + + const sds = await loadNestedStructureDefinitions(ctx.repo, profile, new Set([url]), 1); + + const bundle = bundleResults([profile, ...sds]); + + await sendResponse(req, res, allOk, bundle); +} + +async function fetchProfileByUrl(repo: Repository, url: string): Promise { + return repo.searchOne({ + resourceType: 'StructureDefinition', + filters: [ + { + code: 'url', + operator: Operator.EQUALS, + value: url, + }, + ], + sortRules: [ + { + code: 'version', + descending: true, + }, + ], + }); +} + +async function loadNestedStructureDefinitions( + repo: Repository, + profile: StructureDefinition, + searchedProfiles: Set, + depth: number +): Promise { + // Recurse at most 10 levels deep + if (depth > 10) { + return []; + } + + const profilesUrlsToLoad: string[] = []; + + profile.snapshot?.element?.forEach((element) => { + const profileUrls: string[] | undefined = element.type + ?.map((t) => t.profile) + .flat() + .filter((p): p is NonNullable => p !== undefined); + + profileUrls?.forEach((p) => { + if (!searchedProfiles.has(p)) { + profilesUrlsToLoad.push(p); + searchedProfiles.add(p); + } + }); + }); + + const promises: Promise[] = profilesUrlsToLoad.map((url) => + fetchProfileByUrl(repo, url) + ); + const response = []; + + const sds = await Promise.all(promises); + for (const result of sds) { + if (result === undefined) { + continue; + } + + response.push(result); + + // recursive loop + const nested = await loadNestedStructureDefinitions(repo, result, searchedProfiles, depth + 1); + response.push(...nested); + } + + return response; +} + +function bundleResults(profiles: StructureDefinition[]): Bundle { + const entry: BundleEntry[] = []; + + for (const profile of profiles) { + if (profile.id !== undefined) { + entry.push({ + fullUrl: getFullUrl('StructureDefinition', profile.id), + resource: profile, + }); + } + } + + return { + resourceType: 'Bundle', + type: 'searchset', + entry, + }; +} diff --git a/packages/server/src/fhir/operations/utils/asyncjobexecutor.test.ts b/packages/server/src/fhir/operations/utils/asyncjobexecutor.test.ts index 5c8538785c..ec59fb6900 100644 --- a/packages/server/src/fhir/operations/utils/asyncjobexecutor.test.ts +++ b/packages/server/src/fhir/operations/utils/asyncjobexecutor.test.ts @@ -1,13 +1,14 @@ import express from 'express'; -import { systemRepo } from '../../repo'; -import { AsyncJobExecutor } from './asyncjobexecutor'; import { initApp, shutdownApp } from '../../../app'; import { loadTestConfig } from '../../../config'; import { withTestContext } from '../../../test.setup'; - -const app = express(); +import { getSystemRepo } from '../../repo'; +import { AsyncJobExecutor } from './asyncjobexecutor'; describe('AsyncJobExecutor', () => { + const app = express(); + const systemRepo = getSystemRepo(); + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); diff --git a/packages/server/src/fhir/operations/utils/asyncjobexecutor.ts b/packages/server/src/fhir/operations/utils/asyncjobexecutor.ts index 6356c19bd6..d0663cf54f 100644 --- a/packages/server/src/fhir/operations/utils/asyncjobexecutor.ts +++ b/packages/server/src/fhir/operations/utils/asyncjobexecutor.ts @@ -1,7 +1,7 @@ import { AsyncJob } from '@medplum/fhirtypes'; import { AsyncLocalStorage } from 'async_hooks'; import { getRequestContext } from '../../../context'; -import { Repository, systemRepo } from '../../repo'; +import { Repository, getSystemRepo } from '../../repo'; export class AsyncJobExecutor { readonly repo: Repository; @@ -39,6 +39,7 @@ export class AsyncJobExecutor { if (!this.resource) { throw new Error('AsyncJob missing'); } + const systemRepo = getSystemRepo(); try { await callback(); await systemRepo.updateResource({ diff --git a/packages/server/src/fhir/operations/utils/bulkexporter.ts b/packages/server/src/fhir/operations/utils/bulkexporter.ts index 8572db4843..746d9be213 100644 --- a/packages/server/src/fhir/operations/utils/bulkexporter.ts +++ b/packages/server/src/fhir/operations/utils/bulkexporter.ts @@ -1,7 +1,7 @@ -import { Binary, BulkDataExport, Bundle, Project, Resource, ResourceType } from '@medplum/fhirtypes'; import { getReferenceString } from '@medplum/core'; -import { Repository, systemRepo } from '../../repo'; +import { Binary, BulkDataExport, Bundle, Project, Resource, ResourceType } from '@medplum/fhirtypes'; import { PassThrough } from 'node:stream'; +import { Repository, getSystemRepo } from '../../repo'; import { getBinaryStorage } from '../../storage'; const NDJSON_CONTENT_TYPE = 'application/fhir+ndjson'; @@ -104,6 +104,7 @@ export class BulkExporter { } // Update the BulkDataExport + const systemRepo = getSystemRepo(); return systemRepo.updateResource({ ...this.resource, meta: { diff --git a/packages/server/src/fhir/operations/utils/parameters.test.ts b/packages/server/src/fhir/operations/utils/parameters.test.ts index 56377b6ef8..a5f7bfbf38 100644 --- a/packages/server/src/fhir/operations/utils/parameters.test.ts +++ b/packages/server/src/fhir/operations/utils/parameters.test.ts @@ -231,6 +231,10 @@ describe('Operation Input Parameters parsing', () => { }); describe('Send Operation output Parameters', () => { + const req = { + query: {}, + } as unknown as Request; + const res = { set: jest.fn(), status: jest.fn(), @@ -243,7 +247,7 @@ describe('Send Operation output Parameters', () => { }); test('Single required parameter', async () => { - await sendOutputParameters(opDef, res, allOk, { singleOut: { value: 20.2, unit: 'kg/m^2' } }); + await sendOutputParameters(req, res, opDef, allOk, { singleOut: { value: 20.2, unit: 'kg/m^2' } }); expect(res.status).toHaveBeenCalledWith(200); expect(res.json).toHaveBeenCalledWith<[Parameters]>({ @@ -253,7 +257,7 @@ describe('Send Operation output Parameters', () => { }); test('Optional output parameter', async () => { - await sendOutputParameters(opDef, res, created, { + await sendOutputParameters(req, res, opDef, created, { singleOut: { value: 20.2, unit: 'kg/m^2' }, multiOut: [{ reference: 'Observation/height' }, { reference: 'Observation/weight' }], }); @@ -286,7 +290,7 @@ describe('Send Operation output Parameters', () => { unit: 'kg/m^2', }, } as Observation; - await sendOutputParameters(resourceReturnOp, res, allOk, obs); + await sendOutputParameters(req, res, resourceReturnOp, allOk, obs); expect(res.status).toHaveBeenCalledWith(200); expect(res.json).toHaveBeenCalledWith(obs); @@ -299,7 +303,7 @@ describe('Send Operation output Parameters', () => { parameter: [{ name: 'return', use: 'out', type: 'Observation', min: 1, max: '1' }], }; const ref = { reference: 'Observation/bmi' } as Reference; - await sendOutputParameters(resourceReturnOp, res, allOk, ref); + await sendOutputParameters(req, res, resourceReturnOp, allOk, ref); expect(res.status).toHaveBeenCalledWith(500); expect(res.json).toHaveBeenCalledWith<[OperationOutcome]>( @@ -324,7 +328,7 @@ describe('Send Operation output Parameters', () => { parameter: [{ name: 'return', use: 'out', type: 'Observation', min: 1, max: '1' }], }; const patient = { resourceType: 'Patient' } as Patient; - await sendOutputParameters(resourceReturnOp, res, allOk, patient); + await sendOutputParameters(req, res, resourceReturnOp, allOk, patient); expect(res.status).toHaveBeenCalledWith(500); expect(res.json).toHaveBeenCalledWith<[OperationOutcome]>( @@ -344,7 +348,7 @@ describe('Send Operation output Parameters', () => { test('Missing required parameter', () => withTestContext(async () => { - await sendOutputParameters(opDef, res, allOk, { incorrectOut: { value: 20.2, unit: 'kg/m^2' } }); + await sendOutputParameters(req, res, opDef, allOk, { incorrectOut: { value: 20.2, unit: 'kg/m^2' } }); expect(res.status).toHaveBeenCalledWith(500); expect(res.json).toHaveBeenCalledWith<[OperationOutcome]>( @@ -363,7 +367,7 @@ describe('Send Operation output Parameters', () => { })); test('Omits extraneous parameters', async () => { - await sendOutputParameters(opDef, res, allOk, { singleOut: { value: 20.2, unit: 'kg/m^2' }, extraOut: 'foo' }); + await sendOutputParameters(req, res, opDef, allOk, { singleOut: { value: 20.2, unit: 'kg/m^2' }, extraOut: 'foo' }); expect(res.status).toHaveBeenCalledWith(200); expect(res.json).toHaveBeenCalledWith<[Parameters]>({ @@ -374,7 +378,7 @@ describe('Send Operation output Parameters', () => { test('Returns error on invalid output', () => withTestContext(async () => { - await sendOutputParameters(opDef, res, allOk, { singleOut: { reference: 'Observation/foo' } }); + await sendOutputParameters(req, res, opDef, allOk, { singleOut: { reference: 'Observation/foo' } }); expect(res.status).toHaveBeenCalledWith(500); expect(res.json).toHaveBeenCalledWith<[OperationOutcome]>( diff --git a/packages/server/src/fhir/operations/utils/parameters.ts b/packages/server/src/fhir/operations/utils/parameters.ts index 0f6ff1ee54..3e2a2fb9fc 100644 --- a/packages/server/src/fhir/operations/utils/parameters.ts +++ b/packages/server/src/fhir/operations/utils/parameters.ts @@ -16,9 +16,9 @@ import { ParametersParameter, } from '@medplum/fhirtypes'; import { Request, Response } from 'express'; +import { getLogger } from '../../../context'; import { sendOutcome } from '../../outcomes'; import { sendResponse } from '../../response'; -import { getRequestContext } from '../../../context'; export function parseParameters(input: T | Parameters): T { if (input && typeof input === 'object' && 'resourceType' in input && input.resourceType === 'Parameters') { @@ -42,7 +42,10 @@ export function parseInputParameters(operation: OperationDefinition, req: Req return {} as any; } - const input = req.body; + // If the request is a GET request, use the query parameters + // Otherwise, use the body + const input = req.method === 'GET' ? req.query : req.body; + const inputParameters = operation.parameter.filter((p) => p.use === 'in'); if (input.resourceType === 'Parameters') { if (!input.parameter) { @@ -103,8 +106,9 @@ function parseParams( } export async function sendOutputParameters( - operation: OperationDefinition, + req: Request, res: Response, + operation: OperationDefinition, outcome: OperationOutcome, output: any ): Promise { @@ -118,7 +122,7 @@ export async function sendOutputParameters( ); } else { // Send Resource as output directly, instead of using Parameters format - await sendResponse(res, outcome, output); + await sendResponse(req, res, outcome, output); } return; } @@ -171,7 +175,7 @@ export async function sendOutputParameters( validateResource(response); res.status(getStatus(outcome)).json(response); } catch (err: any) { - getRequestContext().logger.error('Malformed operation output Parameters', { error: err.toString() }); + getLogger().error('Malformed operation output Parameters', { error: err.toString() }); sendOutcome(res, serverError(err)); } } @@ -208,3 +212,7 @@ function makeParameter(param: OperationDefinitionParameter, value: any): Paramet } return undefined; } + +export function clamp(n: number, min: number, max: number): number { + return Math.max(min, Math.min(max, n)); +} diff --git a/packages/server/src/fhir/patient.test.ts b/packages/server/src/fhir/patient.test.ts index c5aa800e1c..23ff1b73ec 100644 --- a/packages/server/src/fhir/patient.test.ts +++ b/packages/server/src/fhir/patient.test.ts @@ -12,7 +12,7 @@ import { initAppServices, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; import { withTestContext } from '../test.setup'; import { getPatientCompartmentParams, getPatientResourceTypes, getPatients } from './patient'; -import { systemRepo } from './repo'; +import { getSystemRepo } from './repo'; describe('FHIR Patient utils', () => { beforeAll(async () => { @@ -155,6 +155,7 @@ describe('FHIR Patient utils', () => { // and that resource has a reference to a patient, // but the patient reference is an external patient ID, // we should silently ignore the patient reference + const systemRepo = getSystemRepo(); const eob = await systemRepo.createResource({ resourceType: 'ExplanationOfBenefit', status: 'active', diff --git a/packages/server/src/fhir/references.test.ts b/packages/server/src/fhir/references.test.ts index e5a4ee6b4d..07431d4e26 100644 --- a/packages/server/src/fhir/references.test.ts +++ b/packages/server/src/fhir/references.test.ts @@ -19,15 +19,16 @@ describe('Reference checks', () => { test('Check references on write', () => withTestContext(async () => { - const { membership } = await registerNew({ + const { membership, project } = await registerNew({ firstName: randomUUID(), lastName: randomUUID(), projectName: randomUUID(), email: randomUUID() + '@example.com', password: randomUUID(), }); + project.checkReferencesOnWrite = true; - const repo = await getRepoForLogin({ resourceType: 'Login' } as Login, membership, true, true, true); + const repo = await getRepoForLogin({ resourceType: 'Login' } as Login, membership, project, true); const patient = await repo.createResource({ resourceType: 'Patient', diff --git a/packages/server/src/fhir/references.ts b/packages/server/src/fhir/references.ts index 9f5e365320..cf8fc62f37 100644 --- a/packages/server/src/fhir/references.ts +++ b/packages/server/src/fhir/references.ts @@ -9,7 +9,7 @@ import { } from '@medplum/core'; import { OperationOutcomeIssue, Reference, Resource } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; -import { systemRepo } from './repo'; +import { getSystemRepo } from './repo'; export async function validateReferences(resource: T): Promise { return new FhirReferenceValidator(resource).validate(); @@ -81,6 +81,7 @@ export class FhirReferenceValidator { } try { + const systemRepo = getSystemRepo(); const existing = await systemRepo.readReference(reference); if (existing.meta?.project && this.root.meta?.project && existing.meta.project !== this.root.meta.project) { this.issues.push(createStructureIssue(path, `Invalid reference (Not found)`)); diff --git a/packages/server/src/fhir/repo.test.ts b/packages/server/src/fhir/repo.test.ts index 68fda8e6a5..7bb1c2d486 100644 --- a/packages/server/src/fhir/repo.test.ts +++ b/packages/server/src/fhir/repo.test.ts @@ -1,4 +1,13 @@ -import { badRequest, createReference, forbidden, isOk, notFound, OperationOutcomeError, Operator } from '@medplum/core'; +import { + badRequest, + createReference, + forbidden, + getReferenceString, + isOk, + notFound, + OperationOutcomeError, + Operator, +} from '@medplum/core'; import { BundleEntry, ElementDefinition, @@ -6,6 +15,7 @@ import { Observation, OperationOutcome, Patient, + Project, ProjectMembership, Questionnaire, ResourceType, @@ -18,14 +28,20 @@ import { initAppServices, shutdownApp } from '../app'; import { registerNew, RegisterRequest } from '../auth/register'; import { loadTestConfig } from '../config'; import { getDatabasePool } from '../database'; -import { bundleContains, withTestContext } from '../test.setup'; +import { bundleContains, createTestProject, withTestContext } from '../test.setup'; import { getRepoForLogin } from './accesspolicy'; -import { Repository, systemRepo } from './repo'; +import { getSystemRepo, Repository } from './repo'; jest.mock('hibp'); jest.mock('ioredis'); describe('FHIR Repo', () => { + const testProject: Project = { + resourceType: 'Project', + id: randomUUID(), + }; + const systemRepo = getSystemRepo(); + beforeAll(async () => { const config = await loadTestConfig(); await initAppServices(config); @@ -37,7 +53,11 @@ describe('FHIR Repo', () => { test('getRepoForLogin', async () => { await expect(() => - getRepoForLogin({ resourceType: 'Login' } as Login, { resourceType: 'ProjectMembership' } as ProjectMembership) + getRepoForLogin( + { resourceType: 'Login' } as Login, + { resourceType: 'ProjectMembership' } as ProjectMembership, + testProject + ) ).rejects.toThrow('Invalid author reference'); }); @@ -166,28 +186,20 @@ describe('FHIR Repo', () => { test('meta.project preserved after attempting to remove it', () => withTestContext(async () => { - const clientApp = 'ClientApplication/' + randomUUID(); - const projectId = randomUUID(); - const repo = new Repository({ - extendedMode: true, - project: projectId, - author: { - reference: clientApp, - }, - }); + const { project, repo } = await createTestProject({ withClient: true, withRepo: true }); const patient1 = await repo.createResource({ resourceType: 'Patient', name: [{ given: ['Update1'], family: 'Update1' }], }); expect(patient1.meta?.project).toBeDefined(); - expect(patient1.meta?.project).toEqual(projectId); + expect(patient1.meta?.project).toEqual(project.id); const patientWithoutProject = { ...patient1 }; delete (patientWithoutProject.meta as any).project; const patient2 = await systemRepo.updateResource(patientWithoutProject); expect(patient2.meta?.project).toBeDefined(); - expect(patient2.meta?.project).toEqual(projectId); + expect(patient2.meta?.project).toEqual(project.id); })); test('Update patient no changes', () => @@ -228,15 +240,7 @@ describe('FHIR Repo', () => { })); test('Create Patient with custom ID', async () => { - const author = 'Practitioner/' + randomUUID(); - - const repo = new Repository({ - project: randomUUID(), - extendedMode: true, - author: { - reference: author, - }, - }); + const { repo } = await createTestProject({ withRepo: true }); // Try to "update" a resource, which does not exist. // Some FHIR systems allow users to set ID's. @@ -281,21 +285,14 @@ describe('FHIR Repo', () => { test('Create Patient as ClientApplication with no author', () => withTestContext(async () => { - const clientApp = 'ClientApplication/' + randomUUID(); - - const repo = new Repository({ - extendedMode: true, - author: { - reference: clientApp, - }, - }); + const { client, repo } = await createTestProject({ withClient: true, withRepo: true }); const patient = await repo.createResource({ resourceType: 'Patient', name: [{ given: ['Alice'], family: 'Smith' }], }); - expect(patient.meta?.author?.reference).toEqual(clientApp); + expect(patient.meta?.author?.reference).toEqual(getReferenceString(client)); })); test('Create Patient as Practitioner with no author', () => @@ -447,7 +444,7 @@ describe('FHIR Repo', () => { const result1 = await registerNew(registration1); expect(result1.profile).toBeDefined(); - const repo1 = await getRepoForLogin({ resourceType: 'Login' } as Login, result1.membership); + const repo1 = await getRepoForLogin({ resourceType: 'Login' } as Login, result1.membership, result1.project); const patient1 = await repo1.createResource({ resourceType: 'Patient', }); @@ -470,7 +467,7 @@ describe('FHIR Repo', () => { const result2 = await registerNew(registration2); expect(result2.profile).toBeDefined(); - const repo2 = await getRepoForLogin({ resourceType: 'Login' } as Login, result2.membership); + const repo2 = await getRepoForLogin({ resourceType: 'Login' } as Login, result2.membership, result2.project); try { await repo2.readResource('Patient', patient1.id as string); fail('Should have thrown'); @@ -514,7 +511,7 @@ describe('FHIR Repo', () => { test('Reindex resource type as non-admin', async () => { const repo = new Repository({ - project: randomUUID(), + projects: [randomUUID()], author: { reference: 'Practitioner/' + randomUUID(), }, @@ -529,12 +526,7 @@ describe('FHIR Repo', () => { }); test('Reindex resource as non-admin', async () => { - const repo = new Repository({ - project: randomUUID(), - author: { - reference: 'Practitioner/' + randomUUID(), - }, - }); + const { repo } = await createTestProject({ withRepo: true }); try { await repo.reindexResource('Practitioner', randomUUID()); @@ -559,7 +551,7 @@ describe('FHIR Repo', () => { test('Rebuild compartments as non-admin', async () => { const repo = new Repository({ - project: randomUUID(), + projects: [randomUUID()], author: { reference: 'Practitioner/' + randomUUID(), }, @@ -722,7 +714,7 @@ describe('FHIR Repo', () => { const author = 'Practitioner/' + randomUUID(); const repo = new Repository({ - project: randomUUID(), + projects: [randomUUID()], extendedMode: true, author: { reference: author, @@ -742,7 +734,7 @@ describe('FHIR Repo', () => { const author = 'Practitioner/' + randomUUID(); const repo = new Repository({ - project: randomUUID(), + projects: [randomUUID()], extendedMode: true, author: { reference: author, @@ -762,7 +754,7 @@ describe('FHIR Repo', () => { const author = 'Practitioner/' + randomUUID(); const repo = new Repository({ - project: randomUUID(), + projects: [randomUUID()], extendedMode: true, author: { reference: author, @@ -874,6 +866,8 @@ describe('FHIR Repo', () => { test('Profile validation', async () => withTestContext(async () => { + const { repo } = await createTestProject({ withRepo: true }); + const profile = JSON.parse( readFileSync(resolve(__dirname, '__test__/us-core-patient.json'), 'utf8') ) as StructureDefinition; @@ -898,25 +892,16 @@ describe('FHIR Repo', () => { // Missing gender property is required by profile }; - await expect(systemRepo.createResource(patient)).resolves.toBeTruthy(); - await systemRepo.createResource(profile); - await expect(systemRepo.createResource(patient)).rejects.toEqual( + await expect(repo.createResource(patient)).resolves.toBeTruthy(); + await repo.createResource(profile); + await expect(repo.createResource(patient)).rejects.toEqual( new Error('Missing required property (Patient.gender)') ); })); test('Profile update', async () => withTestContext(async () => { - const clientApp = 'ClientApplication/' + randomUUID(); - const projectId = randomUUID(); - const repo = new Repository({ - extendedMode: true, - strictMode: true, - project: projectId, - author: { - reference: clientApp, - }, - }); + const { repo } = await createTestProject({ withRepo: true }); const originalProfile = JSON.parse( readFileSync(resolve(__dirname, '__test__/us-core-patient.json'), 'utf8') diff --git a/packages/server/src/fhir/repo.ts b/packages/server/src/fhir/repo.ts index dc379a2116..68473925a7 100644 --- a/packages/server/src/fhir/repo.ts +++ b/packages/server/src/fhir/repo.ts @@ -24,7 +24,7 @@ import { normalizeErrorString, normalizeOperationOutcome, notFound, - parseCriteriaAsSearchRequest, + parseSearchRequest, protectedResourceTypes, resolveId, satisfiedAccessPolicy, @@ -47,15 +47,15 @@ import { ResourceType, SearchParameter, StructureDefinition, - Subscription, } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; import { Pool, PoolClient } from 'pg'; import { Operation, applyPatch } from 'rfc6902'; import validator from 'validator'; import { getConfig } from '../config'; -import { getRequestContext } from '../context'; +import { getLogger, getRequestContext } from '../context'; import { getDatabasePool } from '../database'; +import { globalLogger } from '../logger'; import { getRedis } from '../redis'; import { r4ProjectId } from '../seed'; import { @@ -106,11 +106,14 @@ export interface RepositoryContext { remoteAddress?: string; /** - * The current project reference. - * This should be the ID/UUID of the current project. + * Projects that the Repository is allowed to access. + * This should include the ID/UUID of the current project, but may also include other accessory Projects. + * If this is undefined, the current user is a server user (e.g. Super Admin) + * The usual case has two elements: the user's Project and the base R4 Project + * The user's "primary" Project will be the first element in the array (i.e. projects[0]) * This value will be included in every resource as meta.project. */ - project?: string; + projects?: string[]; /** * Optional compartment restriction. @@ -179,15 +182,14 @@ const lookupTables: LookupTable[] = [ * It is a thin layer on top of the database. * Repository instances should be created per author and project. */ -export class Repository extends BaseRepository implements FhirRepository { +export class Repository extends BaseRepository implements FhirRepository, Disposable { private readonly context: RepositoryContext; - private conn?: PoolClient; - private transactionDepth = 0; private closed = false; constructor(context: RepositoryContext) { super(); this.context = context; + this.context.projects?.push?.(r4ProjectId); if (!this.context.author?.reference) { throw new Error('Invalid author reference'); } @@ -198,18 +200,16 @@ export class Repository extends BaseRepository implements FhirRepository(resource: T): Promise { + const resourceWithId = { + ...resource, + id: randomUUID(), + }; try { - const result = await this.updateResourceImpl( - { - ...resource, - id: randomUUID(), - }, - true - ); + const result = await this.updateResourceImpl(resourceWithId, true); this.logEvent(CreateInteraction, AuditEventOutcome.Success, undefined, result); return result; } catch (err) { - this.logEvent(CreateInteraction, AuditEventOutcome.MinorFailure, err, resource); + this.logEvent(CreateInteraction, AuditEventOutcome.MinorFailure, err, resourceWithId); throw err; } } @@ -276,7 +276,7 @@ export class Repository extends BaseRepository implements FhirRepository (sub.id as string) === (result.id as string) - ); - if (existingIdx !== -1) { - currentWsSubscriptions[existingIdx] = result; - } else { - currentWsSubscriptions.push(result); - } - await redis.set(`medplum:subscriptions:r4:project:${project}`, JSON.stringify(currentWsSubscriptions)); + await redis.sadd(`medplum:subscriptions:r4:project:${project}:active`, `Subscription/${result.id}`); } } @@ -603,13 +591,15 @@ export class Repository extends BaseRepository implements FhirRepository { - const projectId = this.context.project; + const projectIds = this.context.projects; - if (projectId) { - // Try retrieving from cache - const cachedProfile = await getProfileCacheEntry(projectId, url); + if (projectIds?.length) { + // Try loading from cache, using all available Project IDs + const cacheKeys = projectIds.map((id) => getProfileCacheKey(id, url)); + const results = await getRedis().mget(...cacheKeys); + const cachedProfile = results.find(Boolean) as string | undefined; if (cachedProfile) { - return cachedProfile.resource; + return (JSON.parse(cachedProfile) as CacheEntry).resource; } } @@ -631,9 +621,9 @@ export class Repository extends BaseRepository implements FhirRepository { - this.assertNotClosed(); - if (!this.conn) { - this.conn = await getDatabasePool().connect(); - } - return this.conn; - } - - /** - * Releases the database connection. - * Include an error to remove the connection from the pool. - * See: https://github.com/brianc/node-postgres/blob/master/packages/pg-pool/index.js#L333 - * @param err - Optional error to remove the connection from the pool. - */ - private releaseConnection(err?: boolean | Error): void { - if (this.conn) { - this.conn.release(err); - this.conn = undefined; - } + return getDatabasePool(); } async withTransaction(callback: (client: PoolClient) => Promise): Promise { + const conn = await getDatabasePool().connect(); try { - const client = await this.beginTransaction(); - const result = await callback(client); - await this.commitTransaction(); + await conn.query('BEGIN'); + const result = await callback(conn); + await conn.query('COMMIT'); + conn.release(); return result; - } catch (err) { + } catch (err: any) { + globalLogger.error('Transaction error', err); const operationOutcomeError = new OperationOutcomeError(normalizeOperationOutcome(err), err); - await this.rollbackTransaction(operationOutcomeError); + try { + await conn.query('ROLLBACK'); + } catch (err2: any) { + globalLogger.error('Rollback error', err2); + } + conn.release(err); throw operationOutcomeError; - } finally { - this.endTransaction(); - } - } - - private async beginTransaction(): Promise { - this.assertNotClosed(); - this.transactionDepth++; - const conn = await this.getConnection(); - if (this.transactionDepth === 1) { - await conn.query('BEGIN'); - } else { - await conn.query('SAVEPOINT sp' + this.transactionDepth); - } - return conn; - } - - private async commitTransaction(): Promise { - this.assertInTransaction(); - const conn = await this.getConnection(); - if (this.transactionDepth === 1) { - await conn.query('COMMIT'); - } else { - await conn.query('RELEASE SAVEPOINT sp' + this.transactionDepth); - } - } - - private async rollbackTransaction(error: Error): Promise { - this.assertInTransaction(); - const conn = await this.getConnection(); - if (this.transactionDepth === 1) { - await conn.query('ROLLBACK'); - this.releaseConnection(error); - } else { - await conn.query('ROLLBACK TO SAVEPOINT sp' + this.transactionDepth); - } - } - - private endTransaction(): void { - this.assertInTransaction(); - this.transactionDepth--; - if (this.transactionDepth === 0) { - this.releaseConnection(); - } - } - - private assertInTransaction(): void { - if (this.transactionDepth <= 0) { - throw new Error('Not in transaction'); } } close(): void { - if (this.transactionDepth > 0) { - throw new Error('Closing with active transaction'); - } this.assertNotClosed(); - this.releaseConnection(); this.closed = true; } + [Symbol.dispose](): void { + this.close(); + } + private assertNotClosed(): void { if (this.closed) { throw new Error('Already closed'); @@ -1983,20 +1905,6 @@ function getCacheKey(resourceType: string, id: string): string { return `${resourceType}/${id}`; } -/** - * Tries to read a FHIR profile cache entry from Redis by project and profile URL. - * @param projectId - The project ID. - * @param url - The profile URL. - * @returns The cache entry if found; otherwise, undefined. - */ -async function getProfileCacheEntry( - projectId: string, - url: string -): Promise | undefined> { - const cachedValue = await getRedis().get(getProfileCacheKey(projectId, url)); - return cachedValue ? (JSON.parse(cachedValue) as CacheEntry) : undefined; -} - /** * Writes a FHIR profile cache entry to Redis. * @param projectId - The project ID. @@ -2024,12 +1932,14 @@ function getProfileCacheKey(projectId: string, url: string): string { return `Project/${projectId}/StructureDefinition/${url}`; } -export const systemRepo = new Repository({ - superAdmin: true, - strictMode: true, - extendedMode: true, - author: { - reference: 'system', - }, - // System repo does not have an associated Project; it can write to any -}); +export function getSystemRepo(): Repository { + return new Repository({ + superAdmin: true, + strictMode: true, + extendedMode: true, + author: { + reference: 'system', + }, + // System repo does not have an associated Project; it can write to any + }); +} diff --git a/packages/server/src/fhir/response.ts b/packages/server/src/fhir/response.ts index b9012e1d49..2644e38e51 100644 --- a/packages/server/src/fhir/response.ts +++ b/packages/server/src/fhir/response.ts @@ -13,7 +13,12 @@ export function getFullUrl(resourceType: string, id: string): string { return `${getConfig().baseUrl}fhir/R4/${resourceType}/${id}`; } -export async function sendResponse(res: Response, outcome: OperationOutcome, body: Resource): Promise { +export async function sendResponse( + req: Request, + res: Response, + outcome: OperationOutcome, + body: Resource +): Promise { const ctx = getAuthenticatedContext(); if (body.meta?.versionId) { res.set('ETag', `W/"${body.meta.versionId}"`); @@ -24,5 +29,15 @@ export async function sendResponse(res: Response, outcome: OperationOutcome, bod if (isCreated(outcome)) { res.set('Location', getFullUrl(body.resourceType, body.id as string)); } - res.status(getStatus(outcome)).json(await rewriteAttachments(RewriteMode.PRESIGNED_URL, ctx.repo, body)); + + res.status(getStatus(outcome)); + res.set('Content-Type', ContentType.FHIR_JSON); + + const result = await rewriteAttachments(RewriteMode.PRESIGNED_URL, ctx.repo, body); + + if (req.query._pretty === 'true') { + res.send(JSON.stringify(result, undefined, 2)); + } else { + res.json(result); + } } diff --git a/packages/server/src/fhir/rewrite.test.ts b/packages/server/src/fhir/rewrite.test.ts index 9abd6373a5..fc3fa68689 100644 --- a/packages/server/src/fhir/rewrite.test.ts +++ b/packages/server/src/fhir/rewrite.test.ts @@ -5,10 +5,11 @@ import { URL } from 'url'; import { initAppServices, shutdownApp } from '../app'; import { MedplumServerConfig, loadTestConfig } from '../config'; import { withTestContext } from '../test.setup'; -import { systemRepo } from './repo'; +import { getSystemRepo } from './repo'; import { RewriteMode, rewriteAttachments } from './rewrite'; describe('URL rewrite', () => { + const systemRepo = getSystemRepo(); let config: MedplumServerConfig; let binary: Binary; diff --git a/packages/server/src/fhir/rewrite.ts b/packages/server/src/fhir/rewrite.ts index 2b81c05f62..4b9beeec85 100644 --- a/packages/server/src/fhir/rewrite.ts +++ b/packages/server/src/fhir/rewrite.ts @@ -1,6 +1,6 @@ import { Binary, Resource } from '@medplum/fhirtypes'; import { getConfig } from '../config'; -import { getRequestContext } from '../context'; +import { getLogger } from '../context'; import { Repository } from './repo'; import { getPresignedUrl } from './signer'; @@ -158,7 +158,7 @@ class Rewriter { binary = await this.repo.readResource('Binary', id); } } catch (err: any) { - getRequestContext().logger.debug('Error reading binary to generate presigned URL', err); + getLogger().debug('Error reading binary to generate presigned URL', err); return `Binary/${id}`; } return getPresignedUrl(binary); diff --git a/packages/server/src/fhir/routes.test.ts b/packages/server/src/fhir/routes.test.ts index de4d305e10..5e8a74c377 100644 --- a/packages/server/src/fhir/routes.test.ts +++ b/packages/server/src/fhir/routes.test.ts @@ -160,13 +160,21 @@ describe('FHIR Routes', () => { expect(res.status).toBe(400); }); - test('Read resourcex', async () => { + test('Read resource', async () => { const res = await request(app) .get(`/fhir/R4/Patient/${patientId}`) .set('Authorization', 'Bearer ' + accessToken); expect(res.status).toBe(200); }); + test('Read resource _pretty', async () => { + const res = await request(app) + .get(`/fhir/R4/Patient/${patientId}?_pretty=true`) + .set('Authorization', 'Bearer ' + accessToken); + expect(res.status).toBe(200); + expect(res.text).toEqual(JSON.stringify(res.body, undefined, 2)); + }); + test('Read resource invalid UUID', async () => { const res = await request(app) .get(`/fhir/R4/Patient/123`) diff --git a/packages/server/src/fhir/routes.ts b/packages/server/src/fhir/routes.ts index f12100bacb..e037b23fde 100644 --- a/packages/server/src/fhir/routes.ts +++ b/packages/server/src/fhir/routes.ts @@ -30,6 +30,7 @@ import { resourceGraphHandler } from './operations/resourcegraph'; import { sendOutcome } from './outcomes'; import { isFhirJsonContentType, sendResponse } from './response'; import { smartConfigurationHandler, smartStylingHandler } from './smart'; +import { structureDefinitionExpandProfileHandler } from './operations/structuredefinitionexpandprofile'; export const fhirRouter = Router(); @@ -163,6 +164,9 @@ protectedRoutes.post('/:resourceType/:id/([$]|%24)expunge', asyncWrap(expungeHan // $get-ws-binding-token operation protectedRoutes.get('/Subscription/:id/([$]|%24)get-ws-binding-token', asyncWrap(getWsBindingTokenHandler)); +// StructureDefinition $expand-profile operation +protectedRoutes.get('/StructureDefinition/([$]|%24)expand-profile', asyncWrap(structureDefinitionExpandProfileHandler)); + // Validate create resource protectedRoutes.post( '/:resourceType/([$])validate', @@ -221,7 +225,7 @@ protectedRoutes.use( } sendOutcome(res, result[0]); } else { - await sendResponse(res, result[0], result[1]); + await sendResponse(req, res, result[0], result[1]); } }) ); diff --git a/packages/server/src/fhir/search.test.ts b/packages/server/src/fhir/search.test.ts index 29048b6a19..b88d7bf9aa 100644 --- a/packages/server/src/fhir/search.test.ts +++ b/packages/server/src/fhir/search.test.ts @@ -6,9 +6,7 @@ import { normalizeOperationOutcome, OperationOutcomeError, Operator, - parseSearchDefinition, parseSearchRequest, - parseSearchUrl, SearchRequest, SNOMED, } from '@medplum/core'; @@ -30,6 +28,7 @@ import { Patient, PlanDefinition, Practitioner, + Project, Provenance, Questionnaire, QuestionnaireResponse, @@ -43,2107 +42,1893 @@ import { import { randomUUID } from 'crypto'; import { initAppServices, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { bundleContains, withTestContext } from '../test.setup'; -import { systemRepo } from './repo'; +import { bundleContains, createTestProject, withTestContext } from '../test.setup'; +import { getSystemRepo, Repository } from './repo'; jest.mock('hibp'); jest.mock('ioredis'); describe('FHIR Search', () => { - beforeAll(async () => { - const config = await loadTestConfig(); - await initAppServices(config); - }); - - afterAll(async () => { - await shutdownApp(); - }); + describe('project-scoped Repository', () => { + let repo: Repository; - test('Search total', async () => { - const result1 = await systemRepo.search({ - resourceType: 'Patient', + beforeAll(async () => { + const config = await loadTestConfig(); + await initAppServices(config); + const { project } = await createTestProject(); + repo = new Repository({ + projects: [project.id as string], + author: { reference: 'User/' + randomUUID() }, + }); }); - expect(result1.total).toBeUndefined(); - expect(result1.link?.length).toBe(3); - const result2 = await systemRepo.search({ - resourceType: 'Patient', - total: 'none', + afterAll(async () => { + await shutdownApp(); }); - expect(result2.total).toBeUndefined(); - const result3 = await systemRepo.search({ - resourceType: 'Patient', - total: 'accurate', - }); - expect(result3.total).toBeDefined(); - expect(typeof result3.total).toBe('number'); + test('Search total', async () => { + withTestContext(async () => { + await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + }); + await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Bob'], family: 'Smith' }], + }); + const result1 = await repo.search({ + resourceType: 'Patient', + count: 1, + }); + expect(result1.total).toBeUndefined(); + expect(result1.link?.length).toBe(3); - const result4 = await systemRepo.search({ - resourceType: 'Patient', - total: 'estimate', - }); - expect(result4.total).toBeDefined(); - expect(typeof result4.total).toBe('number'); - }); + const result2 = await repo.search({ + resourceType: 'Patient', + total: 'none', + }); + expect(result2.total).toBeUndefined(); + + const result3 = await repo.search({ + resourceType: 'Patient', + total: 'accurate', + }); + expect(result3.total).toBeDefined(); + expect(typeof result3.total).toBe('number'); - test('Search count=0', async () => { - const result1 = await systemRepo.search({ - resourceType: 'Patient', - count: 0, + const result4 = await repo.search({ + resourceType: 'Patient', + total: 'estimate', + }); + expect(result4.total).toBeDefined(); + expect(typeof result4.total).toBe('number'); + }).catch((err) => { + throw err; + }); }); - expect(result1.entry).toBeUndefined(); - expect(result1.link).toBeDefined(); - expect(result1.link?.length).toBe(1); - }); - test('Search _summary', () => - withTestContext(async () => { - const subsetTag: Coding = { system: 'http://hl7.org/fhir/v3/ObservationValue', code: 'SUBSETTED' }; - const patient: Patient = { + test('Search count=0', async () => { + const result1 = await repo.search({ resourceType: 'Patient', - meta: { - profile: ['http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient'], - tag: [{ system: 'http://example.com/', code: 'test' }], - }, - text: { - status: 'generated', - div: '

', - }, - identifier: [ - { - use: 'usual', - type: { - coding: [ - { - system: 'http://terminology.hl7.org/CodeSystem/v2-0203', - code: 'MR', - display: 'Medical Record Number', - }, - ], - text: 'Medical Record Number', - }, - system: 'http://hospital.smarthealthit.org', - value: '1032702', + count: 0, + }); + expect(result1.entry).toBeUndefined(); + expect(result1.link).toBeDefined(); + expect(result1.link?.length).toBe(1); + }); + + test('Search _summary', () => + withTestContext(async () => { + const subsetTag: Coding = { system: 'http://hl7.org/fhir/v3/ObservationValue', code: 'SUBSETTED' }; + const patient: Patient = { + resourceType: 'Patient', + meta: { + profile: ['http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient'], + tag: [{ system: 'http://example.com/', code: 'test' }], }, - ], - active: true, - name: [ - { - use: 'old', - family: 'Shaw', - given: ['Amy', 'V.'], - period: { - start: '2016-12-06', - end: '2020-07-22', - }, + text: { + status: 'generated', + div: '
', }, - { - family: 'Baxter', - given: ['Amy', 'V.'], - suffix: ['PharmD'], - period: { - start: '2020-07-22', + identifier: [ + { + use: 'usual', + type: { + coding: [ + { + system: 'http://terminology.hl7.org/CodeSystem/v2-0203', + code: 'MR', + display: 'Medical Record Number', + }, + ], + text: 'Medical Record Number', + }, + system: 'http://hospital.smarthealthit.org', + value: '1032702', }, - }, - ], - telecom: [ - { - system: 'phone', - value: '555-555-5555', - use: 'home', - }, - { - system: 'email', - value: 'amy.shaw@example.com', - }, - ], - gender: 'female', - birthDate: '1987-02-20', - multipleBirthInteger: 2, - address: [ - { - use: 'old', - line: ['49 Meadow St'], - city: 'Mounds', - state: 'OK', - postalCode: '74047', - country: 'US', - period: { - start: '2016-12-06', - end: '2020-07-22', + ], + active: true, + name: [ + { + use: 'old', + family: 'Shaw', + given: ['Amy', 'V.'], + period: { + start: '2016-12-06', + end: '2020-07-22', + }, }, - }, - { - line: ['183 Mountain View St'], - city: 'Mounds', - state: 'OK', - postalCode: '74048', - country: 'US', - period: { - start: '2020-07-22', + { + family: 'Baxter', + given: ['Amy', 'V.'], + suffix: ['PharmD'], + period: { + start: '2020-07-22', + }, + }, + ], + telecom: [ + { + system: 'phone', + value: '555-555-5555', + use: 'home', + }, + { + system: 'email', + value: 'amy.shaw@example.com', + }, + ], + gender: 'female', + birthDate: '1987-02-20', + multipleBirthInteger: 2, + address: [ + { + use: 'old', + line: ['49 Meadow St'], + city: 'Mounds', + state: 'OK', + postalCode: '74047', + country: 'US', + period: { + start: '2016-12-06', + end: '2020-07-22', + }, }, - }, - ], - }; - const resource = await systemRepo.createResource(patient); - - // _summary=text - const textResults = await systemRepo.search({ - resourceType: 'Patient', - filters: [{ code: '_id', operator: Operator.EQUALS, value: resource.id as string }], - summary: 'text', - }); - expect(textResults.entry).toHaveLength(1); - const textResult = textResults.entry?.[0]?.resource as Resource; - expect(textResult).toEqual>({ - resourceType: 'Patient', - id: resource.id, - meta: expect.objectContaining({ - profile: ['http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient'], - tag: [{ system: 'http://example.com/', code: 'test' }, subsetTag], - }), - text: { - status: 'generated', - div: '
', - }, - }); - - // _summary=data - const dataResults = await systemRepo.search({ - resourceType: 'Patient', - filters: [{ code: '_id', operator: Operator.EQUALS, value: resource.id as string }], - summary: 'data', - }); - expect(dataResults.entry).toHaveLength(1); - const dataResult = dataResults.entry?.[0]?.resource as Resource; - const { text: _1, ...dataExpected } = resource; - dataExpected.meta?.tag?.push(subsetTag); - expect(dataResult).toEqual>({ ...dataExpected }); - - // _summary=true - const summaryResults = await systemRepo.search({ - resourceType: 'Patient', - filters: [{ code: '_id', operator: Operator.EQUALS, value: resource.id as string }], - summary: 'true', - }); - expect(summaryResults.entry).toHaveLength(1); - const summaryResult = summaryResults.entry?.[0]?.resource as Resource; - const { multipleBirthInteger: _2, text: _3, ...summaryResource } = resource; - expect(summaryResult).toEqual>(summaryResource); - })); - - test('Search _elements', () => - withTestContext(async () => { - const subsetTag: Coding = { system: 'http://hl7.org/fhir/v3/ObservationValue', code: 'SUBSETTED' }; - const patient: Patient = { - resourceType: 'Patient', - birthDate: '2000-01-01', - _birthDate: { - extension: [ { - url: 'http://hl7.org/fhir/StructureDefinition/patient-birthTime', - valueDateTime: '2000-01-01T00:00:00.001Z', + line: ['183 Mountain View St'], + city: 'Mounds', + state: 'OK', + postalCode: '74048', + country: 'US', + period: { + start: '2020-07-22', + }, }, ], - }, - multipleBirthInteger: 2, - deceasedBoolean: false, - } as unknown as Patient; - const resource = await systemRepo.createResource(patient); + }; + const resource = await repo.createResource(patient); - const results = await systemRepo.search({ - resourceType: 'Patient', - filters: [{ code: '_id', operator: Operator.EQUALS, value: resource.id as string }], - fields: ['birthDate', 'deceased'], - }); - expect(results.entry).toHaveLength(1); - const result = results.entry?.[0]?.resource as Resource; - expect(result).toEqual>({ - resourceType: 'Patient', - id: resource.id, - meta: expect.objectContaining({ - tag: [subsetTag], - }), - birthDate: resource.birthDate, - _birthDate: (resource as any)._birthDate, - deceasedBoolean: resource.deceasedBoolean, - } as unknown as Patient); - })); - - test('Search next link', () => - withTestContext(async () => { - const family = randomUUID(); - - for (let i = 0; i < 2; i++) { - await systemRepo.createResource({ + // _summary=text + const textResults = await repo.search({ resourceType: 'Patient', - name: [{ family }], + filters: [{ code: '_id', operator: Operator.EQUALS, value: resource.id as string }], + summary: 'text', + }); + expect(textResults.entry).toHaveLength(1); + const textResult = textResults.entry?.[0]?.resource as Resource; + expect(textResult).toEqual>({ + resourceType: 'Patient', + id: resource.id, + meta: expect.objectContaining({ + profile: ['http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient'], + tag: [{ system: 'http://example.com/', code: 'test' }, subsetTag], + }), + text: { + status: 'generated', + div: '
', + }, }); - } - const result1 = await systemRepo.search({ - resourceType: 'Patient', - filters: [{ code: 'name', operator: Operator.EQUALS, value: family }], - count: 1, - }); - expect(result1.entry).toHaveLength(1); - expect(result1.link).toBeDefined(); - expect(result1.link?.find((e) => e.relation === 'next')).toBeDefined(); + // _summary=data + const dataResults = await repo.search({ + resourceType: 'Patient', + filters: [{ code: '_id', operator: Operator.EQUALS, value: resource.id as string }], + summary: 'data', + }); + expect(dataResults.entry).toHaveLength(1); + const dataResult = dataResults.entry?.[0]?.resource as Resource; + const { text: _1, ...dataExpected } = resource; + dataExpected.meta?.tag?.push(subsetTag); + expect(dataResult).toEqual>({ ...dataExpected }); + + // _summary=true + const summaryResults = await repo.search({ + resourceType: 'Patient', + filters: [{ code: '_id', operator: Operator.EQUALS, value: resource.id as string }], + summary: 'true', + }); + expect(summaryResults.entry).toHaveLength(1); + const summaryResult = summaryResults.entry?.[0]?.resource as Resource; + const { multipleBirthInteger: _2, text: _3, ...summaryResource } = resource; + expect(summaryResult).toEqual>(summaryResource); + })); + + test('Search _elements', () => + withTestContext(async () => { + const subsetTag: Coding = { system: 'http://hl7.org/fhir/v3/ObservationValue', code: 'SUBSETTED' }; + const patient: Patient = { + resourceType: 'Patient', + birthDate: '2000-01-01', + _birthDate: { + extension: [ + { + url: 'http://hl7.org/fhir/StructureDefinition/patient-birthTime', + valueDateTime: '2000-01-01T00:00:00.001Z', + }, + ], + }, + multipleBirthInteger: 2, + deceasedBoolean: false, + } as unknown as Patient; + const resource = await repo.createResource(patient); - const result2 = await systemRepo.search({ - resourceType: 'Patient', - filters: [{ code: 'name', operator: Operator.EQUALS, value: family }], - count: 2, - }); - expect(result2.entry).toHaveLength(2); - expect(result2.link).toBeDefined(); - expect(result2.link?.find((e) => e.relation === 'next')).toBeUndefined(); + const results = await repo.search({ + resourceType: 'Patient', + filters: [{ code: '_id', operator: Operator.EQUALS, value: resource.id as string }], + fields: ['birthDate', 'deceased'], + }); + expect(results.entry).toHaveLength(1); + const result = results.entry?.[0]?.resource as Resource; + expect(result).toEqual>({ + resourceType: 'Patient', + id: resource.id, + meta: expect.objectContaining({ + tag: [subsetTag], + }), + birthDate: resource.birthDate, + _birthDate: (resource as any)._birthDate, + deceasedBoolean: resource.deceasedBoolean, + } as unknown as Patient); + })); + + test('Search next link', () => + withTestContext(async () => { + const family = randomUUID(); + + for (let i = 0; i < 2; i++) { + await repo.createResource({ + resourceType: 'Patient', + name: [{ family }], + }); + } - const result3 = await systemRepo.search({ - resourceType: 'Patient', - filters: [{ code: 'name', operator: Operator.EQUALS, value: family }], - count: 3, - }); - expect(result3.entry).toHaveLength(2); - expect(result3.link).toBeDefined(); - expect(result3.link?.find((e) => e.relation === 'next')).toBeUndefined(); - })); + const result1 = await repo.search({ + resourceType: 'Patient', + filters: [{ code: 'name', operator: Operator.EQUALS, value: family }], + count: 1, + }); + expect(result1.entry).toHaveLength(1); + expect(result1.link).toBeDefined(); + expect(result1.link?.find((e) => e.relation === 'next')).toBeDefined(); - test('Search previous link', () => - withTestContext(async () => { - const family = randomUUID(); + const result2 = await repo.search({ + resourceType: 'Patient', + filters: [{ code: 'name', operator: Operator.EQUALS, value: family }], + count: 2, + }); + expect(result2.entry).toHaveLength(2); + expect(result2.link).toBeDefined(); + expect(result2.link?.find((e) => e.relation === 'next')).toBeUndefined(); - for (let i = 0; i < 2; i++) { - await systemRepo.createResource({ + const result3 = await repo.search({ resourceType: 'Patient', - name: [{ family }], + filters: [{ code: 'name', operator: Operator.EQUALS, value: family }], + count: 3, }); - } + expect(result3.entry).toHaveLength(2); + expect(result3.link).toBeDefined(); + expect(result3.link?.find((e) => e.relation === 'next')).toBeUndefined(); + })); + + test('Search previous link', () => + withTestContext(async () => { + const family = randomUUID(); + + for (let i = 0; i < 2; i++) { + await repo.createResource({ + resourceType: 'Patient', + name: [{ family }], + }); + } - const result1 = await systemRepo.search({ - resourceType: 'Patient', - filters: [{ code: 'name', operator: Operator.EQUALS, value: family }], - count: 1, - offset: 1, - }); - expect(result1.entry).toHaveLength(1); - expect(result1.link).toBeDefined(); - expect(result1.link?.find((e) => e.relation === 'previous')).toBeDefined(); - })); + const result1 = await repo.search({ + resourceType: 'Patient', + filters: [{ code: 'name', operator: Operator.EQUALS, value: family }], + count: 1, + offset: 1, + }); + expect(result1.entry).toHaveLength(1); + expect(result1.link).toBeDefined(); + expect(result1.link?.find((e) => e.relation === 'previous')).toBeDefined(); + })); + + test('Search for Communications by Encounter', () => + withTestContext(async () => { + const patient1 = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + }); - test('Search for Communications by Encounter', () => - withTestContext(async () => { - const patient1 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family: 'Smith' }], - }); + expect(patient1).toBeDefined(); - expect(patient1).toBeDefined(); + const encounter1 = await repo.createResource({ + resourceType: 'Encounter', + status: 'in-progress', + class: { + code: 'HH', + display: 'home health', + }, + subject: createReference(patient1 as Patient), + }); - const encounter1 = await systemRepo.createResource({ - resourceType: 'Encounter', - status: 'in-progress', - class: { - code: 'HH', - display: 'home health', - }, - subject: createReference(patient1 as Patient), - }); + expect(encounter1).toBeDefined(); - expect(encounter1).toBeDefined(); + const comm1 = await repo.createResource({ + resourceType: 'Communication', + status: 'completed', + encounter: createReference(encounter1 as Encounter), + subject: createReference(patient1 as Patient), + sender: createReference(patient1 as Patient), + payload: [{ contentString: 'This is a test' }], + }); - const comm1 = await systemRepo.createResource({ - resourceType: 'Communication', - status: 'completed', - encounter: createReference(encounter1 as Encounter), - subject: createReference(patient1 as Patient), - sender: createReference(patient1 as Patient), - payload: [{ contentString: 'This is a test' }], - }); + expect(comm1).toBeDefined(); - expect(comm1).toBeDefined(); + const patient2 = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Bob'], family: 'Jones' }], + }); - const patient2 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Bob'], family: 'Jones' }], - }); + expect(patient2).toBeDefined(); - expect(patient2).toBeDefined(); + const encounter2 = await repo.createResource({ + resourceType: 'Encounter', + status: 'in-progress', + class: { + code: 'HH', + display: 'home health', + }, + subject: createReference(patient2 as Patient), + }); - const encounter2 = await systemRepo.createResource({ - resourceType: 'Encounter', - status: 'in-progress', - class: { - code: 'HH', - display: 'home health', - }, - subject: createReference(patient2 as Patient), - }); + expect(encounter2).toBeDefined(); - expect(encounter2).toBeDefined(); + const comm2 = await repo.createResource({ + resourceType: 'Communication', + status: 'completed', + encounter: createReference(encounter2 as Encounter), + subject: createReference(patient2 as Patient), + sender: createReference(patient2 as Patient), + payload: [{ contentString: 'This is another test' }], + }); - const comm2 = await systemRepo.createResource({ - resourceType: 'Communication', - status: 'completed', - encounter: createReference(encounter2 as Encounter), - subject: createReference(patient2 as Patient), - sender: createReference(patient2 as Patient), - payload: [{ contentString: 'This is another test' }], - }); + expect(comm2).toBeDefined(); - expect(comm2).toBeDefined(); + const searchResult = await repo.search({ + resourceType: 'Communication', + filters: [ + { + code: 'encounter', + operator: Operator.EQUALS, + value: getReferenceString(encounter1 as Encounter), + }, + ], + }); - const searchResult = await systemRepo.search({ - resourceType: 'Communication', - filters: [ - { - code: 'encounter', - operator: Operator.EQUALS, - value: getReferenceString(encounter1 as Encounter), - }, - ], - }); + expect(searchResult.entry?.length).toEqual(1); + expect(searchResult.entry?.[0]?.resource?.id).toEqual(comm1.id); + })); - expect(searchResult.entry?.length).toEqual(1); - expect(searchResult.entry?.[0]?.resource?.id).toEqual(comm1.id); - })); + test('Search for Communications by ServiceRequest', () => + withTestContext(async () => { + const patient1 = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + }); - test('Search for Communications by ServiceRequest', () => - withTestContext(async () => { - const patient1 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family: 'Smith' }], - }); + expect(patient1).toBeDefined(); - expect(patient1).toBeDefined(); + const serviceRequest1 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + code: { + text: 'text', + }, + subject: createReference(patient1 as Patient), + }); - const serviceRequest1 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - code: { - text: 'text', - }, - subject: createReference(patient1 as Patient), - }); + expect(serviceRequest1).toBeDefined(); - expect(serviceRequest1).toBeDefined(); + const comm1 = await repo.createResource({ + resourceType: 'Communication', + status: 'completed', + basedOn: [createReference(serviceRequest1 as ServiceRequest)], + subject: createReference(patient1 as Patient), + sender: createReference(patient1 as Patient), + payload: [{ contentString: 'This is a test' }], + }); - const comm1 = await systemRepo.createResource({ - resourceType: 'Communication', - status: 'completed', - basedOn: [createReference(serviceRequest1 as ServiceRequest)], - subject: createReference(patient1 as Patient), - sender: createReference(patient1 as Patient), - payload: [{ contentString: 'This is a test' }], - }); + expect(comm1).toBeDefined(); - expect(comm1).toBeDefined(); + const patient2 = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Bob'], family: 'Jones' }], + }); - const patient2 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Bob'], family: 'Jones' }], - }); + expect(patient2).toBeDefined(); - expect(patient2).toBeDefined(); + const serviceRequest2 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + code: { + text: 'test', + }, + subject: createReference(patient2 as Patient), + }); - const serviceRequest2 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - code: { - text: 'test', - }, - subject: createReference(patient2 as Patient), - }); + expect(serviceRequest2).toBeDefined(); - expect(serviceRequest2).toBeDefined(); + const comm2 = await repo.createResource({ + resourceType: 'Communication', + status: 'completed', + basedOn: [createReference(serviceRequest2 as ServiceRequest)], + subject: createReference(patient2 as Patient), + sender: createReference(patient2 as Patient), + payload: [{ contentString: 'This is another test' }], + }); - const comm2 = await systemRepo.createResource({ - resourceType: 'Communication', - status: 'completed', - basedOn: [createReference(serviceRequest2 as ServiceRequest)], - subject: createReference(patient2 as Patient), - sender: createReference(patient2 as Patient), - payload: [{ contentString: 'This is another test' }], - }); + expect(comm2).toBeDefined(); - expect(comm2).toBeDefined(); + const searchResult = await repo.search({ + resourceType: 'Communication', + filters: [ + { + code: 'based-on', + operator: Operator.EQUALS, + value: getReferenceString(serviceRequest1 as ServiceRequest), + }, + ], + }); - const searchResult = await systemRepo.search({ - resourceType: 'Communication', - filters: [ - { - code: 'based-on', - operator: Operator.EQUALS, - value: getReferenceString(serviceRequest1 as ServiceRequest), - }, - ], - }); + expect(searchResult.entry?.length).toEqual(1); + expect(searchResult.entry?.[0]?.resource?.id).toEqual(comm1.id); + })); - expect(searchResult.entry?.length).toEqual(1); - expect(searchResult.entry?.[0]?.resource?.id).toEqual(comm1.id); - })); + test('Search for QuestionnaireResponse by Questionnaire', () => + withTestContext(async () => { + const questionnaire = await repo.createResource({ + resourceType: 'Questionnaire', + status: 'active', + }); - test('Search for QuestionnaireResponse by Questionnaire', () => - withTestContext(async () => { - const questionnaire = await systemRepo.createResource({ - resourceType: 'Questionnaire', - status: 'active', - }); + const response1 = await repo.createResource({ + resourceType: 'QuestionnaireResponse', + status: 'completed', + questionnaire: getReferenceString(questionnaire), + }); - const response1 = await systemRepo.createResource({ - resourceType: 'QuestionnaireResponse', - status: 'completed', - questionnaire: getReferenceString(questionnaire), - }); + await repo.createResource({ + resourceType: 'QuestionnaireResponse', + status: 'completed', + questionnaire: `Questionnaire/${randomUUID()}`, + }); - await systemRepo.createResource({ - resourceType: 'QuestionnaireResponse', - status: 'completed', - questionnaire: `Questionnaire/${randomUUID()}`, - }); + const bundle = await repo.search({ + resourceType: 'QuestionnaireResponse', + filters: [ + { + code: 'questionnaire', + operator: Operator.EQUALS, + value: getReferenceString(questionnaire), + }, + ], + }); + expect(bundle.entry?.length).toEqual(1); + expect(bundle.entry?.[0]?.resource?.id).toEqual(response1.id); + })); - const bundle = await systemRepo.search({ - resourceType: 'QuestionnaireResponse', + test('Search for token in array', async () => { + const bundle = await repo.search({ + resourceType: 'SearchParameter', filters: [ { - code: 'questionnaire', + code: 'base', operator: Operator.EQUALS, - value: getReferenceString(questionnaire), + value: 'Patient', }, ], + count: 100, }); - expect(bundle.entry?.length).toEqual(1); - expect(bundle.entry?.[0]?.resource?.id).toEqual(response1.id); - })); - - test('Search for token in array', async () => { - const bundle = await systemRepo.search({ - resourceType: 'SearchParameter', - filters: [ - { - code: 'base', - operator: Operator.EQUALS, - value: 'Patient', - }, - ], - count: 100, - }); - - expect(bundle.entry?.find((e) => (e.resource as SearchParameter).code === 'name')).toBeDefined(); - expect(bundle.entry?.find((e) => (e.resource as SearchParameter).code === 'email')).toBeDefined(); - }); - - test('Search sort by Patient.id', async () => { - const bundle = await systemRepo.search({ - resourceType: 'Patient', - sortRules: [{ code: '_id' }], - }); - - expect(bundle).toBeDefined(); - }); - test('Search sort by Patient.meta.lastUpdated', async () => { - const bundle = await systemRepo.search({ - resourceType: 'Patient', - sortRules: [{ code: '_lastUpdated' }], + expect(bundle.entry?.find((e) => (e.resource as SearchParameter).code === 'name')).toBeDefined(); + expect(bundle.entry?.find((e) => (e.resource as SearchParameter).code === 'email')).toBeDefined(); }); - expect(bundle).toBeDefined(); - }); + test('Search sort by Patient.id', async () => { + const bundle = await repo.search({ + resourceType: 'Patient', + sortRules: [{ code: '_id' }], + }); - test('Search sort by Patient.identifier', async () => { - const bundle = await systemRepo.search({ - resourceType: 'Patient', - sortRules: [{ code: 'identifier' }], + expect(bundle).toBeDefined(); }); - expect(bundle).toBeDefined(); - }); + test('Search sort by Patient.meta.lastUpdated', async () => { + const bundle = await repo.search({ + resourceType: 'Patient', + sortRules: [{ code: '_lastUpdated' }], + }); - test('Search sort by Patient.name', async () => { - const bundle = await systemRepo.search({ - resourceType: 'Patient', - sortRules: [{ code: 'name' }], + expect(bundle).toBeDefined(); }); - expect(bundle).toBeDefined(); - }); + test('Search sort by Patient.identifier', async () => { + const bundle = await repo.search({ + resourceType: 'Patient', + sortRules: [{ code: 'identifier' }], + }); - test('Search sort by Patient.given', async () => { - const bundle = await systemRepo.search({ - resourceType: 'Patient', - sortRules: [{ code: 'given' }], + expect(bundle).toBeDefined(); }); - expect(bundle).toBeDefined(); - }); + test('Search sort by Patient.name', async () => { + const bundle = await repo.search({ + resourceType: 'Patient', + sortRules: [{ code: 'name' }], + }); - test('Search sort by Patient.address', async () => { - const bundle = await systemRepo.search({ - resourceType: 'Patient', - sortRules: [{ code: 'address' }], + expect(bundle).toBeDefined(); }); - expect(bundle).toBeDefined(); - }); + test('Search sort by Patient.given', async () => { + const bundle = await repo.search({ + resourceType: 'Patient', + sortRules: [{ code: 'given' }], + }); - test('Search sort by Patient.telecom', async () => { - const bundle = await systemRepo.search({ - resourceType: 'Patient', - sortRules: [{ code: 'telecom' }], + expect(bundle).toBeDefined(); }); - expect(bundle).toBeDefined(); - }); + test('Search sort by Patient.address', async () => { + const bundle = await repo.search({ + resourceType: 'Patient', + sortRules: [{ code: 'address' }], + }); - test('Search sort by Patient.email', async () => { - const bundle = await systemRepo.search({ - resourceType: 'Patient', - sortRules: [{ code: 'email' }], + expect(bundle).toBeDefined(); }); - expect(bundle).toBeDefined(); - }); + test('Search sort by Patient.telecom', async () => { + const bundle = await repo.search({ + resourceType: 'Patient', + sortRules: [{ code: 'telecom' }], + }); - test('Search sort by Patient.birthDate', async () => { - const bundle = await systemRepo.search({ - resourceType: 'Patient', - sortRules: [{ code: 'birthdate' }], + expect(bundle).toBeDefined(); }); - expect(bundle).toBeDefined(); - }); - - test('Filter and sort on same search parameter', () => - withTestContext(async () => { - await systemRepo.createResource({ + test('Search sort by Patient.email', async () => { + const bundle = await repo.search({ resourceType: 'Patient', - name: [{ given: ['Marge'], family: 'Simpson' }], + sortRules: [{ code: 'email' }], }); - await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Homer'], family: 'Simpson' }], - }); + expect(bundle).toBeDefined(); + }); - const bundle = await systemRepo.search({ + test('Search sort by Patient.birthDate', async () => { + const bundle = await repo.search({ resourceType: 'Patient', - filters: [{ code: 'family', operator: Operator.EQUALS, value: 'Simpson' }], - sortRules: [{ code: 'family' }], + sortRules: [{ code: 'birthdate' }], }); - expect(bundle.entry).toBeDefined(); - expect(bundle.entry?.length).toBeGreaterThanOrEqual(2); - })); + expect(bundle).toBeDefined(); + }); - test('Search birthDate after delete', () => - withTestContext(async () => { - const family = randomUUID(); + test('Filter and sort on same search parameter', () => + withTestContext(async () => { + await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Marge'], family: 'Simpson' }], + }); - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family }], - birthDate: '1971-02-02', - }); + await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Homer'], family: 'Simpson' }], + }); - const searchResult1 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: 'family', - operator: Operator.EQUALS, - value: family, - }, - { - code: 'birthdate', - operator: Operator.EQUALS, - value: '1971-02-02', - }, - ], - }); + const bundle = await repo.search({ + resourceType: 'Patient', + filters: [{ code: 'family', operator: Operator.EQUALS, value: 'Simpson' }], + sortRules: [{ code: 'family' }], + }); - expect(searchResult1.entry?.length).toEqual(1); - expect(searchResult1.entry?.[0]?.resource?.id).toEqual(patient.id); + expect(bundle.entry).toBeDefined(); + expect(bundle.entry?.length).toBeGreaterThanOrEqual(2); + })); - await systemRepo.deleteResource('Patient', patient.id as string); + test('Search birthDate after delete', () => + withTestContext(async () => { + const family = randomUUID(); - const searchResult2 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: 'family', - operator: Operator.EQUALS, - value: family, - }, - { - code: 'birthdate', - operator: Operator.EQUALS, - value: '1971-02-02', - }, - ], - }); - - expect(searchResult2.entry?.length).toEqual(0); - })); + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family }], + birthDate: '1971-02-02', + }); - test('Search identifier after delete', () => - withTestContext(async () => { - const identifier = randomUUID(); + const searchResult1 = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'family', + operator: Operator.EQUALS, + value: family, + }, + { + code: 'birthdate', + operator: Operator.EQUALS, + value: '1971-02-02', + }, + ], + }); - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family: 'Smith' }], - identifier: [{ system: 'https://www.example.com', value: identifier }], - }); + expect(searchResult1.entry?.length).toEqual(1); + expect(searchResult1.entry?.[0]?.resource?.id).toEqual(patient.id); - const searchResult1 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: 'identifier', - operator: Operator.EQUALS, - value: identifier, - }, - ], - }); + await repo.deleteResource('Patient', patient.id as string); - expect(searchResult1.entry?.length).toEqual(1); - expect(searchResult1.entry?.[0]?.resource?.id).toEqual(patient.id); + const searchResult2 = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'family', + operator: Operator.EQUALS, + value: family, + }, + { + code: 'birthdate', + operator: Operator.EQUALS, + value: '1971-02-02', + }, + ], + }); - await systemRepo.deleteResource('Patient', patient.id as string); + expect(searchResult2.entry?.length).toEqual(0); + })); - const searchResult2 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: 'identifier', - operator: Operator.EQUALS, - value: identifier, - }, - ], - }); + test('Search identifier after delete', () => + withTestContext(async () => { + const identifier = randomUUID(); - expect(searchResult2.entry?.length).toEqual(0); - })); + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + identifier: [{ system: 'https://www.example.com', value: identifier }], + }); - test('String filter', async () => { - const bundle1 = await systemRepo.search({ - resourceType: 'StructureDefinition', - filters: [ - { - code: 'name', - operator: Operator.EQUALS, - value: 'Questionnaire', - }, - ], - sortRules: [ - { - code: 'name', - descending: false, - }, - ], - }); - expect(bundle1.entry?.map((e) => e.resource?.name)).toEqual(['Questionnaire', 'QuestionnaireResponse']); - - const bundle2 = await systemRepo.search({ - resourceType: 'StructureDefinition', - filters: [ - { - code: 'name', - operator: Operator.EXACT, - value: 'Questionnaire', - }, - ], - }); - expect(bundle2.entry?.length).toEqual(1); - expect((bundle2.entry?.[0]?.resource as StructureDefinition).name).toEqual('Questionnaire'); - }); + const searchResult1 = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'identifier', + operator: Operator.EQUALS, + value: identifier, + }, + ], + }); - test('Filter by _id', () => - withTestContext(async () => { - // Unique family name to isolate the test - const family = randomUUID(); + expect(searchResult1.entry?.length).toEqual(1); + expect(searchResult1.entry?.[0]?.resource?.id).toEqual(patient.id); - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family }], - }); - expect(patient).toBeDefined(); + await repo.deleteResource('Patient', patient.id as string); - const searchResult1 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: '_id', - operator: Operator.EQUALS, - value: patient.id as string, - }, - ], - }); + const searchResult2 = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'identifier', + operator: Operator.EQUALS, + value: identifier, + }, + ], + }); - expect(searchResult1.entry?.length).toEqual(1); - expect(bundleContains(searchResult1 as Bundle, patient as Patient)).toEqual(true); + expect(searchResult2.entry?.length).toEqual(0); + })); - const searchResult2 = await systemRepo.search({ - resourceType: 'Patient', + test('String filter', async () => { + const bundle1 = await repo.search({ + resourceType: 'StructureDefinition', filters: [ { code: 'name', operator: Operator.EQUALS, - value: family, + value: 'Questionnaire', }, + ], + sortRules: [ { - code: '_id', - operator: Operator.NOT_EQUALS, - value: patient.id as string, + code: 'name', + descending: false, }, ], }); + expect(bundle1.entry?.map((e) => e.resource?.name)).toEqual(['Questionnaire', 'QuestionnaireResponse']); - expect(searchResult2.entry?.length).toEqual(0); - })); - - test('Empty _id', async () => { - const searchResult1 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: '_id', - operator: Operator.EQUALS, - value: '', - }, - ], - }); - - expect(searchResult1.entry?.length).toEqual(0); - }); - - test('Non UUID _id', async () => { - const searchResult1 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: '_id', - operator: Operator.EQUALS, - value: 'x', - }, - ], - }); - - expect(searchResult1.entry?.length).toEqual(0); - }); - - test('Non UUID _compartment', async () => { - const searchResult1 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: '_compartment', - operator: Operator.EQUALS, - value: 'x', - }, - ], - }); - - expect(searchResult1.entry?.length).toEqual(0); - }); - - test('Reference string _compartment', () => - withTestContext(async () => { - const patient = await systemRepo.createResource({ resourceType: 'Patient' }); - - const searchResult1 = await systemRepo.search({ - resourceType: 'Patient', + const bundle2 = await repo.search({ + resourceType: 'StructureDefinition', filters: [ { - code: '_compartment', - operator: Operator.EQUALS, - value: getReferenceString(patient), + code: 'name', + operator: Operator.EXACT, + value: 'Questionnaire', }, ], }); + expect(bundle2.entry?.length).toEqual(1); + expect((bundle2.entry?.[0]?.resource as StructureDefinition).name).toEqual('Questionnaire'); + }); - expect(searchResult1.entry?.length).toEqual(1); - expect(bundleContains(searchResult1 as Bundle, patient as Patient)).toEqual(true); - })); - - test('Filter by _project', () => - withTestContext(async () => { - const project1 = randomUUID(); - const project2 = randomUUID(); - - const patient1 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice1'], family: 'Smith1' }], - meta: { - project: project1, - }, - }); - expect(patient1).toBeDefined(); - - const patient2 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice2'], family: 'Smith2' }], - meta: { - project: project2, - }, - }); - expect(patient2).toBeDefined(); + test('Filter by _id', () => + withTestContext(async () => { + // Unique family name to isolate the test + const family = randomUUID(); - const bundle = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: '_project', - operator: Operator.EQUALS, - value: project1, - }, - ], - }); - expect(bundle.entry?.length).toEqual(1); - expect(bundleContains(bundle as Bundle, patient1 as Patient)).toEqual(true); - expect(bundleContains(bundle as Bundle, patient2 as Patient)).toEqual(false); - })); - - test('Handle malformed _lastUpdated', async () => { - try { - await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: '_lastUpdated', - operator: Operator.GREATER_THAN, - value: 'xyz', - }, - ], - }); - fail('Expected error'); - } catch (err) { - expect(normalizeErrorString(err)).toEqual('Invalid date value: xyz'); - } - }); + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family }], + }); + expect(patient).toBeDefined(); - test('Filter by _lastUpdated', () => - withTestContext(async () => { - // Create 2 patients - // One with a _lastUpdated of 1 second ago - // One with a _lastUpdated of 2 seconds ago - const family = randomUUID(); - const now = new Date(); - const nowMinus1Second = new Date(now.getTime() - 1000); - const nowMinus2Seconds = new Date(now.getTime() - 2000); - const nowMinus3Seconds = new Date(now.getTime() - 3000); - - const patient1 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family }], - meta: { - lastUpdated: nowMinus1Second.toISOString(), - }, - }); - expect(patient1).toBeDefined(); + const searchResult1 = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: '_id', + operator: Operator.EQUALS, + value: patient.id as string, + }, + ], + }); - const patient2 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family }], - meta: { - lastUpdated: nowMinus2Seconds.toISOString(), - }, - }); - expect(patient2).toBeDefined(); + expect(searchResult1.entry?.length).toEqual(1); + expect(bundleContains(searchResult1 as Bundle, patient as Patient)).toEqual(true); - // Greater than (newer than) 2 seconds ago should only return patient 1 - const searchResult1 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: 'name', - operator: Operator.EQUALS, - value: family, - }, - { - code: '_lastUpdated', - operator: Operator.GREATER_THAN, - value: nowMinus2Seconds.toISOString(), - }, - ], - }); + const searchResult2 = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'name', + operator: Operator.EQUALS, + value: family, + }, + { + code: '_id', + operator: Operator.NOT_EQUALS, + value: patient.id as string, + }, + ], + }); - expect(bundleContains(searchResult1 as Bundle, patient1 as Patient)).toEqual(true); - expect(bundleContains(searchResult1 as Bundle, patient2 as Patient)).toEqual(false); + expect(searchResult2.entry?.length).toEqual(0); + })); - // Greater than (newer than) or equal to 2 seconds ago should return both patients - const searchResult2 = await systemRepo.search({ + test('Empty _id', async () => { + const searchResult1 = await repo.search({ resourceType: 'Patient', filters: [ { - code: 'name', + code: '_id', operator: Operator.EQUALS, - value: family, - }, - { - code: '_lastUpdated', - operator: Operator.GREATER_THAN_OR_EQUALS, - value: nowMinus2Seconds.toISOString(), + value: '', }, ], }); - expect(bundleContains(searchResult2 as Bundle, patient1 as Patient)).toEqual(true); - expect(bundleContains(searchResult2 as Bundle, patient2 as Patient)).toEqual(true); + expect(searchResult1.entry?.length).toEqual(0); + }); - // Less than (older than) to 1 seconds ago should only return patient 2 - const searchResult3 = await systemRepo.search({ + test('Non UUID _id', async () => { + const searchResult1 = await repo.search({ resourceType: 'Patient', filters: [ { - code: 'name', + code: '_id', operator: Operator.EQUALS, - value: family, - }, - { - code: '_lastUpdated', - operator: Operator.GREATER_THAN, - value: nowMinus3Seconds.toISOString(), - }, - { - code: '_lastUpdated', - operator: Operator.LESS_THAN, - value: nowMinus1Second.toISOString(), + value: 'x', }, ], }); - expect(bundleContains(searchResult3 as Bundle, patient1 as Patient)).toEqual(false); - expect(bundleContains(searchResult3 as Bundle, patient2 as Patient)).toEqual(true); + expect(searchResult1.entry?.length).toEqual(0); + }); - // Less than (older than) or equal to 1 seconds ago should return both patients - const searchResult4 = await systemRepo.search({ + test('Non UUID _compartment', async () => { + const searchResult1 = await repo.search({ resourceType: 'Patient', filters: [ { - code: 'name', + code: '_compartment', operator: Operator.EQUALS, - value: family, - }, - { - code: '_lastUpdated', - operator: Operator.GREATER_THAN, - value: nowMinus3Seconds.toISOString(), - }, - { - code: '_lastUpdated', - operator: Operator.LESS_THAN_OR_EQUALS, - value: nowMinus1Second.toISOString(), + value: 'x', }, ], }); - expect(bundleContains(searchResult4 as Bundle, patient1 as Patient)).toEqual(true); - expect(bundleContains(searchResult4 as Bundle, patient2 as Patient)).toEqual(true); - })); - - test('Sort by _lastUpdated', () => - withTestContext(async () => { - const project = randomUUID(); - - const patient1 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice1'], family: 'Smith1' }], - meta: { - lastUpdated: '2020-01-01T00:00:00.000Z', - project, - }, - }); - expect(patient1).toBeDefined(); - - const patient2 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice2'], family: 'Smith2' }], - meta: { - lastUpdated: '2020-01-02T00:00:00.000Z', - project, - }, - }); - expect(patient2).toBeDefined(); + expect(searchResult1.entry?.length).toEqual(0); + }); - const bundle3 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: '_project', - operator: Operator.EQUALS, - value: project, - }, - ], - sortRules: [ - { - code: '_lastUpdated', - descending: false, - }, - ], - }); - expect(bundle3.entry?.length).toEqual(2); - expect(bundle3.entry?.[0]?.resource?.id).toEqual(patient1.id); - expect(bundle3.entry?.[1]?.resource?.id).toEqual(patient2.id); + test('Reference string _compartment', () => + withTestContext(async () => { + const patient = await repo.createResource({ resourceType: 'Patient' }); - const bundle4 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: '_project', - operator: Operator.EQUALS, - value: project, - }, - ], - sortRules: [ - { - code: '_lastUpdated', - descending: true, - }, - ], - }); - expect(bundle4.entry?.length).toEqual(2); - expect(bundle4.entry?.[0]?.resource?.id).toEqual(patient2.id); - expect(bundle4.entry?.[1]?.resource?.id).toEqual(patient1.id); - })); - - test('Filter by Coding', () => - withTestContext(async () => { - const auditEvents = [] as AuditEvent[]; - - for (let i = 0; i < 3; i++) { - const resource = await systemRepo.createResource({ - resourceType: 'AuditEvent', - recorded: new Date().toISOString(), - type: { - code: randomUUID(), - }, - agent: [ + const searchResult1 = await repo.search({ + resourceType: 'Patient', + filters: [ { - who: { reference: 'Practitioner/' + randomUUID() }, - requestor: true, + code: '_compartment', + operator: Operator.EQUALS, + value: getReferenceString(patient), }, ], - source: { - observer: { reference: 'Practitioner/' + randomUUID() }, - }, }); - auditEvents.push(resource); - } - for (let i = 0; i < 3; i++) { - const bundle = await systemRepo.search({ - resourceType: 'AuditEvent', + expect(searchResult1.entry?.length).toEqual(1); + expect(bundleContains(searchResult1 as Bundle, patient as Patient)).toEqual(true); + })); + + test('Handle malformed _lastUpdated', async () => { + try { + await repo.search({ + resourceType: 'Patient', filters: [ { - code: 'type', - operator: Operator.CONTAINS, - value: auditEvents[i].type?.code as string, + code: '_lastUpdated', + operator: Operator.GREATER_THAN, + value: 'xyz', }, ], }); - expect(bundle.entry?.length).toEqual(1); - expect(bundle.entry?.[0]?.resource?.id).toEqual(auditEvents[i].id); + fail('Expected error'); + } catch (err) { + expect(normalizeErrorString(err)).toEqual('Invalid date value: xyz'); } - })); + }); - test('Filter by CodeableConcept', () => - withTestContext(async () => { - const x1 = randomUUID(); - const x2 = randomUUID(); - const x3 = randomUUID(); + test('Filter by Coding', () => + withTestContext(async () => { + const auditEvents = [] as AuditEvent[]; - // Create test patient - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['John'], family: 'CodeableConcept' }], - }); + for (let i = 0; i < 3; i++) { + const resource = await repo.createResource({ + resourceType: 'AuditEvent', + recorded: new Date().toISOString(), + type: { + code: randomUUID(), + }, + agent: [ + { + who: { reference: 'Practitioner/' + randomUUID() }, + requestor: true, + }, + ], + source: { + observer: { reference: 'Practitioner/' + randomUUID() }, + }, + }); + auditEvents.push(resource); + } - const serviceRequest1 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: createReference(patient), - code: { coding: [{ code: x1 }] }, - }); + for (let i = 0; i < 3; i++) { + const bundle = await repo.search({ + resourceType: 'AuditEvent', + filters: [ + { + code: 'type', + operator: Operator.CONTAINS, + value: auditEvents[i].type?.code as string, + }, + ], + }); + expect(bundle.entry?.length).toEqual(1); + expect(bundle.entry?.[0]?.resource?.id).toEqual(auditEvents[i].id); + } + })); - const serviceRequest2 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: createReference(patient), - code: { coding: [{ code: x2 }] }, - }); + test('Filter by CodeableConcept', () => + withTestContext(async () => { + const x1 = randomUUID(); + const x2 = randomUUID(); + const x3 = randomUUID(); - const serviceRequest3 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: createReference(patient), - code: { coding: [{ code: x3 }] }, - }); + // Create test patient + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['John'], family: 'CodeableConcept' }], + }); - const bundle1 = await systemRepo.search({ - resourceType: 'ServiceRequest', - filters: [ - { - code: 'code', - operator: Operator.EQUALS, - value: x1, - }, - ], - }); - expect(bundle1.entry?.length).toEqual(1); - expect(bundleContains(bundle1, serviceRequest1)).toEqual(true); - expect(bundleContains(bundle1, serviceRequest2)).toEqual(false); - expect(bundleContains(bundle1, serviceRequest3)).toEqual(false); + const serviceRequest1 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: createReference(patient), + code: { coding: [{ code: x1 }] }, + }); - const bundle2 = await systemRepo.search({ - resourceType: 'ServiceRequest', - filters: [ - { - code: 'code', - operator: Operator.EQUALS, - value: x2, - }, - ], - }); - expect(bundle2.entry?.length).toEqual(1); - expect(bundleContains(bundle2, serviceRequest1)).toEqual(false); - expect(bundleContains(bundle2, serviceRequest2)).toEqual(true); - expect(bundleContains(bundle2, serviceRequest3)).toEqual(false); + const serviceRequest2 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: createReference(patient), + code: { coding: [{ code: x2 }] }, + }); - const bundle3 = await systemRepo.search({ - resourceType: 'ServiceRequest', - filters: [ - { - code: 'code', - operator: Operator.EQUALS, - value: x3, - }, - ], - }); - expect(bundle3.entry?.length).toEqual(1); - expect(bundleContains(bundle3, serviceRequest1)).toEqual(false); - expect(bundleContains(bundle3, serviceRequest2)).toEqual(false); - expect(bundleContains(bundle3, serviceRequest3)).toEqual(true); - })); + const serviceRequest3 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: createReference(patient), + code: { coding: [{ code: x3 }] }, + }); - test('Filter by Quantity.value', () => - withTestContext(async () => { - const code = randomUUID(); + const bundle1 = await repo.search({ + resourceType: 'ServiceRequest', + filters: [ + { + code: 'code', + operator: Operator.EQUALS, + value: x1, + }, + ], + }); + expect(bundle1.entry?.length).toEqual(1); + expect(bundleContains(bundle1, serviceRequest1)).toEqual(true); + expect(bundleContains(bundle1, serviceRequest2)).toEqual(false); + expect(bundleContains(bundle1, serviceRequest3)).toEqual(false); - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['John'], family: 'Quantity' }], - }); + const bundle2 = await repo.search({ + resourceType: 'ServiceRequest', + filters: [ + { + code: 'code', + operator: Operator.EQUALS, + value: x2, + }, + ], + }); + expect(bundle2.entry?.length).toEqual(1); + expect(bundleContains(bundle2, serviceRequest1)).toEqual(false); + expect(bundleContains(bundle2, serviceRequest2)).toEqual(true); + expect(bundleContains(bundle2, serviceRequest3)).toEqual(false); - const observation1 = await systemRepo.createResource({ - resourceType: 'Observation', - status: 'final', - subject: createReference(patient), - code: { coding: [{ code }] }, - valueQuantity: { value: 1, unit: 'mg' }, - }); + const bundle3 = await repo.search({ + resourceType: 'ServiceRequest', + filters: [ + { + code: 'code', + operator: Operator.EQUALS, + value: x3, + }, + ], + }); + expect(bundle3.entry?.length).toEqual(1); + expect(bundleContains(bundle3, serviceRequest1)).toEqual(false); + expect(bundleContains(bundle3, serviceRequest2)).toEqual(false); + expect(bundleContains(bundle3, serviceRequest3)).toEqual(true); + })); - const observation2 = await systemRepo.createResource({ - resourceType: 'Observation', - status: 'final', - subject: createReference(patient), - code: { coding: [{ code }] }, - valueQuantity: { value: 5, unit: 'mg' }, - }); + test('Filter by Quantity.value', () => + withTestContext(async () => { + const code = randomUUID(); - const observation3 = await systemRepo.createResource({ - resourceType: 'Observation', - status: 'final', - subject: createReference(patient), - code: { coding: [{ code }] }, - valueQuantity: { value: 10, unit: 'mg' }, - }); + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['John'], family: 'Quantity' }], + }); - const bundle1 = await systemRepo.search({ - resourceType: 'Observation', - filters: [{ code: 'code', operator: Operator.EQUALS, value: code }], - sortRules: [{ code: 'value-quantity', descending: false }], - }); - expect(bundle1.entry?.length).toEqual(3); - expect(bundle1.entry?.[0]?.resource?.id).toEqual(observation1.id); - expect(bundle1.entry?.[1]?.resource?.id).toEqual(observation2.id); - expect(bundle1.entry?.[2]?.resource?.id).toEqual(observation3.id); - - const bundle2 = await systemRepo.search({ - resourceType: 'Observation', - filters: [{ code: 'code', operator: Operator.EQUALS, value: code }], - sortRules: [{ code: 'value-quantity', descending: true }], - }); - expect(bundle2.entry?.length).toEqual(3); - expect(bundle2.entry?.[0]?.resource?.id).toEqual(observation3.id); - expect(bundle2.entry?.[1]?.resource?.id).toEqual(observation2.id); - expect(bundle2.entry?.[2]?.resource?.id).toEqual(observation1.id); + const observation1 = await repo.createResource({ + resourceType: 'Observation', + status: 'final', + subject: createReference(patient), + code: { coding: [{ code }] }, + valueQuantity: { value: 1, unit: 'mg' }, + }); - const bundle3 = await systemRepo.search({ - resourceType: 'Observation', - filters: [ - { code: 'code', operator: Operator.EQUALS, value: code }, - { code: 'value-quantity', operator: Operator.GREATER_THAN, value: '8' }, - ], - }); - expect(bundle3.entry?.length).toEqual(1); - expect(bundle3.entry?.[0]?.resource?.id).toEqual(observation3.id); - })); - - test('ServiceRequest.orderDetail search', () => - withTestContext(async () => { - const orderDetailText = randomUUID(); - const orderDetailCode = randomUUID(); - - const serviceRequest = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: { - reference: 'Patient/' + randomUUID(), - }, - code: { - coding: [ - { - code: 'order-type', - }, - ], - }, - orderDetail: [ - { - text: orderDetailText, + const observation2 = await repo.createResource({ + resourceType: 'Observation', + status: 'final', + subject: createReference(patient), + code: { coding: [{ code }] }, + valueQuantity: { value: 5, unit: 'mg' }, + }); + + const observation3 = await repo.createResource({ + resourceType: 'Observation', + status: 'final', + subject: createReference(patient), + code: { coding: [{ code }] }, + valueQuantity: { value: 10, unit: 'mg' }, + }); + + const bundle1 = await repo.search({ + resourceType: 'Observation', + filters: [{ code: 'code', operator: Operator.EQUALS, value: code }], + sortRules: [{ code: 'value-quantity', descending: false }], + }); + expect(bundle1.entry?.length).toEqual(3); + expect(bundle1.entry?.[0]?.resource?.id).toEqual(observation1.id); + expect(bundle1.entry?.[1]?.resource?.id).toEqual(observation2.id); + expect(bundle1.entry?.[2]?.resource?.id).toEqual(observation3.id); + + const bundle2 = await repo.search({ + resourceType: 'Observation', + filters: [{ code: 'code', operator: Operator.EQUALS, value: code }], + sortRules: [{ code: 'value-quantity', descending: true }], + }); + expect(bundle2.entry?.length).toEqual(3); + expect(bundle2.entry?.[0]?.resource?.id).toEqual(observation3.id); + expect(bundle2.entry?.[1]?.resource?.id).toEqual(observation2.id); + expect(bundle2.entry?.[2]?.resource?.id).toEqual(observation1.id); + + const bundle3 = await repo.search({ + resourceType: 'Observation', + filters: [ + { code: 'code', operator: Operator.EQUALS, value: code }, + { code: 'value-quantity', operator: Operator.GREATER_THAN, value: '8' }, + ], + }); + expect(bundle3.entry?.length).toEqual(1); + expect(bundle3.entry?.[0]?.resource?.id).toEqual(observation3.id); + })); + + test('ServiceRequest.orderDetail search', () => + withTestContext(async () => { + const orderDetailText = randomUUID(); + const orderDetailCode = randomUUID(); + + const serviceRequest = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: { + reference: 'Patient/' + randomUUID(), + }, + code: { coding: [ { - system: 'custom-order-system', - code: orderDetailCode, + code: 'order-type', }, ], }, - ], - }); + orderDetail: [ + { + text: orderDetailText, + coding: [ + { + system: 'custom-order-system', + code: orderDetailCode, + }, + ], + }, + ], + }); - const bundle1 = await systemRepo.search({ - resourceType: 'ServiceRequest', - filters: [ - { - code: 'order-detail', - operator: Operator.CONTAINS, - value: orderDetailText, - }, - ], - }); - expect(bundle1.entry?.length).toEqual(1); - expect(bundle1.entry?.[0]?.resource?.id).toEqual(serviceRequest.id); - })); - - test('Comma separated value', () => - withTestContext(async () => { - const category = randomUUID(); - const codes = [randomUUID(), randomUUID(), randomUUID()]; - const serviceRequests = []; - - for (const code of codes) { - const serviceRequest = await systemRepo.createResource({ + const bundle1 = await repo.search({ + resourceType: 'ServiceRequest', + filters: [ + { + code: 'order-detail', + operator: Operator.CONTAINS, + value: orderDetailText, + }, + ], + }); + expect(bundle1.entry?.length).toEqual(1); + expect(bundle1.entry?.[0]?.resource?.id).toEqual(serviceRequest.id); + })); + + test('Comma separated value', () => + withTestContext(async () => { + const category = randomUUID(); + const codes = [randomUUID(), randomUUID(), randomUUID()]; + const serviceRequests = []; + + for (const code of codes) { + const serviceRequest = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: { reference: 'Patient/' + randomUUID() }, + category: [{ coding: [{ code: category }] }], + code: { coding: [{ code: code }] }, + }); + serviceRequests.push(serviceRequest); + } + + const bundle1 = await repo.search( + parseSearchRequest('ServiceRequest', { category, code: `${codes[0]},${codes[1]}` }) + ); + expect(bundle1.entry?.length).toEqual(2); + expect(bundleContains(bundle1, serviceRequests[0])).toEqual(true); + expect(bundleContains(bundle1, serviceRequests[1])).toEqual(true); + })); + + test('Token not equals', () => + withTestContext(async () => { + const category = randomUUID(); + const code1 = randomUUID(); + const code2 = randomUUID(); + + const serviceRequest1 = await repo.createResource({ resourceType: 'ServiceRequest', status: 'active', intent: 'order', subject: { reference: 'Patient/' + randomUUID() }, category: [{ coding: [{ code: category }] }], - code: { coding: [{ code: code }] }, + code: { coding: [{ code: code1 }] }, }); - serviceRequests.push(serviceRequest); - } - const bundle1 = await systemRepo.search( - parseSearchRequest('ServiceRequest', { category, code: `${codes[0]},${codes[1]}` }) - ); - expect(bundle1.entry?.length).toEqual(2); - expect(bundleContains(bundle1, serviceRequests[0])).toEqual(true); - expect(bundleContains(bundle1, serviceRequests[1])).toEqual(true); - })); - - test('Token not equals', () => - withTestContext(async () => { - const category = randomUUID(); - const code1 = randomUUID(); - const code2 = randomUUID(); - - const serviceRequest1 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: { reference: 'Patient/' + randomUUID() }, - category: [{ coding: [{ code: category }] }], - code: { coding: [{ code: code1 }] }, - }); + const serviceRequest2 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: { reference: 'Patient/' + randomUUID() }, + category: [{ coding: [{ code: category }] }], + code: { coding: [{ code: code2 }] }, + }); - const serviceRequest2 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: { reference: 'Patient/' + randomUUID() }, - category: [{ coding: [{ code: category }] }], - code: { coding: [{ code: code2 }] }, - }); + const bundle1 = await repo.search(parseSearchRequest('ServiceRequest', { category, 'code:not': code1 })); + expect(bundle1.entry?.length).toEqual(1); + expect(bundleContains(bundle1, serviceRequest1)).toEqual(false); + expect(bundleContains(bundle1, serviceRequest2)).toEqual(true); + })); - const bundle1 = await systemRepo.search(parseSearchRequest('ServiceRequest', { category, 'code:not': code1 })); - expect(bundle1.entry?.length).toEqual(1); - expect(bundleContains(bundle1, serviceRequest1)).toEqual(false); - expect(bundleContains(bundle1, serviceRequest2)).toEqual(true); - })); - - test('Token array not equals', () => - withTestContext(async () => { - const category1 = randomUUID(); - const category2 = randomUUID(); - const code = randomUUID(); - - const serviceRequest1 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: { reference: 'Patient/' + randomUUID() }, - category: [{ coding: [{ code: category1 }] }], - code: { coding: [{ code }] }, - }); + test('Token array not equals', () => + withTestContext(async () => { + const category1 = randomUUID(); + const category2 = randomUUID(); + const code = randomUUID(); - const serviceRequest2 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: { reference: 'Patient/' + randomUUID() }, - category: [{ coding: [{ code: category2 }] }], - code: { coding: [{ code }] }, - }); + const serviceRequest1 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: { reference: 'Patient/' + randomUUID() }, + category: [{ coding: [{ code: category1 }] }], + code: { coding: [{ code }] }, + }); - const bundle1 = await systemRepo.search( - parseSearchRequest('ServiceRequest', { code, 'category:not': category1 }) - ); - expect(bundle1.entry?.length).toEqual(1); - expect(bundleContains(bundle1, serviceRequest1)).toEqual(false); - expect(bundleContains(bundle1, serviceRequest2)).toEqual(true); - })); - - test('Null token array not equals', () => - withTestContext(async () => { - const category1 = randomUUID(); - const code = randomUUID(); - - const serviceRequest1 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: { reference: 'Patient/' + randomUUID() }, - category: [{ coding: [{ code: category1 }] }], - code: { coding: [{ code }] }, - }); + const serviceRequest2 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: { reference: 'Patient/' + randomUUID() }, + category: [{ coding: [{ code: category2 }] }], + code: { coding: [{ code }] }, + }); - const serviceRequest2 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: { reference: 'Patient/' + randomUUID() }, - code: { coding: [{ code }] }, - }); + const bundle1 = await repo.search(parseSearchRequest('ServiceRequest', { code, 'category:not': category1 })); + expect(bundle1.entry?.length).toEqual(1); + expect(bundleContains(bundle1, serviceRequest1)).toEqual(false); + expect(bundleContains(bundle1, serviceRequest2)).toEqual(true); + })); - const bundle1 = await systemRepo.search( - parseSearchRequest('ServiceRequest', { code, 'category:not': category1 }) - ); - expect(bundle1.entry?.length).toEqual(1); - expect(bundleContains(bundle1, serviceRequest1)).toEqual(false); - expect(bundleContains(bundle1, serviceRequest2)).toEqual(true); - })); - - test('Missing', () => - withTestContext(async () => { - const code = randomUUID(); - - // Test both an array column (specimen) and a non-array column (encounter), - // because the resulting SQL could be subtly different. - - const serviceRequest1 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - code: { coding: [{ code }] }, - subject: { reference: 'Patient/' + randomUUID() }, - specimen: [{ reference: 'Specimen/' + randomUUID() }], - encounter: { reference: 'Encounter/' + randomUUID() }, - }); + test('Null token array not equals', () => + withTestContext(async () => { + const category1 = randomUUID(); + const code = randomUUID(); - const serviceRequest2 = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - code: { coding: [{ code }] }, - subject: { reference: 'Patient/' + randomUUID() }, - }); + const serviceRequest1 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: { reference: 'Patient/' + randomUUID() }, + category: [{ coding: [{ code: category1 }] }], + code: { coding: [{ code }] }, + }); - const bundle1 = await systemRepo.search( - parseSearchRequest('ServiceRequest', { code, 'specimen:missing': 'true' }) - ); - expect(bundle1.entry?.length).toEqual(1); - expect(bundleContains(bundle1, serviceRequest1)).toEqual(false); - expect(bundleContains(bundle1, serviceRequest2)).toEqual(true); + const serviceRequest2 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: { reference: 'Patient/' + randomUUID() }, + code: { coding: [{ code }] }, + }); - const bundle2 = await systemRepo.search( - parseSearchRequest('ServiceRequest', { code, 'specimen:missing': 'false' }) - ); - expect(bundle2.entry?.length).toEqual(1); - expect(bundleContains(bundle2, serviceRequest1)).toEqual(true); - expect(bundleContains(bundle2, serviceRequest2)).toEqual(false); - - const bundle3 = await systemRepo.search( - parseSearchRequest('ServiceRequest', { code, 'encounter:missing': 'true' }) - ); - expect(bundle3.entry?.length).toEqual(1); - expect(bundleContains(bundle3, serviceRequest1)).toEqual(false); - expect(bundleContains(bundle3, serviceRequest2)).toEqual(true); - - const bundle4 = await systemRepo.search( - parseSearchRequest('ServiceRequest', { code, 'encounter:missing': 'false' }) - ); - expect(bundle4.entry?.length).toEqual(1); - expect(bundleContains(bundle4, serviceRequest1)).toEqual(true); - expect(bundleContains(bundle4, serviceRequest2)).toEqual(false); - })); - - test('Missing with logical (identifier) references', () => - withTestContext(async () => { - const patientIdentifier = randomUUID(); - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - identifier: [ - { - system: 'http://example.com/guid', - value: patientIdentifier, - }, - ], - generalPractitioner: [ - { + const bundle1 = await repo.search(parseSearchRequest('ServiceRequest', { code, 'category:not': category1 })); + expect(bundle1.entry?.length).toEqual(1); + expect(bundleContains(bundle1, serviceRequest1)).toEqual(false); + expect(bundleContains(bundle1, serviceRequest2)).toEqual(true); + })); + + test('Missing', () => + withTestContext(async () => { + const code = randomUUID(); + + // Test both an array column (specimen) and a non-array column (encounter), + // because the resulting SQL could be subtly different. + + const serviceRequest1 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + code: { coding: [{ code }] }, + subject: { reference: 'Patient/' + randomUUID() }, + specimen: [{ reference: 'Specimen/' + randomUUID() }], + encounter: { reference: 'Encounter/' + randomUUID() }, + }); + + const serviceRequest2 = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + code: { coding: [{ code }] }, + subject: { reference: 'Patient/' + randomUUID() }, + }); + + const bundle1 = await repo.search(parseSearchRequest('ServiceRequest', { code, 'specimen:missing': 'true' })); + expect(bundle1.entry?.length).toEqual(1); + expect(bundleContains(bundle1, serviceRequest1)).toEqual(false); + expect(bundleContains(bundle1, serviceRequest2)).toEqual(true); + + const bundle2 = await repo.search(parseSearchRequest('ServiceRequest', { code, 'specimen:missing': 'false' })); + expect(bundle2.entry?.length).toEqual(1); + expect(bundleContains(bundle2, serviceRequest1)).toEqual(true); + expect(bundleContains(bundle2, serviceRequest2)).toEqual(false); + + const bundle3 = await repo.search(parseSearchRequest('ServiceRequest', { code, 'encounter:missing': 'true' })); + expect(bundle3.entry?.length).toEqual(1); + expect(bundleContains(bundle3, serviceRequest1)).toEqual(false); + expect(bundleContains(bundle3, serviceRequest2)).toEqual(true); + + const bundle4 = await repo.search(parseSearchRequest('ServiceRequest', { code, 'encounter:missing': 'false' })); + expect(bundle4.entry?.length).toEqual(1); + expect(bundleContains(bundle4, serviceRequest1)).toEqual(true); + expect(bundleContains(bundle4, serviceRequest2)).toEqual(false); + })); + + test('Missing with logical (identifier) references', () => + withTestContext(async () => { + const patientIdentifier = randomUUID(); + const patient = await repo.createResource({ + resourceType: 'Patient', + identifier: [ + { + system: 'http://example.com/guid', + value: patientIdentifier, + }, + ], + generalPractitioner: [ + { + identifier: { + system: 'http://hl7.org/fhir/sid/us-npi', + value: '9876543210', + }, + }, + ], + managingOrganization: { identifier: { system: 'http://hl7.org/fhir/sid/us-npi', - value: '9876543210', + value: '0123456789', }, }, - ], - managingOrganization: { - identifier: { - system: 'http://hl7.org/fhir/sid/us-npi', - value: '0123456789', - }, - }, - }); + }); - // Test singlet reference column - let results = await systemRepo.searchResources( - parseSearchDefinition(`Patient?identifier=${patientIdentifier}&organization:missing=false`) - ); - expect(results).toHaveLength(1); - expect(results[0]?.id).toEqual(patient.id); - - // Test array reference column - results = await systemRepo.searchResources( - parseSearchDefinition(`Patient?identifier=${patientIdentifier}&general-practitioner:missing=false`) - ); - expect(results).toHaveLength(1); - expect(results[0]?.id).toEqual(patient.id); - })); - - test('Starts after', () => - withTestContext(async () => { - // Create 2 appointments - // One with a start date of 1 second ago - // One with a start date of 2 seconds ago - const code = randomUUID(); - const now = new Date(); - const nowMinus1Second = new Date(now.getTime() - 1000); - const nowMinus2Seconds = new Date(now.getTime() - 2000); - const nowMinus3Seconds = new Date(now.getTime() - 3000); - const patient: Patient = { resourceType: 'Patient' }; - const patientReference = createReference(patient); - const appt1 = await systemRepo.createResource({ - resourceType: 'Appointment', - status: 'booked', - serviceType: [{ coding: [{ code }] }], - participant: [{ status: 'accepted', actor: patientReference }], - start: nowMinus1Second.toISOString(), - end: now.toISOString(), - }); - expect(appt1).toBeDefined(); - - const appt2 = await systemRepo.createResource({ - resourceType: 'Appointment', - status: 'booked', - serviceType: [{ coding: [{ code }] }], - participant: [{ status: 'accepted', actor: patientReference }], - start: nowMinus2Seconds.toISOString(), - end: now.toISOString(), - }); - expect(appt2).toBeDefined(); + // Test singlet reference column + let results = await repo.searchResources( + parseSearchRequest(`Patient?identifier=${patientIdentifier}&organization:missing=false`) + ); + expect(results).toHaveLength(1); + expect(results[0]?.id).toEqual(patient.id); - // Greater than (newer than) 2 seconds ago should only return appt 1 - const searchResult1 = await systemRepo.search({ - resourceType: 'Appointment', - filters: [ - { - code: 'service-type', - operator: Operator.EQUALS, - value: code, - }, - { - code: 'date', - operator: Operator.STARTS_AFTER, - value: nowMinus2Seconds.toISOString(), - }, - ], - }); + // Test array reference column + results = await repo.searchResources( + parseSearchRequest(`Patient?identifier=${patientIdentifier}&general-practitioner:missing=false`) + ); + expect(results).toHaveLength(1); + expect(results[0]?.id).toEqual(patient.id); + })); + + test('Starts after', () => + withTestContext(async () => { + // Create 2 appointments + // One with a start date of 1 second ago + // One with a start date of 2 seconds ago + const code = randomUUID(); + const now = new Date(); + const nowMinus1Second = new Date(now.getTime() - 1000); + const nowMinus2Seconds = new Date(now.getTime() - 2000); + const nowMinus3Seconds = new Date(now.getTime() - 3000); + const patient: Patient = { resourceType: 'Patient' }; + const patientReference = createReference(patient); + const appt1 = await repo.createResource({ + resourceType: 'Appointment', + status: 'booked', + serviceType: [{ coding: [{ code }] }], + participant: [{ status: 'accepted', actor: patientReference }], + start: nowMinus1Second.toISOString(), + end: now.toISOString(), + }); + expect(appt1).toBeDefined(); + + const appt2 = await repo.createResource({ + resourceType: 'Appointment', + status: 'booked', + serviceType: [{ coding: [{ code }] }], + participant: [{ status: 'accepted', actor: patientReference }], + start: nowMinus2Seconds.toISOString(), + end: now.toISOString(), + }); + expect(appt2).toBeDefined(); - expect(bundleContains(searchResult1 as Bundle, appt1 as Appointment)).toEqual(true); - expect(bundleContains(searchResult1 as Bundle, appt2 as Appointment)).toEqual(false); + // Greater than (newer than) 2 seconds ago should only return appt 1 + const searchResult1 = await repo.search({ + resourceType: 'Appointment', + filters: [ + { + code: 'service-type', + operator: Operator.EQUALS, + value: code, + }, + { + code: 'date', + operator: Operator.STARTS_AFTER, + value: nowMinus2Seconds.toISOString(), + }, + ], + }); - // Greater than (newer than) or equal to 2 seconds ago should return both appts - const searchResult2 = await systemRepo.search({ - resourceType: 'Appointment', - filters: [ - { - code: 'service-type', - operator: Operator.EQUALS, - value: code, - }, - { - code: 'date', - operator: Operator.GREATER_THAN_OR_EQUALS, - value: nowMinus2Seconds.toISOString(), - }, - ], - }); + expect(bundleContains(searchResult1 as Bundle, appt1 as Appointment)).toEqual(true); + expect(bundleContains(searchResult1 as Bundle, appt2 as Appointment)).toEqual(false); - expect(bundleContains(searchResult2 as Bundle, appt1 as Appointment)).toEqual(true); - expect(bundleContains(searchResult2 as Bundle, appt2 as Appointment)).toEqual(true); + // Greater than (newer than) or equal to 2 seconds ago should return both appts + const searchResult2 = await repo.search({ + resourceType: 'Appointment', + filters: [ + { + code: 'service-type', + operator: Operator.EQUALS, + value: code, + }, + { + code: 'date', + operator: Operator.GREATER_THAN_OR_EQUALS, + value: nowMinus2Seconds.toISOString(), + }, + ], + }); - // Less than (older than) to 1 seconds ago should only return appt 2 - const searchResult3 = await systemRepo.search({ - resourceType: 'Appointment', - filters: [ - { - code: 'service-type', - operator: Operator.EQUALS, - value: code, - }, - { - code: 'date', - operator: Operator.STARTS_AFTER, - value: nowMinus3Seconds.toISOString(), - }, - { - code: 'date', - operator: Operator.ENDS_BEFORE, - value: nowMinus1Second.toISOString(), - }, - ], - }); + expect(bundleContains(searchResult2 as Bundle, appt1 as Appointment)).toEqual(true); + expect(bundleContains(searchResult2 as Bundle, appt2 as Appointment)).toEqual(true); - expect(bundleContains(searchResult3 as Bundle, appt1 as Appointment)).toEqual(false); - expect(bundleContains(searchResult3 as Bundle, appt2 as Appointment)).toEqual(true); + // Less than (older than) to 1 seconds ago should only return appt 2 + const searchResult3 = await repo.search({ + resourceType: 'Appointment', + filters: [ + { + code: 'service-type', + operator: Operator.EQUALS, + value: code, + }, + { + code: 'date', + operator: Operator.STARTS_AFTER, + value: nowMinus3Seconds.toISOString(), + }, + { + code: 'date', + operator: Operator.ENDS_BEFORE, + value: nowMinus1Second.toISOString(), + }, + ], + }); - // Less than (older than) or equal to 1 seconds ago should return both appts - const searchResult4 = await systemRepo.search({ - resourceType: 'Appointment', - filters: [ - { - code: 'service-type', - operator: Operator.EQUALS, - value: code, - }, - { - code: 'date', - operator: Operator.STARTS_AFTER, - value: nowMinus3Seconds.toISOString(), - }, - { - code: 'date', - operator: Operator.LESS_THAN_OR_EQUALS, - value: nowMinus1Second.toISOString(), - }, - ], - }); + expect(bundleContains(searchResult3 as Bundle, appt1 as Appointment)).toEqual(false); + expect(bundleContains(searchResult3 as Bundle, appt2 as Appointment)).toEqual(true); - expect(bundleContains(searchResult4 as Bundle, appt1 as Appointment)).toEqual(true); - expect(bundleContains(searchResult4 as Bundle, appt2 as Appointment)).toEqual(true); - })); + // Less than (older than) or equal to 1 seconds ago should return both appts + const searchResult4 = await repo.search({ + resourceType: 'Appointment', + filters: [ + { + code: 'service-type', + operator: Operator.EQUALS, + value: code, + }, + { + code: 'date', + operator: Operator.STARTS_AFTER, + value: nowMinus3Seconds.toISOString(), + }, + { + code: 'date', + operator: Operator.LESS_THAN_OR_EQUALS, + value: nowMinus1Second.toISOString(), + }, + ], + }); - test('Boolean search', () => - withTestContext(async () => { - const family = randomUUID(); - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ family }], - active: true, - }); - const searchResult = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: 'name', - operator: Operator.EQUALS, - value: family, - }, - { - code: 'active', - operator: Operator.EQUALS, - value: 'true', - }, - ], - }); - expect(searchResult.entry).toHaveLength(1); - expect(searchResult.entry?.[0]?.resource?.id).toEqual(patient.id); - })); - - test('Not equals with comma separated values', () => - withTestContext(async () => { - // Create 3 service requests - // All 3 have the same category for test isolation - // Each have a different code - const category = randomUUID(); - const serviceRequests = []; - for (let i = 0; i < 3; i++) { - serviceRequests.push( - await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: { reference: 'Patient/' + randomUUID() }, - category: [{ coding: [{ code: category }] }], - code: { coding: [{ code: randomUUID() }] }, - }) - ); - } + expect(bundleContains(searchResult4 as Bundle, appt1 as Appointment)).toEqual(true); + expect(bundleContains(searchResult4 as Bundle, appt2 as Appointment)).toEqual(true); + })); - // Search for service requests with category - // and code "not equals" the first two separated by a comma - const searchResult = await systemRepo.search({ - resourceType: 'ServiceRequest', - filters: [ - { - code: 'category', - operator: Operator.EQUALS, - value: category, - }, - { - code: 'code', - operator: Operator.NOT_EQUALS, - value: serviceRequests[0].code.coding[0].code + ',' + serviceRequests[1].code.coding[0].code, - }, - ], - }); - expect(searchResult.entry).toHaveLength(1); - })); - - test('_id equals with comma separated values', () => - withTestContext(async () => { - // Create 3 service requests - const serviceRequests = []; - for (let i = 0; i < 3; i++) { - serviceRequests.push( - await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: { reference: 'Patient/' + randomUUID() }, - code: { text: randomUUID() }, - }) - ); - } + test('Boolean search', () => + withTestContext(async () => { + const family = randomUUID(); + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ family }], + active: true, + }); + const searchResult = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'name', + operator: Operator.EQUALS, + value: family, + }, + { + code: 'active', + operator: Operator.EQUALS, + value: 'true', + }, + ], + }); + expect(searchResult.entry).toHaveLength(1); + expect(searchResult.entry?.[0]?.resource?.id).toEqual(patient.id); + })); + + test('Not equals with comma separated values', () => + withTestContext(async () => { + // Create 3 service requests + // All 3 have the same category for test isolation + // Each have a different code + const category = randomUUID(); + const serviceRequests = []; + for (let i = 0; i < 3; i++) { + serviceRequests.push( + await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: { reference: 'Patient/' + randomUUID() }, + category: [{ coding: [{ code: category }] }], + code: { coding: [{ code: randomUUID() }] }, + }) + ); + } - // Search for service requests with _id equals the first two separated by a comma - const searchResult = await systemRepo.search({ - resourceType: 'ServiceRequest', - filters: [ - { - code: '_id', - operator: Operator.EQUALS, - value: serviceRequests[0].id + ',' + serviceRequests[1].id, - }, - ], - }); - expect(searchResult.entry).toHaveLength(2); - })); + // Search for service requests with category + // and code "not equals" the first two separated by a comma + const searchResult = await repo.search({ + resourceType: 'ServiceRequest', + filters: [ + { + code: 'category', + operator: Operator.EQUALS, + value: category, + }, + { + code: 'code', + operator: Operator.NOT_EQUALS, + value: serviceRequests[0].code.coding[0].code + ',' + serviceRequests[1].code.coding[0].code, + }, + ], + }); + expect(searchResult.entry).toHaveLength(1); + })); + + test('_id equals with comma separated values', () => + withTestContext(async () => { + // Create 3 service requests + const serviceRequests = []; + for (let i = 0; i < 3; i++) { + serviceRequests.push( + await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: { reference: 'Patient/' + randomUUID() }, + code: { text: randomUUID() }, + }) + ); + } - test('Error on invalid search parameter', async () => { - try { - await systemRepo.search({ - resourceType: 'ServiceRequest', - filters: [ - { - code: 'basedOn', // should be "based-on" - operator: Operator.EQUALS, - value: 'ServiceRequest/123', - }, - ], - }); - } catch (err) { - const outcome = (err as OperationOutcomeError).outcome; - expect(outcome.issue?.[0]?.details?.text).toEqual('Unknown search parameter: basedOn'); - } - }); + // Search for service requests with _id equals the first two separated by a comma + const searchResult = await repo.search({ + resourceType: 'ServiceRequest', + filters: [ + { + code: '_id', + operator: Operator.EQUALS, + value: serviceRequests[0].id + ',' + serviceRequests[1].id, + }, + ], + }); + expect(searchResult.entry).toHaveLength(2); + })); - test('Patient search without resource type', () => - withTestContext(async () => { - // Create Patient - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - }); + test('Error on invalid search parameter', async () => { + try { + await repo.search({ + resourceType: 'ServiceRequest', + filters: [ + { + code: 'basedOn', // should be "based-on" + operator: Operator.EQUALS, + value: 'ServiceRequest/123', + }, + ], + }); + } catch (err) { + const outcome = (err as OperationOutcomeError).outcome; + expect(outcome.issue?.[0]?.details?.text).toEqual('Unknown search parameter: basedOn'); + } + }); - // Create AllergyIntolerance - const allergyIntolerance = await systemRepo.createResource({ - resourceType: 'AllergyIntolerance', - patient: createReference(patient), - clinicalStatus: { text: 'active' }, - }); + test('Patient search without resource type', () => + withTestContext(async () => { + // Create Patient + const patient = await repo.createResource({ + resourceType: 'Patient', + }); - // Search by patient - const searchResult = await systemRepo.search({ - resourceType: 'AllergyIntolerance', - filters: [ - { - code: 'patient', - operator: Operator.EQUALS, - value: patient.id as string, - }, - ], - }); - expect(searchResult.entry?.[0]?.resource?.id).toEqual(allergyIntolerance.id); - })); + // Create AllergyIntolerance + const allergyIntolerance = await repo.createResource({ + resourceType: 'AllergyIntolerance', + patient: createReference(patient), + clinicalStatus: { text: 'active' }, + }); - test('Subject search without resource type', () => - withTestContext(async () => { - // Create Patient - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - }); + // Search by patient + const searchResult = await repo.search({ + resourceType: 'AllergyIntolerance', + filters: [ + { + code: 'patient', + operator: Operator.EQUALS, + value: patient.id as string, + }, + ], + }); + expect(searchResult.entry?.[0]?.resource?.id).toEqual(allergyIntolerance.id); + })); - // Create Observation - const observation = await systemRepo.createResource({ - resourceType: 'Observation', - status: 'final', - code: { text: 'test' }, - subject: createReference(patient), - }); + test('Subject search without resource type', () => + withTestContext(async () => { + // Create Patient + const patient = await repo.createResource({ + resourceType: 'Patient', + }); - // Search by patient - const searchResult = await systemRepo.search({ - resourceType: 'Observation', - filters: [ - { - code: 'subject', - operator: Operator.EQUALS, - value: patient.id as string, - }, - ], - }); - expect(searchResult.entry?.[0]?.resource?.id).toEqual(observation.id); - })); - - test('Chained search on array columns', () => - withTestContext(async () => { - // Create Practitioner - const pcp = await systemRepo.createResource({ - resourceType: 'Practitioner', - }); - // Create Patient - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - generalPractitioner: [createReference(pcp)], - }); + // Create Observation + const observation = await repo.createResource({ + resourceType: 'Observation', + status: 'final', + code: { text: 'test' }, + subject: createReference(patient), + }); - // Create CareTeam - const code = randomUUID(); - const categorySystem = 'http://example.com/care-team-category'; - await systemRepo.createResource({ - resourceType: 'CareTeam', - category: [ - { - coding: [ - { - system: categorySystem, - code, - display: 'Public health-focused care team', - }, - ], - }, - ], - participant: [{ member: createReference(pcp) }], - }); + // Search by patient + const searchResult = await repo.search({ + resourceType: 'Observation', + filters: [ + { + code: 'subject', + operator: Operator.EQUALS, + value: patient.id as string, + }, + ], + }); + expect(searchResult.entry?.[0]?.resource?.id).toEqual(observation.id); + })); + + test('Chained search on array columns', () => + withTestContext(async () => { + // Create Practitioner + const pcp = await repo.createResource({ + resourceType: 'Practitioner', + }); + // Create Patient + const patient = await repo.createResource({ + resourceType: 'Patient', + generalPractitioner: [createReference(pcp)], + }); - // Search chain - const searchResult = await systemRepo.search( - parseSearchDefinition( - `Patient?general-practitioner:Practitioner._has:CareTeam:participant:category=${categorySystem}|${code}` - ) - ); - expect(searchResult.entry?.[0]?.resource?.id).toEqual(patient.id); - })); - - test('Chained search on singlet columns', () => - withTestContext(async () => { - const code = randomUUID(); - // Create linked resources - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - }); - const encounter = await systemRepo.createResource({ - resourceType: 'Encounter', - status: 'finished', - class: { system: 'http://example.com/appt-type', code }, - }); - const observation = await systemRepo.createResource({ - resourceType: 'Observation', - status: 'final', - code: { text: 'Throat culture' }, - subject: createReference(patient), - encounter: createReference(encounter), - }); - await systemRepo.createResource({ - resourceType: 'DiagnosticReport', - status: 'final', - code: { text: 'Strep test' }, - encounter: createReference(encounter), - result: [createReference(observation)], - }); + // Create CareTeam + const code = randomUUID(); + const categorySystem = 'http://example.com/care-team-category'; + await repo.createResource({ + resourceType: 'CareTeam', + category: [ + { + coding: [ + { + system: categorySystem, + code, + display: 'Public health-focused care team', + }, + ], + }, + ], + participant: [{ member: createReference(pcp) }], + }); - const result = await systemRepo.search( - parseSearchDefinition(`Patient?_has:Observation:subject:encounter:Encounter.class=${code}`) - ); - expect(result.entry?.[0]?.resource?.id).toEqual(patient.id); - })); - - test('Rejects too long chained search', () => - withTestContext(async () => { - await expect(() => - systemRepo.search( - parseSearchDefinition( - `Patient?_has:Observation:subject:encounter:Encounter._has:DiagnosticReport:encounter:result.specimen.parent.collected=2023` + // Search chain + const searchResult = await repo.search( + parseSearchRequest( + `Patient?general-practitioner:Practitioner._has:CareTeam:participant:category=${categorySystem}|${code}` ) - ) - ).rejects.toEqual(new Error('Search chains longer than three links are not currently supported')); - })); - - test.each([ - ['Patient?organization.invalid.name=Kaiser', 'Invalid search parameter in chain: Organization?invalid'], - ['Patient?organization.invalid=true', 'Invalid search parameter at end of chain: Organization?invalid'], - [ - 'Patient?general-practitioner.qualification-period=2023', - 'Unable to identify next resource type for search parameter: Patient?general-practitioner', - ], - ['Patient?_has:Observation:invalid:status=active', 'Invalid search parameter in chain: Observation?invalid'], - [ - 'Patient?_has:Observation:encounter:status=active', - 'Invalid reverse chain link: search parameter Observation?encounter does not refer to Patient', - ], - ['Patient?_has:Observation:status=active', 'Invalid search chain: _has:Observation:status'], - ])('Invalid chained search parameters: %s', (searchString: string, errorMsg: string) => { - return expect(systemRepo.search(parseSearchDefinition(searchString))).rejects.toEqual(new Error(errorMsg)); - }); + ); + expect(searchResult.entry?.[0]?.resource?.id).toEqual(patient.id); + })); + + test('Chained search on singlet columns', () => + withTestContext(async () => { + const code = randomUUID(); + // Create linked resources + const patient = await repo.createResource({ + resourceType: 'Patient', + }); + const encounter = await repo.createResource({ + resourceType: 'Encounter', + status: 'finished', + class: { system: 'http://example.com/appt-type', code }, + }); + const observation = await repo.createResource({ + resourceType: 'Observation', + status: 'final', + code: { text: 'Throat culture' }, + subject: createReference(patient), + encounter: createReference(encounter), + }); + await repo.createResource({ + resourceType: 'DiagnosticReport', + status: 'final', + code: { text: 'Strep test' }, + encounter: createReference(encounter), + result: [createReference(observation)], + }); - test('Include references success', () => - withTestContext(async () => { - const patient = await systemRepo.createResource({ resourceType: 'Patient' }); - const order = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - status: 'active', - intent: 'order', - subject: createReference(patient), - }); - const bundle = await systemRepo.search({ - resourceType: 'ServiceRequest', - include: [ - { - resourceType: 'ServiceRequest', - searchParam: 'subject', - }, - ], - total: 'accurate', - filters: [{ code: '_id', operator: Operator.EQUALS, value: order.id as string }], - }); - expect(bundle.total).toEqual(1); - expect(bundleContains(bundle, order)).toBeTruthy(); - expect(bundleContains(bundle, patient)).toBeTruthy(); - })); - - test('Include canonical success', () => - withTestContext(async () => { - const canonicalURL = 'http://example.com/fhir/Questionnaire/PHQ-9/' + randomUUID(); - const questionnaire = await systemRepo.createResource({ - resourceType: 'Questionnaire', - status: 'active', - url: canonicalURL, - }); - const response = await systemRepo.createResource({ - resourceType: 'QuestionnaireResponse', - status: 'in-progress', - questionnaire: canonicalURL, - }); - const bundle = await systemRepo.search({ - resourceType: 'QuestionnaireResponse', - include: [ - { - resourceType: 'QuestionnaireResponse', - searchParam: 'questionnaire', - }, - ], - total: 'accurate', - filters: [{ code: '_id', operator: Operator.EQUALS, value: response.id as string }], - }); - expect(bundle.total).toEqual(1); - expect(bundleContains(bundle, response)).toBeTruthy(); - expect(bundleContains(bundle, questionnaire)).toBeTruthy(); - })); - - test('Include PlanDefinition mixed types', () => - withTestContext(async () => { - const canonical = 'http://example.com/fhir/R4/ActivityDefinition/' + randomUUID(); - const uri = 'http://example.com/fhir/R4/ActivityDefinition/' + randomUUID(); - const plan = await systemRepo.createResource({ - resourceType: 'PlanDefinition', - status: 'active', - action: [{ definitionCanonical: canonical }, { definitionUri: uri }], - }); - const activity1 = await systemRepo.createResource({ - resourceType: 'ActivityDefinition', - status: 'active', - url: canonical, - }); - const activity2 = await systemRepo.createResource({ - resourceType: 'ActivityDefinition', - status: 'active', - url: uri, - }); - const bundle = await systemRepo.search({ - resourceType: 'PlanDefinition', - include: [ - { - resourceType: 'PlanDefinition', - searchParam: 'definition', - }, - ], - total: 'accurate', - filters: [{ code: '_id', operator: Operator.EQUALS, value: plan.id as string }], - }); - expect(bundle.total).toEqual(1); - expect(bundleContains(bundle, plan)).toBeTruthy(); - expect(bundleContains(bundle, activity1)).toBeTruthy(); - expect(bundleContains(bundle, activity2)).toBeTruthy(); - })); - - test('Include references invalid search param', async () => { - try { - await systemRepo.search({ - resourceType: 'ServiceRequest', - include: [ - { - resourceType: 'ServiceRequest', - searchParam: 'xyz', - }, - ], - }); - } catch (err) { - const outcome = (err as OperationOutcomeError).outcome; - expect(outcome.issue?.[0]?.details?.text).toEqual('Invalid include parameter: ServiceRequest:xyz'); - } - }); + const result = await repo.search( + parseSearchRequest(`Patient?_has:Observation:subject:encounter:Encounter.class=${code}`) + ); + expect(result.entry?.[0]?.resource?.id).toEqual(patient.id); + })); + + test('Rejects too long chained search', () => + withTestContext(async () => { + await expect(() => + repo.search( + parseSearchRequest( + `Patient?_has:Observation:subject:encounter:Encounter._has:DiagnosticReport:encounter:result.specimen.parent.collected=2023` + ) + ) + ).rejects.toEqual(new Error('Search chains longer than three links are not currently supported')); + })); + + test.each([ + ['Patient?organization.invalid.name=Kaiser', 'Invalid search parameter in chain: Organization?invalid'], + ['Patient?organization.invalid=true', 'Invalid search parameter at end of chain: Organization?invalid'], + [ + 'Patient?general-practitioner.qualification-period=2023', + 'Unable to identify next resource type for search parameter: Patient?general-practitioner', + ], + ['Patient?_has:Observation:invalid:status=active', 'Invalid search parameter in chain: Observation?invalid'], + [ + 'Patient?_has:Observation:encounter:status=active', + 'Invalid reverse chain link: search parameter Observation?encounter does not refer to Patient', + ], + ['Patient?_has:Observation:status=active', 'Invalid search chain: _has:Observation:status'], + ])('Invalid chained search parameters: %s', (searchString: string, errorMsg: string) => { + return expect(repo.search(parseSearchRequest(searchString))).rejects.toEqual(new Error(errorMsg)); + }); - test('Reverse include Provenance', () => - withTestContext(async () => { - const family = randomUUID(); + test('Include references success', () => + withTestContext(async () => { + const patient = await repo.createResource({ resourceType: 'Patient' }); + const order = await repo.createResource({ + resourceType: 'ServiceRequest', + status: 'active', + intent: 'order', + subject: createReference(patient), + }); + const bundle = await repo.search({ + resourceType: 'ServiceRequest', + include: [ + { + resourceType: 'ServiceRequest', + searchParam: 'subject', + }, + ], + total: 'accurate', + filters: [{ code: '_id', operator: Operator.EQUALS, value: order.id as string }], + }); + expect(bundle.total).toEqual(1); + expect(bundleContains(bundle, order)).toBeTruthy(); + expect(bundleContains(bundle, patient)).toBeTruthy(); + })); + + test('Include canonical success', () => + withTestContext(async () => { + const canonicalURL = 'http://example.com/fhir/Questionnaire/PHQ-9/' + randomUUID(); + const questionnaire = await repo.createResource({ + resourceType: 'Questionnaire', + status: 'active', + url: canonicalURL, + }); + const response = await repo.createResource({ + resourceType: 'QuestionnaireResponse', + status: 'in-progress', + questionnaire: canonicalURL, + }); + const bundle = await repo.search({ + resourceType: 'QuestionnaireResponse', + include: [ + { + resourceType: 'QuestionnaireResponse', + searchParam: 'questionnaire', + }, + ], + total: 'accurate', + filters: [{ code: '_id', operator: Operator.EQUALS, value: response.id as string }], + }); + expect(bundle.total).toEqual(1); + expect(bundleContains(bundle, response)).toBeTruthy(); + expect(bundleContains(bundle, questionnaire)).toBeTruthy(); + })); + + test('Include PlanDefinition mixed types', () => + withTestContext(async () => { + const canonical = 'http://example.com/fhir/R4/ActivityDefinition/' + randomUUID(); + const uri = 'http://example.com/fhir/R4/ActivityDefinition/' + randomUUID(); + const plan = await repo.createResource({ + resourceType: 'PlanDefinition', + status: 'active', + action: [{ definitionCanonical: canonical }, { definitionUri: uri }], + }); + const activity1 = await repo.createResource({ + resourceType: 'ActivityDefinition', + status: 'active', + url: canonical, + }); + const activity2 = await repo.createResource({ + resourceType: 'ActivityDefinition', + status: 'active', + url: uri, + }); + const bundle = await repo.search({ + resourceType: 'PlanDefinition', + include: [ + { + resourceType: 'PlanDefinition', + searchParam: 'definition', + }, + ], + total: 'accurate', + filters: [{ code: '_id', operator: Operator.EQUALS, value: plan.id as string }], + }); + expect(bundle.total).toEqual(1); + expect(bundleContains(bundle, plan)).toBeTruthy(); + expect(bundleContains(bundle, activity1)).toBeTruthy(); + expect(bundleContains(bundle, activity2)).toBeTruthy(); + })); + + test('Include references invalid search param', async () => { + try { + await repo.search({ + resourceType: 'ServiceRequest', + include: [ + { + resourceType: 'ServiceRequest', + searchParam: 'xyz', + }, + ], + }); + } catch (err) { + const outcome = (err as OperationOutcomeError).outcome; + expect(outcome.issue?.[0]?.details?.text).toEqual('Invalid include parameter: ServiceRequest:xyz'); + } + }); - const practitioner1 = await systemRepo.createResource({ - resourceType: 'Practitioner', - name: [{ given: ['Homer'], family }], - }); + test('Reverse include Provenance', () => + withTestContext(async () => { + const family = randomUUID(); - const practitioner2 = await systemRepo.createResource({ - resourceType: 'Practitioner', - name: [{ given: ['Marge'], family }], - }); + const practitioner1 = await repo.createResource({ + resourceType: 'Practitioner', + name: [{ given: ['Homer'], family }], + }); - const searchRequest: SearchRequest = { - resourceType: 'Practitioner', - filters: [{ code: 'name', operator: Operator.EQUALS, value: family }], - revInclude: [ - { - resourceType: 'Provenance', - searchParam: 'target', - }, - ], - }; - - const searchResult1 = await systemRepo.search(searchRequest); - expect(searchResult1.entry).toHaveLength(2); - expect(bundleContains(searchResult1, practitioner1)).toBeTruthy(); - expect(bundleContains(searchResult1, practitioner2)).toBeTruthy(); - - const provenance1 = await systemRepo.createResource({ - resourceType: 'Provenance', - target: [createReference(practitioner1)], - agent: [{ who: createReference(practitioner1) }], - recorded: new Date().toISOString(), - }); + const practitioner2 = await repo.createResource({ + resourceType: 'Practitioner', + name: [{ given: ['Marge'], family }], + }); + + const searchRequest: SearchRequest = { + resourceType: 'Practitioner', + filters: [{ code: 'name', operator: Operator.EQUALS, value: family }], + revInclude: [ + { + resourceType: 'Provenance', + searchParam: 'target', + }, + ], + }; + + const searchResult1 = await repo.search(searchRequest); + expect(searchResult1.entry).toHaveLength(2); + expect(bundleContains(searchResult1, practitioner1)).toBeTruthy(); + expect(bundleContains(searchResult1, practitioner2)).toBeTruthy(); - const provenance2 = await systemRepo.createResource({ - resourceType: 'Provenance', - target: [createReference(practitioner2)], - agent: [{ who: createReference(practitioner2) }], - recorded: new Date().toISOString(), - }); + const provenance1 = await repo.createResource({ + resourceType: 'Provenance', + target: [createReference(practitioner1)], + agent: [{ who: createReference(practitioner1) }], + recorded: new Date().toISOString(), + }); - const searchResult2 = await systemRepo.search(searchRequest); - expect(searchResult2.entry).toHaveLength(4); - expect(bundleContains(searchResult2, practitioner1)).toBeTruthy(); - expect(bundleContains(searchResult2, practitioner2)).toBeTruthy(); - expect(bundleContains(searchResult2, provenance1)).toBeTruthy(); - expect(bundleContains(searchResult2, provenance2)).toBeTruthy(); - })); - - test('Reverse include canonical', () => - withTestContext(async () => { - const canonicalURL = 'http://example.com/fhir/Questionnaire/PHQ-9/' + randomUUID(); - const questionnaire = await systemRepo.createResource({ - resourceType: 'Questionnaire', - status: 'active', - url: canonicalURL, - }); - const response = await systemRepo.createResource({ - resourceType: 'QuestionnaireResponse', - status: 'in-progress', - questionnaire: canonicalURL, - }); - const bundle = await systemRepo.search({ - resourceType: 'Questionnaire', - revInclude: [ - { - resourceType: 'QuestionnaireResponse', - searchParam: 'questionnaire', - }, - ], - total: 'accurate', - filters: [{ code: '_id', operator: Operator.EQUALS, value: questionnaire.id as string }], - }); - expect(bundle.total).toEqual(1); - expect(bundleContains(bundle, response)).toBeTruthy(); - expect(bundleContains(bundle, questionnaire)).toBeTruthy(); - })); - - test('_include:iterate', () => - withTestContext(async () => { - /* + const provenance2 = await repo.createResource({ + resourceType: 'Provenance', + target: [createReference(practitioner2)], + agent: [{ who: createReference(practitioner2) }], + recorded: new Date().toISOString(), + }); + + const searchResult2 = await repo.search(searchRequest); + expect(searchResult2.entry).toHaveLength(4); + expect(bundleContains(searchResult2, practitioner1)).toBeTruthy(); + expect(bundleContains(searchResult2, practitioner2)).toBeTruthy(); + expect(bundleContains(searchResult2, provenance1)).toBeTruthy(); + expect(bundleContains(searchResult2, provenance2)).toBeTruthy(); + })); + + test('Reverse include canonical', () => + withTestContext(async () => { + const canonicalURL = 'http://example.com/fhir/Questionnaire/PHQ-9/' + randomUUID(); + const questionnaire = await repo.createResource({ + resourceType: 'Questionnaire', + status: 'active', + url: canonicalURL, + }); + const response = await repo.createResource({ + resourceType: 'QuestionnaireResponse', + status: 'in-progress', + questionnaire: canonicalURL, + }); + const bundle = await repo.search({ + resourceType: 'Questionnaire', + revInclude: [ + { + resourceType: 'QuestionnaireResponse', + searchParam: 'questionnaire', + }, + ], + total: 'accurate', + filters: [{ code: '_id', operator: Operator.EQUALS, value: questionnaire.id as string }], + }); + expect(bundle.total).toEqual(1); + expect(bundleContains(bundle, response)).toBeTruthy(); + expect(bundleContains(bundle, questionnaire)).toBeTruthy(); + })); + + test('_include:iterate', () => + withTestContext(async () => { + /* Construct resources for the search to operate on. The test search query and resource graph it will act on are shown below. Query: /Patient?identifier=patient @@ -2171,103 +1956,103 @@ describe('FHIR Search', () => { 3. _include w/o :iterate does not apply recursively (Patient:organization) 4. Resources which are included multiple times are deduplicated in the search results */ - const rootPatientIdentifier = randomUUID(); - const organization1 = await systemRepo.createResource({ - resourceType: 'Organization', - name: 'org1', - }); - const organization2 = await systemRepo.createResource({ - resourceType: 'Organization', - name: 'org2', - }); - const practitioner1 = await systemRepo.createResource({ resourceType: 'Practitioner' }); - const practitioner2 = await systemRepo.createResource({ resourceType: 'Practitioner' }); - const linked3 = await systemRepo.createResource({ - resourceType: 'Patient', - managingOrganization: { reference: `Organization/${organization2.id}` }, - generalPractitioner: [ - { - reference: `Practitioner/${practitioner2.id}`, - }, - ], - }); - const linked1 = await systemRepo.createResource({ - resourceType: 'Patient', - link: [ - { - other: { reference: `Patient/${linked3.id}` }, - type: 'replaces', - }, - ], - }); - const linked2 = await systemRepo.createResource({ - resourceType: 'Patient', - generalPractitioner: [ - { - reference: `Practitioner/${practitioner2.id}`, - }, - ], - }); - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - identifier: [ - { - value: rootPatientIdentifier, - }, - ], - link: [ - { - other: { reference: `Patient/${linked1.id}` }, - type: 'replaces', - }, - { - other: { reference: `Patient/${linked2.id}` }, - type: 'replaces', - }, - ], - managingOrganization: { - reference: `Organization/${organization1.id}`, - }, - generalPractitioner: [ - { - reference: `Practitioner/${practitioner1.id}`, + const rootPatientIdentifier = randomUUID(); + const organization1 = await repo.createResource({ + resourceType: 'Organization', + name: 'org1', + }); + const organization2 = await repo.createResource({ + resourceType: 'Organization', + name: 'org2', + }); + const practitioner1 = await repo.createResource({ resourceType: 'Practitioner' }); + const practitioner2 = await repo.createResource({ resourceType: 'Practitioner' }); + const linked3 = await repo.createResource({ + resourceType: 'Patient', + managingOrganization: { reference: `Organization/${organization2.id}` }, + generalPractitioner: [ + { + reference: `Practitioner/${practitioner2.id}`, + }, + ], + }); + const linked1 = await repo.createResource({ + resourceType: 'Patient', + link: [ + { + other: { reference: `Patient/${linked3.id}` }, + type: 'replaces', + }, + ], + }); + const linked2 = await repo.createResource({ + resourceType: 'Patient', + generalPractitioner: [ + { + reference: `Practitioner/${practitioner2.id}`, + }, + ], + }); + const patient = await repo.createResource({ + resourceType: 'Patient', + identifier: [ + { + value: rootPatientIdentifier, + }, + ], + link: [ + { + other: { reference: `Patient/${linked1.id}` }, + type: 'replaces', + }, + { + other: { reference: `Patient/${linked2.id}` }, + type: 'replaces', + }, + ], + managingOrganization: { + reference: `Organization/${organization1.id}`, }, - ], - }); + generalPractitioner: [ + { + reference: `Practitioner/${practitioner1.id}`, + }, + ], + }); - // Run the test search query - const bundle = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: 'identifier', - operator: Operator.EQUALS, - value: rootPatientIdentifier, - }, - ], - include: [ - { resourceType: 'Patient', searchParam: 'organization' }, - { resourceType: 'Patient', searchParam: 'link', modifier: Operator.ITERATE }, - { resourceType: 'Patient', searchParam: 'general-practitioner', modifier: Operator.ITERATE }, - ], - }); + // Run the test search query + const bundle = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'identifier', + operator: Operator.EQUALS, + value: rootPatientIdentifier, + }, + ], + include: [ + { resourceType: 'Patient', searchParam: 'organization' }, + { resourceType: 'Patient', searchParam: 'link', modifier: Operator.ITERATE }, + { resourceType: 'Patient', searchParam: 'general-practitioner', modifier: Operator.ITERATE }, + ], + }); - const expected = [ - `Patient/${patient.id}`, - `Patient/${linked1.id}`, - `Patient/${linked2.id}`, - `Patient/${linked3.id}`, - `Organization/${organization1.id}`, - `Practitioner/${practitioner1.id}`, - `Practitioner/${practitioner2.id}`, - ].sort(); - - expect(bundle.entry?.map((e) => `${e.resource?.resourceType}/${e.resource?.id}`).sort()).toEqual(expected); - })); - - test('_revinclude:iterate', () => - withTestContext(async () => { - /* + const expected = [ + `Patient/${patient.id}`, + `Patient/${linked1.id}`, + `Patient/${linked2.id}`, + `Patient/${linked3.id}`, + `Organization/${organization1.id}`, + `Practitioner/${practitioner1.id}`, + `Practitioner/${practitioner2.id}`, + ].sort(); + + expect(bundle.entry?.map((e) => `${e.resource?.resourceType}/${e.resource?.id}`).sort()).toEqual(expected); + })); + + test('_revinclude:iterate', () => + withTestContext(async () => { + /* Construct resources for the search to operate on. The test search query and resource graph it will act on are shown below. Query: /Patient?identifier=patient @@ -2294,1116 +2079,1347 @@ describe('FHIR Search', () => { 2. _revinclude w/ :iterate applies to resources from other _revinclude parameters (Observation:subject) 3. _revinclude w/o :iterate does not apply recursively (Patient:link) */ - const rootPatientIdentifier = randomUUID(); - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - identifier: [ - { - value: rootPatientIdentifier, - }, - ], - }); - const linked1 = await systemRepo.createResource({ - resourceType: 'Patient', - link: [ - { - other: { reference: `Patient/${patient.id}` }, - type: 'replaced-by', + const rootPatientIdentifier = randomUUID(); + const patient = await repo.createResource({ + resourceType: 'Patient', + identifier: [ + { + value: rootPatientIdentifier, + }, + ], + }); + const linked1 = await repo.createResource({ + resourceType: 'Patient', + link: [ + { + other: { reference: `Patient/${patient.id}` }, + type: 'replaced-by', + }, + ], + }); + const linked2 = await repo.createResource({ + resourceType: 'Patient', + link: [ + { + other: { reference: `Patient/${patient.id}` }, + type: 'replaced-by', + }, + ], + }); + await repo.createResource({ + resourceType: 'Patient', + link: [ + { + other: { reference: `Patient/${linked1.id}` }, + type: 'replaced-by', + }, + ], + }); + const baseObservation: Observation = { + resourceType: 'Observation', + status: 'final', + code: { + coding: [ + { + system: LOINC, + code: 'fake', + }, + ], }, - ], - }); - const linked2 = await systemRepo.createResource({ - resourceType: 'Patient', - link: [ - { - other: { reference: `Patient/${patient.id}` }, - type: 'replaced-by', + }; + const observation1 = await repo.createResource({ + ...baseObservation, + subject: { + reference: `Patient/${patient.id}`, }, - ], - }); - await systemRepo.createResource({ - resourceType: 'Patient', - link: [ - { - other: { reference: `Patient/${linked1.id}` }, - type: 'replaced-by', + }); + const observation2 = await repo.createResource({ + ...baseObservation, + subject: { + display: 'Alex J. Chalmers', }, - ], - }); - const baseObservation: Observation = { - resourceType: 'Observation', - status: 'final', - code: { - coding: [ - { - system: LOINC, - code: 'fake', - }, - ], - }, - }; - const observation1 = await systemRepo.createResource({ - ...baseObservation, - subject: { - reference: `Patient/${patient.id}`, - }, - }); - const observation2 = await systemRepo.createResource({ - ...baseObservation, - subject: { - display: 'Alex J. Chalmers', - }, - hasMember: [ - { - reference: `Observation/${observation1.id}`, + hasMember: [ + { + reference: `Observation/${observation1.id}`, + }, + ], + }); + const observation3 = await repo.createResource({ + ...baseObservation, + subject: { + reference: `Patient/${linked2.id}`, }, - ], - }); - const observation3 = await systemRepo.createResource({ - ...baseObservation, - subject: { - reference: `Patient/${linked2.id}`, - }, - }); - const observation4 = await systemRepo.createResource({ - ...baseObservation, - subject: { - display: 'Alex J. Chalmers', - }, - hasMember: [ - { - reference: `Observation/${observation2.id}`, + }); + const observation4 = await repo.createResource({ + ...baseObservation, + subject: { + display: 'Alex J. Chalmers', }, - ], - }); + hasMember: [ + { + reference: `Observation/${observation2.id}`, + }, + ], + }); - // Run the test search query - const bundle = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: 'identifier', - operator: Operator.EQUALS, - value: rootPatientIdentifier, - }, - ], - revInclude: [ - { resourceType: 'Patient', searchParam: 'link' }, - { resourceType: 'Observation', searchParam: 'subject', modifier: Operator.ITERATE }, - { resourceType: 'Observation', searchParam: 'has-member', modifier: Operator.ITERATE }, - ], - }); + // Run the test search query + const bundle = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'identifier', + operator: Operator.EQUALS, + value: rootPatientIdentifier, + }, + ], + revInclude: [ + { resourceType: 'Patient', searchParam: 'link' }, + { resourceType: 'Observation', searchParam: 'subject', modifier: Operator.ITERATE }, + { resourceType: 'Observation', searchParam: 'has-member', modifier: Operator.ITERATE }, + ], + }); - const expected = [ - `Patient/${patient.id}`, - `Patient/${linked1.id}`, - `Patient/${linked2.id}`, - `Observation/${observation1.id}`, - `Observation/${observation2.id}`, - `Observation/${observation3.id}`, - `Observation/${observation4.id}`, - ].sort(); - - expect(bundle.entry?.map((e) => `${e.resource?.resourceType}/${e.resource?.id}`).sort()).toEqual(expected); - })); - - test('_include depth limit', () => - withTestContext(async () => { - const rootPatientIdentifier = randomUUID(); - const linked6 = await systemRepo.createResource({ - resourceType: 'Patient', - }); - const linked5 = await systemRepo.createResource({ - resourceType: 'Patient', - link: [ - { - other: { reference: `Patient/${linked6.id}` }, - type: 'replaces', - }, - ], - }); - const linked4 = await systemRepo.createResource({ - resourceType: 'Patient', - link: [ - { - other: { reference: `Patient/${linked5.id}` }, - type: 'replaces', - }, - ], - }); - const linked3 = await systemRepo.createResource({ - resourceType: 'Patient', - link: [ - { - other: { reference: `Patient/${linked4.id}` }, - type: 'replaces', - }, - ], - }); - const linked2 = await systemRepo.createResource({ - resourceType: 'Patient', - link: [ - { - other: { reference: `Patient/${linked3.id}` }, - type: 'replaces', - }, - ], - }); - const linked1 = await systemRepo.createResource({ - resourceType: 'Patient', - link: [ - { - other: { reference: `Patient/${linked2.id}` }, - type: 'replaces', - }, - ], - }); - await systemRepo.createResource({ - resourceType: 'Patient', - identifier: [ - { - value: rootPatientIdentifier, + const expected = [ + `Patient/${patient.id}`, + `Patient/${linked1.id}`, + `Patient/${linked2.id}`, + `Observation/${observation1.id}`, + `Observation/${observation2.id}`, + `Observation/${observation3.id}`, + `Observation/${observation4.id}`, + ].sort(); + + expect(bundle.entry?.map((e) => `${e.resource?.resourceType}/${e.resource?.id}`).sort()).toEqual(expected); + })); + + test('_include depth limit', () => + withTestContext(async () => { + const rootPatientIdentifier = randomUUID(); + const linked6 = await repo.createResource({ + resourceType: 'Patient', + }); + const linked5 = await repo.createResource({ + resourceType: 'Patient', + link: [ + { + other: { reference: `Patient/${linked6.id}` }, + type: 'replaces', + }, + ], + }); + const linked4 = await repo.createResource({ + resourceType: 'Patient', + link: [ + { + other: { reference: `Patient/${linked5.id}` }, + type: 'replaces', + }, + ], + }); + const linked3 = await repo.createResource({ + resourceType: 'Patient', + link: [ + { + other: { reference: `Patient/${linked4.id}` }, + type: 'replaces', + }, + ], + }); + const linked2 = await repo.createResource({ + resourceType: 'Patient', + link: [ + { + other: { reference: `Patient/${linked3.id}` }, + type: 'replaces', + }, + ], + }); + const linked1 = await repo.createResource({ + resourceType: 'Patient', + link: [ + { + other: { reference: `Patient/${linked2.id}` }, + type: 'replaces', + }, + ], + }); + await repo.createResource({ + resourceType: 'Patient', + identifier: [ + { + value: rootPatientIdentifier, + }, + ], + link: [ + { + other: { reference: `Patient/${linked1.id}` }, + type: 'replaces', + }, + ], + }); + + return expect( + repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'identifier', + operator: Operator.EQUALS, + value: rootPatientIdentifier, + }, + ], + include: [{ resourceType: 'Patient', searchParam: 'link', modifier: Operator.ITERATE }], + }) + ).rejects.toBeDefined(); + })); + + test('_include on empty search results', () => + withTestContext(async () => { + return expect( + repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'identifier', + operator: Operator.EQUALS, + value: randomUUID(), + }, + ], + include: [{ resourceType: 'Patient', searchParam: 'link', modifier: Operator.ITERATE }], + }) + ).resolves.toMatchObject({ + resourceType: 'Bundle', + type: 'searchset', + entry: [], + total: undefined, + }); + })); + + test('DiagnosticReport category with system', () => + withTestContext(async () => { + const code = randomUUID(); + const dr = await repo.createResource({ + resourceType: 'DiagnosticReport', + status: 'final', + code: { coding: [{ code }] }, + category: [{ coding: [{ system: LOINC, code: 'LP217198-3' }] }], + }); + + const bundle = await repo.search({ + resourceType: 'DiagnosticReport', + filters: [ + { + code: 'code', + operator: Operator.EQUALS, + value: code, + }, + { + code: 'category', + operator: Operator.EQUALS, + value: `${LOINC}|LP217198-3`, + }, + ], + count: 1, + }); + + expect(bundleContains(bundle, dr)).toBeTruthy(); + })); + + test('Encounter.period date search', () => + withTestContext(async () => { + const e = await repo.createResource({ + resourceType: 'Encounter', + identifier: [{ value: randomUUID() }], + status: 'finished', + class: { code: 'test' }, + period: { + start: '2020-02-01', + end: '2020-02-02', }, - ], - link: [ - { - other: { reference: `Patient/${linked1.id}` }, - type: 'replaces', + }); + + const bundle = await repo.search({ + resourceType: 'Encounter', + filters: [ + { + code: 'identifier', + operator: Operator.EQUALS, + value: e.identifier?.[0]?.value as string, + }, + { + code: 'date', + operator: Operator.GREATER_THAN, + value: '2020-01-01', + }, + ], + count: 1, + }); + + expect(bundleContains(bundle, e)).toBeTruthy(); + })); + + test('Encounter.period dateTime search', () => + withTestContext(async () => { + const e = await repo.createResource({ + resourceType: 'Encounter', + identifier: [{ value: randomUUID() }], + status: 'finished', + class: { code: 'test' }, + period: { + start: '2020-02-01T13:30:00Z', + end: '2020-02-01T14:15:00Z', }, - ], - }); + }); - return expect( - systemRepo.search({ - resourceType: 'Patient', + const bundle = await repo.search({ + resourceType: 'Encounter', filters: [ { code: 'identifier', operator: Operator.EQUALS, - value: rootPatientIdentifier, + value: e.identifier?.[0]?.value as string, + }, + { + code: 'date', + operator: Operator.GREATER_THAN, + value: '2020-02-01T12:00Z', }, ], - include: [{ resourceType: 'Patient', searchParam: 'link', modifier: Operator.ITERATE }], - }) - ).rejects.toBeDefined(); - })); + count: 1, + }); - test('_include on empty search results', () => - withTestContext(async () => { - return expect( - systemRepo.search({ + expect(bundleContains(bundle, e)).toBeTruthy(); + })); + + test('Condition.code system search', () => + withTestContext(async () => { + const p = await repo.createResource({ resourceType: 'Patient', + name: [{ family: randomUUID() }], + }); + + const c1 = await repo.createResource({ + resourceType: 'Condition', + subject: createReference(p), + code: { coding: [{ system: SNOMED, code: '165002' }] }, + }); + + const c2 = await repo.createResource({ + resourceType: 'Condition', + subject: createReference(p), + code: { coding: [{ system: 'https://example.com', code: 'test' }] }, + }); + + const bundle = await repo.search({ + resourceType: 'Condition', filters: [ { - code: 'identifier', + code: 'subject', + operator: Operator.EQUALS, + value: getReferenceString(p), + }, + { + code: 'code', operator: Operator.EQUALS, - value: randomUUID(), + value: `${SNOMED}|`, }, ], - include: [{ resourceType: 'Patient', searchParam: 'link', modifier: Operator.ITERATE }], - }) - ).resolves.toMatchObject({ - resourceType: 'Bundle', - type: 'searchset', - entry: [], - total: undefined, - }); - })); - - test('DiagnosticReport category with system', () => - withTestContext(async () => { - const code = randomUUID(); - const dr = await systemRepo.createResource({ - resourceType: 'DiagnosticReport', - status: 'final', - code: { coding: [{ code }] }, - category: [{ coding: [{ system: LOINC, code: 'LP217198-3' }] }], - }); + }); - const bundle = await systemRepo.search({ - resourceType: 'DiagnosticReport', - filters: [ - { - code: 'code', - operator: Operator.EQUALS, - value: code, - }, - { - code: 'category', - operator: Operator.EQUALS, - value: `${LOINC}|LP217198-3`, - }, - ], - count: 1, - }); + expect(bundle.entry?.length).toEqual(1); + expect(bundleContains(bundle, c1)).toBeTruthy(); + expect(bundleContains(bundle, c2)).not.toBeTruthy(); + })); - expect(bundleContains(bundle, dr)).toBeTruthy(); - })); - - test('Encounter.period date search', () => - withTestContext(async () => { - const e = await systemRepo.createResource({ - resourceType: 'Encounter', - identifier: [{ value: randomUUID() }], - status: 'finished', - class: { code: 'test' }, - period: { - start: '2020-02-01', - end: '2020-02-02', - }, - }); + test('Condition.code :not next URL', () => + withTestContext(async () => { + const p = await repo.createResource({ + resourceType: 'Patient', + name: [{ family: randomUUID() }], + }); - const bundle = await systemRepo.search({ - resourceType: 'Encounter', - filters: [ - { - code: 'identifier', - operator: Operator.EQUALS, - value: e.identifier?.[0]?.value as string, - }, - { - code: 'date', - operator: Operator.GREATER_THAN, - value: '2020-01-01', - }, - ], - count: 1, - }); + await repo.createResource({ + resourceType: 'Condition', + subject: createReference(p), + code: { coding: [{ system: SNOMED, code: '165002' }] }, + }); - expect(bundleContains(bundle, e)).toBeTruthy(); - })); - - test('Encounter.period dateTime search', () => - withTestContext(async () => { - const e = await systemRepo.createResource({ - resourceType: 'Encounter', - identifier: [{ value: randomUUID() }], - status: 'finished', - class: { code: 'test' }, - period: { - start: '2020-02-01T13:30:00Z', - end: '2020-02-01T14:15:00Z', - }, - }); + await repo.createResource({ + resourceType: 'Condition', + subject: createReference(p), + code: { coding: [{ system: 'https://example.com', code: 'test' }] }, + }); - const bundle = await systemRepo.search({ - resourceType: 'Encounter', - filters: [ - { - code: 'identifier', - operator: Operator.EQUALS, - value: e.identifier?.[0]?.value as string, - }, - { - code: 'date', - operator: Operator.GREATER_THAN, - value: '2020-02-01T12:00Z', - }, - ], - count: 1, - }); + const bundle = await repo.search( + parseSearchRequest(`https://x/Condition?subject=${getReferenceString(p)}&code:not=x&_count=1&_total=accurate`) + ); + expect(bundle.entry?.length).toEqual(1); - expect(bundleContains(bundle, e)).toBeTruthy(); - })); + const nextUrl = bundle.link?.find((l) => l.relation === 'next')?.url; + expect(nextUrl).toBeDefined(); + expect(nextUrl).toContain('code:not=x'); + })); - test('Condition.code system search', () => - withTestContext(async () => { - const p = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ family: randomUUID() }], - }); + test('Condition.code :in search', () => + withTestContext(async () => { + // ValueSet: http://hl7.org/fhir/ValueSet/condition-code + // compose includes codes from http://snomed.info/sct + // but does not include codes from https://example.com - const c1 = await systemRepo.createResource({ - resourceType: 'Condition', - subject: createReference(p), - code: { coding: [{ system: SNOMED, code: '165002' }] }, - }); + const p = await repo.createResource({ + resourceType: 'Patient', + name: [{ family: randomUUID() }], + }); - const c2 = await systemRepo.createResource({ - resourceType: 'Condition', - subject: createReference(p), - code: { coding: [{ system: 'https://example.com', code: 'test' }] }, - }); + const c1 = await repo.createResource({ + resourceType: 'Condition', + subject: createReference(p), + code: { coding: [{ system: SNOMED, code: '165002' }] }, + }); - const bundle = await systemRepo.search({ - resourceType: 'Condition', - filters: [ - { - code: 'subject', - operator: Operator.EQUALS, - value: getReferenceString(p), + const c2 = await repo.createResource({ + resourceType: 'Condition', + subject: createReference(p), + code: { coding: [{ system: 'https://example.com', code: 'test' }] }, + }); + + const bundle = await repo.search({ + resourceType: 'Condition', + filters: [ + { + code: 'subject', + operator: Operator.EQUALS, + value: getReferenceString(p), + }, + { + code: 'code', + operator: Operator.IN, + value: 'http://hl7.org/fhir/ValueSet/condition-code', + }, + ], + }); + + expect(bundle.entry?.length).toEqual(1); + expect(bundleContains(bundle, c1)).toBeTruthy(); + expect(bundleContains(bundle, c2)).not.toBeTruthy(); + })); + + test('Reference identifier search', () => + withTestContext(async () => { + const code = randomUUID(); + + const c1 = await repo.createResource({ + resourceType: 'Condition', + code: { coding: [{ code }] }, + subject: { identifier: { system: 'mrn', value: '123456' } }, + }); + + const c2 = await repo.createResource({ + resourceType: 'Condition', + code: { coding: [{ code }] }, + subject: { identifier: { system: 'xyz', value: '123456' } }, + }); + + // Search with system + const bundle1 = await repo.search(parseSearchRequest(`Condition?code=${code}&subject:identifier=mrn|123456`)); + expect(bundle1.entry?.length).toEqual(1); + expect(bundleContains(bundle1, c1)).toBeTruthy(); + expect(bundleContains(bundle1, c2)).not.toBeTruthy(); + + // Search without system + const bundle2 = await repo.search(parseSearchRequest(`Condition?code=${code}&subject:identifier=123456`)); + expect(bundle2.entry?.length).toEqual(2); + expect(bundleContains(bundle2, c1)).toBeTruthy(); + expect(bundleContains(bundle2, c2)).toBeTruthy(); + + // Search with count + const bundle3 = await repo.search( + parseSearchRequest(`Condition?code=${code}&subject:identifier=mrn|123456&_total=accurate`) + ); + expect(bundle3.entry?.length).toEqual(1); + expect(bundle3.total).toBe(1); + expect(bundleContains(bundle3, c1)).toBeTruthy(); + expect(bundleContains(bundle3, c2)).not.toBeTruthy(); + })); + + test('Task patient identifier search', () => + withTestContext(async () => { + const identifier = randomUUID(); + + // Create a Task with a patient identifier reference _with_ Reference.type + const task1 = await repo.createResource({ + resourceType: 'Task', + status: 'accepted', + intent: 'order', + for: { + type: 'Patient', + identifier: { system: 'mrn', value: identifier }, }, - { - code: 'code', - operator: Operator.EQUALS, - value: `${SNOMED}|`, + }); + + // Create a Task with a patient identifier reference _without_ Reference.type + const task2 = await repo.createResource({ + resourceType: 'Task', + status: 'accepted', + intent: 'order', + for: { + identifier: { system: 'mrn', value: identifier }, }, - ], - }); + }); - expect(bundle.entry?.length).toEqual(1); - expect(bundleContains(bundle, c1)).toBeTruthy(); - expect(bundleContains(bundle, c2)).not.toBeTruthy(); - })); + // Search by "subject" + // This will include both Tasks, because the "subject" search parameter does not care about "type" + const bundle1 = await repo.search( + parseSearchRequest(`Task?subject:identifier=mrn|${identifier}&_total=accurate`) + ); + expect(bundle1.total).toEqual(2); + expect(bundle1.entry?.length).toEqual(2); + expect(bundleContains(bundle1, task1)).toBeTruthy(); + expect(bundleContains(bundle1, task2)).toBeTruthy(); + + // Search by "patient" + // This will only include the Task with the explicit "Patient" type, because the "patient" search parameter does care about "type" + const bundle2 = await repo.search( + parseSearchRequest(`Task?patient:identifier=mrn|${identifier}&_total=accurate`) + ); + expect(bundle2.total).toEqual(1); + expect(bundle2.entry?.length).toEqual(1); + expect(bundleContains(bundle2, task1)).toBeTruthy(); + expect(bundleContains(bundle2, task2)).not.toBeTruthy(); + })); + + test('Resource search params', () => + withTestContext(async () => { + const patientIdentifier = randomUUID(); + const patient = await repo.createResource({ + resourceType: 'Patient', + identifier: [{ system: 'http://example.com', value: patientIdentifier }], + meta: { + profile: ['http://example.com/fhir/a-patient-profile'], + security: [{ system: 'http://hl7.org/fhir/v3/Confidentiality', code: 'N' }], + source: 'http://example.org', + tag: [{ system: 'http://hl7.org/fhir/v3/ObservationValue', code: 'SUBSETTED' }], + }, + }); + const identifierFilter = { + code: 'identifier', + operator: Operator.EQUALS, + value: patientIdentifier, + }; - test('Condition.code :not next URL', () => - withTestContext(async () => { - const p = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ family: randomUUID() }], - }); + const bundle1 = await repo.search({ + resourceType: 'Patient', + filters: [ + identifierFilter, + { + code: '_profile', + operator: Operator.EQUALS, + value: 'http://example.com/fhir/a-patient-profile', + }, + ], + }); + expect(bundleContains(bundle1, patient)).toBeTruthy(); - await systemRepo.createResource({ - resourceType: 'Condition', - subject: createReference(p), - code: { coding: [{ system: SNOMED, code: '165002' }] }, - }); + const bundle2 = await repo.search({ + resourceType: 'Patient', + filters: [ + identifierFilter, + { + code: '_security', + operator: Operator.EQUALS, + value: 'http://hl7.org/fhir/v3/Confidentiality|N', + }, + ], + }); + expect(bundleContains(bundle2, patient)).toBeTruthy(); - await systemRepo.createResource({ - resourceType: 'Condition', - subject: createReference(p), - code: { coding: [{ system: 'https://example.com', code: 'test' }] }, - }); + const bundle3 = await repo.search({ + resourceType: 'Patient', + filters: [ + identifierFilter, + { + code: '_source', + operator: Operator.EQUALS, + value: 'http://example.org', + }, + ], + }); + expect(bundleContains(bundle3, patient)).toBeTruthy(); - const bundle = await systemRepo.search( - parseSearchUrl( - new URL(`https://x/Condition?subject=${getReferenceString(p)}&code:not=x&_count=1&_total=accurate`) - ) - ); - expect(bundle.entry?.length).toEqual(1); - - const nextUrl = bundle.link?.find((l) => l.relation === 'next')?.url; - expect(nextUrl).toBeDefined(); - expect(nextUrl).toContain('code:not=x'); - })); - - test('Condition.code :in search', () => - withTestContext(async () => { - // ValueSet: http://hl7.org/fhir/ValueSet/condition-code - // compose includes codes from http://snomed.info/sct - // but does not include codes from https://example.com - - const p = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ family: randomUUID() }], - }); + const bundle4 = await repo.search({ + resourceType: 'Patient', + filters: [ + identifierFilter, + { + code: '_tag', + operator: Operator.EQUALS, + value: 'http://hl7.org/fhir/v3/ObservationValue|SUBSETTED', + }, + ], + }); + expect(bundleContains(bundle4, patient)).toBeTruthy(); + })); + + test('Token :text search', () => + withTestContext(async () => { + const patient = await repo.createResource({ resourceType: 'Patient' }); + + const obs1 = await repo.createResource({ + resourceType: 'Observation', + status: 'final', + code: { text: randomUUID() }, + subject: createReference(patient), + }); - const c1 = await systemRepo.createResource({ - resourceType: 'Condition', - subject: createReference(p), - code: { coding: [{ system: SNOMED, code: '165002' }] }, - }); + const obs2 = await repo.createResource({ + resourceType: 'Observation', + status: 'final', + code: { coding: [{ display: randomUUID() }] }, + subject: createReference(patient), + }); - const c2 = await systemRepo.createResource({ - resourceType: 'Condition', - subject: createReference(p), - code: { coding: [{ system: 'https://example.com', code: 'test' }] }, - }); + const result1 = await repo.search({ + resourceType: 'Observation', + filters: [{ code: 'code', operator: Operator.TEXT, value: obs1.code?.text as string }], + }); + expect(result1.entry?.[0]?.resource?.id).toEqual(obs1.id); - const bundle = await systemRepo.search({ - resourceType: 'Condition', - filters: [ - { - code: 'subject', - operator: Operator.EQUALS, - value: getReferenceString(p), - }, - { - code: 'code', - operator: Operator.IN, - value: 'http://hl7.org/fhir/ValueSet/condition-code', - }, - ], - }); + const result2 = await repo.search({ + resourceType: 'Observation', + filters: [{ code: 'code', operator: Operator.TEXT, value: obs2.code?.coding?.[0]?.display as string }], + }); + expect(result2.entry?.[0]?.resource?.id).toEqual(obs2.id); + })); + + test('_filter search', () => + withTestContext(async () => { + const patient = await repo.createResource({ resourceType: 'Patient' }); + const statuses: ('preliminary' | 'final')[] = ['preliminary', 'final']; + const codes = ['123', '456']; + const observations = []; + + for (const status of statuses) { + for (const code of codes) { + observations.push( + await repo.createResource({ + resourceType: 'Observation', + subject: createReference(patient), + status, + code: { coding: [{ code }] }, + }) + ); + } + } - expect(bundle.entry?.length).toEqual(1); - expect(bundleContains(bundle, c1)).toBeTruthy(); - expect(bundleContains(bundle, c2)).not.toBeTruthy(); - })); + const result = await repo.search({ + resourceType: 'Observation', + filters: [ + { + code: 'subject', + operator: Operator.EQUALS, + value: getReferenceString(patient), + }, + { + code: '_filter', + operator: Operator.EQUALS, + value: '(status eq preliminary and code eq 123) or (not (status eq preliminary) and code eq 456)', + }, + ], + }); + expect(result.entry).toHaveLength(2); + })); - test('Reference identifier search', () => - withTestContext(async () => { - const code = randomUUID(); + test('_filter ne', () => + withTestContext(async () => { + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Eve'] }], + managingOrganization: { reference: 'Organization/' + randomUUID() }, + }); - const c1 = await systemRepo.createResource({ - resourceType: 'Condition', - code: { coding: [{ code }] }, - subject: { identifier: { system: 'mrn', value: '123456' } }, - }); + const result = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'organization', + operator: Operator.EQUALS, + value: patient.managingOrganization?.reference as string, + }, + { + code: '_filter', + operator: Operator.EQUALS, + value: 'given ne Eve', + }, + ], + }); - const c2 = await systemRepo.createResource({ - resourceType: 'Condition', - code: { coding: [{ code }] }, - subject: { identifier: { system: 'xyz', value: '123456' } }, - }); + expect(result.entry).toHaveLength(0); + })); - // Search with system - const bundle1 = await systemRepo.search( - parseSearchDefinition(`Condition?code=${code}&subject:identifier=mrn|123456`) - ); - expect(bundle1.entry?.length).toEqual(1); - expect(bundleContains(bundle1, c1)).toBeTruthy(); - expect(bundleContains(bundle1, c2)).not.toBeTruthy(); - - // Search without system - const bundle2 = await systemRepo.search( - parseSearchDefinition(`Condition?code=${code}&subject:identifier=123456`) - ); - expect(bundle2.entry?.length).toEqual(2); - expect(bundleContains(bundle2, c1)).toBeTruthy(); - expect(bundleContains(bundle2, c2)).toBeTruthy(); - - // Search with count - const bundle3 = await systemRepo.search( - parseSearchDefinition(`Condition?code=${code}&subject:identifier=mrn|123456&_total=accurate`) - ); - expect(bundle3.entry?.length).toEqual(1); - expect(bundle3.total).toBe(1); - expect(bundleContains(bundle3, c1)).toBeTruthy(); - expect(bundleContains(bundle3, c2)).not.toBeTruthy(); - })); - - test('Task patient identifier search', () => - withTestContext(async () => { - const identifier = randomUUID(); - - // Create a Task with a patient identifier reference _with_ Reference.type - const task1 = await systemRepo.createResource({ - resourceType: 'Task', - status: 'accepted', - intent: 'order', - for: { - type: 'Patient', - identifier: { system: 'mrn', value: identifier }, - }, - }); + test('_filter re', () => + withTestContext(async () => { + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Eve'] }], + managingOrganization: { reference: 'Organization/' + randomUUID() }, + }); - // Create a Task with a patient identifier reference _without_ Reference.type - const task2 = await systemRepo.createResource({ - resourceType: 'Task', - status: 'accepted', - intent: 'order', - for: { - identifier: { system: 'mrn', value: identifier }, - }, - }); + const result = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: '_filter', + operator: Operator.EQUALS, + value: 'organization re ' + patient.managingOrganization?.reference, + }, + ], + }); - // Search by "subject" - // This will include both Tasks, because the "subject" search parameter does not care about "type" - const bundle1 = await systemRepo.search( - parseSearchDefinition(`Task?subject:identifier=mrn|${identifier}&_total=accurate`) - ); - expect(bundle1.total).toEqual(2); - expect(bundle1.entry?.length).toEqual(2); - expect(bundleContains(bundle1, task1)).toBeTruthy(); - expect(bundleContains(bundle1, task2)).toBeTruthy(); - - // Search by "patient" - // This will only include the Task with the explicit "Patient" type, because the "patient" search parameter does care about "type" - const bundle2 = await systemRepo.search( - parseSearchDefinition(`Task?patient:identifier=mrn|${identifier}&_total=accurate`) - ); - expect(bundle2.total).toEqual(1); - expect(bundle2.entry?.length).toEqual(1); - expect(bundleContains(bundle2, task1)).toBeTruthy(); - expect(bundleContains(bundle2, task2)).not.toBeTruthy(); - })); - - test('Resource search params', () => - withTestContext(async () => { - const patientIdentifier = randomUUID(); - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - identifier: [{ system: 'http://example.com', value: patientIdentifier }], - meta: { - profile: ['http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient'], - security: [{ system: 'http://hl7.org/fhir/v3/Confidentiality', code: 'N' }], - source: 'http://example.org', - tag: [{ system: 'http://hl7.org/fhir/v3/ObservationValue', code: 'SUBSETTED' }], - }, - }); - const identifierFilter = { - code: 'identifier', - operator: Operator.EQUALS, - value: patientIdentifier, - }; + expect(result.entry).toHaveLength(1); + expect(result.entry?.[0]?.resource?.id).toEqual(patient.id); + })); - const bundle1 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - identifierFilter, - { - code: '_profile', - operator: Operator.EQUALS, - value: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient', - }, - ], - }); - expect(bundleContains(bundle1, patient)).toBeTruthy(); + test('Lookup table exact match with comma disjunction', () => + withTestContext(async () => { + const family = randomUUID(); + const p1 = await repo.createResource({ resourceType: 'Patient', name: [{ given: ['x'], family }] }); + const p2 = await repo.createResource({ resourceType: 'Patient', name: [{ given: ['xx'], family }] }); + const p3 = await repo.createResource({ resourceType: 'Patient', name: [{ given: ['y'], family }] }); + const p4 = await repo.createResource({ resourceType: 'Patient', name: [{ given: ['yy'], family }] }); - const bundle2 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - identifierFilter, - { - code: '_security', - operator: Operator.EQUALS, - value: 'http://hl7.org/fhir/v3/Confidentiality|N', - }, - ], - }); - expect(bundleContains(bundle2, patient)).toBeTruthy(); + const bundle = await repo.search({ + resourceType: 'Patient', + total: 'accurate', + filters: [ + { + code: 'given', + operator: Operator.EXACT, + value: 'x,y', + }, + { + code: 'family', + operator: Operator.EXACT, + value: family, + }, + ], + }); + expect(bundle.entry?.length).toEqual(2); + expect(bundle.total).toEqual(2); + expect(bundleContains(bundle, p1)).toBeTruthy(); + expect(bundleContains(bundle, p2)).not.toBeTruthy(); + expect(bundleContains(bundle, p3)).toBeTruthy(); + expect(bundleContains(bundle, p4)).not.toBeTruthy(); + })); + + test('Duplicate rows from token lookup', () => + withTestContext(async () => { + const code = randomUUID(); + + const p = await repo.createResource({ resourceType: 'Patient' }); + const s = await repo.createResource({ + resourceType: 'ServiceRequest', + subject: createReference(p), + status: 'active', + intent: 'order', + category: [ + { + text: code, + coding: [ + { + system: 'https://example.com/category', + code, + }, + ], + }, + ], + }); - const bundle3 = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - identifierFilter, - { - code: '_source', - operator: Operator.EQUALS, - value: 'http://example.org', - }, - ], - }); - expect(bundleContains(bundle3, patient)).toBeTruthy(); + const bundle = await repo.search({ + resourceType: 'ServiceRequest', + filters: [{ code: 'category', operator: Operator.EQUALS, value: code }], + }); + expect(bundle.entry?.length).toEqual(1); + expect(bundleContains(bundle, s)).toBeTruthy(); + })); + + test('Filter task by due date', () => + withTestContext(async () => { + const code = randomUUID(); + + // Create 3 tasks + // Mix of "no due date", using "start", and using "end" + const task1 = await repo.createResource({ + resourceType: 'Task', + status: 'requested', + intent: 'order', + code: { coding: [{ code }] }, + }); + const task2 = await repo.createResource({ + resourceType: 'Task', + status: 'requested', + intent: 'order', + code: { coding: [{ code }] }, + restriction: { period: { start: '2023-06-02T00:00:00.000Z' } }, + }); + const task3 = await repo.createResource({ + resourceType: 'Task', + status: 'requested', + intent: 'order', + code: { coding: [{ code }] }, + restriction: { period: { end: '2023-06-03T00:00:00.000Z' } }, + }); + + // Sort and filter by due date + const bundle = await repo.search({ + resourceType: 'Task', + filters: [ + { code: 'code', operator: Operator.EQUALS, value: code }, + { code: 'due-date', operator: Operator.GREATER_THAN, value: '2023-06-01T00:00:00.000Z' }, + ], + sortRules: [{ code: 'due-date' }], + }); + expect(bundle.entry?.length).toEqual(2); + expect(bundle.entry?.[0]?.resource?.id).toEqual(task2.id); + expect(bundle.entry?.[1]?.resource?.id).toEqual(task3.id); + expect(bundleContains(bundle, task1)).not.toBeTruthy(); + })); - const bundle4 = await systemRepo.search({ + test('Get estimated count with filter on human name', async () => { + const result = await repo.search({ resourceType: 'Patient', + total: 'estimate', filters: [ - identifierFilter, { - code: '_tag', + code: 'name', operator: Operator.EQUALS, - value: 'http://hl7.org/fhir/v3/ObservationValue|SUBSETTED', + value: 'John', }, ], }); - expect(bundleContains(bundle4, patient)).toBeTruthy(); - })); - - test('Token :text search', () => - withTestContext(async () => { - const patient = await systemRepo.createResource({ resourceType: 'Patient' }); - - const obs1 = await systemRepo.createResource({ - resourceType: 'Observation', - status: 'final', - code: { text: randomUUID() }, - subject: createReference(patient), - }); + expect(result.total).toBeDefined(); + expect(typeof result.total).toBe('number'); + }); - const obs2 = await systemRepo.createResource({ - resourceType: 'Observation', - status: 'final', - code: { coding: [{ display: randomUUID() }] }, - subject: createReference(patient), - }); + test('Organization by name', () => + withTestContext(async () => { + const org = await repo.createResource({ + resourceType: 'Organization', + name: randomUUID(), + }); + const result = await repo.search({ + resourceType: 'Organization', + filters: [ + { + code: 'name', + operator: Operator.EQUALS, + value: `wrongname,${(org.name as string).slice(0, 5)}`, + }, + ], + }); + expect(result.entry?.length).toBe(1); + })); - const result1 = await systemRepo.search({ - resourceType: 'Observation', - filters: [{ code: 'code', operator: Operator.TEXT, value: obs1.code?.text as string }], - }); - expect(result1.entry?.[0]?.resource?.id).toEqual(obs1.id); + test('Patient by name with stop word', () => + withTestContext(async () => { + const seed = randomUUID(); + await repo.createResource({ + resourceType: 'Patient', + name: [ + { + given: [seed + 'Justin', 'Wynn'], + family: 'Sanders' + seed, + }, + ], + }); + const result = await repo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'name', + operator: Operator.CONTAINS, + value: `${seed.slice(-3)}just`, + }, + ], + }); + expect(result.entry?.length).toBe(1); + })); - const result2 = await systemRepo.search({ - resourceType: 'Observation', - filters: [{ code: 'code', operator: Operator.TEXT, value: obs2.code?.coding?.[0]?.display as string }], - }); - expect(result2.entry?.[0]?.resource?.id).toEqual(obs2.id); - })); + test('Sort by ID', () => + withTestContext(async () => { + const org = await repo.createResource({ resourceType: 'Organization', name: 'org1' }); + const managingOrganization = createReference(org); + await repo.createResource({ resourceType: 'Patient', managingOrganization }); + await repo.createResource({ resourceType: 'Patient', managingOrganization }); - test('_filter search', () => - withTestContext(async () => { - const patient = await systemRepo.createResource({ resourceType: 'Patient' }); - const statuses: ('preliminary' | 'final')[] = ['preliminary', 'final']; - const codes = ['123', '456']; - const observations = []; + const result1 = await repo.search({ + resourceType: 'Patient', + filters: [{ code: 'organization', operator: Operator.EQUALS, value: getReferenceString(org) }], + sortRules: [{ code: '_id', descending: false }], + }); + expect(result1.entry).toHaveLength(2); + expect(result1.entry?.[0]?.resource?.id?.localeCompare(result1.entry?.[1]?.resource?.id as string)).toBe(-1); - for (const status of statuses) { - for (const code of codes) { - observations.push( - await systemRepo.createResource({ - resourceType: 'Observation', - subject: createReference(patient), - status, - code: { coding: [{ code }] }, - }) - ); - } - } + const result2 = await repo.search({ + resourceType: 'Patient', + filters: [{ code: 'organization', operator: Operator.EQUALS, value: getReferenceString(org) }], + sortRules: [{ code: '_id', descending: true }], + }); + expect(result2.entry).toHaveLength(2); + expect(result2.entry?.[0]?.resource?.id?.localeCompare(result2.entry?.[1]?.resource?.id as string)).toBe(1); + })); + + test('Numeric parameter', () => + withTestContext(async () => { + const ident = randomUUID(); + const riskAssessment: RiskAssessment = { + resourceType: 'RiskAssessment', + status: 'final', + identifier: [{ value: ident }], + subject: { + reference: 'Patient/test', + }, + prediction: [ + { + outcome: { text: 'Breast Cancer' }, + probabilityDecimal: 0.000168, + whenRange: { + high: { value: 53, unit: 'years' }, + }, + }, + { + outcome: { text: 'Breast Cancer' }, + probabilityDecimal: 0.000368, + whenRange: { + low: { value: 54, unit: 'years' }, + high: { value: 57, unit: 'years' }, + }, + }, + { + outcome: { text: 'Breast Cancer' }, + probabilityDecimal: 0.000594, + whenRange: { + low: { value: 58, unit: 'years' }, + high: { value: 62, unit: 'years' }, + }, + }, + { + outcome: { text: 'Breast Cancer' }, + probabilityDecimal: 0.000838, + whenRange: { + low: { value: 63, unit: 'years' }, + high: { value: 67, unit: 'years' }, + }, + }, + ], + }; - const result = await systemRepo.search({ - resourceType: 'Observation', - filters: [ - { - code: 'subject', - operator: Operator.EQUALS, - value: getReferenceString(patient), - }, - { - code: '_filter', - operator: Operator.EQUALS, - value: '(status eq preliminary and code eq 123) or (not (status eq preliminary) and code eq 456)', - }, - ], - }); - expect(result.entry).toHaveLength(2); - })); + await repo.createResource(riskAssessment); + const result = await repo.search({ + resourceType: 'RiskAssessment', + filters: [ + { code: 'identifier', operator: Operator.EQUALS, value: ident }, + { code: 'probability', operator: Operator.GREATER_THAN, value: '0.0005' }, + ], + }); + expect(result.entry).toHaveLength(1); + })); - test('_filter ne', () => - withTestContext(async () => { - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Eve'] }], - managingOrganization: { reference: 'Organization/' + randomUUID() }, - }); + test('Disjunction with lookup tables', () => + withTestContext(async () => { + const n1 = randomUUID(); + const n2 = randomUUID(); - const result = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: 'organization', - operator: Operator.EQUALS, - value: patient.managingOrganization?.reference as string, - }, - { - code: '_filter', - operator: Operator.EQUALS, - value: 'given ne Eve', - }, - ], - }); + const p1 = await repo.createResource({ + resourceType: 'Patient', + name: [{ family: n1 }], + }); - expect(result.entry).toHaveLength(0); - })); + const p2 = await repo.createResource({ + resourceType: 'Patient', + name: [{ family: n2 }], + }); - test('_filter re', () => - withTestContext(async () => { - const patient = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Eve'] }], - managingOrganization: { reference: 'Organization/' + randomUUID() }, - }); + const result = await repo.search({ + resourceType: 'Patient', + filters: [{ code: '_filter', operator: Operator.EQUALS, value: `name co "${n1}" or name co "${n2}"` }], + }); - const result = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: '_filter', - operator: Operator.EQUALS, - value: 'organization re ' + patient.managingOrganization?.reference, - }, - ], - }); + expect(result.entry).toHaveLength(2); + expect(bundleContains(result, p1)).toBe(true); + expect(bundleContains(result, p2)).toBe(true); + })); - expect(result.entry).toHaveLength(1); - expect(result.entry?.[0]?.resource?.id).toEqual(patient.id); - })); + test('Sort by unknown search parameter', async () => { + try { + await repo.search({ + resourceType: 'Patient', + sortRules: [{ code: 'xyz' }], + }); + } catch (err) { + const outcome = normalizeOperationOutcome(err); + expect(outcome.issue?.[0]?.details?.text).toBe('Unknown search parameter: xyz'); + } + }); - test('Lookup table exact match with comma disjunction', () => - withTestContext(async () => { - const family = randomUUID(); - const p1 = await systemRepo.createResource({ resourceType: 'Patient', name: [{ given: ['x'], family }] }); - const p2 = await systemRepo.createResource({ resourceType: 'Patient', name: [{ given: ['xx'], family }] }); - const p3 = await systemRepo.createResource({ resourceType: 'Patient', name: [{ given: ['y'], family }] }); - const p4 = await systemRepo.createResource({ resourceType: 'Patient', name: [{ given: ['yy'], family }] }); + test('Date range search', () => + withTestContext(async () => { + const ident = randomUUID(); - const bundle = await systemRepo.search({ - resourceType: 'Patient', - total: 'accurate', - filters: [ - { - code: 'given', - operator: Operator.EXACT, - value: 'x,y', - }, - { - code: 'family', - operator: Operator.EXACT, - value: family, + const measureReport = await repo.createResource({ + resourceType: 'MeasureReport', + status: 'complete', + type: 'individual', + measure: 'http://example.com', + identifier: [{ value: ident }], + period: { + start: '2020-01-01T12:00:00.000Z', + end: '2020-01-15T12:00:00.000Z', }, - ], - }); - expect(bundle.entry?.length).toEqual(2); - expect(bundle.total).toEqual(2); - expect(bundleContains(bundle, p1)).toBeTruthy(); - expect(bundleContains(bundle, p2)).not.toBeTruthy(); - expect(bundleContains(bundle, p3)).toBeTruthy(); - expect(bundleContains(bundle, p4)).not.toBeTruthy(); - })); - - test('Duplicate rows from token lookup', () => - withTestContext(async () => { - const code = randomUUID(); - - const p = await systemRepo.createResource({ resourceType: 'Patient' }); - const s = await systemRepo.createResource({ - resourceType: 'ServiceRequest', - subject: createReference(p), - status: 'active', - intent: 'order', - category: [ - { - text: code, - coding: [ - { - system: 'https://example.com/category', - code, - }, + }); + expect(measureReport).toBeDefined(); + + async function searchByPeriod(operator: Operator, value: string): Promise { + const result = await repo.searchOne({ + resourceType: 'MeasureReport', + filters: [ + { code: 'identifier', operator: Operator.EQUALS, value: ident }, + { code: 'period', operator, value }, ], - }, - ], - }); + }); + return !!result; + } - const bundle = await systemRepo.search({ - resourceType: 'ServiceRequest', - filters: [{ code: 'category', operator: Operator.EQUALS, value: code }], - }); - expect(bundle.entry?.length).toEqual(1); - expect(bundleContains(bundle, s)).toBeTruthy(); - })); - - test('Filter task by due date', () => - withTestContext(async () => { - const code = randomUUID(); - - // Create 3 tasks - // Mix of "no due date", using "start", and using "end" - const task1 = await systemRepo.createResource({ - resourceType: 'Task', - status: 'requested', - intent: 'order', - code: { coding: [{ code }] }, - }); - const task2 = await systemRepo.createResource({ - resourceType: 'Task', - status: 'requested', - intent: 'order', - code: { coding: [{ code }] }, - restriction: { period: { start: '2023-06-02T00:00:00.000Z' } }, - }); - const task3 = await systemRepo.createResource({ - resourceType: 'Task', - status: 'requested', - intent: 'order', - code: { coding: [{ code }] }, - restriction: { period: { end: '2023-06-03T00:00:00.000Z' } }, - }); + expect(await searchByPeriod(Operator.EQUALS, '2019-12-31')).toBe(false); + expect(await searchByPeriod(Operator.EQUALS, '2020-01-01')).toBe(true); + expect(await searchByPeriod(Operator.EQUALS, '2020-01-02')).toBe(true); + expect(await searchByPeriod(Operator.EQUALS, '2020-01-15')).toBe(true); + expect(await searchByPeriod(Operator.EQUALS, '2020-01-16')).toBe(false); + + expect(await searchByPeriod(Operator.EQUALS, '2020-01-01T11:59:59.000Z')).toBe(false); + expect(await searchByPeriod(Operator.EQUALS, '2020-01-01T12:00:00.000Z')).toBe(true); + expect(await searchByPeriod(Operator.EQUALS, '2020-01-15T11:59:59.000Z')).toBe(true); + expect(await searchByPeriod(Operator.EQUALS, '2020-01-15T12:00:00.000Z')).toBe(true); + expect(await searchByPeriod(Operator.EQUALS, '2020-01-15T12:00:01.000Z')).toBe(false); + + expect(await searchByPeriod(Operator.NOT_EQUALS, '2019-12-31')).toBe(true); + expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-01')).toBe(false); + expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-02')).toBe(false); + expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-15')).toBe(false); + expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-16')).toBe(true); + + expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-01T11:59:59.000Z')).toBe(true); + expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-01T12:00:00.000Z')).toBe(false); + expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-15T11:59:59.000Z')).toBe(false); + expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-15T12:00:00.000Z')).toBe(false); + expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-15T12:00:01.000Z')).toBe(true); + + expect(await searchByPeriod(Operator.LESS_THAN, '2019-12-31')).toBe(false); + expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-01')).toBe(true); + expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-02')).toBe(true); + expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-15')).toBe(true); + expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-16')).toBe(true); + + expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-01T11:59:59.000Z')).toBe(false); + expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-01T12:00:00.000Z')).toBe(false); + expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-15T11:59:59.000Z')).toBe(true); + expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-15T12:00:00.000Z')).toBe(true); + expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-15T12:00:01.000Z')).toBe(true); + + expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2019-12-31')).toBe(false); + expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-01')).toBe(true); + expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-02')).toBe(true); + expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-15')).toBe(true); + expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-16')).toBe(true); + + expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-01T11:59:59.000Z')).toBe(false); + expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-01T12:00:00.000Z')).toBe(true); + expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-15T11:59:59.000Z')).toBe(true); + expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-15T12:00:00.000Z')).toBe(true); + expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-15T12:00:01.000Z')).toBe(true); + + expect(await searchByPeriod(Operator.GREATER_THAN, '2019-12-31')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-01')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-02')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-15')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-16')).toBe(false); + + expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-01T11:59:59.000Z')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-01T12:00:00.000Z')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-15T11:59:59.000Z')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-15T12:00:00.000Z')).toBe(false); + expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-15T12:00:01.000Z')).toBe(false); + + expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2019-12-31')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-01')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-02')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-15')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-16')).toBe(false); + + expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-01T11:59:59.000Z')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-01T12:00:00.000Z')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-15T11:59:59.000Z')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-15T12:00:00.000Z')).toBe(true); + expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-15T12:00:01.000Z')).toBe(false); + + expect(await searchByPeriod(Operator.STARTS_AFTER, '2019-12-31')).toBe(true); + expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-01')).toBe(false); + expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-02')).toBe(false); + expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-15')).toBe(false); + expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-16')).toBe(false); + + expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-01T11:59:59.000Z')).toBe(true); + expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-01T12:00:00.000Z')).toBe(false); + expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-15T11:59:59.000Z')).toBe(false); + expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-15T12:00:00.000Z')).toBe(false); + expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-15T12:00:01.000Z')).toBe(false); + + expect(await searchByPeriod(Operator.ENDS_BEFORE, '2019-12-31')).toBe(false); + expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-01')).toBe(false); + expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-02')).toBe(false); + expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-15')).toBe(false); + expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-16')).toBe(true); + + expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-01T11:59:59.000Z')).toBe(false); + expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-01T12:00:00.000Z')).toBe(false); + expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-15T11:59:59.000Z')).toBe(false); + expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-15T12:00:00.000Z')).toBe(false); + expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-15T12:00:01.000Z')).toBe(true); + })); + }); - // Sort and filter by due date - const bundle = await systemRepo.search({ - resourceType: 'Task', - filters: [ - { code: 'code', operator: Operator.EQUALS, value: code }, - { code: 'due-date', operator: Operator.GREATER_THAN, value: '2023-06-01T00:00:00.000Z' }, - ], - sortRules: [{ code: 'due-date' }], - }); - expect(bundle.entry?.length).toEqual(2); - expect(bundle.entry?.[0]?.resource?.id).toEqual(task2.id); - expect(bundle.entry?.[1]?.resource?.id).toEqual(task3.id); - expect(bundleContains(bundle, task1)).not.toBeTruthy(); - })); - - test('Get estimated count with filter on human name', async () => { - const result = await systemRepo.search({ - resourceType: 'Patient', - total: 'estimate', - filters: [ - { - code: 'name', - operator: Operator.EQUALS, - value: 'John', - }, - ], + describe('systemRepo', () => { + const systemRepo = getSystemRepo(); + + beforeAll(async () => { + const config = await loadTestConfig(); + await initAppServices(config); }); - expect(result.total).toBeDefined(); - expect(typeof result.total).toBe('number'); - }); - test('Organization by name', () => - withTestContext(async () => { - const org = await systemRepo.createResource({ - resourceType: 'Organization', - name: randomUUID(), - }); - const result = await systemRepo.search({ - resourceType: 'Organization', - filters: [ - { - code: 'name', - operator: Operator.EQUALS, - value: `wrongname,${(org.name as string).slice(0, 5)}`, - }, - ], - }); - expect(result.entry?.length).toBe(1); - })); + afterAll(async () => { + await shutdownApp(); + }); - test('Patient by name with stop word', () => - withTestContext(async () => { - const seed = randomUUID(); - await systemRepo.createResource({ - resourceType: 'Patient', - name: [ - { - given: [seed + 'Justin', 'Wynn'], - family: 'Sanders' + seed, + test('Filter by _project', () => + withTestContext(async () => { + const project1 = (await systemRepo.createResource({ resourceType: 'Project' })).id as string; + const project2 = (await systemRepo.createResource({ resourceType: 'Project' })).id as string; + + const patient1 = await systemRepo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice1'], family: 'Smith1' }], + meta: { + project: project1, }, - ], - }); - const result = await systemRepo.search({ - resourceType: 'Patient', - filters: [ - { - code: 'name', - operator: Operator.CONTAINS, - value: `${seed.slice(-3)}just`, + }); + expect(patient1).toBeDefined(); + + const patient2 = await systemRepo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice2'], family: 'Smith2' }], + meta: { + project: project2, }, - ], - }); - expect(result.entry?.length).toBe(1); - })); + }); + expect(patient2).toBeDefined(); - test('Sort by ID', () => - withTestContext(async () => { - const org = await systemRepo.createResource({ resourceType: 'Organization', name: 'org1' }); - const managingOrganization = createReference(org); - await systemRepo.createResource({ resourceType: 'Patient', managingOrganization }); - await systemRepo.createResource({ resourceType: 'Patient', managingOrganization }); + const bundle = await systemRepo.search({ + resourceType: 'Patient', + filters: [ + { + code: '_project', + operator: Operator.EQUALS, + value: project1, + }, + ], + }); + expect(bundle.entry?.length).toEqual(1); + expect(bundleContains(bundle as Bundle, patient1 as Patient)).toEqual(true); + expect(bundleContains(bundle as Bundle, patient2 as Patient)).toEqual(false); + })); + + test('Filter by _lastUpdated', () => + withTestContext(async () => { + // Create 2 patients + // One with a _lastUpdated of 1 second ago + // One with a _lastUpdated of 2 seconds ago + const family = randomUUID(); + const now = new Date(); + const nowMinus1Second = new Date(now.getTime() - 1000); + const nowMinus2Seconds = new Date(now.getTime() - 2000); + const nowMinus3Seconds = new Date(now.getTime() - 3000); + + const patient1 = await systemRepo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family }], + meta: { + lastUpdated: nowMinus1Second.toISOString(), + }, + }); + expect(patient1).toBeDefined(); - const result1 = await systemRepo.search({ - resourceType: 'Patient', - filters: [{ code: 'organization', operator: Operator.EQUALS, value: getReferenceString(org) }], - sortRules: [{ code: '_id', descending: false }], - }); - expect(result1.entry).toHaveLength(2); - expect(result1.entry?.[0]?.resource?.id?.localeCompare(result1.entry?.[1]?.resource?.id as string)).toBe(-1); + const patient2 = await systemRepo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family }], + meta: { + lastUpdated: nowMinus2Seconds.toISOString(), + }, + }); + expect(patient2).toBeDefined(); - const result2 = await systemRepo.search({ - resourceType: 'Patient', - filters: [{ code: 'organization', operator: Operator.EQUALS, value: getReferenceString(org) }], - sortRules: [{ code: '_id', descending: true }], - }); - expect(result2.entry).toHaveLength(2); - expect(result2.entry?.[0]?.resource?.id?.localeCompare(result2.entry?.[1]?.resource?.id as string)).toBe(1); - })); - - test('Numeric parameter', () => - withTestContext(async () => { - const ident = randomUUID(); - const riskAssessment: RiskAssessment = { - resourceType: 'RiskAssessment', - status: 'final', - identifier: [{ value: ident }], - subject: { - reference: 'Patient/test', - }, - prediction: [ - { - outcome: { text: 'Breast Cancer' }, - probabilityDecimal: 0.000168, - whenRange: { - high: { value: 53, unit: 'years' }, + // Greater than (newer than) 2 seconds ago should only return patient 1 + const searchResult1 = await systemRepo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'name', + operator: Operator.EQUALS, + value: family, }, - }, - { - outcome: { text: 'Breast Cancer' }, - probabilityDecimal: 0.000368, - whenRange: { - low: { value: 54, unit: 'years' }, - high: { value: 57, unit: 'years' }, + { + code: '_lastUpdated', + operator: Operator.GREATER_THAN, + value: nowMinus2Seconds.toISOString(), }, - }, - { - outcome: { text: 'Breast Cancer' }, - probabilityDecimal: 0.000594, - whenRange: { - low: { value: 58, unit: 'years' }, - high: { value: 62, unit: 'years' }, + ], + }); + + expect(bundleContains(searchResult1 as Bundle, patient1 as Patient)).toEqual(true); + expect(bundleContains(searchResult1 as Bundle, patient2 as Patient)).toEqual(false); + + // Greater than (newer than) or equal to 2 seconds ago should return both patients + const searchResult2 = await systemRepo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'name', + operator: Operator.EQUALS, + value: family, }, - }, - { - outcome: { text: 'Breast Cancer' }, - probabilityDecimal: 0.000838, - whenRange: { - low: { value: 63, unit: 'years' }, - high: { value: 67, unit: 'years' }, + { + code: '_lastUpdated', + operator: Operator.GREATER_THAN_OR_EQUALS, + value: nowMinus2Seconds.toISOString(), }, - }, - ], - }; + ], + }); - await systemRepo.createResource(riskAssessment); - const result = await systemRepo.search({ - resourceType: 'RiskAssessment', - filters: [ - { code: 'identifier', operator: Operator.EQUALS, value: ident }, - { code: 'probability', operator: Operator.GREATER_THAN, value: '0.0005' }, - ], - }); - expect(result.entry).toHaveLength(1); - })); + expect(bundleContains(searchResult2 as Bundle, patient1 as Patient)).toEqual(true); + expect(bundleContains(searchResult2 as Bundle, patient2 as Patient)).toEqual(true); - test('Disjunction with lookup tables', () => - withTestContext(async () => { - const n1 = randomUUID(); - const n2 = randomUUID(); + // Less than (older than) to 1 seconds ago should only return patient 2 + const searchResult3 = await systemRepo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'name', + operator: Operator.EQUALS, + value: family, + }, + { + code: '_lastUpdated', + operator: Operator.GREATER_THAN, + value: nowMinus3Seconds.toISOString(), + }, + { + code: '_lastUpdated', + operator: Operator.LESS_THAN, + value: nowMinus1Second.toISOString(), + }, + ], + }); - const p1 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ family: n1 }], - }); + expect(bundleContains(searchResult3 as Bundle, patient1 as Patient)).toEqual(false); + expect(bundleContains(searchResult3 as Bundle, patient2 as Patient)).toEqual(true); - const p2 = await systemRepo.createResource({ - resourceType: 'Patient', - name: [{ family: n2 }], - }); + // Less than (older than) or equal to 1 seconds ago should return both patients + const searchResult4 = await systemRepo.search({ + resourceType: 'Patient', + filters: [ + { + code: 'name', + operator: Operator.EQUALS, + value: family, + }, + { + code: '_lastUpdated', + operator: Operator.GREATER_THAN, + value: nowMinus3Seconds.toISOString(), + }, + { + code: '_lastUpdated', + operator: Operator.LESS_THAN_OR_EQUALS, + value: nowMinus1Second.toISOString(), + }, + ], + }); - const result = await systemRepo.search({ - resourceType: 'Patient', - filters: [{ code: '_filter', operator: Operator.EQUALS, value: `name co "${n1}" or name co "${n2}"` }], - }); + expect(bundleContains(searchResult4 as Bundle, patient1 as Patient)).toEqual(true); + expect(bundleContains(searchResult4 as Bundle, patient2 as Patient)).toEqual(true); + })); - expect(result.entry).toHaveLength(2); - expect(bundleContains(result, p1)).toBe(true); - expect(bundleContains(result, p2)).toBe(true); - })); + test('Sort by _lastUpdated', () => + withTestContext(async () => { + const project = (await systemRepo.createResource({ resourceType: 'Project' })).id as string; - test('Sort by unknown search parameter', async () => { - try { - await systemRepo.search({ - resourceType: 'Patient', - sortRules: [{ code: 'xyz' }], - }); - } catch (err) { - const outcome = normalizeOperationOutcome(err); - expect(outcome.issue?.[0]?.details?.text).toBe('Unknown search parameter: xyz'); - } - }); + const patient1 = await systemRepo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice1'], family: 'Smith1' }], + meta: { + lastUpdated: '2020-01-01T00:00:00.000Z', + project, + }, + }); + expect(patient1).toBeDefined(); - test('Date range search', () => - withTestContext(async () => { - const ident = randomUUID(); - - const measureReport = await systemRepo.createResource({ - resourceType: 'MeasureReport', - status: 'complete', - type: 'individual', - measure: 'http://example.com', - identifier: [{ value: ident }], - period: { - start: '2020-01-01T12:00:00.000Z', - end: '2020-01-15T12:00:00.000Z', - }, - }); - expect(measureReport).toBeDefined(); + const patient2 = await systemRepo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice2'], family: 'Smith2' }], + meta: { + lastUpdated: '2020-01-02T00:00:00.000Z', + project, + }, + }); + expect(patient2).toBeDefined(); - async function searchByPeriod(operator: Operator, value: string): Promise { - const result = await systemRepo.searchOne({ - resourceType: 'MeasureReport', + const bundle3 = await systemRepo.search({ + resourceType: 'Patient', filters: [ - { code: 'identifier', operator: Operator.EQUALS, value: ident }, - { code: 'period', operator, value }, + { + code: '_project', + operator: Operator.EQUALS, + value: project, + }, + ], + sortRules: [ + { + code: '_lastUpdated', + descending: false, + }, ], }); - return !!result; - } + expect(bundle3.entry?.length).toEqual(2); + expect(bundle3.entry?.[0]?.resource?.id).toEqual(patient1.id); + expect(bundle3.entry?.[1]?.resource?.id).toEqual(patient2.id); - expect(await searchByPeriod(Operator.EQUALS, '2019-12-31')).toBe(false); - expect(await searchByPeriod(Operator.EQUALS, '2020-01-01')).toBe(true); - expect(await searchByPeriod(Operator.EQUALS, '2020-01-02')).toBe(true); - expect(await searchByPeriod(Operator.EQUALS, '2020-01-15')).toBe(true); - expect(await searchByPeriod(Operator.EQUALS, '2020-01-16')).toBe(false); - - expect(await searchByPeriod(Operator.EQUALS, '2020-01-01T11:59:59.000Z')).toBe(false); - expect(await searchByPeriod(Operator.EQUALS, '2020-01-01T12:00:00.000Z')).toBe(true); - expect(await searchByPeriod(Operator.EQUALS, '2020-01-15T11:59:59.000Z')).toBe(true); - expect(await searchByPeriod(Operator.EQUALS, '2020-01-15T12:00:00.000Z')).toBe(true); - expect(await searchByPeriod(Operator.EQUALS, '2020-01-15T12:00:01.000Z')).toBe(false); - - expect(await searchByPeriod(Operator.NOT_EQUALS, '2019-12-31')).toBe(true); - expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-01')).toBe(false); - expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-02')).toBe(false); - expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-15')).toBe(false); - expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-16')).toBe(true); - - expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-01T11:59:59.000Z')).toBe(true); - expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-01T12:00:00.000Z')).toBe(false); - expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-15T11:59:59.000Z')).toBe(false); - expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-15T12:00:00.000Z')).toBe(false); - expect(await searchByPeriod(Operator.NOT_EQUALS, '2020-01-15T12:00:01.000Z')).toBe(true); - - expect(await searchByPeriod(Operator.LESS_THAN, '2019-12-31')).toBe(false); - expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-01')).toBe(true); - expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-02')).toBe(true); - expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-15')).toBe(true); - expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-16')).toBe(true); - - expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-01T11:59:59.000Z')).toBe(false); - expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-01T12:00:00.000Z')).toBe(false); - expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-15T11:59:59.000Z')).toBe(true); - expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-15T12:00:00.000Z')).toBe(true); - expect(await searchByPeriod(Operator.LESS_THAN, '2020-01-15T12:00:01.000Z')).toBe(true); - - expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2019-12-31')).toBe(false); - expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-01')).toBe(true); - expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-02')).toBe(true); - expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-15')).toBe(true); - expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-16')).toBe(true); - - expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-01T11:59:59.000Z')).toBe(false); - expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-01T12:00:00.000Z')).toBe(true); - expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-15T11:59:59.000Z')).toBe(true); - expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-15T12:00:00.000Z')).toBe(true); - expect(await searchByPeriod(Operator.LESS_THAN_OR_EQUALS, '2020-01-15T12:00:01.000Z')).toBe(true); - - expect(await searchByPeriod(Operator.GREATER_THAN, '2019-12-31')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-01')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-02')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-15')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-16')).toBe(false); - - expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-01T11:59:59.000Z')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-01T12:00:00.000Z')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-15T11:59:59.000Z')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-15T12:00:00.000Z')).toBe(false); - expect(await searchByPeriod(Operator.GREATER_THAN, '2020-01-15T12:00:01.000Z')).toBe(false); - - expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2019-12-31')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-01')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-02')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-15')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-16')).toBe(false); - - expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-01T11:59:59.000Z')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-01T12:00:00.000Z')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-15T11:59:59.000Z')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-15T12:00:00.000Z')).toBe(true); - expect(await searchByPeriod(Operator.GREATER_THAN_OR_EQUALS, '2020-01-15T12:00:01.000Z')).toBe(false); - - expect(await searchByPeriod(Operator.STARTS_AFTER, '2019-12-31')).toBe(true); - expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-01')).toBe(false); - expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-02')).toBe(false); - expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-15')).toBe(false); - expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-16')).toBe(false); - - expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-01T11:59:59.000Z')).toBe(true); - expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-01T12:00:00.000Z')).toBe(false); - expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-15T11:59:59.000Z')).toBe(false); - expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-15T12:00:00.000Z')).toBe(false); - expect(await searchByPeriod(Operator.STARTS_AFTER, '2020-01-15T12:00:01.000Z')).toBe(false); - - expect(await searchByPeriod(Operator.ENDS_BEFORE, '2019-12-31')).toBe(false); - expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-01')).toBe(false); - expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-02')).toBe(false); - expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-15')).toBe(false); - expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-16')).toBe(true); - - expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-01T11:59:59.000Z')).toBe(false); - expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-01T12:00:00.000Z')).toBe(false); - expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-15T11:59:59.000Z')).toBe(false); - expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-15T12:00:00.000Z')).toBe(false); - expect(await searchByPeriod(Operator.ENDS_BEFORE, '2020-01-15T12:00:01.000Z')).toBe(true); - })); + const bundle4 = await systemRepo.search({ + resourceType: 'Patient', + filters: [ + { + code: '_project', + operator: Operator.EQUALS, + value: project, + }, + ], + sortRules: [ + { + code: '_lastUpdated', + descending: true, + }, + ], + }); + expect(bundle4.entry?.length).toEqual(2); + expect(bundle4.entry?.[0]?.resource?.id).toEqual(patient2.id); + expect(bundle4.entry?.[1]?.resource?.id).toEqual(patient1.id); + })); + }); }); diff --git a/packages/server/src/fhir/search.ts b/packages/server/src/fhir/search.ts index 03377ed243..2aeaa372fe 100644 --- a/packages/server/src/fhir/search.ts +++ b/packages/server/src/fhir/search.ts @@ -394,7 +394,7 @@ function getSearchUrl(searchRequest: SearchRequest): string { * Returns the count for a search request. * This ignores page number and page size. * We always start with an "estimate" count to protect against expensive queries. - * If the estimate is less than 100,000, then we run an accurate count. + * If the estimate is less than the "accurateCountThreshold" config setting (default 1,000,000), then we run an accurate count. * @param repo - The repository. * @param searchRequest - The search request. * @param rowCount - The number of matching results if found. @@ -402,7 +402,7 @@ function getSearchUrl(searchRequest: SearchRequest): string { */ async function getCount(repo: Repository, searchRequest: SearchRequest, rowCount: number | undefined): Promise { const estimateCount = await getEstimateCount(repo, searchRequest, rowCount); - if (estimateCount < 100000) { + if (estimateCount < getConfig().accurateCountThreshold) { return getAccurateCount(repo, searchRequest); } return estimateCount; diff --git a/packages/server/src/fhir/sql.ts b/packages/server/src/fhir/sql.ts index 738b35dbcb..9852505b45 100644 --- a/packages/server/src/fhir/sql.ts +++ b/packages/server/src/fhir/sql.ts @@ -239,6 +239,21 @@ export class Exists implements Expression { } } +export class Union implements Expression { + constructor( + readonly left: SelectQuery, + readonly right: SelectQuery + ) {} + + buildSql(sql: SqlBuilder): void { + sql.append('('); + this.left.buildSql(sql); + sql.append(') UNION ('); + this.right.buildSql(sql); + sql.append(')'); + } +} + export class Join { constructor( readonly joinType: 'LEFT JOIN' | 'INNER JOIN', @@ -386,12 +401,19 @@ export abstract class BaseQuery { } } -export class SelectQuery extends BaseQuery { +interface CTE { + name: string; + expr: Expression; + recursive?: boolean; +} + +export class SelectQuery extends BaseQuery implements Expression { readonly distinctOns: Column[]; readonly columns: Column[]; readonly joins: Join[]; readonly groupBys: GroupBy[]; readonly orderBys: OrderBy[]; + with?: CTE; limit_: number; offset_: number; joinCount = 0; @@ -407,6 +429,11 @@ export class SelectQuery extends BaseQuery { this.offset_ = 0; } + withRecursive(name: string, expr: Expression): this { + this.with = { name, expr: expr, recursive: true }; + return this; + } + distinctOn(column: Column | string): this { this.distinctOns.push(getColumn(column, this.tableName)); return this; @@ -461,6 +488,16 @@ export class SelectQuery extends BaseQuery { if (this.explain) { sql.append('EXPLAIN '); } + if (this.with) { + sql.append('WITH '); + if (this.with.recursive) { + sql.append('RECURSIVE '); + } + sql.appendIdentifier(this.with.name); + sql.append(' AS ('); + this.with.expr.buildSql(sql); + sql.append(') '); + } sql.append('SELECT '); this.buildDistinctOn(sql); this.buildColumns(sql); diff --git a/packages/server/src/fhir/transaction.test.ts b/packages/server/src/fhir/transaction.test.ts index 3532f1575f..f553c75d70 100644 --- a/packages/server/src/fhir/transaction.test.ts +++ b/packages/server/src/fhir/transaction.test.ts @@ -1,27 +1,18 @@ import { OperationOutcomeError, Operator } from '@medplum/core'; import { Patient } from '@medplum/fhirtypes'; -import { randomUUID } from 'crypto'; import { initAppServices, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { withTestContext } from '../test.setup'; +import { createTestProject, withTestContext } from '../test.setup'; import { Repository } from './repo'; -describe('FHIR Repo', () => { +describe.skip('FHIR Repo Transactions', () => { let repo: Repository; beforeAll(async () => { const config = await loadTestConfig(); await initAppServices(config); - const clientApp = 'ClientApplication/' + randomUUID(); - const projectId = randomUUID(); - repo = new Repository({ - extendedMode: true, - project: projectId, - author: { - reference: clientApp, - }, - }); + repo = (await createTestProject({ withRepo: true })).repo; }); afterAll(async () => { diff --git a/packages/server/src/fhircast/websocket.test.ts b/packages/server/src/fhircast/websocket.test.ts index 39b17c783d..f0237334f8 100644 --- a/packages/server/src/fhircast/websocket.test.ts +++ b/packages/server/src/fhircast/websocket.test.ts @@ -16,7 +16,7 @@ describe('FHIRcast WebSocket', () => { beforeAll(async () => { config = await loadTestConfig(); server = await initApp(app, config); - accessToken = await initTestAuth({}, { admin: true }); + accessToken = await initTestAuth({ membership: { admin: true } }); await new Promise((resolve) => { server.listen(0, 'localhost', 511, resolve); }); diff --git a/packages/server/src/migrations/schema/index.ts b/packages/server/src/migrations/schema/index.ts index fbcee4eda6..b3539fbcf4 100644 --- a/packages/server/src/migrations/schema/index.ts +++ b/packages/server/src/migrations/schema/index.ts @@ -58,3 +58,5 @@ export * as v56 from './v56'; export * as v57 from './v57'; export * as v58 from './v58'; export * as v59 from './v59'; +export * as v60 from './v60'; +export * as v61 from './v61'; diff --git a/packages/server/src/migrations/schema/v60.ts b/packages/server/src/migrations/schema/v60.ts new file mode 100644 index 0000000000..a2c272f9cd --- /dev/null +++ b/packages/server/src/migrations/schema/v60.ts @@ -0,0 +1,13 @@ +/* + * Generated by @medplum/generator + * Do not edit manually. + */ + +import { PoolClient } from 'pg'; + +export async function run(client: PoolClient): Promise { + await client.query('ALTER TABLE IF EXISTS "CareTeam" ADD COLUMN IF NOT EXISTS "name" TEXT'); + await client.query('CREATE INDEX CONCURRENTLY IF NOT EXISTS CareTeam_name_idx ON "CareTeam" ("name")'); + await client.query('ALTER TABLE IF EXISTS "Group" ADD COLUMN IF NOT EXISTS "name" TEXT'); + await client.query('CREATE INDEX CONCURRENTLY IF NOT EXISTS Group_name_idx ON "Group" ("name")'); +} diff --git a/packages/server/src/migrations/schema/v61.ts b/packages/server/src/migrations/schema/v61.ts new file mode 100644 index 0000000000..5db22d699f --- /dev/null +++ b/packages/server/src/migrations/schema/v61.ts @@ -0,0 +1,12 @@ +/* + * Generated by @medplum/generator + * Do not edit manually. + */ + +import { PoolClient } from 'pg'; + +export async function run(client: PoolClient): Promise { + await client.query( + 'CREATE INDEX CONCURRENTLY IF NOT EXISTS "Coding_Property_coding_idx" ON "Coding_Property" (coding)' + ); +} diff --git a/packages/server/src/oauth/authorize.test.ts b/packages/server/src/oauth/authorize.test.ts index dfd40bfb63..2c93c11ac5 100644 --- a/packages/server/src/oauth/authorize.test.ts +++ b/packages/server/src/oauth/authorize.test.ts @@ -9,25 +9,26 @@ import { inviteUser } from '../admin/invite'; import { initApp, shutdownApp } from '../app'; import { setPassword } from '../auth/setpassword'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { createTestProject, withTestContext } from '../test.setup'; import { revokeLogin } from './utils'; jest.mock('@aws-sdk/client-sesv2'); -const app = express(); -const email = randomUUID() + '@example.com'; -const password = randomUUID(); -let project: Project; -let client: ClientApplication; - describe('OAuth Authorize', () => { + const app = express(); + const systemRepo = getSystemRepo(); + const email = randomUUID() + '@example.com'; + const password = randomUUID(); + let project: Project; + let client: ClientApplication; + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); // Create a test project - ({ project, client } = await createTestProject()); + ({ project, client } = await createTestProject({ withClient: true })); // Create a test user const { user } = await inviteUser({ diff --git a/packages/server/src/oauth/authorize.ts b/packages/server/src/oauth/authorize.ts index bb44d70022..88678b59d7 100644 --- a/packages/server/src/oauth/authorize.ts +++ b/packages/server/src/oauth/authorize.ts @@ -4,8 +4,8 @@ import { Request, Response } from 'express'; import { URL } from 'url'; import { asyncWrap } from '../async'; import { getConfig } from '../config'; -import { getRequestContext } from '../context'; -import { systemRepo } from '../fhir/repo'; +import { getLogger } from '../context'; +import { getSystemRepo } from '../fhir/repo'; import { MedplumIdTokenClaims, verifyJwt } from './keys'; import { getClientApplication } from './utils'; @@ -110,6 +110,7 @@ async function validateAuthorizeRequest(req: Request, res: Response, params: Rec } if (prompt !== 'login' && existingLogin) { + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...existingLogin, nonce: params.nonce as string, @@ -186,7 +187,7 @@ async function getExistingLoginFromIdTokenHint(req: Request): Promise('Login', existingLoginId); } @@ -212,6 +214,7 @@ async function getExistingLoginFromCookie(req: Request, client: ClientApplicatio return undefined; } + const systemRepo = getSystemRepo(); const bundle = await systemRepo.search({ resourceType: 'Login', filters: [ diff --git a/packages/server/src/oauth/keys.ts b/packages/server/src/oauth/keys.ts index c02acd431c..831bcecde7 100644 --- a/packages/server/src/oauth/keys.ts +++ b/packages/server/src/oauth/keys.ts @@ -14,7 +14,7 @@ import { SignJWT, } from 'jose'; import { MedplumServerConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { globalLogger } from '../logger'; export interface MedplumBaseClaims extends JWTPayload { @@ -101,6 +101,7 @@ export async function initKeys(config: MedplumServerConfig): Promise { throw new Error('Missing issuer'); } + const systemRepo = getSystemRepo(); const searchResult = await systemRepo.searchResources({ resourceType: 'JsonWebKey', filters: [{ code: 'active', operator: Operator.EQUALS, value: 'true' }], diff --git a/packages/server/src/oauth/middleware.test.ts b/packages/server/src/oauth/middleware.test.ts index 7845571154..c5b93db082 100644 --- a/packages/server/src/oauth/middleware.test.ts +++ b/packages/server/src/oauth/middleware.test.ts @@ -5,14 +5,15 @@ import express from 'express'; import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { createTestClient, createTestProject, withTestContext } from '../test.setup'; import { generateAccessToken, generateSecret } from './keys'; -const app = express(); -let client: ClientApplication; - describe('Auth middleware', () => { + const app = express(); + const systemRepo = getSystemRepo(); + let client: ClientApplication; + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); @@ -189,7 +190,7 @@ describe('Auth middleware', () => { }); test('Basic auth with super admin client', async () => { - const { client } = await createTestProject({ superAdmin: true }); + const { client } = await createTestProject({ superAdmin: true, withClient: true }); const res = await request(app) .get('/fhir/R4/Project?_total=accurate') .set('Authorization', 'Basic ' + Buffer.from(client.id + ':' + client.secret).toString('base64')); diff --git a/packages/server/src/oauth/middleware.ts b/packages/server/src/oauth/middleware.ts index c3f2268676..e391561336 100644 --- a/packages/server/src/oauth/middleware.ts +++ b/packages/server/src/oauth/middleware.ts @@ -4,7 +4,7 @@ import { NextFunction, Request, Response } from 'express'; import { IncomingMessage } from 'http'; import { AuthenticatedRequestContext, getRequestContext, requestContextStore } from '../context'; import { getRepoForLogin } from '../fhir/accesspolicy'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { getClientApplicationMembership, getLoginForAccessToken, timingSafeEqualStr } from './utils'; export interface AuthState { @@ -18,13 +18,7 @@ export function authenticateRequest(req: Request, res: Response, next: NextFunct return authenticateTokenImpl(req) .then(async ({ login, project, membership, accessToken }) => { const ctx = getRequestContext(); - const repo = await getRepoForLogin( - login, - membership, - project.strictMode, - isExtendedMode(req), - project.checkReferencesOnWrite - ); + const repo = await getRepoForLogin(login, membership, project, isExtendedMode(req)); requestContextStore.run( new AuthenticatedRequestContext(ctx, login, project, membership, repo, undefined, accessToken), () => next() @@ -61,6 +55,7 @@ async function authenticateBasicAuth(req: IncomingMessage, token: string): Promi throw new OperationOutcomeError(unauthorized); } + const systemRepo = getSystemRepo(); let client = undefined; try { client = await systemRepo.readResource('ClientApplication', username); diff --git a/packages/server/src/oauth/token.test.ts b/packages/server/src/oauth/token.test.ts index b311cdf5d3..b1a8be64d0 100644 --- a/packages/server/src/oauth/token.test.ts +++ b/packages/server/src/oauth/token.test.ts @@ -5,7 +5,7 @@ import { OAuthGrantType, OAuthTokenType, parseJWTPayload, - parseSearchDefinition, + parseSearchRequest, } from '@medplum/core'; import { AccessPolicy, ClientApplication, Login, Project, SmartAppLaunch } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; @@ -18,7 +18,7 @@ import { inviteUser } from '../admin/invite'; import { initApp, shutdownApp } from '../app'; import { setPassword } from '../auth/setpassword'; import { loadTestConfig, MedplumServerConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { createTestProject, withTestContext } from '../test.setup'; import { generateSecret } from './keys'; import { hashCode } from './token'; @@ -56,24 +56,25 @@ jest.mock('jose', () => { jest.mock('node-fetch'); -const app = express(); -const domain = randomUUID() + '.example.com'; -const email = `text@${domain}`; -const password = randomUUID(); -const redirectUri = `https://${domain}/auth/callback`; -let config: MedplumServerConfig; -let project: Project; -let client: ClientApplication; -let pkceOptionalClient: ClientApplication; -let externalAuthClient: ClientApplication; - describe('OAuth2 Token', () => { + const app = express(); + const systemRepo = getSystemRepo(); + const domain = randomUUID() + '.example.com'; + const email = `text@${domain}`; + const password = randomUUID(); + const redirectUri = `https://${domain}/auth/callback`; + let config: MedplumServerConfig; + let project: Project; + let client: ClientApplication; + let pkceOptionalClient: ClientApplication; + let externalAuthClient: ClientApplication; + beforeAll(async () => { config = await loadTestConfig(); await initApp(app, config); // Create a test project - ({ project, client } = await createTestProject()); + ({ project, client } = await createTestProject({ withClient: true })); // Create a 2nd client with PKCE optional pkceOptionalClient = await systemRepo.createResource({ @@ -279,7 +280,7 @@ describe('OAuth2 Token', () => { }); test('Token for client in super admin', async () => { - const { client } = await createTestProject({ superAdmin: true }); + const { client } = await createTestProject({ superAdmin: true, withClient: true }); const res1 = await request(app).post('/oauth2/token').type('form').send({ grant_type: 'client_credentials', client_id: client.id, @@ -297,7 +298,7 @@ describe('OAuth2 Token', () => { }); test('Token for client in "off" status', async () => { - const { client } = await createTestProject(); + const { client } = await createTestProject({ withClient: true }); await withTestContext(() => systemRepo.updateResource({ ...client, status: 'off' })); const res = await request(app).post('/oauth2/token').type('form').send({ @@ -311,7 +312,7 @@ describe('OAuth2 Token', () => { }); test('Token for client in "active" status', async () => { - const { client } = await createTestProject(); + const { client } = await createTestProject({ withClient: true }); await withTestContext(() => systemRepo.updateResource({ ...client, status: 'active' })); const res = await request(app).post('/oauth2/token').type('form').send({ @@ -508,7 +509,7 @@ describe('OAuth2 Token', () => { expect(res.status).toBe(200); // Find the login - const loginBundle = await systemRepo.search(parseSearchDefinition('Login?code=' + res.body.code)); + const loginBundle = await systemRepo.search(parseSearchRequest('Login?code=' + res.body.code)); expect(loginBundle.entry).toHaveLength(1); // Revoke the login @@ -864,7 +865,7 @@ describe('OAuth2 Token', () => { expect(res2.body.refresh_token).toBeDefined(); // Find the login - const loginBundle = await systemRepo.search(parseSearchDefinition('Login?code=' + res.body.code)); + const loginBundle = await systemRepo.search(parseSearchRequest('Login?code=' + res.body.code)); expect(loginBundle.entry).toHaveLength(1); // Revoke the login diff --git a/packages/server/src/oauth/token.ts b/packages/server/src/oauth/token.ts index 922cae830a..a0619e59e1 100644 --- a/packages/server/src/oauth/token.ts +++ b/packages/server/src/oauth/token.ts @@ -20,7 +20,7 @@ import { JWTVerifyOptions, createRemoteJWKSet, jwtVerify } from 'jose'; import { asyncWrap } from '../async'; import { getProjectIdByClientId } from '../auth/utils'; import { getConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { getTopicForUser } from '../fhircast/utils'; import { MedplumRefreshTokenClaims, generateSecret, verifyJwt } from './keys'; import { @@ -100,6 +100,7 @@ async function handleClientCredentials(req: Request, res: Response): Promise('ClientApplication', clientId); @@ -160,6 +161,7 @@ async function handleAuthorizationCode(req: Request, res: Response): Promise { return; } + const systemRepo = getSystemRepo(); const login = await systemRepo.readResource('Login', claims.login_id); if (login.refreshSecret === undefined) { @@ -353,6 +356,7 @@ export async function exchangeExternalAuthToken( return; } + const systemRepo = getSystemRepo(); const projectId = await getProjectIdByClientId(clientId, undefined); const client = await systemRepo.readResource('ClientApplication', clientId); const idp = client.identityProvider; @@ -467,6 +471,7 @@ async function parseClientAssertion( return { error: 'Invalid client assertion issuer' }; } + const systemRepo = getSystemRepo(); const clientId = claims.iss as string; let client: ClientApplication; try { @@ -551,6 +556,7 @@ async function sendTokenResponse(res: Response, login: Login, membership: Projec let encounter = undefined; if (login.launch) { + const systemRepo = getSystemRepo(); const launch = await systemRepo.readReference(login.launch); patient = resolveId(launch.patient); encounter = resolveId(launch.encounter); diff --git a/packages/server/src/oauth/userinfo.ts b/packages/server/src/oauth/userinfo.ts index e0fb14651a..d7c951a8fe 100644 --- a/packages/server/src/oauth/userinfo.ts +++ b/packages/server/src/oauth/userinfo.ts @@ -11,13 +11,14 @@ import { Reference, User } from '@medplum/fhirtypes'; import { Request, RequestHandler, Response } from 'express'; import { asyncWrap } from '../async'; import { getAuthenticatedContext } from '../context'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; /** * Handles the OAuth/OpenID UserInfo Endpoint. * See: https://openid.net/specs/openid-connect-core-1_0.html#UserInfo */ export const userInfoHandler: RequestHandler = asyncWrap(async (_req: Request, res: Response) => { + const systemRepo = getSystemRepo(); const ctx = getAuthenticatedContext(); const user = await systemRepo.readReference(ctx.login.user as Reference); const profile = await ctx.repo.readReference(ctx.profile); diff --git a/packages/server/src/oauth/utils.ts b/packages/server/src/oauth/utils.ts index a9c073edcf..7f5bdb0a81 100644 --- a/packages/server/src/oauth/utils.ts +++ b/packages/server/src/oauth/utils.ts @@ -31,7 +31,7 @@ import fetch from 'node-fetch'; import { authenticator } from 'otplib'; import { getRequestContext } from '../context'; import { getAccessPolicyForLogin } from '../fhir/accesspolicy'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { AuditEventOutcome, logAuthEvent, LoginEvent } from '../util/auditevent'; import { generateAccessToken, @@ -119,6 +119,7 @@ export async function getClientApplication(clientId: string): Promise('ClientApplication', clientId); } @@ -132,6 +133,7 @@ export async function tryLogin(request: LoginRequest): Promise { validatePkce(request, client); + const systemRepo = getSystemRepo(); let launch: SmartAppLaunch | undefined; if (request.launchId) { launch = await systemRepo.readResource('SmartAppLaunch', request.launchId); @@ -275,6 +277,7 @@ export async function verifyMfaToken(login: Login, token: string): Promise); if (!user.mfaEnrolled) { throw new OperationOutcomeError(badRequest('User not enrolled in MFA')); @@ -324,6 +327,7 @@ export async function getMembershipsForLogin(login: Login): Promise({ resourceType: 'ProjectMembership', count: 100, @@ -344,6 +348,7 @@ export async function getMembershipsForLogin(login: Login): Promise { + const systemRepo = getSystemRepo(); return systemRepo.searchOne({ resourceType: 'ProjectMembership', filters: [ @@ -379,6 +384,7 @@ export async function setLoginMembership(login: Login, membershipId: string): Pr } // Find the membership for the user + const systemRepo = getSystemRepo(); let membership = undefined; try { membership = await systemRepo.readResource('ProjectMembership', membershipId); @@ -477,6 +483,7 @@ export async function setLoginScope(login: Login, scope: string): Promise } // Otherwise update scope + const systemRepo = getSystemRepo(); return systemRepo.updateResource({ ...login, scope: submittedScopes.join(' '), @@ -495,6 +502,7 @@ export async function getAuthTokens(login: Login, profile: Reference({ ...login, granted: true, @@ -536,6 +544,7 @@ export async function getAuthTokens(login: Login, profile: Reference { + const systemRepo = getSystemRepo(); await systemRepo.updateResource({ ...login, revoked: true, @@ -550,6 +559,7 @@ export async function revokeLogin(login: Login): Promise { * @returns The user if found; otherwise, undefined. */ export async function getUserByExternalId(externalId: string, projectId: string): Promise { + const systemRepo = getSystemRepo(); const membership = await systemRepo.searchOne({ resourceType: 'ProjectMembership', filters: [ @@ -612,6 +622,7 @@ export async function getUserByEmail(email: string, projectId: string | undefine * @returns The user if found; otherwise, undefined. */ export async function getUserByEmailInProject(email: string, projectId: string): Promise { + const systemRepo = getSystemRepo(); const bundle = await systemRepo.search({ resourceType: 'User', filters: [ @@ -637,6 +648,7 @@ export async function getUserByEmailInProject(email: string, projectId: string): * @returns The user if found; otherwise, undefined. */ export async function getUserByEmailWithoutProject(email: string): Promise { + const systemRepo = getSystemRepo(); const bundle = await systemRepo.search({ resourceType: 'User', filters: [ @@ -784,6 +796,7 @@ export async function getLoginForAccessToken(accessToken: string): Promise('Login', claims.login_id); diff --git a/packages/server/src/scim/routes.test.ts b/packages/server/src/scim/routes.test.ts index 9e85dc9ec6..d8bed0a694 100644 --- a/packages/server/src/scim/routes.test.ts +++ b/packages/server/src/scim/routes.test.ts @@ -6,14 +6,15 @@ import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { registerNew } from '../auth/register'; import { loadTestConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; -import { addTestUser, withTestContext } from '../test.setup'; import { AuthenticatedRequestContext, requestContextStore } from '../context'; - -const app = express(); -let accessToken: string; +import { getSystemRepo } from '../fhir/repo'; +import { addTestUser, withTestContext } from '../test.setup'; describe('SCIM Routes', () => { + const app = express(); + const systemRepo = getSystemRepo(); + let accessToken: string; + beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); diff --git a/packages/server/src/scim/utils.ts b/packages/server/src/scim/utils.ts index 9f67248ba4..c499aed8d9 100644 --- a/packages/server/src/scim/utils.ts +++ b/packages/server/src/scim/utils.ts @@ -2,7 +2,7 @@ import { badRequest, forbidden, getReferenceString, OperationOutcomeError, Opera import { Project, ProjectMembership, Reference, User } from '@medplum/fhirtypes'; import { inviteUser } from '../admin/invite'; import { getConfig } from '../config'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { ScimListResponse, ScimUser } from './types'; /** @@ -14,6 +14,7 @@ import { ScimListResponse, ScimUser } from './types'; * @returns List of SCIM users in the project. */ export async function searchScimUsers(project: Project): Promise> { + const systemRepo = getSystemRepo(); const memberships = await systemRepo.searchResources({ resourceType: 'ProjectMembership', count: 1000, @@ -88,6 +89,7 @@ export async function createScimUser( * @returns The user. */ export async function readScimUser(project: Project, id: string): Promise { + const systemRepo = getSystemRepo(); const membership = await systemRepo.readResource('ProjectMembership', id); if (membership.project?.reference !== getReferenceString(project)) { throw new OperationOutcomeError(forbidden); @@ -107,6 +109,7 @@ export async function readScimUser(project: Project, id: string): Promise { + const systemRepo = getSystemRepo(); let membership = await systemRepo.readResource('ProjectMembership', scimUser.id as string); if (membership.project?.reference !== getReferenceString(project)) { throw new OperationOutcomeError(forbidden); @@ -139,6 +142,7 @@ export async function updateScimUser(project: Project, scimUser: ScimUser): Prom * @returns The user. */ export async function deleteScimUser(project: Project, id: string): Promise { + const systemRepo = getSystemRepo(); const membership = await systemRepo.readResource('ProjectMembership', id); if (membership.project?.reference !== getReferenceString(project)) { throw new OperationOutcomeError(forbidden); diff --git a/packages/server/src/seed.ts b/packages/server/src/seed.ts index 9072492607..a63f1f1f9c 100644 --- a/packages/server/src/seed.ts +++ b/packages/server/src/seed.ts @@ -2,7 +2,7 @@ import { createReference } from '@medplum/core'; import { Practitioner, Project, ProjectMembership, User } from '@medplum/fhirtypes'; import { NIL as nullUuid, v5 } from 'uuid'; import { bcryptHashPassword } from './auth/utils'; -import { systemRepo } from './fhir/repo'; +import { getSystemRepo } from './fhir/repo'; import { globalLogger } from './logger'; import { rebuildR4SearchParameters } from './seeds/searchparameters'; import { rebuildR4StructureDefinitions } from './seeds/structuredefinitions'; @@ -16,6 +16,8 @@ export async function seedDatabase(): Promise { return; } + const systemRepo = getSystemRepo(); + const [firstName, lastName, email] = ['Medplum', 'Admin', 'admin@example.com']; const passwordHash = await bcryptHashPassword('medplum_admin'); const superAdmin = await systemRepo.createResource({ @@ -78,5 +80,6 @@ export async function seedDatabase(): Promise { * @returns True if already seeded. */ function isSeeded(): Promise { + const systemRepo = getSystemRepo(); return systemRepo.searchOne({ resourceType: 'User' }); } diff --git a/packages/server/src/seeds/searchparameters.ts b/packages/server/src/seeds/searchparameters.ts index 81455738c7..efa2c15904 100644 --- a/packages/server/src/seeds/searchparameters.ts +++ b/packages/server/src/seeds/searchparameters.ts @@ -1,7 +1,7 @@ import { readJson } from '@medplum/definitions'; import { BundleEntry, SearchParameter } from '@medplum/fhirtypes'; import { getDatabasePool } from '../database'; -import { systemRepo } from '../fhir/repo'; +import { Repository, getSystemRepo } from '../fhir/repo'; import { globalLogger } from '../logger'; import { r4ProjectId } from '../seed'; @@ -12,15 +12,17 @@ export async function rebuildR4SearchParameters(): Promise { const client = getDatabasePool(); await client.query('DELETE FROM "SearchParameter" WHERE "projectId" = $1', [r4ProjectId]); + const systemRepo = getSystemRepo(); + for (const entry of readJson('fhir/r4/search-parameters.json').entry as BundleEntry[]) { - await createParameter(entry.resource as SearchParameter); + await createParameter(systemRepo, entry.resource as SearchParameter); } for (const entry of readJson('fhir/r4/search-parameters-medplum.json').entry as BundleEntry[]) { - await createParameter(entry.resource as SearchParameter); + await createParameter(systemRepo, entry.resource as SearchParameter); } } -async function createParameter(param: SearchParameter): Promise { +async function createParameter(systemRepo: Repository, param: SearchParameter): Promise { globalLogger.debug('SearchParameter: ' + param.name); await systemRepo.createResource({ ...param, diff --git a/packages/server/src/seeds/structuredefinitions.ts b/packages/server/src/seeds/structuredefinitions.ts index 771bafafda..72ff392c2c 100644 --- a/packages/server/src/seeds/structuredefinitions.ts +++ b/packages/server/src/seeds/structuredefinitions.ts @@ -1,7 +1,7 @@ import { readJson } from '@medplum/definitions'; import { Bundle, BundleEntry, Resource, StructureDefinition } from '@medplum/fhirtypes'; import { getDatabasePool } from '../database'; -import { systemRepo } from '../fhir/repo'; +import { Repository, getSystemRepo } from '../fhir/repo'; import { globalLogger } from '../logger'; import { r4ProjectId } from '../seed'; @@ -11,12 +11,17 @@ import { r4ProjectId } from '../seed'; export async function rebuildR4StructureDefinitions(): Promise { const client = getDatabasePool(); await client.query(`DELETE FROM "StructureDefinition" WHERE "projectId" = $1`, [r4ProjectId]); - await createStructureDefinitionsForBundle(readJson('fhir/r4/profiles-resources.json') as Bundle); - await createStructureDefinitionsForBundle(readJson('fhir/r4/profiles-medplum.json') as Bundle); - await createStructureDefinitionsForBundle(readJson('fhir/r4/profiles-others.json') as Bundle); + + const systemRepo = getSystemRepo(); + await createStructureDefinitionsForBundle(systemRepo, readJson('fhir/r4/profiles-resources.json') as Bundle); + await createStructureDefinitionsForBundle(systemRepo, readJson('fhir/r4/profiles-medplum.json') as Bundle); + await createStructureDefinitionsForBundle(systemRepo, readJson('fhir/r4/profiles-others.json') as Bundle); } -async function createStructureDefinitionsForBundle(structureDefinitions: Bundle): Promise { +async function createStructureDefinitionsForBundle( + systemRepo: Repository, + structureDefinitions: Bundle +): Promise { for (const entry of structureDefinitions.entry as BundleEntry[]) { const resource = entry.resource as Resource; diff --git a/packages/server/src/seeds/valuesets.ts b/packages/server/src/seeds/valuesets.ts index bf5600cb2d..f80ab2d464 100644 --- a/packages/server/src/seeds/valuesets.ts +++ b/packages/server/src/seeds/valuesets.ts @@ -1,19 +1,20 @@ import { Operator } from '@medplum/core'; import { readJson } from '@medplum/definitions'; import { Bundle, BundleEntry, CodeSystem, ValueSet } from '@medplum/fhirtypes'; -import { systemRepo } from '../fhir/repo'; +import { Repository, getSystemRepo } from '../fhir/repo'; import { r4ProjectId } from '../seed'; /** * Imports all built-in ValueSets and CodeSystems into the database. */ export async function rebuildR4ValueSets(): Promise { - const files = ['valuesets.json', 'v2-tables.json', 'v3-codesystems.json', 'valuesets-medplum.json']; + const systemRepo = getSystemRepo(); + const files = ['v2-tables.json', 'v3-codesystems.json', 'valuesets.json', 'valuesets-medplum.json']; for (const file of files) { const bundle = readJson('fhir/r4/' + file) as Bundle; for (const entry of bundle.entry as BundleEntry[]) { const resource = entry.resource as CodeSystem | ValueSet; - await deleteExisting(resource); + await deleteExisting(systemRepo, resource, r4ProjectId); await systemRepo.createResource({ ...resource, meta: { ...resource.meta, project: r4ProjectId }, @@ -22,10 +23,17 @@ export async function rebuildR4ValueSets(): Promise { } } -async function deleteExisting(resource: CodeSystem | ValueSet): Promise { +async function deleteExisting( + systemRepo: Repository, + resource: CodeSystem | ValueSet, + projectId: string +): Promise { const bundle = await systemRepo.search({ resourceType: resource.resourceType, - filters: [{ code: 'url', operator: Operator.EQUALS, value: resource.url as string }], + filters: [ + { code: 'url', operator: Operator.EQUALS, value: resource.url as string }, + { code: '_project', operator: Operator.EQUALS, value: projectId }, + ], }); if (bundle.entry && bundle.entry.length > 0) { for (const entry of bundle.entry) { diff --git a/packages/server/src/storage.test.ts b/packages/server/src/storage.test.ts index cc8a105b4e..b8335fbd62 100644 --- a/packages/server/src/storage.test.ts +++ b/packages/server/src/storage.test.ts @@ -7,8 +7,8 @@ import { resolve } from 'path'; import { Readable } from 'stream'; import request from 'supertest'; import { initApp, shutdownApp } from './app'; -import { loadTestConfig, MedplumServerConfig } from './config'; -import { systemRepo } from './fhir/repo'; +import { MedplumServerConfig, loadTestConfig } from './config'; +import { getSystemRepo } from './fhir/repo'; import { getBinaryStorage } from './fhir/storage'; import { withTestContext } from './test.setup'; @@ -21,12 +21,14 @@ describe('Storage Routes', () => { config = await loadTestConfig(); await initApp(app, config); - binary = await withTestContext(() => - systemRepo.createResource({ + binary = await withTestContext(async () => { + const systemRepo = getSystemRepo(); + const result = await systemRepo.createResource({ resourceType: 'Binary', contentType: ContentType.TEXT, - }) - ); + }); + return result; + }); const req = new Readable(); req.push('hello world'); @@ -55,12 +57,14 @@ describe('Storage Routes', () => { }); test('File not found', async () => { - const resource = await withTestContext(() => - systemRepo.createResource({ + const resource = await withTestContext(async () => { + const systemRepo = getSystemRepo(); + const result = await systemRepo.createResource({ resourceType: 'Binary', contentType: ContentType.TEXT, - }) - ); + }); + return result; + }); const req = new Readable(); req.push('hello world'); diff --git a/packages/server/src/storage.ts b/packages/server/src/storage.ts index 6730ceca6f..035b170a28 100644 --- a/packages/server/src/storage.ts +++ b/packages/server/src/storage.ts @@ -1,7 +1,7 @@ import { Binary } from '@medplum/fhirtypes'; import { Request, Response, Router } from 'express'; import { asyncWrap } from './async'; -import { systemRepo } from './fhir/repo'; +import { getSystemRepo } from './fhir/repo'; import { getBinaryStorage } from './fhir/storage'; export const storageRouter = Router(); @@ -17,6 +17,7 @@ storageRouter.get( } const { id } = req.params; + const systemRepo = getSystemRepo(); const binary = await systemRepo.readResource('Binary', id); try { diff --git a/packages/server/src/subscriptions/websockets.test.ts b/packages/server/src/subscriptions/websockets.test.ts index bb5fbdcadf..d9e7a5e3e9 100644 --- a/packages/server/src/subscriptions/websockets.test.ts +++ b/packages/server/src/subscriptions/websockets.test.ts @@ -1,4 +1,4 @@ -import { getReferenceString, sleep } from '@medplum/core'; +import { OperationOutcomeError, getReferenceString, sleep } from '@medplum/core'; import { Bundle, BundleEntry, @@ -17,6 +17,7 @@ import { initApp, shutdownApp } from '../app'; import { registerNew } from '../auth/register'; import { MedplumServerConfig, loadTestConfig } from '../config'; import { Repository } from '../fhir/repo'; +import { getRedis } from '../redis'; import { withTestContext } from '../test.setup'; import { execSubscriptionJob, getSubscriptionQueue } from '../workers/subscription'; @@ -30,6 +31,7 @@ describe('WebSockets Subscriptions', () => { let project: Project; let repo: Repository; let accessToken: string; + let patientSubscription: Subscription; beforeAll(async () => { app = express(); @@ -52,12 +54,15 @@ describe('WebSockets Subscriptions', () => { repo = new Repository({ extendedMode: true, - project: project.id, + projects: [project.id as string], author: { reference: 'ClientApplication/' + randomUUID(), }, }); + // TODO: Remove this when the websocket-subscriptions feature flag is removed + project = await withTestContext(() => repo.updateResource({ ...project, features: ['websocket-subscriptions'] })); + await new Promise((resolve) => { server.listen(0, 'localhost', 511, resolve); }); @@ -80,7 +85,7 @@ describe('WebSockets Subscriptions', () => { expect(version1.id).toBeDefined(); // Create subscription to watch patient - const subscription = await repo.createResource({ + patientSubscription = await repo.createResource({ resourceType: 'Subscription', reason: 'test', status: 'active', @@ -89,11 +94,11 @@ describe('WebSockets Subscriptions', () => { type: 'websocket', }, }); - expect(subscription).toBeDefined(); + expect(patientSubscription).toBeDefined(); // Call $get-ws-binding-token const res = await request(server) - .get(`/fhir/R4/Subscription/${subscription.id}/$get-ws-binding-token`) + .get(`/fhir/R4/Subscription/${patientSubscription.id}/$get-ws-binding-token`) .set('Authorization', 'Bearer ' + accessToken); expect(res.body).toBeDefined(); @@ -103,11 +108,13 @@ describe('WebSockets Subscriptions', () => { expect(body.parameter?.[0]?.name).toEqual('token'); expect(body.parameter?.[0]?.valueString).toBeDefined(); + const token = body.parameter?.[0]?.valueString as string; + let version2: Patient; await request(server) .ws('/ws/subscriptions-r4') .set('Authorization', 'Bearer ' + accessToken) - .sendJson({ type: 'bind-with-token', payload: { token: body.parameter?.[0]?.valueString as string } }) + .sendJson({ type: 'bind-with-token', payload: { token } }) // Add a new patient for this project .exec(async () => { const queue = getSubscriptionQueue() as any; @@ -133,6 +140,19 @@ describe('WebSockets Subscriptions', () => { // Clear the queue queue.add.mockClear(); + + let subActive = false; + while (!subActive) { + await sleep(0); + subActive = + ( + await getRedis().smismember( + `medplum:subscriptions:r4:project:${project.id}:active`, + `Subscription/${patientSubscription?.id as string}` + ) + )[0] === 1; + } + expect(subActive).toEqual(true); }) .expectJson((msg: Bundle): boolean => { if (!msg.entry?.[1]) { @@ -155,6 +175,27 @@ describe('WebSockets Subscriptions', () => { .expectClosed(); })); + test('Subscription removed from active and deleted after WebSocket closed', () => + withTestContext(async () => { + let subActive = true; + while (subActive) { + await sleep(0); + subActive = + ( + await getRedis().smismember( + `medplum:subscriptions:r4:project:${project.id}:active`, + `Subscription/${patientSubscription?.id as string}` + ) + )[0] === 1; + } + expect(subActive).toEqual(false); + + // Check Patient subscription is NOT still in the cache + await expect(repo.readResource('Subscription', patientSubscription?.id as string)).rejects.toThrow( + OperationOutcomeError + ); + })); + test('Should reject if given an invalid binding token', () => withTestContext(async () => { const version1 = await repo.createResource({ @@ -260,7 +301,7 @@ describe('Subscription Heartbeat', () => { repo = new Repository({ extendedMode: true, - project: project.id, + projects: [project.id as string], author: { reference: 'ClientApplication/' + randomUUID(), }, diff --git a/packages/server/src/subscriptions/websockets.ts b/packages/server/src/subscriptions/websockets.ts index 679f77811b..0404fc55b6 100644 --- a/packages/server/src/subscriptions/websockets.ts +++ b/packages/server/src/subscriptions/websockets.ts @@ -1,9 +1,11 @@ import { badRequest, createReference } from '@medplum/core'; -import { Bundle, Resource } from '@medplum/fhirtypes'; +import { Bundle, Resource, Subscription } from '@medplum/fhirtypes'; import { Redis } from 'ioredis'; import { JWTPayload } from 'jose'; import crypto from 'node:crypto'; import ws from 'ws'; +import { AdditionalWsBindingClaims } from '../fhir/operations/getwsbindingtoken'; +import { CacheEntry } from '../fhir/repo'; import { getFullUrl } from '../fhir/response'; import { heartbeat } from '../heartbeat'; import { globalLogger } from '../logger'; @@ -24,10 +26,19 @@ export async function handleR4SubscriptionConnection(socket: ws.WebSocket): Prom const redis = getRedis(); const subscriptionIds = [] as string[]; let redisSubscriber: Redis; - let onDisconnect: (() => void) | undefined; + let onDisconnect: (() => Promise) | undefined; let heartbeatHandler: (() => void) | undefined; - const onBind = async (tokenPayload: JWTPayload): Promise => { + const onBind = async (tokenPayload: JWTPayload & Partial): Promise => { + const subscriptionId = tokenPayload?.subscription_id; + if (!subscriptionId) { + socket.send( + JSON.stringify(badRequest('Token claims missing subscription_id. Make sure you are sending the correct token.')) + ); + socket.terminate(); + return; + } + if (!redisSubscriber) { // Create a redis client for this connection. // According to Redis documentation: http://redis.io/commands/subscribe @@ -40,16 +51,18 @@ export async function handleR4SubscriptionConnection(socket: ws.WebSocket): Prom socket.send(message, { binary: false }); }); - onDisconnect = () => redisSubscriber.disconnect(); + onDisconnect = async (): Promise => { + redisSubscriber.disconnect(); + const cacheEntryStr = (await redis.get(`Subscription/${subscriptionId}`)) as string | null; + if (!cacheEntryStr) { + globalLogger.error('[WS] Failed to retrieve subscription cache entry on WebSocket disconnect.'); + return; + } + const cacheEntry = JSON.parse(cacheEntryStr) as CacheEntry; + await markInMemorySubscriptionsInactive(cacheEntry.projectId, subscriptionIds); + }; } - const subscriptionId = tokenPayload?.subscription_id as string | undefined; - if (!subscriptionId) { - socket.send( - JSON.stringify(badRequest('Token claims missing subscription_id. Make sure you are sending the correct token.')) - ); - return; - } if (!subscriptionIds.includes(subscriptionId)) { subscriptionIds.push(subscriptionId); } @@ -92,7 +105,7 @@ export async function handleR4SubscriptionConnection(socket: ws.WebSocket): Prom socket.on('close', async () => { if (onDisconnect) { - onDisconnect(); + onDisconnect().catch(console.error); } if (heartbeatHandler) { heartbeat.removeEventListener('heartbeat', heartbeatHandler); @@ -160,3 +173,12 @@ export function createSubEventNotification { + const refStrs = []; + for (const subscriptionId of subscriptionIds) { + refStrs.push(`Subscription/${subscriptionId}`); + } + const redis = getRedis(); + await redis.multi().srem(`medplum:subscriptions:r4:project:${projectId}:active`, refStrs).del(refStrs).exec(); +} diff --git a/packages/server/src/test.setup.ts b/packages/server/src/test.setup.ts index 91325a00c9..327a57f398 100644 --- a/packages/server/src/test.setup.ts +++ b/packages/server/src/test.setup.ts @@ -15,99 +15,145 @@ import { Express } from 'express'; import request from 'supertest'; import { inviteUser } from './admin/invite'; import { AuthenticatedRequestContext, requestContextStore } from './context'; -import { systemRepo } from './fhir/repo'; +import { getSystemRepo, Repository } from './fhir/repo'; import { generateAccessToken } from './oauth/keys'; import { tryLogin } from './oauth/utils'; -export async function createTestProject( - projectOptions?: Partial, - membershipOptions?: Partial -): Promise<{ +export interface TestProjectOptions { + project?: Partial; + accessPolicy?: Partial; + membership?: Partial; + superAdmin?: boolean; + withClient?: boolean; + withAccessToken?: boolean; + withRepo?: boolean; +} + +export type TestProjectResult = { project: Project; - client: ClientApplication; - membership: ProjectMembership; - login: Login; - accessToken: string; -}> { - requestContextStore.enterWith(AuthenticatedRequestContext.system()); - const project = await systemRepo.createResource({ - resourceType: 'Project', - name: 'Test Project', - owner: { - reference: 'User/' + randomUUID(), - }, - strictMode: true, - features: ['bots', 'email', 'graphql-introspection', 'cron'], - secret: [ - { - name: 'foo', - valueString: 'bar', + accessPolicy: T['accessPolicy'] extends AccessPolicy ? AccessPolicy : undefined; + client: T['withClient'] extends true ? ClientApplication : undefined; + membership: T['withClient'] extends true ? ProjectMembership : undefined; + login: T['withAccessToken'] extends true ? Login : undefined; + accessToken: T['withAccessToken'] extends true ? string : undefined; + repo: T['withRepo'] extends true ? Repository : undefined; +}; + +export async function createTestProject( + options: T = {} as T +): Promise> { + return requestContextStore.run(AuthenticatedRequestContext.system(), async () => { + const systemRepo = getSystemRepo(); + + const project = await systemRepo.createResource({ + resourceType: 'Project', + name: 'Test Project', + owner: { + reference: 'User/' + randomUUID(), }, - ], - ...projectOptions, - }); + strictMode: true, + features: ['bots', 'email', 'graphql-introspection', 'cron'], + secret: [ + { + name: 'foo', + valueString: 'bar', + }, + ], + superAdmin: options?.superAdmin, + ...options?.project, + }); - const client = await systemRepo.createResource({ - resourceType: 'ClientApplication', - secret: randomUUID(), - redirectUri: 'https://example.com/', - meta: { - project: project.id as string, - }, - name: 'Test Client Application', - }); + let client: ClientApplication | undefined = undefined; + let accessPolicy: AccessPolicy | undefined = undefined; + let membership: ProjectMembership | undefined = undefined; + let login: Login | undefined = undefined; + let accessToken: string | undefined = undefined; + let repo: Repository | undefined = undefined; - const membership = await systemRepo.createResource({ - resourceType: 'ProjectMembership', - user: createReference(client), - profile: createReference(client), - project: createReference(project), - ...membershipOptions, - }); + if (options?.withClient || options?.withAccessToken || options?.withRepo) { + client = await systemRepo.createResource({ + resourceType: 'ClientApplication', + secret: randomUUID(), + redirectUri: 'https://example.com/', + meta: { + project: project.id as string, + }, + name: 'Test Client Application', + }); - const scope = 'openid'; - - const login = await systemRepo.createResource({ - resourceType: 'Login', - authMethod: 'client', - user: createReference(client), - client: createReference(client), - membership: createReference(membership), - authTime: new Date().toISOString(), - superAdmin: projectOptions?.superAdmin, - scope, - }); + if (options?.accessPolicy) { + accessPolicy = await systemRepo.createResource({ + resourceType: 'AccessPolicy', + meta: { project: project.id }, + ...options.accessPolicy, + }); + } - const accessToken = await generateAccessToken({ - login_id: login.id as string, - sub: client.id as string, - username: client.id as string, - client_id: client.id as string, - profile: client.resourceType + '/' + client.id, - scope, - }); + membership = await systemRepo.createResource({ + resourceType: 'ProjectMembership', + user: createReference(client), + profile: createReference(client), + project: createReference(project), + accessPolicy: accessPolicy && createReference(accessPolicy), + ...options?.membership, + }); - return { - project, - client, - membership, - login, - accessToken, - }; + if (options?.withAccessToken) { + const scope = 'openid'; + + login = await systemRepo.createResource({ + resourceType: 'Login', + authMethod: 'client', + user: createReference(client), + client: createReference(client), + membership: createReference(membership), + authTime: new Date().toISOString(), + superAdmin: options?.superAdmin, + scope, + }); + + accessToken = await generateAccessToken({ + login_id: login.id as string, + sub: client.id as string, + username: client.id as string, + client_id: client.id as string, + profile: client.resourceType + '/' + client.id, + scope, + }); + } + + if (options?.withRepo) { + repo = new Repository({ + projects: [project.id as string], + author: createReference(client), + superAdmin: options?.superAdmin, + projectAdmin: options?.membership?.admin, + accessPolicy, + strictMode: project.strictMode, + extendedMode: true, + checkReferencesOnWrite: project.checkReferencesOnWrite, + }); + } + } + + return { + project, + accessPolicy, + client, + membership, + login, + accessToken, + repo, + } as TestProjectResult; + }); } -export async function createTestClient( - projectOptions?: Partial, - membershipOptions?: Partial -): Promise { - return (await createTestProject(projectOptions, membershipOptions)).client; +export async function createTestClient(options?: TestProjectOptions): Promise { + return (await createTestProject({ ...options, withClient: true })).client as ClientApplication; } -export async function initTestAuth( - projectOptions?: Partial, - membershipOptions?: Partial -): Promise { - return (await createTestProject(projectOptions, membershipOptions)).accessToken; +export async function initTestAuth(options?: TestProjectOptions): Promise { + return (await createTestProject({ ...options, withAccessToken: true })).accessToken as string; } export async function addTestUser( @@ -116,6 +162,7 @@ export async function addTestUser( ): Promise<{ user: User; profile: ProfileResource; accessToken: string }> { requestContextStore.enterWith(AuthenticatedRequestContext.system()); if (accessPolicy) { + const systemRepo = getSystemRepo(); accessPolicy = await systemRepo.createResource({ ...accessPolicy, meta: { project: project.id }, @@ -208,7 +255,7 @@ export function waitFor(fn: () => Promise): Promise { } export async function waitForAsyncJob(contentLocation: string, app: Express, accessToken: string): Promise { - for (let i = 0; i < 10; i++) { + for (let i = 0; i < 30; i++) { const res = await request(app) .get(new URL(contentLocation).pathname) .set('Authorization', 'Bearer ' + accessToken); @@ -220,6 +267,6 @@ export async function waitForAsyncJob(contentLocation: string, app: Express, acc throw new Error('Async Job did not complete'); } -export function withTestContext(fn: () => T): T { - return requestContextStore.run(AuthenticatedRequestContext.system(), fn); +export function withTestContext(fn: () => T, ctx?: { requestId?: string; traceId?: string }): T { + return requestContextStore.run(AuthenticatedRequestContext.system(ctx), fn); } diff --git a/packages/server/src/traceparent.test.ts b/packages/server/src/traceparent.test.ts new file mode 100644 index 0000000000..54f16a6a15 --- /dev/null +++ b/packages/server/src/traceparent.test.ts @@ -0,0 +1,38 @@ +import { Traceparent, parseTraceparent } from './traceparent'; + +describe('parseTraceparent', () => { + const tp: Traceparent = { + version: '00', + traceId: '12345678901234567890123456789012', + parentId: '3456789012345678', + flags: '01', + }; + + it('parses traceparent spec', () => { + expect(parseTraceparent(`${tp.version}-${tp.traceId}-${tp.parentId}-${tp.flags}`)).toEqual(tp); + }); + + it('allows missing version', () => { + expect(parseTraceparent(`${tp.traceId}-${tp.parentId}-${tp.flags}`)).toEqual({ ...tp, version: undefined }); + }); + + it('allows missing flags', () => { + expect(parseTraceparent(`${tp.version}-${tp.traceId}-${tp.parentId}`)).toEqual({ ...tp, flags: undefined }); + }); + + it('allows missing version and flags', () => { + expect(parseTraceparent(`${tp.traceId}-${tp.parentId}`)).toEqual({ ...tp, version: undefined, flags: undefined }); + }); + + it('allows 1 character for flags', () => { + expect(parseTraceparent(`${tp.traceId}-${tp.parentId}-1`)).toEqual({ ...tp, version: undefined, flags: '1' }); + }); + + it('returns null for more than 2 characters for flags', () => { + expect(parseTraceparent(`${tp.traceId}-${tp.parentId}-001`)).toEqual(null); + }); + + it('reports invalid', () => { + expect(parseTraceparent(`invalid-traceparent`)).toEqual(null); + }); +}); diff --git a/packages/server/src/traceparent.ts b/packages/server/src/traceparent.ts new file mode 100644 index 0000000000..0f1d043746 --- /dev/null +++ b/packages/server/src/traceparent.ts @@ -0,0 +1,27 @@ +// https://www.w3.org/TR/trace-context/#traceparent-header + +type hex = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f'; +type twohex = `${hex}${hex}`; + +export type Traceparent = { + version?: twohex; + traceId: string; + parentId: string; + flags?: hex | twohex; +}; + +const traceparentRegex = /^([0-9a-f]{2})?-?([0-9a-f]{32})-([0-9a-f]{16})-?([0-9a-f]{1,2})?$/i; + +export function parseTraceparent(traceparent: string): Traceparent | null { + const match = traceparent.match(traceparentRegex); + if (!match) { + return null; + } + + return { + version: (match[1] ?? undefined) as Traceparent['version'], + traceId: match[2], + parentId: match[3], + flags: (match[4] ?? undefined) as Traceparent['flags'], + }; +} diff --git a/packages/server/src/workers/cron.test.ts b/packages/server/src/workers/cron.test.ts index e36cd71a2e..86cc98bbcc 100644 --- a/packages/server/src/workers/cron.test.ts +++ b/packages/server/src/workers/cron.test.ts @@ -1,29 +1,30 @@ -import { Repository, systemRepo } from '../fhir/repo'; -import { loadTestConfig } from '../config'; -import { initAppServices, shutdownApp } from '../app'; -import { AuditEvent, Bot, Project, ProjectMembership } from '@medplum/fhirtypes'; -import { createTestProject, withTestContext } from '../test.setup'; import { createReference } from '@medplum/core'; -import { convertTimingToCron, CronJobData, execBot, getCronQueue } from './cron'; -import { randomUUID } from 'crypto'; +import { AuditEvent, Bot, Project, ProjectMembership } from '@medplum/fhirtypes'; import { Job } from 'bullmq'; +import { randomUUID } from 'crypto'; +import { initAppServices, shutdownApp } from '../app'; +import { loadTestConfig } from '../config'; +import { Repository, getSystemRepo } from '../fhir/repo'; +import { createTestProject, withTestContext } from '../test.setup'; +import { CronJobData, convertTimingToCron, execBot, getCronQueue } from './cron'; jest.mock('node-fetch'); -let botProject: Project; -let botRepo: Repository; - describe('Cron Worker', () => { + const systemRepo = getSystemRepo(); + let botProject: Project; + let botRepo: Repository; + beforeAll(async () => { const config = await loadTestConfig(); await initAppServices(config); // Create a project - const botProjectDetails = await createTestProject(); + const botProjectDetails = await createTestProject({ withClient: true }); botProject = botProjectDetails.project; botRepo = new Repository({ extendedMode: true, - project: botProjectDetails.project.id, + projects: [botProjectDetails.project.id as string], author: createReference(botProjectDetails.client), }); }); @@ -179,7 +180,7 @@ describe('Cron Worker', () => { const repo = new Repository({ extendedMode: true, - project: testProject.id, + projects: [testProject.id as string], author: { reference: 'ClientApplication/' + randomUUID(), }, diff --git a/packages/server/src/workers/cron.ts b/packages/server/src/workers/cron.ts index c4d38ec0af..81441a06b7 100644 --- a/packages/server/src/workers/cron.ts +++ b/packages/server/src/workers/cron.ts @@ -3,9 +3,9 @@ import { Bot, Project, Resource, Timing } from '@medplum/fhirtypes'; import { Job, Queue, QueueBaseOptions, Worker } from 'bullmq'; import { isValidCron } from 'cron-validator'; import { MedplumServerConfig } from '../config'; -import { getRequestContext } from '../context'; +import { getLogger } from '../context'; import { executeBot } from '../fhir/operations/execute'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo } from '../fhir/repo'; import { globalLogger } from '../logger'; import { findProjectMembership } from './utils'; @@ -97,17 +97,19 @@ export function getCronQueue(): Queue | undefined { * @param resource - The resource that was created or updated. */ export async function addCronJobs(resource: Resource): Promise { - const ctx = getRequestContext(); if (resource.resourceType !== 'Bot') { // For now we have only the bot to execute on a timed job return; } + const logger = getLogger(); const bot = resource; + // Adding a new feature for project that allows users to add a cron + const systemRepo = getSystemRepo(); const project = await systemRepo.readResource('Project', resource.meta?.project as string); if (!project.features?.includes('cron')) { - ctx.logger.debug('Cron not enabled. Cron needs to be enabled in project to create cron job for bot'); + logger.debug('Cron not enabled. Cron needs to be enabled in project to create cron job for bot'); return; } @@ -116,17 +118,17 @@ export async function addCronJobs(resource: Resource): Promise { if (bot.cronTiming) { cron = convertTimingToCron(bot.cronTiming); if (!cron) { - ctx.logger.debug('cronTiming had the wrong format for a timed cron job'); + logger.debug('cronTiming had the wrong format for a timed cron job'); return; } } else if (bot.cronString && isValidCron(bot.cronString)) { cron = bot.cronString; } else if (bot.cronString === '') { await removeBullMQJobByKey(bot.id as string); - ctx.logger.debug(`no job for bot: ${bot.id}`); + logger.debug(`no job for bot: ${bot.id}`); return; } else { - ctx.logger.debug('cronString had the wrong format for a timed cron job'); + logger.debug('cronString had the wrong format for a timed cron job'); return; } @@ -150,15 +152,11 @@ export async function addCronJobs(resource: Resource): Promise { * @param repeatable - The repeat format that instructs BullMQ when to run the job */ async function addCronJobData(job: CronJobData, repeatable: Repeatable): Promise { - const ctx = getRequestContext(); // Check if there was a job previously for this bot, if there was, we remove it. await removeBullMQJobByKey(job.botId); - ctx.logger.debug('Adding Cron job'); // Parameters of queue.add https://api.docs.bullmq.io/classes/Queue.html#add if (queue) { await queue.add(jobName, job, repeatable); - } else { - ctx.logger.debug('Cron queue not initialized'); } } @@ -206,6 +204,7 @@ export function convertTimingToCron(timing: Timing): string | undefined { } export async function execBot(job: Job): Promise { + const systemRepo = getSystemRepo(); const bot = await systemRepo.readReference({ reference: 'Bot/' + job.data.botId }); const project = bot.meta?.project as string; const runAs = await findProjectMembership(project, createReference(bot)); @@ -223,6 +222,6 @@ export async function removeBullMQJobByKey(botId: string): Promise { // There likely should not be more than one repeatable job per bot id. for (const p of previousJobs) { await queue?.removeRepeatableByKey(p.key); - getRequestContext().logger.debug(`Found a previous job for bot ${botId}, updating...`); + getLogger().debug(`Found a previous job for bot ${botId}, updating...`); } } diff --git a/packages/server/src/workers/download.test.ts b/packages/server/src/workers/download.test.ts index 2cd882e7b4..777c1b8896 100644 --- a/packages/server/src/workers/download.test.ts +++ b/packages/server/src/workers/download.test.ts @@ -7,8 +7,8 @@ import { Readable } from 'stream'; import { initAppServices, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; import { Repository } from '../fhir/repo'; +import { createTestProject, withTestContext } from '../test.setup'; import { closeDownloadWorker, execDownloadJob, getDownloadQueue } from './download'; -import { withTestContext } from '../test.setup'; jest.mock('node-fetch'); @@ -19,12 +19,7 @@ describe('Download Worker', () => { const config = await loadTestConfig(); await initAppServices(config); - repo = new Repository({ - project: randomUUID(), - author: { - reference: 'ClientApplication/' + randomUUID(), - }, - }); + repo = (await createTestProject({ withRepo: true })).repo; }); afterAll(async () => { @@ -37,45 +32,53 @@ describe('Download Worker', () => { }); test('Download external URL', () => - withTestContext(async () => { - const url = 'https://example.com/download'; - - const queue = getDownloadQueue() as any; - queue.add.mockClear(); - - const media = await repo.createResource({ - resourceType: 'Media', - status: 'completed', - content: { - contentType: ContentType.TEXT, - url, - }, - }); - expect(media).toBeDefined(); - expect(queue.add).toHaveBeenCalled(); - - const body = new Readable(); - body.push('foo'); - body.push(null); - - (fetch as unknown as jest.Mock).mockImplementation(() => ({ - status: 200, - headers: { - get(name: string): string | undefined { - return { - 'content-disposition': 'attachment; filename=download', - 'content-type': ContentType.TEXT, - }[name]; + withTestContext( + async () => { + const url = 'https://example.com/download'; + + const queue = getDownloadQueue() as any; + queue.add.mockClear(); + + const media = await repo.createResource({ + resourceType: 'Media', + status: 'completed', + content: { + contentType: ContentType.TEXT, + url, }, - }, - body, - })); + }); + expect(media).toBeDefined(); + expect(queue.add).toHaveBeenCalled(); + + const body = new Readable(); + body.push('foo'); + body.push(null); + + (fetch as unknown as jest.Mock).mockImplementation(() => ({ + status: 200, + headers: { + get(name: string): string | undefined { + return { + 'content-disposition': 'attachment; filename=download', + 'content-type': ContentType.TEXT, + }[name]; + }, + }, + body, + })); - const job = { id: 1, data: queue.add.mock.calls[0][1] } as unknown as Job; - await execDownloadJob(job); + const job = { id: 1, data: queue.add.mock.calls[0][1] } as unknown as Job; + await execDownloadJob(job); - expect(fetch).toHaveBeenCalledWith(url); - })); + expect(fetch).toHaveBeenCalledWith(url, { + headers: { + 'x-trace-id': '00-12345678901234567890123456789012-3456789012345678-01', + traceparent: '00-12345678901234567890123456789012-3456789012345678-01', + }, + }); + }, + { traceId: '00-12345678901234567890123456789012-3456789012345678-01' } + )); test('Ignore media missing URL', () => withTestContext(async () => { diff --git a/packages/server/src/workers/download.ts b/packages/server/src/workers/download.ts index 7712b23988..6a39fff448 100644 --- a/packages/server/src/workers/download.ts +++ b/packages/server/src/workers/download.ts @@ -4,10 +4,11 @@ import { Job, Queue, QueueBaseOptions, Worker } from 'bullmq'; import fetch from 'node-fetch'; import { Readable } from 'stream'; import { getConfig, MedplumServerConfig } from '../config'; -import { getRequestContext } from '../context'; -import { systemRepo } from '../fhir/repo'; +import { getRequestContext, tryGetRequestContext, tryRunInRequestContext } from '../context'; +import { getSystemRepo } from '../fhir/repo'; import { getBinaryStorage } from '../fhir/storage'; import { globalLogger } from '../logger'; +import { parseTraceparent } from '../traceparent'; /* * The download worker inspects resources, @@ -24,6 +25,8 @@ export interface DownloadJobData { readonly resourceType: string; readonly id: string; readonly url: string; + readonly requestId?: string; + readonly traceId?: string; } const queueName = 'DownloadQueue'; @@ -53,10 +56,14 @@ export function initDownloadWorker(config: MedplumServerConfig): void { }, }); - worker = new Worker(queueName, execDownloadJob, { - ...defaultOptions, - ...config.bullmq, - }); + worker = new Worker( + queueName, + (job) => tryRunInRequestContext(job.data.requestId, job.data.traceId, () => execDownloadJob(job)), + { + ...defaultOptions, + ...config.bullmq, + } + ); worker.on('completed', (job) => globalLogger.info(`Completed job ${job.id} successfully`)); worker.on('failed', (job, err) => globalLogger.info(`Failed job ${job?.id} with ${err}`)); } @@ -102,12 +109,15 @@ export function getDownloadQueue(): Queue | undefined { * @param resource - The resource that was created or updated. */ export async function addDownloadJobs(resource: Resource): Promise { + const ctx = tryGetRequestContext(); for (const attachment of getAttachments(resource)) { if (isExternalUrl(attachment.url)) { await addDownloadJobData({ resourceType: resource.resourceType, id: resource.id as string, url: attachment.url, + requestId: ctx?.requestId, + traceId: ctx?.traceId, }); } } @@ -137,12 +147,8 @@ function isExternalUrl(url: string | undefined): url is string { * @param job - The download job details. */ async function addDownloadJobData(job: DownloadJobData): Promise { - const ctx = getRequestContext(); - ctx.logger.debug(`Adding Download job`); if (queue) { await queue.add(jobName, job); - } else { - ctx.logger.debug(`Download queue not initialized`); } } @@ -151,6 +157,7 @@ async function addDownloadJobData(job: DownloadJobData): Promise { * @param job - The download job details. */ export async function execDownloadJob(job: Job): Promise { + const systemRepo = getSystemRepo(); const ctx = getRequestContext(); const { resourceType, id, url } = job.data; @@ -171,9 +178,20 @@ export async function execDownloadJob(job: Job): Promise return; } + const headers: HeadersInit = {}; + const traceId = job.data.traceId; + if (traceId) { + headers['x-trace-id'] = traceId; + if (parseTraceparent(traceId)) { + headers['traceparent'] = traceId; + } + } + try { ctx.logger.info('Requesting content at: ' + url); - const response = await fetch(url); + const response = await fetch(url, { + headers, + }); ctx.logger.info('Received status: ' + response.status); if (response.status >= 400) { diff --git a/packages/server/src/workers/subscription.test.ts b/packages/server/src/workers/subscription.test.ts index e7817d3830..a619467371 100644 --- a/packages/server/src/workers/subscription.test.ts +++ b/packages/server/src/workers/subscription.test.ts @@ -1,6 +1,15 @@ import { InvokeCommand, LambdaClient } from '@aws-sdk/client-lambda'; -import { ContentType, Operator, createReference, getReferenceString, stringify } from '@medplum/core'; -import { AuditEvent, Bot, Observation, Patient, Project, ProjectMembership, Subscription } from '@medplum/fhirtypes'; +import { ContentType, LogLevel, Operator, createReference, getReferenceString, stringify } from '@medplum/core'; +import { + AccessPolicy, + AuditEvent, + Bot, + Observation, + Patient, + Project, + ProjectMembership, + Subscription, +} from '@medplum/fhirtypes'; import { AwsClientStub, mockClient } from 'aws-sdk-client-mock'; import { Job } from 'bullmq'; import { createHmac, randomUUID } from 'crypto'; @@ -8,7 +17,8 @@ import fetch from 'node-fetch'; import { initAppServices, shutdownApp } from '../app'; import { loadTestConfig } from '../config'; import { getDatabasePool } from '../database'; -import { Repository, systemRepo } from '../fhir/repo'; +import { Repository, getSystemRepo } from '../fhir/repo'; +import { globalLogger } from '../logger'; import { getRedis } from '../redis'; import { createTestProject, withTestContext } from '../test.setup'; import { AuditEventOutcome } from '../util/auditevent'; @@ -17,10 +27,10 @@ import { closeSubscriptionWorker, execSubscriptionJob, getSubscriptionQueue } fr jest.mock('node-fetch'); jest.mock('ioredis'); -let repo: Repository; -let botRepo: Repository; - describe('Subscription Worker', () => { + const systemRepo = getSystemRepo(); + let repo: Repository; + let botRepo: Repository; let mockLambdaClient: AwsClientStub; beforeEach(() => { @@ -47,29 +57,27 @@ describe('Subscription Worker', () => { await initAppServices(config); // Create one simple project with no advanced features enabled - const testProject = await withTestContext(() => - systemRepo.createResource({ - resourceType: 'Project', - name: 'Test Project', - owner: { - reference: 'User/' + randomUUID(), + const { project, client } = await withTestContext(() => + createTestProject({ + withClient: true, + project: { + name: 'Test Project', + features: [], }, }) ); repo = new Repository({ extendedMode: true, - project: testProject.id, - author: { - reference: 'ClientApplication/' + randomUUID(), - }, + projects: [project.id as string], + author: createReference(client), }); // Create another project, this one with bots enabled - const botProjectDetails = await createTestProject(); + const botProjectDetails = await createTestProject({ withClient: true }); botRepo = new Repository({ extendedMode: true, - project: botProjectDetails.project.id, + projects: [botProjectDetails.project.id as string], author: createReference(botProjectDetails.client), }); }); @@ -182,49 +190,54 @@ describe('Subscription Worker', () => { })); test('Send subscription with custom headers', () => - withTestContext(async () => { - const url = 'https://example.com/subscription'; - - const subscription = await repo.createResource({ - resourceType: 'Subscription', - reason: 'test', - status: 'active', - criteria: 'Patient', - channel: { - type: 'rest-hook', - endpoint: url, - header: ['Authorization: Basic xyz'], - }, - }); - expect(subscription).toBeDefined(); - - const queue = getSubscriptionQueue() as any; - queue.add.mockClear(); - - const patient = await repo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family: 'Smith' }], - }); - expect(patient).toBeDefined(); - expect(queue.add).toHaveBeenCalled(); - - (fetch as unknown as jest.Mock).mockImplementation(() => ({ status: 200 })); - - const job = { id: 1, data: queue.add.mock.calls[0][1] } as unknown as Job; - await execSubscriptionJob(job); - - expect(fetch).toHaveBeenCalledWith( - url, - expect.objectContaining({ - method: 'POST', - body: stringify(patient), - headers: { - 'Content-Type': ContentType.FHIR_JSON, - Authorization: 'Basic xyz', + withTestContext( + async () => { + const url = 'https://example.com/subscription'; + + const subscription = await repo.createResource({ + resourceType: 'Subscription', + reason: 'test', + status: 'active', + criteria: 'Patient', + channel: { + type: 'rest-hook', + endpoint: url, + header: ['Authorization: Basic xyz'], }, - }) - ); - })); + }); + expect(subscription).toBeDefined(); + + const queue = getSubscriptionQueue() as any; + queue.add.mockClear(); + + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + }); + expect(patient).toBeDefined(); + expect(queue.add).toHaveBeenCalled(); + + (fetch as unknown as jest.Mock).mockImplementation(() => ({ status: 200 })); + + const job = { id: 1, data: queue.add.mock.calls[0][1] } as unknown as Job; + await execSubscriptionJob(job); + + expect(fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: 'POST', + body: stringify(patient), + headers: { + 'Content-Type': ContentType.FHIR_JSON, + Authorization: 'Basic xyz', + 'x-trace-id': '00-12345678901234567890123456789012-3456789012345678-01', + traceparent: '00-12345678901234567890123456789012-3456789012345678-01', + }, + }) + ); + }, + { traceId: '00-12345678901234567890123456789012-3456789012345678-01' } + )); test('Create-only subscription', () => withTestContext(async () => { @@ -291,173 +304,188 @@ describe('Subscription Worker', () => { })); test('Delete-only subscription', () => - withTestContext(async () => { - const url = 'https://example.com/subscription'; - - const subscription = await repo.createResource({ - resourceType: 'Subscription', - reason: 'test', - status: 'active', - criteria: 'Patient', - channel: { - type: 'rest-hook', - endpoint: url, - }, - extension: [ - { - url: 'https://medplum.com/fhir/StructureDefinition/subscription-supported-interaction', - valueCode: 'delete', - }, - ], - }); - expect(subscription).toBeDefined(); - - // Clear the queue - const queue = getSubscriptionQueue() as any; - queue.add.mockClear(); - - // Create the patient - const patient = await repo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family: 'Smith' }], - }); - expect(patient).toBeDefined(); - - // Create should trigger the subscription - expect(queue.add).not.toHaveBeenCalled(); - - // Update the patient - await repo.updateResource({ ...patient, active: true }); - - // Update should not trigger the subscription - expect(queue.add).not.toHaveBeenCalled(); - - // Delete the patient - await repo.deleteResource('Patient', patient.id as string); - - expect(queue.add).toHaveBeenCalled(); - const job = { id: 1, data: queue.add.mock.calls[0][1] } as unknown as Job; - await execSubscriptionJob(job); - expect(fetch).toHaveBeenCalledWith( - url, - expect.objectContaining({ - method: 'POST', - body: '{}', - headers: { - 'Content-Type': ContentType.FHIR_JSON, - 'X-Medplum-Deleted-Resource': `Patient/${patient.id}`, + withTestContext( + async () => { + const url = 'https://example.com/subscription'; + + const subscription = await repo.createResource({ + resourceType: 'Subscription', + reason: 'test', + status: 'active', + criteria: 'Patient', + channel: { + type: 'rest-hook', + endpoint: url, }, - }) - ); - })); + extension: [ + { + url: 'https://medplum.com/fhir/StructureDefinition/subscription-supported-interaction', + valueCode: 'delete', + }, + ], + }); + expect(subscription).toBeDefined(); + + // Clear the queue + const queue = getSubscriptionQueue() as any; + queue.add.mockClear(); + + // Create the patient + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + }); + expect(patient).toBeDefined(); + + // Create should trigger the subscription + expect(queue.add).not.toHaveBeenCalled(); + + // Update the patient + await repo.updateResource({ ...patient, active: true }); + + // Update should not trigger the subscription + expect(queue.add).not.toHaveBeenCalled(); + + // Delete the patient + await repo.deleteResource('Patient', patient.id as string); + + expect(queue.add).toHaveBeenCalled(); + const job = { id: 1, data: queue.add.mock.calls[0][1] } as unknown as Job; + await execSubscriptionJob(job); + expect(fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: 'POST', + body: '{}', + headers: { + 'Content-Type': ContentType.FHIR_JSON, + 'X-Medplum-Deleted-Resource': `Patient/${patient.id}`, + 'x-trace-id': '00-12345678901234567890123456789012-3456789012345678-01', + traceparent: '00-12345678901234567890123456789012-3456789012345678-01', + }, + }) + ); + }, + { traceId: '00-12345678901234567890123456789012-3456789012345678-01' } + )); test('Send subscriptions with signature', () => - withTestContext(async () => { - const url = 'https://example.com/subscription'; - const secret = '0123456789'; - - const subscription = await repo.createResource({ - resourceType: 'Subscription', - reason: 'test', - status: 'active', - criteria: 'Patient', - channel: { - type: 'rest-hook', - endpoint: url, - }, - extension: [ - { - url: 'https://www.medplum.com/fhir/StructureDefinition/subscription-secret', - valueString: secret, + withTestContext( + async () => { + const url = 'https://example.com/subscription'; + const secret = '0123456789'; + + const subscription = await repo.createResource({ + resourceType: 'Subscription', + reason: 'test', + status: 'active', + criteria: 'Patient', + channel: { + type: 'rest-hook', + endpoint: url, }, - ], - }); - expect(subscription).toBeDefined(); - - const queue = getSubscriptionQueue() as any; - queue.add.mockClear(); - - const patient = await repo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family: 'Smith' }], - }); - expect(patient).toBeDefined(); - expect(queue.add).toHaveBeenCalled(); - - (fetch as unknown as jest.Mock).mockImplementation(() => ({ status: 200 })); - - const body = stringify(patient); - const signature = createHmac('sha256', secret).update(body).digest('hex'); - - const job = { id: 1, data: queue.add.mock.calls[0][1] } as unknown as Job; - await execSubscriptionJob(job); - - expect(fetch).toHaveBeenCalledWith( - url, - expect.objectContaining({ - method: 'POST', - body, - headers: { - 'Content-Type': ContentType.FHIR_JSON, - 'X-Signature': signature, - }, - }) - ); - })); + extension: [ + { + url: 'https://www.medplum.com/fhir/StructureDefinition/subscription-secret', + valueString: secret, + }, + ], + }); + expect(subscription).toBeDefined(); + + const queue = getSubscriptionQueue() as any; + queue.add.mockClear(); + + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + }); + expect(patient).toBeDefined(); + expect(queue.add).toHaveBeenCalled(); + + (fetch as unknown as jest.Mock).mockImplementation(() => ({ status: 200 })); + + const body = stringify(patient); + const signature = createHmac('sha256', secret).update(body).digest('hex'); + + const job = { id: 1, data: queue.add.mock.calls[0][1] } as unknown as Job; + await execSubscriptionJob(job); + + expect(fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: 'POST', + body, + headers: { + 'Content-Type': ContentType.FHIR_JSON, + 'X-Signature': signature, + 'x-trace-id': '00-12345678901234567890123456789012-3456789012345678-01', + traceparent: '00-12345678901234567890123456789012-3456789012345678-01', + }, + }) + ); + }, + { traceId: '00-12345678901234567890123456789012-3456789012345678-01' } + )); test('Send subscriptions with legacy signature extension', () => - withTestContext(async () => { - const url = 'https://example.com/subscription'; - const secret = '0123456789'; - - const subscription = await repo.createResource({ - resourceType: 'Subscription', - reason: 'test', - status: 'active', - criteria: 'Patient', - channel: { - type: 'rest-hook', - endpoint: url, - }, - extension: [ - { - url: 'https://www.medplum.com/fhir/StructureDefinition-subscriptionSecret', - valueString: secret, + withTestContext( + async () => { + const url = 'https://example.com/subscription'; + const secret = '0123456789'; + + const subscription = await repo.createResource({ + resourceType: 'Subscription', + reason: 'test', + status: 'active', + criteria: 'Patient', + channel: { + type: 'rest-hook', + endpoint: url, }, - ], - }); - expect(subscription).toBeDefined(); - - const queue = getSubscriptionQueue() as any; - queue.add.mockClear(); - - const patient = await repo.createResource({ - resourceType: 'Patient', - name: [{ given: ['Alice'], family: 'Smith' }], - }); - expect(patient).toBeDefined(); - expect(queue.add).toHaveBeenCalled(); - - (fetch as unknown as jest.Mock).mockImplementation(() => ({ status: 200 })); - - const body = stringify(patient); - const signature = createHmac('sha256', secret).update(body).digest('hex'); - - const job = { id: 1, data: queue.add.mock.calls[0][1] } as unknown as Job; - await execSubscriptionJob(job); - - expect(fetch).toHaveBeenCalledWith( - url, - expect.objectContaining({ - method: 'POST', - body, - headers: { - 'Content-Type': ContentType.FHIR_JSON, - 'X-Signature': signature, - }, - }) - ); - })); + extension: [ + { + url: 'https://www.medplum.com/fhir/StructureDefinition-subscriptionSecret', + valueString: secret, + }, + ], + }); + expect(subscription).toBeDefined(); + + const queue = getSubscriptionQueue() as any; + queue.add.mockClear(); + + const patient = await repo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + }); + expect(patient).toBeDefined(); + expect(queue.add).toHaveBeenCalled(); + + (fetch as unknown as jest.Mock).mockImplementation(() => ({ status: 200 })); + + const body = stringify(patient); + const signature = createHmac('sha256', secret).update(body).digest('hex'); + + const job = { id: 1, data: queue.add.mock.calls[0][1] } as unknown as Job; + await execSubscriptionJob(job); + + expect(fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: 'POST', + body, + headers: { + 'Content-Type': ContentType.FHIR_JSON, + 'X-Signature': signature, + 'x-trace-id': '00-12345678901234567890123456789012-3456789012345678-01', + traceparent: '00-12345678901234567890123456789012-3456789012345678-01', + }, + }) + ); + }, + { traceId: '00-12345678901234567890123456789012-3456789012345678-01' } + )); test('Ignore non-subscription subscriptions', () => withTestContext(async () => { @@ -1094,7 +1122,7 @@ describe('Subscription Worker', () => { test('AuditEvent has Subscription account details', () => withTestContext(async () => { - const project = randomUUID(); + const project = (await createTestProject()).project.id as string; const account = { reference: 'Organization/' + randomUUID(), }; @@ -1146,15 +1174,15 @@ describe('Subscription Worker', () => { }); expect(bundle.entry?.length).toEqual(1); - const auditEvent = bundle.entry?.[0].resource as AuditEvent; + const auditEvent = bundle.entry?.[0]?.resource as AuditEvent; expect(auditEvent.meta?.account).toBeDefined(); expect(auditEvent.meta?.account?.reference).toEqual(account.reference); expect(auditEvent.entity).toHaveLength(2); })); - test('Audit Event outcome from custom codes', () => + test('AuditEvent outcome from custom codes', () => withTestContext(async () => { - const project = randomUUID(); + const project = (await createTestProject()).project.id as string; const account = { reference: 'Organization/' + randomUUID(), }; @@ -1313,9 +1341,114 @@ describe('Subscription Worker', () => { expect(queue.add).not.toHaveBeenCalled(); })); - test('WebSocket Subscription', () => + test('Subscription -- AccessPolicy check throws (regression in #3978, see #4003)', () => withTestContext(async () => { - const subscription = await repo.createResource({ + globalLogger.level = LogLevel.WARN; + const originalConsoleLog = console.log; + console.log = jest.fn(); + + const url = 'https://example.com/subscription'; + + // Create an access policy in different project + // This should trigger an error when the subscription is executed + const accessPolicy = await repo.createResource({ + resourceType: 'AccessPolicy', + resource: [{ resourceType: 'Patient', readonly: false }], + }); + + const { project, client } = await createTestProject({ + withClient: true, + project: { + name: 'AccessPolicy Throw Project', + owner: { + reference: 'User/' + randomUUID(), + }, + features: [], + }, + membership: { + accessPolicy: createReference(accessPolicy), + }, + }); + + const apTestRepo = new Repository({ + extendedMode: true, + projects: [project.id as string], + author: { + reference: getReferenceString(client), + }, + }); + + const subscription = await apTestRepo.createResource({ + resourceType: 'Subscription', + reason: 'test', + status: 'active', + criteria: 'Patient', + channel: { + type: 'rest-hook', + endpoint: url, + }, + }); + expect(subscription).toBeDefined(); + expect(subscription.id).toBeDefined(); + + // Create the patient + const patient = await apTestRepo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + }); + expect(patient).toBeDefined(); + + // Clear the queue + const queue = getSubscriptionQueue() as any; + queue.add.mockClear(); + + // Clear the queue + queue.add.mockClear(); + + await systemRepo.deleteResource('AccessPolicy', accessPolicy.id as string); + + // Update the patient + const patient2 = await apTestRepo.updateResource({ ...patient, name: [{ given: ['Bob'], family: 'Smith' }] }); + + expect(queue.add).toHaveBeenCalled(); + (fetch as unknown as jest.Mock).mockImplementation(() => ({ status: 200 })); + + const job = { id: 1, data: queue.add.mock.calls[0][1] } as unknown as Job; + await execSubscriptionJob(job); + expect(fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: 'POST', + body: stringify(patient2), + }) + ); + + expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Error occurred while checking access policy')); + + globalLogger.level = LogLevel.NONE; + console.log = originalConsoleLog; + })); + + test('WebSocket Subscription -- Enabled', () => + withTestContext(async () => { + const wsSubProject = await systemRepo.createResource({ + resourceType: 'Project', + name: 'WebSocket Subs Project', + owner: { + reference: 'User/' + randomUUID(), + }, + features: ['websocket-subscriptions'], + }); + + const wsSubRepo = new Repository({ + extendedMode: true, + projects: [wsSubProject.id as string], + author: { + reference: 'ClientApplication/' + randomUUID(), + }, + }); + + const subscription = await wsSubRepo.createResource({ resourceType: 'Subscription', reason: 'test', status: 'active', @@ -1345,7 +1478,7 @@ describe('Subscription Worker', () => { const queue = getSubscriptionQueue() as any; queue.add.mockClear(); - const patient = await repo.createResource({ + const patient = await wsSubRepo.createResource({ resourceType: 'Patient', name: [{ given: ['Alice'], family: 'Smith' }], }); @@ -1361,7 +1494,7 @@ describe('Subscription Worker', () => { queue.add.mockClear(); // Update the patient - await repo.updateResource({ ...patient, active: true }); + await wsSubRepo.updateResource({ ...patient, active: true }); // Update should also trigger the subscription expect(queue.add).toHaveBeenCalled(); @@ -1370,10 +1503,82 @@ describe('Subscription Worker', () => { queue.add.mockClear(); // Delete the patient - await repo.deleteResource('Patient', patient.id as string); + await wsSubRepo.deleteResource('Patient', patient.id as string); expect(queue.add).toHaveBeenCalled(); await deferredPromise; })); + + test('WebSocket Subscription -- Feature Flag Not Enabled', () => + withTestContext(async () => { + globalLogger.level = LogLevel.WARN; + const originalConsoleLog = console.log; + console.log = jest.fn(); + + const noWsSubProject = await systemRepo.createResource({ + resourceType: 'Project', + name: 'No WebSocket Subs Project', + owner: { + reference: 'User/' + randomUUID(), + }, + }); + + const noWsSubRepo = new Repository({ + extendedMode: true, + projects: [noWsSubProject.id as string], + author: { + reference: 'ClientApplication/' + randomUUID(), + }, + }); + + const subscription = await noWsSubRepo.createResource({ + resourceType: 'Subscription', + reason: 'test', + status: 'active', + criteria: 'Patient', + channel: { + type: 'websocket', + }, + }); + expect(subscription).toBeDefined(); + expect(subscription.id).toBeDefined(); + + // Subscribe to the topic + const subscriber = getRedis().duplicate(); + await subscriber.subscribe(subscription.id as string); + + let resolve: () => void; + let reject: (error: Error) => void; + + const deferredPromise = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + + subscriber.on('message', () => { + reject(new Error('Should not have been called')); + }); + + const queue = getSubscriptionQueue() as any; + queue.add.mockClear(); + + const patient = await noWsSubRepo.createResource({ + resourceType: 'Patient', + name: [{ given: ['Alice'], family: 'Smith' }], + }); + expect(patient).toBeDefined(); + expect(queue.add).not.toHaveBeenCalled(); + + // Give some time for the callback to get called (it shouldn't) + setTimeout(() => { + resolve(); + }, 150); + + await deferredPromise; + expect(console.log).toHaveBeenLastCalledWith(expect.stringMatching(/WebSocket Subscriptions/)); + + console.log = originalConsoleLog; + globalLogger.level = LogLevel.NONE; + })); }); diff --git a/packages/server/src/workers/subscription.ts b/packages/server/src/workers/subscription.ts index 7b71dfa353..0d370ebe65 100644 --- a/packages/server/src/workers/subscription.ts +++ b/packages/server/src/workers/subscription.ts @@ -1,29 +1,33 @@ import { + AccessPolicyInteraction, ContentType, createReference, getExtension, getExtensionValue, + getReferenceString, isGone, matchesSearchRequest, normalizeOperationOutcome, OperationOutcomeError, Operator, - parseSearchUrl, + parseSearchRequest, + satisfiedAccessPolicy, serverError, stringify, } from '@medplum/core'; -import { Bot, ProjectMembership, Reference, Resource, Subscription } from '@medplum/fhirtypes'; +import { Bot, Project, ProjectMembership, Reference, Resource, Subscription } from '@medplum/fhirtypes'; import { Job, Queue, QueueBaseOptions, Worker } from 'bullmq'; import { createHmac } from 'crypto'; import fetch, { HeadersInit } from 'node-fetch'; -import { URL } from 'url'; import { MedplumServerConfig } from '../config'; -import { getRequestContext } from '../context'; +import { getLogger, getRequestContext, tryGetRequestContext, tryRunInRequestContext } from '../context'; +import { buildAccessPolicy } from '../fhir/accesspolicy'; import { executeBot } from '../fhir/operations/execute'; -import { systemRepo } from '../fhir/repo'; +import { getSystemRepo, Repository } from '../fhir/repo'; import { globalLogger } from '../logger'; import { getRedis } from '../redis'; import { createSubEventNotification } from '../subscriptions/websockets'; +import { parseTraceparent } from '../traceparent'; import { AuditEventOutcome } from '../util/auditevent'; import { BackgroundJobContext, BackgroundJobInteraction } from './context'; import { createAuditEvent, findProjectMembership, isFhirCriteriaMet, isJobSuccessful } from './utils'; @@ -55,6 +59,8 @@ export interface SubscriptionJobData { readonly versionId: string; readonly interaction: 'create' | 'update' | 'delete'; readonly requestTime: string; + readonly requestId?: string; + readonly traceId?: string; } const queueName = 'SubscriptionQueue'; @@ -84,10 +90,14 @@ export function initSubscriptionWorker(config: MedplumServerConfig): void { }, }); - worker = new Worker(queueName, execSubscriptionJob, { - ...defaultOptions, - ...config.bullmq, - }); + worker = new Worker( + queueName, + (job) => tryRunInRequestContext(job.data.requestId, job.data.traceId, () => execSubscriptionJob(job)), + { + ...defaultOptions, + ...config.bullmq, + } + ); worker.on('completed', (job) => globalLogger.info(`Completed job ${job.id} successfully`)); worker.on('failed', (job, err) => globalLogger.info(`Failed job ${job?.id} with ${err}`)); } @@ -118,6 +128,61 @@ export function getSubscriptionQueue(): Queue | undefined { return queue; } +/** + * Checks if this resource should create a notification for this `Subscription` based on the access policy that should be applied for this `Subscription`. + * The `AccessPolicy` of author's `ProjectMembership` for this resource's `Project` is used when evaluating whether the `AccessPolicy` is satisfied. + * + * Currently we log if the `AccessPolicy` is not satisfied only. + * + * TODO: Actually prevent notifications for `Subscriptions` where the `AccessPolicy` is not satisfied. + * + * @param resource - The resource to evaluate against the `AccessPolicy`. + * @param project - The project containing the resource. + * @param subscription - The `Subscription` to get the `AccessPolicy` for. + */ +async function checkAccessPolicy(resource: Resource, project: Project, subscription: Subscription): Promise { + try { + // Check access policy + const subAuthor = subscription.meta?.author; + if (subAuthor) { + const membership = await findProjectMembership(project.id as string, subAuthor); + if (membership) { + const accessPolicy = await buildAccessPolicy(membership); + const satisfied = satisfiedAccessPolicy(resource, AccessPolicyInteraction.READ, accessPolicy); + if (!satisfied) { + const resourceReference = getReferenceString(resource); + const subReference = getReferenceString(subscription); + const projectReference = getReferenceString(project); + globalLogger.warn( + `[Subscription Access Policy]: Access Policy not satisfied on '${resourceReference}' for '${subReference}'`, + { subscription: subReference, project: projectReference, accessPolicy } + ); + } + } else { + const projectReference = getReferenceString(project); + const authorReference = getReferenceString(subAuthor); + const subReference = getReferenceString(subscription); + globalLogger.warn( + `[Subscription Access Policy]: No membership for subscription author '${authorReference}' in project '${projectReference}'`, + { subscription: subReference } + ); + } + } else { + // Log it if there is no author for this Subscription (this is not good) + globalLogger.warn( + `[Subscription Access Policy]: No author for subscription '${getReferenceString(subscription)}'` + ); + } + } catch (err: unknown) { + const resourceReference = getReferenceString(resource); + const subReference = getReferenceString(subscription); + globalLogger.warn( + `[Subscription Access Policy]: Error occurred while checking access policy for resource '${resourceReference}' against '${subReference}'`, + { error: err, subscription: subReference } + ); + } +} + /** * Adds all subscription jobs for a given resource. * @@ -134,17 +199,41 @@ export function getSubscriptionQueue(): Queue | undefined { * @param context - The background job context. */ export async function addSubscriptionJobs(resource: Resource, context: BackgroundJobContext): Promise { - const ctx = getRequestContext(); if (resource.resourceType === 'AuditEvent') { // Never send subscriptions for audit events return; } + + const ctx = tryGetRequestContext(); + const logger = getLogger(); + const systemRepo = getSystemRepo(); + let project: Project | undefined; + try { + const projectId = resource.meta?.project; + if (projectId) { + project = await systemRepo.readResource('Project', projectId); + } + } catch (err: unknown) { + const resourceReference = getReferenceString(resource); + globalLogger.error(`[Subscription]: No project found for '${resourceReference}' -- something is very wrong.`, { + error: err, + resource: resourceReference, + }); + return; + } + if (!project) { + logger.warn(`[Subscription Access Policy]: No project for resource '${getReferenceString(resource)}'`); + return; + } + const requestTime = new Date().toISOString(); - const subscriptions = await getSubscriptions(resource); - ctx.logger.debug(`Evaluate ${subscriptions.length} subscription(s)`); + const subscriptions = await getSubscriptions(resource, project); + logger.debug(`Evaluate ${subscriptions.length} subscription(s)`); + for (const subscription of subscriptions) { const criteria = await matchesCriteria(resource, subscription, context); if (criteria) { + await checkAccessPolicy(resource, project, subscription); await addSubscriptionJobData({ subscriptionId: subscription.id as string, resourceType: resource.resourceType, @@ -152,6 +241,8 @@ export async function addSubscriptionJobs(resource: Resource, context: Backgroun versionId: resource.meta?.versionId as string, interaction: context.interaction, requestTime, + requestId: ctx?.requestId, + traceId: ctx?.traceId, }); } } @@ -186,7 +277,7 @@ async function matchesCriteria( return false; } - const searchRequest = parseSearchUrl(new URL(subscriptionCriteria, 'https://api.medplum.com/')); + const searchRequest = parseSearchRequest(subscriptionCriteria); if (resource.resourceType !== searchRequest.resourceType) { ctx.logger.debug( `Ignore rest hook for different resourceType (wanted "${searchRequest.resourceType}", received "${resource.resourceType}")` @@ -225,7 +316,7 @@ function matchesChannelType(subscription: Subscription): boolean { if (channelType === 'rest-hook') { const url = subscription.channel?.endpoint; if (!url) { - getRequestContext().logger.debug(`Ignore rest-hook missing URL`); + getLogger().debug(`Ignore rest-hook missing URL`); return false; } @@ -244,25 +335,20 @@ function matchesChannelType(subscription: Subscription): boolean { * @param job - The subscription job details. */ async function addSubscriptionJobData(job: SubscriptionJobData): Promise { - const ctx = getRequestContext(); - ctx.logger.debug(`Adding Subscription job`); if (queue) { await queue.add(jobName, job); - } else { - ctx.logger.debug(`Subscription queue not initialized`); } } /** * Loads the list of all subscriptions in this repository. * @param resource - The resource that was created or updated. + * @param project - The project that contains this resource. * @returns The list of all subscriptions in this repository. */ -async function getSubscriptions(resource: Resource): Promise { - const project = resource.meta?.project; - if (!project) { - return []; - } +async function getSubscriptions(resource: Resource, project: Project): Promise { + const projectId = project.id as string; + const systemRepo = getSystemRepo(); const subscriptions = await systemRepo.searchResources({ resourceType: 'Subscription', count: 1000, @@ -270,7 +356,7 @@ async function getSubscriptions(resource: Resource): Promise { { code: '_project', operator: Operator.EQUALS, - value: project, + value: projectId, }, { code: 'status', @@ -279,10 +365,20 @@ async function getSubscriptions(resource: Resource): Promise { }, ], }); - const inMemorySubscriptionsStr = await getRedis().get(`medplum:subscriptions:r4:project:${project}`); - if (inMemorySubscriptionsStr) { - const inMemorySubscriptions = JSON.parse(inMemorySubscriptionsStr) as Subscription[]; - subscriptions.push(...inMemorySubscriptions); + const redisOnlySubRefStrs = await getRedis().smembers(`medplum:subscriptions:r4:project:${projectId}:active`); + if (redisOnlySubRefStrs.length) { + const redisOnlySubStrs = await getRedis().mget(redisOnlySubRefStrs); + if (project.features?.includes('websocket-subscriptions')) { + const subArrStr = '[' + redisOnlySubStrs.filter(Boolean).join(',') + ']'; + const inMemorySubs = JSON.parse(subArrStr) as { resource: Subscription; projectId: string }[]; + for (const { resource } of inMemorySubs) { + subscriptions.push(resource); + } + } else { + globalLogger.warn( + `[WebSocket Subscriptions]: subscription for resource '${getReferenceString(resource)}' might have been fired but WebSocket subscriptions are not enabled for project '${project.name ?? getReferenceString(project)}'` + ); + } } return subscriptions; } @@ -292,9 +388,10 @@ async function getSubscriptions(resource: Resource): Promise { * @param job - The subscription job details. */ export async function execSubscriptionJob(job: Job): Promise { + const systemRepo = getSystemRepo(); const { subscriptionId, resourceType, id, versionId, interaction, requestTime } = job.data; - const subscription = await tryGetSubscription(subscriptionId); + const subscription = await tryGetSubscription(systemRepo, subscriptionId); if (!subscription) { // If the subscription was deleted, then stop processing it. return; @@ -306,7 +403,7 @@ export async function execSubscriptionJob(job: Job): Promis } if (interaction !== 'delete') { - const currentVersion = await tryGetCurrentVersion(resourceType, id); + const currentVersion = await tryGetCurrentVersion(systemRepo, resourceType, id); if (!currentVersion) { // If the resource was deleted, then stop processing it. return; @@ -343,7 +440,7 @@ export async function execSubscriptionJob(job: Job): Promis } } -async function tryGetSubscription(subscriptionId: string): Promise { +async function tryGetSubscription(systemRepo: Repository, subscriptionId: string): Promise { try { return await systemRepo.readResource('Subscription', subscriptionId); } catch (err) { @@ -357,7 +454,11 @@ async function tryGetSubscription(subscriptionId: string): Promise { +async function tryGetCurrentVersion( + systemRepo: Repository, + resourceType: string, + id: string +): Promise { try { return await systemRepo.readResource(resourceType, id); } catch (err) { @@ -398,6 +499,13 @@ async function sendRestHook( if (interaction === 'delete') { headers['X-Medplum-Deleted-Resource'] = `${resource.resourceType}/${resource.id}`; } + const traceId = job.data.traceId; + if (traceId) { + headers['x-trace-id'] = traceId; + if (parseTraceparent(traceId)) { + headers['traceparent'] = traceId; + } + } const body = interaction === 'delete' ? '{}' : stringify(resource); let error: Error | undefined = undefined; @@ -481,14 +589,16 @@ async function execBot( interaction: BackgroundJobInteraction, requestTime: string ): Promise { + const ctx = getRequestContext(); const url = subscription.channel?.endpoint as string; if (!url) { // This can happen if a user updates the Subscription after the job is created. - getRequestContext().logger.debug(`Ignore rest hook missing URL`); + ctx.logger.debug(`Ignore rest hook missing URL`); return; } // URL should be a Bot reference string + const systemRepo = getSystemRepo(); const bot = await systemRepo.readReference({ reference: url }); const project = bot.meta?.project as string; @@ -510,6 +620,7 @@ async function execBot( input: interaction === 'delete' ? { deletedResource: resource } : resource, contentType: ContentType.FHIR_JSON, requestTime, + traceId: ctx.traceId, }); } diff --git a/packages/server/src/workers/utils.ts b/packages/server/src/workers/utils.ts index 9fb3b2b53e..01054eb180 100644 --- a/packages/server/src/workers/utils.ts +++ b/packages/server/src/workers/utils.ts @@ -10,11 +10,12 @@ import { Resource, Subscription, } from '@medplum/fhirtypes'; -import { getRequestContext } from '../context'; -import { systemRepo } from '../fhir/repo'; +import { getLogger } from '../context'; +import { getSystemRepo } from '../fhir/repo'; import { AuditEventOutcome } from '../util/auditevent'; export function findProjectMembership(project: string, profile: Reference): Promise { + const systemRepo = getSystemRepo(); return systemRepo.searchOne({ resourceType: 'ProjectMembership', filters: [ @@ -49,6 +50,7 @@ export async function createAuditEvent( subscription?: Subscription, bot?: Bot ): Promise { + const systemRepo = getSystemRepo(); const auditedEvent = subscription ?? resource; await systemRepo.createResource({ @@ -124,6 +126,7 @@ export async function isFhirCriteriaMet(subscription: Subscription, currentResou } async function getPreviousResource(currentResource: Resource): Promise { + const systemRepo = getSystemRepo(); const history = await systemRepo.readHistory(currentResource.resourceType, currentResource?.id as string); return history.entry?.find((_, idx) => { @@ -155,7 +158,7 @@ export function isJobSuccessful(subscription: Subscription, status: number): boo const lowerBound = Number(codeRange[0]); const upperBound = Number(codeRange[1]); if (!(Number.isInteger(lowerBound) && Number.isInteger(upperBound))) { - getRequestContext().logger.debug( + getLogger().debug( `${lowerBound} and ${upperBound} aren't an integer, configured status codes need to be changed. Resorting to default codes` ); return defaultStatusCheck(status); @@ -166,7 +169,7 @@ export function isJobSuccessful(subscription: Subscription, status: number): boo } else { const codeValue = Number(code); if (!Number.isInteger(codeValue)) { - getRequestContext().logger.debug( + getLogger().debug( `${code} isn't an integer, configured status codes need to be changed. Resorting to default codes` ); return defaultStatusCheck(status); diff --git a/scripts/build.sh b/scripts/build.sh index 35693e8b2f..3505e523f5 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -9,7 +9,7 @@ set -e set -x # Set node options -export NODE_OPTIONS='--max-old-space-size=5120' +export NODE_OPTIONS='--max-old-space-size=8192' # Diagnostics node --version diff --git a/scripts/cicd-deploy.sh b/scripts/cicd-deploy.sh index 1992497349..673ffc9e06 100755 --- a/scripts/cicd-deploy.sh +++ b/scripts/cicd-deploy.sh @@ -6,6 +6,9 @@ # Inspects files changed in the most recent commit # and deploys the appropriate service +# Echo commands +set -x + COMMIT_MESSAGE=$(git log -1 --pretty=%B) echo "$COMMIT_MESSAGE" @@ -77,15 +80,17 @@ fi # Send a slack message # +ESCAPED_COMMIT_MESSAGE=$(echo "$COMMIT_MESSAGE" | sed 's/"/\\"/g') + read -r -d '' PAYLOAD <<- EOM { - "text": "Deploying ${COMMIT_MESSAGE}", + "text": "Deploying ${ESCAPED_COMMIT_MESSAGE}", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", - "text": "Deploying ${COMMIT_MESSAGE}\\n\\n* Deploy app: ${DEPLOY_APP}\\n* Deploy graphiql: ${DEPLOY_GRAPHIQL}\\n* Deploy server: ${DEPLOY_SERVER}" + "text": "Deploying ${ESCAPED_COMMIT_MESSAGE}\\n\\n* Deploy app: ${DEPLOY_APP}\\n* Deploy graphiql: ${DEPLOY_GRAPHIQL}\\n* Deploy server: ${DEPLOY_SERVER}" } } ] diff --git a/scripts/prepare-release.sh b/scripts/prepare-release.sh index bc33c6868e..f65f25f050 100755 --- a/scripts/prepare-release.sh +++ b/scripts/prepare-release.sh @@ -42,6 +42,7 @@ sed -i'' -E -e "s/\"version\": \"[^\"]+\"/\"version\": \"$NEW_VERSION\"/g" packa # Set version in all examples/\*/package.json files find examples -name 'package.json' -print0 | xargs -0 sed -i'' -E -e "s/(\"@medplum\/[^\"]+\"): \"[^\"]+\"/\1: \"$NEW_VERSION\"/g" +find packages -name 'package.json' -print0 | xargs -0 sed -i'' -E -e "s/(\"@medplum\/[^\"]+\"): \"[^\"]+\"/\1: \"$NEW_VERSION\"/g" # Run `npm version $version --workspaces` npm version "$NEW_VERSION" --workspaces diff --git a/sonar-project.properties b/sonar-project.properties index 2f4aa4b1ef..6831a363b9 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,7 +1,7 @@ sonar.organization=medplum sonar.projectKey=medplum_medplum sonar.projectName=Medplum -sonar.projectVersion=3.0.2 +sonar.projectVersion=3.0.4 sonar.sources=packages sonar.sourceEncoding=UTF-8 sonar.exclusions=**/node_modules/**,\