Storage
Script library backends: local (IndexedDB), cloud (Worker /api/scripts), and git (GitHub/GitLab).
This page
Storage
Abstract
Storage plugins persist the user’s Pine library and drafts. Built-ins: local, cloud, git — registered from frontend/src/storage/catalog.ts. High-level UI/editor APIs go through frontend/src/storage/service.ts, which always dual-writes drafts to local for crash recovery.
Dynamic kind: 'storage' install via URL is not supported (loader.ts throws).
Conceptual model
Rendering…
Built-in plugins
local (frontend/src/storage/local.ts)
| Concern | Detail |
|---|---|
| Primary | IndexedDB pynescript.axis.storage — stores scripts, kv |
| Fallback | localStorage keys pynescript.axis.library.v1, draft keys |
| Migration | Older library keys (pynescript.axis.library.v1, editor docs) |
| Memory | In-process maps when neither IDB nor LS exist (tests/SSR) |
| Capabilities | Offline-friendly; file versioning (snapshots on each save) |
cloud (frontend/src/storage/cloud.ts)
| Concern | Detail |
|---|---|
| Transport | fetch to Worker /api/scripts |
| Auth | Authorization: Bearer <api_key> (pn_…) |
| Config | endpoint (Worker URL — never the Pine engine host), apiKey (pn_…). Set in Settings → Script storage or Script Library. |
| Concurrency | If-Match revision headers on write |
| Partition | Worker hashes key → userId; rows scoped per user |
git (frontend/src/storage/git.ts)
| Concern | Detail |
|---|---|
| Providers | GitHub Contents API / GitLab repository files |
| Config | provider, token, owner, repo, branch, basePath (default pine-library), commit template |
| Commit boundary | Explicit Save only — not every keystroke |
| Drafts | saveDraft / loadDraft are no-ops; local dual-write handles drafts |
| Version history | listVersions / readAtRevision — GitHub/GitLab commits for the script path; local IndexedDB/localStorage snapshots; Worker script_versions |
| Restore | restoreScriptVersion writes historical content as a new tip commit |
| Capabilities | needsNetwork, needsAuth |
Interface surface
Contract: StoragePlugin.
service.ts API (UI-facing)
| Function | Behavior |
|---|---|
listScripts(prefix?) | Active backend list |
readScript / writeScript / removeScript | CRUD + log |
saveDraft / loadDraft | Local first; also active if supported |
exportLibraryJson / importLibraryJson | Portable backup |
getStorageStatus | Connected / dirty / remote hints |
supportsScriptVersioning | True when active storage implements version history (local, cloud, git) |
listScriptVersions(id) | Version history for a script |
readScriptVersion(id, rev) | Content at a revision |
restoreScriptVersion(id, rev) | Write historical content as the new tip |
copyScriptsBetweenStorages(from, to) | Copy library between engines — never deletes the source |
Catalog helpers
ensureStoragesRegistered()
getStorage(id)
listStorages()
registerDynamicStorage(plugin) // in-process only (tests / future)
unregisterDynamicStorage(id)
Internals
| Path | Role |
|---|---|
storage/catalog.ts | Registration |
storage/local.ts | IDB + LS |
storage/idb.ts | Promise wrappers for IDB |
storage/cloud.ts | Worker client |
storage/git.ts | Orchestration |
storage/git-github.ts / git-gitlab.ts | Provider APIs |
storage/git-config.ts | Config normalize |
storage/service.ts | Facade |
worker/src/scripts.ts | Cloud API implementation |
worker/schemas/scripts.sql | D1 schema |
D1 schema (cloud)
PRIMARY KEY (user_id, id)
-- scripts + script_drafts tables
Without D1, Worker keeps per-isolate in-memory maps (fine for wrangler dev).
Invariants & edge cases
- Draft recovery — never rely solely on remote drafts; service always hits local.
- Git PAT scope — GitHub
contents:write; GitLabapi/write_repository. - Revisions — local uses
local-${timestamp}; cloud/git use remote revisions for conflict detection. - Active default —
getActiveStorageId()→localwhen unset. - Engine switch is a copy — switching storage copies scripts onto the target and never removes them from the source.
- Cloud credentials — Worker URL + API key live in Settings (and Script Library).
store.endpointis the Pine engine and is never used as the scripts Worker base.
Worked examples
Export / import
const docs = await exportLibraryJson();
// …move machine…
await importLibraryJson(docs, { forceNewIds: true });
Point cloud at local Worker
axis setup --prodthenaxis secret put ADMIN_TOKENthenaxis deploy allthenaxis keys createaxis keys create— paste thepn_…key into Settings → Script storage- Local demo:
axis dev worker(:8787) + Settings Generate demo key whenALLOW_OPEN_KEYS=1
Failure modes
| Error | Cause |
|---|---|
Cloud storage requires an API key | Empty apiKey — set it in Settings → Script storage |
401 NO_KEY / INVALID_KEY | Auth path; see Worker auth |
| 409 on write | If-Match revision conflict |
| Git 401/404 | Token, owner/repo, or basePath wrong |
| Quota errors on localStorage | Large libraries should prefer IDB (automatic when available) |