@geanatz/cortex-mcp 5.0.1
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.
- package/LICENSE +21 -0
- package/README.md +281 -0
- package/dist/errors/errors.d.ts +109 -0
- package/dist/errors/errors.js +199 -0
- package/dist/errors/index.d.ts +4 -0
- package/dist/errors/index.js +4 -0
- package/dist/features/task-management/models/artifact.d.ts +169 -0
- package/dist/features/task-management/models/artifact.js +155 -0
- package/dist/features/task-management/models/config.d.ts +54 -0
- package/dist/features/task-management/models/config.js +54 -0
- package/dist/features/task-management/models/index.d.ts +6 -0
- package/dist/features/task-management/models/index.js +6 -0
- package/dist/features/task-management/models/task.d.ts +173 -0
- package/dist/features/task-management/models/task.js +84 -0
- package/dist/features/task-management/storage/file-storage.d.ts +130 -0
- package/dist/features/task-management/storage/file-storage.js +575 -0
- package/dist/features/task-management/storage/index.d.ts +5 -0
- package/dist/features/task-management/storage/index.js +5 -0
- package/dist/features/task-management/storage/storage.d.ts +159 -0
- package/dist/features/task-management/storage/storage.js +37 -0
- package/dist/features/task-management/tools/artifacts/index.d.ts +6 -0
- package/dist/features/task-management/tools/artifacts/index.js +174 -0
- package/dist/features/task-management/tools/base/handlers.d.ts +7 -0
- package/dist/features/task-management/tools/base/handlers.js +15 -0
- package/dist/features/task-management/tools/base/index.d.ts +3 -0
- package/dist/features/task-management/tools/base/index.js +3 -0
- package/dist/features/task-management/tools/base/schemas.d.ts +3 -0
- package/dist/features/task-management/tools/base/schemas.js +6 -0
- package/dist/features/task-management/tools/base/types.d.ts +13 -0
- package/dist/features/task-management/tools/base/types.js +1 -0
- package/dist/features/task-management/tools/tasks/index.d.ts +10 -0
- package/dist/features/task-management/tools/tasks/index.js +500 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +57 -0
- package/dist/server.d.ts +11 -0
- package/dist/server.js +61 -0
- package/dist/types/common.d.ts +10 -0
- package/dist/types/common.js +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +5 -0
- package/dist/utils/cache.d.ts +104 -0
- package/dist/utils/cache.js +196 -0
- package/dist/utils/file-utils.d.ts +101 -0
- package/dist/utils/file-utils.js +270 -0
- package/dist/utils/index.d.ts +12 -0
- package/dist/utils/index.js +12 -0
- package/dist/utils/logger.d.ts +77 -0
- package/dist/utils/logger.js +173 -0
- package/dist/utils/response-builder.d.ts +4 -0
- package/dist/utils/response-builder.js +19 -0
- package/dist/utils/storage-config.d.ts +29 -0
- package/dist/utils/storage-config.js +51 -0
- package/dist/utils/string-utils.d.ts +2 -0
- package/dist/utils/string-utils.js +16 -0
- package/dist/utils/validation.d.ts +9 -0
- package/dist/utils/validation.js +9 -0
- package/dist/utils/version.d.ts +9 -0
- package/dist/utils/version.js +41 -0
- package/package.json +60 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory cache with TTL support
|
|
3
|
+
* Provides efficient caching for storage operations
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Cache configuration options
|
|
7
|
+
*/
|
|
8
|
+
export interface CacheOptions {
|
|
9
|
+
/** Default TTL in milliseconds (default: 5 minutes) */
|
|
10
|
+
defaultTtl?: number;
|
|
11
|
+
/** Maximum number of entries (default: 1000) */
|
|
12
|
+
maxSize?: number;
|
|
13
|
+
/** Enable statistics tracking (default: true) */
|
|
14
|
+
trackStats?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Cache statistics
|
|
18
|
+
*/
|
|
19
|
+
export interface CacheStats {
|
|
20
|
+
hits: number;
|
|
21
|
+
misses: number;
|
|
22
|
+
size: number;
|
|
23
|
+
evictions: number;
|
|
24
|
+
hitRate: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Generic in-memory cache with TTL and LRU eviction
|
|
28
|
+
*/
|
|
29
|
+
export declare class Cache<T> {
|
|
30
|
+
private readonly cache;
|
|
31
|
+
private readonly defaultTtl;
|
|
32
|
+
private readonly maxSize;
|
|
33
|
+
private readonly trackStats;
|
|
34
|
+
private stats;
|
|
35
|
+
constructor(options?: CacheOptions);
|
|
36
|
+
/**
|
|
37
|
+
* Get a value from cache
|
|
38
|
+
*/
|
|
39
|
+
get(key: string): T | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Set a value in cache
|
|
42
|
+
*/
|
|
43
|
+
set(key: string, value: T, ttl?: number): void;
|
|
44
|
+
/**
|
|
45
|
+
* Check if key exists and is not expired
|
|
46
|
+
*/
|
|
47
|
+
has(key: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Delete a key from cache
|
|
50
|
+
*/
|
|
51
|
+
delete(key: string): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Delete all keys matching a prefix
|
|
54
|
+
*/
|
|
55
|
+
deletePrefix(prefix: string): number;
|
|
56
|
+
/**
|
|
57
|
+
* Clear all entries
|
|
58
|
+
*/
|
|
59
|
+
clear(): void;
|
|
60
|
+
/**
|
|
61
|
+
* Get cache statistics
|
|
62
|
+
*/
|
|
63
|
+
getStats(): CacheStats;
|
|
64
|
+
/**
|
|
65
|
+
* Reset statistics
|
|
66
|
+
*/
|
|
67
|
+
resetStats(): void;
|
|
68
|
+
/**
|
|
69
|
+
* Get or set with async factory
|
|
70
|
+
*/
|
|
71
|
+
getOrSet(key: string, factory: () => Promise<T>, ttl?: number): Promise<T>;
|
|
72
|
+
/**
|
|
73
|
+
* Invalidate entries matching a pattern
|
|
74
|
+
*/
|
|
75
|
+
invalidate(pattern: RegExp): number;
|
|
76
|
+
/**
|
|
77
|
+
* Prune expired entries
|
|
78
|
+
*/
|
|
79
|
+
prune(): number;
|
|
80
|
+
/**
|
|
81
|
+
* Evict least recently used entry
|
|
82
|
+
*/
|
|
83
|
+
private evictLRU;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Create cache keys for different entity types
|
|
87
|
+
*/
|
|
88
|
+
export declare const CacheKeys: {
|
|
89
|
+
readonly task: (id: string) => string;
|
|
90
|
+
readonly taskList: (parentId?: string) => string;
|
|
91
|
+
readonly allTasks: () => string;
|
|
92
|
+
readonly taskFolders: () => string;
|
|
93
|
+
readonly artifact: (taskId: string, phase: string) => string;
|
|
94
|
+
readonly allArtifacts: (taskId: string) => string;
|
|
95
|
+
readonly hierarchy: (parentId?: string) => string;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Cache invalidation patterns
|
|
99
|
+
*/
|
|
100
|
+
export declare const InvalidationPatterns: {
|
|
101
|
+
readonly task: (id: string) => RegExp;
|
|
102
|
+
readonly allTasks: () => RegExp;
|
|
103
|
+
readonly artifact: (taskId: string) => RegExp;
|
|
104
|
+
};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory cache with TTL support
|
|
3
|
+
* Provides efficient caching for storage operations
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Generic in-memory cache with TTL and LRU eviction
|
|
7
|
+
*/
|
|
8
|
+
export class Cache {
|
|
9
|
+
cache = new Map();
|
|
10
|
+
defaultTtl;
|
|
11
|
+
maxSize;
|
|
12
|
+
trackStats;
|
|
13
|
+
stats = {
|
|
14
|
+
hits: 0,
|
|
15
|
+
misses: 0,
|
|
16
|
+
evictions: 0,
|
|
17
|
+
};
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
this.defaultTtl = options.defaultTtl ?? 5 * 60 * 1000; // 5 minutes
|
|
20
|
+
this.maxSize = options.maxSize ?? 1000;
|
|
21
|
+
this.trackStats = options.trackStats ?? true;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get a value from cache
|
|
25
|
+
*/
|
|
26
|
+
get(key) {
|
|
27
|
+
const entry = this.cache.get(key);
|
|
28
|
+
if (!entry) {
|
|
29
|
+
if (this.trackStats)
|
|
30
|
+
this.stats.misses++;
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
// Check expiration
|
|
34
|
+
if (Date.now() > entry.expiresAt) {
|
|
35
|
+
this.cache.delete(key);
|
|
36
|
+
if (this.trackStats)
|
|
37
|
+
this.stats.misses++;
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
// Update access metadata
|
|
41
|
+
entry.accessCount++;
|
|
42
|
+
entry.lastAccessedAt = Date.now();
|
|
43
|
+
if (this.trackStats)
|
|
44
|
+
this.stats.hits++;
|
|
45
|
+
return entry.value;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Set a value in cache
|
|
49
|
+
*/
|
|
50
|
+
set(key, value, ttl) {
|
|
51
|
+
// Evict if at capacity
|
|
52
|
+
if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
|
|
53
|
+
this.evictLRU();
|
|
54
|
+
}
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
this.cache.set(key, {
|
|
57
|
+
value,
|
|
58
|
+
createdAt: now,
|
|
59
|
+
expiresAt: now + (ttl ?? this.defaultTtl),
|
|
60
|
+
accessCount: 0,
|
|
61
|
+
lastAccessedAt: now,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Check if key exists and is not expired
|
|
66
|
+
*/
|
|
67
|
+
has(key) {
|
|
68
|
+
const entry = this.cache.get(key);
|
|
69
|
+
if (!entry)
|
|
70
|
+
return false;
|
|
71
|
+
if (Date.now() > entry.expiresAt) {
|
|
72
|
+
this.cache.delete(key);
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Delete a key from cache
|
|
79
|
+
*/
|
|
80
|
+
delete(key) {
|
|
81
|
+
return this.cache.delete(key);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Delete all keys matching a prefix
|
|
85
|
+
*/
|
|
86
|
+
deletePrefix(prefix) {
|
|
87
|
+
let count = 0;
|
|
88
|
+
for (const key of this.cache.keys()) {
|
|
89
|
+
if (key.startsWith(prefix)) {
|
|
90
|
+
this.cache.delete(key);
|
|
91
|
+
count++;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return count;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Clear all entries
|
|
98
|
+
*/
|
|
99
|
+
clear() {
|
|
100
|
+
this.cache.clear();
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get cache statistics
|
|
104
|
+
*/
|
|
105
|
+
getStats() {
|
|
106
|
+
const total = this.stats.hits + this.stats.misses;
|
|
107
|
+
return {
|
|
108
|
+
...this.stats,
|
|
109
|
+
size: this.cache.size,
|
|
110
|
+
hitRate: total > 0 ? this.stats.hits / total : 0,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Reset statistics
|
|
115
|
+
*/
|
|
116
|
+
resetStats() {
|
|
117
|
+
this.stats = { hits: 0, misses: 0, evictions: 0 };
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get or set with async factory
|
|
121
|
+
*/
|
|
122
|
+
async getOrSet(key, factory, ttl) {
|
|
123
|
+
const cached = this.get(key);
|
|
124
|
+
if (cached !== undefined) {
|
|
125
|
+
return cached;
|
|
126
|
+
}
|
|
127
|
+
const value = await factory();
|
|
128
|
+
this.set(key, value, ttl);
|
|
129
|
+
return value;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Invalidate entries matching a pattern
|
|
133
|
+
*/
|
|
134
|
+
invalidate(pattern) {
|
|
135
|
+
let count = 0;
|
|
136
|
+
for (const key of this.cache.keys()) {
|
|
137
|
+
if (pattern.test(key)) {
|
|
138
|
+
this.cache.delete(key);
|
|
139
|
+
count++;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return count;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Prune expired entries
|
|
146
|
+
*/
|
|
147
|
+
prune() {
|
|
148
|
+
const now = Date.now();
|
|
149
|
+
let count = 0;
|
|
150
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
151
|
+
if (now > entry.expiresAt) {
|
|
152
|
+
this.cache.delete(key);
|
|
153
|
+
count++;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return count;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Evict least recently used entry
|
|
160
|
+
*/
|
|
161
|
+
evictLRU() {
|
|
162
|
+
let oldestKey = null;
|
|
163
|
+
let oldestTime = Infinity;
|
|
164
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
165
|
+
if (entry.lastAccessedAt < oldestTime) {
|
|
166
|
+
oldestTime = entry.lastAccessedAt;
|
|
167
|
+
oldestKey = key;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (oldestKey) {
|
|
171
|
+
this.cache.delete(oldestKey);
|
|
172
|
+
if (this.trackStats)
|
|
173
|
+
this.stats.evictions++;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Create cache keys for different entity types
|
|
179
|
+
*/
|
|
180
|
+
export const CacheKeys = {
|
|
181
|
+
task: (id) => `task:${id}`,
|
|
182
|
+
taskList: (parentId) => `tasks:${parentId ?? 'root'}`,
|
|
183
|
+
allTasks: () => 'tasks:all',
|
|
184
|
+
taskFolders: () => 'task-folders',
|
|
185
|
+
artifact: (taskId, phase) => `artifact:${taskId}:${phase}`,
|
|
186
|
+
allArtifacts: (taskId) => `artifacts:${taskId}`,
|
|
187
|
+
hierarchy: (parentId) => `hierarchy:${parentId ?? 'root'}`,
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* Cache invalidation patterns
|
|
191
|
+
*/
|
|
192
|
+
export const InvalidationPatterns = {
|
|
193
|
+
task: (id) => new RegExp(`^(task:${id}|tasks:|hierarchy:)`),
|
|
194
|
+
allTasks: () => /^(task:|tasks:|hierarchy:|task-folders)/,
|
|
195
|
+
artifact: (taskId) => new RegExp(`^artifact(s)?:${taskId}`),
|
|
196
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a file or directory exists
|
|
3
|
+
*/
|
|
4
|
+
export declare function fileExists(filePath: string): Promise<boolean>;
|
|
5
|
+
/**
|
|
6
|
+
* Check if path is readable
|
|
7
|
+
*/
|
|
8
|
+
export declare function isReadable(filePath: string): Promise<boolean>;
|
|
9
|
+
/**
|
|
10
|
+
* Check if path is writable
|
|
11
|
+
*/
|
|
12
|
+
export declare function isWritable(filePath: string): Promise<boolean>;
|
|
13
|
+
/**
|
|
14
|
+
* Check if path is a directory
|
|
15
|
+
*/
|
|
16
|
+
export declare function isDirectory(filePath: string): Promise<boolean>;
|
|
17
|
+
/**
|
|
18
|
+
* Check if path is a file
|
|
19
|
+
*/
|
|
20
|
+
export declare function isFile(filePath: string): Promise<boolean>;
|
|
21
|
+
/**
|
|
22
|
+
* Ensure directory exists, creating it if necessary
|
|
23
|
+
*/
|
|
24
|
+
export declare function ensureDirectory(dirPath: string): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Atomic file write using temporary file
|
|
27
|
+
* Ensures data integrity by writing to temp file first, then renaming
|
|
28
|
+
*/
|
|
29
|
+
export declare function atomicWriteFile(filePath: string, content: string): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Read file with error handling
|
|
32
|
+
*/
|
|
33
|
+
export declare function readFile(filePath: string): Promise<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Read file or return null if not found
|
|
36
|
+
*/
|
|
37
|
+
export declare function readFileOrNull(filePath: string): Promise<string | null>;
|
|
38
|
+
/**
|
|
39
|
+
* Read and parse JSON file
|
|
40
|
+
*/
|
|
41
|
+
export declare function readJsonFile<T>(filePath: string): Promise<T>;
|
|
42
|
+
/**
|
|
43
|
+
* Read JSON file or return null if not found
|
|
44
|
+
*/
|
|
45
|
+
export declare function readJsonFileOrNull<T>(filePath: string): Promise<T | null>;
|
|
46
|
+
/**
|
|
47
|
+
* Write JSON file with pretty printing
|
|
48
|
+
*/
|
|
49
|
+
export declare function writeJsonFile<T>(filePath: string, data: T, indent?: number): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Delete file if it exists
|
|
52
|
+
*/
|
|
53
|
+
export declare function deleteFile(filePath: string): Promise<boolean>;
|
|
54
|
+
/**
|
|
55
|
+
* Delete directory recursively
|
|
56
|
+
*/
|
|
57
|
+
export declare function deleteDirectory(dirPath: string): Promise<boolean>;
|
|
58
|
+
/**
|
|
59
|
+
* List directory contents with filtering options
|
|
60
|
+
*/
|
|
61
|
+
export interface ListDirectoryOptions {
|
|
62
|
+
/** Only return directories */
|
|
63
|
+
directoriesOnly?: boolean;
|
|
64
|
+
/** Only return files */
|
|
65
|
+
filesOnly?: boolean;
|
|
66
|
+
/** Filter by pattern (regex or string) */
|
|
67
|
+
pattern?: RegExp | string;
|
|
68
|
+
/** Sort results */
|
|
69
|
+
sort?: boolean;
|
|
70
|
+
}
|
|
71
|
+
export declare function listDirectory(dirPath: string, options?: ListDirectoryOptions): Promise<string[]>;
|
|
72
|
+
/**
|
|
73
|
+
* Copy file
|
|
74
|
+
*/
|
|
75
|
+
export declare function copyFile(source: string, destination: string): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Move file (rename)
|
|
78
|
+
*/
|
|
79
|
+
export declare function moveFile(source: string, destination: string): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Get file stats or null if not found
|
|
82
|
+
*/
|
|
83
|
+
export declare function getFileStats(filePath: string): Promise<{
|
|
84
|
+
size: number;
|
|
85
|
+
created: Date;
|
|
86
|
+
modified: Date;
|
|
87
|
+
isDirectory: boolean;
|
|
88
|
+
isFile: boolean;
|
|
89
|
+
} | null>;
|
|
90
|
+
/**
|
|
91
|
+
* Resolve path relative to base
|
|
92
|
+
*/
|
|
93
|
+
export declare function resolvePath(base: string, ...paths: string[]): string;
|
|
94
|
+
/**
|
|
95
|
+
* Get relative path
|
|
96
|
+
*/
|
|
97
|
+
export declare function getRelativePath(from: string, to: string): string;
|
|
98
|
+
/**
|
|
99
|
+
* Join paths
|
|
100
|
+
*/
|
|
101
|
+
export declare function joinPath(...paths: string[]): string;
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { promises as fs, constants } from 'fs';
|
|
2
|
+
import { dirname, join, relative, resolve } from 'path';
|
|
3
|
+
import { StorageError } from '../errors/errors.js';
|
|
4
|
+
/**
|
|
5
|
+
* Check if a file or directory exists
|
|
6
|
+
*/
|
|
7
|
+
export async function fileExists(filePath) {
|
|
8
|
+
try {
|
|
9
|
+
await fs.access(filePath, constants.F_OK);
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Check if path is readable
|
|
18
|
+
*/
|
|
19
|
+
export async function isReadable(filePath) {
|
|
20
|
+
try {
|
|
21
|
+
await fs.access(filePath, constants.R_OK);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Check if path is writable
|
|
30
|
+
*/
|
|
31
|
+
export async function isWritable(filePath) {
|
|
32
|
+
try {
|
|
33
|
+
await fs.access(filePath, constants.W_OK);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Check if path is a directory
|
|
42
|
+
*/
|
|
43
|
+
export async function isDirectory(filePath) {
|
|
44
|
+
try {
|
|
45
|
+
const stats = await fs.stat(filePath);
|
|
46
|
+
return stats.isDirectory();
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Check if path is a file
|
|
54
|
+
*/
|
|
55
|
+
export async function isFile(filePath) {
|
|
56
|
+
try {
|
|
57
|
+
const stats = await fs.stat(filePath);
|
|
58
|
+
return stats.isFile();
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Ensure directory exists, creating it if necessary
|
|
66
|
+
*/
|
|
67
|
+
export async function ensureDirectory(dirPath) {
|
|
68
|
+
try {
|
|
69
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
// Ignore if directory already exists
|
|
73
|
+
if (error.code !== 'EEXIST') {
|
|
74
|
+
throw StorageError.directoryError(dirPath, error);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Atomic file write using temporary file
|
|
80
|
+
* Ensures data integrity by writing to temp file first, then renaming
|
|
81
|
+
*/
|
|
82
|
+
export async function atomicWriteFile(filePath, content) {
|
|
83
|
+
const tempFile = `${filePath}.${Date.now()}.tmp`;
|
|
84
|
+
try {
|
|
85
|
+
// Ensure parent directory exists
|
|
86
|
+
await ensureDirectory(dirname(filePath));
|
|
87
|
+
// Write to temp file
|
|
88
|
+
await fs.writeFile(tempFile, content, 'utf-8');
|
|
89
|
+
// Atomic rename
|
|
90
|
+
await fs.rename(tempFile, filePath);
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
// Clean up temp file on error
|
|
94
|
+
try {
|
|
95
|
+
await fs.unlink(tempFile);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Ignore cleanup errors
|
|
99
|
+
}
|
|
100
|
+
throw StorageError.writeError(filePath, error);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Read file with error handling
|
|
105
|
+
*/
|
|
106
|
+
export async function readFile(filePath) {
|
|
107
|
+
try {
|
|
108
|
+
return await fs.readFile(filePath, 'utf-8');
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
throw StorageError.readError(filePath, error);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Read file or return null if not found
|
|
116
|
+
*/
|
|
117
|
+
export async function readFileOrNull(filePath) {
|
|
118
|
+
try {
|
|
119
|
+
return await fs.readFile(filePath, 'utf-8');
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (error.code === 'ENOENT') {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
throw StorageError.readError(filePath, error);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Read and parse JSON file
|
|
130
|
+
*/
|
|
131
|
+
export async function readJsonFile(filePath) {
|
|
132
|
+
const content = await readFile(filePath);
|
|
133
|
+
try {
|
|
134
|
+
return JSON.parse(content);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
throw StorageError.parseError(filePath, 'JSON', error);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Read JSON file or return null if not found
|
|
142
|
+
*/
|
|
143
|
+
export async function readJsonFileOrNull(filePath) {
|
|
144
|
+
const content = await readFileOrNull(filePath);
|
|
145
|
+
if (content === null) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
return JSON.parse(content);
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
throw StorageError.parseError(filePath, 'JSON', error);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Write JSON file with pretty printing
|
|
157
|
+
*/
|
|
158
|
+
export async function writeJsonFile(filePath, data, indent = 2) {
|
|
159
|
+
const content = JSON.stringify(data, null, indent);
|
|
160
|
+
await atomicWriteFile(filePath, content);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Delete file if it exists
|
|
164
|
+
*/
|
|
165
|
+
export async function deleteFile(filePath) {
|
|
166
|
+
try {
|
|
167
|
+
await fs.unlink(filePath);
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
if (error.code === 'ENOENT') {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Delete directory recursively
|
|
179
|
+
*/
|
|
180
|
+
export async function deleteDirectory(dirPath) {
|
|
181
|
+
try {
|
|
182
|
+
await fs.rm(dirPath, { recursive: true, force: true });
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
if (error.code === 'ENOENT') {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
export async function listDirectory(dirPath, options = {}) {
|
|
193
|
+
try {
|
|
194
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
195
|
+
let results = entries.filter(entry => {
|
|
196
|
+
if (options.directoriesOnly && !entry.isDirectory())
|
|
197
|
+
return false;
|
|
198
|
+
if (options.filesOnly && !entry.isFile())
|
|
199
|
+
return false;
|
|
200
|
+
if (options.pattern) {
|
|
201
|
+
const pattern = typeof options.pattern === 'string'
|
|
202
|
+
? new RegExp(options.pattern)
|
|
203
|
+
: options.pattern;
|
|
204
|
+
if (!pattern.test(entry.name))
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
return true;
|
|
208
|
+
}).map(entry => entry.name);
|
|
209
|
+
if (options.sort !== false) {
|
|
210
|
+
results.sort();
|
|
211
|
+
}
|
|
212
|
+
return results;
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
if (error.code === 'ENOENT') {
|
|
216
|
+
return [];
|
|
217
|
+
}
|
|
218
|
+
throw StorageError.directoryError(dirPath, error);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Copy file
|
|
223
|
+
*/
|
|
224
|
+
export async function copyFile(source, destination) {
|
|
225
|
+
await ensureDirectory(dirname(destination));
|
|
226
|
+
await fs.copyFile(source, destination);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Move file (rename)
|
|
230
|
+
*/
|
|
231
|
+
export async function moveFile(source, destination) {
|
|
232
|
+
await ensureDirectory(dirname(destination));
|
|
233
|
+
await fs.rename(source, destination);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Get file stats or null if not found
|
|
237
|
+
*/
|
|
238
|
+
export async function getFileStats(filePath) {
|
|
239
|
+
try {
|
|
240
|
+
const stats = await fs.stat(filePath);
|
|
241
|
+
return {
|
|
242
|
+
size: stats.size,
|
|
243
|
+
created: stats.birthtime,
|
|
244
|
+
modified: stats.mtime,
|
|
245
|
+
isDirectory: stats.isDirectory(),
|
|
246
|
+
isFile: stats.isFile(),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Resolve path relative to base
|
|
255
|
+
*/
|
|
256
|
+
export function resolvePath(base, ...paths) {
|
|
257
|
+
return resolve(base, ...paths);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Get relative path
|
|
261
|
+
*/
|
|
262
|
+
export function getRelativePath(from, to) {
|
|
263
|
+
return relative(from, to);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Join paths
|
|
267
|
+
*/
|
|
268
|
+
export function joinPath(...paths) {
|
|
269
|
+
return join(...paths);
|
|
270
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Central utility exports
|
|
3
|
+
* Import utilities from this module for convenience
|
|
4
|
+
*/
|
|
5
|
+
export * from './file-utils.js';
|
|
6
|
+
export * from './string-utils.js';
|
|
7
|
+
export * from './storage-config.js';
|
|
8
|
+
export * from './validation.js';
|
|
9
|
+
export * from './response-builder.js';
|
|
10
|
+
export * from './cache.js';
|
|
11
|
+
export * from './logger.js';
|
|
12
|
+
export * from './version.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Central utility exports
|
|
3
|
+
* Import utilities from this module for convenience
|
|
4
|
+
*/
|
|
5
|
+
export * from './file-utils.js';
|
|
6
|
+
export * from './string-utils.js';
|
|
7
|
+
export * from './storage-config.js';
|
|
8
|
+
export * from './validation.js';
|
|
9
|
+
export * from './response-builder.js';
|
|
10
|
+
export * from './cache.js';
|
|
11
|
+
export * from './logger.js';
|
|
12
|
+
export * from './version.js';
|