@mikrojs/native 0.20.1 → 0.21.0-next.20260913195055
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 +3 -1
- package/cmake/mikrojs_bytecode.cmake +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/runtime/result/native-result.node-shim.js.map +1 -1
- package/dist/runtime/result/types.d.ts +5 -5
- package/dist/runtime/result/types.d.ts.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/include/mikrojs/mikrojs.h +26 -2
- package/include/mikrojs/ota_env.h +8 -6
- package/include/mikrojs/ota_policy.h +16 -0
- package/include/mikrojs/private.h +13 -0
- package/include/mikrojs/utils.h +24 -0
- package/package.json +10 -10
- 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/gpio/types.ts +116 -0
- package/runtime/i2c/types.ts +25 -31
- package/runtime/i2s/types.ts +23 -12
- package/runtime/internal.d.ts +1 -174
- package/runtime/kv/shared.ts +13 -11
- package/runtime/neopixel/types.ts +24 -6
- package/runtime/observable/native-observable.node-shim.ts +73 -37
- package/runtime/observable/native-operators.node-shim.ts +417 -0
- package/runtime/observable/observable.ts +76 -1
- package/runtime/observable/operators.ts +156 -89
- package/runtime/observable/types.ts +61 -6
- package/runtime/pwm/types.ts +31 -11
- package/runtime/result/native-result.node-shim.ts +1 -1
- package/runtime/result/types.ts +5 -5
- package/runtime/sleep/types.ts +10 -8
- package/runtime/spi/types.ts +16 -14
- package/runtime/uart/types.ts +27 -15
- package/src/gpio_claim.cpp +63 -0
- package/src/mik_abort.cpp +1 -2
- package/src/mik_console.cpp +192 -115
- package/src/mik_http_client.cpp +3 -14
- package/src/mik_inspect.cpp +86 -9
- package/src/mik_observable.cpp +1028 -80
- package/src/mik_ota_client.cpp +5 -2
- package/src/mik_ota_policy.cpp +65 -18
- package/src/mik_result.cpp +10 -9
- package/src/mikrojs.cpp +26 -13
- package/src/modules.cpp +17 -6
- package/src/timers.cpp +6 -2
- package/src/utils.cpp +106 -0
- package/runtime/i2c/i2c.ts +0 -35
- package/runtime/i2s/i2s.ts +0 -46
- package/runtime/neopixel/neopixel.ts +0 -38
- package/runtime/pin/pin.ts +0 -51
- package/runtime/pin/types.ts +0 -49
- package/runtime/pwm/pwm.ts +0 -32
- package/runtime/spi/spi.ts +0 -31
- package/runtime/uart/uart.ts +0 -52
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#include <cstring>
|
|
2
|
+
#include <string>
|
|
3
|
+
|
|
4
|
+
#include "mikrojs/mikrojs.h"
|
|
5
|
+
#include "mikrojs/private.h"
|
|
6
|
+
#include "mikrojs/utils.h"
|
|
7
|
+
|
|
8
|
+
/* Runtime-wide exclusive GPIO pin claims. Only touched from the JS task, so no
|
|
9
|
+
* locking. Owners are static strings, so the table never frees anything. */
|
|
10
|
+
|
|
11
|
+
#define MIK__GPIO_MAX 64
|
|
12
|
+
|
|
13
|
+
static const char* s_owner[MIK__GPIO_MAX] = {};
|
|
14
|
+
|
|
15
|
+
static bool mik__gpio_tracked(int gpio) {
|
|
16
|
+
return gpio >= 0 && gpio < MIK__GPIO_MAX;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
bool MIK_ClaimGpio(int gpio, const char* owner) {
|
|
20
|
+
if (!owner) return false;
|
|
21
|
+
if (!mik__gpio_tracked(gpio)) return true;
|
|
22
|
+
if (s_owner[gpio]) return false;
|
|
23
|
+
s_owner[gpio] = owner;
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
void MIK_ReleaseGpio(int gpio, const char* owner) {
|
|
28
|
+
/* A stale release from one module must not free another module's claim. */
|
|
29
|
+
if (owner && mik__gpio_tracked(gpio) && s_owner[gpio] && strcmp(s_owner[gpio], owner) == 0) {
|
|
30
|
+
s_owner[gpio] = nullptr;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const char* MIK_GpioOwner(int gpio) {
|
|
35
|
+
return mik__gpio_tracked(gpio) ? s_owner[gpio] : nullptr;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Owners are class names ("by Pwm"), except the console ("by the console"). */
|
|
39
|
+
static std::string mik__gpio_in_use_message(int gpio, const char* owner) {
|
|
40
|
+
std::string message = "GPIO " + std::to_string(gpio) + " is already in use by ";
|
|
41
|
+
if (strcmp(owner, "console") == 0) message += "the ";
|
|
42
|
+
return message + owner;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
JSValue mik__claim_gpios(JSContext* ctx, const int* gpios, int count, const char* owner) {
|
|
46
|
+
for (int i = 0; i < count; i++) {
|
|
47
|
+
if (MIK_ClaimGpio(gpios[i], owner)) continue;
|
|
48
|
+
const char* holder = MIK_GpioOwner(gpios[i]);
|
|
49
|
+
JSValue error = JS_NewObject(ctx);
|
|
50
|
+
JS_SetPropertyStr(ctx, error, "name", JS_NewString(ctx, "GpioInUse"));
|
|
51
|
+
JS_SetPropertyStr(ctx, error, "owner", JS_NewString(ctx, holder));
|
|
52
|
+
std::string message = mik__gpio_in_use_message(gpios[i], holder);
|
|
53
|
+
JS_SetPropertyStr(ctx, error, "message",
|
|
54
|
+
JS_NewStringLen(ctx, message.data(), message.size()));
|
|
55
|
+
mik__release_gpios(gpios, i, owner);
|
|
56
|
+
return mik__result_err_obj(ctx, error);
|
|
57
|
+
}
|
|
58
|
+
return JS_UNDEFINED;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
void mik__release_gpios(const int* gpios, int count, const char* owner) {
|
|
62
|
+
for (int i = 0; i < count; i++) MIK_ReleaseGpio(gpios[i], owner);
|
|
63
|
+
}
|
package/src/mik_abort.cpp
CHANGED
|
@@ -52,8 +52,7 @@ static JSValue mik__abort_lazy_get(JSContext* ctx, JSValue this_val, int magic)
|
|
|
52
52
|
* would surface a bare 'null'. Mirrors the module loader's
|
|
53
53
|
* missing-builtin fallback. */
|
|
54
54
|
if (!JS_HasException(ctx)) {
|
|
55
|
-
|
|
56
|
-
"Builtin module 'mikro/abort' is not available in this build");
|
|
55
|
+
JS_ThrowTypeError(ctx, "Failed to resolve module specifier 'mikro/abort'");
|
|
57
56
|
}
|
|
58
57
|
JS_FreeValue(ctx, global_obj);
|
|
59
58
|
return JS_EXCEPTION;
|
package/src/mik_console.cpp
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
#include <cstring>
|
|
4
4
|
#include <string>
|
|
5
5
|
#include <unistd.h>
|
|
6
|
-
#include <vector>
|
|
7
6
|
|
|
8
7
|
#include <quickjs.h>
|
|
9
8
|
|
|
@@ -122,41 +121,6 @@ static std::string mik__format(JSContext* ctx, int argc, JSValue* argv, bool col
|
|
|
122
121
|
return result;
|
|
123
122
|
}
|
|
124
123
|
|
|
125
|
-
/* ── Error formatting (port of console.ts formatError) ───────────── */
|
|
126
|
-
|
|
127
|
-
static std::string mik__format_error(JSContext* ctx, JSValue error) {
|
|
128
|
-
JSValue stack_val = JS_GetPropertyStr(ctx, error, "stack");
|
|
129
|
-
JSValue msg_val = JS_GetPropertyStr(ctx, error, "message");
|
|
130
|
-
|
|
131
|
-
const char* stack = JS_ToCString(ctx, stack_val);
|
|
132
|
-
const char* msg = JS_ToCString(ctx, msg_val);
|
|
133
|
-
|
|
134
|
-
std::string result;
|
|
135
|
-
if (stack && msg && strstr(stack, msg)) {
|
|
136
|
-
/* Stack already includes message */
|
|
137
|
-
result = stack;
|
|
138
|
-
} else {
|
|
139
|
-
JSValue name_val = JS_GetPropertyStr(ctx, error, "name");
|
|
140
|
-
const char* name = JS_ToCString(ctx, name_val);
|
|
141
|
-
result = name ? name : "Error";
|
|
142
|
-
result += ": ";
|
|
143
|
-
result += msg ? msg : "";
|
|
144
|
-
if (stack) {
|
|
145
|
-
result += "\n";
|
|
146
|
-
result += stack;
|
|
147
|
-
}
|
|
148
|
-
if (name) JS_FreeCString(ctx, name);
|
|
149
|
-
JS_FreeValue(ctx, name_val);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (stack) JS_FreeCString(ctx, stack);
|
|
153
|
-
if (msg) JS_FreeCString(ctx, msg);
|
|
154
|
-
JS_FreeValue(ctx, stack_val);
|
|
155
|
-
JS_FreeValue(ctx, msg_val);
|
|
156
|
-
|
|
157
|
-
return result;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
124
|
/* ── Console methods ─────────────────────────────────────────────── */
|
|
161
125
|
|
|
162
126
|
static JSValue mik__console_log(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
@@ -205,24 +169,9 @@ static JSValue mik__console_error_warn(JSContext* ctx, JSValue this_val, int arg
|
|
|
205
169
|
int magic) {
|
|
206
170
|
uint8_t msg_type = magic == 0 ? MIK_MSG_ERROR : MIK_MSG_WARN;
|
|
207
171
|
|
|
208
|
-
/*
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
for (int i = 0; i < argc; i++) {
|
|
213
|
-
if (JS_IsError(argv[i])) {
|
|
214
|
-
std::string err_str = mik__format_error(ctx, argv[i]);
|
|
215
|
-
mapped.push_back(JS_NewString(ctx, err_str.c_str()));
|
|
216
|
-
} else {
|
|
217
|
-
mapped.push_back(JS_DupValue(ctx, argv[i]));
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
std::string output = mik__format(ctx, argc, mapped.data(), true);
|
|
222
|
-
|
|
223
|
-
for (auto& v : mapped) {
|
|
224
|
-
JS_FreeValue(ctx, v);
|
|
225
|
-
}
|
|
172
|
+
/* Error arguments render through inspect: name, message, extra fields,
|
|
173
|
+
* stack and cause chain. Same output as console.log for the same value. */
|
|
174
|
+
std::string output = mik__format(ctx, argc, argv, true);
|
|
226
175
|
|
|
227
176
|
if (mik__repl_is_protocol_mode()) {
|
|
228
177
|
mik__repl_proto_send_output(msg_type, output.c_str(), output.size());
|
|
@@ -236,6 +185,152 @@ static JSValue mik__console_error_warn(JSContext* ctx, JSValue this_val, int arg
|
|
|
236
185
|
|
|
237
186
|
/* ── Uncaught error reporting ─────────────────────────────────────── */
|
|
238
187
|
|
|
188
|
+
/* Bounded stack-buffer writer for the uncaught path: never touches the
|
|
189
|
+
* C++ heap (see mik__report_uncaught). Silently truncates when full. */
|
|
190
|
+
struct MIKBoundedBuf {
|
|
191
|
+
char* buf;
|
|
192
|
+
size_t cap;
|
|
193
|
+
size_t pos = 0;
|
|
194
|
+
void append(const char* s, size_t len) {
|
|
195
|
+
if (pos >= cap - 1) return;
|
|
196
|
+
size_t avail = cap - 1 - pos;
|
|
197
|
+
size_t n = len < avail ? len : avail;
|
|
198
|
+
memcpy(buf + pos, s, n);
|
|
199
|
+
pos += n;
|
|
200
|
+
}
|
|
201
|
+
void cstr(const char* s) {
|
|
202
|
+
if (s) append(s, strlen(s));
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/* One primitive as a log token (strings quoted). Objects print as
|
|
207
|
+
* [object]: nested inspection would need the C++ heap. */
|
|
208
|
+
static void mik__append_primitive(JSContext* ctx, JSValue v, MIKBoundedBuf& out) {
|
|
209
|
+
if (JS_IsObject(v)) {
|
|
210
|
+
out.cstr("[object]");
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
/* Allocation-free spellings first: a null rejection is how QuickJS
|
|
214
|
+
* reports OOM, so this must work with no heap at all */
|
|
215
|
+
if (JS_IsNull(v)) {
|
|
216
|
+
out.cstr("null");
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (JS_IsUndefined(v)) {
|
|
220
|
+
out.cstr("undefined");
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (JS_IsBool(v)) {
|
|
224
|
+
out.cstr(JS_ToBool(ctx, v) ? "true" : "false");
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (JS_VALUE_GET_TAG(v) == JS_TAG_INT) {
|
|
228
|
+
char num[16];
|
|
229
|
+
int n = snprintf(num, sizeof(num), "%d", JS_VALUE_GET_INT(v));
|
|
230
|
+
if (n > 0) out.append(num, (size_t)n);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
bool quote = JS_IsString(v);
|
|
234
|
+
/* JS-heap allocation only; returns NULL on OOM instead of throwing */
|
|
235
|
+
const char* s = JS_ToCString(ctx, v);
|
|
236
|
+
if (!s) {
|
|
237
|
+
/* Symbols reject ToString */
|
|
238
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
239
|
+
out.cstr("[primitive]");
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (quote) out.cstr("'");
|
|
243
|
+
out.cstr(s);
|
|
244
|
+
if (quote) out.cstr("'");
|
|
245
|
+
JS_FreeCString(ctx, s);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/* "Name: message { extra: fields }" then the stack frames, `indent`
|
|
249
|
+
* prefixed to every frame line. Also fits a plain object (a Result error
|
|
250
|
+
* as a panic cause has name/message and no stack). */
|
|
251
|
+
static void mik__append_error_head(JSContext* ctx, JSValue exc, const char* indent, MIKBoundedBuf& out) {
|
|
252
|
+
JSValue name_val = JS_GetPropertyStr(ctx, exc, "name");
|
|
253
|
+
JSValue msg_val = JS_GetPropertyStr(ctx, exc, "message");
|
|
254
|
+
if (JS_IsException(name_val) || JS_IsException(msg_val)) {
|
|
255
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
256
|
+
}
|
|
257
|
+
/* Non-string name/message count as absent (a native Result error has
|
|
258
|
+
* code + message and no name) */
|
|
259
|
+
const char* name = JS_IsString(name_val) ? JS_ToCString(ctx, name_val) : nullptr;
|
|
260
|
+
const char* emsg = JS_IsString(msg_val) ? JS_ToCString(ctx, msg_val) : nullptr;
|
|
261
|
+
|
|
262
|
+
if (name && name[0]) {
|
|
263
|
+
out.cstr(name);
|
|
264
|
+
if (emsg && emsg[0]) {
|
|
265
|
+
out.cstr(": ");
|
|
266
|
+
out.cstr(emsg);
|
|
267
|
+
}
|
|
268
|
+
} else if (emsg && emsg[0]) {
|
|
269
|
+
out.cstr(emsg);
|
|
270
|
+
} else {
|
|
271
|
+
out.cstr("[object]");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (name) JS_FreeCString(ctx, name);
|
|
275
|
+
if (emsg) JS_FreeCString(ctx, emsg);
|
|
276
|
+
JS_FreeValue(ctx, name_val);
|
|
277
|
+
JS_FreeValue(ctx, msg_val);
|
|
278
|
+
|
|
279
|
+
/* Own enumerable fields beyond the standard ones: code, errno, path... */
|
|
280
|
+
JSPropertyEnum* ptab = nullptr;
|
|
281
|
+
uint32_t plen = 0;
|
|
282
|
+
if (JS_GetOwnPropertyNames(ctx, &ptab, &plen, exc, JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) ==
|
|
283
|
+
0) {
|
|
284
|
+
bool first = true;
|
|
285
|
+
for (uint32_t i = 0; i < plen; i++) {
|
|
286
|
+
const char* key = JS_AtomToCString(ctx, ptab[i].atom);
|
|
287
|
+
if (key && strcmp(key, "name") != 0 && strcmp(key, "message") != 0 &&
|
|
288
|
+
strcmp(key, "stack") != 0 && strcmp(key, "cause") != 0) {
|
|
289
|
+
JSValue v = JS_GetProperty(ctx, exc, ptab[i].atom);
|
|
290
|
+
if (JS_IsException(v)) {
|
|
291
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
292
|
+
} else {
|
|
293
|
+
out.cstr(first ? " { " : ", ");
|
|
294
|
+
first = false;
|
|
295
|
+
out.cstr(key);
|
|
296
|
+
out.cstr(": ");
|
|
297
|
+
mik__append_primitive(ctx, v, out);
|
|
298
|
+
}
|
|
299
|
+
JS_FreeValue(ctx, v);
|
|
300
|
+
}
|
|
301
|
+
if (key) JS_FreeCString(ctx, key);
|
|
302
|
+
JS_FreeAtom(ctx, ptab[i].atom);
|
|
303
|
+
}
|
|
304
|
+
if (!first) out.cstr(" }");
|
|
305
|
+
js_free(ctx, ptab);
|
|
306
|
+
} else {
|
|
307
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
JSValue stack_val = JS_GetPropertyStr(ctx, exc, "stack");
|
|
311
|
+
if (JS_IsString(stack_val)) {
|
|
312
|
+
const char* stack = JS_ToCString(ctx, stack_val);
|
|
313
|
+
if (stack) {
|
|
314
|
+
const char* p = stack;
|
|
315
|
+
while (*p) {
|
|
316
|
+
const char* nl = strchr(p, '\n');
|
|
317
|
+
size_t len = nl ? (size_t)(nl - p) : strlen(p);
|
|
318
|
+
if (len) {
|
|
319
|
+
out.cstr("\n");
|
|
320
|
+
out.cstr(indent);
|
|
321
|
+
out.append(p, len);
|
|
322
|
+
}
|
|
323
|
+
if (!nl) break;
|
|
324
|
+
p = nl + 1;
|
|
325
|
+
}
|
|
326
|
+
JS_FreeCString(ctx, stack);
|
|
327
|
+
}
|
|
328
|
+
} else if (JS_IsException(stack_val)) {
|
|
329
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
330
|
+
}
|
|
331
|
+
JS_FreeValue(ctx, stack_val);
|
|
332
|
+
}
|
|
333
|
+
|
|
239
334
|
/* Dedup across report paths: one error can reach this function more than
|
|
240
335
|
* once for a single failure. The sync throw path (mik_dump_error) can report
|
|
241
336
|
* an error whose promise still sits in the deferred flush queue, e.g. an
|
|
@@ -275,77 +370,59 @@ bool mik__report_uncaught(JSContext* ctx, JSValue exc, bool in_promise) {
|
|
|
275
370
|
* abort() — masking the real OOM the reporter was trying to surface.
|
|
276
371
|
* Messages longer than the buffer get truncated, which beats
|
|
277
372
|
* aborting. */
|
|
278
|
-
char buf[
|
|
279
|
-
|
|
280
|
-
auto append = [&](const char* s, size_t len) {
|
|
281
|
-
if (pos >= sizeof(buf) - 1) return;
|
|
282
|
-
size_t avail = sizeof(buf) - 1 - pos;
|
|
283
|
-
size_t n = len < avail ? len : avail;
|
|
284
|
-
memcpy(buf + pos, s, n);
|
|
285
|
-
pos += n;
|
|
286
|
-
};
|
|
287
|
-
auto append_cstr = [&](const char* s) {
|
|
288
|
-
if (s) append(s, strlen(s));
|
|
289
|
-
};
|
|
373
|
+
char buf[1024];
|
|
374
|
+
MIKBoundedBuf out{buf, sizeof(buf)};
|
|
290
375
|
|
|
291
|
-
|
|
376
|
+
out.cstr(in_promise ? "Uncaught (in promise) " : "Uncaught ");
|
|
292
377
|
|
|
293
378
|
if (JS_IsObject(exc)) {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
379
|
+
mik__append_error_head(ctx, exc, "", out);
|
|
380
|
+
|
|
381
|
+
/* Cause chain: a panic's cause is the Result error that triggered
|
|
382
|
+
* it, so this is usually the line that says what actually failed.
|
|
383
|
+
* Bounded depth; `seen` cuts cycles. */
|
|
384
|
+
static const char* const indents[] = {"", " ", " ", " ", " "};
|
|
385
|
+
void* seen[countof(indents)] = {JS_VALUE_GET_PTR(exc)};
|
|
386
|
+
JSValue cur = JS_DupValue(ctx, exc);
|
|
387
|
+
for (size_t level = 1; level < countof(indents); level++) {
|
|
388
|
+
JSValue cause = JS_GetPropertyStr(ctx, cur, "cause");
|
|
389
|
+
JS_FreeValue(ctx, cur);
|
|
390
|
+
cur = cause;
|
|
391
|
+
if (JS_IsException(cause)) {
|
|
392
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
393
|
+
break;
|
|
304
394
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
append_cstr("\n");
|
|
321
|
-
append_cstr(stack);
|
|
395
|
+
if (JS_IsUndefined(cause)) break;
|
|
396
|
+
out.cstr("\n");
|
|
397
|
+
out.cstr(indents[level]);
|
|
398
|
+
out.cstr("[cause]: ");
|
|
399
|
+
if (!JS_IsObject(cause)) {
|
|
400
|
+
mik__append_primitive(ctx, cause, out);
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
bool cyclic = false;
|
|
404
|
+
for (size_t j = 0; j < level; j++) {
|
|
405
|
+
if (seen[j] == JS_VALUE_GET_PTR(cause)) cyclic = true;
|
|
406
|
+
}
|
|
407
|
+
if (cyclic) {
|
|
408
|
+
out.cstr("[Circular]");
|
|
409
|
+
break;
|
|
322
410
|
}
|
|
323
|
-
|
|
411
|
+
seen[level] = JS_VALUE_GET_PTR(cause);
|
|
412
|
+
mik__append_error_head(ctx, cause, indents[level], out);
|
|
324
413
|
}
|
|
325
|
-
JS_FreeValue(ctx,
|
|
414
|
+
JS_FreeValue(ctx, cur);
|
|
326
415
|
} else {
|
|
327
416
|
/* Describe primitive rejections by type without going through
|
|
328
417
|
* mik_inspect (which allocates a std::string). */
|
|
329
|
-
if (
|
|
330
|
-
append_cstr("null");
|
|
331
|
-
} else if (JS_IsUndefined(exc)) {
|
|
332
|
-
append_cstr("undefined");
|
|
333
|
-
} else if (JS_IsBool(exc)) {
|
|
334
|
-
append_cstr(JS_ToBool(ctx, exc) ? "true" : "false");
|
|
335
|
-
} else if (JS_IsNumber(exc)) {
|
|
336
|
-
double d = 0;
|
|
337
|
-
JS_ToFloat64(ctx, &d, exc);
|
|
338
|
-
char num[32];
|
|
339
|
-
int n = snprintf(num, sizeof(num), "%g", d);
|
|
340
|
-
if (n > 0) append(num, (size_t)n);
|
|
341
|
-
} else if (JS_IsString(exc)) {
|
|
418
|
+
if (JS_IsString(exc)) {
|
|
342
419
|
const char* s = JS_ToCString(ctx, exc);
|
|
343
420
|
if (s) {
|
|
344
|
-
|
|
421
|
+
out.cstr(s);
|
|
345
422
|
JS_FreeCString(ctx, s);
|
|
346
423
|
}
|
|
347
424
|
} else {
|
|
348
|
-
|
|
425
|
+
mik__append_primitive(ctx, exc, out);
|
|
349
426
|
}
|
|
350
427
|
|
|
351
428
|
/* Primitive rejections lose the throw-site stack (the async
|
|
@@ -359,17 +436,17 @@ bool mik__report_uncaught(JSContext* ctx, JSValue exc, bool in_promise) {
|
|
|
359
436
|
int n = snprintf(note, sizeof(note),
|
|
360
437
|
"\n (primitive rejection, systemFree=%zuB at report time)",
|
|
361
438
|
plat->get_free_system_mem());
|
|
362
|
-
if (n > 0) append(note, (size_t)n);
|
|
439
|
+
if (n > 0) out.append(note, (size_t)n);
|
|
363
440
|
}
|
|
364
441
|
}
|
|
365
442
|
|
|
366
443
|
if (mik__repl_is_protocol_mode()) {
|
|
367
|
-
mik__repl_proto_send_output(MIK_MSG_ERROR, buf, pos);
|
|
444
|
+
mik__repl_proto_send_output(MIK_MSG_ERROR, buf, out.pos);
|
|
368
445
|
return true;
|
|
369
446
|
}
|
|
370
447
|
|
|
371
|
-
|
|
372
|
-
MIK_GetPlatform()->stderr_write(buf, pos);
|
|
448
|
+
out.cstr("\r\n");
|
|
449
|
+
MIK_GetPlatform()->stderr_write(buf, out.pos);
|
|
373
450
|
return true;
|
|
374
451
|
}
|
|
375
452
|
|
package/src/mik_http_client.cpp
CHANGED
|
@@ -50,20 +50,9 @@ JSValue hc_iter_result(JSContext* ctx, bool done, JSValue value) {
|
|
|
50
50
|
return o;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
JSAtom hc_async_iterator_atom(JSContext* ctx) {
|
|
54
|
-
JSValue g = JS_GetGlobalObject(ctx);
|
|
55
|
-
JSValue sym_ctor = JS_GetPropertyStr(ctx, g, "Symbol");
|
|
56
|
-
JSValue sym = JS_GetPropertyStr(ctx, sym_ctor, "asyncIterator");
|
|
57
|
-
JSAtom atom = JS_ValueToAtom(ctx, sym);
|
|
58
|
-
JS_FreeValue(ctx, sym);
|
|
59
|
-
JS_FreeValue(ctx, sym_ctor);
|
|
60
|
-
JS_FreeValue(ctx, g);
|
|
61
|
-
return atom;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
53
|
/* source[Symbol.asyncIterator]() — returns the iterator (owned) or exception. */
|
|
65
54
|
JSValue hc_get_async_iterator(JSContext* ctx, JSValue source) {
|
|
66
|
-
JSAtom atom =
|
|
55
|
+
JSAtom atom = mik__async_iterator_atom(ctx);
|
|
67
56
|
JSValue fn = JS_GetProperty(ctx, source, atom);
|
|
68
57
|
JS_FreeAtom(ctx, atom);
|
|
69
58
|
if (JS_IsException(fn)) return fn;
|
|
@@ -757,7 +746,7 @@ JSValue hc_make_response(JSContext* ctx, JSValue raw) {
|
|
|
757
746
|
JS_PROP_C_W_E);
|
|
758
747
|
|
|
759
748
|
JSValue body = JS_NewObject(ctx);
|
|
760
|
-
JSAtom iter_atom =
|
|
749
|
+
JSAtom iter_atom = mik__async_iterator_atom(ctx);
|
|
761
750
|
JS_DefinePropertyValue(ctx, body, iter_atom,
|
|
762
751
|
JS_NewCFunctionData(ctx, hc_resp_body_iterator_cf, 0, 0, 2, data),
|
|
763
752
|
JS_PROP_C_W_E);
|
|
@@ -1093,7 +1082,7 @@ JSValue hc_on_headers(JSContext* ctx, JSValue this_val, int argc, JSValue* argv,
|
|
|
1093
1082
|
}
|
|
1094
1083
|
|
|
1095
1084
|
JSValue raw_body = JS_NewObject(ctx);
|
|
1096
|
-
JSAtom iter_atom =
|
|
1085
|
+
JSAtom iter_atom = mik__async_iterator_atom(ctx);
|
|
1097
1086
|
JS_DefinePropertyValue(ctx, raw_body, iter_atom,
|
|
1098
1087
|
JS_NewCFunctionData(ctx, hc_transport_iterator_cf, 0, 0, 1, &st),
|
|
1099
1088
|
JS_PROP_C_W_E);
|
package/src/mik_inspect.cpp
CHANGED
|
@@ -18,6 +18,8 @@ struct InspectOpts {
|
|
|
18
18
|
bool colors;
|
|
19
19
|
bool show_hidden;
|
|
20
20
|
std::vector<void*> seen;
|
|
21
|
+
/* Nesting of [cause] rendering, capped like mik__report_uncaught */
|
|
22
|
+
int cause_level = 0;
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
/* ── Helpers ─────────────────────────────────────────────────────── */
|
|
@@ -85,6 +87,7 @@ static std::string quote_key(const char* key) {
|
|
|
85
87
|
/* ── Forward declaration ─────────────────────────────────────────── */
|
|
86
88
|
|
|
87
89
|
static std::string inspect_value(JSContext* ctx, JSValue value, InspectOpts& opts, int depth);
|
|
90
|
+
static std::string indent_continuation(const std::string& s);
|
|
88
91
|
|
|
89
92
|
/* ── Custom inspect callback state (single-threaded JS) ──────────── */
|
|
90
93
|
|
|
@@ -263,7 +266,7 @@ static std::string inspect_array_items(JSContext* ctx, JSValue arr, uint32_t len
|
|
|
263
266
|
if (JS_IsException(item)) {
|
|
264
267
|
out += getter_threw(ctx, opts);
|
|
265
268
|
} else {
|
|
266
|
-
out += inspect_value(ctx, item, opts, depth);
|
|
269
|
+
out += indent_continuation(inspect_value(ctx, item, opts, depth));
|
|
267
270
|
}
|
|
268
271
|
JS_FreeValue(ctx, item);
|
|
269
272
|
}
|
|
@@ -318,7 +321,7 @@ static std::string inspect_properties(JSContext* ctx, JSValue obj, InspectOpts&
|
|
|
318
321
|
if (JS_IsException(val)) {
|
|
319
322
|
out += getter_threw(ctx, opts);
|
|
320
323
|
} else {
|
|
321
|
-
out += inspect_value(ctx, val, opts, depth);
|
|
324
|
+
out += indent_continuation(inspect_value(ctx, val, opts, depth));
|
|
322
325
|
}
|
|
323
326
|
JS_FreeValue(ctx, val);
|
|
324
327
|
}
|
|
@@ -474,6 +477,51 @@ static std::string inspect_regexp(JSContext* ctx, JSValue value, InspectOpts& op
|
|
|
474
477
|
return stylize("/(?:)/", MIK_TOKEN_REGEXP, opts);
|
|
475
478
|
}
|
|
476
479
|
|
|
480
|
+
/* Indent every line after the first by two spaces so a multi-line value
|
|
481
|
+
* (an error's stack frames, its own cause) nests visibly under its parent. */
|
|
482
|
+
static std::string indent_continuation(const std::string& s) {
|
|
483
|
+
std::string out;
|
|
484
|
+
out.reserve(s.size() + 8);
|
|
485
|
+
for (char c : s) {
|
|
486
|
+
out += c;
|
|
487
|
+
if (c == '\n') out += " ";
|
|
488
|
+
}
|
|
489
|
+
return out;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/* "\n [cause]: <value>" for an own cause, or "". Shared by Errors and
|
|
493
|
+
* plain objects, since Result errors carry cause as an ordinary field.
|
|
494
|
+
* The chain is what a log line exists for, so it renders past the depth
|
|
495
|
+
* limit; the caller keeps the parent on the seen list to bound cycles. */
|
|
496
|
+
static std::string inspect_cause(JSContext* ctx, JSValue value, InspectOpts& opts, int depth) {
|
|
497
|
+
JSValue cause = JS_GetPropertyStr(ctx, value, "cause");
|
|
498
|
+
if (JS_IsException(cause)) {
|
|
499
|
+
drain_exception(ctx);
|
|
500
|
+
return "";
|
|
501
|
+
}
|
|
502
|
+
/* Same cap as mik__report_uncaught; bounds recursion on the JS task stack */
|
|
503
|
+
static const int MAX_CAUSE_LEVEL = 4;
|
|
504
|
+
std::string out;
|
|
505
|
+
if (!JS_IsUndefined(cause)) {
|
|
506
|
+
out = "\n [cause]: ";
|
|
507
|
+
if (opts.cause_level >= MAX_CAUSE_LEVEL) {
|
|
508
|
+
out += TRUNCATOR;
|
|
509
|
+
} else {
|
|
510
|
+
opts.cause_level++;
|
|
511
|
+
out += indent_continuation(inspect_value(ctx, cause, opts, depth < 1 ? 1 : depth));
|
|
512
|
+
opts.cause_level--;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
JS_FreeValue(ctx, cause);
|
|
516
|
+
return out;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/* Errors render as
|
|
520
|
+
* Name: message { extra: props }
|
|
521
|
+
* at frame (file:line:col)
|
|
522
|
+
* [cause]: <inspected cause>
|
|
523
|
+
* The header keeps name, message and every own enumerable field (code,
|
|
524
|
+
* errno, ...) on one line; the stack and the cause chain follow. */
|
|
477
525
|
static std::string inspect_error(JSContext* ctx, JSValue value, InspectOpts& opts, int depth) {
|
|
478
526
|
/* Get name and message */
|
|
479
527
|
JSValue name_val = JS_GetPropertyStr(ctx, value, "name");
|
|
@@ -498,16 +546,40 @@ static std::string inspect_error(JSContext* ctx, JSValue value, InspectOpts& opt
|
|
|
498
546
|
JS_FreeValue(ctx, name_val);
|
|
499
547
|
JS_FreeValue(ctx, msg_val);
|
|
500
548
|
|
|
501
|
-
|
|
549
|
+
if (is_seen(opts, value)) return result + " [Circular]";
|
|
550
|
+
opts.seen.push_back(JS_VALUE_GET_PTR(value));
|
|
551
|
+
|
|
552
|
+
/* Inspect extra properties (skip standard error keys; cause is rendered
|
|
553
|
+
* below so it reads the same whether QuickJS or a panic defined it) */
|
|
502
554
|
static const char* const error_keys[] = {"stack", "line", "column", "name",
|
|
503
555
|
"message", "fileName", "lineNumber", "columnNumber",
|
|
504
|
-
"number", "description"};
|
|
556
|
+
"number", "description", "cause"};
|
|
505
557
|
std::string props =
|
|
506
558
|
inspect_properties(ctx, value, opts, depth, error_keys, countof(error_keys));
|
|
507
559
|
if (!props.empty()) {
|
|
508
560
|
result += " { " + props + " }";
|
|
509
561
|
}
|
|
510
562
|
|
|
563
|
+
/* QuickJS stacks are frame lines only (" at ..."), no header line */
|
|
564
|
+
JSValue stack_val = JS_GetPropertyStr(ctx, value, "stack");
|
|
565
|
+
if (JS_IsException(stack_val)) drain_exception(ctx);
|
|
566
|
+
if (JS_IsString(stack_val)) {
|
|
567
|
+
const char* stack = JS_ToCString(ctx, stack_val);
|
|
568
|
+
if (stack) {
|
|
569
|
+
std::string frames(stack);
|
|
570
|
+
JS_FreeCString(ctx, stack);
|
|
571
|
+
while (!frames.empty() && frames.back() == '\n') frames.pop_back();
|
|
572
|
+
if (!frames.empty()) {
|
|
573
|
+
result += "\n";
|
|
574
|
+
result += frames;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
JS_FreeValue(ctx, stack_val);
|
|
579
|
+
|
|
580
|
+
result += inspect_cause(ctx, value, opts, depth);
|
|
581
|
+
|
|
582
|
+
opts.seen.pop_back();
|
|
511
583
|
return result;
|
|
512
584
|
}
|
|
513
585
|
|
|
@@ -603,9 +675,9 @@ static std::string inspect_map(JSContext* ctx, JSValue value, InspectOpts& opts,
|
|
|
603
675
|
|
|
604
676
|
if (!first) items += ", ";
|
|
605
677
|
first = false;
|
|
606
|
-
items += inspect_value(ctx, key, opts, depth);
|
|
678
|
+
items += indent_continuation(inspect_value(ctx, key, opts, depth));
|
|
607
679
|
items += " => ";
|
|
608
|
-
items += inspect_value(ctx, val, opts, depth);
|
|
680
|
+
items += indent_continuation(inspect_value(ctx, val, opts, depth));
|
|
609
681
|
|
|
610
682
|
JS_FreeValue(ctx, key);
|
|
611
683
|
JS_FreeValue(ctx, val);
|
|
@@ -673,7 +745,7 @@ static std::string inspect_set(JSContext* ctx, JSValue value, InspectOpts& opts,
|
|
|
673
745
|
JSValue val = JS_GetPropertyStr(ctx, step, "value");
|
|
674
746
|
if (!first) items += ", ";
|
|
675
747
|
first = false;
|
|
676
|
-
items += inspect_value(ctx, val, opts, depth);
|
|
748
|
+
items += indent_continuation(inspect_value(ctx, val, opts, depth));
|
|
677
749
|
JS_FreeValue(ctx, val);
|
|
678
750
|
JS_FreeValue(ctx, step);
|
|
679
751
|
}
|
|
@@ -746,11 +818,16 @@ static std::string inspect_object(JSContext* ctx, JSValue value, InspectOpts& op
|
|
|
746
818
|
|
|
747
819
|
if (plen == 0) return "{}";
|
|
748
820
|
|
|
821
|
+
/* A cause field chains below the braces like an Error's, so a Result
|
|
822
|
+
* error wrapped around another keeps its whole chain readable */
|
|
823
|
+
static const char* const cause_key[] = {"cause"};
|
|
749
824
|
opts.seen.push_back(JS_VALUE_GET_PTR(value));
|
|
750
|
-
std::string props = inspect_properties(ctx, value, opts, depth);
|
|
825
|
+
std::string props = inspect_properties(ctx, value, opts, depth, cause_key, 1);
|
|
826
|
+
std::string result = props.empty() ? "{}" : "{ " + props + " }";
|
|
827
|
+
result += inspect_cause(ctx, value, opts, depth);
|
|
751
828
|
opts.seen.pop_back();
|
|
752
829
|
|
|
753
|
-
return
|
|
830
|
+
return result;
|
|
754
831
|
}
|
|
755
832
|
|
|
756
833
|
static std::string inspect_class(JSContext* ctx, JSValue value, const std::string& type_name,
|