@mikrojs/native 0.6.0-pr-70.gd2fdc0d → 0.6.0-pr-72.g464c29c
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 +1 -3
- package/include/mikrojs/private.h +0 -3
- package/package.json +2 -4
- package/prebuilds/darwin-arm64/mikrojs.napi.node +0 -0
- package/prebuilds/linux-arm64/mikrojs.napi.node +0 -0
- package/prebuilds/linux-x64/mikrojs.napi.node +0 -0
- package/runtime/ble/ble.ts +8 -33
- package/runtime/ble/types.ts +14 -7
- package/runtime/internal.d.ts +0 -4
- package/runtime/udp/types.ts +2 -15
- package/runtime/udp/udp.ts +9 -45
- package/runtime/wifi/types.ts +15 -6
- package/runtime/wifi/wifi.ts +16 -38
- package/src/mikrojs.cpp +0 -1
- package/runtime/observable/native-observable.node-shim.ts +0 -220
- package/runtime/observable/observable.ts +0 -1
- package/runtime/observable/operators.ts +0 -140
- package/runtime/observable/types.ts +0 -80
- package/src/mik_observable.cpp +0 -950
package/CMakeLists.txt
CHANGED
|
@@ -50,7 +50,6 @@ set(MIKROJS_CORE_SOURCES
|
|
|
50
50
|
src/mik_repl.cpp
|
|
51
51
|
src/mik_app_config.cpp
|
|
52
52
|
src/mik_udp.cpp
|
|
53
|
-
src/mik_observable.cpp
|
|
54
53
|
)
|
|
55
54
|
|
|
56
55
|
add_library(mikrojs STATIC
|
|
@@ -81,7 +80,7 @@ endif()
|
|
|
81
80
|
include(cmake/mikrojs_bytecode.cmake)
|
|
82
81
|
mikrojs_generate_bytecode(
|
|
83
82
|
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
|
|
83
|
+
MODULES cbor env result schema fs http/helpers http/request i2c kv/nvs kv/rtc kv/shared neopixel pin pwm reader sleep spi sntp stdio stream sys test uart udp wifi
|
|
85
84
|
MODULE_PREFIX "mikrojs"
|
|
86
85
|
SYMBOL_PREFIX "mikrojs"
|
|
87
86
|
TARGET gen_bytecode
|
|
@@ -162,7 +161,6 @@ if(BUILD_TESTING)
|
|
|
162
161
|
test/stream_test.cpp
|
|
163
162
|
test/runtime_recycle_test.cpp
|
|
164
163
|
test/udp_test.cpp
|
|
165
|
-
test/observable_test.cpp
|
|
166
164
|
)
|
|
167
165
|
|
|
168
166
|
target_link_libraries(mikrojs_tests PRIVATE mikrojs)
|
|
@@ -215,9 +215,6 @@ JSModuleDef* mik__result_init(JSContext* ctx);
|
|
|
215
215
|
/* UDP module (mik_udp.cpp) */
|
|
216
216
|
JSModuleDef* mik__udp_init(JSContext* ctx);
|
|
217
217
|
|
|
218
|
-
/* Observable module (mik_observable.cpp) */
|
|
219
|
-
JSModuleDef* mik__observable_init(JSContext* ctx);
|
|
220
|
-
|
|
221
218
|
bool mik__repl_is_evaluating(void);
|
|
222
219
|
|
|
223
220
|
/* REPL protocol mode (mik_repl.cpp) — used by mik_console.cpp, mik_stdio.cpp */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikrojs/native",
|
|
3
|
-
"version": "0.6.0-pr-
|
|
3
|
+
"version": "0.6.0-pr-72.g464c29c",
|
|
4
4
|
"description": "Mikro.js C++ runtime library and Node.js native addon",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"esp32",
|
|
@@ -56,8 +56,6 @@
|
|
|
56
56
|
"./runtime/kv/shared": "./runtime/kv/shared.ts",
|
|
57
57
|
"./runtime/kv/types": "./runtime/kv/types.ts",
|
|
58
58
|
"./runtime/neopixel/types": "./runtime/neopixel/types.ts",
|
|
59
|
-
"./runtime/observable/operators": "./runtime/observable/operators.ts",
|
|
60
|
-
"./runtime/observable/types": "./runtime/observable/types.ts",
|
|
61
59
|
"./runtime/pin/types": "./runtime/pin/types.ts",
|
|
62
60
|
"./runtime/pwm/types": "./runtime/pwm/types.ts",
|
|
63
61
|
"./runtime/reader/types": "./runtime/reader/types.ts",
|
|
@@ -78,7 +76,7 @@
|
|
|
78
76
|
"cmake-js": "^8.0.0",
|
|
79
77
|
"node-addon-api": "^8.7.0",
|
|
80
78
|
"node-gyp-build": "^4.8.4",
|
|
81
|
-
"@mikrojs/quickjs": "0.6.0-pr-
|
|
79
|
+
"@mikrojs/quickjs": "0.6.0-pr-72.g464c29c"
|
|
82
80
|
},
|
|
83
81
|
"devDependencies": {
|
|
84
82
|
"@swc/core": "^1.15.30",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/runtime/ble/ble.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import {Observable} from 'mikrojs/observable'
|
|
2
1
|
import {err, ok} from 'mikrojs/result'
|
|
3
2
|
import {Ble as NativeBle} from 'native:ble'
|
|
4
3
|
|
|
5
|
-
import type {Subscriber} from '../observable/types.js'
|
|
6
4
|
import type {Result} from '../result/types.js'
|
|
7
5
|
import type {
|
|
8
6
|
AdvertiseHandle,
|
|
@@ -11,9 +9,8 @@ import type {
|
|
|
11
9
|
BleError,
|
|
12
10
|
Characteristic,
|
|
13
11
|
CharacteristicProperty,
|
|
14
|
-
ConnectionInfo,
|
|
15
|
-
MtuInfo,
|
|
16
12
|
Peripheral,
|
|
13
|
+
PeripheralEventMap,
|
|
17
14
|
Service,
|
|
18
15
|
} from './types.js'
|
|
19
16
|
import {parseUuid} from './uuid.js'
|
|
@@ -143,32 +140,6 @@ function normalizeServices(services: Service[]) {
|
|
|
143
140
|
|
|
144
141
|
const native = new NativeBle()
|
|
145
142
|
|
|
146
|
-
/* Lazy-attach: native.on runs only after the first JS subscriber and is
|
|
147
|
-
* removed when the last one leaves. Same pattern as wifi.ts; see the note
|
|
148
|
-
* there for the mbedTLS internal-RAM rationale. */
|
|
149
|
-
function lazyEvent<T>(eventName: string): Observable<T> {
|
|
150
|
-
const subscribers: Subscriber<T>[] = []
|
|
151
|
-
function handler(value: unknown): void {
|
|
152
|
-
const snapshot = subscribers.slice()
|
|
153
|
-
for (const s of snapshot) {
|
|
154
|
-
if (!s.closed) s.next(value as T)
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
return new Observable<T>((sub) => {
|
|
158
|
-
subscribers.push(sub)
|
|
159
|
-
if (subscribers.length === 1) {
|
|
160
|
-
native.on(eventName, handler)
|
|
161
|
-
}
|
|
162
|
-
sub.addTeardown(() => {
|
|
163
|
-
const i = subscribers.indexOf(sub)
|
|
164
|
-
if (i >= 0) subscribers.splice(i, 1)
|
|
165
|
-
if (subscribers.length === 0) {
|
|
166
|
-
native.off(eventName, handler)
|
|
167
|
-
}
|
|
168
|
-
})
|
|
169
|
-
})
|
|
170
|
-
}
|
|
171
|
-
|
|
172
143
|
const ble: Ble = {
|
|
173
144
|
get name(): string {
|
|
174
145
|
return native.getName()
|
|
@@ -248,9 +219,13 @@ const peripheral: Peripheral = {
|
|
|
248
219
|
return ok(handle)
|
|
249
220
|
},
|
|
250
221
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
222
|
+
on<K extends keyof PeripheralEventMap>(event: K, listener: PeripheralEventMap[K]) {
|
|
223
|
+
native.on(event, listener as (...args: unknown[]) => void)
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
off<K extends keyof PeripheralEventMap>(event: K, listener: PeripheralEventMap[K]) {
|
|
227
|
+
native.off(event, listener as (...args: unknown[]) => void)
|
|
228
|
+
},
|
|
254
229
|
}
|
|
255
230
|
|
|
256
231
|
export {ble, peripheral}
|
package/runtime/ble/types.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {Observable} from '../observable/types.js'
|
|
2
1
|
import type {Result} from '../result/types.js'
|
|
3
2
|
|
|
4
3
|
/** Advertising interval range in milliseconds. */
|
|
@@ -172,15 +171,23 @@ export interface MtuInfo {
|
|
|
172
171
|
mtu: number
|
|
173
172
|
}
|
|
174
173
|
|
|
174
|
+
export interface PeripheralEventMap {
|
|
175
|
+
connect: (info: ConnectionInfo) => void
|
|
176
|
+
disconnect: (info: ConnectionInfo) => void
|
|
177
|
+
mtu: (info: MtuInfo) => void
|
|
178
|
+
}
|
|
179
|
+
|
|
175
180
|
export interface Peripheral {
|
|
176
181
|
/** Start advertising. Returns a handle whose `stop()` ends this session. */
|
|
177
182
|
advertise(options?: AdvertiseOptions): Promise<Result<AdvertiseHandle, BleError>>
|
|
178
|
-
/**
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
183
|
+
/**
|
|
184
|
+
* Register a listener for a peripheral lifecycle event. Listeners are
|
|
185
|
+
* called on the JS loop thread in registration order. Safe to register
|
|
186
|
+
* before calling `advertise()`.
|
|
187
|
+
*/
|
|
188
|
+
on<K extends keyof PeripheralEventMap>(event: K, listener: PeripheralEventMap[K]): void
|
|
189
|
+
/** Remove a previously-registered listener. */
|
|
190
|
+
off<K extends keyof PeripheralEventMap>(event: K, listener: PeripheralEventMap[K]): void
|
|
184
191
|
}
|
|
185
192
|
|
|
186
193
|
export declare const ble: Ble
|
package/runtime/internal.d.ts
CHANGED
|
@@ -25,10 +25,6 @@ declare module 'native:result' {
|
|
|
25
25
|
export function err<E>(error: E): ErrResult<E>
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
declare module 'native:observable' {
|
|
29
|
-
export {Observable} from '@mikrojs/native/runtime/observable/types'
|
|
30
|
-
}
|
|
31
|
-
|
|
32
28
|
declare module 'native:sys' {
|
|
33
29
|
import type {JsMemoryUsage} from './sys/types.js'
|
|
34
30
|
export function evalScript(code: string): Promise<{value: unknown}>
|
package/runtime/udp/types.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {Observable} from '../observable/types.js'
|
|
2
1
|
import type {Result} from '../result/types.js'
|
|
3
2
|
|
|
4
3
|
export type UdpFamily = 'ipv4' | 'ipv6'
|
|
@@ -32,31 +31,19 @@ export type UdpError =
|
|
|
32
31
|
| {name: 'Closed'}
|
|
33
32
|
| {name: 'NotBound'}
|
|
34
33
|
|
|
35
|
-
/** Inbound datagram delivered via `socket.onMessage`. */
|
|
36
|
-
export interface UdpMessage {
|
|
37
|
-
msg: Uint8Array
|
|
38
|
-
from: PeerAddress
|
|
39
|
-
}
|
|
40
|
-
|
|
41
34
|
export interface UdpSocket {
|
|
42
35
|
readonly port: number
|
|
43
36
|
readonly family: UdpFamily | 'dual'
|
|
44
37
|
/**
|
|
45
38
|
* Counter incremented each time an inbound datagram is dropped:
|
|
46
39
|
* - the per-socket queue is full,
|
|
47
|
-
* - no
|
|
40
|
+
* - no `onMessage` handler is set when a packet arrives,
|
|
48
41
|
* - or the datagram is larger than the 1500-byte receive buffer
|
|
49
42
|
* (typical Ethernet MTU; covers CoAP / mDNS / SNTP / DNS).
|
|
50
43
|
*/
|
|
51
44
|
dropped: number
|
|
52
45
|
|
|
53
|
-
|
|
54
|
-
* Stream of inbound datagrams. Subscribing attaches the native dispatch
|
|
55
|
-
* once the first subscriber is added; the last `unsubscribe()` detaches
|
|
56
|
-
* it again. Multiple subscribers fan out from one native registration.
|
|
57
|
-
* Completes when `close()` is called.
|
|
58
|
-
*/
|
|
59
|
-
readonly onMessage: Observable<UdpMessage>
|
|
46
|
+
onMessage: ((msg: Uint8Array, from: PeerAddress) => void) | null
|
|
60
47
|
|
|
61
48
|
send(data: Uint8Array | string, to: PeerAddress): Promise<Result<void, UdpError>>
|
|
62
49
|
joinMulticastGroup(group: MulticastGroup): Result<void, UdpError>
|
package/runtime/udp/udp.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import {Observable} from 'mikrojs/observable'
|
|
2
1
|
import {ok} from 'mikrojs/result'
|
|
3
2
|
import {bind as nativeBind, type NativeUdpSocket} from 'native:udp'
|
|
4
3
|
|
|
5
|
-
import type {Subscriber} from '../observable/types.js'
|
|
6
4
|
import type {Result} from '../result/types.js'
|
|
7
|
-
import type {BindOptions, MulticastGroup,
|
|
5
|
+
import type {BindOptions, MulticastGroup, PeerAddress, UdpError, UdpSocket} from './types.js'
|
|
8
6
|
|
|
9
7
|
const utf8 = new TextEncoder()
|
|
10
8
|
|
|
@@ -13,37 +11,7 @@ function toBytes(data: Uint8Array | string): Uint8Array {
|
|
|
13
11
|
}
|
|
14
12
|
|
|
15
13
|
function makeSocket(handle: NativeUdpSocket): UdpSocket {
|
|
16
|
-
|
|
17
|
-
* JS subscriber is listening. With zero subscribers, the native side
|
|
18
|
-
* skips JS dispatch and increments the dropped counter for inbound
|
|
19
|
-
* packets — preserving the pre-Observable behavior of unset onMessage
|
|
20
|
-
* silently discarding traffic. */
|
|
21
|
-
const subscribers: Subscriber<UdpMessage>[] = []
|
|
22
|
-
let closed = false
|
|
23
|
-
|
|
24
|
-
const onMessage = new Observable<UdpMessage>((sub) => {
|
|
25
|
-
if (closed) {
|
|
26
|
-
sub.complete()
|
|
27
|
-
return
|
|
28
|
-
}
|
|
29
|
-
subscribers.push(sub)
|
|
30
|
-
if (subscribers.length === 1) {
|
|
31
|
-
handle.setOnMessage((msg, from) => {
|
|
32
|
-
/* Snapshot so unsubscribes during dispatch don't shift indices. */
|
|
33
|
-
const snapshot = subscribers.slice()
|
|
34
|
-
for (const s of snapshot) {
|
|
35
|
-
if (!s.closed) s.next({msg, from})
|
|
36
|
-
}
|
|
37
|
-
})
|
|
38
|
-
}
|
|
39
|
-
sub.addTeardown(() => {
|
|
40
|
-
const i = subscribers.indexOf(sub)
|
|
41
|
-
if (i >= 0) subscribers.splice(i, 1)
|
|
42
|
-
if (subscribers.length === 0 && !closed) {
|
|
43
|
-
handle.setOnMessage(null)
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
|
-
})
|
|
14
|
+
let onMessage: ((msg: Uint8Array, from: PeerAddress) => void) | null = null
|
|
47
15
|
|
|
48
16
|
const sock: UdpSocket = {
|
|
49
17
|
get port() {
|
|
@@ -58,7 +26,13 @@ function makeSocket(handle: NativeUdpSocket): UdpSocket {
|
|
|
58
26
|
set dropped(value: number) {
|
|
59
27
|
handle.dropped = value
|
|
60
28
|
},
|
|
61
|
-
onMessage
|
|
29
|
+
get onMessage() {
|
|
30
|
+
return onMessage
|
|
31
|
+
},
|
|
32
|
+
set onMessage(fn) {
|
|
33
|
+
onMessage = fn
|
|
34
|
+
handle.setOnMessage(fn)
|
|
35
|
+
},
|
|
62
36
|
send(data, to) {
|
|
63
37
|
return handle.send(toBytes(data), to)
|
|
64
38
|
},
|
|
@@ -69,16 +43,6 @@ function makeSocket(handle: NativeUdpSocket): UdpSocket {
|
|
|
69
43
|
return handle.leaveMulticastGroup(group.address)
|
|
70
44
|
},
|
|
71
45
|
close() {
|
|
72
|
-
if (closed) return
|
|
73
|
-
closed = true
|
|
74
|
-
handle.setOnMessage(null)
|
|
75
|
-
/* Complete any active subscriptions. Walk a snapshot in case a
|
|
76
|
-
* complete handler unsubscribes a sibling. */
|
|
77
|
-
const snapshot = subscribers.slice()
|
|
78
|
-
subscribers.length = 0
|
|
79
|
-
for (const s of snapshot) {
|
|
80
|
-
if (!s.closed) s.complete()
|
|
81
|
-
}
|
|
82
46
|
handle.close()
|
|
83
47
|
},
|
|
84
48
|
}
|
package/runtime/wifi/types.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {Observable} from '../observable/types.js'
|
|
2
1
|
import type {Result} from '../result/types.js'
|
|
3
2
|
|
|
4
3
|
export type WifiStatus =
|
|
@@ -88,6 +87,17 @@ export interface ApStartOptions {
|
|
|
88
87
|
maxConnections?: number
|
|
89
88
|
}
|
|
90
89
|
|
|
90
|
+
export interface WifiEventMap {
|
|
91
|
+
connect: (info: WifiConnectionInfo) => void
|
|
92
|
+
disconnect: (reason: WifiDisconnectReason) => void
|
|
93
|
+
'rssi-low': (rssi: number) => void
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface ApEventMap {
|
|
97
|
+
'station-connect': (info: ApStationInfo) => void
|
|
98
|
+
'station-disconnect': (info: ApStationInfo) => void
|
|
99
|
+
}
|
|
100
|
+
|
|
91
101
|
export type WifiError =
|
|
92
102
|
| {name: 'InitFailed'; message: string}
|
|
93
103
|
| {name: 'CountryNotSet'}
|
|
@@ -112,8 +122,8 @@ export interface WifiAp {
|
|
|
112
122
|
readonly stations: ApStationInfo[]
|
|
113
123
|
deauthStation(mac: string): Result<void, WifiError>
|
|
114
124
|
inactiveTimeout: number
|
|
115
|
-
|
|
116
|
-
|
|
125
|
+
on<K extends keyof ApEventMap>(event: K, listener: ApEventMap[K]): void
|
|
126
|
+
off<K extends keyof ApEventMap>(event: K, listener: ApEventMap[K]): void
|
|
117
127
|
}
|
|
118
128
|
|
|
119
129
|
export interface Wifi {
|
|
@@ -125,9 +135,8 @@ export interface Wifi {
|
|
|
125
135
|
isConnected: boolean
|
|
126
136
|
scan(opts?: ScanOptions): Promise<Result<ScanResult[], WifiError>>
|
|
127
137
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
readonly onRssiLow: Observable<number>
|
|
138
|
+
on<K extends keyof WifiEventMap>(event: K, listener: WifiEventMap[K]): void
|
|
139
|
+
off<K extends keyof WifiEventMap>(event: K, listener: WifiEventMap[K]): void
|
|
131
140
|
|
|
132
141
|
readonly mac: string
|
|
133
142
|
hostname: string | undefined
|
package/runtime/wifi/wifi.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {Observable} from 'mikrojs/observable'
|
|
2
1
|
import {err, ok} from 'mikrojs/result'
|
|
3
2
|
import {Wifi as NativeWifi} from 'native:wifi'
|
|
4
3
|
|
|
5
|
-
import type {Subscriber} from '../observable/types.js'
|
|
6
4
|
import type {Result} from '../result/types.js'
|
|
7
5
|
import type {
|
|
6
|
+
ApEventMap,
|
|
8
7
|
ApStartOptions,
|
|
9
8
|
ApStationInfo,
|
|
10
9
|
IpConfig,
|
|
@@ -16,8 +15,8 @@ import type {
|
|
|
16
15
|
WifiAp,
|
|
17
16
|
WifiConnectionInfo,
|
|
18
17
|
WifiCountryCode,
|
|
19
|
-
WifiDisconnectReason,
|
|
20
18
|
WifiError,
|
|
19
|
+
WifiEventMap,
|
|
21
20
|
WifiStatus,
|
|
22
21
|
} from './types.js'
|
|
23
22
|
|
|
@@ -38,36 +37,6 @@ const StatusFromCode = new Map<number, WifiStatus>(
|
|
|
38
37
|
|
|
39
38
|
const native = new NativeWifi()
|
|
40
39
|
|
|
41
|
-
/* Lazy-attach Observable: native.on is only called once a JS subscriber
|
|
42
|
-
* appears, and native.off runs when the last one unsubscribes. Code paths
|
|
43
|
-
* that import wifi but don't subscribe (e.g. a fetch-only flow) take the
|
|
44
|
-
* exact same internal-RAM path as before observables existed — important
|
|
45
|
-
* because mbedTLS handshake needs ~16 KB contiguous internal SRAM and
|
|
46
|
-
* each eager closure pinned at module load chips into that headroom. */
|
|
47
|
-
function lazyEvent<T>(eventName: string): Observable<T> {
|
|
48
|
-
const subscribers: Subscriber<T>[] = []
|
|
49
|
-
function handler(value: unknown): void {
|
|
50
|
-
/* Snapshot so unsubscribes during dispatch don't shift indices. */
|
|
51
|
-
const snapshot = subscribers.slice()
|
|
52
|
-
for (const s of snapshot) {
|
|
53
|
-
if (!s.closed) s.next(value as T)
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return new Observable<T>((sub) => {
|
|
57
|
-
subscribers.push(sub)
|
|
58
|
-
if (subscribers.length === 1) {
|
|
59
|
-
native.on(eventName, handler)
|
|
60
|
-
}
|
|
61
|
-
sub.addTeardown(() => {
|
|
62
|
-
const i = subscribers.indexOf(sub)
|
|
63
|
-
if (i >= 0) subscribers.splice(i, 1)
|
|
64
|
-
if (subscribers.length === 0) {
|
|
65
|
-
native.off(eventName, handler)
|
|
66
|
-
}
|
|
67
|
-
})
|
|
68
|
-
})
|
|
69
|
-
}
|
|
70
|
-
|
|
71
40
|
const ap: WifiAp = {
|
|
72
41
|
start(options: ApStartOptions): Result<void, WifiError> {
|
|
73
42
|
return native.apStart(options as Parameters<typeof native.apStart>[0])
|
|
@@ -102,8 +71,13 @@ const ap: WifiAp = {
|
|
|
102
71
|
native.apSetInactiveTimeout(seconds)
|
|
103
72
|
},
|
|
104
73
|
|
|
105
|
-
|
|
106
|
-
|
|
74
|
+
on<K extends keyof ApEventMap>(event: K, listener: ApEventMap[K]) {
|
|
75
|
+
native.on(event, listener as (...args: unknown[]) => void)
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
off<K extends keyof ApEventMap>(event: K, listener: ApEventMap[K]) {
|
|
79
|
+
native.off(event, listener as (...args: unknown[]) => void)
|
|
80
|
+
},
|
|
107
81
|
}
|
|
108
82
|
|
|
109
83
|
const MAX_CONNECT_RETRIES = 5
|
|
@@ -161,9 +135,13 @@ const wifi: Wifi = {
|
|
|
161
135
|
return ok(asyncResult.value as ScanResult[])
|
|
162
136
|
},
|
|
163
137
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
138
|
+
on<K extends keyof WifiEventMap>(event: K, listener: WifiEventMap[K]) {
|
|
139
|
+
native.on(event, listener as (...args: unknown[]) => void)
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
off<K extends keyof WifiEventMap>(event: K, listener: WifiEventMap[K]) {
|
|
143
|
+
native.off(event, listener as (...args: unknown[]) => void)
|
|
144
|
+
},
|
|
167
145
|
|
|
168
146
|
get mac(): string {
|
|
169
147
|
const result = native.mac()
|
package/src/mikrojs.cpp
CHANGED
|
@@ -317,7 +317,6 @@ MIKRuntime* MIK_NewRuntimeInternal(MIKRunOptions* options) {
|
|
|
317
317
|
mik__result_init(ctx);
|
|
318
318
|
mik__cbor_init(ctx);
|
|
319
319
|
mik__udp_init(ctx);
|
|
320
|
-
mik__observable_init(ctx);
|
|
321
320
|
|
|
322
321
|
/* Native mikrojs modules (replace bytecode builtins) */
|
|
323
322
|
mik__inspect_register(ctx);
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
// Host-side shim for `native:observable`, used only in vitest (Node) where
|
|
2
|
-
// the mikrojs C runtime isn't available. Keep in sync with mik_observable.cpp.
|
|
3
|
-
//
|
|
4
|
-
// Mirrors the locked design in .claude/plans/observable.md:
|
|
5
|
-
// - subscribe() returns a Subscription with unsubscribe() (no AbortSignal)
|
|
6
|
-
// - no error channel — throws inside dispatch or teardown are caught at the
|
|
7
|
-
// boundary, isolated to the offending subscriber, and re-thrown
|
|
8
|
-
// asynchronously via setTimeout(0) so the synchronous producer keeps
|
|
9
|
-
// running but the bug eventually surfaces (and on device, halts the
|
|
10
|
-
// runtime via the existing unhandled-rejection path)
|
|
11
|
-
// - sync emission allowed
|
|
12
|
-
// - pipe-only composition (operators live in operators.ts)
|
|
13
|
-
// - withEmitters() factory: {observable, next, complete}
|
|
14
|
-
|
|
15
|
-
/* Catch a thrown error and re-throw it on the next tick. The synchronous
|
|
16
|
-
* caller keeps going (other subscribers receive the value, remaining
|
|
17
|
-
* teardowns run); the error eventually surfaces as an uncaught exception. */
|
|
18
|
-
function panicAsync(err: unknown): void {
|
|
19
|
-
setTimeout(() => {
|
|
20
|
-
throw err
|
|
21
|
-
}, 0)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
class Subscriber<T> {
|
|
25
|
-
closed = false
|
|
26
|
-
private next_fn: ((v: T) => void) | undefined
|
|
27
|
-
private complete_fn: (() => void) | undefined
|
|
28
|
-
private teardowns: Array<() => void> = []
|
|
29
|
-
|
|
30
|
-
constructor(observer: unknown) {
|
|
31
|
-
if (observer == null) return
|
|
32
|
-
if (typeof observer === 'function') {
|
|
33
|
-
this.next_fn = observer as (v: T) => void
|
|
34
|
-
} else if (typeof observer === 'object') {
|
|
35
|
-
const o = observer as {next?: (v: T) => void; complete?: () => void}
|
|
36
|
-
if (typeof o.next === 'function') this.next_fn = o.next
|
|
37
|
-
if (typeof o.complete === 'function') this.complete_fn = o.complete
|
|
38
|
-
} else {
|
|
39
|
-
throw new TypeError('subscribe: observer must be a function, object, undefined, or null')
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
next(value: T): void {
|
|
44
|
-
if (this.closed) return
|
|
45
|
-
if (this.next_fn) {
|
|
46
|
-
try {
|
|
47
|
-
this.next_fn(value)
|
|
48
|
-
} catch (err) {
|
|
49
|
-
panicAsync(err)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
complete(): void {
|
|
55
|
-
if (this.closed) return
|
|
56
|
-
this.closed = true
|
|
57
|
-
if (this.complete_fn) {
|
|
58
|
-
try {
|
|
59
|
-
this.complete_fn()
|
|
60
|
-
} catch (err) {
|
|
61
|
-
panicAsync(err)
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
this.runTeardowns()
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
addTeardown(fn: () => void): void {
|
|
68
|
-
if (typeof fn !== 'function') {
|
|
69
|
-
throw new TypeError('addTeardown: argument must be a function')
|
|
70
|
-
}
|
|
71
|
-
if (this.closed) {
|
|
72
|
-
try {
|
|
73
|
-
fn()
|
|
74
|
-
} catch (err) {
|
|
75
|
-
panicAsync(err)
|
|
76
|
-
}
|
|
77
|
-
return
|
|
78
|
-
}
|
|
79
|
-
this.teardowns.push(fn)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Used by Subscription.unsubscribe — silent (no observer.complete call).
|
|
83
|
-
closeSilently(): void {
|
|
84
|
-
if (this.closed) return
|
|
85
|
-
this.closed = true
|
|
86
|
-
this.runTeardowns()
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
private runTeardowns(): void {
|
|
90
|
-
const list = this.teardowns
|
|
91
|
-
this.teardowns = []
|
|
92
|
-
for (let i = list.length - 1; i >= 0; i--) {
|
|
93
|
-
try {
|
|
94
|
-
list[i]!()
|
|
95
|
-
} catch (err) {
|
|
96
|
-
panicAsync(err)
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
class Subscription {
|
|
103
|
-
constructor(private subscriber: Subscriber<unknown>) {}
|
|
104
|
-
unsubscribe(): void {
|
|
105
|
-
this.subscriber.closeSilently()
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
type SubscribeCallback<T> = (sub: Subscriber<T>) => void
|
|
110
|
-
|
|
111
|
-
export class Observable<Ok, Err = never> {
|
|
112
|
-
// The shim doesn't actually use `_phantom` at runtime; the type parameters are
|
|
113
|
-
// purely for type-level alignment with the public interface.
|
|
114
|
-
declare readonly _phantom: [Ok, Err]
|
|
115
|
-
#cb: SubscribeCallback<unknown>
|
|
116
|
-
|
|
117
|
-
constructor(cb: SubscribeCallback<unknown>) {
|
|
118
|
-
if (typeof cb !== 'function') {
|
|
119
|
-
throw new TypeError('Observable: constructor requires a function argument')
|
|
120
|
-
}
|
|
121
|
-
this.#cb = cb
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
subscribe(observer?: unknown): Subscription {
|
|
125
|
-
const sub = new Subscriber<unknown>(observer)
|
|
126
|
-
try {
|
|
127
|
-
this.#cb(sub)
|
|
128
|
-
} catch (err) {
|
|
129
|
-
sub.closeSilently()
|
|
130
|
-
throw err
|
|
131
|
-
}
|
|
132
|
-
return new Subscription(sub)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
pipe(...ops: Array<(o: Observable<unknown, unknown>) => Observable<unknown, unknown>>) {
|
|
136
|
-
let current: Observable<unknown, unknown> = this as unknown as Observable<unknown, unknown>
|
|
137
|
-
for (const op of ops) {
|
|
138
|
-
if (typeof op !== 'function') {
|
|
139
|
-
throw new TypeError('pipe: arguments must be operator functions')
|
|
140
|
-
}
|
|
141
|
-
current = op(current)
|
|
142
|
-
}
|
|
143
|
-
return current
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
static from(src: unknown): Observable<unknown, unknown> {
|
|
147
|
-
if (src instanceof Observable) return src
|
|
148
|
-
if (
|
|
149
|
-
src != null &&
|
|
150
|
-
(typeof src === 'object' || typeof src === 'function') &&
|
|
151
|
-
typeof (src as {then?: unknown}).then === 'function'
|
|
152
|
-
) {
|
|
153
|
-
// Promise-shaped
|
|
154
|
-
return new Observable<unknown>((sub) => {
|
|
155
|
-
;(src as PromiseLike<unknown>).then((value) => {
|
|
156
|
-
if (sub.closed) return
|
|
157
|
-
sub.next(value)
|
|
158
|
-
if (sub.closed) return
|
|
159
|
-
sub.complete()
|
|
160
|
-
})
|
|
161
|
-
})
|
|
162
|
-
}
|
|
163
|
-
if (
|
|
164
|
-
src != null &&
|
|
165
|
-
(typeof src === 'object' || typeof src === 'string') &&
|
|
166
|
-
typeof (src as {[Symbol.iterator]?: unknown})[Symbol.iterator] === 'function'
|
|
167
|
-
) {
|
|
168
|
-
return new Observable<unknown>((sub) => {
|
|
169
|
-
for (const value of src as Iterable<unknown>) {
|
|
170
|
-
if (sub.closed) return
|
|
171
|
-
sub.next(value)
|
|
172
|
-
}
|
|
173
|
-
if (!sub.closed) sub.complete()
|
|
174
|
-
})
|
|
175
|
-
}
|
|
176
|
-
throw new TypeError('Observable.from: source must be a Promise, Iterable, or Observable')
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
static withEmitters<Ok, Err = never>(): {
|
|
180
|
-
observable: Observable<Ok, Err>
|
|
181
|
-
next: (value: unknown) => void
|
|
182
|
-
complete: () => void
|
|
183
|
-
} {
|
|
184
|
-
const subs: Array<Subscriber<unknown>> = []
|
|
185
|
-
let completed = false
|
|
186
|
-
|
|
187
|
-
const observable = new Observable<unknown>((sub) => {
|
|
188
|
-
if (completed) {
|
|
189
|
-
sub.complete()
|
|
190
|
-
return
|
|
191
|
-
}
|
|
192
|
-
subs.push(sub)
|
|
193
|
-
sub.addTeardown(() => {
|
|
194
|
-
const i = subs.indexOf(sub)
|
|
195
|
-
if (i >= 0) subs.splice(i, 1)
|
|
196
|
-
})
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
const next = (value: unknown) => {
|
|
200
|
-
if (completed) return
|
|
201
|
-
// Snapshot to be resilient against mid-dispatch unsubscribes.
|
|
202
|
-
const snapshot = subs.slice()
|
|
203
|
-
for (const s of snapshot) {
|
|
204
|
-
if (!s.closed) s.next(value)
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const complete = () => {
|
|
209
|
-
if (completed) return
|
|
210
|
-
completed = true
|
|
211
|
-
const snapshot = subs.slice()
|
|
212
|
-
subs.length = 0
|
|
213
|
-
for (const s of snapshot) {
|
|
214
|
-
if (!s.closed) s.complete()
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return {observable: observable as unknown as Observable<Ok, Err>, next, complete}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {Observable} from 'native:observable'
|