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

Diagram

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:

bash
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:

bash
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.

PathMethodsBehavior
/api/scriptsGETList meta for userId
/api/scriptsPOSTCreate script
/api/scripts/:idGETRead full document
/api/scripts/:idPUTUpsert; honors If-Match revision
/api/scripts/:idDELETERemove
/api/scripts/_draftGET/PUTPer-user draft buffer
/api/scripts/:id/versionsGETVersion history, newest first (git log when Artifacts bound, else script_versions)
/api/scripts/:id/versions/:revGETFull document at a revision (git SHA or rev_…)
/api/scripts/:id/restorePOSTResurrect {revision} as the new tip (recreates deleted scripts)
/api/scripts/_gitGETArtifacts clone remote for the caller (503 when unbound)
/api/scripts/_git/tokenPOSTShort-lived clone token (read/write, TTL 60s–24h)

Document fields

FieldNotes
idClient or server assigned
name, description, pathMeta
contentPine source
revisionOpaque string (rev_…) for concurrency
created_at / updated_atms 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)

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:

bash
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

json
{
  "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

  1. Memory store is not multi-isolate durable — production needs D1.
  2. Drafts ≠ commits — drafts are crash buffers; library list is separate.
  3. CORS — scripts handler sets allow headers including If-Match.
  4. Cloud plugin dual-write — browser still saves drafts to local storage.

Failure modes

Code / symptomMeaning
401Missing/invalid Bearer
404Unknown script id for user
409Revision conflict
503 GIT_BACKEND_UNAVAILABLE/_git routes without the Artifacts binding (axis setup artifacts)
Empty list after deploySchema not applied / wrong DB id

See also