Skip to content

Commit

Permalink
feat(s3Client): add support for AWS S3 Object Storage Class (#16617)
Browse files Browse the repository at this point in the history
  • Loading branch information
Inqnuam authored Jan 23, 2025
1 parent b54f3f3 commit 98c7b84
Show file tree
Hide file tree
Showing 13 changed files with 615 additions and 76 deletions.
22 changes: 22 additions & 0 deletions packages/bun-types/bun.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1435,6 +1435,28 @@ declare module "bun" {
*/
type?: string;

/**
* By default, Amazon S3 uses the STANDARD Storage Class to store newly created objects.
*
* @example
* // Setting explicit Storage class
* const file = s3("my-file.json", {
* storageClass: "STANDARD_IA"
* });
*/
storageClass?:
| "STANDARD"
| "DEEP_ARCHIVE"
| "EXPRESS_ONEZONE"
| "GLACIER"
| "GLACIER_IR"
| "INTELLIGENT_TIERING"
| "ONEZONE_IA"
| "OUTPOSTS"
| "REDUCED_REDUNDANCY"
| "SNOW"
| "STANDARD_IA";

/**
* @deprecated The size of the internal buffer in bytes. Defaults to 5 MiB. use `partSize` and `queueSize` instead.
*/
Expand Down
3 changes: 2 additions & 1 deletion src/bun.js/rare_data.zig
Original file line number Diff line number Diff line change
Expand Up @@ -440,12 +440,13 @@ pub fn nodeFSStatWatcherScheduler(rare: *RareData, vm: *JSC.VirtualMachine) *Sta
pub fn s3DefaultClient(rare: *RareData, globalThis: *JSC.JSGlobalObject) JSC.JSValue {
return rare.s3_default_client.get() orelse {
const vm = globalThis.bunVM();
var aws_options = bun.S3.S3Credentials.getCredentialsWithOptions(vm.transpiler.env.getS3Credentials(), .{}, null, null, globalThis) catch bun.outOfMemory();
var aws_options = bun.S3.S3Credentials.getCredentialsWithOptions(vm.transpiler.env.getS3Credentials(), .{}, null, null, null, globalThis) catch bun.outOfMemory();
defer aws_options.deinit();
const client = JSC.WebCore.S3Client.new(.{
.credentials = aws_options.credentials.dupe(),
.options = aws_options.options,
.acl = aws_options.acl,
.storage_class = aws_options.storage_class,
});
const js_client = client.toJS(globalThis);
js_client.ensureStillAlive();
Expand Down
18 changes: 10 additions & 8 deletions src/bun.js/webcore/S3Client.zig
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,19 @@ pub const S3Client = struct {
credentials: *S3Credentials,
options: bun.S3.MultiPartUploadOptions = .{},
acl: ?bun.S3.ACL = null,
storage_class: ?bun.S3.StorageClass = null,

pub fn constructor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!*@This() {
const arguments = callframe.arguments_old(1).slice();
var args = JSC.Node.ArgumentsSlice.init(globalThis.bunVM(), arguments);
defer args.deinit();
var aws_options = try S3Credentials.getCredentialsWithOptions(globalThis.bunVM().transpiler.env.getS3Credentials(), .{}, args.nextEat(), null, globalThis);
var aws_options = try S3Credentials.getCredentialsWithOptions(globalThis.bunVM().transpiler.env.getS3Credentials(), .{}, args.nextEat(), null, null, globalThis);
defer aws_options.deinit();
return S3Client.new(.{
.credentials = aws_options.credentials.dupe(),
.options = aws_options.options,
.acl = aws_options.acl,
.storage_class = aws_options.storage_class,
});
}

Expand Down Expand Up @@ -138,7 +140,7 @@ pub const S3Client = struct {
};
errdefer path.deinit();
const options = args.nextEat();
var blob = Blob.new(try S3File.constructS3FileWithS3CredentialsAndOptions(globalThis, path, options, ptr.credentials, ptr.options, ptr.acl));
var blob = Blob.new(try S3File.constructS3FileWithS3CredentialsAndOptions(globalThis, path, options, ptr.credentials, ptr.options, ptr.acl, ptr.storage_class));
blob.allocator = bun.default_allocator;
return blob.toJS(globalThis);
}
Expand All @@ -156,7 +158,7 @@ pub const S3Client = struct {
errdefer path.deinit();

const options = args.nextEat();
var blob = try S3File.constructS3FileWithS3CredentialsAndOptions(globalThis, path, options, ptr.credentials, ptr.options, ptr.acl);
var blob = try S3File.constructS3FileWithS3CredentialsAndOptions(globalThis, path, options, ptr.credentials, ptr.options, ptr.acl, ptr.storage_class);
defer blob.detach();
return S3File.getPresignUrlFrom(&blob, globalThis, options);
}
Expand All @@ -173,7 +175,7 @@ pub const S3Client = struct {
};
errdefer path.deinit();
const options = args.nextEat();
var blob = try S3File.constructS3FileWithS3CredentialsAndOptions(globalThis, path, options, ptr.credentials, ptr.options, ptr.acl);
var blob = try S3File.constructS3FileWithS3CredentialsAndOptions(globalThis, path, options, ptr.credentials, ptr.options, ptr.acl, ptr.storage_class);
defer blob.detach();
return S3File.S3BlobStatTask.exists(globalThis, &blob);
}
Expand All @@ -190,7 +192,7 @@ pub const S3Client = struct {
};
errdefer path.deinit();
const options = args.nextEat();
var blob = try S3File.constructS3FileWithS3CredentialsAndOptions(globalThis, path, options, ptr.credentials, ptr.options, ptr.acl);
var blob = try S3File.constructS3FileWithS3CredentialsAndOptions(globalThis, path, options, ptr.credentials, ptr.options, ptr.acl, ptr.storage_class);
defer blob.detach();
return S3File.S3BlobStatTask.size(globalThis, &blob);
}
Expand All @@ -207,7 +209,7 @@ pub const S3Client = struct {
};
errdefer path.deinit();
const options = args.nextEat();
var blob = try S3File.constructS3FileWithS3CredentialsAndOptions(globalThis, path, options, ptr.credentials, ptr.options, ptr.acl);
var blob = try S3File.constructS3FileWithS3CredentialsAndOptions(globalThis, path, options, ptr.credentials, ptr.options, ptr.acl, ptr.storage_class);
defer blob.detach();
return S3File.S3BlobStatTask.stat(globalThis, &blob);
}
Expand All @@ -225,7 +227,7 @@ pub const S3Client = struct {
};

const options = args.nextEat();
var blob = try S3File.constructS3FileWithS3CredentialsAndOptions(globalThis, path, options, ptr.credentials, ptr.options, ptr.acl);
var blob = try S3File.constructS3FileWithS3CredentialsAndOptions(globalThis, path, options, ptr.credentials, ptr.options, ptr.acl, ptr.storage_class);
defer blob.detach();
var blob_internal: PathOrBlob = .{ .blob = blob };
return Blob.writeFileInternal(globalThis, &blob_internal, data, .{
Expand All @@ -243,7 +245,7 @@ pub const S3Client = struct {
};
errdefer path.deinit();
const options = args.nextEat();
var blob = try S3File.constructS3FileWithS3CredentialsAndOptions(globalThis, path, options, ptr.credentials, ptr.options, ptr.acl);
var blob = try S3File.constructS3FileWithS3CredentialsAndOptions(globalThis, path, options, ptr.credentials, ptr.options, ptr.acl, ptr.storage_class);
defer blob.detach();
return blob.store.?.data.s3.unlink(blob.store.?, globalThis, options);
}
Expand Down
10 changes: 8 additions & 2 deletions src/bun.js/webcore/S3File.zig
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,9 @@ pub fn constructS3FileWithS3CredentialsAndOptions(
default_credentials: *S3.S3Credentials,
default_options: bun.S3.MultiPartUploadOptions,
default_acl: ?bun.S3.ACL,
default_storage_class: ?bun.S3.StorageClass,
) bun.JSError!Blob {
var aws_options = try S3.S3Credentials.getCredentialsWithOptions(default_credentials.*, default_options, options, default_acl, globalObject);
var aws_options = try S3.S3Credentials.getCredentialsWithOptions(default_credentials.*, default_options, options, default_acl, default_storage_class, globalObject);
defer aws_options.deinit();

const store = brk: {
Expand All @@ -241,6 +242,8 @@ pub fn constructS3FileWithS3CredentialsAndOptions(
errdefer store.deinit();
store.data.s3.options = aws_options.options;
store.data.s3.acl = aws_options.acl;
store.data.s3.storage_class = aws_options.storage_class;

var blob = Blob.initWithStore(store, globalObject);
if (options) |opts| {
if (opts.isObject()) {
Expand Down Expand Up @@ -276,12 +279,14 @@ pub fn constructS3FileWithS3Credentials(
options: ?JSC.JSValue,
existing_credentials: S3.S3Credentials,
) bun.JSError!Blob {
var aws_options = try S3.S3Credentials.getCredentialsWithOptions(existing_credentials, .{}, options, null, globalObject);
var aws_options = try S3.S3Credentials.getCredentialsWithOptions(existing_credentials, .{}, options, null, null, globalObject);
defer aws_options.deinit();
const store = Blob.Store.initS3(path, null, aws_options.credentials, bun.default_allocator) catch bun.outOfMemory();
errdefer store.deinit();
store.data.s3.options = aws_options.options;
store.data.s3.acl = aws_options.acl;
store.data.s3.storage_class = aws_options.storage_class;

var blob = Blob.initWithStore(store, globalObject);
if (options) |opts| {
if (opts.isObject()) {
Expand Down Expand Up @@ -465,6 +470,7 @@ pub fn getPresignUrlFrom(this: *Blob, globalThis: *JSC.JSGlobalObject, extra_opt
.path = path,
.method = method,
.acl = credentialsWithOptions.acl,
.storage_class = credentialsWithOptions.storage_class,
}, .{ .expires = expires }) catch |sign_err| {
return S3.throwSignError(sign_err, globalThis);
};
Expand Down
13 changes: 12 additions & 1 deletion src/bun.js/webcore/blob.zig
Original file line number Diff line number Diff line change
Expand Up @@ -928,6 +928,7 @@ pub const Blob = struct {
destination_blob.contentTypeOrMimeType(),
aws_options.acl,
proxy_url,
aws_options.storage_class,
@ptrCast(&Wrapper.resolve),
Wrapper.new(.{
.promise = promise,
Expand Down Expand Up @@ -1056,6 +1057,7 @@ pub const Blob = struct {
ctx,
aws_options.options,
aws_options.acl,
aws_options.storage_class,
destination_blob.contentTypeOrMimeType(),
proxy_url,
null,
Expand Down Expand Up @@ -1098,6 +1100,7 @@ pub const Blob = struct {
destination_blob.contentTypeOrMimeType(),
aws_options.acl,
proxy_url,
aws_options.storage_class,
@ptrCast(&Wrapper.resolve),
Wrapper.new(.{
.store = store,
Expand All @@ -1121,6 +1124,7 @@ pub const Blob = struct {
ctx,
s3.options,
aws_options.acl,
aws_options.storage_class,
destination_blob.contentTypeOrMimeType(),
proxy_url,
null,
Expand Down Expand Up @@ -1310,6 +1314,7 @@ pub const Blob = struct {
globalThis,
aws_options.options,
aws_options.acl,
aws_options.storage_class,
destination_blob.contentTypeOrMimeType(),
proxy_url,
null,
Expand Down Expand Up @@ -1369,6 +1374,7 @@ pub const Blob = struct {
globalThis,
aws_options.options,
aws_options.acl,
aws_options.storage_class,
destination_blob.contentTypeOrMimeType(),
proxy_url,
null,
Expand Down Expand Up @@ -3507,6 +3513,8 @@ pub const Blob = struct {
credentials: ?*S3Credentials,
options: bun.S3.MultiPartUploadOptions = .{},
acl: ?S3.ACL = null,
storage_class: ?S3.StorageClass = null,

pub fn isSeekable(_: *const @This()) ?bool {
return true;
}
Expand All @@ -3517,7 +3525,7 @@ pub const Blob = struct {
}

pub fn getCredentialsWithOptions(this: *const @This(), options: ?JSValue, globalObject: *JSC.JSGlobalObject) bun.JSError!S3.S3CredentialsWithOptions {
return S3Credentials.getCredentialsWithOptions(this.getCredentials().*, this.options, options, this.acl, globalObject);
return S3Credentials.getCredentialsWithOptions(this.getCredentials().*, this.options, options, this.acl, this.storage_class, globalObject);
}

pub fn path(this: *@This()) []const u8 {
Expand Down Expand Up @@ -4102,6 +4110,7 @@ pub const Blob = struct {
globalThis,
aws_options.options,
aws_options.acl,
aws_options.storage_class,
this.contentTypeOrMimeType(),
proxy_url,
null,
Expand Down Expand Up @@ -4339,6 +4348,7 @@ pub const Blob = struct {
credentialsWithOptions.options,
this.contentTypeOrMimeType(),
proxy_url,
credentialsWithOptions.storage_class,
);
}
}
Expand All @@ -4349,6 +4359,7 @@ pub const Blob = struct {
.{},
this.contentTypeOrMimeType(),
proxy_url,
null,
);
}
if (store.data != .file) {
Expand Down
4 changes: 3 additions & 1 deletion src/bun.js/webcore/response.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3256,6 +3256,7 @@ pub const Fetch = struct {
.credentials = globalThis.bunVM().transpiler.env.getS3Credentials(),
.options = .{},
.acl = null,
.storage_class = null,
};
defer {
credentialsWithOptions.deinit();
Expand All @@ -3265,7 +3266,7 @@ pub const Fetch = struct {
if (try options.getTruthyComptime(globalThis, "s3")) |s3_options| {
if (s3_options.isObject()) {
s3_options.ensureStillAlive();
credentialsWithOptions = try s3.S3Credentials.getCredentialsWithOptions(credentialsWithOptions.credentials, .{}, s3_options, null, globalThis);
credentialsWithOptions = try s3.S3Credentials.getCredentialsWithOptions(credentialsWithOptions.credentials, .{}, s3_options, null, null, globalThis);
}
}
}
Expand Down Expand Up @@ -3341,6 +3342,7 @@ pub const Fetch = struct {
globalThis,
credentialsWithOptions.options,
credentialsWithOptions.acl,
credentialsWithOptions.storage_class,
if (headers) |h| h.getContentType() else null,
proxy_url,
@ptrCast(&Wrapper.resolve),
Expand Down
7 changes: 7 additions & 0 deletions src/s3/client.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub const ACL = @import("./acl.zig").ACL;
pub const S3HttpDownloadStreamingTask = @import("./download_stream.zig").S3HttpDownloadStreamingTask;
pub const MultiPartUploadOptions = @import("./multipart_options.zig").MultiPartUploadOptions;
pub const MultiPartUpload = @import("./multipart.zig").MultiPartUpload;
pub const StorageClass = @import("./storage_class.zig").StorageClass;

pub const Error = @import("./error.zig");
pub const throwSignError = Error.throwSignError;
Expand Down Expand Up @@ -105,6 +106,7 @@ pub fn upload(
content_type: ?[]const u8,
acl: ?ACL,
proxy_url: ?[]const u8,
storage_class: ?StorageClass,
callback: *const fn (S3UploadResult, *anyopaque) void,
callback_context: *anyopaque,
) void {
Expand All @@ -115,6 +117,7 @@ pub fn upload(
.body = content,
.content_type = content_type,
.acl = acl,
.storage_class = storage_class,
}, .{ .upload = callback }, callback_context);
}
/// returns a writable stream that writes to the s3 path
Expand All @@ -125,6 +128,7 @@ pub fn writableStream(
options: MultiPartUploadOptions,
content_type: ?[]const u8,
proxy: ?[]const u8,
storage_class: ?StorageClass,
) bun.JSError!JSC.JSValue {
const Wrapper = struct {
pub fn callback(result: S3UploadResult, sink: *JSC.WebCore.NetworkSink) void {
Expand Down Expand Up @@ -158,6 +162,7 @@ pub fn writableStream(
.path = bun.default_allocator.dupe(u8, path) catch bun.outOfMemory(),
.proxy = if (proxy_url.len > 0) bun.default_allocator.dupe(u8, proxy_url) catch bun.outOfMemory() else "",
.content_type = if (content_type) |ct| bun.default_allocator.dupe(u8, ct) catch bun.outOfMemory() else null,
.storage_class = storage_class,

.callback = @ptrCast(&Wrapper.callback),
.callback_context = undefined,
Expand Down Expand Up @@ -290,6 +295,7 @@ pub fn uploadStream(
globalThis: *JSC.JSGlobalObject,
options: MultiPartUploadOptions,
acl: ?ACL,
storage_class: ?StorageClass,
content_type: ?[]const u8,
proxy: ?[]const u8,
callback: ?*const fn (S3UploadResult, *anyopaque) void,
Expand Down Expand Up @@ -333,6 +339,7 @@ pub fn uploadStream(
.state = .wait_stream_check,
.options = options,
.acl = acl,
.storage_class = storage_class,
.vm = JSC.VirtualMachine.get(),
});

Expand Down
Loading

0 comments on commit 98c7b84

Please sign in to comment.