Skip to content
Closed
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
2 changes: 1 addition & 1 deletion src/api/writer-generator/csharp/csharp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export class CSharp extends Writer<CSharpGeneratorOptions> {

this.line();
for (const subtype of schema.nested) {
this.generateType(subtype, packageName);
this.generateType(subtype as unknown as SpecializationTypeSchema, packageName);
}
}

Expand Down
9 changes: 6 additions & 3 deletions src/api/writer-generator/python.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,11 @@ export class Python extends Writer<PythonGeneratorOptions> {
override async generate(tsIndex: TypeSchemaIndex): Promise<void> {
this.tsIndex = tsIndex;
const groups: TypeSchemaPackageGroups = {
groupedComplexTypes: groupByPackages(tsIndex.collectComplexTypes()),
groupedResources: groupByPackages(tsIndex.collectResources()),
groupedComplexTypes: groupByPackages(tsIndex.collectComplexTypes()) as Record<
string,
SpecializationTypeSchema[]
>,
groupedResources: groupByPackages(tsIndex.collectResources()) as Record<string, SpecializationTypeSchema[]>,
};
this.generateRootPackages(groups);
this.generateSDKPackages(groups);
Expand Down Expand Up @@ -561,7 +564,7 @@ export class Python extends Writer<PythonGeneratorOptions> {

this.line();
for (const subtype of schema.nested) {
this.generateType(subtype);
this.generateType(subtype as unknown as SpecializationTypeSchema);
}
}

Expand Down
25 changes: 7 additions & 18 deletions src/api/writer-generator/typescript/writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ import {
isChoiceDeclarationField,
isComplexTypeIdentifier,
isLogicalTypeSchema,
isNestedIdentifier,
isPrimitiveIdentifier,
isProfileTypeSchema,
isResourceTypeSchema,
isSpecializationTypeSchema,
type Name,
packageMeta,
packageMetaToFhir,
type SpecializationTypeSchema,
Expand Down Expand Up @@ -156,14 +154,6 @@ export class TypeScript extends Writer<TypeScriptOptions> {
name: tsResourceName(dep),
dep: dep,
});
} else if (isNestedIdentifier(dep)) {
const ndep = { ...dep };
ndep.name = tsNameFromCanonical(dep.url) as Name;
imports.push({
tsPackage: `${importPrefix}${tsModulePath(ndep)}`,
name: tsResourceName(dep),
dep: dep,
});
} else {
skipped.push(dep);
}
Expand Down Expand Up @@ -214,8 +204,6 @@ export class TypeScript extends Writer<TypeScriptOptions> {
const genericTypes = ["Reference", "Coding", "CodeableConcept"];
if (genericTypes.includes(schema.identifier.name)) {
name = `${schema.identifier.name}<T extends string = string>`;
} else if (schema.identifier.kind === "nested") {
name = tsResourceName(schema.identifier);
} else {
name = tsResourceName(schema.identifier);
}
Expand Down Expand Up @@ -320,7 +308,7 @@ export class TypeScript extends Writer<TypeScriptOptions> {
generateNestedTypes(tsIndex: TypeSchemaIndex, schema: SpecializationTypeSchema) {
if (schema.nested) {
for (const subtype of schema.nested) {
this.generateType(tsIndex, subtype);
this.generateType(tsIndex, subtype as unknown as SpecializationTypeSchema);
this.line();
}
}
Expand All @@ -337,18 +325,19 @@ export class TypeScript extends Writer<TypeScriptOptions> {
});
});
} else if (["complex-type", "resource", "logical"].includes(schema.identifier.kind)) {
const resourceSchema = schema as SpecializationTypeSchema;
this.cat(`${tsModuleFileName(schema.identifier)}`, () => {
this.generateDisclaimer();
this.generateDependenciesImports(tsIndex, schema);
this.generateComplexTypeReexports(schema);
this.generateNestedTypes(tsIndex, schema);
this.generateDependenciesImports(tsIndex, resourceSchema);
this.generateComplexTypeReexports(resourceSchema);
this.generateNestedTypes(tsIndex, resourceSchema);
this.comment(
"CanonicalURL:",
schema.identifier.url,
`(pkg: ${packageMetaToFhir(packageMeta(schema))})`,
);
this.generateType(tsIndex, schema);
this.generateResourceTypePredicate(schema);
this.generateType(tsIndex, resourceSchema);
this.generateResourceTypePredicate(resourceSchema);
});
} else {
throw new Error(`Profile generation not implemented for kind: ${schema.identifier.kind}`);
Expand Down
63 changes: 36 additions & 27 deletions src/typeschema/core/transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ import {
type Field,
type Identifier,
isNestedIdentifier,
isProfileIdentifier,
type NestedTypeSchema,
type ProfileIdentifier,
packageMetaToFhir,
type RichFHIRSchema,
type RichValueSet,
type TypeIdentifier,
type TypeSchema,
type ValueSetTypeSchema,
} from "@typeschema/types";
Expand Down Expand Up @@ -60,8 +61,8 @@ export function mkFields(
return fields;
}

function extractFieldDependencies(fields: Record<string, Field>): Identifier[] {
const deps: Identifier[] = [];
function extractFieldDependencies(fields: Record<string, Field>): TypeIdentifier[] {
const deps: TypeIdentifier[] = [];

for (const field of Object.values(fields)) {
if ("type" in field && field.type) {
Expand Down Expand Up @@ -98,21 +99,18 @@ export function extractDependencies(
fields: Record<string, Field> | undefined,
nestedTypes: NestedTypeSchema[] | undefined,
): Identifier[] | undefined {
const deps = [];
const deps: TypeIdentifier[] = [];
if (base) deps.push(base);
if (fields) deps.push(...extractFieldDependencies(fields));
if (nestedTypes) deps.push(...extractNestedDependencies(nestedTypes));

const localNestedTypeUrls = new Set(nestedTypes?.map((nt) => nt.identifier.url));

const filtered = deps.filter((dep) => {
const filtered = deps.filter((dep): dep is Identifier => {
if (dep.url === identifier.url) return false;
if (isProfileIdentifier(identifier)) return true;
if (!isNestedIdentifier(dep)) return true;
return !localNestedTypeUrls.has(dep.url);
if (isNestedIdentifier(dep)) return false;
return true;
});

return concatIdentifiers(filtered);
return concatIdentifiers(filtered) as Identifier[] | undefined;
}

export function transformFhirSchema(register: Register, fhirSchema: RichFHIRSchema, logger?: CodegenLog): TypeSchema[] {
Expand All @@ -133,22 +131,33 @@ export function transformFhirSchema(register: Register, fhirSchema: RichFHIRSche

const fields = mkFields(register, fhirSchema, [], fhirSchema.elements, logger);
const nested = mkNestedTypes(register, fhirSchema, logger);

const extensions =
fhirSchema.derivation === "constraint" ? extractProfileExtensions(register, fhirSchema, logger) : undefined;
const extensionDeps = extensions?.flatMap(extractExtensionDeps);
const dependencies = concatIdentifiers(extractDependencies(identifier, base, fields, nested), extensionDeps);

const typeSchema: TypeSchema = {
identifier,
base,
fields,
nested,
description: fhirSchema.description,
dependencies,
extensions,
typeFamily: undefined, // NOTE: should be populateTypeFamily later.
};
const rawDeps = extractDependencies(identifier, base, fields, nested);

let typeSchema: TypeSchema;
if (fhirSchema.derivation === "constraint") {
if (!base) throw new Error(`Profile ${fhirSchema.url} must have a base type`);
const extensions = extractProfileExtensions(register, fhirSchema, logger);
const extensionDeps = extensions?.flatMap(extractExtensionDeps);
typeSchema = {
identifier: identifier as ProfileIdentifier,
base,
fields,
nested,
description: fhirSchema.description,
dependencies: concatIdentifiers(rawDeps, extensionDeps),
extensions,
};
} else {
typeSchema = {
identifier,
base,
fields,
nested,
description: fhirSchema.description,
dependencies: rawDeps,
typeFamily: undefined, // NOTE: should be populateTypeFamily later.
} as TypeSchema;
}

const bindingSchemas = collectBindingSchemas(register, fhirSchema, logger);
return [typeSchema, ...bindingSchemas];
Expand Down
2 changes: 1 addition & 1 deletion src/typeschema/ir/logic-promotion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const promoteLogical = (tsIndex: TypeSchemaIndex, promotes: LogicalPromot
return Object.fromEntries(
Object.entries(fields).map(([k, f]) => {
if (isChoiceDeclarationField(f)) return [k, f];
return [k, { ...f, type: f.type ? replace(f.type) : undefined }];
return [k, { ...f, type: f.type ? replace(f.type as Identifier) : undefined }];
}),
);
};
Expand Down
8 changes: 4 additions & 4 deletions src/typeschema/ir/tree-shake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,12 @@ export const treeShakeTypeSchema = (schema: TypeSchema, rule: TreeShakeRule, _lo

if (rule.selectFields) {
if (rule.ignoreFields) throw new Error("Cannot use both ignoreFields and selectFields in the same rule");
mutableSelectFields(schema, rule.selectFields);
mutableSelectFields(schema as SpecializationTypeSchema, rule.selectFields);
}

if (rule.ignoreFields) {
if (rule.selectFields) throw new Error("Cannot use both ignoreFields and selectFields in the same rule");
mutableIgnoreFields(schema, rule.ignoreFields);
mutableIgnoreFields(schema as SpecializationTypeSchema, rule.ignoreFields);
}

if (isProfileTypeSchema(schema) && rule.ignoreExtensions) {
Expand All @@ -221,7 +221,7 @@ export const treeShakeTypeSchema = (schema: TypeSchema, rule: TreeShakeRule, _lo
}
});
};
collectUsedNestedTypes(schema);
collectUsedNestedTypes(schema as SpecializationTypeSchema);
schema.nested = schema.nested.filter((n) => usedTypes.has(n.identifier.url));
}

Expand Down Expand Up @@ -267,7 +267,7 @@ export const treeShake = (tsIndex: TypeSchemaIndex, treeShake: TreeShakeConf): T
for (const nest of schema.nested) {
if (isNestedIdentifier(nest.identifier)) continue;
const id = JSON.stringify(nest.identifier);
if (!acc[id]) newSchemas.push(nest);
if (!acc[id]) newSchemas.push(nest as unknown as TypeSchema);
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions src/typeschema/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,12 @@ export type ValueSetIdentifier = { kind: "value-set" } & IdentifierBase;
export type NestedIdentifier = { kind: "nested" } & IdentifierBase;
export type BindingIdentifier = { kind: "binding" } & IdentifierBase;
export type ProfileIdentifier = { kind: "profile" } & IdentifierBase;
type LogicalIdentifier = { kind: "logical" } & IdentifierBase;
export type LogicalIdentifier = { kind: "logical" } & IdentifierBase;

export type Identifier =
| PrimitiveIdentifier
| ComplexTypeIdentifier
| ResourceIdentifier
| NestedIdentifier
| BindingIdentifier
| ValueSetIdentifier
| ProfileIdentifier
Expand Down
3 changes: 2 additions & 1 deletion src/typeschema/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ export const mkTypeSchemaIndex = (
const nurl = nschema.identifier.url;
const npkg = nschema.identifier.package;
nestedIndex[nurl] ??= {};
nestedIndex[nurl][npkg] = nschema;
nestedIndex[nurl][npkg] = nschema as unknown as TypeSchema;
});
}
}
Expand All @@ -232,6 +232,7 @@ export const mkTypeSchemaIndex = (
}
}
if (index[url]?.[pkgName]) return index[url]?.[pkgName];
if (nestedIndex[url]?.[pkgName]) return nestedIndex[url]?.[pkgName];
logger?.dryWarn(`Type '${url}' not found in '${pkgName}'`);

// Fallback: search across all packages when type exists elsewhere
Expand Down
32 changes: 16 additions & 16 deletions test/api/write-generator/__snapshots__/typescript.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -533,8 +533,8 @@ import type { Quantity } from "../../hl7-fhir-r4-core/Quantity";
import type { Reference } from "../../hl7-fhir-r4-core/Reference";

export type Observation_bp_Category_VSCatSliceFlat = Omit<CodeableConcept, "coding">;
export type Observation_bp_Component_SystolicBPSliceFlat = Omit<ObservationComponent, "code">;
export type Observation_bp_Component_DiastolicBPSliceFlat = Omit<ObservationComponent, "code">;
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;

import {
buildResource,
Expand All @@ -545,6 +545,8 @@ import {
getArraySlice,
ensureSliceDefaults,
stripMatchKeys,
wrapSliceChoice,
unwrapSliceChoice,
validateRequired,
validateExcluded,
validateFixedValue,
Expand Down Expand Up @@ -714,7 +716,8 @@ export class observation_bpProfile {
setArraySlice(this.resource.component ??= [], match, input as ObservationComponent)
return this
}
const value = applySliceMatch<ObservationComponent>(input ?? {}, match)
const wrapped = wrapSliceChoice<ObservationComponent>(input ?? {}, "valueQuantity")
const value = applySliceMatch<ObservationComponent>(wrapped, match)
setArraySlice(this.resource.component ??= [], match, value)
return this
}
Expand All @@ -725,7 +728,8 @@ export class observation_bpProfile {
setArraySlice(this.resource.component ??= [], match, input as ObservationComponent)
return this
}
const value = applySliceMatch<ObservationComponent>(input ?? {}, match)
const wrapped = wrapSliceChoice<ObservationComponent>(input ?? {}, "valueQuantity")
const value = applySliceMatch<ObservationComponent>(wrapped, match)
setArraySlice(this.resource.component ??= [], match, value)
return this
}
Expand All @@ -749,7 +753,7 @@ export class observation_bpProfile {
const item = getArraySlice(this.resource.component, match)
if (!item) return undefined
if (mode === 'raw') return item
return stripMatchKeys<Observation_bp_Component_SystolicBPSliceFlat>(item, ["code"])
return unwrapSliceChoice<Observation_bp_Component_SystolicBPSliceFlat>(item, ["code"], "valueQuantity")
}

public getDiastolicBP(mode: 'flat'): Observation_bp_Component_DiastolicBPSliceFlat | undefined;
Expand All @@ -760,7 +764,7 @@ export class observation_bpProfile {
const item = getArraySlice(this.resource.component, match)
if (!item) return undefined
if (mode === 'raw') return item
return stripMatchKeys<Observation_bp_Component_DiastolicBPSliceFlat>(item, ["code"])
return unwrapSliceChoice<Observation_bp_Component_DiastolicBPSliceFlat>(item, ["code"], "valueQuantity")
}

// Validation
Expand Down Expand Up @@ -1066,8 +1070,8 @@ import type { Reference } from "../../hl7-fhir-r4-core/Reference";
import type { SampledData } from "../../hl7-fhir-r4-core/SampledData";

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;
export type USCoreBloodPressureProfile_Component_SystolicSliceFlat = Omit<ObservationComponent, "code">;
export type USCoreBloodPressureProfile_Component_DiastolicSliceFlat = Omit<ObservationComponent, "code">;

import {
buildResource,
Expand All @@ -1078,8 +1082,6 @@ import {
getArraySlice,
ensureSliceDefaults,
stripMatchKeys,
wrapSliceChoice,
unwrapSliceChoice,
validateRequired,
validateExcluded,
validateFixedValue,
Expand Down Expand Up @@ -1339,8 +1341,7 @@ export class USCoreBloodPressureProfile {
setArraySlice(this.resource.component ??= [], match, input as ObservationComponent)
return this
}
const wrapped = wrapSliceChoice<ObservationComponent>(input ?? {}, "valueQuantity")
const value = applySliceMatch<ObservationComponent>(wrapped, match)
const value = applySliceMatch<ObservationComponent>(input ?? {}, match)
setArraySlice(this.resource.component ??= [], match, value)
return this
}
Expand All @@ -1351,8 +1352,7 @@ export class USCoreBloodPressureProfile {
setArraySlice(this.resource.component ??= [], match, input as ObservationComponent)
return this
}
const wrapped = wrapSliceChoice<ObservationComponent>(input ?? {}, "valueQuantity")
const value = applySliceMatch<ObservationComponent>(wrapped, match)
const value = applySliceMatch<ObservationComponent>(input ?? {}, match)
setArraySlice(this.resource.component ??= [], match, value)
return this
}
Expand All @@ -1376,7 +1376,7 @@ export class USCoreBloodPressureProfile {
const item = getArraySlice(this.resource.component, match)
if (!item) return undefined
if (mode === 'raw') return item
return unwrapSliceChoice<USCoreBloodPressureProfile_Component_SystolicSliceFlat>(item, ["code"], "valueQuantity")
return stripMatchKeys<USCoreBloodPressureProfile_Component_SystolicSliceFlat>(item, ["code"])
}

public getDiastolic(mode: 'flat'): USCoreBloodPressureProfile_Component_DiastolicSliceFlat | undefined;
Expand All @@ -1387,7 +1387,7 @@ export class USCoreBloodPressureProfile {
const item = getArraySlice(this.resource.component, match)
if (!item) return undefined
if (mode === 'raw') return item
return unwrapSliceChoice<USCoreBloodPressureProfile_Component_DiastolicSliceFlat>(item, ["code"], "valueQuantity")
return stripMatchKeys<USCoreBloodPressureProfile_Component_DiastolicSliceFlat>(item, ["code"])
}

// Validation
Expand Down
Loading
Loading