Skip to content

TS Profile: duplicate meta object literal key when meta is a factory param (TS1117) #137

@sussdorff

Description

@sussdorff

Summary

When a profile exposes meta as a required factory param (e.g. profiles that pin meta.profile with min: 1, such as the German KBV ITA FOR/ERP profiles), the generated createResource emits two meta: keys in the same object literal. This produces a TypeScript TS1117: An object literal cannot have multiple properties with the same name error and silently discards any caller-supplied meta.tag / meta.source / extra profile URLs, because the second key overwrites the first.

Repro

Any profile where the generated …ProfileRaw includes meta: Meta (i.e. meta ends up in ProfileFactoryInfo.params). In the wild: any KBV FOR/ERP profile in kbv.ita.for@1.3.1 or kbv.ita.erp@1.4.1, e.g. KBV_PR_ERP_Medication_FreeText, KBV_PR_FOR_Patient, KBV_PR_FOR_Organization.

Generated output (versions 0.0.10, 0.0.11, 0.0.12):

static createResource (args: KBV_PR_ERP_Medication_FreeTextProfileRaw) : Medication {
    const resource: Medication = {
        resourceType: "Medication",
        code: args.code,
        id: args.id,
        meta: args.meta,                                                         // <-- #1
        meta: { profile: [KBV_PR_ERP_Medication_FreeTextProfile.canonicalUrl] }, // <-- #2 (overwrites #1)
    }
    return resource;
}

tsc --noEmit reports:

error TS1117: An object literal cannot have multiple properties with the same name.

Impact

  • tsc breaks on every affected profile. In our project (mira-adapters) this hit 9 generated profile files across kbv-ita-erp / kbv-ita-for.
  • Even if the duplicate were ignored, the semantics are wrong: the second meta literal drops args.meta.tag, args.meta.source, args.meta.security, and any extra profile URLs the caller included.

Root cause

src/api/writer-generator/typescript/profile.ts:

  1. collectProfileFactoryInfo puts every required field into params (via the direct scan and collectBaseRequiredParams). Profiles that pin meta.profile cause meta to be required on the base Resource element, so meta lands in params.
  2. allFields (line 371-375) unpacks params into { name, value: "args." + name } entries.
  3. The createResource writer loops over allFields and emits each as name: value, — producing meta: args.meta,.
  4. Immediately afterwards, when hasMeta is true, the writer unconditionally emits meta: { profile: [canonicalUrl] }, — the second key.

This affects both emission branches (the Input-helper branch around line 491-500, and the standard branch around line 533-540).

Suggested fix

When meta is present in allFields, skip the generic meta: args.meta emission, and merge in the profile URL so the caller-supplied fields are preserved:

const hasMetaParam = allFields.some((f) => f.name === "meta");
// ...in the resource literal loop...
for (const f of allFields) {
    if (f.name === "meta" && hasMeta) continue;  // <-- skip, handled below
    w.line(`${f.name}: ${f.value},`);
}
if (hasMeta) {
    if (hasMetaParam) {
        w.line(`meta: { ...args.meta, profile: [...(args.meta?.profile ?? []), ${profileClassName}.canonicalUrl] },`);
    } else {
        w.line(`meta: { profile: [${profileClassName}.canonicalUrl] },`);
    }
}

After applying this, the same profile regenerates as:

static createResource (args: KBV_PR_ERP_Medication_FreeTextProfileRaw) : Medication {
    const resource: Medication = {
        resourceType: "Medication",
        code: args.code,
        id: args.id,
        meta: { ...args.meta, profile: [...(args.meta?.profile ?? []), KBV_PR_ERP_Medication_FreeTextProfile.canonicalUrl] },
    }
    return resource;
}

This preserves caller meta.tag / meta.source / extra profile URLs and keeps from() / apply() happy because canonicalUrl is still guaranteed to be in meta.profile.

PR

Fix branch pushed to our fork: https://github.com/cognovis/codegen/tree/fix/profile-duplicate-meta-key (1 file changed, 18 insertions, 2 deletions). Happy to open a PR against main — will cross-link once done.

Versions affected

0.0.10, 0.0.11, 0.0.12 (tip of main). We stayed on 0.0.9 by manually hand-patching the 9 generated files after each regeneration — the real fix belongs here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions