@stacksjs/storage 0.70.88 → 0.70.90

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.
Files changed (71) hide show
  1. package/dist/adapters/bun.d.ts +31 -0
  2. package/dist/adapters/bun.js +226 -0
  3. package/dist/adapters/index.d.ts +6 -0
  4. package/dist/adapters/index.js +5 -0
  5. package/dist/adapters/local.d.ts +38 -0
  6. package/dist/adapters/local.js +225 -0
  7. package/dist/adapters/memory.d.ts +38 -0
  8. package/dist/adapters/memory.js +318 -0
  9. package/dist/adapters/s3.d.ts +53 -0
  10. package/dist/adapters/s3.js +471 -0
  11. package/dist/adapters/scoped.d.ts +68 -0
  12. package/dist/adapters/scoped.js +142 -0
  13. package/dist/copy.d.ts +3 -0
  14. package/dist/copy.js +30 -0
  15. package/dist/delete.d.ts +8 -0
  16. package/dist/delete.js +103 -0
  17. package/dist/drivers/aws.d.ts +4 -0
  18. package/dist/drivers/aws.js +94 -0
  19. package/dist/drivers/bun.d.ts +4 -0
  20. package/dist/drivers/bun.js +88 -0
  21. package/dist/drivers/index.d.ts +4 -0
  22. package/dist/drivers/index.js +4 -0
  23. package/dist/drivers/local.d.ts +4 -0
  24. package/dist/drivers/local.js +88 -0
  25. package/dist/drivers/memory.d.ts +4 -0
  26. package/dist/drivers/memory.js +67 -0
  27. package/dist/facade.d.ts +53 -0
  28. package/dist/facade.js +226 -0
  29. package/dist/files.d.ts +52 -0
  30. package/dist/files.js +126 -0
  31. package/dist/folders.d.ts +18 -0
  32. package/dist/folders.js +36 -0
  33. package/dist/fs.d.ts +4 -0
  34. package/dist/fs.js +7 -0
  35. package/dist/glob.d.ts +13 -0
  36. package/dist/glob.js +40 -0
  37. package/dist/hash.d.ts +5 -0
  38. package/dist/hash.js +33 -0
  39. package/dist/helpers.d.ts +7 -0
  40. package/dist/helpers.js +28 -0
  41. package/dist/image.d.ts +55 -0
  42. package/dist/image.js +29 -0
  43. package/dist/index.d.ts +60 -0
  44. package/dist/index.js +27 -0
  45. package/dist/mime-verify.d.ts +65 -0
  46. package/dist/mime-verify.js +47 -0
  47. package/dist/move.d.ts +6 -0
  48. package/dist/move.js +55 -0
  49. package/dist/path-sanitize.d.ts +92 -0
  50. package/dist/path-sanitize.js +84 -0
  51. package/dist/put-file.d.ts +53 -0
  52. package/dist/put-file.js +85 -0
  53. package/dist/s3-presigned-post.d.ts +52 -0
  54. package/dist/s3-presigned-post.js +68 -0
  55. package/dist/signed-url.d.ts +69 -0
  56. package/dist/signed-url.js +86 -0
  57. package/dist/static-serve.d.ts +37 -0
  58. package/dist/static-serve.js +110 -0
  59. package/dist/storage.d.ts +9 -0
  60. package/dist/storage.js +9 -0
  61. package/dist/types/filesystem.d.ts +131 -0
  62. package/dist/types/filesystem.js +40 -0
  63. package/dist/types.d.ts +229 -0
  64. package/dist/types.js +25 -0
  65. package/dist/uploaded-file.d.ts +38 -0
  66. package/dist/uploaded-file.js +114 -0
  67. package/dist/visibility.d.ts +3 -0
  68. package/dist/visibility.js +3 -0
  69. package/dist/zip.d.ts +16 -0
  70. package/dist/zip.js +41 -0
  71. package/package.json +6 -6
@@ -0,0 +1,31 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import type { ChecksumOptions, DirectoryListing, FileContents, GetStreamOptions, ListOptions, MimeTypeOptions, PublicUrlOptions, PutResult, PutStreamOptions, SignedUrlOptions, StatEntry, StorageAdapter, StorageAdapterConfig, TemporaryUrlOptions, Visibility } from '../types';
3
+ export declare function createBunStorage(config?: StorageAdapterConfig): BunStorageAdapter;
4
+ export declare class BunStorageAdapter implements StorageAdapter {
5
+ constructor(config?: StorageAdapterConfig);
6
+ write(path: string, contents: FileContents): Promise<PutResult>;
7
+ read(path: string): Promise<FileContents>;
8
+ getStream(path: string, _options?: GetStreamOptions): Promise<ReadableStream<Uint8Array>>;
9
+ putStream(path: string, stream: ReadableStream<Uint8Array>, options?: PutStreamOptions): Promise<PutResult>;
10
+ readToString(path: string): Promise<string>;
11
+ readToBuffer(path: string): Promise<Buffer>;
12
+ readToUint8Array(path: string): Promise<Uint8Array>;
13
+ deleteFile(path: string): Promise<void>;
14
+ deleteDirectory(path: string): Promise<void>;
15
+ createDirectory(path: string): Promise<void>;
16
+ moveFile(from: string, to: string): Promise<void>;
17
+ copyFile(from: string, to: string): Promise<void>;
18
+ stat(path: string): Promise<StatEntry>;
19
+ list(path: string, options?: ListOptions): DirectoryListing;
20
+ changeVisibility(path: string, vis: Visibility): Promise<void>;
21
+ visibility(path: string): Promise<Visibility>;
22
+ fileExists(path: string): Promise<boolean>;
23
+ directoryExists(path: string): Promise<boolean>;
24
+ publicUrl(path: string, options?: PublicUrlOptions): Promise<string>;
25
+ temporaryUrl(path: string, options: TemporaryUrlOptions): Promise<string>;
26
+ signedUrl(path: string, options: SignedUrlOptions): Promise<string>;
27
+ checksum(path: string, options?: ChecksumOptions): Promise<string>;
28
+ mimeType(path: string, _options?: MimeTypeOptions): Promise<string>;
29
+ lastModified(path: string): Promise<number>;
30
+ fileSize(path: string): Promise<number>;
31
+ }
@@ -0,0 +1,226 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { file, write as bunWrite } from "bun";
3
+ import { chmod, lstat } from "node:fs/promises";
4
+ import { basename, dirname, join, relative } from "node:path";
5
+ import { createDirectoryListing, normalizeExpiryToDate } from "../types";
6
+ import { createSignedStorageToken } from "../signed-url";
7
+
8
+ export class BunStorageAdapter {
9
+ root;
10
+ constructor(config = {}) {
11
+ this.root = config.root || process.cwd();
12
+ }
13
+ resolvePath(path) {
14
+ const resolved = join(this.root, path), rel = relative(this.root, resolved);
15
+ if (rel.startsWith("..") || rel.startsWith("../") || rel.startsWith("..\\"))
16
+ throw Error(`Path traversal detected: '${path}' resolves outside storage root`);
17
+ return resolved;
18
+ }
19
+ async write(path, contents) {
20
+ const fullPath = this.resolvePath(path), dir = dirname(fullPath);
21
+ await this.createDirectory(relative(this.root, dir));
22
+ if (typeof contents === "string")
23
+ await bunWrite(fullPath, contents);
24
+ else if (contents instanceof Buffer)
25
+ await bunWrite(fullPath, contents);
26
+ else if (contents instanceof Uint8Array)
27
+ await bunWrite(fullPath, contents);
28
+ else {
29
+ if (typeof contents.getReader !== "function")
30
+ throw TypeError("[storage/bun] contents must be a web-standard ReadableStream (with .getReader()), not a Node stream.Readable. Convert via Readable.toWeb(nodeStream) before passing.");
31
+ const reader = contents.getReader(), chunks = [];
32
+ while (!0) {
33
+ const { done, value } = await reader.read();
34
+ if (done)
35
+ break;
36
+ if (value)
37
+ chunks.push(value);
38
+ }
39
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0), result = new Uint8Array(totalLength);
40
+ let offset = 0;
41
+ for (const chunk of chunks) {
42
+ result.set(chunk, offset);
43
+ offset += chunk.length;
44
+ }
45
+ await bunWrite(fullPath, result);
46
+ }
47
+ const written = file(fullPath);
48
+ return {
49
+ path,
50
+ size: written.size,
51
+ lastModified: written.lastModified
52
+ };
53
+ }
54
+ async read(path) {
55
+ const fullPath = this.resolvePath(path), bunFile = file(fullPath);
56
+ if (!await bunFile.exists())
57
+ throw Error(`File not found: ${path}`);
58
+ return await bunFile.arrayBuffer().then((buf) => new Uint8Array(buf));
59
+ }
60
+ async getStream(path, _options) {
61
+ const fullPath = this.resolvePath(path), bunFile = file(fullPath);
62
+ if (!await bunFile.exists())
63
+ throw Error(`File not found: ${path}`);
64
+ return bunFile.stream();
65
+ }
66
+ async putStream(path, stream, options) {
67
+ const fullPath = this.resolvePath(path), dir = dirname(fullPath);
68
+ await this.createDirectory(relative(this.root, dir));
69
+ try {
70
+ const body = new Response(stream), writePromise = bunWrite(fullPath, body);
71
+ if (options?.signal) {
72
+ const abortHandler = () => {};
73
+ options.signal.addEventListener("abort", abortHandler, { once: !0 });
74
+ try {
75
+ await writePromise;
76
+ } finally {
77
+ options.signal.removeEventListener("abort", abortHandler);
78
+ }
79
+ } else
80
+ await writePromise;
81
+ } catch (err) {
82
+ try {
83
+ await Bun.$.throws(!1)`rm -f ${fullPath}`;
84
+ } catch {}
85
+ throw err;
86
+ }
87
+ const written = file(fullPath);
88
+ return {
89
+ path,
90
+ size: written.size,
91
+ contentType: options?.contentType,
92
+ lastModified: written.lastModified
93
+ };
94
+ }
95
+ async readToString(path) {
96
+ const fullPath = this.resolvePath(path), bunFile = file(fullPath);
97
+ if (!await bunFile.exists())
98
+ throw Error(`File not found: ${path}`);
99
+ return await bunFile.text();
100
+ }
101
+ async readToBuffer(path) {
102
+ const fullPath = this.resolvePath(path), bunFile = file(fullPath);
103
+ if (!await bunFile.exists())
104
+ throw Error(`File not found: ${path}`);
105
+ const arrayBuffer = await bunFile.arrayBuffer();
106
+ return Buffer.from(arrayBuffer);
107
+ }
108
+ async readToUint8Array(path) {
109
+ const fullPath = this.resolvePath(path), bunFile = file(fullPath);
110
+ if (!await bunFile.exists())
111
+ throw Error(`File not found: ${path}`);
112
+ const arrayBuffer = await bunFile.arrayBuffer();
113
+ return new Uint8Array(arrayBuffer);
114
+ }
115
+ async deleteFile(path) {
116
+ const fullPath = this.resolvePath(path);
117
+ if (await file(fullPath).exists())
118
+ await Bun.$.throws(!1)`rm ${fullPath}`;
119
+ }
120
+ async deleteDirectory(path) {
121
+ const fullPath = this.resolvePath(path);
122
+ await Bun.$.throws(!1)`rm -rf ${fullPath}`;
123
+ }
124
+ async createDirectory(path) {
125
+ const fullPath = this.resolvePath(path);
126
+ await Bun.$.throws(!1)`mkdir -p ${fullPath}`;
127
+ }
128
+ async moveFile(from, to) {
129
+ const fromPath = this.resolvePath(from), toPath = this.resolvePath(to), toDir = dirname(toPath);
130
+ await this.createDirectory(relative(this.root, toDir));
131
+ await Bun.$.throws(!0)`mv ${fromPath} ${toPath}`;
132
+ }
133
+ async copyFile(from, to) {
134
+ const fromPath = this.resolvePath(from), toPath = this.resolvePath(to), toDir = dirname(toPath);
135
+ await this.createDirectory(relative(this.root, toDir));
136
+ await Bun.$.throws(!0)`cp ${fromPath} ${toPath}`;
137
+ }
138
+ async stat(path) {
139
+ const fullPath = this.resolvePath(path), bunFile = file(fullPath);
140
+ if (!await bunFile.exists())
141
+ throw Error(`File not found: ${path}`);
142
+ const stats = await Bun.file(fullPath).stat(), isDir = stats.isDirectory();
143
+ return {
144
+ path,
145
+ type: isDir ? "directory" : "file",
146
+ visibility: "private",
147
+ size: isDir ? 0 : stats.size,
148
+ lastModified: stats.mtime?.getTime() || Date.now(),
149
+ mimeType: isDir ? void 0 : bunFile.type
150
+ };
151
+ }
152
+ list(path, options = {}) {
153
+ const fullPath = this.resolvePath(path);
154
+ return this.createAsyncIterator(fullPath, options.deep || !1);
155
+ }
156
+ async* createAsyncIterator(dirPath, deep) {
157
+ const entries = [];
158
+ try {
159
+ const glob = new Bun.Glob(deep ? "**/*" : "*");
160
+ for await (const entry of glob.scan({ cwd: dirPath, onlyFiles: !1 })) {
161
+ const fullEntryPath = join(dirPath, entry), stats = await Bun.file(fullEntryPath).stat();
162
+ entries.push({
163
+ path: relative(this.root, fullEntryPath),
164
+ type: stats.isDirectory() ? "directory" : "file"
165
+ });
166
+ }
167
+ } catch (error) {
168
+ return;
169
+ }
170
+ yield* createDirectoryListing(entries);
171
+ }
172
+ async changeVisibility(path, vis) {
173
+ const fullPath = this.resolvePath(path), isDir = (await lstat(fullPath)).isDirectory();
174
+ await chmod(fullPath, vis === "public" ? isDir ? 493 : 420 : isDir ? 448 : 384);
175
+ }
176
+ async visibility(path) {
177
+ const fullPath = this.resolvePath(path);
178
+ return (await lstat(fullPath)).mode & 511 & 4 ? "public" : "private";
179
+ }
180
+ async fileExists(path) {
181
+ const fullPath = this.resolvePath(path);
182
+ return await file(fullPath).exists();
183
+ }
184
+ async directoryExists(path) {
185
+ const fullPath = this.resolvePath(path);
186
+ try {
187
+ return (await Bun.file(fullPath).stat()).isDirectory();
188
+ } catch {
189
+ return !1;
190
+ }
191
+ }
192
+ async publicUrl(path, options = {}) {
193
+ return `${options.domain || "http://localhost"}/${path}`;
194
+ }
195
+ async temporaryUrl(path, options) {
196
+ const expiry = normalizeExpiryToDate(options.expiresIn);
197
+ return `http://localhost/temp/${Buffer.from(`${path}:${expiry.getTime()}`).toString("base64url")}`;
198
+ }
199
+ async signedUrl(path, options) {
200
+ const token = createSignedStorageToken(path, options);
201
+ return `${(options.baseUrl || process.env.APP_URL || "http://localhost").replace(/\/$/, "")}/__storage/${encodeURIComponent(path)}?token=${token}`;
202
+ }
203
+ async checksum(path, options = {}) {
204
+ const algorithm = options.algorithm || "sha256", fullPath = this.resolvePath(path), bunFile = file(fullPath);
205
+ if (!await bunFile.exists())
206
+ throw Error(`File not found: ${path}`);
207
+ const hasher = new Bun.CryptoHasher(algorithm), arrayBuffer = await bunFile.arrayBuffer();
208
+ hasher.update(new Uint8Array(arrayBuffer));
209
+ return hasher.digest("hex");
210
+ }
211
+ async mimeType(path, _options = {}) {
212
+ const fullPath = this.resolvePath(path), bunFile = file(fullPath);
213
+ if (!await bunFile.exists())
214
+ throw Error(`File not found: ${path}`);
215
+ return bunFile.type || "application/octet-stream";
216
+ }
217
+ async lastModified(path) {
218
+ return (await this.stat(path)).lastModified;
219
+ }
220
+ async fileSize(path) {
221
+ return (await this.stat(path)).size;
222
+ }
223
+ }
224
+ export function createBunStorage(config = {}) {
225
+ return new BunStorageAdapter(config);
226
+ }
@@ -0,0 +1,6 @@
1
+ export * from './local';
2
+ export * from './memory';
3
+ export * from './s3';
4
+ export * from './bun';
5
+ // Per-tenant prefix scoping wrapper (stacksjs/stacks#1887 S-11)
6
+ export * from './scoped';
@@ -0,0 +1,5 @@
1
+ export * from "./local";
2
+ export * from "./memory";
3
+ export * from "./s3";
4
+ export * from "./bun";
5
+ export * from "./scoped";
@@ -0,0 +1,38 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import { copyFile, stat } from 'node:fs/promises';
3
+ import type { ChecksumOptions, DirectoryListing, FileContents, GetStreamOptions, ListOptions, MimeTypeOptions, PublicUrlOptions, PutResult, PutStreamOptions, SignedUrlOptions, StatEntry, StorageAdapter, StorageAdapterConfig, TemporaryUrlOptions, Visibility } from '../types';
4
+ /**
5
+ * Create a local storage adapter instance
6
+ */
7
+ export declare function createLocalStorage(config?: StorageAdapterConfig): LocalStorageAdapter;
8
+ /**
9
+ * Local filesystem storage adapter using Node.js fs APIs
10
+ */
11
+ export declare class LocalStorageAdapter implements StorageAdapter {
12
+ constructor(config?: StorageAdapterConfig);
13
+ write(path: string, contents: FileContents): Promise<PutResult>;
14
+ read(path: string): Promise<FileContents>;
15
+ getStream(path: string, options?: GetStreamOptions): Promise<ReadableStream<Uint8Array>>;
16
+ putStream(path: string, stream: ReadableStream<Uint8Array>, options?: PutStreamOptions): Promise<PutResult>;
17
+ readToString(path: string): Promise<string>;
18
+ readToBuffer(path: string): Promise<Buffer>;
19
+ readToUint8Array(path: string): Promise<Uint8Array>;
20
+ deleteFile(path: string): Promise<void>;
21
+ deleteDirectory(path: string): Promise<void>;
22
+ createDirectory(path: string): Promise<void>;
23
+ moveFile(from: string, to: string): Promise<void>;
24
+ copyFile(from: string, to: string): Promise<void>;
25
+ stat(path: string): Promise<StatEntry>;
26
+ list(path: string, options?: ListOptions): DirectoryListing;
27
+ changeVisibility(path: string, vis: Visibility): Promise<void>;
28
+ visibility(path: string): Promise<Visibility>;
29
+ fileExists(path: string): Promise<boolean>;
30
+ directoryExists(path: string): Promise<boolean>;
31
+ publicUrl(path: string, options?: PublicUrlOptions): Promise<string>;
32
+ temporaryUrl(path: string, options: TemporaryUrlOptions): Promise<string>;
33
+ signedUrl(path: string, options: SignedUrlOptions): Promise<string>;
34
+ checksum(path: string, options?: ChecksumOptions): Promise<string>;
35
+ mimeType(path: string, options?: MimeTypeOptions): Promise<string>;
36
+ lastModified(path: string): Promise<number>;
37
+ fileSize(path: string): Promise<number>;
38
+ }
@@ -0,0 +1,225 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { createHmac } from "node:crypto";
3
+ import { createReadStream, createWriteStream } from "node:fs";
4
+ import { access, chmod, constants, copyFile, lstat, mkdir, readdir, readFile, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
5
+ import { basename, dirname, join, relative } from "node:path";
6
+ import { Readable } from "node:stream";
7
+ import { pipeline } from "node:stream/promises";
8
+ import { createDirectoryListing, normalizeExpiryToDate } from "../types";
9
+ import { createSignedStorageToken } from "../signed-url";
10
+
11
+ export class LocalStorageAdapter {
12
+ root;
13
+ constructor(config = {}) {
14
+ this.root = config.root || process.cwd();
15
+ }
16
+ resolvePath(path) {
17
+ const resolved = join(this.root, path), rel = relative(this.root, resolved);
18
+ if (rel.startsWith("..") || rel.startsWith("../") || rel.startsWith("..\\"))
19
+ throw Error(`Path traversal detected: '${path}' resolves outside storage root`);
20
+ return resolved;
21
+ }
22
+ async write(path, contents) {
23
+ const fullPath = this.resolvePath(path), dir = dirname(fullPath);
24
+ await mkdir(dir, { recursive: !0 });
25
+ if (typeof contents === "string")
26
+ await writeFile(fullPath, contents, "utf8");
27
+ else if (contents instanceof Buffer)
28
+ await writeFile(fullPath, contents);
29
+ else if (contents instanceof Uint8Array)
30
+ await writeFile(fullPath, contents);
31
+ else {
32
+ const writeStream = createWriteStream(fullPath);
33
+ await pipeline(contents, writeStream);
34
+ }
35
+ const st = await stat(fullPath);
36
+ return {
37
+ path,
38
+ size: st.size,
39
+ lastModified: st.mtimeMs
40
+ };
41
+ }
42
+ async read(path) {
43
+ const fullPath = this.resolvePath(path);
44
+ return await readFile(fullPath);
45
+ }
46
+ async getStream(path, options) {
47
+ const fullPath = this.resolvePath(path);
48
+ await access(fullPath, constants.R_OK);
49
+ const nodeStream = createReadStream(fullPath, { signal: options?.signal });
50
+ return Readable.toWeb(nodeStream);
51
+ }
52
+ async putStream(path, stream, options) {
53
+ const fullPath = this.resolvePath(path), dir = dirname(fullPath);
54
+ await mkdir(dir, { recursive: !0 });
55
+ const nodeReadable = Readable.fromWeb(stream), writeStream = createWriteStream(fullPath);
56
+ try {
57
+ await pipeline(nodeReadable, writeStream, { signal: options?.signal });
58
+ } catch (err) {
59
+ try {
60
+ await unlink(fullPath);
61
+ } catch {}
62
+ throw err;
63
+ }
64
+ const st = await stat(fullPath);
65
+ return {
66
+ path,
67
+ size: st.size,
68
+ contentType: options?.contentType,
69
+ lastModified: st.mtimeMs
70
+ };
71
+ }
72
+ async readToString(path) {
73
+ const fullPath = this.resolvePath(path);
74
+ return await readFile(fullPath, "utf8");
75
+ }
76
+ async readToBuffer(path) {
77
+ const fullPath = this.resolvePath(path);
78
+ return await readFile(fullPath);
79
+ }
80
+ async readToUint8Array(path) {
81
+ const fullPath = this.resolvePath(path), buffer = await readFile(fullPath);
82
+ return new Uint8Array(buffer);
83
+ }
84
+ async deleteFile(path) {
85
+ const fullPath = this.resolvePath(path);
86
+ await unlink(fullPath);
87
+ }
88
+ async deleteDirectory(path) {
89
+ const fullPath = this.resolvePath(path);
90
+ await rm(fullPath, { recursive: !0, force: !0 });
91
+ }
92
+ async createDirectory(path) {
93
+ const fullPath = this.resolvePath(path);
94
+ await mkdir(fullPath, { recursive: !0 });
95
+ }
96
+ async moveFile(from, to) {
97
+ const fromPath = this.resolvePath(from), toPath = this.resolvePath(to), toDir = dirname(toPath);
98
+ await mkdir(toDir, { recursive: !0 });
99
+ await rename(fromPath, toPath);
100
+ }
101
+ async copyFile(from, to) {
102
+ const fromPath = this.resolvePath(from), toPath = this.resolvePath(to), toDir = dirname(toPath);
103
+ await mkdir(toDir, { recursive: !0 });
104
+ await copyFile(fromPath, toPath);
105
+ }
106
+ async stat(path) {
107
+ const fullPath = this.resolvePath(path), stats = await lstat(fullPath);
108
+ return {
109
+ path,
110
+ type: stats.isDirectory() ? "directory" : "file",
111
+ visibility: await this.visibility(path),
112
+ size: stats.size,
113
+ lastModified: stats.mtimeMs,
114
+ mimeType: stats.isFile() ? await this.detectMimeType(fullPath) : void 0
115
+ };
116
+ }
117
+ list(path, options = {}) {
118
+ return this.createAsyncIterator(path, options.deep || !1);
119
+ }
120
+ async* createAsyncIterator(path, deep) {
121
+ const fullPath = this.resolvePath(path);
122
+ try {
123
+ await access(fullPath, constants.R_OK);
124
+ } catch {
125
+ return;
126
+ }
127
+ const entries = await this.readDirectoryRecursive(fullPath, deep);
128
+ yield* createDirectoryListing(entries);
129
+ }
130
+ async readDirectoryRecursive(dirPath, deep) {
131
+ const entries = [];
132
+ try {
133
+ const items = await readdir(dirPath, { withFileTypes: !0 });
134
+ for (const item of items) {
135
+ const itemPath = join(dirPath, item.name), relativePath = relative(this.root, itemPath);
136
+ entries.push({
137
+ path: relativePath,
138
+ type: item.isDirectory() ? "directory" : "file"
139
+ });
140
+ if (deep && item.isDirectory()) {
141
+ const subEntries = await this.readDirectoryRecursive(itemPath, !0);
142
+ entries.push(...subEntries);
143
+ }
144
+ }
145
+ } catch (error) {
146
+ if (error?.code !== "EACCES" && error?.code !== "EPERM")
147
+ throw error;
148
+ }
149
+ return entries;
150
+ }
151
+ async changeVisibility(path, vis) {
152
+ const fullPath = this.resolvePath(path), isDir = (await lstat(fullPath)).isDirectory();
153
+ await chmod(fullPath, vis === "public" ? isDir ? 493 : 420 : isDir ? 448 : 384);
154
+ }
155
+ async visibility(path) {
156
+ const fullPath = this.resolvePath(path);
157
+ return (await lstat(fullPath)).mode & 511 & 4 ? "public" : "private";
158
+ }
159
+ async fileExists(path) {
160
+ const fullPath = this.resolvePath(path);
161
+ try {
162
+ return (await lstat(fullPath)).isFile();
163
+ } catch {
164
+ return !1;
165
+ }
166
+ }
167
+ async directoryExists(path) {
168
+ const fullPath = this.resolvePath(path);
169
+ try {
170
+ return (await lstat(fullPath)).isDirectory();
171
+ } catch {
172
+ return !1;
173
+ }
174
+ }
175
+ async publicUrl(path, options = {}) {
176
+ return `${(options.domain || process.env.APP_URL || "http://localhost").replace(/\/$/, "")}/${path}`;
177
+ }
178
+ async temporaryUrl(path, options) {
179
+ const expiry = normalizeExpiryToDate(options.expiresIn), payload = `${path}:${expiry.getTime()}`, appKey = process.env.APP_KEY || "stacks-default-key", signature = createHmac("sha256", appKey).update(payload).digest("hex");
180
+ return `http://localhost/temp/${Buffer.from(`${payload}:${signature}`).toString("base64url")}`;
181
+ }
182
+ async signedUrl(path, options) {
183
+ const token = createSignedStorageToken(path, options);
184
+ return `${(options.baseUrl || process.env.APP_URL || "http://localhost").replace(/\/$/, "")}/__storage/${encodeURIComponent(path)}?token=${token}`;
185
+ }
186
+ async checksum(path, options = {}) {
187
+ const algorithm = options.algorithm || "sha256", fullPath = this.resolvePath(path), content = await readFile(fullPath), hasher = new Bun.CryptoHasher(algorithm);
188
+ hasher.update(content);
189
+ return hasher.digest("hex");
190
+ }
191
+ async mimeType(path, options = {}) {
192
+ const fullPath = this.resolvePath(path);
193
+ return await this.detectMimeType(fullPath);
194
+ }
195
+ async detectMimeType(filePath) {
196
+ const ext = basename(filePath).split(".").pop()?.toLowerCase();
197
+ return {
198
+ txt: "text/plain",
199
+ html: "text/html",
200
+ css: "text/css",
201
+ js: "application/javascript",
202
+ json: "application/json",
203
+ xml: "application/xml",
204
+ pdf: "application/pdf",
205
+ zip: "application/zip",
206
+ jpg: "image/jpeg",
207
+ jpeg: "image/jpeg",
208
+ png: "image/png",
209
+ gif: "image/gif",
210
+ svg: "image/svg+xml",
211
+ mp4: "video/mp4",
212
+ mp3: "audio/mpeg",
213
+ wav: "audio/wav"
214
+ }[ext || ""] || "application/octet-stream";
215
+ }
216
+ async lastModified(path) {
217
+ return (await this.stat(path)).lastModified;
218
+ }
219
+ async fileSize(path) {
220
+ return (await this.stat(path)).size;
221
+ }
222
+ }
223
+ export function createLocalStorage(config = {}) {
224
+ return new LocalStorageAdapter(config);
225
+ }
@@ -0,0 +1,38 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import type { ChecksumOptions, DirectoryListing, FileContents, GetStreamOptions, ListOptions, MimeTypeOptions, PublicUrlOptions, PutResult, PutStreamOptions, SignedUrlOptions, StatEntry, StorageAdapter, TemporaryUrlOptions, Visibility } from '../types';
3
+ /**
4
+ * Create an in-memory storage adapter instance
5
+ */
6
+ export declare function createMemoryStorage(): InMemoryStorageAdapter;
7
+ /**
8
+ * In-memory storage adapter for testing and temporary storage
9
+ */
10
+ export declare class InMemoryStorageAdapter implements StorageAdapter {
11
+ constructor();
12
+ write(path: string, contents: FileContents): Promise<PutResult>;
13
+ read(path: string): Promise<FileContents>;
14
+ getStream(path: string, _options?: GetStreamOptions): Promise<ReadableStream<Uint8Array>>;
15
+ putStream(path: string, stream: ReadableStream<Uint8Array>, options?: PutStreamOptions): Promise<PutResult>;
16
+ readToString(path: string): Promise<string>;
17
+ readToBuffer(path: string): Promise<Buffer>;
18
+ readToUint8Array(path: string): Promise<Uint8Array>;
19
+ deleteFile(path: string): Promise<void>;
20
+ deleteDirectory(path: string): Promise<void>;
21
+ createDirectory(path: string): Promise<void>;
22
+ moveFile(from: string, to: string): Promise<void>;
23
+ copyFile(from: string, to: string): Promise<void>;
24
+ stat(path: string): Promise<StatEntry>;
25
+ list(path: string, options?: ListOptions): DirectoryListing;
26
+ changeVisibility(path: string, visibility: Visibility): Promise<void>;
27
+ visibility(path: string): Promise<Visibility>;
28
+ fileExists(path: string): Promise<boolean>;
29
+ directoryExists(path: string): Promise<boolean>;
30
+ publicUrl(path: string, options?: PublicUrlOptions): Promise<string>;
31
+ temporaryUrl(path: string, options: TemporaryUrlOptions): Promise<string>;
32
+ signedUrl(_path: string, _options: SignedUrlOptions): Promise<string>;
33
+ checksum(path: string, options?: ChecksumOptions): Promise<string>;
34
+ mimeType(path: string, _options?: MimeTypeOptions): Promise<string>;
35
+ lastModified(path: string): Promise<number>;
36
+ fileSize(path: string): Promise<number>;
37
+ clear(): void;
38
+ }