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
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 keysMigration Legacy SuperChart keys (pynescript.superchart.library.v1, editor docs) Memory In-process maps when neither IDB nor LS exist (tests/SSR) Capabilities Offline-friendly
cloud (frontend/src/storage/cloud.ts)
Concern Detail Transport fetch to Worker /api/scriptsAuth Authorization: Bearer <api_key> (pn_…)Config endpoint (default http://127.0.0.1:8787), apiKeyConcurrency If-Match revision headers on writePartition 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 templateCommit boundary Explicit Save only — not every keystrokeDrafts saveDraft / loadDraft are no-ops; local dual-write handles draftsCapabilities needsNetwork, needsAuth
Interface surface
Contract: StoragePlugin .
service.ts API (UI-facing)
Function Behavior listScripts(prefix?)Active backend list readScript / writeScript / removeScriptCRUD + log saveDraft / loadDraftLocal first; also active if supported exportLibraryJson / importLibraryJsonPortable backup getStorageStatusConnected / dirty / remote hints
Catalog helpers
ensureStoragesRegistered()
getStorage(id)
listStorages()
registerDynamicStorage(plugin) // in-process only (tests / future)
unregisterDynamicStorage(id)
Internals
Path Role storage/catalog.tsRegistration storage/local.tsIDB + LS storage/idb.tsPromise wrappers for IDB storage/cloud.tsWorker client storage/git.tsOrchestration storage/git-github.ts / git-gitlab.tsProvider APIs storage/git-config.tsConfig normalize storage/service.tsFacade worker/src/scripts.tsCloud API implementation worker/schemas/scripts.sqlD1 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; GitLab api / write_repository.
Revisions — local uses local-${timestamp}; cloud/git use remote revisions for conflict detection.
Active default — getActiveStorageId() → local when unset.
Worked examples
Export / import
const docs = await exportLibraryJson();
// …move machine…
await importLibraryJson(docs, { forceNewIds: true });
Point cloud at local Worker
cd frontend/worker && npm run dev → :8787
Manager → storage cloud
Config endpoint http://127.0.0.1:8787, key from /api/keys or ALLOW_OPEN_KEYS=1 demo key
Failure modes
Error Cause Cloud storage requires an API keyEmpty apiKey 401 NO_KEY / INVALID_KEY Auth path; see Worker auth 409 on write If-Match revision conflictGit 401/404 Token, owner/repo, or basePath wrong Quota errors on localStorage Large libraries should prefer IDB (automatic when available)
See also
← Previous Engines Next → Dynamic Loader