@hamak/filesystem-server-impl 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ /**
3
+ * @hamak/filesystem-server-impl
4
+ *
5
+ * Backend filesystem server implementation
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
19
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ // Export plugin (main entry point)
23
+ __exportStar(require("./plugin/filesystem-server-plugin"), exports);
24
+ // Export services
25
+ __exportStar(require("./services/workspace-manager"), exports);
26
+ // Export routing
27
+ __exportStar(require("./routing/create-router"), exports);
28
+ __exportStar(require("./routing/file-router"), exports);
29
+ // Export middleware
30
+ __exportStar(require("./middleware/workspace-validator"), exports);
31
+ // Re-export API types for convenience
32
+ __exportStar(require("@hamak/filesystem-server-api"), exports);
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ /**
3
+ * Workspace Validator Middleware
4
+ *
5
+ * Validates workspace ID exists in configuration
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.createWorkspaceValidator = void 0;
9
+ /**
10
+ * Create middleware to validate workspace exists
11
+ *
12
+ * @param config - Server configuration with workspaces map
13
+ * @returns Express middleware function
14
+ */
15
+ function createWorkspaceValidator(config) {
16
+ const workspaces = config.workspaces;
17
+ return (req, res, next) => {
18
+ const workspace = req.params.workspace;
19
+ if (!workspace) {
20
+ return res.status(400).json({ error: 'Workspace ID is required' });
21
+ }
22
+ if (!workspaces[workspace]) {
23
+ return res.status(404).json({ error: `Workspace '${workspace}' not found` });
24
+ }
25
+ // Store workspace info in request for downstream handlers
26
+ req.workspaceId = workspace;
27
+ req.workspacePath = workspaces[workspace];
28
+ next();
29
+ };
30
+ }
31
+ exports.createWorkspaceValidator = createWorkspaceValidator;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ /**
3
+ * FileSystem Server Plugin
4
+ *
5
+ * Microkernel plugin for backend filesystem server
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.createFileSystemServerPlugin = exports.FileSystemServerPlugin = void 0;
9
+ const filesystem_server_api_1 = require("@hamak/filesystem-server-api");
10
+ const workspace_manager_1 = require("../services/workspace-manager");
11
+ const create_router_1 = require("../routing/create-router");
12
+ /**
13
+ * FileSystem Server Plugin Implementation
14
+ *
15
+ * Provides backend filesystem operations via Express routes
16
+ */
17
+ class FileSystemServerPlugin {
18
+ /**
19
+ * Initialize phase - register services in DI container
20
+ */
21
+ initialize(ctx) {
22
+ console.log('[FileSystemServerPlugin] Initializing...');
23
+ // Resolve configuration
24
+ const config = ctx.resolve(filesystem_server_api_1.FILESYSTEM_SERVER_CONFIG_TOKEN);
25
+ // Create and register WorkspaceManager service
26
+ const workspaceManager = new workspace_manager_1.WorkspaceManager(config.workspaces, {
27
+ baseDirectory: config.baseDirectory,
28
+ });
29
+ ctx.provide({
30
+ provide: filesystem_server_api_1.WORKSPACE_MANAGER_TOKEN,
31
+ useValue: workspaceManager,
32
+ });
33
+ console.log('[FileSystemServerPlugin] WorkspaceManager service registered');
34
+ console.log(`[FileSystemServerPlugin] Base directory: ${config.baseDirectory}`);
35
+ console.log(`[FileSystemServerPlugin] Workspaces:`, Object.keys(config.workspaces));
36
+ }
37
+ /**
38
+ * Activate phase - create router and make ready for use
39
+ */
40
+ activate(ctx) {
41
+ var _a;
42
+ console.log('[FileSystemServerPlugin] Activating...');
43
+ // Resolve dependencies
44
+ const workspaceManager = ctx.resolve(filesystem_server_api_1.WORKSPACE_MANAGER_TOKEN);
45
+ const config = ctx.resolve(filesystem_server_api_1.FILESYSTEM_SERVER_CONFIG_TOKEN);
46
+ // Create Express router
47
+ this.router = (0, create_router_1.createFileSystemRouter)(workspaceManager, config);
48
+ // Note: Router is available via getRouter() method, not registered in DI
49
+ // This avoids circular dependencies and keeps the plugin self-contained
50
+ console.log('[FileSystemServerPlugin] Activated successfully');
51
+ console.log(`[FileSystemServerPlugin] Mount path: ${(_a = config.mountPath) !== null && _a !== void 0 ? _a : '/api/workspaces'}`);
52
+ }
53
+ /**
54
+ * Deactivate phase - cleanup
55
+ */
56
+ deactivate() {
57
+ console.log('[FileSystemServerPlugin] Deactivating...');
58
+ this.router = undefined;
59
+ }
60
+ /**
61
+ * Get the Express router for mounting in application
62
+ *
63
+ * @returns Express router with filesystem routes
64
+ * @throws Error if plugin not activated
65
+ */
66
+ getRouter() {
67
+ if (!this.router) {
68
+ throw new Error('[FileSystemServerPlugin] Plugin not activated. Call kernel.activate() first.');
69
+ }
70
+ return this.router;
71
+ }
72
+ }
73
+ exports.FileSystemServerPlugin = FileSystemServerPlugin;
74
+ /**
75
+ * Factory function to create plugin instance
76
+ *
77
+ * @returns FileSystemServerPlugin instance
78
+ */
79
+ function createFileSystemServerPlugin() {
80
+ return new FileSystemServerPlugin();
81
+ }
82
+ exports.createFileSystemServerPlugin = createFileSystemServerPlugin;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ /**
3
+ * Router Factory
4
+ *
5
+ * Creates and configures the Express router for filesystem operations
6
+ */
7
+ var __importDefault = (this && this.__importDefault) || function (mod) {
8
+ return (mod && mod.__esModule) ? mod : { "default": mod };
9
+ };
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.createFileSystemRouter = void 0;
12
+ const express_1 = require("express");
13
+ const cors_1 = __importDefault(require("cors"));
14
+ const body_parser_1 = __importDefault(require("body-parser"));
15
+ const filesystem_server_api_1 = require("@hamak/filesystem-server-api");
16
+ const file_router_1 = require("./file-router");
17
+ /**
18
+ * Create filesystem Express router with middleware
19
+ *
20
+ * @param workspaceManager - Workspace manager instance
21
+ * @param config - Server configuration
22
+ * @returns Configured Express router
23
+ */
24
+ function createFileSystemRouter(workspaceManager, config) {
25
+ var _a, _b;
26
+ const app = (0, express_1.Router)();
27
+ // Apply CORS middleware if enabled
28
+ if ((_a = config.enableCors) !== null && _a !== void 0 ? _a : filesystem_server_api_1.DEFAULT_FILESYSTEM_SERVER_CONFIG.enableCors) {
29
+ app.use((0, cors_1.default)());
30
+ }
31
+ // Apply body parser for JSON payloads
32
+ app.use(body_parser_1.default.json());
33
+ // Create and mount file router
34
+ const fileRouter = new file_router_1.FileRouter(workspaceManager);
35
+ const mountPath = (_b = config.mountPath) !== null && _b !== void 0 ? _b : filesystem_server_api_1.DEFAULT_FILESYSTEM_SERVER_CONFIG.mountPath;
36
+ app.use(mountPath, fileRouter.router);
37
+ return app;
38
+ }
39
+ exports.createFileSystemRouter = createFileSystemRouter;
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ /**
3
+ * File Router
4
+ *
5
+ * Express routes for filesystem operations
6
+ * Migrated from amk/libs/server/ws/ws-server
7
+ */
8
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
9
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
10
+ return new (P || (P = Promise))(function (resolve, reject) {
11
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
12
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
13
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
14
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
15
+ });
16
+ };
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.FileRouter = void 0;
19
+ const express_1 = require("express");
20
+ class FileRouter {
21
+ constructor(workspaceManager) {
22
+ this.router = (0, express_1.Router)();
23
+ this.workspaceManager = workspaceManager;
24
+ this.initializeRoutes();
25
+ }
26
+ initializeRoutes() {
27
+ this.router.get('/:workspace/files/*', this.listFiles.bind(this));
28
+ this.router.post('/:workspace/mkdir/*', this.createDirectory.bind(this));
29
+ this.router.get('/:workspace/get/*', this.getFile.bind(this));
30
+ this.router.get('/:workspace/read/*', this.readFile.bind(this));
31
+ this.router.post('/:workspace/post/*', this.writeFile.bind(this));
32
+ this.router.put('/:workspace/put/*', this.writeFile.bind(this));
33
+ this.router.delete('/:workspace/delete/*', this.deleteFile.bind(this));
34
+ }
35
+ parsePath(pathString) {
36
+ // Split path string into segments, filter out empty segments
37
+ return pathString.split('/').filter(seg => seg.length > 0);
38
+ }
39
+ listFiles(req, res) {
40
+ return __awaiter(this, void 0, void 0, function* () {
41
+ try {
42
+ const { workspace } = req.params;
43
+ const pathSegments = this.parsePath(req.params[0] || '');
44
+ const files = yield this.workspaceManager.listFiles(workspace, pathSegments);
45
+ res.json(files);
46
+ }
47
+ catch (error) {
48
+ res.status(500).json({ error: error.message });
49
+ }
50
+ });
51
+ }
52
+ createDirectory(req, res) {
53
+ return __awaiter(this, void 0, void 0, function* () {
54
+ try {
55
+ const { workspace } = req.params;
56
+ const pathSegments = this.parsePath(req.params[0] || '');
57
+ const dirInfo = yield this.workspaceManager.createDirectory(workspace, pathSegments);
58
+ res.json(dirInfo);
59
+ }
60
+ catch (error) {
61
+ res.status(500).json({ error: error.message });
62
+ }
63
+ });
64
+ }
65
+ getFile(req, res) {
66
+ return __awaiter(this, void 0, void 0, function* () {
67
+ try {
68
+ const { workspace } = req.params;
69
+ const pathSegments = this.parsePath(req.params[0] || '');
70
+ const fileInfo = yield this.workspaceManager.getFile(workspace, pathSegments);
71
+ res.json(fileInfo);
72
+ }
73
+ catch (error) {
74
+ res.status(500).json({ error: error.message });
75
+ }
76
+ });
77
+ }
78
+ readFile(req, res) {
79
+ return __awaiter(this, void 0, void 0, function* () {
80
+ try {
81
+ const { workspace } = req.params;
82
+ const pathSegments = this.parsePath(req.params[0] || '');
83
+ const fileInfo = yield this.workspaceManager.readFile(workspace, pathSegments);
84
+ res.json(fileInfo);
85
+ }
86
+ catch (error) {
87
+ res.status(500).json({ error: error.message });
88
+ }
89
+ });
90
+ }
91
+ writeFile(req, res) {
92
+ return __awaiter(this, void 0, void 0, function* () {
93
+ try {
94
+ const { workspace } = req.params;
95
+ const pathSegments = this.parsePath(req.params[0] || '');
96
+ const { content } = req.body;
97
+ const fileInfo = yield this.workspaceManager.writeFile(workspace, pathSegments, content);
98
+ res.json(fileInfo);
99
+ }
100
+ catch (error) {
101
+ res.status(500).json({ error: error.message });
102
+ }
103
+ });
104
+ }
105
+ deleteFile(req, res) {
106
+ return __awaiter(this, void 0, void 0, function* () {
107
+ try {
108
+ const { workspace } = req.params;
109
+ const pathSegments = this.parsePath(req.params[0] || '');
110
+ const fileInfo = yield this.workspaceManager.deleteFile(workspace, pathSegments);
111
+ res.json(fileInfo);
112
+ }
113
+ catch (error) {
114
+ res.status(500).json({ error: error.message });
115
+ }
116
+ });
117
+ }
118
+ }
119
+ exports.FileRouter = FileRouter;
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ /**
3
+ * Workspace Manager Implementation
4
+ *
5
+ * Provides filesystem operations for workspace-based storage
6
+ * Migrated from amk/libs/server/ws/ws-server
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || function (mod) {
25
+ if (mod && mod.__esModule) return mod;
26
+ var result = {};
27
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
28
+ __setModuleDefault(result, mod);
29
+ return result;
30
+ };
31
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
32
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
33
+ return new (P || (P = Promise))(function (resolve, reject) {
34
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
35
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
36
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
37
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
38
+ });
39
+ };
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.WorkspaceManager = void 0;
42
+ const fs_1 = require("fs");
43
+ const path = __importStar(require("path"));
44
+ class WorkspaceManager {
45
+ constructor(workspaces, options) {
46
+ this.workspaces = workspaces;
47
+ this.options = options;
48
+ }
49
+ resolvePath(workspace, filePath) {
50
+ const basePath = path.resolve(this.options.baseDirectory, this.workspaces[workspace]);
51
+ if (!basePath) {
52
+ throw new Error(`Workspace ${workspace} not found`);
53
+ }
54
+ const relativePath = Array.isArray(filePath) ? filePath.join(path.sep) : filePath;
55
+ return path.resolve(basePath, relativePath);
56
+ }
57
+ listFiles(workspace, filePath) {
58
+ return __awaiter(this, void 0, void 0, function* () {
59
+ const absolutePath = this.resolvePath(workspace, filePath);
60
+ const files = yield fs_1.promises.readdir(absolutePath, { withFileTypes: true });
61
+ const fileInfos = yield Promise.all(files.map((file) => __awaiter(this, void 0, void 0, function* () {
62
+ const fullPath = path.join(absolutePath, file.name);
63
+ const stats = yield fs_1.promises.stat(fullPath);
64
+ return {
65
+ name: file.name,
66
+ path: fullPath,
67
+ isDirectory: file.isDirectory(),
68
+ size: stats.size,
69
+ createdAt: stats.birthtime,
70
+ updatedAt: stats.mtime,
71
+ };
72
+ })));
73
+ return fileInfos;
74
+ });
75
+ }
76
+ createDirectory(workspace, filePath) {
77
+ return __awaiter(this, void 0, void 0, function* () {
78
+ const absolutePath = this.resolvePath(workspace, filePath);
79
+ yield fs_1.promises.mkdir(absolutePath, { recursive: true });
80
+ const stats = yield fs_1.promises.stat(absolutePath);
81
+ return {
82
+ name: path.basename(absolutePath),
83
+ path: absolutePath,
84
+ isDirectory: true,
85
+ size: stats.size,
86
+ createdAt: stats.birthtime,
87
+ updatedAt: stats.mtime,
88
+ };
89
+ });
90
+ }
91
+ getFile(workspace, filePath) {
92
+ return __awaiter(this, void 0, void 0, function* () {
93
+ const absolutePath = this.resolvePath(workspace, filePath);
94
+ const stats = yield fs_1.promises.stat(absolutePath);
95
+ if (stats.isDirectory()) {
96
+ throw new Error('Path is a directory');
97
+ }
98
+ return {
99
+ name: path.basename(absolutePath),
100
+ path: absolutePath,
101
+ isDirectory: false,
102
+ size: stats.size,
103
+ createdAt: stats.birthtime,
104
+ updatedAt: stats.mtime,
105
+ };
106
+ });
107
+ }
108
+ readFile(workspace, filePath) {
109
+ return __awaiter(this, void 0, void 0, function* () {
110
+ const absolutePath = this.resolvePath(workspace, filePath);
111
+ const data = yield fs_1.promises.readFile(absolutePath, 'utf-8');
112
+ const stats = yield fs_1.promises.stat(absolutePath);
113
+ return {
114
+ name: path.basename(absolutePath),
115
+ path: absolutePath,
116
+ isDirectory: false,
117
+ size: stats.size,
118
+ createdAt: stats.birthtime,
119
+ updatedAt: stats.mtime,
120
+ content: data, // Include file content
121
+ };
122
+ });
123
+ }
124
+ writeFile(workspace, filePath, content) {
125
+ return __awaiter(this, void 0, void 0, function* () {
126
+ const absolutePath = this.resolvePath(workspace, filePath);
127
+ const data = typeof content === "string" ? content : JSON.stringify(content !== null && content !== void 0 ? content : "");
128
+ yield fs_1.promises.writeFile(absolutePath, data);
129
+ const stats = yield fs_1.promises.stat(absolutePath);
130
+ return {
131
+ name: path.basename(absolutePath),
132
+ path: absolutePath,
133
+ isDirectory: false,
134
+ size: stats.size,
135
+ createdAt: stats.birthtime,
136
+ updatedAt: stats.mtime,
137
+ };
138
+ });
139
+ }
140
+ deleteFile(workspace, filePath) {
141
+ return __awaiter(this, void 0, void 0, function* () {
142
+ const absolutePath = this.resolvePath(workspace, filePath);
143
+ const stats = yield fs_1.promises.stat(absolutePath);
144
+ yield fs_1.promises.rm(absolutePath, { recursive: true, force: true });
145
+ return {
146
+ name: path.basename(absolutePath),
147
+ path: absolutePath,
148
+ isDirectory: stats.isDirectory(),
149
+ size: stats.size,
150
+ createdAt: stats.birthtime,
151
+ updatedAt: stats.mtime,
152
+ };
153
+ });
154
+ }
155
+ }
156
+ exports.WorkspaceManager = WorkspaceManager;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @hamak/filesystem-server-impl
3
+ *
4
+ * Backend filesystem server implementation
5
+ */
6
+ export * from './plugin/filesystem-server-plugin';
7
+ export * from './services/workspace-manager';
8
+ export * from './routing/create-router';
9
+ export * from './routing/file-router';
10
+ export * from './middleware/workspace-validator';
11
+ export * from '@hamak/filesystem-server-api';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,cAAc,mCAAmC,CAAC;AAGlD,cAAc,8BAA8B,CAAC;AAG7C,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AAGtC,cAAc,kCAAkC,CAAC;AAGjD,cAAc,8BAA8B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @hamak/filesystem-server-impl
3
+ *
4
+ * Backend filesystem server implementation
5
+ */
6
+ // Export plugin (main entry point)
7
+ export * from './plugin/filesystem-server-plugin';
8
+ // Export services
9
+ export * from './services/workspace-manager';
10
+ // Export routing
11
+ export * from './routing/create-router';
12
+ export * from './routing/file-router';
13
+ // Export middleware
14
+ export * from './middleware/workspace-validator';
15
+ // Re-export API types for convenience
16
+ export * from '@hamak/filesystem-server-api';
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Workspace Validator Middleware
3
+ *
4
+ * Validates workspace ID exists in configuration
5
+ */
6
+ import { Request, Response, NextFunction } from 'express';
7
+ import { FileSystemServerConfig } from '@hamak/filesystem-server-api';
8
+ /**
9
+ * Create middleware to validate workspace exists
10
+ *
11
+ * @param config - Server configuration with workspaces map
12
+ * @returns Express middleware function
13
+ */
14
+ export declare function createWorkspaceValidator(config: FileSystemServerConfig): (req: Request, res: Response, next: NextFunction) => Response<any, Record<string, any>> | undefined;
15
+ //# sourceMappingURL=workspace-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-validator.d.ts","sourceRoot":"","sources":["../../src/middleware/workspace-validator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAEtE;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,sBAAsB,SAGxD,OAAO,OAAO,QAAQ,QAAQ,YAAY,oDAiBxD"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Workspace Validator Middleware
3
+ *
4
+ * Validates workspace ID exists in configuration
5
+ */
6
+ /**
7
+ * Create middleware to validate workspace exists
8
+ *
9
+ * @param config - Server configuration with workspaces map
10
+ * @returns Express middleware function
11
+ */
12
+ export function createWorkspaceValidator(config) {
13
+ const workspaces = config.workspaces;
14
+ return (req, res, next) => {
15
+ const workspace = req.params.workspace;
16
+ if (!workspace) {
17
+ return res.status(400).json({ error: 'Workspace ID is required' });
18
+ }
19
+ if (!workspaces[workspace]) {
20
+ return res.status(404).json({ error: `Workspace '${workspace}' not found` });
21
+ }
22
+ // Store workspace info in request for downstream handlers
23
+ req.workspaceId = workspace;
24
+ req.workspacePath = workspaces[workspace];
25
+ next();
26
+ };
27
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * FileSystem Server Plugin
3
+ *
4
+ * Microkernel plugin for backend filesystem server
5
+ */
6
+ import { Router } from 'express';
7
+ import { PluginModule, InitializationContext } from '@hamak/microkernel-spi';
8
+ import { ActivateContext } from '@hamak/microkernel-api';
9
+ /**
10
+ * FileSystem Server Plugin Implementation
11
+ *
12
+ * Provides backend filesystem operations via Express routes
13
+ */
14
+ export declare class FileSystemServerPlugin implements PluginModule {
15
+ private router?;
16
+ /**
17
+ * Initialize phase - register services in DI container
18
+ */
19
+ initialize(ctx: InitializationContext): void;
20
+ /**
21
+ * Activate phase - create router and make ready for use
22
+ */
23
+ activate(ctx: ActivateContext): void;
24
+ /**
25
+ * Deactivate phase - cleanup
26
+ */
27
+ deactivate(): void;
28
+ /**
29
+ * Get the Express router for mounting in application
30
+ *
31
+ * @returns Express router with filesystem routes
32
+ * @throws Error if plugin not activated
33
+ */
34
+ getRouter(): Router;
35
+ }
36
+ /**
37
+ * Factory function to create plugin instance
38
+ *
39
+ * @returns FileSystemServerPlugin instance
40
+ */
41
+ export declare function createFileSystemServerPlugin(): PluginModule;
42
+ //# sourceMappingURL=filesystem-server-plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filesystem-server-plugin.d.ts","sourceRoot":"","sources":["../../src/plugin/filesystem-server-plugin.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAWzD;;;;GAIG;AACH,qBAAa,sBAAuB,YAAW,YAAY;IACzD,OAAO,CAAC,MAAM,CAAC,CAAS;IAExB;;OAEG;IACH,UAAU,CAAC,GAAG,EAAE,qBAAqB,GAAG,IAAI;IAqB5C;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI;IAiBpC;;OAEG;IACH,UAAU,IAAI,IAAI;IAKlB;;;;;OAKG;IACH,SAAS,IAAI,MAAM;CAQpB;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,IAAI,YAAY,CAE3D"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * FileSystem Server Plugin
3
+ *
4
+ * Microkernel plugin for backend filesystem server
5
+ */
6
+ import { FILESYSTEM_SERVER_CONFIG_TOKEN, WORKSPACE_MANAGER_TOKEN, } from '@hamak/filesystem-server-api';
7
+ import { WorkspaceManager } from '../services/workspace-manager';
8
+ import { createFileSystemRouter } from '../routing/create-router';
9
+ /**
10
+ * FileSystem Server Plugin Implementation
11
+ *
12
+ * Provides backend filesystem operations via Express routes
13
+ */
14
+ export class FileSystemServerPlugin {
15
+ /**
16
+ * Initialize phase - register services in DI container
17
+ */
18
+ initialize(ctx) {
19
+ console.log('[FileSystemServerPlugin] Initializing...');
20
+ // Resolve configuration
21
+ const config = ctx.resolve(FILESYSTEM_SERVER_CONFIG_TOKEN);
22
+ // Create and register WorkspaceManager service
23
+ const workspaceManager = new WorkspaceManager(config.workspaces, {
24
+ baseDirectory: config.baseDirectory,
25
+ });
26
+ ctx.provide({
27
+ provide: WORKSPACE_MANAGER_TOKEN,
28
+ useValue: workspaceManager,
29
+ });
30
+ console.log('[FileSystemServerPlugin] WorkspaceManager service registered');
31
+ console.log(`[FileSystemServerPlugin] Base directory: ${config.baseDirectory}`);
32
+ console.log(`[FileSystemServerPlugin] Workspaces:`, Object.keys(config.workspaces));
33
+ }
34
+ /**
35
+ * Activate phase - create router and make ready for use
36
+ */
37
+ activate(ctx) {
38
+ console.log('[FileSystemServerPlugin] Activating...');
39
+ // Resolve dependencies
40
+ const workspaceManager = ctx.resolve(WORKSPACE_MANAGER_TOKEN);
41
+ const config = ctx.resolve(FILESYSTEM_SERVER_CONFIG_TOKEN);
42
+ // Create Express router
43
+ this.router = createFileSystemRouter(workspaceManager, config);
44
+ // Note: Router is available via getRouter() method, not registered in DI
45
+ // This avoids circular dependencies and keeps the plugin self-contained
46
+ console.log('[FileSystemServerPlugin] Activated successfully');
47
+ console.log(`[FileSystemServerPlugin] Mount path: ${config.mountPath ?? '/api/workspaces'}`);
48
+ }
49
+ /**
50
+ * Deactivate phase - cleanup
51
+ */
52
+ deactivate() {
53
+ console.log('[FileSystemServerPlugin] Deactivating...');
54
+ this.router = undefined;
55
+ }
56
+ /**
57
+ * Get the Express router for mounting in application
58
+ *
59
+ * @returns Express router with filesystem routes
60
+ * @throws Error if plugin not activated
61
+ */
62
+ getRouter() {
63
+ if (!this.router) {
64
+ throw new Error('[FileSystemServerPlugin] Plugin not activated. Call kernel.activate() first.');
65
+ }
66
+ return this.router;
67
+ }
68
+ }
69
+ /**
70
+ * Factory function to create plugin instance
71
+ *
72
+ * @returns FileSystemServerPlugin instance
73
+ */
74
+ export function createFileSystemServerPlugin() {
75
+ return new FileSystemServerPlugin();
76
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Router Factory
3
+ *
4
+ * Creates and configures the Express router for filesystem operations
5
+ */
6
+ import { Router } from 'express';
7
+ import { IWorkspaceManager, FileSystemServerConfig } from '@hamak/filesystem-server-api';
8
+ /**
9
+ * Create filesystem Express router with middleware
10
+ *
11
+ * @param workspaceManager - Workspace manager instance
12
+ * @param config - Server configuration
13
+ * @returns Configured Express router
14
+ */
15
+ export declare function createFileSystemRouter(workspaceManager: IWorkspaceManager, config: FileSystemServerConfig): Router;
16
+ //# sourceMappingURL=create-router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-router.d.ts","sourceRoot":"","sources":["../../src/routing/create-router.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAoC,MAAM,8BAA8B,CAAC;AAG3H;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,gBAAgB,EAAE,iBAAiB,EACnC,MAAM,EAAE,sBAAsB,GAC7B,MAAM,CAiBR"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Router Factory
3
+ *
4
+ * Creates and configures the Express router for filesystem operations
5
+ */
6
+ import { Router } from 'express';
7
+ import cors from 'cors';
8
+ import bodyParser from 'body-parser';
9
+ import { DEFAULT_FILESYSTEM_SERVER_CONFIG } from '@hamak/filesystem-server-api';
10
+ import { FileRouter } from './file-router';
11
+ /**
12
+ * Create filesystem Express router with middleware
13
+ *
14
+ * @param workspaceManager - Workspace manager instance
15
+ * @param config - Server configuration
16
+ * @returns Configured Express router
17
+ */
18
+ export function createFileSystemRouter(workspaceManager, config) {
19
+ const app = Router();
20
+ // Apply CORS middleware if enabled
21
+ if (config.enableCors ?? DEFAULT_FILESYSTEM_SERVER_CONFIG.enableCors) {
22
+ app.use(cors());
23
+ }
24
+ // Apply body parser for JSON payloads
25
+ app.use(bodyParser.json());
26
+ // Create and mount file router
27
+ const fileRouter = new FileRouter(workspaceManager);
28
+ const mountPath = config.mountPath ?? DEFAULT_FILESYSTEM_SERVER_CONFIG.mountPath;
29
+ app.use(mountPath, fileRouter.router);
30
+ return app;
31
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * File Router
3
+ *
4
+ * Express routes for filesystem operations
5
+ * Migrated from amk/libs/server/ws/ws-server
6
+ */
7
+ import { Router } from 'express';
8
+ import { IWorkspaceManager } from '@hamak/filesystem-server-api';
9
+ export declare class FileRouter {
10
+ router: Router;
11
+ private workspaceManager;
12
+ constructor(workspaceManager: IWorkspaceManager);
13
+ private initializeRoutes;
14
+ private parsePath;
15
+ private listFiles;
16
+ private createDirectory;
17
+ private getFile;
18
+ private readFile;
19
+ private writeFile;
20
+ private deleteFile;
21
+ }
22
+ //# sourceMappingURL=file-router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-router.d.ts","sourceRoot":"","sources":["../../src/routing/file-router.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE,qBAAa,UAAU;IACd,MAAM,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,gBAAgB,CAAoB;gBAEhC,gBAAgB,EAAE,iBAAiB;IAM/C,OAAO,CAAC,gBAAgB;IAUxB,OAAO,CAAC,SAAS;YAKH,SAAS;YAWT,eAAe;YAWf,OAAO;YAWP,QAAQ;YAWR,SAAS;YAYT,UAAU;CAUzB"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * File Router
3
+ *
4
+ * Express routes for filesystem operations
5
+ * Migrated from amk/libs/server/ws/ws-server
6
+ */
7
+ import { Router } from 'express';
8
+ export class FileRouter {
9
+ constructor(workspaceManager) {
10
+ this.router = Router();
11
+ this.workspaceManager = workspaceManager;
12
+ this.initializeRoutes();
13
+ }
14
+ initializeRoutes() {
15
+ this.router.get('/:workspace/files/*', this.listFiles.bind(this));
16
+ this.router.post('/:workspace/mkdir/*', this.createDirectory.bind(this));
17
+ this.router.get('/:workspace/get/*', this.getFile.bind(this));
18
+ this.router.get('/:workspace/read/*', this.readFile.bind(this));
19
+ this.router.post('/:workspace/post/*', this.writeFile.bind(this));
20
+ this.router.put('/:workspace/put/*', this.writeFile.bind(this));
21
+ this.router.delete('/:workspace/delete/*', this.deleteFile.bind(this));
22
+ }
23
+ parsePath(pathString) {
24
+ // Split path string into segments, filter out empty segments
25
+ return pathString.split('/').filter(seg => seg.length > 0);
26
+ }
27
+ async listFiles(req, res) {
28
+ try {
29
+ const { workspace } = req.params;
30
+ const pathSegments = this.parsePath(req.params[0] || '');
31
+ const files = await this.workspaceManager.listFiles(workspace, pathSegments);
32
+ res.json(files);
33
+ }
34
+ catch (error) {
35
+ res.status(500).json({ error: error.message });
36
+ }
37
+ }
38
+ async createDirectory(req, res) {
39
+ try {
40
+ const { workspace } = req.params;
41
+ const pathSegments = this.parsePath(req.params[0] || '');
42
+ const dirInfo = await this.workspaceManager.createDirectory(workspace, pathSegments);
43
+ res.json(dirInfo);
44
+ }
45
+ catch (error) {
46
+ res.status(500).json({ error: error.message });
47
+ }
48
+ }
49
+ async getFile(req, res) {
50
+ try {
51
+ const { workspace } = req.params;
52
+ const pathSegments = this.parsePath(req.params[0] || '');
53
+ const fileInfo = await this.workspaceManager.getFile(workspace, pathSegments);
54
+ res.json(fileInfo);
55
+ }
56
+ catch (error) {
57
+ res.status(500).json({ error: error.message });
58
+ }
59
+ }
60
+ async readFile(req, res) {
61
+ try {
62
+ const { workspace } = req.params;
63
+ const pathSegments = this.parsePath(req.params[0] || '');
64
+ const fileInfo = await this.workspaceManager.readFile(workspace, pathSegments);
65
+ res.json(fileInfo);
66
+ }
67
+ catch (error) {
68
+ res.status(500).json({ error: error.message });
69
+ }
70
+ }
71
+ async writeFile(req, res) {
72
+ try {
73
+ const { workspace } = req.params;
74
+ const pathSegments = this.parsePath(req.params[0] || '');
75
+ const { content } = req.body;
76
+ const fileInfo = await this.workspaceManager.writeFile(workspace, pathSegments, content);
77
+ res.json(fileInfo);
78
+ }
79
+ catch (error) {
80
+ res.status(500).json({ error: error.message });
81
+ }
82
+ }
83
+ async deleteFile(req, res) {
84
+ try {
85
+ const { workspace } = req.params;
86
+ const pathSegments = this.parsePath(req.params[0] || '');
87
+ const fileInfo = await this.workspaceManager.deleteFile(workspace, pathSegments);
88
+ res.json(fileInfo);
89
+ }
90
+ catch (error) {
91
+ res.status(500).json({ error: error.message });
92
+ }
93
+ }
94
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Workspace Manager Implementation
3
+ *
4
+ * Provides filesystem operations for workspace-based storage
5
+ * Migrated from amk/libs/server/ws/ws-server
6
+ */
7
+ import { FileInfo } from '@hamak/shared-utils';
8
+ import { IWorkspaceManager } from '@hamak/filesystem-server-api';
9
+ export interface WorkspaceManagerOptions {
10
+ baseDirectory: string;
11
+ }
12
+ export declare class WorkspaceManager implements IWorkspaceManager {
13
+ private workspaces;
14
+ private options;
15
+ constructor(workspaces: Record<string, string>, options: WorkspaceManagerOptions);
16
+ private resolvePath;
17
+ listFiles(workspace: string, filePath: string | string[]): Promise<FileInfo[]>;
18
+ createDirectory(workspace: string, filePath: string | string[]): Promise<FileInfo>;
19
+ getFile(workspace: string, filePath: string | string[]): Promise<FileInfo>;
20
+ readFile(workspace: string, filePath: string | string[]): Promise<FileInfo>;
21
+ writeFile(workspace: string, filePath: string | string[], content: string): Promise<FileInfo>;
22
+ deleteFile(workspace: string, filePath: string | string[]): Promise<FileInfo>;
23
+ }
24
+ //# sourceMappingURL=workspace-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-manager.d.ts","sourceRoot":"","sources":["../../src/services/workspace-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGjE,MAAM,WAAW,uBAAuB;IACtC,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,gBAAiB,YAAW,iBAAiB;IACxD,OAAO,CAAC,UAAU,CAAyB;IAC3C,OAAO,CAAC,OAAO,CAA0B;gBAE7B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,uBAAuB;IAKhF,OAAO,CAAC,WAAW;IASN,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAoB9E,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC;IAclF,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC;IAgB1E,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC;IAgB3E,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAe7F,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC;CAa3F"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Workspace Manager Implementation
3
+ *
4
+ * Provides filesystem operations for workspace-based storage
5
+ * Migrated from amk/libs/server/ws/ws-server
6
+ */
7
+ import { promises as fs } from 'fs';
8
+ import * as path from 'path';
9
+ export class WorkspaceManager {
10
+ constructor(workspaces, options) {
11
+ this.workspaces = workspaces;
12
+ this.options = options;
13
+ }
14
+ resolvePath(workspace, filePath) {
15
+ const basePath = path.resolve(this.options.baseDirectory, this.workspaces[workspace]);
16
+ if (!basePath) {
17
+ throw new Error(`Workspace ${workspace} not found`);
18
+ }
19
+ const relativePath = Array.isArray(filePath) ? filePath.join(path.sep) : filePath;
20
+ return path.resolve(basePath, relativePath);
21
+ }
22
+ async listFiles(workspace, filePath) {
23
+ const absolutePath = this.resolvePath(workspace, filePath);
24
+ const files = await fs.readdir(absolutePath, { withFileTypes: true });
25
+ const fileInfos = await Promise.all(files.map(async (file) => {
26
+ const fullPath = path.join(absolutePath, file.name);
27
+ const stats = await fs.stat(fullPath);
28
+ return {
29
+ name: file.name,
30
+ path: fullPath,
31
+ isDirectory: file.isDirectory(),
32
+ size: stats.size,
33
+ createdAt: stats.birthtime,
34
+ updatedAt: stats.mtime,
35
+ };
36
+ }));
37
+ return fileInfos;
38
+ }
39
+ async createDirectory(workspace, filePath) {
40
+ const absolutePath = this.resolvePath(workspace, filePath);
41
+ await fs.mkdir(absolutePath, { recursive: true });
42
+ const stats = await fs.stat(absolutePath);
43
+ return {
44
+ name: path.basename(absolutePath),
45
+ path: absolutePath,
46
+ isDirectory: true,
47
+ size: stats.size,
48
+ createdAt: stats.birthtime,
49
+ updatedAt: stats.mtime,
50
+ };
51
+ }
52
+ async getFile(workspace, filePath) {
53
+ const absolutePath = this.resolvePath(workspace, filePath);
54
+ const stats = await fs.stat(absolutePath);
55
+ if (stats.isDirectory()) {
56
+ throw new Error('Path is a directory');
57
+ }
58
+ return {
59
+ name: path.basename(absolutePath),
60
+ path: absolutePath,
61
+ isDirectory: false,
62
+ size: stats.size,
63
+ createdAt: stats.birthtime,
64
+ updatedAt: stats.mtime,
65
+ };
66
+ }
67
+ async readFile(workspace, filePath) {
68
+ const absolutePath = this.resolvePath(workspace, filePath);
69
+ const data = await fs.readFile(absolutePath, 'utf-8');
70
+ const stats = await fs.stat(absolutePath);
71
+ return {
72
+ name: path.basename(absolutePath),
73
+ path: absolutePath,
74
+ isDirectory: false,
75
+ size: stats.size,
76
+ createdAt: stats.birthtime,
77
+ updatedAt: stats.mtime,
78
+ content: data, // Include file content
79
+ };
80
+ }
81
+ async writeFile(workspace, filePath, content) {
82
+ const absolutePath = this.resolvePath(workspace, filePath);
83
+ const data = typeof content === "string" ? content : JSON.stringify(content ?? "");
84
+ await fs.writeFile(absolutePath, data);
85
+ const stats = await fs.stat(absolutePath);
86
+ return {
87
+ name: path.basename(absolutePath),
88
+ path: absolutePath,
89
+ isDirectory: false,
90
+ size: stats.size,
91
+ createdAt: stats.birthtime,
92
+ updatedAt: stats.mtime,
93
+ };
94
+ }
95
+ async deleteFile(workspace, filePath) {
96
+ const absolutePath = this.resolvePath(workspace, filePath);
97
+ const stats = await fs.stat(absolutePath);
98
+ await fs.rm(absolutePath, { recursive: true, force: true });
99
+ return {
100
+ name: path.basename(absolutePath),
101
+ path: absolutePath,
102
+ isDirectory: stats.isDirectory(),
103
+ size: stats.size,
104
+ createdAt: stats.birthtime,
105
+ updatedAt: stats.mtime,
106
+ };
107
+ }
108
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@hamak/filesystem-server-impl",
3
+ "version": "0.4.8",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "FileSystem Server Implementation - Backend filesystem server with Express routes",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "sideEffects": false,
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/amah/app-framework.git",
16
+ "directory": "packages/backend/filesystem-server/filesystem-server-impl"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.json && tsc -p tsconfig.es2015.json",
23
+ "clean": "rm -rf dist",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest"
26
+ },
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js",
31
+ "require": "./dist/es2015/index.js",
32
+ "default": "./dist/index.js"
33
+ },
34
+ "./es2015": {
35
+ "require": "./dist/es2015/index.js",
36
+ "default": "./dist/es2015/index.js"
37
+ }
38
+ },
39
+ "dependencies": {
40
+ "@hamak/filesystem-server-api": "0.4.7",
41
+ "@hamak/filesystem-server-spi": "0.4.7",
42
+ "@hamak/microkernel-api": "0.4.7",
43
+ "@hamak/microkernel-spi": "0.4.7",
44
+ "@hamak/shared-utils": "0.4.7",
45
+ "cors": "^2.8.5",
46
+ "express": "^4.18.0"
47
+ },
48
+ "devDependencies": {
49
+ "@types/cors": "^2.8.0",
50
+ "@types/express": "^4.17.0",
51
+ "typescript": "~5.4.0",
52
+ "vitest": "^2.0.0"
53
+ }
54
+ }