1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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)));
}
}
}
|