Storage¶
Each extension gets a private, scoped storage directory. All paths are relative to your extension's storage root. Path traversal is blocked.
Basic Operations¶
// Write
await spindle.storage.write('config.json', JSON.stringify({ theme: 'dark' }))
// Read
const data = await spindle.storage.read('config.json')
const config = JSON.parse(data)
// List files
const files = await spindle.storage.list() // all files
const logs = await spindle.storage.list('logs/') // files under logs/
// Delete
await spindle.storage.delete('config.json')
Advanced Operations¶
// Binary I/O
const imgBytes = await spindle.storage.readBinary('images/logo.png')
await spindle.storage.writeBinary('images/logo.png', new Uint8Array([...]))
// File system operations
const exists = await spindle.storage.exists('config.json')
await spindle.storage.mkdir('logs/2024')
await spindle.storage.move('old.json', 'archive/old.json')
// File metadata
const info = await spindle.storage.stat('config.json')
// { exists: true, isFile: true, isDirectory: false, sizeBytes: 1234, modifiedAt: "2024-01-01T..." }
// JSON convenience (handles parse/serialize + fallback)
const config = await spindle.storage.getJson('config.json', { fallback: { theme: 'dark' } })
await spindle.storage.setJson('config.json', { theme: 'light' }, { indent: 2 })
Methods¶
| Method | Returns | Description |
|---|---|---|
read(path) |
Promise<string> |
Read a file as UTF-8 text |
write(path, data) |
Promise<void> |
Write a UTF-8 text file (creates directories as needed) |
readBinary(path) |
Promise<Uint8Array> |
Read a file as raw bytes |
writeBinary(path, data) |
Promise<void> |
Write raw bytes to a file |
delete(path) |
Promise<void> |
Delete a file |
list(prefix?) |
Promise<string[]> |
List files, optionally under a prefix/directory |
exists(path) |
Promise<boolean> |
Check if a file or directory exists |
mkdir(path) |
Promise<void> |
Create a directory (recursive) |
move(from, to) |
Promise<void> |
Move or rename a file |
stat(path) |
Promise<StatResult> |
Get file metadata (see below) |
getJson<T>(path, options?) |
Promise<T> |
Read and parse a JSON file. Options: { fallback?: T } |
setJson(path, value, options?) |
Promise<void> |
Serialize and write a JSON file. Options: { indent?: number } |
StatResult¶
{
exists: boolean
isFile: boolean
isDirectory: boolean
sizeBytes: number
modifiedAt: string // ISO 8601
}
Storage location: {DATA_DIR}/extensions/{identifier}/storage/
Path traversal is blocked — paths like ../../etc/passwd will throw.
User Storage¶
Per-user isolated storage that keeps each user's data separate — even when the extension is installed globally (install_scope: "operator"). Regular spindle.storage routes to a single shared directory for operator-scoped extensions; spindle.userStorage always routes to {DATA_DIR}/users/{userId}/extensions/{identifier}/.
For user-scoped extensions, the userId is inferred automatically from the extension owner. For operator-scoped extensions, you must pass userId explicitly.
// Write per-user config
await spindle.userStorage.setJson('config.json', { theme: 'dark' }, { userId })
// Read per-user config
const config = await spindle.userStorage.getJson('config.json', {
fallback: { theme: 'light' },
userId,
})
// Write raw text
await spindle.userStorage.write('notes.txt', 'Hello world', userId)
// Read raw text
const text = await spindle.userStorage.read('notes.txt', userId)
// List files
const files = await spindle.userStorage.list(undefined, userId)
// Check existence
const exists = await spindle.userStorage.exists('config.json', userId)
// Create directory
await spindle.userStorage.mkdir('cache/', userId)
// Delete
await spindle.userStorage.delete('notes.txt', userId)
User Storage Methods¶
| Method | Returns | Description |
|---|---|---|
read(path, userId?) |
Promise<string> |
Read a file as UTF-8 text |
write(path, data, userId?) |
Promise<void> |
Write a UTF-8 text file (creates directories as needed) |
delete(path, userId?) |
Promise<void> |
Delete a file |
list(prefix?, userId?) |
Promise<string[]> |
List files, optionally under a prefix/directory |
exists(path, userId?) |
Promise<boolean> |
Check if a file or directory exists |
mkdir(path, userId?) |
Promise<void> |
Create a directory (recursive) |
getJson<T>(path, options?) |
Promise<T> |
Read and parse a JSON file. Options: { fallback?: T; userId?: string } |
setJson(path, value, options?) |
Promise<void> |
Serialize and write a JSON file. Options: { indent?: number; userId?: string } |
Storage location: {DATA_DIR}/users/{userId}/extensions/{identifier}/
Path traversal is blocked — paths like ../../etc/passwd will throw.