Skip to content

Migrating from the Samples Tree

If you have a .thymian/samples/ directory, this page is for you. The move is manual and one-time, and this page explains why, and exactly what to do.

It is gone, and nothing replaces it.

Request samples are now a projection of your API description, built in memory on every run. The tree existed so that samples could be found; the price was that they could also be stale, and everything downstream of that was a workaround:

The old problem Why it is gone
Generated request.json files went stale after a spec edit Nothing is generated to disk
sampler validate could flag a stale artifact but not fix it There is no artifact
sampler init --overwrite never deleted the files of removed operations There are no files
Hooks were orphaned at dead paths, because a hook’s directory was its identity A hook’s identity is its Selector, and hook files can live anywhere
Upgrading Thymian broke byte-exact sample validation The committed surface is types, compared canonicalized

thymian sampler check is unchanged and still there. It answers a different question from thymian test — “can these requests be executed at all?” rather than “do the responses conform?” — and it now needs no setup step either.

Your hooks are the part of the old tree that carries real investment, and a migration tool could not do the one thing that matters: decide what each hook is actually for.

In v1 a hook’s target was its position in the directory tree. A file at …/launches/[id]/@GET/create-launch.beforeEach.ts applied to everything below that directory — which might be one Transaction, or a status code’s worth, or every method on that path. The tree could not tell you which of those you meant, and neither can a script. In v2 you say so: one Selector, a list of them, or a filter.

So the migration is: for each old hook, decide what it was targeting, and write that down. That is a small amount of thinking per hook, once, and it is the thinking the compiler then keeps checking for you.

Terminal window
mkdir -p .thymian/sampler/hooks

Hook files can be anywhere under hooks/, at any nesting depth, named anything. The directory layout no longer means anything — it is for your benefit only.

Terminal window
npx thymian sampler show 'GET /launches -> 200 (application/json)'

A wrong Selector prints the nearest matches, which is the quickest way to discover the exact spelling of the ones you want.

The three v1 file-name conventions map like this:

v1 file v2
*.beforeEach.ts beforeEach(target, callback)
*.afterEach.ts afterEach(target, callback)
*.authorize.ts authorize(callback) global, or authorize(target, callback) targeted

And the position in the tree becomes the target:

The old file sat in… Write
a directory for one status and media type that one Selector
a @GET directory { method: 'GET', path: '…' }
a path directory, covering every method and status { path: '/launches' }
a path directory, covering the whole subtree { path: '/launches/**' } — note this excludes /launches itself
the source root, applying to everything a global authorize(callback), or a filter that says what you meant

Three other changes, in the code itself:

Hooks return nothing. A v1 hook returned the modified request; a v2 hook mutates in place.

const hook: BeforeEachRequestHook = async (request, context, utils) => {
request.headers.authorization = 'Bearer …';
return request;
};
export default hook;
export const auth = beforeEach('GET /launches -> 200 (application/json)', (request) => {
request.headers['authorization'] = 'Bearer …';
});

Registrations are exported values. A v1 file default-exported a function; a v2 file exports the result of calling beforeEach/afterEach/… Any export, named or default, is loaded — and a registration you forget to export does nothing, so export it.

utils.request is keyed by Selector, not by "METHOD url", and its forStatusCode option is gone because the Selector carries the status.

await utils.request('POST http://localhost:3000/astronauts', { body }, { runHooks: false });
await utils.request('POST /astronauts (application/json) -> 201 (application/json)', { body }, { runHooks: false });
Terminal window
rm -rf .thymian/samples

Then, optionally, turn on the type gate:

Terminal window
npx thymian sampler init
  • Directory.thymian/sampler/
    • Directorygenerated/ committed, generated, do not edit
      • …
    • Directoryhooks/ your hooks
      • …
    • tsconfig.json scaffolded once — yours from then on

Commit generated/, and remove .thymian/samples from any .gitignore or CI step that referenced it.

Things that used to break and no longer can

Section titled “Things that used to break and no longer can”
  • Renaming an info.title or a server URL. Selectors are host-stripped and carry no source name, so neither is a change at all.
  • Editing a description. The generated surface is compared canonicalized, so prose changes are not drift.
  • Reordering your document. The catalog is sorted by Selector.
  • Moving your hook files. A hook is anchored by its Selector, not its path.
  • Adding a status code or a media type. Existing Selectors keep meaning what they meant; the new ones are simply new.

What does now break loudly, and on purpose: a renamed path, a removed operation or a changed body shape becomes a compile error at the line of every hook that relied on it. That is the trade — see the drift gate.