tigerbeetle-node 0.15.2 → 0.15.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -8
- package/dist/benchmark.js +1 -1
- package/dist/benchmark.js.map +1 -1
- package/dist/bin/aarch64-linux-gnu/client.node +0 -0
- package/dist/bin/aarch64-linux-musl/client.node +0 -0
- package/dist/bin/aarch64-macos/client.node +0 -0
- package/dist/bin/x86_64-linux-gnu/client.node +0 -0
- package/dist/bin/x86_64-linux-musl/client.node +0 -0
- package/dist/bin/x86_64-macos/client.node +0 -0
- package/dist/bin/x86_64-windows/client.node +0 -0
- package/dist/bindings.d.ts +18 -1
- package/dist/bindings.js +8 -1
- package/dist/bindings.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +11 -4
- package/dist/index.js.map +1 -1
- package/dist/test.js +390 -1
- package/dist/test.js.map +1 -1
- package/package.json +2 -3
- package/src/benchmark.ts +2 -1
- package/src/bindings.ts +203 -136
- package/src/index.ts +16 -7
- package/src/test.ts +481 -2
- package/src/translate.zig +24 -7
- package/src/node.zig +0 -529
package/src/node.zig
DELETED
|
@@ -1,529 +0,0 @@
|
|
|
1
|
-
const std = @import("std");
|
|
2
|
-
const assert = std.debug.assert;
|
|
3
|
-
const allocator = std.heap.c_allocator;
|
|
4
|
-
|
|
5
|
-
const c = @import("c.zig");
|
|
6
|
-
const translate = @import("translate.zig");
|
|
7
|
-
const tb = struct {
|
|
8
|
-
pub usingnamespace @import("../../../tigerbeetle.zig");
|
|
9
|
-
pub usingnamespace @import("../../c/tb_client.zig");
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
const Account = tb.Account;
|
|
13
|
-
const AccountFlags = tb.AccountFlags;
|
|
14
|
-
const Transfer = tb.Transfer;
|
|
15
|
-
const TransferFlags = tb.TransferFlags;
|
|
16
|
-
const CreateAccountsResult = tb.CreateAccountsResult;
|
|
17
|
-
const CreateTransfersResult = tb.CreateTransfersResult;
|
|
18
|
-
const AccountFilter = tb.AccountFilter;
|
|
19
|
-
const AccountFilterFlags = tb.AccountFilterFlags;
|
|
20
|
-
const AccountBalance = tb.AccountBalance;
|
|
21
|
-
|
|
22
|
-
const Storage = @import("../../../storage.zig").Storage;
|
|
23
|
-
const StateMachine = @import("../../../state_machine.zig").StateMachineType(Storage, constants.state_machine_config);
|
|
24
|
-
const Operation = StateMachine.Operation;
|
|
25
|
-
const constants = @import("../../../constants.zig");
|
|
26
|
-
const vsr = @import("../../../vsr.zig");
|
|
27
|
-
|
|
28
|
-
pub const std_options = struct {
|
|
29
|
-
// Since this is running in application space, log only critical messages to reduce noise.
|
|
30
|
-
pub const log_level: std.log.Level = .err;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
// Cached value for JS (null).
|
|
34
|
-
var napi_null: c.napi_value = undefined;
|
|
35
|
-
|
|
36
|
-
/// N-API will call this constructor automatically to register the module.
|
|
37
|
-
export fn napi_register_module_v1(env: c.napi_env, exports: c.napi_value) c.napi_value {
|
|
38
|
-
napi_null = translate.capture_null(env) catch return null;
|
|
39
|
-
|
|
40
|
-
translate.register_function(env, exports, "init", init) catch return null;
|
|
41
|
-
translate.register_function(env, exports, "deinit", deinit) catch return null;
|
|
42
|
-
translate.register_function(env, exports, "submit", submit) catch return null;
|
|
43
|
-
return exports;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Add-on code
|
|
47
|
-
|
|
48
|
-
fn init(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {
|
|
49
|
-
const args = translate.extract_args(env, info, .{
|
|
50
|
-
.count = 1,
|
|
51
|
-
.function = "init",
|
|
52
|
-
}) catch return null;
|
|
53
|
-
|
|
54
|
-
const cluster = translate.u128_from_object(env, args[0], "cluster_id") catch return null;
|
|
55
|
-
const concurrency = translate.u32_from_object(env, args[0], "concurrency") catch return null;
|
|
56
|
-
const addresses = translate.slice_from_object(
|
|
57
|
-
env,
|
|
58
|
-
args[0],
|
|
59
|
-
"replica_addresses",
|
|
60
|
-
) catch return null;
|
|
61
|
-
|
|
62
|
-
return create(env, cluster, concurrency, addresses) catch null;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
fn deinit(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {
|
|
66
|
-
const args = translate.extract_args(env, info, .{
|
|
67
|
-
.count = 1,
|
|
68
|
-
.function = "deinit",
|
|
69
|
-
}) catch return null;
|
|
70
|
-
|
|
71
|
-
destroy(env, args[0]) catch {};
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
fn submit(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {
|
|
76
|
-
const args = translate.extract_args(env, info, .{
|
|
77
|
-
.count = 4,
|
|
78
|
-
.function = "submit",
|
|
79
|
-
}) catch return null;
|
|
80
|
-
|
|
81
|
-
const operation_int = translate.u32_from_value(env, args[1], "operation") catch return null;
|
|
82
|
-
if (!@as(vsr.Operation, @enumFromInt(operation_int)).valid(StateMachine)) {
|
|
83
|
-
translate.throw(env, "Unknown operation.") catch return null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
var is_array: bool = undefined;
|
|
87
|
-
if (c.napi_is_array(env, args[2], &is_array) != c.napi_ok) {
|
|
88
|
-
translate.throw(env, "Failed to check array argument type.") catch return null;
|
|
89
|
-
}
|
|
90
|
-
if (!is_array) {
|
|
91
|
-
translate.throw(env, "Array argument must be an [object Array].") catch return null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
var callback_type: c.napi_valuetype = undefined;
|
|
95
|
-
if (c.napi_typeof(env, args[3], &callback_type) != c.napi_ok) {
|
|
96
|
-
translate.throw(env, "Failed to check callback argument type.") catch return null;
|
|
97
|
-
}
|
|
98
|
-
if (callback_type != c.napi_function) {
|
|
99
|
-
translate.throw(env, "Callback argument must be a Function.") catch return null;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
request(
|
|
103
|
-
env,
|
|
104
|
-
args[0], // tb_client
|
|
105
|
-
@enumFromInt(@as(u8, @intCast(operation_int))),
|
|
106
|
-
args[2], // request array
|
|
107
|
-
args[3], // callback
|
|
108
|
-
) catch {};
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// tb_client Logic
|
|
113
|
-
|
|
114
|
-
fn create(env: c.napi_env, cluster_id: u128, concurrency: u32, addresses: []const u8) !c.napi_value {
|
|
115
|
-
var tsfn_name: c.napi_value = undefined;
|
|
116
|
-
if (c.napi_create_string_utf8(env, "tb_client", c.NAPI_AUTO_LENGTH, &tsfn_name) != c.napi_ok) {
|
|
117
|
-
return translate.throw(env, "Failed to create resource name for thread-safe function.");
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
var completion_tsfn: c.napi_threadsafe_function = undefined;
|
|
121
|
-
if (c.napi_create_threadsafe_function(
|
|
122
|
-
env,
|
|
123
|
-
null, // No javascript function to call directly from here.
|
|
124
|
-
null, // No async resource.
|
|
125
|
-
tsfn_name,
|
|
126
|
-
0, // Max queue size of 0 means no limit.
|
|
127
|
-
1, // Number of acquires/threads that will be calling this TSFN.
|
|
128
|
-
null, // No finalization data.
|
|
129
|
-
null, // No finalization callback.
|
|
130
|
-
null, // No custom context.
|
|
131
|
-
on_completion_js, // Function to call on JS thread when TSFN is called.
|
|
132
|
-
&completion_tsfn, // TSFN out handle.
|
|
133
|
-
) != c.napi_ok) {
|
|
134
|
-
return translate.throw(env, "Failed to create thread-safe function.");
|
|
135
|
-
}
|
|
136
|
-
errdefer if (c.napi_release_threadsafe_function(
|
|
137
|
-
completion_tsfn,
|
|
138
|
-
c.napi_tsfn_abort,
|
|
139
|
-
) != c.napi_ok) {
|
|
140
|
-
std.log.warn("Failed to release allocated thread-safe function on error.", .{});
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
if (c.napi_acquire_threadsafe_function(completion_tsfn) != c.napi_ok) {
|
|
144
|
-
return translate.throw(env, "Failed to acquire reference to thread-safe function.");
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const client = tb.init(
|
|
148
|
-
allocator,
|
|
149
|
-
cluster_id,
|
|
150
|
-
addresses,
|
|
151
|
-
concurrency,
|
|
152
|
-
@intFromPtr(completion_tsfn),
|
|
153
|
-
on_completion,
|
|
154
|
-
) catch |err| switch (err) {
|
|
155
|
-
error.OutOfMemory => return translate.throw(env, "Failed to allocate memory for Client."),
|
|
156
|
-
error.Unexpected => return translate.throw(env, "Unexpected error occurred on Client."),
|
|
157
|
-
error.AddressInvalid => return translate.throw(env, "Invalid replica address."),
|
|
158
|
-
error.AddressLimitExceeded => return translate.throw(env, "Too many replica addresses."),
|
|
159
|
-
error.ConcurrencyMaxInvalid => return translate.throw(env, "Concurrency is too high."),
|
|
160
|
-
error.SystemResources => return translate.throw(env, "Failed to reserve system resources."),
|
|
161
|
-
error.NetworkSubsystemFailed => return translate.throw(env, "Network stack failure."),
|
|
162
|
-
};
|
|
163
|
-
errdefer tb.deinit(client);
|
|
164
|
-
|
|
165
|
-
return try translate.create_external(env, client);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
fn destroy(env: c.napi_env, context: c.napi_value) !void {
|
|
169
|
-
const client_ptr = try translate.value_external(
|
|
170
|
-
env,
|
|
171
|
-
context,
|
|
172
|
-
"Failed to get client context pointer.",
|
|
173
|
-
);
|
|
174
|
-
const client: tb.tb_client_t = @ptrCast(@alignCast(client_ptr.?));
|
|
175
|
-
defer tb.deinit(client);
|
|
176
|
-
|
|
177
|
-
const completion_ctx = tb.completion_context(client);
|
|
178
|
-
const completion_tsfn: c.napi_threadsafe_function = @ptrFromInt(completion_ctx);
|
|
179
|
-
|
|
180
|
-
if (c.napi_release_threadsafe_function(completion_tsfn, c.napi_tsfn_abort) != c.napi_ok) {
|
|
181
|
-
return translate.throw(env, "Failed to release allocated thread-safe function on error.");
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
fn request(
|
|
186
|
-
env: c.napi_env,
|
|
187
|
-
context: c.napi_value,
|
|
188
|
-
operation: Operation,
|
|
189
|
-
array: c.napi_value,
|
|
190
|
-
callback: c.napi_value,
|
|
191
|
-
) !void {
|
|
192
|
-
const client_ptr = try translate.value_external(
|
|
193
|
-
env,
|
|
194
|
-
context,
|
|
195
|
-
"Failed to get client context pointer.",
|
|
196
|
-
);
|
|
197
|
-
const client: tb.tb_client_t = @ptrCast(@alignCast(client_ptr.?));
|
|
198
|
-
|
|
199
|
-
const packet = blk: {
|
|
200
|
-
var packet_ptr: ?*tb.tb_packet_t = undefined;
|
|
201
|
-
switch (tb.acquire_packet(client, &packet_ptr)) {
|
|
202
|
-
.ok => break :blk packet_ptr.?,
|
|
203
|
-
.shutdown => return translate.throw(env, "Client was shutdown."),
|
|
204
|
-
.concurrency_max_exceeded => return translate.throw(env, "Too many concurrent requests."),
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
errdefer tb.release_packet(client, packet);
|
|
208
|
-
|
|
209
|
-
// Create a reference to the callback so it stay alive until the packet completes.
|
|
210
|
-
var callback_ref: c.napi_ref = undefined;
|
|
211
|
-
if (c.napi_create_reference(env, callback, 1, &callback_ref) != c.napi_ok) {
|
|
212
|
-
return translate.throw(env, "Failed to create reference to callback.");
|
|
213
|
-
}
|
|
214
|
-
errdefer translate.delete_reference(env, callback_ref) catch {
|
|
215
|
-
std.log.warn("Failed to delete reference to callback on error.", .{});
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
const array_length = try translate.array_length(env, array);
|
|
219
|
-
if (array_length < 1) {
|
|
220
|
-
return translate.throw(env, "Batch must contain at least one event.");
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const packet_data = switch (operation) {
|
|
224
|
-
inline else => |op| blk: {
|
|
225
|
-
const buffer = try BufferType(op).alloc(
|
|
226
|
-
env,
|
|
227
|
-
array_length,
|
|
228
|
-
);
|
|
229
|
-
errdefer buffer.free();
|
|
230
|
-
|
|
231
|
-
const events = buffer.events();
|
|
232
|
-
try decode_array(StateMachine.Event(op), env, array, events);
|
|
233
|
-
break :blk std.mem.sliceAsBytes(events);
|
|
234
|
-
},
|
|
235
|
-
.pulse => unreachable,
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
packet.* = .{
|
|
239
|
-
.next = null,
|
|
240
|
-
.user_data = callback_ref,
|
|
241
|
-
.operation = @intFromEnum(operation),
|
|
242
|
-
.status = .ok,
|
|
243
|
-
.data_size = @intCast(packet_data.len),
|
|
244
|
-
.data = packet_data.ptr,
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
tb.submit(client, packet);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Packet only has one size field which normally tracks `BufferType(op).events().len`.
|
|
251
|
-
// However, completion of the packet can write results.len < `BufferType(op).results().len`.
|
|
252
|
-
// Therefore, we stuff both `BufferType(op).count` and results.len into the packet's size field.
|
|
253
|
-
// Storing both allows reconstruction of `BufferType(op)` while knowing how many results completed.
|
|
254
|
-
const BufferSize = packed struct(u32) {
|
|
255
|
-
event_count: u16,
|
|
256
|
-
result_count: u16,
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
fn on_completion(
|
|
260
|
-
completion_ctx: usize,
|
|
261
|
-
client: tb.tb_client_t,
|
|
262
|
-
packet: *tb.tb_packet_t,
|
|
263
|
-
result_ptr: ?[*]const u8,
|
|
264
|
-
result_len: u32,
|
|
265
|
-
) callconv(.C) void {
|
|
266
|
-
switch (packet.status) {
|
|
267
|
-
.ok => {},
|
|
268
|
-
.too_much_data => unreachable, // We limit packet data size during request().
|
|
269
|
-
.invalid_operation => unreachable, // We check the operation during request().
|
|
270
|
-
.invalid_data_size => unreachable, // We set correct data size during request().
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
switch (@as(Operation, @enumFromInt(packet.operation))) {
|
|
274
|
-
inline else => |op| {
|
|
275
|
-
const event_count = @divExact(packet.data_size, @sizeOf(StateMachine.Event(op)));
|
|
276
|
-
const buffer: BufferType(op) = .{
|
|
277
|
-
.ptr = @ptrCast(packet.data.?),
|
|
278
|
-
.count = event_count,
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
const Result = StateMachine.Result(op);
|
|
282
|
-
const results: []const Result = @alignCast(std.mem.bytesAsSlice(
|
|
283
|
-
Result,
|
|
284
|
-
result_ptr.?[0..result_len],
|
|
285
|
-
));
|
|
286
|
-
@memcpy(buffer.results()[0..results.len], results);
|
|
287
|
-
|
|
288
|
-
packet.data_size = @bitCast(BufferSize{
|
|
289
|
-
.event_count = @intCast(event_count),
|
|
290
|
-
.result_count = @intCast(results.len),
|
|
291
|
-
});
|
|
292
|
-
},
|
|
293
|
-
.pulse => unreachable,
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Stuff client pointer into packet.next to store it until the packet arrives on the JS thread.
|
|
297
|
-
@as(*usize, @ptrCast(&packet.next)).* = @intFromPtr(client);
|
|
298
|
-
|
|
299
|
-
// Queue the packet to be processed on the JS thread to invoke its JS callback.
|
|
300
|
-
const completion_tsfn: c.napi_threadsafe_function = @ptrFromInt(completion_ctx);
|
|
301
|
-
switch (c.napi_call_threadsafe_function(completion_tsfn, packet, c.napi_tsfn_nonblocking)) {
|
|
302
|
-
c.napi_ok => {},
|
|
303
|
-
c.napi_queue_full => @panic("ThreadSafe Function queue is full when created with no limit."),
|
|
304
|
-
else => unreachable,
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
fn on_completion_js(
|
|
309
|
-
env: c.napi_env,
|
|
310
|
-
unused_js_cb: c.napi_value,
|
|
311
|
-
unused_context: ?*anyopaque,
|
|
312
|
-
packet_argument: ?*anyopaque,
|
|
313
|
-
) callconv(.C) void {
|
|
314
|
-
_ = unused_js_cb;
|
|
315
|
-
_ = unused_context;
|
|
316
|
-
|
|
317
|
-
const packet: *tb.tb_packet_t = @ptrCast(@alignCast(packet_argument.?));
|
|
318
|
-
assert(packet.status == .ok);
|
|
319
|
-
|
|
320
|
-
// Decode the packet's Buffer results into an array then free the Buffer.
|
|
321
|
-
const array_or_error = switch (@as(Operation, @enumFromInt(packet.operation))) {
|
|
322
|
-
inline else => |op| blk: {
|
|
323
|
-
const buffer_size: BufferSize = @bitCast(packet.data_size);
|
|
324
|
-
const buffer: BufferType(op) = .{
|
|
325
|
-
.ptr = @ptrCast(packet.data.?),
|
|
326
|
-
.count = buffer_size.event_count,
|
|
327
|
-
};
|
|
328
|
-
defer buffer.free();
|
|
329
|
-
|
|
330
|
-
const results = buffer.results()[0..buffer_size.result_count];
|
|
331
|
-
break :blk encode_array(StateMachine.Result(op), env, results);
|
|
332
|
-
},
|
|
333
|
-
.pulse => unreachable,
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
// Extract the remaining packet information and release it back to the client.
|
|
337
|
-
const client: tb.tb_client_t = @ptrFromInt(@as(*usize, @ptrCast(&packet.next)).*);
|
|
338
|
-
const callback_ref: c.napi_ref = @ptrCast(@alignCast(packet.user_data.?));
|
|
339
|
-
tb.release_packet(client, packet);
|
|
340
|
-
|
|
341
|
-
// Parse Result array out of packet data, freeing it in the process.
|
|
342
|
-
// NOTE: Ensure this is called before anything that could early-return to avoid a alloc leak.
|
|
343
|
-
var callback_error = napi_null;
|
|
344
|
-
const callback_result = array_or_error catch |err| switch (err) {
|
|
345
|
-
error.ExceptionThrown => blk: {
|
|
346
|
-
if (c.napi_get_and_clear_last_exception(env, &callback_error) != c.napi_ok) {
|
|
347
|
-
std.log.warn("Failed to capture callback error from thrown Exception.", .{});
|
|
348
|
-
}
|
|
349
|
-
break :blk napi_null;
|
|
350
|
-
},
|
|
351
|
-
};
|
|
352
|
-
|
|
353
|
-
// Make sure to delete the callback reference once we're done calling it.
|
|
354
|
-
defer if (c.napi_delete_reference(env, callback_ref) != c.napi_ok) {
|
|
355
|
-
std.log.warn("Failed to delete reference to user's JS callback.", .{});
|
|
356
|
-
};
|
|
357
|
-
|
|
358
|
-
const callback = translate.reference_value(
|
|
359
|
-
env,
|
|
360
|
-
callback_ref,
|
|
361
|
-
"Failed to get callback from reference.",
|
|
362
|
-
) catch return;
|
|
363
|
-
|
|
364
|
-
var args = [_]c.napi_value{ callback_error, callback_result };
|
|
365
|
-
_ = translate.call_function(env, napi_null, callback, &args) catch return;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// (De)Serialization
|
|
369
|
-
|
|
370
|
-
fn decode_array(comptime Event: type, env: c.napi_env, array: c.napi_value, events: []Event) !void {
|
|
371
|
-
for (events, 0..) |*event, i| {
|
|
372
|
-
const object = try translate.array_element(env, array, @intCast(i));
|
|
373
|
-
switch (Event) {
|
|
374
|
-
Account, Transfer, AccountFilter, AccountBalance => {
|
|
375
|
-
inline for (std.meta.fields(Event)) |field| {
|
|
376
|
-
const value: field.type = switch (@typeInfo(field.type)) {
|
|
377
|
-
.Struct => |info| @bitCast(try @field(
|
|
378
|
-
translate,
|
|
379
|
-
@typeName(info.backing_integer.?) ++ "_from_object",
|
|
380
|
-
)(
|
|
381
|
-
env,
|
|
382
|
-
object,
|
|
383
|
-
add_trailing_null(field.name),
|
|
384
|
-
)),
|
|
385
|
-
.Int => try @field(translate, @typeName(field.type) ++ "_from_object")(
|
|
386
|
-
env,
|
|
387
|
-
object,
|
|
388
|
-
add_trailing_null(field.name),
|
|
389
|
-
),
|
|
390
|
-
// Arrays are only used for padding/reserved fields,
|
|
391
|
-
// instead of requiring the user to explicitly set an empty buffer,
|
|
392
|
-
// we just hide those fields and preserve their default value.
|
|
393
|
-
.Array => @as(
|
|
394
|
-
*const field.type,
|
|
395
|
-
@ptrCast(@alignCast(field.default_value.?)),
|
|
396
|
-
).*,
|
|
397
|
-
else => unreachable,
|
|
398
|
-
};
|
|
399
|
-
|
|
400
|
-
@field(event, field.name) = value;
|
|
401
|
-
}
|
|
402
|
-
},
|
|
403
|
-
u128 => event.* = try translate.u128_from_value(env, object, "lookup"),
|
|
404
|
-
else => @compileError("invalid Event type"),
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
fn encode_array(comptime Result: type, env: c.napi_env, results: []const Result) !c.napi_value {
|
|
410
|
-
const array = try translate.create_array(
|
|
411
|
-
env,
|
|
412
|
-
@intCast(results.len),
|
|
413
|
-
"Failed to allocate array for results.",
|
|
414
|
-
);
|
|
415
|
-
|
|
416
|
-
for (results, 0..) |*result, i| {
|
|
417
|
-
const object = try translate.create_object(
|
|
418
|
-
env,
|
|
419
|
-
"Failed to create " ++ @typeName(Result) ++ " object.",
|
|
420
|
-
);
|
|
421
|
-
|
|
422
|
-
inline for (std.meta.fields(Result)) |field| {
|
|
423
|
-
const FieldInt = switch (@typeInfo(field.type)) {
|
|
424
|
-
.Struct => |info| info.backing_integer.?,
|
|
425
|
-
.Enum => |info| info.tag_type,
|
|
426
|
-
// Arrays are only used for padding/reserved fields.
|
|
427
|
-
.Array => continue,
|
|
428
|
-
else => field.type,
|
|
429
|
-
};
|
|
430
|
-
|
|
431
|
-
const value: FieldInt = switch (@typeInfo(field.type)) {
|
|
432
|
-
.Struct => @bitCast(@field(result, field.name)),
|
|
433
|
-
.Enum => @intFromEnum(@field(result, field.name)),
|
|
434
|
-
else => @field(result, field.name),
|
|
435
|
-
};
|
|
436
|
-
|
|
437
|
-
try @field(translate, @typeName(FieldInt) ++ "_into_object")(
|
|
438
|
-
env,
|
|
439
|
-
object,
|
|
440
|
-
add_trailing_null(field.name),
|
|
441
|
-
value,
|
|
442
|
-
"Failed to set property \"" ++ field.name ++ "\" of " ++ @typeName(Result) ++ " object",
|
|
443
|
-
);
|
|
444
|
-
|
|
445
|
-
try translate.set_array_element(
|
|
446
|
-
env,
|
|
447
|
-
array,
|
|
448
|
-
@intCast(i),
|
|
449
|
-
object,
|
|
450
|
-
"Failed to set element in results array.",
|
|
451
|
-
);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
return array;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
fn add_trailing_null(comptime input: []const u8) [:0]const u8 {
|
|
459
|
-
// Concatenating `[]const u8` with an empty string `[0:0]const u8`,
|
|
460
|
-
// gives us a null-terminated string `[:0]const u8`.
|
|
461
|
-
const output = input ++ "";
|
|
462
|
-
comptime assert(output.len == input.len);
|
|
463
|
-
comptime assert(output[output.len] == 0);
|
|
464
|
-
return output;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
/// Each packet allocates enough room to hold both its Events and its Results.
|
|
468
|
-
/// Buffer is an abstraction over the memory management for this.
|
|
469
|
-
fn BufferType(comptime op: Operation) type {
|
|
470
|
-
assert(op != .pulse);
|
|
471
|
-
|
|
472
|
-
return struct {
|
|
473
|
-
const Buffer = @This();
|
|
474
|
-
const Event = StateMachine.Event(op);
|
|
475
|
-
const Result = StateMachine.Result(op);
|
|
476
|
-
const max_align: u29 = @max(@alignOf(Event), @alignOf(Result));
|
|
477
|
-
|
|
478
|
-
ptr: [*]u8,
|
|
479
|
-
count: u32,
|
|
480
|
-
|
|
481
|
-
fn alloc(env: c.napi_env, count: u32) !Buffer {
|
|
482
|
-
// Allocate enough bytes to hold memory for the Events and the Results.
|
|
483
|
-
const max_bytes = @max(
|
|
484
|
-
@sizeOf(Event) * count,
|
|
485
|
-
@sizeOf(Result) * event_count(op, count),
|
|
486
|
-
);
|
|
487
|
-
if (@sizeOf(vsr.Header) + max_bytes > constants.message_size_max) {
|
|
488
|
-
return translate.throw(env, "Batch is larger than the maximum message size.");
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
const bytes = allocator.alignedAlloc(u8, max_align, max_bytes) catch |e| switch (e) {
|
|
492
|
-
error.OutOfMemory => return translate.throw(env, "Batch allocation ran out of memory."),
|
|
493
|
-
};
|
|
494
|
-
errdefer allocator.free(bytes);
|
|
495
|
-
|
|
496
|
-
return Buffer{
|
|
497
|
-
.ptr = bytes.ptr,
|
|
498
|
-
.count = count,
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
fn free(buffer: Buffer) void {
|
|
503
|
-
const max_bytes = @max(
|
|
504
|
-
@sizeOf(Event) * buffer.count,
|
|
505
|
-
@sizeOf(Result) * event_count(op, buffer.count),
|
|
506
|
-
);
|
|
507
|
-
const bytes: []align(max_align) u8 = @alignCast(buffer.ptr[0..max_bytes]);
|
|
508
|
-
allocator.free(bytes);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
fn events(buffer: Buffer) []Event {
|
|
512
|
-
const event_bytes = buffer.ptr[0 .. @sizeOf(Event) * buffer.count];
|
|
513
|
-
return @alignCast(std.mem.bytesAsSlice(Event, event_bytes));
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
fn results(buffer: Buffer) []Result {
|
|
517
|
-
const result_bytes = buffer.ptr[0 .. @sizeOf(Result) * event_count(op, buffer.count)];
|
|
518
|
-
return @alignCast(std.mem.bytesAsSlice(Result, result_bytes));
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
fn event_count(operation: Operation, count: usize) usize {
|
|
522
|
-
// TODO(batiati): Refine the way we handle events with asymmetric results.
|
|
523
|
-
return switch (operation) {
|
|
524
|
-
.get_account_transfers, .get_account_balances => 8190,
|
|
525
|
-
else => count,
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
};
|
|
529
|
-
}
|