# Changelog

All notable changes to `@ejosterberg/opensalestax` are documented here.
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

## [0.3.0] — 2026-05-19

Minor bump: adopts the engine's top-level `shipping` field on
`POST /v1/calculate` (engine v0.59.0+ via the `shipping_first_class`
capability flag). Connectors can now express shipping amount + method
+ `separately_stated` + `is_handling_charge` directly instead of
constructing a synthetic `category: "shipping"` line item. The engine
applies per-state shipping-taxability rules (MN's "tax-if-items-
taxable" rule, MO/VA's "separately-stated" rule, MD's
shipping-vs-handling distinction, etc.) on its side. Backward
compatible — existing calls that omit `shipping` continue to send
the same body shape they always did.

### Added

- **`ShippingInput`** type — `amount` (required decimal string), optional
  `method`, `separatelyStated`, `isHandlingCharge`. Exported from
  the package root.
- **`CalculatedShipping`** type — `amount`, `taxAmount`, `ratePct`,
  optional `taxableReason`. Returned on the `shipping` field of
  `CalculationResult` when the calc request included a shipping
  segment.
- **Third argument on `client.calculate(address, lineItems, shipping?)`** —
  optional `ShippingInput`. Backward compatible: existing
  two-argument callers continue to work unchanged.
- **`CalculationResult.shipping`** — `CalculatedShipping | null`.
  `null` when the request omitted shipping OR the engine is older
  than v0.59.0 and ignored the field.
- **`CalculationResult.coverageWarning`** — `string | null`. Surfaces
  the engine v0.59.0+ coverage-warning text (when a calc hits a
  jurisdiction with incomplete rate data).

### Notes

- Engine v0.59.0+ required for the shipping field to actually
  be calculated. Older engines silently ignore the field; the
  response's `shipping` will be `null`. Connectors should
  flag-gate on `(await client.capabilities()).features.shippingFirstClass`
  if they need to detect this at setup time.
- Snapshot tests use the real engine response shape captured
  from `http://10.32.161.126:8080/v1/calculate` on 2026-05-18
  (MN $100 + $12.50 shipping → $9.0250 line tax + $1.1281
  shipping tax).

## [0.2.0] — 2026-05-18

Minor bump: adopts the engine's new `GET /v1/capabilities` endpoint
shipped in engine v0.59.0. Connectors that import this SDK can now
query engine capabilities at setup time + gate feature-flagged code
paths (first-class shipping, future record-back, etc.) on a single
fetch. Backward compatible — existing `health`/`states`/`rates`/`calculate`
APIs unchanged.

### Added

- **`OpenSalesTaxClient.capabilities()`** — `GET /v1/capabilities`;
  returns version + endpoint manifest + typed feature flags.
- **`OpenSalesTaxClient.capabilitiesCached()`** — same as above but
  memoized per-client-instance (single network round-trip for the
  lifetime of the client; suitable for setup-time checks).
- **`CapabilitiesResponse` / `CapabilityEndpoint` / `CapabilityFeatures`**
  TypeScript types exported from the package root.
- **`CapabilityFeatures.extras`** — preserves any future engine feature
  flags the SDK doesn't yet have typed slots for. Forward-compatible
  by design.
- **`MIN_ENGINE_VERSION`** constant (`"0.59.0"`) — exposed for
  connector code that wants to compare against the engine's reported
  version and surface a clear "engine too old" error at setup time.

## [0.1.0] — 2026-05-14

Initial public release. Extracted from the embedded HTTP clients in
the four TS connector repos (Medusa, Vendure, Saleor, Square) per the
constitution §6 / new-connector playbook trigger ("extract when a
third TS connector lands"; we have four).

### Added

- `OpenSalesTaxClient` — thin wrapper over the OpenSalesTax v1 HTTP API
  (`health`, `states`, `rates`, `calculate`, `close`, `healthCheck`)
- Full TypeScript types (camelCase at the public surface; snake_case ↔
  camelCase translation at the request / response boundary)
- Flat error hierarchy: `OpenSalesTaxError`, `OpenSalesTaxNetworkError`,
  `OpenSalesTaxAPIError`, `OpenSalesTaxValidationError`, `NonUSDError`
- SSRF defense via `validateEngineUrl()` (loopback / RFC-1918 / IPv6
  link-local detection, with `allowPrivate` opt-in)
- Injectable `fetch` implementation for tests + custom HTTP behavior
- Dual ESM + CJS publish via `package.json` `exports` map
- 81 Jest tests; 90%+ statement coverage, 91%+ branch coverage

### Notes

- Zero runtime dependencies. Uses built-in `fetch`, `URL`,
  `AbortController` (Node 20+).
- The `verify` option is reserved for parity with the Python SDK; to
  actually disable TLS verification, inject a custom `fetch` built
  on `undici`'s Agent with `rejectUnauthorized: false`. The SDK does
  not pull `undici` in directly to keep the dep tree empty.

[Unreleased]: https://github.com/ejosterberg/opensalestax-js/compare/v0.3.0...HEAD
[0.3.0]: https://github.com/ejosterberg/opensalestax-js/releases/tag/v0.3.0
[0.2.0]: https://github.com/ejosterberg/opensalestax-js/releases/tag/v0.2.0
[0.1.0]: https://github.com/ejosterberg/opensalestax-js/releases/tag/v0.1.0
