Worker data plane
Script library API: /api/scripts, D1 schema, drafts, revisions, and in-memory fallback.
This page
Abstract
The Worker data plane for user scripts is worker/src/scripts.ts — REST under /api/scripts with Bearer auth (auth). Persistence prefers D1 (env.DB); without D1, per-isolate in-memory maps support local wrangler dev. With the optional SCRIPT_ARTIFACTS binding, every save additionally mirrors as a git commit to a per-key Artifacts repo — full history and git clone with no GitHub credentials (see below).
The PWA cloud storage plugin is the primary client.
Conceptual model
Rendering…
Git-native history (Cloudflare® Artifacts, no GitHub creds)
When SCRIPT_ARTIFACTS is bound (axis setup artifacts), every save/delete
mirrors as a git commit to a per-key repo (axis-scripts-<id12>):
scripts/<id>.pine plus a meta sidecar. History endpoints serve from
repo.log() with real SHAs and fall back to script_versions when git is
empty or unbound; responses carry backend: 'artifacts-git' when served
from git. Mirrors run via ctx.waitUntil and never fail the save; identical
re-saves dedupe against HEAD (no clone+push, no history spam).
Clone without any GitHub credential — the caller keeps using their AXIS API key while the Worker mints short-lived repo tokens:
REMOTE=$(curl -s $WORKER/api/scripts/_git -H "Authorization: Bearer $KEY" | jq -r .git.remote)
TOKEN=$(curl -s -X POST $WORKER/api/scripts/_git/token -H "Authorization: Bearer $KEY" \
-H 'Content-Type: application/json' -d '{"scope":"read"}' | jq -r .git.token)
git -c http.extraHeader="Authorization: Bearer $TOKEN" clone "$REMOTE" axis-scripts
Setup:
axis setup artifacts --namespace axis-scripts # writes [[artifacts]], verifies access
axis deploy worker
Artifacts is in closed beta: without access, axis setup artifacts fails the
namespace check (re-run with --skip-check to keep the toml edit), and
unbound Workers return 503 GIT_BACKEND_UNAVAILABLE on /_git routes while
CRUD keeps working on D1/memory.
Interface surface
All routes require valid API key unless noted.
| Path | Methods | Behavior |
|---|---|---|
/api/scripts | GET | List meta for userId |
/api/scripts | POST | Create script |
/api/scripts/:id | GET | Read full document |
/api/scripts/:id | PUT | Upsert; honors If-Match revision |
/api/scripts/:id | DELETE | Remove |
/api/scripts/_draft | GET/PUT | Per-user draft buffer |
/api/scripts/:id/versions | GET | Version history, newest first (git log when Artifacts bound, else script_versions) |
/api/scripts/:id/versions/:rev | GET | Full document at a revision (git SHA or rev_…) |
/api/scripts/:id/restore | POST | Resurrect {revision} as the new tip (recreates deleted scripts) |
/api/scripts/_git | GET | Artifacts clone remote for the caller (503 when unbound) |
/api/scripts/_git/token | POST | Short-lived clone token (read/write, TTL 60s–24h) |
Document fields
| Field | Notes |
|---|---|
id | Client or server assigned |
name, description, path | Meta |
content | Pine source |
revision | Opaque string (rev_…) for concurrency |
created_at / updated_at | ms epochs (API may camelCase in JSON) |
Concurrency
PUT with If-Match: <revision> rejects mismatched revisions (409 path covered in security tests). Fresh writes mint newRevision().
Internals
D1 schema (schemas/scripts.sql)
CREATE TABLE scripts (
user_id TEXT NOT NULL,
id TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT,
path TEXT,
content TEXT NOT NULL,
revision TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
PRIMARY KEY (user_id, id)
);
CREATE TABLE script_drafts (
user_id TEXT PRIMARY KEY,
content TEXT NOT NULL,
name TEXT,
updated_at INTEGER NOT NULL
);
Apply:
wrangler d1 execute pynescript --remote --file=schemas/scripts.sql
wrangler d1 execute pynescript --local --file=schemas/scripts.sql
Binding name in wrangler.toml must be DB (code uses env.DB). Database name can be pynescript.
Partitioning
userId = SHA-256(api_key).hex.slice(0, 32) — raw keys are not stored as D1 partition ids.
Health feature flags
{
"status": "healthy",
"service": "worker-axis",
"features": { "scripts": true, "d1": true/false, "keys": true/false }
}
Optional R2
BUNDLES R2 is reserved for indicator / pynescript wheels (RUNTIME.md). Not required for scripts CRUD.
Invariants & edge cases
- Memory store is not multi-isolate durable — production needs D1.
- Drafts ≠ commits — drafts are crash buffers; library list is separate.
- CORS — scripts handler sets allow headers including
If-Match. - Cloud plugin dual-write — browser still saves drafts to local storage.
Failure modes
| Code / symptom | Meaning |
|---|---|
| 401 | Missing/invalid Bearer |
| 404 | Unknown script id for user |
| 409 | Revision conflict |
503 GIT_BACKEND_UNAVAILABLE | /_git routes without the Artifacts binding (axis setup artifacts) |
| Empty list after deploy | Schema not applied / wrong DB id |