Skip to content

Commit 5d0c604

Browse files
committed
ref: make isFamilyType a TypeSchemaIndex method
The family-root predicate belongs to the index that resolves it: replaces mkIsFamilyType in the TypeScript profile writer and the byte-identical inline copy in the Python naming utils.
1 parent 5d9d3fb commit 5d0c604

5 files changed

Lines changed: 16 additions & 18 deletions

File tree

‎CLAUDE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ Single-element slices (`max: 1`) keep the existing single-item API.
286286
287287
When a reference target is a family type (e.g. `Resource`, `DomainResource`), the generated type uses `Reference<string /* Resource */>` instead of `Reference<"Resource">`. This makes narrower profile references like `Reference<"Patient">` assignable to the base type field.
288288
289-
Detection uses `mkIsFamilyType(tsIndex)` which checks `schema.typeFamily.resources.length > 0`.
289+
Detection uses `tsIndex.isFamilyType` which checks `schema.typeFamily.resources.length > 0`.
290290
291291
### Slice Field Validation
292292

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

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,8 @@ export const pyReferenceTypeParam = (
133133
tsIndex: TypeSchemaIndex,
134134
): string | undefined => {
135135
if (!field.reference || field.reference.resource.length === 0) return undefined;
136-
const isFamilyType = (ref: TypeIdentifier): boolean => {
137-
const schema = tsIndex.resolveType(ref);
138-
if (!schema || !("typeFamily" in schema)) return false;
139-
return (schema.typeFamily?.resources?.length ?? 0) > 0;
140-
};
141136
const resolved = field.reference.resource.map((ref) => tsIndex.findLastSpecializationByIdentifier(ref));
142-
if (resolved.some(isFamilyType)) return undefined;
137+
if (resolved.some(tsIndex.isFamilyType)) return undefined;
143138
const names = [...new Set(resolved.map((ref) => ref.name))];
144139
return `Literal[${names.map((n) => JSON.stringify(n)).join(", ")}]`;
145140
};

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

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -119,14 +119,6 @@ const tryPromoteChoice = (
119119
promotedChoices.add(choiceName);
120120
};
121121

122-
export const mkIsFamilyType =
123-
(tsIndex: TypeSchemaIndex) =>
124-
(ref: TypeIdentifier): boolean => {
125-
const schema = tsIndex.resolveType(ref);
126-
if (!schema || !("typeFamily" in schema)) return false;
127-
return (schema.typeFamily?.resources?.length ?? 0) > 0;
128-
};
129-
130122
export const collectProfileFactoryInfo = (
131123
tsIndex: TypeSchemaIndex,
132124
snapshot: SnapshotProfileTypeSchema,
@@ -139,7 +131,7 @@ export const collectProfileFactoryInfo = (
139131
const fields = snapshot.fields;
140132
const promotedChoices = new Set<string>();
141133
const resolveRef = tsIndex.findLastSpecializationByIdentifier;
142-
const isFamilyType = mkIsFamilyType(tsIndex);
134+
const isFamilyType = tsIndex.isFamilyType;
143135

144136
if (isResourceIdentifier(snapshot.base)) {
145137
autoFields.push({ name: "resourceType", value: JSON.stringify(snapshot.base.name) });

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import {
4242
tsProfileModuleFileName,
4343
tsResourceName,
4444
} from "./name";
45-
import { generateProfileClass, generateProfileImports, generateProfileIndexFile, mkIsFamilyType } from "./profile";
45+
import { generateProfileClass, generateProfileImports, generateProfileIndexFile } from "./profile";
4646
import { resolveFieldTsType } from "./utils";
4747

4848
export const resolveTsAssets = (fn: string) => resolveGeneratorAsset(import.meta.url, "typescript", fn);
@@ -475,7 +475,7 @@ export class TypeScript extends Writer<TypeScriptOptions> {
475475
});
476476
});
477477
} else if (isSpecializationTypeSchema(schema)) {
478-
const isFamilyType = mkIsFamilyType(tsIndex);
478+
const isFamilyType = tsIndex.isFamilyType;
479479
this.cat(`${tsModuleFileName(schema.identifier)}`, () => {
480480
this.generateDisclaimer();
481481
this.generateDependenciesImports(tsIndex, schema);

‎src/typeschema/utils.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ export type TypeSchemaIndex = {
353353
hierarchy: (schema: TypeSchema) => TypeSchema[];
354354
findLastSpecialization: (schema: TypeSchema) => TypeSchema;
355355
findLastSpecializationByIdentifier: (id: TypeIdentifier) => TypeIdentifier;
356+
isFamilyType: (id: TypeIdentifier) => boolean;
356357
flatProfile: (schema: ProfileTypeSchema) => ProfileTypeSchema;
357358
constrainedChoice: (
358359
pkgName: PkgName,
@@ -588,6 +589,15 @@ export const mkTypeSchemaIndex = (
588589
return findLastSpecialization(resolved).identifier;
589590
};
590591

592+
/** True when `id` resolves to a family root — a specialization with concrete
593+
* resource descendants (e.g. Resource, DomainResource), where a reference
594+
* target admits any member of the family. */
595+
const isFamilyType = (id: TypeIdentifier): boolean => {
596+
const schema = resolveType(id);
597+
if (!schema || !("typeFamily" in schema)) return false;
598+
return (schema.typeFamily?.resources?.length ?? 0) > 0;
599+
};
600+
591601
/** Resolve the permitted choice variants monotonically through the profile hierarchy.
592602
* Each profile's constraints (restated declaration, declared instances, exclusions)
593603
* intersect with the inherited permitted set, so a child profile can never
@@ -859,6 +869,7 @@ export const mkTypeSchemaIndex = (
859869
hierarchy,
860870
findLastSpecialization,
861871
findLastSpecializationByIdentifier,
872+
isFamilyType,
862873
flatProfile,
863874
constrainedChoice,
864875
sliceChoiceVariants,

0 commit comments

Comments
 (0)