mirror of
https://github.com/oven-sh/bun
synced 2026-02-25 11:07:19 +01:00
Compare commits
7 Commits
ali/test-f
...
nektro-pat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8863b536fc | ||
|
|
9bee7a64a2 | ||
|
|
ea6f6dff7f | ||
|
|
342fe232d0 | ||
|
|
89c5e40544 | ||
|
|
95af099a0c | ||
|
|
8686361f4f |
57
.vscode/launch.json
generated
vendored
57
.vscode/launch.json
generated
vendored
@@ -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",
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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", .{}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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", .{});
|
||||
|
||||
@@ -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", .{});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
3
src/js/builtins.d.ts
vendored
3
src/js/builtins.d.ts
vendored
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) };
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
4
test/bundler/transpiler_constant_fold_eqeq.test.ts
Normal file
4
test/bundler/transpiler_constant_fold_eqeq.test.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
test("constant fold ==", () => {
|
||||
// @ts-expect-error
|
||||
expect("0" + "1" == 0).toBe(false);
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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",
|
||||
);
|
||||
});
|
||||
|
||||
768
test/js/node/test/parallel/test-eventtarget.js
Normal file
768
test/js/node/test/parallel/test-eventtarget.js
Normal 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);
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
293
test/js/node/test/parallel/test-whatwg-readablebytestream.js
Normal file
293
test/js/node/test/parallel/test-whatwg-readablebytestream.js
Normal 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();
|
||||
}
|
||||
57
test/regression/issue/19661.test.ts
Normal file
57
test/regression/issue/19661.test.ts
Normal 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",
|
||||
}),
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user