fhir-openapi-translator 0.1.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/LICENSE +21 -0
- package/README.md +100 -0
- 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 +1429 -0
- package/dist/chunk-G3DCRADV.js.map +1 -0
- package/dist/cli.js +170 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +253 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/docs/REFERENCE.md +200 -0
- package/docs/demo.gif +0 -0
- package/package.json +77 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
type FhirVersion = "r4" | "r4b" | "r5";
|
|
2
|
+
declare const FHIR_VERSIONS: readonly FhirVersion[];
|
|
3
|
+
declare const FHIR_VERSION_NUMBERS: Record<FhirVersion, string>;
|
|
4
|
+
type OpenApiVersion = "3.0.3" | "3.1.0";
|
|
5
|
+
type SourceBackend = "schema-json" | "structure-def";
|
|
6
|
+
interface TrimOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Replace the Narrative type (human-readable HTML in `Resource.text`) with a
|
|
9
|
+
* generic object. Shrinks generated models that never render narratives.
|
|
10
|
+
*/
|
|
11
|
+
excludeNarrative?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Stub out schema definitions first reached deeper than this many hops from
|
|
14
|
+
* a requested resource with a generic object. Bounds the size of the
|
|
15
|
+
* dependency closure for codegen targets that struggle with large graphs.
|
|
16
|
+
*/
|
|
17
|
+
maxDepth?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Replace required-binding enums on code fields with plain strings (the
|
|
20
|
+
* allowed codes are appended to the description). Codegen'd models then
|
|
21
|
+
* tolerate servers that return codes outside the strict ValueSet — a common
|
|
22
|
+
* reality with legacy data. `resourceType` discriminators keep their enum.
|
|
23
|
+
*/
|
|
24
|
+
noEnums?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** An IG package resolved to profiles + terminology (see ig/package.ts). */
|
|
27
|
+
interface IgContextLike {
|
|
28
|
+
name: string;
|
|
29
|
+
version: string;
|
|
30
|
+
fhirVersion: FhirVersion;
|
|
31
|
+
profiles: unknown[];
|
|
32
|
+
profilesMissingSnapshot: string[];
|
|
33
|
+
resolveValueSet: (valueSetUrl: string) => string[] | undefined;
|
|
34
|
+
}
|
|
35
|
+
/** A parsed CapabilityStatement (see capability.ts). */
|
|
36
|
+
interface CapabilityLike {
|
|
37
|
+
fhirVersion?: string;
|
|
38
|
+
resources: {
|
|
39
|
+
type: string;
|
|
40
|
+
interactions: Set<string>;
|
|
41
|
+
searchParamCodes: Set<string>;
|
|
42
|
+
operations: Set<string>;
|
|
43
|
+
}[];
|
|
44
|
+
}
|
|
45
|
+
interface GenerateOptions {
|
|
46
|
+
/**
|
|
47
|
+
* FHIR resource names, e.g. ["Patient", "Observation"]. Optional when a
|
|
48
|
+
* `capability` statement is given (its declared resources are used); when
|
|
49
|
+
* both are set, generation is narrowed to the requested resources.
|
|
50
|
+
*/
|
|
51
|
+
resources?: string[];
|
|
52
|
+
fhirVersion: FhirVersion;
|
|
53
|
+
/**
|
|
54
|
+
* A parsed CapabilityStatement restricting generation to one server's
|
|
55
|
+
* declared support (resources, interactions, search params, operations).
|
|
56
|
+
* Use `loadCapabilityStatement` / `parseCapabilityStatement` to build it.
|
|
57
|
+
*/
|
|
58
|
+
capability?: CapabilityLike;
|
|
59
|
+
/**
|
|
60
|
+
* IG package to apply profiles from: a local path (`.tgz` or unpacked
|
|
61
|
+
* directory) loaded synchronously, or a pre-resolved IgContext (use the
|
|
62
|
+
* async `loadIg` for registry coordinates). Required when `profiles` is set.
|
|
63
|
+
*/
|
|
64
|
+
ig?: string | IgContextLike;
|
|
65
|
+
/**
|
|
66
|
+
* Profile ids, names, or canonical URLs to apply to their base resources.
|
|
67
|
+
* Each profiled resource's schemas/paths reference the named profile schema.
|
|
68
|
+
*/
|
|
69
|
+
profiles?: string[];
|
|
70
|
+
/** Target OpenAPI version. Default: "3.0.3" (widest codegen support). */
|
|
71
|
+
openApiVersion?: OpenApiVersion;
|
|
72
|
+
/** Definition source backend. Default: "schema-json". */
|
|
73
|
+
source?: SourceBackend;
|
|
74
|
+
/**
|
|
75
|
+
* Also emit the standard FHIR operations applicable to the requested
|
|
76
|
+
* resources ($everything, $validate, ...) from the official
|
|
77
|
+
* OperationDefinitions. Default: false.
|
|
78
|
+
*/
|
|
79
|
+
operations?: boolean;
|
|
80
|
+
/** Server base URL for the `servers` entry. Omitted when not given. */
|
|
81
|
+
baseUrl?: string;
|
|
82
|
+
/** Override the generated `info.title`. */
|
|
83
|
+
title?: string;
|
|
84
|
+
trim?: TrimOptions;
|
|
85
|
+
}
|
|
86
|
+
/** A generated OpenAPI document (3.0.3 or 3.1.0) as a plain object. */
|
|
87
|
+
type OpenApiDocument = {
|
|
88
|
+
[key: string]: unknown;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/** Lists the resource type names available for a FHIR version. */
|
|
92
|
+
declare function listResources(fhirVersion: FhirVersion, source?: SourceBackend): string[];
|
|
93
|
+
/**
|
|
94
|
+
* Generates an OpenAPI document covering the requested FHIR resources: their
|
|
95
|
+
* full schema dependency closure plus the standard FHIR RESTful interactions.
|
|
96
|
+
*/
|
|
97
|
+
declare function generateOpenApi(options: GenerateOptions): OpenApiDocument;
|
|
98
|
+
|
|
99
|
+
/** FHIR RESTful interaction codes (CapabilityStatement.rest.resource.interaction). */
|
|
100
|
+
type FhirInteraction = "read" | "vread" | "update" | "patch" | "delete" | "history-instance" | "history-type" | "create" | "search-type";
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Loads and parses a FHIR CapabilityStatement (a server's `/metadata`) into a
|
|
104
|
+
* per-resource description of what the server actually supports, so a spec can
|
|
105
|
+
* be generated to match exactly one server's surface rather than the full
|
|
106
|
+
* standard interaction set.
|
|
107
|
+
*/
|
|
108
|
+
interface ResourceCapability {
|
|
109
|
+
type: string;
|
|
110
|
+
interactions: Set<FhirInteraction>;
|
|
111
|
+
/** Search parameter codes the server declares for this resource. */
|
|
112
|
+
searchParamCodes: Set<string>;
|
|
113
|
+
/** Operation codes and canonical URLs the server declares for this resource. */
|
|
114
|
+
operations: Set<string>;
|
|
115
|
+
}
|
|
116
|
+
interface Capability {
|
|
117
|
+
/** FHIR version string from the statement, e.g. "4.0.1" (may be undefined). */
|
|
118
|
+
fhirVersion?: string;
|
|
119
|
+
resources: ResourceCapability[];
|
|
120
|
+
}
|
|
121
|
+
interface LoadCapabilityOptions {
|
|
122
|
+
fetchImpl?: typeof fetch;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Loads a CapabilityStatement from a local JSON file or an http(s) URL. A URL
|
|
126
|
+
* that does not already end in `/metadata` has it appended, so a base server
|
|
127
|
+
* URL works directly.
|
|
128
|
+
*/
|
|
129
|
+
declare function loadCapabilityStatement(input: string, options?: LoadCapabilityOptions): Promise<Capability>;
|
|
130
|
+
declare function parseCapabilityStatement(json: unknown): Capability;
|
|
131
|
+
|
|
132
|
+
interface MinElementType {
|
|
133
|
+
code: string;
|
|
134
|
+
targetProfile?: string[];
|
|
135
|
+
fhirType?: string;
|
|
136
|
+
}
|
|
137
|
+
interface MinElement {
|
|
138
|
+
path: string;
|
|
139
|
+
min: number;
|
|
140
|
+
max: string;
|
|
141
|
+
short?: string;
|
|
142
|
+
definition?: string;
|
|
143
|
+
contentReference?: string;
|
|
144
|
+
types?: MinElementType[];
|
|
145
|
+
binding?: {
|
|
146
|
+
strength: string;
|
|
147
|
+
valueSet: string;
|
|
148
|
+
codes?: string[];
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Value the element is fixed to by a profile (`fixed[x]`), normalized to a
|
|
152
|
+
* JSON value. Only populated for profiles loaded from an IG package; the
|
|
153
|
+
* vendored base definitions do not carry it.
|
|
154
|
+
*/
|
|
155
|
+
fixed?: unknown;
|
|
156
|
+
/** Profile `mustSupport` flag (surfaced in descriptions, not enforced). */
|
|
157
|
+
mustSupport?: boolean;
|
|
158
|
+
/**
|
|
159
|
+
* Human labels for profile constraints this tool does not enforce in
|
|
160
|
+
* OpenAPI (e.g. "pattern", "slicing"), surfaced in the property description
|
|
161
|
+
* and `x-fhir-constraints-omitted`. IG profiles only.
|
|
162
|
+
*/
|
|
163
|
+
omittedConstraints?: string[];
|
|
164
|
+
}
|
|
165
|
+
interface MinStructureDefinition {
|
|
166
|
+
name: string;
|
|
167
|
+
url: string;
|
|
168
|
+
kind: "resource" | "complex-type" | "primitive-type";
|
|
169
|
+
type: string;
|
|
170
|
+
abstract: boolean;
|
|
171
|
+
baseDefinition?: string;
|
|
172
|
+
elements: MinElement[];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
type ValueSetResolver = (valueSetUrl: string) => string[] | undefined;
|
|
176
|
+
|
|
177
|
+
interface IgProfile {
|
|
178
|
+
/** Versionless canonical URL. */
|
|
179
|
+
url: string;
|
|
180
|
+
id?: string;
|
|
181
|
+
name: string;
|
|
182
|
+
type: string;
|
|
183
|
+
definition: MinStructureDefinition;
|
|
184
|
+
}
|
|
185
|
+
interface IgContext {
|
|
186
|
+
name: string;
|
|
187
|
+
version: string;
|
|
188
|
+
fhirVersion: FhirVersion;
|
|
189
|
+
profiles: IgProfile[];
|
|
190
|
+
/** Names/URLs of constraint profiles that lack a snapshot (cannot be applied). */
|
|
191
|
+
profilesMissingSnapshot: string[];
|
|
192
|
+
resolveValueSet: ValueSetResolver;
|
|
193
|
+
}
|
|
194
|
+
interface LoadIgOptions {
|
|
195
|
+
/** Resolve core bindings (e.g. administrative-gender) not defined in the IG. */
|
|
196
|
+
coreValueSetFallback?: ValueSetResolver;
|
|
197
|
+
/** Base directory for the registry download cache. Defaults to ~/.fhir-oas. */
|
|
198
|
+
cacheDir?: string;
|
|
199
|
+
fetchImpl?: typeof fetch;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Resolves the `--ig` input to an IgContext. Accepts a path to a package
|
|
203
|
+
* tarball (`.tgz`), a path to an unpacked package directory, or a registry
|
|
204
|
+
* coordinate (`name` or `name@version`) fetched from packages.fhir.org and
|
|
205
|
+
* cached locally. Async because registry coordinates hit the network.
|
|
206
|
+
*/
|
|
207
|
+
declare function loadIg(input: string, options?: LoadIgOptions): Promise<IgContext>;
|
|
208
|
+
/**
|
|
209
|
+
* Synchronous IG loader for local inputs (a `.tgz` or an unpacked directory).
|
|
210
|
+
* Registry coordinates require the async {@link loadIg} because they fetch
|
|
211
|
+
* over the network.
|
|
212
|
+
*/
|
|
213
|
+
declare function loadIgSync(input: string, options?: LoadIgOptions): IgContext;
|
|
214
|
+
|
|
215
|
+
interface MergeOptions {
|
|
216
|
+
/** Overwrite conflicting entries instead of failing. */
|
|
217
|
+
force?: boolean;
|
|
218
|
+
}
|
|
219
|
+
interface MergeConflict {
|
|
220
|
+
/** e.g. "components.schemas.Patient" or "paths./Patient/{id}" */
|
|
221
|
+
location: string;
|
|
222
|
+
}
|
|
223
|
+
declare class MergeConflictError extends Error {
|
|
224
|
+
readonly conflicts: MergeConflict[];
|
|
225
|
+
constructor(conflicts: MergeConflict[]);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Merges a generated OpenAPI document into existing YAML text, preserving the
|
|
229
|
+
* existing file's comments, anchors, and key order. Generated entries are
|
|
230
|
+
* added alongside existing content; an existing entry with different content
|
|
231
|
+
* is a conflict (all conflicts are reported; nothing is written unless every
|
|
232
|
+
* conflict is resolved by `force`). Identical entries are left untouched, so
|
|
233
|
+
* re-running the generator is idempotent.
|
|
234
|
+
*/
|
|
235
|
+
declare function mergeIntoYaml(generated: OpenApiDocument, existingText: string, options?: MergeOptions): string;
|
|
236
|
+
interface SpecDiff {
|
|
237
|
+
/** Entries the file lacks (dot paths), e.g. "components.schemas.Patient". */
|
|
238
|
+
missing: string[];
|
|
239
|
+
/** Entries present in the file but differing from generation. */
|
|
240
|
+
changed: string[];
|
|
241
|
+
/** True when the file already contains exactly what generation produces. */
|
|
242
|
+
inSync: boolean;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Compares existing spec text against a generated document without writing
|
|
246
|
+
* anything — the CI drift guard behind `fhir-oas check`. The file is in sync
|
|
247
|
+
* when a merge would be a no-op: nothing to add, nothing conflicting.
|
|
248
|
+
*/
|
|
249
|
+
declare function diffAgainstYaml(generated: OpenApiDocument, existingText: string): SpecDiff;
|
|
250
|
+
/** Serializes a generated document to YAML text. */
|
|
251
|
+
declare function stringifyDocument(generated: OpenApiDocument): string;
|
|
252
|
+
|
|
253
|
+
export { type Capability, type CapabilityLike, FHIR_VERSIONS, FHIR_VERSION_NUMBERS, type FhirVersion, type GenerateOptions, type IgContext, type IgContextLike, type IgProfile, type MergeConflict, MergeConflictError, type MergeOptions, type OpenApiDocument, type OpenApiVersion, type ResourceCapability, type SourceBackend, type SpecDiff, type TrimOptions, diffAgainstYaml, generateOpenApi, listResources, loadCapabilityStatement, loadIg, loadIgSync, mergeIntoYaml, parseCapabilityStatement, stringifyDocument };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FHIR_VERSIONS,
|
|
3
|
+
FHIR_VERSION_NUMBERS,
|
|
4
|
+
MergeConflictError,
|
|
5
|
+
diffAgainstYaml,
|
|
6
|
+
generateOpenApi,
|
|
7
|
+
listResources,
|
|
8
|
+
loadCapabilityStatement,
|
|
9
|
+
loadIg,
|
|
10
|
+
loadIgSync,
|
|
11
|
+
mergeIntoYaml,
|
|
12
|
+
parseCapabilityStatement,
|
|
13
|
+
stringifyDocument
|
|
14
|
+
} from "./chunk-G3DCRADV.js";
|
|
15
|
+
export {
|
|
16
|
+
FHIR_VERSIONS,
|
|
17
|
+
FHIR_VERSION_NUMBERS,
|
|
18
|
+
MergeConflictError,
|
|
19
|
+
diffAgainstYaml,
|
|
20
|
+
generateOpenApi,
|
|
21
|
+
listResources,
|
|
22
|
+
loadCapabilityStatement,
|
|
23
|
+
loadIg,
|
|
24
|
+
loadIgSync,
|
|
25
|
+
mergeIntoYaml,
|
|
26
|
+
parseCapabilityStatement,
|
|
27
|
+
stringifyDocument
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# fhir-openapi-translator — Reference
|
|
2
|
+
|
|
3
|
+
Full CLI, library API, codegen recipes, and limitations. For a quick start see the [README](../README.md).
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- [CLI](#cli)
|
|
8
|
+
- [Library API](#library-api)
|
|
9
|
+
- [Codegen recipes](#codegen-recipes)
|
|
10
|
+
- [Viewing generated specs](#viewing-generated-specs)
|
|
11
|
+
- [What this is for — and what it is not](#what-this-is-for--and-what-it-is-not)
|
|
12
|
+
- [Profiles: what `--profile` applies](#profiles-what---profile-applies)
|
|
13
|
+
- [Assumptions and known limitations](#assumptions-and-known-limitations)
|
|
14
|
+
- [Development](#development)
|
|
15
|
+
|
|
16
|
+
## CLI
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
# Generate a spec for Patient and Observation on FHIR R4, as YAML to stdout
|
|
20
|
+
fhir-oas generate Patient Observation --fhir-version r4
|
|
21
|
+
|
|
22
|
+
# Write JSON, target OpenAPI 3.1
|
|
23
|
+
fhir-oas generate Patient -f r5 --format json --openapi-version 3.1 -o patient.json
|
|
24
|
+
|
|
25
|
+
# Merge into an existing spec, keeping its comments and key order
|
|
26
|
+
fhir-oas generate Questionnaire -f r4 --merge-into api.yaml
|
|
27
|
+
|
|
28
|
+
# Embed a server URL and trim the output
|
|
29
|
+
fhir-oas generate Patient -f r4 --base-url https://fhir.example.org/r4 \
|
|
30
|
+
--exclude-narrative --max-depth 3
|
|
31
|
+
|
|
32
|
+
# List the resource types available for a version
|
|
33
|
+
fhir-oas list --fhir-version r4b
|
|
34
|
+
|
|
35
|
+
# CI drift guard: fail when api.yaml no longer matches generation
|
|
36
|
+
fhir-oas check Patient Observation -f r4 --file api.yaml
|
|
37
|
+
|
|
38
|
+
# Lenient models: replace binding enums with plain strings
|
|
39
|
+
fhir-oas generate Patient -f r4 --no-enums
|
|
40
|
+
|
|
41
|
+
# Include the standard operations (GET /Patient/{id}/$everything, POST /Patient/$validate, ...)
|
|
42
|
+
fhir-oas generate Patient -f r4 --operations
|
|
43
|
+
|
|
44
|
+
# Apply a profile from an Implementation Guide package
|
|
45
|
+
fhir-oas generate Patient -f r4 --ig ./hl7.fhir.us.core-5.0.1.tgz --profile us-core-patient
|
|
46
|
+
fhir-oas generate Patient -f r4 --ig hl7.fhir.us.core@5.0.1 --profile us-core-patient # fetched + cached
|
|
47
|
+
|
|
48
|
+
# List the profiles in an IG package
|
|
49
|
+
fhir-oas list --ig ./hl7.fhir.us.core-5.0.1.tgz
|
|
50
|
+
|
|
51
|
+
# Match a specific server's declared surface (resources, interactions, search params, operations)
|
|
52
|
+
fhir-oas generate -f r4 --capability https://server.example.org/fhir # appends /metadata
|
|
53
|
+
fhir-oas generate -f r4 --capability ./metadata.json # or a saved statement
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`fhir-oas generate --help` shows all options, including `--source` (definition backend: `schema-json` default, or `structure-def`), `--title`, and `--force` (overwrite conflicting entries when merging).
|
|
57
|
+
|
|
58
|
+
## Library API
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { generateOpenApi, listResources, mergeIntoYaml } from "fhir-openapi-translator";
|
|
62
|
+
|
|
63
|
+
const doc = generateOpenApi({
|
|
64
|
+
resources: ["Patient", "Observation"],
|
|
65
|
+
fhirVersion: "r4",
|
|
66
|
+
openApiVersion: "3.0.3", // default
|
|
67
|
+
baseUrl: "https://fhir.example.org/r4",
|
|
68
|
+
trim: { excludeNarrative: true, maxDepth: 3 },
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
listResources("r5"); // ["Account", "ActivityDefinition", ...]
|
|
72
|
+
|
|
73
|
+
// Merge into existing YAML text, preserving comments and formatting
|
|
74
|
+
const mergedYaml = mergeIntoYaml(doc, existingYamlText, { force: false });
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Profiles from an IG package (`loadIg` is async for registry coordinates; a local path can be passed to `ig` directly):
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { generateOpenApi, loadIg } from "fhir-openapi-translator";
|
|
81
|
+
|
|
82
|
+
const ig = await loadIg("hl7.fhir.us.core@5.0.1"); // or "./us-core.tgz", or a directory
|
|
83
|
+
const doc = generateOpenApi({
|
|
84
|
+
resources: ["Patient"],
|
|
85
|
+
fhirVersion: "r4",
|
|
86
|
+
ig, // or ig: "./us-core.tgz" (loaded synchronously)
|
|
87
|
+
profiles: ["us-core-patient"],
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Match a server's CapabilityStatement (`loadCapabilityStatement` is async; `parseCapabilityStatement` takes an already-loaded object):
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { generateOpenApi, loadCapabilityStatement } from "fhir-openapi-translator";
|
|
95
|
+
|
|
96
|
+
const capability = await loadCapabilityStatement("https://server.example.org/fhir");
|
|
97
|
+
const doc = generateOpenApi({ fhirVersion: "r4", capability }); // resources come from the statement
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
See `GenerateOptions` in the type declarations for the full API surface.
|
|
101
|
+
|
|
102
|
+
## Codegen recipes
|
|
103
|
+
|
|
104
|
+
Generated specs are exercised against [openapi-generator](https://github.com/OpenAPITools/openapi-generator) in CI (`typescript-fetch` and `python` targets). Notes that matter in practice:
|
|
105
|
+
|
|
106
|
+
- **typescript-fetch**: pass `--additional-properties=modelPropertyNaming=original`. FHIR represents extensions on primitive fields as sibling `_field` properties; the generator's default naming strips the underscore and produces colliding class members.
|
|
107
|
+
- Large resources produce large model trees (a Patient R4 spec has ~80 schemas, a full Bundle-of-anything far more). If your generator or build struggles, use `--exclude-narrative` and `--max-depth` to shrink the graph.
|
|
108
|
+
- `Bundle.entry.resource` (`ResourceList`) is narrowed to the resource types you requested. If your server returns other types in bundles (e.g. `_include`d resources you didn't generate), either add those resources to the generation, or treat unknown entries as opaque JSON.
|
|
109
|
+
|
|
110
|
+
## Viewing generated specs
|
|
111
|
+
|
|
112
|
+
Swagger UI is fine for a spec covering a handful of resources, but it renders
|
|
113
|
+
every schema in the document eagerly. FHIR's schemas are large and mutually
|
|
114
|
+
recursive, so it degrades sharply as the spec grows. Measured on specs from
|
|
115
|
+
this tool:
|
|
116
|
+
|
|
117
|
+
| Spec | Swagger UI behaviour |
|
|
118
|
+
|---|---|
|
|
119
|
+
| 2 resources (~73 schemas) | renders fine; models expand promptly |
|
|
120
|
+
| 6 resources (~84 schemas) | renders, but expanding one operation blocks the page for ~90s |
|
|
121
|
+
| 39 resources (~188 schemas) | the **Schemas** section never finished rendering (gave up after 5 minutes) |
|
|
122
|
+
|
|
123
|
+
[Redoc](https://github.com/Redocly/redoc) renders lazily and copes with these
|
|
124
|
+
specs far better. Prefer it for anything beyond a few resources:
|
|
125
|
+
|
|
126
|
+
```sh
|
|
127
|
+
npx @redocly/cli preview-docs fhir-r4.openapi.yaml
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
This is a *viewer* limitation, not a defect in the generated specs — they
|
|
131
|
+
validate against the OpenAPI meta-schemas and feed code generators cleanly at
|
|
132
|
+
any size. If you do need Swagger UI on a large spec, `--exclude-narrative` and
|
|
133
|
+
`--max-depth` shrink the schema graph considerably.
|
|
134
|
+
|
|
135
|
+
## What this is for — and what it is not
|
|
136
|
+
|
|
137
|
+
**Intended use**
|
|
138
|
+
|
|
139
|
+
- Generate typed FHIR models and API clients in any language — especially where no mature FHIR SDK exists (Go, Rust, Kotlin, PHP, C++, ...).
|
|
140
|
+
- Publish an OpenAPI contract to the consumers of your FHIR API, so they integrate against a spec rather than prose.
|
|
141
|
+
- Build typed clients for orchestrator-style FHIR endpoints your services call.
|
|
142
|
+
- Feed API gateways, contract-testing tools, mock servers, and documentation portals that speak OpenAPI.
|
|
143
|
+
- Commit the generated spec next to your service and regenerate on FHIR version bumps; merge mode keeps hand-written spec content intact.
|
|
144
|
+
|
|
145
|
+
**Not intended for — do not use this as**
|
|
146
|
+
|
|
147
|
+
- **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`).
|
|
148
|
+
- **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
|
+
- **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
|
+
- **XML payload handling.** Only the FHIR JSON representation is modeled.
|
|
151
|
+
|
|
152
|
+
## Profiles: what `--profile` applies
|
|
153
|
+
|
|
154
|
+
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
|
+
|
|
156
|
+
| Profile constraint | Schema effect |
|
|
157
|
+
|---|---|
|
|
158
|
+
| `min ≥ 1` | property becomes `required` |
|
|
159
|
+
| `max: "0"` | property omitted |
|
|
160
|
+
| `max: "1"` on a base array | scalar instead of array |
|
|
161
|
+
| required binding, resolvable ValueSet (IG-local, then vendored core; ≤150 codes) | inline `enum` |
|
|
162
|
+
| `fixed[x]` | `const` (3.1) / single-value `enum` (3.0.3) |
|
|
163
|
+
| choice-type narrowing | only the permitted `value[x]` expansions emitted |
|
|
164
|
+
| `pattern[x]`, slicing, invariants, must-support | *not enforced* — noted in `description` + `x-fhir-constraints-omitted` |
|
|
165
|
+
|
|
166
|
+
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
|
+
|
|
168
|
+
## Assumptions and known limitations
|
|
169
|
+
|
|
170
|
+
- Output is inherently lossy relative to the FHIR specification (see above); it trades fidelity for reach across language ecosystems.
|
|
171
|
+
- 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
|
+
- 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 is preserved in `x-fhir-search-type`.
|
|
174
|
+
- 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 *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
|
+
- 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.
|
|
177
|
+
|
|
178
|
+
## Development
|
|
179
|
+
|
|
180
|
+
```sh
|
|
181
|
+
npm install
|
|
182
|
+
npm run typecheck
|
|
183
|
+
npm test # vitest; validates generated specs against the OpenAPI meta-schemas
|
|
184
|
+
npm run build # tsup → dist/
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Vendored FHIR definitions live in `definitions/` as gzipped JSON. To refresh them from their upstream npm packages, run:
|
|
188
|
+
|
|
189
|
+
```sh
|
|
190
|
+
node scripts/update-definitions.mjs
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The README demo (`docs/demo.gif`) is regenerated by `scripts/make-demo.mjs`. It
|
|
194
|
+
drives a real generation plus Swagger UI in headless Chromium, so it needs
|
|
195
|
+
dev-only tooling that is deliberately kept out of `package.json`:
|
|
196
|
+
|
|
197
|
+
```sh
|
|
198
|
+
npm install --no-save playwright swagger-ui-dist gifenc pngjs
|
|
199
|
+
npm run build && node scripts/make-demo.mjs
|
|
200
|
+
```
|
package/docs/demo.gif
ADDED
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fhir-openapi-translator",
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
"keywords": [
|
|
6
|
+
"fhir",
|
|
7
|
+
"fhir-to-openapi",
|
|
8
|
+
"openapi",
|
|
9
|
+
"swagger",
|
|
10
|
+
"hl7",
|
|
11
|
+
"hl7-fhir",
|
|
12
|
+
"codegen",
|
|
13
|
+
"code-generation",
|
|
14
|
+
"fhir-codegen",
|
|
15
|
+
"us-core",
|
|
16
|
+
"structuredefinition",
|
|
17
|
+
"capabilitystatement",
|
|
18
|
+
"healthcare",
|
|
19
|
+
"healthcare-interoperability",
|
|
20
|
+
"openapi-generator",
|
|
21
|
+
"fhir-r4",
|
|
22
|
+
"fhir-r5"
|
|
23
|
+
],
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": "krishgok",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/krishgok/fhir-openapi-translator.git"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/krishgok/fhir-openapi-translator#readme",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/krishgok/fhir-openapi-translator/issues"
|
|
33
|
+
},
|
|
34
|
+
"type": "module",
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=20"
|
|
37
|
+
},
|
|
38
|
+
"bin": {
|
|
39
|
+
"fhir-oas": "dist/cli.js"
|
|
40
|
+
},
|
|
41
|
+
"main": "dist/index.js",
|
|
42
|
+
"types": "dist/index.d.ts",
|
|
43
|
+
"exports": {
|
|
44
|
+
".": {
|
|
45
|
+
"types": "./dist/index.d.ts",
|
|
46
|
+
"import": "./dist/index.js"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"files": [
|
|
50
|
+
"dist",
|
|
51
|
+
"definitions",
|
|
52
|
+
"docs"
|
|
53
|
+
],
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsup",
|
|
56
|
+
"prepare": "npm run build",
|
|
57
|
+
"typecheck": "tsc --noEmit",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"test:watch": "vitest",
|
|
60
|
+
"lint": "eslint src test",
|
|
61
|
+
"prepublishOnly": "npm run typecheck && npm run lint && npm run build && npm run test"
|
|
62
|
+
},
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"commander": "^12.1.0",
|
|
65
|
+
"yaml": "^2.6.0"
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@seriousme/openapi-schema-validator": "^2.9.0",
|
|
69
|
+
"@types/node": "^22.9.0",
|
|
70
|
+
"eslint": "^9.14.0",
|
|
71
|
+
"openapi-types": "^12.1.3",
|
|
72
|
+
"tsup": "^8.3.5",
|
|
73
|
+
"typescript": "^5.6.3",
|
|
74
|
+
"typescript-eslint": "^8.13.0",
|
|
75
|
+
"vitest": "^2.1.4"
|
|
76
|
+
}
|
|
77
|
+
}
|