Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@ import type { Period } from "../../hl7-fhir-r4-core/Period";
import type { Quantity } from "../../hl7-fhir-r4-core/Quantity";
import type { Reference } from "../../hl7-fhir-r4-core/Reference";

export interface observation_bodyweight extends Observation {
category: CodeableConcept<("social-history" | "vital-signs" | "imaging" | "laboratory" | "procedure" | "survey" | "exam" | "therapy" | "activity" | string)>[];
subject: Reference<"Patient">;
}

export type Observation_bodyweight_Category_VSCatSliceFlat = Omit<CodeableConcept, "coding">;

import {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@ import type { Period } from "../../hl7-fhir-r4-core/Period";
import type { Quantity } from "../../hl7-fhir-r4-core/Quantity";
import type { Reference } from "../../hl7-fhir-r4-core/Reference";

export interface observation_bp extends Observation {
category: CodeableConcept<("social-history" | "vital-signs" | "imaging" | "laboratory" | "procedure" | "survey" | "exam" | "therapy" | "activity" | string)>[];
subject: Reference<"Patient">;
}

export type Observation_bp_Category_VSCatSliceFlat = Omit<CodeableConcept, "coding">;
export type Observation_bp_Component_SystolicBPSliceFlat = Omit<ObservationComponent, "code" | "value" | "valueQuantity" | "valueCodeableConcept" | "valueString" | "valueBoolean" | "valueInteger" | "valueRange" | "valueRatio" | "valueSampledData" | "valueTime" | "valueDateTime" | "valuePeriod"> & Quantity;
export type Observation_bp_Component_DiastolicBPSliceFlat = Omit<ObservationComponent, "code" | "value" | "valueQuantity" | "valueCodeableConcept" | "valueString" | "valueBoolean" | "valueInteger" | "valueRange" | "valueRatio" | "valueSampledData" | "valueTime" | "valueDateTime" | "valuePeriod"> & Quantity;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@ import type { Observation } from "../../hl7-fhir-r4-core/Observation";
import type { Period } from "../../hl7-fhir-r4-core/Period";
import type { Reference } from "../../hl7-fhir-r4-core/Reference";

export interface observation_vitalsigns extends Observation {
category: CodeableConcept<("social-history" | "vital-signs" | "imaging" | "laboratory" | "procedure" | "survey" | "exam" | "therapy" | "activity" | string)>[];
subject: Reference<"Patient">;
}

export type Observation_vitalsigns_Category_VSCatSliceFlat = Omit<CodeableConcept, "coding">;

import {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export type { observation_vitalsigns } from "./Observation_observation_vitalsigns";
export { birthPlaceProfile } from "./Extension_birthPlace";
export { birthTimeProfile } from "./Extension_birthTime";
export { nationalityProfile } from "./Extension_nationality";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@ import type { Observation } from "../../hl7-fhir-r4-core/Observation";
import type { Period } from "../../hl7-fhir-r4-core/Period";
import type { Reference } from "../../hl7-fhir-r4-core/Reference";

export interface observation_vitalsigns extends Observation {
category: CodeableConcept<("social-history" | "vital-signs" | "imaging" | "laboratory" | "procedure" | "survey" | "exam" | "therapy" | "activity" | string)>[];
subject: Reference<"Patient">;
}

export type Observation_vitalsigns_Category_VSCatSliceFlat = Omit<CodeableConcept, "coding">;

import {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export type { observation_vitalsigns } from "./Observation_observation_vitalsigns";
export { observation_vitalsignsProfile } from "./Observation_observation_vitalsigns";
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ import type { Ratio } from "../../hl7-fhir-r4-core/Ratio";
import type { Reference } from "../../hl7-fhir-r4-core/Reference";
import type { SampledData } from "../../hl7-fhir-r4-core/SampledData";

export interface USCoreBloodPressureProfile extends Observation {
category: CodeableConcept<("social-history" | "vital-signs" | "imaging" | "laboratory" | "procedure" | "survey" | "exam" | "therapy" | "activity" | string)>[];
subject: Reference<"Patient">;
}

export type USCoreBloodPressureProfile_Category_VSCatSliceFlat = Omit<CodeableConcept, "coding">;
export type USCoreBloodPressureProfile_Component_SystolicSliceFlat = Omit<ObservationComponent, "code" | "value" | "valueQuantity" | "valueCodeableConcept" | "valueString" | "valueBoolean" | "valueInteger" | "valueRange" | "valueRatio" | "valueSampledData" | "valueTime" | "valueDateTime" | "valuePeriod"> & Quantity;
export type USCoreBloodPressureProfile_Component_DiastolicSliceFlat = Omit<ObservationComponent, "code" | "value" | "valueQuantity" | "valueCodeableConcept" | "valueString" | "valueBoolean" | "valueInteger" | "valueRange" | "valueRatio" | "valueSampledData" | "valueTime" | "valueDateTime" | "valuePeriod"> & Quantity;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ import type { Ratio } from "../../hl7-fhir-r4-core/Ratio";
import type { Reference } from "../../hl7-fhir-r4-core/Reference";
import type { SampledData } from "../../hl7-fhir-r4-core/SampledData";

export interface USCoreBodyWeightProfile extends Observation {
category: CodeableConcept<("social-history" | "vital-signs" | "imaging" | "laboratory" | "procedure" | "survey" | "exam" | "therapy" | "activity" | string)>[];
subject: Reference<"Patient">;
}

export type USCoreBodyWeightProfile_Category_VSCatSliceFlat = Omit<CodeableConcept, "coding">;

import {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ import type { Ratio } from "../../hl7-fhir-r4-core/Ratio";
import type { Reference } from "../../hl7-fhir-r4-core/Reference";
import type { SampledData } from "../../hl7-fhir-r4-core/SampledData";

export interface USCoreVitalSignsProfile extends Observation {
category: CodeableConcept<("social-history" | "vital-signs" | "imaging" | "laboratory" | "procedure" | "survey" | "exam" | "therapy" | "activity" | string)>[];
subject: Reference<"Patient">;
}

export type USCoreVitalSignsProfile_Category_VSCatSliceFlat = Omit<CodeableConcept, "coding">;

import {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,6 @@ import {
type USCoreTribalAffiliationExtensionProfileFlat,
} from "./Extension_USCoreTribalAffiliationExtension";

export interface USCorePatientProfile extends Patient {
identifier: Identifier[];
name: HumanName[];
}

import {
buildResource,
ensureProfile,
Expand Down
167 changes: 8 additions & 159 deletions src/api/writer-generator/typescript/profile.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { pascalCase, typeSchemaInfo, uppercaseFirstLetter } from "@root/api/writer-generator/utils";
import { pascalCase, uppercaseFirstLetter } from "@root/api/writer-generator/utils";
import {
type CanonicalUrl,
type Identifier,
Expand All @@ -8,7 +8,6 @@ import {
isNotChoiceDeclarationField,
isPrimitiveIdentifier,
isResourceIdentifier,
isSpecializationTypeSchema,
type ProfileExtension,
type ProfileTypeSchema,
packageMeta,
Expand Down Expand Up @@ -48,7 +47,7 @@ import {
type SliceDef,
} from "./profile-slices";
import { generateValidateMethod } from "./profile-validation";
import { fieldTsType, resolvePrimitiveType, tsEnumType, tsGet, tsTypeFromIdentifier } from "./utils";
import { fieldTsType, tsGet, tsTypeFromIdentifier } from "./utils";
import type { TypeScript } from "./writer";

type ProfileFactoryInfo = {
Expand Down Expand Up @@ -191,93 +190,21 @@ export const generateProfileIndexFile = (
if (initialProfiles.length === 0) return;
w.cd("profiles", () => {
w.cat("index.ts", () => {
const profiles: [ProfileTypeSchema, string, string | undefined][] = initialProfiles.map((profile) => {
const exports: Map<string, string> = new Map();
for (const profile of initialProfiles) {
const className = tsProfileClassName(profile);
const resourceName = tsResourceName(profile.identifier);
const overrides = detectFieldOverrides(tsIndex, profile);
let typeExport;
if (overrides.size > 0) typeExport = resourceName;
return [profile, className, typeExport];
});
if (profiles.length === 0) return;
const classExports: Map<string, string> = new Map();
const typeExports: Map<string, string> = new Map();
for (const [profile, className, typeName] of profiles) {
const moduleName = tsProfileModuleName(tsIndex, profile);
if (!classExports.has(className)) {
classExports.set(className, `export { ${className} } from "./${moduleName}"`);
}
if (typeName && !typeExports.has(typeName) && !classExports.has(typeName)) {
typeExports.set(typeName, `export type { ${typeName} } from "./${moduleName}"`);
if (!exports.has(className)) {
exports.set(className, `export { ${className} } from "./${moduleName}"`);
}
}
const allExports = [...classExports.values(), ...typeExports.values()].sort();
for (const exp of allExports) {
for (const exp of [...exports.values()].sort()) {
w.lineSM(exp);
}
});
});
};

const tsTypeForProfileField = (
tsIndex: TypeSchemaIndex,
flatProfile: ProfileTypeSchema,
fieldName: string,
field: NonNullable<ProfileTypeSchema["fields"]>[string],
): string => {
if (!isNotChoiceDeclarationField(field)) {
throw new Error(`Choice declaration fields not supported for '${fieldName}'`);
}

let tsType: string;
if (field.enum) {
if (field.type?.name === "Coding") {
tsType = `Coding<${tsEnumType(field.enum)}>`;
} else if (field.type?.name === "CodeableConcept") {
tsType = `CodeableConcept<${tsEnumType(field.enum)}>`;
} else {
tsType = tsEnumType(field.enum);
}
} else if (field.reference && field.reference.length > 0) {
const specialization = tsIndex.findLastSpecialization(flatProfile);
if (!isSpecializationTypeSchema(specialization))
throw new Error(`Invalid specialization for ${flatProfile.identifier}`);

const sField = specialization.fields?.[fieldName];
if (sField === undefined || isChoiceDeclarationField(sField) || sField.reference === undefined)
throw new Error(`Invalid field declaration for ${fieldName}`);

const sRefs = sField.reference.map((e) => e.name);
const references = field.reference
.map((ref) => {
const resRef = tsIndex.findLastSpecializationByIdentifier(ref);
if (resRef.name !== ref.name) {
return `"${resRef.name}" /*${ref.name}*/`;
}
return `'${ref.name}'`;
})
.join(" | ");
if (sRefs.length === 1 && sRefs[0] === "Resource" && references !== '"Resource"') {
// FIXME: should be generilized to type families
// Strip inner comments to avoid nested /* */ which is invalid
const cleanRefs = references.replace(/\/\*[^*]*\*\//g, "").trim();
tsType = `Reference<"Resource" /* ${cleanRefs} */ >`;
} else {
tsType = `Reference<${references}>`;
}
} else if (isPrimitiveIdentifier(field.type)) {
tsType = resolvePrimitiveType(field.type.name);
} else if (isNestedIdentifier(field.type)) {
tsType = tsResourceName(field.type);
} else if (field.type === undefined) {
throw new Error(`Undefined type for '${fieldName}' field at ${typeSchemaInfo(flatProfile)}`);
} else {
tsType = field.type.name;
}

return tsType;
};

const generateProfileHelpersImport = (
w: TypeScript,
tsIndex: TypeSchemaIndex,
Expand Down Expand Up @@ -318,12 +245,7 @@ const generateProfileHelpersImport = (
}
};

export const generateProfileImports = (
w: TypeScript,
tsIndex: TypeSchemaIndex,
flatProfile: ProfileTypeSchema,
overrides: FieldOverrides,
) => {
export const generateProfileImports = (w: TypeScript, tsIndex: TypeSchemaIndex, flatProfile: ProfileTypeSchema) => {
const usedTypes = new Map<string, { importPath: string; tsName: string }>();

const getModulePath = (typeId: Identifier): string => {
Expand All @@ -345,7 +267,6 @@ export const generateProfileImports = (
addType(flatProfile.base);
collectTypesFromSlices(tsIndex, flatProfile, addType);
const needsExtensionType = collectTypesFromExtensions(tsIndex, flatProfile, addType);
for (const { typeId } of overrides.values()) addType(typeId);
collectTypesFromFlatInput(tsIndex, flatProfile, addType);

const factoryInfo = collectProfileFactoryInfo(tsIndex, flatProfile);
Expand Down Expand Up @@ -819,75 +740,3 @@ export const generateProfileClass = (w: TypeScript, tsIndex: TypeSchemaIndex, fl
});
w.line();
};

export type FieldOverrides = Map<
string,
{ profileType: string; required: boolean; array: boolean; typeId: Identifier }
>;

export const detectFieldOverrides = (tsIndex: TypeSchemaIndex, flatProfile: ProfileTypeSchema): FieldOverrides => {
const overrides: FieldOverrides = new Map();
const specialization = tsIndex.findLastSpecialization(flatProfile);
if (!isSpecializationTypeSchema(specialization)) return overrides;

const referenceUrl = "http://hl7.org/fhir/StructureDefinition/Reference" as CanonicalUrl;
const referenceSchema = tsIndex.resolveByUrl(flatProfile.identifier.package, referenceUrl);

for (const [fieldName, pField] of Object.entries(flatProfile.fields ?? {})) {
if (!isNotChoiceDeclarationField(pField)) continue;
const sField = specialization.fields?.[fieldName];
if (!sField || isChoiceDeclarationField(sField)) continue;

// Check for Reference narrowing
if (pField.reference && sField.reference && pField.reference.length < sField.reference.length) {
if (!referenceSchema) continue;
const references = pField.reference
.map((ref) => {
const resRef = tsIndex.findLastSpecializationByIdentifier(ref);
if (resRef.name !== ref.name) {
return `"${resRef.name}"`;
}
return `"${ref.name}"`;
})
.join(" | ");
overrides.set(fieldName, {
profileType: `Reference<${references}>`,
required: pField.required ?? false,
array: pField.array ?? false,
typeId: referenceSchema.identifier,
});
}
// Check for cardinality change (optional -> required)
else if (pField.required && !sField.required) {
const tsType = tsTypeForProfileField(tsIndex, flatProfile, fieldName, pField);
overrides.set(fieldName, {
profileType: tsType,
required: true,
array: pField.array ?? false,
typeId: pField.type,
});
}
}
return overrides;
};

export const generateProfileOverrideInterface = (
w: TypeScript,
flatProfile: ProfileTypeSchema,
overrides: FieldOverrides,
) => {
if (overrides.size === 0) return;

const tsProfileName = tsResourceName(flatProfile.identifier);
const tsBaseResourceName = tsResourceName(flatProfile.base);

w.curlyBlock(["export", "interface", tsProfileName, "extends", tsBaseResourceName], () => {
for (const [fieldName, override] of overrides) {
const tsField = tsFieldName(fieldName);
const optionalSymbol = override.required ? "" : "?";
const arraySymbol = override.array ? "[]" : "";
w.lineSM(`${tsField}${optionalSymbol}: ${override.profileType}${arraySymbol}`);
}
});
w.line();
};
12 changes: 2 additions & 10 deletions src/api/writer-generator/typescript/writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,7 @@ import {
tsProfileModuleFileName,
tsResourceName,
} from "./name";
import {
detectFieldOverrides,
generateProfileClass,
generateProfileImports,
generateProfileIndexFile,
generateProfileOverrideInterface,
} from "./profile";
import { generateProfileClass, generateProfileImports, generateProfileIndexFile } from "./profile";
import { resolveFieldTsType } from "./utils";

export const resolveTsAssets = (fn: string) => {
Expand Down Expand Up @@ -308,9 +302,7 @@ export class TypeScript extends Writer<TypeScriptOptions> {
this.cat(`${tsProfileModuleFileName(tsIndex, schema)}`, () => {
this.generateDisclaimer();
const flatProfile = tsIndex.flatProfile(schema);
const overrides = detectFieldOverrides(tsIndex, flatProfile);
generateProfileImports(this, tsIndex, flatProfile, overrides);
generateProfileOverrideInterface(this, flatProfile, overrides);
generateProfileImports(this, tsIndex, flatProfile);
generateProfileClass(this, tsIndex, flatProfile);
});
});
Expand Down
Loading
Loading