@atomic-ehr/codegen 0.0.1-canary.20251008121245.8324bc2 → 0.0.1-canary.20251008162621.549c5d8

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/dist/index.js CHANGED
@@ -1409,21 +1409,28 @@ var enrichFHIRSchema = (schema, packageMeta) => {
1409
1409
  base: schema.base
1410
1410
  };
1411
1411
  };
1412
- var isFhirSchemaBased = (schema) => {
1413
- return schema.identifier.kind !== "value-set";
1412
+ var isNestedIdentifier = (id) => {
1413
+ return id?.kind === "nested";
1414
1414
  };
1415
- var isSpecialization = (schema) => {
1416
- return schema.identifier.kind === "resource" || schema.identifier.kind === "complex-type" || schema.identifier.kind === "logical";
1415
+ var isProfileIdentifier = (id) => {
1416
+ return id?.kind === "profile";
1417
1417
  };
1418
- var isProfile = (schema) => {
1419
- return schema.identifier.kind === "profile";
1418
+ var isFhirSchemaBased = (schema) => {
1419
+ return schema?.identifier.kind !== "value-set";
1420
1420
  };
1421
- var isNotChoiceFieldDeclaration = (field) => {
1422
- return field.choices === void 0;
1421
+ var isSpecializationTypeSchema = (schema) => {
1422
+ return schema?.identifier.kind === "resource" || schema?.identifier.kind === "complex-type" || schema?.identifier.kind === "logical";
1423
+ };
1424
+ var isProfileTypeSchema = (schema) => {
1425
+ return schema?.identifier.kind === "profile";
1423
1426
  };
1424
1427
  function isBindingSchema(schema) {
1425
- return schema.identifier.kind === "binding";
1428
+ return schema?.identifier.kind === "binding";
1426
1429
  }
1430
+ var isNotChoiceDeclarationField = (field) => {
1431
+ if (!field) return false;
1432
+ return field.choices === void 0;
1433
+ };
1427
1434
 
1428
1435
  // src/typeschema/register.ts
1429
1436
  var registerFromManager = async (manager, logger) => {
@@ -2063,7 +2070,11 @@ function extractDependencies(identifier, base, fields, nestedTypes) {
2063
2070
  uniqDeps[dep.url] = dep;
2064
2071
  }
2065
2072
  const localNestedTypeUrls = new Set(nestedTypes?.map((nt) => nt.identifier.url));
2066
- const result = Object.values(uniqDeps).filter((e) => !(e.kind === "nested" && localNestedTypeUrls.has(e.url))).sort((a, b) => a.url.localeCompare(b.url));
2073
+ const result = Object.values(uniqDeps).filter((e) => {
2074
+ if (isProfileIdentifier(identifier)) return true;
2075
+ if (!isNestedIdentifier(e)) return true;
2076
+ return !localNestedTypeUrls.has(e.url);
2077
+ }).sort((a, b) => a.url.localeCompare(b.url));
2067
2078
  return result.length > 0 ? result : void 0;
2068
2079
  }
2069
2080
  function transformFhirSchemaResource(register, fhirSchema, logger) {
@@ -5030,6 +5041,7 @@ var Writer = class extends FileSystemWriter {
5030
5041
  if (tokens.length === 0) {
5031
5042
  this.write("\n");
5032
5043
  } else {
5044
+ this.writeIndent();
5033
5045
  this.write(`${tokens.join(" ")}
5034
5046
  `);
5035
5047
  }
@@ -5068,6 +5080,11 @@ var Writer = class extends FileSystemWriter {
5068
5080
  this.disclaimer().forEach((e) => this.comment(e));
5069
5081
  this.line();
5070
5082
  }
5083
+ indentBlock(gencontent) {
5084
+ this.indent();
5085
+ gencontent();
5086
+ this.deindent();
5087
+ }
5071
5088
  curlyBlock(tokens, gencontent, endTokens) {
5072
5089
  this.line(`${tokens.filter(Boolean).join(" ")} {`);
5073
5090
  this.indent();
@@ -5101,6 +5118,7 @@ var groupByPackages = (typeSchemas) => {
5101
5118
  };
5102
5119
  var collectComplexTypes = (tss) => tss.filter((t) => t.identifier.kind === "complex-type");
5103
5120
  var collectResources = (tss) => tss.filter((t) => t.identifier.kind === "resource");
5121
+ var collectProfiles = (tss) => tss.filter(isProfileTypeSchema);
5104
5122
  var resourceRelatives = (schemas) => {
5105
5123
  const regularSchemas = collectResources(schemas);
5106
5124
  const directPairs = [];
@@ -5154,7 +5172,9 @@ var mkTypeSchemaIndex = (schemas) => {
5154
5172
  if (base === void 0) break;
5155
5173
  const resolved = resolve2(base);
5156
5174
  if (!resolved) {
5157
- throw new Error(`Failed to resolve base type: ${JSON.stringify(base)}`);
5175
+ throw new Error(
5176
+ `Failed to resolve base type: ${res.map((e) => `${e.identifier.url} (${e.identifier.kind})`).join(", ")}`
5177
+ );
5158
5178
  }
5159
5179
  cur = resolved;
5160
5180
  }
@@ -5199,6 +5219,12 @@ var mkTypeSchemaIndex = (schemas) => {
5199
5219
  dependencies
5200
5220
  };
5201
5221
  };
5222
+ const isWithMetaField = (profile) => {
5223
+ return hierarchy(profile).filter(isSpecializationTypeSchema).some((schema) => {
5224
+ console.log(schema.fields?.meta);
5225
+ return schema.fields?.meta !== void 0;
5226
+ });
5227
+ };
5202
5228
  return {
5203
5229
  schemaIndex: index,
5204
5230
  relations,
@@ -5206,7 +5232,8 @@ var mkTypeSchemaIndex = (schemas) => {
5206
5232
  resourceChildren,
5207
5233
  hierarchy,
5208
5234
  findLastSpecialization,
5209
- flatProfile
5235
+ flatProfile,
5236
+ isWithMetaField
5210
5237
  };
5211
5238
  };
5212
5239
 
@@ -5270,6 +5297,8 @@ var tsResourceName = (id) => {
5270
5297
  };
5271
5298
  var tsFieldName = (n) => normalizeTsName(n);
5272
5299
  var normalizeTsName = (n) => {
5300
+ const tsKeywords = /* @__PURE__ */ new Set(["abstract", "any", "as", "async", "await", "boolean", "bigint", "break", "case", "catch", "class", "const", "constructor", "continue", "debugger", "declare", "default", "delete", "do", "else", "enum", "export", "extends", "extern", "false", "finally", "for", "function", "from", "get", "goto", "if", "implements", "import", "in", "infer", "instanceof", "interface", "keyof", "let", "module", "namespace", "never", "new", "null", "number", "object", "of", "override", "private", "protected", "public", "readonly", "return", "satisfies", "set", "static", "string", "super", "switch", "this", "throw", "true", "try", "type", "typeof", "unknown", "var", "void", "while"]);
5301
+ if (tsKeywords.has(n)) n = `${n}_`;
5273
5302
  return n.replace(/[- ]/g, "_");
5274
5303
  };
5275
5304
  var TypeScript = class extends Writer {
@@ -5295,19 +5324,30 @@ var TypeScript = class extends Writer {
5295
5324
  }
5296
5325
  generateDependenciesImports(schema) {
5297
5326
  if (schema.dependencies) {
5298
- const deps = [
5299
- ...schema.dependencies.filter((dep) => ["complex-type", "resource", "logical"].includes(dep.kind)).map((dep) => ({
5300
- tsPackage: `../${kebabCase(dep.package)}/${pascalCase(dep.name)}`,
5301
- name: uppercaseFirstLetter(dep.name)
5302
- })),
5303
- ...schema.dependencies.filter((dep) => ["nested"].includes(dep.kind)).map((dep) => ({
5304
- tsPackage: `../${kebabCase(dep.package)}/${pascalCase(canonicalToName(dep.url) ?? "")}`,
5305
- name: tsResourceName(dep)
5306
- }))
5307
- ].sort((a, b) => a.name.localeCompare(b.name));
5308
- for (const dep of deps) {
5327
+ const imports = [];
5328
+ const skipped = [];
5329
+ for (const dep of schema.dependencies) {
5330
+ if (["complex-type", "resource", "logical"].includes(dep.kind)) {
5331
+ imports.push({
5332
+ tsPackage: `../${kebabCase(dep.package)}/${pascalCase(dep.name)}`,
5333
+ name: uppercaseFirstLetter(dep.name)
5334
+ });
5335
+ } else if (isNestedIdentifier(dep)) {
5336
+ imports.push({
5337
+ tsPackage: `../${kebabCase(dep.package)}/${pascalCase(canonicalToName(dep.url) ?? "")}`,
5338
+ name: tsResourceName(dep)
5339
+ });
5340
+ } else {
5341
+ skipped.push(dep);
5342
+ }
5343
+ }
5344
+ imports.sort((a, b) => a.name.localeCompare(b.name));
5345
+ for (const dep of imports) {
5309
5346
  this.tsImportType(dep.tsPackage, dep.name);
5310
5347
  }
5348
+ for (const dep of skipped) {
5349
+ this.debugComment("skip:", dep);
5350
+ }
5311
5351
  this.line();
5312
5352
  }
5313
5353
  }
@@ -5350,7 +5390,7 @@ var TypeScript = class extends Writer {
5350
5390
  }
5351
5391
  const fields = Object.entries(schema.fields).sort((a, b) => a[0].localeCompare(b[0]));
5352
5392
  for (const [fieldName, field] of fields) {
5353
- if (!isNotChoiceFieldDeclaration(field)) continue;
5393
+ if (!isNotChoiceDeclarationField(field)) continue;
5354
5394
  this.debugComment(fieldName, ":", field);
5355
5395
  const tsName = tsFieldName(fieldName);
5356
5396
  const optionalSymbol = field.required ? "" : "?";
@@ -5389,13 +5429,14 @@ var TypeScript = class extends Writer {
5389
5429
  }
5390
5430
  generateProfileType(schema) {
5391
5431
  const name = tsResourceName(schema.identifier);
5392
- this.debugComment(schema.identifier);
5432
+ this.debugComment("identifier", schema.identifier);
5433
+ this.debugComment("base", schema.base);
5393
5434
  this.curlyBlock(["export", "interface", name], () => {
5394
5435
  this.lineSM(`__profileUrl: "${schema.identifier.url}"`);
5395
5436
  this.line();
5396
5437
  if (!isFhirSchemaBased(schema)) return;
5397
5438
  for (const [fieldName, field] of Object.entries(schema.fields ?? {})) {
5398
- if (!isNotChoiceFieldDeclaration(field)) continue;
5439
+ if (!isNotChoiceDeclarationField(field)) continue;
5399
5440
  this.debugComment(fieldName, field);
5400
5441
  const tsName = tsFieldName(fieldName);
5401
5442
  let tsType;
@@ -5406,10 +5447,10 @@ var TypeScript = class extends Writer {
5406
5447
  } else if (field.reference && field.reference.length > 0) {
5407
5448
  const specializationId = this.tsIndex.findLastSpecialization(schema.identifier);
5408
5449
  const specialization = this.tsIndex.resolve(specializationId);
5409
- if (specialization === void 0 || !isSpecialization(specialization))
5450
+ if (!isSpecializationTypeSchema(specialization))
5410
5451
  throw new Error(`Invalid specialization for ${schema.identifier}`);
5411
5452
  const sField = specialization.fields?.[fieldName];
5412
- if (sField === void 0 || !isNotChoiceFieldDeclaration(sField))
5453
+ if (sField === void 0 || !isNotChoiceDeclarationField(sField))
5413
5454
  throw new Error(`Invalid field declaration for ${fieldName}`);
5414
5455
  const sRefs = (sField.reference ?? []).map((e) => e.name);
5415
5456
  const references = field.reference.map((ref) => {
@@ -5432,32 +5473,129 @@ var TypeScript = class extends Writer {
5432
5473
  });
5433
5474
  this.line();
5434
5475
  }
5476
+ generateAttachProfile(flatProfile) {
5477
+ const tsBaseResourceName = tsResourceName(flatProfile.base);
5478
+ const tsProfileName = tsResourceName(flatProfile.identifier);
5479
+ const profileFields = Object.entries(flatProfile.fields || {}).filter(([_fieldName, field]) => {
5480
+ return field && isNotChoiceDeclarationField(field) && field.type !== void 0;
5481
+ }).map(([fieldName]) => tsFieldName(fieldName));
5482
+ this.curlyBlock(
5483
+ [
5484
+ `export const attach_${tsProfileName} =`,
5485
+ `(resource: ${tsBaseResourceName}, profile: ${tsProfileName}): ${tsBaseResourceName}`,
5486
+ "=>"
5487
+ ],
5488
+ () => {
5489
+ this.curlyBlock(["return"], () => {
5490
+ this.line("...resource,");
5491
+ this.curlyBlock(["meta:"], () => {
5492
+ this.line(`profile: ['${flatProfile.identifier.url}']`);
5493
+ }, [","]);
5494
+ profileFields.forEach((fieldName) => {
5495
+ this.line(`${fieldName}:`, `profile.${fieldName},`);
5496
+ });
5497
+ });
5498
+ }
5499
+ );
5500
+ this.line();
5501
+ }
5502
+ generateExtractProfile(flatProfile) {
5503
+ const tsBaseResourceName = tsResourceName(flatProfile.base);
5504
+ const tsProfileName = tsResourceName(flatProfile.identifier);
5505
+ const profileFields = Object.entries(flatProfile.fields || {}).filter(([_fieldName, field]) => {
5506
+ return isNotChoiceDeclarationField(field) && field.type !== void 0;
5507
+ }).map(([fieldName]) => fieldName);
5508
+ const specialization = this.tsIndex.resolve(this.tsIndex.findLastSpecialization(flatProfile.identifier));
5509
+ if (!isSpecializationTypeSchema(specialization))
5510
+ throw new Error(`Specialization not found for ${flatProfile.identifier.url}`);
5511
+ const shouldCast = {};
5512
+ this.curlyBlock(
5513
+ [
5514
+ `export const extract_${tsBaseResourceName} =`,
5515
+ `(resource: ${tsBaseResourceName}): ${tsProfileName}`,
5516
+ "=>"
5517
+ ],
5518
+ () => {
5519
+ profileFields.forEach((fieldName) => {
5520
+ const tsField = tsFieldName(fieldName);
5521
+ const pField = flatProfile.fields?.[fieldName];
5522
+ const rField = specialization.fields?.[fieldName];
5523
+ if (!isNotChoiceDeclarationField(pField) || !isNotChoiceDeclarationField(rField)) return;
5524
+ if (pField.required && !rField.required) {
5525
+ this.curlyBlock(
5526
+ [`if (resource.${tsField} === undefined)`],
5527
+ () => this.lineSM(
5528
+ `throw new Error("'${tsField}' is required for ${flatProfile.identifier.url}")`
5529
+ )
5530
+ );
5531
+ }
5532
+ const pRefs = pField?.reference?.map((ref) => ref.name);
5533
+ const rRefs = rField?.reference?.map((ref) => ref.name);
5534
+ if (pRefs && rRefs && pRefs.length !== rRefs.length) {
5535
+ const predName = `reference_pred_${tsField}`;
5536
+ this.curlyBlock(["const", predName, "=", "(ref?: Reference)", "=>"], () => {
5537
+ this.line("return !ref");
5538
+ this.indentBlock(() => {
5539
+ rRefs.forEach((ref) => {
5540
+ this.line(`|| ref.reference?.startsWith('${ref}/')`);
5541
+ });
5542
+ this.line(";");
5543
+ });
5544
+ });
5545
+ let cond = !pField?.required ? `!resource.${tsField} || ` : "";
5546
+ if (pField.array) {
5547
+ cond += `resource.${tsField}.every( (ref) => ${predName}(ref) )`;
5548
+ } else {
5549
+ cond += `${predName}(resource.${tsField})`;
5550
+ }
5551
+ this.curlyBlock(["if (", cond, ")"], () => {
5552
+ this.lineSM(
5553
+ `throw new Error("'${fieldName}' has different references in profile and specialization")`
5554
+ );
5555
+ });
5556
+ this.line();
5557
+ shouldCast[fieldName] = true;
5558
+ }
5559
+ });
5560
+ this.curlyBlock(["return"], () => {
5561
+ this.line(`__profileUrl: '${flatProfile.identifier.url}',`);
5562
+ profileFields.forEach((fieldName) => {
5563
+ const tsField = tsFieldName(fieldName);
5564
+ if (shouldCast[fieldName]) {
5565
+ this.line(`${tsField}:`, `resource.${tsField} as ${tsProfileName}['${tsField}'],`);
5566
+ } else {
5567
+ this.line(`${tsField}:`, `resource.${tsField},`);
5568
+ }
5569
+ });
5570
+ });
5571
+ }
5572
+ );
5573
+ }
5435
5574
  generateResourceModule(schema) {
5436
5575
  this.cat(`${tsModuleFileName(schema.identifier)}`, () => {
5437
5576
  this.generateDisclaimer();
5438
- if (["complex-type", "resource", "logical", "nested"].includes(schema.identifier.kind)) {
5577
+ if (["complex-type", "resource", "logical"].includes(schema.identifier.kind)) {
5439
5578
  this.generateDependenciesImports(schema);
5440
5579
  this.generateComplexTypeReexports(schema);
5441
5580
  this.generateNestedTypes(schema);
5442
5581
  this.generateType(schema);
5443
- } else if (isProfile(schema)) {
5582
+ } else if (isProfileTypeSchema(schema)) {
5444
5583
  const flatProfile = this.tsIndex.flatProfile(schema);
5445
- this.debugComment(flatProfile.dependencies);
5446
5584
  this.generateDependenciesImports(flatProfile);
5447
5585
  this.generateProfileType(flatProfile);
5448
- } else {
5449
- throw new Error(`Profile generation not implemented for kind: ${schema.identifier.kind}`);
5450
- }
5586
+ this.generateAttachProfile(flatProfile);
5587
+ this.generateExtractProfile(flatProfile);
5588
+ } else throw new Error(`Profile generation not implemented for kind: ${schema.identifier.kind}`);
5451
5589
  });
5452
5590
  }
5453
5591
  generate(schemas) {
5592
+ this.tsIndex = mkTypeSchemaIndex(schemas);
5454
5593
  const typesToGenerate = [
5455
5594
  ...collectComplexTypes(schemas),
5456
- ...collectResources(schemas)
5595
+ ...collectResources(schemas),
5457
5596
  // ...collectLogicalModels(schemas),
5458
- // ...collectProfiles(schemas),
5597
+ ...collectProfiles(schemas).filter((p) => this.tsIndex.isWithMetaField(p))
5459
5598
  ];
5460
- this.tsIndex = mkTypeSchemaIndex(typesToGenerate);
5461
5599
  const grouped = groupByPackages(typesToGenerate);
5462
5600
  this.cd("/", () => {
5463
5601
  for (const [packageName, packageSchemas] of Object.entries(grouped)) {