Type safety

TaxKit uses TypeScript types and runtime schemas together. TypeScript checks the calculator descriptor and facts you pass at compile time. Runtime schema decoding still validates untrusted values before calculation.

Compile-time calculator inputs

The selected descriptor controls the accepted facts and the returned report:

ts
import { Cents, aud } from "@taxkit/core/primitives";
import { TaxKit } from "@taxkit/sdk";
import { au } from "@taxkit/sdk/au";

const report = await TaxKit.calculate(au.calculations.annualIncomeTax, {
  taxableIncome: aud(Cents.make(9_000_000)),
});

report.liability.cents;

If you pass facts for another calculator, the call should fail type checking:

ts
import { Cents, aud } from "@taxkit/core/primitives";
import { TaxKit } from "@taxkit/sdk";
import { au } from "@taxkit/sdk/au";

await TaxKit.calculate(au.calculations.annualIncomeTax, {
  grossPay: aud(Cents.make(165_400)),
  taxFreeThresholdClaimed: true,
});

Branded values

Money values use canonical branded constructors from @taxkit/core:

ts
import { Cents, aud } from "@taxkit/core/primitives";

const taxableIncome = aud(Cents.make(9_000_000));

Use Cents.make for known constants and aud for cents you have already checked. Use the fallible audFromCents helper when a number still needs checking. See Money and branded values for checked arithmetic and error handling.

Runtime schema decoding

Compile-time checks protect code you compile. Runtime schema decoding protects values that come from users, forms, JSON or other services.

Decode external JSON with the selected calculator's input schema before calling the typed SDK. In this example, annual tax facts fail the pay calculator's schema check; the valid weekly pay input reaches calculation.

ts
import { au } from "@taxkit/sdk/au";
import { Effect, Schema } from "effect";

// Check an external JSON representation before calling a typed calculator.
export const invalidInput = Schema.decodeEffect(
  Schema.fromJsonString(au.calculations.takeHomePay.inputSchema)
)('{"taxableIncome":{"_tag":"Money","cents":9000000,"currency":"AUD"}}');

export const validInput = Schema.decodeEffect(
  Schema.fromJsonString(au.calculations.takeHomePay.inputSchema)
)(
  '{"grossPay":{"_tag":"GrossPay","amount":{"_tag":"Money","cents":165400,"currency":"AUD"},"period":"weekly"},"taxFreeThresholdClaimed":true}'
).pipe(
  Effect.flatMap((input) => Effect.promise(() => au.pay.takeHomePay(input)))
);

These values are Effect programs for your application's runner. invalidInput fails with SchemaError before the SDK is called. The complete example is checked in the example source. See Handle validation errors for handling this error separately from calculation failures.

Full run typing

Effect consumers can preserve the full CalculatorRunResponse shape while narrowing report to the selected descriptor output:

ts
import type { SdkCalculatorRunResponse } from "@taxkit/sdk/effect";
import type { CalculatorRunResponse } from "@taxkit/sdk/schemas";

SdkCalculatorRunResponse<Report> keeps the canonical response shape and narrows report to the decoded report type.