@guren/server 0.2.0-alpha.7 → 1.0.0-rc.9
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/dist/Application-DtWDHXr1.d.ts +2110 -0
- package/dist/BroadcastManager-AkIWUGJo.d.ts +466 -0
- package/dist/CacheManager-BkvHEOZX.d.ts +244 -0
- package/dist/ConsoleKernel-CqCVrdZs.d.ts +207 -0
- package/dist/EventManager-CmIoLt7r.d.ts +207 -0
- package/dist/Gate-CNkBYf8m.d.ts +268 -0
- package/dist/HealthManager-DUyMIzsZ.d.ts +141 -0
- package/dist/I18nManager-Dtgzsf5n.d.ts +270 -0
- package/dist/LogManager-7mxnkaPM.d.ts +256 -0
- package/dist/MailManager-DpMvYiP9.d.ts +292 -0
- package/dist/Scheduler-BstvSca7.d.ts +469 -0
- package/dist/StorageManager-oZTHqaza.d.ts +337 -0
- package/dist/api-token-JOif2CtG.d.ts +1792 -0
- package/dist/app-key-CsBfRC_Q.d.ts +214 -0
- package/dist/auth/index.d.ts +418 -0
- package/dist/auth/index.js +6742 -0
- package/dist/authorization/index.d.ts +129 -0
- package/dist/authorization/index.js +621 -0
- package/dist/broadcasting/index.d.ts +233 -0
- package/dist/broadcasting/index.js +907 -0
- package/dist/cache/index.d.ts +233 -0
- package/dist/cache/index.js +817 -0
- package/dist/encryption/index.d.ts +222 -0
- package/dist/encryption/index.js +602 -0
- package/dist/events/index.d.ts +155 -0
- package/dist/events/index.js +330 -0
- package/dist/health/index.d.ts +185 -0
- package/dist/health/index.js +379 -0
- package/dist/i18n/index.d.ts +101 -0
- package/dist/i18n/index.js +597 -0
- package/dist/index-9_Jzj5jo.d.ts +7 -0
- package/dist/index.d.ts +2628 -619
- package/dist/index.js +22229 -3116
- package/dist/lambda/index.d.ts +156 -0
- package/dist/lambda/index.js +91 -0
- package/dist/logging/index.d.ts +50 -0
- package/dist/logging/index.js +557 -0
- package/dist/mail/index.d.ts +288 -0
- package/dist/mail/index.js +695 -0
- package/dist/mcp/index.d.ts +139 -0
- package/dist/mcp/index.js +382 -0
- package/dist/notifications/index.d.ts +271 -0
- package/dist/notifications/index.js +741 -0
- package/dist/queue/index.d.ts +423 -0
- package/dist/queue/index.js +958 -0
- package/dist/runtime/index.d.ts +93 -0
- package/dist/runtime/index.js +834 -0
- package/dist/scheduling/index.d.ts +41 -0
- package/dist/scheduling/index.js +836 -0
- package/dist/storage/index.d.ts +196 -0
- package/dist/storage/index.js +832 -0
- package/dist/vite/index.js +203 -3
- package/package.json +93 -6
- package/dist/chunk-FK2XQSBF.js +0 -160
|
@@ -0,0 +1,832 @@
|
|
|
1
|
+
// src/storage/drivers/LocalDriver.ts
|
|
2
|
+
import {
|
|
3
|
+
readFile,
|
|
4
|
+
writeFile,
|
|
5
|
+
unlink,
|
|
6
|
+
stat,
|
|
7
|
+
readdir,
|
|
8
|
+
mkdir,
|
|
9
|
+
rm,
|
|
10
|
+
copyFile,
|
|
11
|
+
rename
|
|
12
|
+
} from "fs/promises";
|
|
13
|
+
import { existsSync } from "fs";
|
|
14
|
+
import { join, dirname } from "path";
|
|
15
|
+
var LocalDriver = class {
|
|
16
|
+
root;
|
|
17
|
+
baseUrl;
|
|
18
|
+
defaultVisibility;
|
|
19
|
+
constructor(options) {
|
|
20
|
+
this.root = options.root;
|
|
21
|
+
this.baseUrl = options.url ?? "";
|
|
22
|
+
this.defaultVisibility = options.visibility ?? "private";
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get the full path for a file.
|
|
26
|
+
*/
|
|
27
|
+
fullPath(path) {
|
|
28
|
+
return join(this.root, path);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Ensure the directory exists.
|
|
32
|
+
*/
|
|
33
|
+
async ensureDirectory(filePath) {
|
|
34
|
+
const dir = dirname(filePath);
|
|
35
|
+
if (!existsSync(dir)) {
|
|
36
|
+
await mkdir(dir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async put(path, content, options) {
|
|
40
|
+
const fullPath = this.fullPath(path);
|
|
41
|
+
await this.ensureDirectory(fullPath);
|
|
42
|
+
const buffer = typeof content === "string" ? Buffer.from(content) : content;
|
|
43
|
+
await writeFile(fullPath, buffer);
|
|
44
|
+
return path;
|
|
45
|
+
}
|
|
46
|
+
async putFile(path, localPath, options) {
|
|
47
|
+
const content = await readFile(localPath);
|
|
48
|
+
return this.put(path, content, options);
|
|
49
|
+
}
|
|
50
|
+
async get(path) {
|
|
51
|
+
const fullPath = this.fullPath(path);
|
|
52
|
+
try {
|
|
53
|
+
return await readFile(fullPath);
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async getAsString(path) {
|
|
59
|
+
const content = await this.get(path);
|
|
60
|
+
return content ? content.toString("utf-8") : null;
|
|
61
|
+
}
|
|
62
|
+
async exists(path) {
|
|
63
|
+
return existsSync(this.fullPath(path));
|
|
64
|
+
}
|
|
65
|
+
async delete(path) {
|
|
66
|
+
const fullPath = this.fullPath(path);
|
|
67
|
+
try {
|
|
68
|
+
await unlink(fullPath);
|
|
69
|
+
return true;
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async deleteMany(paths) {
|
|
75
|
+
let deleted = 0;
|
|
76
|
+
for (const path of paths) {
|
|
77
|
+
if (await this.delete(path)) {
|
|
78
|
+
deleted++;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return deleted;
|
|
82
|
+
}
|
|
83
|
+
async copy(from, to) {
|
|
84
|
+
const fromPath = this.fullPath(from);
|
|
85
|
+
const toPath = this.fullPath(to);
|
|
86
|
+
await this.ensureDirectory(toPath);
|
|
87
|
+
await copyFile(fromPath, toPath);
|
|
88
|
+
return to;
|
|
89
|
+
}
|
|
90
|
+
async move(from, to) {
|
|
91
|
+
const fromPath = this.fullPath(from);
|
|
92
|
+
const toPath = this.fullPath(to);
|
|
93
|
+
await this.ensureDirectory(toPath);
|
|
94
|
+
await rename(fromPath, toPath);
|
|
95
|
+
return to;
|
|
96
|
+
}
|
|
97
|
+
url(path) {
|
|
98
|
+
return `${this.baseUrl}/${path}`;
|
|
99
|
+
}
|
|
100
|
+
async temporaryUrl(path, expiration) {
|
|
101
|
+
return this.url(path);
|
|
102
|
+
}
|
|
103
|
+
async size(path) {
|
|
104
|
+
const fullPath = this.fullPath(path);
|
|
105
|
+
const stats = await stat(fullPath);
|
|
106
|
+
return stats.size;
|
|
107
|
+
}
|
|
108
|
+
async lastModified(path) {
|
|
109
|
+
const fullPath = this.fullPath(path);
|
|
110
|
+
const stats = await stat(fullPath);
|
|
111
|
+
return stats.mtime;
|
|
112
|
+
}
|
|
113
|
+
async metadata(path) {
|
|
114
|
+
const fullPath = this.fullPath(path);
|
|
115
|
+
try {
|
|
116
|
+
const stats = await stat(fullPath);
|
|
117
|
+
return {
|
|
118
|
+
path,
|
|
119
|
+
size: stats.size,
|
|
120
|
+
lastModified: stats.mtime,
|
|
121
|
+
visibility: this.defaultVisibility
|
|
122
|
+
};
|
|
123
|
+
} catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async files(directory) {
|
|
128
|
+
const fullPath = this.fullPath(directory);
|
|
129
|
+
if (!existsSync(fullPath)) {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
const entries = await readdir(fullPath, { withFileTypes: true });
|
|
133
|
+
return entries.filter((entry) => entry.isFile()).map((entry) => join(directory, entry.name));
|
|
134
|
+
}
|
|
135
|
+
async directories(directory) {
|
|
136
|
+
const fullPath = this.fullPath(directory);
|
|
137
|
+
if (!existsSync(fullPath)) {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
const entries = await readdir(fullPath, { withFileTypes: true });
|
|
141
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => join(directory, entry.name));
|
|
142
|
+
}
|
|
143
|
+
async allFiles(directory) {
|
|
144
|
+
const files = [];
|
|
145
|
+
const scan = async (dir) => {
|
|
146
|
+
const fullPath = this.fullPath(dir);
|
|
147
|
+
if (!existsSync(fullPath)) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const entries = await readdir(fullPath, { withFileTypes: true });
|
|
151
|
+
for (const entry of entries) {
|
|
152
|
+
const entryPath = join(dir, entry.name);
|
|
153
|
+
if (entry.isFile()) {
|
|
154
|
+
files.push(entryPath);
|
|
155
|
+
} else if (entry.isDirectory()) {
|
|
156
|
+
await scan(entryPath);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
await scan(directory);
|
|
161
|
+
return files;
|
|
162
|
+
}
|
|
163
|
+
async makeDirectory(path) {
|
|
164
|
+
const fullPath = this.fullPath(path);
|
|
165
|
+
await mkdir(fullPath, { recursive: true });
|
|
166
|
+
}
|
|
167
|
+
async deleteDirectory(path) {
|
|
168
|
+
const fullPath = this.fullPath(path);
|
|
169
|
+
if (existsSync(fullPath)) {
|
|
170
|
+
await rm(fullPath, { recursive: true, force: true });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async setVisibility(path, visibility) {
|
|
174
|
+
}
|
|
175
|
+
async getVisibility(path) {
|
|
176
|
+
return this.defaultVisibility;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Get the root directory.
|
|
180
|
+
*/
|
|
181
|
+
getRoot() {
|
|
182
|
+
return this.root;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// src/storage/drivers/S3Driver.ts
|
|
187
|
+
var S3Driver = class {
|
|
188
|
+
constructor(options) {
|
|
189
|
+
this.options = options;
|
|
190
|
+
this.client = options.client;
|
|
191
|
+
this.bucket = options.bucket;
|
|
192
|
+
this.region = options.region ?? "us-east-1";
|
|
193
|
+
this.endpoint = options.endpoint;
|
|
194
|
+
this.accessKeyId = options.accessKeyId;
|
|
195
|
+
this.secretAccessKey = options.secretAccessKey;
|
|
196
|
+
this.prefix = options.prefix ?? "";
|
|
197
|
+
this.baseUrl = options.url ?? `https://${this.bucket}.s3.${this.region}.amazonaws.com`;
|
|
198
|
+
this.defaultVisibility = options.visibility ?? "private";
|
|
199
|
+
}
|
|
200
|
+
client = null;
|
|
201
|
+
bucket;
|
|
202
|
+
region;
|
|
203
|
+
endpoint;
|
|
204
|
+
accessKeyId;
|
|
205
|
+
secretAccessKey;
|
|
206
|
+
prefix;
|
|
207
|
+
baseUrl;
|
|
208
|
+
defaultVisibility;
|
|
209
|
+
/**
|
|
210
|
+
* Get or create the S3 client.
|
|
211
|
+
*/
|
|
212
|
+
async getClient() {
|
|
213
|
+
if (this.client) {
|
|
214
|
+
return this.client;
|
|
215
|
+
}
|
|
216
|
+
const { S3Client } = await importAwsModule("@aws-sdk/client-s3");
|
|
217
|
+
const config = {
|
|
218
|
+
region: this.region
|
|
219
|
+
};
|
|
220
|
+
if (this.endpoint) {
|
|
221
|
+
config.endpoint = this.endpoint;
|
|
222
|
+
config.forcePathStyle = true;
|
|
223
|
+
}
|
|
224
|
+
if (this.accessKeyId && this.secretAccessKey) {
|
|
225
|
+
config.credentials = {
|
|
226
|
+
accessKeyId: this.accessKeyId,
|
|
227
|
+
secretAccessKey: this.secretAccessKey
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
this.client = new S3Client(config);
|
|
231
|
+
return this.client;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Get the prefixed key.
|
|
235
|
+
*/
|
|
236
|
+
prefixKey(path) {
|
|
237
|
+
return this.prefix ? `${this.prefix}/${path}` : path;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Get ACL from visibility.
|
|
241
|
+
*/
|
|
242
|
+
getAcl(visibility) {
|
|
243
|
+
return visibility === "public" ? "public-read" : "private";
|
|
244
|
+
}
|
|
245
|
+
async put(path, content, options) {
|
|
246
|
+
const client = await this.getClient();
|
|
247
|
+
const { PutObjectCommand } = await importAwsModule("@aws-sdk/client-s3");
|
|
248
|
+
const body = typeof content === "string" ? Buffer.from(content) : content;
|
|
249
|
+
const visibility = options?.visibility ?? this.defaultVisibility;
|
|
250
|
+
const command = new PutObjectCommand({
|
|
251
|
+
Bucket: this.bucket,
|
|
252
|
+
Key: this.prefixKey(path),
|
|
253
|
+
Body: body,
|
|
254
|
+
ContentType: options?.contentType,
|
|
255
|
+
ACL: this.getAcl(visibility),
|
|
256
|
+
Metadata: options?.metadata
|
|
257
|
+
});
|
|
258
|
+
await client.send(command);
|
|
259
|
+
return path;
|
|
260
|
+
}
|
|
261
|
+
async putFile(path, localPath, options) {
|
|
262
|
+
const { readFile: readFile2 } = await import("fs/promises");
|
|
263
|
+
const content = await readFile2(localPath);
|
|
264
|
+
return this.put(path, content, options);
|
|
265
|
+
}
|
|
266
|
+
async get(path) {
|
|
267
|
+
const client = await this.getClient();
|
|
268
|
+
const { GetObjectCommand } = await importAwsModule("@aws-sdk/client-s3");
|
|
269
|
+
try {
|
|
270
|
+
const command = new GetObjectCommand({
|
|
271
|
+
Bucket: this.bucket,
|
|
272
|
+
Key: this.prefixKey(path)
|
|
273
|
+
});
|
|
274
|
+
const response = await client.send(command);
|
|
275
|
+
if (!response.Body) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
const bytes = await response.Body.transformToByteArray();
|
|
279
|
+
return Buffer.from(bytes);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
if (error && typeof error === "object" && "name" in error && error.name === "NoSuchKey") {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
throw error;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async getAsString(path) {
|
|
288
|
+
const content = await this.get(path);
|
|
289
|
+
return content ? content.toString("utf-8") : null;
|
|
290
|
+
}
|
|
291
|
+
async exists(path) {
|
|
292
|
+
const client = await this.getClient();
|
|
293
|
+
const { HeadObjectCommand } = await importAwsModule("@aws-sdk/client-s3");
|
|
294
|
+
try {
|
|
295
|
+
const command = new HeadObjectCommand({
|
|
296
|
+
Bucket: this.bucket,
|
|
297
|
+
Key: this.prefixKey(path)
|
|
298
|
+
});
|
|
299
|
+
await client.send(command);
|
|
300
|
+
return true;
|
|
301
|
+
} catch (error) {
|
|
302
|
+
if (error && typeof error === "object" && "name" in error && error.name === "NotFound") {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async delete(path) {
|
|
309
|
+
const client = await this.getClient();
|
|
310
|
+
const { DeleteObjectCommand } = await importAwsModule("@aws-sdk/client-s3");
|
|
311
|
+
try {
|
|
312
|
+
const command = new DeleteObjectCommand({
|
|
313
|
+
Bucket: this.bucket,
|
|
314
|
+
Key: this.prefixKey(path)
|
|
315
|
+
});
|
|
316
|
+
await client.send(command);
|
|
317
|
+
return true;
|
|
318
|
+
} catch {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
async deleteMany(paths) {
|
|
323
|
+
if (paths.length === 0) {
|
|
324
|
+
return 0;
|
|
325
|
+
}
|
|
326
|
+
const client = await this.getClient();
|
|
327
|
+
const { DeleteObjectsCommand } = await importAwsModule("@aws-sdk/client-s3");
|
|
328
|
+
const command = new DeleteObjectsCommand({
|
|
329
|
+
Bucket: this.bucket,
|
|
330
|
+
Delete: {
|
|
331
|
+
Objects: paths.map((path) => ({ Key: this.prefixKey(path) }))
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
const response = await client.send(command);
|
|
335
|
+
return response.Deleted?.length ?? 0;
|
|
336
|
+
}
|
|
337
|
+
async copy(from, to) {
|
|
338
|
+
const client = await this.getClient();
|
|
339
|
+
const { CopyObjectCommand } = await importAwsModule("@aws-sdk/client-s3");
|
|
340
|
+
const command = new CopyObjectCommand({
|
|
341
|
+
Bucket: this.bucket,
|
|
342
|
+
CopySource: `${this.bucket}/${this.prefixKey(from)}`,
|
|
343
|
+
Key: this.prefixKey(to)
|
|
344
|
+
});
|
|
345
|
+
await client.send(command);
|
|
346
|
+
return to;
|
|
347
|
+
}
|
|
348
|
+
async move(from, to) {
|
|
349
|
+
await this.copy(from, to);
|
|
350
|
+
await this.delete(from);
|
|
351
|
+
return to;
|
|
352
|
+
}
|
|
353
|
+
url(path) {
|
|
354
|
+
return `${this.baseUrl}/${this.prefixKey(path)}`;
|
|
355
|
+
}
|
|
356
|
+
async temporaryUrl(path, expiration) {
|
|
357
|
+
const client = await this.getClient();
|
|
358
|
+
const { GetObjectCommand } = await importAwsModule("@aws-sdk/client-s3");
|
|
359
|
+
const { getSignedUrl } = await importAwsModule("@aws-sdk/s3-request-presigner");
|
|
360
|
+
const command = new GetObjectCommand({
|
|
361
|
+
Bucket: this.bucket,
|
|
362
|
+
Key: this.prefixKey(path)
|
|
363
|
+
});
|
|
364
|
+
const expiresIn = Math.max(1, Math.floor((expiration.getTime() - Date.now()) / 1e3));
|
|
365
|
+
return getSignedUrl(client, command, { expiresIn });
|
|
366
|
+
}
|
|
367
|
+
async size(path) {
|
|
368
|
+
const metadata = await this.metadata(path);
|
|
369
|
+
if (!metadata) {
|
|
370
|
+
throw new Error(`File not found: ${path}`);
|
|
371
|
+
}
|
|
372
|
+
return metadata.size;
|
|
373
|
+
}
|
|
374
|
+
async lastModified(path) {
|
|
375
|
+
const metadata = await this.metadata(path);
|
|
376
|
+
if (!metadata) {
|
|
377
|
+
throw new Error(`File not found: ${path}`);
|
|
378
|
+
}
|
|
379
|
+
return metadata.lastModified;
|
|
380
|
+
}
|
|
381
|
+
async metadata(path) {
|
|
382
|
+
const client = await this.getClient();
|
|
383
|
+
const { HeadObjectCommand } = await importAwsModule("@aws-sdk/client-s3");
|
|
384
|
+
try {
|
|
385
|
+
const command = new HeadObjectCommand({
|
|
386
|
+
Bucket: this.bucket,
|
|
387
|
+
Key: this.prefixKey(path)
|
|
388
|
+
});
|
|
389
|
+
const response = await client.send(command);
|
|
390
|
+
return {
|
|
391
|
+
path,
|
|
392
|
+
size: response.ContentLength ?? 0,
|
|
393
|
+
lastModified: response.LastModified ?? /* @__PURE__ */ new Date(),
|
|
394
|
+
contentType: response.ContentType,
|
|
395
|
+
metadata: response.Metadata
|
|
396
|
+
};
|
|
397
|
+
} catch (error) {
|
|
398
|
+
if (error && typeof error === "object" && "name" in error && error.name === "NotFound") {
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
throw error;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
async files(directory) {
|
|
405
|
+
const client = await this.getClient();
|
|
406
|
+
const { ListObjectsV2Command } = await importAwsModule("@aws-sdk/client-s3");
|
|
407
|
+
const prefix = this.prefixKey(directory);
|
|
408
|
+
const command = new ListObjectsV2Command({
|
|
409
|
+
Bucket: this.bucket,
|
|
410
|
+
Prefix: prefix ? `${prefix}/` : "",
|
|
411
|
+
Delimiter: "/"
|
|
412
|
+
});
|
|
413
|
+
const response = await client.send(command);
|
|
414
|
+
return (response.Contents ?? []).map((item) => item.Key?.replace(this.prefix ? `${this.prefix}/` : "", "") ?? "").filter((key) => key && !key.endsWith("/"));
|
|
415
|
+
}
|
|
416
|
+
async directories(directory) {
|
|
417
|
+
const client = await this.getClient();
|
|
418
|
+
const { ListObjectsV2Command } = await importAwsModule("@aws-sdk/client-s3");
|
|
419
|
+
const prefix = this.prefixKey(directory);
|
|
420
|
+
const command = new ListObjectsV2Command({
|
|
421
|
+
Bucket: this.bucket,
|
|
422
|
+
Prefix: prefix ? `${prefix}/` : "",
|
|
423
|
+
Delimiter: "/"
|
|
424
|
+
});
|
|
425
|
+
const response = await client.send(command);
|
|
426
|
+
return (response.CommonPrefixes ?? []).map(
|
|
427
|
+
(item) => item.Prefix?.replace(this.prefix ? `${this.prefix}/` : "", "").replace(/\/$/, "") ?? ""
|
|
428
|
+
).filter(Boolean);
|
|
429
|
+
}
|
|
430
|
+
async allFiles(directory) {
|
|
431
|
+
const client = await this.getClient();
|
|
432
|
+
const { ListObjectsV2Command } = await importAwsModule("@aws-sdk/client-s3");
|
|
433
|
+
const prefix = this.prefixKey(directory);
|
|
434
|
+
const command = new ListObjectsV2Command({
|
|
435
|
+
Bucket: this.bucket,
|
|
436
|
+
Prefix: prefix ? `${prefix}/` : ""
|
|
437
|
+
});
|
|
438
|
+
const response = await client.send(command);
|
|
439
|
+
return (response.Contents ?? []).map((item) => item.Key?.replace(this.prefix ? `${this.prefix}/` : "", "") ?? "").filter((key) => key && !key.endsWith("/"));
|
|
440
|
+
}
|
|
441
|
+
async makeDirectory(path) {
|
|
442
|
+
await this.put(`${path}/.keep`, "");
|
|
443
|
+
}
|
|
444
|
+
async deleteDirectory(path) {
|
|
445
|
+
const files = await this.allFiles(path);
|
|
446
|
+
if (files.length > 0) {
|
|
447
|
+
await this.deleteMany(files);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
async setVisibility(path, visibility) {
|
|
451
|
+
const client = await this.getClient();
|
|
452
|
+
const { PutObjectAclCommand } = await importAwsModule("@aws-sdk/client-s3");
|
|
453
|
+
const command = new PutObjectAclCommand({
|
|
454
|
+
Bucket: this.bucket,
|
|
455
|
+
Key: this.prefixKey(path),
|
|
456
|
+
ACL: this.getAcl(visibility)
|
|
457
|
+
});
|
|
458
|
+
await client.send(command);
|
|
459
|
+
}
|
|
460
|
+
async getVisibility(path) {
|
|
461
|
+
const client = await this.getClient();
|
|
462
|
+
const { GetObjectAclCommand } = await importAwsModule("@aws-sdk/client-s3");
|
|
463
|
+
const command = new GetObjectAclCommand({
|
|
464
|
+
Bucket: this.bucket,
|
|
465
|
+
Key: this.prefixKey(path)
|
|
466
|
+
});
|
|
467
|
+
const response = await client.send(command);
|
|
468
|
+
const isPublic = response.Grants?.some(
|
|
469
|
+
(grant) => grant.Grantee?.URI === "http://acs.amazonaws.com/groups/global/AllUsers" && grant.Permission === "READ"
|
|
470
|
+
);
|
|
471
|
+
return isPublic ? "public" : "private";
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Get the bucket name.
|
|
475
|
+
*/
|
|
476
|
+
getBucket() {
|
|
477
|
+
return this.bucket;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Get the prefix.
|
|
481
|
+
*/
|
|
482
|
+
getPrefix() {
|
|
483
|
+
return this.prefix;
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
function isMissingAwsModule(error, moduleName) {
|
|
487
|
+
if (!error || typeof error !== "object") return false;
|
|
488
|
+
const code = error.code;
|
|
489
|
+
if (code === "ERR_MODULE_NOT_FOUND") return true;
|
|
490
|
+
const message = String(error.message ?? "");
|
|
491
|
+
return message.includes(`Cannot find package '${moduleName}'`) || message.includes(`Cannot find module '${moduleName}'`);
|
|
492
|
+
}
|
|
493
|
+
async function importAwsModule(moduleName) {
|
|
494
|
+
try {
|
|
495
|
+
return await import(moduleName);
|
|
496
|
+
} catch (error) {
|
|
497
|
+
if (isMissingAwsModule(error, moduleName)) {
|
|
498
|
+
throw new Error(
|
|
499
|
+
`Missing optional dependency "${moduleName}". Install @aws-sdk/client-s3 and @aws-sdk/s3-request-presigner to use the S3 driver.`
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
throw error;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// src/storage/drivers/MemoryDriver.ts
|
|
507
|
+
var MemoryDriver = class {
|
|
508
|
+
storage = /* @__PURE__ */ new Map();
|
|
509
|
+
baseUrl;
|
|
510
|
+
constructor(options = {}) {
|
|
511
|
+
this.baseUrl = options.url ?? "memory://";
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Normalize path (remove leading/trailing slashes).
|
|
515
|
+
*/
|
|
516
|
+
normalizePath(path) {
|
|
517
|
+
return path.replace(/^\/+|\/+$/g, "");
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Get directory from path.
|
|
521
|
+
*/
|
|
522
|
+
getDirectory(path) {
|
|
523
|
+
const parts = path.split("/");
|
|
524
|
+
return parts.slice(0, -1).join("/");
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Check if a path is in a directory.
|
|
528
|
+
*/
|
|
529
|
+
isInDirectory(filePath, directory) {
|
|
530
|
+
const normalized = this.normalizePath(filePath);
|
|
531
|
+
const normalizedDir = this.normalizePath(directory);
|
|
532
|
+
if (!normalizedDir) {
|
|
533
|
+
return !normalized.includes("/");
|
|
534
|
+
}
|
|
535
|
+
return normalized.startsWith(normalizedDir + "/") && !normalized.slice(normalizedDir.length + 1).includes("/");
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Check if a path is under a directory (recursive).
|
|
539
|
+
*/
|
|
540
|
+
isUnderDirectory(filePath, directory) {
|
|
541
|
+
const normalized = this.normalizePath(filePath);
|
|
542
|
+
const normalizedDir = this.normalizePath(directory);
|
|
543
|
+
if (!normalizedDir) {
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
return normalized.startsWith(normalizedDir + "/");
|
|
547
|
+
}
|
|
548
|
+
async put(path, content, options) {
|
|
549
|
+
const normalized = this.normalizePath(path);
|
|
550
|
+
const buffer = typeof content === "string" ? Buffer.from(content) : content;
|
|
551
|
+
this.storage.set(normalized, {
|
|
552
|
+
content: buffer,
|
|
553
|
+
contentType: options?.contentType,
|
|
554
|
+
visibility: options?.visibility ?? "private",
|
|
555
|
+
metadata: options?.metadata,
|
|
556
|
+
lastModified: /* @__PURE__ */ new Date()
|
|
557
|
+
});
|
|
558
|
+
return normalized;
|
|
559
|
+
}
|
|
560
|
+
async putFile(path, localPath, options) {
|
|
561
|
+
throw new Error("putFile is not supported in MemoryDriver");
|
|
562
|
+
}
|
|
563
|
+
async get(path) {
|
|
564
|
+
const normalized = this.normalizePath(path);
|
|
565
|
+
const file = this.storage.get(normalized);
|
|
566
|
+
return file?.content ?? null;
|
|
567
|
+
}
|
|
568
|
+
async getAsString(path) {
|
|
569
|
+
const content = await this.get(path);
|
|
570
|
+
return content ? content.toString("utf-8") : null;
|
|
571
|
+
}
|
|
572
|
+
async exists(path) {
|
|
573
|
+
const normalized = this.normalizePath(path);
|
|
574
|
+
return this.storage.has(normalized);
|
|
575
|
+
}
|
|
576
|
+
async delete(path) {
|
|
577
|
+
const normalized = this.normalizePath(path);
|
|
578
|
+
return this.storage.delete(normalized);
|
|
579
|
+
}
|
|
580
|
+
async deleteMany(paths) {
|
|
581
|
+
let deleted = 0;
|
|
582
|
+
for (const path of paths) {
|
|
583
|
+
if (await this.delete(path)) {
|
|
584
|
+
deleted++;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return deleted;
|
|
588
|
+
}
|
|
589
|
+
async copy(from, to) {
|
|
590
|
+
const normalizedFrom = this.normalizePath(from);
|
|
591
|
+
const normalizedTo = this.normalizePath(to);
|
|
592
|
+
const file = this.storage.get(normalizedFrom);
|
|
593
|
+
if (!file) {
|
|
594
|
+
throw new Error(`File not found: ${from}`);
|
|
595
|
+
}
|
|
596
|
+
this.storage.set(normalizedTo, {
|
|
597
|
+
...file,
|
|
598
|
+
content: Buffer.from(file.content),
|
|
599
|
+
lastModified: /* @__PURE__ */ new Date()
|
|
600
|
+
});
|
|
601
|
+
return normalizedTo;
|
|
602
|
+
}
|
|
603
|
+
async move(from, to) {
|
|
604
|
+
await this.copy(from, to);
|
|
605
|
+
await this.delete(from);
|
|
606
|
+
return this.normalizePath(to);
|
|
607
|
+
}
|
|
608
|
+
url(path) {
|
|
609
|
+
const normalized = this.normalizePath(path);
|
|
610
|
+
return `${this.baseUrl}/${normalized}`;
|
|
611
|
+
}
|
|
612
|
+
async temporaryUrl(path, expiration) {
|
|
613
|
+
return this.url(path);
|
|
614
|
+
}
|
|
615
|
+
async size(path) {
|
|
616
|
+
const normalized = this.normalizePath(path);
|
|
617
|
+
const file = this.storage.get(normalized);
|
|
618
|
+
if (!file) {
|
|
619
|
+
throw new Error(`File not found: ${path}`);
|
|
620
|
+
}
|
|
621
|
+
return file.content.length;
|
|
622
|
+
}
|
|
623
|
+
async lastModified(path) {
|
|
624
|
+
const normalized = this.normalizePath(path);
|
|
625
|
+
const file = this.storage.get(normalized);
|
|
626
|
+
if (!file) {
|
|
627
|
+
throw new Error(`File not found: ${path}`);
|
|
628
|
+
}
|
|
629
|
+
return file.lastModified;
|
|
630
|
+
}
|
|
631
|
+
async metadata(path) {
|
|
632
|
+
const normalized = this.normalizePath(path);
|
|
633
|
+
const file = this.storage.get(normalized);
|
|
634
|
+
if (!file) {
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
return {
|
|
638
|
+
path: normalized,
|
|
639
|
+
size: file.content.length,
|
|
640
|
+
lastModified: file.lastModified,
|
|
641
|
+
contentType: file.contentType,
|
|
642
|
+
visibility: file.visibility,
|
|
643
|
+
metadata: file.metadata
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
async files(directory) {
|
|
647
|
+
const normalized = this.normalizePath(directory);
|
|
648
|
+
const result = [];
|
|
649
|
+
for (const path of this.storage.keys()) {
|
|
650
|
+
if (this.isInDirectory(path, normalized)) {
|
|
651
|
+
result.push(path);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return result.sort();
|
|
655
|
+
}
|
|
656
|
+
async directories(directory) {
|
|
657
|
+
const normalized = this.normalizePath(directory);
|
|
658
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
659
|
+
for (const path of this.storage.keys()) {
|
|
660
|
+
if (this.isUnderDirectory(path, normalized)) {
|
|
661
|
+
const relativePath = normalized ? path.slice(normalized.length + 1) : path;
|
|
662
|
+
const firstDir = relativePath.split("/")[0];
|
|
663
|
+
if (relativePath.includes("/")) {
|
|
664
|
+
dirs.add(normalized ? `${normalized}/${firstDir}` : firstDir);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return Array.from(dirs).sort();
|
|
669
|
+
}
|
|
670
|
+
async allFiles(directory) {
|
|
671
|
+
const normalized = this.normalizePath(directory);
|
|
672
|
+
const result = [];
|
|
673
|
+
for (const path of this.storage.keys()) {
|
|
674
|
+
if (this.isUnderDirectory(path, normalized)) {
|
|
675
|
+
result.push(path);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return result.sort();
|
|
679
|
+
}
|
|
680
|
+
async makeDirectory(path) {
|
|
681
|
+
}
|
|
682
|
+
async deleteDirectory(path) {
|
|
683
|
+
const normalized = this.normalizePath(path);
|
|
684
|
+
const keysToDelete = [];
|
|
685
|
+
for (const key of this.storage.keys()) {
|
|
686
|
+
if (this.isUnderDirectory(key, normalized)) {
|
|
687
|
+
keysToDelete.push(key);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
for (const key of keysToDelete) {
|
|
691
|
+
this.storage.delete(key);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
async setVisibility(path, visibility) {
|
|
695
|
+
const normalized = this.normalizePath(path);
|
|
696
|
+
const file = this.storage.get(normalized);
|
|
697
|
+
if (!file) {
|
|
698
|
+
throw new Error(`File not found: ${path}`);
|
|
699
|
+
}
|
|
700
|
+
file.visibility = visibility;
|
|
701
|
+
}
|
|
702
|
+
async getVisibility(path) {
|
|
703
|
+
const normalized = this.normalizePath(path);
|
|
704
|
+
const file = this.storage.get(normalized);
|
|
705
|
+
if (!file) {
|
|
706
|
+
throw new Error(`File not found: ${path}`);
|
|
707
|
+
}
|
|
708
|
+
return file.visibility;
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Clear all files (for testing).
|
|
712
|
+
*/
|
|
713
|
+
clear() {
|
|
714
|
+
this.storage.clear();
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Get the number of stored files.
|
|
718
|
+
*/
|
|
719
|
+
count() {
|
|
720
|
+
return this.storage.size;
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Get all stored file paths.
|
|
724
|
+
*/
|
|
725
|
+
getAllPaths() {
|
|
726
|
+
return Array.from(this.storage.keys());
|
|
727
|
+
}
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
// src/storage/StorageManager.ts
|
|
731
|
+
var StorageManager = class {
|
|
732
|
+
defaultDiskName;
|
|
733
|
+
diskFactories = /* @__PURE__ */ new Map();
|
|
734
|
+
resolvedDisks = /* @__PURE__ */ new Map();
|
|
735
|
+
driverFactories = /* @__PURE__ */ new Map();
|
|
736
|
+
constructor(config = {}) {
|
|
737
|
+
this.defaultDiskName = config.default ?? "local";
|
|
738
|
+
this.registerBuiltinDrivers();
|
|
739
|
+
if (config.disks) {
|
|
740
|
+
for (const [name, diskConfig] of Object.entries(config.disks)) {
|
|
741
|
+
this.registerDiskFromConfig(name, diskConfig);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
if (!this.diskFactories.has(this.defaultDiskName) && this.defaultDiskName === "local") {
|
|
745
|
+
this.diskFactories.set("local", () => new LocalDriver({ root: "./storage" }));
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Register built-in disk drivers.
|
|
750
|
+
*/
|
|
751
|
+
registerBuiltinDrivers() {
|
|
752
|
+
this.driverFactories.set("local", (options) => {
|
|
753
|
+
return new LocalDriver(options);
|
|
754
|
+
});
|
|
755
|
+
this.driverFactories.set("s3", (options) => {
|
|
756
|
+
return new S3Driver(options);
|
|
757
|
+
});
|
|
758
|
+
this.driverFactories.set("memory", (options) => {
|
|
759
|
+
return new MemoryDriver(options);
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Register a disk from configuration.
|
|
764
|
+
*/
|
|
765
|
+
registerDiskFromConfig(name, config) {
|
|
766
|
+
const { driver, ...options } = config;
|
|
767
|
+
const factory = this.driverFactories.get(driver);
|
|
768
|
+
if (!factory) {
|
|
769
|
+
throw new Error(`Unknown storage driver: ${driver}`);
|
|
770
|
+
}
|
|
771
|
+
this.diskFactories.set(name, () => factory(options));
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Get a storage disk by name.
|
|
775
|
+
* Returns the default disk if no name is specified.
|
|
776
|
+
*/
|
|
777
|
+
disk(name) {
|
|
778
|
+
const diskName = name ?? this.defaultDiskName;
|
|
779
|
+
const cached = this.resolvedDisks.get(diskName);
|
|
780
|
+
if (cached) {
|
|
781
|
+
return cached;
|
|
782
|
+
}
|
|
783
|
+
const factory = this.diskFactories.get(diskName);
|
|
784
|
+
if (!factory) {
|
|
785
|
+
throw new Error(`Storage disk not found: ${diskName}`);
|
|
786
|
+
}
|
|
787
|
+
const disk = factory();
|
|
788
|
+
this.resolvedDisks.set(diskName, disk);
|
|
789
|
+
return disk;
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Register a custom disk factory.
|
|
793
|
+
*/
|
|
794
|
+
registerDisk(name, factory) {
|
|
795
|
+
this.diskFactories.set(name, factory);
|
|
796
|
+
this.resolvedDisks.delete(name);
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Register a custom driver.
|
|
800
|
+
*/
|
|
801
|
+
registerDriver(name, factory) {
|
|
802
|
+
this.driverFactories.set(name, factory);
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Check if a disk is registered.
|
|
806
|
+
*/
|
|
807
|
+
hasDisk(name) {
|
|
808
|
+
return this.diskFactories.has(name);
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Get the default disk name.
|
|
812
|
+
*/
|
|
813
|
+
getDefaultDiskName() {
|
|
814
|
+
return this.defaultDiskName;
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Get all registered disk names.
|
|
818
|
+
*/
|
|
819
|
+
getDiskNames() {
|
|
820
|
+
return Array.from(this.diskFactories.keys());
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
function createStorageManager(config) {
|
|
824
|
+
return new StorageManager(config);
|
|
825
|
+
}
|
|
826
|
+
export {
|
|
827
|
+
LocalDriver,
|
|
828
|
+
MemoryDriver,
|
|
829
|
+
S3Driver,
|
|
830
|
+
StorageManager,
|
|
831
|
+
createStorageManager
|
|
832
|
+
};
|