# API Versioning

We currently have three API endpoints:

1. Create customer (CUSTOMER_PATH) is invoked via the command line.
1. License status check (LICENSE_STATUS_PATH) is invoked at Replicache startup.
1. License active ping (LICENSE_ACTIVE_PATH) is invoked at Replicache startup and periodically.

It seems clear that we want to do whatever we can to support old versions of the status check and active ping, otherwise we force customers to use a new version of Replicache.

Regarding the create customer endpoint used via CLI: it's tempting to say that customers always use the latest `get-license` code because they run `npx replicache get-license`, and therefore we could aggressively deprecate old create customer api versions. However `npx` is hard to understand and will go to the project's `node_modules` for the CLI ahead of the most recent npm package unless the customer passes extra CLI flags, and I don't think we want to count on that. So we just assume that a customer uses the same code (Replicache version) to access all our endpoints.

## Goals

We want to:

- know how to make backward compatible and incompatible API changes
- signal to the customer that they will soon need to or need to upgrade to a new version of Replicache because of licensing API changes (which we hope to do ~never)
- enable the licensing server to know whether the client is speaking a comprehensible version of the API (e.g., the client could be too new or too old)
- don't spend too much time or invest too much in machinery for this problem

## API Version

The client has a single MAJOR.MINOR API version that it encodes in the URL of API requests (e.g., `/api/1.2/customer/create`). Major version changes are not forward/backward compatible, minor version changes are.

Sticking a single API version in the URL has the ick that a new major version for a specific endpoint applies across all endpoints, which means wiring them up busywork. But the alternative of having per-endpoint versions just seems like more complexity than we need.

## Request and Response Types

Within a major version, changes to request and response types must be _additive_. That is, within a major version, we don't change the name, type, or semantics of existing fields. Instead, we create new fields or values and add corresponding logic to acommodate clients that don't know how to send or receive them yet.

When it comes to request and response fields, I'm generally a "required considered harmful" guy because required fields frequently become not-required over time, but it is nice to have type validation we can count on. So we do use required fields, but with the following constraints so as not to break client or server:

- within a major version _request_, a required field can become optional but an optional field must not become required.
- within a major version _response_, a required field must stay required and an optional field must stay optional

Request and response types are given in [api-types.ts](server/api-types.ts). These types are validated in the client using arv's lightweight assertions and validated via zod on the server (zod schema generated by `npm run gen-schema`).

## HOWTO Make Changes

HOWTO change a **request** in a **minor version**:

- _add an optional field_: add the optional field to the type and accommodate the potentially absent field in the API endpoint
- _add a required field_: you are a bad person, don't do this
- _replace/rename a field_: add a new optional field and accommodate both the old and new fields in the endpoint. Add a comment that the old field is deprecated so it can be removed in the next major version.
- _remove a field_: do not remove the field :) If the field is required, make it optional, stop sending the field, and accommodate its potential absence in the endpoint. Add a comment that the field is deprecated so it can be removed in the next major version.
- _change the type or semantics of a field_: don't do this, instead replace the field per above
- _make a required field optional_: go for it
- _make an optional field required_: you are a bad person, don't do this
- _add an enum value_: go for it, accommodating in the code the fact that some clients won't know how to send it

HOWTO change a **response** in a **minor version**:

- _add an optional field_: go for it
- _add a required field_: don't do this
- _replace/rename a field_: add a new optional field, send both, and use the new field in the client. Add a comment that the old field is deprecated so it can be removed in the next major version.
- _remove a required field_: don't do this, we need to keep sending it. Comment the field as to be removed in the next major version.
- _remove an optional field_: technically you can do this but better for clarity if you just stop sending it and don't actually remove it from the type (future readers won't know that some clients expect it). Add a comment to remove it in the next major version.
- _change the type or semantics of a field_: don't do this, instead replace the field per above
- _make a required field optional_: don't do this
- _make an optional field required_: don't do this
- _add an enum value_: if it can be done safetly wrt the client, go for it. Be sure to accommodate in the server code the fact that some clients won't know what to do with it, and ensure that the client type check on the enum asserts a string instead of a specific subset of strings (which would of course fail with the new value).

Also, obviously, when you make a change above:

- bump the minor version
- `npm run gen-schema`
- manually add new type checks to client assertions :(

For major versions, go crazy with changes, but be be careful about what you require as required fields have a habit of becoming optional over time.

## Alerting the customer to upgrade

We'd really love to never do this, but it's possible we might need to turn down an old API version. We need customers to upgrade to a newer Replicache
in order to do this. The simplest possible thing here I think is to just return a bit in the status check that logs an `ERROR` in Replicache telling the customer to upgrade. We'd set this bit for calls to the old API version we want to turn down soon. If we then turn the api version down Replicache will get and log an error trying to do its status check but will continue to work. ¯\\_(ツ)_/¯ a problem for another day.

We don't do anything special in the `get-license` command or the active ping to tell the customer to upgrade.
