Skip to content

Commit d6b4b08

Browse files
authored
Merge branch 'main' into main
2 parents ec8c1cb + 2028e21 commit d6b4b08

File tree

12 files changed

+163
-71
lines changed

12 files changed

+163
-71
lines changed

docs/runtime/http/server.mdx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,15 +193,17 @@ This is the maximum amount of time a connection is allowed to be idle before the
193193
Thus far, the examples on this page have used the explicit `Bun.serve` API. Bun also supports an alternate syntax.
194194

195195
```ts server.ts
196-
import { type Serve } from "bun";
196+
import type { Serve } from "bun";
197197

198198
export default {
199199
fetch(req) {
200200
return new Response("Bun!");
201201
},
202-
} satisfies Serve;
202+
} satisfies Serve.Options<undefined>;
203203
```
204204

205+
The type parameter `<undefined>` represents WebSocket data — if you add a `websocket` handler with custom data attached via `server.upgrade(req, { data: ... })`, replace `undefined` with your data type.
206+
205207
Instead of passing the server options into `Bun.serve`, `export default` it. This file can be executed as-is; when Bun sees a file with a `default` export containing a `fetch` handler, it passes it into `Bun.serve` under the hood.
206208

207209
---

packages/bun-types/bun.d.ts

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1740,9 +1740,9 @@ declare module "bun" {
17401740
* @default "esm"
17411741
*/
17421742
format?: /**
1743-
* ECMAScript Module format
1744-
*/
1745-
| "esm"
1743+
* ECMAScript Module format
1744+
*/
1745+
| "esm"
17461746
/**
17471747
* CommonJS format
17481748
* **Experimental**
@@ -3316,10 +3316,10 @@ declare module "bun" {
33163316
function color(
33173317
input: ColorInput,
33183318
outputFormat?: /**
3319-
* True color ANSI color string, for use in terminals
3320-
* @example \x1b[38;2;100;200;200m
3321-
*/
3322-
| "ansi"
3319+
* True color ANSI color string, for use in terminals
3320+
* @example \x1b[38;2;100;200;200m
3321+
*/
3322+
| "ansi"
33233323
| "ansi-16"
33243324
| "ansi-16m"
33253325
/**
@@ -5650,17 +5650,11 @@ declare module "bun" {
56505650
maxBuffer?: number;
56515651
}
56525652

5653-
interface SpawnSyncOptions<In extends Writable, Out extends Readable, Err extends Readable> extends BaseOptions<
5654-
In,
5655-
Out,
5656-
Err
5657-
> {}
5658-
5659-
interface SpawnOptions<In extends Writable, Out extends Readable, Err extends Readable> extends BaseOptions<
5660-
In,
5661-
Out,
5662-
Err
5663-
> {
5653+
interface SpawnSyncOptions<In extends Writable, Out extends Readable, Err extends Readable>
5654+
extends BaseOptions<In, Out, Err> {}
5655+
5656+
interface SpawnOptions<In extends Writable, Out extends Readable, Err extends Readable>
5657+
extends BaseOptions<In, Out, Err> {
56645658
/**
56655659
* If true, stdout and stderr pipes will not automatically start reading
56665660
* data. Reading will only begin when you access the `stdout` or `stderr`

packages/bun-types/s3.d.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,24 @@ declare module "bun" {
281281
*/
282282
type?: string;
283283

284+
/**
285+
* The Content-Disposition header value.
286+
* Controls how the file is presented when downloaded.
287+
*
288+
* @example
289+
* // Setting attachment disposition with filename
290+
* const file = s3.file("report.pdf", {
291+
* contentDisposition: "attachment; filename=\"quarterly-report.pdf\""
292+
* });
293+
*
294+
* @example
295+
* // Setting inline disposition
296+
* await s3.write("image.png", imageData, {
297+
* contentDisposition: "inline"
298+
* });
299+
*/
300+
contentDisposition?: string | undefined;
301+
284302
/**
285303
* By default, Amazon S3 uses the STANDARD Storage Class to store newly created objects.
286304
*

src/bun.js/webcore/Blob.zig

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,7 @@ fn writeFileWithEmptySourceToDestination(ctx: *jsc.JSGlobalObject, destination_b
968968
s3.path(),
969969
"",
970970
destination_blob.contentTypeOrMimeType(),
971+
aws_options.content_disposition,
971972
aws_options.acl,
972973
proxy_url,
973974
aws_options.storage_class,
@@ -1116,6 +1117,7 @@ pub fn writeFileWithSourceDestination(ctx: *jsc.JSGlobalObject, source_blob: *Bl
11161117
aws_options.acl,
11171118
aws_options.storage_class,
11181119
destination_blob.contentTypeOrMimeType(),
1120+
aws_options.content_disposition,
11191121
proxy_url,
11201122
null,
11211123
undefined,
@@ -1154,6 +1156,7 @@ pub fn writeFileWithSourceDestination(ctx: *jsc.JSGlobalObject, source_blob: *Bl
11541156
s3.path(),
11551157
bytes.slice(),
11561158
destination_blob.contentTypeOrMimeType(),
1159+
aws_options.content_disposition,
11571160
aws_options.acl,
11581161
proxy_url,
11591162
aws_options.storage_class,
@@ -1183,6 +1186,7 @@ pub fn writeFileWithSourceDestination(ctx: *jsc.JSGlobalObject, source_blob: *Bl
11831186
aws_options.acl,
11841187
aws_options.storage_class,
11851188
destination_blob.contentTypeOrMimeType(),
1189+
aws_options.content_disposition,
11861190
proxy_url,
11871191
null,
11881192
undefined,
@@ -1387,6 +1391,7 @@ pub fn writeFileInternal(globalThis: *jsc.JSGlobalObject, path_or_blob_: *PathOr
13871391
aws_options.acl,
13881392
aws_options.storage_class,
13891393
destination_blob.contentTypeOrMimeType(),
1394+
aws_options.content_disposition,
13901395
proxy_url,
13911396
null,
13921397
undefined,
@@ -1447,6 +1452,7 @@ pub fn writeFileInternal(globalThis: *jsc.JSGlobalObject, path_or_blob_: *PathOr
14471452
aws_options.acl,
14481453
aws_options.storage_class,
14491454
destination_blob.contentTypeOrMimeType(),
1455+
aws_options.content_disposition,
14501456
proxy_url,
14511457
null,
14521458
undefined,
@@ -2402,6 +2408,7 @@ pub fn pipeReadableStreamToBlob(this: *Blob, globalThis: *jsc.JSGlobalObject, re
24022408
aws_options.acl,
24032409
aws_options.storage_class,
24042410
this.contentTypeOrMimeType(),
2411+
aws_options.content_disposition,
24052412
proxy_url,
24062413
null,
24072414
undefined,
@@ -2629,13 +2636,22 @@ pub fn getWriter(
26292636
}
26302637
}
26312638
}
2639+
var content_disposition_str: ?ZigString.Slice = null;
2640+
defer if (content_disposition_str) |cd| cd.deinit();
2641+
if (try options.getTruthy(globalThis, "contentDisposition")) |content_disposition| {
2642+
if (!content_disposition.isString()) {
2643+
return globalThis.throwInvalidArgumentType("write", "options.contentDisposition", "string");
2644+
}
2645+
content_disposition_str = try content_disposition.toSlice(globalThis, bun.default_allocator);
2646+
}
26322647
const credentialsWithOptions = try s3.getCredentialsWithOptions(options, globalThis);
26332648
return try S3.writableStream(
26342649
credentialsWithOptions.credentials.dupe(),
26352650
path,
26362651
globalThis,
26372652
credentialsWithOptions.options,
26382653
this.contentTypeOrMimeType(),
2654+
if (content_disposition_str) |cd| cd.slice() else null,
26392655
proxy_url,
26402656
credentialsWithOptions.storage_class,
26412657
);
@@ -2647,6 +2663,7 @@ pub fn getWriter(
26472663
globalThis,
26482664
.{},
26492665
this.contentTypeOrMimeType(),
2666+
null,
26502667
proxy_url,
26512668
null,
26522669
);

src/bun.js/webcore/fetch.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,6 +1301,7 @@ pub fn Bun__fetch_(
13011301
credentialsWithOptions.acl,
13021302
credentialsWithOptions.storage_class,
13031303
if (headers) |h| (h.getContentType()) else null,
1304+
if (headers) |h| h.getContentDisposition() else null,
13041305
proxy_url,
13051306
@ptrCast(&Wrapper.resolve),
13061307
s3_stream,

src/http/Headers.zig

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -75,20 +75,12 @@ pub fn deinit(this: *Headers) void {
7575
this.entries.deinit(this.allocator);
7676
this.buf.clearAndFree(this.allocator);
7777
}
78-
pub fn getContentType(this: *const Headers) ?[]const u8 {
79-
if (this.entries.len == 0 or this.buf.items.len == 0) {
80-
return null;
81-
}
82-
const header_entries = this.entries.slice();
83-
const header_names = header_entries.items(.name);
84-
const header_values = header_entries.items(.value);
8578

86-
for (header_names, 0..header_names.len) |name, i| {
87-
if (bun.strings.eqlCaseInsensitiveASCII(this.asStr(name), "content-type", true)) {
88-
return this.asStr(header_values[i]);
89-
}
90-
}
91-
return null;
79+
pub fn getContentDisposition(this: *const Headers) ?[]const u8 {
80+
return this.get("content-disposition");
81+
}
82+
pub fn getContentType(this: *const Headers) ?[]const u8 {
83+
return this.get("content-type");
9284
}
9385
pub fn asStr(this: *const Headers, ptr: api.StringPointer) []const u8 {
9486
return if (ptr.offset + ptr.length <= this.buf.items.len)

src/s3/client.zig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ pub fn upload(
229229
path: []const u8,
230230
content: []const u8,
231231
content_type: ?[]const u8,
232+
content_disposition: ?[]const u8,
232233
acl: ?ACL,
233234
proxy_url: ?[]const u8,
234235
storage_class: ?StorageClass,
@@ -241,6 +242,7 @@ pub fn upload(
241242
.proxy_url = proxy_url,
242243
.body = content,
243244
.content_type = content_type,
245+
.content_disposition = content_disposition,
244246
.acl = acl,
245247
.storage_class = storage_class,
246248
}, .{ .upload = callback }, callback_context);
@@ -252,6 +254,7 @@ pub fn writableStream(
252254
globalThis: *jsc.JSGlobalObject,
253255
options: MultiPartUploadOptions,
254256
content_type: ?[]const u8,
257+
content_disposition: ?[]const u8,
255258
proxy: ?[]const u8,
256259
storage_class: ?StorageClass,
257260
) bun.JSError!jsc.JSValue {
@@ -295,6 +298,7 @@ pub fn writableStream(
295298
.path = bun.handleOom(bun.default_allocator.dupe(u8, path)),
296299
.proxy = if (proxy_url.len > 0) bun.handleOom(bun.default_allocator.dupe(u8, proxy_url)) else "",
297300
.content_type = if (content_type) |ct| bun.handleOom(bun.default_allocator.dupe(u8, ct)) else null,
301+
.content_disposition = if (content_disposition) |cd| bun.handleOom(bun.default_allocator.dupe(u8, cd)) else null,
298302
.storage_class = storage_class,
299303

300304
.callback = @ptrCast(&Wrapper.callback),
@@ -434,6 +438,7 @@ pub fn uploadStream(
434438
acl: ?ACL,
435439
storage_class: ?StorageClass,
436440
content_type: ?[]const u8,
441+
content_disposition: ?[]const u8,
437442
proxy: ?[]const u8,
438443
callback: ?*const fn (S3UploadResult, *anyopaque) void,
439444
callback_context: *anyopaque,
@@ -470,6 +475,7 @@ pub fn uploadStream(
470475
.path = bun.handleOom(bun.default_allocator.dupe(u8, path)),
471476
.proxy = if (proxy_url.len > 0) bun.handleOom(bun.default_allocator.dupe(u8, proxy_url)) else "",
472477
.content_type = if (content_type) |ct| bun.handleOom(bun.default_allocator.dupe(u8, ct)) else null,
478+
.content_disposition = if (content_disposition) |cd| bun.handleOom(bun.default_allocator.dupe(u8, cd)) else null,
473479
.callback = @ptrCast(&S3UploadStreamWrapper.resolve),
474480
.callback_context = undefined,
475481
.globalThis = globalThis,

0 commit comments

Comments
 (0)