Skip to content

Commit 4be9052

Browse files
committed
Split dependency types: Identifier[] for specializations, TypeIdentifier[] for profiles
- SpecializationTypeSchema.dependencies is now Identifier[] (no nested) - ProfileTypeSchema.dependencies remains TypeIdentifier[] (includes nested) - Extract collectRawDeps helper; extractDependencies filters nested, extractProfileDependencies keeps them - Split transformFhirSchema into profile/specialization branches - Split treeShakeTypeSchema dependency recalculation by schema kind - Add nestedIndex fallback in resolveByUrl for constrainedChoice - Remove dead isNestedIdentifier/kind=nested branches in TS writer - Add casts in Python, C#, tree-shake for NestedTypeSchema boundaries
1 parent 4bd9724 commit 4be9052

8 files changed

Lines changed: 98 additions & 65 deletions

File tree

‎src/api/writer-generator/csharp/csharp.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ export class CSharp extends Writer<CSharpGeneratorOptions> {
166166

167167
this.line();
168168
for (const subtype of schema.nested) {
169-
this.generateType(subtype, packageName);
169+
this.generateType(subtype as unknown as SpecializationTypeSchema, packageName);
170170
}
171171
}
172172

‎src/api/writer-generator/python.ts‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,11 @@ export class Python extends Writer<PythonGeneratorOptions> {
167167
override async generate(tsIndex: TypeSchemaIndex): Promise<void> {
168168
this.tsIndex = tsIndex;
169169
const groups: TypeSchemaPackageGroups = {
170-
groupedComplexTypes: groupByPackages(tsIndex.collectComplexTypes()),
171-
groupedResources: groupByPackages(tsIndex.collectResources()),
170+
groupedComplexTypes: groupByPackages(tsIndex.collectComplexTypes()) as Record<
171+
string,
172+
SpecializationTypeSchema[]
173+
>,
174+
groupedResources: groupByPackages(tsIndex.collectResources()) as Record<string, SpecializationTypeSchema[]>,
172175
};
173176
this.generateRootPackages(groups);
174177
this.generateSDKPackages(groups);
@@ -561,7 +564,7 @@ export class Python extends Writer<PythonGeneratorOptions> {
561564

562565
this.line();
563566
for (const subtype of schema.nested) {
564-
this.generateType(subtype);
567+
this.generateType(subtype as unknown as SpecializationTypeSchema);
565568
}
566569
}
567570

‎src/api/writer-generator/typescript/writer.ts‎

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,10 @@ import {
77
isChoiceDeclarationField,
88
isComplexTypeIdentifier,
99
isLogicalTypeSchema,
10-
isNestedIdentifier,
1110
isPrimitiveIdentifier,
1211
isProfileTypeSchema,
1312
isResourceTypeSchema,
1413
isSpecializationTypeSchema,
15-
type Name,
1614
packageMeta,
1715
packageMetaToFhir,
1816
type SpecializationTypeSchema,
@@ -156,14 +154,6 @@ export class TypeScript extends Writer<TypeScriptOptions> {
156154
name: tsResourceName(dep),
157155
dep: dep,
158156
});
159-
} else if (isNestedIdentifier(dep)) {
160-
const ndep = { ...dep };
161-
ndep.name = tsNameFromCanonical(dep.url) as Name;
162-
imports.push({
163-
tsPackage: `${importPrefix}${tsModulePath(ndep)}`,
164-
name: tsResourceName(dep),
165-
dep: dep,
166-
});
167157
} else {
168158
skipped.push(dep);
169159
}
@@ -214,8 +204,6 @@ export class TypeScript extends Writer<TypeScriptOptions> {
214204
const genericTypes = ["Reference", "Coding", "CodeableConcept"];
215205
if (genericTypes.includes(schema.identifier.name)) {
216206
name = `${schema.identifier.name}<T extends string = string>`;
217-
} else if (schema.identifier.kind === "nested") {
218-
name = tsResourceName(schema.identifier);
219207
} else {
220208
name = tsResourceName(schema.identifier);
221209
}
@@ -320,7 +308,7 @@ export class TypeScript extends Writer<TypeScriptOptions> {
320308
generateNestedTypes(tsIndex: TypeSchemaIndex, schema: SpecializationTypeSchema) {
321309
if (schema.nested) {
322310
for (const subtype of schema.nested) {
323-
this.generateType(tsIndex, subtype);
311+
this.generateType(tsIndex, subtype as unknown as SpecializationTypeSchema);
324312
this.line();
325313
}
326314
}
@@ -337,18 +325,19 @@ export class TypeScript extends Writer<TypeScriptOptions> {
337325
});
338326
});
339327
} else if (["complex-type", "resource", "logical"].includes(schema.identifier.kind)) {
328+
const resourceSchema = schema as SpecializationTypeSchema;
340329
this.cat(`${tsModuleFileName(schema.identifier)}`, () => {
341330
this.generateDisclaimer();
342-
this.generateDependenciesImports(tsIndex, schema);
343-
this.generateComplexTypeReexports(schema);
344-
this.generateNestedTypes(tsIndex, schema);
331+
this.generateDependenciesImports(tsIndex, resourceSchema);
332+
this.generateComplexTypeReexports(resourceSchema);
333+
this.generateNestedTypes(tsIndex, resourceSchema);
345334
this.comment(
346335
"CanonicalURL:",
347336
schema.identifier.url,
348337
`(pkg: ${packageMetaToFhir(packageMeta(schema))})`,
349338
);
350-
this.generateType(tsIndex, schema);
351-
this.generateResourceTypePredicate(schema);
339+
this.generateType(tsIndex, resourceSchema);
340+
this.generateResourceTypePredicate(resourceSchema);
352341
});
353342
} else {
354343
throw new Error(`Profile generation not implemented for kind: ${schema.identifier.kind}`);

‎src/typeschema/core/transformer.ts‎

Lines changed: 57 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ import {
1212
concatIdentifiers,
1313
extractExtensionDeps,
1414
type Field,
15+
type Identifier,
1516
isNestedIdentifier,
16-
isProfileIdentifier,
1717
type NestedTypeSchema,
18+
type ProfileIdentifier,
1819
packageMetaToFhir,
1920
type RichFHIRSchema,
2021
type RichValueSet,
@@ -92,26 +93,45 @@ export async function transformValueSet(
9293
};
9394
}
9495

95-
export function extractDependencies(
96-
identifier: TypeIdentifier,
96+
function collectRawDeps(
9797
base: TypeIdentifier | undefined,
9898
fields: Record<string, Field> | undefined,
9999
nestedTypes: NestedTypeSchema[] | undefined,
100-
): TypeIdentifier[] | undefined {
101-
const deps = [];
100+
): TypeIdentifier[] {
101+
const deps: TypeIdentifier[] = [];
102102
if (base) deps.push(base);
103103
if (fields) deps.push(...extractFieldDependencies(fields));
104104
if (nestedTypes) deps.push(...extractNestedDependencies(nestedTypes));
105+
return deps;
106+
}
105107

106-
const localNestedTypeUrls = new Set(nestedTypes?.map((nt) => nt.identifier.url));
108+
export function extractDependencies(
109+
identifier: TypeIdentifier,
110+
base: TypeIdentifier | undefined,
111+
fields: Record<string, Field> | undefined,
112+
nestedTypes: NestedTypeSchema[] | undefined,
113+
): Identifier[] | undefined {
114+
const deps = collectRawDeps(base, fields, nestedTypes);
107115

108-
const filtered = deps.filter((dep) => {
116+
const filtered = deps.filter((dep): dep is Identifier => {
109117
if (dep.url === identifier.url) return false;
110-
if (isProfileIdentifier(identifier)) return true;
111-
if (!isNestedIdentifier(dep)) return true;
112-
return !localNestedTypeUrls.has(dep.url);
118+
if (isNestedIdentifier(dep)) return false;
119+
return true;
113120
});
114121

122+
return concatIdentifiers(filtered) as Identifier[] | undefined;
123+
}
124+
125+
export function extractProfileDependencies(
126+
identifier: TypeIdentifier,
127+
base: TypeIdentifier | undefined,
128+
fields: Record<string, Field> | undefined,
129+
nestedTypes: NestedTypeSchema[] | undefined,
130+
): TypeIdentifier[] | undefined {
131+
const deps = collectRawDeps(base, fields, nestedTypes);
132+
133+
const filtered = deps.filter((dep) => dep.url !== identifier.url);
134+
115135
return concatIdentifiers(filtered);
116136
}
117137

@@ -134,21 +154,33 @@ export function transformFhirSchema(register: Register, fhirSchema: RichFHIRSche
134154
const fields = mkFields(register, fhirSchema, [], fhirSchema.elements, logger);
135155
const nested = mkNestedTypes(register, fhirSchema, logger);
136156

137-
const extensions =
138-
fhirSchema.derivation === "constraint" ? extractProfileExtensions(register, fhirSchema, logger) : undefined;
139-
const extensionDeps = extensions?.flatMap(extractExtensionDeps);
140-
const dependencies = concatIdentifiers(extractDependencies(identifier, base, fields, nested), extensionDeps);
141-
142-
const typeSchema: TypeSchema = {
143-
identifier,
144-
base,
145-
fields,
146-
nested,
147-
description: fhirSchema.description,
148-
dependencies,
149-
extensions,
150-
typeFamily: undefined, // NOTE: should be populateTypeFamily later.
151-
};
157+
let typeSchema: TypeSchema;
158+
if (fhirSchema.derivation === "constraint") {
159+
if (!base) throw new Error(`Profile ${fhirSchema.url} must have a base type`);
160+
const extensions = extractProfileExtensions(register, fhirSchema, logger);
161+
const extensionDeps = extensions?.flatMap(extractExtensionDeps);
162+
const rawDeps = extractProfileDependencies(identifier, base, fields, nested);
163+
typeSchema = {
164+
identifier: identifier as ProfileIdentifier,
165+
base,
166+
fields,
167+
nested,
168+
description: fhirSchema.description,
169+
dependencies: concatIdentifiers(rawDeps, extensionDeps),
170+
extensions,
171+
};
172+
} else {
173+
const rawDeps = extractDependencies(identifier, base, fields, nested);
174+
typeSchema = {
175+
identifier,
176+
base,
177+
fields,
178+
nested,
179+
description: fhirSchema.description,
180+
dependencies: rawDeps,
181+
typeFamily: undefined, // NOTE: should be populateTypeFamily later.
182+
} as TypeSchema;
183+
}
152184

153185
const bindingSchemas = collectBindingSchemas(register, fhirSchema, logger);
154186
return [typeSchema, ...bindingSchemas];

‎src/typeschema/ir/tree-shake.ts‎

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import assert from "node:assert";
22
import type { CodegenLog } from "@root/utils/log";
3-
import { extractDependencies } from "../core/transformer";
3+
import { extractDependencies, extractProfileDependencies } from "../core/transformer";
44
import {
55
type CanonicalUrl,
66
concatIdentifiers,
@@ -193,12 +193,12 @@ export const treeShakeTypeSchema = (schema: TypeSchema, rule: TreeShakeRule, _lo
193193

194194
if (rule.selectFields) {
195195
if (rule.ignoreFields) throw new Error("Cannot use both ignoreFields and selectFields in the same rule");
196-
mutableSelectFields(schema, rule.selectFields);
196+
mutableSelectFields(schema as SpecializationTypeSchema, rule.selectFields);
197197
}
198198

199199
if (rule.ignoreFields) {
200200
if (rule.selectFields) throw new Error("Cannot use both ignoreFields and selectFields in the same rule");
201-
mutableIgnoreFields(schema, rule.ignoreFields);
201+
mutableIgnoreFields(schema as SpecializationTypeSchema, rule.ignoreFields);
202202
}
203203

204204
if (isProfileTypeSchema(schema) && rule.ignoreExtensions) {
@@ -221,15 +221,19 @@ export const treeShakeTypeSchema = (schema: TypeSchema, rule: TreeShakeRule, _lo
221221
}
222222
});
223223
};
224-
collectUsedNestedTypes(schema);
224+
collectUsedNestedTypes(schema as SpecializationTypeSchema);
225225
schema.nested = schema.nested.filter((n) => usedTypes.has(n.identifier.url));
226226
}
227227

228-
const extDeps = isProfileTypeSchema(schema) ? schema.extensions?.flatMap(extractExtensionDeps) : undefined;
229-
schema.dependencies = concatIdentifiers(
230-
extractDependencies(schema.identifier, schema.base, schema.fields, schema.nested),
231-
extDeps,
232-
);
228+
if (isProfileTypeSchema(schema)) {
229+
const extDeps = schema.extensions?.flatMap(extractExtensionDeps);
230+
schema.dependencies = concatIdentifiers(
231+
extractProfileDependencies(schema.identifier, schema.base, schema.fields, schema.nested),
232+
extDeps,
233+
);
234+
} else {
235+
schema.dependencies = extractDependencies(schema.identifier, schema.base, schema.fields, schema.nested);
236+
}
233237
return schema;
234238
};
235239

@@ -267,7 +271,7 @@ export const treeShake = (tsIndex: TypeSchemaIndex, treeShake: TreeShakeConf): T
267271
for (const nest of schema.nested) {
268272
if (isNestedIdentifier(nest.identifier)) continue;
269273
const id = JSON.stringify(nest.identifier);
270-
if (!acc[id]) newSchemas.push(nest);
274+
if (!acc[id]) newSchemas.push(nest as unknown as TypeSchema);
271275
}
272276
}
273277
}

‎src/typeschema/types.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ export interface SpecializationTypeSchema {
271271
description?: string;
272272
fields?: { [k: string]: Field };
273273
nested?: NestedTypeSchema[];
274-
dependencies?: TypeIdentifier[];
274+
dependencies?: Identifier[];
275275
/** Transitive children grouped by kind (e.g. Resource → { resources: [DomainResource, Patient, …] }) */
276276
typeFamily?: {
277277
resources?: ResourceIdentifier[];

‎src/typeschema/utils.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ export const mkTypeSchemaIndex = (
208208
const nurl = nschema.identifier.url;
209209
const npkg = nschema.identifier.package;
210210
nestedIndex[nurl] ??= {};
211-
nestedIndex[nurl][npkg] = nschema;
211+
nestedIndex[nurl][npkg] = nschema as unknown as TypeSchema;
212212
});
213213
}
214214
}
@@ -231,6 +231,7 @@ export const mkTypeSchemaIndex = (
231231
}
232232
}
233233
if (index[url]?.[pkgName]) return index[url]?.[pkgName];
234+
if (nestedIndex[url]?.[pkgName]) return nestedIndex[url]?.[pkgName];
234235
logger?.dryWarn(`Type '${url}' not found in '${pkgName}'`);
235236

236237
// Fallback: search across all packages when type exists elsewhere

‎test/api/write-generator/__snapshots__/typescript.test.ts.snap‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -533,8 +533,8 @@ import type { Quantity } from "../../hl7-fhir-r4-core/Quantity";
533533
import type { Reference } from "../../hl7-fhir-r4-core/Reference";
534534

535535
export type Observation_bp_Category_VSCatSliceFlat = Omit<CodeableConcept, "coding">;
536-
export type Observation_bp_Component_SystolicBPSliceFlat = Omit<ObservationComponent, "code">;
537-
export type Observation_bp_Component_DiastolicBPSliceFlat = Omit<ObservationComponent, "code">;
536+
export type Observation_bp_Component_SystolicBPSliceFlat = Omit<ObservationComponent, "code" | "value" | "valueQuantity" | "valueCodeableConcept" | "valueString" | "valueBoolean" | "valueInteger" | "valueRange" | "valueRatio" | "valueSampledData" | "valueTime" | "valueDateTime" | "valuePeriod"> & Quantity;
537+
export type Observation_bp_Component_DiastolicBPSliceFlat = Omit<ObservationComponent, "code" | "value" | "valueQuantity" | "valueCodeableConcept" | "valueString" | "valueBoolean" | "valueInteger" | "valueRange" | "valueRatio" | "valueSampledData" | "valueTime" | "valueDateTime" | "valuePeriod"> & Quantity;
538538

539539
import {
540540
buildResource,
@@ -545,6 +545,8 @@ import {
545545
getArraySlice,
546546
ensureSliceDefaults,
547547
stripMatchKeys,
548+
wrapSliceChoice,
549+
unwrapSliceChoice,
548550
validateRequired,
549551
validateExcluded,
550552
validateFixedValue,
@@ -714,7 +716,8 @@ export class observation_bpProfile {
714716
setArraySlice(this.resource.component ??= [], match, input as ObservationComponent)
715717
return this
716718
}
717-
const value = applySliceMatch<ObservationComponent>(input ?? {}, match)
719+
const wrapped = wrapSliceChoice<ObservationComponent>(input ?? {}, "valueQuantity")
720+
const value = applySliceMatch<ObservationComponent>(wrapped, match)
718721
setArraySlice(this.resource.component ??= [], match, value)
719722
return this
720723
}
@@ -725,7 +728,8 @@ export class observation_bpProfile {
725728
setArraySlice(this.resource.component ??= [], match, input as ObservationComponent)
726729
return this
727730
}
728-
const value = applySliceMatch<ObservationComponent>(input ?? {}, match)
731+
const wrapped = wrapSliceChoice<ObservationComponent>(input ?? {}, "valueQuantity")
732+
const value = applySliceMatch<ObservationComponent>(wrapped, match)
729733
setArraySlice(this.resource.component ??= [], match, value)
730734
return this
731735
}
@@ -749,7 +753,7 @@ export class observation_bpProfile {
749753
const item = getArraySlice(this.resource.component, match)
750754
if (!item) return undefined
751755
if (mode === 'raw') return item
752-
return stripMatchKeys<Observation_bp_Component_SystolicBPSliceFlat>(item, ["code"])
756+
return unwrapSliceChoice<Observation_bp_Component_SystolicBPSliceFlat>(item, ["code"], "valueQuantity")
753757
}
754758

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

766770
// Validation

0 commit comments

Comments
 (0)