Role. You are a senior TypeScript test engineer. Your goal is to raise
@fhir-dsl/core — the fhir-dsl query-builder DSL — to production-grade
coverage using vitest. No source changes. No new runtime dependencies.
fhir-dsl is a type-safe FHIR query builder + code generator monorepo
inspired by Kysely. @fhir-dsl/core is the DSL core: an immutable
FhirClient, fluent search/read builders, transactions/batches, auth, and
runtime validation via Standard Schema V1. Every builder method returns a new
instance — immutability is a load-bearing invariant.
Public surface (re-exported from packages/core/src/index.ts):
createFhirClient(config)→FhirClient<Schema>(fhir-client.ts).SearchQueryBuilder/ReadQueryBuilderinterfaces (query-builder.ts) and their impls (search-query-builder.ts,read-query-builder.ts).TransactionBuilder,BatchBuilder(transaction-builder.ts).CompiledQuery(compiled-query.ts), HTTP helpers (http.ts),AuthConfig(auth.ts),FhirSchema+ selection helpers (types.ts).SchemaRegistry,ValidationError,ValidationUnavailableError,validateOne,resolveSchema,profileSlugFromUrl(validation.ts).
packages/core/src/index.tspackages/core/src/fhir-client.tspackages/core/src/search-query-builder.tspackages/core/src/read-query-builder.tspackages/core/src/transaction-builder.tspackages/core/src/validation.tspackages/core/src/types.ts(selection / profile inference types)- All existing
*.test.tsinpackages/core/src/
fhir-client.test.ts— createClient + baseUrl handling.search-query-builder.test.ts— where, sort, include, revInclude, compile, execute.read-query-builder.test.ts— compile/execute.transaction-builder.test.ts— bundle assembly.auth.test.ts— auth header application.validate.test.ts—.validate()chain on search + read.select.test.ts/select.test-d.ts— selection runtime + type narrowing.types.test.ts— type-level assertions on registry shapes.
Fill gaps, don't re-assert the above.
Write tests in packages/core/test/. Use *.test-d.ts for compile-only
type assertions (picked up by vitest's typecheck integration).
- baseUrl normalization. Trailing slash present / absent,
/fhirsuffix present, path segments inbaseUrl(e.g.https://host/fhir/r4) — all produce a correct absolute request URL with no doubled slashes. - Header merge.
config.headersmerges with the defaultAccept: application/fhir+jsonandContent-Typewithout clobbering user overrides (user wins on key collision). config.fetchoverride. A custom fetch is used when provided; defaultglobalThis.fetchis used otherwise.- Auth application.
bearer,basic, customprovider— each attaches the rightAuthorizationvalue. A provider that throws surfaces that error to the caller. - Search
.where(...)matrix. For each search-paramtype(string,token,date,number,quantity,reference,uri,composite,special) — the correct prefixes are accepted, and the URL encodessystem|codefor tokens,ge/leetc. for dates. - Search chain immutability. Every chain method returns a new builder; the original is unchanged, and two builders branched off a common root do not share state (mutating one does not affect the other).
- Order independence of chain methods.
.where(...).sort(...)produces the same compiled URL as.sort(...).where(...). - Search includes / revIncludes. Multiple
.include()calls accumulate; duplicates deduplicate;_includevs_revincluderender with the right prefix. - Execute result shape.
.execute()returns{ data, included, total, links, raw }.datacontains entries withsearch.mode !== "include";includedholdssearch.mode === "include".totalandlinkscome from the Bundle. .stream()pages via thenextlink until it's absent, yields each resource once, and honors anAbortSignal.read.execute()hitsGET <baseUrl>/<type>/<id>exactly once.- Transaction / batch. Each adds entries with the correct
request.methodandfullUrl;.execute()POSTs the bundle withtype: "transaction"/"batch". .validate()on builders. (Already tested invalidate.test.ts.) Extend to: streaming validates each yielded resource; profile dispatch with a slug that isn't in the registry throwsValidationErrorwhose message names the missing slug.ValidationErrorfields.resourceType,index,issues[].path,issues[].messageare all populated as documented.profileSlugFromUrl— handles canonical, versioned (|4.0.1), and malformed inputs defensively.
Put these in packages/core/test/*.test-d.ts:
ApplySelection<Patient, ["name", "gender"]>keeps only those fields andresourceType.ResolveProfile<Schema, "Patient", "us-core-patient">resolves to the profile type when the profile exists, and to the base resource otherwise.SearchParamFor<Schema, "Patient">only autocompletes Patient's search params (not globally-defined ones bound to other resources).IncludeFor<Schema, "Patient">andRevIncludeFor<Schema, "Patient">contain the right canonical strings..where("nope", ...)on a resource that lacks that param fails to compile (use@ts-expect-error).
- FHIR Search spec: https://www.hl7.org/fhir/R4/search.html (search parameter
types, prefixes, modifiers,
_include,_revinclude,_has, chaining). - Standard Schema V1 spec: https://standardschema.dev/ — especially the
~standard.validateresult shape and issue format. - FHIR Bundle / transaction semantics:
https://www.hl7.org/fhir/R4/bundle.html — request.method, fullUrl rules,
type: "transaction"vs"batch". - Fetch
AbortSignalsemantics: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal.
- vitest
globals: true; keep imports explicit if it matches existing style. - No network. Pass a custom
fetchmock viaconfig.fetchand assert on the captured calls. - No snapshot tests without a paired semantic assertion.
- Use
@ts-expect-errorfor negative type cases in*.test-d.ts.
- Read the listed source and existing test files.
- Write behavioral tests in
packages/core/test/*.test.tsand type-level tests inpackages/core/test/*.test-d.ts. - Gates:
pnpm test pnpm lint pnpm -r typecheck - Iterate until green.
- Every scenario above has at least one
it(...)orexpectTypeOfassertion. - No source file under
packages/core/src/is modified. - All three gates green.
- Refactoring the builders or the type algebra.
- Adding new auth types.
- Tests that hit a real FHIR server.