From 6bf57766feb8321f860baf300140563cd9539053 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sun, 14 Dec 2025 20:36:24 -0800 Subject: Init --- src/storage/index.ts | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 src/storage/index.ts (limited to 'src/storage') diff --git a/src/storage/index.ts b/src/storage/index.ts new file mode 100644 index 0000000..2c8ffb2 --- /dev/null +++ b/src/storage/index.ts @@ -0,0 +1,114 @@ +import { randomUUID } from 'crypto'; +import { mkdir, writeFile, readFile } from 'fs/promises'; +import { join } from 'path'; +import { isSafeRouteName, type RouteConfig, type StoredRequest } from '../types/index.js'; +import { Either, type IEither } from '@emprespresso/pengueno'; + +export class Storage { + private routes: Map = new Map(); + + constructor(private readonly dataDir: string = './data') {} + + async init(): Promise> { + try { + await mkdir(this.dataDir, { recursive: true }); + await this.loadRoutes(); + return Either.right(undefined); + } catch (err) { + return Either.left(err instanceof Error ? err : new Error(String(err))); + } + } + + private async loadRoutes(): Promise { + try { + const routesPath = join(this.dataDir, 'routes.json'); + const data = await readFile(routesPath, 'utf-8'); + const routes = JSON.parse(data) as RouteConfig[]; + for (const route of routes) { + if (!isSafeRouteName(route.name)) { + continue; + } + this.routes.set(route.name, route); + } + } catch { + // routes file doesn't exist yet, that's ok + } + } + + private async saveRoutes(): Promise> { + try { + const routesPath = join(this.dataDir, 'routes.json'); + const routes = Array.from(this.routes.values()); + await writeFile(routesPath, JSON.stringify(routes, null, 2)); + return Either.right(undefined); + } catch (err) { + return Either.left(err instanceof Error ? err : new Error(String(err))); + } + } + + async registerRoute(config: RouteConfig): Promise> { + if (!isSafeRouteName(config.name)) { + return Either.left(new Error('Invalid route name')); + } + + this.routes.set(config.name, config); + const routeDir = join(this.dataDir, config.name); + try { + await mkdir(routeDir, { recursive: true }); + } catch (err) { + return Either.left(err instanceof Error ? err : new Error(String(err))); + } + return this.saveRoutes(); + } + + getRoute(name: string): RouteConfig | undefined { + if (!isSafeRouteName(name)) return undefined; + return this.routes.get(name); + } + + listRoutes(): RouteConfig[] { + return Array.from(this.routes.values()); + } + + async deleteRoute(name: string): Promise> { + if (!isSafeRouteName(name)) { + return Either.left(new Error('Invalid route name')); + } + this.routes.delete(name); + return this.saveRoutes(); + } + + async storeRequest( + routeName: string, + method: string, + headers: Record, + body: unknown, + files?: StoredRequest['files'], + ): Promise> { + if (!isSafeRouteName(routeName)) { + return Either.left(new Error('Invalid route name')); + } + + const timestamp = Date.now(); + const uuid = randomUUID(); + const filename = `${timestamp}_${uuid}.json`; + + const stored: StoredRequest = { + timestamp, + uuid, + routeName, + method, + headers, + body, + files, + }; + + const filepath = join(this.dataDir, routeName, filename); + try { + await writeFile(filepath, JSON.stringify(stored, null, 2)); + return Either.right(stored); + } catch (err) { + return Either.left(err instanceof Error ? err : new Error(String(err))); + } + } +} -- cgit v1.2.3-70-g09d2