@atomic-ehr/codegen 0.0.1-canary.20250808231821.ab61009
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/README.md +446 -0
- package/dist/api/builder.d.ts +147 -0
- package/dist/api/builder.d.ts.map +1 -0
- package/dist/api/generators/typescript.d.ts +129 -0
- package/dist/api/generators/typescript.d.ts.map +1 -0
- package/dist/api/index.d.ts +51 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/cli/commands/generate/typescript.d.ts +11 -0
- package/dist/cli/commands/generate/typescript.d.ts.map +1 -0
- package/dist/cli/commands/generate.d.ts +23 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/index.d.ts +40 -0
- package/dist/cli/commands/index.d.ts.map +1 -0
- package/dist/cli/commands/typeschema/generate.d.ts +18 -0
- package/dist/cli/commands/typeschema/generate.d.ts.map +1 -0
- package/dist/cli/commands/typeschema.d.ts +11 -0
- package/dist/cli/commands/typeschema.d.ts.map +1 -0
- package/dist/cli/index.d.ts +11 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/utils/prompts.d.ts +57 -0
- package/dist/cli/utils/prompts.d.ts.map +1 -0
- package/dist/cli/utils/spinner.d.ts +111 -0
- package/dist/cli/utils/spinner.d.ts.map +1 -0
- package/dist/config.d.ts +171 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/index.d.ts +83 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4008 -0
- package/dist/logger.d.ts +158 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/types/base.d.ts +66 -0
- package/dist/types/base.d.ts.map +1 -0
- package/dist/typeschema/cache.d.ts +105 -0
- package/dist/typeschema/cache.d.ts.map +1 -0
- package/dist/typeschema/core/binding.d.ts +29 -0
- package/dist/typeschema/core/binding.d.ts.map +1 -0
- package/dist/typeschema/core/field-builder.d.ts +45 -0
- package/dist/typeschema/core/field-builder.d.ts.map +1 -0
- package/dist/typeschema/core/identifier.d.ts +28 -0
- package/dist/typeschema/core/identifier.d.ts.map +1 -0
- package/dist/typeschema/core/nested-types.d.ts +25 -0
- package/dist/typeschema/core/nested-types.d.ts.map +1 -0
- package/dist/typeschema/core/transformer.d.ts +18 -0
- package/dist/typeschema/core/transformer.d.ts.map +1 -0
- package/dist/typeschema/generator.d.ts +57 -0
- package/dist/typeschema/generator.d.ts.map +1 -0
- package/dist/typeschema/index.d.ts +66 -0
- package/dist/typeschema/index.d.ts.map +1 -0
- package/dist/typeschema/parser.d.ts +92 -0
- package/dist/typeschema/parser.d.ts.map +1 -0
- package/dist/typeschema/profile/processor.d.ts +14 -0
- package/dist/typeschema/profile/processor.d.ts.map +1 -0
- package/dist/typeschema/schema.d.ts +486 -0
- package/dist/typeschema/schema.d.ts.map +1 -0
- package/dist/typeschema/types.d.ts +326 -0
- package/dist/typeschema/types.d.ts.map +1 -0
- package/dist/typeschema/utils.d.ts +7 -0
- package/dist/typeschema/utils.d.ts.map +1 -0
- package/dist/typeschema/value-set/processor.d.ts +20 -0
- package/dist/typeschema/value-set/processor.d.ts.map +1 -0
- package/dist/utils.d.ts +23 -0
- package/dist/utils.d.ts.map +1 -0
- package/package.json +60 -0
- package/src/index.ts +86 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4008 @@
|
|
|
1
|
+
// src/typeschema/cache.ts
|
|
2
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { readdir, readFile, stat, unlink, writeFile } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
class TypeSchemaCache {
|
|
7
|
+
cache = new Map;
|
|
8
|
+
config;
|
|
9
|
+
cacheDir;
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.config = {
|
|
12
|
+
enablePersistence: true,
|
|
13
|
+
cacheDir: ".typeschema-cache",
|
|
14
|
+
maxAge: 24 * 60 * 60 * 1000,
|
|
15
|
+
validateCached: true,
|
|
16
|
+
...config
|
|
17
|
+
};
|
|
18
|
+
if (this.config.enablePersistence && this.config.cacheDir) {
|
|
19
|
+
this.cacheDir = this.config.cacheDir;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async set(schema) {
|
|
23
|
+
const key = this.generateKey(schema.identifier);
|
|
24
|
+
this.cache.set(key, schema);
|
|
25
|
+
if (this.config.enablePersistence && this.cacheDir) {
|
|
26
|
+
await this.persistSchema(schema);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
get(identifier) {
|
|
30
|
+
const key = this.generateKey(identifier);
|
|
31
|
+
return this.cache.get(key) || null;
|
|
32
|
+
}
|
|
33
|
+
getByUrl(url) {
|
|
34
|
+
for (const schema of this.cache.values()) {
|
|
35
|
+
if (schema.identifier.url === url) {
|
|
36
|
+
return schema;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
has(identifier) {
|
|
42
|
+
const key = this.generateKey(identifier);
|
|
43
|
+
return this.cache.has(key);
|
|
44
|
+
}
|
|
45
|
+
hasByUrl(url) {
|
|
46
|
+
for (const schema of this.cache.values()) {
|
|
47
|
+
if (schema.identifier.url === url) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
delete(identifier) {
|
|
54
|
+
const key = this.generateKey(identifier);
|
|
55
|
+
return this.cache.delete(key);
|
|
56
|
+
}
|
|
57
|
+
deleteByUrl(url) {
|
|
58
|
+
for (const [key, schema] of this.cache.entries()) {
|
|
59
|
+
if (schema.identifier.url === url) {
|
|
60
|
+
return this.cache.delete(key);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
getByPackage(packageName) {
|
|
66
|
+
const results = [];
|
|
67
|
+
for (const schema of this.cache.values()) {
|
|
68
|
+
if (schema.identifier.package === packageName) {
|
|
69
|
+
results.push(schema);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return results;
|
|
73
|
+
}
|
|
74
|
+
getByKind(kind) {
|
|
75
|
+
const results = [];
|
|
76
|
+
for (const schema of this.cache.values()) {
|
|
77
|
+
if (schema.identifier.kind === kind) {
|
|
78
|
+
results.push(schema);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return results;
|
|
82
|
+
}
|
|
83
|
+
setMany(schemas) {
|
|
84
|
+
for (const schema of schemas) {
|
|
85
|
+
this.set(schema);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
clear() {
|
|
89
|
+
this.cache.clear();
|
|
90
|
+
}
|
|
91
|
+
generateKey(identifier) {
|
|
92
|
+
return `${identifier.package}:${identifier.version}:${identifier.kind}:${identifier.name}`;
|
|
93
|
+
}
|
|
94
|
+
async initialize() {
|
|
95
|
+
if (this.config.enablePersistence && this.cacheDir) {
|
|
96
|
+
if (!existsSync(this.cacheDir)) {
|
|
97
|
+
mkdirSync(this.cacheDir, { recursive: true });
|
|
98
|
+
}
|
|
99
|
+
await this.loadFromDisk();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async loadFromDisk() {
|
|
103
|
+
if (!this.cacheDir || !existsSync(this.cacheDir)) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const files = await readdir(this.cacheDir);
|
|
108
|
+
const schemaFiles = files.filter((f) => f.endsWith(".typeschema.json"));
|
|
109
|
+
for (const file of schemaFiles) {
|
|
110
|
+
const filePath = join(this.cacheDir, file);
|
|
111
|
+
const stats = await stat(filePath);
|
|
112
|
+
if (this.config.maxAge) {
|
|
113
|
+
const age = Date.now() - stats.mtimeMs;
|
|
114
|
+
if (age > this.config.maxAge) {
|
|
115
|
+
await unlink(filePath);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const content = await readFile(filePath, "utf-8");
|
|
121
|
+
const metadata = JSON.parse(content);
|
|
122
|
+
if (this.config.validateCached) {
|
|
123
|
+
if (!metadata.schema?.identifier) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const key = this.generateKey(metadata.schema.identifier);
|
|
128
|
+
this.cache.set(key, metadata.schema);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.warn(`Failed to load cached schema from ${file}:`, error);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.warn("Failed to load cached schemas from disk:", error);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async persistSchema(schema) {
|
|
138
|
+
if (!this.cacheDir) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (!existsSync(this.cacheDir)) {
|
|
142
|
+
mkdirSync(this.cacheDir, { recursive: true });
|
|
143
|
+
}
|
|
144
|
+
const metadata = {
|
|
145
|
+
schema,
|
|
146
|
+
timestamp: Date.now(),
|
|
147
|
+
version: schema.identifier.version
|
|
148
|
+
};
|
|
149
|
+
const fileName = `${schema.identifier.package}-${schema.identifier.version}-${schema.identifier.kind}-${schema.identifier.name}.typeschema.json`.replace(/[^a-zA-Z0-9.-]/g, "_");
|
|
150
|
+
const filePath = join(this.cacheDir, fileName);
|
|
151
|
+
try {
|
|
152
|
+
await writeFile(filePath, JSON.stringify(metadata, null, 2), "utf-8");
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.warn(`Failed to persist schema to ${filePath}:`, error);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async clearDisk() {
|
|
158
|
+
if (!this.cacheDir || !existsSync(this.cacheDir)) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
const files = await readdir(this.cacheDir);
|
|
163
|
+
const schemaFiles = files.filter((f) => f.endsWith(".typeschema.json"));
|
|
164
|
+
for (const file of schemaFiles) {
|
|
165
|
+
await unlink(join(this.cacheDir, file));
|
|
166
|
+
}
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.warn("Failed to clear cache directory:", error);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// node_modules/@atomic-ehr/fhir-canonical-manager/dist/reference/store.js
|
|
173
|
+
import { createHash } from "crypto";
|
|
174
|
+
var generateReferenceId = (metadata) => {
|
|
175
|
+
const input = `${metadata.packageName}@${metadata.packageVersion}:${metadata.filePath}`;
|
|
176
|
+
return createHash("sha256").update(input).digest("base64url");
|
|
177
|
+
};
|
|
178
|
+
// node_modules/@atomic-ehr/fhir-canonical-manager/dist/reference/manager.js
|
|
179
|
+
var createReferenceManager = () => {
|
|
180
|
+
const references = {};
|
|
181
|
+
const urlToIds = {};
|
|
182
|
+
const set = (id, metadata) => {
|
|
183
|
+
references[id] = metadata;
|
|
184
|
+
if (metadata.url) {
|
|
185
|
+
if (!urlToIds[metadata.url]) {
|
|
186
|
+
urlToIds[metadata.url] = [];
|
|
187
|
+
}
|
|
188
|
+
const ids = urlToIds[metadata.url];
|
|
189
|
+
if (ids && !ids.includes(id)) {
|
|
190
|
+
ids.push(id);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
const clear = () => {
|
|
195
|
+
Object.keys(references).forEach((key) => delete references[key]);
|
|
196
|
+
Object.keys(urlToIds).forEach((key) => delete urlToIds[key]);
|
|
197
|
+
};
|
|
198
|
+
return {
|
|
199
|
+
generateId: generateReferenceId,
|
|
200
|
+
get: (id) => references[id],
|
|
201
|
+
set,
|
|
202
|
+
has: (id) => (id in references),
|
|
203
|
+
clear,
|
|
204
|
+
size: () => Object.keys(references).length,
|
|
205
|
+
getIdsByUrl: (url) => urlToIds[url] || [],
|
|
206
|
+
createReference: (id, metadata) => ({
|
|
207
|
+
id,
|
|
208
|
+
resourceType: metadata.resourceType
|
|
209
|
+
}),
|
|
210
|
+
getAllReferences: () => references
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
// node_modules/@atomic-ehr/fhir-canonical-manager/dist/manager/canonical.js
|
|
214
|
+
import * as path8 from "path";
|
|
215
|
+
import * as fs8 from "fs/promises";
|
|
216
|
+
|
|
217
|
+
// node_modules/@atomic-ehr/fhir-canonical-manager/dist/constants.js
|
|
218
|
+
var DEFAULT_REGISTRY = "https://fs.get-ig.org/pkgs/";
|
|
219
|
+
|
|
220
|
+
// node_modules/@atomic-ehr/fhir-canonical-manager/dist/fs/utils.js
|
|
221
|
+
import * as fs from "fs/promises";
|
|
222
|
+
import * as path from "path";
|
|
223
|
+
var fileExists = async (filePath) => {
|
|
224
|
+
try {
|
|
225
|
+
await fs.access(filePath);
|
|
226
|
+
return true;
|
|
227
|
+
} catch {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
var ensureDir = async (dirPath) => {
|
|
232
|
+
try {
|
|
233
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
234
|
+
} catch {}
|
|
235
|
+
};
|
|
236
|
+
var isFhirPackage = async (dirPath) => {
|
|
237
|
+
const indexPath = path.join(dirPath, ".index.json");
|
|
238
|
+
return fileExists(indexPath);
|
|
239
|
+
};
|
|
240
|
+
// node_modules/@atomic-ehr/fhir-canonical-manager/dist/package/detector.js
|
|
241
|
+
import { exec } from "child_process";
|
|
242
|
+
import { promisify } from "util";
|
|
243
|
+
var execAsync = promisify(exec);
|
|
244
|
+
async function detectPackageManager() {
|
|
245
|
+
try {
|
|
246
|
+
await execAsync("bun --version");
|
|
247
|
+
return "bun";
|
|
248
|
+
} catch {
|
|
249
|
+
try {
|
|
250
|
+
await execAsync("npm --version");
|
|
251
|
+
return "npm";
|
|
252
|
+
} catch {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// node_modules/@atomic-ehr/fhir-canonical-manager/dist/package/installer.js
|
|
258
|
+
import * as fs2 from "fs/promises";
|
|
259
|
+
import * as path2 from "path";
|
|
260
|
+
import { exec as exec2 } from "child_process";
|
|
261
|
+
import { promisify as promisify2 } from "util";
|
|
262
|
+
var execAsync2 = promisify2(exec2);
|
|
263
|
+
var installPackages = async (packages, workingDir, registry) => {
|
|
264
|
+
await ensureDir(workingDir);
|
|
265
|
+
const packageJsonPath = path2.join(workingDir, "package.json");
|
|
266
|
+
if (!await fileExists(packageJsonPath)) {
|
|
267
|
+
const minimalPackageJson = {
|
|
268
|
+
name: "fhir-canonical-manager-workspace",
|
|
269
|
+
version: "1.0.0",
|
|
270
|
+
private: true,
|
|
271
|
+
dependencies: {}
|
|
272
|
+
};
|
|
273
|
+
await fs2.writeFile(packageJsonPath, JSON.stringify(minimalPackageJson, null, 2));
|
|
274
|
+
}
|
|
275
|
+
const packageManager = await detectPackageManager();
|
|
276
|
+
if (!packageManager) {
|
|
277
|
+
throw new Error("No package manager found. Please install npm or bun.");
|
|
278
|
+
}
|
|
279
|
+
for (const pkg of packages) {
|
|
280
|
+
try {
|
|
281
|
+
if (packageManager === "bun") {
|
|
282
|
+
const env = {
|
|
283
|
+
...process.env,
|
|
284
|
+
HOME: workingDir,
|
|
285
|
+
NPM_CONFIG_USERCONFIG: "/dev/null"
|
|
286
|
+
};
|
|
287
|
+
const cmd = registry ? `cd ${workingDir} && bun add ${pkg} --registry ${registry}` : `cd ${workingDir} && bun add ${pkg}`;
|
|
288
|
+
await execAsync2(cmd, {
|
|
289
|
+
env,
|
|
290
|
+
maxBuffer: 10 * 1024 * 1024
|
|
291
|
+
});
|
|
292
|
+
} else {
|
|
293
|
+
const cmd = registry ? `cd ${workingDir} && npm add ${pkg} --registry ${registry}` : `cd ${workingDir} && npm add ${pkg}`;
|
|
294
|
+
await execAsync2(cmd, {
|
|
295
|
+
maxBuffer: 10 * 1024 * 1024
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
} catch (err) {
|
|
299
|
+
console.error(`Failed to install package ${pkg}:`, err);
|
|
300
|
+
throw err;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
// node_modules/@atomic-ehr/fhir-canonical-manager/dist/cache/core.js
|
|
305
|
+
var createCache = () => {
|
|
306
|
+
const referenceManager = createReferenceManager();
|
|
307
|
+
return {
|
|
308
|
+
entries: {},
|
|
309
|
+
packages: {},
|
|
310
|
+
references: {},
|
|
311
|
+
referenceManager
|
|
312
|
+
};
|
|
313
|
+
};
|
|
314
|
+
// node_modules/@atomic-ehr/fhir-canonical-manager/dist/cache/persistence.js
|
|
315
|
+
import * as fs4 from "fs/promises";
|
|
316
|
+
import * as path4 from "path";
|
|
317
|
+
|
|
318
|
+
// node_modules/@atomic-ehr/fhir-canonical-manager/dist/cache/validation.js
|
|
319
|
+
import * as fs3 from "fs/promises";
|
|
320
|
+
import * as path3 from "path";
|
|
321
|
+
import { createHash as createHash2 } from "crypto";
|
|
322
|
+
var computePackageLockHash = async (workingDir) => {
|
|
323
|
+
try {
|
|
324
|
+
const packageLockPath = path3.join(workingDir, "package-lock.json");
|
|
325
|
+
try {
|
|
326
|
+
const content = await fs3.readFile(packageLockPath, "utf-8");
|
|
327
|
+
return createHash2("sha256").update(content).digest("hex");
|
|
328
|
+
} catch {
|
|
329
|
+
const bunLockPath = path3.join(workingDir, "bun.lock");
|
|
330
|
+
const content = await fs3.readFile(bunLockPath, "utf-8");
|
|
331
|
+
return createHash2("sha256").update(content).digest("hex");
|
|
332
|
+
}
|
|
333
|
+
} catch {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// node_modules/@atomic-ehr/fhir-canonical-manager/dist/cache/persistence.js
|
|
339
|
+
var saveCacheToDisk = async (cache, cacheDir, workingDir) => {
|
|
340
|
+
const packageLockHash = await computePackageLockHash(workingDir);
|
|
341
|
+
const cacheData = {
|
|
342
|
+
entries: cache.entries,
|
|
343
|
+
packages: cache.packages,
|
|
344
|
+
references: cache.referenceManager.getAllReferences(),
|
|
345
|
+
packageLockHash: packageLockHash || undefined
|
|
346
|
+
};
|
|
347
|
+
const cachePath = path4.join(cacheDir, "index.json");
|
|
348
|
+
await fs4.writeFile(cachePath, JSON.stringify(cacheData, null, 2));
|
|
349
|
+
};
|
|
350
|
+
var loadCacheFromDisk = async (cacheDir) => {
|
|
351
|
+
try {
|
|
352
|
+
const cachePath = path4.join(cacheDir, "index.json");
|
|
353
|
+
const content = await fs4.readFile(cachePath, "utf-8");
|
|
354
|
+
return JSON.parse(content);
|
|
355
|
+
} catch {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
// node_modules/@atomic-ehr/fhir-canonical-manager/dist/scanner/parser.js
|
|
360
|
+
var isValidFileEntry = (entry) => {
|
|
361
|
+
if (!entry || typeof entry !== "object")
|
|
362
|
+
return false;
|
|
363
|
+
if (!entry.filename || typeof entry.filename !== "string")
|
|
364
|
+
return false;
|
|
365
|
+
if (!entry.resourceType || typeof entry.resourceType !== "string")
|
|
366
|
+
return false;
|
|
367
|
+
if (!entry.id || typeof entry.id !== "string")
|
|
368
|
+
return false;
|
|
369
|
+
const optionalStringFields = ["url", "version", "kind", "type"];
|
|
370
|
+
for (const field of optionalStringFields) {
|
|
371
|
+
if (entry[field] !== undefined && typeof entry[field] !== "string") {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return true;
|
|
376
|
+
};
|
|
377
|
+
var isValidIndexFile = (data) => {
|
|
378
|
+
if (!data || typeof data !== "object")
|
|
379
|
+
return false;
|
|
380
|
+
if (!data["index-version"] || typeof data["index-version"] !== "number")
|
|
381
|
+
return false;
|
|
382
|
+
if (!Array.isArray(data.files))
|
|
383
|
+
return false;
|
|
384
|
+
return data.files.every((file) => isValidFileEntry(file));
|
|
385
|
+
};
|
|
386
|
+
var parseIndex = (content, filePath) => {
|
|
387
|
+
try {
|
|
388
|
+
const data = JSON.parse(content);
|
|
389
|
+
if (!isValidIndexFile(data)) {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
return data;
|
|
393
|
+
} catch {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
// node_modules/@atomic-ehr/fhir-canonical-manager/dist/scanner/processor.js
|
|
398
|
+
import * as fs5 from "fs/promises";
|
|
399
|
+
import * as path5 from "path";
|
|
400
|
+
var processIndex = async (basePath, packageJson, cache) => {
|
|
401
|
+
const indexPath = path5.join(basePath, ".index.json");
|
|
402
|
+
try {
|
|
403
|
+
const indexContent = await fs5.readFile(indexPath, "utf-8");
|
|
404
|
+
const index = parseIndex(indexContent, indexPath);
|
|
405
|
+
if (!index)
|
|
406
|
+
return;
|
|
407
|
+
for (const file of index.files) {
|
|
408
|
+
if (!file.url)
|
|
409
|
+
continue;
|
|
410
|
+
const filePath = path5.join(basePath, file.filename);
|
|
411
|
+
const id = cache.referenceManager.generateId({
|
|
412
|
+
packageName: packageJson.name,
|
|
413
|
+
packageVersion: packageJson.version,
|
|
414
|
+
filePath
|
|
415
|
+
});
|
|
416
|
+
cache.referenceManager.set(id, {
|
|
417
|
+
packageName: packageJson.name,
|
|
418
|
+
packageVersion: packageJson.version,
|
|
419
|
+
filePath,
|
|
420
|
+
resourceType: file.resourceType,
|
|
421
|
+
url: file.url,
|
|
422
|
+
version: file.version
|
|
423
|
+
});
|
|
424
|
+
const entry = {
|
|
425
|
+
id,
|
|
426
|
+
resourceType: file.resourceType,
|
|
427
|
+
indexVersion: index["index-version"],
|
|
428
|
+
url: file.url,
|
|
429
|
+
version: file.version,
|
|
430
|
+
kind: file.kind,
|
|
431
|
+
type: file.type,
|
|
432
|
+
package: {
|
|
433
|
+
name: packageJson.name,
|
|
434
|
+
version: packageJson.version
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
if (!cache.entries[file.url]) {
|
|
438
|
+
cache.entries[file.url] = [];
|
|
439
|
+
}
|
|
440
|
+
const entries = cache.entries[file.url];
|
|
441
|
+
if (entries) {
|
|
442
|
+
entries.push(entry);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
} catch {}
|
|
446
|
+
};
|
|
447
|
+
// node_modules/@atomic-ehr/fhir-canonical-manager/dist/scanner/package.js
|
|
448
|
+
import * as fs6 from "fs/promises";
|
|
449
|
+
import * as path6 from "path";
|
|
450
|
+
var scanPackage = async (packagePath, cache) => {
|
|
451
|
+
try {
|
|
452
|
+
const packageJsonPath = path6.join(packagePath, "package.json");
|
|
453
|
+
const packageJsonContent = await fs6.readFile(packageJsonPath, "utf-8");
|
|
454
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
455
|
+
const packageInfo = {
|
|
456
|
+
id: { name: packageJson.name, version: packageJson.version },
|
|
457
|
+
path: packagePath,
|
|
458
|
+
canonical: packageJson.canonical,
|
|
459
|
+
fhirVersions: packageJson.fhirVersions
|
|
460
|
+
};
|
|
461
|
+
cache.packages[packageJson.name] = packageInfo;
|
|
462
|
+
await processIndex(packagePath, packageJson, cache);
|
|
463
|
+
const examplesPath = path6.join(packagePath, "examples");
|
|
464
|
+
if (await fileExists(path6.join(examplesPath, ".index.json"))) {
|
|
465
|
+
await processIndex(examplesPath, packageJson, cache);
|
|
466
|
+
}
|
|
467
|
+
} catch {}
|
|
468
|
+
};
|
|
469
|
+
// node_modules/@atomic-ehr/fhir-canonical-manager/dist/scanner/directory.js
|
|
470
|
+
import * as fs7 from "fs/promises";
|
|
471
|
+
import * as path7 from "path";
|
|
472
|
+
var scanDirectory = async (dirPath, cache) => {
|
|
473
|
+
try {
|
|
474
|
+
const entries = await fs7.readdir(dirPath, { withFileTypes: true });
|
|
475
|
+
for (const entry of entries) {
|
|
476
|
+
if (!entry.isDirectory())
|
|
477
|
+
continue;
|
|
478
|
+
const fullPath = path7.join(dirPath, entry.name);
|
|
479
|
+
if (entry.name.startsWith("@")) {
|
|
480
|
+
const scopedEntries = await fs7.readdir(fullPath, {
|
|
481
|
+
withFileTypes: true
|
|
482
|
+
});
|
|
483
|
+
for (const scopedEntry of scopedEntries) {
|
|
484
|
+
if (!scopedEntry.isDirectory())
|
|
485
|
+
continue;
|
|
486
|
+
const scopedPath = path7.join(fullPath, scopedEntry.name);
|
|
487
|
+
if (await isFhirPackage(scopedPath)) {
|
|
488
|
+
await scanPackage(scopedPath, cache);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
} else if (await isFhirPackage(fullPath)) {
|
|
492
|
+
await scanPackage(fullPath, cache);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
} catch {}
|
|
496
|
+
};
|
|
497
|
+
// node_modules/@atomic-ehr/fhir-canonical-manager/dist/resolver/context.js
|
|
498
|
+
var resolveWithContext = async (url, context, cache, resolveEntry) => {
|
|
499
|
+
if (context.package) {
|
|
500
|
+
try {
|
|
501
|
+
return await resolveEntry(url, {
|
|
502
|
+
package: context.package.name,
|
|
503
|
+
version: context.package.version
|
|
504
|
+
});
|
|
505
|
+
} catch {}
|
|
506
|
+
}
|
|
507
|
+
return null;
|
|
508
|
+
};
|
|
509
|
+
// node_modules/@atomic-ehr/fhir-canonical-manager/dist/search/terms.js
|
|
510
|
+
var expandedTerms = {
|
|
511
|
+
str: ["structure"],
|
|
512
|
+
struct: ["structure"],
|
|
513
|
+
def: ["definition"],
|
|
514
|
+
pati: ["patient"],
|
|
515
|
+
obs: ["observation"],
|
|
516
|
+
org: ["organization"],
|
|
517
|
+
pract: ["practitioner"],
|
|
518
|
+
med: ["medication", "medicinal"],
|
|
519
|
+
req: ["request"],
|
|
520
|
+
resp: ["response"],
|
|
521
|
+
ref: ["reference"],
|
|
522
|
+
val: ["value"],
|
|
523
|
+
code: ["codesystem", "code"],
|
|
524
|
+
cs: ["codesystem"],
|
|
525
|
+
vs: ["valueset"],
|
|
526
|
+
sd: ["structuredefinition"]
|
|
527
|
+
};
|
|
528
|
+
// node_modules/@atomic-ehr/fhir-canonical-manager/dist/search/smart.js
|
|
529
|
+
var filterBySmartSearch = (results, searchTerms) => {
|
|
530
|
+
if (searchTerms.length === 0) {
|
|
531
|
+
return results;
|
|
532
|
+
}
|
|
533
|
+
const terms = searchTerms.map((t) => t.toLowerCase());
|
|
534
|
+
return results.filter((entry) => {
|
|
535
|
+
if (!entry.url)
|
|
536
|
+
return false;
|
|
537
|
+
const urlLower = entry.url.toLowerCase();
|
|
538
|
+
const fullText = [
|
|
539
|
+
urlLower,
|
|
540
|
+
entry.type?.toLowerCase() || "",
|
|
541
|
+
entry.resourceType?.toLowerCase() || ""
|
|
542
|
+
].join(" ");
|
|
543
|
+
return terms.every((term) => {
|
|
544
|
+
const allParts = fullText.split(/[\/\-_\.\s]+/);
|
|
545
|
+
const directMatch = allParts.some((part) => part.startsWith(term));
|
|
546
|
+
if (directMatch)
|
|
547
|
+
return true;
|
|
548
|
+
const expansions = expandedTerms[term] || [];
|
|
549
|
+
for (const expansion of expansions) {
|
|
550
|
+
if (allParts.some((part) => part.startsWith(expansion))) {
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return fullText.includes(term);
|
|
555
|
+
});
|
|
556
|
+
});
|
|
557
|
+
};
|
|
558
|
+
// node_modules/@atomic-ehr/fhir-canonical-manager/dist/manager/canonical.js
|
|
559
|
+
var createCanonicalManager = (config) => {
|
|
560
|
+
const { packages, workingDir } = config;
|
|
561
|
+
const registry = config.registry ? config.registry.endsWith("/") ? config.registry : `${config.registry}/` : DEFAULT_REGISTRY;
|
|
562
|
+
const nodeModulesPath = path8.join(workingDir, "node_modules");
|
|
563
|
+
const cacheDir = path8.join(workingDir, ".fcm", "cache");
|
|
564
|
+
let cache = createCache();
|
|
565
|
+
let initialized = false;
|
|
566
|
+
const searchParamsCache = new Map;
|
|
567
|
+
const ensureInitialized = () => {
|
|
568
|
+
if (!initialized) {
|
|
569
|
+
throw new Error("CanonicalManager not initialized. Call init() first.");
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
const init = async () => {
|
|
573
|
+
if (initialized)
|
|
574
|
+
return;
|
|
575
|
+
await ensureDir(workingDir);
|
|
576
|
+
await ensureDir(cacheDir);
|
|
577
|
+
const currentPackageLockHash = await computePackageLockHash(workingDir);
|
|
578
|
+
const cachedData = await loadCacheFromDisk(cacheDir);
|
|
579
|
+
const cacheValid = cachedData && cachedData.packageLockHash === currentPackageLockHash && currentPackageLockHash !== null;
|
|
580
|
+
if (cacheValid) {
|
|
581
|
+
cache.entries = cachedData.entries;
|
|
582
|
+
cache.packages = cachedData.packages;
|
|
583
|
+
Object.entries(cachedData.references).forEach(([id, metadata]) => {
|
|
584
|
+
cache.referenceManager.set(id, metadata);
|
|
585
|
+
});
|
|
586
|
+
} else {
|
|
587
|
+
if (cachedData && cachedData.packageLockHash !== currentPackageLockHash) {
|
|
588
|
+
console.log("Package dependencies have changed, rebuilding index...");
|
|
589
|
+
}
|
|
590
|
+
await installPackages(packages, workingDir, registry);
|
|
591
|
+
cache = createCache();
|
|
592
|
+
await scanDirectory(nodeModulesPath, cache);
|
|
593
|
+
await saveCacheToDisk(cache, cacheDir, workingDir);
|
|
594
|
+
}
|
|
595
|
+
initialized = true;
|
|
596
|
+
};
|
|
597
|
+
const destroy = async () => {
|
|
598
|
+
cache.entries = {};
|
|
599
|
+
cache.packages = {};
|
|
600
|
+
cache.referenceManager.clear();
|
|
601
|
+
searchParamsCache.clear();
|
|
602
|
+
initialized = false;
|
|
603
|
+
};
|
|
604
|
+
const getPackages = async () => {
|
|
605
|
+
ensureInitialized();
|
|
606
|
+
return Object.values(cache.packages).map((p) => p.id);
|
|
607
|
+
};
|
|
608
|
+
const resolveEntry = async (canonicalUrl, options) => {
|
|
609
|
+
ensureInitialized();
|
|
610
|
+
if (options?.sourceContext) {
|
|
611
|
+
const contextResolved = await resolveWithContext(canonicalUrl, options.sourceContext, cache, resolveEntry);
|
|
612
|
+
if (contextResolved) {
|
|
613
|
+
return contextResolved;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
const entries = cache.entries[canonicalUrl] || [];
|
|
617
|
+
if (entries.length === 0) {
|
|
618
|
+
throw new Error(`Cannot resolve canonical URL: ${canonicalUrl}`);
|
|
619
|
+
}
|
|
620
|
+
let filtered = [...entries];
|
|
621
|
+
if (options?.package) {
|
|
622
|
+
filtered = filtered.filter((e) => e.package?.name === options.package);
|
|
623
|
+
}
|
|
624
|
+
if (options?.version) {
|
|
625
|
+
filtered = filtered.filter((e) => e.version === options.version);
|
|
626
|
+
}
|
|
627
|
+
if (filtered.length === 0) {
|
|
628
|
+
throw new Error(`No matching resource found for ${canonicalUrl} with given options`);
|
|
629
|
+
}
|
|
630
|
+
return filtered[0];
|
|
631
|
+
};
|
|
632
|
+
const resolve = async (canonicalUrl, options) => {
|
|
633
|
+
const entry = await resolveEntry(canonicalUrl, options);
|
|
634
|
+
return read(entry);
|
|
635
|
+
};
|
|
636
|
+
const read = async (reference) => {
|
|
637
|
+
ensureInitialized();
|
|
638
|
+
const metadata = cache.referenceManager.get(reference.id);
|
|
639
|
+
if (!metadata) {
|
|
640
|
+
throw new Error(`Invalid reference ID: ${reference.id}`);
|
|
641
|
+
}
|
|
642
|
+
try {
|
|
643
|
+
const content = await fs8.readFile(metadata.filePath, "utf-8");
|
|
644
|
+
const resource = JSON.parse(content);
|
|
645
|
+
return {
|
|
646
|
+
...resource,
|
|
647
|
+
id: reference.id,
|
|
648
|
+
resourceType: reference.resourceType
|
|
649
|
+
};
|
|
650
|
+
} catch (err) {
|
|
651
|
+
throw new Error(`Failed to read resource: ${err}`);
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
const searchEntries = async (params) => {
|
|
655
|
+
ensureInitialized();
|
|
656
|
+
let results = [];
|
|
657
|
+
if (params.url) {
|
|
658
|
+
results = cache.entries[params.url] || [];
|
|
659
|
+
} else {
|
|
660
|
+
for (const entries of Object.values(cache.entries)) {
|
|
661
|
+
results.push(...entries);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (params.kind !== undefined) {
|
|
665
|
+
results = results.filter((e) => e.kind === params.kind);
|
|
666
|
+
}
|
|
667
|
+
if (params.type !== undefined) {
|
|
668
|
+
results = results.filter((e) => e.type === params.type);
|
|
669
|
+
}
|
|
670
|
+
if (params.version !== undefined) {
|
|
671
|
+
results = results.filter((e) => e.version === params.version);
|
|
672
|
+
}
|
|
673
|
+
if (params.package) {
|
|
674
|
+
const pkg = params.package;
|
|
675
|
+
results = results.filter((e) => e.package?.name === pkg.name && e.package?.version === pkg.version);
|
|
676
|
+
}
|
|
677
|
+
return results;
|
|
678
|
+
};
|
|
679
|
+
const search = async (params) => {
|
|
680
|
+
const entries = await searchEntries(params);
|
|
681
|
+
const resources = await Promise.all(entries.map((entry) => read(entry)));
|
|
682
|
+
return resources;
|
|
683
|
+
};
|
|
684
|
+
const smartSearch = async (searchTerms, filters) => {
|
|
685
|
+
ensureInitialized();
|
|
686
|
+
let results = await searchEntries({
|
|
687
|
+
kind: filters?.kind,
|
|
688
|
+
package: filters?.package
|
|
689
|
+
});
|
|
690
|
+
if (filters?.resourceType) {
|
|
691
|
+
results = results.filter((entry) => entry.resourceType === filters.resourceType);
|
|
692
|
+
}
|
|
693
|
+
if (filters?.type) {
|
|
694
|
+
results = results.filter((entry) => entry.type === filters.type);
|
|
695
|
+
}
|
|
696
|
+
return filterBySmartSearch(results, searchTerms);
|
|
697
|
+
};
|
|
698
|
+
const getSearchParametersForResource = async (resourceType) => {
|
|
699
|
+
ensureInitialized();
|
|
700
|
+
if (searchParamsCache.has(resourceType)) {
|
|
701
|
+
return searchParamsCache.get(resourceType);
|
|
702
|
+
}
|
|
703
|
+
const allEntries = await searchEntries({});
|
|
704
|
+
const searchParamEntries = allEntries.filter((entry) => entry.resourceType === "SearchParameter");
|
|
705
|
+
const results = [];
|
|
706
|
+
for (const entry of searchParamEntries) {
|
|
707
|
+
const resource = await read(entry);
|
|
708
|
+
const bases = resource.base || [];
|
|
709
|
+
if (Array.isArray(bases) && bases.includes(resourceType)) {
|
|
710
|
+
results.push(resource);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
results.sort((a, b) => {
|
|
714
|
+
const codeA = a.code || "";
|
|
715
|
+
const codeB = b.code || "";
|
|
716
|
+
return codeA.localeCompare(codeB);
|
|
717
|
+
});
|
|
718
|
+
searchParamsCache.set(resourceType, results);
|
|
719
|
+
return results;
|
|
720
|
+
};
|
|
721
|
+
return {
|
|
722
|
+
init,
|
|
723
|
+
destroy,
|
|
724
|
+
packages: getPackages,
|
|
725
|
+
resolveEntry,
|
|
726
|
+
resolve,
|
|
727
|
+
read,
|
|
728
|
+
searchEntries,
|
|
729
|
+
search,
|
|
730
|
+
smartSearch,
|
|
731
|
+
getSearchParametersForResource
|
|
732
|
+
};
|
|
733
|
+
};
|
|
734
|
+
// node_modules/@atomic-ehr/fhirschema/dist/converter/path-parser.js
|
|
735
|
+
function parsePath(element) {
|
|
736
|
+
const pathParts = element.path.split(".");
|
|
737
|
+
const relevantParts = pathParts.slice(1);
|
|
738
|
+
const path9 = relevantParts.map((part) => ({ el: part }));
|
|
739
|
+
if (path9.length === 0) {
|
|
740
|
+
return [];
|
|
741
|
+
}
|
|
742
|
+
const lastIndex = path9.length - 1;
|
|
743
|
+
const pathItem = {};
|
|
744
|
+
if (element.slicing) {
|
|
745
|
+
pathItem.slicing = {
|
|
746
|
+
...element.slicing
|
|
747
|
+
};
|
|
748
|
+
if (element.min !== undefined) {
|
|
749
|
+
pathItem.slicing.min = element.min;
|
|
750
|
+
}
|
|
751
|
+
if (element.max && element.max !== "*") {
|
|
752
|
+
pathItem.slicing.max = parseInt(element.max);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
if (element.sliceName) {
|
|
756
|
+
pathItem.slice = {};
|
|
757
|
+
if (element.min !== undefined) {
|
|
758
|
+
pathItem.slice.min = element.min;
|
|
759
|
+
}
|
|
760
|
+
if (element.max && element.max !== "*") {
|
|
761
|
+
pathItem.slice.max = parseInt(element.max);
|
|
762
|
+
}
|
|
763
|
+
pathItem.sliceName = element.sliceName;
|
|
764
|
+
}
|
|
765
|
+
path9[lastIndex] = { ...path9[lastIndex], ...pathItem };
|
|
766
|
+
return path9;
|
|
767
|
+
}
|
|
768
|
+
function getCommonPath(path1, path22) {
|
|
769
|
+
const common = [];
|
|
770
|
+
const minLength = Math.min(path1.length, path22.length);
|
|
771
|
+
for (let i = 0;i < minLength; i++) {
|
|
772
|
+
if (path1[i].el === path22[i].el) {
|
|
773
|
+
common.push({ el: path1[i].el });
|
|
774
|
+
} else {
|
|
775
|
+
break;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
return common;
|
|
779
|
+
}
|
|
780
|
+
function enrichPath(prevPath, newPath) {
|
|
781
|
+
const enriched = [];
|
|
782
|
+
for (let i = 0;i < newPath.length; i++) {
|
|
783
|
+
if (i < prevPath.length && prevPath[i].el === newPath[i].el) {
|
|
784
|
+
enriched.push({
|
|
785
|
+
...prevPath[i],
|
|
786
|
+
...newPath[i],
|
|
787
|
+
...prevPath[i].slicing && !newPath[i].slicing && { slicing: prevPath[i].slicing }
|
|
788
|
+
});
|
|
789
|
+
} else {
|
|
790
|
+
enriched.push(newPath[i]);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return enriched;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// node_modules/@atomic-ehr/fhirschema/dist/converter/action-calculator.js
|
|
797
|
+
function sliceChanged(prevItem, newItem) {
|
|
798
|
+
return !!(prevItem.sliceName && newItem.sliceName && prevItem.sliceName !== newItem.sliceName);
|
|
799
|
+
}
|
|
800
|
+
function exitSliceAction(item) {
|
|
801
|
+
return {
|
|
802
|
+
type: "exit-slice",
|
|
803
|
+
sliceName: item.sliceName,
|
|
804
|
+
...item.slicing && { slicing: item.slicing },
|
|
805
|
+
...item.slice && { slice: item.slice }
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
function calculateExits(prevLength, commonLength, prevPath, newPath) {
|
|
809
|
+
const exits = [];
|
|
810
|
+
for (let i = prevLength - 1;i >= commonLength; i--) {
|
|
811
|
+
const prevItem = prevPath[i];
|
|
812
|
+
if (prevItem.sliceName) {
|
|
813
|
+
exits.push(exitSliceAction(prevItem));
|
|
814
|
+
}
|
|
815
|
+
exits.push({ type: "exit", el: prevItem.el });
|
|
816
|
+
}
|
|
817
|
+
if (commonLength > 0) {
|
|
818
|
+
const prevItem = prevPath[commonLength - 1];
|
|
819
|
+
const newItem = newPath[commonLength - 1];
|
|
820
|
+
if (sliceChanged(prevItem, newItem)) {
|
|
821
|
+
exits.push(exitSliceAction(prevItem));
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
return exits;
|
|
825
|
+
}
|
|
826
|
+
function calculateEnters(commonLength, newLength, prevPath, newPath) {
|
|
827
|
+
const enters = [];
|
|
828
|
+
if (commonLength > 0 && commonLength <= newLength) {
|
|
829
|
+
const prevItem = prevPath[commonLength - 1] || {};
|
|
830
|
+
const newItem = newPath[commonLength - 1];
|
|
831
|
+
if (newItem.sliceName && (!prevItem.sliceName || prevItem.sliceName !== newItem.sliceName)) {
|
|
832
|
+
enters.push({ type: "enter-slice", sliceName: newItem.sliceName });
|
|
833
|
+
}
|
|
834
|
+
if (commonLength === newLength) {
|
|
835
|
+
return enters;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
for (let i = commonLength;i < newLength; i++) {
|
|
839
|
+
const newItem = newPath[i];
|
|
840
|
+
enters.push({ type: "enter", el: newItem.el });
|
|
841
|
+
if (newItem.sliceName) {
|
|
842
|
+
enters.push({ type: "enter-slice", sliceName: newItem.sliceName });
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return enters;
|
|
846
|
+
}
|
|
847
|
+
function calculateActions(prevPath, newPath) {
|
|
848
|
+
const prevLength = prevPath.length;
|
|
849
|
+
const newLength = newPath.length;
|
|
850
|
+
const commonPath = getCommonPath(prevPath, newPath);
|
|
851
|
+
const commonLength = commonPath.length;
|
|
852
|
+
const exits = calculateExits(prevLength, commonLength, prevPath, newPath);
|
|
853
|
+
const enters = calculateEnters(commonLength, newLength, prevPath, newPath);
|
|
854
|
+
return [...exits, ...enters];
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// node_modules/@atomic-ehr/fhirschema/dist/converter/element-transformer.js
|
|
858
|
+
var BINDING_NAME_EXT = "http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName";
|
|
859
|
+
var DEFAULT_TYPE_EXT = "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype";
|
|
860
|
+
var FHIR_TYPE_EXT = "http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type";
|
|
861
|
+
function getExtension(extensions, url) {
|
|
862
|
+
if (!extensions)
|
|
863
|
+
return;
|
|
864
|
+
return extensions.find((ext) => ext.url === url);
|
|
865
|
+
}
|
|
866
|
+
function patternTypeNormalize(typeName) {
|
|
867
|
+
const typeMap = {
|
|
868
|
+
Instant: "instant",
|
|
869
|
+
Time: "time",
|
|
870
|
+
Date: "date",
|
|
871
|
+
DateTime: "dateTime",
|
|
872
|
+
Decimal: "decimal",
|
|
873
|
+
Boolean: "boolean",
|
|
874
|
+
Integer: "integer",
|
|
875
|
+
String: "string",
|
|
876
|
+
Uri: "uri",
|
|
877
|
+
Base64Binary: "base64Binary",
|
|
878
|
+
Code: "code",
|
|
879
|
+
Id: "id",
|
|
880
|
+
Oid: "oid",
|
|
881
|
+
UnsignedInt: "unsignedInt",
|
|
882
|
+
PositiveInt: "positiveInt",
|
|
883
|
+
Markdown: "markdown",
|
|
884
|
+
Url: "url",
|
|
885
|
+
Canonical: "canonical",
|
|
886
|
+
Uuid: "uuid"
|
|
887
|
+
};
|
|
888
|
+
return typeMap[typeName] || typeName;
|
|
889
|
+
}
|
|
890
|
+
function processPatterns(element) {
|
|
891
|
+
const result = {};
|
|
892
|
+
for (const [key, value] of Object.entries(element)) {
|
|
893
|
+
if (typeof key === "string" && key.startsWith("pattern")) {
|
|
894
|
+
const type = patternTypeNormalize(key.replace(/^pattern/, ""));
|
|
895
|
+
result.pattern = { type, value };
|
|
896
|
+
} else if (typeof key === "string" && key.startsWith("fixed")) {
|
|
897
|
+
const type = patternTypeNormalize(key.replace(/^fixed/, ""));
|
|
898
|
+
result.pattern = { type, value };
|
|
899
|
+
} else {
|
|
900
|
+
result[key] = value;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
if (result.pattern?.type && !result.type) {
|
|
904
|
+
result.type = result.pattern.type;
|
|
905
|
+
}
|
|
906
|
+
return result;
|
|
907
|
+
}
|
|
908
|
+
function buildReferenceTargets(types2) {
|
|
909
|
+
const refers = [];
|
|
910
|
+
for (const type of types2) {
|
|
911
|
+
if (type.targetProfile) {
|
|
912
|
+
const profiles = Array.isArray(type.targetProfile) ? type.targetProfile : [type.targetProfile];
|
|
913
|
+
refers.push(...profiles);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
return refers.length > 0 ? [...new Set(refers)].sort() : undefined;
|
|
917
|
+
}
|
|
918
|
+
function preprocessElement(element) {
|
|
919
|
+
if (!element.type || element.type.length === 0) {
|
|
920
|
+
return element;
|
|
921
|
+
}
|
|
922
|
+
const firstType = element.type[0];
|
|
923
|
+
if (firstType.code === "Reference") {
|
|
924
|
+
const refers = buildReferenceTargets(element.type);
|
|
925
|
+
return {
|
|
926
|
+
...element,
|
|
927
|
+
type: [{ code: "Reference" }],
|
|
928
|
+
...refers && { refers }
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
return element;
|
|
932
|
+
}
|
|
933
|
+
function buildElementBinding(element, structureDefinition) {
|
|
934
|
+
const normalizeBinding = (binding2) => {
|
|
935
|
+
const result = {
|
|
936
|
+
strength: binding2.strength,
|
|
937
|
+
...binding2.valueSet && { valueSet: binding2.valueSet }
|
|
938
|
+
};
|
|
939
|
+
const bindingNameExt = getExtension(binding2.extension, BINDING_NAME_EXT);
|
|
940
|
+
if (bindingNameExt?.valueString) {
|
|
941
|
+
result.bindingName = bindingNameExt.valueString;
|
|
942
|
+
}
|
|
943
|
+
return result;
|
|
944
|
+
};
|
|
945
|
+
if (element.choices) {
|
|
946
|
+
const { binding: binding2, ...rest2 } = element;
|
|
947
|
+
return rest2;
|
|
948
|
+
}
|
|
949
|
+
if (element.choiceOf && structureDefinition.snapshot) {
|
|
950
|
+
const declPath = `${structureDefinition.id}.${element.choiceOf}[x]`;
|
|
951
|
+
const decl = structureDefinition.snapshot.element.find((e) => e.path === declPath);
|
|
952
|
+
if (decl?.binding) {
|
|
953
|
+
return { ...element, binding: normalizeBinding(decl.binding) };
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
if (element.binding?.valueSet) {
|
|
957
|
+
return { ...element, binding: normalizeBinding(element.binding) };
|
|
958
|
+
}
|
|
959
|
+
const { binding, ...rest } = element;
|
|
960
|
+
return rest;
|
|
961
|
+
}
|
|
962
|
+
function buildElementConstraints(element) {
|
|
963
|
+
if (!element.constraint || element.constraint.length === 0) {
|
|
964
|
+
return element;
|
|
965
|
+
}
|
|
966
|
+
const constraints = {};
|
|
967
|
+
for (const constraint of element.constraint) {
|
|
968
|
+
constraints[constraint.key] = {
|
|
969
|
+
expression: constraint.expression,
|
|
970
|
+
human: constraint.human,
|
|
971
|
+
severity: constraint.severity
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
return { ...element, constraint: constraints };
|
|
975
|
+
}
|
|
976
|
+
function buildElementType(element, structureDefinition) {
|
|
977
|
+
if (!element.type || element.type.length === 0) {
|
|
978
|
+
return element;
|
|
979
|
+
}
|
|
980
|
+
const typeFromExt = element.type[0]?.extension?.[0];
|
|
981
|
+
if (typeFromExt?.url === FHIR_TYPE_EXT && typeFromExt.valueUrl) {
|
|
982
|
+
return { ...element, type: typeFromExt.valueUrl };
|
|
983
|
+
}
|
|
984
|
+
const typeCode = element.type[0].code;
|
|
985
|
+
const result = { ...element, type: typeCode };
|
|
986
|
+
if (structureDefinition.kind === "logical") {
|
|
987
|
+
const defaultTypeExt = getExtension(element.extension, DEFAULT_TYPE_EXT);
|
|
988
|
+
if (defaultTypeExt?.valueCanonical) {
|
|
989
|
+
result.defaultType = defaultTypeExt.valueCanonical;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
return result;
|
|
993
|
+
}
|
|
994
|
+
function buildElementExtension(element) {
|
|
995
|
+
const type = element.type?.[0]?.code;
|
|
996
|
+
if (type !== "Extension") {
|
|
997
|
+
return element;
|
|
998
|
+
}
|
|
999
|
+
const extUrl = element.type[0]?.profile?.[0];
|
|
1000
|
+
if (!extUrl) {
|
|
1001
|
+
return element;
|
|
1002
|
+
}
|
|
1003
|
+
return {
|
|
1004
|
+
...element,
|
|
1005
|
+
url: extUrl,
|
|
1006
|
+
...element.min && { min: element.min },
|
|
1007
|
+
...element.max && element.max !== "*" && { max: parseInt(element.max) }
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
function buildElementCardinality(element) {
|
|
1011
|
+
if (element.url) {
|
|
1012
|
+
return element;
|
|
1013
|
+
}
|
|
1014
|
+
const isArray = element.max === "*" || element.min && element.min >= 2 || element.max && parseInt(element.max) >= 2;
|
|
1015
|
+
const isRequired = element.min === 1;
|
|
1016
|
+
let result = { ...element };
|
|
1017
|
+
delete result.min;
|
|
1018
|
+
delete result.max;
|
|
1019
|
+
if (isArray) {
|
|
1020
|
+
result.array = true;
|
|
1021
|
+
if (element.min && element.min > 0) {
|
|
1022
|
+
result.min = element.min;
|
|
1023
|
+
}
|
|
1024
|
+
if (element.max && element.max !== "*") {
|
|
1025
|
+
result.max = parseInt(element.max);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
if (isRequired) {
|
|
1029
|
+
result._required = true;
|
|
1030
|
+
}
|
|
1031
|
+
return result;
|
|
1032
|
+
}
|
|
1033
|
+
function contentReferenceToElementReference(ref, structureDefinition) {
|
|
1034
|
+
const pathParts = ref.substring(1).split(".");
|
|
1035
|
+
const result = [structureDefinition.url];
|
|
1036
|
+
for (const part of pathParts.slice(1)) {
|
|
1037
|
+
result.push("elements", part);
|
|
1038
|
+
}
|
|
1039
|
+
return result;
|
|
1040
|
+
}
|
|
1041
|
+
function buildElementContentReference(element, structureDefinition) {
|
|
1042
|
+
if (!element.contentReference) {
|
|
1043
|
+
return element;
|
|
1044
|
+
}
|
|
1045
|
+
const { contentReference, ...rest } = element;
|
|
1046
|
+
return {
|
|
1047
|
+
...rest,
|
|
1048
|
+
elementReference: contentReferenceToElementReference(contentReference, structureDefinition)
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
function clearElement(element) {
|
|
1052
|
+
const { path: path9, slicing, sliceName, id, mapping, example, alias, condition, comment, definition, requirements, extension, ...rest } = element;
|
|
1053
|
+
return rest;
|
|
1054
|
+
}
|
|
1055
|
+
function transformElement(element, structureDefinition) {
|
|
1056
|
+
let transformed = preprocessElement(element);
|
|
1057
|
+
transformed = clearElement(transformed);
|
|
1058
|
+
transformed = buildElementBinding(transformed, structureDefinition);
|
|
1059
|
+
transformed = buildElementConstraints(transformed);
|
|
1060
|
+
transformed = buildElementContentReference(transformed, structureDefinition);
|
|
1061
|
+
transformed = buildElementExtension(transformed);
|
|
1062
|
+
transformed = buildElementCardinality(transformed);
|
|
1063
|
+
transformed = buildElementType(transformed, structureDefinition);
|
|
1064
|
+
transformed = processPatterns(transformed);
|
|
1065
|
+
return transformed;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// node_modules/@atomic-ehr/fhirschema/dist/converter/choice-handler.js
|
|
1069
|
+
function isChoiceElement(element) {
|
|
1070
|
+
if (element.path.endsWith("[x]")) {
|
|
1071
|
+
return true;
|
|
1072
|
+
}
|
|
1073
|
+
if (element.type && element.type.length > 1) {
|
|
1074
|
+
const uniqueCodes = new Set(element.type.map((t) => t.code));
|
|
1075
|
+
return uniqueCodes.size > 1;
|
|
1076
|
+
}
|
|
1077
|
+
return false;
|
|
1078
|
+
}
|
|
1079
|
+
function capitalize(str) {
|
|
1080
|
+
if (!str)
|
|
1081
|
+
return str;
|
|
1082
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
1083
|
+
}
|
|
1084
|
+
function canonicalToName(url) {
|
|
1085
|
+
const parts = url.split("/");
|
|
1086
|
+
return parts[parts.length - 1];
|
|
1087
|
+
}
|
|
1088
|
+
function expandChoiceElement(element) {
|
|
1089
|
+
const basePath = element.path.replace(/\[x\]$/, "");
|
|
1090
|
+
const fieldName = basePath.split(".").pop() || "";
|
|
1091
|
+
if (!element.type) {
|
|
1092
|
+
return [];
|
|
1093
|
+
}
|
|
1094
|
+
const expanded = [];
|
|
1095
|
+
const choices = element.type.map((t) => fieldName + capitalize(canonicalToName(t.code)));
|
|
1096
|
+
const { type, binding, ...restElement } = element;
|
|
1097
|
+
const parentElement = {
|
|
1098
|
+
...restElement,
|
|
1099
|
+
path: basePath,
|
|
1100
|
+
choices
|
|
1101
|
+
};
|
|
1102
|
+
expanded.push(parentElement);
|
|
1103
|
+
for (const type2 of element.type) {
|
|
1104
|
+
const typeName = capitalize(canonicalToName(type2.code));
|
|
1105
|
+
const typedElement = {
|
|
1106
|
+
...element,
|
|
1107
|
+
path: basePath + typeName,
|
|
1108
|
+
type: [type2],
|
|
1109
|
+
choiceOf: fieldName
|
|
1110
|
+
};
|
|
1111
|
+
if (element.binding) {
|
|
1112
|
+
delete typedElement.binding;
|
|
1113
|
+
}
|
|
1114
|
+
expanded.push(typedElement);
|
|
1115
|
+
}
|
|
1116
|
+
return expanded;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// node_modules/@atomic-ehr/fhirschema/dist/converter/stack-processor.js
|
|
1120
|
+
function popAndUpdate(stack, updateFn) {
|
|
1121
|
+
const child = stack[stack.length - 1];
|
|
1122
|
+
const newStack = stack.slice(0, -1);
|
|
1123
|
+
const parent = newStack[newStack.length - 1];
|
|
1124
|
+
const updatedParent = updateFn(parent, child);
|
|
1125
|
+
return [...newStack.slice(0, -1), updatedParent];
|
|
1126
|
+
}
|
|
1127
|
+
function buildMatchForSlice(slicing, sliceSchema) {
|
|
1128
|
+
if (!slicing.discriminator) {
|
|
1129
|
+
return {};
|
|
1130
|
+
}
|
|
1131
|
+
const match = {};
|
|
1132
|
+
for (const discriminator of slicing.discriminator) {
|
|
1133
|
+
if (!discriminator.type || !["pattern", "value", undefined].includes(discriminator.type)) {
|
|
1134
|
+
continue;
|
|
1135
|
+
}
|
|
1136
|
+
const path9 = discriminator.path.trim();
|
|
1137
|
+
if (path9 === "$this") {
|
|
1138
|
+
if (sliceSchema.pattern?.value) {
|
|
1139
|
+
Object.assign(match, sliceSchema.pattern.value);
|
|
1140
|
+
}
|
|
1141
|
+
} else {
|
|
1142
|
+
const pathParts = path9.split(".").filter((p) => p);
|
|
1143
|
+
const patternKeys = Object.keys(sliceSchema).filter((k) => k.startsWith("pattern"));
|
|
1144
|
+
for (const patternKey of patternKeys) {
|
|
1145
|
+
const fieldName = patternKey.replace("pattern", "").toLowerCase();
|
|
1146
|
+
if (pathParts.length === 1 && pathParts[0] === fieldName) {
|
|
1147
|
+
match[fieldName] = sliceSchema[patternKey];
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
let currentPath = ["elements"];
|
|
1151
|
+
for (const part of pathParts) {
|
|
1152
|
+
currentPath.push(part);
|
|
1153
|
+
if (pathParts.indexOf(part) < pathParts.length - 1) {
|
|
1154
|
+
currentPath.push("elements");
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
currentPath.push("pattern");
|
|
1158
|
+
let value = sliceSchema;
|
|
1159
|
+
for (const segment of currentPath) {
|
|
1160
|
+
value = value?.[segment];
|
|
1161
|
+
}
|
|
1162
|
+
if (value?.value !== undefined) {
|
|
1163
|
+
let matchTarget = match;
|
|
1164
|
+
for (let i = 0;i < pathParts.length - 1; i++) {
|
|
1165
|
+
if (!matchTarget[pathParts[i]]) {
|
|
1166
|
+
matchTarget[pathParts[i]] = {};
|
|
1167
|
+
}
|
|
1168
|
+
matchTarget = matchTarget[pathParts[i]];
|
|
1169
|
+
}
|
|
1170
|
+
matchTarget[pathParts[pathParts.length - 1]] = value.value;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
return match;
|
|
1175
|
+
}
|
|
1176
|
+
function buildSliceNode(sliceSchema, match, slice) {
|
|
1177
|
+
const node = {
|
|
1178
|
+
match,
|
|
1179
|
+
schema: sliceSchema
|
|
1180
|
+
};
|
|
1181
|
+
if (slice?.min !== undefined && slice.min !== 0) {
|
|
1182
|
+
node.min = slice.min;
|
|
1183
|
+
}
|
|
1184
|
+
if (slice?.max !== undefined) {
|
|
1185
|
+
node.max = slice.max;
|
|
1186
|
+
}
|
|
1187
|
+
return node;
|
|
1188
|
+
}
|
|
1189
|
+
function buildSlice(action, parent, sliceSchema) {
|
|
1190
|
+
const slicingInfo = parent.slicing || action.slicing || {};
|
|
1191
|
+
const match = buildMatchForSlice(slicingInfo, sliceSchema);
|
|
1192
|
+
const sliceNode = buildSliceNode(sliceSchema, match, action.slice);
|
|
1193
|
+
if (!parent.slicing) {
|
|
1194
|
+
parent.slicing = {};
|
|
1195
|
+
}
|
|
1196
|
+
if (action.slicing) {
|
|
1197
|
+
parent.slicing = { ...parent.slicing, ...action.slicing };
|
|
1198
|
+
}
|
|
1199
|
+
if (!parent.slicing.slices) {
|
|
1200
|
+
parent.slicing.slices = {};
|
|
1201
|
+
}
|
|
1202
|
+
parent.slicing.slices[action.sliceName] = sliceNode;
|
|
1203
|
+
return parent;
|
|
1204
|
+
}
|
|
1205
|
+
function slicingToExtensions(slicingElement) {
|
|
1206
|
+
if (!slicingElement.slicing?.slices) {
|
|
1207
|
+
return {};
|
|
1208
|
+
}
|
|
1209
|
+
const extensions = {};
|
|
1210
|
+
for (const [sliceName, slice] of Object.entries(slicingElement.slicing.slices)) {
|
|
1211
|
+
const { match, schema, ...sliceProps } = slice;
|
|
1212
|
+
const { slicing, elements, type, min, ...cleanSchema } = schema || {};
|
|
1213
|
+
const extension = {};
|
|
1214
|
+
if (match?.url) {
|
|
1215
|
+
extension.url = match.url;
|
|
1216
|
+
}
|
|
1217
|
+
if (sliceProps.min !== undefined && sliceProps.min !== 0) {
|
|
1218
|
+
extension.min = sliceProps.min;
|
|
1219
|
+
}
|
|
1220
|
+
if (sliceProps.max !== undefined) {
|
|
1221
|
+
extension.max = sliceProps.max;
|
|
1222
|
+
}
|
|
1223
|
+
Object.assign(extension, cleanSchema);
|
|
1224
|
+
if (min !== undefined && min !== 0 && extension.min === undefined) {
|
|
1225
|
+
extension.min = min;
|
|
1226
|
+
}
|
|
1227
|
+
extensions[sliceName] = extension;
|
|
1228
|
+
}
|
|
1229
|
+
return extensions;
|
|
1230
|
+
}
|
|
1231
|
+
function addElement(elementName, parent, child) {
|
|
1232
|
+
const updated = { ...parent };
|
|
1233
|
+
if (elementName === "extension") {
|
|
1234
|
+
updated.extensions = slicingToExtensions(child);
|
|
1235
|
+
}
|
|
1236
|
+
if (!updated.elements) {
|
|
1237
|
+
updated.elements = {};
|
|
1238
|
+
}
|
|
1239
|
+
const actualElementName = child.choiceOf || elementName;
|
|
1240
|
+
const { _required, ...cleanChild } = child;
|
|
1241
|
+
updated.elements[elementName] = cleanChild;
|
|
1242
|
+
if (_required) {
|
|
1243
|
+
if (!updated.required) {
|
|
1244
|
+
updated.required = [];
|
|
1245
|
+
}
|
|
1246
|
+
if (!updated.required.includes(actualElementName)) {
|
|
1247
|
+
updated.required.push(actualElementName);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
return updated;
|
|
1251
|
+
}
|
|
1252
|
+
function applyActions(stack, actions, value) {
|
|
1253
|
+
let currentStack = [...stack];
|
|
1254
|
+
for (let i = 0;i < actions.length; i++) {
|
|
1255
|
+
const action = actions[i];
|
|
1256
|
+
const nextAction = actions[i + 1];
|
|
1257
|
+
const valueToUse = nextAction?.type === "enter" ? {} : value;
|
|
1258
|
+
switch (action.type) {
|
|
1259
|
+
case "enter":
|
|
1260
|
+
currentStack.push(valueToUse);
|
|
1261
|
+
break;
|
|
1262
|
+
case "enter-slice":
|
|
1263
|
+
currentStack.push(valueToUse);
|
|
1264
|
+
break;
|
|
1265
|
+
case "exit":
|
|
1266
|
+
currentStack = popAndUpdate(currentStack, (parent, child) => addElement(action.el, parent, child));
|
|
1267
|
+
break;
|
|
1268
|
+
case "exit-slice":
|
|
1269
|
+
currentStack = popAndUpdate(currentStack, (parent, child) => buildSlice(action, parent, child));
|
|
1270
|
+
break;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
return currentStack;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
// node_modules/@atomic-ehr/fhirschema/dist/converter/index.js
|
|
1277
|
+
function buildResourceHeader(structureDefinition, context) {
|
|
1278
|
+
const header = {};
|
|
1279
|
+
header.name = structureDefinition.name;
|
|
1280
|
+
header.type = structureDefinition.type;
|
|
1281
|
+
if (structureDefinition.url)
|
|
1282
|
+
header.url = structureDefinition.url;
|
|
1283
|
+
if (structureDefinition.version)
|
|
1284
|
+
header.version = structureDefinition.version;
|
|
1285
|
+
if (structureDefinition.description)
|
|
1286
|
+
header.description = structureDefinition.description;
|
|
1287
|
+
if (structureDefinition.package_name)
|
|
1288
|
+
header.package_name = structureDefinition.package_name;
|
|
1289
|
+
if (structureDefinition.package_version)
|
|
1290
|
+
header.package_version = structureDefinition.package_version;
|
|
1291
|
+
if (structureDefinition.package_id)
|
|
1292
|
+
header.package_id = structureDefinition.package_id;
|
|
1293
|
+
header.kind = structureDefinition.kind;
|
|
1294
|
+
if (structureDefinition.derivation)
|
|
1295
|
+
header.derivation = structureDefinition.derivation;
|
|
1296
|
+
if (structureDefinition.baseDefinition && structureDefinition.type !== "Element") {
|
|
1297
|
+
header.base = structureDefinition.baseDefinition;
|
|
1298
|
+
}
|
|
1299
|
+
if (structureDefinition.abstract)
|
|
1300
|
+
header.abstract = structureDefinition.abstract;
|
|
1301
|
+
header.class = determineClass(structureDefinition);
|
|
1302
|
+
if (context?.package_meta)
|
|
1303
|
+
header.package_meta = context.package_meta;
|
|
1304
|
+
return header;
|
|
1305
|
+
}
|
|
1306
|
+
function determineClass(structureDefinition) {
|
|
1307
|
+
if (structureDefinition.kind === "resource" && structureDefinition.derivation === "constraint") {
|
|
1308
|
+
return "profile";
|
|
1309
|
+
}
|
|
1310
|
+
if (structureDefinition.type === "Extension") {
|
|
1311
|
+
return "extension";
|
|
1312
|
+
}
|
|
1313
|
+
return structureDefinition.kind || "unknown";
|
|
1314
|
+
}
|
|
1315
|
+
function getDifferential(structureDefinition) {
|
|
1316
|
+
const elements = structureDefinition.differential?.element || [];
|
|
1317
|
+
return elements.filter((e) => e.path.includes("."));
|
|
1318
|
+
}
|
|
1319
|
+
function sortElementsByIndex(elements) {
|
|
1320
|
+
const entries = Object.entries(elements);
|
|
1321
|
+
const sorted = entries.sort((a, b) => {
|
|
1322
|
+
const indexA = a[1].index ?? Infinity;
|
|
1323
|
+
const indexB = b[1].index ?? Infinity;
|
|
1324
|
+
return indexA - indexB;
|
|
1325
|
+
});
|
|
1326
|
+
const result = {};
|
|
1327
|
+
for (const [key, value] of sorted) {
|
|
1328
|
+
if (value.elements) {
|
|
1329
|
+
result[key] = {
|
|
1330
|
+
...value,
|
|
1331
|
+
elements: sortElementsByIndex(value.elements)
|
|
1332
|
+
};
|
|
1333
|
+
} else {
|
|
1334
|
+
result[key] = value;
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
return result;
|
|
1338
|
+
}
|
|
1339
|
+
function normalizeSchema(schema, visited = new WeakSet) {
|
|
1340
|
+
if (Array.isArray(schema)) {
|
|
1341
|
+
return schema.map((item) => normalizeSchema(item, visited));
|
|
1342
|
+
}
|
|
1343
|
+
if (schema && typeof schema === "object") {
|
|
1344
|
+
if (visited.has(schema)) {
|
|
1345
|
+
return "[Circular Reference]";
|
|
1346
|
+
}
|
|
1347
|
+
visited.add(schema);
|
|
1348
|
+
const normalized = {};
|
|
1349
|
+
const keys = Object.keys(schema);
|
|
1350
|
+
for (const key of keys) {
|
|
1351
|
+
if (key === "elements" && schema[key]) {
|
|
1352
|
+
normalized[key] = sortElementsByIndex(normalizeSchema(schema[key], visited));
|
|
1353
|
+
} else if (key === "required" && Array.isArray(schema[key])) {
|
|
1354
|
+
normalized[key] = [...schema[key]].sort();
|
|
1355
|
+
} else {
|
|
1356
|
+
normalized[key] = normalizeSchema(schema[key], visited);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
return normalized;
|
|
1360
|
+
}
|
|
1361
|
+
return schema;
|
|
1362
|
+
}
|
|
1363
|
+
function translate(structureDefinition, context) {
|
|
1364
|
+
if (structureDefinition.kind === "primitive-type") {
|
|
1365
|
+
return normalizeSchema(buildResourceHeader(structureDefinition, context));
|
|
1366
|
+
}
|
|
1367
|
+
const header = buildResourceHeader(structureDefinition, context);
|
|
1368
|
+
const elements = getDifferential(structureDefinition);
|
|
1369
|
+
let stack = [header];
|
|
1370
|
+
let prevPath = [];
|
|
1371
|
+
let elementQueue = [...elements];
|
|
1372
|
+
let index = 0;
|
|
1373
|
+
while (elementQueue.length > 0) {
|
|
1374
|
+
const element = elementQueue.shift();
|
|
1375
|
+
if (isChoiceElement(element)) {
|
|
1376
|
+
const expanded = expandChoiceElement(element);
|
|
1377
|
+
elementQueue.unshift(...expanded);
|
|
1378
|
+
index++;
|
|
1379
|
+
continue;
|
|
1380
|
+
}
|
|
1381
|
+
const parsedPath = parsePath(element);
|
|
1382
|
+
const enrichedPath = enrichPath(prevPath, parsedPath);
|
|
1383
|
+
const actions = calculateActions(prevPath, enrichedPath);
|
|
1384
|
+
const transformedElement = transformElement(element, structureDefinition);
|
|
1385
|
+
const elementWithIndex = { ...transformedElement, index: index++ };
|
|
1386
|
+
stack = applyActions(stack, actions, elementWithIndex);
|
|
1387
|
+
prevPath = enrichedPath;
|
|
1388
|
+
}
|
|
1389
|
+
const finalActions = calculateActions(prevPath, []);
|
|
1390
|
+
stack = applyActions(stack, finalActions, { index });
|
|
1391
|
+
if (stack.length !== 1) {
|
|
1392
|
+
throw new Error(`Invalid stack state: expected 1 element, got ${stack.length}`);
|
|
1393
|
+
}
|
|
1394
|
+
return normalizeSchema(stack[0]);
|
|
1395
|
+
}
|
|
1396
|
+
// src/logger.ts
|
|
1397
|
+
var LogLevel;
|
|
1398
|
+
((LogLevel2) => {
|
|
1399
|
+
LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
|
|
1400
|
+
LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
|
|
1401
|
+
LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
|
|
1402
|
+
LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
|
|
1403
|
+
LogLevel2[LogLevel2["SILENT"] = 4] = "SILENT";
|
|
1404
|
+
})(LogLevel ||= {});
|
|
1405
|
+
|
|
1406
|
+
class ConsoleOutput {
|
|
1407
|
+
useStderr;
|
|
1408
|
+
constructor(useStderr = false) {
|
|
1409
|
+
this.useStderr = useStderr;
|
|
1410
|
+
}
|
|
1411
|
+
write(entry, formatted) {
|
|
1412
|
+
const output = this.useStderr || entry.level >= 2 /* WARN */ ? console.error : console.log;
|
|
1413
|
+
output(formatted);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
class Logger {
|
|
1417
|
+
config;
|
|
1418
|
+
constructor(config = {}) {
|
|
1419
|
+
this.config = {
|
|
1420
|
+
level: 1 /* INFO */,
|
|
1421
|
+
format: "pretty",
|
|
1422
|
+
includeTimestamp: true,
|
|
1423
|
+
includeContext: true,
|
|
1424
|
+
colorize: true,
|
|
1425
|
+
outputs: [new ConsoleOutput],
|
|
1426
|
+
...config
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
configure(config) {
|
|
1430
|
+
this.config = { ...this.config, ...config };
|
|
1431
|
+
}
|
|
1432
|
+
shouldLog(level) {
|
|
1433
|
+
return level >= this.config.level;
|
|
1434
|
+
}
|
|
1435
|
+
createEntry(level, message, context, error, operation) {
|
|
1436
|
+
const entry = {
|
|
1437
|
+
timestamp: new Date().toISOString(),
|
|
1438
|
+
level,
|
|
1439
|
+
levelName: LogLevel[level],
|
|
1440
|
+
message,
|
|
1441
|
+
component: this.config.component
|
|
1442
|
+
};
|
|
1443
|
+
if (context && this.config.includeContext) {
|
|
1444
|
+
entry.context = context;
|
|
1445
|
+
}
|
|
1446
|
+
if (operation) {
|
|
1447
|
+
entry.operation = operation;
|
|
1448
|
+
}
|
|
1449
|
+
if (error) {
|
|
1450
|
+
entry.error = {
|
|
1451
|
+
name: error.name,
|
|
1452
|
+
message: error.message,
|
|
1453
|
+
stack: error.stack
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
return entry;
|
|
1457
|
+
}
|
|
1458
|
+
formatEntry(entry) {
|
|
1459
|
+
switch (this.config.format) {
|
|
1460
|
+
case "json":
|
|
1461
|
+
return JSON.stringify(entry);
|
|
1462
|
+
case "compact":
|
|
1463
|
+
return this.formatCompact(entry);
|
|
1464
|
+
default:
|
|
1465
|
+
return this.formatPretty(entry);
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
formatCompact(entry) {
|
|
1469
|
+
const timestamp = this.config.includeTimestamp ? `${entry.timestamp} ` : "";
|
|
1470
|
+
const component = entry.component ? `[${entry.component}] ` : "";
|
|
1471
|
+
const operation = entry.operation ? `(${entry.operation}) ` : "";
|
|
1472
|
+
const level = this.colorizeLevel(entry.levelName, entry.level);
|
|
1473
|
+
return `${timestamp}${level} ${component}${operation}${entry.message}`;
|
|
1474
|
+
}
|
|
1475
|
+
formatPretty(entry) {
|
|
1476
|
+
let formatted = "";
|
|
1477
|
+
const timestamp = this.config.includeTimestamp ? `${entry.timestamp} ` : "";
|
|
1478
|
+
const component = entry.component ? `[${entry.component}] ` : "";
|
|
1479
|
+
const operation = entry.operation ? `(${entry.operation}) ` : "";
|
|
1480
|
+
const level = this.colorizeLevel(entry.levelName.padEnd(5), entry.level);
|
|
1481
|
+
formatted += `${timestamp}${level} ${component}${operation}${entry.message}`;
|
|
1482
|
+
if (entry.context && Object.keys(entry.context).length > 0) {
|
|
1483
|
+
formatted += `
|
|
1484
|
+
Context: ${JSON.stringify(entry.context, null, 2).split(`
|
|
1485
|
+
`).join(`
|
|
1486
|
+
`)}`;
|
|
1487
|
+
}
|
|
1488
|
+
if (entry.error) {
|
|
1489
|
+
formatted += `
|
|
1490
|
+
Error: [${entry.error.code || entry.error.name}] ${entry.error.message}`;
|
|
1491
|
+
if (entry.error.context && Object.keys(entry.error.context).length > 0) {
|
|
1492
|
+
formatted += `
|
|
1493
|
+
Error Context: ${JSON.stringify(entry.error.context, null, 2).split(`
|
|
1494
|
+
`).join(`
|
|
1495
|
+
`)}`;
|
|
1496
|
+
}
|
|
1497
|
+
if (entry.error.suggestions && entry.error.suggestions.length > 0) {
|
|
1498
|
+
formatted += `
|
|
1499
|
+
Suggestions:
|
|
1500
|
+
${entry.error.suggestions.map((s) => ` • ${s}`).join(`
|
|
1501
|
+
`)}`;
|
|
1502
|
+
}
|
|
1503
|
+
if (entry.level === 0 /* DEBUG */ && entry.error.stack) {
|
|
1504
|
+
formatted += `
|
|
1505
|
+
Stack: ${entry.error.stack.split(`
|
|
1506
|
+
`).join(`
|
|
1507
|
+
`)}`;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
return formatted;
|
|
1511
|
+
}
|
|
1512
|
+
colorizeLevel(levelName, level) {
|
|
1513
|
+
if (!this.config.colorize) {
|
|
1514
|
+
return levelName;
|
|
1515
|
+
}
|
|
1516
|
+
const colors = {
|
|
1517
|
+
[0 /* DEBUG */]: "\x1B[36m",
|
|
1518
|
+
[1 /* INFO */]: "\x1B[32m",
|
|
1519
|
+
[2 /* WARN */]: "\x1B[33m",
|
|
1520
|
+
[3 /* ERROR */]: "\x1B[31m"
|
|
1521
|
+
};
|
|
1522
|
+
const reset = "\x1B[0m";
|
|
1523
|
+
const color = colors[level] || "";
|
|
1524
|
+
return `${color}${levelName}${reset}`;
|
|
1525
|
+
}
|
|
1526
|
+
async writeEntry(entry) {
|
|
1527
|
+
if (!this.shouldLog(entry.level)) {
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
const formatted = this.formatEntry(entry);
|
|
1531
|
+
for (const output of this.config.outputs) {
|
|
1532
|
+
try {
|
|
1533
|
+
await output.write(entry, formatted);
|
|
1534
|
+
} catch (error) {
|
|
1535
|
+
console.error("Logger output failed:", error);
|
|
1536
|
+
console.error(formatted);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
async debug(message, context, operation) {
|
|
1541
|
+
const entry = this.createEntry(0 /* DEBUG */, message, context, undefined, operation);
|
|
1542
|
+
await this.writeEntry(entry);
|
|
1543
|
+
}
|
|
1544
|
+
async info(message, context, operation) {
|
|
1545
|
+
const entry = this.createEntry(1 /* INFO */, message, context, undefined, operation);
|
|
1546
|
+
await this.writeEntry(entry);
|
|
1547
|
+
}
|
|
1548
|
+
async warn(message, context, operation) {
|
|
1549
|
+
const entry = this.createEntry(2 /* WARN */, message, context, undefined, operation);
|
|
1550
|
+
await this.writeEntry(entry);
|
|
1551
|
+
}
|
|
1552
|
+
async error(message, error, context, operation) {
|
|
1553
|
+
const entry = this.createEntry(3 /* ERROR */, message, context, error, operation);
|
|
1554
|
+
await this.writeEntry(entry);
|
|
1555
|
+
}
|
|
1556
|
+
child(component, context) {
|
|
1557
|
+
const childLogger = new Logger({
|
|
1558
|
+
...this.config,
|
|
1559
|
+
component: this.config.component ? `${this.config.component}.${component}` : component
|
|
1560
|
+
});
|
|
1561
|
+
if (context) {
|
|
1562
|
+
const originalMethods = {
|
|
1563
|
+
debug: childLogger.debug.bind(childLogger),
|
|
1564
|
+
info: childLogger.info.bind(childLogger),
|
|
1565
|
+
warn: childLogger.warn.bind(childLogger),
|
|
1566
|
+
error: childLogger.error.bind(childLogger)
|
|
1567
|
+
};
|
|
1568
|
+
childLogger.debug = (message, additionalContext, operation) => originalMethods.debug(message, { ...context, ...additionalContext }, operation);
|
|
1569
|
+
childLogger.info = (message, additionalContext, operation) => originalMethods.info(message, { ...context, ...additionalContext }, operation);
|
|
1570
|
+
childLogger.warn = (message, additionalContext, operation) => originalMethods.warn(message, { ...context, ...additionalContext }, operation);
|
|
1571
|
+
childLogger.error = (message, error, additionalContext, operation) => originalMethods.error(message, error, { ...context, ...additionalContext }, operation);
|
|
1572
|
+
}
|
|
1573
|
+
return childLogger;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
var logger = new Logger;
|
|
1577
|
+
|
|
1578
|
+
// src/typeschema/core/identifier.ts
|
|
1579
|
+
function dropVersionFromUrl(url) {
|
|
1580
|
+
if (!url)
|
|
1581
|
+
return;
|
|
1582
|
+
return url.split("|")[0];
|
|
1583
|
+
}
|
|
1584
|
+
function determineKind(fhirSchema) {
|
|
1585
|
+
if (fhirSchema.derivation === "constraint") {
|
|
1586
|
+
if (fhirSchema.base && (fhirSchema.type === "Resource" || fhirSchema.kind === "resource" || fhirSchema.kind === "complex-type")) {
|
|
1587
|
+
return "profile";
|
|
1588
|
+
}
|
|
1589
|
+
return "profile";
|
|
1590
|
+
}
|
|
1591
|
+
if (fhirSchema.kind) {
|
|
1592
|
+
switch (fhirSchema.kind) {
|
|
1593
|
+
case "primitive-type":
|
|
1594
|
+
return "primitive-type";
|
|
1595
|
+
case "complex-type":
|
|
1596
|
+
return "complex-type";
|
|
1597
|
+
case "resource":
|
|
1598
|
+
return "resource";
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
return "resource";
|
|
1602
|
+
}
|
|
1603
|
+
function buildSchemaIdentifier(fhirSchema, packageInfo) {
|
|
1604
|
+
const kind = determineKind(fhirSchema);
|
|
1605
|
+
return {
|
|
1606
|
+
kind,
|
|
1607
|
+
package: packageInfo?.name || fhirSchema.package_name || "undefined",
|
|
1608
|
+
version: packageInfo?.version || fhirSchema.package_version || "undefined",
|
|
1609
|
+
name: fhirSchema.name,
|
|
1610
|
+
url: fhirSchema.url
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
function buildNestedIdentifier(fhirSchema, path9, packageInfo) {
|
|
1614
|
+
const nestedName = path9.join(".");
|
|
1615
|
+
return {
|
|
1616
|
+
kind: "nested",
|
|
1617
|
+
package: packageInfo?.name || fhirSchema.package_name || "undefined",
|
|
1618
|
+
version: packageInfo?.version || fhirSchema.package_version || "undefined",
|
|
1619
|
+
name: nestedName,
|
|
1620
|
+
url: `${fhirSchema.url}#${nestedName}`
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
function buildValueSetIdentifier(valueSetUrl, valueSet, packageInfo) {
|
|
1624
|
+
const cleanUrl = dropVersionFromUrl(valueSetUrl) || valueSetUrl;
|
|
1625
|
+
let name = "unknown";
|
|
1626
|
+
const urlParts = cleanUrl.split("/");
|
|
1627
|
+
const lastSegment = urlParts[urlParts.length - 1];
|
|
1628
|
+
if (lastSegment && lastSegment.length > 0) {
|
|
1629
|
+
name = lastSegment.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
|
|
1630
|
+
}
|
|
1631
|
+
if (name === "unknown" && valueSet?.id && !/^[a-zA-Z0-9_-]{20,}$/.test(valueSet.id)) {
|
|
1632
|
+
name = valueSet.id;
|
|
1633
|
+
}
|
|
1634
|
+
return {
|
|
1635
|
+
kind: "value-set",
|
|
1636
|
+
package: packageInfo?.name || valueSet?.package_name || "undefined",
|
|
1637
|
+
version: packageInfo?.version || valueSet?.package_version || "undefined",
|
|
1638
|
+
name,
|
|
1639
|
+
url: cleanUrl
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
function buildBindingIdentifier(fhirSchema, path9, bindingName, packageInfo) {
|
|
1643
|
+
const pathStr = path9.join(".");
|
|
1644
|
+
const name = bindingName || `${fhirSchema.name}.${pathStr}_binding`;
|
|
1645
|
+
return {
|
|
1646
|
+
kind: "binding",
|
|
1647
|
+
package: packageInfo?.name || fhirSchema.package_name || "undefined",
|
|
1648
|
+
version: packageInfo?.version || fhirSchema.package_version || "undefined",
|
|
1649
|
+
name,
|
|
1650
|
+
url: bindingName ? `urn:fhir:binding:${name}` : `${fhirSchema.url}#${pathStr}_binding`
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// src/typeschema/profile/processor.ts
|
|
1655
|
+
async function transformProfile(fhirSchema, manager, packageInfo) {
|
|
1656
|
+
const identifier = buildSchemaIdentifier(fhirSchema, packageInfo);
|
|
1657
|
+
if (identifier.kind !== "profile") {
|
|
1658
|
+
throw new Error(`Expected profile, got ${identifier.kind} for ${fhirSchema.name}`);
|
|
1659
|
+
}
|
|
1660
|
+
let base;
|
|
1661
|
+
if (fhirSchema.base) {
|
|
1662
|
+
const baseUrl = fhirSchema.base.includes("/") ? fhirSchema.base : `http://hl7.org/fhir/StructureDefinition/${fhirSchema.base}`;
|
|
1663
|
+
const baseName = fhirSchema.base.split("/").pop() || fhirSchema.base;
|
|
1664
|
+
const baseKind = await determineBaseKind(baseUrl, manager);
|
|
1665
|
+
const isStandardFhir = baseUrl.startsWith("http://hl7.org/fhir/");
|
|
1666
|
+
base = {
|
|
1667
|
+
kind: baseKind,
|
|
1668
|
+
package: isStandardFhir ? "hl7.fhir.r4.core" : packageInfo?.name || fhirSchema.package_name || "undefined",
|
|
1669
|
+
version: isStandardFhir ? "4.0.1" : packageInfo?.version || fhirSchema.package_version || "undefined",
|
|
1670
|
+
name: baseName,
|
|
1671
|
+
url: baseUrl
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
const profileSchema = {
|
|
1675
|
+
identifier,
|
|
1676
|
+
base,
|
|
1677
|
+
dependencies: base ? [base] : []
|
|
1678
|
+
};
|
|
1679
|
+
if (fhirSchema.description) {
|
|
1680
|
+
profileSchema.description = fhirSchema.description;
|
|
1681
|
+
}
|
|
1682
|
+
const metadata = extractProfileMetadata(fhirSchema);
|
|
1683
|
+
if (Object.keys(metadata).length > 0) {
|
|
1684
|
+
profileSchema.metadata = metadata;
|
|
1685
|
+
}
|
|
1686
|
+
const constraints = await processProfileConstraints(fhirSchema, manager);
|
|
1687
|
+
if (Object.keys(constraints).length > 0) {
|
|
1688
|
+
profileSchema.constraints = constraints;
|
|
1689
|
+
}
|
|
1690
|
+
const extensions = await processProfileExtensions(fhirSchema, manager);
|
|
1691
|
+
if (extensions.length > 0) {
|
|
1692
|
+
profileSchema.extensions = extensions;
|
|
1693
|
+
}
|
|
1694
|
+
const validationRules = extractValidationRules(fhirSchema);
|
|
1695
|
+
if (validationRules.length > 0) {
|
|
1696
|
+
profileSchema.validation = validationRules;
|
|
1697
|
+
}
|
|
1698
|
+
return profileSchema;
|
|
1699
|
+
}
|
|
1700
|
+
async function determineBaseKind(baseUrl, manager) {
|
|
1701
|
+
try {
|
|
1702
|
+
const baseSchema = await manager.resolve(baseUrl);
|
|
1703
|
+
if (baseSchema) {
|
|
1704
|
+
if (baseSchema.derivation === "constraint") {
|
|
1705
|
+
return "profile";
|
|
1706
|
+
}
|
|
1707
|
+
if (baseSchema.kind === "resource")
|
|
1708
|
+
return "resource";
|
|
1709
|
+
if (baseSchema.kind === "complex-type")
|
|
1710
|
+
return "complex-type";
|
|
1711
|
+
}
|
|
1712
|
+
} catch (error) {
|
|
1713
|
+
console.warn(`Could not resolve base schema ${baseUrl}:`, error);
|
|
1714
|
+
}
|
|
1715
|
+
if (baseUrl.includes("/us/core/") || baseUrl.includes("StructureDefinition/us-core-")) {
|
|
1716
|
+
return "profile";
|
|
1717
|
+
}
|
|
1718
|
+
if (baseUrl.includes("StructureDefinition/") && !baseUrl.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
|
|
1719
|
+
return "profile";
|
|
1720
|
+
}
|
|
1721
|
+
return "resource";
|
|
1722
|
+
}
|
|
1723
|
+
function extractProfileMetadata(fhirSchema) {
|
|
1724
|
+
const metadata = {};
|
|
1725
|
+
if (fhirSchema.publisher)
|
|
1726
|
+
metadata.publisher = fhirSchema.publisher;
|
|
1727
|
+
if (fhirSchema.contact)
|
|
1728
|
+
metadata.contact = fhirSchema.contact;
|
|
1729
|
+
if (fhirSchema.copyright)
|
|
1730
|
+
metadata.copyright = fhirSchema.copyright;
|
|
1731
|
+
if (fhirSchema.purpose)
|
|
1732
|
+
metadata.purpose = fhirSchema.purpose;
|
|
1733
|
+
if (fhirSchema.experimental !== undefined) {
|
|
1734
|
+
metadata.experimental = fhirSchema.experimental;
|
|
1735
|
+
}
|
|
1736
|
+
if (fhirSchema.date)
|
|
1737
|
+
metadata.date = fhirSchema.date;
|
|
1738
|
+
if (fhirSchema.jurisdiction)
|
|
1739
|
+
metadata.jurisdiction = fhirSchema.jurisdiction;
|
|
1740
|
+
if (fhirSchema.url) {
|
|
1741
|
+
if (fhirSchema.url.includes("/us/core/")) {
|
|
1742
|
+
metadata.package = "hl7.fhir.us.core";
|
|
1743
|
+
} else if (fhirSchema.url.includes("hl7.org/fhir/")) {
|
|
1744
|
+
metadata.package = "hl7.fhir.r4.core";
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
return metadata;
|
|
1748
|
+
}
|
|
1749
|
+
async function processProfileConstraints(fhirSchema, _manager) {
|
|
1750
|
+
const constraints = {};
|
|
1751
|
+
if (!fhirSchema.elements)
|
|
1752
|
+
return constraints;
|
|
1753
|
+
for (const [path9, element] of Object.entries(fhirSchema.elements)) {
|
|
1754
|
+
const elementConstraints = {};
|
|
1755
|
+
if (element.min !== undefined)
|
|
1756
|
+
elementConstraints.min = element.min;
|
|
1757
|
+
if (element.max !== undefined)
|
|
1758
|
+
elementConstraints.max = element.max;
|
|
1759
|
+
if (element.mustSupport)
|
|
1760
|
+
elementConstraints.mustSupport = true;
|
|
1761
|
+
if (element.fixedValue !== undefined)
|
|
1762
|
+
elementConstraints.fixedValue = element.fixedValue;
|
|
1763
|
+
if (element.patternValue !== undefined)
|
|
1764
|
+
elementConstraints.patternValue = element.patternValue;
|
|
1765
|
+
if (element.binding) {
|
|
1766
|
+
elementConstraints.binding = {
|
|
1767
|
+
strength: element.binding.strength,
|
|
1768
|
+
valueSet: element.binding.valueSet
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
if (element.type && Array.isArray(element.type) && element.type.length > 0) {
|
|
1772
|
+
elementConstraints.types = element.type.map((t) => {
|
|
1773
|
+
const typeConstraint = { code: t.code };
|
|
1774
|
+
if (t.profile)
|
|
1775
|
+
typeConstraint.profile = t.profile;
|
|
1776
|
+
if (t.targetProfile)
|
|
1777
|
+
typeConstraint.targetProfile = t.targetProfile;
|
|
1778
|
+
return typeConstraint;
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
if (element.slicing) {
|
|
1782
|
+
elementConstraints.slicing = {
|
|
1783
|
+
discriminator: element.slicing.discriminator,
|
|
1784
|
+
rules: element.slicing.rules,
|
|
1785
|
+
ordered: element.slicing.ordered
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
if (Object.keys(elementConstraints).length > 0) {
|
|
1789
|
+
constraints[path9] = elementConstraints;
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
return constraints;
|
|
1793
|
+
}
|
|
1794
|
+
async function processProfileExtensions(fhirSchema, _manager) {
|
|
1795
|
+
const extensions = [];
|
|
1796
|
+
if (!fhirSchema.elements)
|
|
1797
|
+
return extensions;
|
|
1798
|
+
for (const [path9, element] of Object.entries(fhirSchema.elements)) {
|
|
1799
|
+
if (path9.includes("extension") && element.type && Array.isArray(element.type)) {
|
|
1800
|
+
for (const type of element.type) {
|
|
1801
|
+
if (type.code === "Extension" && type.profile) {
|
|
1802
|
+
extensions.push({
|
|
1803
|
+
path: path9,
|
|
1804
|
+
profile: type.profile,
|
|
1805
|
+
min: element.min,
|
|
1806
|
+
max: element.max,
|
|
1807
|
+
mustSupport: element.mustSupport
|
|
1808
|
+
});
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
return extensions;
|
|
1814
|
+
}
|
|
1815
|
+
function extractValidationRules(fhirSchema) {
|
|
1816
|
+
const rules = [];
|
|
1817
|
+
if (!fhirSchema.elements)
|
|
1818
|
+
return rules;
|
|
1819
|
+
for (const [path9, element] of Object.entries(fhirSchema.elements)) {
|
|
1820
|
+
if (element.constraint && Array.isArray(element.constraint)) {
|
|
1821
|
+
for (const constraint of element.constraint) {
|
|
1822
|
+
rules.push({
|
|
1823
|
+
path: path9,
|
|
1824
|
+
key: constraint.key,
|
|
1825
|
+
severity: constraint.severity,
|
|
1826
|
+
human: constraint.human,
|
|
1827
|
+
expression: constraint.expression
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
return rules;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
// src/typeschema/core/field-builder.ts
|
|
1836
|
+
function getElementHierarchy(fhirSchema, path9, _manager) {
|
|
1837
|
+
const hierarchy = [];
|
|
1838
|
+
let element = fhirSchema.elements;
|
|
1839
|
+
for (const key of path9) {
|
|
1840
|
+
element = element?.[key];
|
|
1841
|
+
if (!element)
|
|
1842
|
+
break;
|
|
1843
|
+
}
|
|
1844
|
+
if (element) {
|
|
1845
|
+
hierarchy.push(element);
|
|
1846
|
+
}
|
|
1847
|
+
return hierarchy;
|
|
1848
|
+
}
|
|
1849
|
+
function mergeElementHierarchy(hierarchy) {
|
|
1850
|
+
if (hierarchy.length === 0) {
|
|
1851
|
+
return {};
|
|
1852
|
+
}
|
|
1853
|
+
const snapshot = { ...hierarchy[0] };
|
|
1854
|
+
for (let i = hierarchy.length - 1;i >= 0; i--) {
|
|
1855
|
+
const element = hierarchy[i];
|
|
1856
|
+
const specificProps = [
|
|
1857
|
+
"choices",
|
|
1858
|
+
"short",
|
|
1859
|
+
"index",
|
|
1860
|
+
"elements",
|
|
1861
|
+
"required",
|
|
1862
|
+
"excluded",
|
|
1863
|
+
"binding",
|
|
1864
|
+
"refers",
|
|
1865
|
+
"elementReference",
|
|
1866
|
+
"mustSupport",
|
|
1867
|
+
"slices",
|
|
1868
|
+
"slicing",
|
|
1869
|
+
"url",
|
|
1870
|
+
"extensions"
|
|
1871
|
+
];
|
|
1872
|
+
for (const [key, value] of Object.entries(element)) {
|
|
1873
|
+
if (!specificProps.includes(key) && value !== undefined) {
|
|
1874
|
+
snapshot[key] = value;
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
for (const prop of ["choices", "binding", "refers", "elementReference"]) {
|
|
1879
|
+
if (hierarchy[0] && hierarchy[0][prop] !== undefined) {
|
|
1880
|
+
snapshot[prop] = hierarchy[0][prop];
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
return snapshot;
|
|
1884
|
+
}
|
|
1885
|
+
function isRequired(fhirSchema, path9, _manager) {
|
|
1886
|
+
if (path9.length === 0)
|
|
1887
|
+
return false;
|
|
1888
|
+
const fieldName = path9[path9.length - 1];
|
|
1889
|
+
const parentPath = path9.slice(0, -1);
|
|
1890
|
+
let parentElement = fhirSchema;
|
|
1891
|
+
for (const key of parentPath) {
|
|
1892
|
+
parentElement = parentElement.elements?.[key];
|
|
1893
|
+
if (!parentElement)
|
|
1894
|
+
break;
|
|
1895
|
+
}
|
|
1896
|
+
if (parentElement?.required?.includes(fieldName)) {
|
|
1897
|
+
return true;
|
|
1898
|
+
}
|
|
1899
|
+
if (parentPath.length === 0 && fieldName && fhirSchema.required?.includes(fieldName)) {
|
|
1900
|
+
return true;
|
|
1901
|
+
}
|
|
1902
|
+
return false;
|
|
1903
|
+
}
|
|
1904
|
+
function isExcluded(fhirSchema, path9, _manager) {
|
|
1905
|
+
if (path9.length === 0)
|
|
1906
|
+
return false;
|
|
1907
|
+
const fieldName = path9[path9.length - 1];
|
|
1908
|
+
const parentPath = path9.slice(0, -1);
|
|
1909
|
+
let parentElement = fhirSchema;
|
|
1910
|
+
for (const key of parentPath) {
|
|
1911
|
+
parentElement = parentElement.elements?.[key];
|
|
1912
|
+
if (!parentElement)
|
|
1913
|
+
break;
|
|
1914
|
+
}
|
|
1915
|
+
if (parentElement?.excluded?.includes(fieldName)) {
|
|
1916
|
+
return true;
|
|
1917
|
+
}
|
|
1918
|
+
if (parentPath.length === 0 && fieldName && fhirSchema.excluded?.includes(fieldName)) {
|
|
1919
|
+
return true;
|
|
1920
|
+
}
|
|
1921
|
+
return false;
|
|
1922
|
+
}
|
|
1923
|
+
async function buildReferences(element, manager, packageInfo) {
|
|
1924
|
+
if (!element.refers || element.refers.length === 0) {
|
|
1925
|
+
return;
|
|
1926
|
+
}
|
|
1927
|
+
const references = [];
|
|
1928
|
+
for (const ref of element.refers) {
|
|
1929
|
+
try {
|
|
1930
|
+
const resource = await manager.resolve(ref);
|
|
1931
|
+
if (resource) {
|
|
1932
|
+
references.push(buildSchemaIdentifier(resource, packageInfo));
|
|
1933
|
+
}
|
|
1934
|
+
} catch {}
|
|
1935
|
+
}
|
|
1936
|
+
return references;
|
|
1937
|
+
}
|
|
1938
|
+
function buildFieldType(fhirSchema, _path, element, _manager, packageInfo) {
|
|
1939
|
+
if (element.elementReference) {
|
|
1940
|
+
const refPath = element.elementReference.filter((_, i) => i % 2 === 1).map((p) => p).filter((p) => p !== "elements");
|
|
1941
|
+
if (refPath.length > 0) {
|
|
1942
|
+
return buildNestedIdentifier(fhirSchema, refPath, packageInfo);
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
if (element.type) {
|
|
1946
|
+
const typeUrl = element.type.includes("/") ? element.type : `http://hl7.org/fhir/StructureDefinition/${element.type}`;
|
|
1947
|
+
const kind = element.type.match(/^[a-z]/) ? "primitive-type" : "complex-type";
|
|
1948
|
+
const isStandardFhir = typeUrl.startsWith("http://hl7.org/fhir/");
|
|
1949
|
+
return {
|
|
1950
|
+
kind,
|
|
1951
|
+
package: isStandardFhir ? "hl7.fhir.r4.core" : packageInfo?.name || "undefined",
|
|
1952
|
+
version: isStandardFhir ? "4.0.1" : packageInfo?.version || "undefined",
|
|
1953
|
+
name: element.type,
|
|
1954
|
+
url: typeUrl
|
|
1955
|
+
};
|
|
1956
|
+
}
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1959
|
+
async function buildField(fhirSchema, path9, element, manager, packageInfo) {
|
|
1960
|
+
const field = {
|
|
1961
|
+
array: element.array || false,
|
|
1962
|
+
required: isRequired(fhirSchema, path9, manager),
|
|
1963
|
+
excluded: isExcluded(fhirSchema, path9, manager)
|
|
1964
|
+
};
|
|
1965
|
+
const type = buildFieldType(fhirSchema, path9, element, manager, packageInfo);
|
|
1966
|
+
if (type) {
|
|
1967
|
+
field.type = type;
|
|
1968
|
+
}
|
|
1969
|
+
if (element.min !== undefined)
|
|
1970
|
+
field.min = element.min;
|
|
1971
|
+
if (element.max !== undefined)
|
|
1972
|
+
field.max = element.max;
|
|
1973
|
+
if (element.choices)
|
|
1974
|
+
field.choices = element.choices;
|
|
1975
|
+
if (element.choiceOf)
|
|
1976
|
+
field.choiceOf = element.choiceOf;
|
|
1977
|
+
if (element.binding) {
|
|
1978
|
+
field.binding = buildBindingIdentifier(fhirSchema, path9, element.binding.bindingName, packageInfo);
|
|
1979
|
+
if (element.binding.strength === "required" && element.type === "code") {
|
|
1980
|
+
const enumValues = await buildEnum(element, manager);
|
|
1981
|
+
if (enumValues && enumValues.length > 0) {
|
|
1982
|
+
field.enum = enumValues;
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
const references = await buildReferences(element, manager, packageInfo);
|
|
1987
|
+
if (references) {
|
|
1988
|
+
field.reference = references;
|
|
1989
|
+
}
|
|
1990
|
+
return removeEmptyValues(field);
|
|
1991
|
+
}
|
|
1992
|
+
function removeEmptyValues(obj) {
|
|
1993
|
+
const result = {};
|
|
1994
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1995
|
+
if (value !== undefined && value !== null) {
|
|
1996
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
1997
|
+
continue;
|
|
1998
|
+
}
|
|
1999
|
+
result[key] = value;
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
return result;
|
|
2003
|
+
}
|
|
2004
|
+
function isNestedElement(element) {
|
|
2005
|
+
return element.type === "BackboneElement" || element.elements && Object.keys(element.elements).length > 0 || false;
|
|
2006
|
+
}
|
|
2007
|
+
function buildNestedField(fhirSchema, path9, element, manager, packageInfo) {
|
|
2008
|
+
return {
|
|
2009
|
+
type: buildNestedIdentifier(fhirSchema, path9, packageInfo),
|
|
2010
|
+
array: element.array || false,
|
|
2011
|
+
required: isRequired(fhirSchema, path9, manager),
|
|
2012
|
+
excluded: isExcluded(fhirSchema, path9, manager)
|
|
2013
|
+
};
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
// src/typeschema/core/binding.ts
|
|
2017
|
+
async function extractValueSetConcepts(valueSetUrl, manager) {
|
|
2018
|
+
try {
|
|
2019
|
+
const cleanUrl = dropVersionFromUrl(valueSetUrl) || valueSetUrl;
|
|
2020
|
+
const valueSet = await manager.resolve(cleanUrl);
|
|
2021
|
+
if (!valueSet)
|
|
2022
|
+
return;
|
|
2023
|
+
if (valueSet.expansion?.contains) {
|
|
2024
|
+
return valueSet.expansion.contains.map((concept) => ({
|
|
2025
|
+
system: concept.system,
|
|
2026
|
+
code: concept.code,
|
|
2027
|
+
display: concept.display
|
|
2028
|
+
}));
|
|
2029
|
+
}
|
|
2030
|
+
const concepts = [];
|
|
2031
|
+
if (valueSet.compose?.include) {
|
|
2032
|
+
for (const include of valueSet.compose.include) {
|
|
2033
|
+
if (include.concept) {
|
|
2034
|
+
for (const concept of include.concept) {
|
|
2035
|
+
concepts.push({
|
|
2036
|
+
system: include.system,
|
|
2037
|
+
code: concept.code,
|
|
2038
|
+
display: concept.display
|
|
2039
|
+
});
|
|
2040
|
+
}
|
|
2041
|
+
} else if (include.system && !include.filter) {
|
|
2042
|
+
try {
|
|
2043
|
+
const codeSystem = await manager.resolve(include.system);
|
|
2044
|
+
if (codeSystem?.concept) {
|
|
2045
|
+
const extractConcepts = (conceptList, system) => {
|
|
2046
|
+
for (const concept of conceptList) {
|
|
2047
|
+
concepts.push({
|
|
2048
|
+
system,
|
|
2049
|
+
code: concept.code,
|
|
2050
|
+
display: concept.display
|
|
2051
|
+
});
|
|
2052
|
+
if (concept.concept) {
|
|
2053
|
+
extractConcepts(concept.concept, system);
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
};
|
|
2057
|
+
extractConcepts(codeSystem.concept, include.system);
|
|
2058
|
+
}
|
|
2059
|
+
} catch {}
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
return concepts.length > 0 ? concepts : undefined;
|
|
2064
|
+
} catch {
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
async function buildEnum(element, manager) {
|
|
2069
|
+
if (!element.binding)
|
|
2070
|
+
return;
|
|
2071
|
+
const { strength, valueSet } = element.binding;
|
|
2072
|
+
if (!valueSet)
|
|
2073
|
+
return;
|
|
2074
|
+
const shouldGenerateEnum = strength === "required" || strength === "extensible" && element.type === "code" || strength === "preferred" && element.type === "code";
|
|
2075
|
+
if (!shouldGenerateEnum) {
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
if (!manager.resolve) {
|
|
2079
|
+
return;
|
|
2080
|
+
}
|
|
2081
|
+
try {
|
|
2082
|
+
const concepts = await extractValueSetConcepts(valueSet, manager);
|
|
2083
|
+
if (!concepts || concepts.length === 0)
|
|
2084
|
+
return;
|
|
2085
|
+
const codes = concepts.map((c) => c.code).filter((code) => code && typeof code === "string" && code.trim().length > 0);
|
|
2086
|
+
return codes.length > 0 && codes.length <= 50 ? codes : undefined;
|
|
2087
|
+
} catch (error) {
|
|
2088
|
+
console.debug(`Failed to extract enum values for ${valueSet}: ${error}`);
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
async function generateBindingSchema(fhirSchema, path9, element, manager, packageInfo) {
|
|
2093
|
+
if (!element.binding?.valueSet)
|
|
2094
|
+
return;
|
|
2095
|
+
const identifier = buildBindingIdentifier(fhirSchema, path9, element.binding.bindingName, packageInfo);
|
|
2096
|
+
const fieldType = buildFieldType(fhirSchema, path9, element, manager, packageInfo);
|
|
2097
|
+
const valueSetIdentifier = buildValueSetIdentifier(element.binding.valueSet, undefined, packageInfo);
|
|
2098
|
+
const binding = {
|
|
2099
|
+
identifier,
|
|
2100
|
+
type: fieldType,
|
|
2101
|
+
valueset: valueSetIdentifier,
|
|
2102
|
+
strength: element.binding.strength,
|
|
2103
|
+
dependencies: []
|
|
2104
|
+
};
|
|
2105
|
+
if (fieldType) {
|
|
2106
|
+
binding.dependencies.push(fieldType);
|
|
2107
|
+
}
|
|
2108
|
+
binding.dependencies.push(valueSetIdentifier);
|
|
2109
|
+
const enumValues = await buildEnum(element, manager);
|
|
2110
|
+
if (enumValues) {
|
|
2111
|
+
binding.enum = enumValues;
|
|
2112
|
+
}
|
|
2113
|
+
return binding;
|
|
2114
|
+
}
|
|
2115
|
+
async function collectBindingSchemas(fhirSchema, manager, packageInfo) {
|
|
2116
|
+
const bindings = [];
|
|
2117
|
+
const processedPaths = new Set;
|
|
2118
|
+
async function processElement(elements, parentPath) {
|
|
2119
|
+
for (const [key, element] of Object.entries(elements)) {
|
|
2120
|
+
const path9 = [...parentPath, key];
|
|
2121
|
+
const pathKey = path9.join(".");
|
|
2122
|
+
if (processedPaths.has(pathKey))
|
|
2123
|
+
continue;
|
|
2124
|
+
processedPaths.add(pathKey);
|
|
2125
|
+
if (element.binding) {
|
|
2126
|
+
const binding = await generateBindingSchema(fhirSchema, path9, element, manager, packageInfo);
|
|
2127
|
+
if (binding) {
|
|
2128
|
+
bindings.push(binding);
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
if (element.elements) {
|
|
2132
|
+
await processElement(element.elements, path9);
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
if (fhirSchema.elements) {
|
|
2137
|
+
await processElement(fhirSchema.elements, []);
|
|
2138
|
+
}
|
|
2139
|
+
bindings.sort((a, b) => a.identifier.name.localeCompare(b.identifier.name));
|
|
2140
|
+
const uniqueBindings = [];
|
|
2141
|
+
const seenUrls = new Set;
|
|
2142
|
+
for (const binding of bindings) {
|
|
2143
|
+
if (!seenUrls.has(binding.identifier.url)) {
|
|
2144
|
+
seenUrls.add(binding.identifier.url);
|
|
2145
|
+
uniqueBindings.push(binding);
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
return uniqueBindings;
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
// src/typeschema/core/nested-types.ts
|
|
2152
|
+
function collectNestedElements(fhirSchema, parentPath, elements) {
|
|
2153
|
+
const nested = [];
|
|
2154
|
+
for (const [key, element] of Object.entries(elements)) {
|
|
2155
|
+
const path9 = [...parentPath, key];
|
|
2156
|
+
if (isNestedElement(element)) {
|
|
2157
|
+
nested.push([path9, element]);
|
|
2158
|
+
}
|
|
2159
|
+
if (element.elements) {
|
|
2160
|
+
nested.push(...collectNestedElements(fhirSchema, path9, element.elements));
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
return nested;
|
|
2164
|
+
}
|
|
2165
|
+
async function transformNestedElements(fhirSchema, parentPath, elements, manager, packageInfo) {
|
|
2166
|
+
const fields = {};
|
|
2167
|
+
for (const [key, element] of Object.entries(elements)) {
|
|
2168
|
+
const path9 = [...parentPath, key];
|
|
2169
|
+
if (isNestedElement(element)) {
|
|
2170
|
+
fields[key] = buildNestedField(fhirSchema, path9, element, manager, packageInfo);
|
|
2171
|
+
} else {
|
|
2172
|
+
fields[key] = await buildField(fhirSchema, path9, element, manager, packageInfo);
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
return fields;
|
|
2176
|
+
}
|
|
2177
|
+
async function buildNestedTypes(fhirSchema, manager, packageInfo) {
|
|
2178
|
+
if (!fhirSchema.elements)
|
|
2179
|
+
return [];
|
|
2180
|
+
const nestedTypes = [];
|
|
2181
|
+
const nestedElements = collectNestedElements(fhirSchema, [], fhirSchema.elements);
|
|
2182
|
+
const actualNested = nestedElements.filter(([_, element]) => element.elements && Object.keys(element.elements).length > 0);
|
|
2183
|
+
for (const [path9, element] of actualNested) {
|
|
2184
|
+
const identifier = buildNestedIdentifier(fhirSchema, path9, packageInfo);
|
|
2185
|
+
let base;
|
|
2186
|
+
if (element.type === "BackboneElement" || !element.type) {
|
|
2187
|
+
base = {
|
|
2188
|
+
kind: "complex-type",
|
|
2189
|
+
package: packageInfo?.name || "hl7.fhir.r4.core",
|
|
2190
|
+
version: packageInfo?.version || "4.0.1",
|
|
2191
|
+
name: "BackboneElement",
|
|
2192
|
+
url: "http://hl7.org/fhir/StructureDefinition/BackboneElement"
|
|
2193
|
+
};
|
|
2194
|
+
} else {
|
|
2195
|
+
base = {
|
|
2196
|
+
kind: "complex-type",
|
|
2197
|
+
package: packageInfo?.name || "hl7.fhir.r4.core",
|
|
2198
|
+
version: packageInfo?.version || "4.0.1",
|
|
2199
|
+
name: element.type,
|
|
2200
|
+
url: `http://hl7.org/fhir/StructureDefinition/${element.type}`
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
const fields = await transformNestedElements(fhirSchema, path9, element.elements, manager, packageInfo);
|
|
2204
|
+
const nestedType = {
|
|
2205
|
+
identifier,
|
|
2206
|
+
base,
|
|
2207
|
+
fields
|
|
2208
|
+
};
|
|
2209
|
+
nestedTypes.push(nestedType);
|
|
2210
|
+
}
|
|
2211
|
+
nestedTypes.sort((a, b) => a.identifier.url.localeCompare(b.identifier.url));
|
|
2212
|
+
return nestedTypes;
|
|
2213
|
+
}
|
|
2214
|
+
function extractNestedDependencies(nestedTypes) {
|
|
2215
|
+
const deps = [];
|
|
2216
|
+
for (const nested of nestedTypes) {
|
|
2217
|
+
deps.push(nested.identifier);
|
|
2218
|
+
if (nested.base) {
|
|
2219
|
+
deps.push(nested.base);
|
|
2220
|
+
}
|
|
2221
|
+
for (const field of Object.values(nested.fields)) {
|
|
2222
|
+
if ("type" in field && field.type) {
|
|
2223
|
+
deps.push(field.type);
|
|
2224
|
+
}
|
|
2225
|
+
if ("binding" in field && field.binding) {
|
|
2226
|
+
deps.push(field.binding);
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
return deps;
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
// src/typeschema/core/transformer.ts
|
|
2234
|
+
async function transformElements(fhirSchema, parentPath, elements, manager, packageInfo) {
|
|
2235
|
+
const fields = {};
|
|
2236
|
+
for (const [key, element] of Object.entries(elements)) {
|
|
2237
|
+
const path9 = [...parentPath, key];
|
|
2238
|
+
const hierarchy = getElementHierarchy(fhirSchema, path9, manager);
|
|
2239
|
+
const snapshot = hierarchy.length > 0 ? mergeElementHierarchy(hierarchy) : element;
|
|
2240
|
+
if (isNestedElement(snapshot)) {
|
|
2241
|
+
fields[key] = buildNestedField(fhirSchema, path9, snapshot, manager, packageInfo);
|
|
2242
|
+
} else {
|
|
2243
|
+
fields[key] = await buildField(fhirSchema, path9, snapshot, manager, packageInfo);
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
return fields;
|
|
2247
|
+
}
|
|
2248
|
+
function extractFieldDependencies(fields) {
|
|
2249
|
+
const deps = [];
|
|
2250
|
+
for (const field of Object.values(fields)) {
|
|
2251
|
+
if ("type" in field && field.type) {
|
|
2252
|
+
deps.push(field.type);
|
|
2253
|
+
}
|
|
2254
|
+
if ("binding" in field && field.binding) {
|
|
2255
|
+
deps.push(field.binding);
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
return deps;
|
|
2259
|
+
}
|
|
2260
|
+
function deduplicateDependencies(deps) {
|
|
2261
|
+
const seen = new Set;
|
|
2262
|
+
const unique = [];
|
|
2263
|
+
for (const dep of deps) {
|
|
2264
|
+
const key = dep.url;
|
|
2265
|
+
if (!seen.has(key)) {
|
|
2266
|
+
seen.add(key);
|
|
2267
|
+
unique.push(dep);
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
unique.sort((a, b) => a.name.localeCompare(b.name));
|
|
2271
|
+
return unique;
|
|
2272
|
+
}
|
|
2273
|
+
function isExtensionSchema(fhirSchema, _identifier) {
|
|
2274
|
+
if (fhirSchema.base === "Extension" || fhirSchema.base === "http://hl7.org/fhir/StructureDefinition/Extension") {
|
|
2275
|
+
return true;
|
|
2276
|
+
}
|
|
2277
|
+
if (fhirSchema.url?.includes("/extension/") || fhirSchema.url?.includes("-extension")) {
|
|
2278
|
+
return true;
|
|
2279
|
+
}
|
|
2280
|
+
if (fhirSchema.name?.toLowerCase().includes("extension")) {
|
|
2281
|
+
return true;
|
|
2282
|
+
}
|
|
2283
|
+
if (fhirSchema.type === "Extension") {
|
|
2284
|
+
return true;
|
|
2285
|
+
}
|
|
2286
|
+
return false;
|
|
2287
|
+
}
|
|
2288
|
+
async function transformValueSet(fhirSchema, _manager, packageInfo) {
|
|
2289
|
+
try {
|
|
2290
|
+
const identifier = buildSchemaIdentifier(fhirSchema, packageInfo);
|
|
2291
|
+
identifier.kind = "value-set";
|
|
2292
|
+
const valueSetSchema = {
|
|
2293
|
+
identifier,
|
|
2294
|
+
description: fhirSchema.description
|
|
2295
|
+
};
|
|
2296
|
+
if (fhirSchema.elements) {
|
|
2297
|
+
const concepts = [];
|
|
2298
|
+
for (const [_key, element] of Object.entries(fhirSchema.elements)) {
|
|
2299
|
+
if ("code" in element && element.code) {
|
|
2300
|
+
concepts.push({
|
|
2301
|
+
code: element.code,
|
|
2302
|
+
display: element.short || element.definition,
|
|
2303
|
+
system: element.system
|
|
2304
|
+
});
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
if (concepts.length > 0) {
|
|
2308
|
+
valueSetSchema.concept = concepts;
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
return valueSetSchema;
|
|
2312
|
+
} catch (error) {
|
|
2313
|
+
console.warn(`Failed to transform value set ${fhirSchema.name}: ${error}`);
|
|
2314
|
+
return null;
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
async function transformExtension(fhirSchema, manager, packageInfo) {
|
|
2318
|
+
try {
|
|
2319
|
+
const identifier = buildSchemaIdentifier(fhirSchema, packageInfo);
|
|
2320
|
+
let base;
|
|
2321
|
+
if (fhirSchema.base && fhirSchema.base !== "Extension") {
|
|
2322
|
+
const baseUrl = fhirSchema.base.includes("/") ? fhirSchema.base : `http://hl7.org/fhir/StructureDefinition/${fhirSchema.base}`;
|
|
2323
|
+
const baseName = fhirSchema.base.split("/").pop() || fhirSchema.base;
|
|
2324
|
+
base = {
|
|
2325
|
+
kind: "complex-type",
|
|
2326
|
+
package: packageInfo?.name || "hl7.fhir.r4.core",
|
|
2327
|
+
version: packageInfo?.version || "4.0.1",
|
|
2328
|
+
name: baseName,
|
|
2329
|
+
url: baseUrl
|
|
2330
|
+
};
|
|
2331
|
+
} else {
|
|
2332
|
+
base = {
|
|
2333
|
+
kind: "complex-type",
|
|
2334
|
+
package: "hl7.fhir.r4.core",
|
|
2335
|
+
version: "4.0.1",
|
|
2336
|
+
name: "Extension",
|
|
2337
|
+
url: "http://hl7.org/fhir/StructureDefinition/Extension"
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2340
|
+
const extensionSchema = {
|
|
2341
|
+
identifier,
|
|
2342
|
+
base,
|
|
2343
|
+
description: fhirSchema.description,
|
|
2344
|
+
dependencies: [],
|
|
2345
|
+
metadata: {
|
|
2346
|
+
isExtension: true
|
|
2347
|
+
}
|
|
2348
|
+
};
|
|
2349
|
+
if (base) {
|
|
2350
|
+
extensionSchema.dependencies.push(base);
|
|
2351
|
+
}
|
|
2352
|
+
if (fhirSchema.elements) {
|
|
2353
|
+
const fields = await transformElements(fhirSchema, [], fhirSchema.elements, manager, packageInfo);
|
|
2354
|
+
if (Object.keys(fields).length > 0) {
|
|
2355
|
+
extensionSchema.fields = fields;
|
|
2356
|
+
extensionSchema.dependencies.push(...extractFieldDependencies(fields));
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
const nestedTypes = await buildNestedTypes(fhirSchema, manager, packageInfo);
|
|
2360
|
+
if (nestedTypes.length > 0) {
|
|
2361
|
+
extensionSchema.nested = nestedTypes;
|
|
2362
|
+
extensionSchema.dependencies.push(...extractNestedDependencies(nestedTypes));
|
|
2363
|
+
}
|
|
2364
|
+
extensionSchema.dependencies = deduplicateDependencies(extensionSchema.dependencies);
|
|
2365
|
+
extensionSchema.dependencies = extensionSchema.dependencies.filter((dep) => dep.url !== identifier.url);
|
|
2366
|
+
return extensionSchema;
|
|
2367
|
+
} catch (error) {
|
|
2368
|
+
console.warn(`Failed to transform extension ${fhirSchema.name}: ${error}`);
|
|
2369
|
+
return null;
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
async function transformFHIRSchema(fhirSchema, manager, packageInfo) {
|
|
2373
|
+
const results = [];
|
|
2374
|
+
if (!packageInfo && (fhirSchema.package_name || fhirSchema.package_id)) {
|
|
2375
|
+
packageInfo = {
|
|
2376
|
+
name: fhirSchema.package_name || fhirSchema.package_id || "undefined",
|
|
2377
|
+
version: fhirSchema.package_version || "undefined"
|
|
2378
|
+
};
|
|
2379
|
+
}
|
|
2380
|
+
const identifier = buildSchemaIdentifier(fhirSchema, packageInfo);
|
|
2381
|
+
if (identifier.kind === "profile") {
|
|
2382
|
+
const profileSchema = await transformProfile(fhirSchema, manager, packageInfo);
|
|
2383
|
+
results.push(profileSchema);
|
|
2384
|
+
const bindingSchemas2 = await collectBindingSchemas(fhirSchema, manager, packageInfo);
|
|
2385
|
+
results.push(...bindingSchemas2);
|
|
2386
|
+
return results;
|
|
2387
|
+
}
|
|
2388
|
+
if (identifier.kind === "value-set" || fhirSchema.kind === "value-set") {
|
|
2389
|
+
const valueSetSchema = await transformValueSet(fhirSchema, manager, packageInfo);
|
|
2390
|
+
if (valueSetSchema) {
|
|
2391
|
+
results.push(valueSetSchema);
|
|
2392
|
+
}
|
|
2393
|
+
return results;
|
|
2394
|
+
}
|
|
2395
|
+
if (isExtensionSchema(fhirSchema, identifier)) {
|
|
2396
|
+
const extensionSchema = await transformExtension(fhirSchema, manager, packageInfo);
|
|
2397
|
+
if (extensionSchema) {
|
|
2398
|
+
results.push(extensionSchema);
|
|
2399
|
+
}
|
|
2400
|
+
return results;
|
|
2401
|
+
}
|
|
2402
|
+
let base;
|
|
2403
|
+
if (fhirSchema.base && fhirSchema.type !== "Element") {
|
|
2404
|
+
const baseUrl = fhirSchema.base.includes("/") ? fhirSchema.base : `http://hl7.org/fhir/StructureDefinition/${fhirSchema.base}`;
|
|
2405
|
+
const baseName = fhirSchema.base.split("/").pop() || fhirSchema.base;
|
|
2406
|
+
const complexTypes = new Set([
|
|
2407
|
+
"Element",
|
|
2408
|
+
"BackboneElement",
|
|
2409
|
+
"Quantity",
|
|
2410
|
+
"Duration",
|
|
2411
|
+
"Distance",
|
|
2412
|
+
"Count",
|
|
2413
|
+
"Age",
|
|
2414
|
+
"Address",
|
|
2415
|
+
"Annotation",
|
|
2416
|
+
"Attachment",
|
|
2417
|
+
"CodeableConcept",
|
|
2418
|
+
"Coding",
|
|
2419
|
+
"ContactPoint",
|
|
2420
|
+
"HumanName",
|
|
2421
|
+
"Identifier",
|
|
2422
|
+
"Period",
|
|
2423
|
+
"Range",
|
|
2424
|
+
"Ratio",
|
|
2425
|
+
"Reference",
|
|
2426
|
+
"Timing",
|
|
2427
|
+
"Money",
|
|
2428
|
+
"SampledData",
|
|
2429
|
+
"Signature",
|
|
2430
|
+
"ContactDetail",
|
|
2431
|
+
"Contributor",
|
|
2432
|
+
"DataRequirement",
|
|
2433
|
+
"Expression",
|
|
2434
|
+
"ParameterDefinition",
|
|
2435
|
+
"RelatedArtifact",
|
|
2436
|
+
"TriggerDefinition",
|
|
2437
|
+
"UsageContext",
|
|
2438
|
+
"Dosage",
|
|
2439
|
+
"Meta",
|
|
2440
|
+
"Extension"
|
|
2441
|
+
]);
|
|
2442
|
+
const kind = complexTypes.has(baseName) ? "complex-type" : "resource";
|
|
2443
|
+
const isStandardFhir = baseUrl.startsWith("http://hl7.org/fhir/");
|
|
2444
|
+
base = {
|
|
2445
|
+
kind,
|
|
2446
|
+
package: isStandardFhir ? "hl7.fhir.r4.core" : packageInfo?.name || fhirSchema.package_name || "undefined",
|
|
2447
|
+
version: isStandardFhir ? "4.0.1" : packageInfo?.version || fhirSchema.package_version || "undefined",
|
|
2448
|
+
name: baseName,
|
|
2449
|
+
url: baseUrl
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
2452
|
+
const mainSchema = {
|
|
2453
|
+
identifier,
|
|
2454
|
+
dependencies: []
|
|
2455
|
+
};
|
|
2456
|
+
const allDependencies = [];
|
|
2457
|
+
if (base) {
|
|
2458
|
+
mainSchema.base = base;
|
|
2459
|
+
allDependencies.push(base);
|
|
2460
|
+
}
|
|
2461
|
+
if (fhirSchema.description) {
|
|
2462
|
+
mainSchema.description = fhirSchema.description;
|
|
2463
|
+
}
|
|
2464
|
+
if (fhirSchema.kind !== "primitive-type" && fhirSchema.elements) {
|
|
2465
|
+
const fields = await transformElements(fhirSchema, [], fhirSchema.elements, manager, packageInfo);
|
|
2466
|
+
if (Object.keys(fields).length > 0) {
|
|
2467
|
+
mainSchema.fields = fields;
|
|
2468
|
+
}
|
|
2469
|
+
allDependencies.push(...extractFieldDependencies(fields));
|
|
2470
|
+
const nestedTypes = await buildNestedTypes(fhirSchema, manager, packageInfo);
|
|
2471
|
+
if (nestedTypes.length > 0) {
|
|
2472
|
+
mainSchema.nested = nestedTypes;
|
|
2473
|
+
allDependencies.push(...extractNestedDependencies(nestedTypes));
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
mainSchema.dependencies = allDependencies;
|
|
2477
|
+
mainSchema.dependencies = deduplicateDependencies(mainSchema.dependencies);
|
|
2478
|
+
mainSchema.dependencies = mainSchema.dependencies.filter((dep) => dep.url !== identifier.url);
|
|
2479
|
+
results.push(mainSchema);
|
|
2480
|
+
const bindingSchemas = await collectBindingSchemas(fhirSchema, manager, packageInfo);
|
|
2481
|
+
results.push(...bindingSchemas);
|
|
2482
|
+
return results;
|
|
2483
|
+
}
|
|
2484
|
+
async function transformFHIRSchemas(fhirSchemas, manager, packageInfo) {
|
|
2485
|
+
const allResults = [];
|
|
2486
|
+
for (const fhirSchema of fhirSchemas) {
|
|
2487
|
+
const results = await transformFHIRSchema(fhirSchema, manager, packageInfo);
|
|
2488
|
+
allResults.push(...results);
|
|
2489
|
+
}
|
|
2490
|
+
return allResults;
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
// src/typeschema/generator.ts
|
|
2494
|
+
class TypeSchemaGenerator {
|
|
2495
|
+
manager;
|
|
2496
|
+
options;
|
|
2497
|
+
cache = null;
|
|
2498
|
+
cacheConfig;
|
|
2499
|
+
logger;
|
|
2500
|
+
constructor(options = {}, cacheConfig) {
|
|
2501
|
+
this.options = {
|
|
2502
|
+
resourceTypes: [],
|
|
2503
|
+
maxDepth: 10,
|
|
2504
|
+
verbose: false,
|
|
2505
|
+
...options
|
|
2506
|
+
};
|
|
2507
|
+
this.manager = createCanonicalManager({ packages: [], workingDir: "tmp/fhir" });
|
|
2508
|
+
this.cacheConfig = cacheConfig;
|
|
2509
|
+
this.logger = new Logger({
|
|
2510
|
+
component: "TypeSchemaGenerator",
|
|
2511
|
+
level: this.options.verbose ? 0 : 1
|
|
2512
|
+
});
|
|
2513
|
+
}
|
|
2514
|
+
async initializeCache() {
|
|
2515
|
+
if (this.cacheConfig && !this.cache) {
|
|
2516
|
+
this.cache = new TypeSchemaCache(this.cacheConfig);
|
|
2517
|
+
await this.cache.initialize();
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
async generateFromPackage(packageName, packageVersion) {
|
|
2521
|
+
await this.initializeCache();
|
|
2522
|
+
const forceRegenerate = this.cacheConfig?.forceRegenerate ?? false;
|
|
2523
|
+
if (this.cache && !forceRegenerate) {
|
|
2524
|
+
const cachedSchemas = this.cache.getByPackage(packageName);
|
|
2525
|
+
if (cachedSchemas.length > 0) {
|
|
2526
|
+
await this.logger.info(`Using cached TypeSchemas for package: ${packageName}`, { schemasCount: cachedSchemas.length });
|
|
2527
|
+
return cachedSchemas;
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
await this.logger.info(`Loading FHIR package: ${packageName}${packageVersion ? `@${packageVersion}` : ""}`, { packageName, packageVersion: packageVersion || "latest" }, "loadPackage");
|
|
2531
|
+
this.manager = createCanonicalManager({
|
|
2532
|
+
packages: [`${packageName}${packageVersion ? `@${packageVersion}` : ""}`],
|
|
2533
|
+
workingDir: "tmp/fhir"
|
|
2534
|
+
});
|
|
2535
|
+
await this.manager.init();
|
|
2536
|
+
const allResources = await this.manager.search({});
|
|
2537
|
+
const structureDefinitions = allResources.filter((resource) => resource.resourceType === "StructureDefinition");
|
|
2538
|
+
const valueSets = allResources.filter((resource) => resource.resourceType === "ValueSet");
|
|
2539
|
+
await this.logger.info(`Found resources in package`, {
|
|
2540
|
+
structureDefinitions: structureDefinitions.length,
|
|
2541
|
+
valueSets: valueSets.length
|
|
2542
|
+
}, "scanPackage");
|
|
2543
|
+
await this.logger.info("Converting StructureDefinitions to FHIRSchemas", { total: structureDefinitions.length }, "convertSchemas");
|
|
2544
|
+
const fhirSchemas = [];
|
|
2545
|
+
let convertedCount = 0;
|
|
2546
|
+
let failedCount = 0;
|
|
2547
|
+
for (const sd of structureDefinitions) {
|
|
2548
|
+
try {
|
|
2549
|
+
const fhirSchema = translate(sd);
|
|
2550
|
+
fhirSchemas.push(fhirSchema);
|
|
2551
|
+
convertedCount++;
|
|
2552
|
+
await this.logger.debug(`Converted StructureDefinition: ${sd.name || sd.id}`, { resourceType: sd.resourceType, url: sd.url });
|
|
2553
|
+
} catch (error) {
|
|
2554
|
+
failedCount++;
|
|
2555
|
+
await this.logger.warn(`Failed to convert StructureDefinition`, {
|
|
2556
|
+
name: sd.name || sd.id,
|
|
2557
|
+
resourceType: sd.resourceType,
|
|
2558
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
await this.logger.info("Schema conversion completed", {
|
|
2563
|
+
converted: convertedCount,
|
|
2564
|
+
failed: failedCount,
|
|
2565
|
+
total: structureDefinitions.length
|
|
2566
|
+
}, "convertSchemas");
|
|
2567
|
+
if (valueSets.length > 0) {
|
|
2568
|
+
await this.logger.debug("ValueSets available for enum extraction", {
|
|
2569
|
+
count: valueSets.length,
|
|
2570
|
+
valueSets: valueSets.map((vs) => vs.name || vs.id).slice(0, 10)
|
|
2571
|
+
});
|
|
2572
|
+
}
|
|
2573
|
+
const packageInfo = {
|
|
2574
|
+
name: packageName,
|
|
2575
|
+
version: packageVersion || "latest"
|
|
2576
|
+
};
|
|
2577
|
+
const schemas = await this.generateFromSchemas(fhirSchemas, packageInfo);
|
|
2578
|
+
if (this.cache && schemas.length > 0) {
|
|
2579
|
+
await this.logger.info("Caching generated schemas", { count: schemas.length, packageName }, "cacheSchemas");
|
|
2580
|
+
for (const schema of schemas) {
|
|
2581
|
+
await this.cache.set(schema);
|
|
2582
|
+
}
|
|
2583
|
+
await this.logger.info(`Cached TypeSchemas for package: ${packageName}`, {
|
|
2584
|
+
count: schemas.length
|
|
2585
|
+
});
|
|
2586
|
+
}
|
|
2587
|
+
return schemas;
|
|
2588
|
+
}
|
|
2589
|
+
async generateFromSchema(fhirSchema, packageInfo) {
|
|
2590
|
+
await this.logger.info("Transforming FHIR schema to TypeSchema", {
|
|
2591
|
+
url: fhirSchema.url,
|
|
2592
|
+
name: fhirSchema.name || "unnamed",
|
|
2593
|
+
schemaType: fhirSchema.type
|
|
2594
|
+
}, "transformSchema");
|
|
2595
|
+
return await transformFHIRSchema(fhirSchema, this.manager, packageInfo);
|
|
2596
|
+
}
|
|
2597
|
+
async generateFromSchemas(fhirSchemas, packageInfo) {
|
|
2598
|
+
await this.logger.info(`Transforming multiple FHIR schemas to TypeSchema`, { count: fhirSchemas.length }, "transformSchemas");
|
|
2599
|
+
const baseSchemas = await transformFHIRSchemas(fhirSchemas, this.manager, packageInfo);
|
|
2600
|
+
const results = [];
|
|
2601
|
+
const groupedSchemas = this.groupTypeSchemas(baseSchemas);
|
|
2602
|
+
results.push(...groupedSchemas.resources);
|
|
2603
|
+
results.push(...groupedSchemas.complexTypes);
|
|
2604
|
+
results.push(...groupedSchemas.primitives);
|
|
2605
|
+
if (groupedSchemas.profiles.length > 0) {
|
|
2606
|
+
await this.logger.info("Enhancing profiles", { count: groupedSchemas.profiles.length }, "enhanceProfiles");
|
|
2607
|
+
const profileResults = await this.enhanceProfiles(groupedSchemas.profiles);
|
|
2608
|
+
results.push(...profileResults);
|
|
2609
|
+
}
|
|
2610
|
+
if (groupedSchemas.extensions.length > 0) {
|
|
2611
|
+
await this.logger.info("Enhancing extensions", { count: groupedSchemas.extensions.length }, "enhanceExtensions");
|
|
2612
|
+
const extensionResults = await this.enhanceExtensions(groupedSchemas.extensions);
|
|
2613
|
+
results.push(...extensionResults);
|
|
2614
|
+
}
|
|
2615
|
+
if (groupedSchemas.valueSets.length > 0) {
|
|
2616
|
+
await this.logger.info("Enhancing value sets", { count: groupedSchemas.valueSets.length }, "enhanceValueSets");
|
|
2617
|
+
const valueSetResults = await this.enhanceValueSets(groupedSchemas.valueSets);
|
|
2618
|
+
results.push(...valueSetResults);
|
|
2619
|
+
}
|
|
2620
|
+
if (groupedSchemas.codeSystems.length > 0) {
|
|
2621
|
+
await this.logger.info("Enhancing code systems", { count: groupedSchemas.codeSystems.length }, "enhanceCodeSystems");
|
|
2622
|
+
const codeSystemResults = await this.enhanceCodeSystems(groupedSchemas.codeSystems);
|
|
2623
|
+
results.push(...codeSystemResults);
|
|
2624
|
+
}
|
|
2625
|
+
await this.logger.info("Generated enhanced FHIR type schemas", {
|
|
2626
|
+
totalSchemas: results.length,
|
|
2627
|
+
resources: groupedSchemas.resources.length,
|
|
2628
|
+
complexTypes: groupedSchemas.complexTypes.length,
|
|
2629
|
+
primitives: groupedSchemas.primitives.length,
|
|
2630
|
+
profiles: groupedSchemas.profiles.length,
|
|
2631
|
+
extensions: groupedSchemas.extensions.length,
|
|
2632
|
+
valueSets: groupedSchemas.valueSets.length,
|
|
2633
|
+
codeSystems: groupedSchemas.codeSystems.length
|
|
2634
|
+
});
|
|
2635
|
+
return results;
|
|
2636
|
+
}
|
|
2637
|
+
groupTypeSchemas(schemas) {
|
|
2638
|
+
const groups = {
|
|
2639
|
+
resources: [],
|
|
2640
|
+
complexTypes: [],
|
|
2641
|
+
primitives: [],
|
|
2642
|
+
profiles: [],
|
|
2643
|
+
extensions: [],
|
|
2644
|
+
valueSets: [],
|
|
2645
|
+
codeSystems: []
|
|
2646
|
+
};
|
|
2647
|
+
for (const schema of schemas) {
|
|
2648
|
+
switch (schema.identifier.kind) {
|
|
2649
|
+
case "resource":
|
|
2650
|
+
groups.resources.push(schema);
|
|
2651
|
+
break;
|
|
2652
|
+
case "complex-type":
|
|
2653
|
+
groups.complexTypes.push(schema);
|
|
2654
|
+
break;
|
|
2655
|
+
case "primitive-type":
|
|
2656
|
+
groups.primitives.push(schema);
|
|
2657
|
+
break;
|
|
2658
|
+
case "binding":
|
|
2659
|
+
if ("metadata" in schema && schema.metadata?.isExtension) {
|
|
2660
|
+
groups.extensions.push(schema);
|
|
2661
|
+
} else {
|
|
2662
|
+
groups.complexTypes.push(schema);
|
|
2663
|
+
}
|
|
2664
|
+
break;
|
|
2665
|
+
case "value-set":
|
|
2666
|
+
groups.valueSets.push(schema);
|
|
2667
|
+
break;
|
|
2668
|
+
default:
|
|
2669
|
+
if ("metadata" in schema && schema.metadata?.isCodeSystem) {
|
|
2670
|
+
groups.codeSystems.push(schema);
|
|
2671
|
+
} else {
|
|
2672
|
+
groups.complexTypes.push(schema);
|
|
2673
|
+
}
|
|
2674
|
+
break;
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
return groups;
|
|
2678
|
+
}
|
|
2679
|
+
async enhanceProfiles(schemas) {
|
|
2680
|
+
return schemas;
|
|
2681
|
+
}
|
|
2682
|
+
async enhanceExtensions(schemas) {
|
|
2683
|
+
return schemas;
|
|
2684
|
+
}
|
|
2685
|
+
async enhanceValueSets(schemas) {
|
|
2686
|
+
return schemas;
|
|
2687
|
+
}
|
|
2688
|
+
async enhanceCodeSystems(schemas) {
|
|
2689
|
+
return schemas;
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
// src/typeschema/parser.ts
|
|
2694
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
2695
|
+
|
|
2696
|
+
class TypeSchemaParser {
|
|
2697
|
+
options;
|
|
2698
|
+
constructor(options = {}) {
|
|
2699
|
+
this.options = {
|
|
2700
|
+
format: "auto",
|
|
2701
|
+
validate: true,
|
|
2702
|
+
strict: false,
|
|
2703
|
+
...options
|
|
2704
|
+
};
|
|
2705
|
+
}
|
|
2706
|
+
async parseFromFile(filePath) {
|
|
2707
|
+
const content = await readFile7(filePath, "utf-8");
|
|
2708
|
+
const format = this.options.format === "auto" ? this.detectFormat(content, filePath) : this.options.format;
|
|
2709
|
+
return this.parseFromString(content, format);
|
|
2710
|
+
}
|
|
2711
|
+
async parseFromString(content, format) {
|
|
2712
|
+
const actualFormat = format || this.detectFormat(content);
|
|
2713
|
+
let schemas;
|
|
2714
|
+
if (actualFormat === "ndjson") {
|
|
2715
|
+
schemas = this.parseNDJSON(content);
|
|
2716
|
+
} else {
|
|
2717
|
+
schemas = this.parseJSON(content);
|
|
2718
|
+
}
|
|
2719
|
+
if (this.options.validate) {
|
|
2720
|
+
this.validateSchemas(schemas);
|
|
2721
|
+
}
|
|
2722
|
+
return schemas;
|
|
2723
|
+
}
|
|
2724
|
+
async parseFromFiles(filePaths) {
|
|
2725
|
+
const allSchemas = [];
|
|
2726
|
+
for (const filePath of filePaths) {
|
|
2727
|
+
const schemas = await this.parseFromFile(filePath);
|
|
2728
|
+
allSchemas.push(...schemas);
|
|
2729
|
+
}
|
|
2730
|
+
return allSchemas;
|
|
2731
|
+
}
|
|
2732
|
+
parseSchema(schemaData) {
|
|
2733
|
+
if (!schemaData.identifier) {
|
|
2734
|
+
throw new Error("TypeSchema must have an identifier");
|
|
2735
|
+
}
|
|
2736
|
+
if (!this.isValidIdentifier(schemaData.identifier)) {
|
|
2737
|
+
throw new Error("TypeSchema identifier is invalid");
|
|
2738
|
+
}
|
|
2739
|
+
return schemaData;
|
|
2740
|
+
}
|
|
2741
|
+
findByIdentifier(schemas, identifier) {
|
|
2742
|
+
return schemas.filter((schema) => this.matchesIdentifier(schema.identifier, identifier));
|
|
2743
|
+
}
|
|
2744
|
+
findByUrl(schemas, url) {
|
|
2745
|
+
return schemas.find((schema) => schema.identifier.url === url);
|
|
2746
|
+
}
|
|
2747
|
+
findByKind(schemas, kind) {
|
|
2748
|
+
return schemas.filter((schema) => schema.identifier.kind === kind);
|
|
2749
|
+
}
|
|
2750
|
+
findByPackage(schemas, packageName) {
|
|
2751
|
+
return schemas.filter((schema) => schema.identifier.package === packageName);
|
|
2752
|
+
}
|
|
2753
|
+
getDependencies(schema) {
|
|
2754
|
+
const dependencies = [];
|
|
2755
|
+
if ("base" in schema && schema.base) {
|
|
2756
|
+
dependencies.push(schema.base);
|
|
2757
|
+
}
|
|
2758
|
+
if ("dependencies" in schema && schema.dependencies) {
|
|
2759
|
+
dependencies.push(...schema.dependencies);
|
|
2760
|
+
}
|
|
2761
|
+
if ("fields" in schema && schema.fields) {
|
|
2762
|
+
for (const field of Object.values(schema.fields)) {
|
|
2763
|
+
if ("type" in field && field.type) {
|
|
2764
|
+
dependencies.push(field.type);
|
|
2765
|
+
}
|
|
2766
|
+
if ("binding" in field && field.binding) {
|
|
2767
|
+
dependencies.push(field.binding);
|
|
2768
|
+
}
|
|
2769
|
+
if ("reference" in field && field.reference) {
|
|
2770
|
+
dependencies.push(...field.reference);
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
if ("nested" in schema && schema.nested) {
|
|
2775
|
+
for (const nested of schema.nested) {
|
|
2776
|
+
dependencies.push(nested.identifier);
|
|
2777
|
+
dependencies.push(nested.base);
|
|
2778
|
+
for (const field of Object.values(nested.fields)) {
|
|
2779
|
+
if ("type" in field && field.type) {
|
|
2780
|
+
dependencies.push(field.type);
|
|
2781
|
+
}
|
|
2782
|
+
if ("binding" in field && field.binding) {
|
|
2783
|
+
dependencies.push(field.binding);
|
|
2784
|
+
}
|
|
2785
|
+
if ("reference" in field && field.reference) {
|
|
2786
|
+
dependencies.push(...field.reference);
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
if ("valueset" in schema) {
|
|
2792
|
+
const bindingSchema = schema;
|
|
2793
|
+
dependencies.push(bindingSchema.valueset);
|
|
2794
|
+
if (bindingSchema.type) {
|
|
2795
|
+
dependencies.push(bindingSchema.type);
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
return this.deduplicateDependencies(dependencies);
|
|
2799
|
+
}
|
|
2800
|
+
resolveDependencies(schemas, targetSchema) {
|
|
2801
|
+
const dependencies = this.getDependencies(targetSchema);
|
|
2802
|
+
const resolved = [];
|
|
2803
|
+
for (const dep of dependencies) {
|
|
2804
|
+
const depSchema = this.findByUrl(schemas, dep.url);
|
|
2805
|
+
if (depSchema) {
|
|
2806
|
+
resolved.push(depSchema);
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
return resolved;
|
|
2810
|
+
}
|
|
2811
|
+
detectFormat(content, filename) {
|
|
2812
|
+
if (filename) {
|
|
2813
|
+
if (filename.endsWith(".ndjson"))
|
|
2814
|
+
return "ndjson";
|
|
2815
|
+
if (filename.endsWith(".json"))
|
|
2816
|
+
return "json";
|
|
2817
|
+
}
|
|
2818
|
+
const trimmed = content.trim();
|
|
2819
|
+
if (trimmed.includes(`
|
|
2820
|
+
`)) {
|
|
2821
|
+
const lines = trimmed.split(`
|
|
2822
|
+
`).filter((line) => line.trim());
|
|
2823
|
+
if (lines.length > 1) {
|
|
2824
|
+
try {
|
|
2825
|
+
if (lines[0]) {
|
|
2826
|
+
JSON.parse(lines[0]);
|
|
2827
|
+
}
|
|
2828
|
+
return "ndjson";
|
|
2829
|
+
} catch {}
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
return "json";
|
|
2833
|
+
}
|
|
2834
|
+
parseNDJSON(content) {
|
|
2835
|
+
const schemas = [];
|
|
2836
|
+
const lines = content.split(`
|
|
2837
|
+
`).filter((line) => line.trim());
|
|
2838
|
+
for (const line of lines) {
|
|
2839
|
+
try {
|
|
2840
|
+
const parsed = JSON.parse(line);
|
|
2841
|
+
schemas.push(this.parseSchema(parsed));
|
|
2842
|
+
} catch (error) {
|
|
2843
|
+
if (this.options.strict) {
|
|
2844
|
+
throw new Error(`Failed to parse NDJSON line: ${error}`);
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
return schemas;
|
|
2849
|
+
}
|
|
2850
|
+
parseJSON(content) {
|
|
2851
|
+
try {
|
|
2852
|
+
const parsed = JSON.parse(content);
|
|
2853
|
+
if (Array.isArray(parsed)) {
|
|
2854
|
+
return parsed.map((item) => this.parseSchema(item));
|
|
2855
|
+
} else {
|
|
2856
|
+
return [this.parseSchema(parsed)];
|
|
2857
|
+
}
|
|
2858
|
+
} catch (error) {
|
|
2859
|
+
throw new Error(`Failed to parse JSON: ${error}`);
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
validateSchemas(schemas) {
|
|
2863
|
+
for (const schema of schemas) {
|
|
2864
|
+
if (!schema.identifier) {
|
|
2865
|
+
throw new Error("Schema missing identifier");
|
|
2866
|
+
}
|
|
2867
|
+
if (!this.isValidIdentifier(schema.identifier)) {
|
|
2868
|
+
throw new Error(`Invalid identifier in schema: ${JSON.stringify(schema.identifier)}`);
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
isValidIdentifier(identifier) {
|
|
2873
|
+
return typeof identifier === "object" && identifier !== null && typeof identifier.kind === "string" && typeof identifier.package === "string" && typeof identifier.version === "string" && typeof identifier.name === "string" && typeof identifier.url === "string";
|
|
2874
|
+
}
|
|
2875
|
+
matchesIdentifier(identifier, criteria) {
|
|
2876
|
+
return (!criteria.kind || identifier.kind === criteria.kind) && (!criteria.package || identifier.package === criteria.package) && (!criteria.version || identifier.version === criteria.version) && (!criteria.name || identifier.name === criteria.name) && (!criteria.url || identifier.url === criteria.url);
|
|
2877
|
+
}
|
|
2878
|
+
deduplicateDependencies(deps) {
|
|
2879
|
+
const seen = new Set;
|
|
2880
|
+
const unique = [];
|
|
2881
|
+
for (const dep of deps) {
|
|
2882
|
+
if (!seen.has(dep.url)) {
|
|
2883
|
+
seen.add(dep.url);
|
|
2884
|
+
unique.push(dep);
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
return unique.sort((a, b) => a.name.localeCompare(b.name));
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
// src/typeschema/utils.ts
|
|
2891
|
+
var isPolymorphicInstanceField = (field) => {
|
|
2892
|
+
return "choiceOf" in field;
|
|
2893
|
+
};
|
|
2894
|
+
var isRegularField = (field) => {
|
|
2895
|
+
return !("choices" in field);
|
|
2896
|
+
};
|
|
2897
|
+
var isTypeSchemaForResourceComplexTypeLogical = (schema) => {
|
|
2898
|
+
return schema.identifier.kind === "resource" || schema.identifier.kind === "complex-type" || schema.identifier.kind === "logical";
|
|
2899
|
+
};
|
|
2900
|
+
// src/api/generators/typescript.ts
|
|
2901
|
+
import { mkdir as mkdir2, writeFile as writeFile4 } from "node:fs/promises";
|
|
2902
|
+
import { dirname, join as join10 } from "node:path";
|
|
2903
|
+
|
|
2904
|
+
// src/utils.ts
|
|
2905
|
+
function toPascalCase(input) {
|
|
2906
|
+
const parts = input.replace(/[^A-Za-z0-9]+/g, " ").split(" ").map((p) => p.trim()).filter(Boolean);
|
|
2907
|
+
if (parts.length === 0)
|
|
2908
|
+
return "";
|
|
2909
|
+
return parts.map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
// src/api/generators/typescript.ts
|
|
2913
|
+
var PRIMITIVE_TYPE_MAP = {
|
|
2914
|
+
string: "string",
|
|
2915
|
+
code: "string",
|
|
2916
|
+
uri: "string",
|
|
2917
|
+
url: "string",
|
|
2918
|
+
canonical: "string",
|
|
2919
|
+
oid: "string",
|
|
2920
|
+
uuid: "string",
|
|
2921
|
+
id: "string",
|
|
2922
|
+
markdown: "string",
|
|
2923
|
+
xhtml: "string",
|
|
2924
|
+
base64Binary: "string",
|
|
2925
|
+
integer: "number",
|
|
2926
|
+
unsignedInt: "number",
|
|
2927
|
+
positiveInt: "number",
|
|
2928
|
+
decimal: "number",
|
|
2929
|
+
boolean: "boolean",
|
|
2930
|
+
date: "string",
|
|
2931
|
+
dateTime: "string",
|
|
2932
|
+
instant: "string",
|
|
2933
|
+
time: "string"
|
|
2934
|
+
};
|
|
2935
|
+
|
|
2936
|
+
class TypeScriptAPIGenerator {
|
|
2937
|
+
options;
|
|
2938
|
+
imports = new Map;
|
|
2939
|
+
exports = new Set;
|
|
2940
|
+
resourceTypes = new Set;
|
|
2941
|
+
currentSchemaName;
|
|
2942
|
+
constructor(options) {
|
|
2943
|
+
this.options = {
|
|
2944
|
+
moduleFormat: "esm",
|
|
2945
|
+
generateIndex: true,
|
|
2946
|
+
includeDocuments: true,
|
|
2947
|
+
namingConvention: "PascalCase",
|
|
2948
|
+
includeExtensions: false,
|
|
2949
|
+
includeProfiles: false,
|
|
2950
|
+
...options
|
|
2951
|
+
};
|
|
2952
|
+
}
|
|
2953
|
+
async transformSchema(schema) {
|
|
2954
|
+
this.currentSchemaName = this.formatTypeName(schema.identifier.name);
|
|
2955
|
+
if (schema.identifier.kind === "value-set" || schema.identifier.kind === "binding" || schema.identifier.kind === "primitive-type" || schema.identifier.kind === "profile") {
|
|
2956
|
+
return;
|
|
2957
|
+
}
|
|
2958
|
+
this.imports.clear();
|
|
2959
|
+
this.exports.clear();
|
|
2960
|
+
this.currentSchemaName = this.formatTypeName(schema.identifier.name);
|
|
2961
|
+
const content = this.generateTypeScriptForSchema(schema);
|
|
2962
|
+
const imports = new Map(this.imports);
|
|
2963
|
+
const filename = this.getFilename(schema.identifier);
|
|
2964
|
+
const exports = Array.from(this.exports.keys());
|
|
2965
|
+
return {
|
|
2966
|
+
content,
|
|
2967
|
+
imports,
|
|
2968
|
+
exports,
|
|
2969
|
+
filename
|
|
2970
|
+
};
|
|
2971
|
+
}
|
|
2972
|
+
generateTypeScriptForSchema(schema) {
|
|
2973
|
+
const lines = [];
|
|
2974
|
+
const interfaceName = this.formatTypeName(schema.identifier.name);
|
|
2975
|
+
let overridedName;
|
|
2976
|
+
if (interfaceName === "Reference") {
|
|
2977
|
+
overridedName = `Reference<T extends ResourceTypes = ResourceTypes>`;
|
|
2978
|
+
}
|
|
2979
|
+
this.exports.add(interfaceName);
|
|
2980
|
+
const baseInterface = this.getBaseInterface(schema);
|
|
2981
|
+
if (baseInterface && !baseInterface.isPrimitive) {
|
|
2982
|
+
this.imports.set(baseInterface.value, baseInterface.value);
|
|
2983
|
+
lines.push(`export interface ${overridedName ?? interfaceName} extends ${baseInterface.value} {`);
|
|
2984
|
+
} else {
|
|
2985
|
+
lines.push(`export interface ${overridedName ?? interfaceName} {`);
|
|
2986
|
+
}
|
|
2987
|
+
if (schema.identifier.kind === "resource" && interfaceName !== "DomainResource" && interfaceName !== "Resource") {
|
|
2988
|
+
this.resourceTypes.add(interfaceName);
|
|
2989
|
+
lines.push(` resourceType: '${interfaceName}';`);
|
|
2990
|
+
}
|
|
2991
|
+
if (isTypeSchemaForResourceComplexTypeLogical(schema)) {
|
|
2992
|
+
if (schema.fields) {
|
|
2993
|
+
for (const [fieldName, field] of Object.entries(schema.fields)) {
|
|
2994
|
+
const fieldLine = this.generateField(fieldName, field, {
|
|
2995
|
+
isNested: "type" in field && field.type.kind === "nested",
|
|
2996
|
+
baseName: interfaceName
|
|
2997
|
+
});
|
|
2998
|
+
if (fieldLine) {
|
|
2999
|
+
lines.push(` ${fieldLine}`);
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
lines.push("}");
|
|
3004
|
+
if (schema.nested) {
|
|
3005
|
+
for (const nested of schema.nested) {
|
|
3006
|
+
lines.push("");
|
|
3007
|
+
lines.push(this.generateNested(this.currentSchemaName ?? "", nested));
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
} else {
|
|
3011
|
+
lines.push("}");
|
|
3012
|
+
}
|
|
3013
|
+
return lines.join(`
|
|
3014
|
+
`);
|
|
3015
|
+
}
|
|
3016
|
+
generateNested(baseName, nested) {
|
|
3017
|
+
const lines = [];
|
|
3018
|
+
const interfaceName = this.formatTypeName(nested.identifier.name);
|
|
3019
|
+
this.exports.add(interfaceName);
|
|
3020
|
+
if (nested.base) {
|
|
3021
|
+
const baseInterface = this.getType(nested.base);
|
|
3022
|
+
if (baseInterface.isPrimitive) {
|
|
3023
|
+
lines.push(`export interface ${baseName}${interfaceName}{`);
|
|
3024
|
+
} else {
|
|
3025
|
+
this.imports.set(baseInterface.value, baseInterface.value);
|
|
3026
|
+
lines.push(`export interface ${baseName}${interfaceName} extends ${baseInterface.value} {`);
|
|
3027
|
+
}
|
|
3028
|
+
} else {
|
|
3029
|
+
lines.push(`export interface ${baseName}${interfaceName}{`);
|
|
3030
|
+
}
|
|
3031
|
+
if (nested.fields) {
|
|
3032
|
+
for (const [fieldName, field] of Object.entries(nested.fields)) {
|
|
3033
|
+
const fieldLine = this.generateField(fieldName, field);
|
|
3034
|
+
if (fieldLine) {
|
|
3035
|
+
lines.push(` ${fieldLine}`);
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
lines.push("}");
|
|
3040
|
+
return lines.join(`
|
|
3041
|
+
`);
|
|
3042
|
+
}
|
|
3043
|
+
generateField(fieldName, field, nestedOpts) {
|
|
3044
|
+
if (isPolymorphicInstanceField(field)) {
|
|
3045
|
+
return this.generatePolymorphicInstance(fieldName, field, nestedOpts);
|
|
3046
|
+
} else if (isRegularField(field)) {
|
|
3047
|
+
return this.generateRegularField(fieldName, field, nestedOpts);
|
|
3048
|
+
}
|
|
3049
|
+
return "";
|
|
3050
|
+
}
|
|
3051
|
+
generatePolymorphicInstance(fieldName, field, nestedOpts) {
|
|
3052
|
+
let typeString = "any";
|
|
3053
|
+
if (field.reference) {
|
|
3054
|
+
typeString = this.buildReferenceType(field.reference);
|
|
3055
|
+
} else if (field.type) {
|
|
3056
|
+
const subType = this.getType(field.type);
|
|
3057
|
+
if (!subType.isPrimitive && !nestedOpts?.isNested) {
|
|
3058
|
+
this.imports.set(subType.value, subType.value);
|
|
3059
|
+
}
|
|
3060
|
+
typeString = subType.value;
|
|
3061
|
+
}
|
|
3062
|
+
const optional = !field.required ? "?" : "";
|
|
3063
|
+
const array = field.array ? "[]" : "";
|
|
3064
|
+
return `${fieldName}${optional}: ${nestedOpts?.isNested && nestedOpts.baseName ? nestedOpts.baseName : ""}${typeString}${array};`;
|
|
3065
|
+
}
|
|
3066
|
+
generateRegularField(fieldName, field, nestedOpts) {
|
|
3067
|
+
let typeString = "any";
|
|
3068
|
+
if (field.enum) {
|
|
3069
|
+
if (field.enum.length > 15) {
|
|
3070
|
+
typeString = "string";
|
|
3071
|
+
} else {
|
|
3072
|
+
typeString = `${field.enum.map((e) => `'${e}'`).join(" | ")}`;
|
|
3073
|
+
}
|
|
3074
|
+
} else if (field.reference) {
|
|
3075
|
+
typeString = this.buildReferenceType(field.reference);
|
|
3076
|
+
} else if (field.type) {
|
|
3077
|
+
const subType = this.getType(field.type);
|
|
3078
|
+
if (!subType.isPrimitive && !nestedOpts?.isNested) {
|
|
3079
|
+
this.imports.set(subType.value, subType.value);
|
|
3080
|
+
}
|
|
3081
|
+
typeString = subType.value;
|
|
3082
|
+
}
|
|
3083
|
+
if (nestedOpts?.baseName === "Reference" && fieldName === "type") {
|
|
3084
|
+
typeString = "T";
|
|
3085
|
+
this.imports.set("ResourceTypes", "utility");
|
|
3086
|
+
}
|
|
3087
|
+
const optional = !field.required ? "?" : "";
|
|
3088
|
+
const array = field.array ? "[]" : "";
|
|
3089
|
+
return `${fieldName}${optional}: ${nestedOpts?.isNested && nestedOpts.baseName ? nestedOpts.baseName : ""}${typeString}${array};`;
|
|
3090
|
+
}
|
|
3091
|
+
buildReferenceType(refers) {
|
|
3092
|
+
this.imports.set("Reference", "Reference");
|
|
3093
|
+
if (refers.length === 0) {
|
|
3094
|
+
return "Reference";
|
|
3095
|
+
}
|
|
3096
|
+
if (refers.length === 1 && refers[0]?.name === "Resource") {
|
|
3097
|
+
return "Reference";
|
|
3098
|
+
}
|
|
3099
|
+
return `Reference<${refers.map((r) => `'${r.name}'`).join(" | ")}>`;
|
|
3100
|
+
}
|
|
3101
|
+
async transformSchemas(schemas) {
|
|
3102
|
+
const results = [];
|
|
3103
|
+
for (const schema of schemas) {
|
|
3104
|
+
const result = await this.transformSchema(schema);
|
|
3105
|
+
if (result) {
|
|
3106
|
+
results.push(result);
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
return results;
|
|
3110
|
+
}
|
|
3111
|
+
async generate(schemas) {
|
|
3112
|
+
const filteredSchemas = this.filterSchemas(schemas);
|
|
3113
|
+
const results = await this.transformSchemas(filteredSchemas);
|
|
3114
|
+
const generatedFiles = [];
|
|
3115
|
+
await mkdir2(this.options.outputDir, { recursive: true });
|
|
3116
|
+
if (this.options.includeProfiles) {
|
|
3117
|
+
await mkdir2(join10(this.options.outputDir, "profiles"), {
|
|
3118
|
+
recursive: true
|
|
3119
|
+
});
|
|
3120
|
+
}
|
|
3121
|
+
if (this.options.includeExtensions) {
|
|
3122
|
+
await mkdir2(join10(this.options.outputDir, "extensions"), {
|
|
3123
|
+
recursive: true
|
|
3124
|
+
});
|
|
3125
|
+
}
|
|
3126
|
+
results.push({
|
|
3127
|
+
filename: "utility.ts",
|
|
3128
|
+
content: `export type ResourceTypes = ${Array.from(this.resourceTypes.keys().map((key) => `'${key}'`)).join(" | ")};
|
|
3129
|
+
|
|
3130
|
+
`,
|
|
3131
|
+
imports: new Map,
|
|
3132
|
+
exports: ["ResourceTypes"]
|
|
3133
|
+
});
|
|
3134
|
+
for (const result of results) {
|
|
3135
|
+
const filePath = join10(this.options.outputDir, result.filename);
|
|
3136
|
+
await this.ensureDirectoryExists(filePath);
|
|
3137
|
+
await writeFile4(filePath, `${this.generateImportStatements(result.imports)}
|
|
3138
|
+
|
|
3139
|
+
${result.content}`, "utf-8");
|
|
3140
|
+
generatedFiles.push({
|
|
3141
|
+
path: filePath,
|
|
3142
|
+
filename: result.filename,
|
|
3143
|
+
content: result.content,
|
|
3144
|
+
exports: result.exports
|
|
3145
|
+
});
|
|
3146
|
+
}
|
|
3147
|
+
if (this.options.generateIndex) {
|
|
3148
|
+
const indexFile = await this.generateIndexFile(results);
|
|
3149
|
+
const indexPath = join10(this.options.outputDir, "index.ts");
|
|
3150
|
+
await writeFile4(indexPath, indexFile.content, "utf-8");
|
|
3151
|
+
generatedFiles.push({
|
|
3152
|
+
path: indexPath,
|
|
3153
|
+
filename: "index.ts",
|
|
3154
|
+
content: indexFile.content,
|
|
3155
|
+
exports: indexFile.exports
|
|
3156
|
+
});
|
|
3157
|
+
await this.generateSubfolderIndexFiles(results, generatedFiles);
|
|
3158
|
+
}
|
|
3159
|
+
return generatedFiles;
|
|
3160
|
+
}
|
|
3161
|
+
generateImportStatements(imports) {
|
|
3162
|
+
const lines = [];
|
|
3163
|
+
for (const [item, pkg] of imports.entries()) {
|
|
3164
|
+
lines.push(`import type { ${item} } from './${pkg}';`);
|
|
3165
|
+
}
|
|
3166
|
+
return lines.join(`
|
|
3167
|
+
`);
|
|
3168
|
+
}
|
|
3169
|
+
async build(schemas) {
|
|
3170
|
+
const filteredSchemas = this.filterSchemas(schemas);
|
|
3171
|
+
const results = await this.transformSchemas(filteredSchemas);
|
|
3172
|
+
const generatedFiles = [];
|
|
3173
|
+
for (const result of results) {
|
|
3174
|
+
generatedFiles.push({
|
|
3175
|
+
path: join10(this.options.outputDir, result.filename),
|
|
3176
|
+
filename: result.filename,
|
|
3177
|
+
content: result.content,
|
|
3178
|
+
exports: result.exports
|
|
3179
|
+
});
|
|
3180
|
+
}
|
|
3181
|
+
if (this.options.generateIndex) {
|
|
3182
|
+
const indexFile = await this.generateIndexFile(results);
|
|
3183
|
+
generatedFiles.push({
|
|
3184
|
+
path: join10(this.options.outputDir, "index.ts"),
|
|
3185
|
+
filename: "index.ts",
|
|
3186
|
+
content: indexFile.content,
|
|
3187
|
+
exports: indexFile.exports
|
|
3188
|
+
});
|
|
3189
|
+
}
|
|
3190
|
+
return generatedFiles;
|
|
3191
|
+
}
|
|
3192
|
+
setOutputDir(directory) {
|
|
3193
|
+
this.options.outputDir = directory;
|
|
3194
|
+
}
|
|
3195
|
+
setOptions(options) {
|
|
3196
|
+
this.options = { ...this.options, ...options };
|
|
3197
|
+
}
|
|
3198
|
+
getOptions() {
|
|
3199
|
+
return { ...this.options };
|
|
3200
|
+
}
|
|
3201
|
+
async generateIndexFile(results) {
|
|
3202
|
+
const lines = [];
|
|
3203
|
+
const exports = [];
|
|
3204
|
+
lines.push("/**");
|
|
3205
|
+
lines.push(" * Generated TypeScript Type Definitions");
|
|
3206
|
+
lines.push(" * ");
|
|
3207
|
+
lines.push(" * Auto-generated from TypeSchema documents using atomic-codegen.");
|
|
3208
|
+
lines.push(" * ");
|
|
3209
|
+
lines.push(" * @packageDocumentation");
|
|
3210
|
+
lines.push(" */");
|
|
3211
|
+
lines.push("");
|
|
3212
|
+
const primitiveTypes = [];
|
|
3213
|
+
const complexTypes = [];
|
|
3214
|
+
const resources = [];
|
|
3215
|
+
const profiles = [];
|
|
3216
|
+
const valueSets = [];
|
|
3217
|
+
for (const result of results) {
|
|
3218
|
+
for (const exportName of result.exports) {
|
|
3219
|
+
if (result.filename.includes("primitive")) {
|
|
3220
|
+
primitiveTypes.push(exportName);
|
|
3221
|
+
} else if (result.filename.includes("complex")) {
|
|
3222
|
+
complexTypes.push(exportName);
|
|
3223
|
+
} else if (result.filename.includes("resource")) {
|
|
3224
|
+
resources.push(exportName);
|
|
3225
|
+
} else if (result.filename.includes("profile")) {
|
|
3226
|
+
profiles.push(exportName);
|
|
3227
|
+
} else if (result.filename.includes("valueset")) {
|
|
3228
|
+
valueSets.push(exportName);
|
|
3229
|
+
} else {
|
|
3230
|
+
complexTypes.push(exportName);
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
const baseName = result.filename.replace(".ts", "");
|
|
3234
|
+
if (baseName.startsWith("valueset") || baseName.startsWith("extension") || baseName.startsWith("profile")) {
|
|
3235
|
+
continue;
|
|
3236
|
+
}
|
|
3237
|
+
if (result.exports.length > 0) {
|
|
3238
|
+
if (result.exports.length === 1) {
|
|
3239
|
+
lines.push(`export type { ${result.exports[0]} } from './${baseName}';`);
|
|
3240
|
+
} else {
|
|
3241
|
+
lines.push(`export type {`);
|
|
3242
|
+
for (let i = 0;i < result.exports.length; i++) {
|
|
3243
|
+
const exportName = result.exports[i];
|
|
3244
|
+
const isLast = i === result.exports.length - 1;
|
|
3245
|
+
lines.push(` ${exportName}${isLast ? "" : ","}`);
|
|
3246
|
+
}
|
|
3247
|
+
lines.push(`} from './${baseName}';`);
|
|
3248
|
+
}
|
|
3249
|
+
exports.push(...result.exports);
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
lines.push("");
|
|
3253
|
+
if (resources.length > 0) {
|
|
3254
|
+
lines.push("// Resource type utilities");
|
|
3255
|
+
lines.push("");
|
|
3256
|
+
lines.push("export const ResourceTypeMap = {");
|
|
3257
|
+
for (const resource of resources.sort()) {
|
|
3258
|
+
lines.push(` ${resource}: true,`);
|
|
3259
|
+
}
|
|
3260
|
+
lines.push("} as const;");
|
|
3261
|
+
lines.push("");
|
|
3262
|
+
lines.push("export type ResourceType = keyof typeof ResourceTypeMap;");
|
|
3263
|
+
lines.push("");
|
|
3264
|
+
lines.push("export type AnyResource =");
|
|
3265
|
+
for (let i = 0;i < resources.length; i++) {
|
|
3266
|
+
const resource = resources[i];
|
|
3267
|
+
const isLast = i === resources.length - 1;
|
|
3268
|
+
lines.push(` ${i === 0 ? "" : "| "}${resource}${isLast ? ";" : ""}`);
|
|
3269
|
+
}
|
|
3270
|
+
lines.push("");
|
|
3271
|
+
lines.push("/**");
|
|
3272
|
+
lines.push(" * Type guard to check if an object is a FHIR resource");
|
|
3273
|
+
lines.push(" */");
|
|
3274
|
+
lines.push("export function isFHIRResource(obj: unknown): obj is AnyResource {");
|
|
3275
|
+
lines.push(' return obj && typeof obj === "object" && "resourceType" in obj && (obj.resourceType as string) in ResourceTypeMap;');
|
|
3276
|
+
lines.push("}");
|
|
3277
|
+
lines.push("");
|
|
3278
|
+
}
|
|
3279
|
+
lines.push("// Namespace exports for organized access");
|
|
3280
|
+
lines.push("");
|
|
3281
|
+
lines.push("// Value sets namespace - contains all FHIR ValueSet types and bindings");
|
|
3282
|
+
lines.push('export * as ValueSets from "./valuesets";');
|
|
3283
|
+
lines.push("");
|
|
3284
|
+
if (this.options.includeExtensions) {
|
|
3285
|
+
lines.push("// Extensions namespace - contains all FHIR Extension types");
|
|
3286
|
+
lines.push('export * as Extensions from "./extensions";');
|
|
3287
|
+
lines.push("");
|
|
3288
|
+
}
|
|
3289
|
+
if (profiles.length > 0) {
|
|
3290
|
+
lines.push("// Profiles namespace - contains all FHIR Profile types");
|
|
3291
|
+
lines.push('export * as Profiles from "./profiles";');
|
|
3292
|
+
lines.push("");
|
|
3293
|
+
}
|
|
3294
|
+
const content = lines.join(`
|
|
3295
|
+
`);
|
|
3296
|
+
return { content, exports };
|
|
3297
|
+
}
|
|
3298
|
+
async ensureDirectoryExists(filePath) {
|
|
3299
|
+
const dir = dirname(filePath);
|
|
3300
|
+
await mkdir2(dir, { recursive: true });
|
|
3301
|
+
}
|
|
3302
|
+
filterSchemas(schemas) {
|
|
3303
|
+
if (this.options.includeExtensions) {
|
|
3304
|
+
return schemas;
|
|
3305
|
+
}
|
|
3306
|
+
return schemas;
|
|
3307
|
+
}
|
|
3308
|
+
async generateSubfolderIndexFiles(results, generatedFiles) {
|
|
3309
|
+
const subfolders = [];
|
|
3310
|
+
if (this.options.includeExtensions) {
|
|
3311
|
+
subfolders.push("extensions");
|
|
3312
|
+
}
|
|
3313
|
+
for (const subfolder of subfolders) {
|
|
3314
|
+
const subfolderResults = results.filter((r) => r.filename.startsWith(`${subfolder}/`));
|
|
3315
|
+
const indexContent = this.generateSubfolderIndex(subfolderResults, subfolder);
|
|
3316
|
+
const indexPath = join10(this.options.outputDir, subfolder, "index.ts");
|
|
3317
|
+
await writeFile4(indexPath, indexContent, "utf-8");
|
|
3318
|
+
generatedFiles.push({
|
|
3319
|
+
path: indexPath,
|
|
3320
|
+
filename: `${subfolder}/index.ts`,
|
|
3321
|
+
content: indexContent,
|
|
3322
|
+
exports: subfolderResults.flatMap((r) => r.exports)
|
|
3323
|
+
});
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
generateSubfolderIndex(results, subfolder) {
|
|
3327
|
+
const lines = [];
|
|
3328
|
+
lines.push("/**");
|
|
3329
|
+
lines.push(` * ${subfolder.charAt(0).toUpperCase() + subfolder.slice(1)} Index`);
|
|
3330
|
+
lines.push(" * ");
|
|
3331
|
+
lines.push(" * Auto-generated exports for all types in this subfolder.");
|
|
3332
|
+
lines.push(" */");
|
|
3333
|
+
lines.push("");
|
|
3334
|
+
if (results.length === 0) {
|
|
3335
|
+
lines.push("// No types in this category");
|
|
3336
|
+
lines.push("export {};");
|
|
3337
|
+
return lines.join(`
|
|
3338
|
+
`);
|
|
3339
|
+
}
|
|
3340
|
+
for (const result of results) {
|
|
3341
|
+
const baseName = result.filename.replace(`${subfolder}/`, "").replace(".ts", "");
|
|
3342
|
+
if (result.exports.length > 0) {
|
|
3343
|
+
if (result.exports.length === 1) {
|
|
3344
|
+
lines.push(`export type { ${result.exports[0]} } from './${baseName}';`);
|
|
3345
|
+
} else {
|
|
3346
|
+
lines.push(`export type {`);
|
|
3347
|
+
for (let i = 0;i < result.exports.length; i++) {
|
|
3348
|
+
const exportName = result.exports[i];
|
|
3349
|
+
const isLast = i === result.exports.length - 1;
|
|
3350
|
+
lines.push(` ${exportName}${isLast ? "" : ","}`);
|
|
3351
|
+
}
|
|
3352
|
+
lines.push(`} from './${baseName}';`);
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
return lines.join(`
|
|
3357
|
+
`);
|
|
3358
|
+
}
|
|
3359
|
+
formatTypeName(name) {
|
|
3360
|
+
if (this.options.namingConvention === "PascalCase") {
|
|
3361
|
+
return toPascalCase(name);
|
|
3362
|
+
}
|
|
3363
|
+
return name;
|
|
3364
|
+
}
|
|
3365
|
+
getType(identifier) {
|
|
3366
|
+
const primitiveType = PRIMITIVE_TYPE_MAP[identifier.name];
|
|
3367
|
+
if (primitiveType) {
|
|
3368
|
+
return { isPrimitive: true, value: primitiveType };
|
|
3369
|
+
}
|
|
3370
|
+
return { isPrimitive: false, value: this.formatTypeName(identifier.name) };
|
|
3371
|
+
}
|
|
3372
|
+
getBaseInterface(schema) {
|
|
3373
|
+
if (isTypeSchemaForResourceComplexTypeLogical(schema) && schema.base) {
|
|
3374
|
+
return this.getType(schema.base);
|
|
3375
|
+
}
|
|
3376
|
+
return null;
|
|
3377
|
+
}
|
|
3378
|
+
getFilename(identifier) {
|
|
3379
|
+
const name = toPascalCase(identifier.name);
|
|
3380
|
+
return `${name}.ts`;
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
|
|
3384
|
+
// src/api/builder.ts
|
|
3385
|
+
class APIBuilder {
|
|
3386
|
+
schemas = [];
|
|
3387
|
+
options;
|
|
3388
|
+
generators = new Map;
|
|
3389
|
+
progressCallback;
|
|
3390
|
+
cache;
|
|
3391
|
+
pendingOperations = [];
|
|
3392
|
+
typeSchemaGenerator;
|
|
3393
|
+
logger;
|
|
3394
|
+
typeSchemaConfig;
|
|
3395
|
+
constructor(options = {}) {
|
|
3396
|
+
this.options = {
|
|
3397
|
+
outputDir: options.outputDir || "./generated",
|
|
3398
|
+
verbose: options.verbose ?? false,
|
|
3399
|
+
overwrite: options.overwrite ?? true,
|
|
3400
|
+
validate: options.validate ?? true,
|
|
3401
|
+
cache: options.cache ?? true,
|
|
3402
|
+
typeSchemaConfig: options.typeSchemaConfig
|
|
3403
|
+
};
|
|
3404
|
+
this.typeSchemaConfig = options.typeSchemaConfig;
|
|
3405
|
+
this.logger = new Logger({
|
|
3406
|
+
component: "APIBuilder",
|
|
3407
|
+
level: this.options.verbose ? 0 : 1
|
|
3408
|
+
});
|
|
3409
|
+
if (this.options.cache) {
|
|
3410
|
+
this.cache = new TypeSchemaCache(this.typeSchemaConfig);
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
fromPackage(packageName, version) {
|
|
3414
|
+
this.logger.info(`Configuring generation from FHIR package`, { packageName, version: version || "latest" }, "fromPackage");
|
|
3415
|
+
const operation = this.loadFromPackage(packageName, version);
|
|
3416
|
+
this.pendingOperations.push(operation);
|
|
3417
|
+
return this;
|
|
3418
|
+
}
|
|
3419
|
+
fromFiles(...filePaths) {
|
|
3420
|
+
this.logger.info(`Configuring generation from TypeSchema files`, { count: filePaths.length, files: filePaths.slice(0, 5) }, "fromFiles");
|
|
3421
|
+
const operation = this.loadFromFiles(filePaths);
|
|
3422
|
+
this.pendingOperations.push(operation);
|
|
3423
|
+
return this;
|
|
3424
|
+
}
|
|
3425
|
+
fromSchemas(schemas) {
|
|
3426
|
+
this.logger.info(`Adding TypeSchemas to generation`, { count: schemas.length }, "fromSchemas");
|
|
3427
|
+
this.schemas = [...this.schemas, ...schemas];
|
|
3428
|
+
return this;
|
|
3429
|
+
}
|
|
3430
|
+
typescript(options = {}) {
|
|
3431
|
+
const typesOutputDir = `${this.options.outputDir}/types`;
|
|
3432
|
+
const generator = new TypeScriptAPIGenerator({
|
|
3433
|
+
outputDir: typesOutputDir,
|
|
3434
|
+
moduleFormat: options.moduleFormat || "esm",
|
|
3435
|
+
generateIndex: options.generateIndex ?? true,
|
|
3436
|
+
includeDocuments: options.includeDocuments ?? true,
|
|
3437
|
+
namingConvention: options.namingConvention || "PascalCase",
|
|
3438
|
+
includeExtensions: options.includeExtensions ?? false,
|
|
3439
|
+
includeProfiles: options.includeProfiles ?? false
|
|
3440
|
+
});
|
|
3441
|
+
this.generators.set("typescript", generator);
|
|
3442
|
+
this.logger.info("Configured TypeScript generator", {
|
|
3443
|
+
outputDir: typesOutputDir,
|
|
3444
|
+
moduleFormat: options.moduleFormat || "esm",
|
|
3445
|
+
generateValidators: options.generateValidators ?? true,
|
|
3446
|
+
generateGuards: options.generateGuards ?? true,
|
|
3447
|
+
generateBuilders: options.generateBuilders ?? false
|
|
3448
|
+
}, "typescript");
|
|
3449
|
+
return this;
|
|
3450
|
+
}
|
|
3451
|
+
onProgress(callback) {
|
|
3452
|
+
this.progressCallback = callback;
|
|
3453
|
+
return this;
|
|
3454
|
+
}
|
|
3455
|
+
outputTo(directory) {
|
|
3456
|
+
this.logger.info(`Setting output directory`, { directory, generatorCount: this.generators.size }, "outputTo");
|
|
3457
|
+
this.options.outputDir = directory;
|
|
3458
|
+
for (const generator of this.generators.values()) {
|
|
3459
|
+
if (generator.setOutputDir) {
|
|
3460
|
+
generator.setOutputDir(directory);
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3463
|
+
return this;
|
|
3464
|
+
}
|
|
3465
|
+
verbose(enabled = true) {
|
|
3466
|
+
this.options.verbose = enabled;
|
|
3467
|
+
return this;
|
|
3468
|
+
}
|
|
3469
|
+
validate(enabled = true) {
|
|
3470
|
+
this.options.validate = enabled;
|
|
3471
|
+
return this;
|
|
3472
|
+
}
|
|
3473
|
+
async generate() {
|
|
3474
|
+
const startTime = performance.now();
|
|
3475
|
+
const result = {
|
|
3476
|
+
success: false,
|
|
3477
|
+
outputDir: this.options.outputDir,
|
|
3478
|
+
filesGenerated: [],
|
|
3479
|
+
errors: [],
|
|
3480
|
+
warnings: [],
|
|
3481
|
+
duration: 0
|
|
3482
|
+
};
|
|
3483
|
+
await this.logger.info("Starting code generation", {
|
|
3484
|
+
outputDir: this.options.outputDir,
|
|
3485
|
+
pendingOperations: this.pendingOperations.length,
|
|
3486
|
+
generatorTypes: Array.from(this.generators.keys()),
|
|
3487
|
+
validate: this.options.validate,
|
|
3488
|
+
cache: this.options.cache
|
|
3489
|
+
}, "generate");
|
|
3490
|
+
try {
|
|
3491
|
+
this.reportProgress("Loading", 0, 4, "Loading TypeSchema data...");
|
|
3492
|
+
await this.resolveSchemas();
|
|
3493
|
+
await this.logger.info("Schemas resolved", { schemasCount: this.schemas.length }, "resolveSchemas");
|
|
3494
|
+
this.reportProgress("Validating", 1, 4, "Validating TypeSchema documents...");
|
|
3495
|
+
if (this.options.validate) {
|
|
3496
|
+
await this.logger.info("Starting schema validation", {}, "validate");
|
|
3497
|
+
await this.validateSchemas(result);
|
|
3498
|
+
await this.logger.info("Schema validation completed", { warnings: result.warnings.length }, "validate");
|
|
3499
|
+
}
|
|
3500
|
+
this.reportProgress("Generating", 2, 4, "Generating code...");
|
|
3501
|
+
await this.logger.info("Starting code generation", { generatorCount: this.generators.size }, "executeGenerators");
|
|
3502
|
+
await this.executeGenerators(result);
|
|
3503
|
+
this.reportProgress("Complete", 4, 4, "Generation completed successfully");
|
|
3504
|
+
result.success = result.errors.length === 0;
|
|
3505
|
+
await this.logger.info("Code generation completed", {
|
|
3506
|
+
success: result.success,
|
|
3507
|
+
filesGenerated: result.filesGenerated.length,
|
|
3508
|
+
errors: result.errors.length,
|
|
3509
|
+
warnings: result.warnings.length,
|
|
3510
|
+
duration: `${Math.round(result.duration)}ms`
|
|
3511
|
+
}, "generate");
|
|
3512
|
+
} catch (error) {
|
|
3513
|
+
await this.logger.error("Code generation failed", error instanceof Error ? error : new Error(String(error)), {
|
|
3514
|
+
outputDir: this.options.outputDir,
|
|
3515
|
+
schemasCount: this.schemas.length
|
|
3516
|
+
});
|
|
3517
|
+
result.errors.push(error instanceof Error ? error.message : String(error));
|
|
3518
|
+
result.success = false;
|
|
3519
|
+
} finally {
|
|
3520
|
+
result.duration = performance.now() - startTime;
|
|
3521
|
+
}
|
|
3522
|
+
return result;
|
|
3523
|
+
}
|
|
3524
|
+
async build() {
|
|
3525
|
+
await this.resolveSchemas();
|
|
3526
|
+
const results = {};
|
|
3527
|
+
for (const [type, generator] of this.generators.entries()) {
|
|
3528
|
+
if (generator.build) {
|
|
3529
|
+
results[type] = await generator.build(this.schemas);
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
return results;
|
|
3533
|
+
}
|
|
3534
|
+
reset() {
|
|
3535
|
+
this.schemas = [];
|
|
3536
|
+
this.generators.clear();
|
|
3537
|
+
this.progressCallback = undefined;
|
|
3538
|
+
return this;
|
|
3539
|
+
}
|
|
3540
|
+
getSchemas() {
|
|
3541
|
+
return [...this.schemas];
|
|
3542
|
+
}
|
|
3543
|
+
getGenerators() {
|
|
3544
|
+
return Array.from(this.generators.keys());
|
|
3545
|
+
}
|
|
3546
|
+
async loadFromPackage(packageName, version) {
|
|
3547
|
+
const generator = new TypeSchemaGenerator({
|
|
3548
|
+
verbose: this.options.verbose
|
|
3549
|
+
}, this.typeSchemaConfig);
|
|
3550
|
+
this.typeSchemaGenerator = generator;
|
|
3551
|
+
const schemas = await generator.generateFromPackage(packageName, version);
|
|
3552
|
+
this.schemas = [...this.schemas, ...schemas];
|
|
3553
|
+
if (this.cache) {
|
|
3554
|
+
this.cache.setMany(schemas);
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
async loadFromFiles(filePaths) {
|
|
3558
|
+
if (!this.typeSchemaGenerator) {
|
|
3559
|
+
this.typeSchemaGenerator = new TypeSchemaGenerator({
|
|
3560
|
+
verbose: this.options.verbose
|
|
3561
|
+
}, this.typeSchemaConfig);
|
|
3562
|
+
}
|
|
3563
|
+
const parser = new TypeSchemaParser({
|
|
3564
|
+
format: "auto",
|
|
3565
|
+
validate: this.options.validate
|
|
3566
|
+
});
|
|
3567
|
+
const schemas = await parser.parseFromFiles(filePaths);
|
|
3568
|
+
this.schemas = [...this.schemas, ...schemas];
|
|
3569
|
+
if (this.cache) {
|
|
3570
|
+
this.cache.setMany(schemas);
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
async resolveSchemas() {
|
|
3574
|
+
if (this.pendingOperations.length > 0) {
|
|
3575
|
+
await Promise.all(this.pendingOperations);
|
|
3576
|
+
this.pendingOperations = [];
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
async validateSchemas(_result) {
|
|
3580
|
+
return;
|
|
3581
|
+
}
|
|
3582
|
+
async executeGenerators(result) {
|
|
3583
|
+
const generatorCount = this.generators.size;
|
|
3584
|
+
let current = 0;
|
|
3585
|
+
for (const [type, generator] of this.generators.entries()) {
|
|
3586
|
+
this.reportProgress("Generating", 2 + current / generatorCount, 4, `Generating ${type}...`);
|
|
3587
|
+
try {
|
|
3588
|
+
const files = await generator.generate(this.schemas);
|
|
3589
|
+
result.filesGenerated.push(...files.map((f) => f.path || f.filename));
|
|
3590
|
+
} catch (error) {
|
|
3591
|
+
result.errors.push(`${type} generator failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
3592
|
+
}
|
|
3593
|
+
current++;
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
reportProgress(phase, current, total, message) {
|
|
3597
|
+
if (this.progressCallback) {
|
|
3598
|
+
this.progressCallback(phase, current, total, message);
|
|
3599
|
+
}
|
|
3600
|
+
if (this.options.verbose && message) {
|
|
3601
|
+
console.log(`[${phase}] ${message}`);
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
function createAPI(options) {
|
|
3606
|
+
return new APIBuilder(options);
|
|
3607
|
+
}
|
|
3608
|
+
async function generateTypesFromPackage(packageName, outputDir, options = {}) {
|
|
3609
|
+
return createAPI({
|
|
3610
|
+
outputDir,
|
|
3611
|
+
verbose: options.verbose,
|
|
3612
|
+
validate: options.validate
|
|
3613
|
+
}).fromPackage(packageName, options.version).typescript().generate();
|
|
3614
|
+
}
|
|
3615
|
+
async function generateTypesFromFiles(inputFiles, outputDir, options = {}) {
|
|
3616
|
+
return createAPI({
|
|
3617
|
+
outputDir,
|
|
3618
|
+
verbose: options.verbose,
|
|
3619
|
+
validate: options.validate
|
|
3620
|
+
}).fromFiles(...inputFiles).typescript().generate();
|
|
3621
|
+
}
|
|
3622
|
+
// src/config.ts
|
|
3623
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
3624
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
3625
|
+
import { resolve } from "node:path";
|
|
3626
|
+
var DEFAULT_CONFIG = {
|
|
3627
|
+
outputDir: "./generated",
|
|
3628
|
+
verbose: false,
|
|
3629
|
+
overwrite: true,
|
|
3630
|
+
validate: true,
|
|
3631
|
+
cache: true,
|
|
3632
|
+
typescript: {
|
|
3633
|
+
moduleFormat: "esm",
|
|
3634
|
+
generateIndex: true,
|
|
3635
|
+
includeDocuments: false,
|
|
3636
|
+
namingConvention: "PascalCase",
|
|
3637
|
+
strictMode: true,
|
|
3638
|
+
generateValidators: true,
|
|
3639
|
+
generateGuards: true,
|
|
3640
|
+
includeProfiles: true,
|
|
3641
|
+
includeExtensions: false,
|
|
3642
|
+
includeValueSets: true,
|
|
3643
|
+
includeCodeSystems: false,
|
|
3644
|
+
includeOperations: false,
|
|
3645
|
+
fhirVersion: "R4",
|
|
3646
|
+
resourceTypes: [],
|
|
3647
|
+
maxDepth: 10,
|
|
3648
|
+
generateBuilders: false,
|
|
3649
|
+
builderOptions: {
|
|
3650
|
+
includeValidation: true,
|
|
3651
|
+
includeFactoryMethods: true,
|
|
3652
|
+
includeInterfaces: true,
|
|
3653
|
+
generateNestedBuilders: true,
|
|
3654
|
+
includeHelperMethods: true,
|
|
3655
|
+
supportPartialBuild: true,
|
|
3656
|
+
includeJSDoc: true,
|
|
3657
|
+
generateFactories: true,
|
|
3658
|
+
includeTypeGuards: true,
|
|
3659
|
+
handleChoiceTypes: true,
|
|
3660
|
+
generateArrayHelpers: true
|
|
3661
|
+
},
|
|
3662
|
+
validatorOptions: {
|
|
3663
|
+
includeCardinality: true,
|
|
3664
|
+
includeTypes: true,
|
|
3665
|
+
includeConstraints: true,
|
|
3666
|
+
includeInvariants: false,
|
|
3667
|
+
validateRequired: true,
|
|
3668
|
+
allowAdditional: false,
|
|
3669
|
+
strictValidation: false,
|
|
3670
|
+
collectMetrics: false,
|
|
3671
|
+
generateAssertions: true,
|
|
3672
|
+
generatePartialValidators: true,
|
|
3673
|
+
optimizePerformance: true,
|
|
3674
|
+
includeJSDoc: true,
|
|
3675
|
+
generateCompositeValidators: true
|
|
3676
|
+
},
|
|
3677
|
+
guardOptions: {
|
|
3678
|
+
includeRuntimeValidation: true,
|
|
3679
|
+
includeErrorMessages: true,
|
|
3680
|
+
treeShakeable: true,
|
|
3681
|
+
targetTSVersion: "5.0",
|
|
3682
|
+
strictGuards: false,
|
|
3683
|
+
includeNullChecks: true,
|
|
3684
|
+
verbose: false
|
|
3685
|
+
}
|
|
3686
|
+
},
|
|
3687
|
+
typeSchema: {
|
|
3688
|
+
enablePersistence: true,
|
|
3689
|
+
cacheDir: ".typeschema-cache",
|
|
3690
|
+
maxAge: 24 * 60 * 60 * 1000,
|
|
3691
|
+
validateCached: true,
|
|
3692
|
+
forceRegenerate: false,
|
|
3693
|
+
shareCache: true,
|
|
3694
|
+
cacheKeyPrefix: ""
|
|
3695
|
+
},
|
|
3696
|
+
packages: [],
|
|
3697
|
+
files: [],
|
|
3698
|
+
$schema: "https://atomic-ehr.github.io/codegen/config-schema.json"
|
|
3699
|
+
};
|
|
3700
|
+
var CONFIG_FILE_NAMES = [
|
|
3701
|
+
"atomic-codegen.config.ts",
|
|
3702
|
+
"atomic-codegen.config.js",
|
|
3703
|
+
"atomic-codegen.config.json",
|
|
3704
|
+
".atomic-codegenrc",
|
|
3705
|
+
"atomic-codegen.json",
|
|
3706
|
+
".atomic-codegen.json",
|
|
3707
|
+
"codegen.config.json",
|
|
3708
|
+
"codegen.json"
|
|
3709
|
+
];
|
|
3710
|
+
|
|
3711
|
+
class ConfigValidator {
|
|
3712
|
+
validate(config) {
|
|
3713
|
+
const result = {
|
|
3714
|
+
valid: true,
|
|
3715
|
+
errors: [],
|
|
3716
|
+
warnings: []
|
|
3717
|
+
};
|
|
3718
|
+
if (!config || typeof config !== "object") {
|
|
3719
|
+
result.valid = false;
|
|
3720
|
+
result.errors.push({
|
|
3721
|
+
path: "root",
|
|
3722
|
+
message: "Configuration must be an object",
|
|
3723
|
+
value: config
|
|
3724
|
+
});
|
|
3725
|
+
return result;
|
|
3726
|
+
}
|
|
3727
|
+
const cfg = config;
|
|
3728
|
+
if (cfg.outputDir !== undefined && typeof cfg.outputDir !== "string") {
|
|
3729
|
+
result.errors.push({
|
|
3730
|
+
path: "outputDir",
|
|
3731
|
+
message: "outputDir must be a string",
|
|
3732
|
+
value: cfg.outputDir
|
|
3733
|
+
});
|
|
3734
|
+
}
|
|
3735
|
+
const booleanFields = ["verbose", "overwrite", "validate", "cache"];
|
|
3736
|
+
for (const field of booleanFields) {
|
|
3737
|
+
if (cfg[field] !== undefined && typeof cfg[field] !== "boolean") {
|
|
3738
|
+
result.errors.push({
|
|
3739
|
+
path: field,
|
|
3740
|
+
message: `${field} must be a boolean`,
|
|
3741
|
+
value: cfg[field]
|
|
3742
|
+
});
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
if (cfg.typescript !== undefined) {
|
|
3746
|
+
const tsErrors = this.validateTypeScriptConfig(cfg.typescript);
|
|
3747
|
+
result.errors.push(...tsErrors);
|
|
3748
|
+
}
|
|
3749
|
+
if (cfg.packages !== undefined) {
|
|
3750
|
+
if (!Array.isArray(cfg.packages)) {
|
|
3751
|
+
result.errors.push({
|
|
3752
|
+
path: "packages",
|
|
3753
|
+
message: "packages must be an array",
|
|
3754
|
+
value: cfg.packages
|
|
3755
|
+
});
|
|
3756
|
+
} else {
|
|
3757
|
+
cfg.packages.forEach((pkg, index) => {
|
|
3758
|
+
if (typeof pkg !== "string") {
|
|
3759
|
+
result.errors.push({
|
|
3760
|
+
path: `packages[${index}]`,
|
|
3761
|
+
message: "package name must be a string",
|
|
3762
|
+
value: pkg
|
|
3763
|
+
});
|
|
3764
|
+
}
|
|
3765
|
+
});
|
|
3766
|
+
}
|
|
3767
|
+
}
|
|
3768
|
+
if (cfg.files !== undefined) {
|
|
3769
|
+
if (!Array.isArray(cfg.files)) {
|
|
3770
|
+
result.errors.push({
|
|
3771
|
+
path: "files",
|
|
3772
|
+
message: "files must be an array",
|
|
3773
|
+
value: cfg.files
|
|
3774
|
+
});
|
|
3775
|
+
} else {
|
|
3776
|
+
cfg.files.forEach((file, index) => {
|
|
3777
|
+
if (typeof file !== "string") {
|
|
3778
|
+
result.errors.push({
|
|
3779
|
+
path: `files[${index}]`,
|
|
3780
|
+
message: "file path must be a string",
|
|
3781
|
+
value: file
|
|
3782
|
+
});
|
|
3783
|
+
}
|
|
3784
|
+
});
|
|
3785
|
+
}
|
|
3786
|
+
}
|
|
3787
|
+
result.valid = result.errors.length === 0;
|
|
3788
|
+
if (result.valid) {
|
|
3789
|
+
result.config = cfg;
|
|
3790
|
+
}
|
|
3791
|
+
return result;
|
|
3792
|
+
}
|
|
3793
|
+
validateTypeScriptConfig(config) {
|
|
3794
|
+
const errors = [];
|
|
3795
|
+
if (typeof config !== "object" || config === null) {
|
|
3796
|
+
errors.push({
|
|
3797
|
+
path: "typescript",
|
|
3798
|
+
message: "typescript config must be an object",
|
|
3799
|
+
value: config
|
|
3800
|
+
});
|
|
3801
|
+
return errors;
|
|
3802
|
+
}
|
|
3803
|
+
const cfg = config;
|
|
3804
|
+
if (cfg.moduleFormat !== undefined) {
|
|
3805
|
+
if (!["esm", "cjs"].includes(cfg.moduleFormat)) {
|
|
3806
|
+
errors.push({
|
|
3807
|
+
path: "typescript.moduleFormat",
|
|
3808
|
+
message: 'moduleFormat must be "esm" or "cjs"',
|
|
3809
|
+
value: cfg.moduleFormat
|
|
3810
|
+
});
|
|
3811
|
+
}
|
|
3812
|
+
}
|
|
3813
|
+
if (cfg.namingConvention !== undefined) {
|
|
3814
|
+
if (!["PascalCase", "camelCase"].includes(cfg.namingConvention)) {
|
|
3815
|
+
errors.push({
|
|
3816
|
+
path: "typescript.namingConvention",
|
|
3817
|
+
message: 'namingConvention must be "PascalCase" or "camelCase"',
|
|
3818
|
+
value: cfg.namingConvention
|
|
3819
|
+
});
|
|
3820
|
+
}
|
|
3821
|
+
}
|
|
3822
|
+
const booleanFields = [
|
|
3823
|
+
"generateIndex",
|
|
3824
|
+
"includeDocuments",
|
|
3825
|
+
"strictMode",
|
|
3826
|
+
"generateValidators",
|
|
3827
|
+
"generateGuards",
|
|
3828
|
+
"includeProfiles",
|
|
3829
|
+
"includeExtensions"
|
|
3830
|
+
];
|
|
3831
|
+
for (const field of booleanFields) {
|
|
3832
|
+
if (cfg[field] !== undefined && typeof cfg[field] !== "boolean") {
|
|
3833
|
+
errors.push({
|
|
3834
|
+
path: `typescript.${field}`,
|
|
3835
|
+
message: `${field} must be a boolean`,
|
|
3836
|
+
value: cfg[field]
|
|
3837
|
+
});
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3840
|
+
if (cfg.validatorOptions !== undefined) {
|
|
3841
|
+
const validatorErrors = this.validateValidatorOptions(cfg.validatorOptions);
|
|
3842
|
+
errors.push(...validatorErrors);
|
|
3843
|
+
}
|
|
3844
|
+
if (cfg.guardOptions !== undefined) {
|
|
3845
|
+
const guardErrors = this.validateGuardOptions(cfg.guardOptions);
|
|
3846
|
+
errors.push(...guardErrors);
|
|
3847
|
+
}
|
|
3848
|
+
return errors;
|
|
3849
|
+
}
|
|
3850
|
+
validateValidatorOptions(config) {
|
|
3851
|
+
const errors = [];
|
|
3852
|
+
if (typeof config !== "object" || config === null) {
|
|
3853
|
+
errors.push({
|
|
3854
|
+
path: "typescript.validatorOptions",
|
|
3855
|
+
message: "validatorOptions must be an object",
|
|
3856
|
+
value: config
|
|
3857
|
+
});
|
|
3858
|
+
return errors;
|
|
3859
|
+
}
|
|
3860
|
+
const cfg = config;
|
|
3861
|
+
const booleanFields = [
|
|
3862
|
+
"includeCardinality",
|
|
3863
|
+
"includeTypes",
|
|
3864
|
+
"includeConstraints",
|
|
3865
|
+
"includeInvariants",
|
|
3866
|
+
"validateRequired",
|
|
3867
|
+
"allowAdditional",
|
|
3868
|
+
"strictValidation",
|
|
3869
|
+
"collectMetrics",
|
|
3870
|
+
"generateAssertions",
|
|
3871
|
+
"generatePartialValidators",
|
|
3872
|
+
"optimizePerformance",
|
|
3873
|
+
"includeJSDoc",
|
|
3874
|
+
"generateCompositeValidators"
|
|
3875
|
+
];
|
|
3876
|
+
for (const field of booleanFields) {
|
|
3877
|
+
if (cfg[field] !== undefined && typeof cfg[field] !== "boolean") {
|
|
3878
|
+
errors.push({
|
|
3879
|
+
path: `typescript.validatorOptions.${field}`,
|
|
3880
|
+
message: `${field} must be a boolean`,
|
|
3881
|
+
value: cfg[field]
|
|
3882
|
+
});
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
return errors;
|
|
3886
|
+
}
|
|
3887
|
+
validateGuardOptions(config) {
|
|
3888
|
+
const errors = [];
|
|
3889
|
+
if (typeof config !== "object" || config === null) {
|
|
3890
|
+
errors.push({
|
|
3891
|
+
path: "typescript.guardOptions",
|
|
3892
|
+
message: "guardOptions must be an object",
|
|
3893
|
+
value: config
|
|
3894
|
+
});
|
|
3895
|
+
return errors;
|
|
3896
|
+
}
|
|
3897
|
+
const cfg = config;
|
|
3898
|
+
if (cfg.targetTSVersion !== undefined) {
|
|
3899
|
+
if (!["3.8", "4.0", "4.5", "5.0"].includes(cfg.targetTSVersion)) {
|
|
3900
|
+
errors.push({
|
|
3901
|
+
path: "typescript.guardOptions.targetTSVersion",
|
|
3902
|
+
message: 'targetTSVersion must be one of: "3.8", "4.0", "4.5", "5.0"',
|
|
3903
|
+
value: cfg.targetTSVersion
|
|
3904
|
+
});
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
const booleanFields = [
|
|
3908
|
+
"includeRuntimeValidation",
|
|
3909
|
+
"includeErrorMessages",
|
|
3910
|
+
"treeShakeable",
|
|
3911
|
+
"strictGuards",
|
|
3912
|
+
"includeNullChecks",
|
|
3913
|
+
"verbose"
|
|
3914
|
+
];
|
|
3915
|
+
for (const field of booleanFields) {
|
|
3916
|
+
if (cfg[field] !== undefined && typeof cfg[field] !== "boolean") {
|
|
3917
|
+
errors.push({
|
|
3918
|
+
path: `typescript.guardOptions.${field}`,
|
|
3919
|
+
message: `${field} must be a boolean`,
|
|
3920
|
+
value: cfg[field]
|
|
3921
|
+
});
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3924
|
+
return errors;
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
|
|
3928
|
+
class ConfigLoader {
|
|
3929
|
+
validator = new ConfigValidator;
|
|
3930
|
+
async autoload(workingDir = process.cwd()) {
|
|
3931
|
+
const configPath = await this.findConfigFile(workingDir);
|
|
3932
|
+
if (configPath) {
|
|
3933
|
+
return this.loadFromFile(configPath);
|
|
3934
|
+
}
|
|
3935
|
+
return { ...DEFAULT_CONFIG };
|
|
3936
|
+
}
|
|
3937
|
+
async loadFromFile(filePath) {
|
|
3938
|
+
try {
|
|
3939
|
+
let config;
|
|
3940
|
+
if (filePath.endsWith(".ts") || filePath.endsWith(".js")) {
|
|
3941
|
+
const absolutePath = resolve(filePath);
|
|
3942
|
+
const importResult = await import(absolutePath);
|
|
3943
|
+
config = importResult.default || importResult;
|
|
3944
|
+
} else {
|
|
3945
|
+
const content = await readFile8(filePath, "utf-8");
|
|
3946
|
+
config = JSON.parse(content);
|
|
3947
|
+
}
|
|
3948
|
+
const validation = this.validator.validate(config);
|
|
3949
|
+
if (!validation.valid) {
|
|
3950
|
+
const errorMessages = validation.errors.map((e) => `${e.path}: ${e.message}`).join(`
|
|
3951
|
+
`);
|
|
3952
|
+
throw new Error(`Configuration validation failed:
|
|
3953
|
+
${errorMessages}`);
|
|
3954
|
+
}
|
|
3955
|
+
return this.mergeWithDefaults(validation.config);
|
|
3956
|
+
} catch (error) {
|
|
3957
|
+
if (error instanceof Error) {
|
|
3958
|
+
throw new Error(`Failed to load config from ${filePath}: ${error.message}`);
|
|
3959
|
+
}
|
|
3960
|
+
throw error;
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
async findConfigFile(startDir) {
|
|
3964
|
+
for (const fileName of CONFIG_FILE_NAMES) {
|
|
3965
|
+
const configPath = resolve(startDir, fileName);
|
|
3966
|
+
if (existsSync2(configPath)) {
|
|
3967
|
+
return configPath;
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
return null;
|
|
3971
|
+
}
|
|
3972
|
+
mergeWithDefaults(userConfig) {
|
|
3973
|
+
return {
|
|
3974
|
+
...DEFAULT_CONFIG,
|
|
3975
|
+
...userConfig,
|
|
3976
|
+
typescript: {
|
|
3977
|
+
...DEFAULT_CONFIG.typescript,
|
|
3978
|
+
...userConfig.typescript
|
|
3979
|
+
}
|
|
3980
|
+
};
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
var configLoader = new ConfigLoader;
|
|
3984
|
+
async function loadConfig(workingDir) {
|
|
3985
|
+
return configLoader.autoload(workingDir);
|
|
3986
|
+
}
|
|
3987
|
+
function isConfig(obj) {
|
|
3988
|
+
const validator = new ConfigValidator;
|
|
3989
|
+
const result = validator.validate(obj);
|
|
3990
|
+
return result.valid;
|
|
3991
|
+
}
|
|
3992
|
+
export {
|
|
3993
|
+
loadConfig,
|
|
3994
|
+
isConfig,
|
|
3995
|
+
generateTypesFromPackage,
|
|
3996
|
+
generateTypesFromFiles,
|
|
3997
|
+
createAPI,
|
|
3998
|
+
configLoader,
|
|
3999
|
+
TypeScriptAPIGenerator,
|
|
4000
|
+
TypeSchemaParser,
|
|
4001
|
+
TypeSchemaGenerator,
|
|
4002
|
+
TypeSchemaCache,
|
|
4003
|
+
DEFAULT_CONFIG,
|
|
4004
|
+
ConfigValidator,
|
|
4005
|
+
ConfigLoader,
|
|
4006
|
+
CONFIG_FILE_NAMES,
|
|
4007
|
+
APIBuilder
|
|
4008
|
+
};
|