babelfhir-ts 1.5.3 → 1.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -30,7 +30,7 @@ export function generateBackboneSliceTypes(ctx) {
30
30
  const { interfaceName, baseResource, newFields, baseFields, interfaces, importManager, inferBackboneType } = ctx;
31
31
  const rules = getRules();
32
32
  // 1. Find all sliced backbone element arrays
33
- const sliceGroups = collectSliceGroups(newFields, baseResource);
33
+ const sliceGroups = collectSliceGroups(newFields, baseFields, baseResource);
34
34
  if (sliceGroups.length === 0)
35
35
  return;
36
36
  for (const group of sliceGroups) {
@@ -109,10 +109,18 @@ export function generateBackboneSliceTypes(ctx) {
109
109
  }
110
110
  /**
111
111
  * Collect sliced backbone element groups from the fields.
112
+ *
113
+ * Handles two scenarios:
114
+ * 1. The differential includes the unsliced parent field (e.g. Observation.component) —
115
+ * the parent field carries slicing metadata and baseTypeCode.
116
+ * 2. The differential only contains slice entries (e.g. Observation.component:confidence)
117
+ * without redeclaring the parent — common in downstream profiles. In this case the
118
+ * parent field is looked up from baseFields.
112
119
  */
113
- function collectSliceGroups(fields, baseResource) {
120
+ function collectSliceGroups(fields, baseFields, baseResource) {
114
121
  const groups = [];
115
122
  const seenPaths = new Set();
123
+ // Pass 1: parent field present in newFields
116
124
  for (const field of fields) {
117
125
  if (field.sliceName)
118
126
  continue; // Skip slice entries themselves
@@ -121,48 +129,71 @@ function collectSliceGroups(fields, baseResource) {
121
129
  const path = field.name;
122
130
  if (seenPaths.has(path))
123
131
  continue;
124
- // Check if there are named slices at this path
125
132
  const sliceFields = fields.filter(f => f.name === path && f.sliceName);
126
133
  if (sliceFields.length === 0)
127
134
  continue;
128
- // Must be a true backbone element — only process fields whose base FHIR type
129
- // is BackboneElement or Element (inline nested types). Skip complex DataTypes
130
- // like Identifier, CodeableConcept, ContactPoint, etc. — those have slices
131
- // handled by the main type mapping and profiled type reference system.
132
- const baseType = field.baseTypeCode || field.type || '';
133
- if (baseType !== 'BackboneElement' && baseType !== 'Element')
135
+ if (!isEligibleBackbonePath(field.baseTypeCode || field.type || '', path, baseResource))
136
+ continue;
137
+ const childFields = gatherChildFields(fields, path);
138
+ const slicingRules = field.slicingRules || 'open';
139
+ seenPaths.add(path);
140
+ groups.push({ parentField: field, sliceFields, childFields, slicingRules });
141
+ }
142
+ // Pass 2: orphaned slice entries whose parent path wasn't found in newFields.
143
+ // The differential may only contain slice entries (e.g. Observation.component:confidence)
144
+ // without the unsliced parent. Resolve the parent from baseFields.
145
+ for (const field of fields) {
146
+ if (!field.sliceName)
147
+ continue;
148
+ const path = field.name;
149
+ if (seenPaths.has(path))
134
150
  continue;
135
- // Skip extension slices handled separately by postProcessExtensions
136
- const fieldName = path.split('.').pop() || '';
137
- if (fieldName === 'extension' || fieldName === 'modifierExtension')
151
+ // Find the parent field in baseFields
152
+ const parentField = baseFields.find(f => f.name === path && !f.sliceName);
153
+ if (!parentField)
138
154
  continue;
139
- // Skip Bundle.entry and similar resource-container slices — these use profiled
140
- // type references that are handled by the external profile resolution system
141
- if (fieldName === 'entry')
155
+ if (!parentField.isArray)
142
156
  continue;
143
- // Must be a root-level field (direct child of the base resource)
144
- const parts = path.split('.');
145
- if (parts.length !== 2)
157
+ if (!isEligibleBackbonePath(parentField.baseTypeCode || parentField.type || '', path, baseResource))
146
158
  continue;
147
- if (baseResource && parts[0] !== baseResource)
159
+ const sliceFields = fields.filter(f => f.name === path && f.sliceName);
160
+ if (sliceFields.length === 0)
148
161
  continue;
149
- // Gather all child fields for any slice of this element
150
- const childFields = fields.filter(f => {
151
- if (f.name === path)
152
- return false; // Skip the parent itself
153
- const childId = f.elementId || f.name;
154
- // Match children that belong to ANY slice of this element
155
- return childId.startsWith(`${path}:`) || (f.name.startsWith(`${path}.`) && f.name !== path);
156
- });
157
- // Determine slicing rules from the parent field's metadata
158
- // The slicing rules come from the SD's element.slicing.rules
159
- // We default to 'open' since that's the FHIR default
160
- const slicingRules = field.slicingRules || 'open';
162
+ const childFields = gatherChildFields(fields, path);
163
+ const slicingRules = parentField.slicingRules || 'open';
161
164
  seenPaths.add(path);
162
- groups.push({ parentField: field, sliceFields, childFields, slicingRules });
165
+ groups.push({ parentField, sliceFields, childFields, slicingRules });
163
166
  }
164
167
  return groups;
165
168
  }
169
+ /** Check whether a field path is eligible for backbone slice typing. */
170
+ function isEligibleBackbonePath(baseType, path, baseResource) {
171
+ if (baseType !== 'BackboneElement' && baseType !== 'Element')
172
+ return false;
173
+ const fieldName = path.split('.').pop() || '';
174
+ // Skip extension slices — handled separately by postProcessExtensions
175
+ if (fieldName === 'extension' || fieldName === 'modifierExtension')
176
+ return false;
177
+ // Skip Bundle.entry and similar resource-container slices
178
+ if (fieldName === 'entry')
179
+ return false;
180
+ // Must be a root-level field (direct child of the base resource)
181
+ const parts = path.split('.');
182
+ if (parts.length !== 2)
183
+ return false;
184
+ if (baseResource && parts[0] !== baseResource)
185
+ return false;
186
+ return true;
187
+ }
188
+ /** Gather all child fields for any slice of the given parent path. */
189
+ function gatherChildFields(fields, path) {
190
+ return fields.filter(f => {
191
+ if (f.name === path)
192
+ return false;
193
+ const childId = f.elementId || f.name;
194
+ return childId.startsWith(`${path}:`) || (f.name.startsWith(`${path}.`) && f.name !== path);
195
+ });
196
+ }
166
197
  /**
167
198
  * Build the body lines for a slice-specific interface.
168
199
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "babelfhir-ts",
3
- "version": "1.5.3",
3
+ "version": "1.5.4",
4
4
  "description": "BabelFHIR-TS: generate TypeScript interfaces, validators, and helper classes from FHIR R4/R4B/R5 StructureDefinitions (profiles) directly inside package archives.",
5
5
  "type": "module",
6
6
  "main": "out/src/main.js",