@stacksjs/storage 0.70.87 → 0.70.88

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/index.js DELETED
@@ -1,2810 +0,0 @@
1
- // @bun
2
- var __require = import.meta.require;
3
-
4
- // src/copy.ts
5
- import { contains } from "@stacksjs/arrays";
6
- import { join } from "@stacksjs/path";
7
-
8
- // src/fs.ts
9
- import * as fs from "fs";
10
- import { existsSync, watch as fsWatch, mkdirSync, readFileSync, watchFile, writeFileSync } from "fs";
11
- function exists(path) {
12
- return existsSync(path);
13
- }
14
-
15
- // src/copy.ts
16
- function copy(src, dest, exclude = []) {
17
- if (Array.isArray(src)) {
18
- src.forEach((file) => {
19
- copy(file, dest, exclude);
20
- });
21
- } else {
22
- if (fs.statSync(src).isDirectory())
23
- copyFolder(src, dest, exclude);
24
- else
25
- copyFile(src, dest);
26
- }
27
- }
28
- function copyFile(src, dest) {
29
- fs.copyFileSync(src, dest);
30
- }
31
- function copyFolder(src, dest, exclude = []) {
32
- if (!fs.existsSync(dest))
33
- fs.mkdirSync(dest, { recursive: true });
34
- if (fs.existsSync(src)) {
35
- fs.readdirSync(src).forEach((file) => {
36
- if (!contains(join(src, file), exclude)) {
37
- const srcPath = join(src, file);
38
- const destPath = join(dest, file);
39
- if (fs.statSync(srcPath).isDirectory())
40
- copyFolder(srcPath, destPath, exclude);
41
- else
42
- fs.copyFileSync(srcPath, destPath);
43
- }
44
- });
45
- }
46
- }
47
- // src/delete.ts
48
- import { italic, log } from "@stacksjs/cli";
49
- import { err, handleError, ok } from "@stacksjs/error-handling";
50
- import { join as join3 } from "@stacksjs/path";
51
-
52
- // src/folders.ts
53
- import { join as join2 } from "@stacksjs/path";
54
- function isFolder(path) {
55
- try {
56
- return fs.statSync(path).isDirectory();
57
- } catch {
58
- return false;
59
- }
60
- }
61
- function isDir(path) {
62
- return isFolder(path);
63
- }
64
- function doesFolderExist(path) {
65
- return fs.existsSync(path);
66
- }
67
- function createFolder(dir) {
68
- return new Promise((resolve, reject) => {
69
- try {
70
- fs.mkdirSync(dir, { recursive: true });
71
- resolve();
72
- } catch (err) {
73
- reject(err);
74
- }
75
- });
76
- }
77
- function getFolders(dir) {
78
- return fs.readdirSync(dir).filter((file) => {
79
- return fs.statSync(join2(dir, file)).isDirectory();
80
- });
81
- }
82
- var folders = {
83
- isFolder,
84
- doesFolderExist,
85
- createFolder,
86
- getFolders
87
- };
88
-
89
- // src/glob.ts
90
- var {Glob: BunGlob } = globalThis.Bun;
91
- function isEnoent(err) {
92
- return !!err && typeof err === "object" && err.code === "ENOENT";
93
- }
94
- function globSync(patterns, options) {
95
- const patternArray = typeof patterns === "string" ? [patterns] : patterns;
96
- const results = [];
97
- for (const pattern of patternArray) {
98
- try {
99
- const globInstance = new BunGlob(pattern);
100
- const matches = globInstance.scanSync({
101
- cwd: options?.cwd,
102
- absolute: options?.absolute,
103
- dot: options?.dot,
104
- onlyFiles: options?.onlyFiles
105
- });
106
- for (const match of matches) {
107
- results.push(match);
108
- }
109
- } catch (err) {
110
- if (!isEnoent(err))
111
- throw err;
112
- }
113
- }
114
- return results;
115
- }
116
- async function glob(patterns, options) {
117
- const patternArray = typeof patterns === "string" ? [patterns] : patterns;
118
- const results = [];
119
- for (const pattern of patternArray) {
120
- try {
121
- const globInstance = new BunGlob(pattern);
122
- const matches = globInstance.scan({
123
- cwd: options?.cwd,
124
- absolute: options?.absolute,
125
- dot: options?.dot,
126
- onlyFiles: options?.onlyFiles
127
- });
128
- for await (const match of matches) {
129
- results.push(match);
130
- }
131
- } catch (err) {
132
- if (!isEnoent(err))
133
- throw err;
134
- }
135
- }
136
- return results;
137
- }
138
-
139
- // src/delete.ts
140
- function deleteFolder(path) {
141
- return new Promise((resolve, reject) => {
142
- try {
143
- if (isFolder(path)) {
144
- fs.rmSync(path, { recursive: true, force: true });
145
- return resolve(ok(`Deleted ${path}`));
146
- }
147
- return resolve(ok(`Path ${path} was not a directory`));
148
- } catch (error) {
149
- return reject(err(error));
150
- }
151
- });
152
- }
153
- async function isDirectoryEmpty(path) {
154
- return new Promise((resolve, reject) => {
155
- try {
156
- if (fs.statSync(path).isDirectory()) {
157
- if (fs.readdirSync(path).length === 0)
158
- return resolve(ok(true));
159
- return resolve(ok(false));
160
- }
161
- return resolve(ok(false));
162
- } catch (error) {
163
- return reject(err(error));
164
- }
165
- });
166
- }
167
- async function deleteEmptyFolder(path) {
168
- return new Promise((resolve, reject) => {
169
- try {
170
- if (fs.statSync(path).isDirectory()) {
171
- if (fs.readdirSync(path).length === 0) {
172
- fs.rmSync(path, { recursive: true, force: true });
173
- return resolve(ok(`Deleted ${path}`));
174
- }
175
- return resolve(ok(`Path ${path} was not empty`));
176
- }
177
- return resolve(ok(`Path ${path} was not a directory`));
178
- } catch (error) {
179
- return reject(err(error));
180
- }
181
- });
182
- }
183
- async function deleteEmptyFolders(dir) {
184
- try {
185
- if (!fs.existsSync(dir))
186
- return ok(`Path ${dir} does not exist`);
187
- const files = fs.readdirSync(dir);
188
- for (const file of files) {
189
- const p = join3(dir, file);
190
- if (isFolder(p)) {
191
- if (fs.readdirSync(p).length === 0)
192
- fs.rmSync(p, { recursive: true, force: true });
193
- else
194
- await deleteEmptyFolders(p);
195
- }
196
- }
197
- return ok(`Deleted empty folders located in ${dir}`);
198
- } catch (error) {
199
- return err(error);
200
- }
201
- }
202
- function deleteFile(path) {
203
- return new Promise((resolve, reject) => {
204
- try {
205
- if (fs.statSync(path).isFile()) {
206
- fs.rmSync(path, { recursive: true, force: true });
207
- return resolve(ok(`Deleted ${path}`));
208
- }
209
- return resolve(ok(`Path ${path} was not a file`));
210
- } catch (error) {
211
- return reject(err(error));
212
- }
213
- });
214
- }
215
- async function deleteGlob(path) {
216
- if (!path.includes("*"))
217
- return err(handleError(`Path ${path} does not contain a glob`));
218
- const directories = await glob([path], { onlyDirectories: true });
219
- for (const directory of directories) {
220
- const result = await deleteFolder(directory);
221
- if (result.isErr) {
222
- log.error(result.error);
223
- return result;
224
- }
225
- log.info(`Deleted ${italic(directory)}`);
226
- }
227
- return ok(`Deleted ${directories.length} directories`);
228
- }
229
- async function del(path) {
230
- if (fs.existsSync(path) && fs.statSync(path).isFile())
231
- return await deleteFile(path);
232
- if (isFolder(path))
233
- return await deleteFolder(path);
234
- if (path.includes("*"))
235
- return await deleteGlob(path);
236
- return err(handleError(`Path ${path} cannot be deleted due to an unhandled condition. Please report this issue.`));
237
- }
238
- // src/files.ts
239
- import { contains as contains2 } from "@stacksjs/arrays";
240
- import { log as log2 } from "@stacksjs/logging";
241
- import { dirname, join as join4, path as p } from "@stacksjs/path";
242
- import { detectIndent, detectNewline } from "@stacksjs/strings";
243
- async function readJsonFile(name, cwd) {
244
- const file = await readTextFile(name, cwd);
245
- let data;
246
- try {
247
- data = JSON.parse(file.data);
248
- } catch (error) {
249
- throw new Error(`Failed to parse JSON file "${name}": ${error.message}`);
250
- }
251
- const indent = detectIndent(file.data).indent;
252
- const newline = detectNewline(file.data);
253
- return { ...file, data, indent, newline };
254
- }
255
- async function readPackageJson(name, cwd) {
256
- const file = await readJsonFile(name, cwd);
257
- return file.data;
258
- }
259
- async function writeFile(path, data) {
260
- if (typeof path === "string") {
261
- const dirPath = dirname(path);
262
- if (!await existsSync(dirPath))
263
- await createFolder(dirPath);
264
- return await Bun.write(Bun.file(path), data);
265
- }
266
- return await Bun.write(path, data);
267
- }
268
- async function writeJsonFile(file) {
269
- let json = JSON.stringify(file.data, undefined, file.indent);
270
- if (file.newline)
271
- json += file.newline;
272
- return writeTextFile({ ...file, data: json });
273
- }
274
- function readTextFile(name, cwd) {
275
- return new Promise((resolve, reject) => {
276
- let filePath;
277
- if (cwd)
278
- filePath = join4(cwd, name);
279
- else
280
- filePath = name;
281
- fs.readFile(filePath, "utf8", (err2, text) => {
282
- if (err2) {
283
- reject(err2);
284
- } else {
285
- resolve({
286
- path: filePath,
287
- data: text
288
- });
289
- }
290
- });
291
- });
292
- }
293
- async function writeTextFile(file) {
294
- return await Bun.write(file.path, file.data);
295
- }
296
- function isFile(path) {
297
- return fs.existsSync(path);
298
- }
299
- function doesExist(path) {
300
- return isFile(path) || isFolder(path);
301
- }
302
- function doesNotExist(path) {
303
- return !isFile(path) && !isFolder(path);
304
- }
305
- function hasFiles(folder) {
306
- try {
307
- return fs.readdirSync(folder).length > 0;
308
- } catch (err2) {
309
- log2.debug(`Error reading folder: ${folder}`, err2);
310
- return false;
311
- }
312
- }
313
- function hasComponents() {
314
- return hasFiles(p.componentsPath());
315
- }
316
- function hasFunctions() {
317
- return hasFiles(p.functionsPath());
318
- }
319
- function deleteFiles(dir, exclude = []) {
320
- if (fs.existsSync(dir)) {
321
- fs.readdirSync(dir).forEach((file) => {
322
- const p2 = join4(dir, file);
323
- if (fs.statSync(p2).isDirectory()) {
324
- if (fs.readdirSync(p2).length === 0)
325
- fs.rmSync(p2, { recursive: true, force: true });
326
- else
327
- deleteFiles(p2, exclude);
328
- } else if (!contains2(p2, exclude)) {
329
- fs.rmSync(p2);
330
- }
331
- });
332
- }
333
- }
334
- function getFiles(dir, exclude = []) {
335
- let results = [];
336
- const list = fs.readdirSync(dir);
337
- list.forEach((file) => {
338
- file = join4(dir, file);
339
- const stat = fs.statSync(file);
340
- if (stat.isDirectory())
341
- results = results.concat(getFiles(file, exclude));
342
- else if (!contains2(file, exclude))
343
- results.push(file);
344
- });
345
- return results;
346
- }
347
- function put(path, contents) {
348
- const dirPath = dirname(path);
349
- if (!fs.existsSync(dirPath))
350
- fs.mkdirSync(dirPath, { recursive: true });
351
- fs.writeFileSync(path, contents, "utf-8");
352
- }
353
- async function get(path) {
354
- return Bun.file(path).text();
355
- }
356
- var files = {
357
- readJsonFile,
358
- readPackageJson,
359
- readTextFile,
360
- writeJsonFile,
361
- writeTextFile,
362
- hasFiles,
363
- hasComponents,
364
- hasFunctions,
365
- deleteFiles,
366
- getFiles,
367
- put,
368
- get
369
- };
370
- // src/hash.ts
371
- import { createHash } from "crypto";
372
- import { path as p2 } from "@stacksjs/path";
373
- function hashFileOrDirectory(path, hash) {
374
- if (!fs.existsSync(path)) {
375
- console.error(`Path does not exist: ${path}`);
376
- return;
377
- }
378
- if (fs.statSync(path).isDirectory()) {
379
- const files2 = fs.readdirSync(path);
380
- for (const file of files2) {
381
- const filePath = p2.join(path, file);
382
- hashFileOrDirectory(filePath, hash);
383
- }
384
- } else {
385
- hash.update(fs.readFileSync(path));
386
- }
387
- }
388
- function hashDirectory(directory) {
389
- const hash = createHash("sha256");
390
- hashFileOrDirectory(directory, hash);
391
- return hash.digest("hex");
392
- }
393
- function hashPath(path) {
394
- const hash = createHash("sha256");
395
- hashFileOrDirectory(path, hash);
396
- return hash.digest("hex");
397
- }
398
- function hashPaths(paths) {
399
- const hash = createHash("sha256");
400
- const pathsArray = Array.isArray(paths) ? paths : [paths];
401
- for (const path of pathsArray)
402
- hashFileOrDirectory(path, hash);
403
- return hash.digest("hex");
404
- }
405
- // src/helpers.ts
406
- import { fileURLToPath } from "url";
407
- import { dirname as dirname2 } from "@stacksjs/path";
408
- var __dirname = "/home/runner/work/stacks/stacks/storage/framework/core/storage/src";
409
- var _dirname = typeof __dirname !== "undefined" ? __dirname : dirname2(fileURLToPath(import.meta.url));
410
- function updateConfigFile(filePath, newConfig) {
411
- return new Promise((resolve, reject) => {
412
- let config;
413
- try {
414
- config = JSON.parse(fs.readFileSync(filePath, "utf8"));
415
- } catch (error) {
416
- reject(new Error(`Failed to parse config file "${filePath}": ${error.message}`));
417
- return;
418
- }
419
- for (const key in newConfig)
420
- config[key] = newConfig[key];
421
- try {
422
- fs.writeFileSync(filePath, JSON.stringify(config, null, 2));
423
- resolve();
424
- } catch (error) {
425
- reject(error);
426
- }
427
- });
428
- }
429
- var helpers = {
430
- _dirname,
431
- updateConfigFile
432
- };
433
- // src/zip.ts
434
- import { runCommand } from "@stacksjs/cli";
435
- function shellEscape(_arg) {
436
- return `'${_arg.replace(/'/g, "'\\''")}'`;
437
- }
438
- async function zip(from, to, options) {
439
- const toPath = to || "archive.zip";
440
- if (Array.isArray(from)) {
441
- const fromPath = from.map((f) => shellEscape(f)).join(" ");
442
- return runCommand(`zip -r ${shellEscape(toPath)} ${fromPath}`, options);
443
- }
444
- return runCommand(`zip -r ${shellEscape(toPath)} ${shellEscape(from)}`, options);
445
- }
446
- async function unzip(paths) {
447
- if (Array.isArray(paths))
448
- return runCommand(`unzip ${paths.map((p3) => shellEscape(p3)).join(" ")}`);
449
- return runCommand(`unzip ${shellEscape(paths)}`);
450
- }
451
- function archive(paths) {
452
- return zip(paths);
453
- }
454
- function unarchive(paths) {
455
- return unzip(paths);
456
- }
457
- function compress(paths) {
458
- return zip(paths);
459
- }
460
- function decompress(paths) {
461
- return unzip(paths);
462
- }
463
- function gzipSync(data, options) {
464
- return Bun.gzipSync(data, options);
465
- }
466
- function gunzipSync(data) {
467
- return Bun.gunzipSync(data);
468
- }
469
- function deflateSync(data, options) {
470
- return Bun.deflateSync(data, options);
471
- }
472
- function inflateSync(data) {
473
- return Bun.inflateSync(data);
474
- }
475
- // src/adapters/local.ts
476
- import { Buffer as Buffer2 } from "buffer";
477
- import { createHmac as createHmac2 } from "crypto";
478
- import { createReadStream, createWriteStream } from "fs";
479
- import { access, chmod, constants, copyFile as copyFile2, lstat, mkdir, readdir, readFile, rename, rm, stat, unlink, writeFile as writeFile2 } from "fs/promises";
480
- import { basename, dirname as dirname3, join as join5, relative } from "path";
481
- import { Readable } from "stream";
482
- import { pipeline } from "stream/promises";
483
-
484
- // src/types.ts
485
- var Visibility;
486
- ((Visibility2) => {
487
- Visibility2["PUBLIC"] = "public";
488
- Visibility2["PRIVATE"] = "private";
489
- })(Visibility ||= {});
490
- async function* createDirectoryListing(entries) {
491
- for (const entry of entries) {
492
- yield entry;
493
- }
494
- }
495
- function normalizeExpiryToMilliseconds(expiry) {
496
- if (expiry instanceof Date) {
497
- return expiry.getTime() - Date.now();
498
- }
499
- return expiry * 1000;
500
- }
501
- function normalizeExpiryToDate(expiry) {
502
- if (expiry instanceof Date) {
503
- return expiry;
504
- }
505
- return new Date(Date.now() + expiry * 1000);
506
- }
507
- function isFile2(entry) {
508
- return entry.type === "file";
509
- }
510
- function isDirectory(entry) {
511
- return entry.type === "directory";
512
- }
513
-
514
- // src/signed-url.ts
515
- import { Buffer } from "buffer";
516
- import { createHmac, timingSafeEqual } from "crypto";
517
- import process2 from "process";
518
- var ALG = "HS256";
519
- function getAppKey() {
520
- const k = process2.env.APP_KEY;
521
- if (!k || k.length < 16) {
522
- if (process2.env.APP_ENV === "production" || process2.env.NODE_ENV === "production") {
523
- throw new Error("[storage/signed-url] APP_KEY is missing or too short (need \u226516 chars). Cannot sign URL.");
524
- }
525
- }
526
- return k || "stacks-default-key-dev-only-do-not-use-prod";
527
- }
528
- function base64UrlEncode(buf) {
529
- return buf.toString("base64url");
530
- }
531
- function normalizeExpiry(expiresIn) {
532
- if (expiresIn instanceof Date)
533
- return Math.floor(expiresIn.getTime() / 1000);
534
- return Math.floor(Date.now() / 1000) + Math.floor(expiresIn);
535
- }
536
- function createSignedStorageToken(path, options) {
537
- const exp = normalizeExpiry(options.expiresIn);
538
- const iat = Math.floor(Date.now() / 1000);
539
- const header = { alg: ALG, typ: "JWT" };
540
- const payload = {
541
- iss: options.issuer || "stacks",
542
- iat,
543
- exp,
544
- path
545
- };
546
- const headerPart = base64UrlEncode(Buffer.from(JSON.stringify(header)));
547
- const payloadPart = base64UrlEncode(Buffer.from(JSON.stringify(payload)));
548
- const signingInput = `${headerPart}.${payloadPart}`;
549
- const sig = base64UrlEncode(createHmac("sha256", getAppKey()).update(signingInput).digest());
550
- return `${signingInput}.${sig}`;
551
- }
552
- var revokedSignatures = new Set;
553
-
554
- // src/adapters/local.ts
555
- class LocalStorageAdapter {
556
- root;
557
- constructor(config = {}) {
558
- this.root = config.root || process.cwd();
559
- }
560
- resolvePath(path) {
561
- const resolved = join5(this.root, path);
562
- const rel = relative(this.root, resolved);
563
- if (rel.startsWith("..") || rel.startsWith("../") || rel.startsWith("..\\")) {
564
- throw new Error(`Path traversal detected: '${path}' resolves outside storage root`);
565
- }
566
- return resolved;
567
- }
568
- async write(path, contents) {
569
- const fullPath = this.resolvePath(path);
570
- const dir = dirname3(fullPath);
571
- await mkdir(dir, { recursive: true });
572
- if (typeof contents === "string") {
573
- await writeFile2(fullPath, contents, "utf8");
574
- } else if (contents instanceof Buffer2) {
575
- await writeFile2(fullPath, contents);
576
- } else if (contents instanceof Uint8Array) {
577
- await writeFile2(fullPath, contents);
578
- } else {
579
- const writeStream = createWriteStream(fullPath);
580
- await pipeline(contents, writeStream);
581
- }
582
- const st = await stat(fullPath);
583
- return {
584
- path,
585
- size: st.size,
586
- lastModified: st.mtimeMs
587
- };
588
- }
589
- async read(path) {
590
- const fullPath = this.resolvePath(path);
591
- return await readFile(fullPath);
592
- }
593
- async getStream(path, options) {
594
- const fullPath = this.resolvePath(path);
595
- await access(fullPath, constants.R_OK);
596
- const nodeStream = createReadStream(fullPath, { signal: options?.signal });
597
- return Readable.toWeb(nodeStream);
598
- }
599
- async putStream(path, stream, options) {
600
- const fullPath = this.resolvePath(path);
601
- const dir = dirname3(fullPath);
602
- await mkdir(dir, { recursive: true });
603
- const nodeReadable = Readable.fromWeb(stream);
604
- const writeStream = createWriteStream(fullPath);
605
- try {
606
- await pipeline(nodeReadable, writeStream, { signal: options?.signal });
607
- } catch (err2) {
608
- try {
609
- await unlink(fullPath);
610
- } catch {}
611
- throw err2;
612
- }
613
- const st = await stat(fullPath);
614
- return {
615
- path,
616
- size: st.size,
617
- contentType: options?.contentType,
618
- lastModified: st.mtimeMs
619
- };
620
- }
621
- async readToString(path) {
622
- const fullPath = this.resolvePath(path);
623
- return await readFile(fullPath, "utf8");
624
- }
625
- async readToBuffer(path) {
626
- const fullPath = this.resolvePath(path);
627
- return await readFile(fullPath);
628
- }
629
- async readToUint8Array(path) {
630
- const fullPath = this.resolvePath(path);
631
- const buffer = await readFile(fullPath);
632
- return new Uint8Array(buffer);
633
- }
634
- async deleteFile(path) {
635
- const fullPath = this.resolvePath(path);
636
- await unlink(fullPath);
637
- }
638
- async deleteDirectory(path) {
639
- const fullPath = this.resolvePath(path);
640
- await rm(fullPath, { recursive: true, force: true });
641
- }
642
- async createDirectory(path) {
643
- const fullPath = this.resolvePath(path);
644
- await mkdir(fullPath, { recursive: true });
645
- }
646
- async moveFile(from, to) {
647
- const fromPath = this.resolvePath(from);
648
- const toPath = this.resolvePath(to);
649
- const toDir = dirname3(toPath);
650
- await mkdir(toDir, { recursive: true });
651
- await rename(fromPath, toPath);
652
- }
653
- async copyFile(from, to) {
654
- const fromPath = this.resolvePath(from);
655
- const toPath = this.resolvePath(to);
656
- const toDir = dirname3(toPath);
657
- await mkdir(toDir, { recursive: true });
658
- await copyFile2(fromPath, toPath);
659
- }
660
- async stat(path) {
661
- const fullPath = this.resolvePath(path);
662
- const stats = await lstat(fullPath);
663
- return {
664
- path,
665
- type: stats.isDirectory() ? "directory" : "file",
666
- visibility: await this.visibility(path),
667
- size: stats.size,
668
- lastModified: stats.mtimeMs,
669
- mimeType: stats.isFile() ? await this.detectMimeType(fullPath) : undefined
670
- };
671
- }
672
- list(path, options = {}) {
673
- return this.createAsyncIterator(path, options.deep || false);
674
- }
675
- async* createAsyncIterator(path, deep) {
676
- const fullPath = this.resolvePath(path);
677
- try {
678
- await access(fullPath, constants.R_OK);
679
- } catch {
680
- return;
681
- }
682
- const entries = await this.readDirectoryRecursive(fullPath, deep);
683
- yield* createDirectoryListing(entries);
684
- }
685
- async readDirectoryRecursive(dirPath, deep) {
686
- const entries = [];
687
- try {
688
- const items = await readdir(dirPath, { withFileTypes: true });
689
- for (const item of items) {
690
- const itemPath = join5(dirPath, item.name);
691
- const relativePath = relative(this.root, itemPath);
692
- entries.push({
693
- path: relativePath,
694
- type: item.isDirectory() ? "directory" : "file"
695
- });
696
- if (deep && item.isDirectory()) {
697
- const subEntries = await this.readDirectoryRecursive(itemPath, true);
698
- entries.push(...subEntries);
699
- }
700
- }
701
- } catch (error) {
702
- if (error?.code !== "EACCES" && error?.code !== "EPERM") {
703
- throw error;
704
- }
705
- }
706
- return entries;
707
- }
708
- async changeVisibility(path, vis) {
709
- const fullPath = this.resolvePath(path);
710
- const stats = await lstat(fullPath);
711
- const isDir2 = stats.isDirectory();
712
- const mode = vis === "public" ? isDir2 ? 493 : 420 : isDir2 ? 448 : 384;
713
- await chmod(fullPath, mode);
714
- }
715
- async visibility(path) {
716
- const fullPath = this.resolvePath(path);
717
- const stats = await lstat(fullPath);
718
- const perms = stats.mode & 511;
719
- return perms & 4 ? "public" : "private";
720
- }
721
- async fileExists(path) {
722
- const fullPath = this.resolvePath(path);
723
- try {
724
- const stats = await lstat(fullPath);
725
- return stats.isFile();
726
- } catch {
727
- return false;
728
- }
729
- }
730
- async directoryExists(path) {
731
- const fullPath = this.resolvePath(path);
732
- try {
733
- const stats = await lstat(fullPath);
734
- return stats.isDirectory();
735
- } catch {
736
- return false;
737
- }
738
- }
739
- async publicUrl(path, options = {}) {
740
- const base = (options.domain || process.env.APP_URL || "http://localhost").replace(/\/$/, "");
741
- return `${base}/${path}`;
742
- }
743
- async temporaryUrl(path, options) {
744
- const expiry = normalizeExpiryToDate(options.expiresIn);
745
- const payload = `${path}:${expiry.getTime()}`;
746
- const appKey = process.env.APP_KEY || "stacks-default-key";
747
- const signature = createHmac2("sha256", appKey).update(payload).digest("hex");
748
- const token = Buffer2.from(`${payload}:${signature}`).toString("base64url");
749
- return `http://localhost/temp/${token}`;
750
- }
751
- async signedUrl(path, options) {
752
- const token = createSignedStorageToken(path, options);
753
- const baseUrl = (options.baseUrl || process.env.APP_URL || "http://localhost").replace(/\/$/, "");
754
- return `${baseUrl}/__storage/${encodeURIComponent(path)}?token=${token}`;
755
- }
756
- async checksum(path, options = {}) {
757
- const algorithm = options.algorithm || "sha256";
758
- const fullPath = this.resolvePath(path);
759
- const content = await readFile(fullPath);
760
- const hasher = new Bun.CryptoHasher(algorithm);
761
- hasher.update(content);
762
- return hasher.digest("hex");
763
- }
764
- async mimeType(path, options = {}) {
765
- const fullPath = this.resolvePath(path);
766
- return await this.detectMimeType(fullPath);
767
- }
768
- async detectMimeType(filePath) {
769
- const ext = basename(filePath).split(".").pop()?.toLowerCase();
770
- const mimeTypes = {
771
- txt: "text/plain",
772
- html: "text/html",
773
- css: "text/css",
774
- js: "application/javascript",
775
- json: "application/json",
776
- xml: "application/xml",
777
- pdf: "application/pdf",
778
- zip: "application/zip",
779
- jpg: "image/jpeg",
780
- jpeg: "image/jpeg",
781
- png: "image/png",
782
- gif: "image/gif",
783
- svg: "image/svg+xml",
784
- mp4: "video/mp4",
785
- mp3: "audio/mpeg",
786
- wav: "audio/wav"
787
- };
788
- return mimeTypes[ext || ""] || "application/octet-stream";
789
- }
790
- async lastModified(path) {
791
- const stats = await this.stat(path);
792
- return stats.lastModified;
793
- }
794
- async fileSize(path) {
795
- const stats = await this.stat(path);
796
- return stats.size;
797
- }
798
- }
799
- function createLocalStorage(config = {}) {
800
- return new LocalStorageAdapter(config);
801
- }
802
-
803
- // src/adapters/memory.ts
804
- import { Buffer as Buffer3 } from "buffer";
805
- import { basename as basename2 } from "path";
806
- class InMemoryStorageAdapter {
807
- files;
808
- directories;
809
- constructor() {
810
- this.files = new Map;
811
- this.directories = new Set;
812
- this.directories.add("");
813
- }
814
- normalizePath(path) {
815
- return path.replace(/^\/+/, "").replace(/\/+$/, "");
816
- }
817
- getDirectoryPath(path) {
818
- const parts = path.split("/").filter(Boolean);
819
- parts.pop();
820
- return parts.join("/");
821
- }
822
- async contentsToUint8Array(contents) {
823
- if (typeof contents === "string") {
824
- return new TextEncoder().encode(contents);
825
- } else if (contents instanceof Buffer3) {
826
- return new Uint8Array(contents);
827
- } else if (contents instanceof Uint8Array) {
828
- return contents;
829
- } else {
830
- const stream = contents;
831
- if (typeof stream.getReader !== "function") {
832
- throw new TypeError("[storage/memory] contents must be a web-standard ReadableStream " + "(with .getReader()), not a Node stream.Readable. " + "Convert via Readable.toWeb(nodeStream) before passing.");
833
- }
834
- const reader = contents.getReader();
835
- const chunks = [];
836
- while (true) {
837
- const { done, value } = await reader.read();
838
- if (done)
839
- break;
840
- if (value)
841
- chunks.push(value);
842
- }
843
- const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
844
- const result = new Uint8Array(totalLength);
845
- let offset = 0;
846
- for (const chunk of chunks) {
847
- result.set(chunk, offset);
848
- offset += chunk.length;
849
- }
850
- return result;
851
- }
852
- }
853
- async write(path, contents) {
854
- const normalized = this.normalizePath(path);
855
- const dirPath = this.getDirectoryPath(normalized);
856
- if (dirPath) {
857
- await this.createDirectory(dirPath);
858
- }
859
- const data = await this.contentsToUint8Array(contents);
860
- const lastModified = Date.now();
861
- const mimeType = this.detectMimeType(normalized);
862
- this.files.set(normalized, {
863
- contents: data,
864
- visibility: "private",
865
- mimeType,
866
- lastModified
867
- });
868
- return {
869
- path: normalized,
870
- size: data.length,
871
- contentType: mimeType,
872
- lastModified
873
- };
874
- }
875
- async read(path) {
876
- const normalized = this.normalizePath(path);
877
- const file = this.files.get(normalized);
878
- if (!file) {
879
- throw new Error(`File not found: ${path}`);
880
- }
881
- return file.contents;
882
- }
883
- async getStream(path, _options) {
884
- const normalized = this.normalizePath(path);
885
- const file = this.files.get(normalized);
886
- if (!file)
887
- throw new Error(`File not found: ${path}`);
888
- const bytes = file.contents;
889
- return new ReadableStream({
890
- start(controller) {
891
- controller.enqueue(bytes);
892
- controller.close();
893
- }
894
- });
895
- }
896
- async putStream(path, stream, options) {
897
- const normalized = this.normalizePath(path);
898
- const dirPath = this.getDirectoryPath(normalized);
899
- if (dirPath)
900
- await this.createDirectory(dirPath);
901
- const chunks = [];
902
- const reader = stream.getReader();
903
- const abort = options?.signal;
904
- try {
905
- while (true) {
906
- if (abort?.aborted)
907
- throw new Error("aborted");
908
- const { done, value } = await reader.read();
909
- if (done)
910
- break;
911
- if (value)
912
- chunks.push(value);
913
- }
914
- } finally {
915
- try {
916
- reader.releaseLock();
917
- } catch {}
918
- }
919
- const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
920
- const data = new Uint8Array(totalLength);
921
- let offset = 0;
922
- for (const c of chunks) {
923
- data.set(c, offset);
924
- offset += c.length;
925
- }
926
- const lastModified = Date.now();
927
- const mimeType = options?.contentType ?? this.detectMimeType(normalized);
928
- this.files.set(normalized, {
929
- contents: data,
930
- visibility: "private",
931
- mimeType,
932
- lastModified
933
- });
934
- return { path: normalized, size: data.length, contentType: mimeType, lastModified };
935
- }
936
- async readToString(path) {
937
- const data = await this.read(path);
938
- return new TextDecoder().decode(data);
939
- }
940
- async readToBuffer(path) {
941
- const data = await this.read(path);
942
- return Buffer3.from(data);
943
- }
944
- async readToUint8Array(path) {
945
- const data = await this.read(path);
946
- return data;
947
- }
948
- async deleteFile(path) {
949
- const normalized = this.normalizePath(path);
950
- if (!this.files.has(normalized)) {
951
- throw new Error(`File not found: ${path}`);
952
- }
953
- this.files.delete(normalized);
954
- }
955
- async deleteDirectory(path) {
956
- const normalized = this.normalizePath(path);
957
- const prefix = normalized ? `${normalized}/` : "";
958
- for (const filePath of this.files.keys()) {
959
- if (filePath === normalized || filePath.startsWith(prefix)) {
960
- this.files.delete(filePath);
961
- }
962
- }
963
- for (const dir of this.directories) {
964
- if (dir === normalized || dir.startsWith(prefix)) {
965
- this.directories.delete(dir);
966
- }
967
- }
968
- }
969
- async createDirectory(path) {
970
- const normalized = this.normalizePath(path);
971
- if (!normalized)
972
- return;
973
- const parts = normalized.split("/").filter(Boolean);
974
- let current = "";
975
- for (const part of parts) {
976
- current = current ? `${current}/${part}` : part;
977
- this.directories.add(current);
978
- }
979
- }
980
- async moveFile(from, to) {
981
- const normalizedFrom = this.normalizePath(from);
982
- const normalizedTo = this.normalizePath(to);
983
- const file = this.files.get(normalizedFrom);
984
- if (!file) {
985
- throw new Error(`File not found: ${from}`);
986
- }
987
- const toDir = this.getDirectoryPath(normalizedTo);
988
- if (toDir) {
989
- await this.createDirectory(toDir);
990
- }
991
- this.files.set(normalizedTo, { ...file, lastModified: Date.now() });
992
- this.files.delete(normalizedFrom);
993
- }
994
- async copyFile(from, to) {
995
- const normalizedFrom = this.normalizePath(from);
996
- const normalizedTo = this.normalizePath(to);
997
- const file = this.files.get(normalizedFrom);
998
- if (!file) {
999
- throw new Error(`File not found: ${from}`);
1000
- }
1001
- const toDir = this.getDirectoryPath(normalizedTo);
1002
- if (toDir) {
1003
- await this.createDirectory(toDir);
1004
- }
1005
- const contentsCopy = new Uint8Array(file.contents);
1006
- this.files.set(normalizedTo, {
1007
- ...file,
1008
- contents: contentsCopy,
1009
- lastModified: Date.now()
1010
- });
1011
- }
1012
- async stat(path) {
1013
- const normalized = this.normalizePath(path);
1014
- const file = this.files.get(normalized);
1015
- if (file) {
1016
- return {
1017
- path: normalized,
1018
- type: "file",
1019
- visibility: file.visibility,
1020
- size: file.contents.length,
1021
- lastModified: file.lastModified,
1022
- mimeType: file.mimeType
1023
- };
1024
- }
1025
- if (this.directories.has(normalized) || normalized === "") {
1026
- return {
1027
- path: normalized,
1028
- type: "directory",
1029
- visibility: "private",
1030
- size: 0,
1031
- lastModified: Date.now()
1032
- };
1033
- }
1034
- throw new Error(`Path not found: ${path}`);
1035
- }
1036
- list(path, options = {}) {
1037
- return this.createAsyncIterator(path, options.deep || false);
1038
- }
1039
- async* createAsyncIterator(path, deep) {
1040
- const normalized = this.normalizePath(path);
1041
- const prefix = normalized ? `${normalized}/` : "";
1042
- const entries = [];
1043
- const seen = new Set;
1044
- for (const [filePath] of this.files) {
1045
- if (filePath.startsWith(prefix) || prefix === "") {
1046
- const relativePath = prefix ? filePath.slice(prefix.length) : filePath;
1047
- if (!deep) {
1048
- const parts = relativePath.split("/").filter(Boolean);
1049
- if (parts.length === 1) {
1050
- entries.push({
1051
- path: filePath,
1052
- type: "file"
1053
- });
1054
- }
1055
- } else {
1056
- entries.push({
1057
- path: filePath,
1058
- type: "file"
1059
- });
1060
- }
1061
- }
1062
- }
1063
- for (const dir of this.directories) {
1064
- if ((dir.startsWith(prefix) || prefix === "") && dir !== normalized) {
1065
- const relativePath = prefix ? dir.slice(prefix.length) : dir;
1066
- if (!deep) {
1067
- const parts = relativePath.split("/").filter(Boolean);
1068
- if (parts.length === 1 && !seen.has(dir)) {
1069
- entries.push({
1070
- path: dir,
1071
- type: "directory"
1072
- });
1073
- seen.add(dir);
1074
- }
1075
- } else if (!seen.has(dir)) {
1076
- entries.push({
1077
- path: dir,
1078
- type: "directory"
1079
- });
1080
- seen.add(dir);
1081
- }
1082
- }
1083
- }
1084
- yield* createDirectoryListing(entries);
1085
- }
1086
- async changeVisibility(path, visibility) {
1087
- const normalized = this.normalizePath(path);
1088
- const file = this.files.get(normalized);
1089
- if (!file) {
1090
- throw new Error(`File not found: ${path}`);
1091
- }
1092
- file.visibility = visibility;
1093
- }
1094
- async visibility(path) {
1095
- const normalized = this.normalizePath(path);
1096
- const file = this.files.get(normalized);
1097
- if (!file) {
1098
- throw new Error(`File not found: ${path}`);
1099
- }
1100
- return file.visibility;
1101
- }
1102
- async fileExists(path) {
1103
- const normalized = this.normalizePath(path);
1104
- return this.files.has(normalized);
1105
- }
1106
- async directoryExists(path) {
1107
- const normalized = this.normalizePath(path);
1108
- return this.directories.has(normalized) || normalized === "";
1109
- }
1110
- async publicUrl(path, options = {}) {
1111
- const base = (options.domain || process.env.APP_URL || "http://localhost").replace(/\/$/, "");
1112
- return `${base}/${this.normalizePath(path)}`;
1113
- }
1114
- async temporaryUrl(path, options) {
1115
- const expiry = normalizeExpiryToDate(options.expiresIn);
1116
- const token = Buffer3.from(`${path}:${expiry.getTime()}`).toString("base64url");
1117
- return `http://localhost/temp/${token}`;
1118
- }
1119
- async signedUrl(_path, _options) {
1120
- throw new Error("[storage/memory] signedUrl is not supported on the in-memory adapter \u2014 switch to local or s3 disk for signed URL generation.");
1121
- }
1122
- async checksum(path, options = {}) {
1123
- const algorithm = options.algorithm || "sha256";
1124
- const data = await this.readToUint8Array(path);
1125
- const hasher = new Bun.CryptoHasher(algorithm);
1126
- hasher.update(data);
1127
- return hasher.digest("hex");
1128
- }
1129
- async mimeType(path, _options = {}) {
1130
- const normalized = this.normalizePath(path);
1131
- const file = this.files.get(normalized);
1132
- if (!file) {
1133
- throw new Error(`File not found: ${path}`);
1134
- }
1135
- return file.mimeType;
1136
- }
1137
- detectMimeType(path) {
1138
- const ext = basename2(path).split(".").pop()?.toLowerCase();
1139
- const mimeTypes = {
1140
- txt: "text/plain",
1141
- html: "text/html",
1142
- css: "text/css",
1143
- js: "application/javascript",
1144
- json: "application/json",
1145
- xml: "application/xml",
1146
- pdf: "application/pdf",
1147
- zip: "application/zip",
1148
- jpg: "image/jpeg",
1149
- jpeg: "image/jpeg",
1150
- png: "image/png",
1151
- gif: "image/gif",
1152
- svg: "image/svg+xml",
1153
- mp4: "video/mp4",
1154
- mp3: "audio/mpeg",
1155
- wav: "audio/wav"
1156
- };
1157
- return mimeTypes[ext || ""] || "application/octet-stream";
1158
- }
1159
- async lastModified(path) {
1160
- const stats = await this.stat(path);
1161
- return stats.lastModified;
1162
- }
1163
- async fileSize(path) {
1164
- const stats = await this.stat(path);
1165
- return stats.size;
1166
- }
1167
- clear() {
1168
- this.files.clear();
1169
- this.directories.clear();
1170
- this.directories.add("");
1171
- }
1172
- }
1173
- function createMemoryStorage() {
1174
- return new InMemoryStorageAdapter;
1175
- }
1176
-
1177
- // src/adapters/s3.ts
1178
- import { Buffer as Buffer5 } from "buffer";
1179
- import { basename as basename3 } from "path";
1180
-
1181
- // src/path-sanitize.ts
1182
- class PathSanitizeError extends Error {
1183
- reason;
1184
- constructor(message, reason) {
1185
- super(message);
1186
- this.name = "PathSanitizeError";
1187
- this.reason = reason;
1188
- }
1189
- }
1190
- var MAX_COMPONENT_LENGTH = 255;
1191
- var ALLOWED_DIR_CHAR = /^[A-Za-z0-9._-]+$/;
1192
- var ALLOWED_FILENAME_CHAR = /^[A-Za-z0-9._-]+$/;
1193
- var ALLOWED_EXTENSION = /^[a-z0-9]+$/;
1194
- function sanitizePresignedDir(dir) {
1195
- if (dir === undefined || dir === "")
1196
- return "";
1197
- if (typeof dir !== "string")
1198
- throw new PathSanitizeError(`dir must be a string, got ${typeof dir}`, "not-string");
1199
- const trimmed = dir.replace(/^\/+/, "").replace(/\/+$/, "");
1200
- if (dir.startsWith("/"))
1201
- throw new PathSanitizeError(`dir must not be absolute: '${dir}'`, "absolute-path");
1202
- if (trimmed === "")
1203
- return "";
1204
- if (trimmed.includes("\x00"))
1205
- throw new PathSanitizeError(`dir contains null byte`, "null-byte");
1206
- if (/[\x00-\x1F\x7F]/.test(trimmed))
1207
- throw new PathSanitizeError(`dir contains control character`, "control-char");
1208
- const segments = trimmed.split("/");
1209
- for (const segment of segments) {
1210
- if (segment === "" || segment === "." || segment === "..")
1211
- throw new PathSanitizeError(`dir contains traversal or empty segment: '${dir}'`, "traversal");
1212
- if (segment.length > MAX_COMPONENT_LENGTH)
1213
- throw new PathSanitizeError(`dir segment exceeds ${MAX_COMPONENT_LENGTH} chars`, "too-long");
1214
- if (!ALLOWED_DIR_CHAR.test(segment))
1215
- throw new PathSanitizeError(`dir segment contains disallowed character: '${segment}'`, "invalid-char");
1216
- }
1217
- return segments.join("/");
1218
- }
1219
- function sanitizePresignedFilename(filename) {
1220
- if (typeof filename !== "string")
1221
- throw new PathSanitizeError(`filename must be a string, got ${typeof filename}`, "not-string");
1222
- if (filename === "")
1223
- throw new PathSanitizeError(`filename must not be empty`, "empty");
1224
- if (filename.length > MAX_COMPONENT_LENGTH)
1225
- throw new PathSanitizeError(`filename exceeds ${MAX_COMPONENT_LENGTH} chars`, "too-long");
1226
- if (filename.includes("\x00"))
1227
- throw new PathSanitizeError(`filename contains null byte`, "null-byte");
1228
- if (/[\x00-\x1F\x7F]/.test(filename))
1229
- throw new PathSanitizeError(`filename contains control character`, "control-char");
1230
- if (filename.includes("/") || filename.includes("\\"))
1231
- throw new PathSanitizeError(`filename must not contain path separators: '${filename}'`, "traversal");
1232
- if (filename === "." || filename === ".." || filename.startsWith("../") || filename.includes("/.."))
1233
- throw new PathSanitizeError(`filename contains traversal token: '${filename}'`, "traversal");
1234
- if (!ALLOWED_FILENAME_CHAR.test(filename))
1235
- throw new PathSanitizeError(`filename contains disallowed character: '${filename}'`, "invalid-char");
1236
- const dotIdx = filename.lastIndexOf(".");
1237
- if (dotIdx > 0 && dotIdx < filename.length - 1) {
1238
- const ext = filename.slice(dotIdx + 1).toLowerCase();
1239
- if (!ALLOWED_EXTENSION.test(ext))
1240
- throw new PathSanitizeError(`filename has invalid extension: '.${ext}'`, "invalid-extension");
1241
- }
1242
- return filename;
1243
- }
1244
-
1245
- // src/s3-presigned-post.ts
1246
- import { createHmac as createHmac3 } from "crypto";
1247
- import { Buffer as Buffer4 } from "buffer";
1248
- var ALGORITHM = "AWS4-HMAC-SHA256";
1249
- var MIN_EXPIRY = 60;
1250
- var MAX_EXPIRY = 7 * 24 * 60 * 60;
1251
- function hmac(key, data) {
1252
- return createHmac3("sha256", key).update(data, "utf8").digest();
1253
- }
1254
- function deriveSigningKey(secretAccessKey, dateStamp, region) {
1255
- const kDate = hmac(`AWS4${secretAccessKey}`, dateStamp);
1256
- const kRegion = hmac(kDate, region);
1257
- const kService = hmac(kRegion, "s3");
1258
- const kSigning = hmac(kService, "aws4_request");
1259
- return kSigning;
1260
- }
1261
- function isoDate(now) {
1262
- const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
1263
- const dateStamp = amzDate.slice(0, 8);
1264
- return { amzDate, dateStamp };
1265
- }
1266
- function signS3PresignedPost(input) {
1267
- const expiresIn = Math.floor(input.expiresIn);
1268
- if (!Number.isFinite(expiresIn) || expiresIn < MIN_EXPIRY || expiresIn > MAX_EXPIRY) {
1269
- throw new RangeError(`[storage/s3-post] expiresIn must be between ${MIN_EXPIRY}s and ${MAX_EXPIRY}s (got ${expiresIn}s)`);
1270
- }
1271
- if (!input.bucket)
1272
- throw new Error("[storage/s3-post] bucket is required");
1273
- if (!input.credentials?.accessKeyId || !input.credentials?.secretAccessKey) {
1274
- throw new Error("[storage/s3-post] credentials.accessKeyId and credentials.secretAccessKey are required");
1275
- }
1276
- const now = new Date;
1277
- const { amzDate, dateStamp } = isoDate(now);
1278
- const credentialScope = `${dateStamp}/${input.region}/s3/aws4_request`;
1279
- const credentialField = `${input.credentials.accessKeyId}/${credentialScope}`;
1280
- const expirationDate = new Date(now.getTime() + expiresIn * 1000).toISOString().replace(/\.\d{3}Z$/, "Z");
1281
- const conditions = [];
1282
- conditions.push({ bucket: input.bucket });
1283
- if (typeof input.key === "string") {
1284
- conditions.push({ key: input.key });
1285
- } else {
1286
- conditions.push(["starts-with", "$key", input.key.startsWith]);
1287
- }
1288
- const acl = input.acl ?? "private";
1289
- conditions.push({ acl });
1290
- if (typeof input.contentType === "string") {
1291
- conditions.push({ "Content-Type": input.contentType });
1292
- } else {
1293
- conditions.push(["starts-with", "$Content-Type", input.contentType.startsWith]);
1294
- }
1295
- if (input.contentLengthRange) {
1296
- if (!Number.isFinite(input.contentLengthRange.min) || input.contentLengthRange.min < 0 || !Number.isFinite(input.contentLengthRange.max) || input.contentLengthRange.max < input.contentLengthRange.min) {
1297
- throw new RangeError("[storage/s3-post] contentLengthRange must satisfy 0 <= min <= max");
1298
- }
1299
- conditions.push(["content-length-range", input.contentLengthRange.min, input.contentLengthRange.max]);
1300
- }
1301
- if (input.fields) {
1302
- for (const [k, v] of Object.entries(input.fields)) {
1303
- conditions.push({ [k]: v });
1304
- }
1305
- }
1306
- conditions.push({ "x-amz-credential": credentialField });
1307
- conditions.push({ "x-amz-algorithm": ALGORITHM });
1308
- conditions.push({ "x-amz-date": amzDate });
1309
- if (input.credentials.sessionToken) {
1310
- conditions.push({ "x-amz-security-token": input.credentials.sessionToken });
1311
- }
1312
- const policy = {
1313
- expiration: expirationDate,
1314
- conditions
1315
- };
1316
- const policyBase64 = Buffer4.from(JSON.stringify(policy), "utf8").toString("base64");
1317
- const signingKey = deriveSigningKey(input.credentials.secretAccessKey, dateStamp, input.region);
1318
- const signature = createHmac3("sha256", signingKey).update(policyBase64, "utf8").digest("hex");
1319
- const fields = {
1320
- key: typeof input.key === "string" ? input.key : `${input.key.startsWith}\${filename}`,
1321
- acl,
1322
- "Content-Type": typeof input.contentType === "string" ? input.contentType : input.contentType.startsWith,
1323
- "x-amz-credential": credentialField,
1324
- "x-amz-algorithm": ALGORITHM,
1325
- "x-amz-date": amzDate,
1326
- policy: policyBase64,
1327
- "x-amz-signature": signature,
1328
- ...input.credentials.sessionToken ? { "x-amz-security-token": input.credentials.sessionToken } : {},
1329
- ...input.fields ?? {}
1330
- };
1331
- return {
1332
- url: `https://${input.bucket}.s3.${input.region}.amazonaws.com/`,
1333
- fields,
1334
- key: typeof input.key === "string" ? input.key : input.key.startsWith
1335
- };
1336
- }
1337
-
1338
- // src/adapters/s3.ts
1339
- import process3 from "process";
1340
- var S3_MIN_PART_SIZE = 5 * 1024 * 1024;
1341
- var S3_MAX_PART_SIZE = 5 * 1024 * 1024 * 1024;
1342
- function clampPartSize(requested) {
1343
- if (!Number.isFinite(requested))
1344
- return S3_MIN_PART_SIZE;
1345
- return Math.max(S3_MIN_PART_SIZE, Math.min(Math.floor(requested), S3_MAX_PART_SIZE));
1346
- }
1347
-
1348
- class ChunkBuffer {
1349
- chunks = [];
1350
- total = 0;
1351
- constructor(_partSize) {}
1352
- get length() {
1353
- return this.total;
1354
- }
1355
- push(c) {
1356
- this.chunks.push(c);
1357
- this.total += c.length;
1358
- }
1359
- take(n) {
1360
- const out = new Uint8Array(n);
1361
- let written = 0;
1362
- while (written < n && this.chunks.length > 0) {
1363
- const head = this.chunks[0];
1364
- const need = n - written;
1365
- if (head.length <= need) {
1366
- out.set(head, written);
1367
- written += head.length;
1368
- this.chunks.shift();
1369
- } else {
1370
- out.set(head.subarray(0, need), written);
1371
- this.chunks[0] = head.subarray(need);
1372
- written += need;
1373
- }
1374
- }
1375
- this.total -= n;
1376
- return out;
1377
- }
1378
- flush() {
1379
- const out = new Uint8Array(this.total);
1380
- let off = 0;
1381
- for (const c of this.chunks) {
1382
- out.set(c, off);
1383
- off += c.length;
1384
- }
1385
- this.chunks = [];
1386
- this.total = 0;
1387
- return out;
1388
- }
1389
- }
1390
- async function isSettled(p3) {
1391
- const sentinel = Symbol("pending");
1392
- const result = await Promise.race([
1393
- p3.then(() => "settled", () => "settled"),
1394
- Promise.resolve(sentinel)
1395
- ]);
1396
- return result !== sentinel;
1397
- }
1398
-
1399
- class S3StorageAdapter {
1400
- _client;
1401
- _clientPromise = null;
1402
- bucket;
1403
- prefix;
1404
- region;
1405
- credentials;
1406
- constructor(client, config) {
1407
- this._client = client;
1408
- this.bucket = config.bucket || "";
1409
- this.prefix = config.prefix || "";
1410
- this.region = config.region || "us-east-1";
1411
- this.credentials = config.credentials;
1412
- if (!this.bucket) {
1413
- throw new Error("S3 bucket name is required");
1414
- }
1415
- }
1416
- async getClient() {
1417
- if (this._client)
1418
- return this._client;
1419
- if (!this._clientPromise) {
1420
- this._clientPromise = import("@stacksjs/ts-cloud").then((cloud) => {
1421
- this._client = new cloud.S3Client(this.region);
1422
- return this._client;
1423
- });
1424
- }
1425
- return this._clientPromise;
1426
- }
1427
- resolveCredentials() {
1428
- if (this.credentials?.accessKeyId && this.credentials.secretAccessKey)
1429
- return this.credentials;
1430
- const accessKeyId = process3.env.AWS_ACCESS_KEY_ID;
1431
- const secretAccessKey = process3.env.AWS_SECRET_ACCESS_KEY;
1432
- const sessionToken = process3.env.AWS_SESSION_TOKEN;
1433
- if (!accessKeyId || !secretAccessKey) {
1434
- throw new Error("[storage/s3] presignedUploadPolicy requires AWS credentials \u2014 " + "pass them via S3DiskConfig.credentials or set AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY.");
1435
- }
1436
- return { accessKeyId, secretAccessKey, sessionToken };
1437
- }
1438
- prefixPath(path) {
1439
- if (!this.prefix)
1440
- return path;
1441
- return `${this.prefix}/${path}`.replace(/\/+/g, "/");
1442
- }
1443
- stripPrefix(path) {
1444
- if (!this.prefix)
1445
- return path;
1446
- const prefixWithSlash = `${this.prefix}/`;
1447
- return path.startsWith(prefixWithSlash) ? path.slice(prefixWithSlash.length) : path;
1448
- }
1449
- async contentsToBuffer(contents) {
1450
- if (typeof contents === "string") {
1451
- return Buffer5.from(contents, "utf8");
1452
- } else if (contents instanceof Buffer5) {
1453
- return contents;
1454
- } else if (contents instanceof Uint8Array) {
1455
- return Buffer5.from(contents);
1456
- } else {
1457
- const stream = contents;
1458
- if (typeof stream.getReader !== "function") {
1459
- throw new TypeError("[storage/s3] contents must be a web-standard ReadableStream (with .getReader()), not a Node stream.Readable. Convert via Readable.toWeb(nodeStream) before passing.");
1460
- }
1461
- const reader = contents.getReader();
1462
- const chunks = [];
1463
- while (true) {
1464
- const { done, value } = await reader.read();
1465
- if (done)
1466
- break;
1467
- if (value)
1468
- chunks.push(value);
1469
- }
1470
- return Buffer5.concat(chunks.map((c) => Buffer5.from(c)));
1471
- }
1472
- }
1473
- async write(path, contents) {
1474
- const key = this.prefixPath(path);
1475
- const body = await this.contentsToBuffer(contents);
1476
- const contentType = this.detectMimeType(path);
1477
- await (await this.getClient()).putObject({
1478
- bucket: this.bucket,
1479
- key,
1480
- body,
1481
- contentType
1482
- });
1483
- return {
1484
- path,
1485
- size: body.length,
1486
- contentType,
1487
- lastModified: Date.now()
1488
- };
1489
- }
1490
- async read(path) {
1491
- const key = this.prefixPath(path);
1492
- const response = await (await this.getClient()).getObject(this.bucket, key);
1493
- if (!response) {
1494
- throw new Error(`Failed to read file: ${path}`);
1495
- }
1496
- return Buffer5.from(response);
1497
- }
1498
- async getStream(path, _options) {
1499
- const key = this.prefixPath(path);
1500
- const buf = await (await this.getClient()).getObjectBuffer(this.bucket, key);
1501
- if (!buf)
1502
- throw new Error(`Failed to read file: ${path}`);
1503
- const bytes = new Uint8Array(buf);
1504
- return new ReadableStream({
1505
- start(controller) {
1506
- controller.enqueue(bytes);
1507
- controller.close();
1508
- }
1509
- });
1510
- }
1511
- async putStream(path, stream, options) {
1512
- const key = this.prefixPath(path);
1513
- const contentType = options?.contentType ?? this.detectMimeType(path);
1514
- const partSize = clampPartSize(options?.partSize ?? 5242880);
1515
- const concurrency = Math.max(1, Math.min(options?.concurrency ?? 4, 100));
1516
- const maxRetries = Math.max(0, options?.maxRetries ?? 3);
1517
- const signal = options?.signal;
1518
- const reader = stream.getReader();
1519
- let firstChunk = null;
1520
- let firstDone = false;
1521
- {
1522
- const buf = new ChunkBuffer(partSize);
1523
- while (!firstDone && buf.length < partSize) {
1524
- if (signal?.aborted) {
1525
- try {
1526
- reader.releaseLock();
1527
- } catch {}
1528
- throw new Error("aborted");
1529
- }
1530
- const { value, done } = await reader.read();
1531
- if (done) {
1532
- firstDone = true;
1533
- break;
1534
- }
1535
- if (value)
1536
- buf.push(value);
1537
- }
1538
- firstChunk = buf.flush();
1539
- }
1540
- if (firstDone) {
1541
- try {
1542
- reader.releaseLock();
1543
- } catch {}
1544
- await (await this.getClient()).putObject({
1545
- bucket: this.bucket,
1546
- key,
1547
- body: Buffer5.from(firstChunk),
1548
- contentType
1549
- });
1550
- return { path, size: firstChunk.length, contentType, lastModified: Date.now() };
1551
- }
1552
- const { UploadId: uploadId } = await (await this.getClient()).createMultipartUpload(this.bucket, key, { contentType });
1553
- const completedParts = [];
1554
- let totalBytes = 0;
1555
- let partNumber = 1;
1556
- const inflight = [];
1557
- const uploadOne = async (body, n) => {
1558
- let attempt = 0;
1559
- while (true) {
1560
- if (signal?.aborted)
1561
- throw new Error("aborted");
1562
- try {
1563
- const { ETag } = await (await this.getClient()).uploadPart(this.bucket, key, uploadId, n, Buffer5.from(body));
1564
- completedParts.push({ PartNumber: n, ETag });
1565
- totalBytes += body.length;
1566
- return;
1567
- } catch (err2) {
1568
- if (attempt >= maxRetries)
1569
- throw err2;
1570
- attempt += 1;
1571
- }
1572
- }
1573
- };
1574
- try {
1575
- inflight.push(uploadOne(firstChunk, partNumber++));
1576
- firstChunk = null;
1577
- const buf = new ChunkBuffer(partSize);
1578
- while (true) {
1579
- if (signal?.aborted)
1580
- throw new Error("aborted");
1581
- const { value, done } = await reader.read();
1582
- if (done)
1583
- break;
1584
- if (value)
1585
- buf.push(value);
1586
- while (buf.length >= partSize) {
1587
- const part = buf.take(partSize);
1588
- if (inflight.length >= concurrency) {
1589
- await Promise.race(inflight.map((p3, i) => p3.then(() => i)));
1590
- for (let i = inflight.length - 1;i >= 0; i--) {
1591
- if (await isSettled(inflight[i]))
1592
- inflight.splice(i, 1);
1593
- }
1594
- }
1595
- inflight.push(uploadOne(part, partNumber++));
1596
- }
1597
- }
1598
- try {
1599
- reader.releaseLock();
1600
- } catch {}
1601
- const tail = buf.flush();
1602
- if (tail.length > 0)
1603
- inflight.push(uploadOne(tail, partNumber++));
1604
- await Promise.all(inflight);
1605
- completedParts.sort((a, b) => a.PartNumber - b.PartNumber);
1606
- await (await this.getClient()).completeMultipartUpload(this.bucket, key, uploadId, completedParts);
1607
- return { path, size: totalBytes, contentType, lastModified: Date.now() };
1608
- } catch (err2) {
1609
- try {
1610
- await (await this.getClient()).abortMultipartUpload(this.bucket, key, uploadId);
1611
- } catch {}
1612
- throw err2;
1613
- }
1614
- }
1615
- async readToString(path) {
1616
- const key = this.prefixPath(path);
1617
- const response = await (await this.getClient()).getObject(this.bucket, key);
1618
- if (!response) {
1619
- throw new Error(`Failed to read file: ${path}`);
1620
- }
1621
- return response;
1622
- }
1623
- async readToBuffer(path) {
1624
- const contents = await this.read(path);
1625
- return contents;
1626
- }
1627
- async readToUint8Array(path) {
1628
- const buffer = await this.readToBuffer(path);
1629
- return new Uint8Array(buffer);
1630
- }
1631
- async deleteFile(path) {
1632
- const key = this.prefixPath(path);
1633
- await (await this.getClient()).deleteObject(this.bucket, key);
1634
- }
1635
- async deleteDirectory(path) {
1636
- const prefix = this.prefixPath(path);
1637
- const normalizedPrefix = prefix.endsWith("/") ? prefix : `${prefix}/`;
1638
- const objects = await (await this.getClient()).listAllObjects({ bucket: this.bucket, prefix: normalizedPrefix });
1639
- const keys = objects.map((obj) => obj.Key).filter((k) => typeof k === "string");
1640
- if (keys.length === 0) {
1641
- return;
1642
- }
1643
- await (await this.getClient()).deleteObjects(this.bucket, keys);
1644
- }
1645
- async createDirectory(_path) {}
1646
- async moveFile(from, to) {
1647
- await this.copyFile(from, to);
1648
- await this.deleteFile(from);
1649
- }
1650
- async copyFile(from, to) {
1651
- const fromKey = this.prefixPath(from);
1652
- const toKey = this.prefixPath(to);
1653
- await (await this.getClient()).copyObject({
1654
- sourceBucket: this.bucket,
1655
- sourceKey: fromKey,
1656
- destinationBucket: this.bucket,
1657
- destinationKey: toKey
1658
- });
1659
- }
1660
- async stat(path) {
1661
- const key = this.prefixPath(path);
1662
- const result = await (await this.getClient()).headObject(this.bucket, key);
1663
- if (!result) {
1664
- throw new Error(`File not found: ${path}`);
1665
- }
1666
- return {
1667
- path,
1668
- type: "file",
1669
- visibility: "private",
1670
- size: result.ContentLength || 0,
1671
- lastModified: result.LastModified ? new Date(result.LastModified).getTime() : Date.now(),
1672
- mimeType: result.ContentType
1673
- };
1674
- }
1675
- list(path, options = {}) {
1676
- return this.createAsyncIterator(path, options.deep || false);
1677
- }
1678
- async* createAsyncIterator(path, deep) {
1679
- const prefix = this.prefixPath(path);
1680
- const normalizedPrefix = prefix ? `${prefix}/` : undefined;
1681
- if (deep) {
1682
- const objects = await (await this.getClient()).listAllObjects({ bucket: this.bucket, prefix: normalizedPrefix });
1683
- for (const obj of objects) {
1684
- yield {
1685
- path: this.stripPrefix(obj.Key),
1686
- type: "file"
1687
- };
1688
- }
1689
- } else {
1690
- let continuationToken;
1691
- do {
1692
- const result = await (await this.getClient()).listObjects({
1693
- bucket: this.bucket,
1694
- prefix: normalizedPrefix,
1695
- continuationToken
1696
- });
1697
- for (const obj of result.objects || []) {
1698
- yield {
1699
- path: this.stripPrefix(obj.Key),
1700
- type: "file"
1701
- };
1702
- }
1703
- continuationToken = result.nextContinuationToken;
1704
- } while (continuationToken);
1705
- }
1706
- }
1707
- async changeVisibility(path, vis) {
1708
- const key = this.prefixPath(path);
1709
- const acl = vis === "public" ? "public-read" : "private";
1710
- await (await this.getClient()).putObjectAcl(this.bucket, key, acl);
1711
- }
1712
- async visibility(path) {
1713
- const key = this.prefixPath(path);
1714
- const acl = await (await this.getClient()).getObjectAcl(this.bucket, key);
1715
- const grants = acl?.Grants ?? [];
1716
- const isPublic = grants.some((g) => g.Grantee?.URI === "http://acs.amazonaws.com/groups/global/AllUsers" && (g.Permission === "READ" || g.Permission === "FULL_CONTROL"));
1717
- return isPublic ? "public" : "private";
1718
- }
1719
- async fileExists(path) {
1720
- const key = this.prefixPath(path);
1721
- try {
1722
- const result = await (await this.getClient()).headObject(this.bucket, key);
1723
- return !!result;
1724
- } catch (error) {
1725
- if (!error.message?.includes("404") && !error.message?.includes("NoSuchKey") && !error.message?.includes("NotFound")) {
1726
- console.debug(`[s3] Unexpected error checking file existence for ${path}: ${error.message}`);
1727
- }
1728
- return false;
1729
- }
1730
- }
1731
- async directoryExists(path) {
1732
- const prefix = this.prefixPath(path);
1733
- const result = await (await this.getClient()).listObjects({
1734
- bucket: this.bucket,
1735
- prefix: `${prefix}/`,
1736
- maxKeys: 1
1737
- });
1738
- return (result.objects || []).length > 0;
1739
- }
1740
- async publicUrl(path, options = {}) {
1741
- const key = this.prefixPath(path);
1742
- const domain = options.domain || `https://${this.bucket}.s3.${this.region}.amazonaws.com`;
1743
- return `${domain}/${key}`;
1744
- }
1745
- async temporaryUrl(path, options) {
1746
- const key = this.prefixPath(path);
1747
- const expiresIn = Math.floor(normalizeExpiryToMilliseconds(options.expiresIn) / 1000);
1748
- const MIN_EXPIRY2 = 60;
1749
- const MAX_EXPIRY2 = 604800;
1750
- if (!Number.isFinite(expiresIn) || expiresIn < MIN_EXPIRY2 || expiresIn > MAX_EXPIRY2) {
1751
- throw new RangeError(`[storage/s3] temporaryUrl expiresIn must be between 60s and 7 days (got ${expiresIn}s)`);
1752
- }
1753
- return await (await this.getClient()).getSignedUrl({
1754
- bucket: this.bucket,
1755
- key,
1756
- expiresIn,
1757
- operation: "getObject"
1758
- });
1759
- }
1760
- async signedUrl(path, options) {
1761
- return this.temporaryUrl(path, { expiresIn: options.expiresIn });
1762
- }
1763
- async presignedUploadUrl(options) {
1764
- if (!options.contentType)
1765
- throw new Error("[storage/s3] presignedUploadUrl requires `contentType` \u2014 S3 signs against the exact header.");
1766
- const expiresIn = Math.floor(options.expiresIn);
1767
- const MIN_EXPIRY2 = 60;
1768
- const MAX_EXPIRY2 = 604800;
1769
- if (!Number.isFinite(expiresIn) || expiresIn < MIN_EXPIRY2 || expiresIn > MAX_EXPIRY2) {
1770
- throw new RangeError(`[storage/s3] presignedUploadUrl expiresIn must be between 60s and 7 days (got ${expiresIn}s)`);
1771
- }
1772
- const safeDir = sanitizePresignedDir(options.dir);
1773
- const safeFilename = options.filename !== undefined ? sanitizePresignedFilename(options.filename) : `${crypto.randomUUID().replace(/-/g, "")}${this.extensionForContentType(options.contentType)}`;
1774
- const path = safeDir ? `${safeDir}/${safeFilename}` : safeFilename;
1775
- const key = this.prefixPath(path);
1776
- const url = await (await this.getClient()).getSignedUrl({
1777
- bucket: this.bucket,
1778
- key,
1779
- expiresIn,
1780
- operation: "putObject"
1781
- });
1782
- return {
1783
- url,
1784
- path,
1785
- key,
1786
- contentType: options.contentType,
1787
- maxBytes: options.maxBytes
1788
- };
1789
- }
1790
- async presignedUploadPolicy(options) {
1791
- const credentials = this.resolveCredentials();
1792
- const scopedKey = typeof options.key === "string" ? this.prefixPath(options.key) : { startsWith: this.prefixPath(options.key.startsWith) };
1793
- return signS3PresignedPost({
1794
- bucket: this.bucket,
1795
- region: this.region,
1796
- credentials,
1797
- key: scopedKey,
1798
- contentType: options.contentType,
1799
- contentLengthRange: options.contentLengthRange,
1800
- acl: options.acl,
1801
- expiresIn: options.expiresIn,
1802
- fields: options.fields
1803
- });
1804
- }
1805
- extensionForContentType(contentType) {
1806
- const mime = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
1807
- const map = {
1808
- "image/jpeg": ".jpg",
1809
- "image/jpg": ".jpg",
1810
- "image/png": ".png",
1811
- "image/webp": ".webp",
1812
- "image/gif": ".gif",
1813
- "image/avif": ".avif",
1814
- "image/svg+xml": ".svg",
1815
- "application/pdf": ".pdf",
1816
- "application/json": ".json",
1817
- "application/zip": ".zip",
1818
- "text/plain": ".txt",
1819
- "text/csv": ".csv",
1820
- "video/mp4": ".mp4",
1821
- "video/webm": ".webm",
1822
- "audio/mpeg": ".mp3",
1823
- "audio/wav": ".wav"
1824
- };
1825
- return map[mime] ?? "";
1826
- }
1827
- async checksum(path, options = {}) {
1828
- const algorithm = options.algorithm || "sha256";
1829
- const content = await this.readToUint8Array(path);
1830
- const hasher = new Bun.CryptoHasher(algorithm);
1831
- hasher.update(content);
1832
- return hasher.digest("hex");
1833
- }
1834
- async mimeType(path, _options = {}) {
1835
- const stats = await this.stat(path);
1836
- return stats.mimeType || this.detectMimeType(path);
1837
- }
1838
- detectMimeType(path) {
1839
- const ext = basename3(path).split(".").pop()?.toLowerCase();
1840
- const mimeTypes = {
1841
- txt: "text/plain",
1842
- html: "text/html",
1843
- css: "text/css",
1844
- js: "application/javascript",
1845
- json: "application/json",
1846
- xml: "application/xml",
1847
- pdf: "application/pdf",
1848
- zip: "application/zip",
1849
- jpg: "image/jpeg",
1850
- jpeg: "image/jpeg",
1851
- png: "image/png",
1852
- gif: "image/gif",
1853
- svg: "image/svg+xml",
1854
- mp4: "video/mp4",
1855
- mp3: "audio/mpeg",
1856
- wav: "audio/wav"
1857
- };
1858
- return mimeTypes[ext || ""] || "application/octet-stream";
1859
- }
1860
- async lastModified(path) {
1861
- const stats = await this.stat(path);
1862
- return stats.lastModified;
1863
- }
1864
- async fileSize(path) {
1865
- const stats = await this.stat(path);
1866
- return stats.size;
1867
- }
1868
- }
1869
- function createS3Storage(client, config) {
1870
- return new S3StorageAdapter(client, config);
1871
- }
1872
-
1873
- // src/adapters/bun.ts
1874
- import { Buffer as Buffer6 } from "buffer";
1875
- var {file, write: bunWrite } = globalThis.Bun;
1876
- import { chmod as chmod2, lstat as lstat2 } from "fs/promises";
1877
- import { dirname as dirname4, join as join6, relative as relative2 } from "path";
1878
- class BunStorageAdapter {
1879
- root;
1880
- constructor(config = {}) {
1881
- this.root = config.root || process.cwd();
1882
- }
1883
- resolvePath(path) {
1884
- const resolved = join6(this.root, path);
1885
- const rel = relative2(this.root, resolved);
1886
- if (rel.startsWith("..") || rel.startsWith("../") || rel.startsWith("..\\")) {
1887
- throw new Error(`Path traversal detected: '${path}' resolves outside storage root`);
1888
- }
1889
- return resolved;
1890
- }
1891
- async write(path, contents) {
1892
- const fullPath = this.resolvePath(path);
1893
- const dir = dirname4(fullPath);
1894
- await this.createDirectory(relative2(this.root, dir));
1895
- if (typeof contents === "string") {
1896
- await bunWrite(fullPath, contents);
1897
- } else if (contents instanceof Buffer6) {
1898
- await bunWrite(fullPath, contents);
1899
- } else if (contents instanceof Uint8Array) {
1900
- await bunWrite(fullPath, contents);
1901
- } else {
1902
- const stream = contents;
1903
- if (typeof stream.getReader !== "function") {
1904
- throw new TypeError("[storage/bun] contents must be a web-standard ReadableStream " + "(with .getReader()), not a Node stream.Readable. " + "Convert via Readable.toWeb(nodeStream) before passing.");
1905
- }
1906
- const reader = contents.getReader();
1907
- const chunks = [];
1908
- while (true) {
1909
- const { done, value } = await reader.read();
1910
- if (done)
1911
- break;
1912
- if (value)
1913
- chunks.push(value);
1914
- }
1915
- const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
1916
- const result = new Uint8Array(totalLength);
1917
- let offset = 0;
1918
- for (const chunk of chunks) {
1919
- result.set(chunk, offset);
1920
- offset += chunk.length;
1921
- }
1922
- await bunWrite(fullPath, result);
1923
- }
1924
- const written = file(fullPath);
1925
- return {
1926
- path,
1927
- size: written.size,
1928
- lastModified: written.lastModified
1929
- };
1930
- }
1931
- async read(path) {
1932
- const fullPath = this.resolvePath(path);
1933
- const bunFile = file(fullPath);
1934
- if (!await bunFile.exists()) {
1935
- throw new Error(`File not found: ${path}`);
1936
- }
1937
- return await bunFile.arrayBuffer().then((buf) => new Uint8Array(buf));
1938
- }
1939
- async getStream(path, _options) {
1940
- const fullPath = this.resolvePath(path);
1941
- const bunFile = file(fullPath);
1942
- if (!await bunFile.exists())
1943
- throw new Error(`File not found: ${path}`);
1944
- return bunFile.stream();
1945
- }
1946
- async putStream(path, stream, options) {
1947
- const fullPath = this.resolvePath(path);
1948
- const dir = dirname4(fullPath);
1949
- await this.createDirectory(relative2(this.root, dir));
1950
- try {
1951
- const body = new Response(stream);
1952
- const writePromise = bunWrite(fullPath, body);
1953
- if (options?.signal) {
1954
- const abortHandler = () => {};
1955
- options.signal.addEventListener("abort", abortHandler, { once: true });
1956
- try {
1957
- await writePromise;
1958
- } finally {
1959
- options.signal.removeEventListener("abort", abortHandler);
1960
- }
1961
- } else {
1962
- await writePromise;
1963
- }
1964
- } catch (err2) {
1965
- try {
1966
- await Bun.$.throws(false)`rm -f ${fullPath}`;
1967
- } catch {}
1968
- throw err2;
1969
- }
1970
- const written = file(fullPath);
1971
- return {
1972
- path,
1973
- size: written.size,
1974
- contentType: options?.contentType,
1975
- lastModified: written.lastModified
1976
- };
1977
- }
1978
- async readToString(path) {
1979
- const fullPath = this.resolvePath(path);
1980
- const bunFile = file(fullPath);
1981
- if (!await bunFile.exists()) {
1982
- throw new Error(`File not found: ${path}`);
1983
- }
1984
- return await bunFile.text();
1985
- }
1986
- async readToBuffer(path) {
1987
- const fullPath = this.resolvePath(path);
1988
- const bunFile = file(fullPath);
1989
- if (!await bunFile.exists()) {
1990
- throw new Error(`File not found: ${path}`);
1991
- }
1992
- const arrayBuffer = await bunFile.arrayBuffer();
1993
- return Buffer6.from(arrayBuffer);
1994
- }
1995
- async readToUint8Array(path) {
1996
- const fullPath = this.resolvePath(path);
1997
- const bunFile = file(fullPath);
1998
- if (!await bunFile.exists()) {
1999
- throw new Error(`File not found: ${path}`);
2000
- }
2001
- const arrayBuffer = await bunFile.arrayBuffer();
2002
- return new Uint8Array(arrayBuffer);
2003
- }
2004
- async deleteFile(path) {
2005
- const fullPath = this.resolvePath(path);
2006
- const bunFile = file(fullPath);
2007
- if (await bunFile.exists()) {
2008
- await Bun.$.throws(false)`rm ${fullPath}`;
2009
- }
2010
- }
2011
- async deleteDirectory(path) {
2012
- const fullPath = this.resolvePath(path);
2013
- await Bun.$.throws(false)`rm -rf ${fullPath}`;
2014
- }
2015
- async createDirectory(path) {
2016
- const fullPath = this.resolvePath(path);
2017
- await Bun.$.throws(false)`mkdir -p ${fullPath}`;
2018
- }
2019
- async moveFile(from, to) {
2020
- const fromPath = this.resolvePath(from);
2021
- const toPath = this.resolvePath(to);
2022
- const toDir = dirname4(toPath);
2023
- await this.createDirectory(relative2(this.root, toDir));
2024
- await Bun.$.throws(true)`mv ${fromPath} ${toPath}`;
2025
- }
2026
- async copyFile(from, to) {
2027
- const fromPath = this.resolvePath(from);
2028
- const toPath = this.resolvePath(to);
2029
- const toDir = dirname4(toPath);
2030
- await this.createDirectory(relative2(this.root, toDir));
2031
- await Bun.$.throws(true)`cp ${fromPath} ${toPath}`;
2032
- }
2033
- async stat(path) {
2034
- const fullPath = this.resolvePath(path);
2035
- const bunFile = file(fullPath);
2036
- if (!await bunFile.exists()) {
2037
- throw new Error(`File not found: ${path}`);
2038
- }
2039
- const stats = await Bun.file(fullPath).stat();
2040
- const isDir2 = stats.isDirectory();
2041
- return {
2042
- path,
2043
- type: isDir2 ? "directory" : "file",
2044
- visibility: "private",
2045
- size: isDir2 ? 0 : stats.size,
2046
- lastModified: stats.mtime?.getTime() || Date.now(),
2047
- mimeType: isDir2 ? undefined : bunFile.type
2048
- };
2049
- }
2050
- list(path, options = {}) {
2051
- const fullPath = this.resolvePath(path);
2052
- return this.createAsyncIterator(fullPath, options.deep || false);
2053
- }
2054
- async* createAsyncIterator(dirPath, deep) {
2055
- const entries = [];
2056
- try {
2057
- const glob2 = new Bun.Glob(deep ? "**/*" : "*");
2058
- for await (const entry of glob2.scan({ cwd: dirPath, onlyFiles: false })) {
2059
- const fullEntryPath = join6(dirPath, entry);
2060
- const stats = await Bun.file(fullEntryPath).stat();
2061
- entries.push({
2062
- path: relative2(this.root, fullEntryPath),
2063
- type: stats.isDirectory() ? "directory" : "file"
2064
- });
2065
- }
2066
- } catch (error) {
2067
- return;
2068
- }
2069
- yield* createDirectoryListing(entries);
2070
- }
2071
- async changeVisibility(path, vis) {
2072
- const fullPath = this.resolvePath(path);
2073
- const stats = await lstat2(fullPath);
2074
- const isDir2 = stats.isDirectory();
2075
- const mode = vis === "public" ? isDir2 ? 493 : 420 : isDir2 ? 448 : 384;
2076
- await chmod2(fullPath, mode);
2077
- }
2078
- async visibility(path) {
2079
- const fullPath = this.resolvePath(path);
2080
- const stats = await lstat2(fullPath);
2081
- const perms = stats.mode & 511;
2082
- return perms & 4 ? "public" : "private";
2083
- }
2084
- async fileExists(path) {
2085
- const fullPath = this.resolvePath(path);
2086
- const bunFile = file(fullPath);
2087
- return await bunFile.exists();
2088
- }
2089
- async directoryExists(path) {
2090
- const fullPath = this.resolvePath(path);
2091
- try {
2092
- const stats = await Bun.file(fullPath).stat();
2093
- return stats.isDirectory();
2094
- } catch {
2095
- return false;
2096
- }
2097
- }
2098
- async publicUrl(path, options = {}) {
2099
- const domain = options.domain || "http://localhost";
2100
- return `${domain}/${path}`;
2101
- }
2102
- async temporaryUrl(path, options) {
2103
- const expiry = normalizeExpiryToDate(options.expiresIn);
2104
- const token = Buffer6.from(`${path}:${expiry.getTime()}`).toString("base64url");
2105
- return `http://localhost/temp/${token}`;
2106
- }
2107
- async signedUrl(path, options) {
2108
- const token = createSignedStorageToken(path, options);
2109
- const baseUrl = (options.baseUrl || process.env.APP_URL || "http://localhost").replace(/\/$/, "");
2110
- return `${baseUrl}/__storage/${encodeURIComponent(path)}?token=${token}`;
2111
- }
2112
- async checksum(path, options = {}) {
2113
- const algorithm = options.algorithm || "sha256";
2114
- const fullPath = this.resolvePath(path);
2115
- const bunFile = file(fullPath);
2116
- if (!await bunFile.exists()) {
2117
- throw new Error(`File not found: ${path}`);
2118
- }
2119
- const hasher = new Bun.CryptoHasher(algorithm);
2120
- const arrayBuffer = await bunFile.arrayBuffer();
2121
- hasher.update(new Uint8Array(arrayBuffer));
2122
- return hasher.digest("hex");
2123
- }
2124
- async mimeType(path, _options = {}) {
2125
- const fullPath = this.resolvePath(path);
2126
- const bunFile = file(fullPath);
2127
- if (!await bunFile.exists()) {
2128
- throw new Error(`File not found: ${path}`);
2129
- }
2130
- return bunFile.type || "application/octet-stream";
2131
- }
2132
- async lastModified(path) {
2133
- const stats = await this.stat(path);
2134
- return stats.lastModified;
2135
- }
2136
- async fileSize(path) {
2137
- const stats = await this.stat(path);
2138
- return stats.size;
2139
- }
2140
- }
2141
- function createBunStorage(config = {}) {
2142
- return new BunStorageAdapter(config);
2143
- }
2144
-
2145
- // src/adapters/scoped.ts
2146
- var DEFAULT_SCOPE_PATTERN = /^[a-z0-9_-]+$/i;
2147
-
2148
- class ScopedStorageAdapter {
2149
- inner;
2150
- scope;
2151
- scopeWithSlash;
2152
- constructor(inner, options) {
2153
- const pattern = options.scopePattern ?? DEFAULT_SCOPE_PATTERN;
2154
- const cleaned = String(options.scope).replace(/^\/+|\/+$/g, "");
2155
- if (!cleaned)
2156
- throw new Error("[storage/scoped] scope is required");
2157
- if (!pattern.test(cleaned))
2158
- throw new Error(`[storage/scoped] scope '${cleaned}' contains disallowed characters`);
2159
- if (cleaned.includes("..") || cleaned.includes("/"))
2160
- throw new Error(`[storage/scoped] scope '${cleaned}' cannot contain path separators or traversal`);
2161
- this.inner = inner;
2162
- this.scope = cleaned;
2163
- this.scopeWithSlash = `${cleaned}/`;
2164
- }
2165
- scopePath(path) {
2166
- if (typeof path !== "string")
2167
- throw new Error("[storage/scoped] path must be a string");
2168
- if (path.length === 0)
2169
- return this.scope;
2170
- if (path.includes("\x00"))
2171
- throw new Error("[storage/scoped] path contains a null byte");
2172
- if (path.startsWith("/") || /^[A-Za-z]:[\\/]/.test(path))
2173
- throw new Error(`[storage/scoped] path '${path}' is absolute \u2014 refusing to escape scope`);
2174
- const segments = path.split(/[/\\]/);
2175
- if (segments.some((s) => s === ".."))
2176
- throw new Error(`[storage/scoped] path '${path}' contains a '..' segment \u2014 refusing to escape scope`);
2177
- return `${this.scope}/${path.replace(/^\/+/, "")}`;
2178
- }
2179
- unscopePath(path) {
2180
- if (path === this.scope)
2181
- return "";
2182
- if (path.startsWith(this.scopeWithSlash))
2183
- return path.slice(this.scopeWithSlash.length);
2184
- return path;
2185
- }
2186
- async write(path, contents) {
2187
- const result = await this.inner.write(this.scopePath(path), contents);
2188
- return { ...result, path: this.unscopePath(result.path) };
2189
- }
2190
- async read(path) {
2191
- return this.inner.read(this.scopePath(path));
2192
- }
2193
- async readToString(path) {
2194
- return this.inner.readToString(this.scopePath(path));
2195
- }
2196
- async readToBuffer(path) {
2197
- return this.inner.readToBuffer(this.scopePath(path));
2198
- }
2199
- async readToUint8Array(path) {
2200
- return this.inner.readToUint8Array(this.scopePath(path));
2201
- }
2202
- async deleteFile(path) {
2203
- return this.inner.deleteFile(this.scopePath(path));
2204
- }
2205
- async deleteDirectory(path) {
2206
- return this.inner.deleteDirectory(this.scopePath(path));
2207
- }
2208
- async createDirectory(path) {
2209
- return this.inner.createDirectory(this.scopePath(path));
2210
- }
2211
- async moveFile(from, to) {
2212
- return this.inner.moveFile(this.scopePath(from), this.scopePath(to));
2213
- }
2214
- async copyFile(from, to) {
2215
- return this.inner.copyFile(this.scopePath(from), this.scopePath(to));
2216
- }
2217
- async stat(path) {
2218
- const entry = await this.inner.stat(this.scopePath(path));
2219
- return { ...entry, path: this.unscopePath(entry.path) };
2220
- }
2221
- list(path, options) {
2222
- const inner = this.inner.list(this.scopePath(path), options);
2223
- const unscope = this.unscopePath.bind(this);
2224
- return async function* () {
2225
- for await (const entry of inner) {
2226
- yield { ...entry, path: unscope(entry.path) };
2227
- }
2228
- }();
2229
- }
2230
- async changeVisibility(path, visibility) {
2231
- return this.inner.changeVisibility(this.scopePath(path), visibility);
2232
- }
2233
- async visibility(path) {
2234
- return this.inner.visibility(this.scopePath(path));
2235
- }
2236
- async fileExists(path) {
2237
- return this.inner.fileExists(this.scopePath(path));
2238
- }
2239
- async directoryExists(path) {
2240
- return this.inner.directoryExists(this.scopePath(path));
2241
- }
2242
- async publicUrl(path, options) {
2243
- return this.inner.publicUrl(this.scopePath(path), options);
2244
- }
2245
- async temporaryUrl(path, options) {
2246
- return this.inner.temporaryUrl(this.scopePath(path), options);
2247
- }
2248
- async signedUrl(path, options) {
2249
- if (typeof this.inner.signedUrl !== "function")
2250
- throw new Error("[storage/scoped] wrapped adapter does not support signedUrl");
2251
- return this.inner.signedUrl(this.scopePath(path), options);
2252
- }
2253
- async presignedUploadUrl(options) {
2254
- if (typeof this.inner.presignedUploadUrl !== "function")
2255
- throw new Error("[storage/scoped] wrapped adapter does not support presignedUploadUrl");
2256
- const scopedDir = options.dir ? `${this.scope}/${options.dir.replace(/^\/+/, "")}` : this.scope;
2257
- const result = await this.inner.presignedUploadUrl({ ...options, dir: scopedDir });
2258
- return { ...result, path: this.unscopePath(result.path), key: this.unscopePath(result.key) };
2259
- }
2260
- async presignedUploadPolicy(options) {
2261
- if (typeof this.inner.presignedUploadPolicy !== "function")
2262
- throw new Error("[storage/scoped] wrapped adapter does not support presignedUploadPolicy");
2263
- const scopedKey = typeof options.key === "string" ? this.scopePath(options.key) : { startsWith: this.scopePath(options.key.startsWith) };
2264
- const result = await this.inner.presignedUploadPolicy({ ...options, key: scopedKey });
2265
- return { ...result, key: this.unscopePath(result.key) };
2266
- }
2267
- async getStream(path, options) {
2268
- if (typeof this.inner.getStream !== "function")
2269
- throw new Error("[storage/scoped] wrapped adapter does not support getStream");
2270
- return this.inner.getStream(this.scopePath(path), options);
2271
- }
2272
- async putStream(path, stream, options) {
2273
- if (typeof this.inner.putStream !== "function")
2274
- throw new Error("[storage/scoped] wrapped adapter does not support putStream");
2275
- const result = await this.inner.putStream(this.scopePath(path), stream, options);
2276
- return { ...result, path: this.unscopePath(result.path) };
2277
- }
2278
- async checksum(path, options) {
2279
- return this.inner.checksum(this.scopePath(path), options);
2280
- }
2281
- async mimeType(path, options) {
2282
- return this.inner.mimeType(this.scopePath(path), options);
2283
- }
2284
- async lastModified(path) {
2285
- return this.inner.lastModified(this.scopePath(path));
2286
- }
2287
- async fileSize(path) {
2288
- return this.inner.fileSize(this.scopePath(path));
2289
- }
2290
- }
2291
- function scoped(inner, options) {
2292
- return new ScopedStorageAdapter(inner, options);
2293
- }
2294
- // src/drivers/aws.ts
2295
- var _adapterPromise = null;
2296
- async function loadConfig() {
2297
- try {
2298
- const { filesystems } = await import("@stacksjs/config");
2299
- const s3Config = filesystems.s3;
2300
- return createS3Storage(null, {
2301
- bucket: s3Config?.bucket || "stacks",
2302
- prefix: s3Config?.prefix || "stx",
2303
- region: s3Config?.region || "us-east-1"
2304
- });
2305
- } catch {
2306
- const { env } = await import("@stacksjs/env");
2307
- return createS3Storage(null, {
2308
- bucket: env.AWS_S3_BUCKET || "stacks",
2309
- prefix: env.AWS_S3_PREFIX || "stx",
2310
- region: env.AWS_REGION || "us-east-1"
2311
- });
2312
- }
2313
- }
2314
- async function getAdapter() {
2315
- if (!_adapterPromise) {
2316
- _adapterPromise = loadConfig();
2317
- }
2318
- return _adapterPromise;
2319
- }
2320
- async function getAwsStorage() {
2321
- return getAdapter();
2322
- }
2323
- var aws = {
2324
- async write(path, contents) {
2325
- const adapter = await getAdapter();
2326
- await adapter.write(path, contents);
2327
- },
2328
- async deleteFile(path) {
2329
- const adapter = await getAdapter();
2330
- await adapter.deleteFile(path);
2331
- },
2332
- async createDirectory(path) {
2333
- const adapter = await getAdapter();
2334
- await adapter.createDirectory(path);
2335
- },
2336
- async moveFile(from, to) {
2337
- const adapter = await getAdapter();
2338
- await adapter.moveFile(from, to);
2339
- },
2340
- async copyFile(from, to) {
2341
- const adapter = await getAdapter();
2342
- await adapter.copyFile(from, to);
2343
- },
2344
- async stat(path) {
2345
- const adapter = await getAdapter();
2346
- return await adapter.stat(path);
2347
- },
2348
- list(path, options = { deep: false }) {
2349
- return async function* () {
2350
- const adapter = await getAdapter();
2351
- yield* adapter.list(path, options);
2352
- }();
2353
- },
2354
- async changeVisibility(path, visibility) {
2355
- const adapter = await getAdapter();
2356
- await adapter.changeVisibility(path, visibility);
2357
- },
2358
- async visibility(path) {
2359
- const adapter = await getAdapter();
2360
- return await adapter.visibility(path);
2361
- },
2362
- async fileExists(path) {
2363
- const adapter = await getAdapter();
2364
- return await adapter.fileExists(path);
2365
- },
2366
- async directoryExists(path) {
2367
- const adapter = await getAdapter();
2368
- return await adapter.directoryExists(path);
2369
- },
2370
- async publicUrl(path, options) {
2371
- const adapter = await getAdapter();
2372
- return await adapter.publicUrl(path, options);
2373
- },
2374
- async temporaryUrl(path, options) {
2375
- const adapter = await getAdapter();
2376
- return await adapter.temporaryUrl(path, options);
2377
- },
2378
- async checksum(path, options) {
2379
- const adapter = await getAdapter();
2380
- return await adapter.checksum(path, options);
2381
- },
2382
- async mimeType(path, options) {
2383
- const adapter = await getAdapter();
2384
- return await adapter.mimeType(path, options);
2385
- },
2386
- async lastModified(path) {
2387
- const adapter = await getAdapter();
2388
- return await adapter.lastModified(path);
2389
- },
2390
- async fileSize(path) {
2391
- const adapter = await getAdapter();
2392
- return await adapter.fileSize(path);
2393
- },
2394
- async read(path) {
2395
- const adapter = await getAdapter();
2396
- return await adapter.read(path);
2397
- },
2398
- async readToString(path) {
2399
- const adapter = await getAdapter();
2400
- return await adapter.readToString(path);
2401
- },
2402
- async readToBuffer(path) {
2403
- const adapter = await getAdapter();
2404
- return await adapter.readToBuffer(path);
2405
- },
2406
- async readToUint8Array(path) {
2407
- const adapter = await getAdapter();
2408
- return await adapter.readToUint8Array(path);
2409
- }
2410
- };
2411
-
2412
- // src/drivers/local.ts
2413
- import { resolve } from "path";
2414
- import process4 from "process";
2415
- var _adapterPromise2 = null;
2416
- async function loadConfig2() {
2417
- try {
2418
- const { filesystems } = await import("@stacksjs/config");
2419
- const rootDirectory = resolve(filesystems.root || process4.cwd());
2420
- return createLocalStorage({ root: rootDirectory });
2421
- } catch {
2422
- const rootDirectory = resolve(process4.cwd());
2423
- return createLocalStorage({ root: rootDirectory });
2424
- }
2425
- }
2426
- async function getAdapter2() {
2427
- if (!_adapterPromise2) {
2428
- _adapterPromise2 = loadConfig2();
2429
- }
2430
- return _adapterPromise2;
2431
- }
2432
- async function getLocalStorage() {
2433
- return getAdapter2();
2434
- }
2435
- var local = {
2436
- async write(path, contents) {
2437
- const adapter = await getAdapter2();
2438
- await adapter.write(path, contents);
2439
- },
2440
- async deleteFile(path) {
2441
- const adapter = await getAdapter2();
2442
- await adapter.deleteFile(path);
2443
- },
2444
- async createDirectory(path) {
2445
- const adapter = await getAdapter2();
2446
- await adapter.createDirectory(path);
2447
- },
2448
- async moveFile(from, to) {
2449
- const adapter = await getAdapter2();
2450
- await adapter.moveFile(from, to);
2451
- },
2452
- async copyFile(from, to) {
2453
- const adapter = await getAdapter2();
2454
- await adapter.copyFile(from, to);
2455
- },
2456
- async stat(path) {
2457
- const adapter = await getAdapter2();
2458
- return await adapter.stat(path);
2459
- },
2460
- list(path, options = { deep: false }) {
2461
- return async function* () {
2462
- const adapter = await getAdapter2();
2463
- yield* adapter.list(path, options);
2464
- }();
2465
- },
2466
- async changeVisibility(path, visibility) {
2467
- const adapter = await getAdapter2();
2468
- await adapter.changeVisibility(path, visibility);
2469
- },
2470
- async visibility(path) {
2471
- const adapter = await getAdapter2();
2472
- return await adapter.visibility(path);
2473
- },
2474
- async fileExists(path) {
2475
- const adapter = await getAdapter2();
2476
- return await adapter.fileExists(path);
2477
- },
2478
- async directoryExists(path) {
2479
- const adapter = await getAdapter2();
2480
- return await adapter.directoryExists(path);
2481
- },
2482
- async publicUrl(path, options) {
2483
- const adapter = await getAdapter2();
2484
- return await adapter.publicUrl(path, options);
2485
- },
2486
- async temporaryUrl(path, options) {
2487
- const adapter = await getAdapter2();
2488
- return await adapter.temporaryUrl(path, options);
2489
- },
2490
- async checksum(path, options) {
2491
- const adapter = await getAdapter2();
2492
- return await adapter.checksum(path, options);
2493
- },
2494
- async mimeType(path, options) {
2495
- const adapter = await getAdapter2();
2496
- return await adapter.mimeType(path, options);
2497
- },
2498
- async lastModified(path) {
2499
- const adapter = await getAdapter2();
2500
- return await adapter.lastModified(path);
2501
- },
2502
- async fileSize(path) {
2503
- const adapter = await getAdapter2();
2504
- return await adapter.fileSize(path);
2505
- },
2506
- async read(path) {
2507
- const adapter = await getAdapter2();
2508
- return await adapter.read(path);
2509
- },
2510
- async readToString(path) {
2511
- const adapter = await getAdapter2();
2512
- return await adapter.readToString(path);
2513
- },
2514
- async readToBuffer(path) {
2515
- const adapter = await getAdapter2();
2516
- return await adapter.readToBuffer(path);
2517
- },
2518
- async readToUint8Array(path) {
2519
- const adapter = await getAdapter2();
2520
- return await adapter.readToUint8Array(path);
2521
- }
2522
- };
2523
-
2524
- // src/drivers/memory.ts
2525
- var adapter = createMemoryStorage();
2526
- var memoryStorage = adapter;
2527
- var memory = {
2528
- async write(path, contents) {
2529
- await adapter.write(path, contents);
2530
- },
2531
- async deleteFile(path) {
2532
- await adapter.deleteFile(path);
2533
- },
2534
- async createDirectory(path) {
2535
- await adapter.createDirectory(path);
2536
- },
2537
- async moveFile(from, to) {
2538
- await adapter.moveFile(from, to);
2539
- },
2540
- async copyFile(from, to) {
2541
- await adapter.copyFile(from, to);
2542
- },
2543
- async stat(path) {
2544
- return await adapter.stat(path);
2545
- },
2546
- list(path, options = { deep: false }) {
2547
- return adapter.list(path, options);
2548
- },
2549
- async changeVisibility(path, visibility) {
2550
- await adapter.changeVisibility(path, visibility);
2551
- },
2552
- async visibility(path) {
2553
- return await adapter.visibility(path);
2554
- },
2555
- async fileExists(path) {
2556
- return await adapter.fileExists(path);
2557
- },
2558
- async directoryExists(path) {
2559
- return await adapter.directoryExists(path);
2560
- },
2561
- async publicUrl(path, options) {
2562
- return await adapter.publicUrl(path, options);
2563
- },
2564
- async temporaryUrl(path, options) {
2565
- return await adapter.temporaryUrl(path, options);
2566
- },
2567
- async checksum(path, options) {
2568
- return await adapter.checksum(path, options);
2569
- },
2570
- async mimeType(path, options) {
2571
- return await adapter.mimeType(path, options);
2572
- },
2573
- async lastModified(path) {
2574
- return await adapter.lastModified(path);
2575
- },
2576
- async fileSize(path) {
2577
- return await adapter.fileSize(path);
2578
- },
2579
- async read(path) {
2580
- return await adapter.read(path);
2581
- },
2582
- async readToString(path) {
2583
- return await adapter.readToString(path);
2584
- },
2585
- async readToBuffer(path) {
2586
- return await adapter.readToBuffer(path);
2587
- },
2588
- async readToUint8Array(path) {
2589
- return await adapter.readToUint8Array(path);
2590
- }
2591
- };
2592
-
2593
- // src/drivers/bun.ts
2594
- import { resolve as resolve2 } from "path";
2595
- import process5 from "process";
2596
- var _adapterPromise3 = null;
2597
- async function loadConfig3() {
2598
- try {
2599
- const { filesystems } = await import("@stacksjs/config");
2600
- const rootDirectory = resolve2(filesystems.root || process5.cwd());
2601
- return createBunStorage({ root: rootDirectory });
2602
- } catch {
2603
- const rootDirectory = resolve2(process5.cwd());
2604
- return createBunStorage({ root: rootDirectory });
2605
- }
2606
- }
2607
- async function getAdapter3() {
2608
- if (!_adapterPromise3) {
2609
- _adapterPromise3 = loadConfig3();
2610
- }
2611
- return _adapterPromise3;
2612
- }
2613
- async function getBunStorage() {
2614
- return getAdapter3();
2615
- }
2616
- var bun = {
2617
- async write(path, contents) {
2618
- const adapter2 = await getAdapter3();
2619
- await adapter2.write(path, contents);
2620
- },
2621
- async deleteFile(path) {
2622
- const adapter2 = await getAdapter3();
2623
- await adapter2.deleteFile(path);
2624
- },
2625
- async createDirectory(path) {
2626
- const adapter2 = await getAdapter3();
2627
- await adapter2.createDirectory(path);
2628
- },
2629
- async moveFile(from, to) {
2630
- const adapter2 = await getAdapter3();
2631
- await adapter2.moveFile(from, to);
2632
- },
2633
- async copyFile(from, to) {
2634
- const adapter2 = await getAdapter3();
2635
- await adapter2.copyFile(from, to);
2636
- },
2637
- async stat(path) {
2638
- const adapter2 = await getAdapter3();
2639
- return await adapter2.stat(path);
2640
- },
2641
- list(path, options = { deep: false }) {
2642
- return async function* () {
2643
- const adapter2 = await getAdapter3();
2644
- yield* adapter2.list(path, options);
2645
- }();
2646
- },
2647
- async changeVisibility(path, visibility) {
2648
- const adapter2 = await getAdapter3();
2649
- await adapter2.changeVisibility(path, visibility);
2650
- },
2651
- async visibility(path) {
2652
- const adapter2 = await getAdapter3();
2653
- return await adapter2.visibility(path);
2654
- },
2655
- async fileExists(path) {
2656
- const adapter2 = await getAdapter3();
2657
- return await adapter2.fileExists(path);
2658
- },
2659
- async directoryExists(path) {
2660
- const adapter2 = await getAdapter3();
2661
- return await adapter2.directoryExists(path);
2662
- },
2663
- async publicUrl(path, options) {
2664
- const adapter2 = await getAdapter3();
2665
- return await adapter2.publicUrl(path, options);
2666
- },
2667
- async temporaryUrl(path, options) {
2668
- const adapter2 = await getAdapter3();
2669
- return await adapter2.temporaryUrl(path, options);
2670
- },
2671
- async checksum(path, options) {
2672
- const adapter2 = await getAdapter3();
2673
- return await adapter2.checksum(path, options);
2674
- },
2675
- async mimeType(path, options) {
2676
- const adapter2 = await getAdapter3();
2677
- return await adapter2.mimeType(path, options);
2678
- },
2679
- async lastModified(path) {
2680
- const adapter2 = await getAdapter3();
2681
- return await adapter2.lastModified(path);
2682
- },
2683
- async fileSize(path) {
2684
- const adapter2 = await getAdapter3();
2685
- return await adapter2.fileSize(path);
2686
- },
2687
- async read(path) {
2688
- const adapter2 = await getAdapter3();
2689
- return await adapter2.read(path);
2690
- },
2691
- async readToString(path) {
2692
- const adapter2 = await getAdapter3();
2693
- return await adapter2.readToString(path);
2694
- },
2695
- async readToBuffer(path) {
2696
- const adapter2 = await getAdapter3();
2697
- return await adapter2.readToBuffer(path);
2698
- },
2699
- async readToUint8Array(path) {
2700
- const adapter2 = await getAdapter3();
2701
- return await adapter2.readToUint8Array(path);
2702
- }
2703
- };
2704
- export {
2705
- zip,
2706
- writeTextFile,
2707
- writeJsonFile,
2708
- writeFileSync,
2709
- writeFile,
2710
- watchFile,
2711
- verifyUploadedMime,
2712
- verifySignedStorageToken,
2713
- uploadedFiles,
2714
- uploadedFile,
2715
- updateConfigFile,
2716
- unzip,
2717
- unarchive,
2718
- storage,
2719
- signS3PresignedPost2 as signS3PresignedPost,
2720
- serveFile,
2721
- scoped,
2722
- sanitizePresignedFilename2 as sanitizePresignedFilename,
2723
- sanitizePresignedDir2 as sanitizePresignedDir,
2724
- s3Disk,
2725
- revokeSignedStorageToken,
2726
- readTextFile,
2727
- readPackageJson,
2728
- readJsonFile,
2729
- readFileSync,
2730
- put,
2731
- parseDiskPath,
2732
- normalizeExpiryToMilliseconds,
2733
- normalizeExpiryToDate,
2734
- mkdirSync,
2735
- memoryStorage,
2736
- memory,
2737
- localDisk,
2738
- local,
2739
- isSignedStorageTokenRevoked,
2740
- isFolder,
2741
- isFile2 as isFile,
2742
- isDirectoryEmpty,
2743
- isDirectory,
2744
- isDir,
2745
- inflateSync,
2746
- helpers,
2747
- hashPaths,
2748
- hashPath,
2749
- hashFileOrDirectory,
2750
- hashDirectory,
2751
- hasFunctions,
2752
- hasFiles,
2753
- hasComponents,
2754
- gzipSync,
2755
- gunzipSync,
2756
- globSync,
2757
- glob,
2758
- getLocalStorage,
2759
- getFolders,
2760
- getFiles,
2761
- getBunStorage,
2762
- getAwsStorage,
2763
- get,
2764
- fsWatch,
2765
- fs,
2766
- folders,
2767
- files,
2768
- existsSync,
2769
- exists,
2770
- doesNotExist,
2771
- doesFolderExist,
2772
- doesExist,
2773
- detectMimeFromMagicBytes,
2774
- deleteGlob,
2775
- deleteFolder,
2776
- deleteFiles,
2777
- deleteFile,
2778
- deleteEmptyFolders,
2779
- deleteEmptyFolder,
2780
- del,
2781
- deflateSync,
2782
- decompress,
2783
- createSignedStorageToken2 as createSignedStorageToken,
2784
- createS3Storage,
2785
- createMemoryStorage,
2786
- createLocalStorage,
2787
- createFolder,
2788
- createDirectoryListing,
2789
- createBunStorage,
2790
- copyFolder,
2791
- copyFile,
2792
- copy,
2793
- configFromEnv,
2794
- compress,
2795
- clearRevokedSignedStorageTokens,
2796
- bun,
2797
- aws,
2798
- archive,
2799
- _dirname,
2800
- Visibility,
2801
- UploadedFile,
2802
- StorageManager,
2803
- Storage,
2804
- ScopedStorageAdapter,
2805
- S3StorageAdapter,
2806
- PathSanitizeError2 as PathSanitizeError,
2807
- LocalStorageAdapter,
2808
- InMemoryStorageAdapter,
2809
- BunStorageAdapter
2810
- };