Packages

Autonoma SDK — automate the Autonoma Environment Factory endpoint

Current section

Files

Jump to
autonoma docs overview.md
Raw

docs/overview.md

<!-- Generated by scripts/build-sdk-docs.mjs from docs/shared/overview.md. Edit the source, not this copy. -->
# Overview
The Autonoma Environment Factory is a single endpoint in your backend that creates fresh, isolated test data before an end-to-end test run and deletes it afterward. This SDK implements that endpoint for you.
## Why it exists
Every end-to-end test needs data. A test for "user adds an item to the cart" needs a user, some products, and a cart. A test for "admin views analytics" needs an organization with users, runs, and history.
Tests cannot share data. If one test deletes a product, another test that expects ten products fails. Running tests in parallel makes this worse. The Environment Factory gives each test run its own organization, its own users, and its own records, then tears all of it down when the run finishes. No interference, no leftover rows.
## How it works
The platform talks to one HTTP POST endpoint that you mount in your app (you choose the path, e.g. `/api/autonoma`). The endpoint handles three actions:
| Action | What the platform asks | What the SDK does |
|--------|------------------------|-------------------|
| `discover` | "What models can you create?" | Returns the model + field list, derived from your registered factories. |
| `up` | "Create this data." | Runs each record through its factory in dependency order, returns login credentials and a signed teardown token. |
| `down` | "Delete what you created." | Verifies the token and calls each factory's teardown in reverse order. |
You do not write routing, signature checks, dependency sorting, or teardown logic. You write factories and an auth callback, and the SDK does the rest.
## Factory-driven: you own every write
This SDK never runs SQL and never touches your database directly. Every model it creates goes through a factory you register - a small function that calls your own code to make one record. This is the core design decision, and it has two consequences worth internalizing before you write anything:
- **Test data is created the way production data is.** If your app hashes passwords in `createUser`, your factory calls `createUser`, so the test user has a real password hash. A raw database seed could never reproduce that. Factories reuse your real creation logic - password hashing, external-service calls, state-machine defaults - instead of guessing at column values.
- **The endpoint is safe by construction.** `up` can only insert (through your factories). `down` can only delete records that `up` created (proven by a signed token). The SDK has no code path that can `UPDATE`, `DROP`, `TRUNCATE`, or run an arbitrary query. See [protocol.md](protocol.md) for the full safety model.
There is no ORM adapter, no schema introspection, and no SQL fallback. A model with no registered factory simply cannot be created.
## What you provide
Four things, passed as configuration when you create the handler:
| Config | What it is |
|--------|------------|
| `scopeField` | The single column name that isolates test data (e.g. `organizationId`). |
| `factories` | One factory per model the platform can create. |
| `sharedSecret` | Shared with Autonoma. Verifies that incoming requests are authentic (HMAC). |
| `signingSecret` | Known only to you. Signs the teardown token so it cannot be forged. |
| `auth` | A callback that returns real login credentials for the created test user. |
## The scope field
Most multi-tenant apps have one foreign key on nearly every model - `organizationId`, `orgId`, `tenantId`, `workspaceId`. That column is your scope field: the boundary that keeps one test run's data separate from another's.
Two things to know:
- **You set it; the SDK does not.** The SDK never injects the scope field into a record. Every model that has it must set it explicitly in the create payload, normally as a `_ref` pointing at the scope record. Your factory writes it. The SDK only *reads* the scope field after creation, to derive the value it hands your auth callback.
- **It must be a single column.** Compound scope keys (e.g. `organizationId` + `countryId`) are not supported. If your app scopes by several fields, use one root entity whose ID the child models reference. If your app is not multi-tenant at all, add a dedicated field like `testRunId` to every model and use that.
## The two secrets
You need two different secret values. The SDK throws `SAME_SECRETS` at startup if they match.
- **Shared secret** - both you and Autonoma hold it. Autonoma signs every request with it (HMAC-SHA256); your endpoint verifies the signature. This stops anyone else from calling your endpoint.
- **Signing secret** - only you hold it. During `up` the SDK signs a token listing every created record's ID. During `down` it verifies that token before deleting. Autonoma stores the token opaquely and passes it back; it can neither read nor forge it.
Generate them as two distinct random values:
```bash
openssl rand -hex 32 # AUTONOMA_SHARED_SECRET
openssl rand -hex 32 # AUTONOMA_SIGNING_SECRET (must differ)
```
## Language availability
The SDK ships for eight languages, each an independent implementation that passes the same conformance suite. The protocol behavior is identical everywhere; only package names and syntax differ.
| Language | Server adapters |
|----------|-----------------|
| TypeScript | Web standard (Next.js App Router, Bun, Deno), Express, Hono, Node http |
| Python | FastAPI, Flask, Django |
| Elixir | Plug (Phoenix) |
| PHP | Laravel |
| Java | Spring Boot |
| Ruby | Rails / Rack |
| Rust | Actix, Axum |
| Go | Gin |
See `implement.md` for the step-by-step setup in your language, `factories.md` for how to write factories, `scenarios.md` for the data format, and `protocol.md` for the wire protocol.