@aigne/afs-gcs 1.11.0-beta.10

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.mjs ADDED
@@ -0,0 +1,1206 @@
1
+ import { AFSBaseProvider, AFSError, AFSNotFoundError, Actions, Delete, Explain, List, Meta, Read, Search, Stat, Write } from "@aigne/afs";
2
+ import { camelize, optionalize, zodParse } from "@aigne/afs/utils/zod";
3
+ import { joinURL } from "ufo";
4
+ import { z } from "zod";
5
+ import { Storage } from "@google-cloud/storage";
6
+
7
+ //#region src/cache.ts
8
+ /**
9
+ * LRU Cache with TTL support
10
+ */
11
+ var LRUCache = class {
12
+ cache = /* @__PURE__ */ new Map();
13
+ maxSize;
14
+ defaultTtl;
15
+ /**
16
+ * Create a new LRU cache
17
+ *
18
+ * @param maxSize - Maximum number of entries (default: 1000)
19
+ * @param defaultTtl - Default TTL in seconds (default: 60)
20
+ */
21
+ constructor(maxSize = 1e3, defaultTtl = 60) {
22
+ this.maxSize = maxSize;
23
+ this.defaultTtl = defaultTtl;
24
+ }
25
+ /**
26
+ * Get a value from the cache
27
+ *
28
+ * @param key - Cache key
29
+ * @returns Cached value or undefined if not found/expired
30
+ */
31
+ get(key) {
32
+ const entry = this.cache.get(key);
33
+ if (!entry) return;
34
+ if (Date.now() > entry.expiresAt) {
35
+ this.cache.delete(key);
36
+ return;
37
+ }
38
+ this.cache.delete(key);
39
+ this.cache.set(key, entry);
40
+ return entry.value;
41
+ }
42
+ /**
43
+ * Set a value in the cache
44
+ *
45
+ * @param key - Cache key
46
+ * @param value - Value to cache
47
+ * @param ttl - TTL in seconds (optional, uses default)
48
+ */
49
+ set(key, value, ttl) {
50
+ if (this.cache.has(key)) this.cache.delete(key);
51
+ while (this.cache.size >= this.maxSize) {
52
+ const oldestKey = this.cache.keys().next().value;
53
+ if (oldestKey) this.cache.delete(oldestKey);
54
+ }
55
+ const expiresAt = Date.now() + (ttl ?? this.defaultTtl) * 1e3;
56
+ this.cache.set(key, {
57
+ value,
58
+ expiresAt
59
+ });
60
+ }
61
+ /**
62
+ * Delete a value from the cache
63
+ *
64
+ * @param key - Cache key
65
+ */
66
+ delete(key) {
67
+ this.cache.delete(key);
68
+ }
69
+ /**
70
+ * Delete all entries matching a prefix
71
+ *
72
+ * @param prefix - Key prefix to match
73
+ */
74
+ deleteByPrefix(prefix) {
75
+ for (const key of this.cache.keys()) if (key.startsWith(prefix)) this.cache.delete(key);
76
+ }
77
+ /**
78
+ * Clear all entries
79
+ */
80
+ clear() {
81
+ this.cache.clear();
82
+ }
83
+ /**
84
+ * Get the number of entries in the cache
85
+ */
86
+ get size() {
87
+ return this.cache.size;
88
+ }
89
+ /**
90
+ * Prune expired entries
91
+ */
92
+ prune() {
93
+ const now = Date.now();
94
+ for (const [key, entry] of this.cache.entries()) if (now > entry.expiresAt) this.cache.delete(key);
95
+ }
96
+ };
97
+ /**
98
+ * Create a cache key from bucket, prefix, and path
99
+ */
100
+ function createCacheKey(bucket, prefix, path, suffix) {
101
+ const base = `${bucket}:${prefix}:${path}`;
102
+ return suffix ? `${base}:${suffix}` : base;
103
+ }
104
+
105
+ //#endregion
106
+ //#region src/client.ts
107
+ /**
108
+ * GCS Client Factory
109
+ *
110
+ * Creates configured Google Cloud Storage clients.
111
+ */
112
+ /**
113
+ * Create a GCS Storage client from options
114
+ *
115
+ * @param options - AFSGCS options
116
+ * @returns Configured Storage client
117
+ */
118
+ function createGCSClient(options) {
119
+ const storageOptions = {};
120
+ if (options.projectId) storageOptions.projectId = options.projectId;
121
+ if (options.endpoint) storageOptions.apiEndpoint = options.endpoint;
122
+ if (options.keyFilename) storageOptions.keyFilename = options.keyFilename;
123
+ if (options.credentials) storageOptions.credentials = {
124
+ client_email: options.credentials.clientEmail,
125
+ private_key: options.credentials.privateKey
126
+ };
127
+ return new Storage(storageOptions);
128
+ }
129
+
130
+ //#endregion
131
+ //#region src/errors.ts
132
+ /**
133
+ * GCS Provider Error Handling
134
+ *
135
+ * Maps GCS SDK errors to AFS error types.
136
+ */
137
+ /**
138
+ * AFS error codes
139
+ */
140
+ const AFSErrorCode = {
141
+ ENTRY_NOT_FOUND: "ENTRY_NOT_FOUND",
142
+ MODULE_NOT_FOUND: "MODULE_NOT_FOUND",
143
+ PERMISSION_DENIED: "PERMISSION_DENIED",
144
+ AUTH_ERROR: "AUTH_ERROR",
145
+ RATE_LIMIT_EXCEEDED: "RATE_LIMIT_EXCEEDED",
146
+ INVALID_OPERATION: "INVALID_OPERATION",
147
+ ALREADY_EXISTS: "ALREADY_EXISTS",
148
+ SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",
149
+ UNKNOWN: "UNKNOWN"
150
+ };
151
+ /**
152
+ * AFS Error class
153
+ */
154
+ var AFSError$1 = class extends Error {
155
+ code;
156
+ retryAfter;
157
+ constructor(code, message, options) {
158
+ super(message);
159
+ this.name = "AFSError";
160
+ this.code = code;
161
+ this.retryAfter = options?.retryAfter;
162
+ }
163
+ };
164
+ /**
165
+ * Custom GCS error class for internal use
166
+ */
167
+ var GCSError = class extends Error {
168
+ constructor(message, code) {
169
+ super(message);
170
+ this.code = code;
171
+ this.name = "GCSError";
172
+ }
173
+ };
174
+ /**
175
+ * Map GCS HTTP status codes to AFS error codes
176
+ */
177
+ const STATUS_TO_AFS_ERROR = {
178
+ 400: AFSErrorCode.INVALID_OPERATION,
179
+ 401: AFSErrorCode.AUTH_ERROR,
180
+ 403: AFSErrorCode.PERMISSION_DENIED,
181
+ 404: AFSErrorCode.ENTRY_NOT_FOUND,
182
+ 409: AFSErrorCode.ALREADY_EXISTS,
183
+ 429: AFSErrorCode.RATE_LIMIT_EXCEEDED,
184
+ 503: AFSErrorCode.SERVICE_UNAVAILABLE
185
+ };
186
+ /**
187
+ * Map GCS error to AFS error
188
+ *
189
+ * @param error - GCS SDK error or generic error
190
+ * @returns AFSError with appropriate error code
191
+ */
192
+ function mapGCSError(error) {
193
+ if (error instanceof GCSError) return new AFSError$1(STATUS_TO_AFS_ERROR[error.code] ?? AFSErrorCode.UNKNOWN, error.message);
194
+ if (typeof error === "object" && error !== null && "code" in error) {
195
+ const err = error;
196
+ const statusCode = typeof err.code === "number" ? err.code : 500;
197
+ const message = err.message ?? "Unknown GCS error";
198
+ return new AFSError$1(STATUS_TO_AFS_ERROR[statusCode] ?? AFSErrorCode.UNKNOWN, message);
199
+ }
200
+ if (error instanceof Error) return new AFSError$1(AFSErrorCode.UNKNOWN, error.message);
201
+ return new AFSError$1(AFSErrorCode.UNKNOWN, String(error));
202
+ }
203
+
204
+ //#endregion
205
+ //#region src/platform-ref.ts
206
+ /**
207
+ * Generate platform reference with GCP Console URL
208
+ *
209
+ * @param bucket - GCS bucket name
210
+ * @param prefix - Object prefix/key (without leading slash)
211
+ * @param isDirectory - Whether this is a directory (prefix)
212
+ * @returns Platform reference with console URL
213
+ */
214
+ function generatePlatformRef(bucket, prefix, isDirectory) {
215
+ if (isDirectory) return { consoleUrl: `https://console.cloud.google.com/storage/browser/${bucket}/${prefix.endsWith("/") ? prefix : prefix ? `${prefix}/` : ""}` };
216
+ return { consoleUrl: `https://console.cloud.google.com/storage/browser/_details/${bucket}/${encodeURIComponent(prefix).replace(/%2F/g, "/")}` };
217
+ }
218
+
219
+ //#endregion
220
+ //#region src/types.ts
221
+ /**
222
+ * AFS GCS Provider Types
223
+ */
224
+ /**
225
+ * GCS bucket name validation regex
226
+ * - 3-63 characters
227
+ * - lowercase letters, numbers, hyphens, dots (but not consecutive dots)
228
+ * - must start and end with letter or number
229
+ */
230
+ const BUCKET_NAME_REGEX = /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/;
231
+ /**
232
+ * Additional validation for bucket names
233
+ */
234
+ function isValidBucketName(name) {
235
+ if (!BUCKET_NAME_REGEX.test(name)) return false;
236
+ if (name.includes("..")) return false;
237
+ if (name.includes("_")) return false;
238
+ return true;
239
+ }
240
+ /**
241
+ * Zod schema for options validation
242
+ */
243
+ const afsgcsOptionsSchema = camelize(z.object({
244
+ name: optionalize(z.string()),
245
+ description: optionalize(z.string()),
246
+ bucket: z.string().refine(isValidBucketName, "Invalid GCS bucket name"),
247
+ prefix: optionalize(z.string()),
248
+ projectId: optionalize(z.string()),
249
+ accessMode: optionalize(z.enum(["readonly", "readwrite"])),
250
+ endpoint: optionalize(z.string().url()),
251
+ keyFilename: optionalize(z.string()),
252
+ credentials: optionalize(z.object({
253
+ clientEmail: z.string(),
254
+ privateKey: z.string()
255
+ })),
256
+ cacheTtl: optionalize(z.number().int().min(0))
257
+ }).strict());
258
+
259
+ //#endregion
260
+ //#region \0@oxc-project+runtime@0.108.0/helpers/decorate.js
261
+ function __decorate(decorators, target, key, desc) {
262
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
263
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
264
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
265
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
266
+ }
267
+
268
+ //#endregion
269
+ //#region src/gcs-afs.ts
270
+ /**
271
+ * AFS GCS Provider
272
+ *
273
+ * GCS provider using AFSBaseProvider decorator routing pattern.
274
+ * Provides access to Google Cloud Storage through AFS.
275
+ */
276
+ /**
277
+ * Default URL expiration time (1 hour)
278
+ */
279
+ const DEFAULT_EXPIRES_IN = 3600;
280
+ /**
281
+ * Maximum expiration time (7 days)
282
+ */
283
+ const MAX_EXPIRES_IN = 604800;
284
+ /**
285
+ * Maximum sources for compose operation
286
+ */
287
+ const MAX_COMPOSE_SOURCES = 32;
288
+ /**
289
+ * AFSGCS Provider using Base Provider pattern
290
+ *
291
+ * Provides access to Google Cloud Storage through AFS.
292
+ * Uses decorator routing (@List, @Read, @Write, @Delete, @Meta, @Actions).
293
+ *
294
+ * @example
295
+ * ```typescript
296
+ * const gcs = new AFSGCS({
297
+ * bucket: "my-bucket",
298
+ * prefix: "data",
299
+ * projectId: "my-project",
300
+ * });
301
+ *
302
+ * // Mount to AFS
303
+ * afs.mount(gcs);
304
+ *
305
+ * // List objects
306
+ * const result = await afs.list("/modules/my-bucket/data");
307
+ *
308
+ * // Read object
309
+ * const content = await afs.read("/modules/my-bucket/data/file.json");
310
+ * ```
311
+ */
312
+ var AFSGCS = class AFSGCS extends AFSBaseProvider {
313
+ name;
314
+ description;
315
+ accessMode;
316
+ options;
317
+ storage;
318
+ bucket;
319
+ listCache;
320
+ statCache;
321
+ constructor(options) {
322
+ super();
323
+ const { uri: _uri, token: _token, auth: _auth, ...cleanOptions } = options;
324
+ const parsed = afsgcsOptionsSchema.parse(cleanOptions);
325
+ this.options = {
326
+ ...parsed,
327
+ bucket: parsed.bucket,
328
+ prefix: parsed.prefix ?? "",
329
+ accessMode: parsed.accessMode ?? "readonly"
330
+ };
331
+ this.name = parsed.name ?? parsed.bucket;
332
+ this.description = parsed.description ?? `GCS bucket: ${parsed.bucket}`;
333
+ this.accessMode = this.options.accessMode ?? "readonly";
334
+ this.storage = createGCSClient(this.options);
335
+ this.bucket = this.storage.bucket(parsed.bucket);
336
+ if (parsed.cacheTtl && parsed.cacheTtl > 0) {
337
+ this.listCache = new LRUCache(1e3, parsed.cacheTtl);
338
+ this.statCache = new LRUCache(5e3, parsed.cacheTtl);
339
+ }
340
+ }
341
+ /**
342
+ * Schema for configuration validation
343
+ */
344
+ static schema() {
345
+ return afsgcsOptionsSchema;
346
+ }
347
+ /**
348
+ * Provider manifest for URI-based discovery
349
+ */
350
+ static manifest() {
351
+ return {
352
+ name: "gcs",
353
+ description: "Google Cloud Storage bucket.\n- Browse, read, write, and delete objects; search by key prefix\n- Exec actions: `presign-download`, `presign-upload`, `compose` (concatenate objects), `rewrite` (copy with transform)\n- Path structure: `/{key-prefix}/{object-key}`",
354
+ uriTemplate: "gcs://{bucket}/{prefix+?}",
355
+ category: "cloud-storage",
356
+ schema: z.object({
357
+ bucket: z.string(),
358
+ prefix: z.string().optional(),
359
+ projectId: z.string().optional(),
360
+ keyFilename: z.string().optional()
361
+ }),
362
+ tags: [
363
+ "gcp",
364
+ "gcs",
365
+ "cloud",
366
+ "storage"
367
+ ]
368
+ };
369
+ }
370
+ /**
371
+ * Load from configuration file
372
+ */
373
+ static async load({ basePath, config } = {}) {
374
+ return new AFSGCS(zodParse(afsgcsOptionsSchema, config, { prefix: basePath }));
375
+ }
376
+ /**
377
+ * Build the full GCS key from a path
378
+ */
379
+ buildGCSKey(path) {
380
+ const normalizedPath = path.replace(/^\/+/, "").replace(/\/+$/, "");
381
+ return this.options.prefix ? `${this.options.prefix}/${normalizedPath}` : normalizedPath;
382
+ }
383
+ /**
384
+ * Generate a unique ID for a GCS object
385
+ */
386
+ generateId(key) {
387
+ const cleanKey = key.replace(/^\/+/, "");
388
+ return `gcs://${this.options.bucket}/${cleanKey}`;
389
+ }
390
+ /**
391
+ * Invalidate caches for a given path
392
+ */
393
+ invalidateCache(path) {
394
+ if (this.statCache) {
395
+ const statKey = createCacheKey(this.options.bucket, this.options.prefix ?? "", path);
396
+ this.statCache.delete(statKey);
397
+ }
398
+ if (this.listCache) {
399
+ const parentPath = path.split("/").slice(0, -1).join("/") || "/";
400
+ const listPrefix = createCacheKey(this.options.bucket, this.options.prefix ?? "", parentPath);
401
+ this.listCache.deleteByPrefix(listPrefix);
402
+ }
403
+ }
404
+ /**
405
+ * Clear all caches
406
+ */
407
+ clearCache() {
408
+ this.listCache?.clear();
409
+ this.statCache?.clear();
410
+ }
411
+ async listHandler(ctx) {
412
+ try {
413
+ const normalizedPath = (ctx.params.path ?? "").replace(/^\/+/, "").replace(/\/+$/, "");
414
+ const fullPrefix = this.options.prefix ? normalizedPath ? `${this.options.prefix}/${normalizedPath}/` : `${this.options.prefix}/` : normalizedPath ? `${normalizedPath}/` : "";
415
+ const maxChildren = ctx.options?.limit ?? 1e3;
416
+ if (this.listCache) {
417
+ const cacheKey = createCacheKey(this.options.bucket, this.options.prefix ?? "", normalizedPath, JSON.stringify(ctx.options ?? {}));
418
+ const cached = this.listCache.get(cacheKey);
419
+ if (cached) return cached;
420
+ }
421
+ const result = await this.listWithDelimiter(fullPrefix, normalizedPath, maxChildren);
422
+ if (this.listCache) {
423
+ const cacheKey = createCacheKey(this.options.bucket, this.options.prefix ?? "", normalizedPath, JSON.stringify(ctx.options ?? {}));
424
+ this.listCache.set(cacheKey, result);
425
+ if (this.statCache) for (const entry of result.data) {
426
+ const statKey = createCacheKey(this.options.bucket, this.options.prefix ?? "", entry.path);
427
+ this.statCache.set(statKey, entry);
428
+ }
429
+ }
430
+ return result;
431
+ } catch (error) {
432
+ if (error instanceof AFSNotFoundError) throw error;
433
+ throw mapGCSError(error);
434
+ }
435
+ }
436
+ /**
437
+ * List with delimiter (single level)
438
+ */
439
+ async listWithDelimiter(prefix, basePath, maxChildren) {
440
+ const childEntries = [];
441
+ const bucketName = this.options.bucket;
442
+ const [files, , apiResponse] = await this.bucket.getFiles({
443
+ prefix,
444
+ delimiter: "/",
445
+ maxResults: maxChildren
446
+ });
447
+ const prefixes = apiResponse?.prefixes;
448
+ if (prefixes) for (const dirPrefix of prefixes) {
449
+ if (!dirPrefix) continue;
450
+ const dirName = dirPrefix.slice(prefix.length).replace(/\/$/, "");
451
+ if (!dirName) continue;
452
+ childEntries.push({
453
+ id: this.generateId(dirPrefix),
454
+ path: joinURL("/", basePath, dirName),
455
+ meta: {
456
+ kind: "afs:node",
457
+ childrenCount: -1,
458
+ platformRef: generatePlatformRef(bucketName, dirPrefix, true)
459
+ }
460
+ });
461
+ if (childEntries.length >= maxChildren) break;
462
+ }
463
+ for (const file of files) {
464
+ if (!file.name) continue;
465
+ if (file.name === prefix) continue;
466
+ if (file.name.endsWith("/")) {
467
+ const dirName = file.name.slice(prefix.length).replace(/\/$/, "");
468
+ if (!dirName) continue;
469
+ childEntries.push({
470
+ id: this.generateId(file.name),
471
+ path: joinURL("/", basePath, dirName),
472
+ updatedAt: file.metadata.updated ? new Date(file.metadata.updated) : void 0,
473
+ meta: {
474
+ kind: "afs:node",
475
+ childrenCount: -1,
476
+ platformRef: generatePlatformRef(bucketName, file.name, true)
477
+ }
478
+ });
479
+ if (childEntries.length >= maxChildren) break;
480
+ continue;
481
+ }
482
+ const fileName = file.name.slice(prefix.length);
483
+ if (!fileName || fileName.includes("/")) continue;
484
+ childEntries.push({
485
+ id: this.generateId(file.name),
486
+ path: joinURL("/", basePath, fileName),
487
+ updatedAt: file.metadata.updated ? new Date(file.metadata.updated) : void 0,
488
+ meta: {
489
+ size: file.metadata.size ? Number(file.metadata.size) : void 0,
490
+ contentType: file.metadata.contentType,
491
+ lastModified: file.metadata.updated,
492
+ etag: file.metadata.etag,
493
+ storageClass: file.metadata.storageClass,
494
+ platformRef: generatePlatformRef(bucketName, file.name, false)
495
+ }
496
+ });
497
+ if (childEntries.length >= maxChildren) break;
498
+ }
499
+ const selfPath = basePath ? basePath.startsWith("/") ? basePath : `/${basePath}` : "/";
500
+ if (childEntries.length === 0 && basePath) {
501
+ const key = this.options.prefix ? `${this.options.prefix}/${basePath}` : basePath;
502
+ const [fileExists] = await this.bucket.file(key).exists();
503
+ if (fileExists) return { data: [] };
504
+ const [dirExists] = await this.bucket.file(`${key}/`).exists();
505
+ if (!dirExists) throw new AFSNotFoundError(selfPath);
506
+ }
507
+ return { data: childEntries };
508
+ }
509
+ async listVersionsHandler(ctx) {
510
+ try {
511
+ const normalizedPath = ctx.params.path.replace(/^\/+/, "").replace(/\/+$/, "");
512
+ const key = this.options.prefix ? `${this.options.prefix}/${normalizedPath}` : normalizedPath;
513
+ const [files] = await this.bucket.getFiles({
514
+ prefix: key,
515
+ versions: true
516
+ });
517
+ const entries = [];
518
+ for (const versionFile of files) {
519
+ if (versionFile.name !== key) continue;
520
+ const generation = versionFile.metadata.generation;
521
+ if (!generation) continue;
522
+ const versionPath = joinURL("/", normalizedPath, "@versions", String(generation));
523
+ const isLive = !versionFile.metadata.timeDeleted;
524
+ entries.push({
525
+ id: `${this.generateId(key)}:${generation}`,
526
+ path: versionPath,
527
+ updatedAt: versionFile.metadata.timeCreated ? new Date(versionFile.metadata.timeCreated) : void 0,
528
+ meta: {
529
+ generation,
530
+ isLive,
531
+ timeCreated: versionFile.metadata.timeCreated,
532
+ size: versionFile.metadata.size ? Number(versionFile.metadata.size) : 0,
533
+ etag: versionFile.metadata.etag
534
+ }
535
+ });
536
+ }
537
+ return { data: entries };
538
+ } catch (error) {
539
+ throw mapGCSError(error);
540
+ }
541
+ }
542
+ async readHandler(ctx) {
543
+ try {
544
+ const key = this.buildGCSKey(ctx.params.path);
545
+ const normalizedOutputPath = ctx.path.startsWith("/") ? ctx.path : `/${ctx.path}`;
546
+ const bucketName = this.options.bucket;
547
+ if (!key) {
548
+ const [files$1, , apiResponse$1] = await this.bucket.getFiles({
549
+ prefix: this.options.prefix ? `${this.options.prefix}/` : "",
550
+ delimiter: "/",
551
+ maxResults: 1e3
552
+ });
553
+ const childrenCount = ((apiResponse$1?.prefixes)?.length ?? 0) + files$1.length;
554
+ return {
555
+ id: this.generateId("/"),
556
+ path: "/",
557
+ content: "",
558
+ meta: {
559
+ kind: "afs:node",
560
+ childrenCount,
561
+ platformRef: generatePlatformRef(bucketName, "", true)
562
+ }
563
+ };
564
+ }
565
+ const file = this.bucket.file(key);
566
+ const [exists] = await file.exists();
567
+ if (exists) {
568
+ const [metadata] = await file.getMetadata();
569
+ if (key.endsWith("/") || metadata.contentType === "application/x-directory") return {
570
+ id: this.generateId(key),
571
+ path: normalizedOutputPath,
572
+ content: "",
573
+ updatedAt: metadata.updated ? new Date(metadata.updated) : void 0,
574
+ meta: {
575
+ kind: "afs:node",
576
+ childrenCount: -1,
577
+ platformRef: generatePlatformRef(bucketName, key, true)
578
+ }
579
+ };
580
+ const [content] = await file.download();
581
+ return {
582
+ id: this.generateId(key),
583
+ path: normalizedOutputPath,
584
+ content: content.toString("utf-8"),
585
+ updatedAt: metadata.updated ? new Date(metadata.updated) : void 0,
586
+ meta: {
587
+ size: metadata.size ? Number(metadata.size) : void 0,
588
+ mimeType: metadata.contentType,
589
+ contentType: metadata.contentType,
590
+ lastModified: metadata.updated,
591
+ etag: metadata.etag
592
+ }
593
+ };
594
+ }
595
+ const [files, , apiResponse] = await this.bucket.getFiles({
596
+ prefix: `${key}/`,
597
+ delimiter: "/",
598
+ maxResults: 1e3
599
+ });
600
+ const prefixes = apiResponse?.prefixes;
601
+ if (files.length > 0 || prefixes && prefixes.length > 0) {
602
+ const childrenCount = (prefixes?.length ?? 0) + files.length;
603
+ return {
604
+ id: this.generateId(`${key}/`),
605
+ path: normalizedOutputPath,
606
+ content: "",
607
+ meta: {
608
+ kind: "afs:node",
609
+ childrenCount,
610
+ platformRef: generatePlatformRef(bucketName, key, true)
611
+ }
612
+ };
613
+ }
614
+ const dirMarker = this.bucket.file(`${key}/`);
615
+ const [dirExists] = await dirMarker.exists();
616
+ if (dirExists) {
617
+ const [dirMetadata] = await dirMarker.getMetadata();
618
+ return {
619
+ id: this.generateId(`${key}/`),
620
+ path: normalizedOutputPath,
621
+ content: "",
622
+ updatedAt: dirMetadata.updated ? new Date(dirMetadata.updated) : void 0,
623
+ meta: {
624
+ kind: "afs:node",
625
+ childrenCount: -1,
626
+ platformRef: generatePlatformRef(bucketName, key, true)
627
+ }
628
+ };
629
+ }
630
+ throw new AFSNotFoundError(`/${ctx.params.path}`);
631
+ } catch (error) {
632
+ if (error instanceof AFSError || error instanceof AFSNotFoundError) throw error;
633
+ throw mapGCSError(error);
634
+ }
635
+ }
636
+ async readVersionHandler(ctx) {
637
+ try {
638
+ const normalizedPath = ctx.params.path.replace(/^\/+/, "").replace(/\/+$/, "");
639
+ const key = this.options.prefix ? `${this.options.prefix}/${normalizedPath}` : normalizedPath;
640
+ const generation = ctx.params.generation;
641
+ const file = this.bucket.file(key, { generation: parseInt(generation, 10) });
642
+ const [exists] = await file.exists();
643
+ if (!exists) throw new AFSNotFoundError(`/${normalizedPath}/@versions/${generation}`);
644
+ const [content] = await file.download();
645
+ const [metadata] = await file.getMetadata();
646
+ return {
647
+ id: `${this.generateId(key)}:${generation}`,
648
+ path: ctx.path,
649
+ content: content.toString("utf-8"),
650
+ updatedAt: metadata.timeCreated ? new Date(metadata.timeCreated) : void 0,
651
+ meta: {
652
+ size: metadata.size ? Number(metadata.size) : void 0,
653
+ contentType: metadata.contentType,
654
+ timeCreated: metadata.timeCreated,
655
+ etag: metadata.etag,
656
+ generation: metadata.generation
657
+ }
658
+ };
659
+ } catch (error) {
660
+ if (error instanceof AFSNotFoundError) throw error;
661
+ throw mapGCSError(error);
662
+ }
663
+ }
664
+ async metaHandler(ctx) {
665
+ try {
666
+ const path = ctx.params.path ?? "";
667
+ const normalizedPath = path.replace(/^\/+/, "").replace(/\/+$/, "");
668
+ const key = this.options.prefix ? normalizedPath ? `${this.options.prefix}/${normalizedPath}` : this.options.prefix : normalizedPath;
669
+ const bucketName = this.options.bucket;
670
+ if (!key) {
671
+ const [files$1, , apiResponse] = await this.bucket.getFiles({
672
+ prefix: this.options.prefix ? `${this.options.prefix}/` : "",
673
+ delimiter: "/",
674
+ maxResults: 1e3
675
+ });
676
+ const childrenCount = ((apiResponse?.prefixes)?.length ?? 0) + files$1.length;
677
+ return {
678
+ id: this.generateId("/"),
679
+ path: "/.meta",
680
+ meta: {
681
+ kind: "afs:node",
682
+ childrenCount,
683
+ platformRef: generatePlatformRef(bucketName, "", true)
684
+ }
685
+ };
686
+ }
687
+ if (this.statCache) {
688
+ const cacheKey = createCacheKey(bucketName, this.options.prefix ?? "", path);
689
+ const cached = this.statCache.get(cacheKey);
690
+ if (cached) return {
691
+ id: cached.id,
692
+ path: ctx.path,
693
+ meta: cached.meta
694
+ };
695
+ }
696
+ const file = this.bucket.file(key);
697
+ const [exists] = await file.exists();
698
+ if (exists) {
699
+ const [metadata] = await file.getMetadata();
700
+ if (key.endsWith("/") || metadata.contentType === "application/x-directory") return {
701
+ id: this.generateId(key),
702
+ path: ctx.path,
703
+ updatedAt: metadata.updated ? new Date(metadata.updated) : void 0,
704
+ meta: {
705
+ kind: "afs:node",
706
+ childrenCount: -1,
707
+ platformRef: generatePlatformRef(bucketName, key, true)
708
+ }
709
+ };
710
+ const result = {
711
+ id: this.generateId(key),
712
+ path: ctx.path,
713
+ updatedAt: metadata.updated ? new Date(metadata.updated) : void 0,
714
+ meta: {
715
+ kind: "afs:document",
716
+ size: metadata.size ? Number(metadata.size) : void 0,
717
+ contentType: metadata.contentType,
718
+ lastModified: metadata.updated,
719
+ etag: metadata.etag,
720
+ storageClass: metadata.storageClass,
721
+ generation: metadata.generation,
722
+ metageneration: metadata.metageneration,
723
+ crc32c: metadata.crc32c,
724
+ md5Hash: metadata.md5Hash,
725
+ platformRef: generatePlatformRef(bucketName, key, false)
726
+ }
727
+ };
728
+ if (this.statCache) {
729
+ const cacheKey = createCacheKey(bucketName, this.options.prefix ?? "", path);
730
+ this.statCache.set(cacheKey, result);
731
+ }
732
+ return result;
733
+ }
734
+ const [files] = await this.bucket.getFiles({
735
+ prefix: `${key}/`,
736
+ maxResults: 1
737
+ });
738
+ if (files.length > 0) return {
739
+ id: this.generateId(`${key}/`),
740
+ path: ctx.path,
741
+ meta: {
742
+ kind: "afs:node",
743
+ childrenCount: -1,
744
+ platformRef: generatePlatformRef(bucketName, key, true)
745
+ }
746
+ };
747
+ const dirMarker = this.bucket.file(`${key}/`);
748
+ const [dirExists] = await dirMarker.exists();
749
+ if (dirExists) {
750
+ const [dirMetadata] = await dirMarker.getMetadata();
751
+ return {
752
+ id: this.generateId(`${key}/`),
753
+ path: ctx.path,
754
+ updatedAt: dirMetadata.updated ? new Date(dirMetadata.updated) : void 0,
755
+ meta: {
756
+ kind: "afs:node",
757
+ childrenCount: -1,
758
+ platformRef: generatePlatformRef(bucketName, key, true)
759
+ }
760
+ };
761
+ }
762
+ throw new AFSNotFoundError(path.startsWith("/") ? path : `/${path}`);
763
+ } catch (error) {
764
+ if (error instanceof AFSNotFoundError) throw error;
765
+ throw mapGCSError(error);
766
+ }
767
+ }
768
+ async statHandler(ctx) {
769
+ const metaEntry = await this.metaHandler({
770
+ ...ctx,
771
+ path: ctx.path.endsWith("/.meta") ? ctx.path : `${ctx.path}/.meta`
772
+ });
773
+ const pathSegments = ctx.path.split("/").filter(Boolean);
774
+ return { data: {
775
+ id: pathSegments.length > 0 ? pathSegments[pathSegments.length - 1] : "/",
776
+ path: ctx.path,
777
+ meta: metaEntry.meta
778
+ } };
779
+ }
780
+ async writeHandler(ctx, payload) {
781
+ try {
782
+ const key = this.buildGCSKey(ctx.params.path);
783
+ const file = this.bucket.file(key);
784
+ let content;
785
+ if (typeof payload.content === "string") content = payload.content;
786
+ else if (Buffer.isBuffer(payload.content)) content = payload.content;
787
+ else if (payload.content !== void 0) content = JSON.stringify(payload.content);
788
+ else content = "";
789
+ const saveOptions = { validation: false };
790
+ if (payload.meta?.mimeType || payload.meta?.contentType) saveOptions.contentType = payload.meta.mimeType ?? payload.meta.contentType;
791
+ await file.save(content, saveOptions);
792
+ const [metadata] = await file.getMetadata();
793
+ const normalizedOutputPath = ctx.path.startsWith("/") ? ctx.path : `/${ctx.path}`;
794
+ this.invalidateCache(ctx.params.path);
795
+ return { data: {
796
+ id: this.generateId(key),
797
+ path: normalizedOutputPath,
798
+ content: payload.content,
799
+ updatedAt: metadata.updated ? new Date(metadata.updated) : /* @__PURE__ */ new Date(),
800
+ meta: {
801
+ size: metadata.size ? Number(metadata.size) : 0,
802
+ etag: metadata.etag,
803
+ generation: metadata.generation,
804
+ ...payload.meta
805
+ }
806
+ } };
807
+ } catch (error) {
808
+ throw mapGCSError(error);
809
+ }
810
+ }
811
+ async deleteHandler(ctx) {
812
+ try {
813
+ const key = this.buildGCSKey(ctx.params.path);
814
+ const file = this.bucket.file(key);
815
+ const [exists] = await file.exists();
816
+ if (exists) {
817
+ await file.delete();
818
+ this.invalidateCache(ctx.params.path);
819
+ return { message: `Successfully deleted: ${ctx.params.path}` };
820
+ }
821
+ const [files] = await this.bucket.getFiles({
822
+ prefix: `${key}/`,
823
+ maxResults: 1
824
+ });
825
+ if (files.length === 0) throw new AFSNotFoundError(`/${ctx.params.path}`);
826
+ throw new AFSError(`Cannot delete non-empty directory: ${ctx.params.path}. Use recursive option.`, "AFS_INVALID_OPERATION");
827
+ } catch (error) {
828
+ if (error instanceof AFSError || error instanceof AFSNotFoundError) throw error;
829
+ throw mapGCSError(error);
830
+ }
831
+ }
832
+ async listActionsHandler(ctx) {
833
+ return { data: [
834
+ {
835
+ id: "presign-download",
836
+ path: joinURL(ctx.path, "presign-download"),
837
+ summary: "Generate signed download URL",
838
+ meta: {
839
+ kind: "afs:executable",
840
+ kinds: ["afs:executable", "afs:node"],
841
+ inputSchema: {
842
+ type: "object",
843
+ properties: { expiresIn: {
844
+ type: "number",
845
+ description: "Expiration in seconds (default: 3600, max: 604800)"
846
+ } }
847
+ }
848
+ }
849
+ },
850
+ {
851
+ id: "presign-upload",
852
+ path: joinURL(ctx.path, "presign-upload"),
853
+ summary: "Generate signed upload URL",
854
+ meta: {
855
+ kind: "afs:executable",
856
+ kinds: ["afs:executable", "afs:node"],
857
+ inputSchema: {
858
+ type: "object",
859
+ properties: {
860
+ expiresIn: {
861
+ type: "number",
862
+ description: "Expiration in seconds"
863
+ },
864
+ contentType: {
865
+ type: "string",
866
+ description: "Content-Type for upload"
867
+ }
868
+ }
869
+ }
870
+ }
871
+ },
872
+ {
873
+ id: "compose",
874
+ path: joinURL(ctx.path, "compose"),
875
+ summary: "Compose multiple objects into one",
876
+ meta: {
877
+ kind: "afs:executable",
878
+ kinds: ["afs:executable", "afs:node"],
879
+ inputSchema: {
880
+ type: "object",
881
+ properties: { sources: {
882
+ type: "array",
883
+ description: "Array of source paths (2-32)",
884
+ items: { type: "string" }
885
+ } },
886
+ required: ["sources"]
887
+ }
888
+ }
889
+ },
890
+ {
891
+ id: "rewrite",
892
+ path: joinURL(ctx.path, "rewrite"),
893
+ summary: "Rewrite object to new location",
894
+ meta: {
895
+ kind: "afs:executable",
896
+ kinds: ["afs:executable", "afs:node"],
897
+ inputSchema: {
898
+ type: "object",
899
+ properties: {
900
+ destination: {
901
+ type: "string",
902
+ description: "Destination path"
903
+ },
904
+ storageClass: {
905
+ type: "string",
906
+ description: "Target storage class"
907
+ },
908
+ contentType: {
909
+ type: "string",
910
+ description: "Override content type"
911
+ }
912
+ },
913
+ required: ["destination"]
914
+ }
915
+ }
916
+ }
917
+ ] };
918
+ }
919
+ async signDownloadActionHandler(ctx, args) {
920
+ const normalizedPath = ctx.params.path.replace(/^\/+/, "").replace(/\/+$/, "");
921
+ const key = this.options.prefix ? `${this.options.prefix}/${normalizedPath}` : normalizedPath;
922
+ const file = this.bucket.file(key);
923
+ let expiresIn = args.expiresIn ?? DEFAULT_EXPIRES_IN;
924
+ if (expiresIn > MAX_EXPIRES_IN) expiresIn = MAX_EXPIRES_IN;
925
+ if (expiresIn < 1) expiresIn = 1;
926
+ const expiresAt = Date.now() + expiresIn * 1e3;
927
+ const [url] = await file.getSignedUrl({
928
+ action: "read",
929
+ expires: expiresAt
930
+ });
931
+ return {
932
+ success: true,
933
+ data: {
934
+ url,
935
+ expiresAt: new Date(expiresAt).toISOString()
936
+ }
937
+ };
938
+ }
939
+ async signUploadActionHandler(ctx, args) {
940
+ const normalizedPath = ctx.params.path.replace(/^\/+/, "").replace(/\/+$/, "");
941
+ const key = this.options.prefix ? `${this.options.prefix}/${normalizedPath}` : normalizedPath;
942
+ const file = this.bucket.file(key);
943
+ let expiresIn = args.expiresIn ?? DEFAULT_EXPIRES_IN;
944
+ if (expiresIn > MAX_EXPIRES_IN) expiresIn = MAX_EXPIRES_IN;
945
+ if (expiresIn < 1) expiresIn = 1;
946
+ const expiresAt = Date.now() + expiresIn * 1e3;
947
+ const contentType = args.contentType ?? "application/octet-stream";
948
+ const [url] = await file.getSignedUrl({
949
+ action: "write",
950
+ expires: expiresAt,
951
+ contentType
952
+ });
953
+ return {
954
+ success: true,
955
+ data: {
956
+ url,
957
+ expiresAt: new Date(expiresAt).toISOString()
958
+ }
959
+ };
960
+ }
961
+ async composeActionHandler(ctx, args) {
962
+ const normalizedPath = ctx.params.path.replace(/^\/+/, "").replace(/\/+$/, "");
963
+ const destinationKey = this.options.prefix ? `${this.options.prefix}/${normalizedPath}` : normalizedPath;
964
+ const sources = args.sources;
965
+ if (!sources || !Array.isArray(sources) || sources.length < 2) throw new AFSError("compose requires at least 2 source objects", "AFS_INVALID_ARGUMENT");
966
+ if (sources.length > MAX_COMPOSE_SOURCES) throw new AFSError(`compose supports maximum ${MAX_COMPOSE_SOURCES} sources`, "AFS_INVALID_ARGUMENT");
967
+ const sourceFiles = sources.map((source) => {
968
+ const sourcePath = source.replace(/^\/+/, "").replace(/\/+$/, "");
969
+ const sourceKey = this.options.prefix ? `${this.options.prefix}/${sourcePath}` : sourcePath;
970
+ return this.bucket.file(sourceKey);
971
+ });
972
+ const destinationFile = this.bucket.file(destinationKey);
973
+ await this.bucket.combine(sourceFiles, destinationFile);
974
+ const [metadata] = await destinationFile.getMetadata();
975
+ this.invalidateCache(ctx.params.path);
976
+ return {
977
+ success: true,
978
+ data: {
979
+ generation: metadata.generation,
980
+ size: metadata.size ? Number(metadata.size) : 0,
981
+ etag: metadata.etag
982
+ }
983
+ };
984
+ }
985
+ async rewriteActionHandler(ctx, args) {
986
+ const normalizedPath = ctx.params.path.replace(/^\/+/, "").replace(/\/+$/, "");
987
+ const sourceKey = this.options.prefix ? `${this.options.prefix}/${normalizedPath}` : normalizedPath;
988
+ const destination = args.destination;
989
+ if (!destination) throw new AFSError("rewrite requires destination path", "AFS_INVALID_ARGUMENT");
990
+ const destPath = destination.replace(/^\/+/, "").replace(/\/+$/, "");
991
+ const destKey = this.options.prefix ? `${this.options.prefix}/${destPath}` : destPath;
992
+ const sourceFile = this.bucket.file(sourceKey);
993
+ const destFile = this.bucket.file(destKey);
994
+ const copyOptions = {};
995
+ if (args.storageClass) copyOptions.metadata = { storageClass: args.storageClass };
996
+ if (args.contentType) copyOptions.contentType = args.contentType;
997
+ await sourceFile.copy(destFile, copyOptions);
998
+ const [metadata] = await destFile.getMetadata();
999
+ this.invalidateCache(destPath);
1000
+ return {
1001
+ success: true,
1002
+ data: {
1003
+ destination: `/${destPath}`,
1004
+ generation: metadata.generation,
1005
+ size: metadata.size ? Number(metadata.size) : 0,
1006
+ etag: metadata.etag,
1007
+ storageClass: metadata.storageClass
1008
+ }
1009
+ };
1010
+ }
1011
+ async explainHandler(ctx) {
1012
+ const normalizedPath = (ctx.params.path ?? "").replace(/^\/+/, "").replace(/\/+$/, "");
1013
+ if (!normalizedPath) {
1014
+ const [files$1, , response$1] = await this.bucket.getFiles({
1015
+ prefix: this.options.prefix ? `${this.options.prefix}/` : void 0,
1016
+ delimiter: "/",
1017
+ maxResults: 1e3
1018
+ });
1019
+ const objectCount$1 = files$1.length;
1020
+ const prefixCount$1 = response$1?.prefixes?.length ?? 0;
1021
+ const lines$1 = [];
1022
+ lines$1.push(`# ${this.options.bucket}`);
1023
+ lines$1.push("");
1024
+ lines$1.push(`- **Type**: GCS Bucket`);
1025
+ lines$1.push(`- **Bucket**: ${this.options.bucket}`);
1026
+ if (this.options.prefix) lines$1.push(`- **Prefix**: ${this.options.prefix}`);
1027
+ if (this.options.projectId) lines$1.push(`- **Project**: ${this.options.projectId}`);
1028
+ lines$1.push(`- **Access Mode**: ${this.accessMode}`);
1029
+ lines$1.push(`- **Top-level Objects**: ${objectCount$1}`);
1030
+ lines$1.push(`- **Top-level Prefixes**: ${prefixCount$1}`);
1031
+ return {
1032
+ format: "markdown",
1033
+ content: lines$1.join("\n")
1034
+ };
1035
+ }
1036
+ const key = this.buildGCSKey(normalizedPath);
1037
+ const file = this.bucket.file(key);
1038
+ const [exists] = await file.exists();
1039
+ if (exists) {
1040
+ const [metadata] = await file.getMetadata();
1041
+ const lines$1 = [];
1042
+ lines$1.push(`# ${normalizedPath}`);
1043
+ lines$1.push("");
1044
+ lines$1.push(`- **Type**: Object`);
1045
+ lines$1.push(`- **Key**: ${key}`);
1046
+ lines$1.push(`- **Size**: ${metadata.size ?? 0} bytes`);
1047
+ if (metadata.contentType) lines$1.push(`- **Content-Type**: ${metadata.contentType}`);
1048
+ if (metadata.storageClass) lines$1.push(`- **Storage Class**: ${metadata.storageClass}`);
1049
+ if (metadata.updated) lines$1.push(`- **Last Modified**: ${metadata.updated}`);
1050
+ if (metadata.etag) lines$1.push(`- **ETag**: ${metadata.etag}`);
1051
+ if (metadata.generation) lines$1.push(`- **Generation**: ${metadata.generation}`);
1052
+ return {
1053
+ format: "markdown",
1054
+ content: lines$1.join("\n")
1055
+ };
1056
+ }
1057
+ const prefix = key.endsWith("/") ? key : `${key}/`;
1058
+ const [files, , response] = await this.bucket.getFiles({
1059
+ prefix,
1060
+ delimiter: "/",
1061
+ maxResults: 1e3
1062
+ });
1063
+ const objectCount = files.length;
1064
+ const prefixCount = response?.prefixes?.length ?? 0;
1065
+ if (objectCount === 0 && prefixCount === 0) throw new AFSNotFoundError(`/${normalizedPath}`, `Path not found: ${normalizedPath}`);
1066
+ const lines = [];
1067
+ lines.push(`# ${normalizedPath}/`);
1068
+ lines.push("");
1069
+ lines.push(`- **Type**: Prefix (directory)`);
1070
+ lines.push(`- **Prefix**: ${prefix}`);
1071
+ lines.push(`- **Objects**: ${objectCount}`);
1072
+ lines.push(`- **Sub-prefixes**: ${prefixCount}`);
1073
+ return {
1074
+ format: "markdown",
1075
+ content: lines.join("\n")
1076
+ };
1077
+ }
1078
+ async searchHandler(ctx, query) {
1079
+ const { minimatch } = await import("minimatch");
1080
+ const normalizedPath = (ctx.params.path ?? "").replace(/^\/+/, "").replace(/\/+$/, "");
1081
+ const prefix = normalizedPath ? this.options.prefix ? `${this.options.prefix}/${normalizedPath}/` : `${normalizedPath}/` : this.options.prefix ? `${this.options.prefix}/` : "";
1082
+ const [files] = await this.bucket.getFiles({ prefix: prefix || void 0 });
1083
+ const results = [];
1084
+ for (const file of files) {
1085
+ const relativePath = prefix ? file.name.slice(prefix.length) : file.name;
1086
+ if (!relativePath) continue;
1087
+ if (minimatch(relativePath, query)) {
1088
+ const displayPath = joinURL("/", normalizedPath, relativePath);
1089
+ results.push({
1090
+ id: this.generateId(file.name),
1091
+ path: displayPath,
1092
+ meta: {
1093
+ size: file.metadata?.size ? Number(file.metadata.size) : void 0,
1094
+ contentType: file.metadata?.contentType,
1095
+ lastModified: file.metadata?.updated
1096
+ }
1097
+ });
1098
+ }
1099
+ }
1100
+ return { data: results };
1101
+ }
1102
+ async readCapabilities(_ctx) {
1103
+ const capabilities = {
1104
+ schemaVersion: 1,
1105
+ provider: "gcs",
1106
+ description: `GCS bucket: ${this.options.bucket}`,
1107
+ tools: [],
1108
+ operations: this.getOperationsDeclaration(),
1109
+ actions: [{
1110
+ description: "GCS object actions",
1111
+ catalog: [
1112
+ {
1113
+ name: "presign-download",
1114
+ description: "Generate a signed download URL",
1115
+ inputSchema: {
1116
+ type: "object",
1117
+ properties: { expiresIn: {
1118
+ type: "number",
1119
+ description: "Expiration in seconds (default: 3600, max: 604800)"
1120
+ } }
1121
+ }
1122
+ },
1123
+ {
1124
+ name: "presign-upload",
1125
+ description: "Generate a signed upload URL",
1126
+ inputSchema: {
1127
+ type: "object",
1128
+ properties: {
1129
+ expiresIn: {
1130
+ type: "number",
1131
+ description: "Expiration in seconds"
1132
+ },
1133
+ contentType: {
1134
+ type: "string",
1135
+ description: "Content-Type for upload"
1136
+ }
1137
+ }
1138
+ }
1139
+ },
1140
+ {
1141
+ name: "compose",
1142
+ description: "Compose multiple objects into one (max 32 sources)",
1143
+ inputSchema: {
1144
+ type: "object",
1145
+ properties: { sources: {
1146
+ type: "array",
1147
+ description: "Array of source paths (2-32)",
1148
+ items: { type: "string" }
1149
+ } },
1150
+ required: ["sources"]
1151
+ }
1152
+ },
1153
+ {
1154
+ name: "rewrite",
1155
+ description: "Copy/rewrite object to a new location",
1156
+ inputSchema: {
1157
+ type: "object",
1158
+ properties: {
1159
+ destination: {
1160
+ type: "string",
1161
+ description: "Destination path"
1162
+ },
1163
+ storageClass: {
1164
+ type: "string",
1165
+ description: "Target storage class"
1166
+ },
1167
+ contentType: {
1168
+ type: "string",
1169
+ description: "Override content type"
1170
+ }
1171
+ },
1172
+ required: ["destination"]
1173
+ }
1174
+ }
1175
+ ],
1176
+ discovery: { pathTemplate: "/{path}/.actions" }
1177
+ }]
1178
+ };
1179
+ return {
1180
+ id: ".capabilities",
1181
+ path: "/.meta/.capabilities",
1182
+ content: JSON.stringify(capabilities, null, 2),
1183
+ meta: { kind: "afs:capabilities" }
1184
+ };
1185
+ }
1186
+ };
1187
+ __decorate([List("/"), List("/:path*")], AFSGCS.prototype, "listHandler", null);
1188
+ __decorate([List("/:path*/@versions")], AFSGCS.prototype, "listVersionsHandler", null);
1189
+ __decorate([Read("/:path*")], AFSGCS.prototype, "readHandler", null);
1190
+ __decorate([Read("/:path*/@versions/:generation")], AFSGCS.prototype, "readVersionHandler", null);
1191
+ __decorate([Meta("/"), Meta("/:path*")], AFSGCS.prototype, "metaHandler", null);
1192
+ __decorate([Stat("/"), Stat("/:path*")], AFSGCS.prototype, "statHandler", null);
1193
+ __decorate([Write("/:path*")], AFSGCS.prototype, "writeHandler", null);
1194
+ __decorate([Delete("/:path*")], AFSGCS.prototype, "deleteHandler", null);
1195
+ __decorate([Actions("/:path*")], AFSGCS.prototype, "listActionsHandler", null);
1196
+ __decorate([Actions.Exec("/:path*", "presign-download")], AFSGCS.prototype, "signDownloadActionHandler", null);
1197
+ __decorate([Actions.Exec("/:path*", "presign-upload")], AFSGCS.prototype, "signUploadActionHandler", null);
1198
+ __decorate([Actions.Exec("/:path*", "compose")], AFSGCS.prototype, "composeActionHandler", null);
1199
+ __decorate([Actions.Exec("/:path*", "rewrite")], AFSGCS.prototype, "rewriteActionHandler", null);
1200
+ __decorate([Explain("/"), Explain("/:path*")], AFSGCS.prototype, "explainHandler", null);
1201
+ __decorate([Search("/"), Search("/:path*")], AFSGCS.prototype, "searchHandler", null);
1202
+ __decorate([Read("/.meta/.capabilities")], AFSGCS.prototype, "readCapabilities", null);
1203
+
1204
+ //#endregion
1205
+ export { AFSGCS, AFSGCS as default };
1206
+ //# sourceMappingURL=index.mjs.map