aboutsummaryrefslogtreecommitdiff
path: root/src/storage
diff options
context:
space:
mode:
Diffstat (limited to 'src/storage')
-rw-r--r--src/storage/index.ts114
1 files changed, 114 insertions, 0 deletions
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<string, RouteConfig> = new Map();
+
+ constructor(private readonly dataDir: string = './data') {}
+
+ async init(): Promise<IEither<Error, void>> {
+ try {
+ await mkdir(this.dataDir, { recursive: true });
+ await this.loadRoutes();
+ return Either.right(<void>undefined);
+ } catch (err) {
+ return Either.left(err instanceof Error ? err : new Error(String(err)));
+ }
+ }
+
+ private async loadRoutes(): Promise<void> {
+ 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<IEither<Error, void>> {
+ 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(<void>undefined);
+ } catch (err) {
+ return Either.left(err instanceof Error ? err : new Error(String(err)));
+ }
+ }
+
+ async registerRoute(config: RouteConfig): Promise<IEither<Error, void>> {
+ 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<IEither<Error, void>> {
+ 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<string, string>,
+ body: unknown,
+ files?: StoredRequest['files'],
+ ): Promise<IEither<Error, StoredRequest>> {
+ 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)));
+ }
+ }
+}