fhir-resource-diff 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +397 -0
- package/dist/chunk-2UUKQJDB.js +2318 -0
- package/dist/chunk-2UUKQJDB.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +615 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/index.d.ts +354 -0
- package/dist/core/index.js +77 -0
- package/dist/core/index.js.map +1 -0
- package/package.json +89 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/cli/index.ts","../../src/cli/commands/compare.ts","../../src/presets/ignore-fields.ts","../../src/presets/normalization.ts","../../src/presets/index.ts","../../src/cli/utils/read-file.ts","../../src/cli/utils/read-stdin.ts","../../src/cli/utils/resolve-version.ts","../../src/cli/commands/validate.ts","../../src/cli/commands/normalize.ts","../../src/cli/commands/info.ts","../../src/cli/commands/list-resources.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport { registerCompareCommand } from \"@/cli/commands/compare.js\";\nimport { registerValidateCommand } from \"@/cli/commands/validate.js\";\nimport { registerNormalizeCommand } from \"@/cli/commands/normalize.js\";\nimport { registerInfoCommand } from \"@/cli/commands/info.js\";\nimport { registerListResourcesCommand } from \"@/cli/commands/list-resources.js\";\n\nconst program = new Command();\nprogram\n .name(\"fhir-resource-diff\")\n .description(\"CLI for diffing and validating FHIR JSON resources\")\n .version(\"0.1.0\");\n\nregisterCompareCommand(program);\nregisterValidateCommand(program);\nregisterNormalizeCommand(program);\nregisterInfoCommand(program);\nregisterListResourcesCommand(program);\n\nprogram.parse();\n","import type { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport { parseJson } from \"@/core/parse.js\";\nimport { validate } from \"@/core/validate.js\";\nimport { diff } from \"@/core/diff.js\";\nimport { normalize } from \"@/core/normalize.js\";\nimport { formatText } from \"@/formatters/text.js\";\nimport { formatJson } from \"@/formatters/json.js\";\nimport { formatMarkdown } from \"@/formatters/markdown.js\";\nimport {\n getIgnorePreset,\n getNormalizationPreset,\n mergeIgnorePresets,\n} from \"@/presets/index.js\";\nimport type { DiffOptions, FhirResource } from \"@/core/types.js\";\nimport { readFileOrExit } from \"@/cli/utils/read-file.js\";\nimport { parseVersionFlag } from \"@/cli/utils/resolve-version.js\";\nimport { detectFhirVersion, resolveFhirVersion } from \"@/core/fhir-version.js\";\nimport { getResourceDocUrl } from \"@/core/resource-registry.js\";\nimport { summarizeDiff } from \"@/core/summary.js\";\nimport { buildEnvelope } from \"@/core/envelope.js\";\n\ntype OutputFormat = \"text\" | \"json\" | \"markdown\";\n\ninterface CompareOptions {\n format: OutputFormat;\n ignore?: string;\n preset?: string;\n normalize?: string;\n color: boolean;\n exitOnDiff: boolean;\n fhirVersion?: string;\n quiet: boolean;\n envelope: boolean;\n}\n\nconst SECTION_HEADERS = new Set([\"Changed:\", \"Added:\", \"Removed:\", \"Type-changed:\"]);\n\n/**\n * Apply color post-processing to text-format diff output.\n * Colorizes section headers, arrows, and added/removed lines.\n */\nfunction applyColor(text: string): string {\n const lines = text.split(\"\\n\");\n let currentSection: string | null = null;\n const colored: string[] = [];\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n if (SECTION_HEADERS.has(trimmed)) {\n currentSection = trimmed;\n colored.push(pc.yellow(line));\n continue;\n }\n\n // Empty lines reset section tracking\n if (trimmed === \"\") {\n currentSection = null;\n colored.push(line);\n continue;\n }\n\n // Dim the arrow in changed/type-changed lines\n let processed = line;\n if (line.includes(\" \\u2192 \")) {\n processed = line.replace(\n \" \\u2192 \",\n ` ${pc.dim(\"\\u2192\")} `,\n );\n }\n\n if (currentSection === \"Added:\") {\n colored.push(pc.green(processed));\n } else if (currentSection === \"Removed:\") {\n colored.push(pc.red(processed));\n } else {\n colored.push(processed);\n }\n }\n\n return colored.join(\"\\n\");\n}\n\nfunction parseOrExit(filePath: string, raw: string): FhirResource {\n const result = parseJson(raw);\n if (!result.success) {\n process.stderr.write(`Error: \"${filePath}\" is not valid FHIR JSON: ${result.error}\\n`);\n process.exit(2);\n }\n return result.resource;\n}\n\n/**\n * Registers the compare command on the given Commander program.\n */\nexport function registerCompareCommand(program: Command): void {\n program\n .command(\"compare <file-a> <file-b>\")\n .description(\"Compare two FHIR JSON resource files and report differences\")\n .option(\n \"--format <fmt>\",\n 'Output format: text | json | markdown (default: \"text\")',\n \"text\",\n )\n .option(\n \"--ignore <paths>\",\n \"Comma-separated field paths to exclude from comparison\",\n )\n .option(\"--preset <name>\", \"Named ignore preset (e.g. metadata, clinical, strict)\")\n .option(\"--normalize <name>\", \"Named normalization preset to apply before diffing\")\n .option(\"--no-color\", \"Disable color output\")\n .option(\"--exit-on-diff\", \"Exit with code 1 if differences are found\")\n .option(\"--fhir-version <ver>\", \"FHIR version: R4 | R4B | R5 (default: auto-detect or R4)\")\n .option(\"--quiet\", \"Suppress all stdout output. Only exit code indicates result.\")\n .option(\"--envelope\", \"Wrap JSON output in a metadata envelope (requires --format json)\")\n .action(async (fileA: string, fileB: string, opts: CompareOptions) => {\n if (fileA === \"-\" && fileB === \"-\") {\n process.stderr.write(\n \"Error: cannot read both resources from stdin. Provide at least one file path.\\n\",\n );\n process.exit(2);\n }\n\n // 1. Read files\n const rawA = await readFileOrExit(fileA);\n const rawB = await readFileOrExit(fileB);\n\n // 2. Parse — exit(2) on failure\n let resourceA: FhirResource = parseOrExit(fileA, rawA);\n let resourceB: FhirResource = parseOrExit(fileB, rawB);\n\n // 3. Resolve FHIR version\n const explicitVersion = parseVersionFlag(opts.fhirVersion);\n const resolvedVersionA = resolveFhirVersion(explicitVersion, resourceA);\n void resolveFhirVersion(explicitVersion, resourceB);\n\n if (explicitVersion === undefined) {\n const detectedA = detectFhirVersion(resourceA);\n const detectedB = detectFhirVersion(resourceB);\n if (detectedA !== undefined && detectedB !== undefined && detectedA !== detectedB) {\n process.stderr.write(\n `Warning: resources appear to be from different FHIR versions (${detectedA} vs ${detectedB})\\n`,\n );\n }\n }\n\n // 4. Validate — warn but do not exit\n const validateVersion = explicitVersion !== undefined ? resolvedVersionA : undefined;\n const validA = validate(resourceA, validateVersion);\n if (!validA.valid) {\n process.stderr.write(`Warning: \"${fileA}\" has validation issues\\n`);\n }\n\n const validB = validate(resourceB, validateVersion);\n if (!validB.valid) {\n process.stderr.write(`Warning: \"${fileB}\" has validation issues\\n`);\n }\n\n // 5. Normalize if requested\n if (opts.normalize !== undefined) {\n const normPreset = getNormalizationPreset(opts.normalize);\n if (normPreset === undefined) {\n process.stderr.write(\n `Error: Unknown normalization preset \"${opts.normalize}\". Available: canonical, none\\n`,\n );\n process.exit(2);\n }\n resourceA = normalize(resourceA, normPreset.options);\n resourceB = normalize(resourceB, normPreset.options);\n }\n\n // 6. Build DiffOptions from --ignore and --preset\n let ignorePaths: string[] = [];\n\n if (opts.preset !== undefined) {\n const namedPreset = getIgnorePreset(opts.preset);\n if (namedPreset === undefined) {\n process.stderr.write(\n `Error: Unknown ignore preset \"${opts.preset}\". Available: metadata, clinical, strict\\n`,\n );\n process.exit(2);\n }\n ignorePaths = mergeIgnorePresets(namedPreset);\n }\n\n if (opts.ignore !== undefined && opts.ignore.trim() !== \"\") {\n const manualPaths = opts.ignore.split(\",\").map((p) => p.trim()).filter((p) => p !== \"\");\n ignorePaths = Array.from(new Set([...ignorePaths, ...manualPaths]));\n }\n\n const diffOptions: DiffOptions = ignorePaths.length > 0\n ? { ignorePaths }\n : {};\n\n // 7. Diff\n const result = diff(resourceA, resourceB, diffOptions);\n\n // 8. Resolve envelope flag — only valid with --format json\n let useEnvelope = opts.envelope;\n if (useEnvelope && opts.format !== \"json\") {\n process.stderr.write(\n \"Warning: --envelope requires --format json. Ignoring --envelope.\\n\",\n );\n useEnvelope = false;\n }\n\n // 9. Format\n const colorsEnabled = opts.color && process.env[\"NO_COLOR\"] === undefined;\n const format = opts.format;\n\n let output: string;\n if (format === \"json\") {\n if (useEnvelope) {\n const summary = summarizeDiff(result);\n const documentation = getResourceDocUrl(result.resourceType, resolvedVersionA);\n const envelopeResult = { ...result, summary, documentation };\n const envelope = buildEnvelope(\"compare\", resolvedVersionA, envelopeResult);\n output = JSON.stringify(envelope, null, 2);\n } else {\n output = formatJson(result);\n }\n } else if (format === \"markdown\") {\n output = formatMarkdown(result);\n } else {\n // Default: text\n const textOutput = formatText(result);\n output = colorsEnabled ? applyColor(textOutput) : textOutput;\n }\n\n // 10. Write to stdout (suppressed when --quiet)\n if (!opts.quiet) {\n process.stdout.write(output + \"\\n\");\n }\n\n // 11. Exit code\n if (opts.exitOnDiff && !result.identical) {\n process.exit(1);\n }\n });\n}\n","import type { IgnorePreset } from \"@/core/types.js\";\n\nexport const IGNORE_METADATA: IgnorePreset = {\n name: \"metadata\",\n description: \"Ignore id, meta, and text fields — focus on clinical content\",\n paths: [\"id\", \"meta\", \"text\"],\n};\n\nexport const IGNORE_CLINICAL: IgnorePreset = {\n name: \"clinical\",\n description: \"Ignore metadata and extensions — compare core clinical fields only\",\n paths: [\"id\", \"meta\", \"text\", \"extension\", \"modifierExtension\"],\n};\n\nexport const IGNORE_STRICT: IgnorePreset = {\n name: \"strict\",\n description: \"Ignore nothing — compare all fields\",\n paths: [],\n};\n","import type { NormalizationPreset } from \"@/core/types.js\";\n\nexport const NORMALIZE_CANONICAL: NormalizationPreset = {\n name: \"canonical\",\n description: \"Sort keys, trim strings, normalize dates — maximally comparable form\",\n options: {\n sortObjectKeys: true,\n trimStrings: true,\n normalizeDates: true,\n },\n};\n\nexport const NORMALIZE_NONE: NormalizationPreset = {\n name: \"none\",\n description: \"No normalization — compare exact values\",\n options: {},\n};\n","import type { IgnorePreset, NormalizationPreset } from \"@/core/types.js\";\nexport { IGNORE_METADATA, IGNORE_CLINICAL, IGNORE_STRICT } from \"@/presets/ignore-fields.js\";\nexport { NORMALIZE_CANONICAL, NORMALIZE_NONE } from \"@/presets/normalization.js\";\n\nimport { IGNORE_METADATA, IGNORE_CLINICAL, IGNORE_STRICT } from \"@/presets/ignore-fields.js\";\nimport { NORMALIZE_CANONICAL, NORMALIZE_NONE } from \"@/presets/normalization.js\";\n\nconst IGNORE_PRESETS: Record<string, IgnorePreset> = {\n [IGNORE_METADATA.name]: IGNORE_METADATA,\n [IGNORE_CLINICAL.name]: IGNORE_CLINICAL,\n [IGNORE_STRICT.name]: IGNORE_STRICT,\n};\n\nconst NORMALIZATION_PRESETS: Record<string, NormalizationPreset> = {\n [NORMALIZE_CANONICAL.name]: NORMALIZE_CANONICAL,\n [NORMALIZE_NONE.name]: NORMALIZE_NONE,\n};\n\n/**\n * Look up an ignore preset by name. Returns undefined if not found.\n */\nexport function getIgnorePreset(name: string): IgnorePreset | undefined {\n return IGNORE_PRESETS[name];\n}\n\n/**\n * Look up a normalization preset by name. Returns undefined if not found.\n */\nexport function getNormalizationPreset(name: string): NormalizationPreset | undefined {\n return NORMALIZATION_PRESETS[name];\n}\n\n/**\n * Merge multiple ignore presets into a single deduplicated path list.\n */\nexport function mergeIgnorePresets(...presets: IgnorePreset[]): string[] {\n const seen = new Set<string>();\n for (const preset of presets) {\n for (const path of preset.paths) {\n seen.add(path);\n }\n }\n return Array.from(seen);\n}\n","import { readFileSync } from \"node:fs\";\nimport { readStdinSync } from \"@/cli/utils/read-stdin.js\";\n\n/**\n * Reads a file and returns its contents as a string.\n * Pass \"-\" to read from stdin (async — waits for the full stream to close).\n * Exits with code 2 if the file cannot be read.\n */\nexport async function readFileOrExit(filePath: string): Promise<string> {\n if (filePath === \"-\") {\n try {\n return await readStdinSync();\n } catch {\n process.stderr.write(\n \"Error: no input on stdin. Pipe data to stdin or provide a file path.\\nUsage: cat resource.json | fhir-resource-diff validate -\\n\",\n );\n process.exit(2);\n }\n }\n try {\n return readFileSync(filePath, \"utf-8\");\n } catch (e) {\n process.stderr.write(\n `Error: Cannot read file \"${filePath}\": ${e instanceof Error ? e.message : String(e)}\\n`,\n );\n process.exit(2);\n }\n}\n","/**\n * Reads all of stdin and returns it as a UTF-8 string.\n * Throws if stdin is a TTY (no piped input).\n *\n * Uses an async stream approach instead of readFileSync(0) because Node.js ESM\n * puts stdin in non-blocking mode at startup. readFileSync(0) throws EAGAIN when\n * the pipe source (e.g. curl) hasn't written data yet, even though data is coming.\n */\nexport function readStdinSync(): Promise<string> {\n if (process.stdin.isTTY) {\n return Promise.reject(new Error(\"No input on stdin (stdin is a TTY)\"));\n }\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n process.stdin.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n process.stdin.on(\"end\", () => resolve(Buffer.concat(chunks).toString(\"utf-8\")));\n process.stdin.on(\"error\", reject);\n });\n}\n","import {\n isSupportedFhirVersion,\n type FhirVersion,\n} from \"@/core/fhir-version.js\";\n\n/**\n * Parses and validates the --fhir-version CLI flag value.\n * Returns a FhirVersion if valid, undefined if the flag was not provided.\n * Writes an error to stderr and exits with code 2 if the value is invalid.\n */\nexport function parseVersionFlag(value: string | undefined): FhirVersion | undefined {\n if (value === undefined) return undefined;\n if (isSupportedFhirVersion(value)) return value;\n process.stderr.write(\n `Error: Unknown FHIR version \"${value}\". Supported: R4, R4B, R5\\n`,\n );\n process.exit(2);\n}\n","import type { Command } from \"commander\";\nimport { parseJson } from \"@/core/parse.js\";\nimport { validate } from \"@/core/validate.js\";\nimport { formatValidationText } from \"@/formatters/text.js\";\nimport { formatValidationJson } from \"@/formatters/json.js\";\nimport { readFileOrExit } from \"@/cli/utils/read-file.js\";\nimport { parseVersionFlag } from \"@/cli/utils/resolve-version.js\";\nimport { resolveFhirVersion } from \"@/core/fhir-version.js\";\nimport { getResourceDocUrl } from \"@/core/resource-registry.js\";\nimport { buildEnvelope } from \"@/core/envelope.js\";\n\ntype OutputFormat = \"text\" | \"json\";\n\ninterface ValidateOptions {\n format: OutputFormat;\n fhirVersion?: string;\n quiet: boolean;\n envelope: boolean;\n}\n\n/**\n * Registers the validate command on the given Commander program.\n */\nexport function registerValidateCommand(program: Command): void {\n program\n .command(\"validate <file>\")\n .description(\"Validate a FHIR JSON resource file\")\n .option(\n \"--format <fmt>\",\n 'Output format: text | json (default: \"text\")',\n \"text\",\n )\n .option(\"--fhir-version <ver>\", \"FHIR version: R4 | R4B | R5 (default: auto-detect or R4)\")\n .option(\"--quiet\", \"Suppress all stdout output. Only exit code indicates result.\")\n .option(\"--envelope\", \"Wrap JSON output in a metadata envelope (requires --format json)\")\n .action(async (file: string, opts: ValidateOptions) => {\n // 1. Read file\n const raw = await readFileOrExit(file);\n\n // 2. Parse — exit(2) on failure\n const parsed = parseJson(raw);\n if (!parsed.success) {\n process.stderr.write(`Error: \"${file}\" is not valid FHIR JSON: ${parsed.error}\\n`);\n process.exit(2);\n }\n\n // 3. Resolve FHIR version\n const explicitVersion = parseVersionFlag(opts.fhirVersion);\n const resolvedVersion = resolveFhirVersion(explicitVersion, parsed.resource);\n\n // 4. Validate (version-aware)\n const result = validate(parsed.resource, explicitVersion !== undefined ? resolvedVersion : undefined);\n\n // 5. Resolve envelope flag — only valid with --format json\n let useEnvelope = opts.envelope;\n if (useEnvelope && opts.format !== \"json\") {\n process.stderr.write(\n \"Warning: --envelope requires --format json. Ignoring --envelope.\\n\",\n );\n useEnvelope = false;\n }\n\n // 6. Format\n const format = opts.format;\n let output: string;\n if (format === \"json\") {\n if (useEnvelope) {\n const documentation = getResourceDocUrl(parsed.resource.resourceType, resolvedVersion);\n const envelopeResult = { ...result, documentation };\n const envelope = buildEnvelope(\"validate\", resolvedVersion, envelopeResult);\n output = JSON.stringify(envelope, null, 2);\n } else {\n output = formatValidationJson(result);\n }\n } else {\n output = formatValidationText(result);\n }\n\n // 7. Write to stdout (suppressed when --quiet)\n if (!opts.quiet) {\n process.stdout.write(output + \"\\n\");\n }\n\n // 8. Exit: 1 if error-severity issues present, 0 otherwise\n const hasErrors = result.valid === false && result.errors.some((e) => e.severity === \"error\");\n process.exit(hasErrors ? 1 : 0);\n });\n}\n","import type { Command } from \"commander\";\nimport { writeFileSync } from \"node:fs\";\nimport { parseJson } from \"@/core/parse.js\";\nimport { normalize } from \"@/core/normalize.js\";\nimport { getNormalizationPreset } from \"@/presets/index.js\";\nimport { readFileOrExit } from \"@/cli/utils/read-file.js\";\nimport { parseVersionFlag } from \"@/cli/utils/resolve-version.js\";\nimport { resolveFhirVersion } from \"@/core/fhir-version.js\";\n\nconst DEFAULT_PRESET_NAME = \"canonical\";\n\ninterface NormalizeOptions {\n preset: string;\n output?: string;\n fhirVersion?: string;\n quiet: boolean;\n}\n\n/**\n * Registers the normalize command on the given Commander program.\n */\nexport function registerNormalizeCommand(program: Command): void {\n program\n .command(\"normalize <file>\")\n .description(\"Normalize a FHIR JSON resource file and output the result\")\n .option(\n \"--preset <name>\",\n `Named normalization preset (default: \"${DEFAULT_PRESET_NAME}\")`,\n DEFAULT_PRESET_NAME,\n )\n .option(\"--output <path>\", \"Write output to a file instead of stdout\")\n .option(\"--fhir-version <ver>\", \"FHIR version: R4 | R4B | R5 (default: auto-detect or R4)\")\n .option(\"--quiet\", \"Suppress all stdout output.\")\n .action(async (file: string, opts: NormalizeOptions) => {\n // 1. Read file\n const raw = await readFileOrExit(file);\n\n // 2. Parse — exit(2) on failure\n const parsed = parseJson(raw);\n if (!parsed.success) {\n process.stderr.write(`Error: \"${file}\" is not valid FHIR JSON: ${parsed.error}\\n`);\n process.exit(2);\n }\n\n // 3. Resolve FHIR version (result will be used by spec 17 version-aware validation)\n const explicitVersion = parseVersionFlag(opts.fhirVersion);\n void resolveFhirVersion(explicitVersion, parsed.resource);\n\n // 4. Look up normalization preset\n const presetName = opts.preset;\n const preset = getNormalizationPreset(presetName);\n if (preset === undefined) {\n process.stderr.write(\n `Error: Unknown normalization preset \"${presetName}\". Available: canonical, none\\n`,\n );\n process.exit(2);\n }\n\n // 5. Normalize\n const normalized = normalize(parsed.resource, preset.options);\n\n // 6. Format as pretty-printed JSON\n const output = JSON.stringify(normalized, null, 2);\n\n // 7. Write to file or stdout (stdout suppressed when --quiet)\n if (opts.output !== undefined) {\n try {\n writeFileSync(opts.output, output + \"\\n\", \"utf-8\");\n } catch (e) {\n process.stderr.write(\n `Error: Cannot write to \"${opts.output}\": ${e instanceof Error ? e.message : String(e)}\\n`,\n );\n process.exit(2);\n }\n } else if (!opts.quiet) {\n process.stdout.write(output + \"\\n\");\n }\n });\n}\n","import type { Command } from \"commander\";\nimport { getResourceDocUrl, getResourceInfo } from \"@/core/resource-registry.js\";\nimport type { FhirVersion } from \"@/core/fhir-version.js\";\nimport { parseVersionFlag } from \"@/cli/utils/resolve-version.js\";\n\nfunction buildMaturityLabel(maturityLevel: number | \"N\" | undefined): string {\n if (maturityLevel === undefined) return \"\";\n if (maturityLevel === \"N\") return \" — Normative ★\";\n return ` — FMM ${maturityLevel}`;\n}\n\nfunction buildTextOutput(resourceType: string, fhirVersion: FhirVersion | undefined): string {\n const info = getResourceInfo(resourceType);\n\n if (!info) {\n return [\n `Unknown resource type: \"${resourceType}\"`,\n \"\",\n \"Run 'fhir-resource-diff list-resources' to see known types.\",\n \"Full resource list: https://hl7.org/fhir/resourcelist.html\",\n ].join(\"\\n\");\n }\n\n const versionsToShow: readonly FhirVersion[] = fhirVersion ? [fhirVersion] : info.versions;\n const versionLine = fhirVersion\n ? `FHIR version: ${fhirVersion}`\n : `Available in: ${info.versions.join(\" · \")}`;\n\n const lines: string[] = [];\n\n // Header\n lines.push(`${info.resourceType} (${info.category})${buildMaturityLabel(info.maturityLevel)}`);\n lines.push(versionLine);\n lines.push(\"\");\n lines.push(info.description);\n\n // Use cases\n if (info.useCases && info.useCases.length > 0) {\n lines.push(\"\");\n lines.push(\"Use cases:\");\n for (const useCase of info.useCases) {\n lines.push(` • ${useCase}`);\n }\n }\n\n // Key fields\n if (info.keyFields && info.keyFields.length > 0) {\n lines.push(\"\");\n lines.push(\"Key fields:\");\n // Calculate column width for field names (name + optional *)\n const nameColWidth = Math.max(...info.keyFields.map((f) => f.name.length + (f.required ? 2 : 0)));\n for (const field of info.keyFields) {\n const nameWithMarker = field.required ? `${field.name} *` : field.name;\n const paddedName = nameWithMarker.padEnd(nameColWidth);\n lines.push(` ${paddedName} ${field.note}`);\n }\n }\n\n // Version notes (only when no --fhir-version specified)\n if (!fhirVersion && info.versionNotes) {\n const r4ToR4b = info.versionNotes[\"R4→R4B\"];\n const r4bToR5 = info.versionNotes[\"R4B→R5\"];\n if (r4ToR4b !== undefined || r4bToR5 !== undefined) {\n lines.push(\"\");\n lines.push(\"Version notes:\");\n if (r4ToR4b !== undefined) {\n lines.push(` R4 → R4B ${r4ToR4b}`);\n }\n if (r4bToR5 !== undefined) {\n lines.push(` R4B → R5 ${r4bToR5}`);\n }\n }\n }\n\n // Documentation\n const docLines = versionsToShow.map((v) => {\n const label = `${v}:`.padEnd(5);\n return ` ${label} ${getResourceDocUrl(resourceType, v)}`;\n });\n\n lines.push(\"\");\n lines.push(\"Documentation:\");\n lines.push(...docLines);\n\n return lines.join(\"\\n\");\n}\n\nfunction buildJsonOutput(resourceType: string, fhirVersion: FhirVersion | undefined): string {\n const info = getResourceInfo(resourceType);\n\n if (!info) {\n return JSON.stringify(\n {\n error: \"Unknown resource type\",\n resourceType,\n help: \"https://hl7.org/fhir/resourcelist.html\",\n },\n null,\n 2,\n );\n }\n\n const versionsToShow: readonly FhirVersion[] = fhirVersion ? [fhirVersion] : info.versions;\n const documentation: Record<string, string> = {};\n for (const v of versionsToShow) {\n documentation[v] = getResourceDocUrl(resourceType, v);\n }\n\n const output: Record<string, unknown> = {\n resourceType: info.resourceType,\n category: info.category,\n versions: fhirVersion ? [fhirVersion] : [...info.versions],\n description: info.description,\n };\n\n if (info.maturityLevel !== undefined) {\n output.maturityLevel = info.maturityLevel;\n }\n if (info.useCases !== undefined) {\n output.useCases = [...info.useCases];\n }\n if (info.keyFields !== undefined) {\n output.keyFields = info.keyFields.map((f) => ({ name: f.name, required: f.required, note: f.note }));\n }\n if (!fhirVersion && info.versionNotes !== undefined) {\n output.versionNotes = { ...info.versionNotes };\n }\n\n output.documentation = documentation;\n\n return JSON.stringify(output, null, 2);\n}\n\nexport function registerInfoCommand(program: Command): void {\n program\n .command(\"info <resourceType>\")\n .description(\"Show metadata and HL7 documentation links for a FHIR resource type\")\n .option(\"--fhir-version <ver>\", \"Show docs link for a specific version only (R4 | R4B | R5)\")\n .option(\"--format <fmt>\", \"Output format: text | json\", \"text\")\n .action((resourceType: string, opts: { fhirVersion?: string; format?: string }) => {\n const fhirVersion = parseVersionFlag(opts.fhirVersion);\n const format = opts.format ?? \"text\";\n\n const isKnown = getResourceInfo(resourceType) !== undefined;\n const exitCode = isKnown ? 0 : 2;\n\n let output: string;\n if (format === \"json\") {\n output = buildJsonOutput(resourceType, fhirVersion);\n } else {\n output = buildTextOutput(resourceType, fhirVersion);\n }\n\n process.stdout.write(output + \"\\n\");\n process.exit(exitCode);\n });\n}\n","import type { Command } from \"commander\";\nimport {\n listResourceTypes,\n type ResourceCategory,\n type ResourceTypeInfo,\n} from \"@/core/resource-registry.js\";\nimport { type FhirVersion } from \"@/core/fhir-version.js\";\nimport { parseVersionFlag } from \"@/cli/utils/resolve-version.js\";\n\nconst VALID_CATEGORIES: readonly ResourceCategory[] = [\n \"foundation\",\n \"base\",\n \"clinical\",\n \"financial\",\n \"specialized\",\n \"conformance\",\n];\n\nconst CATEGORY_ORDER: readonly ResourceCategory[] = [\n \"foundation\",\n \"base\",\n \"clinical\",\n \"financial\",\n \"specialized\",\n \"conformance\",\n];\n\nconst HL7_RESOURCE_LIST_URL = \"https://hl7.org/fhir/resourcelist.html\";\nconst RESOURCE_TYPE_COL_WIDTH = 24;\n\nfunction buildTitle(\n fhirVersion: FhirVersion | undefined,\n category: ResourceCategory | undefined,\n total: number,\n): string {\n const parts: string[] = [];\n if (category) parts.push(category);\n if (fhirVersion) parts.push(fhirVersion);\n const qualifier = parts.length > 0 ? ` — ${parts.join(\", \")}` : \"\";\n return `FHIR Resource Types${qualifier} (${total} total)`;\n}\n\nfunction buildTextOutput(\n resources: readonly ResourceTypeInfo[],\n fhirVersion: FhirVersion | undefined,\n category: ResourceCategory | undefined,\n): string {\n if (resources.length === 0) {\n return \"No resource types match the given filters.\";\n }\n\n const lines: string[] = [buildTitle(fhirVersion, category, resources.length), \"\"];\n\n if (category) {\n // No category headers — single category filter\n for (const r of resources) {\n lines.push(` ${r.resourceType.padEnd(RESOURCE_TYPE_COL_WIDTH)}${r.description}`);\n }\n } else {\n // Group by category in order\n for (const cat of CATEGORY_ORDER) {\n const group = resources.filter((r) => r.category === cat);\n if (group.length === 0) continue;\n lines.push(cat);\n for (const r of group) {\n lines.push(` ${r.resourceType.padEnd(RESOURCE_TYPE_COL_WIDTH)}${r.description}`);\n }\n lines.push(\"\");\n }\n }\n\n lines.push(`Full resource list: ${HL7_RESOURCE_LIST_URL}`);\n return lines.join(\"\\n\");\n}\n\nfunction buildJsonOutput(\n resources: readonly ResourceTypeInfo[],\n fhirVersion: FhirVersion | undefined,\n category: ResourceCategory | undefined,\n): string {\n const filters: Record<string, string> = {};\n if (fhirVersion) filters.fhirVersion = fhirVersion;\n if (category) filters.category = category;\n\n return JSON.stringify(\n {\n total: resources.length,\n filters,\n resources: resources.map((r) => ({\n resourceType: r.resourceType,\n category: r.category,\n versions: [...r.versions],\n description: r.description,\n })),\n },\n null,\n 2,\n );\n}\n\nexport function registerListResourcesCommand(program: Command): void {\n program\n .command(\"list-resources\")\n .description(\n \"List known FHIR resource types, optionally filtered by version and category\",\n )\n .option(\n \"--fhir-version <ver>\",\n \"Filter to resource types available in a specific version (R4 | R4B | R5)\",\n )\n .option(\n \"--category <cat>\",\n \"Filter by category (foundation | base | clinical | financial | specialized | conformance)\",\n )\n .option(\"--format <fmt>\", \"Output format: text | json\", \"text\")\n .action(\n (opts: { fhirVersion?: string; category?: string; format?: string }) => {\n const fhirVersion = parseVersionFlag(opts.fhirVersion);\n const format = opts.format ?? \"text\";\n\n let category: ResourceCategory | undefined;\n if (opts.category !== undefined) {\n if (!(VALID_CATEGORIES as readonly string[]).includes(opts.category)) {\n process.stderr.write(\n `Error: Unknown category \"${opts.category}\". Available: ${VALID_CATEGORIES.join(\", \")}\\n`,\n );\n process.exit(2);\n }\n category = opts.category as ResourceCategory;\n }\n\n const resources = listResourceTypes({\n ...(fhirVersion !== undefined && { version: fhirVersion }),\n ...(category !== undefined && { category }),\n });\n\n let output: string;\n if (format === \"json\") {\n output = buildJsonOutput(resources, fhirVersion, category);\n } else {\n output = buildTextOutput(resources, fhirVersion, category);\n }\n\n process.stdout.write(output + \"\\n\");\n },\n );\n}\n"],"mappings":"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","names":["program","program","program","program","buildTextOutput","buildJsonOutput","program"]}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/** Minimum shape of any FHIR resource. */
|
|
2
|
+
interface FhirResource {
|
|
3
|
+
resourceType: string;
|
|
4
|
+
id?: string;
|
|
5
|
+
meta?: FhirMeta;
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
interface FhirMeta {
|
|
9
|
+
versionId?: string;
|
|
10
|
+
lastUpdated?: string;
|
|
11
|
+
fhirVersion?: string;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
type ParseResult = {
|
|
15
|
+
success: true;
|
|
16
|
+
resource: FhirResource;
|
|
17
|
+
} | {
|
|
18
|
+
success: false;
|
|
19
|
+
error: string;
|
|
20
|
+
};
|
|
21
|
+
type ValidationSeverity = "error" | "warning" | "info";
|
|
22
|
+
interface ValidationError {
|
|
23
|
+
/** Dot-notation path, e.g. "name[0].given" */
|
|
24
|
+
path: string;
|
|
25
|
+
message: string;
|
|
26
|
+
severity: ValidationSeverity;
|
|
27
|
+
/** Optional HL7 documentation link for context. */
|
|
28
|
+
docUrl?: string;
|
|
29
|
+
/** Identifier of the rule that produced this finding, e.g. "fhir-id-format". */
|
|
30
|
+
ruleId?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* An informational note about the tool's validation scope — not a finding about
|
|
34
|
+
* the resource itself. Rendered as a footer, never counted as a warning.
|
|
35
|
+
*/
|
|
36
|
+
interface ValidationHint {
|
|
37
|
+
message: string;
|
|
38
|
+
docUrl: string;
|
|
39
|
+
}
|
|
40
|
+
type ValidationResult = {
|
|
41
|
+
valid: true;
|
|
42
|
+
hint?: ValidationHint;
|
|
43
|
+
} | {
|
|
44
|
+
valid: false;
|
|
45
|
+
errors: ValidationError[];
|
|
46
|
+
hint?: ValidationHint;
|
|
47
|
+
};
|
|
48
|
+
type DiffChangeKind = "added" | "removed" | "changed" | "type-changed";
|
|
49
|
+
interface DiffEntry {
|
|
50
|
+
kind: DiffChangeKind;
|
|
51
|
+
/** Dot-notation path to the changed value, e.g. "name[0].given[1]" */
|
|
52
|
+
path: string;
|
|
53
|
+
/** Value in the left (base) resource; absent for "added" */
|
|
54
|
+
left?: unknown;
|
|
55
|
+
/** Value in the right (target) resource; absent for "removed" */
|
|
56
|
+
right?: unknown;
|
|
57
|
+
}
|
|
58
|
+
interface DiffResult {
|
|
59
|
+
resourceType: string;
|
|
60
|
+
identical: boolean;
|
|
61
|
+
entries: DiffEntry[];
|
|
62
|
+
}
|
|
63
|
+
interface NormalizeOptions {
|
|
64
|
+
sortObjectKeys?: boolean;
|
|
65
|
+
trimStrings?: boolean;
|
|
66
|
+
normalizeDates?: boolean;
|
|
67
|
+
/** Dot-notation paths of arrays to sort before comparison. */
|
|
68
|
+
sortArrayPaths?: string[];
|
|
69
|
+
}
|
|
70
|
+
interface DiffOptions {
|
|
71
|
+
/** Field paths to exclude from comparison. Dot-notation, supports wildcards: "meta.*" */
|
|
72
|
+
ignorePaths?: string[];
|
|
73
|
+
normalize?: NormalizeOptions;
|
|
74
|
+
}
|
|
75
|
+
interface IgnorePreset {
|
|
76
|
+
name: string;
|
|
77
|
+
description: string;
|
|
78
|
+
paths: string[];
|
|
79
|
+
}
|
|
80
|
+
interface NormalizationPreset {
|
|
81
|
+
name: string;
|
|
82
|
+
description: string;
|
|
83
|
+
options: NormalizeOptions;
|
|
84
|
+
}
|
|
85
|
+
interface DiffSummary {
|
|
86
|
+
added: number;
|
|
87
|
+
removed: number;
|
|
88
|
+
changed: number;
|
|
89
|
+
typeChanged: number;
|
|
90
|
+
total: number;
|
|
91
|
+
}
|
|
92
|
+
interface OutputEnvelope<T> {
|
|
93
|
+
/** Tool identifier — always "fhir-resource-diff". */
|
|
94
|
+
tool: string;
|
|
95
|
+
/** Tool version from package.json. */
|
|
96
|
+
version: string;
|
|
97
|
+
/** Command that produced this output. */
|
|
98
|
+
command: string;
|
|
99
|
+
/** Resolved FHIR version used for this operation. */
|
|
100
|
+
fhirVersion: string;
|
|
101
|
+
/** Timestamp of when the operation ran (ISO 8601). */
|
|
102
|
+
timestamp: string;
|
|
103
|
+
/** The actual result payload. */
|
|
104
|
+
result: T;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Supported FHIR release identifiers. */
|
|
108
|
+
type FhirVersion = "R4" | "R4B" | "R5";
|
|
109
|
+
declare const SUPPORTED_FHIR_VERSIONS: readonly FhirVersion[];
|
|
110
|
+
declare const DEFAULT_FHIR_VERSION: FhirVersion;
|
|
111
|
+
/**
|
|
112
|
+
* Maps concrete FHIR version strings (as found in meta.fhirVersion or CapabilityStatement)
|
|
113
|
+
* to the corresponding release identifier.
|
|
114
|
+
*/
|
|
115
|
+
declare const VERSION_STRING_MAP: ReadonlyMap<string, FhirVersion>;
|
|
116
|
+
/**
|
|
117
|
+
* Detects the FHIR version from a resource's meta.fhirVersion field.
|
|
118
|
+
* Returns undefined if the field is absent or the version string is unrecognized.
|
|
119
|
+
*/
|
|
120
|
+
declare function detectFhirVersion(resource: FhirResource): FhirVersion | undefined;
|
|
121
|
+
/**
|
|
122
|
+
* Resolves a FHIR version from an explicit flag, auto-detection, or the default.
|
|
123
|
+
* Priority: explicit > detected > default.
|
|
124
|
+
*/
|
|
125
|
+
declare function resolveFhirVersion(explicit: FhirVersion | undefined, resource: FhirResource): FhirVersion;
|
|
126
|
+
/**
|
|
127
|
+
* Returns a human-readable label, e.g. "FHIR R4 (v4.0.1)".
|
|
128
|
+
*/
|
|
129
|
+
declare function fhirVersionLabel(version: FhirVersion): string;
|
|
130
|
+
/**
|
|
131
|
+
* Returns the base URL for the HL7 FHIR spec for the given version.
|
|
132
|
+
* e.g. "https://hl7.org/fhir/R4"
|
|
133
|
+
*/
|
|
134
|
+
declare function fhirBaseUrl(version: FhirVersion): string;
|
|
135
|
+
/**
|
|
136
|
+
* Validates that a string is a supported FhirVersion.
|
|
137
|
+
* Useful for parsing CLI flags.
|
|
138
|
+
*/
|
|
139
|
+
declare function isSupportedFhirVersion(value: string): value is FhirVersion;
|
|
140
|
+
|
|
141
|
+
type ResourceCategory = "foundation" | "base" | "clinical" | "financial" | "specialized" | "conformance";
|
|
142
|
+
interface ResourceKeyField {
|
|
143
|
+
/** Field name. Use "[x]" suffix for polymorphic fields, e.g. "value[x]". */
|
|
144
|
+
name: string;
|
|
145
|
+
/** Whether this field is required (1..1 or 1..*) in the base spec. */
|
|
146
|
+
required: boolean;
|
|
147
|
+
/** Concise note explaining the field's purpose, type choices, or common gotchas. */
|
|
148
|
+
note: string;
|
|
149
|
+
}
|
|
150
|
+
interface ResourceVersionNotes {
|
|
151
|
+
/** What changed moving from R4 to R4B. Omit if no significant changes. */
|
|
152
|
+
"R4→R4B"?: string;
|
|
153
|
+
/** What changed moving from R4B to R5. Omit if no significant changes. */
|
|
154
|
+
"R4B→R5"?: string;
|
|
155
|
+
}
|
|
156
|
+
interface ResourceTypeInfo {
|
|
157
|
+
/** The exact resourceType string, e.g. "Patient". */
|
|
158
|
+
resourceType: string;
|
|
159
|
+
/** High-level category for grouping and filtering. */
|
|
160
|
+
category: ResourceCategory;
|
|
161
|
+
/** Which FHIR versions include this resource type. */
|
|
162
|
+
versions: readonly FhirVersion[];
|
|
163
|
+
/** One-line description. Not a full definition — just enough for CLI display. */
|
|
164
|
+
description: string;
|
|
165
|
+
/**
|
|
166
|
+
* FHIR Maturity Model level.
|
|
167
|
+
* 1–2 = draft/experimental, 3–4 = trial use, 5 = mature trial use, "N" = Normative.
|
|
168
|
+
* Normative means backwards-compatibility is guaranteed by HL7.
|
|
169
|
+
* Omitted for rarely-used resources.
|
|
170
|
+
*/
|
|
171
|
+
maturityLevel?: number | "N";
|
|
172
|
+
/**
|
|
173
|
+
* 2–3 real-world use cases. Helps product owners and newcomers understand when
|
|
174
|
+
* to use this resource vs alternatives.
|
|
175
|
+
*/
|
|
176
|
+
useCases?: readonly string[];
|
|
177
|
+
/**
|
|
178
|
+
* Key fields worth knowing, with brief explanatory notes.
|
|
179
|
+
* Not a full field list — just the ones that trip people up or need context.
|
|
180
|
+
*/
|
|
181
|
+
keyFields?: readonly ResourceKeyField[];
|
|
182
|
+
/**
|
|
183
|
+
* Notable changes at each version boundary.
|
|
184
|
+
* Only populated when something meaningful changed — absence means "no significant change".
|
|
185
|
+
*/
|
|
186
|
+
versionNotes?: ResourceVersionNotes;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Curated registry of common FHIR resource types.
|
|
190
|
+
* This is intentionally incomplete — it covers the most commonly used types.
|
|
191
|
+
* Full list: https://hl7.org/fhir/resourcelist.html
|
|
192
|
+
*/
|
|
193
|
+
declare const RESOURCE_REGISTRY: readonly ResourceTypeInfo[];
|
|
194
|
+
/**
|
|
195
|
+
* Looks up a resource type by name (case-sensitive, exact match).
|
|
196
|
+
*/
|
|
197
|
+
declare function getResourceInfo(resourceType: string): ResourceTypeInfo | undefined;
|
|
198
|
+
/**
|
|
199
|
+
* Builds the HL7 documentation URL for a resource type and version.
|
|
200
|
+
* e.g. getResourceDocUrl("Patient", "R4") → "https://hl7.org/fhir/R4/patient.html"
|
|
201
|
+
* Falls back to DEFAULT_FHIR_VERSION if version is not provided.
|
|
202
|
+
*/
|
|
203
|
+
declare function getResourceDocUrl(resourceType: string, version?: FhirVersion): string;
|
|
204
|
+
/**
|
|
205
|
+
* Returns true if the resource type is in the registry, optionally filtered by version.
|
|
206
|
+
*/
|
|
207
|
+
declare function isKnownResourceType(resourceType: string, version?: FhirVersion): boolean;
|
|
208
|
+
/**
|
|
209
|
+
* Returns all resource types, optionally filtered by version and/or category.
|
|
210
|
+
*/
|
|
211
|
+
declare function listResourceTypes(filters?: {
|
|
212
|
+
version?: FhirVersion;
|
|
213
|
+
category?: ResourceCategory;
|
|
214
|
+
}): readonly ResourceTypeInfo[];
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Narrows an unknown value to FhirResource if it has a string resourceType.
|
|
218
|
+
* Useful for callers that already have a parsed JS object.
|
|
219
|
+
*/
|
|
220
|
+
declare function isFhirResource(value: unknown): value is FhirResource;
|
|
221
|
+
/**
|
|
222
|
+
* Parses a raw JSON string into a FhirResource.
|
|
223
|
+
* Returns a discriminated union — always check `.success` before using `.resource`.
|
|
224
|
+
* Does not validate FHIR shape beyond JSON well-formedness.
|
|
225
|
+
*/
|
|
226
|
+
declare function parseJson(input: string): ParseResult;
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Validates that a parsed FhirResource meets the minimum required shape.
|
|
230
|
+
* Optionally runs version-aware checks when `version` is provided.
|
|
231
|
+
*/
|
|
232
|
+
declare function validate(resource: FhirResource, version?: FhirVersion): ValidationResult;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* A specific, named FHIR profile with a known canonical URL.
|
|
236
|
+
* Used for exact-match lookups.
|
|
237
|
+
*/
|
|
238
|
+
interface ProfileInfo {
|
|
239
|
+
/** The exact canonical URL of this profile. */
|
|
240
|
+
canonical: string;
|
|
241
|
+
/** Human-readable name, e.g. "Vital Signs Observation". */
|
|
242
|
+
name: string;
|
|
243
|
+
/** Short IG identifier for display, e.g. "FHIR Base", "US Core". */
|
|
244
|
+
igShort: string;
|
|
245
|
+
/** Full IG name, e.g. "US Core Implementation Guide". */
|
|
246
|
+
ig: string;
|
|
247
|
+
/** Direct link to this profile's documentation page. */
|
|
248
|
+
docUrl: string;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* A namespace entry for an Implementation Guide.
|
|
252
|
+
* Used when we recognize the IG by URL prefix but don't have the exact profile.
|
|
253
|
+
*/
|
|
254
|
+
interface ProfileNamespace {
|
|
255
|
+
/** URL prefix that identifies this IG, e.g. "http://hl7.org/fhir/us/core/". */
|
|
256
|
+
prefix: string;
|
|
257
|
+
/** Short IG identifier for display. */
|
|
258
|
+
igShort: string;
|
|
259
|
+
/** Full IG name. */
|
|
260
|
+
ig: string;
|
|
261
|
+
/** IG home/documentation page. */
|
|
262
|
+
igUrl: string;
|
|
263
|
+
}
|
|
264
|
+
declare const KNOWN_PROFILES: readonly ProfileInfo[];
|
|
265
|
+
/**
|
|
266
|
+
* Namespace entries ordered from most specific to least specific.
|
|
267
|
+
* First match wins during prefix lookup.
|
|
268
|
+
*/
|
|
269
|
+
declare const PROFILE_NAMESPACES: readonly ProfileNamespace[];
|
|
270
|
+
/**
|
|
271
|
+
* Returns true if the given string is a valid FHIR canonical URL.
|
|
272
|
+
* Accepts http(s):// URLs and urn: URIs.
|
|
273
|
+
*/
|
|
274
|
+
declare function isValidCanonicalUrl(url: string): boolean;
|
|
275
|
+
/**
|
|
276
|
+
* Looks up an exact profile match by canonical URL.
|
|
277
|
+
* Returns undefined if not found.
|
|
278
|
+
*/
|
|
279
|
+
declare function lookupProfile(canonical: string): ProfileInfo | undefined;
|
|
280
|
+
/**
|
|
281
|
+
* Looks up the first matching IG namespace for a canonical URL.
|
|
282
|
+
* Returns undefined if no namespace prefix matches.
|
|
283
|
+
*/
|
|
284
|
+
declare function lookupProfileNamespace(canonical: string): ProfileNamespace | undefined;
|
|
285
|
+
|
|
286
|
+
type FieldVisitor = (path: string, key: string, value: unknown, parent: Record<string, unknown>) => void;
|
|
287
|
+
/** Walk every key-value pair in a FHIR resource tree, depth-first. */
|
|
288
|
+
declare function walkResource(resource: FhirResource, visitor: FieldVisitor): void;
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* A validation rule that inspects a parsed FHIR resource and returns
|
|
292
|
+
* zero or more validation findings (warnings/info).
|
|
293
|
+
*/
|
|
294
|
+
interface ValidationRule {
|
|
295
|
+
/** Unique rule identifier, e.g. "fhir-id-format". */
|
|
296
|
+
id: string;
|
|
297
|
+
/** Human-readable short description. */
|
|
298
|
+
description: string;
|
|
299
|
+
/** Run the rule against a resource. Returns findings (empty = pass). */
|
|
300
|
+
check(resource: FhirResource, version?: FhirVersion): ValidationError[];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** Format and pattern rules — always run, no version required. */
|
|
304
|
+
declare const FORMAT_RULES: readonly ValidationRule[];
|
|
305
|
+
/** Profile awareness rules — always run, profile detection doesn't require version. */
|
|
306
|
+
declare const PROFILE_RULES: readonly ValidationRule[];
|
|
307
|
+
/** Run a set of rules against a resource and collect all findings. */
|
|
308
|
+
declare function runRules(resource: FhirResource, rules: readonly ValidationRule[], version?: FhirVersion): ValidationError[];
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Compares two FHIR resources and returns a structured DiffResult.
|
|
312
|
+
* Pure function — no I/O, no side effects.
|
|
313
|
+
*/
|
|
314
|
+
declare function diff(left: FhirResource, right: FhirResource, options?: DiffOptions): DiffResult;
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Determines the kind of change between two values at a given path.
|
|
318
|
+
* Pure function — does not recurse; the diff engine calls it per leaf node.
|
|
319
|
+
*/
|
|
320
|
+
declare function classifyChange(left: unknown, right: unknown): DiffChangeKind;
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Returns a normalized deep copy of the resource.
|
|
324
|
+
* Does not mutate the input — always returns a new object.
|
|
325
|
+
*/
|
|
326
|
+
declare function normalize(resource: FhirResource, options: NormalizeOptions): FhirResource;
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Tool version constant. Update this when bumping the package version.
|
|
330
|
+
* Not read from package.json at runtime (would require node:fs, breaking browser safety).
|
|
331
|
+
*/
|
|
332
|
+
declare const TOOL_VERSION = "0.2.0";
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Computes summary counts from a DiffResult.
|
|
336
|
+
* Pure function — no I/O, no side effects.
|
|
337
|
+
*/
|
|
338
|
+
declare function summarizeDiff(result: DiffResult): DiffSummary;
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Wraps a result payload in the standard output envelope.
|
|
342
|
+
* Browser-safe — reads version from a constant, not from filesystem.
|
|
343
|
+
*/
|
|
344
|
+
declare function buildEnvelope<T>(command: string, fhirVersion: string, result: T): OutputEnvelope<T>;
|
|
345
|
+
|
|
346
|
+
declare function formatText(result: DiffResult): string;
|
|
347
|
+
declare function formatValidationText(result: ValidationResult): string;
|
|
348
|
+
|
|
349
|
+
declare function formatJson(result: DiffResult): string;
|
|
350
|
+
declare function formatValidationJson(result: ValidationResult): string;
|
|
351
|
+
|
|
352
|
+
declare function formatMarkdown(result: DiffResult): string;
|
|
353
|
+
|
|
354
|
+
export { DEFAULT_FHIR_VERSION, type DiffChangeKind, type DiffEntry, type DiffOptions, type DiffResult, type DiffSummary, FORMAT_RULES, type FhirMeta, type FhirResource, type FhirVersion, type IgnorePreset, KNOWN_PROFILES, type NormalizationPreset, type NormalizeOptions, type OutputEnvelope, PROFILE_NAMESPACES, PROFILE_RULES, type ParseResult, type ProfileInfo, type ProfileNamespace, RESOURCE_REGISTRY, type ResourceCategory, type ResourceTypeInfo, SUPPORTED_FHIR_VERSIONS, TOOL_VERSION, VERSION_STRING_MAP, type ValidationError, type ValidationHint, type ValidationResult, type ValidationRule, buildEnvelope, classifyChange, detectFhirVersion, diff, fhirBaseUrl, fhirVersionLabel, formatJson, formatMarkdown, formatText, formatValidationJson, formatValidationText, getResourceDocUrl, getResourceInfo, isFhirResource, isKnownResourceType, isSupportedFhirVersion, isValidCanonicalUrl, listResourceTypes, lookupProfile, lookupProfileNamespace, normalize, parseJson, resolveFhirVersion, runRules, summarizeDiff, validate, walkResource };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_FHIR_VERSION,
|
|
3
|
+
FORMAT_RULES,
|
|
4
|
+
KNOWN_PROFILES,
|
|
5
|
+
PROFILE_NAMESPACES,
|
|
6
|
+
PROFILE_RULES,
|
|
7
|
+
RESOURCE_REGISTRY,
|
|
8
|
+
SUPPORTED_FHIR_VERSIONS,
|
|
9
|
+
TOOL_VERSION,
|
|
10
|
+
VERSION_STRING_MAP,
|
|
11
|
+
buildEnvelope,
|
|
12
|
+
classifyChange,
|
|
13
|
+
detectFhirVersion,
|
|
14
|
+
diff,
|
|
15
|
+
fhirBaseUrl,
|
|
16
|
+
fhirVersionLabel,
|
|
17
|
+
formatJson,
|
|
18
|
+
formatMarkdown,
|
|
19
|
+
formatText,
|
|
20
|
+
formatValidationJson,
|
|
21
|
+
formatValidationText,
|
|
22
|
+
getResourceDocUrl,
|
|
23
|
+
getResourceInfo,
|
|
24
|
+
isFhirResource,
|
|
25
|
+
isKnownResourceType,
|
|
26
|
+
isSupportedFhirVersion,
|
|
27
|
+
isValidCanonicalUrl,
|
|
28
|
+
listResourceTypes,
|
|
29
|
+
lookupProfile,
|
|
30
|
+
lookupProfileNamespace,
|
|
31
|
+
normalize,
|
|
32
|
+
parseJson,
|
|
33
|
+
resolveFhirVersion,
|
|
34
|
+
runRules,
|
|
35
|
+
summarizeDiff,
|
|
36
|
+
validate,
|
|
37
|
+
walkResource
|
|
38
|
+
} from "../chunk-2UUKQJDB.js";
|
|
39
|
+
export {
|
|
40
|
+
DEFAULT_FHIR_VERSION,
|
|
41
|
+
FORMAT_RULES,
|
|
42
|
+
KNOWN_PROFILES,
|
|
43
|
+
PROFILE_NAMESPACES,
|
|
44
|
+
PROFILE_RULES,
|
|
45
|
+
RESOURCE_REGISTRY,
|
|
46
|
+
SUPPORTED_FHIR_VERSIONS,
|
|
47
|
+
TOOL_VERSION,
|
|
48
|
+
VERSION_STRING_MAP,
|
|
49
|
+
buildEnvelope,
|
|
50
|
+
classifyChange,
|
|
51
|
+
detectFhirVersion,
|
|
52
|
+
diff,
|
|
53
|
+
fhirBaseUrl,
|
|
54
|
+
fhirVersionLabel,
|
|
55
|
+
formatJson,
|
|
56
|
+
formatMarkdown,
|
|
57
|
+
formatText,
|
|
58
|
+
formatValidationJson,
|
|
59
|
+
formatValidationText,
|
|
60
|
+
getResourceDocUrl,
|
|
61
|
+
getResourceInfo,
|
|
62
|
+
isFhirResource,
|
|
63
|
+
isKnownResourceType,
|
|
64
|
+
isSupportedFhirVersion,
|
|
65
|
+
isValidCanonicalUrl,
|
|
66
|
+
listResourceTypes,
|
|
67
|
+
lookupProfile,
|
|
68
|
+
lookupProfileNamespace,
|
|
69
|
+
normalize,
|
|
70
|
+
parseJson,
|
|
71
|
+
resolveFhirVersion,
|
|
72
|
+
runRules,
|
|
73
|
+
summarizeDiff,
|
|
74
|
+
validate,
|
|
75
|
+
walkResource
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fhir-resource-diff",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "CLI and library for diffing and validating FHIR JSON resources",
|
|
5
|
+
"author": "Daniel Veronez",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"fhir",
|
|
9
|
+
"hl7",
|
|
10
|
+
"healthcare",
|
|
11
|
+
"diff",
|
|
12
|
+
"validate",
|
|
13
|
+
"r4",
|
|
14
|
+
"r4b",
|
|
15
|
+
"r5",
|
|
16
|
+
"cli",
|
|
17
|
+
"json"
|
|
18
|
+
],
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/dnlbox/fhir-resource-diff.git"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/dnlbox/fhir-resource-diff#readme",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/dnlbox/fhir-resource-diff/issues"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist/",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"type": "module",
|
|
33
|
+
"bin": {
|
|
34
|
+
"fhir-resource-diff": "./dist/cli/index.js"
|
|
35
|
+
},
|
|
36
|
+
"main": "./dist/core/index.js",
|
|
37
|
+
"types": "./dist/core/index.d.ts",
|
|
38
|
+
"exports": {
|
|
39
|
+
".": {
|
|
40
|
+
"import": "./dist/core/index.js",
|
|
41
|
+
"types": "./dist/core/index.d.ts"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsup",
|
|
46
|
+
"dev": "tsup --watch",
|
|
47
|
+
"typecheck": "tsc --noEmit",
|
|
48
|
+
"lint": "eslint src tests --ext .ts --no-error-on-unmatched-pattern",
|
|
49
|
+
"lint:fix": "eslint src tests --ext .ts --fix --no-error-on-unmatched-pattern",
|
|
50
|
+
"format": "prettier --write .",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"test:watch": "vitest",
|
|
53
|
+
"test:coverage": "vitest run --coverage",
|
|
54
|
+
"prepublishOnly": "pnpm build && pnpm typecheck && pnpm test",
|
|
55
|
+
"cli": "tsx src/cli/index.ts",
|
|
56
|
+
"cli:watch": "tsx --watch src/cli/index.ts",
|
|
57
|
+
"docs:dev": "vitepress dev docs/site",
|
|
58
|
+
"docs:build": "vitepress build docs/site",
|
|
59
|
+
"docs:preview": "vitepress preview docs/site"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"commander": "^12.1.0",
|
|
63
|
+
"picocolors": "^1.1.1",
|
|
64
|
+
"zod": "^3.23.8"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@types/node": "^20.0.0",
|
|
68
|
+
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
69
|
+
"@typescript-eslint/parser": "^7.18.0",
|
|
70
|
+
"@vitest/coverage-v8": "^2.1.8",
|
|
71
|
+
"eslint": "^8.57.1",
|
|
72
|
+
"eslint-config-prettier": "^9.1.0",
|
|
73
|
+
"prettier": "^3.3.3",
|
|
74
|
+
"tsup": "^8.3.5",
|
|
75
|
+
"tsx": "^4.19.1",
|
|
76
|
+
"typescript": "^5.6.3",
|
|
77
|
+
"vitepress": "^1.6.4",
|
|
78
|
+
"vitest": "^2.1.8"
|
|
79
|
+
},
|
|
80
|
+
"engines": {
|
|
81
|
+
"node": ">=20.0.0",
|
|
82
|
+
"pnpm": ">=9.0.0"
|
|
83
|
+
},
|
|
84
|
+
"pnpm": {
|
|
85
|
+
"onlyBuiltDependencies": [
|
|
86
|
+
"esbuild"
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
}
|