/** * S3 client implementation. * * Uses the native S3 plugin type from @superblocksteam/types. */ import type { PartialMessage } from "@bufbuild/protobuf"; import type { z } from "zod"; import type { Plugin as S3Plugin } from "@superblocksteam/types/dist/src/plugins/s3/v1/plugin_pb"; import { RestApiValidationError } from "../../errors.js"; import { IntegrationError } from "../../runtime/errors.js"; import type { QueryExecutor, TraceMetadata } from "../registry.js"; import type { IntegrationConfig, IntegrationClientImpl } from "../types.js"; import type { S3Client, S3Action, S3ListObjectsOptions, S3GetObjectOptions, } from "./types.js"; /** * Internal implementation of S3Client. */ export class S3ClientImpl implements S3Client, IntegrationClientImpl { readonly config: IntegrationConfig; private readonly executeQuery: QueryExecutor; constructor(config: IntegrationConfig, executeQuery: QueryExecutor) { this.config = config; this.executeQuery = executeQuery; } get name(): string { return this.config.name; } get pluginId(): string { return this.config.pluginId; } /** * Builds an S3 plugin request and executes it. */ private async execute( action: S3Action, request: PartialMessage, metadata?: TraceMetadata, ): Promise { const fullRequest: PartialMessage = { ...request, action, }; return this.executeQuery( fullRequest as Record, undefined, metadata, ); } /** * Validates a result against a Zod schema. */ private validate( result: unknown, schema: z.ZodSchema, operation: string, ): T { const parseResult = schema.safeParse(result); if (!parseResult.success) { throw new RestApiValidationError( `Result validation failed: ${parseResult.error.message}`, { zodError: parseResult.error, data: result, }, ); } return parseResult.data; } /** * Wraps an operation with standard error handling. */ private async withErrorHandling( operation: string, fn: () => Promise, ): Promise { try { return await fn(); } catch (error) { if ( error instanceof RestApiValidationError || error instanceof IntegrationError ) { throw error; } throw new IntegrationError(this.config.name, operation, error); } } async listBuckets( schema: z.ZodSchema, metadata?: TraceMetadata, ): Promise { return this.withErrorHandling("listBuckets", async () => { const result = await this.execute("LIST_BUCKETS", {}, metadata); return this.validate(result, schema, "listBuckets"); }); } async listObjects( bucket: string, schema: z.ZodSchema, options?: S3ListObjectsOptions, metadata?: TraceMetadata, ): Promise { return this.withErrorHandling("listObjects", async () => { const request: PartialMessage = { resource: bucket, }; if (options?.prefix || options?.delimiter) { request.listFilesConfig = { prefix: options.prefix, delimiter: options.delimiter, }; } const result = await this.execute( "LIST_BUCKET_OBJECTS", request, metadata, ); return this.validate(result, schema, "listObjects"); }); } async getObject( bucket: string, path: string, schema: z.ZodSchema, options?: S3GetObjectOptions, metadata?: TraceMetadata, ): Promise { return this.withErrorHandling("getObject", async () => { const request: PartialMessage = { resource: bucket, path, }; if (options?.responseType) { request.responseType = options.responseType; } const result = await this.execute("GET_OBJECT", request, metadata); return this.validate(result, schema, "getObject"); }); } async deleteObject( bucket: string, path: string, metadata?: TraceMetadata, ): Promise { return this.withErrorHandling("deleteObject", async () => { await this.execute( "DELETE_OBJECT", { resource: bucket, path, }, metadata, ); }); } async uploadObject( bucket: string, path: string, body: string, metadata?: TraceMetadata, ): Promise { return this.withErrorHandling("uploadObject", async () => { await this.execute( "UPLOAD_OBJECT", { resource: bucket, path, body, }, metadata, ); }); } async uploadMultipleObjects( bucket: string, fileObjects: string, metadata?: TraceMetadata, ): Promise { return this.withErrorHandling("uploadMultipleObjects", async () => { await this.execute( "UPLOAD_MULTIPLE_OBJECTS", { resource: bucket, fileObjects, }, metadata, ); }); } async generatePresignedUrl( bucket: string, path: string, schema: z.ZodSchema, metadata?: TraceMetadata, ): Promise { return this.withErrorHandling("generatePresignedUrl", async () => { const result = await this.execute( "GENERATE_PRESIGNED_URL", { resource: bucket, path, }, metadata, ); return this.validate(result, schema, "generatePresignedUrl"); }); } }