fhir-openapi-translator 0.1.0 → 0.2.0
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/CHANGELOG.md +106 -0
- package/README.md +16 -7
- package/definitions/r4/fhir.schema.json.gz +0 -0
- package/definitions/r4/operation-definitions.json.gz +0 -0
- package/definitions/r4/search-parameters.json.gz +0 -0
- package/definitions/r4/structure-definitions.json.gz +0 -0
- package/definitions/r4b/fhir.schema.json.gz +0 -0
- package/definitions/r4b/operation-definitions.json.gz +0 -0
- package/definitions/r4b/search-parameters.json.gz +0 -0
- package/definitions/r4b/structure-definitions.json.gz +0 -0
- package/definitions/r5/fhir.schema.json.gz +0 -0
- package/definitions/r5/operation-definitions.json.gz +0 -0
- package/definitions/r5/search-parameters.json.gz +0 -0
- package/definitions/r5/structure-definitions.json.gz +0 -0
- package/dist/{chunk-G3DCRADV.js → chunk-RRAIF3A2.js} +232 -12
- package/dist/chunk-RRAIF3A2.js.map +1 -0
- package/dist/cli.js +31 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +36 -0
- package/dist/index.js +1 -1
- package/docs/REFERENCE.md +166 -28
- package/package.json +5 -3
- package/dist/chunk-G3DCRADV.js.map +0 -1
package/dist/index.js
CHANGED
package/docs/REFERENCE.md
CHANGED
|
@@ -7,6 +7,7 @@ Full CLI, library API, codegen recipes, and limitations. For a quick start see t
|
|
|
7
7
|
- [CLI](#cli)
|
|
8
8
|
- [Library API](#library-api)
|
|
9
9
|
- [Codegen recipes](#codegen-recipes)
|
|
10
|
+
- [Search parameters](#search-parameters)
|
|
10
11
|
- [Viewing generated specs](#viewing-generated-specs)
|
|
11
12
|
- [What this is for — and what it is not](#what-this-is-for--and-what-it-is-not)
|
|
12
13
|
- [Profiles: what `--profile` applies](#profiles-what---profile-applies)
|
|
@@ -48,6 +49,13 @@ fhir-oas generate Patient -f r4 --ig hl7.fhir.us.core@5.0.1 --profile us-core-pa
|
|
|
48
49
|
# List the profiles in an IG package
|
|
49
50
|
fhir-oas list --ig ./hl7.fhir.us.core-5.0.1.tgz
|
|
50
51
|
|
|
52
|
+
# Limit the search parameters. Observation defines 38 on R4:
|
|
53
|
+
fhir-oas generate Observation -f r4 --search-params none # -> 0
|
|
54
|
+
fhir-oas generate Observation -f r4 --search-params minimal # -> 8, a common core
|
|
55
|
+
fhir-oas generate Observation -f r4 --search-params code,date # -> exactly the 2 you name
|
|
56
|
+
fhir-oas generate Patient Observation -f r4 \
|
|
57
|
+
--search-params Patient:name,birthdate Observation:code,date # per resource
|
|
58
|
+
|
|
51
59
|
# Match a specific server's declared surface (resources, interactions, search params, operations)
|
|
52
60
|
fhir-oas generate -f r4 --capability https://server.example.org/fhir # appends /metadata
|
|
53
61
|
fhir-oas generate -f r4 --capability ./metadata.json # or a saved statement
|
|
@@ -58,17 +66,21 @@ fhir-oas generate -f r4 --capability ./metadata.json # or a s
|
|
|
58
66
|
## Library API
|
|
59
67
|
|
|
60
68
|
```ts
|
|
61
|
-
import {
|
|
69
|
+
import {
|
|
70
|
+
generateOpenApi,
|
|
71
|
+
listResources,
|
|
72
|
+
mergeIntoYaml,
|
|
73
|
+
} from "fhir-openapi-translator";
|
|
62
74
|
|
|
63
75
|
const doc = generateOpenApi({
|
|
64
76
|
resources: ["Patient", "Observation"],
|
|
65
77
|
fhirVersion: "r4",
|
|
66
|
-
openApiVersion: "3.0.3",
|
|
78
|
+
openApiVersion: "3.0.3", // default
|
|
67
79
|
baseUrl: "https://fhir.example.org/r4",
|
|
68
80
|
trim: { excludeNarrative: true, maxDepth: 3 },
|
|
69
81
|
});
|
|
70
82
|
|
|
71
|
-
listResources("r5");
|
|
83
|
+
listResources("r5"); // ["Account", "ActivityDefinition", ...]
|
|
72
84
|
|
|
73
85
|
// Merge into existing YAML text, preserving comments and formatting
|
|
74
86
|
const mergedYaml = mergeIntoYaml(doc, existingYamlText, { force: false });
|
|
@@ -79,11 +91,11 @@ Profiles from an IG package (`loadIg` is async for registry coordinates; a local
|
|
|
79
91
|
```ts
|
|
80
92
|
import { generateOpenApi, loadIg } from "fhir-openapi-translator";
|
|
81
93
|
|
|
82
|
-
const ig = await loadIg("hl7.fhir.us.core@5.0.1");
|
|
94
|
+
const ig = await loadIg("hl7.fhir.us.core@5.0.1"); // or "./us-core.tgz", or a directory
|
|
83
95
|
const doc = generateOpenApi({
|
|
84
96
|
resources: ["Patient"],
|
|
85
97
|
fhirVersion: "r4",
|
|
86
|
-
ig,
|
|
98
|
+
ig, // or ig: "./us-core.tgz" (loaded synchronously)
|
|
87
99
|
profiles: ["us-core-patient"],
|
|
88
100
|
});
|
|
89
101
|
```
|
|
@@ -91,10 +103,15 @@ const doc = generateOpenApi({
|
|
|
91
103
|
Match a server's CapabilityStatement (`loadCapabilityStatement` is async; `parseCapabilityStatement` takes an already-loaded object):
|
|
92
104
|
|
|
93
105
|
```ts
|
|
94
|
-
import {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
106
|
+
import {
|
|
107
|
+
generateOpenApi,
|
|
108
|
+
loadCapabilityStatement,
|
|
109
|
+
} from "fhir-openapi-translator";
|
|
110
|
+
|
|
111
|
+
const capability = await loadCapabilityStatement(
|
|
112
|
+
"https://server.example.org/fhir",
|
|
113
|
+
);
|
|
114
|
+
const doc = generateOpenApi({ fhirVersion: "r4", capability }); // resources come from the statement
|
|
98
115
|
```
|
|
99
116
|
|
|
100
117
|
See `GenerateOptions` in the type declarations for the full API surface.
|
|
@@ -114,10 +131,10 @@ every schema in the document eagerly. FHIR's schemas are large and mutually
|
|
|
114
131
|
recursive, so it degrades sharply as the spec grows. Measured on specs from
|
|
115
132
|
this tool:
|
|
116
133
|
|
|
117
|
-
| Spec
|
|
118
|
-
|
|
119
|
-
| 2 resources (~73 schemas)
|
|
120
|
-
| 6 resources (~84 schemas)
|
|
134
|
+
| Spec | Swagger UI behaviour |
|
|
135
|
+
| --------------------------- | -------------------------------------------------------------------------- |
|
|
136
|
+
| 2 resources (~73 schemas) | renders fine; models expand promptly |
|
|
137
|
+
| 6 resources (~84 schemas) | renders, but expanding one operation blocks the page for ~90s |
|
|
121
138
|
| 39 resources (~188 schemas) | the **Schemas** section never finished rendering (gave up after 5 minutes) |
|
|
122
139
|
|
|
123
140
|
[Redoc](https://github.com/Redocly/redoc) renders lazily and copes with these
|
|
@@ -127,11 +144,120 @@ specs far better. Prefer it for anything beyond a few resources:
|
|
|
127
144
|
npx @redocly/cli preview-docs fhir-r4.openapi.yaml
|
|
128
145
|
```
|
|
129
146
|
|
|
130
|
-
This is a
|
|
147
|
+
This is a _viewer_ limitation, not a defect in the generated specs — they
|
|
131
148
|
validate against the OpenAPI meta-schemas and feed code generators cleanly at
|
|
132
149
|
any size. If you do need Swagger UI on a large spec, `--exclude-narrative` and
|
|
133
150
|
`--max-depth` shrink the schema graph considerably.
|
|
134
151
|
|
|
152
|
+
## Search parameters
|
|
153
|
+
|
|
154
|
+
Every search parameter is typed as a string (except `_count`). FHIR search
|
|
155
|
+
values carry prefixes, modifiers and `system|code` forms that a stricter schema
|
|
156
|
+
would reject, so the accepted values are advertised as metadata instead of
|
|
157
|
+
constraints:
|
|
158
|
+
|
|
159
|
+
| Field | On | Meaning |
|
|
160
|
+
| ------------------------ | ---------------------------------------- | -------------------------------------------------------- |
|
|
161
|
+
| `x-fhir-search-type` | every parameter | the FHIR search type (`token`, `date`, `reference`, ...) |
|
|
162
|
+
| `x-fhir-search-values` | token parameters over a required binding | the codes that FHIR version defines for that element |
|
|
163
|
+
| `x-fhir-search-prefixes` | `number`, `date`, `quantity` | the comparison prefixes the value may carry |
|
|
164
|
+
|
|
165
|
+
The same information is appended to the parameter `description`, because HL7's
|
|
166
|
+
own `SearchParameter.description` text is not uniform — `Encounter-status`
|
|
167
|
+
spells its codes out, `Observation-status` does not, though both are token
|
|
168
|
+
parameters over a required binding. Deriving them from the definitions makes
|
|
169
|
+
every parameter of a given kind read the same way, and makes them correct per
|
|
170
|
+
version: `Encounter.status` is `arrived | triaged | onleave | finished ...` on
|
|
171
|
+
R4 and `on-hold | completed | discharged ...` on R5.
|
|
172
|
+
|
|
173
|
+
Parameters shared across resources (`Patient.gender | Person.gender | ...`)
|
|
174
|
+
are resolved to the branch for the resource being generated, so
|
|
175
|
+
`MedicationRequest.status` and `MedicationDispense.status` each get their own
|
|
176
|
+
code list rather than one standing in for the other.
|
|
177
|
+
|
|
178
|
+
**Why not `enum`?** Because it would reject valid searches: comma-OR
|
|
179
|
+
(`?status=final,amended`), the token `system|code` form
|
|
180
|
+
(`?status=http://hl7.org/fhir/observation-status|final`), and every
|
|
181
|
+
`:modifier` spelling (`?status:not=entered-in-error`). Consumers that want hard
|
|
182
|
+
validation can read `x-fhir-search-values` and generate it themselves.
|
|
183
|
+
|
|
184
|
+
### Emitting fewer of them
|
|
185
|
+
|
|
186
|
+
Servers commonly index only a subset of the standard search parameters — each
|
|
187
|
+
one indexed costs storage and write throughput — and a spec that advertises
|
|
188
|
+
parameters the server does not support is wrong in the direction that causes
|
|
189
|
+
support tickets. `--search-params` narrows what is emitted:
|
|
190
|
+
|
|
191
|
+
```sh
|
|
192
|
+
fhir-oas generate Observation -f r4 --search-params none # none at all
|
|
193
|
+
fhir-oas generate Observation -f r4 --search-params minimal # the common core below
|
|
194
|
+
fhir-oas generate Observation -f r4 --search-params code,date # exactly the codes you name
|
|
195
|
+
fhir-oas generate Patient Observation -f r4 \
|
|
196
|
+
--search-params Patient:name,birthdate Observation:code,date
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
A code list is taken literally — it is _your_ list, and nothing is added to it.
|
|
200
|
+
|
|
201
|
+
### Tuning a preset
|
|
202
|
+
|
|
203
|
+
No fixed rule can know which parameters matter for a given resource.
|
|
204
|
+
`Observation.based-on`, `CarePlan.goal` and `MedicationStatement.adherence` are
|
|
205
|
+
each central to their resource, and none is common enough to curate globally. A
|
|
206
|
+
preset is therefore a starting point that `+code` and `-code` adjust:
|
|
207
|
+
|
|
208
|
+
```sh
|
|
209
|
+
--search-params minimal,+based-on # add to a preset
|
|
210
|
+
--search-params minimal,+goal,+condition,+focus # add as many as you like
|
|
211
|
+
--search-params minimal,-category # remove from a preset
|
|
212
|
+
--search-params minimal,+based-on,-category # add and remove together
|
|
213
|
+
--search-params all,-note,-derived-from # everything except
|
|
214
|
+
--search-params none,+code,+date # build up from nothing
|
|
215
|
+
--search-params Observation:minimal,+based-on CarePlan:minimal,+goal
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The preset comes first; everything after it is an adjustment. There is no
|
|
219
|
+
limit on how many, and additions and removals may be mixed in any order. Every code named
|
|
220
|
+
anywhere — in a list, an `+add`, or a `-remove` — must exist on that resource,
|
|
221
|
+
so a typo fails loudly instead of quietly shrinking the contract. Mixing a
|
|
222
|
+
preset with bare codes is rejected rather than guessed at: write `minimal,+code`
|
|
223
|
+
to extend the preset, or drop the preset to give an exact list.
|
|
224
|
+
|
|
225
|
+
`minimal` is the one curated selection, and it is tiered so that it means
|
|
226
|
+
something for every resource rather than only those that happen to use common
|
|
227
|
+
parameter names:
|
|
228
|
+
|
|
229
|
+
1. The parameters most servers index regardless of resource type —
|
|
230
|
+
`identifier`, `status`, `patient`, `subject`, `encounter`, `code`,
|
|
231
|
+
`category`, `date`, `type`, `url`, `name` — where the resource defines any.
|
|
232
|
+
2. Otherwise, parameters addressing a **top-level element** directly
|
|
233
|
+
(`Linkage.author`) rather than something nested or filtered
|
|
234
|
+
(`Linkage.item.resource`). These address the resource itself and are the
|
|
235
|
+
cheapest to index.
|
|
236
|
+
3. Otherwise, everything the resource defines.
|
|
237
|
+
|
|
238
|
+
On R4 that takes `Observation` from 38 parameters to 8 (`category`, `code`,
|
|
239
|
+
`date`, `encounter`, `identifier`, `patient`, `status`, `subject`) and
|
|
240
|
+
`Patient` from 23 to 2 (`identifier`, `name`), both via tier 1. `Linkage`,
|
|
241
|
+
whose parameters are `author`, `item` and `source` and which names no common
|
|
242
|
+
code, goes from 3 to 1 via tier 2 rather than to nothing.
|
|
243
|
+
|
|
244
|
+
Across R4/R4B/R5 no resource that defines a search parameter is left with none,
|
|
245
|
+
while a median resource keeps 4 and roughly two thirds of resource-specific
|
|
246
|
+
parameters are dropped. A resource that defines **no** search parameters at all
|
|
247
|
+
(`Binary`, `OperationOutcome`) yields none, because there is nothing to select
|
|
248
|
+
— that is not `minimal` doing anything.
|
|
249
|
+
|
|
250
|
+
It remains a convenience and a judgement call: FHIR has no "commonly indexed"
|
|
251
|
+
marker, so tier 1 is a fixed hand-picked list. When the selection matters, name
|
|
252
|
+
the codes explicitly or derive them from your server with `--capability` —
|
|
253
|
+
either is exact, where `minimal` is only a reasonable default.
|
|
254
|
+
|
|
255
|
+
Note that this trims the _contract_, not the server — it does not itself make
|
|
256
|
+
anything faster. Its value is that the published spec matches the deployment.
|
|
257
|
+
If your server already declares its tuned surface, `--capability` derives the
|
|
258
|
+
same restriction from `/metadata` with nothing to maintain by hand, and the two
|
|
259
|
+
combine: passing both emits only parameters that satisfy each.
|
|
260
|
+
|
|
135
261
|
## What this is for — and what it is not
|
|
136
262
|
|
|
137
263
|
**Intended use**
|
|
@@ -144,8 +270,8 @@ any size. If you do need Swagger UI on a large spec, `--exclude-narrative` and
|
|
|
144
270
|
|
|
145
271
|
**Not intended for — do not use this as**
|
|
146
272
|
|
|
147
|
-
- **A FHIR validator.** Passing schema validation does
|
|
148
|
-
- **A full profile / conformance engine.** `--profile` applies the
|
|
273
|
+
- **A FHIR validator.** Passing schema validation does _not_ make a resource FHIR-conformant: FHIRPath invariants, terminology bindings, and profile constraints (slicing, must-support, cardinality refinements) are not fully represented in OpenAPI. Validate with a real FHIR validator (HAPI, the official validator, server-side `$validate`).
|
|
274
|
+
- **A full profile / conformance engine.** `--profile` applies the _representable_ profile constraints (see the table below), but slicing, extension slices, `pattern[x]`, FHIRPath invariants, and must-support are **not enforced** — they are surfaced as description notes and `x-fhir-constraints-omitted`, not as schema rules.
|
|
149
275
|
- **A replacement for a full FHIR SDK.** HAPI FHIR and Firely offer richer, spec-aware models and conformance tooling than any OpenAPI codegen can; where you need that depth, use them alongside this rather than instead of it.
|
|
150
276
|
- **XML payload handling.** Only the FHIR JSON representation is modeled.
|
|
151
277
|
|
|
@@ -153,27 +279,39 @@ any size. If you do need Swagger UI on a large spec, `--exclude-narrative` and
|
|
|
153
279
|
|
|
154
280
|
Given `--ig <package> --profile <id>`, the profile snapshot is turned into a schema named after the profile (`USCorePatient`), referenced from the base `/Patient` paths:
|
|
155
281
|
|
|
156
|
-
| Profile constraint
|
|
157
|
-
|
|
158
|
-
| `min ≥ 1`
|
|
159
|
-
| `max: "0"`
|
|
160
|
-
| `max: "1"` on a base array
|
|
161
|
-
| required binding, resolvable ValueSet (IG-local, then vendored core; ≤150 codes)
|
|
162
|
-
| `fixed[x]` | `const` (3.1) / single-value `enum` (3.0.3)
|
|
163
|
-
|
|
|
164
|
-
|
|
|
282
|
+
| Profile constraint | Schema effect |
|
|
283
|
+
| ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
|
284
|
+
| `min ≥ 1` | property becomes `required` |
|
|
285
|
+
| `max: "0"` | property omitted |
|
|
286
|
+
| `max: "1"` on a base array | scalar instead of array |
|
|
287
|
+
| required binding, resolvable ValueSet (IG-local, then vendored core; ≤150 codes) | inline `enum` |
|
|
288
|
+
| `fixed[x]` on an element emitted as its own property | `const` (3.1) / single-value `enum` (3.0.3) |
|
|
289
|
+
| `fixed[x]` **inside** a datatype or slice (e.g. `Observation.category.coding.code`) | _not applied_ — see below |
|
|
290
|
+
| choice-type narrowing | only the permitted `value[x]` expansions emitted |
|
|
291
|
+
| `pattern[x]`, slicing, invariants, must-support | _not enforced_ — noted in `description` + `x-fhir-constraints-omitted` |
|
|
292
|
+
|
|
293
|
+
Note on `fixed[x]`: real IGs usually pin values at paths _inside_ a datatype
|
|
294
|
+
(`Observation.category.coding.code`) or inside a slice, rather than on a
|
|
295
|
+
resource element directly. Those datatypes are emitted once as shared schemas
|
|
296
|
+
and referenced with `$ref`, so a constraint that applies to one profile's use
|
|
297
|
+
of `CodeableConcept` cannot be written into the shared `CodeableConcept`
|
|
298
|
+
schema. Such fixed values are therefore **not** represented — US Core Blood
|
|
299
|
+
Pressure, for example, declares six and none appear as `const`. Only fixed
|
|
300
|
+
values on elements the profile emits as their own property are applied.
|
|
165
301
|
|
|
166
302
|
The IG package is a local `.tgz` / unpacked directory, or a `name@version` coordinate fetched from `packages.fhir.org` and cached under `~/.fhir-oas/packages`. Profiles must ship a snapshot (differential-only packages error); the package FHIR version must match `--fhir-version`; only the public registry is supported (no auth).
|
|
167
303
|
|
|
168
304
|
## Assumptions and known limitations
|
|
169
305
|
|
|
170
306
|
- Output is inherently lossy relative to the FHIR specification (see above); it trades fidelity for reach across language ecosystems.
|
|
171
|
-
- Enums cover
|
|
307
|
+
- Enums cover _required_ bindings whose ValueSet expands to a bounded code list (≤150 codes, no filters); extensible/preferred bindings and open code systems (BCP-47 languages, MIME types) stay plain strings by design.
|
|
172
308
|
- Primitive-extension properties (`_field`) are kept for JSON fidelity; they roughly double the property count of each model.
|
|
173
|
-
- Search parameters are typed as strings (except `_count`), because FHIR search values carry prefixes and modifiers (`ge2021-01-01`, `code:below=...`) that stricter types would reject. The FHIR type
|
|
309
|
+
- Search parameters are typed as strings (except `_count`), because FHIR search values carry prefixes and modifiers (`ge2021-01-01`, `code:below=...`) that stricter types would reject. The FHIR type, accepted codes and accepted prefixes are surfaced as metadata instead — see [Search parameters](#search-parameters).
|
|
310
|
+
- Search _modifiers_ (`:exact`, `:contains`, `:not`, `:missing`, `:below`, ...) are not emitted as separate parameters; only the base parameter name is. Chained (`subject.name`) and composite-on-the-fly parameters are likewise not enumerated beyond those the specification defines.
|
|
174
311
|
- Operations are resource-scoped only: system-level operations (`GET /$export`-style, `$convert`, ...), `POST /_search`, batch/transaction semantics, and conditional headers beyond `If-Match` are not modeled. Multi-part operation parameters collapse to a generic `Parameters` body.
|
|
175
|
-
- `--capability` reflects a server's
|
|
312
|
+
- `--capability` reflects a server's _declared_ surface: resource types the FHIR version doesn't define are skipped, and declared operations are emitted only when they map to a known OperationDefinition (system-level interactions like transaction/batch are not modeled). It restricts what's generated; it does not verify the server actually behaves as declared.
|
|
176
313
|
- The two definition backends can differ cosmetically (e.g. naming of deeply nested backbone elements, primitive regex patterns); `schema-json` is the default and the reference.
|
|
314
|
+
- They also differ on one thing that is **not** cosmetic: **mandatory primitive elements**. `structure-def` marks them `required` (the StructureDefinition says `min: 1`), while `schema-json` does not, because the official `fhir.schema.json` omits them — a FHIR primitive may legitimately appear as only its `_element` extension sibling (e.g. carrying a `dataAbsentReason`), so the JSON property itself is not strictly required. `Observation.status` is the canonical example: required under `structure-def`, optional under `schema-json`. Mandatory _complex_ elements (`Observation.code`) are required under both. Pick `structure-def` if you want stricter generated models.
|
|
177
315
|
|
|
178
316
|
## Development
|
|
179
317
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fhir-openapi-translator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Generate an OpenAPI (Swagger) spec for any FHIR resource (R4/R4B/R5) — for typed model & client codegen in any language. Supports US Core profiles, operations, and CapabilityStatements.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fhir",
|
|
@@ -44,12 +44,14 @@
|
|
|
44
44
|
".": {
|
|
45
45
|
"types": "./dist/index.d.ts",
|
|
46
46
|
"import": "./dist/index.js"
|
|
47
|
-
}
|
|
47
|
+
},
|
|
48
|
+
"./package.json": "./package.json"
|
|
48
49
|
},
|
|
49
50
|
"files": [
|
|
50
51
|
"dist",
|
|
51
52
|
"definitions",
|
|
52
|
-
"docs"
|
|
53
|
+
"docs",
|
|
54
|
+
"CHANGELOG.md"
|
|
53
55
|
],
|
|
54
56
|
"scripts": {
|
|
55
57
|
"build": "tsup",
|