aboutsummaryrefslogtreecommitdiff
path: root/src/storage
diff options
context:
space:
mode:
Diffstat (limited to 'src/storage')
-rw-r--r--src/storage/index.ts95
1 files changed, 56 insertions, 39 deletions
diff --git a/src/storage/index.ts b/src/storage/index.ts
index c3c97d8..8d7debd 100644
--- a/src/storage/index.ts
+++ b/src/storage/index.ts
@@ -1,7 +1,9 @@
import { randomUUID } from 'crypto';
import { mkdir, writeFile, readFile } from 'fs/promises';
+import { watch } from 'fs';
import { basename, join } from 'path';
-import { isSafeRouteName, type RouteConfig, type StoredRequest } from '../types/index.js';
+import { parse as parseToml } from 'smol-toml';
+import { isSafeRouteName, isRouteConfig, type RouteConfig, type StoredRequest } from '../types/index.js';
import { Either, type IEither } from '@emprespresso/pengueno';
type IncomingUpload = {
@@ -20,13 +22,20 @@ function sanitizeFilename(filename: string): string {
export class Storage {
private routes: Map<string, RouteConfig> = new Map();
+ private configPath: string;
- constructor(private readonly dataDir: string = './data') {}
+ constructor(
+ private readonly dataDir: string = './data',
+ configPath: string = './routes.toml',
+ ) {
+ this.configPath = configPath;
+ }
async init(): Promise<IEither<Error, void>> {
try {
await mkdir(this.dataDir, { recursive: true });
await this.loadRoutes();
+ this.watchConfig();
return Either.right(<void>undefined);
} catch (err) {
return Either.left(err instanceof Error ? err : new Error(String(err)));
@@ -35,44 +44,60 @@ export class Storage {
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[];
+ const data = await readFile(this.configPath, 'utf-8');
+ const parsed = parseToml(data);
+
+ if (!parsed || typeof parsed !== 'object' || !('route' in parsed)) {
+ console.error('Invalid routes.toml: missing [[route]] sections');
+ process.exit(1);
+ }
+
+ const routes = parsed.route;
+ if (!Array.isArray(routes)) {
+ console.error('Invalid routes.toml: "route" must be an array of tables');
+ process.exit(1);
+ }
+
+ const newRoutes = new Map<string, RouteConfig>();
for (const route of routes) {
- if (!isSafeRouteName(route.name)) {
- continue;
+ if (!isRouteConfig(route)) {
+ console.error('Invalid route configuration:', route);
+ process.exit(1);
+ }
+ if (newRoutes.has(route.name)) {
+ console.error(`Duplicate route name: ${route.name}`);
+ process.exit(1);
}
- this.routes.set(route.name, route);
+ newRoutes.set(route.name, route);
+
+ // Ensure route directory exists
+ const routeDir = join(this.dataDir, route.name);
+ await mkdir(routeDir, { recursive: true });
}
- } 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);
+ this.routes = newRoutes;
+ console.log(`Loaded ${this.routes.size} route(s) from ${this.configPath}`);
} catch (err) {
- return Either.left(err instanceof Error ? err : new Error(String(err)));
+ if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
+ console.log(`No ${this.configPath} found, starting with empty routes`);
+ return;
+ }
+ console.error(`Failed to load routes from ${this.configPath}:`, err);
+ process.exit(1);
}
}
- async registerRoute(config: RouteConfig): Promise<IEither<Error, void>> {
- if (!isSafeRouteName(config.name)) {
- return Either.left(new Error('Invalid route name'));
- }
+ private watchConfig(): void {
+ const watcher = watch(this.configPath, async (eventType) => {
+ if (eventType === 'change') {
+ console.log(`${this.configPath} changed, reloading...`);
+ await this.loadRoutes();
+ }
+ });
- 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();
+ watcher.on('error', (err) => {
+ console.error(`Error watching ${this.configPath}:`, err);
+ });
}
getRoute(name: string): RouteConfig | undefined {
@@ -84,14 +109,6 @@ export class Storage {
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,