@mikrojs/native 0.8.0 → 0.10.0-next.7.gdacecb0
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 -3
- package/cmake/mikrojs_bytecode.cmake +1 -1
- package/include/mikrojs/mikrojs.h +12 -0
- package/include/mikrojs/platform.h +4 -0
- package/include/mikrojs/private.h +10 -0
- package/package.json +6 -5
- 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/ble/ble.ts +2 -2
- package/runtime/fs/fs.ts +1 -1
- package/runtime/globals.d.ts +0 -24
- package/runtime/http/helpers.ts +1 -1
- package/runtime/http/native.ts +2 -2
- package/runtime/internal.d.ts +21 -16
- package/runtime/kv/nvs.ts +1 -1
- package/runtime/kv/rtc.ts +1 -1
- package/runtime/kv/shared.ts +2 -2
- package/runtime/module/module.ts +37 -0
- package/runtime/module/types.ts +21 -0
- package/runtime/pwm/pwm.ts +1 -1
- package/runtime/reader/reader.ts +1 -1
- package/runtime/reader/types.ts +1 -1
- package/runtime/schema/schema.ts +1 -1
- package/runtime/sntp/sntp.ts +1 -1
- package/runtime/stream/stream.ts +1 -1
- package/runtime/stream/types.ts +1 -1
- package/runtime/sys/sys.ts +1 -1
- package/runtime/test/test.ts +1 -1
- package/runtime/uart/uart.ts +1 -1
- package/runtime/udp/udp.ts +1 -1
- package/runtime/wifi/wifi.ts +2 -2
- package/scripts/bundle-runtime.js +2 -2
- package/src/builtins.cpp +2 -2
- package/src/mik_app_config.cpp +13 -1
- package/src/mik_sys.cpp +23 -0
- package/src/mik_text_encoding.cpp +135 -61
- package/src/mikrojs.cpp +11 -2
- package/src/modules.cpp +36 -50
package/CMakeLists.txt
CHANGED
|
@@ -81,9 +81,9 @@ endif()
|
|
|
81
81
|
include(cmake/mikrojs_bytecode.cmake)
|
|
82
82
|
mikrojs_generate_bytecode(
|
|
83
83
|
RUNTIME_DIR "${CMAKE_CURRENT_SOURCE_DIR}/runtime"
|
|
84
|
-
MODULES cbor env result schema fs http/helpers http/request i2c kv/nvs kv/rtc kv/shared neopixel observable observable/lazy observable/operators pin pwm reader sleep spi sntp stdio stream sys test uart udp wifi
|
|
85
|
-
MODULE_PREFIX "
|
|
86
|
-
SYMBOL_PREFIX "
|
|
84
|
+
MODULES cbor env result schema fs http/helpers http/request i2c kv/nvs kv/rtc kv/shared module neopixel observable observable/lazy observable/operators pin pwm reader sleep spi sntp stdio stream sys test uart udp wifi
|
|
85
|
+
MODULE_PREFIX "mikro"
|
|
86
|
+
SYMBOL_PREFIX "mikro"
|
|
87
87
|
TARGET gen_bytecode
|
|
88
88
|
)
|
|
89
89
|
add_dependencies(mikrojs gen_bytecode)
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# mikrojs_generate_bytecode(
|
|
8
8
|
# RUNTIME_DIR <path> # Directory containing <mod>/<mod>.ts files
|
|
9
9
|
# MODULES <mod1> <mod2> # Module names to compile
|
|
10
|
-
# MODULE_PREFIX <prefix> # Module name prefix (e.g. "
|
|
10
|
+
# MODULE_PREFIX <prefix> # Module name prefix (e.g. "mikro" -> "mikro/pin")
|
|
11
11
|
# SYMBOL_PREFIX <prefix> # C symbol prefix (e.g. "mikrojs" -> "mikrojs_pin_bytecode")
|
|
12
12
|
# TARGET <name> # Custom target name (default: gen_bytecode)
|
|
13
13
|
# WORKING_DIRECTORY <dir> # Working dir for esbuild (default: CMAKE_CURRENT_SOURCE_DIR)
|
|
@@ -29,8 +29,20 @@ typedef enum MIKLogFlush {
|
|
|
29
29
|
MIK_LOG_FLUSH_LINE = 1,
|
|
30
30
|
} MIKLogFlush;
|
|
31
31
|
|
|
32
|
+
/* What the device does after an uncaught exception (a "panic"). Mirror of
|
|
33
|
+
* the `onPanic.mode` field in the host-side TS config. */
|
|
34
|
+
typedef enum MIKPanicMode {
|
|
35
|
+
MIK_PANIC_RESTART = 0, /* default: reboot after the grace window */
|
|
36
|
+
MIK_PANIC_DEEP_SLEEP = 1, /* deep-sleep panic_sleep_duration_ms; wake reboots */
|
|
37
|
+
} MIKPanicMode;
|
|
38
|
+
|
|
32
39
|
typedef struct MIKConfig {
|
|
40
|
+
/* Grace window after a panic, awake and reachable, before the action
|
|
41
|
+
* fires (applies to both modes). */
|
|
33
42
|
int panic_restart_delay_ms;
|
|
43
|
+
MIKPanicMode panic_mode;
|
|
44
|
+
/* Deep-sleep length for MIK_PANIC_DEEP_SLEEP; ignored otherwise. */
|
|
45
|
+
int panic_sleep_duration_ms;
|
|
34
46
|
size_t stack_size;
|
|
35
47
|
uint32_t mem_reserved;
|
|
36
48
|
uint32_t fs_read_max; /* 0 = keep runtime default (65536) */
|
|
@@ -14,6 +14,10 @@ typedef struct MIKPlatform {
|
|
|
14
14
|
int64_t (*get_rtc_us)(void); /* RTC timer, survives deep sleep */
|
|
15
15
|
uint32_t (*random)(void);
|
|
16
16
|
void (*restart)(void);
|
|
17
|
+
/** Enter deep sleep for `us` microseconds; the timer wake reboots the
|
|
18
|
+
* chip, so this never returns on hardware. NULL on platforms without
|
|
19
|
+
* deep sleep (hosts), where callers fall back to restart(). */
|
|
20
|
+
void (*deep_sleep_us)(uint64_t us);
|
|
17
21
|
void (*yield)(void);
|
|
18
22
|
size_t (*get_free_system_mem)(void);
|
|
19
23
|
size_t (*get_min_free_system_mem)(void); /* All-time low watermark */
|
|
@@ -184,6 +184,16 @@ char* mik_module_normalizer(JSContext* ctx, const char* base_name, const char* n
|
|
|
184
184
|
* mid-evaluation, or unknown modules. */
|
|
185
185
|
int mik__unload_module(JSContext* ctx, const char* normalized_name);
|
|
186
186
|
|
|
187
|
+
/* Unload the module whose namespace object is `ns` (reverse lookup via
|
|
188
|
+
* JS_FindModuleByNamespace, then mik__unload_module by name). Returns the
|
|
189
|
+
* number of modules freed, 0 if `ns` is not a currently-loaded module's
|
|
190
|
+
* namespace, or -1 with a pending JS exception. Backs `disposable()`. */
|
|
191
|
+
int mik__unload_namespace(JSContext* ctx, JSValueConst ns);
|
|
192
|
+
|
|
193
|
+
/* True if `ns` is the namespace of a currently-loaded, non-anchored module
|
|
194
|
+
* (i.e. one that disposable() is allowed to unload). */
|
|
195
|
+
bool mik__is_unloadable_namespace(JSContext* ctx, JSValueConst ns);
|
|
196
|
+
|
|
187
197
|
int js_module_set_import_meta(JSContext* ctx, JSValue func_val, bool use_realpath, bool is_main);
|
|
188
198
|
|
|
189
199
|
JSValue mik__get_args(JSContext* ctx);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikrojs/native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0-next.7.gdacecb0",
|
|
4
4
|
"description": "Mikro.js C++ runtime library and Node.js native addon",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"esp32",
|
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
"quickjs",
|
|
10
10
|
"runtime"
|
|
11
11
|
],
|
|
12
|
-
"homepage": "https://github.com/mikrojs/
|
|
12
|
+
"homepage": "https://github.com/mikrojs/mikro#readme",
|
|
13
13
|
"bugs": {
|
|
14
|
-
"url": "https://github.com/mikrojs/
|
|
14
|
+
"url": "https://github.com/mikrojs/mikro/issues"
|
|
15
15
|
},
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"author": "Bjørge Næss <bjoerge@gmail.com>",
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
|
-
"url": "git+https://github.com/mikrojs/
|
|
20
|
+
"url": "git+https://github.com/mikrojs/mikro.git"
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
23
|
"dist",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"./runtime/inspect/types": "./runtime/inspect/types.ts",
|
|
56
56
|
"./runtime/kv/shared": "./runtime/kv/shared.ts",
|
|
57
57
|
"./runtime/kv/types": "./runtime/kv/types.ts",
|
|
58
|
+
"./runtime/module/types": "./runtime/module/types.ts",
|
|
58
59
|
"./runtime/neopixel/types": "./runtime/neopixel/types.ts",
|
|
59
60
|
"./runtime/observable/operators": "./runtime/observable/operators.ts",
|
|
60
61
|
"./runtime/observable/types": "./runtime/observable/types.ts",
|
|
@@ -78,7 +79,7 @@
|
|
|
78
79
|
"cmake-js": "^8.0.0",
|
|
79
80
|
"node-addon-api": "^8.7.0",
|
|
80
81
|
"node-gyp-build": "^4.8.4",
|
|
81
|
-
"@mikrojs/quickjs": "0.
|
|
82
|
+
"@mikrojs/quickjs": "0.10.0-next.7.gdacecb0"
|
|
82
83
|
},
|
|
83
84
|
"devDependencies": {
|
|
84
85
|
"@swc/core": "^1.15.30",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/runtime/ble/ble.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {lazyEvent} from '
|
|
2
|
-
import {err, ok} from '
|
|
1
|
+
import {lazyEvent} from 'mikro/observable/lazy'
|
|
2
|
+
import {err, ok} from 'mikro/result'
|
|
3
3
|
import {Ble as NativeBle} from 'native:ble'
|
|
4
4
|
|
|
5
5
|
import type {Result} from '../result/types.js'
|
package/runtime/fs/fs.ts
CHANGED
package/runtime/globals.d.ts
CHANGED
|
@@ -64,28 +64,4 @@ declare interface ImportMeta {
|
|
|
64
64
|
readonly basename: string
|
|
65
65
|
readonly path: string
|
|
66
66
|
readonly env: Readonly<Record<string, string | undefined>>
|
|
67
|
-
/**
|
|
68
|
-
* Evict a module from the runtime's module cache, recursively freeing
|
|
69
|
-
* any transitive dependency whose only importer was the unloaded module.
|
|
70
|
-
* Builtin modules (native:*, mikrojs/*, @mikrojs/*) are anchored
|
|
71
|
-
* and cannot be unloaded.
|
|
72
|
-
*
|
|
73
|
-
* Intended for use with dynamic imports: a static `import` at the top of
|
|
74
|
-
* the module creates a binding that pins the imported module's exports,
|
|
75
|
-
* so unloading it while the binding is live reclaims nothing. Prefer
|
|
76
|
-
* `const x = await import(spec)` + local scope + `import.meta.unload(spec)`.
|
|
77
|
-
*
|
|
78
|
-
* **Incompatible with `bundle: true`.** When the build bundles modules
|
|
79
|
-
* into chunks, source-level specifiers like `./phases/display.js` no
|
|
80
|
-
* longer correspond to loaded module names (they become chunk names like
|
|
81
|
-
* `display-GYVGL5MB.js`) — the unload call silently resolves to 0 and
|
|
82
|
-
* frees nothing. Projects that rely on dynamic-import-then-unload for
|
|
83
|
-
* memory reclaim need `bundle: false` so specifier strings survive to
|
|
84
|
-
* runtime unchanged.
|
|
85
|
-
*
|
|
86
|
-
* Resolves to the number of modules actually freed (root + transitive
|
|
87
|
-
* orphans), after a GC pass has run so a subsequent `memoryUsage()`
|
|
88
|
-
* snapshot reflects the reclaim.
|
|
89
|
-
*/
|
|
90
|
-
unload(specifier: string): Promise<number>
|
|
91
67
|
}
|
package/runtime/http/helpers.ts
CHANGED
package/runtime/http/native.ts
CHANGED
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
type Request,
|
|
9
9
|
RequestError,
|
|
10
10
|
type RequestOptions,
|
|
11
|
-
} from '
|
|
12
|
-
import {err, ok} from '
|
|
11
|
+
} from 'mikro/http/helpers'
|
|
12
|
+
import {err, ok} from 'mikro/result'
|
|
13
13
|
|
|
14
14
|
import type {ErrResult, Result} from '../result/types.js'
|
|
15
15
|
|
package/runtime/internal.d.ts
CHANGED
|
@@ -4,20 +4,20 @@ type NR<T> = {ok: true; value: T} | {ok: false; error: {code: number; message: s
|
|
|
4
4
|
|
|
5
5
|
// Internal shared runtime modules. Registered as builtins so sibling bundles
|
|
6
6
|
// can reference them at runtime to dedupe shared code, but not exposed via
|
|
7
|
-
// the public `
|
|
7
|
+
// the public `mikro/*` subpath exports so user apps can't accidentally
|
|
8
8
|
// import them. Keep these in sync with the MODULES list in the firmware +
|
|
9
9
|
// @mikrojs/native CMakeLists.
|
|
10
|
-
declare module '
|
|
10
|
+
declare module 'mikro/kv/shared' {
|
|
11
11
|
export {KVError, makeCreateValue, type NativeKvFns} from './kv/shared.js'
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
declare module '
|
|
14
|
+
declare module 'mikro/observable/lazy' {
|
|
15
15
|
export {lazyEvent} from './observable/lazy.js'
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
declare module 'native:cbor' {
|
|
19
19
|
import type {CborError} from '@mikrojs/native/runtime/cbor/types'
|
|
20
|
-
import type {Result} from '
|
|
20
|
+
import type {Result} from 'mikro/result'
|
|
21
21
|
export function encode(value: unknown): Result<Uint8Array, CborError>
|
|
22
22
|
export function decode(data: Uint8Array): Result<unknown, CborError>
|
|
23
23
|
}
|
|
@@ -46,6 +46,11 @@ declare module 'native:sys' {
|
|
|
46
46
|
}
|
|
47
47
|
export function jsMemoryUsage(): JsMemoryUsage
|
|
48
48
|
export function gc(): void
|
|
49
|
+
/** Unload the module whose namespace object is `ns`; returns the number of
|
|
50
|
+
* modules freed (0 if `ns` is not a loaded module's namespace). */
|
|
51
|
+
export function unloadNamespace(ns: object): number
|
|
52
|
+
/** True if `ns` is the namespace of a loaded, non-anchored (non-builtin) module. */
|
|
53
|
+
export function isUnloadableNamespace(ns: object): boolean
|
|
49
54
|
export function activeTimers(): number
|
|
50
55
|
export function setTime(millisSinceEpoch: number): void
|
|
51
56
|
export function uptime(): {boot: number; rtc: number}
|
|
@@ -117,7 +122,7 @@ declare module 'native:stdio' {
|
|
|
117
122
|
|
|
118
123
|
declare module 'native:pin' {
|
|
119
124
|
import type {PinError} from '@mikrojs/native/runtime/pin/types'
|
|
120
|
-
import type {Result} from '
|
|
125
|
+
import type {Result} from 'mikro/result'
|
|
121
126
|
export type PinMode = 0x01 | 0x03 | 0x05
|
|
122
127
|
export function pinMode(pin: number, value: PinMode): Result<void, PinError>
|
|
123
128
|
export function digitalWrite(pin: number, value: 0 | 1): Result<void, PinError>
|
|
@@ -138,8 +143,8 @@ declare module 'native:sleep' {
|
|
|
138
143
|
}
|
|
139
144
|
|
|
140
145
|
declare module 'native:http' {
|
|
141
|
-
import type {RequestError} from '
|
|
142
|
-
import type {Result} from '
|
|
146
|
+
import type {RequestError} from 'mikro/http/helpers'
|
|
147
|
+
import type {Result} from 'mikro/result'
|
|
143
148
|
|
|
144
149
|
import type {ErrResult} from './result/types.js'
|
|
145
150
|
type HeadersMsg = {status: number; headers: [string, string][]}
|
|
@@ -160,7 +165,7 @@ declare module 'native:http' {
|
|
|
160
165
|
}
|
|
161
166
|
declare module 'native:i2c' {
|
|
162
167
|
import type {I2cError} from '@mikrojs/native/runtime/i2c/types'
|
|
163
|
-
import type {Result} from '
|
|
168
|
+
import type {Result} from 'mikro/result'
|
|
164
169
|
|
|
165
170
|
export interface I2cBaseOptions {
|
|
166
171
|
freq?: number
|
|
@@ -192,7 +197,7 @@ declare module 'native:i2c' {
|
|
|
192
197
|
}
|
|
193
198
|
declare module 'native:spi' {
|
|
194
199
|
import type {SpiError} from '@mikrojs/native/runtime/spi/types'
|
|
195
|
-
import type {Result} from '
|
|
200
|
+
import type {Result} from 'mikro/result'
|
|
196
201
|
|
|
197
202
|
export interface SpiOptions {
|
|
198
203
|
clk: number
|
|
@@ -221,7 +226,7 @@ declare module 'native:spi' {
|
|
|
221
226
|
|
|
222
227
|
declare module 'native:sntp' {
|
|
223
228
|
import type {SntpError} from '@mikrojs/native/runtime/sntp/types'
|
|
224
|
-
import type {Result} from '
|
|
229
|
+
import type {Result} from 'mikro/result'
|
|
225
230
|
/**
|
|
226
231
|
* Native errors: the outer Result covers init/config failures (InitFailed);
|
|
227
232
|
* the inner Result on resolve covers post-start errors (Cancelled).
|
|
@@ -257,7 +262,7 @@ declare module 'native:nvs_kv' {
|
|
|
257
262
|
|
|
258
263
|
declare module 'native:pwm' {
|
|
259
264
|
import type {PwmError} from '@mikrojs/native/runtime/pwm/types'
|
|
260
|
-
import type {Result} from '
|
|
265
|
+
import type {Result} from 'mikro/result'
|
|
261
266
|
|
|
262
267
|
export declare const Pwm: {
|
|
263
268
|
prototype: Pwm
|
|
@@ -274,7 +279,7 @@ declare module 'native:pwm' {
|
|
|
274
279
|
|
|
275
280
|
declare module 'native:neopixel' {
|
|
276
281
|
import type {NeoPixelError} from '@mikrojs/native/runtime/neopixel/types'
|
|
277
|
-
import type {Result} from '
|
|
282
|
+
import type {Result} from 'mikro/result'
|
|
278
283
|
|
|
279
284
|
export declare const NeoPixel: {
|
|
280
285
|
prototype: NeoPixel
|
|
@@ -292,7 +297,7 @@ declare module 'native:neopixel' {
|
|
|
292
297
|
|
|
293
298
|
declare module 'native:uart' {
|
|
294
299
|
import type {UartError} from '@mikrojs/native/runtime/uart/types'
|
|
295
|
-
import type {Result} from '
|
|
300
|
+
import type {Result} from 'mikro/result'
|
|
296
301
|
|
|
297
302
|
export interface UartOptions {
|
|
298
303
|
tx?: number
|
|
@@ -324,7 +329,7 @@ declare module 'native:uart' {
|
|
|
324
329
|
|
|
325
330
|
declare module 'native:ble' {
|
|
326
331
|
import type {BleError} from '@mikrojs/native/runtime/ble/types'
|
|
327
|
-
import type {Result} from '
|
|
332
|
+
import type {Result} from 'mikro/result'
|
|
328
333
|
|
|
329
334
|
type R<T> = Result<T, BleError>
|
|
330
335
|
|
|
@@ -378,7 +383,7 @@ declare module 'native:ble' {
|
|
|
378
383
|
|
|
379
384
|
declare module 'native:wifi' {
|
|
380
385
|
import type {WifiError} from '@mikrojs/native/runtime/wifi/types'
|
|
381
|
-
import type {Result} from '
|
|
386
|
+
import type {Result} from 'mikro/result'
|
|
382
387
|
|
|
383
388
|
type R<T> = Result<T, WifiError>
|
|
384
389
|
|
|
@@ -464,7 +469,7 @@ declare module 'native:udp' {
|
|
|
464
469
|
UdpError,
|
|
465
470
|
UdpFamily,
|
|
466
471
|
} from '@mikrojs/native/runtime/udp/types'
|
|
467
|
-
import type {Result} from '
|
|
472
|
+
import type {Result} from 'mikro/result'
|
|
468
473
|
|
|
469
474
|
export interface NativeUdpSocket {
|
|
470
475
|
readonly port: number
|
package/runtime/kv/nvs.ts
CHANGED
package/runtime/kv/rtc.ts
CHANGED
package/runtime/kv/shared.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {err, ok} from '
|
|
1
|
+
import {err, ok} from 'mikro/result'
|
|
2
2
|
// Typed loosely to avoid deep type instantiation from Infer<S> in parse's generics.
|
|
3
3
|
// Type safety is provided by the storage interface overloads in types.ts.
|
|
4
|
-
import {parse as _parse} from '
|
|
4
|
+
import {parse as _parse} from 'mikro/schema'
|
|
5
5
|
|
|
6
6
|
import type {NativeError} from '../result/types.js'
|
|
7
7
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {isUnloadableNamespace, unloadNamespace} from 'native:sys'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Load a dynamically imported module, run `use` with it, then unload it and
|
|
5
|
+
* reclaim its memory. Use for app phases that pull in code you won't need again:
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* const status = await withUnload(
|
|
9
|
+
* import('./phases/modem.js'),
|
|
10
|
+
* (modem) => modem.runPhase(input),
|
|
11
|
+
* )
|
|
12
|
+
* // the modem phase is unloaded and reclaimed by the time this resolves
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* The module is unloaded when `use` returns (or throws), including any transitive
|
|
16
|
+
* dependency whose only importer was this module. Don't return one of the
|
|
17
|
+
* module's exports from `use`; that keeps the module alive. Throws if the
|
|
18
|
+
* resolved value isn't an unloadable module namespace (e.g. a builtin
|
|
19
|
+
* `mikrojs/*` or `native:*` module).
|
|
20
|
+
*
|
|
21
|
+
* This is non-standard: standard JavaScript modules stay loaded for the life of
|
|
22
|
+
* the program, and a later `import` re-evaluates an unloaded module from scratch.
|
|
23
|
+
*/
|
|
24
|
+
export async function withUnload<T extends object, R>(
|
|
25
|
+
mod: Promise<T>,
|
|
26
|
+
use: (mod: T) => R | Promise<R>,
|
|
27
|
+
): Promise<R> {
|
|
28
|
+
const ns = await mod
|
|
29
|
+
if (!isUnloadableNamespace(ns)) {
|
|
30
|
+
throw new TypeError('withUnload() expects an unloadable module namespace')
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
return await use(ns)
|
|
34
|
+
} finally {
|
|
35
|
+
unloadNamespace(ns)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Load a dynamically imported module, run `use` with it, then unload it and
|
|
2
|
+
* reclaim its memory:
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* const status = await withUnload(
|
|
6
|
+
* import('./phases/modem.js'),
|
|
7
|
+
* (modem) => modem.runPhase(input),
|
|
8
|
+
* )
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* The module (and any transitive dependency whose only importer was it) is
|
|
12
|
+
* unloaded when `use` returns or throws. Don't return one of the module's
|
|
13
|
+
* exports from `use`; that keeps it alive. Throws if the resolved value isn't
|
|
14
|
+
* an unloadable module namespace (e.g. a builtin `mikrojs/*` or `native:*`).
|
|
15
|
+
*
|
|
16
|
+
* Non-standard: standard JavaScript modules stay loaded for the life of the
|
|
17
|
+
* program, and a later `import` re-evaluates an unloaded module from scratch. */
|
|
18
|
+
export declare function withUnload<T extends object, R>(
|
|
19
|
+
mod: Promise<T>,
|
|
20
|
+
use: (mod: T) => R | Promise<R>,
|
|
21
|
+
): Promise<R>
|
package/runtime/pwm/pwm.ts
CHANGED
package/runtime/reader/reader.ts
CHANGED
package/runtime/reader/types.ts
CHANGED
package/runtime/schema/schema.ts
CHANGED
package/runtime/sntp/sntp.ts
CHANGED
package/runtime/stream/stream.ts
CHANGED
package/runtime/stream/types.ts
CHANGED
package/runtime/sys/sys.ts
CHANGED
package/runtime/test/test.ts
CHANGED
package/runtime/uart/uart.ts
CHANGED
package/runtime/udp/udp.ts
CHANGED
package/runtime/wifi/wifi.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {lazyEvent} from '
|
|
2
|
-
import {err, ok} from '
|
|
1
|
+
import {lazyEvent} from 'mikro/observable/lazy'
|
|
2
|
+
import {err, ok} from 'mikro/result'
|
|
3
3
|
import {Wifi as NativeWifi} from 'native:wifi'
|
|
4
4
|
|
|
5
5
|
import type {Result} from '../result/types.js'
|
|
@@ -104,7 +104,7 @@ for (const name of moduleNames) {
|
|
|
104
104
|
target: 'es2024',
|
|
105
105
|
platform: 'neutral',
|
|
106
106
|
format: 'esm',
|
|
107
|
-
external: ['
|
|
107
|
+
external: ['mikro', 'mikro/*', '@mikrojs/*', 'native:*'],
|
|
108
108
|
})
|
|
109
109
|
|
|
110
110
|
const output = result.outputFiles?.[0]
|
|
@@ -128,7 +128,7 @@ for (const name of moduleNames) {
|
|
|
128
128
|
// Extract external imports so CMake can pass them as -M flags to qjsc
|
|
129
129
|
const externals = []
|
|
130
130
|
for (const match of source.matchAll(
|
|
131
|
-
/(?:from|import)\s*["']((?:native:|
|
|
131
|
+
/(?:from|import)\s*["']((?:native:|mikro\/|@mikrojs\/)[^"']+)["']/g,
|
|
132
132
|
)) {
|
|
133
133
|
externals.push(match[1])
|
|
134
134
|
}
|
package/src/builtins.cpp
CHANGED
|
@@ -14,8 +14,8 @@ typedef struct {
|
|
|
14
14
|
|
|
15
15
|
/* Generated by mikrojs_generate_bytecode() in CMake — includes + lookup table.
|
|
16
16
|
* Adding a new module only requires updating the MODULES list in CMakeLists.txt. */
|
|
17
|
-
#include "gen/
|
|
18
|
-
#define builtins
|
|
17
|
+
#include "gen/mikro_builtins_table.h"
|
|
18
|
+
#define builtins mikro_builtins
|
|
19
19
|
|
|
20
20
|
/* External builtins registered by board/driver packages via MIK_REGISTER_BUILTIN() */
|
|
21
21
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
package/src/mik_app_config.cpp
CHANGED
|
@@ -13,6 +13,8 @@ static const char* TAG = "mik_app_config";
|
|
|
13
13
|
|
|
14
14
|
void MIK_DefaultConfig(MIKConfig* config) {
|
|
15
15
|
config->panic_restart_delay_ms = 1000;
|
|
16
|
+
config->panic_mode = MIK_PANIC_RESTART;
|
|
17
|
+
config->panic_sleep_duration_ms = 0;
|
|
16
18
|
config->stack_size = 0;
|
|
17
19
|
config->mem_reserved = 64 * 1024;
|
|
18
20
|
config->fs_read_max = 0; /* 0 = runtime default (65536) */
|
|
@@ -318,9 +320,19 @@ int MIK_LoadConfig(const char* base_path, MIKConfig* config) {
|
|
|
318
320
|
if (buf) {
|
|
319
321
|
double num_val;
|
|
320
322
|
|
|
321
|
-
if (mik__json_get_number(buf, "
|
|
323
|
+
if (mik__json_get_number(buf, "onPanic.delay", &num_val)) {
|
|
322
324
|
config->panic_restart_delay_ms = (int)num_val;
|
|
323
325
|
}
|
|
326
|
+
char panic_mode_str[16];
|
|
327
|
+
if (mik__json_get_string(buf, "onPanic.mode", panic_mode_str,
|
|
328
|
+
sizeof(panic_mode_str))) {
|
|
329
|
+
config->panic_mode = strcmp(panic_mode_str, "deepSleep") == 0
|
|
330
|
+
? MIK_PANIC_DEEP_SLEEP
|
|
331
|
+
: MIK_PANIC_RESTART;
|
|
332
|
+
}
|
|
333
|
+
if (mik__json_get_number(buf, "onPanic.duration", &num_val)) {
|
|
334
|
+
config->panic_sleep_duration_ms = (int)num_val;
|
|
335
|
+
}
|
|
324
336
|
if (mik__json_get_number(buf, "stackSize", &num_val)) {
|
|
325
337
|
config->stack_size = (size_t)num_val;
|
|
326
338
|
}
|
package/src/mik_sys.cpp
CHANGED
|
@@ -96,6 +96,24 @@ static JSValue mik__sys_gc(JSContext* ctx, JSValue this_val, int argc, JSValue*
|
|
|
96
96
|
return JS_UNDEFINED;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/* Backs withUnload()'s unload step. Unloads the module whose
|
|
100
|
+
* namespace is argv[0]; returns the number of modules freed (0 if not a
|
|
101
|
+
* loaded module's namespace). Refcounting reclaims the memory once the
|
|
102
|
+
* caller's reference drops; no GC here. */
|
|
103
|
+
static JSValue mik__sys_unload_namespace(JSContext* ctx, JSValue this_val, int argc,
|
|
104
|
+
JSValue* argv) {
|
|
105
|
+
int rv = mik__unload_namespace(ctx, argv[0]);
|
|
106
|
+
if (rv < 0) return JS_EXCEPTION;
|
|
107
|
+
return JS_NewInt32(ctx, rv);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* True if argv[0] is the namespace of a loaded, non-anchored module.
|
|
111
|
+
* withUnload() uses this to reject builtins (native:/mikrojs//@mikrojs/). */
|
|
112
|
+
static JSValue mik__sys_is_unloadable_namespace(JSContext* ctx, JSValue this_val, int argc,
|
|
113
|
+
JSValue* argv) {
|
|
114
|
+
return JS_NewBool(ctx, mik__is_unloadable_namespace(ctx, argv[0]));
|
|
115
|
+
}
|
|
116
|
+
|
|
99
117
|
static JSValue mik__sys_active_timers(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
100
118
|
MIKRuntime* mik_rt = static_cast<MIKRuntime*>(JS_GetContextOpaque(ctx));
|
|
101
119
|
CHECK_NOT_NULL(mik_rt);
|
|
@@ -220,6 +238,11 @@ void mik__sys_api_init(JSContext* ctx, JSValue ns) {
|
|
|
220
238
|
JS_SetPropertyStr(ctx, ns, "jsMemoryUsage",
|
|
221
239
|
JS_NewCFunction(ctx, mik__sys_js_memory_usage, "jsMemoryUsage", 0));
|
|
222
240
|
JS_SetPropertyStr(ctx, ns, "gc", JS_NewCFunction(ctx, mik__sys_gc, "gc", 0));
|
|
241
|
+
JS_SetPropertyStr(ctx, ns, "unloadNamespace",
|
|
242
|
+
JS_NewCFunction(ctx, mik__sys_unload_namespace, "unloadNamespace", 1));
|
|
243
|
+
JS_SetPropertyStr(
|
|
244
|
+
ctx, ns, "isUnloadableNamespace",
|
|
245
|
+
JS_NewCFunction(ctx, mik__sys_is_unloadable_namespace, "isUnloadableNamespace", 1));
|
|
223
246
|
JS_SetPropertyStr(ctx, ns, "activeTimers",
|
|
224
247
|
JS_NewCFunction(ctx, mik__sys_active_timers, "activeTimers", 0));
|
|
225
248
|
JS_SetPropertyStr(ctx, ns, "setTime",
|
|
@@ -242,44 +242,138 @@ static const JSCFunctionListEntry mik__text_decoder_proto_funcs[] = {
|
|
|
242
242
|
|
|
243
243
|
static const char b64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
244
244
|
|
|
245
|
-
|
|
245
|
+
/* The base64 decoder below is ported from QuickJS-NG (quickjs.c, `b64_decode`
|
|
246
|
+
* and its lookup tables), which implements the WHATWG forgiving-base64 decode
|
|
247
|
+
* algorithm. QuickJS-NG is MIT licensed:
|
|
248
|
+
* Copyright (c) 2017-2024 Fabrice Bellard, Charlie Gordon and contributors.
|
|
249
|
+
* We use only the standard alphabet (atob/btoa never decode base64url) and
|
|
250
|
+
* surface decode failures as our own error type rather than a DOMException. */
|
|
251
|
+
enum { K_VAL = 1u, K_WS = 2u };
|
|
252
|
+
|
|
253
|
+
/* Sextet value per base64 character (0 for non-base64 bytes; validity is gated
|
|
254
|
+
* by b64_flags below). Positional initializer to stay standard C++. */
|
|
255
|
+
static const uint8_t b64_val[256] = {
|
|
246
256
|
/* clang-format off */
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
257
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
258
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
259
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,62, 0, 0, 0,63,
|
|
260
|
+
52,53,54,55,56,57,58,59,60,61, 0, 0, 0, 0, 0, 0,
|
|
261
|
+
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
|
|
262
|
+
15,16,17,18,19,20,21,22,23,24,25, 0, 0, 0, 0, 0,
|
|
263
|
+
0,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
|
|
264
|
+
41,42,43,44,45,46,47,48,49,50,51, 0, 0, 0, 0, 0,
|
|
265
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
266
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
267
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
268
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
269
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
270
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
271
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
272
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
263
273
|
/* clang-format on */
|
|
264
274
|
};
|
|
265
275
|
|
|
276
|
+
/* Per-byte class: K_WS (2) for ASCII whitespace, K_VAL (1) for the 64 standard
|
|
277
|
+
* base64 alphabet characters, 0 (invalid) otherwise. */
|
|
278
|
+
static const char b64_flags[256] = {
|
|
279
|
+
/* clang-format off */
|
|
280
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 2, 2, 0, 0,
|
|
281
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
282
|
+
2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1,
|
|
283
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
|
|
284
|
+
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
285
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
|
|
286
|
+
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
287
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
|
|
288
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
289
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
290
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
291
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
292
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
293
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
294
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
295
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
296
|
+
/* clang-format on */
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
/* Implements https://infra.spec.whatwg.org/#forgiving-base64-decode.
|
|
300
|
+
* Writes decoded bytes to dst and returns the count; sets *err on malformed
|
|
301
|
+
* input. dst must have room for at least (len / 4) * 3 + 3 bytes. */
|
|
302
|
+
static size_t b64_decode(const char* src, size_t len, uint8_t* dst, int* err) {
|
|
303
|
+
size_t i, j;
|
|
304
|
+
uint32_t acc;
|
|
305
|
+
int seen, pad;
|
|
306
|
+
unsigned ch;
|
|
307
|
+
|
|
308
|
+
acc = 0;
|
|
309
|
+
seen = 0;
|
|
310
|
+
for (i = 0, j = 0; i < len; i++) {
|
|
311
|
+
ch = (unsigned char)src[i];
|
|
312
|
+
if ((b64_flags[ch] & K_WS)) continue;
|
|
313
|
+
if (!(b64_flags[ch] & K_VAL)) break;
|
|
314
|
+
acc = (acc << 6) | b64_val[ch];
|
|
315
|
+
seen++;
|
|
316
|
+
if (seen == 4) {
|
|
317
|
+
dst[j++] = (acc >> 16) & 0xFF;
|
|
318
|
+
dst[j++] = (acc >> 8) & 0xFF;
|
|
319
|
+
dst[j++] = acc & 0xFF;
|
|
320
|
+
seen = 0;
|
|
321
|
+
acc = 0;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (seen != 0) {
|
|
326
|
+
if (seen == 3) {
|
|
327
|
+
dst[j++] = (acc >> 10) & 0xFF;
|
|
328
|
+
dst[j++] = (acc >> 2) & 0xFF;
|
|
329
|
+
} else if (seen == 2) {
|
|
330
|
+
dst[j++] = (acc >> 4) & 0xFF;
|
|
331
|
+
} else {
|
|
332
|
+
*err = 1;
|
|
333
|
+
return 0;
|
|
334
|
+
}
|
|
335
|
+
for (pad = 0; i < len; i++) {
|
|
336
|
+
ch = (unsigned char)src[i];
|
|
337
|
+
if (pad < 2 && ch == '=') {
|
|
338
|
+
pad++;
|
|
339
|
+
} else if (!(b64_flags[ch] & K_WS)) {
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (pad != 0 && seen + pad != 4) {
|
|
344
|
+
*err = 1;
|
|
345
|
+
return 0;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
*err = i < len;
|
|
350
|
+
return j;
|
|
351
|
+
}
|
|
352
|
+
|
|
266
353
|
/* btoa: encode a binary string to base64.
|
|
267
|
-
* Per the web spec,
|
|
268
|
-
*
|
|
269
|
-
*
|
|
354
|
+
* Per the web spec, the argument is first coerced to a string (ToString), then
|
|
355
|
+
* each character's code point is treated as a raw byte. We iterate by code
|
|
356
|
+
* point, not by UTF-8 bytes, since JS_ToCStringLen would turn e.g. U+0080 into
|
|
357
|
+
* two bytes (\xC2\x80). */
|
|
270
358
|
static JSValue mik__btoa(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
271
|
-
JSValue str_val = argv[0];
|
|
359
|
+
JSValue str_val = JS_ToString(ctx, argv[0]);
|
|
360
|
+
if (JS_IsException(str_val)) return JS_EXCEPTION;
|
|
361
|
+
|
|
272
362
|
JSValue len_val = JS_GetPropertyStr(ctx, str_val, "length");
|
|
273
363
|
int64_t len;
|
|
274
364
|
if (JS_ToInt64(ctx, &len, len_val)) {
|
|
275
365
|
JS_FreeValue(ctx, len_val);
|
|
366
|
+
JS_FreeValue(ctx, str_val);
|
|
276
367
|
return JS_EXCEPTION;
|
|
277
368
|
}
|
|
278
369
|
JS_FreeValue(ctx, len_val);
|
|
279
370
|
|
|
280
371
|
/* Extract code points into a byte buffer, validating latin1 range */
|
|
281
372
|
uint8_t* bytes = static_cast<uint8_t*>(js_malloc(ctx, len > 0 ? len : 1));
|
|
282
|
-
if (!bytes)
|
|
373
|
+
if (!bytes) {
|
|
374
|
+
JS_FreeValue(ctx, str_val);
|
|
375
|
+
return JS_EXCEPTION;
|
|
376
|
+
}
|
|
283
377
|
|
|
284
378
|
JSAtom charCodeAt_atom = JS_NewAtom(ctx, "charCodeAt");
|
|
285
379
|
for (int64_t i = 0; i < len; i++) {
|
|
@@ -289,6 +383,7 @@ static JSValue mik__btoa(JSContext* ctx, JSValue this_val, int argc, JSValue* ar
|
|
|
289
383
|
if (JS_IsException(code)) {
|
|
290
384
|
JS_FreeAtom(ctx, charCodeAt_atom);
|
|
291
385
|
js_free(ctx, bytes);
|
|
386
|
+
JS_FreeValue(ctx, str_val);
|
|
292
387
|
return JS_EXCEPTION;
|
|
293
388
|
}
|
|
294
389
|
int32_t cp;
|
|
@@ -297,6 +392,7 @@ static JSValue mik__btoa(JSContext* ctx, JSValue this_val, int argc, JSValue* ar
|
|
|
297
392
|
if (cp > 0xFF) {
|
|
298
393
|
JS_FreeAtom(ctx, charCodeAt_atom);
|
|
299
394
|
js_free(ctx, bytes);
|
|
395
|
+
JS_FreeValue(ctx, str_val);
|
|
300
396
|
return JS_ThrowRangeError(ctx,
|
|
301
397
|
"The string to be encoded contains characters outside of the "
|
|
302
398
|
"Latin1 range");
|
|
@@ -304,6 +400,7 @@ static JSValue mik__btoa(JSContext* ctx, JSValue this_val, int argc, JSValue* ar
|
|
|
304
400
|
bytes[i] = (uint8_t)cp;
|
|
305
401
|
}
|
|
306
402
|
JS_FreeAtom(ctx, charCodeAt_atom);
|
|
403
|
+
JS_FreeValue(ctx, str_val);
|
|
307
404
|
|
|
308
405
|
size_t out_len = 4 * ((len + 2) / 3);
|
|
309
406
|
char* out = static_cast<char*>(js_malloc(ctx, out_len + 1));
|
|
@@ -332,55 +429,32 @@ static JSValue mik__btoa(JSContext* ctx, JSValue this_val, int argc, JSValue* ar
|
|
|
332
429
|
return ret;
|
|
333
430
|
}
|
|
334
431
|
|
|
335
|
-
/* atob: decode a base64 string to a binary string
|
|
432
|
+
/* atob: decode a base64 string to a binary string. The argument is coerced to
|
|
433
|
+
* a string (ToString) per spec, then decoded via the ported forgiving-base64
|
|
434
|
+
* decoder above. */
|
|
336
435
|
static JSValue mik__atob(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
436
|
+
JSValue str_val = JS_ToString(ctx, argv[0]);
|
|
437
|
+
if (JS_IsException(str_val)) return JS_EXCEPTION;
|
|
337
438
|
size_t len;
|
|
338
|
-
const char* str = JS_ToCStringLen(ctx, &len,
|
|
439
|
+
const char* str = JS_ToCStringLen(ctx, &len, str_val);
|
|
440
|
+
JS_FreeValue(ctx, str_val);
|
|
339
441
|
if (!str) return JS_EXCEPTION;
|
|
340
442
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
char* out = static_cast<char*>(js_malloc(ctx, out_cap));
|
|
443
|
+
size_t out_cap = (len / 4) * 3 + 3;
|
|
444
|
+
uint8_t* out = static_cast<uint8_t*>(js_malloc(ctx, out_cap));
|
|
344
445
|
if (!out) {
|
|
345
446
|
JS_FreeCString(ctx, str);
|
|
346
447
|
return JS_EXCEPTION;
|
|
347
448
|
}
|
|
348
449
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
size_t j = 0;
|
|
352
|
-
int pad = 0;
|
|
353
|
-
|
|
354
|
-
for (size_t i = 0; i < len; i++) {
|
|
355
|
-
char ch = str[i];
|
|
356
|
-
if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\f') continue;
|
|
357
|
-
if (ch == '=') {
|
|
358
|
-
pad++;
|
|
359
|
-
continue;
|
|
360
|
-
}
|
|
361
|
-
if (pad > 0) {
|
|
362
|
-
/* Data after padding */
|
|
363
|
-
js_free(ctx, out);
|
|
364
|
-
JS_FreeCString(ctx, str);
|
|
365
|
-
return JS_ThrowSyntaxError(ctx,
|
|
366
|
-
"The string to be decoded is not correctly encoded");
|
|
367
|
-
}
|
|
368
|
-
uint8_t val = b64_decode_table[(uint8_t)ch];
|
|
369
|
-
if (val == 255) {
|
|
370
|
-
js_free(ctx, out);
|
|
371
|
-
JS_FreeCString(ctx, str);
|
|
372
|
-
return JS_ThrowSyntaxError(ctx,
|
|
373
|
-
"The string to be decoded is not correctly encoded");
|
|
374
|
-
}
|
|
375
|
-
accum = (accum << 6) | val;
|
|
376
|
-
bits += 6;
|
|
377
|
-
if (bits >= 8) {
|
|
378
|
-
bits -= 8;
|
|
379
|
-
out[j++] = (char)((accum >> bits) & 0xFF);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
450
|
+
int err = 0;
|
|
451
|
+
size_t j = b64_decode(str, len, out, &err);
|
|
383
452
|
JS_FreeCString(ctx, str);
|
|
453
|
+
if (err) {
|
|
454
|
+
js_free(ctx, out);
|
|
455
|
+
return JS_ThrowSyntaxError(ctx,
|
|
456
|
+
"The string to be decoded is not correctly encoded");
|
|
457
|
+
}
|
|
384
458
|
|
|
385
459
|
/* Convert decoded bytes to a JS string. Bytes 0x80-0xFF must become
|
|
386
460
|
* their corresponding Unicode code points (U+0080-U+00FF), which in
|
package/src/mikrojs.cpp
CHANGED
|
@@ -89,8 +89,9 @@ static void mik__add_exports(JSContext* ctx, JSModuleDef* m, const char* const*
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
static const char* const sys_exports[] = {
|
|
92
|
-
"evalScript", "memoryUsage", "jsMemoryUsage",
|
|
93
|
-
"restart",
|
|
92
|
+
"evalScript", "memoryUsage", "jsMemoryUsage", "gc", "setTime",
|
|
93
|
+
"uptime", "restart", "version", "board", "firmware",
|
|
94
|
+
"deviceId", "activeTimers", "unloadNamespace", "isUnloadableNamespace"};
|
|
94
95
|
|
|
95
96
|
static int mik__sys_module_init(JSContext* ctx, JSModuleDef* m) {
|
|
96
97
|
JSValue ns = JS_NewObjectProto(ctx, JS_NULL);
|
|
@@ -542,6 +543,14 @@ int MIK_Loop(MIKRuntime* mik_rt) {
|
|
|
542
543
|
if (mik_rt->restart_at_us > 0) {
|
|
543
544
|
const MIKPlatform* platform = MIK_GetPlatform();
|
|
544
545
|
if (platform->get_boot_us() >= mik_rt->restart_at_us) {
|
|
546
|
+
/* The grace window has elapsed; take the configured panic action.
|
|
547
|
+
* In deep-sleep mode the timer wake reboots the chip, so the wake
|
|
548
|
+
* IS the restart — and the live --recover window is forfeit by
|
|
549
|
+
* design (the CPU is suspended). If the platform has no deep-sleep
|
|
550
|
+
* hook (hosts), fall through to a plain restart. */
|
|
551
|
+
if (mik_rt->config.panic_mode == MIK_PANIC_DEEP_SLEEP && platform->deep_sleep_us) {
|
|
552
|
+
platform->deep_sleep_us((uint64_t)mik_rt->config.panic_sleep_duration_ms * 1000);
|
|
553
|
+
}
|
|
545
554
|
platform->restart();
|
|
546
555
|
}
|
|
547
556
|
return 0;
|
package/src/modules.cpp
CHANGED
|
@@ -200,7 +200,7 @@ static JSModuleDef* mik_module_load_from_fs(JSContext* ctx, const char* module_n
|
|
|
200
200
|
|
|
201
201
|
static JSModuleDef* mik_module_loader_inner(JSContext* ctx, const char* module_name,
|
|
202
202
|
void* opaque) {
|
|
203
|
-
static const char
|
|
203
|
+
static const char mikro_prefix[] = "mikro/";
|
|
204
204
|
|
|
205
205
|
/* Virtual modules take priority over builtins — allows host-side JS to
|
|
206
206
|
* override any native:* C module (e.g. mocking device modules for dev). */
|
|
@@ -239,7 +239,7 @@ static JSModuleDef* mik_module_loader_inner(JSContext* ctx, const char* module_n
|
|
|
239
239
|
return nullptr;
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
if (strncmp(
|
|
242
|
+
if (strncmp(mikro_prefix, module_name, strlen(mikro_prefix)) == 0) {
|
|
243
243
|
JSModuleDef* builtin_m = mik__load_builtin(ctx, module_name);
|
|
244
244
|
if (builtin_m) return builtin_m;
|
|
245
245
|
/* mik__load_builtin returns NULL either because the module isn't in
|
|
@@ -360,39 +360,6 @@ JSModuleDef* mik_module_loader(JSContext* ctx, const char* module_name, void* op
|
|
|
360
360
|
#define MIK__PATHSEP '/'
|
|
361
361
|
#define MIK__PATHSEP_STR "/"
|
|
362
362
|
|
|
363
|
-
/* import.meta.unload(specifier) implementation. Resolves `specifier` via
|
|
364
|
-
* the standard normalizer using the captured base path (the owning
|
|
365
|
-
* module's logical path, stripped of the `file://` prefix), evicts the
|
|
366
|
-
* resulting module + orphaned deps via mik__unload_module, then runs a
|
|
367
|
-
* GC pass so the caller's next memoryUsage() snapshot reflects the free. */
|
|
368
|
-
static JSValue mik__import_meta_unload(JSContext* ctx, JSValueConst this_val,
|
|
369
|
-
int argc, JSValueConst* argv,
|
|
370
|
-
int magic, JSValueConst* func_data) {
|
|
371
|
-
(void)magic;
|
|
372
|
-
(void)this_val;
|
|
373
|
-
if (argc < 1 || !JS_IsString(argv[0])) {
|
|
374
|
-
return JS_ThrowTypeError(ctx, "import.meta.unload: specifier must be a string");
|
|
375
|
-
}
|
|
376
|
-
const char* specifier = JS_ToCString(ctx, argv[0]);
|
|
377
|
-
if (!specifier) return JS_EXCEPTION;
|
|
378
|
-
const char* base = JS_ToCString(ctx, func_data[0]);
|
|
379
|
-
if (!base) {
|
|
380
|
-
JS_FreeCString(ctx, specifier);
|
|
381
|
-
return JS_EXCEPTION;
|
|
382
|
-
}
|
|
383
|
-
char* normalized = mik_module_normalizer(ctx, base, specifier, nullptr);
|
|
384
|
-
JS_FreeCString(ctx, specifier);
|
|
385
|
-
JS_FreeCString(ctx, base);
|
|
386
|
-
if (!normalized) return JS_EXCEPTION;
|
|
387
|
-
|
|
388
|
-
int rv = mik__unload_module(ctx, normalized);
|
|
389
|
-
js_free(ctx, normalized);
|
|
390
|
-
if (rv < 0) return JS_EXCEPTION;
|
|
391
|
-
|
|
392
|
-
JS_RunGC(JS_GetRuntime(ctx));
|
|
393
|
-
return JS_NewInt32(ctx, rv);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
363
|
int js_module_set_import_meta(JSContext* ctx, JSValue func_val, bool use_realpath, bool is_main) {
|
|
397
364
|
JSModuleDef* m;
|
|
398
365
|
char buf[PATH_MAX + 16] = {0};
|
|
@@ -454,16 +421,6 @@ int js_module_set_import_meta(JSContext* ctx, JSValue func_val, bool use_realpat
|
|
|
454
421
|
JS_PROP_C_W_E);
|
|
455
422
|
}
|
|
456
423
|
|
|
457
|
-
/* Attach import.meta.unload(specifier). Needs the owning module's
|
|
458
|
-
* logical path (not URL) as the normalizer base. Only exposed for
|
|
459
|
-
* file-backed modules; synthetic modules don't have a stable path. */
|
|
460
|
-
if (use_realpath) {
|
|
461
|
-
JSValue base_str = JS_NewString(ctx, buf + 7 /* strip "file://" */);
|
|
462
|
-
JSValue unload_fn = JS_NewCFunctionData(ctx, mik__import_meta_unload, 1, 0, 1, &base_str);
|
|
463
|
-
JS_FreeValue(ctx, base_str);
|
|
464
|
-
JS_DefinePropertyValueStr(ctx, meta_obj, "unload", unload_fn, JS_PROP_C_W_E);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
424
|
JS_FreeValue(ctx, meta_obj);
|
|
468
425
|
return 0;
|
|
469
426
|
}
|
|
@@ -698,11 +655,11 @@ static char* mik__resolve_node_modules(JSContext* ctx, const char* base_dir,
|
|
|
698
655
|
return NULL;
|
|
699
656
|
}
|
|
700
657
|
|
|
701
|
-
/* Anchored names (native:,
|
|
658
|
+
/* Anchored names (native:, mikro/, @mikrojs/) are never unloaded
|
|
702
659
|
* and don't contribute meaningfully to the import graph for orphan
|
|
703
660
|
* detection. We skip recording edges where the target is anchored. */
|
|
704
661
|
static bool mik__is_anchored_name(const char* name) {
|
|
705
|
-
return strncmp(name, "native:", 7) == 0 || strncmp(name, "
|
|
662
|
+
return strncmp(name, "native:", 7) == 0 || strncmp(name, "mikro/", 6) == 0 ||
|
|
706
663
|
strncmp(name, "@mikrojs/", 9) == 0;
|
|
707
664
|
}
|
|
708
665
|
|
|
@@ -726,8 +683,8 @@ static char* mik__module_normalizer_impl(JSContext* ctx, const char* base_name,
|
|
|
726
683
|
|
|
727
684
|
static const char internal_prefix[] = "native:";
|
|
728
685
|
if (strncmp(name, internal_prefix, strlen(internal_prefix)) == 0) {
|
|
729
|
-
// Only built-in modules (native:,
|
|
730
|
-
if (strncmp(base_name, "native:", 7) != 0 && strncmp(base_name, "
|
|
686
|
+
// Only built-in modules (native:, mikro/, @mikrojs/) may import native: internals
|
|
687
|
+
if (strncmp(base_name, "native:", 7) != 0 && strncmp(base_name, "mikro/", 6) != 0 &&
|
|
731
688
|
strncmp(base_name, "@mikrojs/", 9) != 0) {
|
|
732
689
|
JS_ThrowTypeError(ctx, "Failed to resolve module specifier '%s'", name);
|
|
733
690
|
return NULL;
|
|
@@ -736,7 +693,7 @@ static char* mik__module_normalizer_impl(JSContext* ctx, const char* base_name,
|
|
|
736
693
|
|
|
737
694
|
if (name[0] != '.' && name[0] != '/') {
|
|
738
695
|
/* Built-in modules pass through unchanged */
|
|
739
|
-
if (strncmp(name, "
|
|
696
|
+
if (strncmp(name, "mikro/", 6) == 0 || strncmp(name, "native:", 7) == 0) {
|
|
740
697
|
return js_strdup(ctx, name);
|
|
741
698
|
}
|
|
742
699
|
/* Check if this bare specifier matches an external builtin (board/driver
|
|
@@ -942,3 +899,32 @@ int mik__unload_module(JSContext* ctx, const char* normalized_name) {
|
|
|
942
899
|
|
|
943
900
|
return freed_count;
|
|
944
901
|
}
|
|
902
|
+
|
|
903
|
+
/* Resolve `ns` to the loaded module it is the namespace of, returning its
|
|
904
|
+
* normalized name as a C string (caller frees via JS_FreeCString), or NULL
|
|
905
|
+
* if `ns` is not an object or not a loaded module's namespace. */
|
|
906
|
+
static const char* mik__module_name_from_ns(JSContext* ctx, JSValueConst ns) {
|
|
907
|
+
if (!JS_IsObject(ns)) return nullptr;
|
|
908
|
+
JSModuleDef* m = JS_FindModuleByNamespace(ctx, ns);
|
|
909
|
+
if (!m) return nullptr;
|
|
910
|
+
JSAtom atom = JS_GetModuleName(ctx, m);
|
|
911
|
+
const char* name = JS_AtomToCString(ctx, atom);
|
|
912
|
+
JS_FreeAtom(ctx, atom);
|
|
913
|
+
return name;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
int mik__unload_namespace(JSContext* ctx, JSValueConst ns) {
|
|
917
|
+
const char* name = mik__module_name_from_ns(ctx, ns);
|
|
918
|
+
if (!name) return 0; /* not a loaded module's namespace: idempotent no-op */
|
|
919
|
+
int rv = mik__unload_module(ctx, name);
|
|
920
|
+
JS_FreeCString(ctx, name);
|
|
921
|
+
return rv;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
bool mik__is_unloadable_namespace(JSContext* ctx, JSValueConst ns) {
|
|
925
|
+
const char* name = mik__module_name_from_ns(ctx, ns);
|
|
926
|
+
if (!name) return false;
|
|
927
|
+
bool ok = !mik__is_anchored_name(name);
|
|
928
|
+
JS_FreeCString(ctx, name);
|
|
929
|
+
return ok;
|
|
930
|
+
}
|