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

Diagram

Rendering…

Built-in plugins

local (frontend/src/storage/local.ts)

ConcernDetail
PrimaryIndexedDB pynescript.axis.storage — stores scripts, kv
FallbacklocalStorage keys pynescript.axis.library.v1, draft keys
MigrationOlder library keys (pynescript.axis.library.v1, editor docs)
MemoryIn-process maps when neither IDB nor LS exist (tests/SSR)
CapabilitiesOffline-friendly; file versioning (snapshots on each save)

cloud (frontend/src/storage/cloud.ts)

ConcernDetail
Transportfetch to Worker /api/scripts
AuthAuthorization: Bearer <api_key> (pn_…)
Configendpoint (Worker URL — never the Pine engine host), apiKey (pn_…). Set in Settings → Script storage or Script Library.
ConcurrencyIf-Match revision headers on write
PartitionWorker hashes key → userId; rows scoped per user

git (frontend/src/storage/git.ts)

ConcernDetail
ProvidersGitHub Contents API / GitLab repository files
Configprovider, token, owner, repo, branch, basePath (default pine-library), commit template
Commit boundaryExplicit Save only — not every keystroke
DraftssaveDraft / loadDraft are no-ops; local dual-write handles drafts
Version historylistVersions / readAtRevision — GitHub/GitLab commits for the script path; local IndexedDB/localStorage snapshots; Worker script_versions
RestorerestoreScriptVersion writes historical content as a new tip commit
CapabilitiesneedsNetwork, needsAuth

Interface surface

Contract: StoragePlugin.

service.ts API (UI-facing)

FunctionBehavior
listScripts(prefix?)Active backend list
readScript / writeScript / removeScriptCRUD + log
saveDraft / loadDraftLocal first; also active if supported
exportLibraryJson / importLibraryJsonPortable backup
getStorageStatusConnected / dirty / remote hints
supportsScriptVersioningTrue 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

PathRole
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

  1. Draft recovery — never rely solely on remote drafts; service always hits local.
  2. Git PAT scope — GitHub contents:write; GitLab api / write_repository.
  3. Revisions — local uses local-${timestamp}; cloud/git use remote revisions for conflict detection.
  4. Active defaultgetActiveStorageId()local when unset.
  5. Engine switch is a copy — switching storage copies scripts onto the target and never removes them from the source.
  6. Cloud credentials — Worker URL + API key live in Settings (and Script Library). store.endpoint is 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

  1. axis setup --prod then axis secret put ADMIN_TOKEN then axis deploy all then axis keys create
  2. axis keys create — paste the pn_… key into Settings → Script storage
  3. Local demo: axis dev worker (:8787) + Settings Generate demo key when ALLOW_OPEN_KEYS=1

Failure modes

ErrorCause
Cloud storage requires an API keyEmpty apiKey — set it in Settings → Script storage
401 NO_KEY / INVALID_KEYAuth path; see Worker auth
409 on writeIf-Match revision conflict
Git 401/404Token, owner/repo, or basePath wrong
Quota errors on localStorageLarge libraries should prefer IDB (automatic when available)

See also