Compare commits

..

7 Commits

Author SHA1 Message Date
Meghan Denny
8863b536fc vscode: update windows webkit sourceFileMap 2025-05-17 01:02:22 -07:00
Jarred Sumner
9bee7a64a2 Support method-specific routes for html & static routes (#19710) 2025-05-17 00:28:01 -07:00
Jarred Sumner
ea6f6dff7f bun init: Add --react flag (#19687) 2025-05-16 23:40:56 -07:00
pfg
342fe232d0 test-eventtarget.js (#19547)
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Co-authored-by: Ciro Spaciari <ciro.spaciari@gmail.com>
2025-05-16 23:17:35 -07:00
pfg
89c5e40544 ReadableStream & ReadableByteStream fixes (#19683) 2025-05-16 22:30:58 -07:00
Jarred Sumner
95af099a0c Fixes #19695 (#19712) 2025-05-16 22:29:28 -07:00
pfg
8686361f4f fix constant folding bug (#19707) 2025-05-16 20:32:29 -07:00
46 changed files with 2146 additions and 918 deletions

57
.vscode/launch.json generated vendored
View File

@@ -481,7 +481,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun test [file]",
@@ -506,7 +506,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun test --only [file]",
@@ -531,7 +531,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun test [file] (fast)",
@@ -556,7 +556,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun test [file] (verbose)",
@@ -581,7 +581,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun test [file] --inspect",
@@ -615,7 +615,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun test [file] --inspect-brk",
@@ -650,7 +650,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun run [file]",
@@ -675,7 +675,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun install",
@@ -696,7 +696,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun run [file] (verbose)",
@@ -721,7 +721,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun run [file] --inspect",
@@ -751,7 +751,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun run [file] --inspect-brk",
@@ -782,7 +782,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun test [...]",
@@ -807,7 +807,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun test [...] (fast)",
@@ -832,7 +832,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun test [...] (verbose)",
@@ -857,7 +857,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun test [...] --watch",
@@ -882,7 +882,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun test [...] --hot",
@@ -907,7 +907,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun test [...] --inspect",
@@ -941,7 +941,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun test [...] --inspect-brk",
@@ -976,7 +976,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun exec [...]",
@@ -998,7 +998,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun test [*]",
@@ -1019,7 +1019,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun test [*] (fast)",
@@ -1044,7 +1044,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun test [*] --inspect",
@@ -1078,7 +1078,7 @@
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
"C:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\vendor\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun test [*] (ci)",
@@ -1113,6 +1113,17 @@
"BUN_GARBAGE_COLLECTOR_LEVEL": "2",
},
},
{
"type": "bun",
"name": "[JS] bun run [file]",
"runtime": "${workspaceFolder}/build/debug/bun-debug",
"runtimeArgs": ["run", "${file}"],
"cwd": "${workspaceFolder}",
"env": {
"BUN_DEBUG_QUIET_LOGS": "1",
"BUN_GARBAGE_COLLECTOR_LEVEL": "2",
},
},
{
"type": "midas-rr",
"request": "attach",

View File

@@ -40,3 +40,32 @@ At the end, it runs `bun install` to install `@types/bun`.
{% /details %}
{% bunCLIUsage command="init" /%}
## React
The `--react` flag will scaffold a React project:
```bash
$ bun init --react
```
The `--react` flag accepts the following values:
- `tailwind` - Scaffold a React project with Tailwind CSS
- `shadcn` - Scaffold a React project with Shadcn/UI and Tailwind CSS
### React + TailwindCSS
This will create a React project with Tailwind CSS configured with Bun's bundler and dev server.
```bash
$ bun init --react=tailwind
```
### React + @shadcn/ui
This will create a React project with shadcn/ui and Tailwind CSS configured with Bun's bundler and dev server.
```bash
$ bun init --react=shadcn
```

View File

@@ -32,6 +32,11 @@
#include <iostream>
#include "MoveOnlyFunction.h"
#include "HttpParser.h"
#include <span>
#include <array>
#include <mutex>
namespace uWS {
template<bool> struct HttpResponse;
@@ -48,6 +53,78 @@ private:
/* Minimum allowed receive throughput per second (clients uploading less than 16kB/sec get dropped) */
static constexpr int HTTP_RECEIVE_THROUGHPUT_BYTES = 16 * 1024;
#define FOR_EACH_HTTP_METHOD(MACRO) \
MACRO("ACL") \
MACRO("BIND") \
MACRO("CHECKOUT") \
MACRO("CONNECT") \
MACRO("COPY") \
MACRO("DELETE") \
MACRO("GET") \
MACRO("HEAD") \
MACRO("LINK") \
MACRO("LOCK") \
MACRO("M-SEARCH") \
MACRO("MERGE") \
MACRO("MKACTIVITY") \
MACRO("MKCALENDAR") \
MACRO("MKCOL") \
MACRO("MOVE") \
MACRO("NOTIFY") \
MACRO("OPTIONS") \
MACRO("PATCH") \
MACRO("POST") \
MACRO("PROPFIND") \
MACRO("PROPPATCH") \
MACRO("PURGE") \
MACRO("PUT") \
MACRO("QUERY") \
MACRO("REBIND") \
MACRO("REPORT") \
MACRO("SEARCH") \
MACRO("SOURCE") \
MACRO("SUBSCRIBE") \
MACRO("TRACE") \
MACRO("UNBIND") \
MACRO("UNLINK") \
MACRO("UNLOCK") \
MACRO("UNSUBSCRIBE") \
#ifndef _WIN32
static constexpr std::array<const std::string, 35> HTTP_METHODS = {
#define MACRO(name) std::string {name},
FOR_EACH_HTTP_METHOD(MACRO)
#undef MACRO
};
static std::span<const std::string> getAllHttpMethods() {
return {HTTP_METHODS.data(), HTTP_METHODS.size()};
}
#else
// Windows, and older C++ can't do constexpr std::array<const std::string, 35>
static constexpr std::array<const char*, 35> HTTP_METHODS = {
#define MACRO(name) name,
FOR_EACH_HTTP_METHOD(MACRO)
#undef MACRO
};
static std::span<const std::string> getAllHttpMethods() {
static std::once_flag flag;
static std::array<std::string, 35> methods;
std::call_once(flag, []() {
methods = {
#define MACRO(name) std::string {name},
FOR_EACH_HTTP_METHOD(MACRO)
#undef MACRO
};
});
return {methods.data(), methods.size()};
}
#endif
#undef FOR_EACH_HTTP_METHOD
us_socket_context_t *getSocketContext() {
return (us_socket_context_t *) this;
}
@@ -504,13 +581,23 @@ public:
void onHttp(std::string_view method, std::string_view pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler, bool upgrade = false) {
HttpContextData<SSL> *httpContextData = getSocketContextData();
std::vector<std::string> methods{std::string(method)};
std::span<const std::string> methods;
std::array<std::string, 1> methods_buffer;
// When it's NOT node:http, allow the uWS default precedence ordering.
if (method == "*" && !httpContextData->flags.useStrictMethodValidation) {
methods = getAllHttpMethods();
} else {
methods_buffer[0] = std::string(method);
methods = {methods_buffer.data(), 1};
}
uint32_t priority = method == "*" ? httpContextData->currentRouter->LOW_PRIORITY : (upgrade ? httpContextData->currentRouter->HIGH_PRIORITY : httpContextData->currentRouter->MEDIUM_PRIORITY);
/* If we are passed nullptr then remove this */
if (!handler) {
httpContextData->currentRouter->remove(methods[0], pattern, priority);
for (const auto &method : methods) {
httpContextData->currentRouter->remove(method, pattern, priority);
}
return;
}

View File

@@ -26,7 +26,7 @@
#include <algorithm>
#include <memory>
#include <utility>
#include <span>
#include <iostream>
#include "MoveOnlyFunction.h"
@@ -278,7 +278,7 @@ public:
}
/* Adds the corresponding entires in matching tree and handler list */
void add(const std::vector<std::string> &methods, std::string_view pattern, MoveOnlyFunction<bool(HttpRouter *)> &&handler, uint32_t priority = MEDIUM_PRIORITY) {
void add(const std::span<const std::string> &methods, std::string_view pattern, MoveOnlyFunction<bool(HttpRouter *)> &&handler, uint32_t priority = MEDIUM_PRIORITY) {
/* First remove existing handler */
remove(methods[0], pattern, priority);

View File

@@ -1432,12 +1432,13 @@ fn deferRequest(
resp: AnyResponse,
) !void {
const deferred = dev.deferred_request_pool.get();
const method = bun.http.Method.which(req.method()) orelse .POST;
deferred.data = .{
.route_bundle_index = route_bundle_index,
.handler = switch (kind) {
.bundled_html_page => .{ .bundled_html_page = .{ .response = resp, .method = bun.http.Method.which(req.method()) orelse .POST } },
.bundled_html_page => .{ .bundled_html_page = .{ .response = resp, .method = method } },
.server_handler => .{
.server_handler = dev.server.?.prepareAndSaveJsRequestContext(req, resp, dev.vm.global) orelse return,
.server_handler = dev.server.?.prepareAndSaveJsRequestContext(req, resp, dev.vm.global, method) orelse return,
},
},
};
@@ -7288,10 +7289,12 @@ pub const EntryPointList = struct {
/// This structure does not increment the reference count of its contents, as
/// the lifetime of them are all tied to the underling Bun.serve instance.
const HTMLRouter = struct {
map: bun.StringHashMapUnmanaged(*HTMLBundle.HTMLBundleRoute),
map: Map,
/// If a catch-all route exists, it is not stored in map, but here.
fallback: ?*HTMLBundle.HTMLBundleRoute,
pub const Map = bun.StringHashMapUnmanaged(*HTMLBundle.HTMLBundleRoute);
pub const empty: HTMLRouter = .{
.map = .empty,
.fallback = null,

View File

@@ -258,14 +258,9 @@ pub const AnyRoute = union(enum) {
}
}
pub fn fromJS(
global: *JSC.JSGlobalObject,
path: []const u8,
argument: JSC.JSValue,
init_ctx: *ServerInitContext,
) bun.JSError!AnyRoute {
pub fn htmlRouteFromJS(argument: JSC.JSValue, init_ctx: *ServerInitContext) ?AnyRoute {
if (argument.as(HTMLBundle)) |html_bundle| {
const entry = try init_ctx.dedupe_html_bundle_map.getOrPut(html_bundle);
const entry = init_ctx.dedupe_html_bundle_map.getOrPut(html_bundle) catch bun.outOfMemory();
if (!entry.found_existing) {
entry.value_ptr.* = HTMLBundle.Route.init(html_bundle);
return .{ .html = entry.value_ptr.* };
@@ -274,6 +269,19 @@ pub const AnyRoute = union(enum) {
}
}
return null;
}
pub fn fromJS(
global: *JSC.JSGlobalObject,
path: []const u8,
argument: JSC.JSValue,
init_ctx: *ServerInitContext,
) bun.JSError!?AnyRoute {
if (AnyRoute.htmlRouteFromJS(argument, init_ctx)) |html_route| {
return html_route;
}
if (argument.isObject()) {
const FrameworkRouter = bun.bake.FrameworkRouter;
if (try argument.getOptional(global, "dir", bun.String.Slice)) |dir| {
@@ -314,7 +322,7 @@ pub const AnyRoute = union(enum) {
}
}
return .{ .static = try StaticRoute.fromJS(global, argument) };
return .{ .static = try StaticRoute.fromJS(global, argument) orelse return null };
}
};
@@ -444,6 +452,7 @@ pub const ServerConfig = struct {
pub const StaticRouteEntry = struct {
path: []const u8,
route: AnyRoute,
method: HTTP.Method.Optional = .any,
pub fn memoryCost(this: *const StaticRouteEntry) usize {
return this.path.len + this.route.memoryCost();
@@ -457,6 +466,7 @@ pub const ServerConfig = struct {
return .{
.path = try bun.default_allocator.dupe(u8, this.path),
.route = this.route,
.method = this.method,
};
}
@@ -472,6 +482,53 @@ pub const ServerConfig = struct {
}
};
fn normalizeStaticRoutesList(this: *ServerConfig) !void {
const Context = struct {
// Ac
pub fn hash(route: *StaticRouteEntry) u64 {
var hasher = std.hash.Wyhash.init(0);
switch (route.method) {
.any => hasher.update("ANY"),
.method => |*set| {
var iter = set.iterator();
while (iter.next()) |method| {
hasher.update(@tagName(method));
}
},
}
hasher.update(route.path);
return hasher.final();
}
};
var static_routes_dedupe_list = std.ArrayList(u64).init(bun.default_allocator);
try static_routes_dedupe_list.ensureTotalCapacity(@truncate(this.static_routes.items.len));
defer static_routes_dedupe_list.deinit();
// Iterate through the list of static routes backwards
// Later ones added override earlier ones
var list = &this.static_routes;
if (list.items.len > 0) {
var index = list.items.len - 1;
while (true) {
const route = &list.items[index];
const hash = Context.hash(route);
if (std.mem.indexOfScalar(u64, static_routes_dedupe_list.items, hash) != null) {
var item = list.orderedRemove(index);
item.deinit();
} else {
try static_routes_dedupe_list.append(hash);
}
if (index == 0) break;
index -= 1;
}
}
// sort the cloned static routes by name for determinism
std.mem.sort(StaticRouteEntry, list.items, {}, StaticRouteEntry.isLessThan);
}
pub fn cloneForReloadingStaticRoutes(this: *ServerConfig) !ServerConfig {
var that = this.*;
this.ssl_config = null;
@@ -480,43 +537,20 @@ pub const ServerConfig = struct {
this.websocket = null;
this.bake = null;
var static_routes_dedupe_list = bun.StringHashMap(void).init(bun.default_allocator);
try static_routes_dedupe_list.ensureTotalCapacity(@truncate(this.static_routes.items.len));
defer static_routes_dedupe_list.deinit();
try that.normalizeStaticRoutesList();
// Iterate through the list of static routes backwards
// Later ones added override earlier ones
var static_routes = this.static_routes;
this.static_routes = std.ArrayList(StaticRouteEntry).init(bun.default_allocator);
if (static_routes.items.len > 0) {
var index = static_routes.items.len - 1;
while (true) {
const route = &static_routes.items[index];
const entry = static_routes_dedupe_list.getOrPut(route.path) catch unreachable;
if (entry.found_existing) {
var item = static_routes.orderedRemove(index);
item.deinit();
}
if (index == 0) break;
index -= 1;
}
}
// sort the cloned static routes by name for determinism
std.mem.sort(StaticRouteEntry, static_routes.items, {}, StaticRouteEntry.isLessThan);
that.static_routes = static_routes;
return that;
}
pub fn appendStaticRoute(this: *ServerConfig, path: []const u8, route: AnyRoute) !void {
pub fn appendStaticRoute(this: *ServerConfig, path: []const u8, route: AnyRoute, method: HTTP.Method.Optional) !void {
try this.static_routes.append(StaticRouteEntry{
.path = try bun.default_allocator.dupe(u8, path),
.route = route,
.method = method,
});
}
fn applyStaticRoute(server: AnyServer, comptime ssl: bool, app: *uws.NewApp(ssl), comptime T: type, entry: T, path: []const u8) void {
fn applyStaticRoute(server: AnyServer, comptime ssl: bool, app: *uws.NewApp(ssl), comptime T: type, entry: T, path: []const u8, method: HTTP.Method.Optional) void {
entry.server = server;
const handler_wrap = struct {
pub fn handler(route: T, req: *uws.Request, resp: *uws.NewApp(ssl).Response) void {
@@ -534,7 +568,17 @@ pub const ServerConfig = struct {
}
};
app.head(path, T, entry, handler_wrap.HEAD);
app.any(path, T, entry, handler_wrap.handler);
switch (method) {
.any => {
app.any(path, T, entry, handler_wrap.handler);
},
.method => |*m| {
var iter = m.iterator();
while (iter.next()) |method_| {
app.method(method_, path, T, entry, handler_wrap.handler);
}
},
}
}
pub fn deinit(this: *ServerConfig) void {
@@ -1419,20 +1463,29 @@ pub const ServerConfig = struct {
var found = false;
inline for (methods) |method| {
if (value.getOwn(global, @tagName(method))) |function| {
if (!function.isCallable()) {
return global.throwInvalidArguments("Expected {s} in {} route to be a function", .{ @tagName(method), bun.fmt.quote(path) });
}
if (!found) {
try validateRouteName(global, path);
}
found = true;
args.user_routes_to_build.append(.{
.route = .{
.path = bun.default_allocator.dupeZ(u8, path) catch bun.outOfMemory(),
.method = .{ .specific = method },
},
.callback = .create(function.withAsyncContextIfNeeded(global), global),
}) catch bun.outOfMemory();
if (function.isCallable()) {
args.user_routes_to_build.append(.{
.route = .{
.path = bun.default_allocator.dupeZ(u8, path) catch bun.outOfMemory(),
.method = .{ .specific = method },
},
.callback = .create(function.withAsyncContextIfNeeded(global), global),
}) catch bun.outOfMemory();
} else if (try AnyRoute.fromJS(global, path, function, &init_ctx)) |html_route| {
var method_set = bun.http.Method.Set.initEmpty();
method_set.insert(method);
args.static_routes.append(.{
.path = bun.default_allocator.dupe(u8, path) catch bun.outOfMemory(),
.route = html_route,
.method = .{ .method = method_set },
}) catch bun.outOfMemory();
}
}
}
@@ -1442,7 +1495,47 @@ pub const ServerConfig = struct {
}
}
const route = try AnyRoute.fromJS(global, path, value, &init_ctx);
const route = try AnyRoute.fromJS(global, path, value, &init_ctx) orelse {
return global.throwInvalidArguments(
\\'routes' expects a Record<string, Response | HTMLBundle | {[method: string]: (req: BunRequest) => Response|Promise<Response>}>
\\
\\To bundle frontend apps on-demand with Bun.serve(), import HTML files.
\\
\\Example:
\\
\\```js
\\import { serve } from "bun";
\\import app from "./app.html";
\\
\\serve({
\\ routes: {
\\ "/index.json": Response.json({ message: "Hello World" }),
\\ "/app": app,
\\ "/path/:param": (req) => {
\\ const param = req.params.param;
\\ return Response.json({ message: `Hello ${param}` });
\\ },
\\ "/path": {
\\ GET(req) {
\\ return Response.json({ message: "Hello World" });
\\ },
\\ POST(req) {
\\ return Response.json({ message: "Hello World" });
\\ },
\\ },
\\ },
\\
\\ fetch(request) {
\\ return new Response("fallback response");
\\ },
\\});
\\```
\\
\\See https://bun.sh/docs/api/http for more information.
,
.{},
);
};
args.static_routes.append(.{
.path = path,
.route = route,
@@ -2707,12 +2800,12 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
return this.sendWritableBytesForCompleteResponseBuffer(this.response_buf_owned.items, write_offset, resp);
}
pub fn create(this: *RequestContext, server: *ThisServer, req: *uws.Request, resp: *App.Response, should_deinit_context: ?*bool) void {
pub fn create(this: *RequestContext, server: *ThisServer, req: *uws.Request, resp: *App.Response, should_deinit_context: ?*bool, method: ?bun.http.Method) void {
this.* = .{
.allocator = server.allocator,
.resp = resp,
.req = req,
.method = HTTP.Method.which(req.method()) orelse .GET,
.method = method orelse HTTP.Method.which(req.method()) orelse .GET,
.server = server,
.defer_deinit_until_callback_completes = should_deinit_context,
};
@@ -5333,8 +5426,8 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
}
}
pub fn appendStaticRoute(this: *ThisServer, path: []const u8, route: AnyRoute) !void {
try this.config.appendStaticRoute(path, route);
pub fn appendStaticRoute(this: *ThisServer, path: []const u8, route: AnyRoute, method: HTTP.Method.Optional) !void {
try this.config.appendStaticRoute(path, route, method);
}
pub fn publish(this: *ThisServer, globalThis: *JSC.JSGlobalObject, topic: ZigString, message_value: JSValue, compress_value: ?JSValue) bun.JSError!JSValue {
@@ -5684,9 +5777,8 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
// These get re-applied when we set the static routes again.
if (this.dev_server) |dev_server| {
// Prevent a use-after-free in the hash table keys.
dev_server.html_router.map.deinit(bun.default_allocator);
dev_server.html_router.clear();
dev_server.html_router.fallback = null;
dev_server.html_router.map = .{};
}
var static_routes = this.config.static_routes;
@@ -6680,7 +6772,10 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
const index = user_route.id;
var should_deinit_context = false;
var prepared = server.prepareJsRequestContext(req, resp, &should_deinit_context, false) orelse return;
var prepared = server.prepareJsRequestContext(req, resp, &should_deinit_context, false, switch (user_route.route.method) {
.any => null,
.specific => |m| m,
}) orelse return;
const server_request_list = js.routeListGetCached(server.jsValueAssertAlive()).?;
var response_value = Bun__ServerRouteList__callRoute(server.globalThis, index, prepared.request_object, server.jsValueAssertAlive(), server_request_list, &prepared.js_request, req);
@@ -6721,9 +6816,13 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
ctx.toAsync(req, prepared.request_object);
}
pub fn onRequest(this: *ThisServer, req: *uws.Request, resp: *App.Response) void {
pub fn onRequest(
this: *ThisServer,
req: *uws.Request,
resp: *App.Response,
) void {
var should_deinit_context = false;
const prepared = this.prepareJsRequestContext(req, resp, &should_deinit_context, true) orelse return;
const prepared = this.prepareJsRequestContext(req, resp, &should_deinit_context, true, null) orelse return;
bun.assert(this.config.onRequest != .zero);
@@ -6747,7 +6846,7 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
extra_args: [arg_count]JSValue,
) void {
const prepared: PreparedRequest = switch (req) {
.stack => |r| this.prepareJsRequestContext(r, resp, null, true) orelse return,
.stack => |r| this.prepareJsRequestContext(r, resp, null, true, null) orelse return,
.saved => |data| .{
.js_request = data.js_request.get() orelse @panic("Request was unexpectedly freed"),
.request_object = data.request,
@@ -6823,7 +6922,7 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
}
};
pub fn prepareJsRequestContext(this: *ThisServer, req: *uws.Request, resp: *App.Response, should_deinit_context: ?*bool, create_js_request: bool) ?PreparedRequest {
pub fn prepareJsRequestContext(this: *ThisServer, req: *uws.Request, resp: *App.Response, should_deinit_context: ?*bool, create_js_request: bool, method: ?bun.http.Method) ?PreparedRequest {
JSC.markBinding(@src());
this.onPendingRequest();
if (comptime Environment.isDebug) {
@@ -6845,7 +6944,7 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
}
const ctx = this.request_pool_allocator.tryGet() catch bun.outOfMemory();
ctx.create(this, req, resp, should_deinit_context);
ctx.create(this, req, resp, should_deinit_context, method);
this.vm.jsc.reportExtraMemory(@sizeOf(RequestContext));
const body = this.vm.initRequestBodyValue(.{ .Null = {} }) catch unreachable;
@@ -6922,12 +7021,12 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
};
}
fn upgradeWebSocketUserRoute(this: *UserRoute, resp: *App.Response, req: *uws.Request, upgrade_ctx: *uws.uws_socket_context_t) void {
fn upgradeWebSocketUserRoute(this: *UserRoute, resp: *App.Response, req: *uws.Request, upgrade_ctx: *uws.uws_socket_context_t, method: ?bun.http.Method) void {
const server = this.server;
const index = this.id;
var should_deinit_context = false;
var prepared = server.prepareJsRequestContext(req, resp, &should_deinit_context, false) orelse return;
var prepared = server.prepareJsRequestContext(req, resp, &should_deinit_context, false, method) orelse return;
prepared.ctx.upgrade_context = upgrade_ctx; // set the upgrade context
const server_request_list = js.routeListGetCached(server.jsValueAssertAlive()).?;
var response_value = Bun__ServerRouteList__callRoute(server.globalThis, index, prepared.request_object, server.jsValueAssertAlive(), server_request_list, &prepared.js_request, req);
@@ -6949,7 +7048,7 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
JSC.markBinding(@src());
if (id == 1) {
// This is actually a UserRoute if id is 1 so it's safe to cast
upgradeWebSocketUserRoute(@ptrCast(this), resp, req, upgrade_ctx);
upgradeWebSocketUserRoute(@ptrCast(this), resp, req, upgrade_ctx, null);
return;
}
// Access `this` as *ThisServer only if id is 0
@@ -6969,7 +7068,7 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
req.setYield(false);
var ctx = this.request_pool_allocator.tryGet() catch bun.outOfMemory();
var should_deinit_context = false;
ctx.create(this, req, resp, &should_deinit_context);
ctx.create(this, req, resp, &should_deinit_context, null);
var body = this.vm.initRequestBodyValue(.{ .Null = {} }) catch unreachable;
ctx.request_body = body;
@@ -7022,71 +7121,70 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
fn setRoutes(this: *ThisServer) JSC.JSValue {
var route_list_value = JSC.JSValue.zero;
// TODO: move devserver and plugin logic away
const app = this.app.?;
const any_server = AnyServer.from(this);
const dev_server = this.dev_server;
// Plugins need to be registered if any of the following are
// assigned. This is done in `setRoutes` so that reloading
// a server can initialize such state.
// - DevServer
// - HTML Bundle
var needs_plugins = dev_server != null;
// --- 1. Handle user_routes_to_build (dynamic JS routes) ---
// (This part remains conceptually the same: populate this.user_routes and route_list_value
// Crucially, ServerConfig.fromJS must ensure `route.method` is correctly .specific or .any)
if (this.config.user_routes_to_build.items.len > 0) {
var user_routes_to_build = this.config.user_routes_to_build.moveToUnmanaged();
var user_routes_to_build_list = this.config.user_routes_to_build.moveToUnmanaged();
var old_user_routes = this.user_routes;
defer {
for (old_user_routes.items) |*route| {
route.route.deinit();
}
for (old_user_routes.items) |*r| r.route.deinit();
old_user_routes.deinit(bun.default_allocator);
}
this.user_routes = std.ArrayListUnmanaged(UserRoute).initCapacity(bun.default_allocator, user_routes_to_build.items.len) catch bun.outOfMemory();
const paths = bun.default_allocator.alloc(ZigString, user_routes_to_build.items.len) catch bun.outOfMemory();
const callbacks = bun.default_allocator.alloc(JSC.JSValue, user_routes_to_build.items.len) catch bun.outOfMemory();
defer bun.default_allocator.free(paths);
defer bun.default_allocator.free(callbacks);
this.user_routes = std.ArrayListUnmanaged(UserRoute).initCapacity(bun.default_allocator, user_routes_to_build_list.items.len) catch @panic("OOM");
const paths_zig = bun.default_allocator.alloc(ZigString, user_routes_to_build_list.items.len) catch @panic("OOM");
defer bun.default_allocator.free(paths_zig);
const callbacks_js = bun.default_allocator.alloc(JSC.JSValue, user_routes_to_build_list.items.len) catch @panic("OOM");
defer bun.default_allocator.free(callbacks_js);
for (user_routes_to_build.items, paths, callbacks, 0..) |*route, *path, *callback, i| {
path.* = ZigString.init(route.route.path);
callback.* = route.callback.get().?;
for (user_routes_to_build_list.items, paths_zig, callbacks_js, 0..) |*builder, *p_zig, *cb_js, i| {
p_zig.* = ZigString.init(builder.route.path);
cb_js.* = builder.callback.get().?;
this.user_routes.appendAssumeCapacity(.{
.id = @truncate(i),
.server = this,
.route = route.route,
.route = builder.route,
});
route.route = .{};
builder.route = .{}; // Mark as moved
}
route_list_value = Bun__ServerRouteList__create(this.globalThis, callbacks.ptr, paths.ptr, user_routes_to_build.items.len);
for (user_routes_to_build.items) |*route| {
route.deinit();
}
user_routes_to_build.deinit(bun.default_allocator);
route_list_value = Bun__ServerRouteList__create(this.globalThis, callbacks_js.ptr, paths_zig.ptr, user_routes_to_build_list.items.len);
for (user_routes_to_build_list.items) |*builder| builder.deinit();
user_routes_to_build_list.deinit(bun.default_allocator);
}
var has_any_ws = false;
// --- 2. Setup WebSocket handler's app reference ---
if (this.config.websocket) |*websocket| {
websocket.globalObject = this.globalThis;
websocket.handler.app = app;
websocket.handler.flags.ssl = ssl_enabled;
}
// This may get applied multiple times.
// --- 3. Register compiled user routes (this.user_routes) & Track "/*" Coverage ---
var star_methods_covered_by_user = bun.http.Method.Set.initEmpty();
var has_any_user_route_for_star_path = false; // True if "/*" path appears in user_routes at all
var has_any_ws_route_for_star_path = false;
for (this.user_routes.items) |*user_route| {
const is_star_path = strings.eqlComptime(user_route.route.path, "/*");
if (is_star_path) {
has_any_user_route_for_star_path = true;
}
// Register HTTP routes
switch (user_route.route.method) {
.any => {
app.any(user_route.route.path, *UserRoute, user_route, onUserRouteRequest);
if (is_star_path) {
star_methods_covered_by_user = .initFull();
}
if (this.config.websocket) |*websocket| {
// Setup user websocket in the route if needed.
if (!has_any_ws) {
// mark if the route is a catch-all so we dont override it
has_any_ws = strings.eqlComptime(user_route.route.path, "/*");
if (is_star_path) {
has_any_ws_route_for_star_path = true;
}
app.ws(
user_route.route.path,
@@ -7096,12 +7194,16 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
);
}
},
.specific => |method| {
app.method(method, user_route.route.path, *UserRoute, user_route, onUserRouteRequest);
.specific => |method_val| { // method_val is HTTP.Method here
app.method(method_val, user_route.route.path, *UserRoute, user_route, onUserRouteRequest);
if (is_star_path) {
star_methods_covered_by_user.insert(method_val);
}
// Setup user websocket in the route if needed.
if (this.config.websocket) |*websocket| {
// Websocket upgrade is a GET request
if (method == HTTP.Method.GET) {
if (method_val == .GET) {
app.ws(
user_route.route.path,
user_route,
@@ -7114,22 +7216,36 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
}
}
// negative routes have backwards precedence.
for (this.config.negative_routes.items) |route| {
// Since .applyStaticRoute does head, we need to do it first here too.
app.head(route, *ThisServer, this, onRequest);
app.any(route, *ThisServer, this, onRequest);
// --- 4. Register negative routes ---
for (this.config.negative_routes.items) |route_path| {
app.head(route_path, *ThisServer, this, onRequest);
app.any(route_path, *ThisServer, this, onRequest);
}
// --- 5. Register static routes & Track "/*" Coverage ---
var needs_plugins = dev_server != null;
var has_static_route_for_star_path = false;
if (this.config.static_routes.items.len > 0) {
for (this.config.static_routes.items) |*entry| {
if (strings.eqlComptime(entry.path, "/*")) {
has_static_route_for_star_path = true;
switch (entry.method) {
.any => {
star_methods_covered_by_user = .initFull();
},
.method => |method| {
star_methods_covered_by_user.setUnion(method);
},
}
}
switch (entry.route) {
.static => |static_route| {
ServerConfig.applyStaticRoute(any_server, ssl_enabled, app, *StaticRoute, static_route, entry.path);
ServerConfig.applyStaticRoute(any_server, ssl_enabled, app, *StaticRoute, static_route, entry.path, entry.method);
},
.html => |html_bundle_route| {
ServerConfig.applyStaticRoute(any_server, ssl_enabled, app, *HTMLBundle.Route, html_bundle_route.data, entry.path);
ServerConfig.applyStaticRoute(any_server, ssl_enabled, app, *HTMLBundle.Route, html_bundle_route.data, entry.path, entry.method);
if (dev_server) |dev| {
dev.html_router.put(dev.allocator, entry.path, html_bundle_route.data) catch bun.outOfMemory();
}
@@ -7140,23 +7256,37 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
}
}
// If there are plugins, initialize the ServePlugins object in
// an unqueued state. The first thing (HTML Bundle, DevServer)
// that needs plugins will cause the load to happen.
if (needs_plugins and this.plugins == null) if (this.vm.transpiler.options.serve_plugins) |serve_plugins| {
if (serve_plugins.len > 0) {
this.plugins = ServePlugins.init(serve_plugins);
// --- 6. Initialize plugins if needed ---
if (needs_plugins and this.plugins == null) {
if (this.vm.transpiler.options.serve_plugins) |serve_plugins_config| {
if (serve_plugins_config.len > 0) {
this.plugins = ServePlugins.init(serve_plugins_config);
}
}
};
}
const @"has /*" = for (this.config.static_routes.items) |route| {
if (strings.eqlComptime(route.path, "/*")) break true;
} else for (this.user_routes.items) |route| {
if (strings.eqlComptime(route.route.path, "/*")) break true;
} else false;
// --- 7. Debug mode specific routes ---
if (debug_mode) {
app.get("/bun:info", *ThisServer, this, onBunInfoRequest);
if (this.config.inspector) {
JSC.markBinding(@src());
Bun__addInspector(ssl_enabled, app, this.globalThis);
}
}
// --- 8. Handle DevServer routes & Track "/*" Coverage ---
var has_dev_server_for_star_path = false;
if (dev_server) |dev| {
// dev.setRoutes might register its own "/*" HTTP handler
has_dev_server_for_star_path = dev.setRoutes(this) catch bun.outOfMemory();
if (has_dev_server_for_star_path) {
// Assume dev server "/*" covers all methods if it exists
star_methods_covered_by_user = .initFull();
}
}
// Setup user websocket fallback route aka fetch function if fetch is not provided will respond with 403.
if (!has_any_ws) {
if (!has_any_ws_route_for_star_path) {
if (this.config.websocket) |*websocket| {
app.ws(
"/*",
@@ -7166,66 +7296,40 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
);
}
}
// --- 9. Consolidated "/*" HTTP Fallback Registration ---
if (star_methods_covered_by_user.eql(bun.http.Method.Set.initFull())) {
// User/Static/Dev has already provided a "/*" handler for ALL methods.
// No further global "/*" HTTP fallback needed.
} else if (has_any_user_route_for_star_path or has_static_route_for_star_path or has_dev_server_for_star_path) {
// A "/*" route exists, but doesn't cover all methods.
// Apply the global handler to the *remaining* methods for "/*".
// So we flip the bits for the methods that are not covered by the user/static/dev routes
star_methods_covered_by_user.toggleAll();
var iter = star_methods_covered_by_user.iterator();
while (iter.next()) |method_to_cover| {
switch (this.config.onNodeHTTPRequest) {
.zero => switch (this.config.onRequest) {
.zero => app.method(method_to_cover, "/*", *ThisServer, this, on404),
else => app.method(method_to_cover, "/*", *ThisServer, this, onRequest),
},
else => app.method(method_to_cover, "/*", *ThisServer, this, onNodeHTTPRequest),
}
}
} else {
switch (this.config.onNodeHTTPRequest) {
.zero => switch (this.config.onRequest) {
.zero => app.any("/*", *ThisServer, this, on404),
else => app.any("/*", *ThisServer, this, onRequest),
},
else => app.any("/*", *ThisServer, this, onNodeHTTPRequest),
}
}
// If onNodeHTTPRequest is configured, it might be needed for Node.js compatibility layer
// for specific Node API routes, even if it's not the main "/*" handler.
if (this.config.onNodeHTTPRequest != .zero) {
app.any("/*", *ThisServer, this, onNodeHTTPRequest);
NodeHTTP_assignOnCloseFunction(ssl_enabled, app);
} else if (this.config.onRequest != .zero and !@"has /*") {
app.any("/*", *ThisServer, this, onRequest);
}
if (debug_mode) {
app.get("/bun:info", *ThisServer, this, onBunInfoRequest);
if (this.config.inspector) {
JSC.markBinding(@src());
Bun__addInspector(ssl_enabled, app, this.globalThis);
}
}
var has_dev_catch_all = false;
if (dev_server) |dev| {
// DevServer adds a catch-all handler to use FrameworkRouter (full stack apps)
has_dev_catch_all = dev.setRoutes(this) catch bun.outOfMemory();
}
// "/*" routes are added backwards, so if they have a static route, it will never be matched
// so we need to check for that first
if (!has_dev_catch_all and !@"has /*" and this.config.onNodeHTTPRequest != .zero) {
app.any("/*", *ThisServer, this, onNodeHTTPRequest);
} else if (!has_dev_catch_all and !@"has /*" and this.config.onRequest != .zero) {
app.any("/*", *ThisServer, this, onRequest);
} else if (!has_dev_catch_all and this.config.onNodeHTTPRequest != .zero) {
app.post("/*", *ThisServer, this, onNodeHTTPRequest);
app.put("/*", *ThisServer, this, onNodeHTTPRequest);
app.patch("/*", *ThisServer, this, onNodeHTTPRequest);
app.delete("/*", *ThisServer, this, onNodeHTTPRequest);
app.options("/*", *ThisServer, this, onNodeHTTPRequest);
app.trace("/*", *ThisServer, this, onNodeHTTPRequest);
app.connect("/*", *ThisServer, this, onNodeHTTPRequest);
} else if (!has_dev_catch_all and this.config.onRequest != .zero) {
// "/*" routes are added backwards, so if they have a static route,
// it will never be matched so we need to check for that first
if (!@"has /*") {
app.any("/*", *ThisServer, this, onRequest);
} else {
// The HTML catch-all receives GET, HEAD.
app.post("/*", *ThisServer, this, onRequest);
app.put("/*", *ThisServer, this, onRequest);
app.patch("/*", *ThisServer, this, onRequest);
app.delete("/*", *ThisServer, this, onRequest);
app.options("/*", *ThisServer, this, onRequest);
app.trace("/*", *ThisServer, this, onRequest);
app.connect("/*", *ThisServer, this, onRequest);
}
} else if (!has_dev_catch_all and this.config.onRequest == .zero and !@"has /*") {
app.any("/*", *ThisServer, this, on404);
} else if (!has_dev_catch_all and this.config.onRequest == .zero) {
app.post("/*", *ThisServer, this, on404);
app.put("/*", *ThisServer, this, on404);
app.patch("/*", *ThisServer, this, on404);
app.delete("/*", *ThisServer, this, on404);
app.options("/*", *ThisServer, this, on404);
app.trace("/*", *ThisServer, this, on404);
app.connect("/*", *ThisServer, this, on404);
}
return route_list_value;
@@ -7600,12 +7704,12 @@ pub const AnyServer = struct {
};
}
pub fn appendStaticRoute(this: AnyServer, path: []const u8, route: AnyRoute) !void {
pub fn appendStaticRoute(this: AnyServer, path: []const u8, route: AnyRoute, method: HTTP.Method.Optional) !void {
return switch (this.ptr.tag()) {
Ptr.case(HTTPServer) => this.ptr.as(HTTPServer).appendStaticRoute(path, route),
Ptr.case(HTTPSServer) => this.ptr.as(HTTPSServer).appendStaticRoute(path, route),
Ptr.case(DebugHTTPServer) => this.ptr.as(DebugHTTPServer).appendStaticRoute(path, route),
Ptr.case(DebugHTTPSServer) => this.ptr.as(DebugHTTPSServer).appendStaticRoute(path, route),
Ptr.case(HTTPServer) => this.ptr.as(HTTPServer).appendStaticRoute(path, route, method),
Ptr.case(HTTPSServer) => this.ptr.as(HTTPSServer).appendStaticRoute(path, route, method),
Ptr.case(DebugHTTPServer) => this.ptr.as(DebugHTTPServer).appendStaticRoute(path, route, method),
Ptr.case(DebugHTTPSServer) => this.ptr.as(DebugHTTPSServer).appendStaticRoute(path, route, method),
else => bun.unreachablePanic("Invalid pointer tag", .{}),
};
}
@@ -7722,12 +7826,13 @@ pub const AnyServer = struct {
req: *uws.Request,
resp: uws.AnyResponse,
global: *JSC.JSGlobalObject,
method: ?bun.http.Method,
) ?SavedRequest {
return switch (server.ptr.tag()) {
Ptr.case(HTTPServer) => (server.ptr.as(HTTPServer).prepareJsRequestContext(req, resp.TCP, null, true) orelse return null).save(global, req, resp.TCP),
Ptr.case(HTTPSServer) => (server.ptr.as(HTTPSServer).prepareJsRequestContext(req, resp.SSL, null, true) orelse return null).save(global, req, resp.SSL),
Ptr.case(DebugHTTPServer) => (server.ptr.as(DebugHTTPServer).prepareJsRequestContext(req, resp.TCP, null, true) orelse return null).save(global, req, resp.TCP),
Ptr.case(DebugHTTPSServer) => (server.ptr.as(DebugHTTPSServer).prepareJsRequestContext(req, resp.SSL, null, true) orelse return null).save(global, req, resp.SSL),
Ptr.case(HTTPServer) => (server.ptr.as(HTTPServer).prepareJsRequestContext(req, resp.TCP, null, true, method) orelse return null).save(global, req, resp.TCP),
Ptr.case(HTTPSServer) => (server.ptr.as(HTTPSServer).prepareJsRequestContext(req, resp.SSL, null, true, method) orelse return null).save(global, req, resp.SSL),
Ptr.case(DebugHTTPServer) => (server.ptr.as(DebugHTTPServer).prepareJsRequestContext(req, resp.TCP, null, true, method) orelse return null).save(global, req, resp.TCP),
Ptr.case(DebugHTTPSServer) => (server.ptr.as(DebugHTTPSServer).prepareJsRequestContext(req, resp.SSL, null, true, method) orelse return null).save(global, req, resp.SSL),
else => bun.unreachablePanic("Invalid pointer tag", .{}),
};
}

View File

@@ -64,6 +64,11 @@ pub const Route = struct {
/// When state == .pending, incomplete responses are stored here.
pending_responses: std.ArrayListUnmanaged(*PendingResponse) = .{},
method: union(enum) {
any: void,
method: bun.http.Method.Set,
} = .any,
pub fn memoryCost(this: *const Route) usize {
var cost: usize = 0;
cost += @sizeOf(Route);
@@ -405,7 +410,7 @@ pub const Route = struct {
route_path = route_path[1..];
}
server.appendStaticRoute(route_path, .{ .static = static_route }) catch bun.outOfMemory();
server.appendStaticRoute(route_path, .{ .static = static_route }, .any) catch bun.outOfMemory();
}
const html_route: *StaticRoute = this_html_route orelse @panic("Internal assertion failure: HTML entry point not found in HTMLBundle.");

View File

@@ -76,7 +76,7 @@ pub fn memoryCost(this: *const StaticRoute) usize {
return @sizeOf(StaticRoute) + this.blob.memoryCost() + this.headers.memoryCost();
}
pub fn fromJS(globalThis: *JSC.JSGlobalObject, argument: JSC.JSValue) bun.JSError!*StaticRoute {
pub fn fromJS(globalThis: *JSC.JSGlobalObject, argument: JSC.JSValue) bun.JSError!?*StaticRoute {
if (argument.as(JSC.WebCore.Response)) |response| {
// The user may want to pass in the same Response object multiple endpoints
@@ -147,45 +147,7 @@ pub fn fromJS(globalThis: *JSC.JSGlobalObject, argument: JSC.JSValue) bun.JSErro
});
}
return globalThis.throwInvalidArguments(
\\'routes' expects a Record<string, Response | HTMLBundle | {[method: string]: (req: BunRequest) => Response|Promise<Response>}>
\\
\\To bundle frontend apps on-demand with Bun.serve(), import HTML files.
\\
\\Example:
\\
\\```js
\\import { serve } from "bun";
\\import app from "./app.html";
\\
\\serve({
\\ routes: {
\\ "/index.json": Response.json({ message: "Hello World" }),
\\ "/app": app,
\\ "/path/:param": (req) => {
\\ const param = req.params.param;
\\ return Response.json({ message: `Hello ${param}` });
\\ },
\\ "/path": {
\\ GET(req) {
\\ return Response.json({ message: "Hello World" });
\\ },
\\ POST(req) {
\\ return Response.json({ message: "Hello World" });
\\ },
\\ },
\\ },
\\
\\ fetch(request) {
\\ return new Response("fallback response");
\\ },
\\});
\\```
\\
\\See https://bun.sh/docs/api/http for more information.
,
.{},
);
return null;
}
// HEAD requests have no body.

View File

@@ -1390,12 +1390,40 @@ extern "C" void Bun__Process__emitWarning(Zig::GlobalObject* globalObject, Encod
JSValue::decode(ctor));
}
JSValue Process::emitWarning(JSC::JSGlobalObject* lexicalGlobalObject, JSValue warning, JSValue type, JSValue code, JSValue ctor)
JSValue Process::emitWarningErrorInstance(JSC::JSGlobalObject* lexicalGlobalObject, JSValue errorInstance)
{
Zig::GlobalObject* globalObject = defaultGlobalObject(lexicalGlobalObject);
VM& vm = getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
auto* process = jsCast<Process*>(globalObject->processObject());
auto warningName = errorInstance.get(lexicalGlobalObject, vm.propertyNames->name);
RETURN_IF_EXCEPTION(scope, {});
if (isJSValueEqualToASCIILiteral(globalObject, warningName, "DeprecationWarning"_s)) {
if (Bun__Node__ProcessNoDeprecation) {
return jsUndefined();
}
if (Bun__Node__ProcessThrowDeprecation) {
// // Delay throwing the error to guarantee that all former warnings were properly logged.
// return process.nextTick(() => {
// throw warning;
// });
auto func = JSFunction::create(vm, globalObject, 1, ""_s, jsFunction_throwValue, JSC::ImplementationVisibility::Private);
process->queueNextTick(globalObject, func, errorInstance);
return jsUndefined();
}
}
// process.nextTick(doEmitWarning, warning);
auto func = JSFunction::create(vm, globalObject, 1, ""_s, jsFunction_emitWarning, JSC::ImplementationVisibility::Private);
process->queueNextTick(globalObject, func, errorInstance);
return jsUndefined();
}
JSValue Process::emitWarning(JSC::JSGlobalObject* lexicalGlobalObject, JSValue warning, JSValue type, JSValue code, JSValue ctor)
{
Zig::GlobalObject* globalObject = defaultGlobalObject(lexicalGlobalObject);
VM& vm = getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue detail = jsUndefined();
if (Bun__Node__ProcessNoDeprecation && isJSValueEqualToASCIILiteral(globalObject, type, "DeprecationWarning"_s)) {
@@ -1453,25 +1481,7 @@ JSValue Process::emitWarning(JSC::JSGlobalObject* lexicalGlobalObject, JSValue w
if (!detail.isUndefined()) errorInstance->putDirect(vm, vm.propertyNames->detail, detail, JSC::PropertyAttribute::DontEnum | 0);
// ErrorCaptureStackTrace(warning, ctor || process.emitWarning);
if (isJSValueEqualToASCIILiteral(globalObject, type, "DeprecationWarning"_s)) {
if (Bun__Node__ProcessNoDeprecation) {
return jsUndefined();
}
if (Bun__Node__ProcessThrowDeprecation) {
// // Delay throwing the error to guarantee that all former warnings were properly logged.
// return process.nextTick(() => {
// throw warning;
// });
auto func = JSFunction::create(vm, globalObject, 1, ""_s, jsFunction_throwValue, JSC::ImplementationVisibility::Private);
process->queueNextTick(globalObject, func, errorInstance);
return jsUndefined();
}
}
// process.nextTick(doEmitWarning, warning);
auto func = JSFunction::create(vm, globalObject, 1, ""_s, jsFunction_emitWarning, JSC::ImplementationVisibility::Private);
process->queueNextTick(globalObject, func, errorInstance);
return jsUndefined();
RELEASE_AND_RETURN(scope, emitWarningErrorInstance(lexicalGlobalObject, errorInstance));
}
JSC_DEFINE_HOST_FUNCTION(Process_emitWarning, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))

View File

@@ -65,6 +65,7 @@ public:
// This is equivalent to `process.nextTick(() => process.emit(eventName, event))` from JavaScript.
void emitOnNextTick(Zig::GlobalObject* globalObject, ASCIILiteral eventName, JSValue event);
static JSValue emitWarningErrorInstance(JSC::JSGlobalObject* lexicalGlobalObject, JSValue errorInstance);
static JSValue emitWarning(JSC::JSGlobalObject* lexicalGlobalObject, JSValue warning, JSValue type, JSValue code, JSValue ctor);
JSString* cachedCwd() { return m_cachedCwd.get(); }

View File

@@ -70,6 +70,7 @@ const errors: ErrorCodeMapping = [
["ERR_DNS_SET_SERVERS_FAILED", Error],
["ERR_ENCODING_INVALID_ENCODED_DATA", TypeError],
["ERR_ENCODING_NOT_SUPPORTED", RangeError],
["ERR_EVENT_RECURSION", Error],
["ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE", Error],
["ERR_FORMDATA_PARSE_ERROR", TypeError],
["ERR_FS_CP_DIR_TO_NON_DIR", Error],

View File

@@ -73,9 +73,11 @@ enum ExceptionCode : uint8_t {
// Used to indicate to the bindings that a JS exception was thrown below and it should be propagated.
ExistingExceptionError,
// Node errors
InvalidThisError,
InvalidURLError,
CryptoOperationFailedError,
EVENT_RECURSION,
};
} // namespace WebCore

View File

@@ -185,6 +185,9 @@ JSValue createDOMException(JSGlobalObject* lexicalGlobalObject, ExceptionCode ec
case ExceptionCode::CryptoOperationFailedError:
return Bun::createError(lexicalGlobalObject, Bun::ErrorCode::ERR_CRYPTO_OPERATION_FAILED, message.isEmpty() ? "Crypto operation failed"_s : message);
case ExceptionCode::EVENT_RECURSION:
return Bun::createError(lexicalGlobalObject, Bun::ErrorCode::ERR_EVENT_RECURSION, message);
default: {
// FIXME: All callers to createDOMException need to pass in the correct global object.
// For now, we're going to assume the lexicalGlobalObject. Which is wrong in cases like this:

View File

@@ -554,7 +554,6 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_validateEncoding, (JSC::JSGlobalObject * glo
auto encoding = callFrame->argument(1);
auto normalized = WebCore::parseEnumeration<BufferEncodingType>(*globalObject, encoding);
if (normalized == BufferEncodingType::hex) {
auto data = callFrame->argument(0);
@@ -573,7 +572,6 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_validateEncoding, (JSC::JSGlobalObject * glo
length = lengthValue.toLength(globalObject);
RETURN_IF_EXCEPTION(scope, {});
}
if (length % 2 != 0) {
return Bun::ERR::INVALID_ARG_VALUE(scope, globalObject, "encoding"_s, encoding, makeString("is invalid for data of length "_s, length));
}

View File

@@ -56,6 +56,7 @@
#include <wtf/SetForScope.h>
#include <wtf/StdLibExtras.h>
#include <wtf/Vector.h>
#include "ErrorCode.h"
namespace WebCore {
@@ -231,9 +232,13 @@ bool EventTarget::hasActiveEventListeners(const AtomString& eventType) const
ExceptionOr<bool> EventTarget::dispatchEventForBindings(Event& event)
{
if (!event.isInitialized() || event.isBeingDispatched())
if (!event.isInitialized())
return Exception { InvalidStateError };
if (event.isBeingDispatched()) {
return Exception { EVENT_RECURSION, makeString("The event \""_s, event.type(), "\" is already being dispatched"_s) };
}
if (!scriptExecutionContext())
return false;

View File

@@ -55,6 +55,8 @@
#include <wtf/GetPtr.h>
#include <wtf/PointerPreparations.h>
#include <wtf/URL.h>
#include "ErrorCode.h"
#include "NodeValidator.h"
namespace WebCore {
using namespace JSC;
@@ -158,6 +160,10 @@ template<> JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSEventDOMConstructor::c
auto type = convert<IDLAtomStringAdaptor<IDLDOMString>>(*lexicalGlobalObject, argument0.value());
RETURN_IF_EXCEPTION(throwScope, {});
EnsureStillAliveScope argument1 = callFrame->argument(1);
if (!argument1.value().isUndefinedOrNull() && !argument1.value().isObject() && !argument1.value().isCallable()) {
Bun::ERR::INVALID_ARG_TYPE(throwScope, lexicalGlobalObject, "options"_s, "object"_s, argument1.value());
}
RETURN_IF_EXCEPTION(throwScope, {});
auto eventInitDict = convert<IDLDictionary<EventInit>>(*lexicalGlobalObject, argument1.value());
RETURN_IF_EXCEPTION(throwScope, {});
auto object = Event::create(WTFMove(type), WTFMove(eventInitDict));

View File

@@ -20,6 +20,7 @@
#include "config.h"
#include "JSEventListener.h"
#include "BunProcess.h"
// #include "BeforeUnloadEvent.h"
// #include "ContentSecurityPolicy.h"
#include "EventNames.h"
@@ -123,6 +124,22 @@ void JSEventListener::visitJSFunction(SlotVisitor& visitor) { visitJSFunctionImp
// event.setReturnValue(returnValue);
// }
JSC_DEFINE_HOST_FUNCTION(jsFunctionEmitUncaughtException, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
{
auto exception = callFrame->argument(0);
reportException(lexicalGlobalObject, exception);
return JSValue::encode(JSC::jsUndefined());
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionEmitUncaughtExceptionNextTick, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
{
Zig::GlobalObject* globalObject = defaultGlobalObject(lexicalGlobalObject);
Bun::Process* process = jsCast<Bun::Process*>(globalObject->processObject());
auto exception = callFrame->argument(0);
auto func = JSFunction::create(globalObject->vm(), globalObject, 1, String(), jsFunctionEmitUncaughtException, JSC::ImplementationVisibility::Private);
process->queueNextTick(lexicalGlobalObject, func, exception);
return JSC::JSValue::encode(JSC::jsUndefined());
}
void JSEventListener::handleEvent(ScriptExecutionContext& scriptExecutionContext, Event& event)
{
if (scriptExecutionContext.isJSExecutionForbidden())
@@ -234,6 +251,32 @@ void JSEventListener::handleEvent(ScriptExecutionContext& scriptExecutionContext
if (handleExceptionIfNeeded(uncaughtException))
return;
// Node handles promises in the return value and throws an uncaught exception on nextTick if it rejects.
// See event_target.js function addCatch in node
if (retval.isObject()) {
auto then = retval.get(lexicalGlobalObject, vm.propertyNames->then);
if (UNLIKELY(scope.exception())) {
auto* exception = scope.exception();
scope.clearException();
event.target()->uncaughtExceptionInEventHandler();
reportException(lexicalGlobalObject, exception);
return;
}
if (then.isCallable()) {
MarkedArgumentBuffer arglist;
arglist.append(JSValue(JSC::jsUndefined()));
arglist.append(JSValue(JSC::JSFunction::create(vm, lexicalGlobalObject, 1, String(), jsFunctionEmitUncaughtExceptionNextTick, ImplementationVisibility::Public, NoIntrinsic))); // err => process.nextTick(() => throw err)
JSC::call(lexicalGlobalObject, then, retval, arglist, "Promise.then is not callable"_s);
if (UNLIKELY(scope.exception())) {
auto* exception = scope.exception();
scope.clearException();
event.target()->uncaughtExceptionInEventHandler();
reportException(lexicalGlobalObject, exception);
return;
}
}
}
if (!m_isAttribute) {
// This is an EventListener and there is therefore no need for any return value handling.
return;

View File

@@ -55,6 +55,7 @@
#include <wtf/GetPtr.h>
#include <wtf/PointerPreparations.h>
#include <wtf/URL.h>
#include "BunProcess.h"
namespace WebCore {
using namespace JSC;
@@ -223,6 +224,23 @@ static inline JSC::EncodedJSValue jsEventTargetPrototypeFunction_addEventListene
EnsureStillAliveScope argument2 = callFrame->argument(2);
auto options = argument2.value().isUndefined() ? false : convert<IDLUnion<IDLDictionary<AddEventListenerOptions>, IDLBoolean>>(*lexicalGlobalObject, argument2.value());
RETURN_IF_EXCEPTION(throwScope, {});
// Emit a warning if listener is null, as it has no effect
if (!listener) {
String warningMessage;
if (argument1.value().isNull()) {
warningMessage = "addEventListener called with null listener, which has no effect."_s;
} else {
warningMessage = "addEventListener called with undefined listener, which has no effect."_s;
}
auto errorInstance = JSC::ErrorInstance::create(vm, lexicalGlobalObject->errorStructure(JSC::ErrorType::Error), warningMessage, JSValue(), nullptr, RuntimeType::TypeNothing, JSC::ErrorType::Error);
errorInstance->putDirect(vm, vm.propertyNames->name, jsString(vm, String("AddEventListenerArgumentTypeWarning"_s)));
JSObject& target = *castedThis;
errorInstance->putDirect(vm, vm.propertyNames->target, &target);
RETURN_IF_EXCEPTION(throwScope, {});
errorInstance->putDirect(vm, vm.propertyNames->type, jsString(vm, WTFMove(type)));
Bun::Process::emitWarningErrorInstance(lexicalGlobalObject, errorInstance);
RETURN_IF_EXCEPTION(throwScope, {});
}
auto result = JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.addEventListenerForBindings(WTFMove(type), WTFMove(listener), WTFMove(options)); }));
RETURN_IF_EXCEPTION(throwScope, {});
vm.writeBarrier(&static_cast<JSObject&>(*castedThis), argument1.value());

View File

@@ -2519,32 +2519,6 @@ pub const Arguments = struct {
}
}
if (buffer_value.?.isString() and args.encoding == .hex) {
var str = try bun.String.fromJS(buffer_value.?, ctx);
defer str.deref();
var utf8_bytes = str.toUTF8(bun.default_allocator);
defer utf8_bytes.deinit();
const input = utf8_bytes.slice();
const len = input.len;
if (len % 2 != 0) {
return ctx.ERR(.INVALID_ARG_VALUE, "'encoding' is invalid for data of length {d}", .{len}).throw();
}
const dest_len = len / 2;
const buf = try bun.default_allocator.alloc(u8, dest_len);
const written = strings.decodeHexToBytes(buf, u8, input) catch
return ctx.ERR(.INVALID_ARG_VALUE, "'encoding' is invalid for data of length {d}", .{len}).throw();
bun.assert(written == dest_len);
const slice = JSC.ZigString.Slice.init(bun.default_allocator, buf);
defer ctx.vm().reportExtraMemory(slice.len);
args.buffer.deinit();
args.buffer = .{ .encoded_slice = slice };
}
return args;
}
};

View File

@@ -2099,7 +2099,7 @@ pub const Expect = struct {
return .undefined;
}
const expected_diff = std.math.pow(f64, 10, -precision) / 2;
const expected_diff = bun.pow(10, -precision) / 2;
const actual_diff = @abs(received - expected);
var pass = actual_diff < expected_diff;
@@ -2318,7 +2318,6 @@ pub const Expect = struct {
result_.?;
const _received_message: ?JSValue = if (result.isObject())
try result.fastGet(globalThis, .message)
else
JSValue.fromCell(try result.toJSString(globalThis));

View File

@@ -2430,10 +2430,15 @@ pub const Command = struct {
\\ <cyan>--help<r> Print this menu
\\ <cyan>-y, --yes<r> Accept all default options
\\ <cyan>-m, --minimal<r> Only initialize type definitions
\\ <cyan>-r, --react<r> Initialize a React project
\\ <cyan>--react=tailwind<r> Initialize a React project with TailwindCSS
\\ <cyan>--react=shadcn<r> Initialize a React project with @shadcn/ui and TailwindCSS
\\
\\<b>Examples:<r>
\\ <b><green>bun init<r>
\\ <b><green>bun init<r> <cyan>--yes<r>
\\ <b><green>bun init<r> <cyan>--react<r>
\\ <b><green>bun init<r> <cyan>--react=tailwind<r> <blue>my-app<r>
;
Output.pretty(intro_text ++ "\n", .{});

View File

@@ -372,6 +372,9 @@ pub const InitCommand = struct {
var auto_yes = false;
var parse_flags = true;
var initialize_in_folder: ?[]const u8 = null;
var template: Template = .blank;
var prev_flag_was_react = false;
for (init_args) |arg_| {
const arg = bun.span(arg_);
if (parse_flags and arg.len > 0 and arg[0] == '-') {
@@ -380,12 +383,27 @@ pub const InitCommand = struct {
Global.exit(0);
} else if (strings.eqlComptime(arg, "-m") or strings.eqlComptime(arg, "--minimal")) {
minimal = true;
prev_flag_was_react = false;
} else if (strings.eqlComptime(arg, "-y") or strings.eqlComptime(arg, "--yes")) {
auto_yes = true;
prev_flag_was_react = false;
} else if (strings.eqlComptime(arg, "--")) {
parse_flags = false;
prev_flag_was_react = false;
} else if (strings.eqlComptime(arg, "--react") or strings.eqlComptime(arg, "-r")) {
template = .react_blank;
prev_flag_was_react = true;
auto_yes = true;
} else if ((template == .react_blank and prev_flag_was_react and strings.eqlComptime(arg, "tailwind") or strings.eqlComptime(arg, "--react=tailwind")) or strings.eqlComptime(arg, "r=tailwind")) {
template = .react_tailwind;
prev_flag_was_react = false;
auto_yes = true;
} else if ((template == .react_blank and prev_flag_was_react and strings.eqlComptime(arg, "shadcn") or strings.eqlComptime(arg, "--react=shadcn")) or strings.eqlComptime(arg, "r=shadcn")) {
template = .react_tailwind_shadcn;
prev_flag_was_react = false;
auto_yes = true;
} else {
// invalid flag; ignore
prev_flag_was_react = false;
}
} else {
if (initialize_in_folder == null) {
@@ -541,8 +559,6 @@ pub const InitCommand = struct {
).data.e_object;
}
var template: Template = .blank;
if (!auto_yes) {
if (!did_load_package_json) {
Output.pretty("\n", .{});

View File

@@ -50,8 +50,9 @@ extern "C"
}
}
void uws_app_get(int ssl, uws_app_t *app, const char *pattern, uws_method_handler handler, void *user_data)
void uws_app_get(int ssl, uws_app_t *app, const char *pattern_ptr, size_t pattern_len, uws_method_handler handler, void *user_data)
{
std::string_view pattern = std::string_view(pattern_ptr, pattern_len);
if (ssl)
{
uWS::SSLApp *uwsApp = (uWS::SSLApp *)app;
@@ -76,9 +77,9 @@ extern "C"
}
}
void uws_app_post(int ssl, uws_app_t *app, const char *pattern, uws_method_handler handler, void *user_data)
void uws_app_post(int ssl, uws_app_t *app, const char *pattern_ptr, size_t pattern_len, uws_method_handler handler, void *user_data)
{
std::string_view pattern = std::string_view(pattern_ptr, pattern_len);
if (ssl)
{
uWS::SSLApp *uwsApp = (uWS::SSLApp *)app;
@@ -103,8 +104,9 @@ extern "C"
}
}
void uws_app_options(int ssl, uws_app_t *app, const char *pattern, uws_method_handler handler, void *user_data)
void uws_app_options(int ssl, uws_app_t *app, const char *pattern_ptr, size_t pattern_len, uws_method_handler handler, void *user_data)
{
std::string_view pattern = std::string_view(pattern_ptr, pattern_len);
if (ssl)
{
uWS::SSLApp *uwsApp = (uWS::SSLApp *)app;
@@ -141,8 +143,9 @@ extern "C"
}
}
void uws_app_delete(int ssl, uws_app_t *app, const char *pattern, uws_method_handler handler, void *user_data)
void uws_app_delete(int ssl, uws_app_t *app, const char *pattern_ptr, size_t pattern_len, uws_method_handler handler, void *user_data)
{
std::string_view pattern = std::string_view(pattern_ptr, pattern_len);
if (ssl)
{
uWS::SSLApp *uwsApp = (uWS::SSLApp *)app;
@@ -167,8 +170,9 @@ extern "C"
}
}
void uws_app_patch(int ssl, uws_app_t *app, const char *pattern, uws_method_handler handler, void *user_data)
void uws_app_patch(int ssl, uws_app_t *app, const char *pattern_ptr, size_t pattern_len, uws_method_handler handler, void *user_data)
{
std::string_view pattern = std::string_view(pattern_ptr, pattern_len);
if (ssl)
{
uWS::SSLApp *uwsApp = (uWS::SSLApp *)app;
@@ -193,8 +197,9 @@ extern "C"
}
}
void uws_app_put(int ssl, uws_app_t *app, const char *pattern, uws_method_handler handler, void *user_data)
void uws_app_put(int ssl, uws_app_t *app, const char *pattern_ptr, size_t pattern_len, uws_method_handler handler, void *user_data)
{
std::string_view pattern = std::string_view(pattern_ptr, pattern_len);
if (ssl)
{
uWS::SSLApp *uwsApp = (uWS::SSLApp *)app;
@@ -221,7 +226,7 @@ extern "C"
void uws_app_head(int ssl, uws_app_t *app, const char *pattern_ptr, size_t pattern_len, uws_method_handler handler, void *user_data)
{
std::string pattern = std::string(pattern_ptr, pattern_len);
std::string_view pattern = std::string_view(pattern_ptr, pattern_len);
if (ssl)
{
uWS::SSLApp *uwsApp = (uWS::SSLApp *)app;
@@ -245,8 +250,9 @@ extern "C"
{ handler((uws_res_t *)res, (uws_req_t *)req, user_data); });
}
}
void uws_app_connect(int ssl, uws_app_t *app, const char *pattern, uws_method_handler handler, void *user_data)
void uws_app_connect(int ssl, uws_app_t *app, const char *pattern_ptr, size_t pattern_len, uws_method_handler handler, void *user_data)
{
std::string_view pattern = std::string_view(pattern_ptr, pattern_len);
if (ssl)
{
uWS::SSLApp *uwsApp = (uWS::SSLApp *)app;
@@ -271,8 +277,9 @@ extern "C"
}
}
void uws_app_trace(int ssl, uws_app_t *app, const char *pattern, uws_method_handler handler, void *user_data)
void uws_app_trace(int ssl, uws_app_t *app, const char *pattern_ptr, size_t pattern_len, uws_method_handler handler, void *user_data)
{
std::string_view pattern = std::string_view(pattern_ptr, pattern_len);
if (ssl)
{
uWS::SSLApp *uwsApp = (uWS::SSLApp *)app;
@@ -312,7 +319,7 @@ extern "C"
void uws_app_any(int ssl, uws_app_t *app, const char *pattern_ptr, size_t pattern_len, uws_method_handler handler, void *user_data)
{
std::string pattern = std::string(pattern_ptr, pattern_len);
std::string_view pattern = std::string_view(pattern_ptr, pattern_len);
if (ssl)
{
uWS::SSLApp *uwsApp = (uWS::SSLApp *)app;

View File

@@ -3384,57 +3384,57 @@ pub fn NewApp(comptime ssl: bool) type {
pub fn get(
app: *ThisApp,
pattern: [:0]const u8,
pattern: []const u8,
comptime UserDataType: type,
user_data: UserDataType,
comptime handler: (fn (UserDataType, *Request, *Response) void),
) void {
uws_app_get(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data);
uws_app_get(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data);
}
pub fn post(
app: *ThisApp,
pattern: [:0]const u8,
pattern: []const u8,
comptime UserDataType: type,
user_data: UserDataType,
comptime handler: (fn (UserDataType, *Request, *Response) void),
) void {
uws_app_post(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data);
uws_app_post(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data);
}
pub fn options(
app: *ThisApp,
pattern: [:0]const u8,
pattern: []const u8,
comptime UserDataType: type,
user_data: UserDataType,
comptime handler: (fn (UserDataType, *Request, *Response) void),
) void {
uws_app_options(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data);
uws_app_options(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data);
}
pub fn delete(
app: *ThisApp,
pattern: [:0]const u8,
pattern: []const u8,
comptime UserDataType: type,
user_data: UserDataType,
comptime handler: (fn (UserDataType, *Request, *Response) void),
) void {
uws_app_delete(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data);
uws_app_delete(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data);
}
pub fn patch(
app: *ThisApp,
pattern: [:0]const u8,
pattern: []const u8,
comptime UserDataType: type,
user_data: UserDataType,
comptime handler: (fn (UserDataType, *Request, *Response) void),
) void {
uws_app_patch(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data);
uws_app_patch(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data);
}
pub fn put(
app: *ThisApp,
pattern: [:0]const u8,
pattern: []const u8,
comptime UserDataType: type,
user_data: UserDataType,
comptime handler: (fn (UserDataType, *Request, *Response) void),
) void {
uws_app_put(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data);
uws_app_put(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data);
}
pub fn head(
app: *ThisApp,
@@ -3447,26 +3447,26 @@ pub fn NewApp(comptime ssl: bool) type {
}
pub fn connect(
app: *ThisApp,
pattern: [:0]const u8,
pattern: []const u8,
comptime UserDataType: type,
user_data: UserDataType,
comptime handler: (fn (UserDataType, *Request, *Response) void),
) void {
uws_app_connect(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data);
uws_app_connect(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data);
}
pub fn trace(
app: *ThisApp,
pattern: [:0]const u8,
pattern: []const u8,
comptime UserDataType: type,
user_data: UserDataType,
comptime handler: (fn (UserDataType, *Request, *Response) void),
) void {
uws_app_trace(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data);
uws_app_trace(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data);
}
pub fn method(
app: *ThisApp,
method_: bun.http.Method,
pattern: [:0]const u8,
pattern: []const u8,
comptime UserDataType: type,
user_data: UserDataType,
comptime handler: (fn (UserDataType, *Request, *Response) void),
@@ -3481,7 +3481,7 @@ pub fn NewApp(comptime ssl: bool) type {
.HEAD => app.head(pattern, UserDataType, user_data, handler),
.CONNECT => app.connect(pattern, UserDataType, user_data, handler),
.TRACE => app.trace(pattern, UserDataType, user_data, handler),
else => @panic("TODO: implement other methods"),
else => {},
}
}
pub fn any(
@@ -3491,6 +3491,7 @@ pub fn NewApp(comptime ssl: bool) type {
user_data: UserDataType,
comptime handler: (fn (UserDataType, *Request, *Response) void),
) void {
uws_app_any(ssl_flag, @as(*uws_app_t, @ptrCast(app)), pattern.ptr, pattern.len, RouteHandler(UserDataType, handler).handle, if (UserDataType == void) null else user_data);
}
pub fn domain(app: *ThisApp, pattern: [:0]const u8) void {
@@ -3996,15 +3997,15 @@ extern fn uws_create_app(ssl: i32, options: us_bun_socket_context_options_t) ?*u
extern fn uws_app_destroy(ssl: i32, app: *uws_app_t) void;
extern fn uws_app_set_flags(ssl: i32, app: *uws_app_t, require_host_header: bool, use_strict_method_validation: bool) void;
extern fn uws_app_set_max_http_header_size(ssl: i32, app: *uws_app_t, max_header_size: u64) void;
extern fn uws_app_get(ssl: i32, app: *uws_app_t, pattern: [*c]const u8, handler: uws_method_handler, user_data: ?*anyopaque) void;
extern fn uws_app_post(ssl: i32, app: *uws_app_t, pattern: [*c]const u8, handler: uws_method_handler, user_data: ?*anyopaque) void;
extern fn uws_app_options(ssl: i32, app: *uws_app_t, pattern: [*c]const u8, handler: uws_method_handler, user_data: ?*anyopaque) void;
extern fn uws_app_delete(ssl: i32, app: *uws_app_t, pattern: [*c]const u8, handler: uws_method_handler, user_data: ?*anyopaque) void;
extern fn uws_app_patch(ssl: i32, app: *uws_app_t, pattern: [*c]const u8, handler: uws_method_handler, user_data: ?*anyopaque) void;
extern fn uws_app_put(ssl: i32, app: *uws_app_t, pattern: [*c]const u8, handler: uws_method_handler, user_data: ?*anyopaque) void;
extern fn uws_app_get(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void;
extern fn uws_app_post(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void;
extern fn uws_app_options(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void;
extern fn uws_app_delete(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void;
extern fn uws_app_patch(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void;
extern fn uws_app_put(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void;
extern fn uws_app_head(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void;
extern fn uws_app_connect(ssl: i32, app: *uws_app_t, pattern: [*c]const u8, handler: uws_method_handler, user_data: ?*anyopaque) void;
extern fn uws_app_trace(ssl: i32, app: *uws_app_t, pattern: [*c]const u8, handler: uws_method_handler, user_data: ?*anyopaque) void;
extern fn uws_app_connect(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void;
extern fn uws_app_trace(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void;
extern fn uws_app_any(ssl: i32, app: *uws_app_t, pattern: [*]const u8, pattern_len: usize, handler: uws_method_handler, user_data: ?*anyopaque) void;
extern fn uws_app_run(ssl: i32, *uws_app_t) void;
extern fn uws_app_domain(ssl: i32, app: *uws_app_t, domain: [*c]const u8) void;

View File

@@ -49,16 +49,17 @@ pub const Method = enum(u8) {
UNSUBSCRIBE = 34,
pub const fromJS = Map.fromJS;
pub const Set = std.enums.EnumSet(Method);
const with_body: std.enums.EnumSet(Method) = brk: {
var values = std.enums.EnumSet(Method).initFull();
const with_body: Set = brk: {
var values = Set.initFull();
values.remove(.HEAD);
values.remove(.TRACE);
break :brk values;
};
const with_request_body: std.enums.EnumSet(Method) = brk: {
var values = std.enums.EnumSet(Method).initFull();
const with_request_body: Set = brk: {
var values = Set.initFull();
values.remove(.GET);
values.remove(.HEAD);
values.remove(.OPTIONS);
@@ -161,6 +162,34 @@ pub const Method = enum(u8) {
pub const toJS = Bun__HTTPMethod__toJS;
const JSC = bun.JSC;
pub const Optional = union(enum) {
any: void,
method: Set,
pub fn contains(this: Optional, other: Optional) bool {
if (this == .any) {
return true;
}
if (other == .any) {
return true;
}
return this.method.intersectWith(other.method).count() > 0;
}
pub fn insert(this: *Optional, method: Method) void {
switch (this.*) {
.any => {},
.method => |*set| {
set.insert(method);
if (set.eql(Set.initFull())) {
this.* = .any;
}
},
}
}
};
};
export fn Bun__HTTPMethod__from(str: [*]const u8, len: usize) i16 {

View File

@@ -703,6 +703,9 @@ declare function $ERR_INVALID_CHAR(name, field?): TypeError;
declare function $ERR_HTTP_INVALID_HEADER_VALUE(value: string, name: string): TypeError;
declare function $ERR_HTTP_HEADERS_SENT(action: string): Error;
declare function $ERR_INVALID_PROTOCOL(proto, expected): TypeError;
declare function $ERR_INVALID_STATE(message: string): Error;
declare function $ERR_INVALID_STATE_TypeError(message: string): TypeError;
declare function $ERR_INVALID_STATE_RangeError(message: string): RangeError;
declare function $ERR_UNESCAPED_CHARACTERS(arg): TypeError;
declare function $ERR_HTTP_INVALID_STATUS_CODE(code): RangeError;
declare function $ERR_UNHANDLED_ERROR(err?): Error;

View File

@@ -33,13 +33,13 @@ export function initializeReadableByteStreamController(this, stream, underlyingB
export function enqueue(this: ReadableByteStreamController, chunk: ArrayBufferView) {
if (!$isReadableByteStreamController(this)) throw $ERR_INVALID_THIS("ReadableByteStreamController");
if ($getByIdDirectPrivate(this, "closeRequested"))
throw new TypeError("ReadableByteStreamController is requested to close");
if ($getByIdDirectPrivate(this, "closeRequested")) throw $ERR_INVALID_STATE_TypeError("Controller is already closed");
if ($getByIdDirectPrivate($getByIdDirectPrivate(this, "controlledReadableStream"), "state") !== $streamReadable)
throw new TypeError("ReadableStream is not readable");
throw $ERR_INVALID_STATE_TypeError("Controller is already closed");
if (!$isObject(chunk) || !ArrayBuffer.$isView(chunk)) throw new TypeError("Provided chunk is not a TypedArray");
if (!$isObject(chunk) || !ArrayBuffer.$isView(chunk))
throw $ERR_INVALID_ARG_TYPE("buffer", "Buffer, TypedArray, or DataView", chunk);
return $readableByteStreamControllerEnqueue(this, chunk);
}
@@ -48,7 +48,7 @@ export function error(this: ReadableByteStreamController, error: any) {
if (!$isReadableByteStreamController(this)) throw $ERR_INVALID_THIS("ReadableByteStreamController");
if ($getByIdDirectPrivate($getByIdDirectPrivate(this, "controlledReadableStream"), "state") !== $streamReadable)
throw new TypeError("ReadableStream is not readable");
throw $ERR_INVALID_STATE_TypeError("Controller is already closed");
$readableByteStreamControllerError(this, error);
}
@@ -59,7 +59,7 @@ export function close(this: ReadableByteStreamController) {
if ($getByIdDirectPrivate(this, "closeRequested")) throw new TypeError("Close has already been requested");
if ($getByIdDirectPrivate($getByIdDirectPrivate(this, "controlledReadableStream"), "state") !== $streamReadable)
throw new TypeError("ReadableStream is not readable");
throw $ERR_INVALID_STATE_TypeError("Controller is already closed");
$readableByteStreamControllerClose(this);
}

View File

@@ -96,7 +96,9 @@ export function isReadableByteStreamController(controller) {
export function isReadableStreamBYOBRequest(byobRequest) {
// Same test mechanism as in isReadableStreamDefaultController (ReadableStreamInternals.js).
// See corresponding function for explanations.
return $isObject(byobRequest) && !!$getByIdDirectPrivate(byobRequest, "associatedReadableByteStreamController");
return (
$isObject(byobRequest) && $getByIdDirectPrivate(byobRequest, "associatedReadableByteStreamController") !== undefined
);
}
export function isReadableStreamBYOBReader(reader) {
@@ -369,7 +371,7 @@ export function readableByteStreamControllerRespondWithNewView(controller, view)
if (firstDescriptor!.byteOffset + firstDescriptor!.bytesFilled !== view.byteOffset)
throw new RangeError("Invalid value for view.byteOffset");
if (firstDescriptor!.byteLength !== view.byteLength) throw new RangeError("Invalid value for view.byteLength");
if (firstDescriptor!.byteLength < view.byteLength) throw $ERR_INVALID_ARG_VALUE("view", view);
firstDescriptor!.buffer = view.buffer;
$readableByteStreamControllerRespondInternal(controller, view.byteLength);
@@ -536,7 +538,7 @@ export function readableByteStreamControllerShiftPendingDescriptor(controller):
export function readableByteStreamControllerInvalidateBYOBRequest(controller) {
if ($getByIdDirectPrivate(controller, "byobRequest") === undefined) return;
const byobRequest = $getByIdDirectPrivate(controller, "byobRequest");
$putByIdDirectPrivate(byobRequest, "associatedReadableByteStreamController", undefined);
$putByIdDirectPrivate(byobRequest, "associatedReadableByteStreamController", null);
$putByIdDirectPrivate(byobRequest, "view", undefined);
$putByIdDirectPrivate(controller, "byobRequest", undefined);
}

View File

@@ -38,7 +38,7 @@ export function cancel(this, reason) {
if (!$isReadableStreamBYOBReader(this)) return Promise.$reject($ERR_INVALID_THIS("ReadableStreamBYOBReader"));
if (!$getByIdDirectPrivate(this, "ownerReadableStream"))
return Promise.$reject($makeTypeError("cancel() called on a reader owned by no readable stream"));
return Promise.$reject($ERR_INVALID_STATE_TypeError("The reader is not attached to a stream"));
return $readableStreamReaderGenericCancel(this, reason);
}
@@ -47,11 +47,12 @@ export function read(this, view: DataView) {
if (!$isReadableStreamBYOBReader(this)) return Promise.$reject($ERR_INVALID_THIS("ReadableStreamBYOBReader"));
if (!$getByIdDirectPrivate(this, "ownerReadableStream"))
return Promise.$reject($makeTypeError("read() called on a reader owned by no readable stream"));
return Promise.$reject($ERR_INVALID_STATE_TypeError("The reader is not attached to a stream"));
if (!$isObject(view)) return Promise.$reject($makeTypeError("Provided view is not an object"));
if (!$isObject(view)) return Promise.$reject($ERR_INVALID_ARG_TYPE("view", "Buffer, TypedArray, or DataView", view));
if (!ArrayBuffer.$isView(view)) return Promise.$reject($makeTypeError("Provided view is not an ArrayBufferView"));
if (!ArrayBuffer.$isView(view))
return Promise.$reject($ERR_INVALID_ARG_TYPE("view", "Buffer, TypedArray, or DataView", view));
if (view.byteLength === 0) return Promise.$reject($makeTypeError("Provided view cannot have a 0 byteLength"));

View File

@@ -33,8 +33,8 @@ export function initializeReadableStreamBYOBRequest(this, controller, view) {
export function respond(this, bytesWritten) {
if (!$isReadableStreamBYOBRequest(this)) throw $ERR_INVALID_THIS("ReadableStreamBYOBRequest");
if ($getByIdDirectPrivate(this, "associatedReadableByteStreamController") === undefined)
throw new TypeError("ReadableStreamBYOBRequest.associatedReadableByteStreamController is undefined");
if ($getByIdDirectPrivate(this, "associatedReadableByteStreamController") == null)
throw $ERR_INVALID_STATE_TypeError("This BYOB request has been invalidated");
return $readableByteStreamControllerRespond(
$getByIdDirectPrivate(this, "associatedReadableByteStreamController"),
@@ -45,12 +45,12 @@ export function respond(this, bytesWritten) {
export function respondWithNewView(this, view) {
if (!$isReadableStreamBYOBRequest(this)) throw $ERR_INVALID_THIS("ReadableStreamBYOBRequest");
if ($getByIdDirectPrivate(this, "associatedReadableByteStreamController") === undefined)
throw new TypeError("ReadableStreamBYOBRequest.associatedReadableByteStreamController is undefined");
if ($getByIdDirectPrivate(this, "associatedReadableByteStreamController") == null)
throw $ERR_INVALID_STATE_TypeError("This BYOB request has been invalidated");
if (!$isObject(view)) throw new TypeError("Provided view is not an object");
if (!$isObject(view)) throw $ERR_INVALID_ARG_TYPE("view", "Buffer, TypedArray, or DataView", view);
if (!ArrayBuffer.$isView(view)) throw new TypeError("Provided view is not an ArrayBufferView");
if (!ArrayBuffer.$isView(view)) throw $ERR_INVALID_ARG_TYPE("view", "Buffer, TypedArray, or DataView", view);
return $readableByteStreamControllerRespondWithNewView(
$getByIdDirectPrivate(this, "associatedReadableByteStreamController"),
@@ -60,7 +60,7 @@ export function respondWithNewView(this, view) {
$getter;
export function view(this) {
if (!$isReadableStreamBYOBRequest(this)) throw $makeGetterTypeError("ReadableStreamBYOBRequest", "view");
if (!$isReadableStreamBYOBRequest(this)) throw $ERR_INVALID_THIS("ReadableStreamBYOBRequest");
return $getByIdDirectPrivate(this, "view");
}

View File

@@ -34,7 +34,7 @@ export function enqueue(this, chunk) {
if (!$isReadableStreamDefaultController(this)) throw $ERR_INVALID_THIS("ReadableStreamDefaultController");
if (!$readableStreamDefaultControllerCanCloseOrEnqueue(this)) {
throw $ERR_INVALID_STATE("ReadableStreamDefaultController is not in a state where chunk can be enqueued");
throw $ERR_INVALID_STATE_TypeError("Controller is already closed");
}
return $readableStreamDefaultControllerEnqueue(this, chunk);
@@ -49,7 +49,7 @@ export function close(this) {
if (!$isReadableStreamDefaultController(this)) throw $ERR_INVALID_THIS("ReadableStreamDefaultController");
if (!$readableStreamDefaultControllerCanCloseOrEnqueue(this))
throw new TypeError("ReadableStreamDefaultController is not in a state where it can be closed");
throw $ERR_INVALID_STATE_TypeError("Controller is already closed");
$readableStreamDefaultControllerClose(this);
}

View File

@@ -37,7 +37,7 @@ export function cancel(this, reason) {
if (!$isReadableStreamDefaultReader(this)) return Promise.$reject($ERR_INVALID_THIS("ReadableStreamDefaultReader"));
if (!$getByIdDirectPrivate(this, "ownerReadableStream"))
return Promise.$reject(new TypeError("cancel() called on a reader owned by no readable stream"));
return Promise.$reject($ERR_INVALID_STATE_TypeError("The reader is not attached to a stream"));
return $readableStreamReaderGenericCancel(this, reason);
}
@@ -47,7 +47,7 @@ export function readMany(this: ReadableStreamDefaultReader): ReadableStreamDefau
throw new TypeError("ReadableStreamDefaultReader.readMany() should not be called directly");
const stream = $getByIdDirectPrivate(this, "ownerReadableStream");
if (!stream) throw new TypeError("readMany() called on a reader owned by no readable stream");
if (!stream) throw $ERR_INVALID_STATE_TypeError("The reader is not attached to a stream");
const state = $getByIdDirectPrivate(stream, "state");
stream.$disturbed = true;
@@ -172,7 +172,7 @@ export function readMany(this: ReadableStreamDefaultReader): ReadableStreamDefau
export function read(this) {
if (!$isReadableStreamDefaultReader(this)) return Promise.$reject($ERR_INVALID_THIS("ReadableStreamDefaultReader"));
if (!$getByIdDirectPrivate(this, "ownerReadableStream"))
return Promise.$reject(new TypeError("read() called on a reader owned by no readable stream"));
return Promise.$reject($ERR_INVALID_STATE_TypeError("The reader is not attached to a stream"));
return $readableStreamDefaultReaderRead(this);
}

View File

@@ -676,11 +676,11 @@ export function isReadableStreamDefaultController(controller) {
// However, since it is a private slot, it cannot be checked using hasOwnProperty().
// underlyingSource is obtained in ReadableStream constructor: if undefined, it is set
// to an empty object. Therefore, following test is ok.
return $isObject(controller) && !!$getByIdDirectPrivate(controller, "underlyingSource");
return $isObject(controller) && $getByIdDirectPrivate(controller, "underlyingSource") !== undefined;
}
export function readDirectStream(stream, sink, underlyingSource) {
$putByIdDirectPrivate(stream, "underlyingSource", undefined);
$putByIdDirectPrivate(stream, "underlyingSource", null); // doing this causes isReadableStreamDefaultController to return false
$putByIdDirectPrivate(stream, "start", undefined);
function close(stream, reason) {
const cancelFn = underlyingSource?.cancel;
@@ -849,13 +849,12 @@ export async function readStreamIntoSink(stream: ReadableStream, sink, isNative)
var readableStreamController = $getByIdDirectPrivate(stream, "readableStreamController");
if (readableStreamController) {
if ($getByIdDirectPrivate(readableStreamController, "underlyingSource"))
$putByIdDirectPrivate(readableStreamController, "underlyingSource", undefined);
$putByIdDirectPrivate(readableStreamController, "underlyingSource", null);
if ($getByIdDirectPrivate(readableStreamController, "controlledReadableStream"))
$putByIdDirectPrivate(readableStreamController, "controlledReadableStream", undefined);
$putByIdDirectPrivate(readableStreamController, "controlledReadableStream", null);
$putByIdDirectPrivate(stream, "readableStreamController", null);
if ($getByIdDirectPrivate(stream, "underlyingSource"))
$putByIdDirectPrivate(stream, "underlyingSource", undefined);
if ($getByIdDirectPrivate(stream, "underlyingSource")) $putByIdDirectPrivate(stream, "underlyingSource", null);
readableStreamController = undefined;
}
@@ -1264,7 +1263,7 @@ export function initializeTextStream(underlyingSource, highWaterMark: number) {
};
$putByIdDirectPrivate(this, "readableStreamController", controller);
$putByIdDirectPrivate(this, "underlyingSource", undefined);
$putByIdDirectPrivate(this, "underlyingSource", null);
$putByIdDirectPrivate(this, "start", undefined);
return closingPromise;
}
@@ -1324,7 +1323,7 @@ export function initializeArrayStream(underlyingSource, _highWaterMark: number)
};
$putByIdDirectPrivate(this, "readableStreamController", controller);
$putByIdDirectPrivate(this, "underlyingSource", undefined);
$putByIdDirectPrivate(this, "underlyingSource", null);
$putByIdDirectPrivate(this, "start", undefined);
return closingPromise;
}
@@ -1360,7 +1359,7 @@ export function initializeArrayBufferStream(underlyingSource, highWaterMark: num
};
$putByIdDirectPrivate(this, "readableStreamController", controller);
$putByIdDirectPrivate(this, "underlyingSource", undefined);
$putByIdDirectPrivate(this, "underlyingSource", null);
$putByIdDirectPrivate(this, "start", undefined);
}
@@ -2118,7 +2117,7 @@ export function readableStreamToArrayBufferDirect(
asUint8Array: boolean,
) {
var sink = new Bun.ArrayBufferSink();
$putByIdDirectPrivate(stream, "underlyingSource", undefined);
$putByIdDirectPrivate(stream, "underlyingSource", null);
var highWaterMark = $getByIdDirectPrivate(stream, "highWaterMark");
sink.start({ highWaterMark, asUint8Array });
var capability = $newPromiseCapability(Promise);

View File

@@ -4,7 +4,6 @@ const EventEmitter = require("node:events");
const fs = $zig("node_fs_binding.zig", "createBinding") as $ZigGeneratedClasses.NodeJSFS;
const { glob } = require("internal/fs/glob");
const constants = $processBindingConstants.fs;
const { validateInteger, validateEncoding } = require("internal/validators");
var PromisePrototypeFinally = Promise.prototype.finally; //TODO
var SymbolAsyncDispose = Symbol.asyncDispose;
@@ -23,6 +22,8 @@ const kDeserialize = Symbol("kDeserialize");
const kEmptyObject = ObjectFreeze({ __proto__: null });
const kFlag = Symbol("kFlag");
const { validateInteger } = require("internal/validators");
function watch(
filename: string | Buffer | URL,
options: { encoding?: BufferEncoding; persistent?: boolean; recursive?: boolean; signal?: AbortSignal } = {},
@@ -417,13 +418,6 @@ function asyncWrap(fn: any, name: string) {
}
async stat(options) {
if (!(this instanceof FileHandle)) {
const err = new TypeError("handle must be an instance of FileHandle");
err.code = "ERR_INTERNAL_ASSERTION";
err.name = "AssertionError";
throw err;
}
const fd = this[kFd];
throwEBADFIfNecessary("fstat", fd);
@@ -474,13 +468,9 @@ function asyncWrap(fn: any, name: string) {
if (offset == null) {
offset = 0;
}
if (typeof length !== "number") length = buffer.byteLength - offset;
if (typeof position !== "number") position = null;
} else {
validateEncoding(buffer, length); // length is the encoding in this overload
}
try {
this[kRef]();
return { buffer, bytesWritten: await write(fd, buffer, offset, length, position) };

View File

@@ -2489,6 +2489,7 @@ pub const E = struct {
}
pub fn eqlComptime(s: *const String, comptime value: []const u8) bool {
bun.assert(s.next == null);
return if (s.isUTF8())
strings.eqlComptime(s.data, value)
else
@@ -6209,6 +6210,7 @@ pub const Expr = struct {
},
.e_number => |r| {
if (comptime kind == .loose) {
l.resolveRopeIfNeeded(p.allocator);
if (r.value == 0 and (l.isBlank() or l.eqlComptime("0"))) {
return Equality.true;
}

View File

@@ -7461,7 +7461,7 @@ fn NewParser_(
.bin_pow => {
if (p.should_fold_typescript_constant_expressions) {
if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| {
return p.newExpr(E.Number{ .value = std.math.pow(f64, vals[0], vals[1]) }, v.loc);
return p.newExpr(E.Number{ .value = bun.pow(vals[0], vals[1]) }, v.loc);
}
}
},

View File

@@ -194,3 +194,12 @@ describe("with statement", () => {
expect(exitCode).toBe(0);
});
});
test("math.pow", () => {
function func_result(foo) {
return 10 ** (foo / 20);
}
expect(func_result(-1) + "").toEqual("0.8912509381337456");
expect(10 ** (-1 / 20) + "").toEqual("0.8912509381337456");
});

View File

@@ -0,0 +1,4 @@
test("constant fold ==", () => {
// @ts-expect-error
expect("0" + "1" == 0).toBe(false);
});

View File

@@ -218,3 +218,78 @@ test("bun init twice", async () => {
`"my edited tsconfig.json"`,
);
});
test("bun init --react works", () => {
const temp = tmpdirSync();
const out = Bun.spawnSync({
cmd: [bunExe(), "init", "--react"],
cwd: temp,
stdio: ["ignore", "inherit", "inherit"],
env: bunEnv,
});
expect(out.signalCode).toBeUndefined();
expect(out.exitCode).toBe(0);
const pkg = JSON.parse(fs.readFileSync(path.join(temp, "package.json"), "utf8"));
expect(pkg).toHaveProperty("dependencies.react");
expect(pkg).toHaveProperty("dependencies.react-dom");
expect(pkg).toHaveProperty("devDependencies.@types/react");
expect(pkg).toHaveProperty("devDependencies.@types/react-dom");
expect(fs.existsSync(path.join(temp, "src"))).toBe(true);
expect(fs.existsSync(path.join(temp, "src/index.tsx"))).toBe(true);
expect(fs.existsSync(path.join(temp, "tsconfig.json"))).toBe(true);
}, 30_000);
test("bun init --react=tailwind works", () => {
const temp = tmpdirSync();
const out = Bun.spawnSync({
cmd: [bunExe(), "init", "--react=tailwind"],
cwd: temp,
stdio: ["ignore", "inherit", "inherit"],
env: bunEnv,
});
expect(out.signalCode).toBeUndefined();
expect(out.exitCode).toBe(0);
const pkg = JSON.parse(fs.readFileSync(path.join(temp, "package.json"), "utf8"));
expect(pkg).toHaveProperty("dependencies.react");
expect(pkg).toHaveProperty("dependencies.react-dom");
expect(pkg).toHaveProperty("devDependencies.@types/react");
expect(pkg).toHaveProperty("devDependencies.@types/react-dom");
expect(pkg).toHaveProperty("dependencies.bun-plugin-tailwind");
expect(fs.existsSync(path.join(temp, "src"))).toBe(true);
expect(fs.existsSync(path.join(temp, "src/index.tsx"))).toBe(true);
}, 30_000);
test("bun init --react=shadcn works", () => {
const temp = tmpdirSync();
const out = Bun.spawnSync({
cmd: [bunExe(), "init", "--react=shadcn"],
cwd: temp,
stdio: ["ignore", "inherit", "inherit"],
env: bunEnv,
});
expect(out.signalCode).toBeUndefined();
expect(out.exitCode).toBe(0);
const pkg = JSON.parse(fs.readFileSync(path.join(temp, "package.json"), "utf8"));
expect(pkg).toHaveProperty("dependencies.react");
expect(pkg).toHaveProperty("dependencies.react-dom");
expect(pkg).toHaveProperty("dependencies.@radix-ui/react-slot");
expect(pkg).toHaveProperty("dependencies.class-variance-authority");
expect(pkg).toHaveProperty("dependencies.clsx");
expect(pkg).toHaveProperty("dependencies.bun-plugin-tailwind");
expect(fs.existsSync(path.join(temp, "src"))).toBe(true);
expect(fs.existsSync(path.join(temp, "src/index.tsx"))).toBe(true);
expect(fs.existsSync(path.join(temp, "src/components"))).toBe(true);
expect(fs.existsSync(path.join(temp, "src/components/ui"))).toBe(true);
}, 30_000);

View File

@@ -34,7 +34,7 @@ const words: Record<string, { reason: string; limit?: number; regex?: boolean }>
[String.raw`: [a-zA-Z0-9_\.\*\?\[\]\(\)]+ = undefined,`]: { reason: "Do not default a struct field to undefined", limit: 240, regex: true },
"usingnamespace": { reason: "Zig 0.15 will remove `usingnamespace`" },
"catch unreachable": { reason: "For out-of-memory, prefer 'catch bun.outOfMemory()'", limit: 1850 },
"catch unreachable": { reason: "For out-of-memory, prefer 'catch bun.outOfMemory()'", limit: 1849 },
"std.fs.Dir": { reason: "Prefer bun.sys + bun.FD instead of std.fs", limit: 180 },
"std.fs.cwd": { reason: "Prefer bun.FD.cwd()", limit: 103 },

View File

@@ -772,6 +772,55 @@ test("serve html with JSX runtime in production mode", async () => {
expect(js).toContain(`("h1",{children:"Hello from JSX"})`);
});
test("you can have HTML imports apply to only specific methods outside of the dev server", async () => {
const dir = join(import.meta.dir, "jsx-runtime");
const { default: html } = await import(join(dir, "index.html"));
using server = Bun.serve({
port: 0,
development: false,
static: {
"/boop": html,
"/": {
GET: html,
POST: html,
async PATCH() {
return new Response("PATCH!", { status: 200 });
},
},
},
fetch(req) {
return new Response("Not found", { status: 404 });
},
});
const response = await fetch(server.url);
expect(response.status).toBe(200);
const htmlText = await response.text();
const jsSrc = htmlText.match(/<script type="module" crossorigin src="([^"]+)"/)?.[1]!;
const js = await (await fetch(new URL(jsSrc, server.url))).text();
// jsxDEV looks like this:
// jsxDEV("button", {
// children: "Click me"
// }, undefined, false, undefined, this)
expect(js).toContain(`("h1",{children:"Hello from JSX"})`);
const response2 = await fetch(server.url, {
method: "POST",
});
expect(response2.status).toBe(200);
expect(await response2.text()).toEqual(htmlText);
const response3 = await fetch(server.url, {
method: "PATCH",
});
expect(response3.status).toBe(200);
expect(await response3.text()).toBe("PATCH!");
expect(await (await fetch(server.url + "/boop")).text()).toEqual(htmlText);
expect(await (await fetch(server.url + "/boop", { method: "POST" })).text()).toEqual(htmlText);
expect(await (await fetch(server.url + "/boop", { method: "PATCH" })).text()).toBe(htmlText);
});
for (let development of [true, false, { hmr: false }]) {
test(`mixed api and html routes with non-* false routes`, async () => {
const dir = join(import.meta.dir, "jsx-runtime");

View File

@@ -356,6 +356,80 @@ describe("route reloading", () => {
expect(await res.text()).toBe("updated");
});
it("handles different HTTP methods on reload", async () => {
// Reload with routes for different HTTP methods
server.reload({
fetch: () => new Response("fallback"),
routes: {
"/method-test": {
GET: () => new Response("GET response"),
POST: () => new Response("POST response"),
PUT: () => new Response("PUT response"),
DELETE: () => new Response("DELETE response"),
OPTIONS: () => new Response("OPTIONS response"),
},
},
} as ServeOptions);
// Test GET request
let res = await fetch(new URL(`/method-test`, server.url).href);
expect(res.status).toBe(200);
expect(await res.text()).toBe("GET response");
// Test POST request
res = await fetch(new URL(`/method-test`, server.url).href, { method: "POST" });
expect(res.status).toBe(200);
expect(await res.text()).toBe("POST response");
// Test PUT request
res = await fetch(new URL(`/method-test`, server.url).href, { method: "PUT" });
expect(res.status).toBe(200);
expect(await res.text()).toBe("PUT response");
// Test DELETE request
res = await fetch(new URL(`/method-test`, server.url).href, { method: "DELETE" });
expect(res.status).toBe(200);
expect(await res.text()).toBe("DELETE response");
// Test OPTIONS request
res = await fetch(new URL(`/method-test`, server.url).href, { method: "OPTIONS" });
expect(res.status).toBe(200);
expect(await res.text()).toBe("OPTIONS response");
server.reload({
fetch: () => new Response("fallback"),
routes: {
"/method-test": {
OPTIONS: new Response("OPTIONS response 2"),
GET: () => new Response("GET response 2"),
POST: () => new Response("POST response 2"),
PUT: () => new Response("PUT response 2"),
DELETE: () => new Response("DELETE response 2"),
},
},
} as ServeOptions);
res = await fetch(new URL(`/method-test`, server.url).href, { method: "GET" });
expect(res.status).toBe(200);
expect(await res.text()).toBe("GET response 2");
res = await fetch(new URL(`/method-test`, server.url).href, { method: "POST" });
expect(res.status).toBe(200);
expect(await res.text()).toBe("POST response 2");
res = await fetch(new URL(`/method-test`, server.url).href, { method: "PUT" });
expect(res.status).toBe(200);
expect(await res.text()).toBe("PUT response 2");
res = await fetch(new URL(`/method-test`, server.url).href, { method: "DELETE" });
expect(res.status).toBe(200);
expect(await res.text()).toBe("DELETE response 2");
res = await fetch(new URL(`/method-test`, server.url).href, { method: "OPTIONS" });
expect(res.status).toBe(200);
expect(await res.text()).toBe("OPTIONS response 2");
});
it("handles removing routes on reload", async () => {
// Reload with empty routes
server.reload({
@@ -549,3 +623,97 @@ it("don't crash on server.fetch()", async () => {
expect(server.fetch("/test")).rejects.toThrow("fetch() requires the server to have a fetch handler");
});
it("route precedence for any routes", async () => {
await using server = Bun.serve({
port: 0,
routes: {
"/test": () => new Response("test"),
"/test/GET": () => new Response("GET /test/GET"),
"/*": () => new Response("/*"),
},
fetch(req) {
return new Response("fallback");
},
});
expect(await fetch(new URL("/test", server.url)).then(res => res.text())).toBe("test");
expect(await fetch(new URL("/test/GET", server.url)).then(res => res.text())).toBe("GET /test/GET");
});
it("route precedence for method-specific routes", async () => {
await using server = Bun.serve({
port: 0,
routes: {
"/test": {
GET: () => new Response("GET /test"),
POST: () => new Response("POST /test"),
},
"/test/POST": {
POST: () => new Response("POST /test/POST"),
},
"/test/GET": {
GET: () => new Response("GET /test/GET"),
},
"/*": () => new Response("/*"),
},
fetch(req) {
return new Response("fallback");
},
});
expect(await fetch(new URL("/test", server.url), { method: "GET" }).then(res => res.text())).toBe("GET /test");
expect(await fetch(new URL("/test/GET", server.url), { method: "GET" }).then(res => res.text())).toBe(
"GET /test/GET",
);
expect(await fetch(new URL("/test/POST", server.url), { method: "POST" }).then(res => res.text())).toBe(
"POST /test/POST",
);
});
it("route precedence for mix of method-specific routes and any routes", async () => {
await using server = Bun.serve({
port: 0,
routes: {
"/test": {
GET: () => new Response("GET /test"),
POST: () => new Response("POST /test"),
},
"/test/POST": {
POST: () => new Response("POST /test/POST"),
},
"/test/GET": {
GET: () => new Response("GET /test/GET"),
},
"/test/ANY": () => new Response("ANY /test/ANY"),
"/test/ANY/POST": {
POST: () => new Response("POST /test/ANY/POST"),
},
"/*": {
GET: () => new Response("GET /*"),
POST: () => new Response("POST /*"),
},
},
fetch(req) {
return new Response("fallback");
},
});
expect(await fetch(new URL("/test", server.url), { method: "GET" }).then(res => res.text())).toBe("GET /test");
expect(await fetch(new URL("/test/GET", server.url), { method: "GET" }).then(res => res.text())).toBe(
"GET /test/GET",
);
expect(await fetch(new URL("/test/POST", server.url), { method: "POST" }).then(res => res.text())).toBe(
"POST /test/POST",
);
expect(await fetch(new URL("/test/ANY", server.url), { method: "GET" }).then(res => res.text())).toBe(
"ANY /test/ANY",
);
expect(await fetch(new URL("/test/ANY/POST", server.url), { method: "POST" }).then(res => res.text())).toBe(
"POST /test/ANY/POST",
);
expect(await fetch(new URL("/test/ANY/POST", server.url), { method: "GET" }).then(res => res.text())).toBe("GET /*");
expect(await fetch(new URL("/test/ANY/POST", server.url), { method: "POST" }).then(res => res.text())).toBe(
"POST /test/ANY/POST",
);
});

View File

@@ -0,0 +1,768 @@
// Flags: --expose-internals --no-warnings --expose-gc
'use strict';
const common = require('../common');
let defineEventHandler;
let kWeakHandler;
if (typeof Bun === "undefined") {
({
defineEventHandler,
kWeakHandler,
} = require('internal/event_target'));
}
const {
ok,
deepStrictEqual,
strictEqual,
throws,
} = require('assert');
const { once } = require('events');
const { inspect } = require('util');
const { setTimeout: delay } = require('timers/promises');
// The globals are defined.
ok(Event);
ok(EventTarget);
// The warning event has special behavior regarding attaching listeners
let lastWarning;
process.on('warning', (e) => {
lastWarning = e;
});
// Utility promise for parts of the test that need to wait for eachother -
// Namely tests for warning events
/* eslint-disable no-unused-vars */
let asyncTest = Promise.resolve();
// First, test Event
{
const ev = new Event('foo');
strictEqual(ev.type, 'foo');
strictEqual(ev.cancelable, false);
strictEqual(ev.defaultPrevented, false);
strictEqual(typeof ev.timeStamp, 'number');
// Compatibility properties with the DOM
deepStrictEqual(ev.composedPath(), []);
strictEqual(ev.returnValue, true);
strictEqual(ev.bubbles, false);
strictEqual(ev.composed, false);
strictEqual(ev.isTrusted, false);
strictEqual(ev.eventPhase, 0);
strictEqual(ev.cancelBubble, false);
// Not cancelable
ev.preventDefault();
strictEqual(ev.defaultPrevented, false);
}
{
[
'foo',
1,
false,
].forEach((i) => (
throws(() => new Event('foo', i), {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "options" argument must be of type object.' +
common.invalidArgTypeHelper(i),
})
));
}
{
const ev = new Event('foo');
strictEqual(ev.cancelBubble, false);
ev.cancelBubble = true;
strictEqual(ev.cancelBubble, true);
}
{
const ev = new Event('foo');
strictEqual(ev.cancelBubble, false);
ev.stopPropagation();
strictEqual(ev.cancelBubble, true);
}
{
const ev = new Event('foo');
strictEqual(ev.cancelBubble, false);
ev.cancelBubble = 'some-truthy-value';
strictEqual(ev.cancelBubble, true);
}
{
// No argument behavior - throw TypeError
throws(() => {
new Event();
}, TypeError);
// Too many arguments passed behavior - ignore additional arguments
const ev = new Event('foo', {}, {});
strictEqual(ev.type, 'foo');
}
{
const ev = new Event('foo');
strictEqual(ev.cancelBubble, false);
ev.cancelBubble = true;
strictEqual(ev.cancelBubble, true);
}
{
const ev = new Event('foo');
strictEqual(ev.cancelBubble, false);
ev.stopPropagation();
strictEqual(ev.cancelBubble, true);
}
{
const ev = new Event('foo');
strictEqual(ev.cancelBubble, false);
ev.cancelBubble = 'some-truthy-value';
strictEqual(ev.cancelBubble, true);
}
{
const ev = new Event('foo', { cancelable: true });
strictEqual(ev.type, 'foo');
strictEqual(ev.cancelable, true);
strictEqual(ev.defaultPrevented, false);
ev.preventDefault();
strictEqual(ev.defaultPrevented, true);
throws(() => new Event(Symbol()), TypeError);
}
{
const ev = new Event('foo');
strictEqual(ev.isTrusted, false);
}
{
const eventTarget = new EventTarget();
const ev1 = common.mustCall(function(event) {
strictEqual(event.type, 'foo');
strictEqual(this, eventTarget);
strictEqual(event.eventPhase, 2);
}, 2);
const ev2 = {
handleEvent: common.mustCall(function(event) {
strictEqual(event.type, 'foo');
strictEqual(this, ev2);
}),
};
eventTarget.addEventListener('foo', ev1);
eventTarget.addEventListener('foo', ev2, { once: true });
ok(eventTarget.dispatchEvent(new Event('foo')));
eventTarget.dispatchEvent(new Event('foo'));
eventTarget.removeEventListener('foo', ev1);
eventTarget.dispatchEvent(new Event('foo'));
}
{
// event subclassing
const SubEvent = class extends Event {};
const ev = new SubEvent('foo');
const eventTarget = new EventTarget();
const fn = common.mustCall((event) => strictEqual(event, ev));
eventTarget.addEventListener('foo', fn, { once: true });
eventTarget.dispatchEvent(ev);
}
{
// Same event dispatched multiple times.
const event = new Event('foo');
const eventTarget1 = new EventTarget();
const eventTarget2 = new EventTarget();
eventTarget1.addEventListener('foo', common.mustCall((event) => {
strictEqual(event.eventPhase, Event.AT_TARGET);
strictEqual(event.target, eventTarget1);
deepStrictEqual(event.composedPath(), [eventTarget1]);
}));
eventTarget2.addEventListener('foo', common.mustCall((event) => {
strictEqual(event.eventPhase, Event.AT_TARGET);
strictEqual(event.target, eventTarget2);
deepStrictEqual(event.composedPath(), [eventTarget2]);
}));
eventTarget1.dispatchEvent(event);
strictEqual(event.eventPhase, Event.NONE);
strictEqual(event.target, eventTarget1);
deepStrictEqual(event.composedPath(), []);
eventTarget2.dispatchEvent(event);
strictEqual(event.eventPhase, Event.NONE);
strictEqual(event.target, eventTarget2);
deepStrictEqual(event.composedPath(), []);
}
{
// Same event dispatched multiple times, without listeners added.
const event = new Event('foo');
const eventTarget1 = new EventTarget();
const eventTarget2 = new EventTarget();
eventTarget1.dispatchEvent(event);
strictEqual(event.eventPhase, Event.NONE);
strictEqual(event.target, eventTarget1);
deepStrictEqual(event.composedPath(), []);
eventTarget2.dispatchEvent(event);
strictEqual(event.eventPhase, Event.NONE);
strictEqual(event.target, eventTarget2);
deepStrictEqual(event.composedPath(), []);
}
{
const eventTarget = new EventTarget();
const event = new Event('foo', { cancelable: true });
eventTarget.addEventListener('foo', (event) => event.preventDefault());
ok(!eventTarget.dispatchEvent(event));
}
{
// Adding event listeners with a boolean useCapture
const eventTarget = new EventTarget();
const event = new Event('foo');
const fn = common.mustCall((event) => strictEqual(event.type, 'foo'));
eventTarget.addEventListener('foo', fn, false);
eventTarget.dispatchEvent(event);
}
{
// The `options` argument can be `null`.
const eventTarget = new EventTarget();
const event = new Event('foo');
const fn = common.mustCall((event) => strictEqual(event.type, 'foo'));
eventTarget.addEventListener('foo', fn, null);
eventTarget.dispatchEvent(event);
}
{
const target = new EventTarget();
const listener = {};
// AddEventListener should not require handleEvent to be
// defined on an EventListener.
target.addEventListener('foo', listener);
listener.handleEvent = common.mustCall(function(event) {
strictEqual(event.type, 'foo');
strictEqual(this, listener);
});
target.dispatchEvent(new Event('foo'));
}
{
const target = new EventTarget();
const listener = {};
// do not throw
target.removeEventListener('foo', listener);
target.addEventListener('foo', listener);
target.removeEventListener('foo', listener);
listener.handleEvent = common.mustNotCall();
target.dispatchEvent(new Event('foo'));
}
{
const uncaughtException = common.mustCall((err, origin) => {
strictEqual(err.message, 'boom');
strictEqual(origin, 'uncaughtException');
}, 4);
// Make sure that we no longer call 'error' on error.
process.on('error', common.mustNotCall());
// Don't call rejection even for async handlers.
process.on('unhandledRejection', common.mustNotCall());
process.on('uncaughtException', uncaughtException);
const eventTarget = new EventTarget();
const ev1 = async () => { throw new Error('boom'); };
const ev2 = () => { throw new Error('boom'); };
const ev3 = { handleEvent() { throw new Error('boom'); } };
const ev4 = { async handleEvent() { throw new Error('boom'); } };
// Errors in a handler won't stop calling the others.
eventTarget.addEventListener('foo', ev1, { once: true });
eventTarget.addEventListener('foo', ev2, { once: true });
eventTarget.addEventListener('foo', ev3, { once: true });
eventTarget.addEventListener('foo', ev4, { once: true });
eventTarget.dispatchEvent(new Event('foo'));
}
{
const eventTarget = new EventTarget();
// Once handler only invoked once
const ev = common.mustCall((event) => {
// Can invoke the same event name recursively
eventTarget.dispatchEvent(new Event('foo'));
});
// Errors in a handler won't stop calling the others.
eventTarget.addEventListener('foo', ev, { once: true });
eventTarget.dispatchEvent(new Event('foo'));
}
{
// Coercion to string works
strictEqual((new Event(1)).type, '1');
strictEqual((new Event(false)).type, 'false');
strictEqual((new Event({})).type, String({}));
const target = new EventTarget();
[
'foo',
{}, // No type event
undefined,
1,
false,
].forEach((i) => {
throws(() => target.dispatchEvent(i), {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: typeof Bun === "undefined" ? 'The "event" argument must be an instance of Event.' +
common.invalidArgTypeHelper(i) : 'Argument 1 (\'event\') to EventTarget.dispatchEvent must be an instance of Event',
});
});
const err = (arg) => ({
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
...typeof Bun === "undefined" ? {message: 'The "listener" argument must be an instance of EventListener.' +
common.invalidArgTypeHelper(arg)} : {},
});
[
'foo',
1,
false,
].forEach((i) => throws(() => target.addEventListener('foo', i), err(i)));
}
{
const target = new EventTarget();
once(target, 'foo').then(common.mustCall());
target.dispatchEvent(new Event('foo'));
}
{
const target = new EventTarget();
const event = new Event('foo');
strictEqual(event.cancelBubble, false);
event.stopImmediatePropagation();
strictEqual(event.cancelBubble, true);
target.addEventListener('foo', common.mustNotCall());
target.dispatchEvent(event);
}
{
const target = new EventTarget();
const event = new Event('foo');
target.addEventListener('foo', common.mustCall((event) => {
event.stopImmediatePropagation();
}));
target.addEventListener('foo', common.mustNotCall());
target.dispatchEvent(event);
}
{
const target = new EventTarget();
const event = new Event('foo');
target.addEventListener('foo', common.mustCall((event) => {
event.stopImmediatePropagation();
}));
target.addEventListener('foo', common.mustNotCall());
target.dispatchEvent(event);
}
{
const target = new EventTarget();
const event = new Event('foo');
strictEqual(event.target, null);
target.addEventListener('foo', common.mustCall((event) => {
strictEqual(event.target, target);
strictEqual(event.currentTarget, target);
strictEqual(event.srcElement, target);
}));
target.dispatchEvent(event);
}
{
const target1 = new EventTarget();
const target2 = new EventTarget();
const event = new Event('foo');
target1.addEventListener('foo', common.mustCall((event) => {
throws(() => target2.dispatchEvent(event), {
code: 'ERR_EVENT_RECURSION',
});
}));
target1.dispatchEvent(event);
}
{
const target = new EventTarget();
const a = common.mustCall(() => target.removeEventListener('foo', a));
const b = common.mustCall(2);
target.addEventListener('foo', a);
target.addEventListener('foo', b);
target.dispatchEvent(new Event('foo'));
target.dispatchEvent(new Event('foo'));
}
{
const target = new EventTarget();
const a = common.mustCall(3);
target.addEventListener('foo', a, { capture: true });
target.addEventListener('foo', a, { capture: false });
target.dispatchEvent(new Event('foo'));
target.removeEventListener('foo', a, { capture: true });
target.dispatchEvent(new Event('foo'));
target.removeEventListener('foo', a, { capture: false });
target.dispatchEvent(new Event('foo'));
}
{
const target = new EventTarget();
strictEqual(target.toString(), '[object EventTarget]');
const event = new Event('');
strictEqual(event.toString(), '[object Event]');
}
if (typeof Bun === "undefined") { // Node internal
const target = new EventTarget();
defineEventHandler(target, 'foo');
target.onfoo = common.mustCall();
target.dispatchEvent(new Event('foo'));
}
if (typeof Bun === "undefined") { // Node internal
const target = new EventTarget();
defineEventHandler(target, 'foo');
strictEqual(target.onfoo, null);
}
if (typeof Bun === "undefined") { // Node internal
const target = new EventTarget();
defineEventHandler(target, 'foo');
let count = 0;
target.onfoo = () => count++;
target.onfoo = common.mustCall(() => count++);
target.dispatchEvent(new Event('foo'));
strictEqual(count, 1);
}
if (typeof Bun === "undefined") { // Node internal
const target = new EventTarget();
defineEventHandler(target, 'foo');
let count = 0;
target.addEventListener('foo', () => count++);
target.onfoo = common.mustCall(() => count++);
target.dispatchEvent(new Event('foo'));
strictEqual(count, 2);
}
if (typeof Bun === "undefined") { // Node internal
const target = new EventTarget();
defineEventHandler(target, 'foo');
const fn = common.mustNotCall();
target.onfoo = fn;
strictEqual(target.onfoo, fn);
target.onfoo = null;
target.dispatchEvent(new Event('foo'));
}
{
// `this` value of dispatchEvent
const target = new EventTarget();
const target2 = new EventTarget();
const event = new Event('foo');
ok(target.dispatchEvent.call(target2, event));
[
'foo',
{},
[],
1,
...(typeof Bun === "undefined" ? [
// In the web standard, EventTarget.prototype.dispatchEvent === globalThis.dispatchEvent, and calling with this as null or undefined will call it on the global object
// Node does not have globalThis.dispatchEvent.
null,
undefined,
] : []),
false,
Symbol(),
/a/,
].forEach((i) => {
throws(() => target.dispatchEvent.call(i, event), {
code: 'ERR_INVALID_THIS',
});
});
}
{
// Event Statics
strictEqual(Event.NONE, 0);
strictEqual(Event.CAPTURING_PHASE, 1);
strictEqual(Event.AT_TARGET, 2);
strictEqual(Event.BUBBLING_PHASE, 3);
strictEqual(new Event('foo').eventPhase, Event.NONE);
const target = new EventTarget();
target.addEventListener('foo', common.mustCall((e) => {
strictEqual(e.eventPhase, Event.AT_TARGET);
}), { once: true });
target.dispatchEvent(new Event('foo'));
// Event is a function
strictEqual(Event.length, 1);
}
{
const target = new EventTarget();
const ev = new Event('toString');
const fn = common.mustCall((event) => strictEqual(event.type, 'toString'));
target.addEventListener('toString', fn);
target.dispatchEvent(ev);
}
{
const target = new EventTarget();
const ev = new Event('__proto__');
const fn = common.mustCall((event) => strictEqual(event.type, '__proto__'));
target.addEventListener('__proto__', fn);
target.dispatchEvent(ev);
}
{
const eventTarget = new EventTarget();
// Single argument throws
throws(() => eventTarget.addEventListener('foo'), TypeError);
// Null events - does not throw
eventTarget.addEventListener('foo', null);
eventTarget.removeEventListener('foo', null);
eventTarget.addEventListener('foo', undefined);
eventTarget.removeEventListener('foo', undefined);
// Strings, booleans
throws(() => eventTarget.addEventListener('foo', 'hello'), TypeError);
throws(() => eventTarget.addEventListener('foo', false), TypeError);
throws(() => eventTarget.addEventListener('foo', Symbol()), TypeError);
asyncTest = asyncTest.then(async () => {
const eventTarget = new EventTarget();
// Single argument throws
throws(() => eventTarget.addEventListener('foo'), TypeError);
// Null events - does not throw
eventTarget.addEventListener('foo', null);
eventTarget.removeEventListener('foo', null);
// Warnings always happen after nextTick, so wait for a timer of 0
await delay(0);
strictEqual(lastWarning.name, 'AddEventListenerArgumentTypeWarning');
strictEqual(lastWarning.target, eventTarget);
lastWarning = null;
eventTarget.addEventListener('foo', undefined);
await delay(0);
strictEqual(lastWarning.name, 'AddEventListenerArgumentTypeWarning');
strictEqual(lastWarning.target, eventTarget);
eventTarget.removeEventListener('foo', undefined);
// Strings, booleans
throws(() => eventTarget.addEventListener('foo', 'hello'), TypeError);
throws(() => eventTarget.addEventListener('foo', false), TypeError);
throws(() => eventTarget.addEventListener('foo', Symbol()), TypeError);
});
}
{
const eventTarget = new EventTarget();
const event = new Event('foo');
eventTarget.dispatchEvent(event);
strictEqual(event.target, eventTarget);
}
{
// Event target exported keys
const eventTarget = new EventTarget();
deepStrictEqual(Object.keys(eventTarget), []);
deepStrictEqual(Object.getOwnPropertyNames(eventTarget), []);
const parentKeys = Object.keys(Object.getPrototypeOf(eventTarget)).sort();
const keys = ['addEventListener', 'dispatchEvent', 'removeEventListener'];
deepStrictEqual(parentKeys, keys);
}
{
// Subclassing
class SubTarget extends EventTarget {}
const target = new SubTarget();
target.addEventListener('foo', common.mustCall());
target.dispatchEvent(new Event('foo'));
}
{
// Test event order
const target = new EventTarget();
let state = 0;
target.addEventListener('foo', common.mustCall(() => {
strictEqual(state, 0);
state++;
}));
target.addEventListener('foo', common.mustCall(() => {
strictEqual(state, 1);
}));
target.dispatchEvent(new Event('foo'));
}
if (typeof Bun === "undefined") { // Node internal
const target = new EventTarget();
defineEventHandler(target, 'foo');
const descriptor = Object.getOwnPropertyDescriptor(target, 'onfoo');
strictEqual(descriptor.configurable, true);
strictEqual(descriptor.enumerable, true);
}
if (typeof Bun === "undefined") { // Node internal
const target = new EventTarget();
defineEventHandler(target, 'foo');
const output = [];
target.addEventListener('foo', () => output.push(1));
target.onfoo = common.mustNotCall();
target.addEventListener('foo', () => output.push(3));
target.onfoo = () => output.push(2);
target.addEventListener('foo', () => output.push(4));
target.dispatchEvent(new Event('foo'));
deepStrictEqual(output, [1, 2, 3, 4]);
}
if (typeof Bun === "undefined") { // Node internal
const target = new EventTarget();
defineEventHandler(target, 'foo', 'bar');
const output = [];
target.addEventListener('bar', () => output.push(1));
target.onfoo = () => output.push(2);
target.dispatchEvent(new Event('bar'));
deepStrictEqual(output, [1, 2]);
}
{
const et = new EventTarget();
const listener = common.mustNotCall();
et.addEventListener('foo', common.mustCall((e) => {
et.removeEventListener('foo', listener);
}));
et.addEventListener('foo', listener);
et.dispatchEvent(new Event('foo'));
}
{
const ev = new Event('test');
const evConstructorName = inspect(ev, {
depth: -1,
});
if (typeof Bun === "undefined") {
strictEqual(evConstructorName, 'Event');
} else {
strictEqual(evConstructorName, '[Event]');
}
const inspectResult = inspect(ev, {
depth: 1,
});
ok(inspectResult.includes('Event'));
}
{
const et = new EventTarget();
const inspectResult = inspect(et, {
depth: 1,
});
ok(inspectResult.includes('EventTarget'));
}
{
const ev = new Event('test');
strictEqual(ev.constructor.name, 'Event');
const et = new EventTarget();
strictEqual(et.constructor.name, 'EventTarget');
}
if (typeof Bun === "undefined") { // Node internal
// Weak event listeners work
const et = new EventTarget();
const listener = common.mustCall();
et.addEventListener('foo', listener, { [kWeakHandler]: et });
et.dispatchEvent(new Event('foo'));
}
if (typeof Bun === "undefined") { // Node internal
// Weak event listeners can be removed and weakness is not part of the key
const et = new EventTarget();
const listener = common.mustNotCall();
et.addEventListener('foo', listener, { [kWeakHandler]: et });
et.removeEventListener('foo', listener);
et.dispatchEvent(new Event('foo'));
}
if (typeof Bun === "undefined") { // Node internal
// Test listeners are held weakly
const et = new EventTarget();
et.addEventListener('foo', common.mustNotCall(), { [kWeakHandler]: {} });
setImmediate(() => {
global.gc();
et.dispatchEvent(new Event('foo'));
});
}
{
const et = new EventTarget();
throws(() => et.addEventListener(), {
code: 'ERR_MISSING_ARGS',
name: 'TypeError',
});
throws(() => et.addEventListener('foo'), {
code: 'ERR_MISSING_ARGS',
name: 'TypeError',
});
throws(() => et.removeEventListener(), {
code: 'ERR_MISSING_ARGS',
name: 'TypeError',
});
throws(() => et.removeEventListener('foo'), {
code: 'ERR_MISSING_ARGS',
name: 'TypeError',
});
throws(() => et.dispatchEvent(), {
code: 'ERR_MISSING_ARGS',
name: 'TypeError',
});
}
{
const et = new EventTarget();
throws(() => {
et.addEventListener(Symbol('symbol'), () => {});
}, TypeError);
throws(() => {
et.removeEventListener(Symbol('symbol'), () => {});
}, TypeError);
}
{
// Test that event listeners are removed by signal even when
// signal's abort event propagation stopped
const controller = new AbortController();
const { signal } = controller;
signal.addEventListener('abort', (e) => e.stopImmediatePropagation(), { once: true });
const et = new EventTarget();
et.addEventListener('foo', common.mustNotCall(), { signal });
controller.abort();
et.dispatchEvent(new Event('foo'));
}
{
const event = new Event('foo');
strictEqual(event.cancelBubble, false);
event.cancelBubble = true;
strictEqual(event.cancelBubble, true);
}
{
// A null eventInitDict should not throw an error.
new Event('', null);
new Event('', undefined);
}

View File

@@ -1,512 +0,0 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const tmpdir = require('../common/tmpdir');
const fixtures = require('../common/fixtures');
const path = require('path');
const fs = require('fs');
const fsPromises = fs.promises;
const {
access,
chmod,
chown,
copyFile,
lchown,
link,
lchmod,
lstat,
lutimes,
mkdir,
mkdtemp,
open,
readFile,
readdir,
readlink,
realpath,
rename,
rmdir,
stat,
statfs,
symlink,
truncate,
unlink,
utimes,
writeFile
} = fsPromises;
const tmpDir = tmpdir.path;
let dirc = 0;
function nextdir() {
return `test${++dirc}`;
}
// fs.promises should be enumerable.
assert.strictEqual(
Object.prototype.propertyIsEnumerable.call(fs, 'promises'),
true
);
{
access(__filename, 0)
.then(common.mustCall());
assert.rejects(
access('this file does not exist', 0),
{
code: 'ENOENT',
name: 'Error',
message: /^ENOENT: no such file or directory, access/,
// stack: /at async ok\.rejects/ // TODO: Bun async stack trace
}
).then(common.mustCall());
assert.rejects(
access(__filename, 8),
{
code: 'ERR_OUT_OF_RANGE',
}
).then(common.mustCall());
assert.rejects(
access(__filename, { [Symbol.toPrimitive]() { return 5; } }),
{
code: 'ERR_INVALID_ARG_TYPE',
}
).then(common.mustCall());
}
function verifyStatObject(stat) {
assert.strictEqual(typeof stat, 'object');
assert.strictEqual(typeof stat.dev, 'number');
assert.strictEqual(typeof stat.mode, 'number');
}
function verifyStatFsObject(stat, isBigint = false) {
const valueType = isBigint ? 'bigint' : 'number';
assert.strictEqual(typeof stat, 'object');
assert.strictEqual(typeof stat.type, valueType);
assert.strictEqual(typeof stat.bsize, valueType);
assert.strictEqual(typeof stat.blocks, valueType);
assert.strictEqual(typeof stat.bfree, valueType);
assert.strictEqual(typeof stat.bavail, valueType);
assert.strictEqual(typeof stat.files, valueType);
assert.strictEqual(typeof stat.ffree, valueType);
}
async function getHandle(dest) {
await copyFile(fixtures.path('baz.js'), dest);
await access(dest);
return open(dest, 'r+');
}
async function executeOnHandle(dest, func) {
let handle;
try {
handle = await getHandle(dest);
await func(handle);
} finally {
if (handle) {
await handle.close();
}
}
}
{
async function doTest() {
tmpdir.refresh();
const dest = path.resolve(tmpDir, 'baz.js');
// handle is object
{
await executeOnHandle(dest, async (handle) => {
assert.strictEqual(typeof handle, 'object');
});
}
// file stats
{
await executeOnHandle(dest, async (handle) => {
let stats = await handle.stat();
verifyStatObject(stats);
assert.strictEqual(stats.size, 35);
await handle.truncate(1);
stats = await handle.stat();
verifyStatObject(stats);
assert.strictEqual(stats.size, 1);
stats = await stat(dest);
verifyStatObject(stats);
stats = await handle.stat();
verifyStatObject(stats);
await handle.datasync();
await handle.sync();
});
}
// File system stats
{
const statFs = await statfs(dest);
verifyStatFsObject(statFs);
}
// File system stats bigint
{
const statFs = await statfs(dest, { bigint: true });
verifyStatFsObject(statFs, true);
}
// Test fs.read promises when length to read is zero bytes
{
const dest = path.resolve(tmpDir, 'test1.js');
await executeOnHandle(dest, async (handle) => {
const buf = Buffer.from('DAWGS WIN');
const bufLen = buf.length;
await handle.write(buf);
const ret = await handle.read(Buffer.alloc(bufLen), 0, 0, 0);
assert.strictEqual(ret.bytesRead, 0);
await unlink(dest);
});
}
// Use fallback buffer allocation when first argument is null
{
await executeOnHandle(dest, async (handle) => {
const ret = await handle.read(null, 0, 0, 0);
assert.strictEqual(ret.buffer.length, 16384);
});
}
// TypeError if buffer is not ArrayBufferView or nullable object
{
await executeOnHandle(dest, async (handle) => {
await assert.rejects(
async () => handle.read(0, 0, 0, 0),
{ code: 'ERR_INVALID_ARG_TYPE' }
);
});
}
// Bytes written to file match buffer
{
await executeOnHandle(dest, async (handle) => {
const buf = Buffer.from('hello fsPromises');
const bufLen = buf.length;
await handle.write(buf);
const ret = await handle.read(Buffer.alloc(bufLen), 0, bufLen, 0);
assert.strictEqual(ret.bytesRead, bufLen);
assert.deepStrictEqual(ret.buffer, buf);
});
}
// Truncate file to specified length
{
await executeOnHandle(dest, async (handle) => {
const buf = Buffer.from('hello FileHandle');
const bufLen = buf.length;
await handle.write(buf, 0, bufLen, 0);
const ret = await handle.read(Buffer.alloc(bufLen), 0, bufLen, 0);
assert.strictEqual(ret.bytesRead, bufLen);
assert.deepStrictEqual(ret.buffer, buf);
await truncate(dest, 5);
assert.strictEqual((await readFile(dest)).toString(), 'hello');
});
}
// Invalid change of ownership
{
await executeOnHandle(dest, async (handle) => {
await chmod(dest, 0o666);
await handle.chmod(0o666);
await chmod(dest, (0o10777));
await handle.chmod(0o10777);
if (!common.isWindows) {
await chown(dest, process.getuid(), process.getgid());
await handle.chown(process.getuid(), process.getgid());
}
await assert.rejects(
async () => {
await chown(dest, 1, -2);
},
{
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "gid" is out of range. ' +
'It must be >= -1 and <= 4294967295. Received -2'
});
await assert.rejects(
async () => {
await handle.chown(1, -2);
},
{
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "gid" is out of range. ' +
'It must be >= -1 and <= 4294967295. Received -2'
});
});
}
// Set modification times
{
await executeOnHandle(dest, async (handle) => {
await utimes(dest, new Date(), new Date());
try {
await handle.utimes(new Date(), new Date());
} catch (err) {
// Some systems do not have futimes. If there is an error,
// expect it to be ENOSYS
common.expectsError({
code: 'ENOSYS',
name: 'Error'
})(err);
}
});
}
// Set modification times with lutimes
{
const a_time = new Date();
a_time.setMinutes(a_time.getMinutes() - 1);
const m_time = new Date();
m_time.setHours(m_time.getHours() - 1);
await lutimes(dest, a_time, m_time);
const stats = await stat(dest);
assert.strictEqual(a_time.toString(), stats.atime.toString());
assert.strictEqual(m_time.toString(), stats.mtime.toString());
}
// create symlink
{
const newPath = path.resolve(tmpDir, 'baz2.js');
await rename(dest, newPath);
let stats = await stat(newPath);
verifyStatObject(stats);
if (common.canCreateSymLink()) {
const newLink = path.resolve(tmpDir, 'baz3.js');
await symlink(newPath, newLink);
if (!common.isWindows) {
await lchown(newLink, process.getuid(), process.getgid());
}
stats = await lstat(newLink);
verifyStatObject(stats);
assert.strictEqual(newPath.toLowerCase(),
(await realpath(newLink)).toLowerCase());
assert.strictEqual(newPath.toLowerCase(),
(await readlink(newLink)).toLowerCase());
const newMode = 0o666;
if (common.isMacOS) {
// `lchmod` is only available on macOS.
await lchmod(newLink, newMode);
stats = await lstat(newLink);
assert.strictEqual(stats.mode & 0o777, newMode);
} else {
await Promise.all([
assert.rejects(
lchmod(newLink, newMode),
common.expectsError({
code: 'ERR_METHOD_NOT_IMPLEMENTED',
name: 'Error',
message: 'The lchmod() method is not implemented'
})
),
]);
}
await unlink(newLink);
}
}
// specify symlink type
{
const dir = path.join(tmpDir, nextdir());
await symlink(tmpDir, dir, 'dir');
const stats = await lstat(dir);
assert.strictEqual(stats.isSymbolicLink(), true);
await unlink(dir);
}
// create hard link
{
const newPath = path.resolve(tmpDir, 'baz2.js');
const newLink = path.resolve(tmpDir, 'baz4.js');
await link(newPath, newLink);
await unlink(newLink);
}
// Testing readdir lists both files and directories
{
const newDir = path.resolve(tmpDir, 'dir');
const newFile = path.resolve(tmpDir, 'foo.js');
await mkdir(newDir);
await writeFile(newFile, 'DAWGS WIN!', 'utf8');
const stats = await stat(newDir);
assert(stats.isDirectory());
const list = await readdir(tmpDir);
assert.notStrictEqual(list.indexOf('dir'), -1);
assert.notStrictEqual(list.indexOf('foo.js'), -1);
await rmdir(newDir);
await unlink(newFile);
}
// Use fallback encoding when input is null
{
const newFile = path.resolve(tmpDir, 'dogs_running.js');
await writeFile(newFile, 'dogs running', { encoding: null });
const fileExists = fs.existsSync(newFile);
assert.strictEqual(fileExists, true);
}
// `mkdir` when options is number.
{
const dir = path.join(tmpDir, nextdir());
await mkdir(dir, 777);
const stats = await stat(dir);
assert(stats.isDirectory());
}
// `mkdir` when options is string.
{
const dir = path.join(tmpDir, nextdir());
await mkdir(dir, '777');
const stats = await stat(dir);
assert(stats.isDirectory());
}
// `mkdirp` when folder does not yet exist.
{
const dir = path.join(tmpDir, nextdir(), nextdir());
await mkdir(dir, { recursive: true });
const stats = await stat(dir);
assert(stats.isDirectory());
}
// `mkdirp` when path is a file.
{
const dir = path.join(tmpDir, nextdir(), nextdir());
await mkdir(path.dirname(dir));
await writeFile(dir, '');
await assert.rejects(
mkdir(dir, { recursive: true }),
{
code: 'EEXIST',
message: /EEXIST: .*mkdir/,
name: 'Error',
syscall: 'mkdir',
}
);
}
// `mkdirp` when part of the path is a file.
{
const file = path.join(tmpDir, nextdir(), nextdir());
const dir = path.join(file, nextdir(), nextdir());
await mkdir(path.dirname(file));
await writeFile(file, '');
await assert.rejects(
mkdir(dir, { recursive: true }),
{
code: 'ENOTDIR',
message: /ENOTDIR: .*mkdir/,
name: 'Error',
syscall: 'mkdir',
}
);
}
// mkdirp ./
{
const dir = path.resolve(tmpDir, `${nextdir()}/./${nextdir()}`);
await mkdir(dir, { recursive: true });
const stats = await stat(dir);
assert(stats.isDirectory());
}
// mkdirp ../
{
const dir = path.resolve(tmpDir, `${nextdir()}/../${nextdir()}`);
await mkdir(dir, { recursive: true });
const stats = await stat(dir);
assert(stats.isDirectory());
}
// fs.mkdirp requires the recursive option to be of type boolean.
// Everything else generates an error.
{
const dir = path.join(tmpDir, nextdir(), nextdir());
['', 1, {}, [], null, Symbol('test'), () => {}].forEach((recursive) => {
assert.rejects(
// mkdir() expects to get a boolean value for options.recursive.
async () => mkdir(dir, { recursive }),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
}
).then(common.mustCall());
});
}
// `mkdtemp` with invalid numeric prefix
{
await mkdtemp(path.resolve(tmpDir, 'FOO'));
await assert.rejects(
// mkdtemp() expects to get a string prefix.
async () => mkdtemp(1),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
}
);
}
// Regression test for https://github.com/nodejs/node/issues/38168
{
await executeOnHandle(dest, async (handle) => {
await assert.rejects(
async () => handle.write('abc', 0, 'hex'),
{
code: 'ERR_INVALID_ARG_VALUE',
message: /'encoding' is invalid for data of length 3/
}
);
const ret = await handle.write('abcd', 0, 'hex');
assert.strictEqual(ret.bytesWritten, 2);
});
}
// Test prototype methods calling with contexts other than FileHandle
{
await executeOnHandle(dest, async (handle) => {
await assert.rejects(() => handle.stat.call({}), {
code: 'ERR_INTERNAL_ASSERTION',
message: /handle must be an instance of FileHandle/
});
});
}
}
doTest().then(common.mustCall());
}

View File

@@ -0,0 +1,293 @@
// Flags: --expose-internals --no-warnings
'use strict';
const common = require('../common');
const assert = require('assert');
const {
ReadableStream,
ReadableByteStreamController,
ReadableStreamDefaultReader,
ReadableStreamBYOBReader,
ReadableStreamBYOBRequest,
} = require('stream/web');
let kState;
if(typeof Bun === "undefined") {
({
kState,
} = require('internal/webstreams/util'));
}
const {
open,
} = require('fs/promises');
const {
readFileSync,
} = require('fs');
const {
Buffer,
} = require('buffer');
const {
inspect,
} = require('util');
{
const r = new ReadableStream({
type: 'bytes',
});
if(typeof Bun === "undefined") {
assert(r[kState].controller instanceof ReadableByteStreamController);
}
assert.strictEqual(typeof r.locked, 'boolean');
assert.strictEqual(typeof r.cancel, 'function');
assert.strictEqual(typeof r.getReader, 'function');
assert.strictEqual(typeof r.pipeThrough, 'function');
assert.strictEqual(typeof r.pipeTo, 'function');
assert.strictEqual(typeof r.tee, 'function');
['', null, 'asdf'].forEach((mode) => {
assert.throws(() => r.getReader({ mode }), {
code: 'ERR_INVALID_ARG_VALUE',
});
});
[1, 'asdf'].forEach((options) => {
assert.throws(() => r.getReader(options), {
code: 'ERR_INVALID_ARG_TYPE',
});
});
assert(!r.locked);
const defaultReader = r.getReader();
assert(r.locked);
assert(defaultReader instanceof ReadableStreamDefaultReader);
defaultReader.releaseLock();
const byobReader = r.getReader({ mode: 'byob' });
assert(byobReader instanceof ReadableStreamBYOBReader);
assert.match(
inspect(byobReader, { depth: 0 }),
/ReadableStreamBYOBReader/);
}
class Source {
constructor() {
this.controllerClosed = false;
}
async start(controller) {
this.file = await open(__filename);
this.controller = controller;
}
async pull(controller) {
const byobRequest = controller.byobRequest;
assert.match(inspect(byobRequest), /ReadableStreamBYOBRequest/);
const view = byobRequest.view;
const {
bytesRead,
} = await this.file.read({
buffer: view,
offset: view.byteOffset,
length: view.byteLength
});
if (bytesRead === 0) {
await this.file.close();
this.controller.close();
}
assert.throws(() => byobRequest.respondWithNewView({}), {
code: 'ERR_INVALID_ARG_TYPE',
});
byobRequest.respond(bytesRead);
assert.throws(() => byobRequest.respond(bytesRead), {
code: 'ERR_INVALID_STATE',
});
assert.throws(() => byobRequest.respondWithNewView(view), {
code: 'ERR_INVALID_STATE',
});
}
get type() { return 'bytes'; }
get autoAllocateChunkSize() { return 1024; }
}
{
const stream = new ReadableStream(new Source());
if(typeof Bun === "undefined") {
assert(stream[kState].controller instanceof ReadableByteStreamController);
}
async function read(stream) {
const reader = stream.getReader({ mode: 'byob' });
const chunks = [];
let result;
do {
result = await reader.read(Buffer.alloc(100));
if (result.value !== undefined)
chunks.push(Buffer.from(result.value));
} while (!result.done);
return Buffer.concat(chunks);
}
read(stream).then(common.mustCall((data) => {
const check = readFileSync(__filename);
assert.deepStrictEqual(check, data);
}));
}
{
const stream = new ReadableStream(new Source());
if(typeof Bun === "undefined") {
assert(stream[kState].controller instanceof ReadableByteStreamController);
}
async function read(stream) {
const chunks = [];
for await (const chunk of stream)
chunks.push(chunk);
return Buffer.concat(chunks);
}
read(stream).then(common.mustCall((data) => {
const check = readFileSync(__filename);
assert.deepStrictEqual(check, data);
}));
}
{
const stream = new ReadableStream(new Source());
if(typeof Bun === "undefined") {
assert(stream[kState].controller instanceof ReadableByteStreamController);
}
async function read(stream) {
// eslint-disable-next-line no-unused-vars
for await (const _ of stream)
break;
}
read(stream).then(common.mustCall());
}
{
const stream = new ReadableStream(new Source());
if(typeof Bun === "undefined") {
assert(stream[kState].controller instanceof ReadableByteStreamController);
}
const error = new Error('boom');
async function read(stream) {
// eslint-disable-next-line no-unused-vars
for await (const _ of stream)
throw error;
}
assert.rejects(read(stream), error).then(common.mustCall());
}
{
assert.throws(() => {
Reflect.get(ReadableStreamBYOBRequest.prototype, 'view', {});
}, {
code: 'ERR_INVALID_THIS',
});
assert.throws(() => ReadableStreamBYOBRequest.prototype.respond.call({}), {
code: 'ERR_INVALID_THIS',
});
assert.throws(() => {
ReadableStreamBYOBRequest.prototype.respondWithNewView.call({});
}, {
code: 'ERR_INVALID_THIS',
});
}
{
const readable = new ReadableStream({ type: 'bytes' });
const reader = readable.getReader({ mode: 'byob' });
reader.releaseLock();
reader.releaseLock();
assert.rejects(reader.read(new Uint8Array(10)), {
code: 'ERR_INVALID_STATE',
}).then(common.mustCall());
assert.rejects(reader.cancel(), {
code: 'ERR_INVALID_STATE',
}).then(common.mustCall());
}
{
let controller;
new ReadableStream({
type: 'bytes',
start(c) { controller = c; }
});
assert.throws(() => controller.enqueue(1), {
code: 'ERR_INVALID_ARG_TYPE',
});
controller.close();
assert.throws(() => controller.enqueue(new Uint8Array(10)), {
code: 'ERR_INVALID_STATE',
});
assert.throws(() => controller.close(), {
code: 'ERR_INVALID_STATE',
});
}
{
let controller;
new ReadableStream({
type: 'bytes',
start(c) { controller = c; }
});
controller.enqueue(new Uint8Array(10));
controller.close();
assert.throws(() => controller.enqueue(new Uint8Array(10)), {
code: 'ERR_INVALID_STATE',
});
}
{
const stream = new ReadableStream({
type: 'bytes',
pull(c) {
const v = new Uint8Array(c.byobRequest.view.buffer, 0, 3);
v.set([20, 21, 22]);
c.byobRequest.respondWithNewView(v);
},
});
const buffer = new ArrayBuffer(10);
const view = new Uint8Array(buffer, 0, 3);
view.set([10, 11, 12]);
const reader = stream.getReader({ mode: 'byob' });
reader.read(view);
}
{
const stream = new ReadableStream({
type: 'bytes',
autoAllocateChunkSize: 10,
pull(c) {
const v = new Uint8Array(c.byobRequest.view.buffer, 0, 3);
v.set([20, 21, 22]);
c.byobRequest.respondWithNewView(v);
},
});
const reader = stream.getReader();
reader.read();
}

View File

@@ -0,0 +1,57 @@
test("ReadableStream", async () => {
const { resolve, promise } = Promise.withResolvers();
let controller: ReadableStreamDefaultController;
let stream = () =>
new ReadableStream({
start(controller1) {
controller = controller1;
controller1.close();
process.nextTick(resolve);
},
});
stream();
await promise;
expect(() => controller!.close()).toThrowError(
expect.objectContaining({
name: "TypeError",
message: "Invalid state: Controller is already closed",
code: "ERR_INVALID_STATE",
}),
);
});
test("server version", async () => {
const { resolve, promise } = Promise.withResolvers();
let controller: ReadableStreamDefaultController;
let stream = () =>
new ReadableStream({
start(controller1) {
controller = controller1;
controller.close();
process.nextTick(resolve);
},
});
// will start the server on default port 3000
const server = Bun.serve({
fetch(req) {
return new Response(stream());
},
});
await fetch(server.url, {});
await promise;
expect(() => controller!.close()).toThrowError(
expect.objectContaining({
name: "TypeError",
message: "Invalid state: Controller is already closed",
code: "ERR_INVALID_STATE",
}),
);
});