Compare commits

...

1 Commits

Author SHA1 Message Date
Jarred Sumner
bda25b6b2d Starting to Brotli 2024-01-09 23:31:50 -08:00
12 changed files with 719 additions and 32 deletions

View File

@@ -1,7 +1,12 @@
const bun = @import("root").bun;
const std = @import("std");
const c = @import("./deps/brotli_decoder.zig");
const c = struct {
pub usingnamespace @import("./deps/brotli_decoder.zig");
pub usingnamespace @import("./deps/brotli_encoder.zig");
};
const BrotliDecoder = c.BrotliDecoder;
const BrotliEncoder = c.BrotliEncoder;
const mimalloc = bun.Mimalloc;
@@ -15,7 +20,7 @@ const BrotliAllocator = struct {
return mimalloc.mi_malloc(len) orelse unreachable;
}
pub fn free(_: ?*anyopaque, data: *anyopaque) callconv(.C) void {
pub fn free(_: ?*anyopaque, data: ?*anyopaque) callconv(.C) void {
if (comptime bun.is_heap_breakdown_enabled) {
const zone = bun.HeapBreakdown.malloc_zone_t.get(BrotliAllocator);
zone.malloc_zone_free(data);
@@ -26,7 +31,7 @@ const BrotliAllocator = struct {
}
};
pub const Options = struct {
pub const DecoderOptions = struct {
pub const Params = std.enums.EnumFieldStruct(c.BrotliDecoderParameter, bool, false);
params: Params = Params{
@@ -54,7 +59,7 @@ pub const BrotliReaderArrayList = struct {
pub usingnamespace bun.New(BrotliReaderArrayList);
pub fn initWithOptions(input: []const u8, list: *std.ArrayListUnmanaged(u8), allocator: std.mem.Allocator, options: Options) !*BrotliReaderArrayList {
pub fn initWithOptions(input: []const u8, list: *std.ArrayListUnmanaged(u8), allocator: std.mem.Allocator, options: DecoderOptions) !*BrotliReaderArrayList {
if (!BrotliDecoder.initializeBrotli()) {
return error.BrotliFailedToLoad;
}
@@ -163,3 +168,98 @@ pub const BrotliReaderArrayList = struct {
this.destroy();
}
};
pub const BrotliCompressionStream = struct {
pub const State = enum {
Inflating,
End,
Error,
};
brotli: *BrotliEncoder,
state: State = State.Inflating,
total_out: usize = 0,
total_in: usize = 0,
pub fn init() !BrotliCompressionStream {
const instance = BrotliEncoder.createInstance(&BrotliAllocator.alloc, &BrotliAllocator.free, null) orelse return error.BrotliFailedToCreateInstance;
return BrotliCompressionStream{
.brotli = instance,
};
}
pub fn writeChunk(this: *BrotliCompressionStream, input: []const u8, last: bool) ![]const u8 {
const result = this.brotli.compressStream(if (last) BrotliEncoder.Operation.finish else .process, input);
if (!result.success) {
this.state = .Error;
return error.BrotliCompressionError;
}
return result.output;
}
pub fn write(this: *BrotliCompressionStream, input: []const u8, last: bool) ![]const u8 {
if (this.state == .End or this.state == .Error) {
return "";
}
return this.writeChunk(input, last);
}
pub fn end(this: *BrotliCompressionStream) ![]const u8 {
defer this.state = .End;
return try this.write("", true);
}
pub fn deinit(this: *BrotliCompressionStream) void {
this.brotli.destroyInstance();
}
fn NewWriter(comptime InputWriter: type) type {
return struct {
compressor: *BrotliCompressionStream,
input_writer: InputWriter,
const Self = @This();
pub fn init(compressor: *BrotliCompressionStream, input_writer: InputWriter) Self {
return Self{
.compressor = compressor,
.input_writer = input_writer,
};
}
pub const WriteError = error{BrotliCompressionError} || InputWriter.Error;
pub fn write(self: Self, to_compress: []const u8) WriteError!usize {
const decompressed = try self.compressor.write(to_compress, false);
try self.input_writer.writeAll(decompressed);
return to_compress.len;
}
pub fn end(self: Self) !usize {
const decompressed = try self.compressor.end();
try self.input_writer.writeAll(decompressed);
}
pub const Writer = std.io.Writer(@This(), WriteError, Self.write);
pub fn writer(self: Self) Writer {
return Writer{
.context = self,
};
}
};
}
pub fn writerContext(this: *BrotliCompressionStream, writable: anytype) NewWriter(@TypeOf(writable)) {
return NewWriter(@TypeOf(writable)).init(this, writable);
}
pub fn writer(this: *BrotliCompressionStream, writable: anytype) NewWriter(@TypeOf(writable)).Writer {
return this.writerContext(writable).writer();
}
};

View File

@@ -0,0 +1,63 @@
import { define } from "../../codegen/class-definitions";
export default [
define({
name: "BrotliEncoder",
construct: true,
noConstructor: true,
finalize: true,
configurable: false,
hasPendingActivity: true,
klass: {},
JSType: "0b11101110",
values: ["callback"],
proto: {
encode: {
fn: "encode",
length: 2,
},
encodeSync: {
fn: "encodeSync",
length: 2,
},
end: {
fn: "end",
length: 2,
},
endSync: {
fn: "endSync",
length: 2,
},
},
}),
define({
name: "BrotliDecoder",
construct: true,
noConstructor: true,
finalize: true,
configurable: false,
hasPendingActivity: true,
klass: {},
JSType: "0b11101110",
values: ["callback"],
proto: {
decode: {
fn: "decode",
length: 2,
},
decodeSync: {
fn: "decodeSync",
length: 2,
},
end: {
fn: "end",
length: 2,
},
endSync: {
fn: "endSync",
length: 2,
},
},
}),
];

354
src/bun.js/api/brotli.zig Normal file
View File

@@ -0,0 +1,354 @@
const bun = @import("root").bun;
const JSC = bun.JSC;
const std = @import("std");
const brotli = bun.brotli;
const Queue = std.fifo.LinearFifo(JSC.Node.BlobOrStringOrBuffer, .Dynamic);
fn ConcurrentByteProcessor(comptime Processor: type) type {
_ = Processor; // autofix
return struct {};
}
pub const BrotliEncoder = struct {
pub usingnamespace bun.NewRefCounted(@This(), deinit);
pub usingnamespace JSC.Codegen.JSBrotliEncoder;
stream: brotli.BrotliCompressionStream,
freelist: Queue = Queue.init(bun.default_allocator),
freelist_write_lock: bun.Lock = bun.Lock.init(),
globalObject: *JSC.JSGlobalObject,
input: Queue = Queue.init(bun.default_allocator),
input_lock: bun.Lock = bun.Lock.init(),
has_called_end: bool = false,
callback_value: JSC.Strong = .{},
output: std.ArrayListUnmanaged(u8) = .{},
output_lock: bun.Lock = bun.Lock.init(),
has_pending_activity: std.atomic.Value(u32) = std.atomic.Value(u32).init(0),
pending_encode_job_count: std.atomic.Value(u32) = std.atomic.Value(u32).init(0),
ref_count: u32 = 1,
write_failed: bool = false,
poll_ref: bun.Async.KeepAlive = bun.Async.KeepAlive{},
pub fn hasPendingActivity(this: *BrotliEncoder) callconv(.C) bool {
return this.has_pending_activity.load(.Monotonic) > 0;
}
pub fn constructor(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) ?*BrotliEncoder {
globalThis.throw("BrotliEncoder is not constructable", .{});
return null;
}
pub fn create(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
const arguments = callframe.arguments(3).slice();
if (arguments.len < 3) {
globalThis.throwNotEnoughArguments("BrotliEncoder", 3, arguments.len);
return .zero;
}
const callback = arguments[2];
if (!callback.isCallable(globalThis.vm())) {
globalThis.throwInvalidArguments("BrotliEncoder callback is not callable", .{});
return .zero;
}
var this: *BrotliEncoder = BrotliEncoder.new(.{
.globalObject = globalThis,
.stream = brotli.BrotliCompressionStream.init() catch {
globalThis.throw("Failed to create BrotliEncoder", .{});
return .zero;
},
});
const out = this.toJS(globalThis);
@This().callbackSetCached(out, globalThis, callback);
this.callback_value.set(globalThis, callback);
return out;
}
pub fn finalize(this: *BrotliEncoder) callconv(.C) void {
this.deref();
}
pub fn deinit(this: *BrotliEncoder) void {
this.callback_value.deinit();
this.drainFreelist();
this.output.deinit(bun.default_allocator);
this.stream.deinit();
this.input.deinit();
}
fn drainFreelist(this: *BrotliEncoder) void {
this.freelist_write_lock.lock();
defer this.freelist_write_lock.unlock();
const to_free = this.freelist.readableSlice(0);
for (to_free) |*input| {
input.deinit();
}
this.freelist.discard(to_free.len);
}
pub fn runFromJSThread(this: *BrotliEncoder) void {
this.poll_ref.unref(this.globalObject.bunVM());
defer {
this.deref();
}
this.drainFreelist();
const value = brk: {
this.output_lock.lock();
defer this.output_lock.unlock();
if (this.output.items.len == 0)
return;
if (this.output.items.len > 16 * 1024) {
defer this.output.items = &.{};
break :brk JSC.JSValue.createBuffer(this.globalObject, this.output.items, bun.default_allocator);
} else {
defer this.output.clearRetainingCapacity();
break :brk JSC.ArrayBuffer.createBuffer(this.globalObject, this.output.items);
}
};
const result = this.callback_value.get().?.call(this.globalObject, &.{
if (this.write_failed)
this.globalObject.createErrorInstance("BrotliError", .{})
else
JSC.JSValue.null,
value,
});
if (result.toError()) |err| {
this.globalObject.bunVM().runErrorHandler(err, null);
}
}
// We can only run one encode job at a time
// But we don't have an idea of a serial dispatch queue
// So instead, we let you enqueue as many times as you want
// and if one is already running, we just don't do anything
const EncodeJob = struct {
task: JSC.WorkPoolTask = .{ .callback = &runTask },
encoder: *BrotliEncoder,
pub usingnamespace bun.New(@This());
pub fn run(this: *EncodeJob) void {
defer {
_ = this.encoder.has_pending_activity.fetchSub(1, .Monotonic);
this.encoder.deref();
this.destroy();
}
var any = false;
if (this.encoder.pending_encode_job_count.fetchAdd(1, .Monotonic) == 0) {
var is_last = false;
while (true) {
const pending: []bun.JSC.Node.BlobOrStringOrBuffer = brk: {
this.encoder.input_lock.lock();
defer this.encoder.input_lock.unlock();
is_last = this.encoder.has_called_end;
const readable = this.encoder.input.readableSlice(0);
const out = bun.default_allocator.dupe(std.meta.Child(@TypeOf(readable)), readable) catch bun.outOfMemory();
this.encoder.input.discard(readable.len);
break :brk out;
};
defer bun.default_allocator.free(pending);
const Writer = struct {
encoder: *BrotliEncoder,
pub const Error = error{OutOfMemory};
pub fn writeAll(writer: @This(), chunk: []const u8) Error!void {
writer.encoder.output_lock.lock();
defer writer.encoder.output_lock.unlock();
try writer.encoder.output.appendSlice(bun.default_allocator, chunk);
}
};
defer {
this.encoder.freelist_write_lock.lock();
this.encoder.freelist.write(pending) catch unreachable;
this.encoder.freelist_write_lock.unlock();
}
for (pending) |input| {
var writer = this.encoder.stream.writer(Writer{ .encoder = this.encoder });
writer.writeAll(input.slice()) catch {
_ = this.encoder.pending_encode_job_count.fetchSub(1, .Monotonic);
this.encoder.write_failed = true;
return;
};
}
any = any or pending.len > 0;
if (this.encoder.pending_encode_job_count.fetchSub(1, .Monotonic) == 0)
break;
}
if (is_last and any) {
var output = &this.encoder.output;
this.encoder.output_lock.lock();
defer {
this.encoder.output_lock.unlock();
}
output.appendSlice(bun.default_allocator, this.encoder.stream.end() catch {
_ = this.encoder.pending_encode_job_count.fetchSub(1, .Monotonic);
this.encoder.write_failed = true;
return;
}) catch {
_ = this.encoder.pending_encode_job_count.fetchSub(1, .Monotonic);
this.encoder.write_failed = true;
return;
};
}
}
if (any) {
var vm = this.encoder.globalObject.bunVMConcurrently();
this.encoder.ref();
this.encoder.poll_ref.refConcurrently(vm);
vm.enqueueTaskConcurrent(JSC.ConcurrentTask.create(JSC.Task.init(this.encoder)));
}
}
pub fn runTask(this: *JSC.WorkPoolTask) void {
var job: *EncodeJob = @fieldParentPtr(EncodeJob, "task", this);
job.run();
}
};
pub fn encode(this: *BrotliEncoder, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
const arguments = callframe.arguments(3);
if (arguments.len < 2) {
globalObject.throwNotEnoughArguments("BrotliEncoder.encode", 2, arguments.len);
return .zero;
}
if (this.has_called_end) {
globalObject.throw("BrotliEncoder.encode called after BrotliEncoder.end", .{});
return .zero;
}
const input = callframe.argument(0);
const optional_encoding = callframe.argument(1);
const is_last = callframe.argument(2).toBoolean();
const input_to_queue = JSC.Node.BlobOrStringOrBuffer.fromJSWithEncodingValueMaybeAsync(globalObject, bun.default_allocator, input, optional_encoding, true) orelse {
globalObject.throwInvalidArgumentType("BrotliEncoder.encode", "input", "Blob, String, or Buffer");
return .zero;
};
_ = this.has_pending_activity.fetchAdd(1, .Monotonic);
if (is_last)
this.has_called_end = true;
var task = EncodeJob.new(.{
.encoder = this,
});
{
this.input_lock.lock();
defer this.input_lock.unlock();
this.input.writeItem(input_to_queue) catch unreachable;
}
this.ref();
JSC.WorkPool.schedule(&task.task);
return .undefined;
}
pub fn encodeSync(this: *BrotliEncoder, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
_ = this;
_ = globalObject;
_ = callframe;
return .zero;
}
pub fn end(this: *BrotliEncoder, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
_ = this;
_ = globalObject;
_ = callframe;
return .zero;
}
pub fn endSync(this: *BrotliEncoder, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
_ = this;
_ = globalObject;
_ = callframe;
return .zero;
}
};
pub const BrotliDecoder = struct {
pub usingnamespace bun.NewRefCounted(@This(), deinit);
pub usingnamespace JSC.Codegen.JSBrotliDecoder;
has_pending_activity: std.atomic.Value(u32) = std.atomic.Value(u32).init(0),
ref_count: u32 = 1,
pub fn hasPendingActivity(this: *BrotliDecoder) callconv(.C) bool {
return this.has_pending_activity.load(.Monotonic) > 0;
}
pub fn deinit(this: *BrotliDecoder) void {
this.destroy();
}
pub fn constructor(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) ?*BrotliDecoder {
globalThis.throw("Crypto is not constructable", .{});
return null;
}
pub fn finalize(this: *BrotliDecoder) callconv(.C) void {
this.destroy();
}
pub fn decode(this: *BrotliDecoder, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
_ = this;
_ = globalObject;
_ = callframe;
return .zero;
}
pub fn decodeSync(this: *BrotliDecoder, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
_ = this;
_ = globalObject;
_ = callframe;
return .zero;
}
pub fn end(this: *BrotliDecoder, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
_ = this;
_ = globalObject;
_ = callframe;
return .zero;
}
pub fn endSync(this: *BrotliDecoder, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
_ = this;
_ = globalObject;
_ = callframe;
return .zero;
}
};
pub fn exportAll() void {
@export(BrotliEncoder.create, .{ .name = "BrotliEncoder__createFromJS" });
}

View File

@@ -4916,5 +4916,6 @@ comptime {
_ = Crypto.JSPasswordObject.JSPasswordObject__create;
BunObject.exportAll();
@export(InternalTestingAPIs.BunInternalFunction__syntaxHighlighter, .{ .name = "BunInternalFunction__syntaxHighlighter" });
@import("./brotli.zig").exportAll();
}
}

View File

@@ -1701,7 +1701,7 @@ JSC_DEFINE_HOST_FUNCTION(jsReceiveMessageOnPort, (JSGlobalObject * lexicalGlobal
}
extern "C" EncodedJSValue BunInternalFunction__syntaxHighlighter(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame);
extern "C" EncodedJSValue BrotliEncoder__createFromJS(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame);
// we're trying out a new way to do this lazy loading
// this is $lazy() in js code
JSC_DEFINE_HOST_FUNCTION(functionLazyLoad,
@@ -1894,6 +1894,15 @@ JSC_DEFINE_HOST_FUNCTION(functionLazyLoad,
vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "getUnpackedSettings"_s)), JSC::JSFunction::create(vm, globalObject, 1, "getUnpackedSettings"_s, BUN__HTTP2__getUnpackedSettings, ImplementationVisibility::Public, NoIntrinsic), 0);
return JSValue::encode(obj);
}
if (string == "internal/zlib"_s) {
auto* obj = constructEmptyObject(globalObject);
obj->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "createBrotliEncoder"_s), 1, BrotliEncoder__createFromJS, ImplementationVisibility::Public, NoIntrinsic, PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly);
return JSValue::encode(obj);
}
if (string == "internal/tls"_s) {
auto* obj = constructEmptyObject(globalObject);

View File

@@ -843,9 +843,7 @@ bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2,
}
return (
left->sanitizedNameString(globalObject) == right->sanitizedNameString(globalObject) &&
left->sanitizedMessageString(globalObject) == right->sanitizedMessageString(globalObject)
);
left->sanitizedNameString(globalObject) == right->sanitizedNameString(globalObject) && left->sanitizedMessageString(globalObject) == right->sanitizedMessageString(globalObject));
}
}
case Int8ArrayType:
@@ -2473,8 +2471,8 @@ JSC__JSValue JSC__JSValue__fromEntries(JSC__JSGlobalObject* globalObject, ZigStr
return JSC::JSValue::encode(object);
}
JSC__JSValue JSC__JSValue__keys(JSC__JSGlobalObject* globalObject, JSC__JSValue objectValue) {
JSC__JSValue JSC__JSValue__keys(JSC__JSGlobalObject* globalObject, JSC__JSValue objectValue)
{
JSC::VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
@@ -2482,16 +2480,15 @@ JSC__JSValue JSC__JSValue__keys(JSC__JSGlobalObject* globalObject, JSC__JSValue
JSC::JSObject* object = JSC::JSValue::decode(objectValue).toObject(globalObject);
RETURN_IF_EXCEPTION(scope, encodedJSValue());
RELEASE_AND_RETURN(scope, JSValue::encode(ownPropertyKeys(globalObject, object, PropertyNameMode::Strings, DontEnumPropertiesMode::Exclude)));
RELEASE_AND_RETURN(scope, JSValue::encode(ownPropertyKeys(globalObject, object, PropertyNameMode::Strings, DontEnumPropertiesMode::Exclude)));
}
bool JSC__JSValue__hasOwnProperty(JSC__JSValue jsValue, JSC__JSGlobalObject* globalObject, ZigString key) {
bool JSC__JSValue__hasOwnProperty(JSC__JSValue jsValue, JSC__JSGlobalObject* globalObject, ZigString key)
{
JSC::VM& vm = globalObject->vm();
JSC::JSValue value = JSC::JSValue::decode(jsValue);
return value.toObject(globalObject)->hasOwnProperty(globalObject, JSC::PropertyName(JSC::Identifier::fromString(vm, Zig::toString(key))));
}
bool JSC__JSValue__asArrayBuffer_(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1,
@@ -3413,7 +3410,7 @@ JSC__JSValue JSC__JSValue__fromTimevalNoTruncate(JSC__JSGlobalObject* globalObje
auto big_nsec = JSC::JSBigInt::createFrom(globalObject, nsec);
auto big_sec = JSC::JSBigInt::createFrom(globalObject, sec);
auto big_1e6 = JSC::JSBigInt::createFrom(globalObject, 1e6);
auto sec_as_nsec = JSC::JSBigInt::multiply(globalObject, big_1e6, big_sec);
auto sec_as_nsec = JSC::JSBigInt::multiply(globalObject, big_1e6, big_sec);
ASSERT(sec_as_nsec.isHeapBigInt());
auto* big_sec_as_nsec = sec_as_nsec.asHeapBigInt();
ASSERT(big_sec_as_nsec);

View File

@@ -64,4 +64,7 @@ pub const Classes = struct {
pub const Crypto = JSC.WebCore.Crypto;
pub const FFI = JSC.FFI;
pub const H2FrameParser = JSC.API.H2FrameParser;
pub const BrotliEncoder = JSC.API.BrotliEncoder;
pub const BrotliDecoder = JSC.API.BrotliDecoder;
};

View File

@@ -337,6 +337,8 @@ const Lchown = JSC.Node.Async.lchown;
const Unlink = JSC.Node.Async.unlink;
const WaitPidResultTask = JSC.Subprocess.WaiterThread.WaitPidResultTask;
const TimerReference = JSC.BunTimer.Timeout.TimerReference;
const BrotliDecoder = JSC.API.BrotliDecoder;
const BrotliEncoder = JSC.API.BrotliEncoder;
// Task.get(ReadFileTask) -> ?ReadFileTask
pub const Task = TaggedPointerUnion(.{
FetchTasklet,
@@ -397,6 +399,7 @@ pub const Task = TaggedPointerUnion(.{
Unlink,
WaitPidResultTask,
TimerReference,
BrotliEncoder,
});
const UnboundedQueue = @import("./unbounded_queue.zig").UnboundedQueue;
pub const ConcurrentTask = struct {
@@ -934,6 +937,10 @@ pub const EventLoop = struct {
var any: *TimerReference = task.get(TimerReference).?;
any.runFromJSThread();
},
@field(Task.Tag, typeBaseName(@typeName(BrotliEncoder))) => {
var any: *BrotliEncoder = task.get(BrotliEncoder).?;
any.runFromJSThread();
},
else => if (Environment.allow_assert) {
bun.Output.prettyln("\nUnexpected tag: {s}\n", .{@tagName(task.tag())});

View File

@@ -205,7 +205,7 @@ pub const BlobOrStringOrBuffer = union(enum) {
return .{ .string_or_buffer = StringOrBuffer.fromJS(global, allocator, value) orelse return null };
}
pub fn fromJSWithEncodingValue(global: *JSC.JSGlobalObject, allocator: std.mem.Allocator, value: JSC.JSValue, encoding_value: JSC.JSValue) ?BlobOrStringOrBuffer {
pub fn fromJSWithEncodingValueMaybeAsync(global: *JSC.JSGlobalObject, allocator: std.mem.Allocator, value: JSC.JSValue, encoding_value: JSC.JSValue, is_async: bool) ?BlobOrStringOrBuffer {
if (value.as(JSC.WebCore.Blob)) |blob| {
if (blob.store) |store| {
store.ref();
@@ -214,7 +214,11 @@ pub const BlobOrStringOrBuffer = union(enum) {
return .{ .blob = blob.* };
}
return .{ .string_or_buffer = StringOrBuffer.fromJSWithEncodingValue(global, allocator, value, encoding_value) orelse return null };
return .{ .string_or_buffer = StringOrBuffer.fromJSWithEncodingValueMaybeAsync(global, allocator, value, encoding_value, is_async) orelse return null };
}
pub fn fromJSWithEncodingValue(global: *JSC.JSGlobalObject, allocator: std.mem.Allocator, value: JSC.JSValue, encoding_value: JSC.JSValue) ?BlobOrStringOrBuffer {
return fromJSWithEncodingValueMaybeAsync(global, allocator, value, encoding_value, false);
}
};
@@ -397,6 +401,16 @@ pub const StringOrBuffer = union(enum) {
return fromJSWithEncoding(global, allocator, value, encoding);
}
pub fn fromJSWithEncodingValueMaybeAsync(global: *JSC.JSGlobalObject, allocator: std.mem.Allocator, value: JSC.JSValue, encoding_value: JSC.JSValue, maybe_async: bool) ?StringOrBuffer {
const encoding: Encoding = brk: {
if (!encoding_value.isCell())
break :brk .utf8;
break :brk Encoding.fromJS(encoding_value, global) orelse .utf8;
};
return fromJSWithEncodingMaybeAsync(global, allocator, value, encoding, maybe_async);
}
};
pub const ErrorCode = @import("./nodejs_error_code.zig").Code;

141
src/deps/brotli_encoder.zig Normal file
View File

@@ -0,0 +1,141 @@
const bun = @import("root").bun;
const std = @import("std");
pub const brotli_alloc_func = ?*const fn (?*anyopaque, usize) callconv(.C) ?*anyopaque;
pub const brotli_free_func = ?*const fn (?*anyopaque, ?*anyopaque) callconv(.C) void;
pub const struct_BrotliSharedDictionaryStruct = opaque {};
pub const BrotliSharedDictionary = struct_BrotliSharedDictionaryStruct;
pub const BROTLI_SHARED_DICTIONARY_RAW: c_int = 0;
pub const BROTLI_SHARED_DICTIONARY_SERIALIZED: c_int = 1;
pub const enum_BrotliSharedDictionaryType = c_uint;
pub const BrotliSharedDictionaryType = enum_BrotliSharedDictionaryType;
extern fn BrotliSharedDictionaryCreateInstance(alloc_func: brotli_alloc_func, free_func: brotli_free_func, @"opaque": ?*anyopaque) ?*BrotliSharedDictionary;
extern fn BrotliSharedDictionaryDestroyInstance(dict: ?*BrotliSharedDictionary) void;
extern fn BrotliSharedDictionaryAttach(dict: ?*BrotliSharedDictionary, @"type": BrotliSharedDictionaryType, data_size: usize, data: [*c]const u8) c_int;
pub const BROTLI_MODE_GENERIC: c_int = 0;
pub const BROTLI_MODE_TEXT: c_int = 1;
pub const BROTLI_MODE_FONT: c_int = 2;
pub const BrotliEncoderMode = enum(c_uint) {
generic = 0,
text = 1,
font = 2,
};
pub const BROTLI_OPERATION_PROCESS: c_int = 0;
pub const BROTLI_OPERATION_FLUSH: c_int = 1;
pub const BROTLI_OPERATION_FINISH: c_int = 2;
pub const BROTLI_OPERATION_EMIT_METADATA: c_int = 3;
pub const BROTLI_PARAM_MODE: c_int = 0;
pub const BROTLI_PARAM_QUALITY: c_int = 1;
pub const BROTLI_PARAM_LGWIN: c_int = 2;
pub const BROTLI_PARAM_LGBLOCK: c_int = 3;
pub const BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING: c_int = 4;
pub const BROTLI_PARAM_SIZE_HINT: c_int = 5;
pub const BROTLI_PARAM_LARGE_WINDOW: c_int = 6;
pub const BROTLI_PARAM_NPOSTFIX: c_int = 7;
pub const BROTLI_PARAM_NDIRECT: c_int = 8;
pub const BROTLI_PARAM_STREAM_OFFSET: c_int = 9;
pub const BrotliEncoderParameter = enum(c_uint) {
mode = 0,
quality = 1,
lgwin = 2,
lgblock = 3,
disable_literal_context_modeling = 4,
size_hint = 5,
large_window = 6,
npostfix = 7,
ndirect = 8,
stream_offset = 9,
};
pub const BrotliEncoder = opaque {
pub const Operation = enum(c_uint) {
process = 0,
flush = 1,
finish = 2,
emit_metadata = 3,
};
extern fn BrotliEncoderSetParameter(state: *BrotliEncoder, param: BrotliEncoderParameter, value: u32) c_int;
extern fn BrotliEncoderCreateInstance(alloc_func: brotli_alloc_func, free_func: brotli_free_func, @"opaque": ?*anyopaque) *BrotliEncoder;
extern fn BrotliEncoderDestroyInstance(state: *BrotliEncoder) void;
pub const struct_BrotliEncoderPreparedDictionaryStruct = opaque {};
pub const BrotliEncoderPreparedDictionary = struct_BrotliEncoderPreparedDictionaryStruct;
extern fn BrotliEncoderPrepareDictionary(@"type": BrotliSharedDictionaryType, data_size: usize, data: [*c]const u8, quality: c_int, alloc_func: brotli_alloc_func, free_func: brotli_free_func, @"opaque": ?*anyopaque) *BrotliEncoderPreparedDictionary;
extern fn BrotliEncoderDestroyPreparedDictionary(dictionary: *BrotliEncoderPreparedDictionary) void;
extern fn BrotliEncoderAttachPreparedDictionary(state: *BrotliEncoder, dictionary: ?*const BrotliEncoderPreparedDictionary) c_int;
extern fn BrotliEncoderMaxCompressedSize(input_size: usize) usize;
extern fn BrotliEncoderCompress(quality: c_int, lgwin: c_int, mode: BrotliEncoderMode, input_size: usize, input_buffer: [*]const u8, encoded_size: *usize, encoded_buffer: [*]u8) c_int;
extern fn BrotliEncoderCompressStream(state: *BrotliEncoder, op: Operation, available_in: *usize, next_in: *?[*]const u8, available_out: *usize, next_out: ?[*]u8, total_out: ?*usize) c_int;
extern fn BrotliEncoderIsFinished(state: *BrotliEncoder) c_int;
extern fn BrotliEncoderHasMoreOutput(state: *BrotliEncoder) c_int;
extern fn BrotliEncoderTakeOutput(state: *BrotliEncoder, size: *usize) ?[*]const u8;
extern fn BrotliEncoderEstimatePeakMemoryUsage(quality: c_int, lgwin: c_int, input_size: usize) usize;
extern fn BrotliEncoderGetPreparedDictionarySize(dictionary: ?*const BrotliEncoderPreparedDictionary) usize;
extern fn BrotliEncoderVersion() u32;
pub fn createInstance(alloc_func: brotli_alloc_func, free_func: brotli_free_func, @"opaque": ?*anyopaque) callconv(.C) ?*BrotliEncoder {
return BrotliEncoderCreateInstance(alloc_func, free_func, @"opaque");
}
pub fn destroyInstance(state: *BrotliEncoder) callconv(.C) void {
return BrotliEncoderDestroyInstance(state);
}
pub fn hasMoreOutput(state: *BrotliEncoder) callconv(.C) bool {
return BrotliEncoderHasMoreOutput(state) > 0;
}
pub fn takeOutput(state: *BrotliEncoder) []const u8 {
var size: usize = 0;
if (BrotliEncoderTakeOutput(state, &size)) |ptr| {
return ptr[0..size];
}
return "";
}
pub const CompressionResult = struct {
success: bool = false,
has_more: bool = false,
output: []const u8 = "",
};
// https://github.com/google/brotli/blob/2ad58d8603294f5ee33d23bb725e0e6a17c1de50/go/cbrotli/writer.go#L23-L40
pub fn compressStream(state: *BrotliEncoder, op: Operation, data: []const u8) CompressionResult {
var available_in = data.len;
var next_in: ?[*]const u8 = data.ptr;
var available_out: usize = 0;
var result = CompressionResult{};
result.success = BrotliEncoderCompressStream(state, op, &available_in, &next_in, &available_out, null, null) > 0;
if (result.success) {
result.output = takeOutput(state);
}
result.has_more = BrotliEncoderHasMoreOutput(state) > 0;
return result;
}
pub fn setParameter(state: *BrotliEncoder, param: BrotliEncoderParameter, value: u32) bool {
return BrotliEncoderSetParameter(state, param, value) > 0;
}
};
pub const SHARED_BROTLI_MIN_DICTIONARY_WORD_LENGTH = 4;
pub const SHARED_BROTLI_MAX_DICTIONARY_WORD_LENGTH = 31;
pub const SHARED_BROTLI_NUM_DICTIONARY_CONTEXTS = 64;
pub const SHARED_BROTLI_MAX_COMPOUND_DICTS = 15;
pub const BROTLI_MIN_WINDOW_BITS = 10;
pub const BROTLI_MAX_WINDOW_BITS = 24;
pub const BROTLI_LARGE_MAX_WINDOW_BITS = 30;
pub const BROTLI_MIN_INPUT_BLOCK_BITS = 16;
pub const BROTLI_MAX_INPUT_BLOCK_BITS = 24;
pub const BROTLI_MIN_QUALITY = 0;
pub const BROTLI_MAX_QUALITY = 11;
pub const BROTLI_DEFAULT_QUALITY = 11;
pub const BROTLI_DEFAULT_WINDOW = 22;
pub const BROTLI_DEFAULT_MODE = BROTLI_MODE_GENERIC;

View File

@@ -8,6 +8,8 @@ const BufferModule = require("node:buffer");
const StreamModule = require("node:stream");
const Util = require("node:util");
const { createBrotliEncoder } = $lazy("internal/zlib");
var __getOwnPropNames = Object.getOwnPropertyNames;
var __commonJS = (cb, mod) =>
function __require() {
@@ -4067,21 +4069,15 @@ var require_lib = __commonJS({
return zlibBufferSync(new InflateRaw(opts), buffer);
};
// not implemented, stubs
for (const method of [
"BrotliCompress",
"BrotliDecompress",
"brotliCompress",
"brotliCompressSync",
"brotliDecompress",
"brotliDecompressSync",
"createBrotliCompress",
"createBrotliDecompress",
]) {
exports[method] = function (buffer, opts, callback) {
throw new Error(`zlib.${method} is not implemented`);
};
}
exports.brotliCompress = function (buffer, opts, callback) {
if (typeof opts === "function") {
callback = opts;
opts = {};
}
const encoder = createBrotliEncoder(opts, {}, callback);
encoder.encode(buffer, undefined, true);
};
function zlibBuffer(engine, buffer, callback) {
var buffers = [];

View File

@@ -47,6 +47,8 @@ pub const API = struct {
pub const TLSSocket = @import("./bun.js/api/bun/socket.zig").TLSSocket;
pub const Listener = @import("./bun.js/api/bun/socket.zig").Listener;
pub const H2FrameParser = @import("./bun.js/api/bun/h2_frame_parser.zig").H2FrameParser;
pub const BrotliEncoder = @import("./bun.js/api/brotli.zig").BrotliEncoder;
pub const BrotliDecoder = @import("./bun.js/api/brotli.zig").BrotliDecoder;
};
pub const DNS = @import("./bun.js/api/bun/dns_resolver.zig");
pub const FFI = @import("./bun.js/api/ffi.zig").FFI;