@atomic-ehr/codegen 0.0.16 → 0.0.17-canary.20260727063227.c50c42b
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.
- package/LICENSE +1 -1
- package/README.md +67 -1
- package/assets/api/writer-generator/python/fhirpy_base_model.py +5 -1
- package/assets/api/writer-generator/python/fhirpy_base_model_camel_case.py +5 -1
- package/assets/api/writer-generator/python/profile_helpers.py +55 -19
- package/assets/api/writer-generator/typescript/profile-helpers.ts +11 -0
- package/dist/cli/index.js +8 -8
- package/dist/index.d.ts +14 -2
- package/dist/index.js +446 -208
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
- [Quick Start](#quick-start)
|
|
17
17
|
- [Usage Examples](#usage-examples)
|
|
18
18
|
- [Architecture](#architecture)
|
|
19
|
+
- [Generator Options](#generator-options)
|
|
19
20
|
- [Input - FHIR packages & resolves canonicals](#input---fhir-packages--resolves-canonicals)
|
|
20
21
|
- [Load Local StructureDefinitions & TGZ Archives](#load-local-structuredefinitions--tgz-archives)
|
|
21
22
|
- [Intermediate - Type Schema](#intermediate---type-schema)
|
|
@@ -188,6 +189,44 @@ const builder = new APIBuilder()
|
|
|
188
189
|
|
|
189
190
|
Each method returns the builder instance, allowing method chaining. The `generate()` method executes the pipeline and returns a report with success status and generated file details.
|
|
190
191
|
|
|
192
|
+
### Generator Options
|
|
193
|
+
|
|
194
|
+
Each language generator accepts its own option object. All options are optional; the tables below list the defaults.
|
|
195
|
+
|
|
196
|
+
**TypeScript** — `.typescript({ ... })`
|
|
197
|
+
|
|
198
|
+
| Option | Type | Default | Description |
|
|
199
|
+
|--------|------|---------|-------------|
|
|
200
|
+
| `generateProfile` | `boolean` | `true` | Generate profile wrapper classes (factories, typed slice/extension accessors, `validate()`). |
|
|
201
|
+
| `primitiveTypeExtension` | `boolean` | `true` | Emit sibling `_field` properties for [primitive element extensions](https://www.hl7.org/fhir/element.html#json). |
|
|
202
|
+
| `openResourceTypeSet` | `boolean` | `false` | For resource families (`Resource`, `DomainResource`), keep the `resourceType` union open by adding a `string` fallback instead of a closed literal union. |
|
|
203
|
+
| `extensionGetterDefault` | `"flat" \| "profile" \| "raw"` | `"flat"` | Default return shape for generated extension getters. |
|
|
204
|
+
| `sliceGetterDefault` | `"flat" \| "raw"` | `"flat"` | Default return shape for generated slice getters (`flat` strips discriminators, `raw` returns the full FHIR element). |
|
|
205
|
+
| `lineWidth` | `number` | `120` | Maximum line width before wrapping. |
|
|
206
|
+
| `withDebugComment` | `boolean` | `false` | Emit comments tracing each generated type back to its source schema. |
|
|
207
|
+
|
|
208
|
+
**Python** — `.python({ ... })`
|
|
209
|
+
|
|
210
|
+
| Option | Type | Default | Description |
|
|
211
|
+
|--------|------|---------|-------------|
|
|
212
|
+
| `client` | `"fhirpy" \| "none"` | `"fhirpy"` | Client integration baked into the models: `"fhirpy"` makes models extend `FhirpyBaseModel` for the fhirpy async client; `"none"` emits plain Pydantic models. |
|
|
213
|
+
| `fieldFormat` | `"camelCase" \| "snake_case" \| "PascalCase"` | `"camelCase"` | Naming convention for generated model fields. |
|
|
214
|
+
| `generateProfile` | `boolean` | `false` | Generate profile wrapper classes (`create()`, typed accessors, `validate()`) around the Pydantic models. |
|
|
215
|
+
| `primitiveTypeExtension` | `boolean` | `false` | Emit primitive element extension fields. |
|
|
216
|
+
| `allowExtraFields` | `boolean` | `false` | Allow fields not present in the schema on generated models (Pydantic `extra`). |
|
|
217
|
+
| `rootPackageName` | `string` | `"fhir_types"` | Root Python package name for the generated module tree. |
|
|
218
|
+
| `withDebugComment` | `boolean` | `false` | Emit comments tracing each generated type back to its source schema. |
|
|
219
|
+
|
|
220
|
+
> `fhirpyClient?: boolean` is **deprecated** — use `client` instead (`true` → `"fhirpy"`, `false` → `"none"`).
|
|
221
|
+
|
|
222
|
+
**C#** — `.csharp({ ... })`
|
|
223
|
+
|
|
224
|
+
| Option | Type | Default | Description |
|
|
225
|
+
|--------|------|---------|-------------|
|
|
226
|
+
| `rootNamespace` | `string` | `"Fhir.Types"` | Root namespace for generated classes. |
|
|
227
|
+
| `staticSourceDir` | `string` | — | Directory of static `.cs` source files copied verbatim into the output. |
|
|
228
|
+
| `withDebugComment` | `boolean` | `false` | Emit comments tracing each generated type back to its source schema. |
|
|
229
|
+
|
|
191
230
|
### Input - FHIR packages & resolves canonicals
|
|
192
231
|
|
|
193
232
|
The input stage leverages [Canonical Manager](https://github.com/atomic-ehr/canonical-manager) to handle FHIR package management and dependency resolution. It processes FHIR packages from multiple sources (registry, local files, TGZ archives) and resolves all canonical URLs to their concrete definitions, ensuring all references between resources are properly linked before transformation.
|
|
@@ -240,7 +279,7 @@ Tree shaking optimizes the generated output by including only the resources you
|
|
|
240
279
|
})
|
|
241
280
|
```
|
|
242
281
|
|
|
243
|
-
This feature automatically resolves and includes all dependencies (
|
|
282
|
+
This feature automatically resolves and includes all dependencies (field types, base resources, nested types, and extension definitions used by profiles) while excluding unused resources, significantly reducing the size of generated code and improving compilation times. Reference targets are not dependencies: a field typed `Reference<"Patient">` does not pull the `Patient` type into the output, since the reference is rendered as a string literal.
|
|
244
283
|
|
|
245
284
|
##### Field-Level Tree Shaking
|
|
246
285
|
|
|
@@ -272,6 +311,33 @@ Beyond resource-level filtering, tree shaking supports fine-grained field select
|
|
|
272
311
|
|
|
273
312
|
FHIR choice types (like `multipleBirth[x]` which can be boolean or integer) are handled intelligently. Selecting/ignoring the base field affects all variants, while targeting specific variants only affects those types.
|
|
274
313
|
|
|
314
|
+
##### Following Reference Targets
|
|
315
|
+
|
|
316
|
+
To also generate types for a schema's reference targets — both the base resources (`reference.resource`) and the target profiles (`reference.profiles`) — enable `followReferences` on the rule (default: `false`):
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
.typeSchema({
|
|
320
|
+
treeShake: {
|
|
321
|
+
"kbv.basis": {
|
|
322
|
+
"https://fhir.kbv.de/StructureDefinition/KBV_PR_Base_Condition_Diagnosis": {
|
|
323
|
+
followReferences: true
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
})
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
With this rule, `Condition.subject` targeting `KBV_PR_Base_Patient` pulls the Patient profile (and the `Patient`/`Group` resources) into the generated output, including their regular dependencies. Following is **non-transitive**: the pulled-in types keep their own reference targets as plain string literals, so a single flag cannot drag in the whole package. If a followed target is also listed as a tree-shake root, the root's rule wins.
|
|
331
|
+
|
|
332
|
+
To enable it for every tree-shake root at once, use `treeShakeDefaults` (a rule's own `followReferences` still wins over the default):
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
.typeSchema({
|
|
336
|
+
treeShake: { ... },
|
|
337
|
+
treeShakeDefaults: { followReferences: true }
|
|
338
|
+
})
|
|
339
|
+
```
|
|
340
|
+
|
|
275
341
|
#### Logical Model Promotion
|
|
276
342
|
|
|
277
343
|
Some implementation guides expose logical models (logical-kind StructureDefinitions) that are intended to be used like resources in generated SDKs. The code generator supports promoting selected logical models to behave as resources during generation.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import Any, Union, Optional, Iterator, Tuple, Dict
|
|
2
2
|
from pydantic import BaseModel, Field
|
|
3
|
+
from pydantic_core import PydanticUndefined
|
|
3
4
|
from typing import Protocol
|
|
4
5
|
|
|
5
6
|
|
|
@@ -22,7 +23,10 @@ class FhirpyBaseModel(BaseModel):
|
|
|
22
23
|
def __pydantic_init_subclass__(cls, **kwargs: Any) -> None:
|
|
23
24
|
super().__pydantic_init_subclass__(**kwargs)
|
|
24
25
|
field = cls.model_fields.get("resource_type") or cls.model_fields.get("resourceType")
|
|
25
|
-
|
|
26
|
+
# Only concrete resources carry a default resourceType. Abstract/family base types
|
|
27
|
+
# (Resource, DomainResource) leave it unset, so we skip them to avoid registering a
|
|
28
|
+
# class attribute that concrete subclasses would shadow.
|
|
29
|
+
if field is not None and field.default is not None and field.default is not PydanticUndefined:
|
|
26
30
|
type.__setattr__(cls, "resourceType", str(field.default))
|
|
27
31
|
|
|
28
32
|
def __iter__(self) -> Iterator[Tuple[str, Any]]: # type: ignore[override]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import Any, Union, Optional, Iterator, Tuple, Dict
|
|
2
2
|
from pydantic import BaseModel, Field
|
|
3
|
+
from pydantic_core import PydanticUndefined
|
|
3
4
|
from typing import Protocol
|
|
4
5
|
|
|
5
6
|
|
|
@@ -21,7 +22,10 @@ class FhirpyBaseModel(BaseModel):
|
|
|
21
22
|
def __pydantic_init_subclass__(cls, **kwargs: Any) -> None:
|
|
22
23
|
super().__pydantic_init_subclass__(**kwargs)
|
|
23
24
|
field = cls.model_fields.get("resource_type") or cls.model_fields.get("resourceType")
|
|
24
|
-
|
|
25
|
+
# Only concrete resources carry a default resourceType. Abstract/family base types
|
|
26
|
+
# (Resource, DomainResource) leave it unset, so we skip them to avoid registering a
|
|
27
|
+
# class attribute that concrete subclasses would shadow.
|
|
28
|
+
if field is not None and field.default is not None and field.default is not PydanticUndefined:
|
|
25
29
|
type.__setattr__(cls, "resourceType", str(field.default))
|
|
26
30
|
|
|
27
31
|
def __iter__(self) -> Iterator[Tuple[str, Any]]: # type: ignore[override]
|
|
@@ -26,6 +26,8 @@ from __future__ import annotations
|
|
|
26
26
|
import copy
|
|
27
27
|
from typing import Any, Iterable, Mapping, MutableMapping, MutableSequence, Sequence, TypeVar
|
|
28
28
|
|
|
29
|
+
from typing_extensions import TypeGuard
|
|
30
|
+
|
|
29
31
|
T = TypeVar("T")
|
|
30
32
|
|
|
31
33
|
# ---------------------------------------------------------------------------
|
|
@@ -33,9 +35,9 @@ T = TypeVar("T")
|
|
|
33
35
|
# ---------------------------------------------------------------------------
|
|
34
36
|
|
|
35
37
|
|
|
36
|
-
def is_record(value:
|
|
38
|
+
def is_record(value: object) -> TypeGuard[MutableMapping[str, Any]]:
|
|
37
39
|
"""True when ``value`` is a non-None mapping (dict-like, not a list)."""
|
|
38
|
-
return isinstance(value,
|
|
40
|
+
return isinstance(value, MutableMapping)
|
|
39
41
|
|
|
40
42
|
|
|
41
43
|
def ensure_path(root: MutableMapping[str, Any], path: Sequence[str]) -> MutableMapping[str, Any]:
|
|
@@ -85,14 +87,14 @@ def apply_slice_match(input_obj: Mapping[str, Any], match: Mapping[str, Any]) ->
|
|
|
85
87
|
return result
|
|
86
88
|
|
|
87
89
|
|
|
88
|
-
def _get_key(obj:
|
|
90
|
+
def _get_key(obj: object, key: str) -> Any:
|
|
89
91
|
"""Retrieve ``key`` from a dict-like or Pydantic-model-like object."""
|
|
90
92
|
if is_record(obj):
|
|
91
93
|
return obj.get(key)
|
|
92
94
|
return getattr(obj, key, None)
|
|
93
95
|
|
|
94
96
|
|
|
95
|
-
def _model_get(value:
|
|
97
|
+
def _model_get(value: object, key: str) -> Any:
|
|
96
98
|
"""Get an attribute from a Pydantic model by Python name or field alias.
|
|
97
99
|
|
|
98
100
|
Pydantic stores fields as snake_case attributes but slice match dicts use
|
|
@@ -110,7 +112,7 @@ def _model_get(value: Any, key: str) -> Any:
|
|
|
110
112
|
return None
|
|
111
113
|
|
|
112
114
|
|
|
113
|
-
def matches_value(value:
|
|
115
|
+
def matches_value(value: object, match: object) -> bool:
|
|
114
116
|
"""Recursively test whether ``value`` structurally contains everything in
|
|
115
117
|
``match``. Lists use "every match item has a corresponding value item"
|
|
116
118
|
semantics; mappings are matched key-by-key; primitives use ``==``.
|
|
@@ -149,7 +151,7 @@ def matches_value(value: Any, match: Any) -> bool:
|
|
|
149
151
|
return bool(value == match)
|
|
150
152
|
|
|
151
153
|
|
|
152
|
-
def is_extension(value:
|
|
154
|
+
def is_extension(value: object, url: str | None = None) -> bool:
|
|
153
155
|
"""True when ``value`` looks like a raw FHIR Extension (has a ``url``).
|
|
154
156
|
When ``url`` is given, also checks the URL matches.
|
|
155
157
|
Works with both plain dicts and Pydantic model instances."""
|
|
@@ -159,7 +161,7 @@ def is_extension(value: Any, url: str | None = None) -> bool:
|
|
|
159
161
|
return url is None or bool(ext_url == url)
|
|
160
162
|
|
|
161
163
|
|
|
162
|
-
def get_extension_value(ext:
|
|
164
|
+
def get_extension_value(ext: object | None, field: str) -> Any:
|
|
163
165
|
"""Read a single typed value field from an Extension dict or Pydantic model,
|
|
164
166
|
returning ``None`` when the extension itself is absent or the field is not set."""
|
|
165
167
|
if ext is None:
|
|
@@ -192,7 +194,7 @@ def push_extension(target: Any, ext: Any) -> None:
|
|
|
192
194
|
|
|
193
195
|
|
|
194
196
|
def extract_complex_extension(
|
|
195
|
-
extension:
|
|
197
|
+
extension: object | None,
|
|
196
198
|
config: Sequence[Mapping[str, Any]],
|
|
197
199
|
) -> dict[str, Any] | None:
|
|
198
200
|
"""Read a complex (nested) FHIR extension into a plain key/value dict.
|
|
@@ -325,7 +327,7 @@ def ensure_profile(resource: Any, canonical_url: str) -> None:
|
|
|
325
327
|
profiles.append(canonical_url)
|
|
326
328
|
|
|
327
329
|
|
|
328
|
-
def set_array_slice(lst: MutableSequence[Any], match: Mapping[str, Any], value:
|
|
330
|
+
def set_array_slice(lst: MutableSequence[Any], match: Mapping[str, Any], value: object) -> None:
|
|
329
331
|
"""Find or insert a slice element. If an element matching ``match``
|
|
330
332
|
already exists it is replaced in place; otherwise ``value`` is appended."""
|
|
331
333
|
for i, item in enumerate(lst):
|
|
@@ -355,7 +357,7 @@ def get_array_slices(lst: Sequence[Any] | None, match: Mapping[str, Any]) -> lis
|
|
|
355
357
|
def set_array_slices(
|
|
356
358
|
lst: MutableSequence[Any],
|
|
357
359
|
match: Mapping[str, Any],
|
|
358
|
-
values: Sequence[
|
|
360
|
+
values: Sequence[object],
|
|
359
361
|
) -> None:
|
|
360
362
|
"""Remove all elements matching ``match``, then append ``values``."""
|
|
361
363
|
indices = [i for i, item in enumerate(lst) if matches_value(item, match)]
|
|
@@ -372,13 +374,13 @@ def set_array_slices(
|
|
|
372
374
|
# ---------------------------------------------------------------------------
|
|
373
375
|
|
|
374
376
|
|
|
375
|
-
def _get_field(res:
|
|
377
|
+
def _get_field(res: object, field: str) -> Any:
|
|
376
378
|
if isinstance(res, Mapping):
|
|
377
379
|
return res.get(field)
|
|
378
380
|
return getattr(res, field, None)
|
|
379
381
|
|
|
380
382
|
|
|
381
|
-
def validate_required(res:
|
|
383
|
+
def validate_required(res: object, profile_name: str, field: str) -> list[str]:
|
|
382
384
|
"""Checks that ``field`` is present (not ``None``)."""
|
|
383
385
|
return (
|
|
384
386
|
[f"{profile_name}: required field '{field}' is missing"]
|
|
@@ -387,7 +389,7 @@ def validate_required(res: Any, profile_name: str, field: str) -> list[str]:
|
|
|
387
389
|
)
|
|
388
390
|
|
|
389
391
|
|
|
390
|
-
def validate_must_support(res:
|
|
392
|
+
def validate_must_support(res: object, profile_name: str, field: str) -> list[str]:
|
|
391
393
|
"""Checks that a must-support field is populated (warning, not error)."""
|
|
392
394
|
return (
|
|
393
395
|
[f"{profile_name}: must-support field '{field}' is not populated"]
|
|
@@ -396,7 +398,7 @@ def validate_must_support(res: Any, profile_name: str, field: str) -> list[str]:
|
|
|
396
398
|
)
|
|
397
399
|
|
|
398
400
|
|
|
399
|
-
def validate_excluded(res:
|
|
401
|
+
def validate_excluded(res: object, profile_name: str, field: str) -> list[str]:
|
|
400
402
|
"""Checks that ``field`` is absent."""
|
|
401
403
|
return (
|
|
402
404
|
[f"{profile_name}: field '{field}' must not be present"]
|
|
@@ -405,7 +407,7 @@ def validate_excluded(res: Any, profile_name: str, field: str) -> list[str]:
|
|
|
405
407
|
)
|
|
406
408
|
|
|
407
409
|
|
|
408
|
-
def validate_fixed_value(res:
|
|
410
|
+
def validate_fixed_value(res: object, profile_name: str, field: str, expected: object) -> list[str]:
|
|
409
411
|
"""Checks that ``field`` structurally contains the expected fixed value."""
|
|
410
412
|
actual = _get_field(res, field)
|
|
411
413
|
return (
|
|
@@ -416,7 +418,7 @@ def validate_fixed_value(res: Any, profile_name: str, field: str, expected: Any)
|
|
|
416
418
|
|
|
417
419
|
|
|
418
420
|
def validate_slice_cardinality(
|
|
419
|
-
res:
|
|
421
|
+
res: object,
|
|
420
422
|
profile_name: str,
|
|
421
423
|
field: str,
|
|
422
424
|
match: Mapping[str, Any],
|
|
@@ -442,14 +444,48 @@ def validate_slice_cardinality(
|
|
|
442
444
|
return errors
|
|
443
445
|
|
|
444
446
|
|
|
445
|
-
def
|
|
447
|
+
def validate_slice_fields(
|
|
448
|
+
res: object,
|
|
449
|
+
profile_name: str,
|
|
450
|
+
field: str,
|
|
451
|
+
match: Mapping[str, Any],
|
|
452
|
+
slice_name: str,
|
|
453
|
+
required_fields: Sequence[str],
|
|
454
|
+
) -> list[str]:
|
|
455
|
+
"""Validates required fields within matched slice elements. For each array
|
|
456
|
+
item matching the discriminator, checks that the listed fields are present."""
|
|
457
|
+
items = _get_field(res, field) or []
|
|
458
|
+
if not isinstance(items, Iterable):
|
|
459
|
+
items = []
|
|
460
|
+
errors: list[str] = []
|
|
461
|
+
for item in items:
|
|
462
|
+
if not matches_value(item, match):
|
|
463
|
+
continue
|
|
464
|
+
for rf in required_fields:
|
|
465
|
+
if _get_field(item, rf) is None:
|
|
466
|
+
errors.append(f"{profile_name}.{field}[{slice_name}].{rf} is required")
|
|
467
|
+
return errors
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def validate_choice_required(res: object, profile_name: str, choices: Sequence[str]) -> list[str]:
|
|
446
471
|
"""Checks that at least one of the listed choice-type variants is present."""
|
|
447
472
|
if any(_get_field(res, c) is not None for c in choices):
|
|
448
473
|
return []
|
|
449
474
|
return [f"{profile_name}: at least one of {', '.join(choices)} is required"]
|
|
450
475
|
|
|
451
476
|
|
|
452
|
-
def
|
|
477
|
+
def validate_choice_prohibited(res: object, profile_name: str, prohibited: Sequence[str]) -> list[str]:
|
|
478
|
+
"""Checks that none of the listed prohibited choice-type variants is present.
|
|
479
|
+
E.g. a profile narrowing ``value[x]`` to ``value_quantity`` prohibits every
|
|
480
|
+
other variant."""
|
|
481
|
+
return [
|
|
482
|
+
f"{profile_name}: field '{c}' must not be present"
|
|
483
|
+
for c in prohibited
|
|
484
|
+
if _get_field(res, c) is not None
|
|
485
|
+
]
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def validate_enum(res: object, profile_name: str, field: str, allowed: Sequence[str]) -> list[str]:
|
|
453
489
|
"""Checks that the value of ``field`` has a code within ``allowed``.
|
|
454
490
|
Handles plain strings, Coding, and CodeableConcept."""
|
|
455
491
|
value = _get_field(res, field)
|
|
@@ -481,7 +517,7 @@ def validate_enum(res: Any, profile_name: str, field: str, allowed: Sequence[str
|
|
|
481
517
|
return []
|
|
482
518
|
|
|
483
519
|
|
|
484
|
-
def validate_reference(res:
|
|
520
|
+
def validate_reference(res: object, profile_name: str, field: str, allowed: Sequence[str]) -> list[str]:
|
|
485
521
|
"""Checks that a Reference field points to one of the ``allowed`` resource
|
|
486
522
|
types. Extracts the type from the ``reference`` string (the part before
|
|
487
523
|
the first ``/``)."""
|
|
@@ -437,6 +437,17 @@ export const validateChoiceRequired = (res: object, profileName: string, choices
|
|
|
437
437
|
: [`${profileName}: at least one of ${choices.join(", ")} is required`];
|
|
438
438
|
};
|
|
439
439
|
|
|
440
|
+
/**
|
|
441
|
+
* Checks that none of the listed prohibited choice-type variants is present.
|
|
442
|
+
* E.g. a profile narrowing `value[x]` to `valueAddress` prohibits every other variant.
|
|
443
|
+
*/
|
|
444
|
+
export const validateChoiceProhibited = (res: object, profileName: string, prohibited: string[]): string[] => {
|
|
445
|
+
const rec = res as Record<string, unknown>;
|
|
446
|
+
return prohibited
|
|
447
|
+
.filter((c) => rec[c] !== undefined)
|
|
448
|
+
.map((c) => `${profileName}: field '${c}' must not be present`);
|
|
449
|
+
};
|
|
450
|
+
|
|
440
451
|
/**
|
|
441
452
|
* Checks that the value of `field` has a code within `allowed`.
|
|
442
453
|
* Handles plain strings, Coding objects, and CodeableConcept objects.
|
package/dist/cli/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import T from'picocolors';import Dn from'yargs';import {hideBin}from'yargs/helpers';import {mkdir,writeFile}from'fs/promises';import {dirname}from'path';import Pe from'assert';import {createHash}from'crypto';import {CanonicalManager}from'@atomic-ehr/fhir-canonical-manager';import*as he from'@atomic-ehr/fhirschema';import {isStructureDefinition}from'@atomic-ehr/fhirschema';var ie=e=>{console.log(),console.log(T.cyan(T.bold(`\u2501\u2501\u2501 ${e} \u2501\u2501\u2501`)));},re=(e,n,i)=>{let t=e;if(n&&(t+=` ${T.gray(`(${n}ms)`)}`),console.log(`${T.green("")} ${t}`),i)for(let[r,o]of Object.entries(i))console.log(T.gray(` ${r}: ${o}`));},F=(e,n="\u2022")=>{for(let i of e)console.log(T.gray(` ${n} ${i}`));};var oe={DEBUG:0,INFO:1,WARN:2,ERROR:3,SILENT:4};function k(e={}){let n=e.prefix??"",i=new Set(e.suppressTags??[]),t={},r=[],o=new Set,a=e.level??"INFO",s=p=>oe[p]>=oe[a],c={DEBUG:p=>p,INFO:p=>p,WARN:T.yellow,ERROR:T.red,SILENT:p=>p},l=(p,m,f,y)=>{let h=n?`${n}: `:"",S=y?` ${T.dim(`(${y})`)}`:"";return c[p](`${m} ${h}${f}`)+S},d=(p,m,f,y=false)=>{r.push({level:p,tag:f,message:m,suppressed:y,prefix:n,timestamp:Date.now()});},u=(p,m,f,y=false)=>(...h)=>{let S=h.length===2?h[0]:void 0,x=h.length===2?h[1]:h[0];S&&(t[S]=(t[S]??0)+1);let C=S!==void 0&&i.has(S);if(d(p,x,S,C),!C&&s(p)){if(y){let N=`${p}::${S??""}::${x}`;if(o.has(N))return;o.add(N);}f(l(p,m,x,S));}},g={warn:u("WARN","!",console.warn),dryWarn:u("WARN","!",console.warn,true),info:u("INFO","i",console.log),error:u("ERROR","X",console.error),debug:u("DEBUG","D",console.log),fork(p,m){let f=n?`${n}/${p}`:p,y=[...i,...m?.suppressTags??[]];return k({prefix:f,suppressTags:y,level:m?.level??a})},as(){return g},tagCounts(){return t},printTagSummary(){let p=Object.entries(t);if(p.length===0)return;let m=n?`${n}: `:"",f=p.filter(([h])=>!i.has(h)),y=p.filter(([h])=>i.has(h));if(f.length>0){let h=f.reduce((x,[,C])=>x+C,0),S=f.map(([x,C])=>`${x}: ${C}`).join(", ");console.warn(T.yellow(`! ${m}${h} warnings (${S})`));}if(y.length>0){let h=y.reduce((x,[,C])=>x+C,0),S=y.map(([x,C])=>`${x}: ${C}`).join(", ");console.log(T.dim(`i ${m}${h} suppressed (${S})`));}},buffer(){return r},bufferClear(){r.length=0;}};return g}var b="Use CodeableReference which is not provided by FHIR R4.",Oe="Use Availability which is not provided by FHIR R4.",A={"hl7.fhir.uv.extensions.r4":{"http://hl7.org/fhir/StructureDefinition/biologicallyderivedproduct-manipulation":b,"http://hl7.org/fhir/StructureDefinition/biologicallyderivedproduct-processing":b,"http://hl7.org/fhir/StructureDefinition/extended-contact-availability":Oe,"http://hl7.org/fhir/StructureDefinition/immunization-procedure":b,"http://hl7.org/fhir/StructureDefinition/specimen-additive":b,"http://hl7.org/fhir/StructureDefinition/workflow-barrier":b,"http://hl7.org/fhir/StructureDefinition/workflow-protectiveFactor":b,"http://hl7.org/fhir/StructureDefinition/workflow-reason":b},"hl7.fhir.r5.core#5.0.0":{"http://hl7.org/fhir/StructureDefinition/shareablecodesystem":"FIXME: CodeSystem.concept.concept defined by ElementReference. FHIR Schema generator output broken value in it, so we just skip it for now.","http://hl7.org/fhir/StructureDefinition/publishablecodesystem":"Uses R5-only base types not available in R4 generation."}};function v(e,n){let i=`${e.name}#${e.version}`,t=A[i]?.[n];if(t)return {shouldSkip:true,reason:t};let r=A[e.name]?.[n];return r?{shouldSkip:true,reason:r}:{shouldSkip:false}}var R=e=>`${e.name}#${e.version}`,ae=e=>`${e.name}@${e.version}`,se=e=>{let n=JSON.stringify(e);return createHash("sha256").update(n).digest("hex").slice(0,16)},ce=(e,n)=>{let i=e.derivation==="constraint"?"constraint":"specialization";return {...e,derivation:i,kind:e.kind,package_meta:e.package_meta||n,name:e.name,url:e.url,base:e.base}};var G=e=>e?.kind==="nested";var E=(...e)=>{let n=e.filter(t=>t!==void 0).flatMap(t=>t.map(r=>[r.url,r]));return n.length===0?void 0:Object.values(Object.fromEntries(n)).sort((t,r)=>t.url.localeCompare(r.url))};var le=e=>[...e.valueFieldTypes??[],...e.profile?[e.profile]:[],...e.subExtensions?.flatMap(n=>n.valueFieldType?[n.valueFieldType]:[])??[]];var pe=(e,n)=>{if(!e.url)throw new Error("ValueSet must have a URL");if(!e.name)throw new Error("ValueSet must have a name");return {...e,package_meta:e.package_meta||n,name:e.name,url:e.url}};function z(e){let n=e.split("|")[0];return n||e}function Be(e){return e.split("|")[1]}var Me=e=>({package:e.package_meta.name,version:e.package_meta.version,name:e.name,url:e.url});function I(e){let n=Me(e);return e.derivation==="constraint"?{kind:"profile",...n}:e.kind==="primitive-type"?{kind:"primitive-type",...n}:e.kind==="complex-type"?{kind:"complex-type",...n}:e.kind==="resource"?{kind:"resource",...n}:e.kind==="logical"?{kind:"logical",...n}:{kind:"resource",...n}}var je=/[-_]/,_e=/^[a-zA-Z0-9_-]{20,}$/,Ae=e=>{let n=e.split("/"),i=n[n.length-1];return i&&i.length>0?i.split(je).map(t=>t.charAt(0).toUpperCase()+t.slice(1).toLowerCase()).join(""):e};function L(e,n,i){let t=z(i),r=Ae(t),o={package_meta:{name:"missing_valuesets",version:Be(t)||"0.0.0"},id:i},a=e.resolveVs(n,t)||o,s=a?.id&&!_e.test(a.id)?a.id:r;return {kind:"value-set",package:a.package_meta.name,version:a.package_meta.version,name:s,url:t}}function H(e,n,i){let t=i.binding?.bindingName,r=n.join("."),[o,a,s]=t?[{name:"shared",version:"1.0.0"},t,`urn:fhir:binding:${t}`]:[e.package_meta,`${e.name}.${r}_binding`,`${e.url}#${r}_binding`];return {kind:"binding",package:o.name,version:o.version,name:a,url:s}}function q(e,n,i,t){let r=z(i)||i,o=e.resolveVs(n,r);if(o)return ze(e,o)}function ze(e,n,i){if(n.expansion?.contains)return n.expansion.contains.filter(r=>r.code!==void 0).map(r=>(Pe(r.code),{code:r.code,display:r.display,system:r.system}));let t=[];if(n.compose?.include){for(let r of n.compose.include)if(r.concept)for(let o of r.concept)t.push({system:r.system,code:o.code,display:o.display});else if(r.system&&!r.filter)try{let o=e.resolveAny(r.system);if(o?.concept){let a=(s,c)=>{for(let l of s)t.push({system:c,code:l.code,display:l.display}),l.concept&&a(l.concept,c);};a(o.concept,r.system);}}catch{}}return t.length>0?t:void 0}var de=100,qe=new Set(["UNK"]),W=new Set(["code","Coding","CodeableConcept","CodeableReference","Quantity","string","uri","Duration"]);function K(e,n,i,t){if(!i.binding)return;let r=i.binding.strength,o=i.binding.valueSet;if(!o)return;if(!W.has(i.type??"")){t?.dryWarn("#binding",`eld-11: Binding on non-bindable type '${i.type}' (valueSet: ${o})`);return}if(!(r==="required"||r==="extensible"||r==="preferred"))return;let s=q(e,n.package_meta,o);if(!s||s.length===0)return;let c=s.map(d=>d.code).filter(d=>d&&typeof d=="string"&&d.trim().length>0),l=c.length===1?c[0]:void 0;if(l&&qe.has(l)){t?.dryWarn("#placeholderValueSet",`Value set ${o} only expands to placeholder code '${l}'; skipping enum generation.`);return}if(c.length>de){t?.dryWarn("#largeValueSet",`Value set ${o} has ${c.length} which is more than ${de} codes, which may cause issues with code generation.`);return}if(c.length!==0)return {isOpen:r!=="required",values:c}}function We(e,n,i,t,r){if(!t.binding?.valueSet)return;let o=H(n,i,t),a=L(e,n.package_meta,t.binding.valueSet),s=K(e,n,t,r);return {identifier:o,valueset:a,strength:t.binding.strength,enum:s,dependencies:[a]}}function me(e,n,i){let t=new Set;if(!n.elements)return [];let r=[];function o(c,l){for(let[d,u]of Object.entries(c)){let g=[...l,d],p=g.join("."),m=e.resolveElementSnapshot(n,g);if(!t.has(p)){if(t.add(p),m.binding){let f=We(e,n,g,m,i);f&&r.push(f);}u.elements&&o(u.elements,g);}}}o(n.elements,[]),r.sort((c,l)=>c.identifier.name.localeCompare(l.identifier.name));let a=[],s=new Set;for(let c of r)s.has(c.identifier.url)||(s.add(c.identifier.url),a.push(c));return a}var Ke=/(?<=[a-z])(?=[A-Z])|[-_.\s]/,Ye=e=>e.split(Ke).filter(Boolean);var Je=e=>{if(e.length===0)throw new Error("Empty string");return e[0]?.toUpperCase()+e.substring(1).toLowerCase()},ue=e=>{if(e.length===0)throw new Error("Empty string");let[n,...i]=Ye(e);return [n?.toLowerCase(),...i.map(Je)].join("")};var Y=e=>!e||e.length===0?e:e.charAt(0).toUpperCase()+e.slice(1);var Ze=e=>{let n=e.replace(/\[x\]/g,"").replace(/[- :.]/g,"_");return n?Y(n):""},w=e=>{let n=e.replace(/\[x\]/g,"").replace(/:/g,"_");return n?Y(ue(n)):""},Qe=(e,n)=>{let i=w(e)||"Extension",t=n.split(".").filter(a=>a&&a!=="extension").join("_"),o=`${t?w(t):""}${i}`;return [i,o,`${o}Extension`]},Xe=(e,n)=>{let i=Ze(n)||"Slice",r=`${w(e)||"Field"}${i}`;return [i,r,`${r}Slice`]},en=(e,n,i)=>e.reduce((t,r)=>{let o=r.candidates[n]??"";return t[o]=(t[o]??0)+1,i.has(o)&&(t[o]=(t[o]??0)+1),t},{}),nn=(e,n)=>{let i=e[0]?.candidates.length??0,t=(r,o)=>{if(r.length===0||o>=i)return {};let a=en(r,o,n),s=o>=i-1,[c,l]=r.reduce(([d,u],g)=>{let p=g.candidates[o]??"";return (a[p]??0)>1&&!s?[d,[...u,g]]:[{...d,[g.key]:p},u]},[{},[]]);return {...c,...t(l,o+1)}};return t(e,0)},fe=e=>({candidates:Qe(e.name,e.path),recommended:""}),ge=(e,n)=>({candidates:Xe(e,n),recommended:""}),ye=e=>{let n=(e.extensions??[]).filter(a=>a.url).map(a=>({key:`ext:${a.url}:${a.path}`,candidates:a.nameCandidates.candidates})),i=Object.entries(e.fields??{}).flatMap(([a,s])=>!("slicing"in s)||!s.slicing?.slices?[]:Object.entries(s.slicing.slices).map(([c,l])=>({key:`slice:${a}:${c}`,candidates:l.nameCandidates.candidates}))),t=new Set(Object.keys(e.fields??{}).map(w)),r=[...n,...i];if(r.length===0)return;let o=nn(r,t);for(let a of e.extensions??[]){if(!a.url)continue;let s=`ext:${a.url}:${a.path}`;o[s]&&(a.nameCandidates.recommended=o[s]);}for(let[a,s]of Object.entries(e.fields??{}))if(!(!("slicing"in s)||!s.slicing?.slices))for(let[c,l]of Object.entries(s.slicing.slices)){let d=`slice:${a}:${c}`;o[d]&&(l.nameCandidates.recommended=o[d]);}};var J=e=>e!==null&&typeof e=="object"&&e.resourceType==="CodeSystem";var U=e=>e!==null&&typeof e=="object"&&e.resourceType==="ValueSet";var rn=/^[a-zA-Z0-9]+$/,on=async(e,n)=>{let i=await e.packageJson(n.name);if(!i)return [];let t=i.dependencies;return t!==void 0?Object.entries(t).map(([r,o])=>({name:r,version:o})):[]},Se=e=>({pkg:e,canonicalResolution:{},fhirSchemas:{},valueSets:{}}),Re=async(e,n,i,t,r)=>{let o=R(n);if(r?.info(`${" ".repeat(i*2)}+ ${o}`),t[o])return t[o];let a=Se(n);t[o]=a;for(let c of await e.search({package:n})){let l=c.url;if(!l||!(isStructureDefinition(c)||U(c)||J(c)))continue;let d=l;a.canonicalResolution[d]&&r?.dryWarn("#duplicateCanonical",`Duplicate canonical URL: ${d} at ${o}.`),a.canonicalResolution[d]=[{deep:i,pkg:n,pkgId:o,resource:c}];}let s=await on(e,n);for(let c of s){let{canonicalResolution:l}=await Re(e,c,i+1,t,r);for(let[d,u]of Object.entries(l)){let g=d;a.canonicalResolution[g]=[...a.canonicalResolution[g]||[],...u];}}for(let c of Object.values(a.canonicalResolution))c.sort((l,d)=>l.deep-d.deep);return a},an=(e,n)=>{for(let{pkg:i,canonicalResolution:t}of Object.values(e)){let r=R(i);if(!e[r])throw new Error(`Package ${r} not found`);let o=0;n?.info(`FHIR Schema conversion for '${R(i)}' begins...`);for(let[a,s]of Object.entries(t)){let c=s[0];if(!c)throw new Error("Resource not found");let l=c.resource,d=c.pkg;if(isStructureDefinition(l)){let u=he.translate(l),g=ce(u,d);o++,e[r].fhirSchemas[g.url]=g;}if(U(l)){let u=pe(l,d);e[r].valueSets[u.url]=u;}}n?.info(`FHIR Schema conversion for '${R(i)}' completed: ${o} successful`);}},sn=(e,n,i)=>{let t=Object.values(e).flatMap(r=>r.canonicalResolution[n]);if(!t)throw new Error(`No canonical resolution found for ${n} in any package`);return t[0]?.resource},cn=async(e,{logger:n,focusedPackages:i})=>{let t=i??await e.packages(),r={};for(let p of t)await Re(e,p,0,r,n);an(r,n);let o=(p,m)=>{let f=r[R(p)];if(f){let y=f.canonicalResolution[m]?.[0];if(y)return r[y.pkgId]?.fhirSchemas[m]}for(let y of Object.values(r)){let h=y.fhirSchemas[m];if(h&&h.package_meta.name===p.name)return h}for(let y of Object.values(r)){let h=y.fhirSchemas[m];if(h)return h}},a=(p,m)=>{let f=r[R(p)];if(f){let y=f.canonicalResolution[m]?.[0];if(y)return r[y.pkgId]?.valueSets[m]}for(let y of Object.values(r)){let h=y.valueSets[m];if(h&&h.package_meta.name===p.name)return h}for(let y of Object.values(r)){let h=y.valueSets[m];if(h)return h}},s=p=>(p.includes("|")&&(p=p.split("|")[0]),rn.test(p)?`http://hl7.org/fhir/StructureDefinition/${p}`:p),c=(p,m)=>{let f=o(p,m);if(f===void 0)throw new Error(`Failed to resolve FHIR Schema: '${m}'`);let y=[f];for(;f?.base;){let h=f.package_meta,S=s(f.base);if(f=o(h,S),f===void 0)throw new Error(`Failed to resolve FHIR Schema base for '${m}'. Problem: '${S}' from '${R(h)}'`);y.push(f);}return y},l=(p,m)=>c(p,m).filter(f=>f.derivation==="specialization"),d=(p,m)=>{let f=c(p.package_meta,p.url),y=O(f,m);return Z(y)},u=p=>{let m=new Set;for(let[f,y]of Object.entries(p)){m.add(f);for(let h of y?.choices||[])p[h]||m.add(h);}return Array.from(m)},g;return {testAppendFs(p){let m=R(p.package_meta);r[m]||(r[m]=Se(p.package_meta)),r[m].fhirSchemas[p.url]=p,g=void 0;},resolveFs:o,resolveFsGenealogy:c,resolveFsSpecializations:l,ensureSpecializationCanonicalUrl:s,resolveSd:(p,m)=>{let f=r[R(p)]?.canonicalResolution[m]?.[0]?.resource;if(isStructureDefinition(f))return f},allSd:()=>Object.values(r).flatMap(p=>Object.values(p.canonicalResolution).flatMap(m=>m.map(f=>{let y=f.resource;return y.package_name?y:{...y,package_name:p.pkg.name,package_version:p.pkg.version}}))).filter(p=>isStructureDefinition(p)).sort((p,m)=>p.url.localeCompare(m.url)),allFs:()=>Object.values(r).flatMap(p=>Object.values(p.fhirSchemas)),allVs:()=>Object.values(r).flatMap(p=>Object.values(p.valueSets)),resolveVs:a,resolveAny:p=>sn(r,p),resolveElementSnapshot:d,getAllElementKeys:u,resolver:r,resolutionTree:()=>{if(g)return g;let p={};for(let[m,f]of Object.entries(r)){let y=f.pkg.name;p[y]={};for(let[h,S]of Object.entries(f.canonicalResolution)){let x=h;p[y][x]=[];for(let C of S)p[y][x].push({deep:C.deep,pkg:C.pkg});}}return g=p,p}}},xe=async(e,n)=>{let i=e.map(ae);n?.logger?.info(`Loading FHIR packages: ${i.join(", ")}`);let t=CanonicalManager({packages:i,workingDir:".codegen-cache/canonical-manager-cache",registry:n.registry||void 0});return await t.init(),await cn(t,{...n,focusedPackages:e})},O=(e,n)=>{let[i,...t]=n;return i===void 0?[]:e.map(r=>{if(!r.elements)return;let o=r.elements?.[i];for(let a of t)o=o?.elements?.[a];return o}).filter(r=>r!==void 0)};function Z(e){let n=e.reverse(),i=Object.assign({},...n);return i.elements=void 0,i}var Ce=(e,n,i)=>{let t=e.resolveFsSpecializations(n.package_meta,n.url),r=O(t,i),o=Z(r).type,a;if(o){let s=e.ensureSpecializationCanonicalUrl(o),l=e.resolveFsGenealogy(n.package_meta,s).flatMap(d=>Object.keys(d.elements??{}));l.length>0&&(a=new Set(l));}for(let s of r)if(!(!s.elements||Object.keys(s.elements).length===0)&&!(a&&!Object.keys(s.elements).some(c=>!a.has(c))))return true;return false},Q=(e,n,i,t,r)=>t.type==="BackboneElement"?true:!r?.elements||r.choiceOf!==void 0?false:Ce(e,n,i),ln=e=>e.elements?new Set(X(e,[],e.elements).filter(([n,i])=>i.elements&&Object.keys(i.elements).length>0).map(([n])=>n.join("."))):new Set;function V(e,n,i){let t={},r=n.derivation==="constraint"?e.resolveFsSpecializations(n.package_meta,n.url):e.resolveFsGenealogy(n.package_meta,n.url);for(let d of [...r].reverse()){let u=ln(d);for(let g of u)t[g]=`${d.url}#${g}`;}let o=i.join("."),a=t[o]??`${n.url}#${o}`,s=a.split("#")[0],l=e.resolveFs(n.package_meta,s)?.package_meta??n.package_meta;return {kind:"nested",package:l.name,version:l.version,name:o,url:a}}function X(e,n,i){let t=[];for(let[r,o]of Object.entries(i)){let a=[...n,r];o.elements&&o.choiceOf===void 0&&t.push([a,o]),o.elements&&t.push(...X(e,a,o.elements));}return t}function pn(e,n,i,t,r){let o={},a=e.resolveFsGenealogy(n.package_meta,n.url),s=O(a,i),c=new Set;for(let l of s)if(l.elements)for(let d of Object.keys(l.elements))c.add(d);for(let l of c){let d=[...i,l],u=e.resolveElementSnapshot(n,d);Q(e,n,d,u,t[l])?o[l]=M(e,n,d,u):o[l]=B(e,n,d,u,r);}return o}function ke(e,n,i){if(!n.elements)return;let t=X(n,[],n.elements).filter(([o,a])=>!a.elements||Object.keys(a.elements).length===0?false:a.type!=="BackboneElement"?Ce(e,n,o):true),r=[];for(let[o,a]of t){let s=V(e,n,o),c;a.type==="BackboneElement"||!a.type?c="BackboneElement":c=a.type;let l=e.ensureSpecializationCanonicalUrl(c),d=e.resolveFs(n.package_meta,l);if(!d)throw new Error(`Could not resolve base type ${c}`);let u={kind:"complex-type",package:d.package_meta.name,version:d.package_meta.version,name:c,url:l},g=pn(e,n,o,a.elements??{},i),p={identifier:s,base:u,fields:g};r.push(p);}return r.sort((o,a)=>o.identifier.url.localeCompare(a.identifier.url)),r.length===0?void 0:r}function Ie(e){let n=[];for(let i of e){i.base&&n.push(i.base);for(let t of Object.values(i.fields||{}))"type"in t&&t.type&&n.push(t.type),"binding"in t&&t.binding&&n.push(t.binding);}return n}var dn=new Set(["Availability","CodeableReference","ExtendedContactDetail","MonetaryComponent","RatioRange","VirtualServiceDetail"]),mn=(e,n)=>{let i=e.resolver[R(n)];if(!i)return false;for(let t of Object.values(i.canonicalResolution))for(let r of t)if(r.pkg.name==="hl7.fhir.r4.core")return true;return false},un=(e,n,i)=>!dn.has(i)||!mn(e,n)?"":`
|
|
2
|
+
import F from'picocolors';import $n from'yargs';import {hideBin}from'yargs/helpers';import {mkdir,writeFile}from'fs/promises';import {dirname}from'path';import we from'assert';import {createHash}from'crypto';import {CanonicalManager}from'@atomic-ehr/fhir-canonical-manager';import*as Ie from'@atomic-ehr/fhirschema';import {isStructureDefinition}from'@atomic-ehr/fhirschema';var se=e=>{console.log(),console.log(F.cyan(F.bold(`\u2501\u2501\u2501 ${e} \u2501\u2501\u2501`)));},ae=(e,n,i)=>{let t=e;if(n&&(t+=` ${F.gray(`(${n}ms)`)}`),console.log(`${F.green("")} ${t}`),i)for(let[r,o]of Object.entries(i))console.log(F.gray(` ${r}: ${o}`));},T=(e,n="\u2022")=>{for(let i of e)console.log(F.gray(` ${n} ${i}`));};var ce={DEBUG:0,INFO:1,WARN:2,ERROR:3,SILENT:4};function I(e={}){let n=e.prefix??"",i=new Set(e.suppressTags??[]),t={},r=[],o=new Set,s=e.level??"INFO",a=d=>ce[d]>=ce[s],c={DEBUG:d=>d,INFO:d=>d,WARN:F.yellow,ERROR:F.red,SILENT:d=>d},l=(d,u,f,y)=>{let h=n?`${n}: `:"",S=y?` ${F.dim(`(${y})`)}`:"";return c[d](`${u} ${h}${f}`)+S},p=(d,u,f,y=false)=>{r.push({level:d,tag:f,message:u,suppressed:y,prefix:n,timestamp:Date.now()});},m=(d,u,f,y=false)=>(...h)=>{let S=h.length===2?h[0]:void 0,x=h.length===2?h[1]:h[0];S&&(t[S]=(t[S]??0)+1);let C=S!==void 0&&i.has(S);if(p(d,x,S,C),!C&&a(d)){if(y){let N=`${d}::${S??""}::${x}`;if(o.has(N))return;o.add(N);}f(l(d,u,x,S));}},g={warn:m("WARN","!",console.warn),dryWarn:m("WARN","!",console.warn,true),info:m("INFO","i",console.log),error:m("ERROR","X",console.error),debug:m("DEBUG","D",console.log),fork(d,u){let f=n?`${n}/${d}`:d,y=[...i,...u?.suppressTags??[]];return I({prefix:f,suppressTags:y,level:u?.level??s})},as(){return g},tagCounts(){return t},printTagSummary(){let d=Object.entries(t);if(d.length===0)return;let u=n?`${n}: `:"",f=d.filter(([h])=>!i.has(h)),y=d.filter(([h])=>i.has(h));if(f.length>0){let h=f.reduce((x,[,C])=>x+C,0),S=f.map(([x,C])=>`${x}: ${C}`).join(", ");console.warn(F.yellow(`! ${u}${h} warnings (${S})`));}if(y.length>0){let h=y.reduce((x,[,C])=>x+C,0),S=y.map(([x,C])=>`${x}: ${C}`).join(", ");console.log(F.dim(`i ${u}${h} suppressed (${S})`));}},buffer(){return r},bufferClear(){r.length=0;}};return g}var L=(e,n)=>e<n?-1:e>n?1:0,z=(e,n)=>L(e.sourcePackage,n.sourcePackage)||L(e.sourceCanonical,n.sourceCanonical),le=e=>JSON.stringify([...e].sort(z).map(({sourcePackage:n,sourceCanonical:i})=>[n,i])),pe=(e,n)=>n.sources.length-e.sources.length||L(le(e.sources),le(n.sources))||L(e.schemaHash,n.schemaHash);var b="Use CodeableReference which is not provided by FHIR R4.",_e="Use Availability which is not provided by FHIR R4.",q={"hl7.fhir.uv.extensions.r4":{"http://hl7.org/fhir/StructureDefinition/biologicallyderivedproduct-manipulation":b,"http://hl7.org/fhir/StructureDefinition/biologicallyderivedproduct-processing":b,"http://hl7.org/fhir/StructureDefinition/extended-contact-availability":_e,"http://hl7.org/fhir/StructureDefinition/immunization-procedure":b,"http://hl7.org/fhir/StructureDefinition/specimen-additive":b,"http://hl7.org/fhir/StructureDefinition/workflow-barrier":b,"http://hl7.org/fhir/StructureDefinition/workflow-protectiveFactor":b,"http://hl7.org/fhir/StructureDefinition/workflow-reason":b},"hl7.fhir.r5.core#5.0.0":{"http://hl7.org/fhir/StructureDefinition/shareablecodesystem":"FIXME: CodeSystem.concept.concept defined by ElementReference. FHIR Schema generator output broken value in it, so we just skip it for now.","http://hl7.org/fhir/StructureDefinition/publishablecodesystem":"Uses R5-only base types not available in R4 generation."}};function v(e,n){let i=`${e.name}#${e.version}`,t=q[i]?.[n];if(t)return {shouldSkip:true,reason:t};let r=q[e.name]?.[n];return r?{shouldSkip:true,reason:r}:{shouldSkip:false}}var R=e=>`${e.name}#${e.version}`,de=e=>`${e.name}@${e.version}`,me=e=>{let n=JSON.stringify(e);return createHash("sha256").update(n).digest("hex").slice(0,16)},ue=(e,n)=>{let i=e.derivation==="constraint"?"constraint":"specialization";return {...e,derivation:i,kind:e.kind,package_meta:e.package_meta||n,name:e.name,url:e.url,base:e.base}};var W=e=>e?.kind==="nested",fe=e=>e?.kind==="profile";var E=(...e)=>{let n=e.filter(t=>t!==void 0).flatMap(t=>t.map(r=>[r.url,r]));return n.length===0?void 0:Object.values(Object.fromEntries(n)).sort((t,r)=>t.url.localeCompare(r.url))};var ge=e=>[...e.valueFieldTypes??[],...e.profile?[e.profile]:[],...e.subExtensions?.flatMap(n=>n.valueFieldType?[n.valueFieldType]:[])??[]];var ye=(e,n)=>{if(!e.url)throw new Error("ValueSet must have a URL");if(!e.name)throw new Error("ValueSet must have a name");return {...e,package_meta:e.package_meta||n,name:e.name,url:e.url}};function K(e){let n=e.split("|")[0];return n||e}function Ge(e){return e.split("|")[1]}var ze=e=>({package:e.package_meta.name,version:e.package_meta.version,name:e.name,url:e.url});function k(e){let n=ze(e);return e.derivation==="constraint"?{kind:"profile",...n}:e.kind==="primitive-type"?{kind:"primitive-type",...n}:e.kind==="complex-type"?{kind:"complex-type",...n}:e.kind==="resource"?{kind:"resource",...n}:e.kind==="logical"?{kind:"logical",...n}:{kind:"resource",...n}}var qe=/[-_]/,We=/^[a-zA-Z0-9_-]{20,}$/,Ke=e=>{let n=e.split("/"),i=n[n.length-1];return i&&i.length>0?i.split(qe).map(t=>t.charAt(0).toUpperCase()+t.slice(1).toLowerCase()).join(""):e};function H(e,n,i){let t=K(i),r=Ke(t),o={package_meta:{name:"missing_valuesets",version:Ge(t)||"0.0.0"},id:i},s=e.resolveVs(n,t)||o,a=s?.id&&!We.test(s.id)?s.id:r;return {kind:"value-set",package:s.package_meta.name,version:s.package_meta.version,name:a,url:t}}function w(e,n,i){let t=i.binding?.bindingName,r=n.join("."),[o,s,a]=t?[{name:"shared",version:"1.0.0"},t,`urn:fhir:binding:${t}`]:[e.package_meta,`${e.name}.${r}_binding`,`${e.url}#${r}_binding`];return {kind:"binding",package:o.name,version:o.version,name:s,url:a}}function Y(e,n,i,t){let r=K(i)||i,o=e.resolveVs(n,r);if(o)return Je(e,o)}function Je(e,n,i){if(n.expansion?.contains)return n.expansion.contains.filter(r=>r.code!==void 0).map(r=>(we(r.code),{code:r.code,display:r.display,system:r.system}));let t=[];if(n.compose?.include){for(let r of n.compose.include)if(r.concept)for(let o of r.concept)t.push({system:r.system,code:o.code,display:o.display});else if(r.system&&!r.filter)try{let o=e.resolveAny(r.system);if(o?.concept){let s=(a,c)=>{for(let l of a)t.push({system:c,code:l.code,display:l.display}),l.concept&&s(l.concept,c);};s(o.concept,r.system);}}catch{}}return t.length>0?t:void 0}var he=100,Ze=new Set(["UNK"]),J=new Set(["code","Coding","CodeableConcept","CodeableReference","Quantity","string","uri","Duration"]);function Z(e,n,i,t){if(!i.binding)return;let r=i.binding.strength,o=i.binding.valueSet;if(!o)return;if(!J.has(i.type??"")){t?.dryWarn("#binding",`eld-11: Binding on non-bindable type '${i.type}' (valueSet: ${o})`);return}if(!(r==="required"||r==="extensible"||r==="preferred"))return;let a=Y(e,n.package_meta,o);if(!a||a.length===0)return;let c=a.map(p=>p.code).filter(p=>p&&typeof p=="string"&&p.trim().length>0),l=c.length===1?c[0]:void 0;if(l&&Ze.has(l)){t?.dryWarn("#placeholderValueSet",`Value set ${o} only expands to placeholder code '${l}'; skipping enum generation.`);return}if(c.length>he){t?.dryWarn("#largeValueSet",`Value set ${o} has ${c.length} which is more than ${he} codes, which may cause issues with code generation.`);return}if(c.length!==0)return {isOpen:r!=="required",values:c}}function Qe(e,n,i,t,r){if(!t.binding?.valueSet)return;let o=w(n,i,t),s=H(e,n.package_meta,t.binding.valueSet),a=Z(e,n,t,r);return {identifier:o,valueset:s,strength:t.binding.strength,enum:a,dependencies:[s]}}function Se(e,n,i){let t=new Set;if(!n.elements)return [];let r=[];function o(c,l){for(let[p,m]of Object.entries(c)){let g=[...l,p],d=g.join("."),u=e.resolveElementSnapshot(n,g);if(!t.has(d)){if(t.add(d),u.binding){let f=Qe(e,n,g,u,i);f&&r.push(f);}m.elements&&o(m.elements,g);}}}o(n.elements,[]),r.sort((c,l)=>c.identifier.name.localeCompare(l.identifier.name));let s=[],a=new Set;for(let c of r)a.has(c.identifier.url)||(a.add(c.identifier.url),s.push(c));return s}var Xe=/(?<=[a-z])(?=[A-Z])|[-_.\s]/,en=e=>e.split(Xe).filter(Boolean);var nn=e=>{if(e.length===0)throw new Error("Empty string");return e[0]?.toUpperCase()+e.substring(1).toLowerCase()},Re=e=>{if(e.length===0)throw new Error("Empty string");let[n,...i]=en(e);return [n?.toLowerCase(),...i.map(nn)].join("")};var Q=e=>!e||e.length===0?e:e.charAt(0).toUpperCase()+e.slice(1);var tn=e=>{let n=e.replace(/\[x\]/g,"").replace(/[- :.]/g,"_");return n?Q(n):""},U=e=>{let n=e.replace(/\[x\]/g,"").replace(/:/g,"_");return n?Q(Re(n)):""},rn=(e,n)=>{let i=U(e)||"Extension",t=n.split(".").filter(s=>s&&s!=="extension").join("_"),o=`${t?U(t):""}${i}`;return [i,o,`${o}Extension`]},on=(e,n)=>{let i=tn(n)||"Slice",r=`${U(e)||"Field"}${i}`;return [i,r,`${r}Slice`]},sn=(e,n,i)=>e.reduce((t,r)=>{let o=r.candidates[n]??"";return t[o]=(t[o]??0)+1,i.has(o)&&(t[o]=(t[o]??0)+1),t},{}),an=(e,n)=>{let i=e[0]?.candidates.length??0,t=(r,o)=>{if(r.length===0||o>=i)return {};let s=sn(r,o,n),a=o>=i-1,[c,l]=r.reduce(([p,m],g)=>{let d=g.candidates[o]??"";return (s[d]??0)>1&&!a?[p,[...m,g]]:[{...p,[g.key]:d},m]},[{},[]]);return {...c,...t(l,o+1)}};return t(e,0)},xe=e=>({candidates:rn(e.name,e.path),recommended:""}),Ce=(e,n)=>({candidates:on(e,n),recommended:""}),ke=e=>{let n=(e.extensions??[]).filter(s=>s.url).map(s=>({key:`ext:${s.url}:${s.path}`,candidates:s.nameCandidates.candidates})),i=Object.entries(e.slicing??{}).flatMap(([s,a])=>a.slices?Object.entries(a.slices).map(([c,l])=>({key:`slice:${s}:${c}`,candidates:l.nameCandidates.candidates})):[]),t=new Set(Object.keys(e.fields??{}).map(U)),r=[...n,...i];if(r.length===0)return;let o=an(r,t);for(let s of e.extensions??[]){if(!s.url)continue;let a=`ext:${s.url}:${s.path}`;o[a]&&(s.nameCandidates.recommended=o[a]);}for(let[s,a]of Object.entries(e.slicing??{}))if(a.slices)for(let[c,l]of Object.entries(a.slices)){let p=`slice:${s}:${c}`;o[p]&&(l.nameCandidates.recommended=o[p]);}};var X=e=>e!==null&&typeof e=="object"&&e.resourceType==="CodeSystem";var $=e=>e!==null&&typeof e=="object"&&e.resourceType==="ValueSet";var ln=/^[a-zA-Z0-9]+$/,pn=async(e,n)=>{let i=await e.packageJson(n.name);if(!i)return [];let t=i.dependencies;return t!==void 0?Object.entries(t).map(([r,o])=>({name:r,version:o})):[]},be=e=>({pkg:e,canonicalResolution:{},fhirSchemas:{},valueSets:{}}),Fe=async(e,n,i,t,r)=>{let o=R(n);if(r?.info(`${" ".repeat(i*2)}+ ${o}`),t[o])return t[o];let s=be(n);t[o]=s;for(let c of await e.search({package:n})){let l=c.url;if(!l||!(isStructureDefinition(c)||$(c)||X(c)))continue;let p=l;s.canonicalResolution[p]&&r?.dryWarn("#duplicateCanonical",`Duplicate canonical URL: ${p} at ${o}.`),s.canonicalResolution[p]=[{deep:i,pkg:n,pkgId:o,resource:c}];}let a=await pn(e,n);for(let c of a){let{canonicalResolution:l}=await Fe(e,c,i+1,t,r);for(let[p,m]of Object.entries(l)){let g=p;s.canonicalResolution[g]=[...s.canonicalResolution[g]||[],...m];}}for(let c of Object.values(s.canonicalResolution))c.sort((l,p)=>l.deep-p.deep);return s},dn=(e,n)=>{for(let{pkg:i,canonicalResolution:t}of Object.values(e)){let r=R(i);if(!e[r])throw new Error(`Package ${r} not found`);let o=0;n?.info(`FHIR Schema conversion for '${R(i)}' begins...`);for(let[s,a]of Object.entries(t)){let c=a[0];if(!c)throw new Error("Resource not found");let l=c.resource,p=c.pkg;if(isStructureDefinition(l)){let m=Ie.translate(l),g=ue(m,p);o++,e[r].fhirSchemas[g.url]=g;}if($(l)){let m=ye(l,p);e[r].valueSets[m.url]=m;}}n?.info(`FHIR Schema conversion for '${R(i)}' completed: ${o} successful`);}},mn=(e,n,i)=>{let t=Object.values(e).flatMap(r=>r.canonicalResolution[n]);if(!t)throw new Error(`No canonical resolution found for ${n} in any package`);return t[0]?.resource},un=async(e,{logger:n,focusedPackages:i})=>{let t=i??await e.packages(),r={};for(let d of t)await Fe(e,d,0,r,n);dn(r,n);let o=(d,u)=>{let f=r[R(d)];if(f){let y=f.canonicalResolution[u]?.[0];if(y)return r[y.pkgId]?.fhirSchemas[u]}for(let y of Object.values(r)){let h=y.fhirSchemas[u];if(h&&h.package_meta.name===d.name)return h}for(let y of Object.values(r)){let h=y.fhirSchemas[u];if(h)return h}},s=(d,u)=>{let f=r[R(d)];if(f){let y=f.canonicalResolution[u]?.[0];if(y)return r[y.pkgId]?.valueSets[u]}for(let y of Object.values(r)){let h=y.valueSets[u];if(h&&h.package_meta.name===d.name)return h}for(let y of Object.values(r)){let h=y.valueSets[u];if(h)return h}},a=d=>(d.includes("|")&&(d=d.split("|")[0]),ln.test(d)?`http://hl7.org/fhir/StructureDefinition/${d}`:d),c=(d,u)=>{let f=o(d,u);if(f===void 0)throw new Error(`Failed to resolve FHIR Schema: '${u}'`);let y=[f];for(;f?.base;){let h=f.package_meta,S=a(f.base);if(f=o(h,S),f===void 0)throw new Error(`Failed to resolve FHIR Schema base for '${u}'. Problem: '${S}' from '${R(h)}'`);y.push(f);}return y},l=(d,u)=>c(d,u).filter(f=>f.derivation==="specialization"),p=(d,u)=>{let f=c(d.package_meta,d.url),y=O(f,u);return ee(y)},m=d=>{let u=new Set;for(let[f,y]of Object.entries(d)){u.add(f);for(let h of y?.choices||[])d[h]||u.add(h);}return Array.from(u)},g;return {testAppendFs(d){let u=R(d.package_meta);r[u]||(r[u]=be(d.package_meta)),r[u].fhirSchemas[d.url]=d,g=void 0;},resolveFs:o,resolveFsGenealogy:c,resolveFsSpecializations:l,ensureSpecializationCanonicalUrl:a,resolveSd:(d,u)=>{let f=r[R(d)]?.canonicalResolution[u]?.[0]?.resource;if(isStructureDefinition(f))return f},allSd:()=>Object.values(r).flatMap(d=>Object.values(d.canonicalResolution).flatMap(u=>u.map(f=>{let y=f.resource;return y.package_name?y:{...y,package_name:f.pkg.name,package_version:f.pkg.version}}))).filter(d=>isStructureDefinition(d)).sort((d,u)=>d.url.localeCompare(u.url)),allFs:()=>Object.values(r).flatMap(d=>Object.values(d.fhirSchemas)),allVs:()=>Object.values(r).flatMap(d=>Object.values(d.valueSets)),resolveVs:s,resolveAny:d=>mn(r,d),resolveElementSnapshot:p,getAllElementKeys:m,resolver:r,resolutionTree:()=>{if(g)return g;let d={};for(let[u,f]of Object.entries(r)){let y=f.pkg.name;d[y]={};for(let[h,S]of Object.entries(f.canonicalResolution)){let x=h;d[y][x]=[];for(let C of S)d[y][x].push({deep:C.deep,pkg:C.pkg});}}return g=d,d}}},Te=async(e,n)=>{let i=e.map(de);n?.logger?.info(`Loading FHIR packages: ${i.join(", ")}`);let t=CanonicalManager({packages:i,workingDir:".codegen-cache/canonical-manager-cache",registry:n.registry||void 0});return await t.init(),await un(t,{...n,focusedPackages:e})},O=(e,n)=>{let[i,...t]=n;return i===void 0?[]:e.map(r=>{if(!r.elements)return;let o=r.elements?.[i];for(let s of t)o=o?.elements?.[s];return o}).filter(r=>r!==void 0)};function ee(e){let n=e.reverse(),i=Object.assign({},...n);return i.elements=void 0,i}var ve=(e,n,i)=>{let t=e.resolveFsSpecializations(n.package_meta,n.url),r=O(t,i),o=ee(r).type,s;if(o){let a=e.ensureSpecializationCanonicalUrl(o),l=e.resolveFsGenealogy(n.package_meta,a).flatMap(p=>Object.keys(p.elements??{}));l.length>0&&(s=new Set(l));}for(let a of r)if(!(!a.elements||Object.keys(a.elements).length===0)&&!(s&&!Object.keys(a.elements).some(c=>!s.has(c))))return true;return false},ne=(e,n,i,t,r)=>t.type==="BackboneElement"?true:!r?.elements||r.choiceOf!==void 0?false:ve(e,n,i),fn=e=>e.elements?new Set(te(e,[],e.elements).filter(([n,i])=>i.elements&&Object.keys(i.elements).length>0).map(([n])=>n.join("."))):new Set;function B(e,n,i){let t={},r=n.derivation==="constraint"?e.resolveFsSpecializations(n.package_meta,n.url):e.resolveFsGenealogy(n.package_meta,n.url);for(let p of [...r].reverse()){let m=fn(p);for(let g of m)t[g]=`${p.url}#${g}`;}let o=i.join("."),s=t[o]??`${n.url}#${o}`,a=s.split("#")[0],l=e.resolveFs(n.package_meta,a)?.package_meta??n.package_meta;return {kind:"nested",package:l.name,version:l.version,name:o,url:s}}function te(e,n,i){let t=[];for(let[r,o]of Object.entries(i)){let s=[...n,r];o.elements&&o.choiceOf===void 0&&t.push([s,o]),o.elements&&t.push(...te(e,s,o.elements));}return t}function gn(e,n,i,t,r){let o={},s={},a=e.resolveFsGenealogy(n.package_meta,n.url),c=O(a,i),l=new Set;for(let p of c)if(p.elements)for(let m of Object.keys(p.elements))l.add(m);for(let p of l){let m=[...i,p],g=e.resolveElementSnapshot(n,m);ne(e,n,m,g,t[p])?o[p]=_(e,n,m,g):o[p]=j(e,n,m,g,r);let d=M(p,g);d&&(s[p]=d);}return {fields:o,slicing:Object.keys(s).length>0?s:void 0}}function Ee(e,n,i){if(!n.elements)return;let t=te(n,[],n.elements).filter(([o,s])=>!s.elements||Object.keys(s.elements).length===0?false:s.type!=="BackboneElement"?ve(e,n,o):true),r=[];for(let[o,s]of t){let a=B(e,n,o),c;s.type==="BackboneElement"||!s.type?c="BackboneElement":c=s.type;let l=e.ensureSpecializationCanonicalUrl(c),p=e.resolveFs(n.package_meta,l);if(!p)throw new Error(`Could not resolve base type ${c}`);let m={kind:"complex-type",package:p.package_meta.name,version:p.package_meta.version,name:c,url:l},{fields:g,slicing:d}=gn(e,n,o,s.elements??{},i),u={identifier:a,base:m,fields:g,slicing:d};r.push(u);}return r.sort((o,s)=>o.identifier.url.localeCompare(s.identifier.url)),r.length===0?void 0:r}function Pe(e){let n=[];for(let i of e){i.base&&n.push(i.base);for(let t of Object.values(i.fields||{}))"type"in t&&t.type&&n.push(t.type),"binding"in t&&t.binding&&n.push(t.binding);}return n}var yn=new Set(["Availability","CodeableReference","ExtendedContactDetail","MonetaryComponent","RatioRange","VirtualServiceDetail"]),hn=(e,n)=>{let i=e.resolver[R(n)];if(!i)return false;for(let t of Object.values(i.canonicalResolution))for(let r of t)if(r.pkg.name==="hl7.fhir.r4.core")return true;return false},Sn=(e,n,i)=>!yn.has(i)||!hn(e,n)?"":`
|
|
3
3
|
hint: '${i}' is an R5+ type and is not available when generating against R4.
|
|
4
|
-
Either skip this canonical via skip-hack.ts, or upgrade the target to R5.`;function
|
|
5
|
-
package: ${
|
|
4
|
+
Either skip this canonical via skip-hack.ts, or upgrade the target to R5.`;function Ne(e,n,i){let t=i[i.length-1];if(!t)throw new Error(`Internal error: fieldName is missing for path ${i.join("/")}`);let r=i.slice(0,-1),o=e.resolveFsGenealogy(n.package_meta,n.url).flatMap(s=>{if(r.length===0)return s.required||[];if(!s.elements)return [];let a=s;for(let c of r)a=a?.elements?.[c];return a?.required||[]});return new Set(o).has(t)}function De(e,n,i){let t=i[i.length-1];if(!t)throw new Error(`Internal error: fieldName is missing for path ${i.join("/")}`);let r=i.slice(0,-1),o=e.resolveFsGenealogy(n.package_meta,n.url).flatMap(s=>{if(r.length===0)return s.excluded||[];if(!s.elements)return [];let a=s;for(let c of r)a=a?.elements?.[c];return a?.excluded||[]});return new Set(o).has(t)}var Rn=(e,n,i)=>{if(!i.refers)return;let t=[],r=[],o=new Set;for(let s of i.refers){let a=e.ensureSpecializationCanonicalUrl(s),c=e.resolveFs(n.package_meta,a);if(!c)throw new Error(`Failed to resolve fs for ${a}`);let l=k(c),p=l;if(fe(l)){r.push(l);let m=e.resolveFsSpecializations(c.package_meta,c.url)[0];if(!m)throw new Error(`Failed to resolve base specialization for ${a}`);p=k(m);}o.has(p.url)||(o.add(p.url),t.push(p));}return {resource:t,profiles:r.length>0?r:void 0}},xn=e=>{let n=new Set,i=new Set;if(e.required)for(let r of e.required)n.add(r);if(e.excluded)for(let r of e.excluded)i.add(r);if(e.elements)for(let[r,o]of Object.entries(e.elements))o.min!==void 0&&o.min>0&&n.add(r);let t=e.elements?Object.keys(e.elements):void 0;return {required:n.size>0?Array.from(n):void 0,excluded:i.size>0?Array.from(i):void 0,elements:t&&t.length>0?t:void 0}},Cn=e=>!e||typeof e=="object"&&Object.keys(e).length===0,A=(e,n,i)=>{let t=e;for(let o=0;o<n.length-1;o++){let s=n[o];(!t[s]||typeof t[s]!="object")&&(t[s]={}),t=t[s];}let r=n[n.length-1];t[r]=i;},kn=(e,n)=>{let i=e;for(let t of n)if(i&&typeof i=="object"&&!Array.isArray(i))i=i[t];else return;return i},Le=(e,n,i,t,r)=>{if(i>=n.length||!e.elements)return;let o=n[i],s=e.elements[o];if(s){if(i===n.length-1&&s.fixed?.value!==void 0){A(t,n,s.fixed.value);return}if(s.slicing?.slices){r.add(n.slice(0,i+1).join("."));let a=n.slice(i+1);for(let c of Object.values(s.slicing.slices)){if(!c.min||c.min<1||!c.match||typeof c.match!="object")continue;let l=c.match;if(Object.keys(l).length!==0)if(a.length>0){let p=kn(l,a);p!==void 0&&A(t,n,p);}else A(t,n.slice(0,i+1),l);}return}Le(s,n,i+1,t,r);}},In=(e,n,i)=>{if(e==="$this")return;let t=e.split("."),r=n;for(let s of t)if(r=r?.elements?.[s],!r)return;let o=r.type;!o||o.includes("/")||A(i,t,{resourceType:o});},bn=(e,n)=>{if(!n||!e||e.length===0)return;let i={},t=new Set;for(let r of e)if(r.type==="type")In(r.path,n,i);else {if(!n.elements)continue;let o=r.path.split(".");Le(n,o,0,i,t);}if(Object.keys(i).length!==0){for(let r of t){let o=r.split("."),s=i;for(let c=0;c<o.length-1;c++){let l=s[o[c]];if(!l||typeof l!="object"||Array.isArray(l))break;s=l;}let a=o[o.length-1];s[a]&&typeof s[a]=="object"&&!Array.isArray(s[a])&&(s[a]=[s[a]]);}return i}},M=(e,n)=>{let i=n.slicing;if(!i)return;let t={};for(let[r,o]of Object.entries(i.slices??{})){if(!o)continue;let{required:s,excluded:a,elements:c}=o.schema?xn(o.schema):{};t[r]={min:o.min,max:o.max,match:Cn(o.match)?bn(i.discriminator??[],o.schema):o.match,required:s,excluded:a,elements:c,nameCandidates:Ce(e,r)};}return {discriminator:i.discriminator??[],rules:i.rules,ordered:i.ordered,slices:Object.keys(t).length>0?t:void 0}};function P(e,n,i,t,r){if(t.elementReference){let o=t.elementReference.slice(1).filter((s,a)=>a%2===1);return B(e,n,o)}if(t.type){let o=e.ensureSpecializationCanonicalUrl(t.type),s=e.resolveFs(n.package_meta,o);if(!s){let a=R(n.package_meta),c=i.join("."),l=Sn(e,n.package_meta,t.type);throw new Error(`Could not resolve field type:
|
|
5
|
+
package: ${a}
|
|
6
6
|
schema: ${n.url}
|
|
7
7
|
field: ${c}
|
|
8
|
-
type: ${t.type}${l}`)}return
|
|
9
|
-
`),await writeFile(
|
|
8
|
+
type: ${t.type}${l}`)}return k(s)}t.choices||n.derivation!=="constraint"&&r?.dryWarn("#fieldTypeNotFound",`Can't recognize element type: <${n.url}>.${i.join(".")} (pkg: '${R(n.package_meta)}'): missing type info`);}var j=(e,n,i,t,r,o)=>{let s,a;t.binding&&(s=w(n,i,t),J.has(t.type??"")&&(a=Z(e,n,t,r)));let c=P(e,n,i,t,r);c||r?.dryWarn("#fieldTypeNotFound",`Field type not found for '${n.url}#${i.join(".")}' (${n.derivation})`);let l;t.pattern?l={kind:"pattern",type:t.pattern.type,value:t.pattern.value}:t.fixed&&(l={kind:"fixed",type:t.fixed.type,value:t.fixed.value});let p=o??t;if(!l&&p.elements?.coding?.slicing?.slices){let m=p.elements.coding.slicing.slices,g=Object.values(m);g.length>0&&g.every(u=>u.min!==void 0&&u.min>=1&&u.match&&typeof u.match=="object"&&Object.keys(u.match).length>0)&&(l={kind:"fixed",type:"CodeableConcept",value:{coding:g.flatMap(f=>f.match?[f.match]:[])}});}return {type:c,required:Ne(e,n,i),excluded:De(e,n,i),reference:Rn(e,n,t),array:t.array||false,min:t.min,max:t.max,choices:t.choices,choiceOf:t.choiceOf,binding:s,enum:a,valueConstraint:l,mustSupport:t.mustSupport}};function _(e,n,i,t){return {type:B(e,n,i),array:t.array||false,required:Ne(e,n,i),excluded:De(e,n,i)}}var Fn=(e,n,i,t)=>{let r=e.resolveFs(n.package_meta,i);if(!r?.elements)return;let o=[];for(let[s,a]of Object.entries(r.elements)){if(a.choiceOf!=="value"&&!s.startsWith("value"))continue;let c=P(e,r,[s],a,t);c&&o.push(c);}return E(o)},Tn=(e,n,i)=>{let t=[];if(!n.elements)return t;for(let[r,o]of Object.entries(n.elements)){if(!r.startsWith("extension:"))continue;let s=r.split(":")[1];if(!s)continue;let a;for(let[c,l]of Object.entries(o.elements??{}))if(!(l.choiceOf!=="value"&&!c.startsWith("value"))&&(a=P(e,n,[r,c],l,i),a))break;t.push({name:s,url:o.url??s,valueFieldType:a,min:o.min,max:o.max!==void 0?String(o.max):void 0});}return t},vn=(e,n,i)=>{let t=[],o=n.elements?.extension?.slicing?.slices;if(!o||typeof o!="object")return t;for(let[s,a]of Object.entries(o)){let c=a,l=c.schema;if(!l)continue;let p;for(let[m,g]of Object.entries(l.elements??{})){let d=g;if(!(d.choiceOf!=="value"&&!m.startsWith("value"))&&(p=P(e,n,[m],d,i),p))break}t.push({name:s,url:c.match?.url??s,valueFieldType:p,min:l._required?1:l.min??0,max:l.max!==void 0?String(l.max):l.array?"*":"1"});}return t},En=(e,n,i,t)=>{let r=e.resolveFs(n.package_meta,i);if(!r?.elements)return;let o=Tn(e,r,t),s=vn(e,r,t),a=[...o,...s];return a.length>0?a:void 0},He=(e,n,i)=>{let t=[],r=(c,l,p)=>{let m=p.url,g=m?Fn(e,n,m,i):void 0,d=m?En(e,n,m,i):void 0;if(!m){let S=n.elements?.extension?.slicing?.slices?.[l]?.schema;if(S){m=S.elements?.url?.fixed?.value??l;for(let[x,C]of Object.entries(S.elements??{})){let N=C;if(N.choiceOf==="value"||x.startsWith("value")){let oe=P(e,n,[x],N,i);if(oe){g=[oe];break}}}}}let u=d&&d.length>0,f=m?e.resolveFs(n.package_meta,m):void 0,y=f?k(f):void 0,h=[...c,"extension"].join(".");t.push({name:l,path:h,url:m,profile:y,min:p.min,max:p.max!==void 0?String(p.max):void 0,mustSupport:p.mustSupport,valueFieldTypes:g,subExtensions:d,isComplex:u,nameCandidates:xe({name:l,path:h})});},o=(c,l)=>{if(l.extensions)for(let[p,m]of Object.entries(l.extensions))r(c,p,m);if(l.elements)for(let[p,m]of Object.entries(l.elements))o([...c,p],m);};o([],n);let s=new Set,a=t.filter(c=>{let l=`${c.url}:${c.path}`;return s.has(l)?false:(s.add(l),true)});return a.length===0?void 0:a};function Pn(e,n,i,t,r){if(!t)return {};let o={},s={};for(let a of e.getAllElementKeys(t)){let c=[...i,a],l=e.resolveElementSnapshot(n,c),p=l.type?e.ensureSpecializationCanonicalUrl(l.type):void 0;if(p&&v(n.package_meta,p).shouldSkip){r?.warn("#skipCanonical",`Skipping field ${c} for ${p} due to skip hack ${v(n.package_meta,p).reason}`);continue}ne(e,n,c,l,t[a])?o[a]=_(e,n,c,l):o[a]=j(e,n,c,l,r,t[a]);let m=M(a,l);m&&(s[a]=m);}return {fields:o,slicing:Object.keys(s).length>0?s:void 0}}function Nn(e){let n=[];for(let i of Object.values(e))"type"in i&&i.type&&n.push(i.type),"binding"in i&&i.binding&&n.push(i.binding);return n}async function $e(e,n,i){if(!n.url)throw new Error("ValueSet URL is required");let t=H(e,n.package_meta,n.url),r=Y(e,n.package_meta,n.url);return {identifier:t,description:n.description,concept:r,compose:r?void 0:n.compose}}var Ve=(e,n,i)=>{let t=[];return e&&t.push(e),n&&t.push(...Nn(n)),i&&t.push(...Pe(i)),t},Ue=(e,n,i,t)=>{let o=Ve(n,i,t).filter(s=>!(s.url===e.url||W(s)));return E(o)},Dn=(e,n,i,t)=>{let o=Ve(n,i,t).filter(s=>s.url!==e.url);return E(o)};function Oe(e,n,i){let t;if(n.base){let p=e.resolveFs(n.package_meta,e.ensureSpecializationCanonicalUrl(n.base));if(!p)throw new Error(`Base resource not found '${n.base}' for <${n.url}> from ${R(n.package_meta)}`);let m=k(p);we(!W(m),`Unexpected nested base for ${n.url}`),t=m;}let{fields:r,slicing:o}=Pn(e,n,[],n.elements,i),s=Ee(e,n,i),a=Se(e,n,i);if(n.derivation==="constraint"){let p=k(n);if(!t)throw new Error(`Profile ${n.url} must have a base type`);let m=He(e,n,i),g=m?.flatMap(ge),d=Dn(p,t,r,s),u={identifier:p,base:t,fields:r,slicing:o,nested:s,description:n.description,dependencies:E(d,g),extensions:m};return ke(u),[u,...a]}if(n.kind==="primitive-type"){let p=k(n);return we(t,`Primitive type ${n.url} must have a base type`),[{identifier:p,description:n.description,base:t,dependencies:Ue(p,t,r,s)},...a]}let c=k(n);return [{identifier:c,base:t,fields:r,slicing:o,nested:s,description:n.description,dependencies:Ue(c,t,r,s),typeFamily:void 0},...a]}var Ln=(e,n,i)=>{let t={};for(let s of e){let a=`${s.schema.identifier.url}|${s.schema.identifier.package}`,c=me(s.schema);t[a]??={},t[a][c]??={typeSchema:s.schema,sources:[]},t[a][c].sources.push(s);}let r=[],o={};for(let s of Object.values(t)){let a=Object.entries(s).map(([l,p])=>({...p,schemaHash:l,sources:[...p.sources].sort(z)})).sort(pe),c=a[0];if(c)if(a.length>1){let l=c.typeSchema.identifier.url,p=c.typeSchema.identifier.package;i?.dryWarn("#duplicateSchema",`'${l}' from '${p}' has ${a.length} versions`),r.push(c.typeSchema);o[p]??={},o[p][l]=a.flatMap(g=>g.sources.map(d=>({typeSchema:g.typeSchema,sourcePackage:d.sourcePackage,sourceCanonical:d.sourceCanonical})));}else r.push(c.typeSchema);}return {schemas:r,collisions:o}},Be=async(e,n,i)=>{let t=[];for(let r of e.allFs()){let o=R(r.package_meta),s=v(r.package_meta,r.url);if(s.shouldSkip){i?.dryWarn("#skipCanonical",`Skip ${r.url} from ${o}. Reason: ${s.reason}`);continue}for(let a of Oe(e,r,i))t.push({schema:a,sourcePackage:o,sourceCanonical:r.url});}for(let r of e.allVs())t.push({schema:await $e(e,r),sourcePackage:R(r.package_meta),sourceCanonical:r.url});return Ln(t,n,i)};var Me={command:"generate <packages..>",describe:"Generate TypeSchema files from FHIR packages",builder:{packages:{type:"string",array:true,demandOption:true,describe:"FHIR packages to process (e.g., hl7.fhir.r4.core@4.0.1)"},output:{alias:"o",type:"string",describe:"Output file or directory",default:"./schemas.ndjson"},format:{alias:"f",type:"string",choices:["ndjson","json"],default:"ndjson",describe:"Output format for TypeSchema files"},treeshake:{alias:"t",type:"string",array:true,describe:"Only generate TypeSchemas for specific ResourceTypes (treeshaking)"},singleFile:{alias:"s",type:"boolean",default:false,describe:"Generate single TypeSchema file instead of multiple files (NDJSON format)"},verbose:{alias:"v",type:"boolean",default:false,describe:"Enable verbose output"},registry:{alias:"r",type:"string",describe:"Custom FHIR package registry URL (default: https://fs.get-ig.org/pkgs/)"}},handler:async e=>{let n=I({prefix:"TypeSchema"});try{n.info("Generating TypeSchema from FHIR packages"),n.info(`Packages: ${e.packages.join(", ")}`),n.info(`Output: ${e.output}`);let i=e.singleFile?"ndjson":e.format;n.debug(`Format: ${i}${e.singleFile&&e.format==="json"?" (forced from json due to singleFile)":""}`),e.treeshake&&e.treeshake.length>0&&n.info(`Treeshaking enabled for ResourceTypes: ${e.treeshake.join(", ")}`),e.singleFile&&n.info("Single file output enabled (NDJSON format)"),e.registry&&n.info(`Using custom registry: ${e.registry}`);let t=Date.now(),r=e.packages.map(p=>{if(p.includes("@")){let m=p.lastIndexOf("@");return {name:p.slice(0,m),version:p.slice(m+1)||"latest"}}return {name:p,version:"latest"}});n.info(`Processing packages: ${r.map(p=>`${p.name}@${p.version}`).join(", ")}`);let o=await Te(r,{logger:n,registry:e.registry,focusedPackages:r}),{schemas:s}=await Be(o,void 0,n);if(s.length===0)throw new Error("No schemas were generated from the specified packages");let a=e.output;if(!a)throw new Error("Output format not specified");await mkdir(dirname(a),{recursive:!0});let c;i==="json"?c=JSON.stringify(s,null,2):c=s.map(p=>JSON.stringify(p)).join(`
|
|
9
|
+
`),await writeFile(a,c,"utf-8");let l=Date.now()-t;if(ae(`Generated ${s.length} TypeSchema definitions`,l,{schemas:s.length}),n.info(`Output: ${a}`),e.verbose){n.debug("Generated schemas:");let p=s.map(m=>`${m.identifier?.name||"Unknown"} (${m.identifier?.kind||"unknown"})`);T(p);}}catch(i){n.error(`Failed to generate TypeSchema: ${i instanceof Error?i.message:String(i)}`),process.exit(1);}}};var ie=I({prefix:"typeschema"}),je={command:"typeschema [subcommand]",describe:"TypeSchema operations - generate, validate and merge schemas",builder:e=>e.command(Me).help().example("$0 typeschema generate hl7.fhir.r4.core@4.0.1","Generate TypeSchema from FHIR R4 core package"),handler:e=>{if(!e.subcommand&&e._.length===1){ie.info("Available typeschema subcommands:"),T(["generate Generate TypeSchema files from FHIR packages"]),console.log(`
|
|
10
10
|
Use 'atomic-codegen typeschema <subcommand> --help' for more information about a subcommand.`),console.log(`
|
|
11
|
-
Examples:`),
|
|
12
|
-
Use 'atomic-codegen typeschema <subcommand> --help' for more information about a subcommand.`),process.exit(1));}};var
|
|
11
|
+
Examples:`),T(["atomic-codegen typeschema generate hl7.fhir.r4.core@4.0.1 -o schemas.ndjson","atomic-codegen typeschema validate schemas.ndjson","atomic-codegen typeschema merge schema1.ndjson schema2.ndjson -o merged.ndjson"]);return}e.subcommand&&!["generate","validate","merge"].includes(e.subcommand)&&(ie.error(`Unknown typeschema subcommand: ${e.subcommand}`),ie.info("Available typeschema subcommands:"),T(["generate Generate TypeSchema files from FHIR packages","validate Validate TypeSchema files for correctness and consistency","merge Merge multiple TypeSchema files into a single file"]),console.log(`
|
|
12
|
+
Use 'atomic-codegen typeschema <subcommand> --help' for more information about a subcommand.`),process.exit(1));}};var G=I({prefix:"cli"});async function On(e){let n=e.logLevel??(e.debug||e.verbose?"DEBUG":"INFO");G=I({prefix:"cli",level:n});}function Bn(){return $n(hideBin(process.argv)).scriptName("atomic-codegen").usage("$0 <command> [options]").middleware(On).command(je).option("verbose",{alias:"v",type:"boolean",description:"Enable verbose output",default:false,global:true}).option("debug",{alias:"d",type:"boolean",description:"Enable debug output with detailed logging",default:false,global:true}).option("log-level",{alias:"l",type:"string",choices:["DEBUG","INFO","WARN","ERROR","SILENT"],description:"Set the log level (default: INFO)",global:true}).demandCommand(0).middleware(e=>{e._.length===0&&(se("Welcome to Atomic Codegen!"),console.log("Available commands:"),console.log(" typeschema Generate, validate and merge TypeSchema files"),console.log(`
|
|
13
13
|
Use 'atomic-codegen <command> --help' for more information about a command.`),console.log(`
|
|
14
14
|
Quick examples:`),console.log(" atomic-codegen typeschema generate hl7.fhir.r4.core@4.0.1 -o schemas.ndjson"),console.log(`
|
|
15
|
-
Use 'atomic-codegen --help' to see all options.`),process.exit(0));}).help().version("0.1.0").example("$0 typeschema generate hl7.fhir.r4.core@4.0.1 -o schemas.ndjson","Generate TypeSchemas from FHIR package").fail((e,n,i)=>{
|
|
15
|
+
Use 'atomic-codegen --help' to see all options.`),process.exit(0));}).help().version("0.1.0").example("$0 typeschema generate hl7.fhir.r4.core@4.0.1 -o schemas.ndjson","Generate TypeSchemas from FHIR package").fail((e,n,i)=>{G.error(n?n.message:e),G.error("Use --help for usage information"),process.exit(1);}).wrap(Math.min(120,process.stdout.columns||80))}async function re(){await Bn().parseAsync();}import.meta.main&&re().catch(e=>{G.error(String(e)),process.exit(1);});re().catch(e=>{console.error("CLI Error:",e instanceof Error?e.message:e),process.exit(1);});
|
package/dist/index.d.ts
CHANGED
|
@@ -871,15 +871,22 @@ type CollisionResolution = {
|
|
|
871
871
|
type ResolveCollisionsConf = Record<string, CollisionResolution>;
|
|
872
872
|
type IrConf = {
|
|
873
873
|
treeShake?: TreeShakeConf;
|
|
874
|
+
/** Rule defaults applied to every treeShake root; a rule's own value wins. */
|
|
875
|
+
treeShakeDefaults?: TreeShakeDefaults;
|
|
874
876
|
promoteLogical?: LogicalPromotionConf;
|
|
875
877
|
resolveCollisions?: ResolveCollisionsConf;
|
|
876
878
|
};
|
|
879
|
+
type TreeShakeDefaults = Pick<TreeShakeRule, "followReferences">;
|
|
877
880
|
type LogicalPromotionConf = Record<PkgName$1, CanonicalUrl[]>;
|
|
878
881
|
type TreeShakeConf = Record<string, Record<string, TreeShakeRule>>;
|
|
879
882
|
type TreeShakeRule = {
|
|
880
883
|
ignoreFields?: string[];
|
|
881
884
|
selectFields?: string[];
|
|
882
885
|
ignoreExtensions?: string[];
|
|
886
|
+
/** Also generate types for the schema's reference targets (base resources
|
|
887
|
+
* and target profiles). Non-transitive: followed types keep their own
|
|
888
|
+
* reference targets as plain string literals. Default: false. */
|
|
889
|
+
followReferences?: boolean;
|
|
883
890
|
};
|
|
884
891
|
|
|
885
892
|
type LogLevel = "DEBUG" | "INFO" | "WARN" | "ERROR" | "SILENT";
|
|
@@ -913,7 +920,7 @@ type LoggerOptions<T extends string> = {
|
|
|
913
920
|
level?: LogLevel;
|
|
914
921
|
};
|
|
915
922
|
|
|
916
|
-
type CodegenTag = "#binding" | "#largeValueSet" | "#placeholderValueSet" | "#fieldTypeNotFound" | "#skipCanonical" | "#duplicateSchema" | "#duplicateCanonical" | "#resolveBase" | "#resolveCollisionMiss";
|
|
923
|
+
type CodegenTag = "#binding" | "#largeValueSet" | "#placeholderValueSet" | "#fieldTypeNotFound" | "#skipCanonical" | "#duplicateSchema" | "#duplicateCanonical" | "#resolveBase" | "#resolveCollisionMiss" | "#nonMonotonicChoice";
|
|
917
924
|
type CodegenLog = Log<CodegenTag>;
|
|
918
925
|
type CodegenLogManager = LogManager<CodegenTag>;
|
|
919
926
|
declare const mkCodegenLogger: (opts?: LoggerOptions<CodegenTag>) => LogManager<CodegenTag>;
|
|
@@ -1006,7 +1013,12 @@ interface PythonGeneratorOptions extends WriterOptions {
|
|
|
1006
1013
|
}
|
|
1007
1014
|
|
|
1008
1015
|
interface IntrospectionWriterOptions extends FileSystemWriterOptions {
|
|
1009
|
-
|
|
1016
|
+
/** if target is .ndjson -- put in one file, else -- split into separated files;
|
|
1017
|
+
* profileSnapshots additionally exports flattened profile snapshots next to profiles as <name>.snapshot.json */
|
|
1018
|
+
typeSchemas?: string | {
|
|
1019
|
+
target: string;
|
|
1020
|
+
profileSnapshots?: boolean;
|
|
1021
|
+
};
|
|
1010
1022
|
typeTree?: string /** .json or .yaml file */;
|
|
1011
1023
|
fhirSchemas?: string /** if .ndjson -- put in one file, else -- split into separated files*/;
|
|
1012
1024
|
structureDefinitions?: string /** if .ndjson -- put in one file, else -- split into separated files*/;
|