@aigne/afs-s3 1.11.0-beta.6 → 1.11.0-beta.7
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.d.mts +13 -64
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +258 -146
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AFSAccessMode, AFSBaseProvider, AFSDeleteResult, AFSEntry, AFSExecResult, AFSListResult, AFSModuleLoadParams, AFSStatResult, AFSWriteEntryPayload, AFSWriteResult, RouteContext } from "@aigne/afs";
|
|
1
|
+
import { AFSAccessMode, AFSBaseProvider, AFSDeleteResult, AFSEntry, AFSExecResult, AFSExplainResult, AFSListResult, AFSModuleLoadParams, AFSSearchResult, AFSStatResult, AFSWriteEntryPayload, AFSWriteResult, RouteContext } from "@aigne/afs";
|
|
2
2
|
import { S3Client } from "@aws-sdk/client-s3";
|
|
3
3
|
import * as zod0 from "zod";
|
|
4
4
|
import { z } from "zod";
|
|
@@ -157,7 +157,10 @@ declare class AFSS3 extends AFSBaseProvider {
|
|
|
157
157
|
/**
|
|
158
158
|
* Load from configuration file
|
|
159
159
|
*/
|
|
160
|
-
static load(
|
|
160
|
+
static load({
|
|
161
|
+
basePath,
|
|
162
|
+
config
|
|
163
|
+
}?: AFSModuleLoadParams): Promise<AFSS3>;
|
|
161
164
|
/**
|
|
162
165
|
* Build the full S3 key from a path
|
|
163
166
|
*/
|
|
@@ -215,68 +218,14 @@ declare class AFSS3 extends AFSBaseProvider {
|
|
|
215
218
|
presignUploadActionHandler(ctx: RouteContext<{
|
|
216
219
|
path: string;
|
|
217
220
|
}>, args: Record<string, unknown>): Promise<AFSExecResult>;
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Generate a presigned URL for uploading an object
|
|
227
|
-
* @deprecated Use action /.actions/presign-upload instead
|
|
228
|
-
*/
|
|
229
|
-
getPresignedUploadUrl(path: string, options?: {
|
|
230
|
-
expiresIn?: number;
|
|
231
|
-
contentType?: string;
|
|
232
|
-
}): Promise<string>;
|
|
233
|
-
/**
|
|
234
|
-
* List all versions of an object
|
|
235
|
-
* @deprecated Use list on /:path/@versions instead
|
|
236
|
-
*/
|
|
237
|
-
listVersions(path: string): Promise<Array<{
|
|
238
|
-
versionId: string;
|
|
239
|
-
isLatest: boolean;
|
|
240
|
-
lastModified?: Date;
|
|
241
|
-
size: number;
|
|
242
|
-
etag?: string;
|
|
243
|
-
}>>;
|
|
244
|
-
/**
|
|
245
|
-
* Read a specific version of an object
|
|
246
|
-
* @deprecated Use read on /:path/@versions/:versionId instead
|
|
247
|
-
*/
|
|
248
|
-
readVersion(path: string, versionId: string): Promise<{
|
|
249
|
-
content: string;
|
|
250
|
-
metadata: Record<string, unknown>;
|
|
251
|
-
}>;
|
|
252
|
-
/**
|
|
253
|
-
* Run a SQL-like query on a CSV/JSON/Parquet file (S3 Select)
|
|
254
|
-
* @deprecated Use action /.actions/select instead
|
|
255
|
-
*/
|
|
256
|
-
select(path: string, query: string, options?: {
|
|
257
|
-
inputFormat?: "CSV" | "JSON" | "Parquet";
|
|
258
|
-
csv?: {
|
|
259
|
-
fieldDelimiter?: string;
|
|
260
|
-
recordDelimiter?: string;
|
|
261
|
-
fileHeaderInfo?: "USE" | "IGNORE" | "NONE";
|
|
262
|
-
};
|
|
263
|
-
json?: {
|
|
264
|
-
type?: "DOCUMENT" | "LINES";
|
|
265
|
-
};
|
|
266
|
-
}): Promise<{
|
|
267
|
-
records: unknown[];
|
|
268
|
-
stats?: {
|
|
269
|
-
bytesScanned: number;
|
|
270
|
-
bytesProcessed: number;
|
|
271
|
-
bytesReturned: number;
|
|
272
|
-
};
|
|
273
|
-
}>;
|
|
274
|
-
/**
|
|
275
|
-
* Create a directory marker
|
|
276
|
-
* @deprecated Use write with empty content instead
|
|
277
|
-
*/
|
|
278
|
-
mkdir(path: string): Promise<void>;
|
|
221
|
+
explainHandler(ctx: RouteContext<{
|
|
222
|
+
path?: string;
|
|
223
|
+
}>): Promise<AFSExplainResult>;
|
|
224
|
+
searchHandler(ctx: RouteContext<{
|
|
225
|
+
path?: string;
|
|
226
|
+
}>, query: string): Promise<AFSSearchResult>;
|
|
227
|
+
readCapabilities(_ctx: RouteContext): Promise<AFSEntry>;
|
|
279
228
|
}
|
|
280
229
|
//#endregion
|
|
281
|
-
export { AFSS3, type AFSS3Options, type ParsedS3Uri };
|
|
230
|
+
export { AFSS3, AFSS3 as default, type AFSS3Options, type ParsedS3Uri };
|
|
282
231
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/s3-afs.ts"],"mappings":";;;;;;;AAWA;;UAAiB,YAAA;EA0CE;EAxCjB,IAAA;EAGA;EAAA,WAAA;EAMA;EAHA,MAAA;EASA;EANA,MAAA;EAYA;EATA,MAAA;EAeA;EAZA,UAAA;EAcE;EAXF,QAAA;EAgBA;EAbA,cAAA;EAmBS;EAhBT,OAAA;EAgBiB;EAbjB,WAAA;IACE,WAAA;IACA,eAAA;IACA,YAAA;EAAA;;EAIF,QAAA;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/s3-afs.ts"],"mappings":";;;;;;;AAWA;;UAAiB,YAAA;EA0CE;EAxCjB,IAAA;EAGA;EAAA,WAAA;EAMA;EAHA,MAAA;EASA;EANA,MAAA;EAYA;EATA,MAAA;EAeA;EAZA,UAAA;EAcE;EAXF,QAAA;EAgBA;EAbA,cAAA;EAmBS;EAhBT,OAAA;EAgBiB;EAbjB,WAAA;IACE,WAAA;IACA,eAAA;IACA,YAAA;EAAA;;EAIF,QAAA;;AC0CF;;;EDpCE,MAAA,GAAS,QAAA;AAAA;;;;UAyCM,WAAA;EACf,MAAA;EACA,MAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;AAFF;;;;cCLa,KAAA,SAAc,eAAA;EAAA,SACP,IAAA;EAAA,SACA,WAAA;EAAA,SACA,UAAA,EAAY,aAAA;EAAA,QAEtB,OAAA;EAAA,QACA,MAAA;EAAA,QACA,SAAA;EAAA,QACA,SAAA;cAEI,OAAA,EAAS,YAAA;EAAY;;;EAAA,OAiC1B,MAAA,CAAA,GAAM,IAAA,CAAA,UAAA,MAAA,SAAA;2CAjCoB,IAAA,CAAA,UAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+jC1B;;;EAAA,OAvhCM,IAAA,CAAA;IAAO,QAAA;IAAU;EAAA,IAAU,mBAAA,GAA2B,OAAA,CAAQ,KAAA;EAklCvB;;;EAAA,QAxkC5C,UAAA;EA5DgC;;;EAAA,QAoEhC,UAAA;EAjEU;;;EAAA,QAyEV,eAAA;EArEA;;;EAsFR,UAAA,CAAA;EASM,WAAA,CAAY,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,KAAmB,OAAA,CAAQ,aAAA;EA3DpD;;;EAAA,QAiIC,iBAAA;EAkIR,mBAAA,CAAoB,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,KAAkB,OAAA,CAAQ,aAAA;EA4ClE,WAAA,CAAY,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,KAAkB,OAAA,CAAQ,QAAA;EA8J1D,kBAAA,CACJ,GAAA,EAAK,YAAA;IAAe,IAAA;IAAc,SAAA;EAAA,KACjC,OAAA,CAAQ,QAAA;EAqCL,WAAA,CAAY,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,KAAmB,OAAA,CAAQ,QAAA;EAyK3D,WAAA,CAAY,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,KAAmB,OAAA,CAAQ,aAAA;EAuB3D,YAAA,CACJ,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,IACpB,OAAA,EAAS,oBAAA,GACR,OAAA,CAAQ,cAAA;EA2EL,aAAA,CAAc,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,KAAkB,OAAA,CAAQ,eAAA;EA0C5D,kBAAA,CAAmB,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,KAAkB,OAAA,CAAQ,aAAA;EA4DjE,mBAAA,CACJ,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,IACpB,IAAA,EAAM,MAAA,oBACL,OAAA,CAAQ,aAAA;EAwBL,4BAAA,CACJ,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,IACpB,IAAA,EAAM,MAAA,oBACL,OAAA,CAAQ,aAAA;EA0BL,0BAAA,CACJ,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,IACpB,IAAA,EAAM,MAAA,oBACL,OAAA,CAAQ,aAAA;EA8BL,cAAA,CAAe,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,KAAmB,OAAA,CAAQ,gBAAA;EA4F9D,aAAA,CACJ,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,IACpB,KAAA,WACC,OAAA,CAAQ,eAAA;EAyDL,gBAAA,CAAiB,IAAA,EAAM,YAAA,GAAe,OAAA,CAAQ,QAAA;AAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { AFSBaseProvider, AFSError, AFSNotFoundError, Actions, Delete, List, Meta, Read, Stat, Write } from "@aigne/afs";
|
|
1
|
+
import { AFSBaseProvider, AFSError, AFSNotFoundError, Actions, Delete, Explain, List, Meta, Read, Search, Stat, Write } from "@aigne/afs";
|
|
2
2
|
import { camelize, optionalize, zodParse } from "@aigne/afs/utils/zod";
|
|
3
3
|
import { AbortMultipartUploadCommand, CompleteMultipartUploadCommand, CreateMultipartUploadCommand, DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, ListObjectVersionsCommand, ListObjectsV2Command, PutObjectCommand, S3Client, SelectObjectContentCommand, UploadPartCommand } from "@aws-sdk/client-s3";
|
|
4
4
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
5
|
+
import { joinURL } from "ufo";
|
|
5
6
|
import { z } from "zod";
|
|
6
7
|
|
|
7
8
|
//#region src/cache.ts
|
|
@@ -549,8 +550,8 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
549
550
|
/**
|
|
550
551
|
* Load from configuration file
|
|
551
552
|
*/
|
|
552
|
-
static async load(
|
|
553
|
-
return new AFSS3(zodParse(afss3OptionsSchema,
|
|
553
|
+
static async load({ basePath, config } = {}) {
|
|
554
|
+
return new AFSS3(zodParse(afss3OptionsSchema, config, { prefix: basePath }));
|
|
554
555
|
}
|
|
555
556
|
/**
|
|
556
557
|
* Build the full S3 key from a path
|
|
@@ -563,7 +564,8 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
563
564
|
* Generate a unique ID for an S3 object
|
|
564
565
|
*/
|
|
565
566
|
generateId(key) {
|
|
566
|
-
|
|
567
|
+
const cleanKey = key.replace(/^\/+/, "");
|
|
568
|
+
return `s3://${this.options.bucket}/${cleanKey}`;
|
|
567
569
|
}
|
|
568
570
|
/**
|
|
569
571
|
* Invalidate caches for a given path
|
|
@@ -630,13 +632,12 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
630
632
|
if (!commonPrefix.Prefix) continue;
|
|
631
633
|
const dirName = commonPrefix.Prefix.slice(prefix.length).replace(/\/$/, "");
|
|
632
634
|
if (!dirName) continue;
|
|
633
|
-
const
|
|
634
|
-
const normalizedPath = entryPath.startsWith("/") ? entryPath : `/${entryPath}`;
|
|
635
|
+
const normalizedPath = joinURL("/", basePath, dirName);
|
|
635
636
|
childEntries.push({
|
|
636
637
|
id: this.generateId(commonPrefix.Prefix),
|
|
637
638
|
path: normalizedPath,
|
|
638
|
-
|
|
639
|
-
childrenCount:
|
|
639
|
+
meta: {
|
|
640
|
+
childrenCount: -1,
|
|
640
641
|
platformRef: generatePlatformRef(this.options.bucket, this.options.region, commonPrefix.Prefix, true)
|
|
641
642
|
}
|
|
642
643
|
});
|
|
@@ -647,13 +648,12 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
647
648
|
if (object.Key === prefix) continue;
|
|
648
649
|
const fileName = object.Key.slice(prefix.length);
|
|
649
650
|
if (!fileName || fileName.includes("/")) continue;
|
|
650
|
-
const
|
|
651
|
-
const normalizedPath = entryPath.startsWith("/") ? entryPath : `/${entryPath}`;
|
|
651
|
+
const normalizedPath = joinURL("/", basePath, fileName);
|
|
652
652
|
childEntries.push({
|
|
653
653
|
id: this.generateId(object.Key),
|
|
654
654
|
path: normalizedPath,
|
|
655
655
|
updatedAt: object.LastModified,
|
|
656
|
-
|
|
656
|
+
meta: {
|
|
657
657
|
size: object.Size,
|
|
658
658
|
lastModified: object.LastModified?.toISOString(),
|
|
659
659
|
etag: object.ETag?.replace(/"/g, ""),
|
|
@@ -665,7 +665,7 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
665
665
|
}
|
|
666
666
|
continuationToken = response.IsTruncated ? response.NextContinuationToken : void 0;
|
|
667
667
|
} while (continuationToken && childEntries.length < maxChildren);
|
|
668
|
-
const selfPath = basePath ?
|
|
668
|
+
const selfPath = basePath ? joinURL("/", basePath) : "/";
|
|
669
669
|
if (childEntries.length === 0 && basePath) {
|
|
670
670
|
const key = this.options.prefix ? `${this.options.prefix}/${basePath}` : basePath;
|
|
671
671
|
try {
|
|
@@ -673,20 +673,8 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
673
673
|
Bucket: this.options.bucket,
|
|
674
674
|
Key: key
|
|
675
675
|
});
|
|
676
|
-
|
|
677
|
-
return { data: [
|
|
678
|
-
id: this.generateId(key),
|
|
679
|
-
path: selfPath,
|
|
680
|
-
updatedAt: headResponse.LastModified,
|
|
681
|
-
metadata: {
|
|
682
|
-
size: headResponse.ContentLength,
|
|
683
|
-
contentType: headResponse.ContentType,
|
|
684
|
-
lastModified: headResponse.LastModified?.toISOString(),
|
|
685
|
-
etag: headResponse.ETag?.replace(/"/g, ""),
|
|
686
|
-
childrenCount: 0,
|
|
687
|
-
platformRef: generatePlatformRef(this.options.bucket, this.options.region, key, false)
|
|
688
|
-
}
|
|
689
|
-
}] };
|
|
676
|
+
await this.client.send(headCommand);
|
|
677
|
+
return { data: [] };
|
|
690
678
|
} catch (error) {
|
|
691
679
|
if (error?.name === "NotFound" || error?.$metadata?.httpStatusCode === 404) try {
|
|
692
680
|
const dirMarkerCommand = new HeadObjectCommand({
|
|
@@ -700,15 +688,7 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
700
688
|
else throw error;
|
|
701
689
|
}
|
|
702
690
|
}
|
|
703
|
-
return { data:
|
|
704
|
-
id: this.generateId(prefix || "/"),
|
|
705
|
-
path: selfPath,
|
|
706
|
-
metadata: {
|
|
707
|
-
kind: "afs:node",
|
|
708
|
-
childrenCount: childEntries.length,
|
|
709
|
-
platformRef: generatePlatformRef(this.options.bucket, this.options.region, prefix, true)
|
|
710
|
-
}
|
|
711
|
-
}, ...childEntries] };
|
|
691
|
+
return { data: childEntries };
|
|
712
692
|
}
|
|
713
693
|
async listVersionsHandler(ctx) {
|
|
714
694
|
try {
|
|
@@ -723,12 +703,12 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
723
703
|
const entries = [];
|
|
724
704
|
if (response.Versions) {
|
|
725
705
|
for (const version of response.Versions) if (version.Key === key && version.VersionId) {
|
|
726
|
-
const versionPath =
|
|
706
|
+
const versionPath = joinURL("/", normalizedPath, "@versions", version.VersionId);
|
|
727
707
|
entries.push({
|
|
728
708
|
id: `${this.generateId(key)}:${version.VersionId}`,
|
|
729
709
|
path: versionPath,
|
|
730
710
|
updatedAt: version.LastModified,
|
|
731
|
-
|
|
711
|
+
meta: {
|
|
732
712
|
versionId: version.VersionId,
|
|
733
713
|
isLatest: version.IsLatest ?? false,
|
|
734
714
|
lastModified: version.LastModified?.toISOString(),
|
|
@@ -760,7 +740,7 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
760
740
|
id: this.generateId("/"),
|
|
761
741
|
path: "/",
|
|
762
742
|
content: "",
|
|
763
|
-
|
|
743
|
+
meta: {
|
|
764
744
|
kind: "afs:node",
|
|
765
745
|
childrenCount,
|
|
766
746
|
platformRef: generatePlatformRef(this.options.bucket, this.options.region, "", true)
|
|
@@ -778,9 +758,9 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
778
758
|
path: normalizedOutputPath,
|
|
779
759
|
content: "",
|
|
780
760
|
updatedAt: response.LastModified,
|
|
781
|
-
|
|
761
|
+
meta: {
|
|
782
762
|
kind: "afs:node",
|
|
783
|
-
childrenCount:
|
|
763
|
+
childrenCount: -1,
|
|
784
764
|
platformRef: generatePlatformRef(this.options.bucket, this.options.region, key, true)
|
|
785
765
|
}
|
|
786
766
|
};
|
|
@@ -794,7 +774,7 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
794
774
|
path: normalizedOutputPath,
|
|
795
775
|
content,
|
|
796
776
|
updatedAt: response.LastModified,
|
|
797
|
-
|
|
777
|
+
meta: {
|
|
798
778
|
size: response.ContentLength,
|
|
799
779
|
mimeType: response.ContentType,
|
|
800
780
|
contentType: response.ContentType,
|
|
@@ -819,7 +799,7 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
819
799
|
id: this.generateId(`${key}/`),
|
|
820
800
|
path: normalizedOutputPath,
|
|
821
801
|
content: "",
|
|
822
|
-
|
|
802
|
+
meta: {
|
|
823
803
|
kind: "afs:node",
|
|
824
804
|
childrenCount,
|
|
825
805
|
platformRef: generatePlatformRef(this.options.bucket, this.options.region, key, true)
|
|
@@ -836,7 +816,7 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
836
816
|
id: this.generateId(`${key}/`),
|
|
837
817
|
path: normalizedOutputPath,
|
|
838
818
|
content: "",
|
|
839
|
-
|
|
819
|
+
meta: {
|
|
840
820
|
kind: "afs:node",
|
|
841
821
|
childrenCount: 0,
|
|
842
822
|
platformRef: generatePlatformRef(this.options.bucket, this.options.region, key, true)
|
|
@@ -869,7 +849,7 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
869
849
|
path: ctx.path,
|
|
870
850
|
content,
|
|
871
851
|
updatedAt: response.LastModified,
|
|
872
|
-
|
|
852
|
+
meta: {
|
|
873
853
|
size: response.ContentLength,
|
|
874
854
|
contentType: response.ContentType,
|
|
875
855
|
lastModified: response.LastModified?.toISOString(),
|
|
@@ -898,7 +878,7 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
898
878
|
return {
|
|
899
879
|
id: this.generateId("/"),
|
|
900
880
|
path: "/.meta",
|
|
901
|
-
|
|
881
|
+
meta: {
|
|
902
882
|
kind: "afs:node",
|
|
903
883
|
childrenCount,
|
|
904
884
|
platformRef: generatePlatformRef(this.options.bucket, this.options.region, "", true)
|
|
@@ -911,7 +891,7 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
911
891
|
if (cached) return {
|
|
912
892
|
id: cached.id,
|
|
913
893
|
path: ctx.path,
|
|
914
|
-
|
|
894
|
+
meta: cached.meta
|
|
915
895
|
};
|
|
916
896
|
}
|
|
917
897
|
try {
|
|
@@ -924,9 +904,9 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
924
904
|
id: this.generateId(key),
|
|
925
905
|
path: ctx.path,
|
|
926
906
|
updatedAt: response.LastModified,
|
|
927
|
-
|
|
907
|
+
meta: {
|
|
928
908
|
kind: "afs:node",
|
|
929
|
-
childrenCount:
|
|
909
|
+
childrenCount: -1,
|
|
930
910
|
platformRef: generatePlatformRef(this.options.bucket, this.options.region, key, true)
|
|
931
911
|
}
|
|
932
912
|
};
|
|
@@ -934,7 +914,7 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
934
914
|
id: this.generateId(key),
|
|
935
915
|
path: ctx.path,
|
|
936
916
|
updatedAt: response.LastModified,
|
|
937
|
-
|
|
917
|
+
meta: {
|
|
938
918
|
kind: "afs:document",
|
|
939
919
|
size: response.ContentLength,
|
|
940
920
|
contentType: response.ContentType,
|
|
@@ -960,9 +940,9 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
960
940
|
if (listResponse.Contents && listResponse.Contents.length > 0) return {
|
|
961
941
|
id: this.generateId(`${key}/`),
|
|
962
942
|
path: ctx.path,
|
|
963
|
-
|
|
943
|
+
meta: {
|
|
964
944
|
kind: "afs:node",
|
|
965
|
-
childrenCount:
|
|
945
|
+
childrenCount: -1,
|
|
966
946
|
platformRef: generatePlatformRef(this.options.bucket, this.options.region, key, true)
|
|
967
947
|
}
|
|
968
948
|
};
|
|
@@ -976,9 +956,9 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
976
956
|
id: this.generateId(`${key}/`),
|
|
977
957
|
path: ctx.path,
|
|
978
958
|
updatedAt: markerResponse.LastModified,
|
|
979
|
-
|
|
959
|
+
meta: {
|
|
980
960
|
kind: "afs:node",
|
|
981
|
-
childrenCount:
|
|
961
|
+
childrenCount: -1,
|
|
982
962
|
platformRef: generatePlatformRef(this.options.bucket, this.options.region, key, true)
|
|
983
963
|
}
|
|
984
964
|
};
|
|
@@ -998,11 +978,11 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
998
978
|
...ctx,
|
|
999
979
|
path: ctx.path.endsWith("/.meta") ? ctx.path : `${ctx.path}/.meta`
|
|
1000
980
|
});
|
|
981
|
+
const pathSegments = ctx.path.split("/").filter(Boolean);
|
|
1001
982
|
return { data: {
|
|
983
|
+
id: pathSegments.length > 0 ? pathSegments[pathSegments.length - 1] : "/",
|
|
1002
984
|
path: ctx.path,
|
|
1003
|
-
|
|
1004
|
-
childrenCount: metaEntry.metadata?.childrenCount,
|
|
1005
|
-
meta: metaEntry.metadata
|
|
985
|
+
meta: metaEntry.meta
|
|
1006
986
|
} };
|
|
1007
987
|
}
|
|
1008
988
|
async writeHandler(ctx, payload) {
|
|
@@ -1013,7 +993,7 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
1013
993
|
else if (Buffer.isBuffer(payload.content)) body = payload.content;
|
|
1014
994
|
else if (payload.content !== void 0) body = Buffer.from(JSON.stringify(payload.content), "utf-8");
|
|
1015
995
|
else body = Buffer.from("");
|
|
1016
|
-
const contentType = payload.
|
|
996
|
+
const contentType = payload.meta?.mimeType ?? payload.meta?.contentType ?? "application/octet-stream";
|
|
1017
997
|
let etag;
|
|
1018
998
|
let versionId;
|
|
1019
999
|
if (shouldUseMultipart(body.length)) {
|
|
@@ -1037,11 +1017,11 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
1037
1017
|
id: this.generateId(key),
|
|
1038
1018
|
path: normalizedOutputPath,
|
|
1039
1019
|
content: payload.content,
|
|
1040
|
-
|
|
1020
|
+
meta: {
|
|
1041
1021
|
size: body.length,
|
|
1042
1022
|
etag,
|
|
1043
1023
|
versionId,
|
|
1044
|
-
...payload.
|
|
1024
|
+
...payload.meta
|
|
1045
1025
|
}
|
|
1046
1026
|
} };
|
|
1047
1027
|
} catch (error) {
|
|
@@ -1074,33 +1054,70 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
1074
1054
|
}
|
|
1075
1055
|
}
|
|
1076
1056
|
async listActionsHandler(ctx) {
|
|
1077
|
-
const basePath = ctx.path.replace(/\/\.actions$/, "");
|
|
1078
1057
|
return { data: [
|
|
1079
1058
|
{
|
|
1080
1059
|
id: "select",
|
|
1081
|
-
path:
|
|
1060
|
+
path: joinURL(ctx.path, ".actions", "select"),
|
|
1082
1061
|
summary: "Query with S3 Select",
|
|
1083
|
-
|
|
1062
|
+
meta: {
|
|
1084
1063
|
kind: "afs:executable",
|
|
1085
|
-
kinds: ["afs:executable", "afs:node"]
|
|
1064
|
+
kinds: ["afs:executable", "afs:node"],
|
|
1065
|
+
inputSchema: {
|
|
1066
|
+
type: "object",
|
|
1067
|
+
properties: {
|
|
1068
|
+
expression: {
|
|
1069
|
+
type: "string",
|
|
1070
|
+
description: "SQL expression"
|
|
1071
|
+
},
|
|
1072
|
+
inputFormat: {
|
|
1073
|
+
type: "string",
|
|
1074
|
+
description: "CSV | JSON | Parquet"
|
|
1075
|
+
},
|
|
1076
|
+
outputFormat: {
|
|
1077
|
+
type: "string",
|
|
1078
|
+
description: "CSV | JSON"
|
|
1079
|
+
}
|
|
1080
|
+
},
|
|
1081
|
+
required: ["expression"]
|
|
1082
|
+
}
|
|
1086
1083
|
}
|
|
1087
1084
|
},
|
|
1088
1085
|
{
|
|
1089
1086
|
id: "presign-download",
|
|
1090
|
-
path:
|
|
1087
|
+
path: joinURL(ctx.path, ".actions", "presign-download"),
|
|
1091
1088
|
summary: "Generate download URL",
|
|
1092
|
-
|
|
1089
|
+
meta: {
|
|
1093
1090
|
kind: "afs:executable",
|
|
1094
|
-
kinds: ["afs:executable", "afs:node"]
|
|
1091
|
+
kinds: ["afs:executable", "afs:node"],
|
|
1092
|
+
inputSchema: {
|
|
1093
|
+
type: "object",
|
|
1094
|
+
properties: { expiresIn: {
|
|
1095
|
+
type: "number",
|
|
1096
|
+
description: "Expiration in seconds (default: 3600, max: 604800)"
|
|
1097
|
+
} }
|
|
1098
|
+
}
|
|
1095
1099
|
}
|
|
1096
1100
|
},
|
|
1097
1101
|
{
|
|
1098
1102
|
id: "presign-upload",
|
|
1099
|
-
path:
|
|
1103
|
+
path: joinURL(ctx.path, ".actions", "presign-upload"),
|
|
1100
1104
|
summary: "Generate upload URL",
|
|
1101
|
-
|
|
1105
|
+
meta: {
|
|
1102
1106
|
kind: "afs:executable",
|
|
1103
|
-
kinds: ["afs:executable", "afs:node"]
|
|
1107
|
+
kinds: ["afs:executable", "afs:node"],
|
|
1108
|
+
inputSchema: {
|
|
1109
|
+
type: "object",
|
|
1110
|
+
properties: {
|
|
1111
|
+
expiresIn: {
|
|
1112
|
+
type: "number",
|
|
1113
|
+
description: "Expiration in seconds"
|
|
1114
|
+
},
|
|
1115
|
+
contentType: {
|
|
1116
|
+
type: "string",
|
|
1117
|
+
description: "Content-Type for upload"
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1104
1121
|
}
|
|
1105
1122
|
}
|
|
1106
1123
|
] };
|
|
@@ -1159,87 +1176,179 @@ var AFSS3 = class AFSS3 extends AFSBaseProvider {
|
|
|
1159
1176
|
}
|
|
1160
1177
|
};
|
|
1161
1178
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1179
|
+
async explainHandler(ctx) {
|
|
1180
|
+
const normalizedPath = (ctx.params.path ?? "").replace(/^\/+/, "").replace(/\/+$/, "");
|
|
1181
|
+
if (!normalizedPath) {
|
|
1182
|
+
const response$1 = await this.client.send(new ListObjectsV2Command({
|
|
1183
|
+
Bucket: this.options.bucket,
|
|
1184
|
+
Prefix: this.options.prefix ? `${this.options.prefix}/` : void 0,
|
|
1185
|
+
Delimiter: "/",
|
|
1186
|
+
MaxKeys: 1e3
|
|
1187
|
+
}));
|
|
1188
|
+
const objectCount$1 = response$1.Contents?.length ?? 0;
|
|
1189
|
+
const prefixCount$1 = response$1.CommonPrefixes?.length ?? 0;
|
|
1190
|
+
const lines$1 = [];
|
|
1191
|
+
lines$1.push(`# ${this.options.bucket}`);
|
|
1192
|
+
lines$1.push("");
|
|
1193
|
+
lines$1.push(`- **Type**: S3 Bucket`);
|
|
1194
|
+
lines$1.push(`- **Bucket**: ${this.options.bucket}`);
|
|
1195
|
+
if (this.options.prefix) lines$1.push(`- **Prefix**: ${this.options.prefix}`);
|
|
1196
|
+
if (this.options.region) lines$1.push(`- **Region**: ${this.options.region}`);
|
|
1197
|
+
lines$1.push(`- **Access Mode**: ${this.accessMode}`);
|
|
1198
|
+
lines$1.push(`- **Top-level Objects**: ${objectCount$1}`);
|
|
1199
|
+
lines$1.push(`- **Top-level Prefixes**: ${prefixCount$1}`);
|
|
1200
|
+
return {
|
|
1201
|
+
format: "markdown",
|
|
1202
|
+
content: lines$1.join("\n")
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
const key = this.buildS3Key(normalizedPath);
|
|
1206
|
+
try {
|
|
1207
|
+
const head = await this.client.send(new HeadObjectCommand({
|
|
1208
|
+
Bucket: this.options.bucket,
|
|
1209
|
+
Key: key
|
|
1210
|
+
}));
|
|
1211
|
+
const lines$1 = [];
|
|
1212
|
+
lines$1.push(`# ${normalizedPath}`);
|
|
1213
|
+
lines$1.push("");
|
|
1214
|
+
lines$1.push(`- **Type**: Object`);
|
|
1215
|
+
lines$1.push(`- **Key**: ${key}`);
|
|
1216
|
+
lines$1.push(`- **Size**: ${head.ContentLength ?? 0} bytes`);
|
|
1217
|
+
if (head.ContentType) lines$1.push(`- **Content-Type**: ${head.ContentType}`);
|
|
1218
|
+
if (head.StorageClass) lines$1.push(`- **Storage Class**: ${head.StorageClass}`);
|
|
1219
|
+
if (head.LastModified) lines$1.push(`- **Last Modified**: ${head.LastModified.toISOString()}`);
|
|
1220
|
+
if (head.ETag) lines$1.push(`- **ETag**: ${head.ETag}`);
|
|
1221
|
+
return {
|
|
1222
|
+
format: "markdown",
|
|
1223
|
+
content: lines$1.join("\n")
|
|
1224
|
+
};
|
|
1225
|
+
} catch {}
|
|
1226
|
+
const prefix = key.endsWith("/") ? key : `${key}/`;
|
|
1227
|
+
const response = await this.client.send(new ListObjectsV2Command({
|
|
1228
|
+
Bucket: this.options.bucket,
|
|
1229
|
+
Prefix: prefix,
|
|
1230
|
+
Delimiter: "/",
|
|
1231
|
+
MaxKeys: 1e3
|
|
1202
1232
|
}));
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
versionId
|
|
1214
|
-
},
|
|
1215
|
-
options: {}
|
|
1216
|
-
});
|
|
1233
|
+
const objectCount = response.Contents?.length ?? 0;
|
|
1234
|
+
const prefixCount = response.CommonPrefixes?.length ?? 0;
|
|
1235
|
+
if (objectCount === 0 && prefixCount === 0) throw new AFSNotFoundError(`/${normalizedPath}`, `Path not found: ${normalizedPath}`);
|
|
1236
|
+
const lines = [];
|
|
1237
|
+
lines.push(`# ${normalizedPath}/`);
|
|
1238
|
+
lines.push("");
|
|
1239
|
+
lines.push(`- **Type**: Prefix (directory)`);
|
|
1240
|
+
lines.push(`- **Prefix**: ${prefix}`);
|
|
1241
|
+
lines.push(`- **Objects**: ${objectCount}`);
|
|
1242
|
+
lines.push(`- **Sub-prefixes**: ${prefixCount}`);
|
|
1217
1243
|
return {
|
|
1218
|
-
|
|
1219
|
-
|
|
1244
|
+
format: "markdown",
|
|
1245
|
+
content: lines.join("\n")
|
|
1220
1246
|
};
|
|
1221
1247
|
}
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1248
|
+
async searchHandler(ctx, query) {
|
|
1249
|
+
const { minimatch } = await import("minimatch");
|
|
1250
|
+
const normalizedPath = (ctx.params.path ?? "").replace(/^\/+/, "").replace(/\/+$/, "");
|
|
1251
|
+
const prefix = normalizedPath ? this.options.prefix ? `${this.options.prefix}/${normalizedPath}/` : `${normalizedPath}/` : this.options.prefix ? `${this.options.prefix}/` : "";
|
|
1252
|
+
const results = [];
|
|
1253
|
+
let continuationToken;
|
|
1254
|
+
do {
|
|
1255
|
+
const response = await this.client.send(new ListObjectsV2Command({
|
|
1256
|
+
Bucket: this.options.bucket,
|
|
1257
|
+
Prefix: prefix || void 0,
|
|
1258
|
+
ContinuationToken: continuationToken,
|
|
1259
|
+
MaxKeys: 1e3
|
|
1260
|
+
}));
|
|
1261
|
+
for (const obj of response.Contents ?? []) {
|
|
1262
|
+
if (!obj.Key) continue;
|
|
1263
|
+
const relativePath = prefix ? obj.Key.slice(prefix.length) : obj.Key;
|
|
1264
|
+
if (!relativePath) continue;
|
|
1265
|
+
if (minimatch(relativePath, query)) {
|
|
1266
|
+
const displayPath = joinURL("/", normalizedPath, relativePath);
|
|
1267
|
+
results.push({
|
|
1268
|
+
id: this.generateId(obj.Key),
|
|
1269
|
+
path: displayPath,
|
|
1270
|
+
meta: {
|
|
1271
|
+
size: obj.Size,
|
|
1272
|
+
lastModified: obj.LastModified?.toISOString(),
|
|
1273
|
+
etag: obj.ETag?.replace(/"/g, "")
|
|
1274
|
+
}
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
continuationToken = response.IsTruncated ? response.NextContinuationToken : void 0;
|
|
1279
|
+
} while (continuationToken);
|
|
1280
|
+
return { data: results };
|
|
1228
1281
|
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1282
|
+
async readCapabilities(_ctx) {
|
|
1283
|
+
const capabilities = {
|
|
1284
|
+
schemaVersion: 1,
|
|
1285
|
+
provider: "s3",
|
|
1286
|
+
description: `S3 bucket: ${this.options.bucket}`,
|
|
1287
|
+
tools: [],
|
|
1288
|
+
operations: this.getOperationsDeclaration(),
|
|
1289
|
+
actions: [{
|
|
1290
|
+
description: "S3 object actions",
|
|
1291
|
+
catalog: [
|
|
1292
|
+
{
|
|
1293
|
+
name: "select",
|
|
1294
|
+
description: "Query object contents using S3 Select (CSV/JSON/Parquet)",
|
|
1295
|
+
inputSchema: {
|
|
1296
|
+
type: "object",
|
|
1297
|
+
properties: {
|
|
1298
|
+
expression: {
|
|
1299
|
+
type: "string",
|
|
1300
|
+
description: "SQL expression"
|
|
1301
|
+
},
|
|
1302
|
+
inputFormat: {
|
|
1303
|
+
type: "string",
|
|
1304
|
+
description: "CSV | JSON | Parquet"
|
|
1305
|
+
},
|
|
1306
|
+
outputFormat: {
|
|
1307
|
+
type: "string",
|
|
1308
|
+
description: "CSV | JSON"
|
|
1309
|
+
}
|
|
1310
|
+
},
|
|
1311
|
+
required: ["expression"]
|
|
1312
|
+
}
|
|
1313
|
+
},
|
|
1314
|
+
{
|
|
1315
|
+
name: "presign-download",
|
|
1316
|
+
description: "Generate a pre-signed download URL",
|
|
1317
|
+
inputSchema: {
|
|
1318
|
+
type: "object",
|
|
1319
|
+
properties: { expiresIn: {
|
|
1320
|
+
type: "number",
|
|
1321
|
+
description: "Expiration in seconds (default: 3600, max: 604800)"
|
|
1322
|
+
} }
|
|
1323
|
+
}
|
|
1324
|
+
},
|
|
1325
|
+
{
|
|
1326
|
+
name: "presign-upload",
|
|
1327
|
+
description: "Generate a pre-signed upload URL",
|
|
1328
|
+
inputSchema: {
|
|
1329
|
+
type: "object",
|
|
1330
|
+
properties: {
|
|
1331
|
+
expiresIn: {
|
|
1332
|
+
type: "number",
|
|
1333
|
+
description: "Expiration in seconds"
|
|
1334
|
+
},
|
|
1335
|
+
contentType: {
|
|
1336
|
+
type: "string",
|
|
1337
|
+
description: "Content-Type for upload"
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
],
|
|
1343
|
+
discovery: { pathTemplate: "/{path}/.actions" }
|
|
1344
|
+
}]
|
|
1345
|
+
};
|
|
1346
|
+
return {
|
|
1347
|
+
id: ".capabilities",
|
|
1348
|
+
path: "/.meta/.capabilities",
|
|
1349
|
+
content: JSON.stringify(capabilities, null, 2),
|
|
1350
|
+
meta: { kind: "afs:capabilities" }
|
|
1351
|
+
};
|
|
1243
1352
|
}
|
|
1244
1353
|
};
|
|
1245
1354
|
__decorate([List("/"), List("/:path*")], AFSS3.prototype, "listHandler", null);
|
|
@@ -1254,7 +1363,10 @@ __decorate([Actions("/:path*")], AFSS3.prototype, "listActionsHandler", null);
|
|
|
1254
1363
|
__decorate([Actions.Exec("/:path*", "select")], AFSS3.prototype, "selectActionHandler", null);
|
|
1255
1364
|
__decorate([Actions.Exec("/:path*", "presign-download")], AFSS3.prototype, "presignDownloadActionHandler", null);
|
|
1256
1365
|
__decorate([Actions.Exec("/:path*", "presign-upload")], AFSS3.prototype, "presignUploadActionHandler", null);
|
|
1366
|
+
__decorate([Explain("/"), Explain("/:path*")], AFSS3.prototype, "explainHandler", null);
|
|
1367
|
+
__decorate([Search("/"), Search("/:path*")], AFSS3.prototype, "searchHandler", null);
|
|
1368
|
+
__decorate([Read("/.meta/.capabilities")], AFSS3.prototype, "readCapabilities", null);
|
|
1257
1369
|
|
|
1258
1370
|
//#endregion
|
|
1259
|
-
export { AFSS3 };
|
|
1371
|
+
export { AFSS3, AFSS3 as default };
|
|
1260
1372
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["AFSError"],"sources":["../src/cache.ts","../src/client.ts","../src/errors.ts","../src/operations/multipart.ts","../src/operations/select.ts","../src/platform-ref.ts","../src/types.ts","../src/s3-afs.ts"],"sourcesContent":["/**\n * S3 Response Caching (Phase 4)\n *\n * LRU cache with TTL for S3 list and stat results.\n */\n\n/**\n * Cache entry with expiration\n */\ninterface CacheEntry<T> {\n value: T;\n expiresAt: number;\n}\n\n/**\n * LRU Cache with TTL support\n */\nexport class LRUCache<T> {\n private cache = new Map<string, CacheEntry<T>>();\n private maxSize: number;\n private defaultTtl: number;\n\n /**\n * Create a new LRU cache\n *\n * @param maxSize - Maximum number of entries (default: 1000)\n * @param defaultTtl - Default TTL in seconds (default: 60)\n */\n constructor(maxSize = 1000, defaultTtl = 60) {\n this.maxSize = maxSize;\n this.defaultTtl = defaultTtl;\n }\n\n /**\n * Get a value from the cache\n *\n * @param key - Cache key\n * @returns Cached value or undefined if not found/expired\n */\n get(key: string): T | undefined {\n const entry = this.cache.get(key);\n\n if (!entry) {\n return undefined;\n }\n\n // Check if expired\n if (Date.now() > entry.expiresAt) {\n this.cache.delete(key);\n return undefined;\n }\n\n // Move to end (most recently used)\n this.cache.delete(key);\n this.cache.set(key, entry);\n\n return entry.value;\n }\n\n /**\n * Set a value in the cache\n *\n * @param key - Cache key\n * @param value - Value to cache\n * @param ttl - TTL in seconds (optional, uses default)\n */\n set(key: string, value: T, ttl?: number): void {\n // Remove if already exists (to update position)\n if (this.cache.has(key)) {\n this.cache.delete(key);\n }\n\n // Evict oldest entries if at capacity\n while (this.cache.size >= this.maxSize) {\n const oldestKey = this.cache.keys().next().value;\n if (oldestKey) {\n this.cache.delete(oldestKey);\n }\n }\n\n const expiresAt = Date.now() + (ttl ?? this.defaultTtl) * 1000;\n this.cache.set(key, { value, expiresAt });\n }\n\n /**\n * Delete a value from the cache\n *\n * @param key - Cache key\n */\n delete(key: string): void {\n this.cache.delete(key);\n }\n\n /**\n * Delete all entries matching a prefix\n *\n * @param prefix - Key prefix to match\n */\n deleteByPrefix(prefix: string): void {\n for (const key of this.cache.keys()) {\n if (key.startsWith(prefix)) {\n this.cache.delete(key);\n }\n }\n }\n\n /**\n * Clear all entries\n */\n clear(): void {\n this.cache.clear();\n }\n\n /**\n * Get the number of entries in the cache\n */\n get size(): number {\n return this.cache.size;\n }\n\n /**\n * Prune expired entries\n */\n prune(): void {\n const now = Date.now();\n for (const [key, entry] of this.cache.entries()) {\n if (now > entry.expiresAt) {\n this.cache.delete(key);\n }\n }\n }\n}\n\n/**\n * Create a cache key from bucket, prefix, and path\n */\nexport function createCacheKey(\n bucket: string,\n prefix: string,\n path: string,\n suffix?: string,\n): string {\n const base = `${bucket}:${prefix}:${path}`;\n return suffix ? `${base}:${suffix}` : base;\n}\n","/**\n * S3 Client Factory\n *\n * Creates and configures AWS S3 clients.\n */\n\nimport { S3Client, type S3ClientConfig } from \"@aws-sdk/client-s3\";\nimport type { AFSS3Options } from \"./types.js\";\n\n/**\n * Create an S3 client with the given options\n *\n * Uses AWS SDK's default credential chain:\n * 1. Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)\n * 2. Shared credentials file (~/.aws/credentials)\n * 3. IAM role (EC2/ECS/Lambda)\n * 4. SSO credentials\n *\n * @param options - S3 provider options\n * @returns Configured S3 client\n */\nexport function createS3Client(options: AFSS3Options): S3Client {\n const config: S3ClientConfig = {};\n\n // Region configuration\n // For S3-compatible services (MinIO, R2, B2), default to \"us-east-1\" if no region specified\n if (options.region) {\n config.region = options.region;\n } else if (options.endpoint) {\n config.region = \"us-east-1\";\n }\n\n // Custom endpoint for S3-compatible services\n if (options.endpoint) {\n config.endpoint = options.endpoint;\n }\n\n // Force path-style URLs (needed for MinIO, some S3-compatible services)\n if (options.forcePathStyle) {\n config.forcePathStyle = true;\n }\n\n // Explicit credentials (for testing or non-AWS environments)\n if (options.credentials) {\n config.credentials = {\n accessKeyId: options.credentials.accessKeyId,\n secretAccessKey: options.credentials.secretAccessKey,\n sessionToken: options.credentials.sessionToken,\n };\n }\n\n return new S3Client(config);\n}\n","/**\n * S3 Error Handling\n *\n * Maps AWS S3 errors to AFS errors.\n */\n\nimport { AFSNotFoundError } from \"@aigne/afs\";\n\n/**\n * AFS error codes\n */\nexport const AFSErrorCode = {\n ENTRY_NOT_FOUND: \"ENTRY_NOT_FOUND\",\n MODULE_NOT_FOUND: \"MODULE_NOT_FOUND\",\n PERMISSION_DENIED: \"PERMISSION_DENIED\",\n AUTH_ERROR: \"AUTH_ERROR\",\n RATE_LIMITED: \"RATE_LIMITED\",\n TYPE_MISMATCH: \"TYPE_MISMATCH\",\n INTERNAL_ERROR: \"INTERNAL_ERROR\",\n} as const;\n\nexport type AFSErrorCode = (typeof AFSErrorCode)[keyof typeof AFSErrorCode];\n\n/**\n * AFS Error class\n */\nexport class AFSError extends Error {\n readonly code: AFSErrorCode;\n readonly retryAfter?: number;\n\n constructor(code: AFSErrorCode, message: string, options?: { retryAfter?: number }) {\n super(message);\n this.name = \"AFSError\";\n this.code = code;\n this.retryAfter = options?.retryAfter;\n }\n}\n\n/**\n * Map S3 error to AFS error\n *\n * @param error - AWS S3 error\n * @param path - Optional path for AFSNotFoundError (with leading slash)\n * @returns AFS error (or throws the original error if it's already an AFS error)\n */\nexport function mapS3Error(error: unknown, path?: string): AFSError | AFSNotFoundError | never {\n // Pass through AFS errors unchanged (check by name since different module instances)\n if (error && typeof error === \"object\" && \"name\" in error) {\n const errorObj = error as { name?: string; code?: string };\n if (\n errorObj.name === \"AFSNotFoundError\" ||\n errorObj.name === \"AFSError\" ||\n errorObj.code === \"AFS_NOT_FOUND\"\n ) {\n throw error;\n }\n }\n\n // Handle AWS SDK errors\n if (error && typeof error === \"object\" && \"name\" in error) {\n const awsError = error as {\n name: string;\n message?: string;\n $metadata?: { httpStatusCode?: number };\n };\n const message = awsError.message ?? \"Unknown S3 error\";\n\n switch (awsError.name) {\n case \"NoSuchBucket\":\n return new AFSError(AFSErrorCode.MODULE_NOT_FOUND, `Bucket not found: ${message}`);\n\n case \"NoSuchKey\":\n case \"NotFound\":\n if (path) {\n return new AFSNotFoundError(path);\n }\n return new AFSError(AFSErrorCode.ENTRY_NOT_FOUND, `Object not found: ${message}`);\n\n case \"AccessDenied\":\n case \"Forbidden\":\n return new AFSError(AFSErrorCode.PERMISSION_DENIED, `Access denied: ${message}`);\n\n case \"InvalidAccessKeyId\":\n case \"SignatureDoesNotMatch\":\n case \"ExpiredToken\":\n case \"TokenRefreshRequired\":\n return new AFSError(AFSErrorCode.AUTH_ERROR, `Authentication failed: ${message}`);\n\n case \"SlowDown\":\n case \"ServiceUnavailable\":\n return new AFSError(AFSErrorCode.RATE_LIMITED, `Rate limited: ${message}`, {\n retryAfter: 1000,\n });\n\n default: {\n // Check HTTP status code\n const statusCode = awsError.$metadata?.httpStatusCode;\n if (statusCode === 404) {\n if (path) {\n return new AFSNotFoundError(path);\n }\n return new AFSError(AFSErrorCode.ENTRY_NOT_FOUND, message);\n }\n if (statusCode === 403) {\n return new AFSError(AFSErrorCode.PERMISSION_DENIED, message);\n }\n if (statusCode === 401) {\n return new AFSError(AFSErrorCode.AUTH_ERROR, message);\n }\n if (statusCode === 429 || statusCode === 503) {\n return new AFSError(AFSErrorCode.RATE_LIMITED, message, { retryAfter: 1000 });\n }\n\n return new AFSError(AFSErrorCode.INTERNAL_ERROR, message);\n }\n }\n }\n\n // Handle generic errors\n if (error instanceof Error) {\n return new AFSError(AFSErrorCode.INTERNAL_ERROR, error.message);\n }\n\n return new AFSError(AFSErrorCode.INTERNAL_ERROR, String(error));\n}\n","/**\n * S3 Multipart Upload (Phase 2)\n *\n * Handles multipart upload for large files (>5GB).\n * S3 requires multipart upload for objects larger than 5GB.\n */\n\nimport {\n AbortMultipartUploadCommand,\n CompleteMultipartUploadCommand,\n CreateMultipartUploadCommand,\n type S3Client,\n UploadPartCommand,\n} from \"@aws-sdk/client-s3\";\nimport { mapS3Error } from \"../errors.js\";\n\n/**\n * Minimum part size (5MB) - AWS S3 requirement\n */\nconst MIN_PART_SIZE = 5 * 1024 * 1024;\n\n/**\n * Maximum part size (5GB) - AWS S3 requirement\n */\nconst MAX_PART_SIZE = 5 * 1024 * 1024 * 1024;\n\n/**\n * Threshold for switching to multipart upload (100MB)\n * Files larger than this will use multipart upload\n */\nexport const MULTIPART_THRESHOLD = 100 * 1024 * 1024;\n\n/**\n * Default part size (10MB)\n */\nconst DEFAULT_PART_SIZE = 10 * 1024 * 1024;\n\n/**\n * Maximum number of parts (10,000) - AWS S3 requirement\n */\nconst MAX_PARTS = 10000;\n\ninterface MultipartUploadOptions {\n /** Part size in bytes (default: 10MB) */\n partSize?: number;\n /** Content type */\n contentType?: string;\n /** Metadata to attach */\n metadata?: Record<string, string>;\n}\n\ninterface MultipartUploadResult {\n /** ETag of the completed upload */\n etag: string;\n /** Version ID if versioning is enabled */\n versionId?: string;\n}\n\n/**\n * Calculate optimal part size based on file size\n */\nfunction calculatePartSize(fileSize: number, requestedPartSize?: number): number {\n let partSize = requestedPartSize ?? DEFAULT_PART_SIZE;\n\n // Ensure minimum part size\n if (partSize < MIN_PART_SIZE) {\n partSize = MIN_PART_SIZE;\n }\n\n // Ensure maximum part size\n if (partSize > MAX_PART_SIZE) {\n partSize = MAX_PART_SIZE;\n }\n\n // If file is too large for current part size, increase it\n const partsNeeded = Math.ceil(fileSize / partSize);\n if (partsNeeded > MAX_PARTS) {\n // Calculate minimum part size needed to fit in MAX_PARTS\n partSize = Math.ceil(fileSize / MAX_PARTS);\n // Round up to next MB for efficiency\n partSize = Math.ceil(partSize / (1024 * 1024)) * 1024 * 1024;\n }\n\n return partSize;\n}\n\n/**\n * Upload a large file using multipart upload\n *\n * @param client - S3 client\n * @param bucket - Bucket name\n * @param key - Object key\n * @param data - File content as Buffer\n * @param options - Upload options\n * @returns Upload result with ETag\n */\nexport async function multipartUpload(\n client: S3Client,\n bucket: string,\n key: string,\n data: Buffer,\n options?: MultipartUploadOptions,\n): Promise<MultipartUploadResult> {\n const fileSize = data.length;\n const partSize = calculatePartSize(fileSize, options?.partSize);\n\n // Initiate multipart upload\n const createCommand = new CreateMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n ContentType: options?.contentType ?? \"application/octet-stream\",\n Metadata: options?.metadata,\n });\n\n const createResponse = await client.send(createCommand);\n const uploadId = createResponse.UploadId;\n\n if (!uploadId) {\n throw new Error(\"Failed to initiate multipart upload: no uploadId returned\");\n }\n\n const parts: { ETag: string; PartNumber: number }[] = [];\n\n try {\n // Upload parts\n const totalParts = Math.ceil(fileSize / partSize);\n\n for (let partNumber = 1; partNumber <= totalParts; partNumber++) {\n const start = (partNumber - 1) * partSize;\n const end = Math.min(start + partSize, fileSize);\n const partData = data.subarray(start, end);\n\n const uploadPartCommand = new UploadPartCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n PartNumber: partNumber,\n Body: partData,\n });\n\n const uploadPartResponse = await client.send(uploadPartCommand);\n\n if (!uploadPartResponse.ETag) {\n throw new Error(`Failed to upload part ${partNumber}: no ETag returned`);\n }\n\n parts.push({\n ETag: uploadPartResponse.ETag,\n PartNumber: partNumber,\n });\n }\n\n // Complete multipart upload\n const completeCommand = new CompleteMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n MultipartUpload: {\n Parts: parts,\n },\n });\n\n const completeResponse = await client.send(completeCommand);\n\n return {\n etag: completeResponse.ETag?.replace(/\"/g, \"\") ?? \"\",\n versionId: completeResponse.VersionId,\n };\n } catch (error) {\n // Abort the multipart upload on failure\n try {\n const abortCommand = new AbortMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n });\n await client.send(abortCommand);\n } catch {\n // Ignore abort errors\n }\n\n throw mapS3Error(error);\n }\n}\n\n/**\n * Check if a file should use multipart upload\n */\nexport function shouldUseMultipart(size: number): boolean {\n return size >= MULTIPART_THRESHOLD;\n}\n","/**\n * S3 Select Support (Phase 3)\n *\n * Runs SQL-like queries on CSV and JSON files stored in S3.\n * This enables server-side filtering, reducing data transfer.\n */\n\nimport {\n type S3Client,\n SelectObjectContentCommand,\n type SelectObjectContentEventStream,\n} from \"@aws-sdk/client-s3\";\nimport { mapS3Error } from \"../errors.js\";\n\n/**\n * Input serialization format\n */\nexport type InputFormat = \"CSV\" | \"JSON\" | \"Parquet\";\n\n/**\n * Select query options\n */\nexport interface SelectOptions {\n /** Input format (default: auto-detected from extension) */\n inputFormat?: InputFormat;\n /** CSV options */\n csv?: {\n /** Field delimiter (default: ,) */\n fieldDelimiter?: string;\n /** Record delimiter (default: \\n) */\n recordDelimiter?: string;\n /** Whether first row is header (default: true) */\n fileHeaderInfo?: \"USE\" | \"IGNORE\" | \"NONE\";\n /** Quote character (default: \") */\n quoteCharacter?: string;\n /** Comment character */\n comments?: string;\n };\n /** JSON options */\n json?: {\n /** JSON document type */\n type?: \"DOCUMENT\" | \"LINES\";\n };\n /** Output format (default: JSON) */\n outputFormat?: \"CSV\" | \"JSON\";\n}\n\n/**\n * Select query result\n */\nexport interface SelectResult {\n /** Query results as array of records */\n records: unknown[];\n /** Stats about the query */\n stats?: {\n bytesScanned: number;\n bytesProcessed: number;\n bytesReturned: number;\n };\n}\n\n/**\n * Detect input format from file extension\n */\nfunction detectInputFormat(path: string): InputFormat {\n const ext = path.toLowerCase().split(\".\").pop();\n switch (ext) {\n case \"csv\":\n case \"tsv\":\n return \"CSV\";\n case \"json\":\n case \"jsonl\":\n case \"ndjson\":\n return \"JSON\";\n case \"parquet\":\n return \"Parquet\";\n default:\n return \"JSON\";\n }\n}\n\n/**\n * Run a SQL-like query on an S3 object\n *\n * @param client - S3 client\n * @param bucket - Bucket name\n * @param mountPrefix - Mount prefix from options\n * @param path - Path relative to mount point\n * @param query - SQL query (e.g., \"SELECT * FROM s3object WHERE age > 21\")\n * @param options - Select options\n * @returns Query results\n */\nexport async function selectQuery(\n client: S3Client,\n bucket: string,\n mountPrefix: string,\n path: string,\n query: string,\n options?: SelectOptions,\n): Promise<SelectResult> {\n try {\n const normalizedPath = path.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n const key = mountPrefix ? `${mountPrefix}/${normalizedPath}` : normalizedPath;\n\n const inputFormat = options?.inputFormat ?? detectInputFormat(path);\n const outputFormat = options?.outputFormat ?? \"JSON\";\n\n // Build input serialization based on format\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const inputSerialization: Record<string, any> = {};\n\n if (inputFormat === \"CSV\") {\n inputSerialization.CSV = {\n FieldDelimiter: options?.csv?.fieldDelimiter ?? \",\",\n RecordDelimiter: options?.csv?.recordDelimiter ?? \"\\n\",\n FileHeaderInfo: options?.csv?.fileHeaderInfo ?? \"USE\",\n QuoteCharacter: options?.csv?.quoteCharacter ?? '\"',\n Comments: options?.csv?.comments,\n };\n } else if (inputFormat === \"JSON\") {\n inputSerialization.JSON = {\n Type: options?.json?.type ?? \"DOCUMENT\",\n };\n } else if (inputFormat === \"Parquet\") {\n inputSerialization.Parquet = {};\n }\n\n // Build output serialization\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const outputSerialization: Record<string, any> = {};\n\n if (outputFormat === \"JSON\") {\n outputSerialization.JSON = {};\n } else {\n outputSerialization.CSV = {};\n }\n\n const command = new SelectObjectContentCommand({\n Bucket: bucket,\n Key: key,\n ExpressionType: \"SQL\",\n Expression: query,\n InputSerialization: inputSerialization,\n OutputSerialization: outputSerialization,\n });\n\n const response = await client.send(command);\n\n // Process the event stream\n const records: unknown[] = [];\n let stats: SelectResult[\"stats\"] | undefined;\n\n if (response.Payload) {\n for await (const event of response.Payload as AsyncIterable<SelectObjectContentEventStream>) {\n if (event.Records?.Payload) {\n // Parse records from payload\n const text = new TextDecoder().decode(event.Records.Payload);\n // Split by newline and parse each line as JSON\n const lines = text.split(\"\\n\").filter((line) => line.trim());\n for (const line of lines) {\n try {\n records.push(JSON.parse(line));\n } catch {\n // If not valid JSON, just push as string\n records.push(line);\n }\n }\n }\n if (event.Stats?.Details) {\n stats = {\n bytesScanned: Number(event.Stats.Details.BytesScanned ?? 0),\n bytesProcessed: Number(event.Stats.Details.BytesProcessed ?? 0),\n bytesReturned: Number(event.Stats.Details.BytesReturned ?? 0),\n };\n }\n }\n }\n\n return { records, stats };\n } catch (error) {\n throw mapS3Error(error);\n }\n}\n","/**\n * S3 Platform Reference\n *\n * Generates AWS Console URLs for S3 objects and directories.\n */\n\n/**\n * Platform reference containing console URL\n */\nexport interface S3PlatformRef {\n consoleUrl: string;\n}\n\n/**\n * Generate platform reference with AWS Console URL\n *\n * @param bucket - S3 bucket name\n * @param region - AWS region (defaults to us-east-1)\n * @param key - S3 object key (without leading slash)\n * @param isDirectory - Whether this is a directory (prefix)\n * @returns Platform reference with console URL\n */\nexport function generatePlatformRef(\n bucket: string,\n region: string | undefined,\n key: string,\n isDirectory: boolean,\n): S3PlatformRef {\n const effectiveRegion = region || \"us-east-1\";\n\n if (isDirectory) {\n // Directory URL: https://s3.console.aws.amazon.com/s3/buckets/{bucket}?region={region}&prefix={prefix}/\n const normalizedPrefix = key.endsWith(\"/\") ? key : key ? `${key}/` : \"\";\n return {\n consoleUrl: `https://s3.console.aws.amazon.com/s3/buckets/${bucket}?region=${effectiveRegion}&prefix=${normalizedPrefix}`,\n };\n }\n\n // File URL: https://s3.console.aws.amazon.com/s3/object/{bucket}?region={region}&prefix={key}\n const encodedKey = encodeURIComponent(key).replace(/%2F/g, \"/\");\n return {\n consoleUrl: `https://s3.console.aws.amazon.com/s3/object/${bucket}?region=${effectiveRegion}&prefix=${encodedKey}`,\n };\n}\n","/**\n * AFS S3 Provider Types\n */\n\nimport { camelize, optionalize } from \"@aigne/afs/utils/zod\";\nimport type { S3Client } from \"@aws-sdk/client-s3\";\nimport { z } from \"zod\";\n\n/**\n * Configuration options for AFSS3\n */\nexport interface AFSS3Options {\n /** Module name (used as mount path segment) */\n name?: string;\n\n /** Module description */\n description?: string;\n\n /** S3 bucket name */\n bucket: string;\n\n /** Key prefix (optional) */\n prefix?: string;\n\n /** AWS region (defaults to environment) */\n region?: string;\n\n /** Access mode */\n accessMode?: \"readonly\" | \"readwrite\";\n\n /** Custom endpoint for S3-compatible services (MinIO, R2, B2) */\n endpoint?: string;\n\n /** Force path-style URLs (needed for some S3-compatible services) */\n forcePathStyle?: boolean;\n\n /** AWS credentials profile name */\n profile?: string;\n\n /** Explicit credentials (for testing or non-AWS environments) */\n credentials?: {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string;\n };\n\n /** Cache TTL in seconds (0 = no cache, Phase 4) */\n cacheTtl?: number;\n\n /**\n * Pre-configured S3 client (for testing or advanced use cases)\n * If provided, endpoint/region/credentials/profile options are ignored.\n */\n client?: S3Client;\n}\n\n/**\n * S3 bucket name validation regex\n * - 3-63 characters\n * - lowercase letters, numbers, hyphens, dots\n * - must start and end with letter or number\n */\nconst BUCKET_NAME_REGEX = /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/;\n\n/**\n * Zod schema for options validation\n */\nexport const afss3OptionsSchema = camelize(\n z\n .object({\n name: optionalize(z.string()),\n description: optionalize(z.string()),\n bucket: z.string().regex(BUCKET_NAME_REGEX, \"Invalid S3 bucket name\"),\n prefix: optionalize(z.string()),\n region: optionalize(z.string()),\n accessMode: optionalize(z.enum([\"readonly\", \"readwrite\"])),\n endpoint: optionalize(z.string().url()),\n forcePathStyle: optionalize(z.boolean()),\n profile: optionalize(z.string()),\n credentials: optionalize(\n z.object({\n accessKeyId: z.string(),\n secretAccessKey: z.string(),\n sessionToken: optionalize(z.string()),\n }),\n ),\n cacheTtl: optionalize(z.number().int().min(0)),\n })\n .strict(),\n);\n\n/**\n * Parsed S3 URI\n */\nexport interface ParsedS3Uri {\n bucket: string;\n prefix: string;\n}\n","/**\n * AFS S3 Provider\n *\n * S3 provider using AFSBaseProvider decorator routing pattern.\n * Provides access to AWS S3 and S3-compatible storage (MinIO, R2, B2).\n */\n\nimport {\n Actions,\n type AFSAccessMode,\n AFSBaseProvider,\n type AFSDeleteResult,\n type AFSEntry,\n AFSError,\n type AFSExecResult,\n type AFSListResult,\n type AFSModuleClass,\n type AFSModuleLoadParams,\n AFSNotFoundError,\n type AFSStatResult,\n type AFSWriteEntryPayload,\n type AFSWriteResult,\n Delete,\n List,\n Meta,\n Read,\n type RouteContext,\n Stat,\n Write,\n} from \"@aigne/afs\";\nimport { zodParse } from \"@aigne/afs/utils/zod\";\nimport {\n DeleteObjectCommand,\n GetObjectCommand,\n HeadObjectCommand,\n ListObjectsV2Command,\n ListObjectVersionsCommand,\n PutObjectCommand,\n type S3Client,\n} from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport { createCacheKey, LRUCache } from \"./cache.js\";\nimport { createS3Client } from \"./client.js\";\nimport { mapS3Error } from \"./errors.js\";\nimport { multipartUpload, shouldUseMultipart } from \"./operations/multipart.js\";\nimport { selectQuery } from \"./operations/select.js\";\nimport { generatePlatformRef } from \"./platform-ref.js\";\nimport { type AFSS3Options, afss3OptionsSchema } from \"./types.js\";\n\n/**\n * Default URL expiration time (1 hour)\n */\nconst DEFAULT_EXPIRES_IN = 3600;\n\n/**\n * Maximum expiration time (7 days)\n */\nconst MAX_EXPIRES_IN = 604800;\n\n/**\n * AFSS3 Provider using Base Provider pattern\n *\n * Provides access to AWS S3 and S3-compatible storage through AFS.\n * Uses decorator routing (@List, @Read, @Write, @Delete, @Meta, @Actions).\n *\n * @example\n * ```typescript\n * const s3 = new AFSS3({\n * bucket: \"my-bucket\",\n * prefix: \"data\",\n * region: \"us-east-1\",\n * });\n *\n * // Mount to AFS\n * afs.mount(s3);\n *\n * // List objects\n * const result = await afs.list(\"/modules/my-bucket/data\");\n *\n * // Read object\n * const content = await afs.read(\"/modules/my-bucket/data/file.json\");\n * ```\n */\nexport class AFSS3 extends AFSBaseProvider {\n override readonly name: string;\n override readonly description?: string;\n override readonly accessMode: AFSAccessMode;\n\n private options: Required<Pick<AFSS3Options, \"bucket\">> & AFSS3Options;\n private client: S3Client;\n private listCache?: LRUCache<AFSListResult>;\n private statCache?: LRUCache<AFSEntry>;\n\n constructor(options: AFSS3Options) {\n super();\n\n // Extract client before validation (not serializable)\n const { client, ...restOptions } = options;\n\n // Validate options (excluding client)\n const parsed = afss3OptionsSchema.parse(restOptions);\n\n this.options = {\n ...parsed,\n bucket: parsed.bucket,\n prefix: parsed.prefix ?? \"\",\n accessMode: parsed.accessMode ?? \"readonly\",\n };\n\n this.name = parsed.name ?? parsed.bucket;\n this.description = parsed.description ?? `S3 bucket: ${parsed.bucket}`;\n this.accessMode = this.options.accessMode ?? \"readonly\";\n\n // Use provided client or create one\n this.client = client ?? createS3Client(this.options);\n\n // Initialize caches if cacheTtl is set\n if (parsed.cacheTtl && parsed.cacheTtl > 0) {\n this.listCache = new LRUCache<AFSListResult>(1000, parsed.cacheTtl);\n this.statCache = new LRUCache<AFSEntry>(5000, parsed.cacheTtl);\n }\n }\n\n /**\n * Schema for configuration validation\n */\n static schema() {\n return afss3OptionsSchema;\n }\n\n /**\n * Load from configuration file\n */\n static async load(params: AFSModuleLoadParams): Promise<AFSS3> {\n const options = zodParse(afss3OptionsSchema, params.parsed, { prefix: params.filepath });\n return new AFSS3(options);\n }\n\n // ========== Helper Methods ==========\n\n /**\n * Build the full S3 key from a path\n */\n private buildS3Key(path: string): string {\n const normalizedPath = path.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n return this.options.prefix ? `${this.options.prefix}/${normalizedPath}` : normalizedPath;\n }\n\n /**\n * Generate a unique ID for an S3 object\n */\n private generateId(key: string): string {\n return `s3://${this.options.bucket}/${key}`;\n }\n\n /**\n * Invalidate caches for a given path\n */\n private invalidateCache(path: string): void {\n if (this.statCache) {\n const statKey = createCacheKey(this.options.bucket, this.options.prefix ?? \"\", path);\n this.statCache.delete(statKey);\n }\n\n if (this.listCache) {\n // Invalidate list cache for parent directory\n const parentPath = path.split(\"/\").slice(0, -1).join(\"/\") || \"/\";\n const listPrefix = createCacheKey(this.options.bucket, this.options.prefix ?? \"\", parentPath);\n this.listCache.deleteByPrefix(listPrefix);\n }\n }\n\n /**\n * Clear all caches\n */\n clearCache(): void {\n this.listCache?.clear();\n this.statCache?.clear();\n }\n\n // ========== List Operations ==========\n\n @List(\"/\")\n @List(\"/:path*\")\n async listHandler(ctx: RouteContext<{ path?: string }>): Promise<AFSListResult> {\n try {\n const path = ctx.params.path ?? \"\";\n const normalizedPath = path.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n\n // Build the full S3 prefix\n const fullPrefix = this.options.prefix\n ? normalizedPath\n ? `${this.options.prefix}/${normalizedPath}/`\n : `${this.options.prefix}/`\n : normalizedPath\n ? `${normalizedPath}/`\n : \"\";\n\n const opts = ctx.options as { limit?: number; maxChildren?: number } | undefined;\n const maxChildren = opts?.limit ?? opts?.maxChildren ?? 1000;\n\n // Check cache first\n if (this.listCache) {\n const cacheKey = createCacheKey(\n this.options.bucket,\n this.options.prefix ?? \"\",\n normalizedPath,\n JSON.stringify(ctx.options ?? {}),\n );\n const cached = this.listCache.get(cacheKey);\n if (cached) {\n return cached;\n }\n }\n\n const result = await this.listWithDelimiter(fullPrefix, normalizedPath, maxChildren);\n\n // Cache the result\n if (this.listCache) {\n const cacheKey = createCacheKey(\n this.options.bucket,\n this.options.prefix ?? \"\",\n normalizedPath,\n JSON.stringify(ctx.options ?? {}),\n );\n this.listCache.set(cacheKey, result);\n\n // Also populate stat cache from list results\n if (this.statCache) {\n for (const entry of result.data) {\n const statKey = createCacheKey(\n this.options.bucket,\n this.options.prefix ?? \"\",\n entry.path,\n );\n this.statCache.set(statKey, entry);\n }\n }\n }\n\n return result;\n } catch (error) {\n const normalizedPath = ctx.params.path\n ? ctx.params.path.startsWith(\"/\")\n ? ctx.params.path\n : `/${ctx.params.path}`\n : \"/\";\n throw mapS3Error(error, normalizedPath);\n }\n }\n\n /**\n * List with delimiter (single level)\n */\n private async listWithDelimiter(\n prefix: string,\n basePath: string,\n maxChildren: number,\n ): Promise<AFSListResult> {\n const childEntries: AFSEntry[] = [];\n let continuationToken: string | undefined;\n\n do {\n const command = new ListObjectsV2Command({\n Bucket: this.options.bucket,\n Prefix: prefix,\n Delimiter: \"/\",\n MaxKeys: Math.min(maxChildren - childEntries.length, 1000),\n ContinuationToken: continuationToken,\n });\n\n const response = await this.client.send(command);\n\n // Add directories from CommonPrefixes\n if (response.CommonPrefixes) {\n for (const commonPrefix of response.CommonPrefixes) {\n if (!commonPrefix.Prefix) continue;\n\n // Extract directory name\n const dirName = commonPrefix.Prefix.slice(prefix.length).replace(/\\/$/, \"\");\n if (!dirName) continue;\n\n const entryPath = basePath ? `${basePath}/${dirName}` : dirName;\n const normalizedPath = entryPath.startsWith(\"/\") ? entryPath : `/${entryPath}`;\n\n childEntries.push({\n id: this.generateId(commonPrefix.Prefix),\n path: normalizedPath,\n metadata: {\n childrenCount: 0,\n platformRef: generatePlatformRef(\n this.options.bucket,\n this.options.region,\n commonPrefix.Prefix,\n true,\n ),\n },\n });\n\n if (childEntries.length >= maxChildren) break;\n }\n }\n\n // Add files from Contents\n if (response.Contents) {\n for (const object of response.Contents) {\n if (!object.Key) continue;\n\n // Skip the prefix itself (if it's a \"directory marker\")\n if (object.Key === prefix) continue;\n\n // Extract file name\n const fileName = object.Key.slice(prefix.length);\n if (!fileName || fileName.includes(\"/\")) continue;\n\n const entryPath = basePath ? `${basePath}/${fileName}` : fileName;\n const normalizedPath = entryPath.startsWith(\"/\") ? entryPath : `/${entryPath}`;\n\n childEntries.push({\n id: this.generateId(object.Key),\n path: normalizedPath,\n updatedAt: object.LastModified,\n metadata: {\n size: object.Size,\n lastModified: object.LastModified?.toISOString(),\n etag: object.ETag?.replace(/\"/g, \"\"),\n storageClass: object.StorageClass,\n platformRef: generatePlatformRef(\n this.options.bucket,\n this.options.region,\n object.Key,\n false,\n ),\n },\n });\n\n if (childEntries.length >= maxChildren) break;\n }\n }\n\n continuationToken = response.IsTruncated ? response.NextContinuationToken : undefined;\n } while (continuationToken && childEntries.length < maxChildren);\n\n // Build result with self entry first\n const selfPath = basePath ? `/${basePath}` : \"/\";\n\n // For non-root paths with no children, check if the path exists\n if (childEntries.length === 0 && basePath) {\n // Check if this path exists as a file or directory marker\n const key = this.options.prefix ? `${this.options.prefix}/${basePath}` : basePath;\n\n try {\n const headCommand = new HeadObjectCommand({\n Bucket: this.options.bucket,\n Key: key,\n });\n const headResponse = await this.client.send(headCommand);\n\n // Path exists as an object (file)\n return {\n data: [\n {\n id: this.generateId(key),\n path: selfPath,\n updatedAt: headResponse.LastModified,\n metadata: {\n size: headResponse.ContentLength,\n contentType: headResponse.ContentType,\n lastModified: headResponse.LastModified?.toISOString(),\n etag: headResponse.ETag?.replace(/\"/g, \"\"),\n childrenCount: 0,\n platformRef: generatePlatformRef(\n this.options.bucket,\n this.options.region,\n key,\n false,\n ),\n },\n },\n ],\n };\n } catch (error: any) {\n // Also check for directory marker\n if (error?.name === \"NotFound\" || error?.$metadata?.httpStatusCode === 404) {\n try {\n const dirMarkerCommand = new HeadObjectCommand({\n Bucket: this.options.bucket,\n Key: `${key}/`,\n });\n await this.client.send(dirMarkerCommand);\n // Directory marker exists but is empty\n } catch {\n // Neither file nor directory marker exists\n throw new AFSNotFoundError(selfPath);\n }\n } else {\n throw error;\n }\n }\n }\n\n // Create self entry\n const selfEntry: AFSEntry = {\n id: this.generateId(prefix || \"/\"),\n path: selfPath,\n metadata: {\n kind: \"afs:node\",\n childrenCount: childEntries.length,\n platformRef: generatePlatformRef(this.options.bucket, this.options.region, prefix, true),\n },\n };\n\n return { data: [selfEntry, ...childEntries] };\n }\n\n // ========== Versioning List ==========\n\n @List(\"/:path*/@versions\")\n async listVersionsHandler(ctx: RouteContext<{ path: string }>): Promise<AFSListResult> {\n try {\n const normalizedPath = ctx.params.path.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n const key = this.options.prefix ? `${this.options.prefix}/${normalizedPath}` : normalizedPath;\n\n const command = new ListObjectVersionsCommand({\n Bucket: this.options.bucket,\n Prefix: key,\n MaxKeys: 1000,\n });\n\n const response = await this.client.send(command);\n const entries: AFSEntry[] = [];\n\n if (response.Versions) {\n for (const version of response.Versions) {\n // Only include exact key matches\n if (version.Key === key && version.VersionId) {\n const versionPath = `/${normalizedPath}/@versions/${version.VersionId}`;\n entries.push({\n id: `${this.generateId(key)}:${version.VersionId}`,\n path: versionPath,\n updatedAt: version.LastModified,\n metadata: {\n versionId: version.VersionId,\n isLatest: version.IsLatest ?? false,\n lastModified: version.LastModified?.toISOString(),\n size: version.Size ?? 0,\n etag: version.ETag?.replace(/\"/g, \"\"),\n },\n });\n }\n }\n }\n\n return { data: entries };\n } catch (error) {\n throw mapS3Error(error, `/${ctx.params.path}/@versions`);\n }\n }\n\n // ========== Read Operations ==========\n\n @Read(\"/:path*\")\n async readHandler(ctx: RouteContext<{ path: string }>): Promise<AFSEntry> {\n const normalizedOutputPath = ctx.path.startsWith(\"/\") ? ctx.path : `/${ctx.path}`;\n\n try {\n const key = this.buildS3Key(ctx.params.path);\n\n // Handle root path - return bucket metadata with childrenCount\n if (!key) {\n // Count children in root\n const listCommand = new ListObjectsV2Command({\n Bucket: this.options.bucket,\n Prefix: this.options.prefix ? `${this.options.prefix}/` : \"\",\n Delimiter: \"/\",\n MaxKeys: 1000,\n });\n const listResponse = await this.client.send(listCommand);\n const childrenCount =\n (listResponse.CommonPrefixes?.length ?? 0) + (listResponse.Contents?.length ?? 0);\n\n return {\n id: this.generateId(\"/\"),\n path: \"/\",\n content: \"\",\n metadata: {\n kind: \"afs:node\",\n childrenCount,\n platformRef: generatePlatformRef(this.options.bucket, this.options.region, \"\", true),\n },\n };\n }\n\n // Try to get the object directly (file case)\n try {\n const command = new GetObjectCommand({\n Bucket: this.options.bucket,\n Key: key,\n });\n\n const response = await this.client.send(command);\n\n // Check if this is a directory marker\n if (key.endsWith(\"/\") || response.ContentType === \"application/x-directory\") {\n // This is a directory marker - return directory info\n return {\n id: this.generateId(key),\n path: normalizedOutputPath,\n content: \"\",\n updatedAt: response.LastModified,\n metadata: {\n kind: \"afs:node\",\n childrenCount: undefined,\n platformRef: generatePlatformRef(this.options.bucket, this.options.region, key, true),\n },\n };\n }\n\n // Read body as string\n let content: string;\n if (response.Body) {\n const bytes = await response.Body.transformToByteArray();\n content = new TextDecoder().decode(bytes);\n } else {\n content = \"\";\n }\n\n return {\n id: this.generateId(key),\n path: normalizedOutputPath,\n content,\n updatedAt: response.LastModified,\n metadata: {\n size: response.ContentLength,\n mimeType: response.ContentType,\n contentType: response.ContentType,\n contentLength: response.ContentLength,\n lastModified: response.LastModified?.toISOString(),\n etag: response.ETag?.replace(/\"/g, \"\"),\n contentRange: response.ContentRange,\n },\n };\n } catch (error: any) {\n // If 404, check if it's a directory prefix\n if (error?.name === \"NotFound\" || error?.$metadata?.httpStatusCode === 404) {\n // Check if there are objects with this prefix (it's a directory)\n const listCommand = new ListObjectsV2Command({\n Bucket: this.options.bucket,\n Prefix: `${key}/`,\n Delimiter: \"/\",\n MaxKeys: 1000,\n });\n\n const listResponse = await this.client.send(listCommand);\n const hasChildren =\n (listResponse.Contents && listResponse.Contents.length > 0) ||\n (listResponse.CommonPrefixes && listResponse.CommonPrefixes.length > 0);\n\n if (hasChildren) {\n // It's a directory - return directory info\n const childrenCount =\n (listResponse.CommonPrefixes?.length ?? 0) + (listResponse.Contents?.length ?? 0);\n return {\n id: this.generateId(`${key}/`),\n path: normalizedOutputPath,\n content: \"\",\n metadata: {\n kind: \"afs:node\",\n childrenCount,\n platformRef: generatePlatformRef(\n this.options.bucket,\n this.options.region,\n key,\n true,\n ),\n },\n };\n }\n\n // Also check for directory marker (key ending with /)\n try {\n const markerCommand = new HeadObjectCommand({\n Bucket: this.options.bucket,\n Key: `${key}/`,\n });\n await this.client.send(markerCommand);\n // Directory marker exists\n return {\n id: this.generateId(`${key}/`),\n path: normalizedOutputPath,\n content: \"\",\n metadata: {\n kind: \"afs:node\",\n childrenCount: 0,\n platformRef: generatePlatformRef(\n this.options.bucket,\n this.options.region,\n key,\n true,\n ),\n },\n };\n } catch {\n // Neither file nor directory exists\n throw new AFSNotFoundError(normalizedOutputPath);\n }\n }\n throw error;\n }\n } catch (error) {\n if (error instanceof AFSNotFoundError) {\n throw error;\n }\n throw mapS3Error(error, normalizedOutputPath);\n }\n }\n\n // ========== Version Read ==========\n\n @Read(\"/:path*/@versions/:versionId\")\n async readVersionHandler(\n ctx: RouteContext<{ path: string; versionId: string }>,\n ): Promise<AFSEntry> {\n try {\n const normalizedPath = ctx.params.path.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n const key = this.options.prefix ? `${this.options.prefix}/${normalizedPath}` : normalizedPath;\n\n const command = new GetObjectCommand({\n Bucket: this.options.bucket,\n Key: key,\n VersionId: ctx.params.versionId,\n });\n\n const response = await this.client.send(command);\n\n const content = response.Body ? await response.Body.transformToString() : \"\";\n\n return {\n id: `${this.generateId(key)}:${ctx.params.versionId}`,\n path: ctx.path,\n content,\n updatedAt: response.LastModified,\n metadata: {\n size: response.ContentLength,\n contentType: response.ContentType,\n lastModified: response.LastModified?.toISOString(),\n etag: response.ETag?.replace(/\"/g, \"\"),\n versionId: response.VersionId,\n },\n };\n } catch (error) {\n throw mapS3Error(error, `/${ctx.params.path}/@versions/${ctx.params.versionId}`);\n }\n }\n\n // ========== Meta Operations ==========\n\n @Meta(\"/\")\n @Meta(\"/:path*\")\n async metaHandler(ctx: RouteContext<{ path?: string }>): Promise<AFSEntry> {\n try {\n const path = ctx.params.path ?? \"\";\n const normalizedPath = path.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n const key = this.options.prefix\n ? normalizedPath\n ? `${this.options.prefix}/${normalizedPath}`\n : this.options.prefix\n : normalizedPath;\n\n // Root metadata - count children\n if (!key) {\n const listCommand = new ListObjectsV2Command({\n Bucket: this.options.bucket,\n Prefix: this.options.prefix ? `${this.options.prefix}/` : \"\",\n Delimiter: \"/\",\n MaxKeys: 1000,\n });\n const listResponse = await this.client.send(listCommand);\n const childrenCount =\n (listResponse.CommonPrefixes?.length ?? 0) + (listResponse.Contents?.length ?? 0);\n\n return {\n id: this.generateId(\"/\"),\n path: \"/.meta\",\n metadata: {\n kind: \"afs:node\",\n childrenCount,\n platformRef: generatePlatformRef(this.options.bucket, this.options.region, \"\", true),\n },\n };\n }\n\n // Check cache first\n if (this.statCache) {\n const cacheKey = createCacheKey(this.options.bucket, this.options.prefix ?? \"\", path);\n const cached = this.statCache.get(cacheKey);\n if (cached) {\n return {\n id: cached.id,\n path: ctx.path,\n metadata: cached.metadata,\n };\n }\n }\n\n // Try to get the object directly (file case)\n try {\n const command = new HeadObjectCommand({\n Bucket: this.options.bucket,\n Key: key,\n });\n\n const response = await this.client.send(command);\n\n // Check if this is a directory marker\n if (key.endsWith(\"/\") || response.ContentType === \"application/x-directory\") {\n return {\n id: this.generateId(key),\n path: ctx.path,\n updatedAt: response.LastModified,\n metadata: {\n kind: \"afs:node\",\n childrenCount: undefined,\n platformRef: generatePlatformRef(this.options.bucket, this.options.region, key, true),\n },\n };\n }\n\n const result = {\n id: this.generateId(key),\n path: ctx.path,\n updatedAt: response.LastModified,\n metadata: {\n kind: \"afs:document\",\n size: response.ContentLength,\n contentType: response.ContentType,\n lastModified: response.LastModified?.toISOString(),\n etag: response.ETag?.replace(/\"/g, \"\"),\n storageClass: response.StorageClass,\n platformRef: generatePlatformRef(this.options.bucket, this.options.region, key, false),\n },\n };\n\n // Cache the result\n if (this.statCache) {\n const cacheKey = createCacheKey(this.options.bucket, this.options.prefix ?? \"\", path);\n this.statCache.set(cacheKey, result);\n }\n\n return result;\n } catch (error: any) {\n // If 404, check if it's a directory prefix\n if (error?.name === \"NotFound\" || error?.$metadata?.httpStatusCode === 404) {\n // Check if there are objects with this prefix\n const listCommand = new ListObjectsV2Command({\n Bucket: this.options.bucket,\n Prefix: `${key}/`,\n MaxKeys: 1,\n });\n\n const listResponse = await this.client.send(listCommand);\n\n if (listResponse.Contents && listResponse.Contents.length > 0) {\n // There are objects under this prefix, so it's a directory\n return {\n id: this.generateId(`${key}/`),\n path: ctx.path,\n metadata: {\n kind: \"afs:node\",\n childrenCount: undefined,\n platformRef: generatePlatformRef(\n this.options.bucket,\n this.options.region,\n key,\n true,\n ),\n },\n };\n }\n\n // Also check for directory marker (key ending with /)\n try {\n const markerCommand = new HeadObjectCommand({\n Bucket: this.options.bucket,\n Key: `${key}/`,\n });\n const markerResponse = await this.client.send(markerCommand);\n\n return {\n id: this.generateId(`${key}/`),\n path: ctx.path,\n updatedAt: markerResponse.LastModified,\n metadata: {\n kind: \"afs:node\",\n childrenCount: undefined,\n platformRef: generatePlatformRef(\n this.options.bucket,\n this.options.region,\n key,\n true,\n ),\n },\n };\n } catch {\n // Neither file nor directory exists - ensure path has leading slash\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n throw new AFSNotFoundError(normalizedPath);\n }\n }\n\n throw error;\n }\n } catch (error) {\n if (error instanceof AFSNotFoundError) {\n throw error;\n }\n // Ensure path has leading slash for mapS3Error\n const normalizedPath = (ctx.params.path ?? \"\").startsWith(\"/\")\n ? (ctx.params.path ?? \"/\")\n : `/${ctx.params.path ?? \"\"}`;\n throw mapS3Error(error, normalizedPath);\n }\n }\n\n // ========== Stat Operations ==========\n\n @Stat(\"/\")\n @Stat(\"/:path*\")\n async statHandler(ctx: RouteContext<{ path?: string }>): Promise<AFSStatResult> {\n // Delegate to meta handler and convert to stat result\n const metaEntry = await this.metaHandler({\n ...ctx,\n path: ctx.path.endsWith(\"/.meta\") ? ctx.path : `${ctx.path}/.meta`,\n });\n\n return {\n data: {\n path: ctx.path,\n size: metaEntry.metadata?.size as number | undefined,\n childrenCount: metaEntry.metadata?.childrenCount as number | undefined,\n meta: metaEntry.metadata as Record<string, unknown>,\n },\n };\n }\n\n // ========== Write Operations ==========\n\n @Write(\"/:path*\")\n async writeHandler(\n ctx: RouteContext<{ path: string }>,\n payload: AFSWriteEntryPayload,\n ): Promise<AFSWriteResult> {\n try {\n const key = this.buildS3Key(ctx.params.path);\n\n // Prepare content\n let body: Buffer;\n if (typeof payload.content === \"string\") {\n body = Buffer.from(payload.content, \"utf-8\");\n } else if (Buffer.isBuffer(payload.content)) {\n body = payload.content;\n } else if (payload.content !== undefined) {\n // JSON content\n body = Buffer.from(JSON.stringify(payload.content), \"utf-8\");\n } else {\n body = Buffer.from(\"\");\n }\n\n // Determine content type from metadata or default\n const contentType =\n (payload.metadata?.mimeType as string) ??\n (payload.metadata?.contentType as string) ??\n \"application/octet-stream\";\n\n let etag: string | undefined;\n let versionId: string | undefined;\n\n // Use multipart upload for large files\n if (shouldUseMultipart(body.length)) {\n const result = await multipartUpload(this.client, this.options.bucket, key, body, {\n contentType,\n });\n etag = result.etag;\n versionId = result.versionId;\n } else {\n const command = new PutObjectCommand({\n Bucket: this.options.bucket,\n Key: key,\n Body: body,\n ContentType: contentType,\n });\n\n const response = await this.client.send(command);\n etag = response.ETag?.replace(/\"/g, \"\");\n versionId = response.VersionId;\n }\n\n const normalizedOutputPath = ctx.path.startsWith(\"/\") ? ctx.path : `/${ctx.path}`;\n\n // Invalidate caches for the written path\n this.invalidateCache(ctx.params.path);\n\n return {\n data: {\n id: this.generateId(key),\n path: normalizedOutputPath,\n content: payload.content,\n metadata: {\n size: body.length,\n etag,\n versionId,\n ...payload.metadata,\n },\n },\n };\n } catch (error) {\n const normalizedPath = ctx.params.path.startsWith(\"/\")\n ? ctx.params.path\n : `/${ctx.params.path}`;\n throw mapS3Error(error, normalizedPath);\n }\n }\n\n // ========== Delete Operations ==========\n\n @Delete(\"/:path*\")\n async deleteHandler(ctx: RouteContext<{ path: string }>): Promise<AFSDeleteResult> {\n try {\n const key = this.buildS3Key(ctx.params.path);\n\n // Check if the object exists first (S3 DeleteObject is idempotent and doesn't error on missing objects)\n try {\n const headCommand = new HeadObjectCommand({\n Bucket: this.options.bucket,\n Key: key,\n });\n await this.client.send(headCommand);\n } catch (error: any) {\n if (error?.name === \"NotFound\" || error?.$metadata?.httpStatusCode === 404) {\n throw new AFSNotFoundError(`/${ctx.params.path}`);\n }\n throw error;\n }\n\n const command = new DeleteObjectCommand({\n Bucket: this.options.bucket,\n Key: key,\n });\n\n await this.client.send(command);\n\n // Invalidate caches for the deleted path\n this.invalidateCache(ctx.params.path);\n\n return {\n message: `Successfully deleted: ${ctx.params.path}`,\n };\n } catch (error) {\n if (error instanceof AFSNotFoundError) {\n throw error;\n }\n throw mapS3Error(error, `/${ctx.params.path}`);\n }\n }\n\n // ========== Action System ==========\n\n @Actions(\"/:path*\")\n async listActionsHandler(ctx: RouteContext<{ path: string }>): Promise<AFSListResult> {\n const basePath = ctx.path.replace(/\\/\\.actions$/, \"\");\n return {\n data: [\n {\n id: \"select\",\n path: `${basePath}/.actions/select`,\n summary: \"Query with S3 Select\",\n metadata: { kind: \"afs:executable\", kinds: [\"afs:executable\", \"afs:node\"] },\n },\n {\n id: \"presign-download\",\n path: `${basePath}/.actions/presign-download`,\n summary: \"Generate download URL\",\n metadata: { kind: \"afs:executable\", kinds: [\"afs:executable\", \"afs:node\"] },\n },\n {\n id: \"presign-upload\",\n path: `${basePath}/.actions/presign-upload`,\n summary: \"Generate upload URL\",\n metadata: { kind: \"afs:executable\", kinds: [\"afs:executable\", \"afs:node\"] },\n },\n ],\n };\n }\n\n @Actions.Exec(\"/:path*\", \"select\")\n async selectActionHandler(\n ctx: RouteContext<{ path: string }>,\n args: Record<string, unknown>,\n ): Promise<AFSExecResult> {\n const expression = args.expression as string;\n if (!expression) {\n throw new AFSError(\"Missing required argument: expression\", \"AFS_INVALID_ARGUMENT\");\n }\n\n const normalizedPath = ctx.params.path.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n const key = this.options.prefix ? `${this.options.prefix}/${normalizedPath}` : normalizedPath;\n\n const result = await selectQuery(this.client, this.options.bucket, \"\", key, expression, {\n inputFormat: args.inputFormat as \"CSV\" | \"JSON\" | \"Parquet\" | undefined,\n outputFormat: args.outputFormat as \"CSV\" | \"JSON\" | undefined,\n });\n\n return {\n success: true,\n data: {\n records: result.records,\n stats: result.stats,\n },\n };\n }\n\n @Actions.Exec(\"/:path*\", \"presign-download\")\n async presignDownloadActionHandler(\n ctx: RouteContext<{ path: string }>,\n args: Record<string, unknown>,\n ): Promise<AFSExecResult> {\n const normalizedPath = ctx.params.path.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n const key = this.options.prefix ? `${this.options.prefix}/${normalizedPath}` : normalizedPath;\n\n let expiresIn = (args.expiresIn as number) ?? DEFAULT_EXPIRES_IN;\n if (expiresIn > MAX_EXPIRES_IN) expiresIn = MAX_EXPIRES_IN;\n if (expiresIn < 1) expiresIn = 1;\n\n const command = new GetObjectCommand({\n Bucket: this.options.bucket,\n Key: key,\n });\n\n const url = await getSignedUrl(this.client, command, { expiresIn });\n const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString();\n\n return {\n success: true,\n data: {\n url,\n expiresAt,\n },\n };\n }\n\n @Actions.Exec(\"/:path*\", \"presign-upload\")\n async presignUploadActionHandler(\n ctx: RouteContext<{ path: string }>,\n args: Record<string, unknown>,\n ): Promise<AFSExecResult> {\n const normalizedPath = ctx.params.path.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n const key = this.options.prefix ? `${this.options.prefix}/${normalizedPath}` : normalizedPath;\n\n let expiresIn = (args.expiresIn as number) ?? DEFAULT_EXPIRES_IN;\n if (expiresIn > MAX_EXPIRES_IN) expiresIn = MAX_EXPIRES_IN;\n if (expiresIn < 1) expiresIn = 1;\n\n const command = new PutObjectCommand({\n Bucket: this.options.bucket,\n Key: key,\n ContentType: (args.contentType as string) ?? \"application/octet-stream\",\n });\n\n const url = await getSignedUrl(this.client, command, { expiresIn });\n const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString();\n\n return {\n success: true,\n data: {\n url,\n expiresAt,\n },\n };\n }\n\n // ========== Legacy API compatibility (for migration) ==========\n\n /**\n * Generate a presigned URL for downloading an object\n * @deprecated Use action /.actions/presign-download instead\n */\n async getPresignedDownloadUrl(path: string, options?: { expiresIn?: number }): Promise<string> {\n const result = await this.presignDownloadActionHandler(\n { path: `/${path}`, params: { path }, options: {} },\n { expiresIn: options?.expiresIn },\n );\n return result.data!.url as string;\n }\n\n /**\n * Generate a presigned URL for uploading an object\n * @deprecated Use action /.actions/presign-upload instead\n */\n async getPresignedUploadUrl(\n path: string,\n options?: { expiresIn?: number; contentType?: string },\n ): Promise<string> {\n const result = await this.presignUploadActionHandler(\n { path: `/${path}`, params: { path }, options: {} },\n { expiresIn: options?.expiresIn, contentType: options?.contentType },\n );\n return result.data!.url as string;\n }\n\n /**\n * List all versions of an object\n * @deprecated Use list on /:path/@versions instead\n */\n async listVersions(path: string): Promise<\n Array<{\n versionId: string;\n isLatest: boolean;\n lastModified?: Date;\n size: number;\n etag?: string;\n }>\n > {\n const result = await this.listVersionsHandler({\n path: `/${path}/@versions`,\n params: { path },\n options: {},\n });\n return result.data.map((entry) => ({\n versionId: entry.metadata?.versionId as string,\n isLatest: entry.metadata?.isLatest as boolean,\n lastModified: entry.updatedAt,\n size: (entry.metadata?.size as number) ?? 0,\n etag: entry.metadata?.etag as string | undefined,\n }));\n }\n\n /**\n * Read a specific version of an object\n * @deprecated Use read on /:path/@versions/:versionId instead\n */\n async readVersion(\n path: string,\n versionId: string,\n ): Promise<{ content: string; metadata: Record<string, unknown> }> {\n const result = await this.readVersionHandler({\n path: `/${path}/@versions/${versionId}`,\n params: { path, versionId },\n options: {},\n });\n return {\n content: result.content as string,\n metadata: result.metadata as Record<string, unknown>,\n };\n }\n\n /**\n * Run a SQL-like query on a CSV/JSON/Parquet file (S3 Select)\n * @deprecated Use action /.actions/select instead\n */\n async select(\n path: string,\n query: string,\n options?: {\n inputFormat?: \"CSV\" | \"JSON\" | \"Parquet\";\n csv?: {\n fieldDelimiter?: string;\n recordDelimiter?: string;\n fileHeaderInfo?: \"USE\" | \"IGNORE\" | \"NONE\";\n };\n json?: {\n type?: \"DOCUMENT\" | \"LINES\";\n };\n },\n ): Promise<{\n records: unknown[];\n stats?: { bytesScanned: number; bytesProcessed: number; bytesReturned: number };\n }> {\n return selectQuery(\n this.client,\n this.options.bucket,\n this.options.prefix ?? \"\",\n path,\n query,\n options,\n );\n }\n\n /**\n * Create a directory marker\n * @deprecated Use write with empty content instead\n */\n async mkdir(path: string): Promise<void> {\n const key = this.buildS3Key(path);\n const dirKey = key.endsWith(\"/\") ? key : `${key}/`;\n\n const command = new PutObjectCommand({\n Bucket: this.options.bucket,\n Key: dirKey,\n Body: Buffer.from(\"\"),\n ContentType: \"application/x-directory\",\n });\n\n await this.client.send(command);\n }\n}\n\n// Type check: AFSS3 should implement AFSModuleClass\nconst _typeCheck: AFSModuleClass<AFSS3, AFSS3Options> = AFSS3;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["AFSError","response","objectCount","prefixCount","lines"],"sources":["../src/cache.ts","../src/client.ts","../src/errors.ts","../src/operations/multipart.ts","../src/operations/select.ts","../src/platform-ref.ts","../src/types.ts","../src/s3-afs.ts"],"sourcesContent":["/**\n * S3 Response Caching (Phase 4)\n *\n * LRU cache with TTL for S3 list and stat results.\n */\n\n/**\n * Cache entry with expiration\n */\ninterface CacheEntry<T> {\n value: T;\n expiresAt: number;\n}\n\n/**\n * LRU Cache with TTL support\n */\nexport class LRUCache<T> {\n private cache = new Map<string, CacheEntry<T>>();\n private maxSize: number;\n private defaultTtl: number;\n\n /**\n * Create a new LRU cache\n *\n * @param maxSize - Maximum number of entries (default: 1000)\n * @param defaultTtl - Default TTL in seconds (default: 60)\n */\n constructor(maxSize = 1000, defaultTtl = 60) {\n this.maxSize = maxSize;\n this.defaultTtl = defaultTtl;\n }\n\n /**\n * Get a value from the cache\n *\n * @param key - Cache key\n * @returns Cached value or undefined if not found/expired\n */\n get(key: string): T | undefined {\n const entry = this.cache.get(key);\n\n if (!entry) {\n return undefined;\n }\n\n // Check if expired\n if (Date.now() > entry.expiresAt) {\n this.cache.delete(key);\n return undefined;\n }\n\n // Move to end (most recently used)\n this.cache.delete(key);\n this.cache.set(key, entry);\n\n return entry.value;\n }\n\n /**\n * Set a value in the cache\n *\n * @param key - Cache key\n * @param value - Value to cache\n * @param ttl - TTL in seconds (optional, uses default)\n */\n set(key: string, value: T, ttl?: number): void {\n // Remove if already exists (to update position)\n if (this.cache.has(key)) {\n this.cache.delete(key);\n }\n\n // Evict oldest entries if at capacity\n while (this.cache.size >= this.maxSize) {\n const oldestKey = this.cache.keys().next().value;\n if (oldestKey) {\n this.cache.delete(oldestKey);\n }\n }\n\n const expiresAt = Date.now() + (ttl ?? this.defaultTtl) * 1000;\n this.cache.set(key, { value, expiresAt });\n }\n\n /**\n * Delete a value from the cache\n *\n * @param key - Cache key\n */\n delete(key: string): void {\n this.cache.delete(key);\n }\n\n /**\n * Delete all entries matching a prefix\n *\n * @param prefix - Key prefix to match\n */\n deleteByPrefix(prefix: string): void {\n for (const key of this.cache.keys()) {\n if (key.startsWith(prefix)) {\n this.cache.delete(key);\n }\n }\n }\n\n /**\n * Clear all entries\n */\n clear(): void {\n this.cache.clear();\n }\n\n /**\n * Get the number of entries in the cache\n */\n get size(): number {\n return this.cache.size;\n }\n\n /**\n * Prune expired entries\n */\n prune(): void {\n const now = Date.now();\n for (const [key, entry] of this.cache.entries()) {\n if (now > entry.expiresAt) {\n this.cache.delete(key);\n }\n }\n }\n}\n\n/**\n * Create a cache key from bucket, prefix, and path\n */\nexport function createCacheKey(\n bucket: string,\n prefix: string,\n path: string,\n suffix?: string,\n): string {\n const base = `${bucket}:${prefix}:${path}`;\n return suffix ? `${base}:${suffix}` : base;\n}\n","/**\n * S3 Client Factory\n *\n * Creates and configures AWS S3 clients.\n */\n\nimport { S3Client, type S3ClientConfig } from \"@aws-sdk/client-s3\";\nimport type { AFSS3Options } from \"./types.js\";\n\n/**\n * Create an S3 client with the given options\n *\n * Uses AWS SDK's default credential chain:\n * 1. Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)\n * 2. Shared credentials file (~/.aws/credentials)\n * 3. IAM role (EC2/ECS/Lambda)\n * 4. SSO credentials\n *\n * @param options - S3 provider options\n * @returns Configured S3 client\n */\nexport function createS3Client(options: AFSS3Options): S3Client {\n const config: S3ClientConfig = {};\n\n // Region configuration\n // For S3-compatible services (MinIO, R2, B2), default to \"us-east-1\" if no region specified\n if (options.region) {\n config.region = options.region;\n } else if (options.endpoint) {\n config.region = \"us-east-1\";\n }\n\n // Custom endpoint for S3-compatible services\n if (options.endpoint) {\n config.endpoint = options.endpoint;\n }\n\n // Force path-style URLs (needed for MinIO, some S3-compatible services)\n if (options.forcePathStyle) {\n config.forcePathStyle = true;\n }\n\n // Explicit credentials (for testing or non-AWS environments)\n if (options.credentials) {\n config.credentials = {\n accessKeyId: options.credentials.accessKeyId,\n secretAccessKey: options.credentials.secretAccessKey,\n sessionToken: options.credentials.sessionToken,\n };\n }\n\n return new S3Client(config);\n}\n","/**\n * S3 Error Handling\n *\n * Maps AWS S3 errors to AFS errors.\n */\n\nimport { AFSNotFoundError } from \"@aigne/afs\";\n\n/**\n * AFS error codes\n */\nexport const AFSErrorCode = {\n ENTRY_NOT_FOUND: \"ENTRY_NOT_FOUND\",\n MODULE_NOT_FOUND: \"MODULE_NOT_FOUND\",\n PERMISSION_DENIED: \"PERMISSION_DENIED\",\n AUTH_ERROR: \"AUTH_ERROR\",\n RATE_LIMITED: \"RATE_LIMITED\",\n TYPE_MISMATCH: \"TYPE_MISMATCH\",\n INTERNAL_ERROR: \"INTERNAL_ERROR\",\n} as const;\n\nexport type AFSErrorCode = (typeof AFSErrorCode)[keyof typeof AFSErrorCode];\n\n/**\n * AFS Error class\n */\nexport class AFSError extends Error {\n readonly code: AFSErrorCode;\n readonly retryAfter?: number;\n\n constructor(code: AFSErrorCode, message: string, options?: { retryAfter?: number }) {\n super(message);\n this.name = \"AFSError\";\n this.code = code;\n this.retryAfter = options?.retryAfter;\n }\n}\n\n/**\n * Map S3 error to AFS error\n *\n * @param error - AWS S3 error\n * @param path - Optional path for AFSNotFoundError (with leading slash)\n * @returns AFS error (or throws the original error if it's already an AFS error)\n */\nexport function mapS3Error(error: unknown, path?: string): AFSError | AFSNotFoundError | never {\n // Pass through AFS errors unchanged (check by name since different module instances)\n if (error && typeof error === \"object\" && \"name\" in error) {\n const errorObj = error as { name?: string; code?: string };\n if (\n errorObj.name === \"AFSNotFoundError\" ||\n errorObj.name === \"AFSError\" ||\n errorObj.code === \"AFS_NOT_FOUND\"\n ) {\n throw error;\n }\n }\n\n // Handle AWS SDK errors\n if (error && typeof error === \"object\" && \"name\" in error) {\n const awsError = error as {\n name: string;\n message?: string;\n $metadata?: { httpStatusCode?: number };\n };\n const message = awsError.message ?? \"Unknown S3 error\";\n\n switch (awsError.name) {\n case \"NoSuchBucket\":\n return new AFSError(AFSErrorCode.MODULE_NOT_FOUND, `Bucket not found: ${message}`);\n\n case \"NoSuchKey\":\n case \"NotFound\":\n if (path) {\n return new AFSNotFoundError(path);\n }\n return new AFSError(AFSErrorCode.ENTRY_NOT_FOUND, `Object not found: ${message}`);\n\n case \"AccessDenied\":\n case \"Forbidden\":\n return new AFSError(AFSErrorCode.PERMISSION_DENIED, `Access denied: ${message}`);\n\n case \"InvalidAccessKeyId\":\n case \"SignatureDoesNotMatch\":\n case \"ExpiredToken\":\n case \"TokenRefreshRequired\":\n return new AFSError(AFSErrorCode.AUTH_ERROR, `Authentication failed: ${message}`);\n\n case \"SlowDown\":\n case \"ServiceUnavailable\":\n return new AFSError(AFSErrorCode.RATE_LIMITED, `Rate limited: ${message}`, {\n retryAfter: 1000,\n });\n\n default: {\n // Check HTTP status code\n const statusCode = awsError.$metadata?.httpStatusCode;\n if (statusCode === 404) {\n if (path) {\n return new AFSNotFoundError(path);\n }\n return new AFSError(AFSErrorCode.ENTRY_NOT_FOUND, message);\n }\n if (statusCode === 403) {\n return new AFSError(AFSErrorCode.PERMISSION_DENIED, message);\n }\n if (statusCode === 401) {\n return new AFSError(AFSErrorCode.AUTH_ERROR, message);\n }\n if (statusCode === 429 || statusCode === 503) {\n return new AFSError(AFSErrorCode.RATE_LIMITED, message, { retryAfter: 1000 });\n }\n\n return new AFSError(AFSErrorCode.INTERNAL_ERROR, message);\n }\n }\n }\n\n // Handle generic errors\n if (error instanceof Error) {\n return new AFSError(AFSErrorCode.INTERNAL_ERROR, error.message);\n }\n\n return new AFSError(AFSErrorCode.INTERNAL_ERROR, String(error));\n}\n","/**\n * S3 Multipart Upload (Phase 2)\n *\n * Handles multipart upload for large files (>5GB).\n * S3 requires multipart upload for objects larger than 5GB.\n */\n\nimport {\n AbortMultipartUploadCommand,\n CompleteMultipartUploadCommand,\n CreateMultipartUploadCommand,\n type S3Client,\n UploadPartCommand,\n} from \"@aws-sdk/client-s3\";\nimport { mapS3Error } from \"../errors.js\";\n\n/**\n * Minimum part size (5MB) - AWS S3 requirement\n */\nconst MIN_PART_SIZE = 5 * 1024 * 1024;\n\n/**\n * Maximum part size (5GB) - AWS S3 requirement\n */\nconst MAX_PART_SIZE = 5 * 1024 * 1024 * 1024;\n\n/**\n * Threshold for switching to multipart upload (100MB)\n * Files larger than this will use multipart upload\n */\nexport const MULTIPART_THRESHOLD = 100 * 1024 * 1024;\n\n/**\n * Default part size (10MB)\n */\nconst DEFAULT_PART_SIZE = 10 * 1024 * 1024;\n\n/**\n * Maximum number of parts (10,000) - AWS S3 requirement\n */\nconst MAX_PARTS = 10000;\n\ninterface MultipartUploadOptions {\n /** Part size in bytes (default: 10MB) */\n partSize?: number;\n /** Content type */\n contentType?: string;\n /** Metadata to attach */\n metadata?: Record<string, string>;\n}\n\ninterface MultipartUploadResult {\n /** ETag of the completed upload */\n etag: string;\n /** Version ID if versioning is enabled */\n versionId?: string;\n}\n\n/**\n * Calculate optimal part size based on file size\n */\nfunction calculatePartSize(fileSize: number, requestedPartSize?: number): number {\n let partSize = requestedPartSize ?? DEFAULT_PART_SIZE;\n\n // Ensure minimum part size\n if (partSize < MIN_PART_SIZE) {\n partSize = MIN_PART_SIZE;\n }\n\n // Ensure maximum part size\n if (partSize > MAX_PART_SIZE) {\n partSize = MAX_PART_SIZE;\n }\n\n // If file is too large for current part size, increase it\n const partsNeeded = Math.ceil(fileSize / partSize);\n if (partsNeeded > MAX_PARTS) {\n // Calculate minimum part size needed to fit in MAX_PARTS\n partSize = Math.ceil(fileSize / MAX_PARTS);\n // Round up to next MB for efficiency\n partSize = Math.ceil(partSize / (1024 * 1024)) * 1024 * 1024;\n }\n\n return partSize;\n}\n\n/**\n * Upload a large file using multipart upload\n *\n * @param client - S3 client\n * @param bucket - Bucket name\n * @param key - Object key\n * @param data - File content as Buffer\n * @param options - Upload options\n * @returns Upload result with ETag\n */\nexport async function multipartUpload(\n client: S3Client,\n bucket: string,\n key: string,\n data: Buffer,\n options?: MultipartUploadOptions,\n): Promise<MultipartUploadResult> {\n const fileSize = data.length;\n const partSize = calculatePartSize(fileSize, options?.partSize);\n\n // Initiate multipart upload\n const createCommand = new CreateMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n ContentType: options?.contentType ?? \"application/octet-stream\",\n Metadata: options?.metadata,\n });\n\n const createResponse = await client.send(createCommand);\n const uploadId = createResponse.UploadId;\n\n if (!uploadId) {\n throw new Error(\"Failed to initiate multipart upload: no uploadId returned\");\n }\n\n const parts: { ETag: string; PartNumber: number }[] = [];\n\n try {\n // Upload parts\n const totalParts = Math.ceil(fileSize / partSize);\n\n for (let partNumber = 1; partNumber <= totalParts; partNumber++) {\n const start = (partNumber - 1) * partSize;\n const end = Math.min(start + partSize, fileSize);\n const partData = data.subarray(start, end);\n\n const uploadPartCommand = new UploadPartCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n PartNumber: partNumber,\n Body: partData,\n });\n\n const uploadPartResponse = await client.send(uploadPartCommand);\n\n if (!uploadPartResponse.ETag) {\n throw new Error(`Failed to upload part ${partNumber}: no ETag returned`);\n }\n\n parts.push({\n ETag: uploadPartResponse.ETag,\n PartNumber: partNumber,\n });\n }\n\n // Complete multipart upload\n const completeCommand = new CompleteMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n MultipartUpload: {\n Parts: parts,\n },\n });\n\n const completeResponse = await client.send(completeCommand);\n\n return {\n etag: completeResponse.ETag?.replace(/\"/g, \"\") ?? \"\",\n versionId: completeResponse.VersionId,\n };\n } catch (error) {\n // Abort the multipart upload on failure\n try {\n const abortCommand = new AbortMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n });\n await client.send(abortCommand);\n } catch {\n // Ignore abort errors\n }\n\n throw mapS3Error(error);\n }\n}\n\n/**\n * Check if a file should use multipart upload\n */\nexport function shouldUseMultipart(size: number): boolean {\n return size >= MULTIPART_THRESHOLD;\n}\n","/**\n * S3 Select Support (Phase 3)\n *\n * Runs SQL-like queries on CSV and JSON files stored in S3.\n * This enables server-side filtering, reducing data transfer.\n */\n\nimport {\n type S3Client,\n SelectObjectContentCommand,\n type SelectObjectContentEventStream,\n} from \"@aws-sdk/client-s3\";\nimport { mapS3Error } from \"../errors.js\";\n\n/**\n * Input serialization format\n */\nexport type InputFormat = \"CSV\" | \"JSON\" | \"Parquet\";\n\n/**\n * Select query options\n */\nexport interface SelectOptions {\n /** Input format (default: auto-detected from extension) */\n inputFormat?: InputFormat;\n /** CSV options */\n csv?: {\n /** Field delimiter (default: ,) */\n fieldDelimiter?: string;\n /** Record delimiter (default: \\n) */\n recordDelimiter?: string;\n /** Whether first row is header (default: true) */\n fileHeaderInfo?: \"USE\" | \"IGNORE\" | \"NONE\";\n /** Quote character (default: \") */\n quoteCharacter?: string;\n /** Comment character */\n comments?: string;\n };\n /** JSON options */\n json?: {\n /** JSON document type */\n type?: \"DOCUMENT\" | \"LINES\";\n };\n /** Output format (default: JSON) */\n outputFormat?: \"CSV\" | \"JSON\";\n}\n\n/**\n * Select query result\n */\nexport interface SelectResult {\n /** Query results as array of records */\n records: unknown[];\n /** Stats about the query */\n stats?: {\n bytesScanned: number;\n bytesProcessed: number;\n bytesReturned: number;\n };\n}\n\n/**\n * Detect input format from file extension\n */\nfunction detectInputFormat(path: string): InputFormat {\n const ext = path.toLowerCase().split(\".\").pop();\n switch (ext) {\n case \"csv\":\n case \"tsv\":\n return \"CSV\";\n case \"json\":\n case \"jsonl\":\n case \"ndjson\":\n return \"JSON\";\n case \"parquet\":\n return \"Parquet\";\n default:\n return \"JSON\";\n }\n}\n\n/**\n * Run a SQL-like query on an S3 object\n *\n * @param client - S3 client\n * @param bucket - Bucket name\n * @param mountPrefix - Mount prefix from options\n * @param path - Path relative to mount point\n * @param query - SQL query (e.g., \"SELECT * FROM s3object WHERE age > 21\")\n * @param options - Select options\n * @returns Query results\n */\nexport async function selectQuery(\n client: S3Client,\n bucket: string,\n mountPrefix: string,\n path: string,\n query: string,\n options?: SelectOptions,\n): Promise<SelectResult> {\n try {\n const normalizedPath = path.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n const key = mountPrefix ? `${mountPrefix}/${normalizedPath}` : normalizedPath;\n\n const inputFormat = options?.inputFormat ?? detectInputFormat(path);\n const outputFormat = options?.outputFormat ?? \"JSON\";\n\n // Build input serialization based on format\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const inputSerialization: Record<string, any> = {};\n\n if (inputFormat === \"CSV\") {\n inputSerialization.CSV = {\n FieldDelimiter: options?.csv?.fieldDelimiter ?? \",\",\n RecordDelimiter: options?.csv?.recordDelimiter ?? \"\\n\",\n FileHeaderInfo: options?.csv?.fileHeaderInfo ?? \"USE\",\n QuoteCharacter: options?.csv?.quoteCharacter ?? '\"',\n Comments: options?.csv?.comments,\n };\n } else if (inputFormat === \"JSON\") {\n inputSerialization.JSON = {\n Type: options?.json?.type ?? \"DOCUMENT\",\n };\n } else if (inputFormat === \"Parquet\") {\n inputSerialization.Parquet = {};\n }\n\n // Build output serialization\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const outputSerialization: Record<string, any> = {};\n\n if (outputFormat === \"JSON\") {\n outputSerialization.JSON = {};\n } else {\n outputSerialization.CSV = {};\n }\n\n const command = new SelectObjectContentCommand({\n Bucket: bucket,\n Key: key,\n ExpressionType: \"SQL\",\n Expression: query,\n InputSerialization: inputSerialization,\n OutputSerialization: outputSerialization,\n });\n\n const response = await client.send(command);\n\n // Process the event stream\n const records: unknown[] = [];\n let stats: SelectResult[\"stats\"] | undefined;\n\n if (response.Payload) {\n for await (const event of response.Payload as AsyncIterable<SelectObjectContentEventStream>) {\n if (event.Records?.Payload) {\n // Parse records from payload\n const text = new TextDecoder().decode(event.Records.Payload);\n // Split by newline and parse each line as JSON\n const lines = text.split(\"\\n\").filter((line) => line.trim());\n for (const line of lines) {\n try {\n records.push(JSON.parse(line));\n } catch {\n // If not valid JSON, just push as string\n records.push(line);\n }\n }\n }\n if (event.Stats?.Details) {\n stats = {\n bytesScanned: Number(event.Stats.Details.BytesScanned ?? 0),\n bytesProcessed: Number(event.Stats.Details.BytesProcessed ?? 0),\n bytesReturned: Number(event.Stats.Details.BytesReturned ?? 0),\n };\n }\n }\n }\n\n return { records, stats };\n } catch (error) {\n throw mapS3Error(error);\n }\n}\n","/**\n * S3 Platform Reference\n *\n * Generates AWS Console URLs for S3 objects and directories.\n */\n\n/**\n * Platform reference containing console URL\n */\nexport interface S3PlatformRef {\n consoleUrl: string;\n}\n\n/**\n * Generate platform reference with AWS Console URL\n *\n * @param bucket - S3 bucket name\n * @param region - AWS region (defaults to us-east-1)\n * @param key - S3 object key (without leading slash)\n * @param isDirectory - Whether this is a directory (prefix)\n * @returns Platform reference with console URL\n */\nexport function generatePlatformRef(\n bucket: string,\n region: string | undefined,\n key: string,\n isDirectory: boolean,\n): S3PlatformRef {\n const effectiveRegion = region || \"us-east-1\";\n\n if (isDirectory) {\n // Directory URL: https://s3.console.aws.amazon.com/s3/buckets/{bucket}?region={region}&prefix={prefix}/\n const normalizedPrefix = key.endsWith(\"/\") ? key : key ? `${key}/` : \"\";\n return {\n consoleUrl: `https://s3.console.aws.amazon.com/s3/buckets/${bucket}?region=${effectiveRegion}&prefix=${normalizedPrefix}`,\n };\n }\n\n // File URL: https://s3.console.aws.amazon.com/s3/object/{bucket}?region={region}&prefix={key}\n const encodedKey = encodeURIComponent(key).replace(/%2F/g, \"/\");\n return {\n consoleUrl: `https://s3.console.aws.amazon.com/s3/object/${bucket}?region=${effectiveRegion}&prefix=${encodedKey}`,\n };\n}\n","/**\n * AFS S3 Provider Types\n */\n\nimport { camelize, optionalize } from \"@aigne/afs/utils/zod\";\nimport type { S3Client } from \"@aws-sdk/client-s3\";\nimport { z } from \"zod\";\n\n/**\n * Configuration options for AFSS3\n */\nexport interface AFSS3Options {\n /** Module name (used as mount path segment) */\n name?: string;\n\n /** Module description */\n description?: string;\n\n /** S3 bucket name */\n bucket: string;\n\n /** Key prefix (optional) */\n prefix?: string;\n\n /** AWS region (defaults to environment) */\n region?: string;\n\n /** Access mode */\n accessMode?: \"readonly\" | \"readwrite\";\n\n /** Custom endpoint for S3-compatible services (MinIO, R2, B2) */\n endpoint?: string;\n\n /** Force path-style URLs (needed for some S3-compatible services) */\n forcePathStyle?: boolean;\n\n /** AWS credentials profile name */\n profile?: string;\n\n /** Explicit credentials (for testing or non-AWS environments) */\n credentials?: {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string;\n };\n\n /** Cache TTL in seconds (0 = no cache, Phase 4) */\n cacheTtl?: number;\n\n /**\n * Pre-configured S3 client (for testing or advanced use cases)\n * If provided, endpoint/region/credentials/profile options are ignored.\n */\n client?: S3Client;\n}\n\n/**\n * S3 bucket name validation regex\n * - 3-63 characters\n * - lowercase letters, numbers, hyphens, dots\n * - must start and end with letter or number\n */\nconst BUCKET_NAME_REGEX = /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/;\n\n/**\n * Zod schema for options validation\n */\nexport const afss3OptionsSchema = camelize(\n z\n .object({\n name: optionalize(z.string()),\n description: optionalize(z.string()),\n bucket: z.string().regex(BUCKET_NAME_REGEX, \"Invalid S3 bucket name\"),\n prefix: optionalize(z.string()),\n region: optionalize(z.string()),\n accessMode: optionalize(z.enum([\"readonly\", \"readwrite\"])),\n endpoint: optionalize(z.string().url()),\n forcePathStyle: optionalize(z.boolean()),\n profile: optionalize(z.string()),\n credentials: optionalize(\n z.object({\n accessKeyId: z.string(),\n secretAccessKey: z.string(),\n sessionToken: optionalize(z.string()),\n }),\n ),\n cacheTtl: optionalize(z.number().int().min(0)),\n })\n .strict(),\n);\n\n/**\n * Parsed S3 URI\n */\nexport interface ParsedS3Uri {\n bucket: string;\n prefix: string;\n}\n","/**\n * AFS S3 Provider\n *\n * S3 provider using AFSBaseProvider decorator routing pattern.\n * Provides access to AWS S3 and S3-compatible storage (MinIO, R2, B2).\n */\n\nimport {\n Actions,\n type AFSAccessMode,\n AFSBaseProvider,\n type AFSDeleteResult,\n type AFSEntry,\n AFSError,\n type AFSExecResult,\n type AFSExplainResult,\n type AFSListResult,\n type AFSModuleClass,\n type AFSModuleLoadParams,\n AFSNotFoundError,\n type AFSSearchResult,\n type AFSStatResult,\n type AFSWriteEntryPayload,\n type AFSWriteResult,\n type CapabilitiesManifest,\n Delete,\n Explain,\n List,\n Meta,\n Read,\n type RouteContext,\n Search,\n Stat,\n Write,\n} from \"@aigne/afs\";\nimport { zodParse } from \"@aigne/afs/utils/zod\";\nimport {\n DeleteObjectCommand,\n GetObjectCommand,\n HeadObjectCommand,\n ListObjectsV2Command,\n ListObjectVersionsCommand,\n PutObjectCommand,\n type S3Client,\n} from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport { joinURL } from \"ufo\";\nimport { createCacheKey, LRUCache } from \"./cache.js\";\nimport { createS3Client } from \"./client.js\";\nimport { mapS3Error } from \"./errors.js\";\nimport { multipartUpload, shouldUseMultipart } from \"./operations/multipart.js\";\nimport { selectQuery } from \"./operations/select.js\";\nimport { generatePlatformRef } from \"./platform-ref.js\";\nimport { type AFSS3Options, afss3OptionsSchema } from \"./types.js\";\n\n/**\n * Default URL expiration time (1 hour)\n */\nconst DEFAULT_EXPIRES_IN = 3600;\n\n/**\n * Maximum expiration time (7 days)\n */\nconst MAX_EXPIRES_IN = 604800;\n\n/**\n * AFSS3 Provider using Base Provider pattern\n *\n * Provides access to AWS S3 and S3-compatible storage through AFS.\n * Uses decorator routing (@List, @Read, @Write, @Delete, @Meta, @Actions).\n *\n * @example\n * ```typescript\n * const s3 = new AFSS3({\n * bucket: \"my-bucket\",\n * prefix: \"data\",\n * region: \"us-east-1\",\n * });\n *\n * // Mount to AFS\n * afs.mount(s3);\n *\n * // List objects\n * const result = await afs.list(\"/modules/my-bucket/data\");\n *\n * // Read object\n * const content = await afs.read(\"/modules/my-bucket/data/file.json\");\n * ```\n */\nexport class AFSS3 extends AFSBaseProvider {\n override readonly name: string;\n override readonly description?: string;\n override readonly accessMode: AFSAccessMode;\n\n private options: Required<Pick<AFSS3Options, \"bucket\">> & AFSS3Options;\n private client: S3Client;\n private listCache?: LRUCache<AFSListResult>;\n private statCache?: LRUCache<AFSEntry>;\n\n constructor(options: AFSS3Options) {\n super();\n\n // Extract client before validation (not serializable)\n const { client, ...restOptions } = options;\n\n // Validate options (excluding client)\n const parsed = afss3OptionsSchema.parse(restOptions);\n\n this.options = {\n ...parsed,\n bucket: parsed.bucket,\n prefix: parsed.prefix ?? \"\",\n accessMode: parsed.accessMode ?? \"readonly\",\n };\n\n this.name = parsed.name ?? parsed.bucket;\n this.description = parsed.description ?? `S3 bucket: ${parsed.bucket}`;\n this.accessMode = this.options.accessMode ?? \"readonly\";\n\n // Use provided client or create one\n this.client = client ?? createS3Client(this.options);\n\n // Initialize caches if cacheTtl is set\n if (parsed.cacheTtl && parsed.cacheTtl > 0) {\n this.listCache = new LRUCache<AFSListResult>(1000, parsed.cacheTtl);\n this.statCache = new LRUCache<AFSEntry>(5000, parsed.cacheTtl);\n }\n }\n\n /**\n * Schema for configuration validation\n */\n static schema() {\n return afss3OptionsSchema;\n }\n\n /**\n * Load from configuration file\n */\n static async load({ basePath, config }: AFSModuleLoadParams = {}): Promise<AFSS3> {\n const options = zodParse(afss3OptionsSchema, config, { prefix: basePath });\n return new AFSS3(options);\n }\n\n // ========== Helper Methods ==========\n\n /**\n * Build the full S3 key from a path\n */\n private buildS3Key(path: string): string {\n const normalizedPath = path.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n return this.options.prefix ? `${this.options.prefix}/${normalizedPath}` : normalizedPath;\n }\n\n /**\n * Generate a unique ID for an S3 object\n */\n private generateId(key: string): string {\n const cleanKey = key.replace(/^\\/+/, \"\");\n return `s3://${this.options.bucket}/${cleanKey}`;\n }\n\n /**\n * Invalidate caches for a given path\n */\n private invalidateCache(path: string): void {\n if (this.statCache) {\n const statKey = createCacheKey(this.options.bucket, this.options.prefix ?? \"\", path);\n this.statCache.delete(statKey);\n }\n\n if (this.listCache) {\n // Invalidate list cache for parent directory\n const parentPath = path.split(\"/\").slice(0, -1).join(\"/\") || \"/\";\n const listPrefix = createCacheKey(this.options.bucket, this.options.prefix ?? \"\", parentPath);\n this.listCache.deleteByPrefix(listPrefix);\n }\n }\n\n /**\n * Clear all caches\n */\n clearCache(): void {\n this.listCache?.clear();\n this.statCache?.clear();\n }\n\n // ========== List Operations ==========\n\n @List(\"/\")\n @List(\"/:path*\")\n async listHandler(ctx: RouteContext<{ path?: string }>): Promise<AFSListResult> {\n try {\n const path = ctx.params.path ?? \"\";\n const normalizedPath = path.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n\n // Build the full S3 prefix\n const fullPrefix = this.options.prefix\n ? normalizedPath\n ? `${this.options.prefix}/${normalizedPath}/`\n : `${this.options.prefix}/`\n : normalizedPath\n ? `${normalizedPath}/`\n : \"\";\n\n const opts = ctx.options as { limit?: number; maxChildren?: number } | undefined;\n const maxChildren = opts?.limit ?? opts?.maxChildren ?? 1000;\n\n // Check cache first\n if (this.listCache) {\n const cacheKey = createCacheKey(\n this.options.bucket,\n this.options.prefix ?? \"\",\n normalizedPath,\n JSON.stringify(ctx.options ?? {}),\n );\n const cached = this.listCache.get(cacheKey);\n if (cached) {\n return cached;\n }\n }\n\n const result = await this.listWithDelimiter(fullPrefix, normalizedPath, maxChildren);\n\n // Cache the result\n if (this.listCache) {\n const cacheKey = createCacheKey(\n this.options.bucket,\n this.options.prefix ?? \"\",\n normalizedPath,\n JSON.stringify(ctx.options ?? {}),\n );\n this.listCache.set(cacheKey, result);\n\n // Also populate stat cache from list results\n if (this.statCache) {\n for (const entry of result.data) {\n const statKey = createCacheKey(\n this.options.bucket,\n this.options.prefix ?? \"\",\n entry.path,\n );\n this.statCache.set(statKey, entry);\n }\n }\n }\n\n return result;\n } catch (error) {\n const normalizedPath = ctx.params.path\n ? ctx.params.path.startsWith(\"/\")\n ? ctx.params.path\n : `/${ctx.params.path}`\n : \"/\";\n throw mapS3Error(error, normalizedPath);\n }\n }\n\n /**\n * List with delimiter (single level)\n */\n private async listWithDelimiter(\n prefix: string,\n basePath: string,\n maxChildren: number,\n ): Promise<AFSListResult> {\n const childEntries: AFSEntry[] = [];\n let continuationToken: string | undefined;\n\n do {\n const command = new ListObjectsV2Command({\n Bucket: this.options.bucket,\n Prefix: prefix,\n Delimiter: \"/\",\n MaxKeys: Math.min(maxChildren - childEntries.length, 1000),\n ContinuationToken: continuationToken,\n });\n\n const response = await this.client.send(command);\n\n // Add directories from CommonPrefixes\n if (response.CommonPrefixes) {\n for (const commonPrefix of response.CommonPrefixes) {\n if (!commonPrefix.Prefix) continue;\n\n // Extract directory name\n const dirName = commonPrefix.Prefix.slice(prefix.length).replace(/\\/$/, \"\");\n if (!dirName) continue;\n\n const normalizedPath = joinURL(\"/\", basePath, dirName);\n\n childEntries.push({\n id: this.generateId(commonPrefix.Prefix),\n path: normalizedPath,\n meta: {\n childrenCount: -1,\n platformRef: generatePlatformRef(\n this.options.bucket,\n this.options.region,\n commonPrefix.Prefix,\n true,\n ),\n },\n });\n\n if (childEntries.length >= maxChildren) break;\n }\n }\n\n // Add files from Contents\n if (response.Contents) {\n for (const object of response.Contents) {\n if (!object.Key) continue;\n\n // Skip the prefix itself (if it's a \"directory marker\")\n if (object.Key === prefix) continue;\n\n // Extract file name\n const fileName = object.Key.slice(prefix.length);\n if (!fileName || fileName.includes(\"/\")) continue;\n\n const normalizedPath = joinURL(\"/\", basePath, fileName);\n\n childEntries.push({\n id: this.generateId(object.Key),\n path: normalizedPath,\n updatedAt: object.LastModified,\n meta: {\n size: object.Size,\n lastModified: object.LastModified?.toISOString(),\n etag: object.ETag?.replace(/\"/g, \"\"),\n storageClass: object.StorageClass,\n platformRef: generatePlatformRef(\n this.options.bucket,\n this.options.region,\n object.Key,\n false,\n ),\n },\n });\n\n if (childEntries.length >= maxChildren) break;\n }\n }\n\n continuationToken = response.IsTruncated ? response.NextContinuationToken : undefined;\n } while (continuationToken && childEntries.length < maxChildren);\n\n const selfPath = basePath ? joinURL(\"/\", basePath) : \"/\";\n\n // For non-root paths with no children, check if the path exists\n if (childEntries.length === 0 && basePath) {\n // Check if this path exists as a file or directory marker\n const key = this.options.prefix ? `${this.options.prefix}/${basePath}` : basePath;\n\n try {\n const headCommand = new HeadObjectCommand({\n Bucket: this.options.bucket,\n Key: key,\n });\n await this.client.send(headCommand);\n\n // Path exists as an object (file) — files have no children\n return { data: [] };\n } catch (error: any) {\n // Also check for directory marker\n if (error?.name === \"NotFound\" || error?.$metadata?.httpStatusCode === 404) {\n try {\n const dirMarkerCommand = new HeadObjectCommand({\n Bucket: this.options.bucket,\n Key: `${key}/`,\n });\n await this.client.send(dirMarkerCommand);\n // Directory marker exists but is empty\n } catch {\n // Neither file nor directory marker exists\n throw new AFSNotFoundError(selfPath);\n }\n } else {\n throw error;\n }\n }\n }\n\n // Return children only (no self-entry per AFS convention)\n return { data: childEntries };\n }\n\n // ========== Versioning List ==========\n\n @List(\"/:path*/@versions\")\n async listVersionsHandler(ctx: RouteContext<{ path: string }>): Promise<AFSListResult> {\n try {\n const normalizedPath = ctx.params.path.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n const key = this.options.prefix ? `${this.options.prefix}/${normalizedPath}` : normalizedPath;\n\n const command = new ListObjectVersionsCommand({\n Bucket: this.options.bucket,\n Prefix: key,\n MaxKeys: 1000,\n });\n\n const response = await this.client.send(command);\n const entries: AFSEntry[] = [];\n\n if (response.Versions) {\n for (const version of response.Versions) {\n // Only include exact key matches\n if (version.Key === key && version.VersionId) {\n const versionPath = joinURL(\"/\", normalizedPath, \"@versions\", version.VersionId);\n entries.push({\n id: `${this.generateId(key)}:${version.VersionId}`,\n path: versionPath,\n updatedAt: version.LastModified,\n meta: {\n versionId: version.VersionId,\n isLatest: version.IsLatest ?? false,\n lastModified: version.LastModified?.toISOString(),\n size: version.Size ?? 0,\n etag: version.ETag?.replace(/\"/g, \"\"),\n },\n });\n }\n }\n }\n\n return { data: entries };\n } catch (error) {\n throw mapS3Error(error, `/${ctx.params.path}/@versions`);\n }\n }\n\n // ========== Read Operations ==========\n\n @Read(\"/:path*\")\n async readHandler(ctx: RouteContext<{ path: string }>): Promise<AFSEntry> {\n const normalizedOutputPath = ctx.path.startsWith(\"/\") ? ctx.path : `/${ctx.path}`;\n\n try {\n const key = this.buildS3Key(ctx.params.path);\n\n // Handle root path - return bucket metadata with childrenCount\n if (!key) {\n // Count children in root\n const listCommand = new ListObjectsV2Command({\n Bucket: this.options.bucket,\n Prefix: this.options.prefix ? `${this.options.prefix}/` : \"\",\n Delimiter: \"/\",\n MaxKeys: 1000,\n });\n const listResponse = await this.client.send(listCommand);\n const childrenCount =\n (listResponse.CommonPrefixes?.length ?? 0) + (listResponse.Contents?.length ?? 0);\n\n return {\n id: this.generateId(\"/\"),\n path: \"/\",\n content: \"\",\n meta: {\n kind: \"afs:node\",\n childrenCount,\n platformRef: generatePlatformRef(this.options.bucket, this.options.region, \"\", true),\n },\n };\n }\n\n // Try to get the object directly (file case)\n try {\n const command = new GetObjectCommand({\n Bucket: this.options.bucket,\n Key: key,\n });\n\n const response = await this.client.send(command);\n\n // Check if this is a directory marker\n if (key.endsWith(\"/\") || response.ContentType === \"application/x-directory\") {\n // This is a directory marker - return directory info\n return {\n id: this.generateId(key),\n path: normalizedOutputPath,\n content: \"\",\n updatedAt: response.LastModified,\n meta: {\n kind: \"afs:node\",\n childrenCount: -1,\n platformRef: generatePlatformRef(this.options.bucket, this.options.region, key, true),\n },\n };\n }\n\n // Read body as string\n let content: string;\n if (response.Body) {\n const bytes = await response.Body.transformToByteArray();\n content = new TextDecoder().decode(bytes);\n } else {\n content = \"\";\n }\n\n return {\n id: this.generateId(key),\n path: normalizedOutputPath,\n content,\n updatedAt: response.LastModified,\n meta: {\n size: response.ContentLength,\n mimeType: response.ContentType,\n contentType: response.ContentType,\n contentLength: response.ContentLength,\n lastModified: response.LastModified?.toISOString(),\n etag: response.ETag?.replace(/\"/g, \"\"),\n contentRange: response.ContentRange,\n },\n };\n } catch (error: any) {\n // If 404, check if it's a directory prefix\n if (error?.name === \"NotFound\" || error?.$metadata?.httpStatusCode === 404) {\n // Check if there are objects with this prefix (it's a directory)\n const listCommand = new ListObjectsV2Command({\n Bucket: this.options.bucket,\n Prefix: `${key}/`,\n Delimiter: \"/\",\n MaxKeys: 1000,\n });\n\n const listResponse = await this.client.send(listCommand);\n const hasChildren =\n (listResponse.Contents && listResponse.Contents.length > 0) ||\n (listResponse.CommonPrefixes && listResponse.CommonPrefixes.length > 0);\n\n if (hasChildren) {\n // It's a directory - return directory info\n const childrenCount =\n (listResponse.CommonPrefixes?.length ?? 0) + (listResponse.Contents?.length ?? 0);\n return {\n id: this.generateId(`${key}/`),\n path: normalizedOutputPath,\n content: \"\",\n meta: {\n kind: \"afs:node\",\n childrenCount,\n platformRef: generatePlatformRef(\n this.options.bucket,\n this.options.region,\n key,\n true,\n ),\n },\n };\n }\n\n // Also check for directory marker (key ending with /)\n try {\n const markerCommand = new HeadObjectCommand({\n Bucket: this.options.bucket,\n Key: `${key}/`,\n });\n await this.client.send(markerCommand);\n // Directory marker exists\n return {\n id: this.generateId(`${key}/`),\n path: normalizedOutputPath,\n content: \"\",\n meta: {\n kind: \"afs:node\",\n childrenCount: 0,\n platformRef: generatePlatformRef(\n this.options.bucket,\n this.options.region,\n key,\n true,\n ),\n },\n };\n } catch {\n // Neither file nor directory exists\n throw new AFSNotFoundError(normalizedOutputPath);\n }\n }\n throw error;\n }\n } catch (error) {\n if (error instanceof AFSNotFoundError) {\n throw error;\n }\n throw mapS3Error(error, normalizedOutputPath);\n }\n }\n\n // ========== Version Read ==========\n\n @Read(\"/:path*/@versions/:versionId\")\n async readVersionHandler(\n ctx: RouteContext<{ path: string; versionId: string }>,\n ): Promise<AFSEntry> {\n try {\n const normalizedPath = ctx.params.path.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n const key = this.options.prefix ? `${this.options.prefix}/${normalizedPath}` : normalizedPath;\n\n const command = new GetObjectCommand({\n Bucket: this.options.bucket,\n Key: key,\n VersionId: ctx.params.versionId,\n });\n\n const response = await this.client.send(command);\n\n const content = response.Body ? await response.Body.transformToString() : \"\";\n\n return {\n id: `${this.generateId(key)}:${ctx.params.versionId}`,\n path: ctx.path,\n content,\n updatedAt: response.LastModified,\n meta: {\n size: response.ContentLength,\n contentType: response.ContentType,\n lastModified: response.LastModified?.toISOString(),\n etag: response.ETag?.replace(/\"/g, \"\"),\n versionId: response.VersionId,\n },\n };\n } catch (error) {\n throw mapS3Error(error, `/${ctx.params.path}/@versions/${ctx.params.versionId}`);\n }\n }\n\n // ========== Meta Operations ==========\n\n @Meta(\"/\")\n @Meta(\"/:path*\")\n async metaHandler(ctx: RouteContext<{ path?: string }>): Promise<AFSEntry> {\n try {\n const path = ctx.params.path ?? \"\";\n const normalizedPath = path.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n const key = this.options.prefix\n ? normalizedPath\n ? `${this.options.prefix}/${normalizedPath}`\n : this.options.prefix\n : normalizedPath;\n\n // Root metadata - count children\n if (!key) {\n const listCommand = new ListObjectsV2Command({\n Bucket: this.options.bucket,\n Prefix: this.options.prefix ? `${this.options.prefix}/` : \"\",\n Delimiter: \"/\",\n MaxKeys: 1000,\n });\n const listResponse = await this.client.send(listCommand);\n const childrenCount =\n (listResponse.CommonPrefixes?.length ?? 0) + (listResponse.Contents?.length ?? 0);\n\n return {\n id: this.generateId(\"/\"),\n path: \"/.meta\",\n meta: {\n kind: \"afs:node\",\n childrenCount,\n platformRef: generatePlatformRef(this.options.bucket, this.options.region, \"\", true),\n },\n };\n }\n\n // Check cache first\n if (this.statCache) {\n const cacheKey = createCacheKey(this.options.bucket, this.options.prefix ?? \"\", path);\n const cached = this.statCache.get(cacheKey);\n if (cached) {\n return {\n id: cached.id,\n path: ctx.path,\n meta: cached.meta,\n };\n }\n }\n\n // Try to get the object directly (file case)\n try {\n const command = new HeadObjectCommand({\n Bucket: this.options.bucket,\n Key: key,\n });\n\n const response = await this.client.send(command);\n\n // Check if this is a directory marker\n if (key.endsWith(\"/\") || response.ContentType === \"application/x-directory\") {\n return {\n id: this.generateId(key),\n path: ctx.path,\n updatedAt: response.LastModified,\n meta: {\n kind: \"afs:node\",\n childrenCount: -1,\n platformRef: generatePlatformRef(this.options.bucket, this.options.region, key, true),\n },\n };\n }\n\n const result = {\n id: this.generateId(key),\n path: ctx.path,\n updatedAt: response.LastModified,\n meta: {\n kind: \"afs:document\",\n size: response.ContentLength,\n contentType: response.ContentType,\n lastModified: response.LastModified?.toISOString(),\n etag: response.ETag?.replace(/\"/g, \"\"),\n storageClass: response.StorageClass,\n platformRef: generatePlatformRef(this.options.bucket, this.options.region, key, false),\n },\n };\n\n // Cache the result\n if (this.statCache) {\n const cacheKey = createCacheKey(this.options.bucket, this.options.prefix ?? \"\", path);\n this.statCache.set(cacheKey, result);\n }\n\n return result;\n } catch (error: any) {\n // If 404, check if it's a directory prefix\n if (error?.name === \"NotFound\" || error?.$metadata?.httpStatusCode === 404) {\n // Check if there are objects with this prefix\n const listCommand = new ListObjectsV2Command({\n Bucket: this.options.bucket,\n Prefix: `${key}/`,\n MaxKeys: 1,\n });\n\n const listResponse = await this.client.send(listCommand);\n\n if (listResponse.Contents && listResponse.Contents.length > 0) {\n // There are objects under this prefix, so it's a directory\n return {\n id: this.generateId(`${key}/`),\n path: ctx.path,\n meta: {\n kind: \"afs:node\",\n childrenCount: -1,\n platformRef: generatePlatformRef(\n this.options.bucket,\n this.options.region,\n key,\n true,\n ),\n },\n };\n }\n\n // Also check for directory marker (key ending with /)\n try {\n const markerCommand = new HeadObjectCommand({\n Bucket: this.options.bucket,\n Key: `${key}/`,\n });\n const markerResponse = await this.client.send(markerCommand);\n\n return {\n id: this.generateId(`${key}/`),\n path: ctx.path,\n updatedAt: markerResponse.LastModified,\n meta: {\n kind: \"afs:node\",\n childrenCount: -1,\n platformRef: generatePlatformRef(\n this.options.bucket,\n this.options.region,\n key,\n true,\n ),\n },\n };\n } catch {\n // Neither file nor directory exists - ensure path has leading slash\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n throw new AFSNotFoundError(normalizedPath);\n }\n }\n\n throw error;\n }\n } catch (error) {\n if (error instanceof AFSNotFoundError) {\n throw error;\n }\n // Ensure path has leading slash for mapS3Error\n const normalizedPath = (ctx.params.path ?? \"\").startsWith(\"/\")\n ? (ctx.params.path ?? \"/\")\n : `/${ctx.params.path ?? \"\"}`;\n throw mapS3Error(error, normalizedPath);\n }\n }\n\n // ========== Stat Operations ==========\n\n @Stat(\"/\")\n @Stat(\"/:path*\")\n async statHandler(ctx: RouteContext<{ path?: string }>): Promise<AFSStatResult> {\n // Delegate to meta handler and convert to stat result\n const metaEntry = await this.metaHandler({\n ...ctx,\n path: ctx.path.endsWith(\"/.meta\") ? ctx.path : `${ctx.path}/.meta`,\n });\n\n // Extract id from path\n const pathSegments = ctx.path.split(\"/\").filter(Boolean);\n const id = pathSegments.length > 0 ? (pathSegments[pathSegments.length - 1] as string) : \"/\";\n\n return {\n data: {\n id,\n path: ctx.path,\n meta: metaEntry.meta as Record<string, unknown>,\n },\n };\n }\n\n // ========== Write Operations ==========\n\n @Write(\"/:path*\")\n async writeHandler(\n ctx: RouteContext<{ path: string }>,\n payload: AFSWriteEntryPayload,\n ): Promise<AFSWriteResult> {\n try {\n const key = this.buildS3Key(ctx.params.path);\n\n // Prepare content\n let body: Buffer;\n if (typeof payload.content === \"string\") {\n body = Buffer.from(payload.content, \"utf-8\");\n } else if (Buffer.isBuffer(payload.content)) {\n body = payload.content;\n } else if (payload.content !== undefined) {\n // JSON content\n body = Buffer.from(JSON.stringify(payload.content), \"utf-8\");\n } else {\n body = Buffer.from(\"\");\n }\n\n // Determine content type from metadata or default\n const contentType =\n (payload.meta?.mimeType as string) ??\n (payload.meta?.contentType as string) ??\n \"application/octet-stream\";\n\n let etag: string | undefined;\n let versionId: string | undefined;\n\n // Use multipart upload for large files\n if (shouldUseMultipart(body.length)) {\n const result = await multipartUpload(this.client, this.options.bucket, key, body, {\n contentType,\n });\n etag = result.etag;\n versionId = result.versionId;\n } else {\n const command = new PutObjectCommand({\n Bucket: this.options.bucket,\n Key: key,\n Body: body,\n ContentType: contentType,\n });\n\n const response = await this.client.send(command);\n etag = response.ETag?.replace(/\"/g, \"\");\n versionId = response.VersionId;\n }\n\n const normalizedOutputPath = ctx.path.startsWith(\"/\") ? ctx.path : `/${ctx.path}`;\n\n // Invalidate caches for the written path\n this.invalidateCache(ctx.params.path);\n\n return {\n data: {\n id: this.generateId(key),\n path: normalizedOutputPath,\n content: payload.content,\n meta: {\n size: body.length,\n etag,\n versionId,\n ...payload.meta,\n },\n },\n };\n } catch (error) {\n const normalizedPath = ctx.params.path.startsWith(\"/\")\n ? ctx.params.path\n : `/${ctx.params.path}`;\n throw mapS3Error(error, normalizedPath);\n }\n }\n\n // ========== Delete Operations ==========\n\n @Delete(\"/:path*\")\n async deleteHandler(ctx: RouteContext<{ path: string }>): Promise<AFSDeleteResult> {\n try {\n const key = this.buildS3Key(ctx.params.path);\n\n // Check if the object exists first (S3 DeleteObject is idempotent and doesn't error on missing objects)\n try {\n const headCommand = new HeadObjectCommand({\n Bucket: this.options.bucket,\n Key: key,\n });\n await this.client.send(headCommand);\n } catch (error: any) {\n if (error?.name === \"NotFound\" || error?.$metadata?.httpStatusCode === 404) {\n throw new AFSNotFoundError(`/${ctx.params.path}`);\n }\n throw error;\n }\n\n const command = new DeleteObjectCommand({\n Bucket: this.options.bucket,\n Key: key,\n });\n\n await this.client.send(command);\n\n // Invalidate caches for the deleted path\n this.invalidateCache(ctx.params.path);\n\n return {\n message: `Successfully deleted: ${ctx.params.path}`,\n };\n } catch (error) {\n if (error instanceof AFSNotFoundError) {\n throw error;\n }\n throw mapS3Error(error, `/${ctx.params.path}`);\n }\n }\n\n // ========== Action System ==========\n\n @Actions(\"/:path*\")\n async listActionsHandler(ctx: RouteContext<{ path: string }>): Promise<AFSListResult> {\n return {\n data: [\n {\n id: \"select\",\n path: joinURL(ctx.path, \".actions\", \"select\"),\n summary: \"Query with S3 Select\",\n meta: {\n kind: \"afs:executable\",\n kinds: [\"afs:executable\", \"afs:node\"],\n inputSchema: {\n type: \"object\",\n properties: {\n expression: { type: \"string\", description: \"SQL expression\" },\n inputFormat: { type: \"string\", description: \"CSV | JSON | Parquet\" },\n outputFormat: { type: \"string\", description: \"CSV | JSON\" },\n },\n required: [\"expression\"],\n },\n },\n },\n {\n id: \"presign-download\",\n path: joinURL(ctx.path, \".actions\", \"presign-download\"),\n summary: \"Generate download URL\",\n meta: {\n kind: \"afs:executable\",\n kinds: [\"afs:executable\", \"afs:node\"],\n inputSchema: {\n type: \"object\",\n properties: {\n expiresIn: {\n type: \"number\",\n description: \"Expiration in seconds (default: 3600, max: 604800)\",\n },\n },\n },\n },\n },\n {\n id: \"presign-upload\",\n path: joinURL(ctx.path, \".actions\", \"presign-upload\"),\n summary: \"Generate upload URL\",\n meta: {\n kind: \"afs:executable\",\n kinds: [\"afs:executable\", \"afs:node\"],\n inputSchema: {\n type: \"object\",\n properties: {\n expiresIn: { type: \"number\", description: \"Expiration in seconds\" },\n contentType: { type: \"string\", description: \"Content-Type for upload\" },\n },\n },\n },\n },\n ],\n };\n }\n\n @Actions.Exec(\"/:path*\", \"select\")\n async selectActionHandler(\n ctx: RouteContext<{ path: string }>,\n args: Record<string, unknown>,\n ): Promise<AFSExecResult> {\n const expression = args.expression as string;\n if (!expression) {\n throw new AFSError(\"Missing required argument: expression\", \"AFS_INVALID_ARGUMENT\");\n }\n\n const normalizedPath = ctx.params.path.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n const key = this.options.prefix ? `${this.options.prefix}/${normalizedPath}` : normalizedPath;\n\n const result = await selectQuery(this.client, this.options.bucket, \"\", key, expression, {\n inputFormat: args.inputFormat as \"CSV\" | \"JSON\" | \"Parquet\" | undefined,\n outputFormat: args.outputFormat as \"CSV\" | \"JSON\" | undefined,\n });\n\n return {\n success: true,\n data: {\n records: result.records,\n stats: result.stats,\n },\n };\n }\n\n @Actions.Exec(\"/:path*\", \"presign-download\")\n async presignDownloadActionHandler(\n ctx: RouteContext<{ path: string }>,\n args: Record<string, unknown>,\n ): Promise<AFSExecResult> {\n const normalizedPath = ctx.params.path.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n const key = this.options.prefix ? `${this.options.prefix}/${normalizedPath}` : normalizedPath;\n\n let expiresIn = (args.expiresIn as number) ?? DEFAULT_EXPIRES_IN;\n if (expiresIn > MAX_EXPIRES_IN) expiresIn = MAX_EXPIRES_IN;\n if (expiresIn < 1) expiresIn = 1;\n\n const command = new GetObjectCommand({\n Bucket: this.options.bucket,\n Key: key,\n });\n\n const url = await getSignedUrl(this.client, command, { expiresIn });\n const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString();\n\n return {\n success: true,\n data: {\n url,\n expiresAt,\n },\n };\n }\n\n @Actions.Exec(\"/:path*\", \"presign-upload\")\n async presignUploadActionHandler(\n ctx: RouteContext<{ path: string }>,\n args: Record<string, unknown>,\n ): Promise<AFSExecResult> {\n const normalizedPath = ctx.params.path.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n const key = this.options.prefix ? `${this.options.prefix}/${normalizedPath}` : normalizedPath;\n\n let expiresIn = (args.expiresIn as number) ?? DEFAULT_EXPIRES_IN;\n if (expiresIn > MAX_EXPIRES_IN) expiresIn = MAX_EXPIRES_IN;\n if (expiresIn < 1) expiresIn = 1;\n\n const command = new PutObjectCommand({\n Bucket: this.options.bucket,\n Key: key,\n ContentType: (args.contentType as string) ?? \"application/octet-stream\",\n });\n\n const url = await getSignedUrl(this.client, command, { expiresIn });\n const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString();\n\n return {\n success: true,\n data: {\n url,\n expiresAt,\n },\n };\n }\n\n // ========== Explain Operations ==========\n\n @Explain(\"/\")\n @Explain(\"/:path*\")\n async explainHandler(ctx: RouteContext<{ path?: string }>): Promise<AFSExplainResult> {\n const normalizedPath = (ctx.params.path ?? \"\").replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n\n // Root explain\n if (!normalizedPath) {\n const response = await this.client.send(\n new ListObjectsV2Command({\n Bucket: this.options.bucket,\n Prefix: this.options.prefix ? `${this.options.prefix}/` : undefined,\n Delimiter: \"/\",\n MaxKeys: 1000,\n }),\n );\n\n const objectCount = response.Contents?.length ?? 0;\n const prefixCount = response.CommonPrefixes?.length ?? 0;\n\n const lines: string[] = [];\n lines.push(`# ${this.options.bucket}`);\n lines.push(\"\");\n lines.push(`- **Type**: S3 Bucket`);\n lines.push(`- **Bucket**: ${this.options.bucket}`);\n if (this.options.prefix) {\n lines.push(`- **Prefix**: ${this.options.prefix}`);\n }\n if (this.options.region) {\n lines.push(`- **Region**: ${this.options.region}`);\n }\n lines.push(`- **Access Mode**: ${this.accessMode}`);\n lines.push(`- **Top-level Objects**: ${objectCount}`);\n lines.push(`- **Top-level Prefixes**: ${prefixCount}`);\n\n return { format: \"markdown\", content: lines.join(\"\\n\") };\n }\n\n const key = this.buildS3Key(normalizedPath);\n\n // Try as object first (HeadObject)\n try {\n const head = await this.client.send(\n new HeadObjectCommand({ Bucket: this.options.bucket, Key: key }),\n );\n\n const lines: string[] = [];\n lines.push(`# ${normalizedPath}`);\n lines.push(\"\");\n lines.push(`- **Type**: Object`);\n lines.push(`- **Key**: ${key}`);\n lines.push(`- **Size**: ${head.ContentLength ?? 0} bytes`);\n if (head.ContentType) lines.push(`- **Content-Type**: ${head.ContentType}`);\n if (head.StorageClass) lines.push(`- **Storage Class**: ${head.StorageClass}`);\n if (head.LastModified) lines.push(`- **Last Modified**: ${head.LastModified.toISOString()}`);\n if (head.ETag) lines.push(`- **ETag**: ${head.ETag}`);\n\n return { format: \"markdown\", content: lines.join(\"\\n\") };\n } catch {\n // Not an object, try as prefix (directory)\n }\n\n // Try as prefix\n const prefix = key.endsWith(\"/\") ? key : `${key}/`;\n const response = await this.client.send(\n new ListObjectsV2Command({\n Bucket: this.options.bucket,\n Prefix: prefix,\n Delimiter: \"/\",\n MaxKeys: 1000,\n }),\n );\n\n const objectCount = response.Contents?.length ?? 0;\n const prefixCount = response.CommonPrefixes?.length ?? 0;\n\n if (objectCount === 0 && prefixCount === 0) {\n throw new AFSNotFoundError(`/${normalizedPath}`, `Path not found: ${normalizedPath}`);\n }\n\n const lines: string[] = [];\n lines.push(`# ${normalizedPath}/`);\n lines.push(\"\");\n lines.push(`- **Type**: Prefix (directory)`);\n lines.push(`- **Prefix**: ${prefix}`);\n lines.push(`- **Objects**: ${objectCount}`);\n lines.push(`- **Sub-prefixes**: ${prefixCount}`);\n\n return { format: \"markdown\", content: lines.join(\"\\n\") };\n }\n\n // ========== Search Operations ==========\n\n @Search(\"/\")\n @Search(\"/:path*\")\n async searchHandler(\n ctx: RouteContext<{ path?: string }>,\n query: string,\n ): Promise<AFSSearchResult> {\n const { minimatch } = await import(\"minimatch\");\n const path = ctx.params.path ?? \"\";\n const normalizedPath = path.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n const prefix = normalizedPath\n ? this.options.prefix\n ? `${this.options.prefix}/${normalizedPath}/`\n : `${normalizedPath}/`\n : this.options.prefix\n ? `${this.options.prefix}/`\n : \"\";\n\n const results: AFSEntry[] = [];\n let continuationToken: string | undefined;\n\n // Paginate through all objects under the prefix\n do {\n const response = await this.client.send(\n new ListObjectsV2Command({\n Bucket: this.options.bucket,\n Prefix: prefix || undefined,\n ContinuationToken: continuationToken,\n MaxKeys: 1000,\n }),\n );\n\n for (const obj of response.Contents ?? []) {\n if (!obj.Key) continue;\n\n // Get relative name (strip prefix)\n const relativePath = prefix ? obj.Key.slice(prefix.length) : obj.Key;\n if (!relativePath) continue;\n\n // Match against glob pattern\n if (minimatch(relativePath, query)) {\n const displayPath = joinURL(\"/\", normalizedPath, relativePath);\n results.push({\n id: this.generateId(obj.Key),\n path: displayPath,\n meta: {\n size: obj.Size,\n lastModified: obj.LastModified?.toISOString(),\n etag: obj.ETag?.replace(/\"/g, \"\"),\n },\n });\n }\n }\n\n continuationToken = response.IsTruncated ? response.NextContinuationToken : undefined;\n } while (continuationToken);\n\n return { data: results };\n }\n\n // ========== Capabilities ==========\n\n @Read(\"/.meta/.capabilities\")\n async readCapabilities(_ctx: RouteContext): Promise<AFSEntry> {\n const capabilities: CapabilitiesManifest = {\n schemaVersion: 1,\n provider: \"s3\",\n description: `S3 bucket: ${this.options.bucket}`,\n tools: [],\n operations: this.getOperationsDeclaration(),\n actions: [\n {\n description: \"S3 object actions\",\n catalog: [\n {\n name: \"select\",\n description: \"Query object contents using S3 Select (CSV/JSON/Parquet)\",\n inputSchema: {\n type: \"object\",\n properties: {\n expression: { type: \"string\", description: \"SQL expression\" },\n inputFormat: { type: \"string\", description: \"CSV | JSON | Parquet\" },\n outputFormat: { type: \"string\", description: \"CSV | JSON\" },\n },\n required: [\"expression\"],\n },\n },\n {\n name: \"presign-download\",\n description: \"Generate a pre-signed download URL\",\n inputSchema: {\n type: \"object\",\n properties: {\n expiresIn: {\n type: \"number\",\n description: \"Expiration in seconds (default: 3600, max: 604800)\",\n },\n },\n },\n },\n {\n name: \"presign-upload\",\n description: \"Generate a pre-signed upload URL\",\n inputSchema: {\n type: \"object\",\n properties: {\n expiresIn: { type: \"number\", description: \"Expiration in seconds\" },\n contentType: { type: \"string\", description: \"Content-Type for upload\" },\n },\n },\n },\n ],\n discovery: { pathTemplate: \"/{path}/.actions\" },\n },\n ],\n };\n\n return {\n id: \".capabilities\",\n path: \"/.meta/.capabilities\",\n content: JSON.stringify(capabilities, null, 2),\n meta: { kind: \"afs:capabilities\" },\n };\n }\n}\n\n// Type check: AFSS3 should implement AFSModuleClass\nconst _typeCheck: AFSModuleClass<AFSS3, AFSS3Options> = AFSS3;\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aigne/afs-s3",
|
|
3
|
-
"version": "1.11.0-beta.
|
|
3
|
+
"version": "1.11.0-beta.7",
|
|
4
4
|
"description": "AIGNE AFS module for AWS S3 and S3-compatible storage",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"publishConfig": {
|
|
@@ -35,9 +35,11 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@aws-sdk/client-s3": "^3.821.0",
|
|
37
37
|
"@aws-sdk/s3-request-presigner": "^3.975.0",
|
|
38
|
+
"minimatch": "^10.1.1",
|
|
38
39
|
"tslib": "^2.8.1",
|
|
40
|
+
"ufo": "^1.6.3",
|
|
39
41
|
"zod": "^3.25.67",
|
|
40
|
-
"@aigne/afs": "^1.11.0-beta.
|
|
42
|
+
"@aigne/afs": "^1.11.0-beta.7"
|
|
41
43
|
},
|
|
42
44
|
"devDependencies": {
|
|
43
45
|
"@types/bun": "^1.3.6",
|
|
@@ -45,6 +47,7 @@
|
|
|
45
47
|
"rimraf": "^6.1.2",
|
|
46
48
|
"tsdown": "0.20.0-beta.3",
|
|
47
49
|
"typescript": "5.9.2",
|
|
50
|
+
"@aigne/afs-testing": "1.11.0-beta.7",
|
|
48
51
|
"@aigne/scripts": "0.0.0",
|
|
49
52
|
"@aigne/typescript-config": "0.0.0"
|
|
50
53
|
},
|