aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-06-18 10:47:42 -0700
committerGravatar GitHub <noreply@github.com> 2023-06-18 10:47:42 -0700
commitfdb7940c4e435a5b7f5a368f4168d748baf6b5b6 (patch)
treeee23b0ee654cb7ff9307ccc1db961fed444e01f6
parent873f615358b78f913b3f8fe6adda47da7e6e57ac (diff)
downloadbun-fdb7940c4e435a5b7f5a368f4168d748baf6b5b6.tar.gz
bun-fdb7940c4e435a5b7f5a368f4168d748baf6b5b6.tar.zst
bun-fdb7940c4e435a5b7f5a368f4168d748baf6b5b6.zip
Fix a bunch of bugs (#3352)
* Fix a bunch of bugs * undo that one * Fix crash in readdir() * woops * woops * Add comment * :scissors: * Make `readlink()` and `realpath` use much less memory * Update BunString.cpp * woopsie * Unnecessary * Don't commit these --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
-rw-r--r--bench/snippets/realpath.mjs8
-rw-r--r--src/bun.js/api/bun/dns_resolver.zig8
-rw-r--r--src/bun.js/base.zig9
-rw-r--r--src/bun.js/bindings/BunString.cpp57
-rw-r--r--src/bun.js/bindings/bindings.cpp12
-rw-r--r--src/bun.js/bindings/bindings.zig5
-rw-r--r--src/bun.js/bindings/headers.h1
-rw-r--r--src/bun.js/bindings/headers.zig1
-rw-r--r--src/bun.js/event_loop.zig5
-rw-r--r--src/bun.js/node/dir_iterator.zig15
-rw-r--r--src/bun.js/node/node_fs.zig247
-rw-r--r--src/bun.js/node/node_fs_binding.zig8
-rw-r--r--src/bun.js/node/types.zig183
-rw-r--r--src/bun.zig1
-rw-r--r--src/comptime_string_map.zig37
-rw-r--r--src/string.zig163
-rw-r--r--src/string_immutable.zig10
-rw-r--r--test/js/node/fs/fs.test.ts57
-rw-r--r--test/js/third_party/prisma/.gitignore1
-rw-r--r--test/js/third_party/prisma/prisma/sqlite/migrations/20230618153912_init/migration.sql18
-rw-r--r--test/js/third_party/prisma/prisma/sqlite/migrations/migration_lock.toml3
21 files changed, 715 insertions, 134 deletions
diff --git a/bench/snippets/realpath.mjs b/bench/snippets/realpath.mjs
index 9c3793d80..4793ee3d6 100644
--- a/bench/snippets/realpath.mjs
+++ b/bench/snippets/realpath.mjs
@@ -1,4 +1,10 @@
import { realpathSync } from "node:fs";
const count = parseInt(process.env.ITERATIONS || "1", 10) || 1;
const arg = process.argv[process.argv.length - 1];
-for (let i = 0; i < count; i++) realpathSync(arg);
+import { bench, run } from "./runner.mjs";
+
+bench("realpathSync x " + count, () => {
+ for (let i = 0; i < count; i++) realpathSync(arg, "utf-8");
+});
+
+await run();
diff --git a/src/bun.js/api/bun/dns_resolver.zig b/src/bun.js/api/bun/dns_resolver.zig
index 82411701d..aec295056 100644
--- a/src/bun.js/api/bun/dns_resolver.zig
+++ b/src/bun.js/api/bun/dns_resolver.zig
@@ -385,7 +385,7 @@ pub const GetAddrInfo = struct {
return .unspecified;
if (value.isNumber()) {
- return switch (value.to(i32)) {
+ return switch (value.coerce(i32, globalObject)) {
0 => .unspecified,
4 => .inet,
6 => .inet6,
@@ -394,11 +394,11 @@ pub const GetAddrInfo = struct {
}
if (value.isString()) {
- const str = value.getZigString(globalObject);
- if (str.len == 0)
+ const str = value.toBunString(globalObject);
+ if (str.isEmpty())
return .unspecified;
- return map.getWithEql(str, JSC.ZigString.eqlComptime) orelse return error.InvalidFamily;
+ return str.inMap(map) orelse return error.InvalidFamily;
}
return error.InvalidFamily;
diff --git a/src/bun.js/base.zig b/src/bun.js/base.zig
index 4e3e83bd7..038f7f38b 100644
--- a/src/bun.js/base.zig
+++ b/src/bun.js/base.zig
@@ -207,6 +207,15 @@ pub const To = struct {
return array;
},
+ []const bun.String => {
+ defer {
+ for (value) |out| {
+ out.deref();
+ }
+ bun.default_allocator.free(value);
+ }
+ return bun.String.toJSArray(context, value).asObjectRef();
+ },
[]const PathString, []const []const u8, []const []u8, [][]const u8, [][:0]const u8, [][:0]u8 => {
if (value.len == 0)
return JSC.C.JSObjectMakeArray(context, 0, null, exception);
diff --git a/src/bun.js/bindings/BunString.cpp b/src/bun.js/bindings/BunString.cpp
index 249f68435..f737342f4 100644
--- a/src/bun.js/bindings/BunString.cpp
+++ b/src/bun.js/bindings/BunString.cpp
@@ -4,6 +4,7 @@
#include "helpers.h"
#include "simdutf.h"
#include "wtf/text/ExternalStringImpl.h"
+#include "GCDefferalContext.h"
using namespace JSC;
extern "C" void Bun__WTFStringImpl__deref(WTF::StringImpl* impl)
@@ -125,6 +126,22 @@ extern "C" JSC::EncodedJSValue BunString__toJS(JSC::JSGlobalObject* globalObject
return JSValue::encode(Bun::toJS(globalObject, *bunString));
}
+extern "C" BunString BunString__fromUTF8(const char* bytes, size_t length)
+{
+ if (simdutf::validate_utf8(bytes, length)) {
+ size_t u16Length = simdutf::utf16_length_from_utf8(bytes, length);
+ UChar* ptr;
+ auto impl = WTF::StringImpl::createUninitialized(static_cast<unsigned int>(u16Length), ptr);
+ RELEASE_ASSERT(simdutf::convert_utf8_to_utf16(bytes, length, ptr) == u16Length);
+ impl->ref();
+ return { BunStringTag::WTFStringImpl, { .wtf = &impl.leakRef() } };
+ }
+
+ auto str = WTF::String::fromUTF8ReplacingInvalidSequences(reinterpret_cast<const LChar*>(bytes), length);
+ str.impl()->ref();
+ return Bun::fromString(str);
+}
+
extern "C" BunString BunString__fromLatin1(const char* bytes, size_t length)
{
return { BunStringTag::WTFStringImpl, { .wtf = &WTF::StringImpl::create(bytes, length).leakRef() } };
@@ -136,8 +153,7 @@ extern "C" BunString BunString__fromBytes(const char* bytes, size_t length)
return BunString__fromLatin1(bytes, length);
}
- auto str = WTF::String::fromUTF8ReplacingInvalidSequences(reinterpret_cast<const LChar*>(bytes), length);
- return Bun::fromString(str);
+ return BunString__fromUTF8(bytes, length);
}
extern "C" BunString BunString__createExternal(const char* bytes, size_t length, bool isLatin1, void* ctx, void (*callback)(void* arg0, void* arg1, size_t arg2))
@@ -149,6 +165,43 @@ extern "C" BunString BunString__createExternal(const char* bytes, size_t length,
return { BunStringTag::WTFStringImpl, { .wtf = &impl.leakRef() } };
}
+extern "C" EncodedJSValue BunString__createArray(
+ JSC::JSGlobalObject* globalObject,
+ const BunString* ptr, size_t length)
+{
+ if (length == 0)
+ return JSValue::encode(JSC::constructEmptyArray(globalObject, nullptr));
+
+ auto& vm = globalObject->vm();
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+
+ // We must do this or Bun.gc(true) in a loop creating large arrays of strings will crash due to GC'ing.
+ MarkedArgumentBuffer arguments;
+ JSC::ObjectInitializationScope scope(vm);
+ GCDeferralContext context(vm);
+
+ arguments.fill(length, [&](JSC::JSValue* value) {
+ const BunString* end = ptr + length;
+ while (ptr != end) {
+ *value++ = Bun::toJS(globalObject, *ptr++);
+ }
+ });
+
+ if (JSC::JSArray* array = JSC::JSArray::tryCreateUninitializedRestricted(
+ scope,
+ globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous),
+ length)) {
+
+ for (size_t i = 0; i < length; ++i) {
+ array->initializeIndex(scope, i, arguments.at(i));
+ }
+ return JSValue::encode(array);
+ }
+
+ JSC::throwOutOfMemoryError(globalObject, throwScope);
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(JSC::JSValue()));
+}
+
extern "C" void BunString__toWTFString(BunString* bunString)
{
if (bunString->tag == BunStringTag::ZigString) {
diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp
index e64162b34..74357f225 100644
--- a/src/bun.js/bindings/bindings.cpp
+++ b/src/bun.js/bindings/bindings.cpp
@@ -1192,6 +1192,16 @@ void WebCore__DOMURL__pathname_(WebCore__DOMURL* domURL, ZigString* arg1)
*arg1 = Zig::toZigString(pathname);
}
+BunString WebCore__DOMURL__fileSystemPath(WebCore__DOMURL* arg0)
+{
+ const WTF::URL& url = arg0->href();
+ if (url.isLocalFile()) {
+ return Bun::toString(url.fileSystemPath());
+ }
+
+ return BunStringEmpty;
+}
+
extern "C" JSC__JSValue ZigString__toJSONObject(const ZigString* strPtr, JSC::JSGlobalObject* globalObject)
{
auto str = Zig::toString(*strPtr);
@@ -3690,12 +3700,10 @@ JSC__JSValue JSC__VM__runGC(JSC__VM* vm, bool sync)
JSC::JSLockHolder lock(vm);
vm->finalizeSynchronousJSExecution();
-
WTF::releaseFastMallocFreeMemory();
if (sync) {
vm->heap.collectNow(JSC::Sync, JSC::CollectionScope::Full);
- WTF::releaseFastMallocFreeMemory();
} else {
vm->heap.collectSync(JSC::CollectionScope::Full);
}
diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig
index bd6c11fc4..c9c733ebc 100644
--- a/src/bun.js/bindings/bindings.zig
+++ b/src/bun.js/bindings/bindings.zig
@@ -933,6 +933,10 @@ pub const DOMURL = opaque {
return out;
}
+ pub fn fileSystemPath(this: *DOMURL) bun.String {
+ return shim.cppFn("fileSystemPath", .{this});
+ }
+
pub fn pathname_(this: *DOMURL, out: *ZigString) void {
return shim.cppFn("pathname_", .{ this, out });
}
@@ -947,6 +951,7 @@ pub const DOMURL = opaque {
"cast_",
"href_",
"pathname_",
+ "fileSystemPath",
};
};
diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h
index 216a4f6cc..92639843a 100644
--- a/src/bun.js/bindings/headers.h
+++ b/src/bun.js/bindings/headers.h
@@ -163,6 +163,7 @@ CPP_DECL JSC__JSValue ZigString__toTypeErrorInstance(const ZigString* arg0, JSC_
CPP_DECL JSC__JSValue ZigString__toValue(const ZigString* arg0, JSC__JSGlobalObject* arg1);
CPP_DECL JSC__JSValue ZigString__toValueGC(const ZigString* arg0, JSC__JSGlobalObject* arg1);
CPP_DECL WebCore__DOMURL* WebCore__DOMURL__cast_(JSC__JSValue JSValue0, JSC__VM* arg1);
+CPP_DECL BunString WebCore__DOMURL__fileSystemPath(WebCore__DOMURL* arg0);
CPP_DECL void WebCore__DOMURL__href_(WebCore__DOMURL* arg0, ZigString* arg1);
CPP_DECL void WebCore__DOMURL__pathname_(WebCore__DOMURL* arg0, ZigString* arg1);
diff --git a/src/bun.js/bindings/headers.zig b/src/bun.js/bindings/headers.zig
index 082f21418..99af5aecc 100644
--- a/src/bun.js/bindings/headers.zig
+++ b/src/bun.js/bindings/headers.zig
@@ -99,6 +99,7 @@ pub extern fn ZigString__toTypeErrorInstance(arg0: [*c]const ZigString, arg1: *b
pub extern fn ZigString__toValue(arg0: [*c]const ZigString, arg1: *bindings.JSGlobalObject) JSC__JSValue;
pub extern fn ZigString__toValueGC(arg0: [*c]const ZigString, arg1: *bindings.JSGlobalObject) JSC__JSValue;
pub extern fn WebCore__DOMURL__cast_(JSValue0: JSC__JSValue, arg1: *bindings.VM) ?*bindings.DOMURL;
+pub extern fn WebCore__DOMURL__fileSystemPath(arg0: ?*bindings.DOMURL) BunString;
pub extern fn WebCore__DOMURL__href_(arg0: ?*bindings.DOMURL, arg1: [*c]ZigString) void;
pub extern fn WebCore__DOMURL__pathname_(arg0: ?*bindings.DOMURL, arg1: [*c]ZigString) void;
pub extern fn WebCore__DOMFormData__append(arg0: ?*bindings.DOMFormData, arg1: [*c]ZigString, arg2: [*c]ZigString) void;
diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig
index c2125e64f..8441bd064 100644
--- a/src/bun.js/event_loop.zig
+++ b/src/bun.js/event_loop.zig
@@ -348,7 +348,10 @@ pub const GarbageCollectionController = struct {
pub fn processGCTimer(this: *GarbageCollectionController) void {
var vm = this.bunVM().global.vm();
- const this_heap_size = vm.blockBytesAllocated();
+ this.processGCTimerWithHeapSize(vm, vm.blockBytesAllocated());
+ }
+
+ pub fn processGCTimerWithHeapSize(this: *GarbageCollectionController, vm: *JSC.VM, this_heap_size: usize) void {
const prev = this.gc_last_heap_size;
switch (this.gc_timer_state) {
diff --git a/src/bun.js/node/dir_iterator.zig b/src/bun.js/node/dir_iterator.zig
index f1063621e..aa939679c 100644
--- a/src/bun.js/node/dir_iterator.zig
+++ b/src/bun.js/node/dir_iterator.zig
@@ -17,7 +17,12 @@ const mem = std.mem;
const strings = @import("root").bun.strings;
const Maybe = JSC.Maybe;
const File = std.fs.File;
-const Result = Maybe(?Entry);
+const IteratorResult = struct {
+ name: PathString,
+ kind: Entry.Kind,
+};
+
+const Result = Maybe(?IteratorResult);
const Entry = JSC.Node.Dirent;
@@ -84,7 +89,7 @@ pub const Iterator = switch (builtin.os.tag) {
else => Entry.Kind.Unknown,
};
return .{
- .result = Entry{
+ .result = IteratorResult{
.name = PathString.init(name),
.kind = entry_kind,
},
@@ -139,7 +144,7 @@ pub const Iterator = switch (builtin.os.tag) {
else => Entry.Kind.Unknown,
};
return .{
- .result = Entry{
+ .result = IteratorResult{
.name = PathString.init(name),
.kind = entry_kind,
},
@@ -213,7 +218,7 @@ pub const Iterator = switch (builtin.os.tag) {
break :blk Entry.Kind.File;
};
return .{
- .result = Entry{
+ .result = IteratorResult{
.name = PathString.init(name_utf8),
.kind = kind,
},
@@ -278,7 +283,7 @@ pub const Iterator = switch (builtin.os.tag) {
.SOCKET_STREAM, .SOCKET_DGRAM => Entry.Kind.UnixDomainSocket,
else => Entry.Kind.Unknown,
};
- return Entry{
+ return IteratorResult{
.name = name,
.kind = entry_kind,
};
diff --git a/src/bun.js/node/node_fs.zig b/src/bun.js/node/node_fs.zig
index f0f9c4980..254d58455 100644
--- a/src/bun.js/node/node_fs.zig
+++ b/src/bun.js/node/node_fs.zig
@@ -65,6 +65,11 @@ pub const Arguments = struct {
old_path: PathLike,
new_path: PathLike,
+ pub fn deinit(this: @This()) void {
+ this.old_path.deinit();
+ this.new_path.deinit();
+ }
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Rename {
const old_path = PathLike.fromJS(ctx, arguments, exception) orelse {
if (exception.* == null) {
@@ -99,6 +104,10 @@ pub const Arguments = struct {
path: PathOrFileDescriptor,
len: JSC.WebCore.Blob.SizeType = 0,
+ pub fn deinit(this: @This()) void {
+ this.path.deinit();
+ }
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Truncate {
const path = PathOrFileDescriptor.fromJS(ctx, arguments, arguments.arena.allocator(), exception) orelse {
if (exception.* == null) {
@@ -131,6 +140,10 @@ pub const Arguments = struct {
fd: FileDescriptor,
len: ?JSC.WebCore.Blob.SizeType = null,
+ pub fn deinit(this: @This()) void {
+ _ = this;
+ }
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?FTruncate {
const fd = JSC.Node.fileDescriptorFromJS(ctx, arguments.next() orelse {
if (exception.* == null) {
@@ -177,6 +190,10 @@ pub const Arguments = struct {
uid: uid_t = 0,
gid: gid_t = 0,
+ pub fn deinit(this: @This()) void {
+ this.path.deinit();
+ }
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Chown {
const path = PathLike.fromJS(ctx, arguments, exception) orelse {
if (exception.* == null) {
@@ -233,6 +250,8 @@ pub const Arguments = struct {
uid: uid_t,
gid: gid_t,
+ pub fn deinit(_: @This()) void {}
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Fchown {
const fd = JSC.Node.fileDescriptorFromJS(ctx, arguments.next() orelse {
if (exception.* == null) {
@@ -303,6 +322,10 @@ pub const Arguments = struct {
atime: TimeLike,
mtime: TimeLike,
+ pub fn deinit(this: @This()) void {
+ this.path.deinit();
+ }
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Lutimes {
const path = PathLike.fromJS(ctx, arguments, exception) orelse {
if (exception.* == null) {
@@ -374,6 +397,10 @@ pub const Arguments = struct {
path: PathLike,
mode: Mode = 0x777,
+ pub fn deinit(this: @This()) void {
+ this.path.deinit();
+ }
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Chmod {
const path = PathLike.fromJS(ctx, arguments, exception) orelse {
if (exception.* == null) {
@@ -479,6 +506,10 @@ pub const Arguments = struct {
path: PathLike,
big_int: bool = false,
+ pub fn deinit(this: Stat) void {
+ this.path.deinit();
+ }
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Stat {
const path = PathLike.fromJS(ctx, arguments, exception) orelse {
if (exception.* == null) {
@@ -500,8 +531,8 @@ pub const Arguments = struct {
if (next_val.isCallable(ctx.ptr().vm())) break :brk false;
arguments.eat();
- if (next_val.getIfPropertyExists(ctx.ptr(), "bigint")) |big_int| {
- break :brk big_int.toBoolean();
+ if (next_val.getOptional(ctx.ptr(), "bigint", bool) catch false) |big_int| {
+ break :brk big_int;
}
}
}
@@ -518,6 +549,8 @@ pub const Arguments = struct {
fd: FileDescriptor,
big_int: bool = false,
+ pub fn deinit(_: @This()) void {}
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Fstat {
const fd = JSC.Node.fileDescriptorFromJS(ctx, arguments.next() orelse {
if (exception.* == null) {
@@ -549,8 +582,8 @@ pub const Arguments = struct {
if (next_val.isCallable(ctx.ptr().vm())) break :brk false;
arguments.eat();
- if (next_val.getIfPropertyExists(ctx.ptr(), "bigint")) |big_int| {
- break :brk big_int.toBoolean();
+ if (next_val.getOptional(ctx.ptr(), "bigint", bool) catch false) |big_int| {
+ break :brk big_int;
}
}
}
@@ -569,6 +602,11 @@ pub const Arguments = struct {
old_path: PathLike,
new_path: PathLike,
+ pub fn deinit(this: Link) void {
+ this.old_path.deinit();
+ this.new_path.deinit();
+ }
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Link {
const old_path = PathLike.fromJS(ctx, arguments, exception) orelse {
if (exception.* == null) {
@@ -606,6 +644,11 @@ pub const Arguments = struct {
old_path: PathLike,
new_path: PathLike,
+ pub fn deinit(this: Symlink) void {
+ this.old_path.deinit();
+ this.new_path.deinit();
+ }
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Symlink {
const old_path = PathLike.fromJS(ctx, arguments, exception) orelse {
if (exception.* == null) {
@@ -658,6 +701,10 @@ pub const Arguments = struct {
path: PathLike,
encoding: Encoding = Encoding.utf8,
+ pub fn deinit(this: Readlink) void {
+ this.path.deinit();
+ }
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Readlink {
const path = PathLike.fromJS(ctx, arguments, exception) orelse {
if (exception.* == null) {
@@ -678,12 +725,12 @@ pub const Arguments = struct {
switch (val.jsType()) {
JSC.JSValue.JSType.String, JSC.JSValue.JSType.StringObject, JSC.JSValue.JSType.DerivedStringObject => {
- encoding = Encoding.fromStringValue(val, ctx.ptr()) orelse Encoding.utf8;
+ encoding = Encoding.fromJS(val, ctx.ptr()) orelse Encoding.utf8;
},
else => {
if (val.isObject()) {
if (val.getIfPropertyExists(ctx.ptr(), "encoding")) |encoding_| {
- encoding = Encoding.fromStringValue(encoding_, ctx.ptr()) orelse Encoding.utf8;
+ encoding = Encoding.fromJS(encoding_, ctx.ptr()) orelse Encoding.utf8;
}
}
},
@@ -698,6 +745,10 @@ pub const Arguments = struct {
path: PathLike,
encoding: Encoding = Encoding.utf8,
+ pub fn deinit(this: Realpath) void {
+ this.path.deinit();
+ }
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Realpath {
const path = PathLike.fromJS(ctx, arguments, exception) orelse {
if (exception.* == null) {
@@ -718,12 +769,12 @@ pub const Arguments = struct {
switch (val.jsType()) {
JSC.JSValue.JSType.String, JSC.JSValue.JSType.StringObject, JSC.JSValue.JSType.DerivedStringObject => {
- encoding = Encoding.fromStringValue(val, ctx.ptr()) orelse Encoding.utf8;
+ encoding = Encoding.fromJS(val, ctx.ptr()) orelse Encoding.utf8;
},
else => {
if (val.isObject()) {
if (val.getIfPropertyExists(ctx.ptr(), "encoding")) |encoding_| {
- encoding = Encoding.fromStringValue(encoding_, ctx.ptr()) orelse Encoding.utf8;
+ encoding = Encoding.fromJS(encoding_, ctx.ptr()) orelse Encoding.utf8;
}
}
},
@@ -775,6 +826,10 @@ pub const Arguments = struct {
recursive: bool = false,
retry_delay: c_uint = 100,
+ pub fn deinit(this: RmDir) void {
+ this.path.deinit();
+ }
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?RmDir {
const path = PathLike.fromJS(ctx, arguments, exception) orelse {
if (exception.* == null) {
@@ -789,18 +844,25 @@ pub const Arguments = struct {
};
if (exception.* != null) return null;
+
var recursive = false;
var force = false;
if (arguments.next()) |val| {
arguments.eat();
if (val.isObject()) {
- if (val.get(ctx.ptr(), "recursive")) |boolean| {
- recursive = boolean.toBoolean();
+ if (val.getOptional(ctx.ptr(), "recursive", bool) catch {
+ path.deinit();
+ return null;
+ }) |boolean| {
+ recursive = boolean;
}
- if (val.get(ctx.ptr(), "force")) |boolean| {
- force = boolean.toBoolean();
+ if (val.getOptional(ctx.ptr(), "force", bool) catch {
+ path.deinit();
+ return null;
+ }) |boolean| {
+ force = boolean;
}
}
}
@@ -824,7 +886,11 @@ pub const Arguments = struct {
/// @default
mode: Mode = 0o777,
- pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Mkdir {
+ pub fn deinit(this: Mkdir) void {
+ this.path.deinit();
+ }
+
+ pub fn fromJS(ctx: *JSC.JSGlobalObject, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Mkdir {
const path = PathLike.fromJS(ctx, arguments, exception) orelse {
if (exception.* == null) {
JSC.throwInvalidArguments(
@@ -846,8 +912,11 @@ pub const Arguments = struct {
arguments.eat();
if (val.isObject()) {
- if (val.getIfPropertyExists(ctx.ptr(), "recursive")) |recursive_| {
- recursive = recursive_.toBoolean();
+ if (val.getOptional(ctx.ptr(), "recursive", bool) catch {
+ path.deinit();
+ return null;
+ }) |boolean| {
+ recursive = boolean;
}
if (val.getIfPropertyExists(ctx.ptr(), "mode")) |mode_| {
@@ -868,6 +937,10 @@ pub const Arguments = struct {
prefix: JSC.Node.SliceOrBuffer = .{ .buffer = .{ .buffer = JSC.ArrayBuffer.empty } },
encoding: Encoding = Encoding.utf8,
+ pub fn deinit(this: MkdirTemp) void {
+ this.prefix.deinit();
+ }
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?MkdirTemp {
const prefix_value = arguments.next() orelse return MkdirTemp{};
@@ -894,12 +967,12 @@ pub const Arguments = struct {
switch (val.jsType()) {
JSC.JSValue.JSType.String, JSC.JSValue.JSType.StringObject, JSC.JSValue.JSType.DerivedStringObject => {
- encoding = Encoding.fromStringValue(val, ctx.ptr()) orelse Encoding.utf8;
+ encoding = Encoding.fromJS(val, ctx.ptr()) orelse Encoding.utf8;
},
else => {
if (val.isObject()) {
if (val.getIfPropertyExists(ctx.ptr(), "encoding")) |encoding_| {
- encoding = Encoding.fromStringValue(encoding_, ctx.ptr()) orelse Encoding.utf8;
+ encoding = Encoding.fromJS(encoding_, ctx.ptr()) orelse Encoding.utf8;
}
}
},
@@ -918,6 +991,10 @@ pub const Arguments = struct {
encoding: Encoding = Encoding.utf8,
with_file_types: bool = false,
+ pub fn deinit(this: Readdir) void {
+ this.path.deinit();
+ }
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Readdir {
const path = PathLike.fromJS(ctx, arguments, exception) orelse {
if (exception.* == null) {
@@ -941,16 +1018,19 @@ pub const Arguments = struct {
switch (val.jsType()) {
JSC.JSValue.JSType.String, JSC.JSValue.JSType.StringObject, JSC.JSValue.JSType.DerivedStringObject => {
- encoding = Encoding.fromStringValue(val, ctx.ptr()) orelse Encoding.utf8;
+ encoding = Encoding.fromJS(val, ctx.ptr()) orelse Encoding.utf8;
},
else => {
if (val.isObject()) {
if (val.getIfPropertyExists(ctx.ptr(), "encoding")) |encoding_| {
- encoding = Encoding.fromStringValue(encoding_, ctx.ptr()) orelse Encoding.utf8;
+ encoding = Encoding.fromJS(encoding_, ctx.ptr()) orelse Encoding.utf8;
}
- if (val.getIfPropertyExists(ctx.ptr(), "withFileTypes")) |with_file_types_| {
- with_file_types = with_file_types_.toBoolean();
+ if (val.getOptional(ctx.ptr(), "withFileTypes", bool) catch {
+ path.deinit();
+ return null;
+ }) |with_file_types_| {
+ with_file_types = with_file_types_;
}
}
},
@@ -968,6 +1048,8 @@ pub const Arguments = struct {
pub const Close = struct {
fd: FileDescriptor,
+ pub fn deinit(_: Close) void {}
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Close {
const fd = JSC.Node.fileDescriptorFromJS(ctx, arguments.next() orelse {
if (exception.* == null) {
@@ -1004,6 +1086,10 @@ pub const Arguments = struct {
flags: FileSystemFlags = FileSystemFlags.r,
mode: Mode = default_permission,
+ pub fn deinit(this: Open) void {
+ this.path.deinit();
+ }
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Open {
const path = PathLike.fromJS(ctx, arguments, exception) orelse {
if (exception.* == null) {
@@ -1068,6 +1154,8 @@ pub const Arguments = struct {
atime: TimeLike,
mtime: TimeLike,
+ pub fn deinit(_: Futimes) void {}
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Futimes {
const fd = JSC.Node.fileDescriptorFromJS(ctx, arguments.next() orelse {
if (exception.* == null) {
@@ -1152,6 +1240,8 @@ pub const Arguments = struct {
pub const FSync = struct {
fd: FileDescriptor,
+ pub fn deinit(_: FSync) void {}
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?FSync {
const fd = JSC.Node.fileDescriptorFromJS(ctx, arguments.next() orelse {
if (exception.* == null) {
@@ -1216,6 +1306,8 @@ pub const Arguments = struct {
position: ?ReadPosition = null,
encoding: Encoding = Encoding.buffer,
+ pub fn deinit(_: Write) void {}
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Write {
const fd = JSC.Node.fileDescriptorFromJS(ctx, arguments.next() orelse {
if (exception.* == null) {
@@ -1291,7 +1383,7 @@ pub const Arguments = struct {
}
if (current.isString()) {
- args.encoding = Encoding.fromStringValue(current, ctx.ptr()) orelse Encoding.utf8;
+ args.encoding = Encoding.fromJS(current, ctx.ptr()) orelse Encoding.utf8;
arguments.eat();
}
},
@@ -1330,6 +1422,8 @@ pub const Arguments = struct {
length: u64 = std.math.maxInt(u64),
position: ?ReadPosition = null,
+ pub fn deinit(_: Read) void {}
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Read {
const fd = JSC.Node.fileDescriptorFromJS(ctx, arguments.next() orelse {
if (exception.* == null) {
@@ -1460,6 +1554,10 @@ pub const Arguments = struct {
flag: FileSystemFlags = FileSystemFlags.r,
+ pub fn deinit(self: ReadFile) void {
+ self.path.deinit();
+ }
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?ReadFile {
const path = PathOrFileDescriptor.fromJS(ctx, arguments, arguments.arena.allocator(), exception) orelse {
if (exception.* == null) {
@@ -1481,7 +1579,7 @@ pub const Arguments = struct {
if (arguments.next()) |arg| {
arguments.eat();
if (arg.isString()) {
- encoding = Encoding.fromStringValue(arg, ctx.ptr()) orelse {
+ encoding = Encoding.fromJS(arg, ctx.ptr()) orelse {
if (exception.* == null) {
JSC.throwInvalidArguments(
"Invalid encoding",
@@ -1495,7 +1593,7 @@ pub const Arguments = struct {
} else if (arg.isObject()) {
if (arg.getIfPropertyExists(ctx.ptr(), "encoding")) |encoding_| {
if (!encoding_.isUndefinedOrNull()) {
- encoding = Encoding.fromStringValue(encoding_, ctx.ptr()) orelse {
+ encoding = Encoding.fromJS(encoding_, ctx.ptr()) orelse {
if (exception.* == null) {
JSC.throwInvalidArguments(
"Invalid encoding",
@@ -1542,6 +1640,10 @@ pub const Arguments = struct {
data: StringOrBuffer,
dirfd: FileDescriptor = @intCast(FileDescriptor, std.fs.cwd().fd),
+ pub fn deinit(self: WriteFile) void {
+ self.file.deinit();
+ }
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?WriteFile {
const file = PathOrFileDescriptor.fromJS(ctx, arguments, arguments.arena.allocator(), exception) orelse {
if (exception.* == null) {
@@ -1589,7 +1691,7 @@ pub const Arguments = struct {
if (arguments.next()) |arg| {
arguments.eat();
if (arg.isString()) {
- encoding = Encoding.fromStringValue(arg, ctx.ptr()) orelse {
+ encoding = Encoding.fromJS(arg, ctx.ptr()) orelse {
if (exception.* == null) {
JSC.throwInvalidArguments(
"Invalid encoding",
@@ -1602,7 +1704,7 @@ pub const Arguments = struct {
};
} else if (arg.isObject()) {
if (arg.getTruthy(ctx.ptr(), "encoding")) |encoding_| {
- encoding = Encoding.fromStringValue(encoding_, ctx.ptr()) orelse {
+ encoding = Encoding.fromJS(encoding_, ctx.ptr()) orelse {
if (exception.* == null) {
JSC.throwInvalidArguments(
"Invalid encoding",
@@ -1665,6 +1767,10 @@ pub const Arguments = struct {
/// Number of directory entries that are buffered internally when reading from the directory. Higher values lead to better performance but higher memory usage. Default: 32
buffer_size: c_int = 32,
+ pub fn deinit(self: OpenDir) void {
+ self.path.deinit();
+ }
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?OpenDir {
const path = PathLike.fromJS(ctx, arguments, exception) orelse {
if (exception.* == null) {
@@ -1686,7 +1792,7 @@ pub const Arguments = struct {
if (arguments.next()) |arg| {
arguments.eat();
if (arg.isString()) {
- encoding = Encoding.fromStringValue(arg, ctx.ptr()) orelse {
+ encoding = Encoding.fromJS(arg, ctx.ptr()) orelse {
if (exception.* == null) {
JSC.throwInvalidArguments(
"Invalid encoding",
@@ -1700,7 +1806,7 @@ pub const Arguments = struct {
} else if (arg.isObject()) {
if (arg.getIfPropertyExists(ctx.ptr(), "encoding")) |encoding_| {
if (!encoding_.isUndefinedOrNull()) {
- encoding = Encoding.fromStringValue(encoding_, ctx.ptr()) orelse {
+ encoding = Encoding.fromJS(encoding_, ctx.ptr()) orelse {
if (exception.* == null) {
JSC.throwInvalidArguments(
"Invalid encoding",
@@ -1741,6 +1847,12 @@ pub const Arguments = struct {
pub const Exists = struct {
path: ?PathLike,
+ pub fn deinit(this: Exists) void {
+ if (this.path) |path| {
+ path.deinit();
+ }
+ }
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Exists {
return Exists{
.path = PathLike.fromJS(ctx, arguments, exception),
@@ -1752,6 +1864,10 @@ pub const Arguments = struct {
path: PathLike,
mode: FileSystemFlags = FileSystemFlags.r,
+ pub fn deinit(this: Access) void {
+ this.path.deinit();
+ }
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Access {
const path = PathLike.fromJS(ctx, arguments, exception) orelse {
if (exception.* == null) {
@@ -1805,6 +1921,10 @@ pub const Arguments = struct {
highwater_mark: u32 = 64 * 1024,
global_object: *JSC.JSGlobalObject,
+ pub fn deinit(this: CreateReadStream) void {
+ this.file.deinit();
+ }
+
pub fn copyToState(this: CreateReadStream, state: *JSC.Node.Readable.State) void {
state.encoding = this.encoding;
state.highwater_mark = this.highwater_mark;
@@ -1826,7 +1946,7 @@ pub const Arguments = struct {
if (arguments.next()) |arg| {
arguments.eat();
if (arg.isString()) {
- stream.encoding = Encoding.fromStringValue(arg, ctx.ptr()) orelse {
+ stream.encoding = Encoding.fromJS(arg, ctx.ptr()) orelse {
if (exception.* != null) {
JSC.throwInvalidArguments(
"Invalid encoding",
@@ -1853,7 +1973,7 @@ pub const Arguments = struct {
}
if (arg.getIfPropertyExists(ctx.ptr(), "encoding")) |encoding| {
- stream.encoding = Encoding.fromStringValue(encoding, ctx.ptr()) orelse {
+ stream.encoding = Encoding.fromJS(encoding, ctx.ptr()) orelse {
if (exception.* != null) {
JSC.throwInvalidArguments(
"Invalid encoding",
@@ -1939,6 +2059,10 @@ pub const Arguments = struct {
highwater_mark: u32 = 256 * 1024,
global_object: *JSC.JSGlobalObject,
+ pub fn deinit(this: @This()) void {
+ this.file.deinit();
+ }
+
pub fn copyToState(this: CreateWriteStream, state: *JSC.Node.Writable.State) void {
state.encoding = this.encoding;
state.highwater_mark = this.highwater_mark;
@@ -1960,7 +2084,7 @@ pub const Arguments = struct {
if (arguments.next()) |arg| {
arguments.eat();
if (arg.isString()) {
- stream.encoding = Encoding.fromStringValue(arg, ctx.ptr()) orelse {
+ stream.encoding = Encoding.fromJS(arg, ctx.ptr()) orelse {
if (exception.* != null) {
JSC.throwInvalidArguments(
"Invalid encoding",
@@ -1987,7 +2111,7 @@ pub const Arguments = struct {
}
if (arg.getIfPropertyExists(ctx.ptr(), "encoding")) |encoding| {
- stream.encoding = Encoding.fromStringValue(encoding, ctx.ptr()) orelse {
+ stream.encoding = Encoding.fromJS(encoding, ctx.ptr()) orelse {
if (exception.* != null) {
JSC.throwInvalidArguments(
"Invalid encoding",
@@ -2057,6 +2181,8 @@ pub const Arguments = struct {
pub const FdataSync = struct {
fd: FileDescriptor,
+ pub fn deinit(_: FdataSync) void {}
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?FdataSync {
const fd = JSC.Node.fileDescriptorFromJS(ctx, arguments.next() orelse {
if (exception.* == null) {
@@ -2093,6 +2219,11 @@ pub const Arguments = struct {
dest: PathLike,
mode: Constants.Copyfile,
+ fn deinit(this: CopyFile) void {
+ this.src.deinit();
+ this.dest.deinit();
+ }
+
pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?CopyFile {
const src = PathLike.fromJS(ctx, arguments, exception) orelse {
if (exception.* == null) {
@@ -2311,7 +2442,7 @@ const Return = struct {
pub const Readdir = union(Tag) {
with_file_types: []Dirent,
buffers: []const Buffer,
- files: []const JSC.ZigString,
+ files: []const bun.String,
pub const Tag = enum {
with_file_types,
@@ -2323,7 +2454,7 @@ const Return = struct {
return switch (this) {
.with_file_types => JSC.To.JS.withType([]const Dirent, this.with_file_types, ctx, exception),
.buffers => JSC.To.JS.withType([]const Buffer, this.buffers, ctx, exception),
- .files => JSC.To.JS.withType([]const JSC.ZigString, this.files, ctx, exception),
+ .files => JSC.To.JS.withType([]const bun.String, this.files, ctx, exception),
};
}
};
@@ -2333,8 +2464,8 @@ const Return = struct {
buffer: JSC.Node.Buffer,
null_terminated: [:0]const u8,
};
- pub const Readlink = StringOrBuffer;
- pub const Realpath = StringOrBuffer;
+ pub const Readlink = JSC.Node.StringOrBunStringOrBuffer;
+ pub const Realpath = JSC.Node.StringOrBunStringOrBuffer;
pub const RealpathNative = Realpath;
pub const Rename = void;
pub const Rmdir = void;
@@ -3183,7 +3314,7 @@ pub const NodeFS = struct {
return _readdir(
this,
args,
- JSC.ZigString,
+ bun.String,
flavor,
);
}
@@ -3206,7 +3337,7 @@ pub const NodeFS = struct {
) Maybe(Return.Readdir) {
const file_type = comptime switch (ExpectedType) {
Dirent => "with_file_types",
- JSC.ZigString => "files",
+ bun.String => "files",
Buffer => "buffers",
else => unreachable,
};
@@ -3234,13 +3365,13 @@ pub const NodeFS = struct {
for (entries.items) |*item| {
switch (comptime ExpectedType) {
Dirent => {
- bun.default_allocator.free(item.name.slice());
+ item.name.deref();
},
Buffer => {
item.destroy();
},
- JSC.ZigString => {
- bun.default_allocator.free(item.slice());
+ bun.String => {
+ item.deref();
},
else => unreachable,
}
@@ -3254,19 +3385,19 @@ pub const NodeFS = struct {
},
.result => |ent| ent,
}) |current| : (entry = iterator.next()) {
+ const utf8_name = current.name.slice();
switch (comptime ExpectedType) {
Dirent => {
entries.append(.{
- .name = PathString.init(bun.default_allocator.dupe(u8, current.name.slice()) catch unreachable),
+ .name = bun.String.create(utf8_name),
.kind = current.kind,
}) catch unreachable;
},
Buffer => {
- const slice = current.name.slice();
- entries.append(Buffer.fromString(slice, bun.default_allocator) catch unreachable) catch unreachable;
+ entries.append(Buffer.fromString(utf8_name, bun.default_allocator) catch unreachable) catch unreachable;
},
- JSC.ZigString => {
- entries.append(JSC.ZigString.dupeForJS(current.name.slice(), bun.default_allocator) catch unreachable) catch unreachable;
+ bun.String => {
+ entries.append(bun.String.create(utf8_name)) catch unreachable;
},
else => unreachable,
}
@@ -3576,9 +3707,15 @@ pub const NodeFS = struct {
.buffer => .{
.buffer = Buffer.fromString(outbuf[0..len], bun.default_allocator) catch unreachable,
},
- else => .{
- .string = bun.default_allocator.dupe(u8, outbuf[0..len]) catch unreachable,
- },
+ else => if (args.path == .slice_with_underlying_string and
+ strings.eqlLong(args.path.slice_with_underlying_string.slice(), outbuf[0..len], true))
+ .{
+ .BunString = args.path.slice_with_underlying_string.underlying.dupeRef(),
+ }
+ else
+ .{
+ .BunString = bun.String.create(outbuf[0..len]),
+ },
},
};
},
@@ -3630,9 +3767,15 @@ pub const NodeFS = struct {
.buffer => .{
.buffer = Buffer.fromString(buf, bun.default_allocator) catch unreachable,
},
- else => .{
- .string = bun.default_allocator.dupe(u8, buf) catch unreachable,
- },
+ else => if (args.path == .slice_with_underlying_string and
+ strings.eqlLong(args.path.slice_with_underlying_string.slice(), buf, true))
+ .{
+ .BunString = args.path.slice_with_underlying_string.underlying.dupeRef(),
+ }
+ else
+ .{
+ .BunString = bun.String.create(buf),
+ },
},
};
},
diff --git a/src/bun.js/node/node_fs_binding.zig b/src/bun.js/node/node_fs_binding.zig
index 0e0875a18..74b769bf6 100644
--- a/src/bun.js/node/node_fs_binding.zig
+++ b/src/bun.js/node/node_fs_binding.zig
@@ -44,12 +44,16 @@ fn callSync(comptime FunctionEnum: NodeFSFunctionEnum) NodeFSFunction {
const args = if (comptime Arguments != void)
(Arguments.fromJS(globalObject, &slice, &exceptionref) orelse {
- std.debug.assert(exceptionref != null);
- globalObject.throwValue(JSC.JSValue.c(exceptionref));
+ // we might've already thrown
+ if (exceptionref != null)
+ globalObject.throwValue(JSC.JSValue.c(exceptionref));
return .zero;
})
else
Arguments{};
+ defer {
+ if (comptime Arguments != void and @hasDecl(Arguments, "deinit")) args.deinit();
+ }
const exception1 = JSC.JSValue.c(exceptionref);
diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig
index 95a2270a5..23ae60c69 100644
--- a/src/bun.js/node/types.zig
+++ b/src/bun.js/node/types.zig
@@ -248,6 +248,61 @@ pub const StringOrBuffer = union(Tag) {
}
};
+pub const StringOrBunStringOrBuffer = union(enum) {
+ BunString: bun.String,
+ string: string,
+ buffer: Buffer,
+
+ pub fn toJS(this: StringOrBunStringOrBuffer, ctx: JSC.C.JSContextRef, exception: JSC.C.ExceptionRef) JSC.C.JSValueRef {
+ return switch (this) {
+ .string => {
+ if (this.string.len == 0)
+ return JSC.ZigString.Empty.toValue(ctx).asObjectRef();
+
+ const input = this.string;
+ if (strings.toUTF16Alloc(bun.default_allocator, input, false) catch null) |utf16| {
+ bun.default_allocator.free(bun.constStrToU8(input));
+ return JSC.ZigString.toExternalU16(utf16.ptr, utf16.len, ctx.ptr()).asObjectRef();
+ }
+
+ return JSC.ZigString.init(input).toExternalValue(ctx.ptr()).asObjectRef();
+ },
+ .buffer => this.buffer.toJSObjectRef(ctx, exception),
+ .BunString => {
+ defer this.BunString.deref();
+ return this.BunString.toJSConst(ctx).asObjectRef();
+ },
+ };
+ }
+
+ pub fn fromJS(global: *JSC.JSGlobalObject, allocator: std.mem.Allocator, value: JSC.JSValue, exception: JSC.C.ExceptionRef) ?StringOrBuffer {
+ return switch (value.jsType()) {
+ JSC.JSValue.JSType.String, JSC.JSValue.JSType.StringObject, JSC.JSValue.JSType.DerivedStringObject, JSC.JSValue.JSType.Object => {
+ var zig_str = value.toSlice(global, allocator);
+ return StringOrBuffer{ .string = zig_str.slice() };
+ },
+
+ .ArrayBuffer,
+ .Int8Array,
+ .Uint8Array,
+ .Uint8ClampedArray,
+ .Int16Array,
+ .Uint16Array,
+ .Int32Array,
+ .Uint32Array,
+ .Float32Array,
+ .Float64Array,
+ .BigInt64Array,
+ .BigUint64Array,
+ .DataView,
+ => StringOrBuffer{
+ .buffer = Buffer.fromArrayBuffer(global, value, exception),
+ },
+ else => null,
+ };
+ }
+};
+
/// Like StringOrBuffer but actually returns a Node.js Buffer
pub const StringOrNodeBuffer = union(Tag) {
string: string,
@@ -388,14 +443,7 @@ pub const SliceOrBuffer = union(Tag) {
const encoding: Encoding = brk: {
if (encoding_value.isEmptyOrUndefinedOrNull())
break :brk .utf8;
- var encoding_str = encoding_value.toSlice(global, allocator);
- if (encoding_str.len == 0)
- break :brk .utf8;
-
- defer encoding_str.deinit();
-
- // TODO: better error
- break :brk Encoding.from(encoding_str.slice()) orelse return null;
+ break :brk Encoding.fromJS(encoding_value, global) orelse .utf8;
};
if (encoding == .utf8) {
@@ -431,6 +479,21 @@ pub const Encoding = enum(u8) {
/// Refer to the buffer's encoding
buffer,
+ pub const map = bun.ComptimeStringMap(Encoding, .{
+ .{ "utf-8", Encoding.utf8 },
+ .{ "utf8", Encoding.utf8 },
+ .{ "ucs-2", Encoding.utf16le },
+ .{ "utf16-le", Encoding.utf16le },
+ .{ "utf16le", Encoding.utf16le },
+ .{ "binary", Encoding.latin1 },
+ .{ "latin1", Encoding.latin1 },
+ .{ "ascii", Encoding.ascii },
+ .{ "base64", Encoding.base64 },
+ .{ "hex", Encoding.hex },
+ .{ "buffer", Encoding.buffer },
+ .{ "base64url", Encoding.base64url },
+ });
+
pub fn isBinaryToText(this: Encoding) bool {
return switch (this) {
.hex, .base64, .base64url => true,
@@ -438,39 +501,18 @@ pub const Encoding = enum(u8) {
};
}
- const Eight = strings.ExactSizeMatcher(8);
/// Caller must verify the value is a string
- pub fn fromStringValue(value: JSC.JSValue, global: *JSC.JSGlobalObject) ?Encoding {
- var sliced = value.toSlice(global, bun.default_allocator);
- defer sliced.deinit();
- return from(sliced.slice());
+ pub fn fromJS(value: JSC.JSValue, global: *JSC.JSGlobalObject) ?Encoding {
+ if (bun.String.tryFromJS(value, global)) |str| {
+ return str.inMapCaseInsensitive(map);
+ }
+
+ return null;
}
/// Caller must verify the value is a string
pub fn from(slice: []const u8) ?Encoding {
- return switch (slice.len) {
- 0...2 => null,
- else => switch (Eight.matchLower(slice)) {
- Eight.case("utf-8"), Eight.case("utf8") => Encoding.utf8,
- Eight.case("ucs-2"), Eight.case("ucs2") => Encoding.ucs2,
- Eight.case("utf16-le"), Eight.case("utf16le") => Encoding.utf16le,
-
- // "binary" is an alias for "latin1"
- Eight.case("binary"), Eight.case("latin1") => Encoding.latin1,
-
- Eight.case("ascii") => Encoding.ascii,
- Eight.case("base64") => Encoding.base64,
- Eight.case("hex") => Encoding.hex,
- Eight.case("buffer") => Encoding.buffer,
- else => null,
- },
- "base64url".len => brk: {
- if (strings.eqlCaseInsensitiveASCII(slice, "base64url", false)) {
- break :brk Encoding.base64url;
- }
- break :brk null;
- },
- };
+ return strings.inMapCaseInsensitive(slice, map);
}
pub fn encodeWithSize(encoding: Encoding, globalThis: *JSC.JSGlobalObject, comptime size: usize, input: *const [size]u8) JSC.JSValue {
@@ -557,17 +599,23 @@ pub fn CallbackTask(comptime Result: type) type {
}
pub const PathLike = union(Tag) {
- string: PathString,
+ string: bun.PathString,
buffer: Buffer,
- url: void,
+ slice_with_underlying_string: bun.SliceWithUnderlyingString,
- pub const Tag = enum { string, buffer, url };
+ pub const Tag = enum { string, buffer, slice_with_underlying_string };
+
+ pub fn deinit(this: *const PathLike) void {
+ if (this.* == .slice_with_underlying_string) {
+ this.slice_with_underlying_string.deinit();
+ }
+ }
pub inline fn slice(this: PathLike) string {
return switch (this) {
.string => this.string.slice(),
.buffer => this.buffer.slice(),
- else => unreachable, // TODO:
+ .slice_with_underlying_string => this.slice_with_underlying_string.slice(),
};
}
@@ -603,6 +651,7 @@ pub const PathLike = union(Tag) {
return switch (this) {
.string => this.string.toJS(ctx, exception),
.buffer => this.buffer.toJSObjectRef(ctx, exception),
+ .slice_with_underlying_string => this.slice_with_underlying_string.toJS(ctx).asObjectRef(),
else => unreachable,
};
}
@@ -638,31 +687,35 @@ pub const PathLike = union(Tag) {
JSC.JSValue.JSType.StringObject,
JSC.JSValue.JSType.DerivedStringObject,
=> {
- var zig_str = arg.toSlice(ctx, allocator);
+ var str = arg.toBunString(ctx);
+
+ arguments.eat();
- if (!Valid.pathSlice(zig_str, ctx, exception)) {
- zig_str.deinit();
+ if (!Valid.pathStringLength(str.length(), ctx, exception)) {
return null;
}
- arguments.eat();
- arg.ensureStillAlive();
+ str.ref();
- return PathLike{ .string = PathString.init(zig_str.slice()) };
+ return PathLike{ .slice_with_underlying_string = str.toSlice(allocator) };
},
else => {
if (arg.as(JSC.DOMURL)) |domurl| {
- var zig_str = domurl.pathname();
- if (!Valid.pathString(zig_str, ctx, exception)) return null;
-
- arguments.protectEat();
+ const path_str: bun.String = domurl.fileSystemPath();
+ if (path_str.isEmpty()) {
+ JSC.throwInvalidArguments("URL must be a non-empty \"file:\" path", .{}, ctx, exception);
+ return null;
+ }
+ arguments.eat();
- if (zig_str.is16Bit()) {
- var printed = bun.asByteSlice(std.fmt.allocPrintZ(arguments.arena.allocator(), "{}", .{zig_str}) catch unreachable);
- return PathLike{ .string = PathString.init(printed.ptr[0 .. printed.len + 1]) };
+ if (!Valid.pathStringLength(path_str.length(), ctx, exception)) {
+ defer path_str.deref();
+ return null;
}
- return PathLike{ .string = PathString.init(zig_str.slice()) };
+ return PathLike{
+ .slice_with_underlying_string = path_str.toSlice(allocator),
+ };
}
return null;
@@ -703,8 +756,8 @@ pub const Valid = struct {
unreachable;
}
- pub fn pathString(zig_str: JSC.ZigString, ctx: JSC.C.JSContextRef, exception: JSC.C.ExceptionRef) bool {
- switch (zig_str.len) {
+ pub fn pathStringLength(len: usize, ctx: JSC.C.JSContextRef, exception: JSC.C.ExceptionRef) bool {
+ switch (len) {
0 => {
JSC.throwInvalidArguments("Invalid path string: can't be empty", .{}, ctx, exception);
return false;
@@ -725,6 +778,10 @@ pub const Valid = struct {
unreachable;
}
+ pub fn pathString(zig_str: JSC.ZigString, ctx: JSC.C.JSContextRef, exception: JSC.C.ExceptionRef) bool {
+ return pathStringLength(zig_str.len, ctx, exception);
+ }
+
pub fn pathBuffer(buffer: Buffer, ctx: JSC.C.JSContextRef, exception: JSC.C.ExceptionRef) bool {
const slice = buffer.slice();
switch (slice.len) {
@@ -900,6 +957,14 @@ pub const PathOrFileDescriptor = union(Tag) {
pub const Tag = enum { fd, path };
+ /// This will unref() the path string if it is a PathLike.
+ /// Does nothing for file descriptors, **does not** close file descriptors.
+ pub fn deinit(this: PathOrFileDescriptor) void {
+ if (this == .path) {
+ this.path.deinit();
+ }
+ }
+
pub fn hash(this: JSC.Node.PathOrFileDescriptor) u64 {
return switch (this) {
.path => std.hash.Wyhash.hash(0, this.path.slice()),
@@ -1339,7 +1404,7 @@ pub const Stats = union(enum) {
/// closed after the iterator exits.
/// @since v12.12.0
pub const Dirent = struct {
- name: PathString,
+ name: bun.String,
// not publicly exposed
kind: Kind,
@@ -1352,7 +1417,7 @@ pub const Dirent = struct {
}
pub fn getName(this: *Dirent, globalObject: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue {
- return JSC.ZigString.fromUTF8(this.name.slice()).toValueGC(globalObject);
+ return this.name.toJS(globalObject);
}
pub fn isBlockDevice(
@@ -1406,7 +1471,7 @@ pub const Dirent = struct {
}
pub fn finalize(this: *Dirent) callconv(.C) void {
- bun.default_allocator.free(this.name.slice());
+ this.name.deref();
bun.default_allocator.destroy(this);
}
};
diff --git a/src/bun.zig b/src/bun.zig
index ab3b3e18c..f2d76df53 100644
--- a/src/bun.zig
+++ b/src/bun.zig
@@ -1525,6 +1525,7 @@ pub const StringPointer = Schema.Api.StringPointer;
pub const StandaloneModuleGraph = @import("./standalone_bun.zig").StandaloneModuleGraph;
pub const String = @import("./string.zig").String;
+pub const SliceWithUnderlyingString = @import("./string.zig").SliceWithUnderlyingString;
pub const WTF = struct {
/// The String type from WebKit's WTF library.
diff --git a/src/comptime_string_map.zig b/src/comptime_string_map.zig
index 01b5de5b0..e60fcb1cb 100644
--- a/src/comptime_string_map.zig
+++ b/src/comptime_string_map.zig
@@ -77,6 +77,8 @@ pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, co
break :blk k[0..];
};
+ pub const Value = V;
+
pub fn keys() []const []const KeyType {
return keys_list;
}
@@ -131,6 +133,25 @@ pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, co
return null;
}
+ pub fn getWithLengthAndEqlList(str: anytype, comptime len: usize, comptime eqls: anytype) ?V {
+ const end = comptime brk: {
+ var i = len_indexes[len];
+ @setEvalBranchQuota(99999);
+
+ while (i < kvs.len and kvs[i].key.len == len) : (i += 1) {}
+
+ break :brk i;
+ };
+
+ const start = comptime len_indexes[len];
+ const range = comptime keys()[start..end];
+ if (eqls(str, range)) |k| {
+ return kvs[start + k].value;
+ }
+
+ return null;
+ }
+
pub fn get(str: []const KeyType) ?V {
if (str.len < precomputed.min_len or str.len > precomputed.max_len)
return null;
@@ -160,6 +181,22 @@ pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, co
return null;
}
+
+ pub fn getWithEqlList(input: anytype, comptime eql: anytype) ?V {
+ const Input = @TypeOf(input);
+ const length = if (comptime std.meta.trait.isSlice(Input) or std.meta.trait.isZigString(Input)) input.len else input.length();
+ if (length < precomputed.min_len or length > precomputed.max_len)
+ return null;
+
+ comptime var i: usize = precomputed.min_len;
+ inline while (i <= precomputed.max_len) : (i += 1) {
+ if (length == i) {
+ return getWithLengthAndEqlList(input, i, eql);
+ }
+ }
+
+ return null;
+ }
};
}
diff --git a/src/string.zig b/src/string.zig
index f09428f69..cf4109d40 100644
--- a/src/string.zig
+++ b/src/string.zig
@@ -250,6 +250,15 @@ pub const String = extern struct {
return BunString__fromBytes(bytes.ptr, bytes.len);
}
+ pub fn isEmpty(this: String) bool {
+ return this.tag == .Empty or this.length() == 0;
+ }
+
+ pub fn dupeRef(this: String) String {
+ this.ref();
+ return this;
+ }
+
pub fn initWithType(comptime Type: type, value: Type) String {
switch (comptime Type) {
ZigString => return String{ .tag = .ZigString, .value = .{ .ZigString = value } },
@@ -337,6 +346,24 @@ pub const String = extern struct {
return BunString__toJS(globalObject, this);
}
+ pub fn toJSConst(this: *const String, globalObject: *bun.JSC.JSGlobalObject) JSC.JSValue {
+ JSC.markBinding(@src());
+ var a = this.*;
+ return toJS(&a, globalObject);
+ }
+
+ extern fn BunString__createArray(
+ globalObject: *bun.JSC.JSGlobalObject,
+ ptr: [*]const String,
+ len: usize,
+ ) JSC.JSValue;
+
+ pub fn toJSArray(globalObject: *bun.JSC.JSGlobalObject, array: []const bun.String) JSC.JSValue {
+ JSC.markBinding(@src());
+
+ return BunString__createArray(globalObject, array.ptr, array.len);
+ }
+
pub fn toZigString(this: String) ZigString {
if (this.tag == .StaticZigString or this.tag == .ZigString) {
return this.value.ZigString;
@@ -449,6 +476,13 @@ pub const String = extern struct {
return ZigString.Slice.empty;
}
+ pub fn toSlice(this: String, allocator: std.mem.Allocator) SliceWithUnderlyingString {
+ return SliceWithUnderlyingString{
+ .utf8 = this.toUTF8(allocator),
+ .underlying = this,
+ };
+ }
+
extern fn BunString__fromJS(globalObject: *JSC.JSGlobalObject, value: bun.JSC.JSValue, out: *String) bool;
extern fn BunString__toJS(globalObject: *JSC.JSGlobalObject, in: *String) JSC.JSValue;
extern fn BunString__toWTFString(this: *String) void;
@@ -473,11 +507,114 @@ pub const String = extern struct {
return this.toZigString().eqlComptime(value);
}
+ pub fn is8Bit(this: String) bool {
+ return switch (this.tag) {
+ .WTFStringImpl => this.value.WTFStringImpl.is8Bit(),
+ .ZigString => !this.value.ZigString.is16Bit(),
+ else => true,
+ };
+ }
+
+ pub fn indexOfComptimeWithCheckLen(this: String, comptime values: []const []const u8, comptime check_len: usize) ?usize {
+ if (this.is8Bit()) {
+ const bytes = this.byteSlice();
+ for (values, 0..) |val, i| {
+ if (bun.strings.eqlComptimeCheckLenWithType(u8, bytes, val, check_len)) {
+ return i;
+ }
+ }
+
+ return null;
+ }
+
+ const u16_bytes = this.byteSlice();
+ inline for (values, 0..) |val, i| {
+ if (bun.strings.eqlComptimeCheckLenWithType(u16, u16_bytes, comptime bun.strings.toUTF16Literal(val), check_len)) {
+ return i;
+ }
+ }
+
+ return null;
+ }
+
+ pub fn indexOfComptimeArrayAssumeSameLength(this: String, comptime values: []const []const u8) ?usize {
+ if (this.is8Bit()) {
+ const bytes = this.byteSlice();
+
+ inline for (0..values.len) |i| {
+ std.debug.assert(bytes.len == values[i].len);
+ if (bun.strings.eqlComptimeCheckLenWithType(u8, bytes, values[i], false)) {
+ return i;
+ }
+ }
+
+ return null;
+ }
+
+ const u16_bytes = this.utf16();
+ var buffer: [values[0].len]u8 = undefined;
+ inline for (0..values[0].len) |i| {
+ const uchar = u16_bytes[i];
+ if (uchar > 255)
+ return null;
+
+ buffer[i] = @intCast(u8, uchar);
+ }
+
+ inline for (0..values.len) |i| {
+ if (bun.strings.eqlComptimeCheckLenWithType(u8, &buffer, values[i], false)) {
+ return i;
+ }
+ }
+
+ return null;
+ }
+
+ pub fn inMap(this: String, comptime ComptimeStringMap: anytype) ?ComptimeStringMap.Value {
+ return ComptimeStringMap.getWithEqlList(this, indexOfComptimeArrayAssumeSameLength);
+ }
+
+ pub fn inMapCaseInsensitive(this: String, comptime ComptimeStringMap: anytype) ?ComptimeStringMap.Value {
+ return ComptimeStringMap.getWithEqlList(this, indexOfComptimeArrayCaseInsensitiveSameLength);
+ }
+
+ pub fn indexOfComptimeArrayCaseInsensitiveSameLength(this: String, comptime values: []const []const u8) ?usize {
+ if (this.is8Bit()) {
+ const bytes = this.byteSlice();
+
+ inline for (0..values.len) |i| {
+ std.debug.assert(bytes.len == values[i].len);
+ if (bun.strings.eqlCaseInsensitiveASCIIIgnoreLength(bytes, values[i])) {
+ return i;
+ }
+ }
+
+ return null;
+ }
+
+ const u16_bytes = this.utf16();
+ const buffer: [values[0].len]u8 = brk: {
+ var bytes: [values[0].len]u8 = undefined;
+ for (&bytes, u16_bytes) |*byte, uchar| {
+ if (uchar > 255)
+ return null;
+
+ byte.* = @intCast(u8, uchar);
+ }
+ break :brk bytes;
+ };
+
+ inline for (0..values.len) |i| {
+ if (bun.strings.eqlCaseInsensitiveASCIIIgnoreLength(&buffer, values[i])) {
+ return i;
+ }
+ }
+
+ return null;
+ }
+
pub fn hasPrefixComptime(this: String, comptime value: []const u8) bool {
- _ = value;
- _ = this;
- return false;
- // return this.toZigString().substring(0, value.len).eqlComptime(value);
+ return this.toZigString().substring(0, value.len).eqlComptime(value);
}
pub fn isWTFAllocator(this: std.mem.Allocator) bool {
@@ -492,3 +629,21 @@ pub const String = extern struct {
return this.toZigString().eql(other.toZigString());
}
};
+
+pub const SliceWithUnderlyingString = struct {
+ utf8: ZigString.Slice,
+ underlying: String,
+
+ pub fn deinit(this: SliceWithUnderlyingString) void {
+ this.utf8.deinit();
+ this.underlying.deref();
+ }
+
+ pub fn slice(this: SliceWithUnderlyingString) []const u8 {
+ return this.utf8.slice();
+ }
+
+ pub fn toJS(this: SliceWithUnderlyingString, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
+ return this.underlying.toJS(globalObject);
+ }
+};
diff --git a/src/string_immutable.zig b/src/string_immutable.zig
index 8aaa6f8e8..9e069bb99 100644
--- a/src/string_immutable.zig
+++ b/src/string_immutable.zig
@@ -74,6 +74,10 @@ pub inline fn containsComptime(self: string, comptime str: string) bool {
}
pub const includes = contains;
+pub fn inMapCaseInsensitive(self: string, comptime ComptimeStringMap: anytype) ?ComptimeStringMap.Value {
+ return bun.String.static(self).inMapCaseInsensitive(ComptimeStringMap);
+}
+
pub inline fn containsAny(in: anytype, target: string) bool {
for (in) |str| if (contains(if (@TypeOf(str) == u8) &[1]u8{str} else bun.span(str), target)) return true;
return false;
@@ -1071,7 +1075,11 @@ pub fn index(self: string, str: string) i32 {
}
pub fn eqlUtf16(comptime self: string, other: []const u16) bool {
- return std.mem.eql(u16, toUTF16Literal(self), other);
+ if (self.len != other.len) return false;
+
+ if (self.len == 0) return true;
+
+ return bun.C.memcmp(bun.cast([*]const u8, self.ptr), bun.cast([*]const u8, other.ptr), self.len * @sizeOf(u16)) == 0;
}
pub fn toUTF8Alloc(allocator: std.mem.Allocator, js: []const u16) !string {
diff --git a/test/js/node/fs/fs.test.ts b/test/js/node/fs/fs.test.ts
index e9cafe957..999547c93 100644
--- a/test/js/node/fs/fs.test.ts
+++ b/test/js/node/fs/fs.test.ts
@@ -26,6 +26,9 @@ import fs, {
constants,
Dirent,
Stats,
+ realpathSync,
+ readlinkSync,
+ symlinkSync,
} from "node:fs";
import _promises from "node:fs/promises";
@@ -126,6 +129,18 @@ describe("mkdirSync", () => {
expect(tempdir.includes(mkdirSync(tempdir, { recursive: true })!)).toBe(true);
expect(existsSync(tempdir)).toBe(true);
});
+
+ it("throws for invalid options", () => {
+ const path = `${tmpdir()}/${Date.now()}.rm.dir2/foo/bar`;
+
+ expect(() =>
+ mkdirSync(
+ path,
+ // @ts-expect-error
+ { recursive: "lalala" },
+ ),
+ ).toThrow("recursive must be a boolean");
+ });
});
it("readdirSync on import.meta.dir", () => {
@@ -319,7 +334,25 @@ describe("readFileSync", () => {
it("works with a file url", () => {
gc();
- const text = readFileSync(new URL("file://" + import.meta.dir + "/readFileSync.txt"), "utf8");
+ const text = readFileSync(new URL("./readFileSync.txt", import.meta.url), "utf8");
+ gc();
+ expect(text).toBe("File read successfully");
+ });
+
+ it("works with a file path which contains spaces", async () => {
+ gc();
+ const outpath = join(tmpdir(), "read file sync with space characters " + Math.random().toString(32) + " .txt");
+ await Bun.write(outpath, Bun.file(Bun.fileURLToPath(new URL("./readFileSync.txt", import.meta.url))));
+ const text = readFileSync(outpath, "utf8");
+ gc();
+ expect(text).toBe("File read successfully");
+ });
+
+ it("works with a file URL which contains spaces", async () => {
+ gc();
+ const outpath = join(tmpdir(), "read file sync with space characters " + Math.random().toString(32) + " .txt");
+ await Bun.write(outpath, Bun.file(Bun.fileURLToPath(new URL("./readFileSync.txt", import.meta.url))));
+ const text = readFileSync(new URL(outpath, import.meta.url), "utf8");
gc();
expect(text).toBe("File read successfully");
});
@@ -458,6 +491,28 @@ describe("lstat", () => {
});
});
+it("symlink", () => {
+ const actual = join(tmpdir(), Math.random().toString(32) + "-fs-symlink.txt");
+ try {
+ unlinkSync(actual);
+ } catch (e) {}
+
+ symlinkSync(import.meta.path, actual);
+
+ expect(realpathSync(actual)).toBe(realpathSync(import.meta.path));
+});
+
+it("readlink", () => {
+ const actual = join(tmpdir(), Math.random().toString(32) + "-fs-readlink.txt");
+ try {
+ unlinkSync(actual);
+ } catch (e) {}
+
+ symlinkSync(import.meta.path, actual);
+
+ expect(readlinkSync(actual)).toBe(realpathSync(import.meta.path));
+});
+
describe("stat", () => {
it("file metadata is correct", () => {
const fileStats = statSync(new URL("./fs-stream.js", import.meta.url).toString().slice("file://".length - 1));
diff --git a/test/js/third_party/prisma/.gitignore b/test/js/third_party/prisma/.gitignore
new file mode 100644
index 000000000..cc4c7e795
--- /dev/null
+++ b/test/js/third_party/prisma/.gitignore
@@ -0,0 +1 @@
+**/client
diff --git a/test/js/third_party/prisma/prisma/sqlite/migrations/20230618153912_init/migration.sql b/test/js/third_party/prisma/prisma/sqlite/migrations/20230618153912_init/migration.sql
new file mode 100644
index 000000000..c4ab82f18
--- /dev/null
+++ b/test/js/third_party/prisma/prisma/sqlite/migrations/20230618153912_init/migration.sql
@@ -0,0 +1,18 @@
+-- CreateTable
+CREATE TABLE "User" (
+ "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ "testId" INTEGER NOT NULL,
+ "email" TEXT NOT NULL,
+ "name" TEXT
+);
+
+-- CreateTable
+CREATE TABLE "Post" (
+ "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ "testId" INTEGER NOT NULL,
+ "title" TEXT NOT NULL,
+ "content" TEXT,
+ "published" BOOLEAN NOT NULL DEFAULT false,
+ "authorId" INTEGER NOT NULL,
+ CONSTRAINT "Post_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
+);
diff --git a/test/js/third_party/prisma/prisma/sqlite/migrations/migration_lock.toml b/test/js/third_party/prisma/prisma/sqlite/migrations/migration_lock.toml
new file mode 100644
index 000000000..e5e5c4705
--- /dev/null
+++ b/test/js/third_party/prisma/prisma/sqlite/migrations/migration_lock.toml
@@ -0,0 +1,3 @@
+# Please do not edit this file manually
+# It should be added in your version-control system (i.e. Git)
+provider = "sqlite" \ No newline at end of file