Skip to content

Commit e559105

Browse files
committed
TS: regenerate R4 and US Core examples for bindings + profile-is
- Add `bindings.ts` and `export * from "./bindings"` in each FHIR package's index. - Update copied `profile-helpers.ts` with the new `parse*` runtime helpers. - Refresh US Core profile classes (catches up the recently-added static `is()` predicate that the previous regen on this branch missed).
1 parent 632bd05 commit e559105

8 files changed

Lines changed: 1987 additions & 4 deletions

File tree

‎examples/typescript-r4/fhir-types/hl7-fhir-r4-core/bindings.ts‎

Lines changed: 869 additions & 0 deletions
Large diffs are not rendered by default.

‎examples/typescript-r4/fhir-types/hl7-fhir-r4-core/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from "./profiles";
2+
export * from "./bindings";
23
export type { Address } from "./Address";
34
export type { Age } from "./Age";
45
export type { Annotation } from "./Annotation";

‎examples/typescript-r4/fhir-types/profile-helpers.ts‎

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/**
2-
* Runtime helpers for generated FHIR profile classes.
2+
* Runtime helpers for generated FHIR types and profile classes.
33
*
44
* This file is copied verbatim into every generated TypeScript output and
5-
* imported by profile modules. It provides:
5+
* imported by profile and binding modules. It provides:
66
*
77
* - **Slice helpers** – match, get, set, and default-fill array slices
88
* defined by a FHIR StructureDefinition.
@@ -12,6 +12,8 @@
1212
* profile classes can expose a flat API.
1313
* - **Validation helpers** – lightweight structural checks that profile
1414
* classes call from their `validate()` method.
15+
* - **Parse helpers** – validate / enrich values coming from external
16+
* sources (CSV, HTTP form, untyped JSON) into typed FHIR values.
1517
* - **Misc utilities** – deep-match, deep-merge, path navigation.
1618
*/
1719

@@ -447,3 +449,92 @@ export const validateReference = (res: object, profileName: string, field: strin
447449
? []
448450
: [`${profileName}: field '${field}' references '${refType}' but only ${allowed.join(", ")} are allowed`];
449451
};
452+
453+
// ---------------------------------------------------------------------------
454+
// Parse helpers
455+
//
456+
// Each `parse*` validates an untyped value (typically string from CSV or HTTP
457+
// form input) and returns a value of the typed FHIR shape. All throw an
458+
// `Error` on failure; the message includes `fieldName` when provided so the
459+
// call site can be located in stack traces.
460+
// ---------------------------------------------------------------------------
461+
462+
/**
463+
* Validate that `input` is one of the literal values in `allowed`, returning
464+
* it as the narrowed type. Throws when `input` is not in the set.
465+
*
466+
* @example
467+
* parseLiteral(row.gender, ["male", "female", "other", "unknown"], "Patient.gender")
468+
*/
469+
export const parseLiteral = <const T extends string>(input: unknown, allowed: readonly T[], fieldName?: string): T => {
470+
if (typeof input === "string" && (allowed as readonly string[]).includes(input)) return input as T;
471+
const where = fieldName ? `${fieldName}: ` : "";
472+
throw new Error(`${where}invalid value ${JSON.stringify(input)}. Expected one of: ${allowed.join(", ")}`);
473+
};
474+
475+
/**
476+
* Look up `input` in a code → `{system, code, display}` table and return the
477+
* corresponding FHIR Coding. Throws when the code is not in the table.
478+
*
479+
* Generated per-binding helpers wrap this with the binding's lookup table so
480+
* callers only need to supply the code string.
481+
*
482+
* @example
483+
* parseCoding(row.raceCode, USCoreOmbRaceCategoriesCodes, "Race.ombCategory")
484+
*/
485+
export const parseCoding = <T extends string>(
486+
input: unknown,
487+
lookup: Readonly<Record<string, { system?: string; code: T; display?: string }>>,
488+
fieldName?: string,
489+
): { system?: string; code: T; display?: string } => {
490+
if (typeof input === "string" && Object.hasOwn(lookup, input)) {
491+
const concept = lookup[input];
492+
if (concept) return concept;
493+
}
494+
const where = fieldName ? `${fieldName}: ` : "";
495+
const allowed = Object.keys(lookup).join(", ");
496+
throw new Error(`${where}invalid code ${JSON.stringify(input)}. Expected one of: ${allowed}`);
497+
};
498+
499+
/**
500+
* Coerce `input` into a boolean. Accepts `true`/`false`, `"true"`/`"false"`,
501+
* `"1"`/`"0"` (case-insensitive). Throws on anything else.
502+
*/
503+
export const parseBoolean = (input: unknown, fieldName?: string): boolean => {
504+
if (typeof input === "boolean") return input;
505+
if (typeof input === "string") {
506+
const v = input.trim().toLowerCase();
507+
if (v === "true" || v === "1") return true;
508+
if (v === "false" || v === "0") return false;
509+
}
510+
const where = fieldName ? `${fieldName}: ` : "";
511+
throw new Error(`${where}invalid boolean ${JSON.stringify(input)}`);
512+
};
513+
514+
/**
515+
* Coerce `input` into a finite number. Accepts numbers and numeric strings.
516+
* Throws on `NaN`, `Infinity`, or non-numeric input.
517+
*/
518+
export const parseNumber = (input: unknown, fieldName?: string): number => {
519+
if (typeof input === "number" && Number.isFinite(input)) return input;
520+
if (typeof input === "string" && input.trim() !== "") {
521+
const n = Number(input);
522+
if (Number.isFinite(n)) return n;
523+
}
524+
const where = fieldName ? `${fieldName}: ` : "";
525+
throw new Error(`${where}invalid number ${JSON.stringify(input)}`);
526+
};
527+
528+
/**
529+
* Validate that `input` is a FHIR `instant` string (ISO 8601 with timezone).
530+
* Returns the original string on success; throws on malformed input.
531+
*/
532+
export const parseInstant = (input: unknown, fieldName?: string): string => {
533+
if (typeof input === "string") {
534+
// FHIR instant: YYYY-MM-DDTHH:MM:SS(.sss)?(Z|[+-]HH:MM)
535+
const re = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
536+
if (re.test(input) && !Number.isNaN(Date.parse(input))) return input;
537+
}
538+
const where = fieldName ? `${fieldName}: ` : "";
539+
throw new Error(`${where}invalid instant ${JSON.stringify(input)}`);
540+
};

0 commit comments

Comments
 (0)