tigerbeetle-node 0.14.157 → 0.14.159
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/dist/benchmark.js +2 -120
- 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/index.d.ts +8 -10
- package/dist/index.js +26 -135
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/benchmark.ts +2 -162
- package/src/index.ts +40 -177
- package/src/node.zig +387 -694
- package/src/translate.zig +30 -134
- package/package-lock.json +0 -26
package/src/node.zig
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
const std = @import("std");
|
|
2
2
|
const assert = std.debug.assert;
|
|
3
|
+
const allocator = std.heap.c_allocator;
|
|
3
4
|
|
|
4
5
|
const c = @import("c.zig");
|
|
5
6
|
const translate = @import("translate.zig");
|
|
6
|
-
const tb =
|
|
7
|
+
const tb = struct {
|
|
8
|
+
pub usingnamespace @import("../../../tigerbeetle.zig");
|
|
9
|
+
pub usingnamespace @import("../../c/tb_client.zig");
|
|
10
|
+
};
|
|
7
11
|
|
|
8
12
|
const Account = tb.Account;
|
|
9
13
|
const AccountFlags = tb.AccountFlags;
|
|
@@ -15,783 +19,472 @@ const CreateTransfersResult = tb.CreateTransfersResult;
|
|
|
15
19
|
const Storage = @import("../../../storage.zig").Storage;
|
|
16
20
|
const StateMachine = @import("../../../state_machine.zig").StateMachineType(Storage, constants.state_machine_config);
|
|
17
21
|
const Operation = StateMachine.Operation;
|
|
18
|
-
const MessageBus = @import("../../../message_bus.zig").MessageBusClient;
|
|
19
|
-
const MessagePool = @import("../../../message_pool.zig").MessagePool;
|
|
20
|
-
const IO = @import("../../../io.zig").IO;
|
|
21
22
|
const constants = @import("../../../constants.zig");
|
|
22
|
-
|
|
23
23
|
const vsr = @import("../../../vsr.zig");
|
|
24
|
-
const Header = vsr.Header;
|
|
25
|
-
const Client = vsr.Client(StateMachine, MessageBus);
|
|
26
24
|
|
|
27
25
|
pub const std_options = struct {
|
|
28
26
|
// Since this is running in application space, log only critical messages to reduce noise.
|
|
29
27
|
pub const log_level: std.log.Level = .err;
|
|
30
28
|
};
|
|
31
29
|
|
|
30
|
+
// Cached value for JS (null).
|
|
31
|
+
var napi_null: c.napi_value = undefined;
|
|
32
|
+
|
|
32
33
|
/// N-API will call this constructor automatically to register the module.
|
|
33
34
|
export fn napi_register_module_v1(env: c.napi_env, exports: c.napi_value) c.napi_value {
|
|
35
|
+
napi_null = translate.capture_null(env) catch return null;
|
|
36
|
+
|
|
34
37
|
translate.register_function(env, exports, "init", init) catch return null;
|
|
35
38
|
translate.register_function(env, exports, "deinit", deinit) catch return null;
|
|
36
|
-
translate.register_function(env, exports, "
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
translate.register_function(env, exports, "submit", submit) catch return null;
|
|
40
|
+
return exports;
|
|
41
|
+
}
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
// Add-on code
|
|
44
|
+
|
|
45
|
+
fn init(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {
|
|
46
|
+
const args = translate.extract_args(env, info, .{
|
|
47
|
+
.count = 1,
|
|
48
|
+
.function = "init",
|
|
49
|
+
}) catch return null;
|
|
50
|
+
|
|
51
|
+
const cluster = translate.u32_from_object(env, args[0], "cluster_id") catch return null;
|
|
52
|
+
const concurrency = translate.u32_from_object(env, args[0], "concurrency") catch return null;
|
|
53
|
+
const addresses = translate.slice_from_object(
|
|
41
54
|
env,
|
|
42
|
-
|
|
43
|
-
"
|
|
44
|
-
constants.tick_ms,
|
|
45
|
-
"failed to add tick_ms to exports",
|
|
55
|
+
args[0],
|
|
56
|
+
"replica_addresses",
|
|
46
57
|
) catch return null;
|
|
47
58
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
std.log.err("Failed to initialise environment.\n", .{});
|
|
51
|
-
return null;
|
|
52
|
-
};
|
|
53
|
-
errdefer global.deinit();
|
|
54
|
-
|
|
55
|
-
// Tie the global state to this Node.js environment. This allows us to be thread safe.
|
|
56
|
-
// See https://nodejs.org/api/n-api.html#n_api_environment_life_cycle_apis.
|
|
57
|
-
// A cleanup function is registered as well that Node will call when the environment
|
|
58
|
-
// is torn down. Be careful not to call this function again as it will overwrite the global
|
|
59
|
-
// state.
|
|
60
|
-
translate.set_instance_data(
|
|
61
|
-
env,
|
|
62
|
-
@ptrCast(@alignCast(global)),
|
|
63
|
-
Globals.destroy,
|
|
64
|
-
) catch {
|
|
65
|
-
global.deinit();
|
|
66
|
-
return null;
|
|
67
|
-
};
|
|
59
|
+
return create(env, cluster, concurrency, addresses) catch null;
|
|
60
|
+
}
|
|
68
61
|
|
|
69
|
-
|
|
62
|
+
fn deinit(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {
|
|
63
|
+
const args = translate.extract_args(env, info, .{
|
|
64
|
+
.count = 1,
|
|
65
|
+
.function = "deinit",
|
|
66
|
+
}) catch return null;
|
|
67
|
+
|
|
68
|
+
destroy(env, args[0]) catch {};
|
|
69
|
+
return null;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
pub fn init(allocator: std.mem.Allocator, env: c.napi_env) !*Globals {
|
|
78
|
-
const self = try allocator.create(Globals);
|
|
79
|
-
errdefer allocator.destroy(self);
|
|
80
|
-
|
|
81
|
-
self.allocator = allocator;
|
|
82
|
-
|
|
83
|
-
// Be careful to size the SQ ring to only a few SQE entries and to share a single IO
|
|
84
|
-
// instance across multiple clients to stay under kernel limits:
|
|
85
|
-
//
|
|
86
|
-
// The memory required by io_uring is accounted under the rlimit memlocked option, which can
|
|
87
|
-
// be quite low on some setups (64K). The default is usually enough for most use cases, but
|
|
88
|
-
// bigger rings or things like registered buffers deplete it quickly. Root isn't under this
|
|
89
|
-
// restriction, but regular users are.
|
|
90
|
-
//
|
|
91
|
-
// Check `/etc/security/limits.conf` for user settings, or `/etc/systemd/user.conf` and
|
|
92
|
-
// `/etc/systemd/system.conf` for systemd setups.
|
|
93
|
-
self.io = IO.init(32, 0) catch {
|
|
94
|
-
return translate.throw(env, "Failed to initialize io_uring");
|
|
95
|
-
};
|
|
96
|
-
errdefer self.io.deinit();
|
|
97
|
-
|
|
98
|
-
if (c.napi_get_undefined(env, &self.napi_undefined) != c.napi_ok) {
|
|
99
|
-
return translate.throw(env, "Failed to capture the value of \"undefined\".");
|
|
100
|
-
}
|
|
72
|
+
fn submit(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {
|
|
73
|
+
const args = translate.extract_args(env, info, .{
|
|
74
|
+
.count = 4,
|
|
75
|
+
.function = "submit",
|
|
76
|
+
}) catch return null;
|
|
101
77
|
|
|
102
|
-
|
|
78
|
+
const operation_int = translate.u32_from_value(env, args[1], "operation") catch return null;
|
|
79
|
+
if (!@as(vsr.Operation, @enumFromInt(operation_int)).valid(StateMachine)) {
|
|
80
|
+
translate.throw(env, "Unknown operation.") catch return null;
|
|
103
81
|
}
|
|
104
82
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
83
|
+
var is_array: bool = undefined;
|
|
84
|
+
if (c.napi_is_array(env, args[2], &is_array) != c.napi_ok) {
|
|
85
|
+
translate.throw(env, "Failed to check array argument type.") catch return null;
|
|
86
|
+
}
|
|
87
|
+
if (!is_array) {
|
|
88
|
+
translate.throw(env, "Array argument must be an [object Array].") catch return null;
|
|
108
89
|
}
|
|
109
90
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
91
|
+
var callback_type: c.napi_valuetype = undefined;
|
|
92
|
+
if (c.napi_typeof(env, args[3], &callback_type) != c.napi_ok) {
|
|
93
|
+
translate.throw(env, "Failed to check callback argument type.") catch return null;
|
|
94
|
+
}
|
|
95
|
+
if (callback_type != c.napi_function) {
|
|
96
|
+
translate.throw(env, "Callback argument must be a Function.") catch return null;
|
|
116
97
|
}
|
|
117
|
-
};
|
|
118
98
|
|
|
119
|
-
|
|
120
|
-
|
|
99
|
+
request(
|
|
100
|
+
env,
|
|
101
|
+
args[0], // tb_client
|
|
102
|
+
@enumFromInt(@as(u8, @intCast(operation_int))),
|
|
103
|
+
args[2], // request array
|
|
104
|
+
args[3], // callback
|
|
105
|
+
) catch {};
|
|
106
|
+
return null;
|
|
121
107
|
}
|
|
122
108
|
|
|
123
|
-
|
|
124
|
-
io: *IO,
|
|
125
|
-
addresses: []std.net.Address,
|
|
126
|
-
client: Client,
|
|
127
|
-
message_pool: MessagePool,
|
|
128
|
-
|
|
129
|
-
fn create(
|
|
130
|
-
env: c.napi_env,
|
|
131
|
-
allocator: std.mem.Allocator,
|
|
132
|
-
io: *IO,
|
|
133
|
-
cluster: u32,
|
|
134
|
-
addresses_raw: []const u8,
|
|
135
|
-
) !c.napi_value {
|
|
136
|
-
const context = try allocator.create(Context);
|
|
137
|
-
errdefer allocator.destroy(context);
|
|
138
|
-
|
|
139
|
-
context.io = io;
|
|
140
|
-
context.message_pool = try MessagePool.init(allocator, .client);
|
|
141
|
-
errdefer context.message_pool.deinit(allocator);
|
|
142
|
-
|
|
143
|
-
context.addresses = try vsr.parse_addresses(allocator, addresses_raw, constants.replicas_max);
|
|
144
|
-
errdefer allocator.free(context.addresses);
|
|
145
|
-
assert(context.addresses.len > 0);
|
|
146
|
-
|
|
147
|
-
const client_id = std.crypto.random.int(u128);
|
|
148
|
-
context.client = try Client.init(
|
|
149
|
-
allocator,
|
|
150
|
-
client_id,
|
|
151
|
-
cluster,
|
|
152
|
-
@as(u8, @intCast(context.addresses.len)),
|
|
153
|
-
&context.message_pool,
|
|
154
|
-
.{
|
|
155
|
-
.configuration = context.addresses,
|
|
156
|
-
.io = context.io,
|
|
157
|
-
},
|
|
158
|
-
);
|
|
159
|
-
errdefer context.client.deinit(allocator);
|
|
109
|
+
// tb_client Logic
|
|
160
110
|
|
|
161
|
-
|
|
111
|
+
fn create(env: c.napi_env, cluster_id: u32, concurrency: u32, addresses: []const u8) !c.napi_value {
|
|
112
|
+
var tsfn_name: c.napi_value = undefined;
|
|
113
|
+
if (c.napi_create_string_utf8(env, "tb_client", c.NAPI_AUTO_LENGTH, &tsfn_name) != c.napi_ok) {
|
|
114
|
+
return translate.throw(env, "Failed to create resource name for thread-safe function.");
|
|
162
115
|
}
|
|
163
|
-
};
|
|
164
116
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
117
|
+
var completion_tsfn: c.napi_threadsafe_function = undefined;
|
|
118
|
+
if (c.napi_create_threadsafe_function(
|
|
119
|
+
env,
|
|
120
|
+
null, // No javascript function to call directly from here.
|
|
121
|
+
null, // No async resource.
|
|
122
|
+
tsfn_name,
|
|
123
|
+
0, // Max queue size of 0 means no limit.
|
|
124
|
+
1, // Number of acquires/threads that will be calling this TSFN.
|
|
125
|
+
null, // No finalization data.
|
|
126
|
+
null, // No finalization callback.
|
|
127
|
+
null, // No custom context.
|
|
128
|
+
on_completion_js, // Function to call on JS thread when TSFN is called.
|
|
129
|
+
&completion_tsfn, // TSFN out handle.
|
|
130
|
+
) != c.napi_ok) {
|
|
131
|
+
return translate.throw(env, "Failed to create thread-safe function.");
|
|
176
132
|
}
|
|
133
|
+
errdefer if (c.napi_release_threadsafe_function(
|
|
134
|
+
completion_tsfn,
|
|
135
|
+
c.napi_tsfn_abort,
|
|
136
|
+
) != c.napi_ok) {
|
|
137
|
+
std.log.warn("Failed to release allocated thread-safe function on error.", .{});
|
|
138
|
+
};
|
|
177
139
|
|
|
178
|
-
|
|
179
|
-
|
|
140
|
+
if (c.napi_acquire_threadsafe_function(completion_tsfn) != c.napi_ok) {
|
|
141
|
+
return translate.throw(env, "Failed to acquire reference to thread-safe function.");
|
|
142
|
+
}
|
|
180
143
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
.timestamp = try validate_timestamp(env, object),
|
|
197
|
-
},
|
|
198
|
-
Account => Account{
|
|
199
|
-
.id = try translate.u128_from_object(env, object, "id"),
|
|
200
|
-
.debits_pending = try translate.u128_from_object(env, object, "debits_pending"),
|
|
201
|
-
.debits_posted = try translate.u128_from_object(env, object, "debits_posted"),
|
|
202
|
-
.credits_pending = try translate.u128_from_object(env, object, "credits_pending"),
|
|
203
|
-
.credits_posted = try translate.u128_from_object(env, object, "credits_posted"),
|
|
204
|
-
.user_data_128 = try translate.u128_from_object(env, object, "user_data_128"),
|
|
205
|
-
.user_data_64 = try translate.u64_from_object(env, object, "user_data_64"),
|
|
206
|
-
.user_data_32 = try translate.u32_from_object(env, object, "user_data_32"),
|
|
207
|
-
.reserved = try translate.u32_from_object(env, object, "reserved"),
|
|
208
|
-
.ledger = try translate.u32_from_object(env, object, "ledger"),
|
|
209
|
-
.code = try translate.u16_from_object(env, object, "code"),
|
|
210
|
-
.flags = @bitCast(try translate.u16_from_object(env, object, "flags")),
|
|
211
|
-
.timestamp = try validate_timestamp(env, object),
|
|
212
|
-
},
|
|
213
|
-
u128 => try translate.u128_from_value(env, object, "lookup"),
|
|
214
|
-
else => unreachable,
|
|
144
|
+
const client = tb.init(
|
|
145
|
+
allocator,
|
|
146
|
+
cluster_id,
|
|
147
|
+
addresses,
|
|
148
|
+
concurrency,
|
|
149
|
+
@intFromPtr(completion_tsfn),
|
|
150
|
+
on_completion,
|
|
151
|
+
) catch |err| switch (err) {
|
|
152
|
+
error.OutOfMemory => return translate.throw(env, "Failed to allocate memory for Client."),
|
|
153
|
+
error.Unexpected => return translate.throw(env, "Unexpected error occured on Client."),
|
|
154
|
+
error.AddressInvalid => return translate.throw(env, "Invalid replica address."),
|
|
155
|
+
error.AddressLimitExceeded => return translate.throw(env, "Too many replica addresses."),
|
|
156
|
+
error.ConcurrencyMaxInvalid => return translate.throw(env, "Concurrency is too high."),
|
|
157
|
+
error.SystemResources => return translate.throw(env, "Failed to reserve system resources."),
|
|
158
|
+
error.NetworkSubsystemFailed => return translate.throw(env, "Network stack failure."),
|
|
215
159
|
};
|
|
216
|
-
|
|
160
|
+
errdefer tb.deinit(client);
|
|
217
161
|
|
|
218
|
-
|
|
219
|
-
env: c.napi_env,
|
|
220
|
-
array: c.napi_value,
|
|
221
|
-
operation: Operation,
|
|
222
|
-
output: []u8,
|
|
223
|
-
) !usize {
|
|
224
|
-
return switch (operation) {
|
|
225
|
-
.create_accounts => try decode_events_from_array(env, array, Account, output),
|
|
226
|
-
.create_transfers => try decode_events_from_array(env, array, Transfer, output),
|
|
227
|
-
.lookup_accounts => try decode_events_from_array(env, array, u128, output),
|
|
228
|
-
.lookup_transfers => try decode_events_from_array(env, array, u128, output),
|
|
229
|
-
};
|
|
162
|
+
return try translate.create_external(env, client);
|
|
230
163
|
}
|
|
231
164
|
|
|
232
|
-
fn
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
)
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const body_length = @sizeOf(T) * array_length;
|
|
242
|
-
if (@sizeOf(Header) + body_length > constants.message_size_max) {
|
|
243
|
-
return translate.throw(env, "Batch is larger than the maximum message size.");
|
|
244
|
-
}
|
|
165
|
+
fn destroy(env: c.napi_env, context: c.napi_value) !void {
|
|
166
|
+
const client_ptr = try translate.value_external(
|
|
167
|
+
env,
|
|
168
|
+
context,
|
|
169
|
+
"Failed to get client context pointer.",
|
|
170
|
+
);
|
|
171
|
+
const client: tb.tb_client_t = @ptrCast(@alignCast(client_ptr.?));
|
|
172
|
+
defer tb.deinit(client);
|
|
245
173
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
var results = std.mem.bytesAsSlice(T, output[0..body_length]);
|
|
174
|
+
const completion_ctx = tb.completion_context(client);
|
|
175
|
+
const completion_tsfn: c.napi_threadsafe_function = @ptrFromInt(completion_ctx);
|
|
249
176
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const entry = try translate.array_element(env, array, i);
|
|
253
|
-
results[i] = try decode_from_object(T, env, entry);
|
|
177
|
+
if (c.napi_release_threadsafe_function(completion_tsfn, c.napi_tsfn_abort) != c.napi_ok) {
|
|
178
|
+
return translate.throw(env, "Failed to release allocated thread-safe function on error.");
|
|
254
179
|
}
|
|
255
|
-
|
|
256
|
-
return body_length;
|
|
257
180
|
}
|
|
258
181
|
|
|
259
|
-
fn
|
|
260
|
-
comptime Result: type,
|
|
182
|
+
fn request(
|
|
261
183
|
env: c.napi_env,
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
184
|
+
context: c.napi_value,
|
|
185
|
+
operation: Operation,
|
|
186
|
+
array: c.napi_value,
|
|
187
|
+
callback: c.napi_value,
|
|
188
|
+
) !void {
|
|
189
|
+
const client_ptr = try translate.value_external(
|
|
266
190
|
env,
|
|
267
|
-
|
|
268
|
-
"Failed to
|
|
191
|
+
context,
|
|
192
|
+
"Failed to get client context pointer.",
|
|
269
193
|
);
|
|
194
|
+
const client: tb.tb_client_t = @ptrCast(@alignCast(client_ptr.?));
|
|
195
|
+
|
|
196
|
+
const packet = blk: {
|
|
197
|
+
var packet_ptr: ?*tb.tb_packet_t = undefined;
|
|
198
|
+
switch (tb.acquire_packet(client, &packet_ptr)) {
|
|
199
|
+
.ok => break :blk packet_ptr.?,
|
|
200
|
+
.shutdown => return translate.throw(env, "Client was shutdown."),
|
|
201
|
+
.concurrency_max_exceeded => return translate.throw(env, "Too many concurrent requests."),
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
errdefer tb.release_packet(client, packet);
|
|
270
205
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const result = results[i];
|
|
276
|
-
const napi_object = try translate.create_object(
|
|
277
|
-
env,
|
|
278
|
-
"Failed to create result object",
|
|
279
|
-
);
|
|
280
|
-
|
|
281
|
-
try translate.u32_into_object(
|
|
282
|
-
env,
|
|
283
|
-
napi_object,
|
|
284
|
-
"index",
|
|
285
|
-
result.index,
|
|
286
|
-
"Failed to set property \"index\" of result.",
|
|
287
|
-
);
|
|
288
|
-
|
|
289
|
-
try translate.u32_into_object(
|
|
290
|
-
env,
|
|
291
|
-
napi_object,
|
|
292
|
-
"result",
|
|
293
|
-
@intFromEnum(result.result),
|
|
294
|
-
"Failed to set property \"result\" of result.",
|
|
295
|
-
);
|
|
296
|
-
|
|
297
|
-
try translate.set_array_element(
|
|
298
|
-
env,
|
|
299
|
-
napi_array,
|
|
300
|
-
i,
|
|
301
|
-
napi_object,
|
|
302
|
-
"Failed to set element in results array.",
|
|
303
|
-
);
|
|
304
|
-
}
|
|
305
|
-
},
|
|
306
|
-
Account => {
|
|
307
|
-
var i: u32 = 0;
|
|
308
|
-
while (i < results.len) : (i += 1) {
|
|
309
|
-
const result = results[i];
|
|
310
|
-
const napi_object = try translate.create_object(
|
|
311
|
-
env,
|
|
312
|
-
"Failed to create account lookup result object.",
|
|
313
|
-
);
|
|
314
|
-
|
|
315
|
-
try translate.u128_into_object(
|
|
316
|
-
env,
|
|
317
|
-
napi_object,
|
|
318
|
-
"id",
|
|
319
|
-
result.id,
|
|
320
|
-
"Failed to set property \"id\" of account lookup result.",
|
|
321
|
-
);
|
|
322
|
-
|
|
323
|
-
try translate.u128_into_object(
|
|
324
|
-
env,
|
|
325
|
-
napi_object,
|
|
326
|
-
"debits_pending",
|
|
327
|
-
result.debits_pending,
|
|
328
|
-
"Failed to set property \"debits_pending\" of account lookup result.",
|
|
329
|
-
);
|
|
330
|
-
|
|
331
|
-
try translate.u128_into_object(
|
|
332
|
-
env,
|
|
333
|
-
napi_object,
|
|
334
|
-
"debits_posted",
|
|
335
|
-
result.debits_posted,
|
|
336
|
-
"Failed to set property \"debits_posted\" of account lookup result.",
|
|
337
|
-
);
|
|
338
|
-
|
|
339
|
-
try translate.u128_into_object(
|
|
340
|
-
env,
|
|
341
|
-
napi_object,
|
|
342
|
-
"credits_pending",
|
|
343
|
-
result.credits_pending,
|
|
344
|
-
"Failed to set property \"credits_pending\" of account lookup result.",
|
|
345
|
-
);
|
|
346
|
-
|
|
347
|
-
try translate.u128_into_object(
|
|
348
|
-
env,
|
|
349
|
-
napi_object,
|
|
350
|
-
"credits_posted",
|
|
351
|
-
result.credits_posted,
|
|
352
|
-
"Failed to set property \"credits_posted\" of account lookup result.",
|
|
353
|
-
);
|
|
354
|
-
|
|
355
|
-
try translate.u128_into_object(
|
|
356
|
-
env,
|
|
357
|
-
napi_object,
|
|
358
|
-
"user_data_128",
|
|
359
|
-
result.user_data_128,
|
|
360
|
-
"Failed to set property \"user_data_128\" of account lookup result.",
|
|
361
|
-
);
|
|
362
|
-
|
|
363
|
-
try translate.u64_into_object(
|
|
364
|
-
env,
|
|
365
|
-
napi_object,
|
|
366
|
-
"user_data_64",
|
|
367
|
-
result.user_data_64,
|
|
368
|
-
"Failed to set property \"user_data_64\" of account lookup result.",
|
|
369
|
-
);
|
|
370
|
-
|
|
371
|
-
try translate.u32_into_object(
|
|
372
|
-
env,
|
|
373
|
-
napi_object,
|
|
374
|
-
"user_data_32",
|
|
375
|
-
result.user_data_32,
|
|
376
|
-
"Failed to set property \"user_data_32\" of account lookup result.",
|
|
377
|
-
);
|
|
378
|
-
|
|
379
|
-
try translate.u32_into_object(
|
|
380
|
-
env,
|
|
381
|
-
napi_object,
|
|
382
|
-
"reserved",
|
|
383
|
-
result.reserved,
|
|
384
|
-
"Failed to set property \"reserved\" of account lookup result.",
|
|
385
|
-
);
|
|
386
|
-
|
|
387
|
-
try translate.u32_into_object(
|
|
388
|
-
env,
|
|
389
|
-
napi_object,
|
|
390
|
-
"ledger",
|
|
391
|
-
@as(u32, @intCast(result.ledger)),
|
|
392
|
-
"Failed to set property \"ledger\" of account lookup result.",
|
|
393
|
-
);
|
|
394
|
-
|
|
395
|
-
try translate.u16_into_object(
|
|
396
|
-
env,
|
|
397
|
-
napi_object,
|
|
398
|
-
"code",
|
|
399
|
-
@as(u16, @intCast(result.code)),
|
|
400
|
-
"Failed to set property \"code\" of account lookup result.",
|
|
401
|
-
);
|
|
402
|
-
|
|
403
|
-
try translate.u16_into_object(
|
|
404
|
-
env,
|
|
405
|
-
napi_object,
|
|
406
|
-
"flags",
|
|
407
|
-
@as(u16, @bitCast(result.flags)),
|
|
408
|
-
"Failed to set property \"flags\" of account lookup result.",
|
|
409
|
-
);
|
|
410
|
-
|
|
411
|
-
try translate.u64_into_object(
|
|
412
|
-
env,
|
|
413
|
-
napi_object,
|
|
414
|
-
"timestamp",
|
|
415
|
-
result.timestamp,
|
|
416
|
-
"Failed to set property \"timestamp\" of account lookup result.",
|
|
417
|
-
);
|
|
418
|
-
|
|
419
|
-
try translate.set_array_element(
|
|
420
|
-
env,
|
|
421
|
-
napi_array,
|
|
422
|
-
i,
|
|
423
|
-
napi_object,
|
|
424
|
-
"Failed to set element in results array.",
|
|
425
|
-
);
|
|
426
|
-
}
|
|
427
|
-
},
|
|
428
|
-
Transfer => {
|
|
429
|
-
var i: u32 = 0;
|
|
430
|
-
while (i < results.len) : (i += 1) {
|
|
431
|
-
const result = results[i];
|
|
432
|
-
const napi_object = try translate.create_object(
|
|
433
|
-
env,
|
|
434
|
-
"Failed to create transfer lookup result object.",
|
|
435
|
-
);
|
|
436
|
-
|
|
437
|
-
try translate.u128_into_object(
|
|
438
|
-
env,
|
|
439
|
-
napi_object,
|
|
440
|
-
"id",
|
|
441
|
-
result.id,
|
|
442
|
-
"Failed to set property \"id\" of transfer lookup result.",
|
|
443
|
-
);
|
|
444
|
-
|
|
445
|
-
try translate.u128_into_object(
|
|
446
|
-
env,
|
|
447
|
-
napi_object,
|
|
448
|
-
"debit_account_id",
|
|
449
|
-
result.debit_account_id,
|
|
450
|
-
"Failed to set property \"debit_account_id\" of transfer lookup result.",
|
|
451
|
-
);
|
|
452
|
-
|
|
453
|
-
try translate.u128_into_object(
|
|
454
|
-
env,
|
|
455
|
-
napi_object,
|
|
456
|
-
"credit_account_id",
|
|
457
|
-
result.credit_account_id,
|
|
458
|
-
"Failed to set property \"credit_account_id\" of transfer lookup result.",
|
|
459
|
-
);
|
|
460
|
-
|
|
461
|
-
try translate.u128_into_object(
|
|
462
|
-
env,
|
|
463
|
-
napi_object,
|
|
464
|
-
"amount",
|
|
465
|
-
result.amount,
|
|
466
|
-
"Failed to set property \"amount\" of transfer lookup result.",
|
|
467
|
-
);
|
|
468
|
-
|
|
469
|
-
try translate.u128_into_object(
|
|
470
|
-
env,
|
|
471
|
-
napi_object,
|
|
472
|
-
"pending_id",
|
|
473
|
-
result.pending_id,
|
|
474
|
-
"Failed to set property \"pending_id\" of transfer lookup result.",
|
|
475
|
-
);
|
|
476
|
-
|
|
477
|
-
try translate.u128_into_object(
|
|
478
|
-
env,
|
|
479
|
-
napi_object,
|
|
480
|
-
"user_data_128",
|
|
481
|
-
result.user_data_128,
|
|
482
|
-
"Failed to set property \"user_data_128\" of transfer lookup result.",
|
|
483
|
-
);
|
|
484
|
-
|
|
485
|
-
try translate.u64_into_object(
|
|
486
|
-
env,
|
|
487
|
-
napi_object,
|
|
488
|
-
"user_data_64",
|
|
489
|
-
result.user_data_64,
|
|
490
|
-
"Failed to set property \"user_data_64\" of transfer lookup result.",
|
|
491
|
-
);
|
|
492
|
-
|
|
493
|
-
try translate.u32_into_object(
|
|
494
|
-
env,
|
|
495
|
-
napi_object,
|
|
496
|
-
"user_data_32",
|
|
497
|
-
result.user_data_32,
|
|
498
|
-
"Failed to set property \"user_data_32\" of transfer lookup result.",
|
|
499
|
-
);
|
|
500
|
-
|
|
501
|
-
try translate.u32_into_object(
|
|
502
|
-
env,
|
|
503
|
-
napi_object,
|
|
504
|
-
"timeout",
|
|
505
|
-
result.timeout,
|
|
506
|
-
"Failed to set property \"timeout\" of transfer lookup result.",
|
|
507
|
-
);
|
|
508
|
-
|
|
509
|
-
try translate.u32_into_object(
|
|
510
|
-
env,
|
|
511
|
-
napi_object,
|
|
512
|
-
"ledger",
|
|
513
|
-
@as(u32, @intCast(result.ledger)),
|
|
514
|
-
"Failed to set property \"ledger\" of transfer lookup result.",
|
|
515
|
-
);
|
|
516
|
-
|
|
517
|
-
try translate.u16_into_object(
|
|
518
|
-
env,
|
|
519
|
-
napi_object,
|
|
520
|
-
"code",
|
|
521
|
-
@as(u16, @intCast(result.code)),
|
|
522
|
-
"Failed to set property \"code\" of transfer lookup result.",
|
|
523
|
-
);
|
|
524
|
-
|
|
525
|
-
try translate.u16_into_object(
|
|
526
|
-
env,
|
|
527
|
-
napi_object,
|
|
528
|
-
"flags",
|
|
529
|
-
@as(u16, @bitCast(result.flags)),
|
|
530
|
-
"Failed to set property \"flags\" of transfer lookup result.",
|
|
531
|
-
);
|
|
532
|
-
|
|
533
|
-
try translate.u64_into_object(
|
|
534
|
-
env,
|
|
535
|
-
napi_object,
|
|
536
|
-
"timestamp",
|
|
537
|
-
result.timestamp,
|
|
538
|
-
"Failed to set property \"timestamp\" of transfer lookup result.",
|
|
539
|
-
);
|
|
540
|
-
|
|
541
|
-
try translate.set_array_element(
|
|
542
|
-
env,
|
|
543
|
-
napi_array,
|
|
544
|
-
i,
|
|
545
|
-
napi_object,
|
|
546
|
-
"Failed to set element in results array.",
|
|
547
|
-
);
|
|
548
|
-
}
|
|
549
|
-
},
|
|
550
|
-
else => unreachable,
|
|
206
|
+
// Create a reference to the callback so it stay alive until the packet completes.
|
|
207
|
+
var callback_ref: c.napi_ref = undefined;
|
|
208
|
+
if (c.napi_create_reference(env, callback, 1, &callback_ref) != c.napi_ok) {
|
|
209
|
+
return translate.throw(env, "Failed to create reference to callback.");
|
|
551
210
|
}
|
|
211
|
+
errdefer translate.delete_reference(env, callback_ref) catch {
|
|
212
|
+
std.log.warn("Failed to delete reference to callback on error.", .{});
|
|
213
|
+
};
|
|
552
214
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
/// Add-on code
|
|
557
|
-
fn init(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {
|
|
558
|
-
var argc: usize = 1;
|
|
559
|
-
var argv: [1]c.napi_value = undefined;
|
|
560
|
-
if (c.napi_get_cb_info(env, info, &argc, &argv, null, null) != c.napi_ok) {
|
|
561
|
-
translate.throw(env, "Failed to get args.") catch return null;
|
|
215
|
+
const array_length = try translate.array_length(env, array);
|
|
216
|
+
if (array_length < 1) {
|
|
217
|
+
return translate.throw(env, "Batch must contain at least one event.");
|
|
562
218
|
}
|
|
563
|
-
if (argc != 1) translate.throw(
|
|
564
|
-
env,
|
|
565
|
-
"Function init() must receive 1 argument exactly.",
|
|
566
|
-
) catch return null;
|
|
567
|
-
|
|
568
|
-
const cluster = translate.u32_from_object(env, argv[0], "cluster_id") catch return null;
|
|
569
|
-
const addresses = translate.slice_from_object(
|
|
570
|
-
env,
|
|
571
|
-
argv[0],
|
|
572
|
-
"replica_addresses",
|
|
573
|
-
) catch return null;
|
|
574
219
|
|
|
575
|
-
const
|
|
220
|
+
const packet_data = switch (operation) {
|
|
221
|
+
inline else => |op| blk: {
|
|
222
|
+
const buffer = try BufferType(op).alloc(env, array_length);
|
|
223
|
+
errdefer buffer.free();
|
|
576
224
|
|
|
577
|
-
|
|
578
|
-
|
|
225
|
+
const events = buffer.events();
|
|
226
|
+
try decode_array(StateMachine.Event(op), env, array, events);
|
|
227
|
+
break :blk std.mem.sliceAsBytes(events);
|
|
228
|
+
},
|
|
229
|
+
};
|
|
579
230
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
231
|
+
packet.* = .{
|
|
232
|
+
.next = null,
|
|
233
|
+
.user_data = callback_ref,
|
|
234
|
+
.operation = @intFromEnum(operation),
|
|
235
|
+
.status = .ok,
|
|
236
|
+
.data_size = @intCast(packet_data.len),
|
|
237
|
+
.data = packet_data.ptr,
|
|
583
238
|
};
|
|
584
239
|
|
|
585
|
-
|
|
240
|
+
tb.submit(client, packet);
|
|
586
241
|
}
|
|
587
242
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
if (argc != 4) translate.throw(
|
|
598
|
-
env,
|
|
599
|
-
"Function request() requires 4 arguments exactly.",
|
|
600
|
-
) catch return null;
|
|
601
|
-
|
|
602
|
-
const context_raw = translate.value_external(
|
|
603
|
-
env,
|
|
604
|
-
argv[0],
|
|
605
|
-
"Failed to get Client Context pointer.",
|
|
606
|
-
) catch return null;
|
|
607
|
-
const context = contextCast(context_raw.?) catch return null;
|
|
608
|
-
const operation_int = translate.u32_from_value(env, argv[1], "operation") catch return null;
|
|
609
|
-
|
|
610
|
-
if (!@as(vsr.Operation, @enumFromInt(operation_int)).valid(StateMachine)) {
|
|
611
|
-
translate.throw(env, "Unknown operation.") catch return null;
|
|
612
|
-
}
|
|
243
|
+
// Packet only has one size field which normally tracks `BufferType(op).events().len`.
|
|
244
|
+
// However, completion of the packet can write results.len < `BufferType(op).results().len`.
|
|
245
|
+
// Therefore, we stuff both `BufferType(op).count` and results.len into the packet's size field.
|
|
246
|
+
// Storing both allows reconstruction of `BufferType(op)` while knowing how many results completed.
|
|
247
|
+
const BufferSize = packed struct(u32) {
|
|
248
|
+
event_count: u16,
|
|
249
|
+
result_count: u16,
|
|
250
|
+
};
|
|
613
251
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
252
|
+
fn on_completion(
|
|
253
|
+
completion_ctx: usize,
|
|
254
|
+
client: tb.tb_client_t,
|
|
255
|
+
packet: *tb.tb_packet_t,
|
|
256
|
+
result_ptr: ?[*]const u8,
|
|
257
|
+
result_len: u32,
|
|
258
|
+
) callconv(.C) void {
|
|
259
|
+
switch (packet.status) {
|
|
260
|
+
.ok => {},
|
|
261
|
+
.too_much_data => unreachable, // We limit packet data size during request().
|
|
262
|
+
.invalid_operation => unreachable, // We check the operation during request().
|
|
263
|
+
.invalid_data_size => unreachable, // We set correct data size during request().
|
|
619
264
|
}
|
|
620
|
-
const message = context.client.get_message();
|
|
621
|
-
errdefer context.client.release(message);
|
|
622
|
-
|
|
623
|
-
const operation = @as(Operation, @enumFromInt(@as(u8, @intCast(operation_int))));
|
|
624
|
-
const body_length = decode_events(
|
|
625
|
-
env,
|
|
626
|
-
argv[2],
|
|
627
|
-
operation,
|
|
628
|
-
message.buffer[@sizeOf(Header)..],
|
|
629
|
-
) catch |err| switch (err) {
|
|
630
|
-
error.ExceptionThrown => return null,
|
|
631
|
-
};
|
|
632
|
-
|
|
633
|
-
// This will create a reference (in V8) to the user's JS callback that we must eventually also
|
|
634
|
-
// free in order to avoid a leak. We therefore do this last to ensure we cannot fail after
|
|
635
|
-
// taking this reference.
|
|
636
|
-
const user_data = translate.user_data_from_value(env, argv[3]) catch return null;
|
|
637
|
-
context.client.request(@as(u128, @bitCast(user_data)), on_result, operation, message, body_length);
|
|
638
|
-
|
|
639
|
-
return null;
|
|
640
|
-
}
|
|
641
265
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
266
|
+
switch (@as(Operation, @enumFromInt(packet.operation))) {
|
|
267
|
+
inline else => |op| {
|
|
268
|
+
const event_count = @divExact(packet.data_size, @sizeOf(StateMachine.Event(op)));
|
|
269
|
+
const buffer: BufferType(op) = .{
|
|
270
|
+
.ptr = @ptrCast(packet.data.?),
|
|
271
|
+
.count = event_count,
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const Result = StateMachine.Result(op);
|
|
275
|
+
const results: []const Result = @alignCast(std.mem.bytesAsSlice(
|
|
276
|
+
Result,
|
|
277
|
+
result_ptr.?[0..result_len],
|
|
278
|
+
));
|
|
279
|
+
@memcpy(buffer.results()[0..results.len], results);
|
|
280
|
+
|
|
281
|
+
packet.data_size = @bitCast(BufferSize{
|
|
282
|
+
.event_count = @intCast(event_count),
|
|
283
|
+
.result_count = @intCast(results.len),
|
|
284
|
+
});
|
|
285
|
+
},
|
|
650
286
|
}
|
|
651
287
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
"Function request() requires 4 arguments exactly.",
|
|
655
|
-
) catch return null;
|
|
656
|
-
|
|
657
|
-
const context_raw = translate.value_external(
|
|
658
|
-
env,
|
|
659
|
-
argv[0],
|
|
660
|
-
"Failed to get Client Context pointer.",
|
|
661
|
-
) catch return null;
|
|
662
|
-
const context = contextCast(context_raw.?) catch return null;
|
|
663
|
-
const operation_int = translate.u32_from_value(env, argv[1], "operation") catch return null;
|
|
664
|
-
|
|
665
|
-
if (!@as(vsr.Operation, @enumFromInt(operation_int)).valid(StateMachine)) {
|
|
666
|
-
translate.throw(env, "Unknown operation.") catch return null;
|
|
667
|
-
}
|
|
668
|
-
const operation = @as(Operation, @enumFromInt(@as(u8, @intCast(operation_int))));
|
|
288
|
+
// Stuff client pointer into packet.next to store it until the packet arrives on the JS thread.
|
|
289
|
+
@as(*usize, @ptrCast(&packet.next)).* = @intFromPtr(client);
|
|
669
290
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
291
|
+
// Queue the packet to be processed on the JS thread to invoke its JS callback.
|
|
292
|
+
const completion_tsfn: c.napi_threadsafe_function = @ptrFromInt(completion_ctx);
|
|
293
|
+
switch (c.napi_call_threadsafe_function(completion_tsfn, packet, c.napi_tsfn_nonblocking)) {
|
|
294
|
+
c.napi_ok => {},
|
|
295
|
+
c.napi_queue_full => @panic("ThreadSafe Function queue is full when created with no limit."),
|
|
296
|
+
else => unreachable,
|
|
675
297
|
}
|
|
676
|
-
|
|
677
|
-
errdefer context.client.release(message);
|
|
298
|
+
}
|
|
678
299
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
300
|
+
fn on_completion_js(
|
|
301
|
+
env: c.napi_env,
|
|
302
|
+
unused_js_cb: c.napi_value,
|
|
303
|
+
unused_context: ?*anyopaque,
|
|
304
|
+
packet_argument: ?*anyopaque,
|
|
305
|
+
) callconv(.C) void {
|
|
306
|
+
_ = unused_js_cb;
|
|
307
|
+
_ = unused_context;
|
|
308
|
+
|
|
309
|
+
const packet: *tb.tb_packet_t = @ptrCast(@alignCast(packet_argument.?));
|
|
310
|
+
assert(packet.status == .ok);
|
|
311
|
+
|
|
312
|
+
// Decode the packet's Buffer results into an array then free the Buffer.
|
|
313
|
+
const array_or_error = switch (@as(Operation, @enumFromInt(packet.operation))) {
|
|
314
|
+
inline else => |op| blk: {
|
|
315
|
+
const buffer_size: BufferSize = @bitCast(packet.data_size);
|
|
316
|
+
const buffer: BufferType(op) = .{
|
|
317
|
+
.ptr = @ptrCast(packet.data.?),
|
|
318
|
+
.count = buffer_size.event_count,
|
|
319
|
+
};
|
|
320
|
+
defer buffer.free();
|
|
321
|
+
|
|
322
|
+
const results = buffer.results()[0..buffer_size.result_count];
|
|
323
|
+
break :blk encode_array(StateMachine.Result(op), env, results);
|
|
324
|
+
},
|
|
686
325
|
};
|
|
687
326
|
|
|
688
|
-
//
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
return
|
|
695
|
-
|
|
327
|
+
// Extract the remaining packet information and release it back to the client.
|
|
328
|
+
const client: tb.tb_client_t = @ptrFromInt(@as(*usize, @ptrCast(&packet.next)).*);
|
|
329
|
+
const callback_ref: c.napi_ref = @ptrCast(@alignCast(packet.user_data.?));
|
|
330
|
+
tb.release_packet(client, packet);
|
|
331
|
+
|
|
332
|
+
// Parse Result array out of packet data, freeing it in the process.
|
|
333
|
+
// NOTE: Ensure this is called before anything that could early-return to avoid a alloc leak.
|
|
334
|
+
var callback_error = napi_null;
|
|
335
|
+
const callback_result = array_or_error catch |err| switch (err) {
|
|
336
|
+
error.ExceptionThrown => blk: {
|
|
337
|
+
if (c.napi_get_and_clear_last_exception(env, &callback_error) != c.napi_ok) {
|
|
338
|
+
std.log.warn("Failed to capture callback error from thrown Exception.", .{});
|
|
339
|
+
}
|
|
340
|
+
break :blk napi_null;
|
|
341
|
+
},
|
|
342
|
+
};
|
|
696
343
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
const env = @as(translate.UserData, @bitCast(user_data)).env;
|
|
701
|
-
const callback_reference = @as(translate.UserData, @bitCast(user_data)).callback_reference;
|
|
702
|
-
defer translate.delete_reference(env, callback_reference) catch {
|
|
703
|
-
std.log.warn("on_result: Failed to delete reference to user's JS callback.", .{});
|
|
344
|
+
// Make sure to delete the callback reference once we're done calling it.
|
|
345
|
+
defer if (c.napi_delete_reference(env, callback_ref) != c.napi_ok) {
|
|
346
|
+
std.log.warn("Failed to delete reference to user's JS callback.", .{});
|
|
704
347
|
};
|
|
705
348
|
|
|
706
|
-
const
|
|
707
|
-
env,
|
|
708
|
-
callback_reference,
|
|
709
|
-
"Failed to get callback reference.",
|
|
710
|
-
) catch return;
|
|
711
|
-
const scope = translate.scope(
|
|
349
|
+
const callback = translate.reference_value(
|
|
712
350
|
env,
|
|
713
|
-
|
|
351
|
+
callback_ref,
|
|
352
|
+
"Failed to get callback from reference.",
|
|
714
353
|
) catch return;
|
|
715
|
-
const globals_raw = translate.globals(env) catch return;
|
|
716
|
-
const globals = globalsCast(globals_raw.?);
|
|
717
|
-
const argc: usize = 2;
|
|
718
|
-
var argv: [argc]c.napi_value = undefined;
|
|
719
|
-
|
|
720
|
-
const napi_results = switch (operation) {
|
|
721
|
-
.create_accounts => encode_napi_results_array(
|
|
722
|
-
CreateAccountsResult,
|
|
723
|
-
env,
|
|
724
|
-
results,
|
|
725
|
-
) catch return,
|
|
726
|
-
.create_transfers => encode_napi_results_array(
|
|
727
|
-
CreateTransfersResult,
|
|
728
|
-
env,
|
|
729
|
-
results,
|
|
730
|
-
) catch return,
|
|
731
|
-
.lookup_accounts => encode_napi_results_array(Account, env, results) catch return,
|
|
732
|
-
.lookup_transfers => encode_napi_results_array(Transfer, env, results) catch return,
|
|
733
|
-
};
|
|
734
354
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
translate.call_function(env, scope, napi_callback, argc, argv[0..]) catch {
|
|
739
|
-
translate.throw(env, "Failed to call JS results callback.") catch return;
|
|
740
|
-
};
|
|
355
|
+
var args = [_]c.napi_value{ callback_error, callback_result };
|
|
356
|
+
_ = translate.call_function(env, napi_null, callback, &args) catch return;
|
|
741
357
|
}
|
|
742
358
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
translate.
|
|
359
|
+
// (De)Serialization
|
|
360
|
+
|
|
361
|
+
fn decode_array(comptime Event: type, env: c.napi_env, array: c.napi_value, events: []Event) !void {
|
|
362
|
+
for (events, 0..) |*event, i| {
|
|
363
|
+
const object = try translate.array_element(env, array, @intCast(i));
|
|
364
|
+
switch (Event) {
|
|
365
|
+
Account, Transfer => {
|
|
366
|
+
inline for (std.meta.fields(Event)) |field| {
|
|
367
|
+
const FieldInt = switch (@typeInfo(field.type)) {
|
|
368
|
+
.Struct => |info| info.backing_integer.?,
|
|
369
|
+
else => field.type,
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const value = try @field(translate, @typeName(FieldInt) ++ "_from_object")(
|
|
373
|
+
env,
|
|
374
|
+
object,
|
|
375
|
+
@ptrCast(field.name ++ "\x00"),
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
if (std.mem.eql(u8, field.name, "timestamp") and value != 0) {
|
|
379
|
+
return translate.throw(
|
|
380
|
+
env,
|
|
381
|
+
"Timestamp should be set to 0 as this will be set correctly by Replica.",
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
@field(event, field.name) = switch (@typeInfo(field.type)) {
|
|
386
|
+
.Struct => @as(field.type, @bitCast(value)),
|
|
387
|
+
else => value,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
u128 => event.* = try translate.u128_from_value(env, object, "lookup"),
|
|
392
|
+
else => @compileError("invalid Event type"),
|
|
393
|
+
}
|
|
748
394
|
}
|
|
395
|
+
}
|
|
749
396
|
|
|
750
|
-
|
|
397
|
+
fn encode_array(comptime Result: type, env: c.napi_env, results: []const Result) !c.napi_value {
|
|
398
|
+
const array = try translate.create_array(
|
|
751
399
|
env,
|
|
752
|
-
|
|
753
|
-
|
|
400
|
+
@intCast(results.len),
|
|
401
|
+
"Failed to allocate array for results.",
|
|
402
|
+
);
|
|
754
403
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
const context = contextCast(context_raw.?) catch return null;
|
|
404
|
+
for (results, 0..) |*result, i| {
|
|
405
|
+
const object = try translate.create_object(
|
|
406
|
+
env,
|
|
407
|
+
"Failed to create " ++ @typeName(Result) ++ " object.",
|
|
408
|
+
);
|
|
761
409
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
410
|
+
inline for (std.meta.fields(Result)) |field| {
|
|
411
|
+
const FieldInt = switch (@typeInfo(field.type)) {
|
|
412
|
+
.Struct => |info| info.backing_integer.?,
|
|
413
|
+
.Enum => |info| info.tag_type,
|
|
414
|
+
else => field.type,
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
const value: FieldInt = switch (@typeInfo(field.type)) {
|
|
418
|
+
.Struct => @bitCast(@field(result, field.name)),
|
|
419
|
+
.Enum => @intFromEnum(@field(result, field.name)),
|
|
420
|
+
else => @field(result, field.name),
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
try @field(translate, @typeName(FieldInt) ++ "_into_object")(
|
|
424
|
+
env,
|
|
425
|
+
object,
|
|
426
|
+
@ptrCast(field.name ++ "\x00"),
|
|
427
|
+
value,
|
|
428
|
+
"Failed to set property \"" ++ field.name ++ "\" of " ++ @typeName(Result) ++ " object",
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
try translate.set_array_element(
|
|
432
|
+
env,
|
|
433
|
+
array,
|
|
434
|
+
@intCast(i),
|
|
435
|
+
object,
|
|
436
|
+
"Failed to set element in results array.",
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return array;
|
|
770
442
|
}
|
|
771
443
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
444
|
+
/// Each packet allocates enough room to hold both its Events and its Results.
|
|
445
|
+
/// Buffer is an abstraction over the memory management for this.
|
|
446
|
+
fn BufferType(comptime op: Operation) type {
|
|
447
|
+
return struct {
|
|
448
|
+
const Buffer = @This();
|
|
449
|
+
const Event = StateMachine.Event(op);
|
|
450
|
+
const Result = StateMachine.Result(op);
|
|
451
|
+
const max_align: u29 = @max(@alignOf(Event), @alignOf(Result));
|
|
452
|
+
|
|
453
|
+
ptr: [*]u8,
|
|
454
|
+
count: u32,
|
|
455
|
+
|
|
456
|
+
fn alloc(env: c.napi_env, count: u32) !Buffer {
|
|
457
|
+
// Allocate enough bytes to hold memory for the Events and the Results.
|
|
458
|
+
const max_bytes = @max(@sizeOf(Event) * count, @sizeOf(Result) * count);
|
|
459
|
+
if (@sizeOf(vsr.Header) + max_bytes > constants.message_size_max) {
|
|
460
|
+
return translate.throw(env, "Batch is larger than the maximum message size.");
|
|
461
|
+
}
|
|
778
462
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
463
|
+
const bytes = allocator.alignedAlloc(u8, max_align, max_bytes) catch |e| switch (e) {
|
|
464
|
+
error.OutOfMemory => return translate.throw(env, "Batch allocation ran out of memory."),
|
|
465
|
+
};
|
|
466
|
+
errdefer allocator.free(bytes);
|
|
783
467
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
468
|
+
return Buffer{
|
|
469
|
+
.ptr = bytes.ptr,
|
|
470
|
+
.count = count,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
fn free(buffer: Buffer) void {
|
|
475
|
+
const max_bytes = @max(@sizeOf(Event) * buffer.count, @sizeOf(Result) * buffer.count);
|
|
476
|
+
const bytes: []align(max_align) u8 = @alignCast(buffer.ptr[0..max_bytes]);
|
|
477
|
+
allocator.free(bytes);
|
|
478
|
+
}
|
|
790
479
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
480
|
+
fn events(buffer: Buffer) []Event {
|
|
481
|
+
const event_bytes = buffer.ptr[0 .. @sizeOf(Event) * buffer.count];
|
|
482
|
+
return @alignCast(std.mem.bytesAsSlice(Event, event_bytes));
|
|
483
|
+
}
|
|
795
484
|
|
|
796
|
-
|
|
485
|
+
fn results(buffer: Buffer) []Result {
|
|
486
|
+
const result_bytes = buffer.ptr[0 .. @sizeOf(Result) * buffer.count];
|
|
487
|
+
return @alignCast(std.mem.bytesAsSlice(Result, result_bytes));
|
|
488
|
+
}
|
|
489
|
+
};
|
|
797
490
|
}
|