@mikrojs/native 0.18.2 → 0.18.3-next.20260829153835

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,17 +2,153 @@ import type {Result} from '../result/types.js'
2
2
 
3
3
  type Primitive = string | number | boolean
4
4
 
5
- export interface StringSchema {
5
+ export type Format = 'url' | 'hostname' | 'ipv4' | 'mac' | 'email'
6
+
7
+ export type Unit =
8
+ | 'm'
9
+ | 'kg'
10
+ | 's'
11
+ | 'A'
12
+ | 'K'
13
+ | 'cd'
14
+ | 'mol'
15
+ | 'Hz'
16
+ | 'rad'
17
+ | 'sr'
18
+ | 'N'
19
+ | 'Pa'
20
+ | 'J'
21
+ | 'W'
22
+ | 'C'
23
+ | 'V'
24
+ | 'F'
25
+ | 'Ohm'
26
+ | 'S'
27
+ | 'Wb'
28
+ | 'T'
29
+ | 'H'
30
+ | 'Cel'
31
+ | 'lm'
32
+ | 'lx'
33
+ | 'Bq'
34
+ | 'Gy'
35
+ | 'Sv'
36
+ | 'kat'
37
+ | 'm2'
38
+ | 'm3'
39
+ | 'm/s'
40
+ | 'm/s2'
41
+ | 'm3/s'
42
+ | 'W/m2'
43
+ | 'cd/m2'
44
+ | 'bit'
45
+ | 'bit/s'
46
+ | 'lat'
47
+ | 'lon'
48
+ | 'pH'
49
+ | 'dB'
50
+ | 'dBW'
51
+ | 'count'
52
+ | '/'
53
+ | '%RH'
54
+ | '%EL'
55
+ | 'EL'
56
+ | '1/s'
57
+ | 'S/m'
58
+ | 'B'
59
+ | 'VA'
60
+ | 'VAs'
61
+ | 'var'
62
+ | 'vars'
63
+ | 'J/m'
64
+ | 'kg/m3'
65
+ | 'deg'
66
+ | 'NTU'
67
+ | 'ms'
68
+ | 'min'
69
+ | 'h'
70
+ | 'MHz'
71
+ | 'kW'
72
+ | 'kVA'
73
+ | 'kvar'
74
+ | 'Ah'
75
+ | 'Wh'
76
+ | 'kWh'
77
+ | 'varh'
78
+ | 'kvarh'
79
+ | 'kVAh'
80
+ | 'Wh/km'
81
+ | 'KiB'
82
+ | 'GB'
83
+ | 'Mbit/s'
84
+ | 'B/s'
85
+ | 'MB/s'
86
+ | 'mV'
87
+ | 'mA'
88
+ | 'dBm'
89
+ | 'ug/m3'
90
+ | 'mm/h'
91
+ | 'm/h'
92
+ | 'ppm'
93
+ | '/100'
94
+ | '/1000'
95
+ | 'hPa'
96
+ | 'mm'
97
+ | 'cm'
98
+ | 'km'
99
+ | 'km/h'
100
+ | 'ppb'
101
+ | 'ppt'
102
+ | 'VAh'
103
+ | 'mg/l'
104
+ | 'ug/l'
105
+ | 'g/l'
106
+ | 'us'
107
+ | 'kHz'
108
+ | 'GHz'
109
+ | 'mW'
110
+ | 'uA'
111
+ | 'uV'
112
+ | 'mAh'
113
+ | 'MiB'
114
+ | 'kB'
115
+ | 'MB'
116
+ | 'kbit/s'
117
+ | 'KiB/s'
118
+ | 'kohm'
119
+ | 'Mohm'
120
+ | 'kPa'
121
+ | 'bar'
122
+ | 'Bd'
123
+
124
+ /* Display annotations, carried by every node a form can render. They never
125
+ * change what validates. Not on optional(): the wrapper expresses absence, the
126
+ * node it wraps expresses identity, so annotations go on the inner. */
127
+ interface Annotated {
128
+ readonly title?: string
129
+ readonly description?: string
130
+ }
131
+
132
+ export interface StringSchema extends Annotated {
6
133
  readonly kind: 'string'
7
134
  readonly default?: string
135
+ readonly mask?: boolean
136
+ readonly minLength?: number
137
+ readonly maxLength?: number
138
+ readonly format?: Format
8
139
  }
9
140
 
10
- export interface NumberSchema {
141
+ export interface NumberSchema extends Annotated {
11
142
  readonly kind: 'number'
12
143
  readonly default?: number
144
+ readonly mask?: boolean
145
+ readonly min?: number
146
+ readonly max?: number
147
+ readonly integer?: boolean
148
+ readonly unit?: Unit
13
149
  }
14
150
 
15
- export interface BooleanSchema {
151
+ export interface BooleanSchema extends Annotated {
16
152
  readonly kind: 'boolean'
17
153
  readonly default?: boolean
18
154
  }
@@ -21,19 +157,23 @@ export interface UnknownSchema {
21
157
  readonly kind: 'unknown'
22
158
  }
23
159
 
24
- export interface LiteralSchema<T extends Primitive = Primitive> {
160
+ export interface LiteralSchema<T extends Primitive = Primitive> extends Annotated {
25
161
  readonly kind: 'literal'
26
162
  readonly value: T
27
163
  readonly default?: T
28
164
  }
29
165
 
30
- export interface ArraySchema<S extends Schema = Schema> {
166
+ export interface ArraySchema<S extends Schema = Schema> extends Annotated {
31
167
  readonly kind: 'array'
32
168
  readonly element: S
33
169
  readonly default?: unknown
170
+ readonly minItems?: number
171
+ readonly maxItems?: number
34
172
  }
35
173
 
36
- export interface ObjectSchema<Shape extends Record<string, Schema> = Record<string, Schema>> {
174
+ export interface ObjectSchema<
175
+ Shape extends Record<string, Schema> = Record<string, Schema>,
176
+ > extends Annotated {
37
177
  readonly kind: 'object'
38
178
  readonly shape: Shape
39
179
  }
@@ -43,13 +183,17 @@ export interface OptionalSchema<S extends Schema = Schema> {
43
183
  readonly inner: S
44
184
  }
45
185
 
46
- export interface TupleSchema<Elements extends readonly Schema[] = readonly Schema[]> {
186
+ export interface TupleSchema<
187
+ Elements extends readonly Schema[] = readonly Schema[],
188
+ > extends Annotated {
47
189
  readonly kind: 'tuple'
48
190
  readonly elements: Elements
49
191
  readonly default?: unknown
50
192
  }
51
193
 
52
- export interface UnionSchema<Members extends readonly Schema[] = readonly Schema[]> {
194
+ export interface UnionSchema<
195
+ Members extends readonly Schema[] = readonly Schema[],
196
+ > extends Annotated {
53
197
  readonly kind: 'union'
54
198
  readonly members: Members
55
199
  readonly default?: unknown
@@ -58,7 +202,7 @@ export interface UnionSchema<Members extends readonly Schema[] = readonly Schema
58
202
  export interface TaggedUnionSchema<
59
203
  Key extends string = string,
60
204
  Branches extends Record<string, ObjectSchema> = Record<string, ObjectSchema>,
61
- > {
205
+ > extends Annotated {
62
206
  readonly kind: 'taggedUnion'
63
207
  readonly key: Key
64
208
  readonly branches: Branches
@@ -162,22 +306,52 @@ type InferReadObject<Shape> = {
162
306
 
163
307
  export type SchemaError = {name: 'ValidationFailed'; message: string; path: string}
164
308
 
165
- export interface ScalarOptions<T> {
309
+ export interface DisplayOptions {
310
+ readonly title?: string
311
+ readonly description?: string
312
+ }
313
+ export interface ScalarOptions<T> extends DisplayOptions {
166
314
  readonly default?: T
167
315
  }
168
- export interface DefaultOption<T> {
316
+ export interface DefaultOption<T> extends DisplayOptions {
169
317
  readonly default?: T
170
318
  }
319
+ export interface MaskableOptions<T> extends ScalarOptions<T> {
320
+ readonly mask?: boolean
321
+ }
322
+ export interface StringOptions<T> extends MaskableOptions<T> {
323
+ readonly minLength?: number
324
+ readonly maxLength?: number
325
+ readonly format?: Format
326
+ }
327
+ export interface NumberOptions<T> extends MaskableOptions<T> {
328
+ readonly min?: number
329
+ readonly max?: number
330
+ readonly integer?: boolean
331
+ readonly unit?: Unit
332
+ }
333
+ export interface ArrayOptions<T> extends DefaultOption<T> {
334
+ readonly minItems?: number
335
+ readonly maxItems?: number
336
+ }
337
+ export interface EnumEntry<T extends Primitive> {
338
+ readonly value: T
339
+ readonly title?: string
340
+ readonly description?: string
341
+ }
342
+ type EnumMembers<Entries extends readonly EnumEntry<Primitive>[]> = {
343
+ [K in keyof Entries]: LiteralSchema<Entries[K]['value']>
344
+ }
171
345
 
172
346
  /* Mirrors core.ts: the constructors record a `default` annotation in their
173
347
  * return type, which is what lets InferRead see it. */
174
348
  type Defaulted<S, D> = [D] extends [undefined] ? S : S & {readonly default: unknown}
175
349
 
176
350
  export declare function string<D extends string | undefined = undefined>(
177
- options?: ScalarOptions<D>,
351
+ options?: StringOptions<D>,
178
352
  ): Defaulted<StringSchema, D>
179
353
  export declare function number<D extends number | undefined = undefined>(
180
- options?: ScalarOptions<D>,
354
+ options?: NumberOptions<D>,
181
355
  ): Defaulted<NumberSchema, D>
182
356
  export declare function boolean<D extends boolean | undefined = undefined>(
183
357
  options?: ScalarOptions<D>,
@@ -190,7 +364,7 @@ export declare function literal<T extends Primitive, D extends T | undefined = u
190
364
  export declare function array<
191
365
  S extends Schema,
192
366
  D extends NoInfer<Infer<S>>[] | undefined = undefined,
193
- >(element: S, options?: DefaultOption<D>): Defaulted<ArraySchema<S>, D>
367
+ >(element: S, options?: ArrayOptions<D>): Defaulted<ArraySchema<S>, D>
194
368
  export declare function object<Shape extends Record<string, Schema>>(
195
369
  shape: Shape,
196
370
  options?: DefaultOption<never>,
@@ -204,6 +378,10 @@ export declare function union<
204
378
  Members extends readonly Schema[],
205
379
  D extends NoInfer<Infer<UnionSchema<Members>>> | undefined = undefined,
206
380
  >(members: [...Members], options?: DefaultOption<D>): Defaulted<UnionSchema<Members>, D>
381
+ export declare function enumOf<
382
+ const Entries extends readonly EnumEntry<Primitive>[],
383
+ D extends Entries[number]['value'] | undefined = undefined,
384
+ >(entries: Entries, options?: DefaultOption<D>): Defaulted<UnionSchema<EnumMembers<Entries>>, D>
207
385
  export declare function taggedUnion<
208
386
  Key extends string,
209
387
  Branches extends Record<string, ObjectSchema>,
@@ -1,7 +1,12 @@
1
1
  import type {Result} from '../result/types.js'
2
2
 
3
3
  export interface MemoryUsage {
4
+ /** Bytes allocated from the JS heap. On device this counts real
5
+ * allocator block sizes (requested bytes rounded up to the heap's
6
+ * granularity), so it reflects what the heap actually spends; small
7
+ * run-to-run variation is normal. */
4
8
  heapUsed: number
9
+ /** The JS heap's configured memory limit in bytes. */
5
10
  heapTotal: number
6
11
  /** Free system heap in bytes (0 on host) */
7
12
  systemFree: number
@@ -87,6 +92,11 @@ export declare function gc(): void
87
92
  /** Firmware version string (e.g. "0.1.0") */
88
93
  export declare const version: string
89
94
 
95
+ /** Values {@link BoardInfo.features} can contain. `wifi`, `ble` and `bt` need
96
+ * both the silicon and the matching stack compiled into the firmware;
97
+ * `ieee802154` reports silicon alone. */
98
+ export type BoardFeature = 'wifi' | 'ble' | 'bt' | 'ieee802154'
99
+
90
100
  export interface BoardInfo {
91
101
  /** Board name from the firmware build (e.g. "xiao-esp32c6") or "generic" */
92
102
  name: string
@@ -97,7 +107,7 @@ export interface BoardInfo {
97
107
  /** Silicon revision (major * 100 + minor on ESP32, 0 on host) */
98
108
  revision: number
99
109
  /** Supported features (e.g. ["wifi", "ble"]) */
100
- features: string[]
110
+ features: BoardFeature[]
101
111
  /** Flash size in bytes (0 on host) */
102
112
  flash: number
103
113
  /** PSRAM size in bytes (0 if unavailable) */
package/src/mem.cpp CHANGED
@@ -9,15 +9,56 @@
9
9
 
10
10
  /*
11
11
  * QuickJS uses malloc_usable_size to track memory consumption against its
12
- * memory limit. ESP-IDF lacks a standard malloc_usable_size, and the
13
- * heap_caps_get_allocated_size alternative reports block sizes including
14
- * allocator overhead, causing QuickJS to over-count usage.
12
+ * memory limit. When the platform reports live-allocation sizes
13
+ * (malloc_usable_size hook: libc malloc_usable_size / malloc_size,
14
+ * heap_caps_get_allocated_size on ESP-IDF), allocations are returned as-is
15
+ * and QuickJS accounts blocks by their real (rounded-up) size.
15
16
  *
16
- * We prepend each allocation with a size_t header storing the requested size.
17
+ * Without the hook, each allocation is prepended with a size_t header
18
+ * storing the requested size. That costs one word of RAM per live block —
19
+ * ~13 KB across a loaded app's ~3400 blocks on a 32-bit device — which is
20
+ * why the hook path is preferred.
21
+ *
22
+ * The mode is latched at the first allocation and never changes, so every
23
+ * pointer is freed the same way it was allocated. Platforms install before
24
+ * the first runtime exists (ESP32 and node have theirs as the compile-time
25
+ * default), so the latch sees the final platform. The latch is a single
26
+ * word-sized store (null = unlatched, mik__hdr_usable_size = header mode),
27
+ * so concurrent first allocations on a dual-core target can at worst both
28
+ * compute and store the same value — there is no two-variable ordering to
29
+ * observe half-done.
17
30
  */
18
31
 
19
32
  #define HDR_SIZE sizeof(size_t)
20
33
 
34
+ static inline size_t hdr_read(void* user) {
35
+ size_t sz;
36
+ memcpy(&sz, static_cast<char*>(user) - HDR_SIZE, sizeof(sz));
37
+ return sz;
38
+ }
39
+
40
+ /* Header-mode usable_size; doubles as the latch marker for that mode. */
41
+ static size_t mik__hdr_usable_size(const void* ptr) {
42
+ return hdr_read(const_cast<void*>(ptr));
43
+ }
44
+
45
+ typedef size_t (*MIKUsableSizeFn)(const void*);
46
+ static MIKUsableSizeFn g_usable_size_fn = nullptr; /* null = not latched yet */
47
+
48
+ static inline MIKUsableSizeFn usable_size_fn(void) {
49
+ MIKUsableSizeFn fn = g_usable_size_fn;
50
+ if (!fn) {
51
+ const MIKPlatform* p = MIK_GetPlatform();
52
+ fn = (p && p->malloc_usable_size) ? p->malloc_usable_size : mik__hdr_usable_size;
53
+ g_usable_size_fn = fn;
54
+ }
55
+ return fn;
56
+ }
57
+
58
+ static inline size_t hdr_size(void) {
59
+ return usable_size_fn() == mik__hdr_usable_size ? HDR_SIZE : 0;
60
+ }
61
+
21
62
  /* Fault injection for the host OOM tests: fail the JS-heap allocators after
22
63
  * a countdown, and keep a net live-allocation counter across every allocator
23
64
  * here (all of them free through mik__free, so one counter balances). */
@@ -39,6 +80,11 @@ static bool oom_inject_should_fail(void) {
39
80
  g_oom_countdown--;
40
81
  return false;
41
82
  }
83
+
84
+ void mik__usable_size_latch_override(size_t (*fn)(const void*)) {
85
+ g_usable_size_fn = fn ? fn : mik__hdr_usable_size;
86
+ }
87
+
42
88
  #define OOM_INJECT_FAIL() oom_inject_should_fail()
43
89
  #define LIVE_ALLOCS_ADD(d) (g_live_allocs += (d))
44
90
  #else
@@ -46,27 +92,22 @@ static bool oom_inject_should_fail(void) {
46
92
  #define LIVE_ALLOCS_ADD(d) ((void)0)
47
93
  #endif
48
94
 
49
- static inline size_t hdr_read(void* user) {
50
- size_t sz;
51
- memcpy(&sz, static_cast<char*>(user) - HDR_SIZE, sizeof(sz));
52
- return sz;
53
- }
54
-
55
- static inline void hdr_write(void* raw, size_t sz) {
56
- memcpy(raw, &sz, sizeof(sz));
95
+ static inline void hdr_write(void* raw, size_t size, size_t hdr) {
96
+ if (hdr) memcpy(raw, &size, sizeof(size));
57
97
  }
58
98
 
59
99
  size_t mik__malloc_usable_size(const void* ptr) {
60
100
  if (!ptr) return 0;
61
- return hdr_read(const_cast<void*>(ptr));
101
+ return usable_size_fn()(ptr);
62
102
  }
63
103
 
64
104
  void* mik__malloc(size_t size) {
65
- void* raw = malloc(size + HDR_SIZE);
105
+ size_t hdr = hdr_size();
106
+ void* raw = malloc(size + hdr);
66
107
  if (!raw) return nullptr;
67
- hdr_write(raw, size);
108
+ hdr_write(raw, size, hdr);
68
109
  LIVE_ALLOCS_ADD(1);
69
- return static_cast<char*>(raw) + HDR_SIZE;
110
+ return static_cast<char*>(raw) + hdr;
70
111
  }
71
112
 
72
113
  void* mik__mallocz(size_t size) { return mik__calloc(1, size); }
@@ -74,26 +115,28 @@ void* mik__mallocz(size_t size) { return mik__calloc(1, size); }
74
115
  void* mik__calloc(size_t count, size_t size) {
75
116
  if (size && count > SIZE_MAX / size) return nullptr;
76
117
  size_t total = count * size;
77
- void* raw = calloc(1, total + HDR_SIZE);
118
+ size_t hdr = hdr_size();
119
+ void* raw = calloc(1, total + hdr);
78
120
  if (!raw) return nullptr;
79
- hdr_write(raw, total);
121
+ hdr_write(raw, total, hdr);
80
122
  LIVE_ALLOCS_ADD(1);
81
- return static_cast<char*>(raw) + HDR_SIZE;
123
+ return static_cast<char*>(raw) + hdr;
82
124
  }
83
125
 
84
126
  void mik__free(void* ptr) {
85
127
  if (!ptr) return;
86
128
  LIVE_ALLOCS_ADD(-1);
87
- free(static_cast<char*>(ptr) - HDR_SIZE);
129
+ free(static_cast<char*>(ptr) - hdr_size());
88
130
  }
89
131
 
90
132
  void* mik__realloc(void* ptr, size_t size) {
91
- void* raw = ptr ? static_cast<char*>(ptr) - HDR_SIZE : nullptr;
92
- raw = realloc(raw, size + HDR_SIZE);
133
+ size_t hdr = hdr_size();
134
+ void* raw = ptr ? static_cast<char*>(ptr) - hdr : nullptr;
135
+ raw = realloc(raw, size + hdr);
93
136
  if (!raw) return nullptr;
94
- hdr_write(raw, size);
137
+ hdr_write(raw, size, hdr);
95
138
  if (!ptr) LIVE_ALLOCS_ADD(1);
96
- return static_cast<char*>(raw) + HDR_SIZE;
139
+ return static_cast<char*>(raw) + hdr;
97
140
  }
98
141
 
99
142
  /* QuickJS-heap allocator. When the PSRAM flag is set, route through the
@@ -115,51 +158,54 @@ bool mik__is_quickjs_heap_psram(void) {
115
158
 
116
159
  void* mik__js_malloc(size_t size) {
117
160
  if (OOM_INJECT_FAIL()) return nullptr;
161
+ size_t hdr = hdr_size();
118
162
  void* raw = nullptr;
119
163
  if (g_quickjs_heap_psram) {
120
164
  const MIKPlatform* p = MIK_GetPlatform();
121
165
  if (p && p->malloc_psram) {
122
- raw = p->malloc_psram(size + HDR_SIZE);
166
+ raw = p->malloc_psram(size + hdr);
123
167
  }
124
168
  }
125
- if (!raw) raw = malloc(size + HDR_SIZE);
169
+ if (!raw) raw = malloc(size + hdr);
126
170
  if (!raw) return nullptr;
127
- hdr_write(raw, size);
171
+ hdr_write(raw, size, hdr);
128
172
  LIVE_ALLOCS_ADD(1);
129
- return static_cast<char*>(raw) + HDR_SIZE;
173
+ return static_cast<char*>(raw) + hdr;
130
174
  }
131
175
 
132
176
  void* mik__js_calloc(size_t count, size_t size) {
133
177
  if (OOM_INJECT_FAIL()) return nullptr;
134
178
  if (size && count > SIZE_MAX / size) return nullptr;
135
179
  size_t total = count * size;
180
+ size_t hdr = hdr_size();
136
181
  void* raw = nullptr;
137
182
  if (g_quickjs_heap_psram) {
138
183
  const MIKPlatform* p = MIK_GetPlatform();
139
184
  if (p && p->calloc_psram) {
140
- raw = p->calloc_psram(1, total + HDR_SIZE);
185
+ raw = p->calloc_psram(1, total + hdr);
141
186
  }
142
187
  }
143
- if (!raw) raw = calloc(1, total + HDR_SIZE);
188
+ if (!raw) raw = calloc(1, total + hdr);
144
189
  if (!raw) return nullptr;
145
- hdr_write(raw, total);
190
+ hdr_write(raw, total, hdr);
146
191
  LIVE_ALLOCS_ADD(1);
147
- return static_cast<char*>(raw) + HDR_SIZE;
192
+ return static_cast<char*>(raw) + hdr;
148
193
  }
149
194
 
150
195
  void* mik__js_realloc(void* ptr, size_t size) {
151
196
  if (OOM_INJECT_FAIL()) return nullptr;
152
- void* raw = ptr ? static_cast<char*>(ptr) - HDR_SIZE : nullptr;
197
+ size_t hdr = hdr_size();
198
+ void* raw = ptr ? static_cast<char*>(ptr) - hdr : nullptr;
153
199
  void* new_raw = nullptr;
154
200
  if (g_quickjs_heap_psram) {
155
201
  const MIKPlatform* p = MIK_GetPlatform();
156
202
  if (p && p->realloc_psram) {
157
- new_raw = p->realloc_psram(raw, size + HDR_SIZE);
203
+ new_raw = p->realloc_psram(raw, size + hdr);
158
204
  }
159
205
  }
160
- if (!new_raw) new_raw = realloc(raw, size + HDR_SIZE);
206
+ if (!new_raw) new_raw = realloc(raw, size + hdr);
161
207
  if (!new_raw) return nullptr;
162
- hdr_write(new_raw, size);
208
+ hdr_write(new_raw, size, hdr);
163
209
  if (!ptr) LIVE_ALLOCS_ADD(1);
164
- return static_cast<char*>(new_raw) + HDR_SIZE;
210
+ return static_cast<char*>(new_raw) + hdr;
165
211
  }
@@ -384,10 +384,9 @@ void MIKOtaClient::Check(const MIKOtaCheckOptions& options, MIKOtaCheckSink sink
384
384
  * config — the floor is what bounds the damage a mistyped document can do. */
385
385
  constexpr uint32_t kMinCheckinIntervalMs = 30000;
386
386
 
387
- void MIKOtaClient::Watch(const MIKOtaWatchOptions& options) {
387
+ bool MIKOtaClient::Watch(const MIKOtaWatchOptions& options) {
388
388
  if (!Enrollment(&watch_registry_url_, &watch_bearer_)) {
389
- Log(MIK_LOG_INFO, "ota: device not enrolled; run `mikro ota enroll` to enable OTA updates");
390
- return;
389
+ return false;
391
390
  }
392
391
  watch_options_ = options;
393
392
  if (watch_options_.checkin_interval_ms < kMinCheckinIntervalMs) {
@@ -402,6 +401,7 @@ void MIKOtaClient::Watch(const MIKOtaWatchOptions& options) {
402
401
  wait_started_ms_ = Now();
403
402
  deadline_ms_ = wait_started_ms_ + scheduled_delay_ms_;
404
403
  pending_wait_ = PendingWait::kInitial;
404
+ return true;
405
405
  }
406
406
 
407
407
  void MIKOtaClient::SetCheckinInterval(uint32_t interval_ms) {
package/src/mik_sys.cpp CHANGED
@@ -187,15 +187,23 @@ static JSValue mik__sys_board(JSContext* ctx) {
187
187
  JS_SetPropertyStr(ctx, obj, "cores", JS_NewInt32(ctx, chip_info.cores));
188
188
  JS_SetPropertyStr(ctx, obj, "revision", JS_NewInt32(ctx, chip_info.revision));
189
189
 
190
- /* Features as string array */
190
+ /* Features as string array. Callers use this to decide whether an
191
+ * optional module will work, so a radio only counts when the silicon
192
+ * has it AND the stack behind it was compiled in. */
191
193
  JSValue features = JS_NewArray(ctx);
192
194
  uint32_t fi = 0;
195
+ #ifdef CONFIG_ESP_WIFI_ENABLED
193
196
  if (chip_info.features & CHIP_FEATURE_WIFI_BGN)
194
197
  JS_SetPropertyUint32(ctx, features, fi++, JS_NewString(ctx, "wifi"));
198
+ #endif
199
+ #ifdef CONFIG_BT_ENABLED
195
200
  if (chip_info.features & CHIP_FEATURE_BLE)
196
201
  JS_SetPropertyUint32(ctx, features, fi++, JS_NewString(ctx, "ble"));
197
202
  if (chip_info.features & CHIP_FEATURE_BT)
198
203
  JS_SetPropertyUint32(ctx, features, fi++, JS_NewString(ctx, "bt"));
204
+ #endif
205
+ /* No stack to gate on: mikrojs has no 802.15.4 API, so this stays a
206
+ * plain silicon report. */
199
207
  if (chip_info.features & CHIP_FEATURE_IEEE802154)
200
208
  JS_SetPropertyUint32(ctx, features, fi++, JS_NewString(ctx, "ieee802154"));
201
209
  JS_SetPropertyStr(ctx, obj, "features", features);