Skip to content

Commit 2b199a1

Browse files
authored
Merge pull request #111 from atomic-ehr/ref-type-family-in-type-schema
TypeSchema: Add typeFamily.complexTypes, remove resourceChildren
2 parents b56abfe + 6f015fc commit 2b199a1

11 files changed

Lines changed: 199 additions & 99 deletions

File tree

src/api/mustache/generator/ViewModelFactory.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,17 +122,15 @@ export class ViewModelFactory {
122122
}
123123

124124
private _createChildrenFor(typeRef: Identifier, cache: ViewModelCache, nestedIn?: TypeSchema): TypeViewModel[] {
125+
const schema = this.tsIndex.resolve(typeRef);
126+
if (!schema || !("typeFamily" in schema)) return [];
125127
if (isComplexTypeIdentifier(typeRef)) {
126-
return this.tsIndex
127-
.resourceChildren(typeRef)
128-
.filter(isComplexTypeIdentifier)
128+
return (schema.typeFamily?.complexTypes ?? [])
129129
.filter(this.filterPred)
130130
.map((childRef: Identifier) => this._createFor(childRef, cache, nestedIn));
131131
}
132132
if (isResourceIdentifier(typeRef)) {
133-
return this.tsIndex
134-
.resourceChildren(typeRef)
135-
.filter(isResourceIdentifier)
133+
return (schema.typeFamily?.resources ?? [])
136134
.filter(this.filterPred)
137135
.map((childRef: Identifier) => this._createFor(childRef, cache, nestedIn));
138136
}

src/api/writer-generator/python.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -365,8 +365,7 @@ export class Python extends Writer<PythonGeneratorOptions> {
365365
}
366366

367367
private shouldImportResourceFamily(resource: RegularTypeSchema): boolean {
368-
assert(this.tsIndex !== undefined);
369-
return resource.identifier.kind === "resource" && this.tsIndex.resourceChildren(resource.identifier).length > 0;
368+
return resource.identifier.kind === "resource" && (resource.typeFamily?.resources?.length ?? 0) > 0;
370369
}
371370

372371
private generateExportsDeclaration(
@@ -448,8 +447,7 @@ export class Python extends Writer<PythonGeneratorOptions> {
448447
}
449448

450449
private generateResourceTypeField(schema: RegularTypeSchema): void {
451-
assert(this.tsIndex !== undefined);
452-
const hasChildren = this.tsIndex.resourceChildren(schema.identifier).length > 0;
450+
const hasChildren = (schema.typeFamily?.resources?.length ?? 0) > 0;
453451

454452
if (hasChildren) {
455453
this.line(`${this.nameFormatFunction("resourceType")}: str = Field(`);
@@ -660,7 +658,7 @@ export class Python extends Writer<PythonGeneratorOptions> {
660658
);
661659
const families: Record<string, string[]> = {};
662660
for (const resource of this.tsIndex.collectResources()) {
663-
const children: string[] = this.tsIndex.resourceChildren(resource.identifier).map((c) => c.name);
661+
const children = (resource.typeFamily?.resources ?? []).map((c) => c.name);
664662
if (children.length > 0) {
665663
const familyName = `${resource.identifier.name}Family`;
666664
families[familyName] = children;

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
isNestedIdentifier,
1111
isPrimitiveIdentifier,
1212
isProfileTypeSchema,
13-
isResourceIdentifier,
1413
isResourceTypeSchema,
1514
isSpecializationTypeSchema,
1615
type Name,
@@ -225,7 +224,11 @@ export class TypeScript extends Writer<TypeScriptOptions> {
225224
const typeFamilyFields: { fieldName: string; familyTypeName: string }[] = [];
226225
for (const [fieldName, field] of Object.entries(schema.fields ?? {})) {
227226
if (isChoiceDeclarationField(field) || !field.type) continue;
228-
if (isResourceIdentifier(field.type) && tsIndex.resourceChildren(field.type).length > 0) {
227+
const fieldTypeSchema = tsIndex.resolve(field.type);
228+
if (
229+
isSpecializationTypeSchema(fieldTypeSchema) &&
230+
(fieldTypeSchema.typeFamily?.resources?.length ?? 0) > 0
231+
) {
229232
typeFamilyFields.push({ fieldName: tsFieldName(fieldName), familyTypeName: field.type.name });
230233
}
231234
}
@@ -257,8 +260,7 @@ export class TypeScript extends Writer<TypeScriptOptions> {
257260
}
258261
this.curlyBlock(["export", "interface", name, extendsClause], () => {
259262
if (isResourceTypeSchema(schema)) {
260-
const possibleResourceTypes = [schema.identifier];
261-
possibleResourceTypes.push(...tsIndex.resourceChildren(schema.identifier));
263+
const possibleResourceTypes = [schema.identifier, ...(schema.typeFamily?.resources ?? [])];
262264
const openSetSuffix =
263265
this.opts.openResourceTypeSet && possibleResourceTypes.length > 1 ? " | string" : "";
264266
this.lineSM(

src/typeschema/core/transformer.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,7 @@ export function extractDependencies(
115115
return concatIdentifiers(filtered);
116116
}
117117

118-
function transformFhirSchemaResource(
119-
register: Register,
120-
fhirSchema: RichFHIRSchema,
121-
logger?: CodegenLog,
122-
): TypeSchema[] {
118+
export function transformFhirSchema(register: Register, fhirSchema: RichFHIRSchema, logger?: CodegenLog): TypeSchema[] {
123119
const identifier = mkIdentifier(fhirSchema);
124120

125121
let base: Identifier | undefined;
@@ -151,16 +147,9 @@ function transformFhirSchemaResource(
151147
description: fhirSchema.description,
152148
dependencies,
153149
extensions,
150+
typeFamily: undefined, // NOTE: should be populateTypeFamily later.
154151
};
155152

156153
const bindingSchemas = collectBindingSchemas(register, fhirSchema, logger);
157154
return [typeSchema, ...bindingSchemas];
158155
}
159-
160-
export async function transformFhirSchema(
161-
register: Register,
162-
fhirSchema: RichFHIRSchema,
163-
logger?: CodegenLog,
164-
): Promise<TypeSchema[]> {
165-
return transformFhirSchemaResource(register, fhirSchema, logger);
166-
}

src/typeschema/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export const generateTypeSchemas = async (
118118
continue;
119119
}
120120

121-
for (const schema of await transformFhirSchema(register, fhirSchema, logger)) {
121+
for (const schema of transformFhirSchema(register, fhirSchema, logger)) {
122122
schemasWithSources.push({
123123
schema,
124124
sourcePackage: pkgId,

src/typeschema/types.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ type IdentifierBase = {
9494
};
9595

9696
type PrimitiveIdentifier = { kind: "primitive-type" } & IdentifierBase;
97-
type ComplexTypeIdentifier = { kind: "complex-type" } & IdentifierBase;
98-
type ResourceIdentifier = { kind: "resource" } & IdentifierBase;
97+
export type ComplexTypeIdentifier = { kind: "complex-type" } & IdentifierBase;
98+
export type ResourceIdentifier = { kind: "resource" } & IdentifierBase;
9999
export type ValueSetIdentifier = { kind: "value-set" } & IdentifierBase;
100100
export type NestedIdentifier = { kind: "nested" } & IdentifierBase;
101101
export type BindingIdentifier = { kind: "binding" } & IdentifierBase;
@@ -271,6 +271,11 @@ export interface RegularTypeSchema {
271271
fields?: { [k: string]: Field };
272272
nested?: NestedType[];
273273
dependencies?: Identifier[];
274+
/** Transitive children grouped by kind (e.g. Resource → { resources: [DomainResource, Patient, …] }) */
275+
typeFamily?: {
276+
resources?: ResourceIdentifier[];
277+
complexTypes?: ComplexTypeIdentifier[];
278+
};
274279
}
275280

276281
export interface RegularField {

src/typeschema/utils.ts

Lines changed: 31 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ import {
1212
type Identifier,
1313
isChoiceDeclarationField,
1414
isChoiceInstanceField,
15+
isComplexTypeIdentifier,
1516
isComplexTypeTypeSchema,
1617
isLogicalTypeSchema,
1718
isProfileTypeSchema,
19+
isResourceIdentifier,
1820
isResourceTypeSchema,
1921
isSpecializationTypeSchema,
2022
type PkgName,
@@ -103,74 +105,48 @@ export const sortAsDeclarationSequence = (schemas: RegularTypeSchema[]): Regular
103105
};
104106

105107
///////////////////////////////////////////////////////////
106-
// Type Schema Relations
107-
108-
interface TypeRelation {
109-
parent: Identifier;
110-
child: Identifier;
111-
}
112-
113-
const resourceRelatives = (schemas: TypeSchema[]): TypeRelation[] => {
114-
const regularSchemas = schemas.filter(
115-
(e) => isResourceTypeSchema(e) || isLogicalTypeSchema(e) || isComplexTypeTypeSchema(e),
116-
);
117-
118-
const directPairs: TypeRelation[] = [];
119-
const childrenByParent = new Map<string, Identifier[]>();
120-
121-
for (const schema of regularSchemas) {
122-
if (schema.base) {
123-
directPairs.push({ parent: schema.base, child: schema.identifier });
124-
const parentName = schema.base.name;
125-
let children = childrenByParent.get(parentName);
126-
if (!children) {
127-
children = [];
128-
childrenByParent.set(parentName, children);
129-
}
130-
children.push(schema.identifier);
131-
}
132-
}
108+
// Type Family
133109

134-
const transitiveCache = new Map<string, Identifier[]>();
135-
const getTransitiveChildren = (parentName: string): Identifier[] => {
136-
const cached = transitiveCache.get(parentName);
137-
if (cached) return cached;
110+
/** Populate `typeFamily` on specialization schemas with transitive children grouped by kind. */
111+
const populateTypeFamily = (schemas: TypeSchema[]): void => {
112+
const directChildrenByParent: Record<string, Identifier[]> = {};
113+
for (const schema of schemas) {
114+
if (!isSpecializationTypeSchema(schema) || !schema.base) continue;
115+
const parentUrl = schema.base.url;
116+
if (!directChildrenByParent[parentUrl]) directChildrenByParent[parentUrl] = [];
117+
directChildrenByParent[parentUrl].push(schema.identifier);
118+
}
138119

139-
const directChildren = childrenByParent.get(parentName) ?? [];
140-
const result: Identifier[] = [...directChildren];
141-
for (const child of directChildren) {
142-
result.push(...getTransitiveChildren(child.name));
120+
const transitiveCache: Record<string, Identifier[]> = {};
121+
const getTransitiveChildren = (parentUrl: string): Identifier[] => {
122+
if (transitiveCache[parentUrl]) return transitiveCache[parentUrl];
123+
const direct = directChildrenByParent[parentUrl] ?? [];
124+
const result: Identifier[] = [...direct];
125+
for (const child of direct) {
126+
result.push(...getTransitiveChildren(child.url));
143127
}
144-
transitiveCache.set(parentName, result);
128+
transitiveCache[parentUrl] = result;
145129
return result;
146130
};
147131

148-
const seen = new Set<string>();
149-
const allPairs: TypeRelation[] = [];
150-
151-
for (const pair of directPairs) {
152-
const key = `${pair.parent.name}|${pair.child.name}`;
153-
seen.add(key);
154-
allPairs.push(pair);
155-
156-
for (const transitiveChild of getTransitiveChildren(pair.child.name)) {
157-
const transitiveKey = `${pair.parent.name}|${transitiveChild.name}`;
158-
if (!seen.has(transitiveKey)) {
159-
seen.add(transitiveKey);
160-
allPairs.push({ parent: pair.parent, child: transitiveChild });
161-
}
162-
}
132+
for (const schema of schemas) {
133+
if (!isSpecializationTypeSchema(schema)) continue;
134+
const allChildren = getTransitiveChildren(schema.identifier.url);
135+
if (allChildren.length === 0) continue;
136+
const resources = allChildren.filter(isResourceIdentifier);
137+
const complexTypes = allChildren.filter(isComplexTypeIdentifier);
138+
const family: NonNullable<RegularTypeSchema["typeFamily"]> = {};
139+
if (resources.length > 0) family.resources = resources;
140+
if (complexTypes.length > 0) family.complexTypes = complexTypes;
141+
if (Object.keys(family).length > 0) schema.typeFamily = family;
163142
}
164-
165-
return allPairs;
166143
};
167144

168145
///////////////////////////////////////////////////////////
169146
// Type Schema Index
170147

171148
export type TypeSchemaIndex = {
172149
_schemaIndex: Record<CanonicalUrl, Record<PkgName, TypeSchema>>;
173-
_relations: TypeRelation[];
174150
schemas: TypeSchema[];
175151
schemasByPackage: Record<PkgName, TypeSchema[]>;
176152
register?: Register;
@@ -180,7 +156,6 @@ export type TypeSchemaIndex = {
180156
collectProfiles: () => ProfileTypeSchema[];
181157
resolve: (id: Identifier) => TypeSchema | undefined;
182158
resolveByUrl: (pkgName: PkgName, url: CanonicalUrl) => TypeSchema | undefined;
183-
resourceChildren: (id: Identifier) => Identifier[];
184159
tryHierarchy: (schema: TypeSchema) => TypeSchema[] | undefined;
185160
hierarchy: (schema: TypeSchema) => TypeSchema[];
186161
findLastSpecialization: (schema: TypeSchema) => TypeSchema;
@@ -241,7 +216,7 @@ export const mkTypeSchemaIndex = (
241216
for (const schema of schemas) {
242217
append(schema);
243218
}
244-
const relations = resourceRelatives(schemas);
219+
populateTypeFamily(schemas);
245220

246221
const resolve = (id: Identifier) => {
247222
if (id.kind === "nested") return nestedIndex[id.url]?.[id.package];
@@ -269,10 +244,6 @@ export const mkTypeSchemaIndex = (
269244
return undefined;
270245
};
271246

272-
const resourceChildren = (id: Identifier): Identifier[] => {
273-
return relations.filter((relative) => relative.parent.name === id.name).map((relative) => relative.child);
274-
};
275-
276247
const tryHierarchy = (schema: TypeSchema): TypeSchema[] | undefined => {
277248
const res: TypeSchema[] = [];
278249
let cur: TypeSchema | undefined = schema;
@@ -476,7 +447,6 @@ export const mkTypeSchemaIndex = (
476447

477448
return {
478449
_schemaIndex: index,
479-
_relations: relations,
480450
schemas,
481451
schemasByPackage: groupByPackages(schemas),
482452
register,
@@ -486,7 +456,6 @@ export const mkTypeSchemaIndex = (
486456
collectProfiles: () => schemas.filter(isProfileTypeSchema),
487457
resolve,
488458
resolveByUrl,
489-
resourceChildren,
490459
tryHierarchy,
491460
hierarchy,
492461
findLastSpecialization,

0 commit comments

Comments
 (0)