

# Type safety [#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 [#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 [#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](/concepts/money-and-branded-values)
for checked arithmetic and error handling.

## Runtime schema decoding [#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](https://github.com/crcorbett/taxkit/blob/a151e51e8a30247526fa93412df046955846eca4/packages/docs-examples/src/validate-external-input.ts).
See [Handle validation errors](/guides/handle-validation-errors) for
handling this error separately from calculation failures.

## Full run typing [#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.

## Related pages [#related-pages]

* [Schemas](/sdk/schemas)
* [Plain SDK](/sdk/plain-sdk)
* [Safe SDK](/sdk/safe-sdk)
