UNPKG

accounts

Version:

Tempo Accounts SDK

128 lines 4.21 kB
import { chmod, mkdir, open, readFile, rename, unlink } from 'node:fs/promises'; import { homedir } from 'node:os'; import { dirname, join } from 'node:path'; import { Json } from 'ox'; import * as Storage from '../core/Storage.js'; const mode_directory = 0o700; const mode_file = 0o600; const operations = new Map(); /** Returns the default CLI provider storage path. */ export function defaultPath() { return join(homedir(), '.tempo', 'wallet', 'store.json'); } /** Creates a filesystem-backed storage adapter for CLI provider state. */ export function filesystem(options = {}) { const path = expandPath(options.path ?? defaultPath()); function enqueue(fn) { const operation = operations.get(path) ?? Promise.resolve(); const next = operation.then(fn, fn); const done = next.then(() => undefined, () => undefined); operations.set(path, done); void done.then(() => { if (operations.get(path) === done) operations.delete(path); }); return next; } return Storage.from({ async getItem(name) { return await enqueue(async () => { const value = await read(path); return value[name] ?? null; }); }, async removeItem(name) { await enqueue(async () => { const value = await read(path); delete value[name]; await write(path, value); }); }, async setItem(name, item) { await enqueue(async () => { const value = await read(path); value[name] = item; await write(path, value); }); }, }, { key: options.key ?? 'tempo-cli' }); } class FilesystemStorageError extends Error { cause; path; constructor(path, message, cause) { super(message); this.name = 'FilesystemStorageError'; this.cause = cause; this.path = path; } } async function ensureDirectory(path) { const dir = dirname(path); const created = await mkdir(dir, { mode: mode_directory, recursive: true }); if (created) await chmod(dir, mode_directory); } async function read(path) { let text; try { text = await readFile(path, 'utf8'); } catch (error) { if (error.code === 'ENOENT') return {}; throw new FilesystemStorageError(path, `Failed to read CLI storage file at ${path}.`, error); } const value = (() => { try { return Json.parse(text); } catch (error) { throw new FilesystemStorageError(path, `Failed to parse CLI storage file at ${path}. The file is not valid JSON.`, error); } })(); if (!value || typeof value !== 'object' || Array.isArray(value)) throw new FilesystemStorageError(path, `Failed to parse CLI storage file at ${path}. The file is not a JSON object.`); try { await chmod(path, mode_file); } catch (error) { throw new FilesystemStorageError(path, `Failed to secure CLI storage file at ${path}.`, error); } return value; } async function write(path, value) { await ensureDirectory(path); const path_temp = tempPath(path); const handle = await open(path_temp, 'wx', mode_file); try { await handle.writeFile(Json.stringify(value), 'utf8'); await handle.sync(); } catch (error) { await handle.close().catch(() => undefined); await unlink(path_temp).catch(() => undefined); throw error; } await handle.close(); try { await chmod(path_temp, mode_file); await rename(path_temp, path); await chmod(path, mode_file); } catch (error) { await unlink(path_temp).catch(() => undefined); throw error; } } function expandPath(path) { if (path === '~') return homedir(); if (path.startsWith('~/')) return join(homedir(), path.slice(2)); return path; } function tempPath(path) { return `${path}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`; } //# sourceMappingURL=storage.js.map