@mikrojs/native 0.21.1-next.20260927212749 → 0.22.0

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.
@@ -2,6 +2,7 @@
2
2
  #include <string.h>
3
3
 
4
4
  #include "mikrojs/mikrojs.h"
5
+ #include "mikrojs/private.h"
5
6
  #include "mikrojs/utils.h"
6
7
 
7
8
  /* ---- TextEncoder ---- */
@@ -23,11 +24,12 @@ static JSValue mik__text_encoder_constructor(JSContext* ctx, JSValue new_target,
23
24
 
24
25
  static JSValue mik__text_encoder_encode(JSContext* ctx, JSValue this_val, int argc,
25
26
  JSValue* argv) {
26
- const char* str = JS_ToCString(ctx, argv[0]);
27
+ size_t len = 0;
28
+ const char* str = JS_ToCStringLen(ctx, &len, argv[0]);
27
29
  if (!str) {
28
30
  return JS_EXCEPTION;
29
31
  }
30
- JSValue ret = JS_NewUint8ArrayCopy(ctx, (const uint8_t*)str, strlen(str));
32
+ JSValue ret = JS_NewUint8ArrayCopy(ctx, (const uint8_t*)str, len);
31
33
  JS_FreeCString(ctx, str);
32
34
  return ret;
33
35
  }
@@ -135,11 +137,38 @@ static bool utf8_seq_valid(const uint8_t* p, int len) {
135
137
  return false;
136
138
  }
137
139
 
138
- /* Append U+FFFD (EF BF BD) to buffer. */
139
- static void append_replacement(uint8_t* out, size_t* out_len) {
140
- out[(*out_len)++] = 0xEF;
141
- out[(*out_len)++] = 0xBF;
142
- out[(*out_len)++] = 0xBD;
140
+ /* Copy `n` bytes to the output when there is one; always count them. */
141
+ static void utf8_put(uint8_t* out, size_t* out_len, const uint8_t* src, size_t n) {
142
+ if (out) memcpy(out + *out_len, src, n);
143
+ *out_len += n;
144
+ }
145
+
146
+ size_t mik__utf8_replace(const uint8_t* in, size_t len, bool hold_tail, uint8_t* out,
147
+ size_t* tail) {
148
+ static const uint8_t replacement[3] = {0xEF, 0xBF, 0xBD};
149
+ size_t out_len = 0;
150
+ size_t i = 0;
151
+ *tail = 0;
152
+ while (i < len) {
153
+ int seq = utf8_seq_len(in[i]);
154
+ if (seq > 0 && i + seq > len) {
155
+ /* Incomplete trailing sequence: hold it, or one U+FFFD per byte. */
156
+ if (hold_tail) {
157
+ *tail = len - i;
158
+ break;
159
+ }
160
+ for (; i < len; i++) utf8_put(out, &out_len, replacement, 3);
161
+ break;
162
+ }
163
+ if (seq == 0 || !utf8_seq_valid(in + i, seq)) {
164
+ utf8_put(out, &out_len, replacement, 3);
165
+ i++;
166
+ continue;
167
+ }
168
+ utf8_put(out, &out_len, in + i, seq);
169
+ i += seq;
170
+ }
171
+ return out_len;
143
172
  }
144
173
 
145
174
  static JSValue mik__text_decoder_decode(JSContext* ctx, JSValue this_val, int argc,
@@ -185,53 +214,28 @@ static JSValue mik__text_decoder_decode(JSContext* ctx, JSValue this_val, int ar
185
214
  memcpy(heap_input + state->pending_len, data, len);
186
215
  input = heap_input;
187
216
  }
188
- state->pending_len = 0;
189
-
190
- /* Worst case output: every byte becomes U+FFFD (3 bytes). */
191
- size_t out_cap = total * 3;
192
- if (out_cap == 0) out_cap = 1;
193
- uint8_t* out = static_cast<uint8_t*>(js_malloc(ctx, out_cap));
194
- if (!out) {
195
- if (heap_input) js_free(ctx, heap_input);
196
- return JS_EXCEPTION;
197
- }
198
- size_t out_len = 0;
199
217
 
200
- size_t i = 0;
201
- while (i < total) {
202
- uint8_t b = input[i];
203
- int seq = utf8_seq_len(b);
204
- if (seq == 0) {
205
- append_replacement(out, &out_len);
206
- i++;
207
- continue;
208
- }
209
- if (i + seq > total) {
210
- /* Incomplete trailing sequence. */
211
- if (stream) {
212
- size_t held = total - i;
213
- for (size_t k = 0; k < held; k++) state->pending[k] = input[i + k];
214
- state->pending_len = (uint8_t)held;
215
- break;
216
- }
217
- /* Flush: one U+FFFD per held byte. */
218
- for (size_t k = i; k < total; k++) append_replacement(out, &out_len);
219
- i = total;
220
- break;
221
- }
222
- if (!utf8_seq_valid(input + i, seq)) {
223
- append_replacement(out, &out_len);
224
- i++;
225
- continue;
218
+ /* Valid input (the usual case) becomes a string as is; only invalid input
219
+ * pays for a repaired copy, sized by the counting pass. */
220
+ size_t tail = 0;
221
+ size_t out_len = mik__utf8_replace(input, total, stream, nullptr, &tail);
222
+ size_t used = total - tail;
223
+ JSValue ret;
224
+ if (out_len == used) {
225
+ ret = JS_NewStringLen(ctx, (const char*)input, used);
226
+ } else {
227
+ uint8_t* out = static_cast<uint8_t*>(js_malloc(ctx, out_len));
228
+ if (!out) {
229
+ if (heap_input) js_free(ctx, heap_input);
230
+ return JS_EXCEPTION;
226
231
  }
227
- memcpy(out + out_len, input + i, seq);
228
- out_len += seq;
229
- i += seq;
232
+ mik__utf8_replace(input, total, stream, out, &tail);
233
+ ret = JS_NewStringLen(ctx, (const char*)out, out_len);
234
+ js_free(ctx, out);
230
235
  }
231
-
236
+ if (tail) memcpy(state->pending, input + used, tail);
237
+ state->pending_len = (uint8_t)tail;
232
238
  if (heap_input) js_free(ctx, heap_input);
233
- JSValue ret = JS_NewStringLen(ctx, (const char*)out, out_len);
234
- js_free(ctx, out);
235
239
  return ret;
236
240
  }
237
241
 
@@ -353,55 +357,41 @@ static size_t b64_decode(const char* src, size_t len, uint8_t* dst, int* err) {
353
357
 
354
358
  /* btoa: encode a binary string to base64.
355
359
  * Per the web spec, the argument is first coerced to a string (ToString), then
356
- * each character's code point is treated as a raw byte. We iterate by code
357
- * point, not by UTF-8 bytes, since JS_ToCStringLen would turn e.g. U+0080 into
358
- * two bytes (\xC2\x80). */
360
+ * each character's code point is treated as a raw byte. The string arrives as
361
+ * UTF-8, so a code point is one byte (ASCII), two bytes (U+0080..U+00FF), or
362
+ * out of range. */
359
363
  static JSValue mik__btoa(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
360
- JSValue str_val = JS_ToString(ctx, argv[0]);
361
- if (JS_IsException(str_val)) return JS_EXCEPTION;
362
-
363
- JSValue len_val = JS_GetPropertyStr(ctx, str_val, "length");
364
- int64_t len;
365
- if (JS_ToInt64(ctx, &len, len_val)) {
366
- JS_FreeValue(ctx, len_val);
367
- JS_FreeValue(ctx, str_val);
368
- return JS_EXCEPTION;
369
- }
370
- JS_FreeValue(ctx, len_val);
364
+ size_t str_len = 0;
365
+ const char* str = JS_ToCStringLen(ctx, &str_len, argv[0]);
366
+ if (!str) return JS_EXCEPTION;
371
367
 
372
- /* Extract code points into a byte buffer, validating latin1 range */
373
- uint8_t* bytes = static_cast<uint8_t*>(js_malloc(ctx, len > 0 ? len : 1));
368
+ /* Never more code points than UTF-8 bytes. */
369
+ uint8_t* bytes = static_cast<uint8_t*>(js_malloc(ctx, str_len > 0 ? str_len : 1));
374
370
  if (!bytes) {
375
- JS_FreeValue(ctx, str_val);
371
+ JS_FreeCString(ctx, str);
376
372
  return JS_EXCEPTION;
377
373
  }
378
-
379
- JSAtom charCodeAt_atom = JS_NewAtom(ctx, "charCodeAt");
380
- for (int64_t i = 0; i < len; i++) {
381
- JSValue idx = JS_NewInt64(ctx, i);
382
- JSValue code = JS_Invoke(ctx, str_val, charCodeAt_atom, 1, &idx);
383
- JS_FreeValue(ctx, idx);
384
- if (JS_IsException(code)) {
385
- JS_FreeAtom(ctx, charCodeAt_atom);
386
- js_free(ctx, bytes);
387
- JS_FreeValue(ctx, str_val);
388
- return JS_EXCEPTION;
374
+ size_t len = 0;
375
+ for (size_t i = 0; i < str_len;) {
376
+ uint8_t b = (uint8_t)str[i];
377
+ uint32_t cp = 0x100;
378
+ if (b < 0x80) {
379
+ cp = b;
380
+ i += 1;
381
+ } else if ((b & 0xE0) == 0xC0 && i + 1 < str_len) {
382
+ cp = ((b & 0x1Fu) << 6) | ((uint8_t)str[i + 1] & 0x3Fu);
383
+ i += 2;
389
384
  }
390
- int32_t cp;
391
- JS_ToInt32(ctx, &cp, code);
392
- JS_FreeValue(ctx, code);
393
385
  if (cp > 0xFF) {
394
- JS_FreeAtom(ctx, charCodeAt_atom);
395
386
  js_free(ctx, bytes);
396
- JS_FreeValue(ctx, str_val);
387
+ JS_FreeCString(ctx, str);
397
388
  return JS_ThrowRangeError(ctx,
398
389
  "The string to be encoded contains characters outside of the "
399
390
  "Latin1 range");
400
391
  }
401
- bytes[i] = (uint8_t)cp;
392
+ bytes[len++] = (uint8_t)cp;
402
393
  }
403
- JS_FreeAtom(ctx, charCodeAt_atom);
404
- JS_FreeValue(ctx, str_val);
394
+ JS_FreeCString(ctx, str);
405
395
 
406
396
  size_t out_len = 4 * ((len + 2) / 3);
407
397
  char* out = static_cast<char*>(js_malloc(ctx, out_len + 1));
@@ -411,7 +401,7 @@ static JSValue mik__btoa(JSContext* ctx, JSValue this_val, int argc, JSValue* ar
411
401
  }
412
402
 
413
403
  size_t j = 0;
414
- for (int64_t i = 0; i < len; i += 3) {
404
+ for (size_t i = 0; i < len; i += 3) {
415
405
  uint32_t a = bytes[i];
416
406
  uint32_t b = (i + 1 < len) ? bytes[i + 1] : 0;
417
407
  uint32_t c = (i + 2 < len) ? bytes[i + 2] : 0;
package/src/mik_udp.cpp CHANGED
@@ -520,7 +520,10 @@ static JSValue mik__udp_bind(JSContext* ctx, JSValue this_val, int argc, JSValue
520
520
  JS_FreeValue(ctx, rq_val);
521
521
  if (recv_queue < 1) recv_queue = 1;
522
522
 
523
- /* Create socket */
523
+ /* Create socket. The stack may not be running yet on platforms that
524
+ * start it on demand (nothing else in this file needs an interface). */
525
+ const MIKPlatform* platform = MIK_GetPlatform();
526
+ if (platform->net_init) platform->net_init();
524
527
  int fd = socket(family, SOCK_DGRAM, 0);
525
528
  if (fd < 0) {
526
529
  if (address) JS_FreeCString(ctx, address);
@@ -650,6 +653,13 @@ static JSValue mik__udp_bind(JSContext* ctx, JSValue this_val, int argc, JSValue
650
653
 
651
654
  /* ── Loop consumer ───────────────────────────────────────────────────*/
652
655
 
656
+ bool mik__udp_has_open_sockets(JSContext* ctx) {
657
+ for (UdpSocketState* s : g_open_sockets) {
658
+ if (s && !s->dead && !s->closed && s->ctx == ctx) return true;
659
+ }
660
+ return false;
661
+ }
662
+
653
663
  static void mik__udp_consume(JSContext* ctx) {
654
664
  g_iteration_depth++;
655
665
 
package/src/mikrojs.cpp CHANGED
@@ -316,6 +316,7 @@ MIKRuntime* MIK_NewRuntimeInternal(MIKRunOptions* options) {
316
316
  mik_rt->result_proto = JS_UNDEFINED;
317
317
  mik_rt->result_ok_void_singleton = JS_UNDEFINED;
318
318
  mik_rt->http_body_consumed_ctor = JS_UNDEFINED;
319
+ mik_rt->inspect_cache.object_proto = JS_UNDEFINED;
319
320
 
320
321
  /* Default fs read cap: 64 KiB. Large enough for typical config/JSON
321
322
  * payloads on MCU; small enough that a runaway readFile() can't
@@ -379,11 +380,6 @@ MIKRuntime* MIK_NewRuntimeInternal(MIKRunOptions* options) {
379
380
  mik__inspect_register(ctx);
380
381
  mik__pub_fs_register(ctx);
381
382
 
382
- /* Call registered native module init functions */
383
- for (const auto& mod : mik_rt->native_modules) {
384
- mod.init_fn(ctx);
385
- }
386
-
387
383
  JSModuleDef* stdio_mod = JS_NewCModule(ctx, "native:mikro/stdio", mik__stdio_module_init);
388
384
  CHECK_NOT_NULL(stdio_mod);
389
385
  mik__add_exports(ctx, stdio_mod, stdio_exports, countof(stdio_exports));
@@ -455,7 +451,16 @@ void MIK_FreeRuntime(MIKRuntime* mik_rt) {
455
451
  mik_rt->http_body_consumed_ctor = JS_UNDEFINED;
456
452
  JS_FreeValue(mik_rt->ctx, mik_rt->result_proto);
457
453
  mik_rt->result_proto = JS_UNDEFINED;
454
+ MIKInspectCache& ic = mik_rt->inspect_cache;
455
+ JS_FreeValue(mik_rt->ctx, ic.object_proto);
456
+ ic.object_proto = JS_UNDEFINED;
458
457
  JS_FreeContext(mik_rt->ctx);
458
+ MIKResultAtoms& ra = mik_rt->result_atoms;
459
+ JS_FreeAtomsRT(mik_rt->rt, ra.ok, ra.value, ra.error, ra.err, ra.code, ra.message, ra.name);
460
+ ra = {};
461
+ JS_FreeAtomsRT(mik_rt->rt, ic.inspect_symbol, ic.to_string_tag);
462
+ ic.inspect_symbol = JS_ATOM_NULL;
463
+ ic.to_string_tag = JS_ATOM_NULL;
459
464
  JS_FreeRuntime(mik_rt->rt);
460
465
 
461
466
  /* Call destructor for C++ members, then free */
@@ -531,11 +536,6 @@ void MIK_SetTestEmitHandler(MIKRuntime* mik_rt, MIKTestEmitHandlerFn fn, void* o
531
536
  mik_rt->test_emit_opaque = opaque;
532
537
  }
533
538
 
534
- void MIK_RegisterNativeModuleInit(MIKRuntime* mik_rt, const char* name,
535
- MIKNativeModuleInitFn init_fn) {
536
- mik_rt->native_modules.push_back({name, init_fn});
537
- }
538
-
539
539
  void MIK_RegisterLoopConsumer(MIKRuntime* mik_rt, MIKLoopConsumeFn consume_fn,
540
540
  MIKLoopDestroyFn destroy_fn) {
541
541
  /* Idempotent: a module can be brought up twice — once by native code that
@@ -768,6 +768,51 @@ int MIK_Loop(MIKRuntime* mik_rt) {
768
768
  return mik_rt->stop_requested ? 1 : 0;
769
769
  }
770
770
 
771
+ /* The nearer of a wake (-1 = none) and a deadline `delay` us away. */
772
+ static int64_t mik__nearer_wake(int64_t wake, int64_t delay) {
773
+ if (delay <= 0) return 0;
774
+ return (wake < 0 || delay < wake) ? delay : wake;
775
+ }
776
+
777
+ /* The UDP consumer polls recvfrom: bound sockets get this poll period. */
778
+ static constexpr int64_t MIK__UDP_POLL_US = 10 * 1000;
779
+
780
+ int64_t mik__next_wake_us(MIKRuntime* mik_rt) {
781
+ int64_t now = MIK_GetPlatform()->get_boot_us();
782
+ /* The grace window has one job, the restart, and MIK_Loop takes it at
783
+ * the deadline; nothing else runs meanwhile. */
784
+ if (mik_rt->restart_at_us > 0) {
785
+ return mik__nearer_wake(-1, mik_rt->restart_at_us - now);
786
+ }
787
+ if (mik_rt->stop_requested || JS_IsJobPending(mik_rt->rt)) {
788
+ return 0;
789
+ }
790
+ int64_t wake = -1;
791
+ for (const auto& entry : mik_rt->timers->entries) {
792
+ wake = mik__nearer_wake(wake, entry.next_deadline - now);
793
+ if (wake == 0) return 0;
794
+ }
795
+ /* The feed and awake checks run at the top of the pass, so a wait must
796
+ * end by their deadlines for them to fire on time. */
797
+ if (mik_rt->feed_armed) {
798
+ wake = mik__nearer_wake(
799
+ wake, mik_rt->feed_last_us + (int64_t)mik_rt->config.feed_timeout_ms * 1000 - now);
800
+ }
801
+ if (mik_rt->awake_armed) {
802
+ wake = mik__nearer_wake(wake, mik_rt->awake_pause_offset_us +
803
+ (int64_t)mik_rt->config.awake_timeout_ms * 1000 - now);
804
+ }
805
+ if (mik__udp_has_open_sockets(mik_rt->ctx)) {
806
+ wake = mik__nearer_wake(wake, MIK__UDP_POLL_US);
807
+ }
808
+ return wake;
809
+ }
810
+
811
+ void MIK_Wake(void) {
812
+ const MIKPlatform* platform = MIK_GetPlatform();
813
+ if (platform->wake) platform->wake();
814
+ }
815
+
771
816
  void MIK_SetConfig(MIKRuntime* mik_rt, const MIKConfig* config) {
772
817
  CHECK_NOT_NULL(mik_rt);
773
818
  CHECK_NOT_NULL(config);
package/src/modules.cpp CHANGED
@@ -252,6 +252,7 @@ static JSModuleDef* mik_module_loader_inner(JSContext* ctx, const char* module_n
252
252
  {"mikro/http/helpers", mik__http_helpers_load},
253
253
  {"mikro/http/request", mik__http_request_load},
254
254
  {"mikro/observable/operators", mik__observable_operators_load},
255
+ {"mikro/test", mik__test_load},
255
256
  {"mikro/wifi", mik__wifi_client_load},
256
257
  };
257
258
  for (const auto& cm : c_modules) {
@@ -1,5 +1,7 @@
1
1
  #include "mikrojs/platform.h"
2
2
 
3
+ #include <errno.h>
4
+ #include <pthread.h>
3
5
  #include <stdarg.h>
4
6
  #include <stdio.h>
5
7
  #include <stdlib.h>
@@ -29,6 +31,38 @@ static void posix_yield(void) {
29
31
  usleep(1000);
30
32
  }
31
33
 
34
+ /* One waiter (the serve loop's thread); wakes are latched so one that lands
35
+ * before the wait is not lost. */
36
+ static pthread_mutex_t posix_wake_mutex = PTHREAD_MUTEX_INITIALIZER;
37
+ static pthread_cond_t posix_wake_cond = PTHREAD_COND_INITIALIZER;
38
+ static bool posix_wake_pending = false;
39
+
40
+ static void posix_wait(int64_t timeout_us) {
41
+ struct timespec deadline;
42
+ clock_gettime(CLOCK_REALTIME, &deadline);
43
+ deadline.tv_sec += timeout_us / 1000000;
44
+ deadline.tv_nsec += (timeout_us % 1000000) * 1000;
45
+ if (deadline.tv_nsec >= 1000000000L) {
46
+ deadline.tv_sec += 1;
47
+ deadline.tv_nsec -= 1000000000L;
48
+ }
49
+ pthread_mutex_lock(&posix_wake_mutex);
50
+ while (!posix_wake_pending) {
51
+ if (pthread_cond_timedwait(&posix_wake_cond, &posix_wake_mutex, &deadline) == ETIMEDOUT) {
52
+ break;
53
+ }
54
+ }
55
+ posix_wake_pending = false;
56
+ pthread_mutex_unlock(&posix_wake_mutex);
57
+ }
58
+
59
+ static void posix_wake(void) {
60
+ pthread_mutex_lock(&posix_wake_mutex);
61
+ posix_wake_pending = true;
62
+ pthread_cond_signal(&posix_wake_cond);
63
+ pthread_mutex_unlock(&posix_wake_mutex);
64
+ }
65
+
32
66
  static size_t posix_get_free_system_mem(void) {
33
67
  return 0; /* Not available on desktop */
34
68
  }
@@ -152,6 +186,8 @@ static const MIKPlatform posix_platform = {
152
186
  .random = posix_random,
153
187
  .restart = posix_restart,
154
188
  .yield = posix_yield,
189
+ .wait = posix_wait,
190
+ .wake = posix_wake,
155
191
  .get_free_system_mem = posix_get_free_system_mem,
156
192
  .get_min_free_system_mem = posix_get_min_free_system_mem,
157
193
  .get_total_system_mem = posix_get_total_system_mem,
package/src/timers.cpp CHANGED
@@ -152,9 +152,9 @@ void mik__timers_consume(JSContext* ctx) {
152
152
 
153
153
  for (auto& entry : timers->entries) {
154
154
  if (entry.next_deadline <= now) {
155
- if (due_count < MIK_MAX_DUE_TIMERS) {
156
- due_ids[due_count++] = entry.id;
157
- }
155
+ // Past the cap, a timer stays due and runs on the next pass.
156
+ if (due_count == MIK_MAX_DUE_TIMERS) break;
157
+ due_ids[due_count++] = entry.id;
158
158
  // Drift-free: advance from the previous deadline, not from now, so the rate
159
159
  // stays exact. After a stall of a whole period, resume from now; never burst.
160
160
  if (entry.is_interval) {
package/src/utils.cpp CHANGED
@@ -24,8 +24,7 @@ JSValue mik_throw_errno(JSContext* ctx, int err) {
24
24
  }
25
25
 
26
26
  void mik_assert(const struct AssertionInfo info) {
27
- fprintf(stderr, "%s:%s%s Assertion `%s' failed.\n", info.file_line, info.function,
28
- *info.function ? ":" : "", info.message);
27
+ fprintf(stderr, "%s Assertion '%s' failed\n", info.file_line, info.message);
29
28
  fflush(stderr);
30
29
  abort();
31
30
  }
@@ -234,12 +233,6 @@ void MIK_ClearPromise(JSContext* ctx, MIKPromise* p) {
234
233
  p->rfuncs[1] = JS_UNDEFINED;
235
234
  }
236
235
 
237
- void MIK_MarkPromise(JSRuntime* rt, MIKPromise* p, JS_MarkFunc* mark_func) {
238
- JS_MarkValue(rt, p->p, mark_func);
239
- JS_MarkValue(rt, p->rfuncs[0], mark_func);
240
- JS_MarkValue(rt, p->rfuncs[1], mark_func);
241
- }
242
-
243
236
  void MIK_SettlePromise(JSContext* ctx, MIKPromise* p, bool is_reject, int argc, JSValue* argv) {
244
237
  JSValue ret = JS_Call(ctx, p->rfuncs[is_reject], JS_UNDEFINED, argc, argv);
245
238
  for (int i = 0; i < argc; i++) {
@@ -301,7 +294,19 @@ static void* mik__buf_realloc(JSRuntime* rt, void* opaque, void* ptr, size_t siz
301
294
  }
302
295
 
303
296
  JSValue MIK_NewUint8Array(JSContext* ctx, uint8_t* data, size_t size) {
304
- return JS_NewUint8Array(ctx, data, size, mik__buf_realloc, NULL, false);
297
+ /* JS_NewUint8Array leaks `data` when the ArrayBuffer object cannot be
298
+ * allocated. Once JS_NewArrayBuffer succeeds, the buffer owns `data`. */
299
+ JSValue ab = JS_NewArrayBuffer(ctx, data, size, 0, mik__buf_realloc, NULL, false);
300
+ if (JS_IsException(ab)) {
301
+ js_free(ctx, data);
302
+ return ab;
303
+ }
304
+ /* Before quickjs-ng 0.17.0, JS_NewTypedArray reads argv[1] and argv[2]
305
+ * whatever argc says, so pass all three. */
306
+ JSValueConst args[3] = {ab, JS_UNDEFINED, JS_UNDEFINED};
307
+ JSValue u8 = JS_NewTypedArray(ctx, 3, args, JS_TYPED_ARRAY_UINT8);
308
+ JS_FreeValue(ctx, ab);
309
+ return u8;
305
310
  }
306
311
 
307
312
  void mik_dbuf_init(JSContext* ctx, DynBuf* s) {