Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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 packages/fhir-eswatini/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"type": "module",
"fhir": {
"specUrl": "http://172.209.216.154/definitions.json.zip",
"adaptorGeneratedDate": "2026-06-02T15:23:39.432Z",
"adaptorGeneratedDate": "2026-06-02T20:25:01.139Z",
"generatorVersion": "0.7.9",
"options": {
"base": "fhir-4"
Expand Down
1 change: 1 addition & 0 deletions packages/fhir-eswatini/src/datatypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const {
coding,
composite,
concept,
ensureConceptText,
ext,
extendSystemMap,
extendValues,
Expand Down
31 changes: 31 additions & 0 deletions packages/fhir-eswatini/src/profiles/SzPatient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,37 @@ export default function(props: Partial<Patient_SzPatient_Props>) {
}
}

{
if (!_.isNil(props._birthDate)) {
if (_.isPlainObject(props._birthDate)) {
resource._birthDate = Object.assign({}, props._birthDate);
} else {
delete resource._birthDate;
resource._birthDate = {};

dt.addExtension(
resource._birthDate,
"http://hl7.org/fhir/StructureDefinition/patient-birthTime",
props._birthDate
);
}
}

if (!_.isNil(props._birthTime)) {
delete resource._birthTime;

if (!resource._birthDate) {
resource._birthDate = {};
}

dt.addExtension(
resource._birthDate,
"http://hl7.org/fhir/StructureDefinition/patient-birthTime",
props._birthTime
);
}
}

if (!_.isNil(props.deceased)) {
delete resource.deceased;
dt.composite(resource, "deceased", props.deceased);
Expand Down
19 changes: 19 additions & 0 deletions packages/fhir-eswatini/test/resources/Patient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -439,4 +439,23 @@ describe('SzPatient', () => {
valueDateTime: '2025-06-01T10:00:00Z',
});
});
it('should map primitive extensions', ()=>{
Comment thread
hunterachieng marked this conversation as resolved.
Outdated
const state = {};

const resource = b.patient('SzPatient',{
birthDate:'10/07/1990',
Comment thread
hunterachieng marked this conversation as resolved.
Outdated
_birthDate:'2000-01-01T14:35:45-05:00'
});

assert.deepEqual(resource._birthDate, {
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/patient-birthTime",
"valueDateTime": "2000-01-01T14:35:45-05:00"
}
]
});

assert.deepEqual(resource.birthDate, '10/07/1990');
});
});
3 changes: 2 additions & 1 deletion packages/fhir-eswatini/types/builders.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,7 @@ declare const cc: (codings: (builders.Coding | [string, string, Omit<builders.Co
declare const coding: typeof builders.coding;
declare const composite: (object: any, key: any, value: any) => void;
declare const concept: (codings: (builders.Coding | [string, string, Omit<builders.Coding, "system" | "code">?]) | (builders.Coding | [string, string, Omit<builders.Coding, "system" | "code">?])[], extra?: Omit<builders.CodeableConcept, "coding">) => builders.CodeableConcept;
declare const ensureConceptText: (concept: any) => void;
declare const ext: (url: string, value: any, props?: Omit<builders.Extension, "url">) => {
extension: ({
url: string;
Expand Down Expand Up @@ -1239,5 +1240,5 @@ declare function serviceRequest(type: "SzReferral", props: ServiceRequest_SzRefe
declare function specimen(type: "SzLabSpecimen", props: Specimen_SzLabSpecimen_Props): any;
declare function specimen(props: Specimen_SzLabSpecimen_Props): any;

export { addExtension, appointment, c, cc, coding, composite, concept, condition, encounter, episodeOfCare, ext, extendSystemMap, extendValues, extension, findExtension, id, identifier, location, lookupValue, mapSystems, mapValues, medication, medicationDispense, medicationRequest, observation, organization, patient, practitioner, procedure, ref, reference, serviceRequest, setSystemMap, setValues, specimen, value };
export { addExtension, appointment, c, cc, coding, composite, concept, condition, encounter, ensureConceptText, episodeOfCare, ext, extendSystemMap, extendValues, extension, findExtension, id, identifier, location, lookupValue, mapSystems, mapValues, medication, medicationDispense, medicationRequest, observation, organization, patient, practitioner, procedure, ref, reference, serviceRequest, setSystemMap, setValues, specimen, value };

128 changes: 128 additions & 0 deletions tools/generate-fhir/src/generate-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -600,10 +600,138 @@ const mapSimpleProp = (propName: string, mapping: Mapping, schema: Schema) => {
return ifPropInInput(propName, [assignProp], elseStatement);
};

const mapPrimitiveTypeDef = (propName: string, schema: Schema) => {
// Primitive metadata elements lives in the sibling underscore property, eg _birthDate
const primitivePropName = `_${propName}`;
// Only retrieve extension-backed child props
const primitiveExtensions = Object.entries(schema.typeDef || {}).filter(
([, spec]: [string, any]) => spec.extension,
);
const statements: StatementKind[] = [];

const inputPropRef = (name: string) => safelyRefProp(INPUT_NAME, name);
const resourcePropRef = (name: string) => safelyRefProp(RESOURCE_NAME, name);
const copyPrimitiveMetadata = (name: string) =>
b.expressionStatement(
b.assignmentExpression(
'=',
resourcePropRef(name),
b.callExpression(
b.memberExpression(b.identifier('Object'), b.identifier('assign')),
[b.objectExpression([]), inputPropRef(name)],
),
),
);
const createEmptyPrimitiveMetadata = (name: string) =>
b.expressionStatement(
b.assignmentExpression('=', resourcePropRef(name), b.objectExpression([])),
);
const addPrimitiveExtension = (
targetName: string,
sourceName: string,
url: string,
) =>
b.expressionStatement(
b.callExpression(
b.memberExpression(b.identifier('dt'), b.identifier('addExtension')),
[resourcePropRef(targetName), b.stringLiteral(url), inputPropRef(sourceName)],
),
);

if (primitiveExtensions.length === 1) {
const [[, spec]] = primitiveExtensions as [string, any][];
statements.push(
ifPropInInput(
primitivePropName,
[
b.ifStatement(
b.callExpression(
b.memberExpression(b.identifier('_'), b.identifier('isPlainObject')),
[inputPropRef(primitivePropName)],
),
b.blockStatement([
// If the caller already passed primitive metadata, preserve it
copyPrimitiveMetadata(primitivePropName),
]),
b.blockStatement([
// Otherwise treat the underscore value as shorthand for the single known extension
b.expressionStatement(
b.unaryExpression('delete', resourcePropRef(primitivePropName)),
),
createEmptyPrimitiveMetadata(primitivePropName),
addPrimitiveExtension(
primitivePropName,
primitivePropName,
spec.extension.url,
),
]),
),
],
),
);
} else {
statements.push(
ifPropInInput(
primitivePropName,
[
b.ifStatement(
b.callExpression(
b.memberExpression(b.identifier('_'), b.identifier('isPlainObject')),
[inputPropRef(primitivePropName)],
),
b.blockStatement([
// Multiple extension choices: only pass through explicit primitive metadata objects
copyPrimitiveMetadata(primitivePropName),
]),
),
],
),
);
}

for (const [key, spec] of primitiveExtensions as [string, any][]) {
// Support shorthand child props like _birthTime and rewrite them into _birthDate.extension[]
const inputPropName = `_${key}`;
statements.push(
ifPropInInput(
inputPropName,
[
b.expressionStatement(
b.unaryExpression('delete', resourcePropRef(inputPropName)),
),
b.ifStatement(
b.unaryExpression('!', resourcePropRef(primitivePropName)),
b.blockStatement([
// Create the primitive metadata container before appending extension shorthand
createEmptyPrimitiveMetadata(primitivePropName),
]),
),
addPrimitiveExtension(
primitivePropName,
inputPropName,
spec.extension.url,
),
],
),
);
}

return b.blockStatement(statements);
};

const isPrimitiveTypeDefParent = (schema: Schema) =>
!schema.isArray &&
!!schema.typeDef &&
schema.type.every(type => type[0] === type[0]?.toLowerCase());

// map a type def (ie, a nested object) property by property
// TODO this is designed to handle singleton and array types
// The array stuff adds a lot of complication and I need tests on both formats
const mapTypeDef = (propName: string, mapping: Mapping, schema: Schema) => {
if (isPrimitiveTypeDefParent(schema)) {
return mapPrimitiveTypeDef(propName, schema);
}

const statements: any[] = [];

statements.push(
Expand Down
21 changes: 18 additions & 3 deletions tools/generate-fhir/src/generate-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ async function parseProp(
data,
) {
let [parent, prop] = path.split('.');
const isExtensionPath = prop === 'extension';
// TODO skip if multiple dots

if (/\[x\]/.test(prop)) {
Expand All @@ -366,7 +367,16 @@ async function parseProp(
if (schema.props[parent]) {
const def: PropDef = {};

if (!data.type || schema.props[parent].type.includes('date')) {
// Keep primitive props
const isExtensionChild = isExtensionPath;
const hasSlice = !!data.sliceName;
const parentTypes = schema.props[parent].type || [];
const isPrimitiveParent =
!schema.props[parent].typeDef &&
parentTypes.length > 0 &&
parentTypes.every(type => type[0] === type[0]?.toLowerCase());

if (!data.type || (isPrimitiveParent && !(isExtensionChild && hasSlice))) {
return;
}

Expand All @@ -384,7 +394,8 @@ async function parseProp(
type.profile.length &&
type.profile[0].match(/\/StructureDefinition/)
) {
const typeId = type.profile[0].split('/').at(-1);
const extensionUrl = type.profile[0].split('|')[0];
const typeId = extensionUrl.split('/').at(-1);
const spec = fullSpec[typeId];

if (spec) {
Expand All @@ -396,7 +407,11 @@ async function parseProp(
// look for extension.value[x] in the spec
};
} else {
console.log('WARNING: spec not found for ', typeId);
// Some extension profiles are not in the downloaded spec
// The profile URL is still enough for codegen
def.extension = {
url: extensionUrl,
};
}
} else {
simpleType = typeDefs[type.code] || type.code;
Expand Down
Loading
Loading