@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.
- package/CMakeLists.txt +2 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -5
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +1 -2
- package/dist/types.d.ts.map +1 -1
- package/include/mikrojs/mem.h +0 -1
- package/include/mikrojs/mikrojs.h +6 -6
- package/include/mikrojs/platform.h +13 -0
- package/include/mikrojs/private.h +60 -19
- package/include/mikrojs/utils.h +13 -8
- package/package.json +4 -4
- package/prebuilds/darwin-arm64/mikrojs.napi.node +0 -0
- package/prebuilds/linux-arm64/mikrojs.napi.node +0 -0
- package/prebuilds/linux-x64/mikrojs.napi.node +0 -0
- package/runtime/modules.json +1 -1
- package/runtime/sys/types.ts +5 -2
- package/src/mem.cpp +2 -6
- package/src/mik_http_client.cpp +49 -32
- package/src/mik_inspect.cpp +101 -86
- package/src/mik_observable.cpp +26 -6
- package/src/mik_ota_client.cpp +3 -0
- package/src/mik_ota_js_hooks.cpp +3 -0
- package/src/mik_repl.cpp +40 -25
- package/src/mik_result.cpp +51 -43
- package/src/mik_sys.cpp +32 -1
- package/src/mik_test.cpp +1334 -0
- package/src/mik_text_encoding.cpp +77 -87
- package/src/mik_udp.cpp +11 -1
- package/src/mikrojs.cpp +55 -10
- package/src/modules.cpp +1 -0
- package/src/platform_posix.cpp +36 -0
- package/src/timers.cpp +3 -3
- package/src/utils.cpp +14 -9
- package/runtime/test/test.ts +0 -686
- package/src/eval_bytecode.cpp +0 -43
package/src/mik_inspect.cpp
CHANGED
|
@@ -20,6 +20,7 @@ struct InspectOpts {
|
|
|
20
20
|
std::vector<void*> seen;
|
|
21
21
|
/* Nesting of [cause] rendering, capped like mik__report_uncaught */
|
|
22
22
|
int cause_level = 0;
|
|
23
|
+
const MIKInspectCache* cache = nullptr;
|
|
23
24
|
};
|
|
24
25
|
|
|
25
26
|
/* ── Helpers ─────────────────────────────────────────────────────── */
|
|
@@ -106,23 +107,10 @@ static JSValue mik__custom_inspect_cb(JSContext* ctx, JSValue /*this_val*/, int
|
|
|
106
107
|
* Returns true and sets `out` if custom inspect was used. */
|
|
107
108
|
static bool try_custom_inspect(JSContext* ctx, JSValue value, InspectOpts& opts, int depth,
|
|
108
109
|
std::string& out) {
|
|
109
|
-
JSValue global = JS_GetGlobalObject(ctx);
|
|
110
|
-
JSValue symbol_ctor = JS_GetPropertyStr(ctx, global, "Symbol");
|
|
111
|
-
JSValue for_fn = JS_GetPropertyStr(ctx, symbol_ctor, "for");
|
|
112
|
-
|
|
113
|
-
JSValue key_str = JS_NewString(ctx, "mikrojs.inspect");
|
|
114
|
-
JSValue inspect_sym = JS_Call(ctx, for_fn, symbol_ctor, 1, &key_str);
|
|
115
|
-
JS_FreeValue(ctx, key_str);
|
|
116
|
-
JS_FreeValue(ctx, for_fn);
|
|
117
|
-
|
|
118
110
|
bool used = false;
|
|
119
111
|
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
} else {
|
|
123
|
-
JSAtom sym_atom = JS_ValueToAtom(ctx, inspect_sym);
|
|
124
|
-
JSValue custom_fn = JS_GetProperty(ctx, value, sym_atom);
|
|
125
|
-
JS_FreeAtom(ctx, sym_atom);
|
|
112
|
+
if (opts.cache->inspect_symbol != JS_ATOM_NULL) {
|
|
113
|
+
JSValue custom_fn = JS_GetProperty(ctx, value, opts.cache->inspect_symbol);
|
|
126
114
|
if (JS_IsException(custom_fn)) drain_exception(ctx);
|
|
127
115
|
|
|
128
116
|
if (JS_IsFunction(ctx, custom_fn)) {
|
|
@@ -153,9 +141,6 @@ static bool try_custom_inspect(JSContext* ctx, JSValue value, InspectOpts& opts,
|
|
|
153
141
|
}
|
|
154
142
|
JS_FreeValue(ctx, custom_fn);
|
|
155
143
|
}
|
|
156
|
-
JS_FreeValue(ctx, inspect_sym);
|
|
157
|
-
JS_FreeValue(ctx, symbol_ctor);
|
|
158
|
-
JS_FreeValue(ctx, global);
|
|
159
144
|
|
|
160
145
|
return used;
|
|
161
146
|
}
|
|
@@ -206,16 +191,11 @@ static std::string escape_string(const char* str, size_t len) {
|
|
|
206
191
|
return out;
|
|
207
192
|
}
|
|
208
193
|
|
|
209
|
-
/* ── Type detection
|
|
194
|
+
/* ── Type detection ──────────────────────────────────────────────── */
|
|
210
195
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
JSValue
|
|
214
|
-
JSValue object_ctor = JS_GetPropertyStr(ctx, global, "Object");
|
|
215
|
-
JSValue prototype = JS_GetPropertyStr(ctx, object_ctor, "prototype");
|
|
216
|
-
JSValue to_string = JS_GetPropertyStr(ctx, prototype, "toString");
|
|
217
|
-
|
|
218
|
-
JSValue result = JS_Call(ctx, to_string, value, 0, nullptr);
|
|
196
|
+
/* Tag from the engine's Object.prototype.toString, "Foo" out of "[object Foo]" */
|
|
197
|
+
static std::string to_string_type(JSContext* ctx, JSValue value) {
|
|
198
|
+
JSValue result = JS_ToObjectString(ctx, value);
|
|
219
199
|
|
|
220
200
|
std::string type;
|
|
221
201
|
if (JS_IsException(result)) {
|
|
@@ -236,11 +216,47 @@ static std::string get_object_type(JSContext* ctx, JSValue value) {
|
|
|
236
216
|
}
|
|
237
217
|
|
|
238
218
|
JS_FreeValue(ctx, result);
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
JS_FreeValue(ctx, object_ctor);
|
|
242
|
-
JS_FreeValue(ctx, global);
|
|
219
|
+
return type;
|
|
220
|
+
}
|
|
243
221
|
|
|
222
|
+
/* Class checks plus one Symbol.toStringTag read give toString's tag; other
|
|
223
|
+
* classes go through toString. */
|
|
224
|
+
static std::string get_object_type(JSContext* ctx, JSValue value, const MIKInspectCache& cache) {
|
|
225
|
+
const char* builtin = nullptr;
|
|
226
|
+
if (JS_IsError(value)) {
|
|
227
|
+
builtin = "Error";
|
|
228
|
+
} else if (JS_IsDate(value)) {
|
|
229
|
+
builtin = "Date";
|
|
230
|
+
} else if (JS_IsRegExp(value)) {
|
|
231
|
+
builtin = "RegExp";
|
|
232
|
+
} else if (JS_GetClassID(value) == cache.object_class_id || JS_IsMap(value) ||
|
|
233
|
+
JS_IsSet(value) || JS_IsWeakMap(value) || JS_IsWeakSet(value) ||
|
|
234
|
+
JS_IsWeakRef(value) || JS_IsPromise(value) || JS_IsArrayBuffer(value) ||
|
|
235
|
+
JS_IsDataView(value) || JS_GetTypedArrayType(value) >= 0) {
|
|
236
|
+
builtin = "Object";
|
|
237
|
+
}
|
|
238
|
+
if (!builtin || cache.to_string_tag == JS_ATOM_NULL) {
|
|
239
|
+
return to_string_type(ctx, value);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
JSValue tag = JS_GetProperty(ctx, value, cache.to_string_tag);
|
|
243
|
+
std::string type;
|
|
244
|
+
if (JS_IsException(tag)) {
|
|
245
|
+
/* A throwing Symbol.toStringTag getter or proxy trap */
|
|
246
|
+
drain_exception(ctx);
|
|
247
|
+
} else if (JS_IsString(tag)) {
|
|
248
|
+
const char* str = JS_ToCString(ctx, tag);
|
|
249
|
+
if (str) {
|
|
250
|
+
/* An empty tag gives "[object ]", which to_string_type keeps whole */
|
|
251
|
+
type = str[0] ? str : "[object ]";
|
|
252
|
+
JS_FreeCString(ctx, str);
|
|
253
|
+
} else {
|
|
254
|
+
drain_exception(ctx);
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
type = builtin;
|
|
258
|
+
}
|
|
259
|
+
JS_FreeValue(ctx, tag);
|
|
244
260
|
return type;
|
|
245
261
|
}
|
|
246
262
|
|
|
@@ -275,28 +291,31 @@ static std::string inspect_array_items(JSContext* ctx, JSValue arr, uint32_t len
|
|
|
275
291
|
|
|
276
292
|
/* ── Inspect object properties ───────────────────────────────────── */
|
|
277
293
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
uint32_t plen = 0;
|
|
283
|
-
|
|
294
|
+
/* Own string keys to render (enumerable only unless showHidden). On failure
|
|
295
|
+
* drains the exception and returns false. Free with JS_FreePropertyEnum. */
|
|
296
|
+
static bool get_own_keys(JSContext* ctx, JSValue obj, const InspectOpts& opts,
|
|
297
|
+
JSPropertyEnum** ptab, uint32_t* plen) {
|
|
284
298
|
int flags = JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY;
|
|
285
299
|
if (opts.show_hidden) {
|
|
286
300
|
flags = JS_GPN_STRING_MASK;
|
|
287
301
|
}
|
|
288
302
|
|
|
289
|
-
if (JS_GetOwnPropertyNames(ctx,
|
|
303
|
+
if (JS_GetOwnPropertyNames(ctx, ptab, plen, obj, flags) != 0) {
|
|
290
304
|
drain_exception(ctx);
|
|
291
|
-
return
|
|
305
|
+
return false;
|
|
292
306
|
}
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
293
309
|
|
|
310
|
+
static std::string inspect_properties(JSContext* ctx, JSValue obj, const JSPropertyEnum* ptab,
|
|
311
|
+
uint32_t plen, InspectOpts& opts, int depth,
|
|
312
|
+
const char* const* skip_keys = nullptr,
|
|
313
|
+
size_t skip_count = 0) {
|
|
294
314
|
std::string out;
|
|
295
315
|
bool first = true;
|
|
296
316
|
for (uint32_t i = 0; i < plen; i++) {
|
|
297
317
|
const char* key = JS_AtomToCString(ctx, ptab[i].atom);
|
|
298
318
|
if (!key) {
|
|
299
|
-
JS_FreeAtom(ctx, ptab[i].atom);
|
|
300
319
|
continue;
|
|
301
320
|
}
|
|
302
321
|
|
|
@@ -315,7 +334,7 @@ static std::string inspect_properties(JSContext* ctx, JSValue obj, InspectOpts&
|
|
|
315
334
|
if (!first) out += ", ";
|
|
316
335
|
first = false;
|
|
317
336
|
|
|
318
|
-
JSValue val =
|
|
337
|
+
JSValue val = JS_GetProperty(ctx, obj, ptab[i].atom);
|
|
319
338
|
out += quote_key(key);
|
|
320
339
|
out += ": ";
|
|
321
340
|
if (JS_IsException(val)) {
|
|
@@ -327,9 +346,7 @@ static std::string inspect_properties(JSContext* ctx, JSValue obj, InspectOpts&
|
|
|
327
346
|
}
|
|
328
347
|
|
|
329
348
|
JS_FreeCString(ctx, key);
|
|
330
|
-
JS_FreeAtom(ctx, ptab[i].atom);
|
|
331
349
|
}
|
|
332
|
-
js_free(ctx, ptab);
|
|
333
350
|
return out;
|
|
334
351
|
}
|
|
335
352
|
|
|
@@ -375,12 +392,8 @@ static std::string inspect_function(JSContext* ctx, JSValue value, InspectOpts&
|
|
|
375
392
|
|
|
376
393
|
/* Check for Symbol.toStringTag */
|
|
377
394
|
std::string func_type = "Function";
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
JSValue tag_sym = JS_GetPropertyStr(ctx, symbol_ctor, "toStringTag");
|
|
381
|
-
if (!JS_IsUndefined(tag_sym)) {
|
|
382
|
-
JSAtom tag_atom = JS_ValueToAtom(ctx, tag_sym);
|
|
383
|
-
JSValue tag_val = JS_GetProperty(ctx, value, tag_atom);
|
|
395
|
+
if (opts.cache->to_string_tag != JS_ATOM_NULL) {
|
|
396
|
+
JSValue tag_val = JS_GetProperty(ctx, value, opts.cache->to_string_tag);
|
|
384
397
|
if (JS_IsException(tag_val)) drain_exception(ctx);
|
|
385
398
|
if (JS_IsString(tag_val)) {
|
|
386
399
|
const char* tag = JS_ToCString(ctx, tag_val);
|
|
@@ -390,11 +403,7 @@ static std::string inspect_function(JSContext* ctx, JSValue value, InspectOpts&
|
|
|
390
403
|
}
|
|
391
404
|
}
|
|
392
405
|
JS_FreeValue(ctx, tag_val);
|
|
393
|
-
JS_FreeAtom(ctx, tag_atom);
|
|
394
406
|
}
|
|
395
|
-
JS_FreeValue(ctx, tag_sym);
|
|
396
|
-
JS_FreeValue(ctx, symbol_ctor);
|
|
397
|
-
JS_FreeValue(ctx, global);
|
|
398
407
|
|
|
399
408
|
if (name && name[0]) {
|
|
400
409
|
result = "[" + func_type + " " + name + "]";
|
|
@@ -554,8 +563,14 @@ static std::string inspect_error(JSContext* ctx, JSValue value, InspectOpts& opt
|
|
|
554
563
|
static const char* const error_keys[] = {"stack", "line", "column", "name",
|
|
555
564
|
"message", "fileName", "lineNumber", "columnNumber",
|
|
556
565
|
"number", "description", "cause"};
|
|
557
|
-
std::string props
|
|
558
|
-
|
|
566
|
+
std::string props;
|
|
567
|
+
JSPropertyEnum* ptab = nullptr;
|
|
568
|
+
uint32_t plen = 0;
|
|
569
|
+
if (get_own_keys(ctx, value, opts, &ptab, &plen)) {
|
|
570
|
+
props = inspect_properties(ctx, value, ptab, plen, opts, depth, error_keys,
|
|
571
|
+
countof(error_keys));
|
|
572
|
+
JS_FreePropertyEnum(ctx, ptab, plen);
|
|
573
|
+
}
|
|
559
574
|
if (!props.empty()) {
|
|
560
575
|
result += " { " + props + " }";
|
|
561
576
|
}
|
|
@@ -708,15 +723,10 @@ static std::string inspect_set(JSContext* ctx, JSValue value, InspectOpts& opts,
|
|
|
708
723
|
if (is_seen(opts, value)) return "[Circular]";
|
|
709
724
|
opts.seen.push_back(JS_VALUE_GET_PTR(value));
|
|
710
725
|
|
|
711
|
-
/* Get values via forEach */
|
|
712
|
-
JSValue for_each_fn = JS_GetPropertyStr(ctx, value, "forEach");
|
|
713
|
-
|
|
714
|
-
/* Build array of values to inspect */
|
|
715
726
|
std::string items;
|
|
716
727
|
JSValue values_fn = JS_GetPropertyStr(ctx, value, "values");
|
|
717
728
|
JSValue iterator = JS_Call(ctx, values_fn, value, 0, nullptr);
|
|
718
729
|
JS_FreeValue(ctx, values_fn);
|
|
719
|
-
JS_FreeValue(ctx, for_each_fn);
|
|
720
730
|
|
|
721
731
|
if (JS_IsException(iterator)) {
|
|
722
732
|
drain_exception(ctx);
|
|
@@ -799,30 +809,20 @@ static std::string inspect_typed_array(JSContext* ctx, JSValue value, const std:
|
|
|
799
809
|
static std::string inspect_object(JSContext* ctx, JSValue value, InspectOpts& opts, int depth) {
|
|
800
810
|
if (is_seen(opts, value)) return "[Circular]";
|
|
801
811
|
|
|
802
|
-
/* Check for empty object */
|
|
803
812
|
JSPropertyEnum* ptab = nullptr;
|
|
804
813
|
uint32_t plen = 0;
|
|
805
|
-
|
|
806
|
-
if (
|
|
807
|
-
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
if (JS_GetOwnPropertyNames(ctx, &ptab, &plen, value, flags) != 0) {
|
|
811
|
-
drain_exception(ctx);
|
|
814
|
+
if (!get_own_keys(ctx, value, opts, &ptab, &plen)) return "{}";
|
|
815
|
+
if (plen == 0) {
|
|
816
|
+
JS_FreePropertyEnum(ctx, ptab, plen);
|
|
812
817
|
return "{}";
|
|
813
818
|
}
|
|
814
|
-
for (uint32_t i = 0; i < plen; i++) {
|
|
815
|
-
JS_FreeAtom(ctx, ptab[i].atom);
|
|
816
|
-
}
|
|
817
|
-
js_free(ctx, ptab);
|
|
818
|
-
|
|
819
|
-
if (plen == 0) return "{}";
|
|
820
819
|
|
|
821
820
|
/* A cause field chains below the braces like an Error's, so a Result
|
|
822
821
|
* error wrapped around another keeps its whole chain readable */
|
|
823
822
|
static const char* const cause_key[] = {"cause"};
|
|
824
823
|
opts.seen.push_back(JS_VALUE_GET_PTR(value));
|
|
825
|
-
std::string props = inspect_properties(ctx, value, opts, depth, cause_key, 1);
|
|
824
|
+
std::string props = inspect_properties(ctx, value, ptab, plen, opts, depth, cause_key, 1);
|
|
825
|
+
JS_FreePropertyEnum(ctx, ptab, plen);
|
|
826
826
|
std::string result = props.empty() ? "{}" : "{ " + props + " }";
|
|
827
827
|
result += inspect_cause(ctx, value, opts, depth);
|
|
828
828
|
opts.seen.pop_back();
|
|
@@ -915,9 +915,12 @@ static std::string inspect_value(JSContext* ctx, JSValue value, InspectOpts& opt
|
|
|
915
915
|
}
|
|
916
916
|
|
|
917
917
|
/* Objects — detect type */
|
|
918
|
-
|
|
918
|
+
if (JS_IsArray(value)) {
|
|
919
|
+
return inspect_array(ctx, value, opts, depth - 1);
|
|
920
|
+
}
|
|
921
|
+
std::string type = get_object_type(ctx, value, *opts.cache);
|
|
919
922
|
|
|
920
|
-
if (
|
|
923
|
+
if (type == "Array") {
|
|
921
924
|
return inspect_array(ctx, value, opts, depth - 1);
|
|
922
925
|
}
|
|
923
926
|
if (JS_IsError(value) || type == "Error") {
|
|
@@ -955,16 +958,8 @@ static std::string inspect_value(JSContext* ctx, JSValue value, InspectOpts& opt
|
|
|
955
958
|
/* Plain object vs class instance */
|
|
956
959
|
JSValue proto = JS_GetPrototype(ctx, value);
|
|
957
960
|
if (JS_IsException(proto)) drain_exception(ctx);
|
|
958
|
-
JSValue global = JS_GetGlobalObject(ctx);
|
|
959
|
-
JSValue obj_ctor = JS_GetPropertyStr(ctx, global, "Object");
|
|
960
|
-
JSValue obj_proto = JS_GetPropertyStr(ctx, obj_ctor, "prototype");
|
|
961
|
-
|
|
962
961
|
bool is_plain = JS_IsNull(proto) ||
|
|
963
|
-
(JS_VALUE_GET_PTR(proto) == JS_VALUE_GET_PTR(
|
|
964
|
-
|
|
965
|
-
JS_FreeValue(ctx, obj_proto);
|
|
966
|
-
JS_FreeValue(ctx, obj_ctor);
|
|
967
|
-
JS_FreeValue(ctx, global);
|
|
962
|
+
(JS_VALUE_GET_PTR(proto) == JS_VALUE_GET_PTR(opts.cache->object_proto));
|
|
968
963
|
JS_FreeValue(ctx, proto);
|
|
969
964
|
|
|
970
965
|
if (is_plain) {
|
|
@@ -982,6 +977,7 @@ std::string mik_inspect(JSContext* ctx, JSValue value, int depth, bool colors, b
|
|
|
982
977
|
opts.truncate = 0; /* 0 = unlimited */
|
|
983
978
|
opts.colors = colors;
|
|
984
979
|
opts.show_hidden = show_hidden;
|
|
980
|
+
opts.cache = &MIK_GetRuntime(ctx)->inspect_cache;
|
|
985
981
|
return inspect_value(ctx, value, opts, depth);
|
|
986
982
|
}
|
|
987
983
|
|
|
@@ -1026,6 +1022,25 @@ int mik__inspect_module_init(JSContext* ctx, JSModuleDef* m) {
|
|
|
1026
1022
|
}
|
|
1027
1023
|
|
|
1028
1024
|
void mik__inspect_register(JSContext* ctx) {
|
|
1025
|
+
MIKInspectCache& cache = MIK_GetRuntime(ctx)->inspect_cache;
|
|
1026
|
+
/* is_global makes this the registered symbol Symbol.for returns */
|
|
1027
|
+
JSValue sym = JS_NewSymbol(ctx, "mikrojs.inspect", true);
|
|
1028
|
+
if (!JS_IsException(sym)) cache.inspect_symbol = JS_ValueToAtom(ctx, sym);
|
|
1029
|
+
JS_FreeValue(ctx, sym);
|
|
1030
|
+
|
|
1031
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
1032
|
+
JSValue symbol_ctor = JS_GetPropertyStr(ctx, global, "Symbol");
|
|
1033
|
+
JSValue tag_sym = JS_GetPropertyStr(ctx, symbol_ctor, "toStringTag");
|
|
1034
|
+
if (JS_IsSymbol(tag_sym)) cache.to_string_tag = JS_ValueToAtom(ctx, tag_sym);
|
|
1035
|
+
JS_FreeValue(ctx, tag_sym);
|
|
1036
|
+
JS_FreeValue(ctx, symbol_ctor);
|
|
1037
|
+
|
|
1038
|
+
JSValue object_ctor = JS_GetPropertyStr(ctx, global, "Object");
|
|
1039
|
+
cache.object_proto = JS_GetPropertyStr(ctx, object_ctor, "prototype");
|
|
1040
|
+
cache.object_class_id = JS_GetClassID(cache.object_proto);
|
|
1041
|
+
JS_FreeValue(ctx, object_ctor);
|
|
1042
|
+
JS_FreeValue(ctx, global);
|
|
1043
|
+
|
|
1029
1044
|
JSModuleDef* m = JS_NewCModule(ctx, "mikro/inspect", mik__inspect_module_init);
|
|
1030
1045
|
if (m) {
|
|
1031
1046
|
JS_AddModuleExport(ctx, m, "inspect");
|
package/src/mik_observable.cpp
CHANGED
|
@@ -71,6 +71,10 @@ extern "C" {
|
|
|
71
71
|
#include "quickjs.h"
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/* Property names the per-value paths use, atomized once per runtime */
|
|
75
|
+
enum ObservableAtom { OBS_NEXT, OBS_DONE, OBS_VALUE, OBS_ATOM_COUNT };
|
|
76
|
+
static const char* const observable_atom_names[OBS_ATOM_COUNT] = {"next", "done", "value"};
|
|
77
|
+
|
|
74
78
|
/* Per-runtime dispatch queue. Entries hold their own references (dup on
|
|
75
79
|
* enqueue, freed after the drained delivery). The queue is only non-empty
|
|
76
80
|
* while a dispatch is on the stack, so runtime teardown never sees values. */
|
|
@@ -83,6 +87,7 @@ struct MIKObservableDispatch {
|
|
|
83
87
|
bool active = false;
|
|
84
88
|
size_t head = 0;
|
|
85
89
|
std::vector<Entry> queue;
|
|
90
|
+
JSAtom atoms[OBS_ATOM_COUNT] = {};
|
|
86
91
|
};
|
|
87
92
|
|
|
88
93
|
namespace {
|
|
@@ -234,6 +239,11 @@ static MIKObservableDispatch* dispatch_state(JSContext* ctx) {
|
|
|
234
239
|
return mik_rt ? mik_rt->observable_dispatch : nullptr;
|
|
235
240
|
}
|
|
236
241
|
|
|
242
|
+
/* JS_NewAtom(name) without hashing the name: dups the cached atom. Free with JS_FreeAtom. */
|
|
243
|
+
static JSAtom observable_atom(JSContext* ctx, ObservableAtom which) {
|
|
244
|
+
return JS_DupAtom(ctx, dispatch_state(ctx)->atoms[which]);
|
|
245
|
+
}
|
|
246
|
+
|
|
237
247
|
/* True once a panic is armed. The producer's own callback keeps running (JS
|
|
238
248
|
* cannot be stopped mid-function), so its later next/complete calls have to
|
|
239
249
|
* become no-ops rather than delivering against crashed state. */
|
|
@@ -656,12 +666,14 @@ static JSValue get_iterator(JSContext* ctx, JSValueConst src) {
|
|
|
656
666
|
* Sets *done = true if iteration is complete (and returns JS_UNDEFINED).
|
|
657
667
|
* Returns JS_EXCEPTION on protocol error. Caller frees the returned value. */
|
|
658
668
|
static JSValue iterator_next(JSContext* ctx, JSValueConst iterator, bool* done) {
|
|
659
|
-
JSAtom next_atom =
|
|
669
|
+
JSAtom next_atom = observable_atom(ctx, OBS_NEXT);
|
|
660
670
|
JSValue result = JS_Invoke(ctx, iterator, next_atom, 0, nullptr);
|
|
661
671
|
JS_FreeAtom(ctx, next_atom);
|
|
662
672
|
if (JS_IsException(result)) return result;
|
|
663
673
|
|
|
664
|
-
|
|
674
|
+
JSAtom done_atom = observable_atom(ctx, OBS_DONE);
|
|
675
|
+
JSValue done_val = JS_GetProperty(ctx, result, done_atom);
|
|
676
|
+
JS_FreeAtom(ctx, done_atom);
|
|
665
677
|
int done_int = JS_ToBool(ctx, done_val);
|
|
666
678
|
JS_FreeValue(ctx, done_val);
|
|
667
679
|
if (done_int < 0) {
|
|
@@ -674,7 +686,9 @@ static JSValue iterator_next(JSContext* ctx, JSValueConst iterator, bool* done)
|
|
|
674
686
|
JS_FreeValue(ctx, result);
|
|
675
687
|
return JS_UNDEFINED;
|
|
676
688
|
}
|
|
677
|
-
|
|
689
|
+
JSAtom value_atom = observable_atom(ctx, OBS_VALUE);
|
|
690
|
+
JSValue value = JS_GetProperty(ctx, result, value_atom);
|
|
691
|
+
JS_FreeAtom(ctx, value_atom);
|
|
678
692
|
JS_FreeValue(ctx, result);
|
|
679
693
|
return value;
|
|
680
694
|
}
|
|
@@ -721,7 +735,7 @@ static JSValue from_iterable_subscribe(JSContext* ctx, JSValueConst this_val, in
|
|
|
721
735
|
JSValue iterator = get_iterator(ctx, c->iterable);
|
|
722
736
|
if (JS_IsException(iterator)) return iterator;
|
|
723
737
|
|
|
724
|
-
JSAtom next_atom =
|
|
738
|
+
JSAtom next_atom = observable_atom(ctx, OBS_NEXT);
|
|
725
739
|
|
|
726
740
|
bool done = false;
|
|
727
741
|
while (!done) {
|
|
@@ -792,7 +806,7 @@ static JSValue from_promise_on_resolve(JSContext* ctx, JSValueConst this_val, in
|
|
|
792
806
|
if (!sd || sd->closed) return JS_UNDEFINED;
|
|
793
807
|
|
|
794
808
|
JSValue value = argc > 0 ? argv[0] : JS_UNDEFINED;
|
|
795
|
-
JSAtom next_atom =
|
|
809
|
+
JSAtom next_atom = observable_atom(ctx, OBS_NEXT);
|
|
796
810
|
invoke_safely(ctx, subscriber, next_atom, 1, &value);
|
|
797
811
|
JS_FreeAtom(ctx, next_atom);
|
|
798
812
|
|
|
@@ -1008,7 +1022,7 @@ static JSValue multicast_emit_next(JSContext* ctx, JSValueConst this_val, int ar
|
|
|
1008
1022
|
for (auto& s : m->subscribers) snapshot.push_back(JS_DupValue(ctx, s));
|
|
1009
1023
|
|
|
1010
1024
|
JSValue value = argc > 0 ? argv[0] : JS_UNDEFINED;
|
|
1011
|
-
JSAtom next_atom =
|
|
1025
|
+
JSAtom next_atom = observable_atom(ctx, OBS_NEXT);
|
|
1012
1026
|
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
1013
1027
|
for (auto& s : snapshot) {
|
|
1014
1028
|
auto* sd = static_cast<SubscriberData*>(JS_GetOpaque(s, subscriber_class_id));
|
|
@@ -1982,6 +1996,9 @@ int mik__observable_multicast_new(JSContext* ctx, JSValue* observable, JSValue*
|
|
|
1982
1996
|
}
|
|
1983
1997
|
|
|
1984
1998
|
void mik__observable_dispatch_free(MIKRuntime* mik_rt) {
|
|
1999
|
+
if (mik_rt->observable_dispatch) {
|
|
2000
|
+
for (JSAtom a : mik_rt->observable_dispatch->atoms) JS_FreeAtom(mik_rt->ctx, a);
|
|
2001
|
+
}
|
|
1985
2002
|
delete mik_rt->observable_dispatch;
|
|
1986
2003
|
mik_rt->observable_dispatch = nullptr;
|
|
1987
2004
|
}
|
|
@@ -1992,6 +2009,9 @@ JSModuleDef* mik__observable_init(JSContext* ctx) {
|
|
|
1992
2009
|
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
1993
2010
|
if (mik_rt && !mik_rt->observable_dispatch) {
|
|
1994
2011
|
mik_rt->observable_dispatch = new MIKObservableDispatch();
|
|
2012
|
+
for (int i = 0; i < OBS_ATOM_COUNT; i++) {
|
|
2013
|
+
mik_rt->observable_dispatch->atoms[i] = JS_NewAtom(ctx, observable_atom_names[i]);
|
|
2014
|
+
}
|
|
1995
2015
|
}
|
|
1996
2016
|
|
|
1997
2017
|
/* The IDs are process-wide (MIK_NewClassID); the classes register once per runtime. */
|
package/src/mik_ota_client.cpp
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
#include <vector>
|
|
11
11
|
|
|
12
12
|
#include "mikrojs/cbor_helpers.h"
|
|
13
|
+
#include "mikrojs/mikrojs.h"
|
|
13
14
|
#include "mikrojs/ota_config.h"
|
|
14
15
|
#include "mikrojs/ota_slots.h"
|
|
15
16
|
#include "mikrojs/platform.h"
|
|
@@ -388,6 +389,8 @@ void MIKOtaClient::Check(const MIKOtaCheckOptions& options, MIKOtaCheckSink sink
|
|
|
388
389
|
return;
|
|
389
390
|
}
|
|
390
391
|
queue_.push_back(std::move(round));
|
|
392
|
+
/* Poll runs from the loop consumer, which may have run already this pass. */
|
|
393
|
+
MIK_Wake();
|
|
391
394
|
}
|
|
392
395
|
|
|
393
396
|
/* Floor for the check-in cadence: each round's TLS session leaves heap and
|
package/src/mik_ota_js_hooks.cpp
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
#include <map>
|
|
4
4
|
|
|
5
|
+
#include "mikrojs/mikrojs.h"
|
|
5
6
|
#include "mikrojs/utils.h"
|
|
6
7
|
|
|
7
8
|
namespace mikrojs {
|
|
@@ -178,6 +179,8 @@ JSValue MIKOtaJsHooks::OnFulfilled(JSContext* ctx, JSValueConst this_val, int ar
|
|
|
178
179
|
(void)this_val;
|
|
179
180
|
MIKOtaJsHooks* hooks = take_self(ctx, func_data);
|
|
180
181
|
if (!hooks) return JS_UNDEFINED;
|
|
182
|
+
/* A job settled the hook; the OTA consumer that polls it has run this pass. */
|
|
183
|
+
MIK_Wake();
|
|
181
184
|
if (magic == 0) {
|
|
182
185
|
/* Rejected: a failed hook, whichever half was running. */
|
|
183
186
|
hooks->state_ = MIKOtaHookState::kFailed;
|
package/src/mik_repl.cpp
CHANGED
|
@@ -62,6 +62,30 @@ bool mik__repl_is_protocol_mode(void) {
|
|
|
62
62
|
return repl_protocol_mode && repl_active;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/* Longest sleep between passes: a backstop against a producer that forgot
|
|
66
|
+
* MIK_Wake, not a poll period. */
|
|
67
|
+
static constexpr int64_t LOOP_WAIT_CAP_US = 100 * 1000;
|
|
68
|
+
|
|
69
|
+
/* Sleep until the next deadline, a wake, or the cap. Work due now yields
|
|
70
|
+
* instead; a paused or absent runtime waits for the cap (console RX wakes it). */
|
|
71
|
+
static void repl_wait_for_work(void) {
|
|
72
|
+
const MIKPlatform* platform = MIK_GetPlatform();
|
|
73
|
+
int64_t wake_us = LOOP_WAIT_CAP_US;
|
|
74
|
+
if (repl_mik_rt && !repl_paused) {
|
|
75
|
+
wake_us = mik__next_wake_us(repl_mik_rt);
|
|
76
|
+
if (wake_us == 0) {
|
|
77
|
+
platform->yield();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (wake_us < 0 || wake_us > LOOP_WAIT_CAP_US) wake_us = LOOP_WAIT_CAP_US;
|
|
81
|
+
}
|
|
82
|
+
if (platform->wait) {
|
|
83
|
+
platform->wait(wake_us);
|
|
84
|
+
} else {
|
|
85
|
+
platform->yield();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
65
89
|
/* ── TLV frame helpers ───────────────────────────────────────────── */
|
|
66
90
|
|
|
67
91
|
/* Plain bool, not atomic — relies on the assumption that mik__proto_send
|
|
@@ -124,7 +148,6 @@ void mik__repl_proto_send_output(uint8_t msg_type, const void* data, size_t len)
|
|
|
124
148
|
* reports the attached runtime has halted without a panic grace window
|
|
125
149
|
* (e.g. an unhandled rejection in a test that crashed mid-execution). */
|
|
126
150
|
bool mik__proto_read_exact(MIKReplTransport* transport, void* buf, size_t n) {
|
|
127
|
-
const MIKPlatform* platform = MIK_GetPlatform();
|
|
128
151
|
uint8_t* p = static_cast<uint8_t*>(buf);
|
|
129
152
|
size_t total = 0;
|
|
130
153
|
while (total < n) {
|
|
@@ -143,19 +166,12 @@ bool mik__proto_read_exact(MIKReplTransport* transport, void* buf, size_t n) {
|
|
|
143
166
|
return false;
|
|
144
167
|
}
|
|
145
168
|
}
|
|
146
|
-
/* Microtasks
|
|
147
|
-
*
|
|
148
|
-
* or touch the filesystem mid-deploy. Gate on pause too — and
|
|
149
|
-
* on a pending panic-restart, since the runtime is on its way
|
|
150
|
-
* out and no further user JS should fire on it. */
|
|
151
|
-
if (repl_ctx && !repl_paused &&
|
|
152
|
-
(!repl_mik_rt || repl_mik_rt->restart_at_us == 0)) {
|
|
153
|
-
mik__execute_jobs(repl_ctx);
|
|
154
|
-
}
|
|
169
|
+
/* Microtasks drain inside MIK_Loop, so none run while paused or
|
|
170
|
+
* while a panic-restart is pending. */
|
|
155
171
|
/* A pumped job (e.g. the test runtime's __testFileDone) may
|
|
156
|
-
* have signalled exit. Bail before
|
|
172
|
+
* have signalled exit. Bail before sleeping again. */
|
|
157
173
|
if (s_exit_serve_loop) return false;
|
|
158
|
-
|
|
174
|
+
repl_wait_for_work();
|
|
159
175
|
}
|
|
160
176
|
}
|
|
161
177
|
return true;
|
|
@@ -264,8 +280,6 @@ static JSValue repl_eval_and_pump(JSContext* ctx, const char* code, size_t len)
|
|
|
264
280
|
return result;
|
|
265
281
|
}
|
|
266
282
|
|
|
267
|
-
const MIKPlatform* platform = MIK_GetPlatform();
|
|
268
|
-
|
|
269
283
|
for (;;) {
|
|
270
284
|
JSPromiseStateEnum state = JS_PromiseState(ctx, result);
|
|
271
285
|
if (state == JS_PROMISE_FULFILLED) {
|
|
@@ -296,8 +310,8 @@ static JSValue repl_eval_and_pump(JSContext* ctx, const char* code, size_t len)
|
|
|
296
310
|
}
|
|
297
311
|
|
|
298
312
|
MIK_Loop(repl_mik_rt);
|
|
299
|
-
|
|
300
|
-
|
|
313
|
+
/* The pass may have settled it (a job or a due timer): report now. */
|
|
314
|
+
if (JS_PromiseState(ctx, result) == JS_PROMISE_PENDING) repl_wait_for_work();
|
|
301
315
|
}
|
|
302
316
|
}
|
|
303
317
|
|
|
@@ -903,17 +917,19 @@ static std::vector<uint8_t> proto_complete(JSContext* ctx, const char* partial,
|
|
|
903
917
|
/* ── Protocol-mode REPL ──────────────────────────────────────────── */
|
|
904
918
|
|
|
905
919
|
/* Fills ready_buf/ready_len with CBOR device info:
|
|
906
|
-
* {"chip": tstr, "id": tstr, "v": tstr, "board": tstr, "
|
|
907
|
-
*
|
|
908
|
-
*
|
|
920
|
+
* {"chip": tstr, "id": tstr, "v": tstr, "board": tstr, "proto": uint
|
|
921
|
+
* (MIK_PROTO_REV), "fw": tstr (when built with MIK_BOARD_NAME), "name": tstr
|
|
922
|
+
* (only when named), "features": [tstr] (when built with MIK_FW_FEATURES; host
|
|
923
|
+
* builds omit it), "natives": [tstr]}.
|
|
909
924
|
* `board` is mik__board_name(). `features` lists the firmware features the
|
|
910
925
|
* build compiled in (from the comma-separated MIK_FW_FEATURES define), so the
|
|
911
926
|
* host knows which feature-gated builtins this firmware carries. `natives`
|
|
912
927
|
* lists the registered C modules outside the core namespace (package native
|
|
913
928
|
* modules compiled in, e.g. "@mikrojs/drivers/sh8601"; empty on a generic
|
|
914
929
|
* build), so the host can refuse an app importing one this firmware lacks.
|
|
915
|
-
* `fw` is the firmware identity: the
|
|
916
|
-
*
|
|
930
|
+
* `fw` is the firmware identity: the name the build set (MIK_BOARD_NAME). `board`
|
|
931
|
+
* reports the same unless `mikro flash` wrote a board's name into a generic
|
|
932
|
+
* image (mik__board_name). The host only auto-flashes its bundled image over
|
|
917
933
|
* a device whose identity matches that image's name; omitting it reads as
|
|
918
934
|
* firmware predating identity reporting, which the host treats as its own
|
|
919
935
|
* bundled firmware.
|
|
@@ -949,7 +965,7 @@ static size_t encode_ready(uint8_t* buf, size_t cap, const char* chip, const cha
|
|
|
949
965
|
static uint8_t measure_base;
|
|
950
966
|
nanocbor_encoder_t enc;
|
|
951
967
|
nanocbor_encoder_init(&enc, buf ? buf : &measure_base, buf ? cap : 0);
|
|
952
|
-
nanocbor_fmt_map(&enc,
|
|
968
|
+
nanocbor_fmt_map(&enc, 5 + (fw ? 1 : 0) + (name ? 1 : 0) + (features ? 1 : 0) +
|
|
953
969
|
(natives ? 1 : 0) + (mem ? 3 : 0));
|
|
954
970
|
nanocbor_put_tstr(&enc, "chip");
|
|
955
971
|
nanocbor_put_tstr(&enc, chip);
|
|
@@ -959,6 +975,8 @@ static size_t encode_ready(uint8_t* buf, size_t cap, const char* chip, const cha
|
|
|
959
975
|
nanocbor_put_tstr(&enc, version);
|
|
960
976
|
nanocbor_put_tstr(&enc, "board");
|
|
961
977
|
nanocbor_put_tstr(&enc, board);
|
|
978
|
+
nanocbor_put_tstr(&enc, "proto");
|
|
979
|
+
nanocbor_fmt_uint(&enc, MIK_PROTO_REV);
|
|
962
980
|
if (fw) {
|
|
963
981
|
nanocbor_put_tstr(&enc, "fw");
|
|
964
982
|
nanocbor_put_tstr(&enc, fw);
|
|
@@ -1324,9 +1342,6 @@ void MIK_ProtocolServeLoop(void) {
|
|
|
1324
1342
|
if (repl_mik_rt && !repl_paused) {
|
|
1325
1343
|
MIK_Loop(repl_mik_rt);
|
|
1326
1344
|
}
|
|
1327
|
-
if (ctx && !repl_paused) {
|
|
1328
|
-
mik__execute_jobs(ctx);
|
|
1329
|
-
}
|
|
1330
1345
|
}
|
|
1331
1346
|
}
|
|
1332
1347
|
|