Skip to content

Refactor TypeScript profile codegen: typed helpers, reduced casts, resolved references#101

Merged
ryukzak merged 6 commits intomainfrom
ref/ts-profile-clean
Mar 6, 2026
Merged

Refactor TypeScript profile codegen: typed helpers, reduced casts, resolved references#101
ryukzak merged 6 commits intomainfrom
ref/ts-profile-clean

Conversation

@ryukzak
Copy link
Collaborator

@ryukzak ryukzak commented Mar 5, 2026

  • Refactor generated profile classes to use shared profile-helpers.ts runtime asset
    • Extract slice match objects to class-level static constants
    • Replace inline slice logic with generic helpers (setArraySlice, getArraySlice, ensureSliceDefaults)
    • Refactor validate to declarative spread array with unified (res, profileName, ...) signature
    • Rename helpers for clarity: stripMatchKeys, unwrapSliceChoice, ensurePath, ensureProfile
  • Resolve profile references to base resource types in params and validate
  • Fix CCDA profile generation: hasMeta check, base required fields, primitive types, reference variance
  • Add JSDoc documentation to profile-helpers.ts and section comments to generated classes
  • Clean up README: update TOC, feature table, inline footnote, fix headings

Slice setters — match literals were duplicated in every method; now shared via static constants, reducing generated code size and making discriminators single-source-of-truth:

// before
public setSystolicBP (input?: ...): this {
    const match = {"code":{"coding":{"code":"8480-6","system":"http://loinc.org"}}} as Record<string, unknown>
    const value = applySliceMatch(wrapSliceChoice((input ?? {}) as Record<string, unknown>, "valueQuantity"), match) as unknown as ObservationComponent
    const list = (this.resource.component ??= [])
    const index = list.findIndex((item) => matchesSlice(item, match))
    if (index === -1) { list.push(value) } else { list[index] = value }
    return this
}

// after
public setSystolicBP (input?: ...): this {
    const match = observation_bpProfile.SystolicBPSliceMatch
    const wrapped = wrapSliceChoice<ObservationComponent>(input ?? {}, "valueQuantity")
    const value = applySliceMatch<ObservationComponent>(wrapped, match)
    setArraySlice(this.resource.component ??= [], match, value)
    return this
}

Validate — each helper had a different signature and mixed string | null returns with string[], making the generator emit inconsistent boilerplate; now all helpers share (res, profileName, field, ...) → string[] so the generator just spreads them:

// before
validate () : string[] {
    const errors: string[] = []
    const r = this.resource as unknown as Record<string, unknown>
    { const e = validateRequired(r, "status", "observation-bp"); if (e) errors.push(e) }
    { const e = validateEnum(r["status"], [...], "status", "observation-bp"); if (e) errors.push(e) }
    { const e = validateReference(r["subject"], ["Patient"], "subject", "observation-bp"); if (e) errors.push(e) }
    errors.push(...validateSliceCardinality(r["category"] as unknown[] | undefined, {...}, "VSCat", 1, 1, "observation-bp.category"))
    return errors
}

// after
validate(): string[] {
    const profileName = "observation-bp"
    const res = this.resource as unknown as Record<string, unknown>
    return [
        ...validateRequired(res, profileName, "status"),
        ...validateEnum(res, profileName, "status", ["registered","preliminary",...]),
        ...validateReference(res, profileName, "subject", ["Patient"]),
        ...validateSliceCardinality(res, profileName, "category", {...}, "VSCat", 1, 1),
    ]
}

createResource — slice default logic was duplicated per-slice with inline match literals and manual array checks; now a single ensureSliceDefaults call handles any number of slices:

// before
const categoryDefaults = [{"coding":{"code":"vital-signs",...}}] as unknown[]
const categoryWithDefaults = [...(args.category ?? [])] as unknown[]
if (!categoryWithDefaults.some(item => matchesSlice(item, {"coding":{"code":"vital-signs",...}} as Record<string, unknown>)))
    categoryWithDefaults.push(categoryDefaults[0]!)

// after
const categoryWithDefaults = ensureSliceDefaults(
    [...(args.category ?? [])],
    observation_bpProfile.VSCatSliceMatch,
)

🤖 Generated with Claude Code

ryukzak added 5 commits March 6, 2026 13:31
Extract profile-helpers.ts into a static asset copied into generated output.
Refactor validate helpers, extract slice match objects to static constants,
replace inline slice logic with generic helpers, resolve profile references
to base resource types, fix CCDA generation issues.
@ryukzak ryukzak force-pushed the ref/ts-profile-clean branch from 24a66ef to 1a58a26 Compare March 6, 2026 12:32
@ryukzak ryukzak merged commit b2014dd into main Mar 6, 2026
31 checks passed
@ryukzak ryukzak deleted the ref/ts-profile-clean branch March 6, 2026 12:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant