@mastra/files-sdk 0.3.0 → 0.3.1-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +6 -4
- package/dist/filesystem/index.d.ts.map +1 -1
- package/dist/index.cjs +435 -405
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +433 -402
- package/dist/index.js.map +1 -1
- package/package.json +15 -15
- package/CHANGELOG.md +0 -91
package/dist/index.js
CHANGED
|
@@ -1,424 +1,455 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { DirectoryNotEmptyError, FileExistsError, FileNotFoundError, MastraFilesystem, WorkspaceReadOnlyError } from "@mastra/core/workspace";
|
|
2
|
+
//#region src/filesystem/index.ts
|
|
3
|
+
/**
|
|
4
|
+
* Normalize a POSIX-style path to an object-storage key.
|
|
5
|
+
* Strips leading slashes, resolves `.`/`./` to empty string.
|
|
6
|
+
*/
|
|
4
7
|
function toKey(path) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
let start = 0;
|
|
9
|
+
while (start < path.length && path.charCodeAt(start) === 47) start++;
|
|
10
|
+
let end = path.length;
|
|
11
|
+
while (end > start && path.charCodeAt(end - 1) === 47) end--;
|
|
12
|
+
const key = path.slice(start, end);
|
|
13
|
+
if (key === "." || key === "./") return "";
|
|
14
|
+
return key;
|
|
12
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Extract the basename (last path segment) from a key.
|
|
18
|
+
*/
|
|
13
19
|
function basename(key) {
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
const idx = key.lastIndexOf("/");
|
|
21
|
+
return idx === -1 ? key : key.slice(idx + 1);
|
|
16
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Walk the FilesSDK error code chain.
|
|
25
|
+
*
|
|
26
|
+
* FilesSDK frequently wraps an inner `NotFound` / `Unauthorized` error in an
|
|
27
|
+
* outer `Provider` error (`error.cause` carries the original). Some adapters
|
|
28
|
+
* may wrap multiple levels deep, so we walk the cause chain and return any
|
|
29
|
+
* matching code found along the way.
|
|
30
|
+
*/
|
|
17
31
|
function hasFilesSDKCode(err, code, depth = 0) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
32
|
+
if (depth > 5 || !err || typeof err !== "object") return false;
|
|
33
|
+
const e = err;
|
|
34
|
+
if (e.code === code) return true;
|
|
35
|
+
if (e.cause) return hasFilesSDKCode(e.cause, code, depth + 1);
|
|
36
|
+
return false;
|
|
23
37
|
}
|
|
24
38
|
function isNotFoundError(err) {
|
|
25
|
-
|
|
39
|
+
return hasFilesSDKCode(err, "NotFound");
|
|
26
40
|
}
|
|
27
41
|
function isUnauthorizedError(err) {
|
|
28
|
-
|
|
42
|
+
return hasFilesSDKCode(err, "Unauthorized");
|
|
29
43
|
}
|
|
30
44
|
function generateId() {
|
|
31
|
-
|
|
45
|
+
return `files-sdk-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
32
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Convert FileContent (string | Buffer | Uint8Array) to a body acceptable by files-sdk.
|
|
49
|
+
*/
|
|
33
50
|
function toBody(content) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
51
|
+
if (typeof content === "string") return content;
|
|
52
|
+
if (Buffer.isBuffer(content)) return new Uint8Array(content);
|
|
53
|
+
return content;
|
|
37
54
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Infer MIME type from a file path extension.
|
|
57
|
+
*/
|
|
58
|
+
const MIME_TYPES = {
|
|
59
|
+
".txt": "text/plain",
|
|
60
|
+
".md": "text/markdown",
|
|
61
|
+
".markdown": "text/markdown",
|
|
62
|
+
".html": "text/html",
|
|
63
|
+
".htm": "text/html",
|
|
64
|
+
".css": "text/css",
|
|
65
|
+
".csv": "text/csv",
|
|
66
|
+
".xml": "text/xml",
|
|
67
|
+
".js": "text/javascript",
|
|
68
|
+
".mjs": "text/javascript",
|
|
69
|
+
".ts": "text/typescript",
|
|
70
|
+
".tsx": "text/typescript",
|
|
71
|
+
".jsx": "text/javascript",
|
|
72
|
+
".json": "application/json",
|
|
73
|
+
".yaml": "text/yaml",
|
|
74
|
+
".yml": "text/yaml",
|
|
75
|
+
".py": "text/x-python",
|
|
76
|
+
".rb": "text/x-ruby",
|
|
77
|
+
".sh": "text/x-shellscript",
|
|
78
|
+
".bash": "text/x-shellscript",
|
|
79
|
+
".png": "image/png",
|
|
80
|
+
".jpg": "image/jpeg",
|
|
81
|
+
".jpeg": "image/jpeg",
|
|
82
|
+
".gif": "image/gif",
|
|
83
|
+
".svg": "image/svg+xml",
|
|
84
|
+
".webp": "image/webp",
|
|
85
|
+
".ico": "image/x-icon",
|
|
86
|
+
".pdf": "application/pdf",
|
|
87
|
+
".zip": "application/zip",
|
|
88
|
+
".gz": "application/gzip",
|
|
89
|
+
".tar": "application/x-tar"
|
|
70
90
|
};
|
|
71
91
|
function getMimeType(path) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
92
|
+
const dot = path.lastIndexOf(".");
|
|
93
|
+
if (dot === -1) return "application/octet-stream";
|
|
94
|
+
return MIME_TYPES[path.slice(dot).toLowerCase()] ?? "application/octet-stream";
|
|
75
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Workspace filesystem adapter backed by [FilesSDK](https://files-sdk.dev).
|
|
98
|
+
*
|
|
99
|
+
* Accepts a pre-configured `Files` instance so users choose their own adapter
|
|
100
|
+
* (S3, R2, GCS, Azure, local fs, etc.) and this class bridges it to the
|
|
101
|
+
* Mastra `WorkspaceFilesystem` interface.
|
|
102
|
+
*
|
|
103
|
+
* Object-storage semantics are bridged to the POSIX-like interface:
|
|
104
|
+
* - `mkdir` is a no-op (directories don't exist in object storage)
|
|
105
|
+
* - `readdir` uses `list()` with prefix filtering to synthesize directory entries
|
|
106
|
+
* - `rmdir` lists all keys under a prefix and batch-deletes them
|
|
107
|
+
*/
|
|
76
108
|
var FilesSDKFilesystem = class extends MastraFilesystem {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
* directory). Uses prefix listing constrained to the exact key, which only
|
|
381
|
-
* matches actually-stored objects across all adapters.
|
|
382
|
-
*/
|
|
383
|
-
async isFile(key) {
|
|
384
|
-
if (!key) return false;
|
|
385
|
-
const result = await this._files.list({ prefix: key, limit: 10 });
|
|
386
|
-
return result.items.some((item) => item.key === key);
|
|
387
|
-
}
|
|
388
|
-
/** Convert a FilesSDK StoredFile to a Mastra FileStat. */
|
|
389
|
-
storedFileToStat(file, path) {
|
|
390
|
-
return {
|
|
391
|
-
name: basename(file.key ?? path),
|
|
392
|
-
path,
|
|
393
|
-
type: "file",
|
|
394
|
-
size: file.size ?? 0,
|
|
395
|
-
createdAt: file.lastModified ? new Date(file.lastModified) : /* @__PURE__ */ new Date(),
|
|
396
|
-
modifiedAt: file.lastModified ? new Date(file.lastModified) : /* @__PURE__ */ new Date(),
|
|
397
|
-
mimeType: file.type || void 0
|
|
398
|
-
};
|
|
399
|
-
}
|
|
109
|
+
id;
|
|
110
|
+
name = "FilesSDKFilesystem";
|
|
111
|
+
provider = "files-sdk";
|
|
112
|
+
status = "pending";
|
|
113
|
+
readOnly;
|
|
114
|
+
icon;
|
|
115
|
+
displayName;
|
|
116
|
+
description;
|
|
117
|
+
_files;
|
|
118
|
+
constructor(options) {
|
|
119
|
+
super({
|
|
120
|
+
name: "FilesSDKFilesystem",
|
|
121
|
+
...options
|
|
122
|
+
});
|
|
123
|
+
this._files = options.files;
|
|
124
|
+
this.id = options.id ?? generateId();
|
|
125
|
+
this.readOnly = options.readOnly;
|
|
126
|
+
this.icon = options.icon;
|
|
127
|
+
this.displayName = options.displayName;
|
|
128
|
+
this.description = options.description;
|
|
129
|
+
}
|
|
130
|
+
/** The underlying FilesSDK instance, for escape-hatch access. */
|
|
131
|
+
get files() {
|
|
132
|
+
return this._files;
|
|
133
|
+
}
|
|
134
|
+
async init() {
|
|
135
|
+
try {
|
|
136
|
+
await this._files.list({ limit: 1 });
|
|
137
|
+
} catch (err) {
|
|
138
|
+
if (isUnauthorizedError(err)) throw new Error("Access denied — check credentials and storage permissions");
|
|
139
|
+
throw err;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
getInfo() {
|
|
143
|
+
return {
|
|
144
|
+
id: this.id,
|
|
145
|
+
name: this.name,
|
|
146
|
+
provider: this.provider,
|
|
147
|
+
status: this.status,
|
|
148
|
+
error: this.error,
|
|
149
|
+
readOnly: this.readOnly,
|
|
150
|
+
icon: this.icon,
|
|
151
|
+
metadata: { adapter: this._files.adapter?.name ?? "unknown" }
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
getInstructions() {
|
|
155
|
+
const parts = [`Unified storage via FilesSDK (${this._files.adapter?.name ?? "unknown"} adapter).`];
|
|
156
|
+
if (this.readOnly) parts.push("Mounted read-only.");
|
|
157
|
+
parts.push("Persistent storage — files are retained across sessions.");
|
|
158
|
+
return parts.join(" ");
|
|
159
|
+
}
|
|
160
|
+
async readFile(path, options) {
|
|
161
|
+
await this.ensureReady();
|
|
162
|
+
const key = toKey(path);
|
|
163
|
+
try {
|
|
164
|
+
const file = await this._files.download(key);
|
|
165
|
+
const buf = Buffer.from(await file.arrayBuffer());
|
|
166
|
+
if (options?.encoding) return buf.toString(options.encoding);
|
|
167
|
+
return buf;
|
|
168
|
+
} catch (err) {
|
|
169
|
+
if (isNotFoundError(err)) throw new FileNotFoundError(path);
|
|
170
|
+
throw err;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async writeFile(path, content, options) {
|
|
174
|
+
await this.ensureReady();
|
|
175
|
+
this.assertWritable("writeFile");
|
|
176
|
+
const key = toKey(path);
|
|
177
|
+
if (options?.overwrite === false && await this.isFile(key)) throw new FileExistsError(path);
|
|
178
|
+
const body = toBody(content);
|
|
179
|
+
await this._files.upload(key, body, { contentType: options?.mimeType ?? getMimeType(path) });
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Append content to a file.
|
|
183
|
+
*
|
|
184
|
+
* **Not atomic.** Object storage has no native append, so this is implemented
|
|
185
|
+
* as a read-modify-write: the existing object is downloaded, the new content
|
|
186
|
+
* is concatenated, and the result is uploaded as a new object. Concurrent
|
|
187
|
+
* appends to the same key may overwrite each other. This limitation is
|
|
188
|
+
* inherent to object storage, not specific to FilesSDK, and matches the
|
|
189
|
+
* behavior of the sibling S3, GCS, and Azure workspace providers.
|
|
190
|
+
*/
|
|
191
|
+
async appendFile(path, content) {
|
|
192
|
+
await this.ensureReady();
|
|
193
|
+
this.assertWritable("appendFile");
|
|
194
|
+
const key = toKey(path);
|
|
195
|
+
let existing = Buffer.alloc(0);
|
|
196
|
+
try {
|
|
197
|
+
const file = await this._files.download(key);
|
|
198
|
+
existing = Buffer.from(await file.arrayBuffer());
|
|
199
|
+
} catch (err) {
|
|
200
|
+
if (!isNotFoundError(err)) throw err;
|
|
201
|
+
}
|
|
202
|
+
const append = typeof content === "string" ? Buffer.from(content) : toBody(content);
|
|
203
|
+
const merged = Buffer.concat([existing, Buffer.isBuffer(append) ? append : Buffer.from(append)]);
|
|
204
|
+
await this._files.upload(key, new Uint8Array(merged), { contentType: getMimeType(path) });
|
|
205
|
+
}
|
|
206
|
+
async deleteFile(path, options) {
|
|
207
|
+
await this.ensureReady();
|
|
208
|
+
this.assertWritable("deleteFile");
|
|
209
|
+
const key = toKey(path);
|
|
210
|
+
if (await this.isDirectory(key)) {
|
|
211
|
+
await this.rmdir(path, {
|
|
212
|
+
recursive: true,
|
|
213
|
+
force: options?.force
|
|
214
|
+
});
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (!options?.force && !await this.isFile(key)) throw new FileNotFoundError(path);
|
|
218
|
+
try {
|
|
219
|
+
await this._files.delete(key);
|
|
220
|
+
} catch (err) {
|
|
221
|
+
if (options?.force) return;
|
|
222
|
+
if (isNotFoundError(err)) throw new FileNotFoundError(path);
|
|
223
|
+
throw err;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
async copyFile(src, dest, options) {
|
|
227
|
+
await this.ensureReady();
|
|
228
|
+
this.assertWritable("copyFile");
|
|
229
|
+
const fromKey = toKey(src);
|
|
230
|
+
const toKey_ = toKey(dest);
|
|
231
|
+
if (options?.overwrite === false && await this.isFile(toKey_)) throw new FileExistsError(dest);
|
|
232
|
+
try {
|
|
233
|
+
await this._files.copy(fromKey, toKey_);
|
|
234
|
+
} catch (err) {
|
|
235
|
+
if (isNotFoundError(err)) throw new FileNotFoundError(src);
|
|
236
|
+
throw err;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
async moveFile(src, dest, options) {
|
|
240
|
+
await this.copyFile(src, dest, options);
|
|
241
|
+
await this.deleteFile(src, { force: true });
|
|
242
|
+
}
|
|
243
|
+
async mkdir(_path, _options) {
|
|
244
|
+
await this.ensureReady();
|
|
245
|
+
this.assertWritable("mkdir");
|
|
246
|
+
}
|
|
247
|
+
async rmdir(path, options) {
|
|
248
|
+
await this.ensureReady();
|
|
249
|
+
this.assertWritable("rmdir");
|
|
250
|
+
const key = toKey(path);
|
|
251
|
+
const prefix = key ? `${key}/` : "";
|
|
252
|
+
const allKeys = [];
|
|
253
|
+
let cursor;
|
|
254
|
+
do {
|
|
255
|
+
const result = await this._files.list({
|
|
256
|
+
prefix,
|
|
257
|
+
cursor,
|
|
258
|
+
limit: 1e3
|
|
259
|
+
});
|
|
260
|
+
for (const item of result.items) allKeys.push(item.key);
|
|
261
|
+
cursor = result.cursor;
|
|
262
|
+
} while (cursor);
|
|
263
|
+
if (allKeys.length === 0) return;
|
|
264
|
+
if (!options?.recursive) throw new DirectoryNotEmptyError(path);
|
|
265
|
+
await this._files.delete(allKeys);
|
|
266
|
+
}
|
|
267
|
+
async readdir(path, options) {
|
|
268
|
+
await this.ensureReady();
|
|
269
|
+
const key = toKey(path);
|
|
270
|
+
const prefix = key ? `${key}/` : "";
|
|
271
|
+
const entries = [];
|
|
272
|
+
const seenDirs = /* @__PURE__ */ new Set();
|
|
273
|
+
let cursor;
|
|
274
|
+
const maxDepth = options?.maxDepth;
|
|
275
|
+
const recursive = options?.recursive ?? false;
|
|
276
|
+
const extensions = options?.extension ? Array.isArray(options.extension) ? options.extension : [options.extension] : void 0;
|
|
277
|
+
do {
|
|
278
|
+
const result = await this._files.list({
|
|
279
|
+
prefix,
|
|
280
|
+
cursor,
|
|
281
|
+
limit: 1e3
|
|
282
|
+
});
|
|
283
|
+
for (const item of result.items) {
|
|
284
|
+
const relativePath = prefix ? item.key.slice(prefix.length) : item.key;
|
|
285
|
+
if (!relativePath) continue;
|
|
286
|
+
const segments = relativePath.split("/");
|
|
287
|
+
if (segments.length === 1) {
|
|
288
|
+
const name = segments[0];
|
|
289
|
+
if (extensions) {
|
|
290
|
+
const ext = name.lastIndexOf(".") !== -1 ? name.slice(name.lastIndexOf(".")) : "";
|
|
291
|
+
if (!extensions.includes(ext)) continue;
|
|
292
|
+
}
|
|
293
|
+
entries.push({
|
|
294
|
+
name,
|
|
295
|
+
type: "file",
|
|
296
|
+
size: item.size
|
|
297
|
+
});
|
|
298
|
+
} else if (!recursive) {
|
|
299
|
+
const dirName = segments[0];
|
|
300
|
+
if (!seenDirs.has(dirName)) {
|
|
301
|
+
seenDirs.add(dirName);
|
|
302
|
+
entries.push({
|
|
303
|
+
name: dirName,
|
|
304
|
+
type: "directory"
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
} else {
|
|
308
|
+
for (let i = 1; i < segments.length; i++) {
|
|
309
|
+
const dirPath = segments.slice(0, i).join("/");
|
|
310
|
+
if (maxDepth !== void 0 && i > maxDepth) continue;
|
|
311
|
+
if (!seenDirs.has(dirPath)) {
|
|
312
|
+
seenDirs.add(dirPath);
|
|
313
|
+
entries.push({
|
|
314
|
+
name: dirPath,
|
|
315
|
+
type: "directory"
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (maxDepth !== void 0 && segments.length > maxDepth) continue;
|
|
320
|
+
const name = relativePath;
|
|
321
|
+
if (extensions) {
|
|
322
|
+
const ext = name.lastIndexOf(".") !== -1 ? name.slice(name.lastIndexOf(".")) : "";
|
|
323
|
+
if (!extensions.includes(ext)) continue;
|
|
324
|
+
}
|
|
325
|
+
entries.push({
|
|
326
|
+
name,
|
|
327
|
+
type: "file",
|
|
328
|
+
size: item.size
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
cursor = result.cursor;
|
|
333
|
+
} while (cursor);
|
|
334
|
+
return entries;
|
|
335
|
+
}
|
|
336
|
+
async exists(path) {
|
|
337
|
+
await this.ensureReady();
|
|
338
|
+
const key = toKey(path);
|
|
339
|
+
if (!key) return true;
|
|
340
|
+
if (await this.isDirectory(key)) return true;
|
|
341
|
+
return this.isFile(key);
|
|
342
|
+
}
|
|
343
|
+
async stat(path) {
|
|
344
|
+
await this.ensureReady();
|
|
345
|
+
const key = toKey(path);
|
|
346
|
+
if (!key) {
|
|
347
|
+
const now = /* @__PURE__ */ new Date();
|
|
348
|
+
return {
|
|
349
|
+
name: "/",
|
|
350
|
+
path: "/",
|
|
351
|
+
type: "directory",
|
|
352
|
+
size: 0,
|
|
353
|
+
createdAt: now,
|
|
354
|
+
modifiedAt: now
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
try {
|
|
358
|
+
const file = await this._files.head(key);
|
|
359
|
+
return this.storedFileToStat(file, path);
|
|
360
|
+
} catch (err) {
|
|
361
|
+
if (!isNotFoundError(err)) throw err;
|
|
362
|
+
}
|
|
363
|
+
if (await this.isDirectory(key)) {
|
|
364
|
+
const now = /* @__PURE__ */ new Date();
|
|
365
|
+
return {
|
|
366
|
+
name: basename(key),
|
|
367
|
+
path,
|
|
368
|
+
type: "directory",
|
|
369
|
+
size: 0,
|
|
370
|
+
createdAt: now,
|
|
371
|
+
modifiedAt: now
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
throw new FileNotFoundError(path);
|
|
375
|
+
}
|
|
376
|
+
assertWritable(operation) {
|
|
377
|
+
if (this.readOnly) throw new WorkspaceReadOnlyError(operation);
|
|
378
|
+
}
|
|
379
|
+
/** Check if a key prefix has any children (i.e. acts like a directory). */
|
|
380
|
+
async isDirectory(key) {
|
|
381
|
+
if (!key) return true;
|
|
382
|
+
const prefix = `${key}/`;
|
|
383
|
+
return (await this._files.list({
|
|
384
|
+
prefix,
|
|
385
|
+
limit: 1
|
|
386
|
+
})).items.length > 0;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Check whether `key` refers to a real stored file (not an empty leftover
|
|
390
|
+
* directory). Uses prefix listing constrained to the exact key, which only
|
|
391
|
+
* matches actually-stored objects across all adapters.
|
|
392
|
+
*/
|
|
393
|
+
async isFile(key) {
|
|
394
|
+
if (!key) return false;
|
|
395
|
+
return (await this._files.list({
|
|
396
|
+
prefix: key,
|
|
397
|
+
limit: 10
|
|
398
|
+
})).items.some((item) => item.key === key);
|
|
399
|
+
}
|
|
400
|
+
/** Convert a FilesSDK StoredFile to a Mastra FileStat. */
|
|
401
|
+
storedFileToStat(file, path) {
|
|
402
|
+
return {
|
|
403
|
+
name: basename(file.key ?? path),
|
|
404
|
+
path,
|
|
405
|
+
type: "file",
|
|
406
|
+
size: file.size ?? 0,
|
|
407
|
+
createdAt: file.lastModified ? new Date(file.lastModified) : /* @__PURE__ */ new Date(),
|
|
408
|
+
modifiedAt: file.lastModified ? new Date(file.lastModified) : /* @__PURE__ */ new Date(),
|
|
409
|
+
mimeType: file.type || void 0
|
|
410
|
+
};
|
|
411
|
+
}
|
|
400
412
|
};
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
413
|
+
//#endregion
|
|
414
|
+
//#region src/provider.ts
|
|
415
|
+
const filesSDKFilesystemProvider = {
|
|
416
|
+
id: "files-sdk",
|
|
417
|
+
name: "FilesSDK (Unified Storage)",
|
|
418
|
+
description: "Unified storage via FilesSDK — supports S3, R2, GCS, Azure, Vercel Blob, local filesystem, and more",
|
|
419
|
+
configSchema: {
|
|
420
|
+
type: "object",
|
|
421
|
+
required: ["files"],
|
|
422
|
+
properties: {
|
|
423
|
+
files: {
|
|
424
|
+
type: "object",
|
|
425
|
+
description: "Pre-configured FilesSDK Files instance"
|
|
426
|
+
},
|
|
427
|
+
id: {
|
|
428
|
+
type: "string",
|
|
429
|
+
description: "Unique filesystem ID"
|
|
430
|
+
},
|
|
431
|
+
displayName: {
|
|
432
|
+
type: "string",
|
|
433
|
+
description: "Human-friendly display name"
|
|
434
|
+
},
|
|
435
|
+
icon: {
|
|
436
|
+
type: "string",
|
|
437
|
+
description: "Icon identifier for UI"
|
|
438
|
+
},
|
|
439
|
+
description: {
|
|
440
|
+
type: "string",
|
|
441
|
+
description: "Description for UI"
|
|
442
|
+
},
|
|
443
|
+
readOnly: {
|
|
444
|
+
type: "boolean",
|
|
445
|
+
description: "Mount as read-only",
|
|
446
|
+
default: false
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
createFilesystem: (config) => new FilesSDKFilesystem(config)
|
|
420
451
|
};
|
|
421
|
-
|
|
452
|
+
//#endregion
|
|
422
453
|
export { FilesSDKFilesystem, filesSDKFilesystemProvider };
|
|
423
|
-
|
|
454
|
+
|
|
424
455
|
//# sourceMappingURL=index.js.map
|