@mikrojs/native 0.0.7

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.
Files changed (109) hide show
  1. package/CMakeLists.txt +198 -0
  2. package/LICENSE +21 -0
  3. package/README.md +49 -0
  4. package/cmake/mikrojs_bytecode.cmake +146 -0
  5. package/cmake.js +22 -0
  6. package/dist/index.d.ts +52 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +132 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/types.d.ts +43 -0
  11. package/dist/types.d.ts.map +1 -0
  12. package/dist/types.js +2 -0
  13. package/dist/types.js.map +1 -0
  14. package/include/byteorder_apple.h +11 -0
  15. package/include/byteorder_windows.h +12 -0
  16. package/include/mikrojs/cbor_helpers.h +24 -0
  17. package/include/mikrojs/cutils_wrap.h +59 -0
  18. package/include/mikrojs/errors.h +144 -0
  19. package/include/mikrojs/mem.h +11 -0
  20. package/include/mikrojs/mik_color.h +32 -0
  21. package/include/mikrojs/mikrojs.h +331 -0
  22. package/include/mikrojs/platform.h +82 -0
  23. package/include/mikrojs/private.h +281 -0
  24. package/include/mikrojs/utils.h +125 -0
  25. package/package.json +100 -0
  26. package/prebuilds/darwin-arm64/mikrojs.napi.node +0 -0
  27. package/prebuilds/linux-arm64/mikrojs.napi.node +0 -0
  28. package/prebuilds/linux-x64/mikrojs.napi.node +0 -0
  29. package/runtime/ble/ble.ts +231 -0
  30. package/runtime/ble/types.ts +194 -0
  31. package/runtime/ble/uuid.ts +89 -0
  32. package/runtime/ble/validators.ts +61 -0
  33. package/runtime/cbor/cbor.ts +1 -0
  34. package/runtime/cbor/types.ts +8 -0
  35. package/runtime/console/types.ts +50 -0
  36. package/runtime/env/env.ts +17 -0
  37. package/runtime/env/types.ts +12 -0
  38. package/runtime/format/types.ts +4 -0
  39. package/runtime/fs/fs.ts +93 -0
  40. package/runtime/fs/types.ts +92 -0
  41. package/runtime/globals.d.ts +87 -0
  42. package/runtime/http/helpers.ts +222 -0
  43. package/runtime/http/native.ts +151 -0
  44. package/runtime/http/request.ts +25 -0
  45. package/runtime/i2c/i2c.ts +35 -0
  46. package/runtime/i2c/types.ts +55 -0
  47. package/runtime/inspect/types.ts +10 -0
  48. package/runtime/internal.d.ts +456 -0
  49. package/runtime/kv/nvs.ts +17 -0
  50. package/runtime/kv/rtc.ts +17 -0
  51. package/runtime/kv/shared.ts +107 -0
  52. package/runtime/kv/types.ts +150 -0
  53. package/runtime/neopixel/neopixel.ts +38 -0
  54. package/runtime/neopixel/types.ts +27 -0
  55. package/runtime/pin/pin.ts +51 -0
  56. package/runtime/pin/types.ts +49 -0
  57. package/runtime/pwm/pwm.ts +32 -0
  58. package/runtime/pwm/types.ts +29 -0
  59. package/runtime/reader/reader.ts +167 -0
  60. package/runtime/reader/types.ts +34 -0
  61. package/runtime/result/native-result.node-shim.ts +44 -0
  62. package/runtime/result/result.ts +26 -0
  63. package/runtime/result/types.ts +60 -0
  64. package/runtime/schema/schema.ts +321 -0
  65. package/runtime/schema/types.ts +152 -0
  66. package/runtime/sleep/sleep.ts +14 -0
  67. package/runtime/sleep/types.ts +44 -0
  68. package/runtime/sntp/sntp.ts +54 -0
  69. package/runtime/sntp/types.ts +38 -0
  70. package/runtime/spi/spi.ts +31 -0
  71. package/runtime/spi/types.ts +42 -0
  72. package/runtime/stdio/stdio.ts +44 -0
  73. package/runtime/stdio/types.ts +22 -0
  74. package/runtime/stream/stream.ts +150 -0
  75. package/runtime/stream/types.ts +47 -0
  76. package/runtime/sys/sys.ts +90 -0
  77. package/runtime/sys/types.ts +131 -0
  78. package/runtime/test/test.ts +595 -0
  79. package/runtime/test/types.ts +97 -0
  80. package/runtime/uart/types.ts +75 -0
  81. package/runtime/uart/uart.ts +51 -0
  82. package/runtime/wifi/types.ts +156 -0
  83. package/runtime/wifi/wifi.ts +208 -0
  84. package/scripts/bundle-runtime.js +149 -0
  85. package/scripts/compare-minifiers.js +189 -0
  86. package/scripts/compile-bytecode.sh +38 -0
  87. package/scripts/copy-prebuild.js +20 -0
  88. package/scripts/generate-symbol-map.js +146 -0
  89. package/src/builtins.cpp +82 -0
  90. package/src/cutils_compat.c +38 -0
  91. package/src/eval_bytecode.cpp +42 -0
  92. package/src/fs.cpp +878 -0
  93. package/src/mem.cpp +63 -0
  94. package/src/mik_abort.cpp +160 -0
  95. package/src/mik_app_config.cpp +358 -0
  96. package/src/mik_cbor.cpp +334 -0
  97. package/src/mik_color.cpp +46 -0
  98. package/src/mik_console.cpp +422 -0
  99. package/src/mik_inspect.cpp +850 -0
  100. package/src/mik_repl.cpp +1122 -0
  101. package/src/mik_result.cpp +344 -0
  102. package/src/mik_stdio.cpp +147 -0
  103. package/src/mik_sys.cpp +239 -0
  104. package/src/mik_text_encoding.cpp +443 -0
  105. package/src/mikrojs.cpp +942 -0
  106. package/src/modules.cpp +944 -0
  107. package/src/platform_posix.cpp +134 -0
  108. package/src/timers.cpp +208 -0
  109. package/src/utils.cpp +173 -0
@@ -0,0 +1,150 @@
1
+ import type {Result} from '../result/types.js'
2
+ import type {
3
+ ArraySchema,
4
+ BooleanSchema,
5
+ Infer,
6
+ NumberSchema,
7
+ ObjectSchema,
8
+ OptionalSchema,
9
+ SchemaError,
10
+ StringSchema,
11
+ TupleSchema,
12
+ UnknownSchema,
13
+ } from '../schema/types.js'
14
+
15
+ export interface RtcMemoryInfo {
16
+ used: number
17
+ total: number
18
+ entries: number
19
+ }
20
+
21
+ export interface NvsStorageInfo {
22
+ /** Number of entries in the kv namespace. */
23
+ entries: number
24
+ /** Used entries across all NVS namespaces. */
25
+ used: number
26
+ /** Total entries in the NVS partition. */
27
+ total: number
28
+ /** Free entries in the NVS partition. */
29
+ free: number
30
+ }
31
+
32
+ /** Values that can be stored (CBOR-serializable). */
33
+ export type Storable =
34
+ | number
35
+ | string
36
+ | boolean
37
+ | undefined
38
+ | Uint8Array
39
+ | Storable[]
40
+ | {[key: string]: Storable}
41
+
42
+ /** Values that can be stored (CBOR-serializable). */
43
+ export type StorableSchema =
44
+ | NumberSchema
45
+ | StringSchema
46
+ | UnknownSchema
47
+ | BooleanSchema
48
+ | OptionalSchema
49
+ | ArraySchema<StorableSchema>
50
+ | TupleSchema<StorableSchema[]>
51
+ | ObjectSchema<{[key: string]: StorableSchema}>
52
+
53
+ export type KVError =
54
+ | {name: 'StorageFull'; message: string}
55
+ | {name: 'EncodeFailed'; message: string}
56
+ | {name: 'WriteFailed'; message: string}
57
+ | {name: 'ValidationFailed'; message: string; path: string}
58
+
59
+ export type KVOptions<S> =
60
+ | {
61
+ schema: S
62
+ initialValue: Infer<S>
63
+ /** Called when reading stored data fails (decode error, schema mismatch).
64
+ * On decode failure, corrupt data is deleted. On schema mismatch, stored data is kept.
65
+ * Return a fallback value, or undefined. Default: `() => undefined`. */
66
+ onReadError: (error: KVError | SchemaError) => Infer<S>
67
+ }
68
+ | {
69
+ schema: OptionalSchema
70
+ initialValue?: Infer<S>
71
+ /** Called when reading stored data fails (decode error, schema mismatch).
72
+ * On decode failure, corrupt data is deleted. On schema mismatch, stored data is kept.
73
+ * Return a fallback value, or undefined. Default: `() => undefined`. */
74
+ onReadError?: (error: KVError | SchemaError) => Infer<S>
75
+ }
76
+ | {
77
+ schema?: never
78
+ initialValue: unknown
79
+ /** Called when reading stored data fails (decode error, schema mismatch).
80
+ * On decode failure, corrupt data is deleted. On schema mismatch, stored data is kept.
81
+ * Return a fallback value, or undefined. Default: `() => undefined`. */
82
+ onReadError: (error: KVError) => unknown
83
+ }
84
+ | {
85
+ schema?: never
86
+ initialValue?: unknown
87
+ /** Called when reading stored data fails (decode error, schema mismatch).
88
+ * On decode failure, corrupt data is deleted. On schema mismatch, stored data is kept.
89
+ * Return a fallback value, or undefined. Default: `() => undefined`. */
90
+ onReadError?: (error: KVError) => unknown
91
+ }
92
+
93
+ export interface KVValue<T> {
94
+ /** Read the value. Returns undefined if the key doesn't exist. */
95
+ get(): T
96
+ /** Write a value, or delete the key if undefined. Returns the written value on success. */
97
+ set(value: T): Result<T, KVError>
98
+ /** Read, transform, and write. Receives undefined if key is missing. Return undefined to delete. */
99
+ update(updater: (value: T) => T): Result<T, KVError>
100
+ /** Remove the key. Returns ok on success, err if the storage operation fails. */
101
+ delete(): Result<void, KVError>
102
+ }
103
+
104
+ export type InferOpts<
105
+ Schema extends StorableSchema,
106
+ Options extends KVOptions<Schema>,
107
+ > = Options extends {
108
+ schema?: never
109
+ initialValue: infer I
110
+ onReadError: (error: KVError) => infer V
111
+ }
112
+ ? I | V
113
+ : Options extends {schema: infer S}
114
+ ? Infer<S>
115
+ : unknown
116
+
117
+ export interface RtcStorage {
118
+ createValue<Schema extends StorableSchema, Opts extends KVOptions<Schema>>(
119
+ key: string,
120
+ options?: Opts,
121
+ ): KVValue<InferOpts<Schema, Opts>>
122
+
123
+ /** Clear all RTC data. */
124
+ clear(): void
125
+ /** Get info about RTC memory usage. */
126
+ info(): RtcMemoryInfo
127
+ }
128
+
129
+ /**
130
+ * NVS-backed key-value storage. Survives power cycles.
131
+ * Keys are limited to 15 characters (NVS constraint).
132
+ * Flash-backed: avoid high-frequency writes.
133
+ */
134
+ export interface NvsStorage {
135
+ createValue<Schema extends StorableSchema, Opts extends KVOptions<Schema>>(
136
+ key: string,
137
+ options?: Opts,
138
+ ): KVValue<InferOpts<Schema, Opts>>
139
+
140
+ /** Erase all NVS key-value data. */
141
+ clear(): void
142
+ /** Get info about NVS key-value usage. */
143
+ info(): NvsStorageInfo
144
+ }
145
+
146
+ /** RTC memory store. Survives deep sleep, lost on power off. */
147
+ export declare const rtcStorage: RtcStorage
148
+
149
+ /** NVS flash store. Survives power cycles. */
150
+ export declare const nvsStorage: NvsStorage
@@ -0,0 +1,38 @@
1
+ import {NeoPixel as NativeNeoPixel} from 'native:neopixel'
2
+
3
+ import type {Result} from '../result/types.js'
4
+ import type {NeoPixelError, NeoPixelOptions} from './types.js'
5
+
6
+ export class NeoPixel {
7
+ #native: InstanceType<typeof NativeNeoPixel>
8
+
9
+ constructor(pin: number, options: NeoPixelOptions) {
10
+ this.#native = new NativeNeoPixel(pin, options.count, options.rgbw ? 4 : 3)
11
+ }
12
+
13
+ setPixel(
14
+ index: number,
15
+ r: number,
16
+ g: number,
17
+ b: number,
18
+ w?: number,
19
+ ): Result<void, NeoPixelError> {
20
+ return this.#native.setPixel(index, r, g, b, w ?? 0)
21
+ }
22
+
23
+ fill(r: number, g: number, b: number, w?: number): Result<void, NeoPixelError> {
24
+ return this.#native.fill(r, g, b, w ?? 0)
25
+ }
26
+
27
+ show(): Result<void, NeoPixelError> {
28
+ return this.#native.show()
29
+ }
30
+
31
+ clear(): Result<void, NeoPixelError> {
32
+ return this.#native.clear()
33
+ }
34
+
35
+ end(): Result<void, NeoPixelError> {
36
+ return this.#native.end()
37
+ }
38
+ }
@@ -0,0 +1,27 @@
1
+ import type {Result} from '../result/types.js'
2
+
3
+ export interface NeoPixelOptions {
4
+ /** Number of LEDs in the strip */
5
+ count: number
6
+ /** Set to true for RGBW strips (SK6812). Defaults to false (RGB/WS2812). */
7
+ rgbw?: boolean
8
+ }
9
+
10
+ export type NeoPixelError =
11
+ | {name: 'NotActive'}
12
+ | {name: 'IndexOutOfRange'}
13
+ | {name: 'ShowFailed'; message: string}
14
+
15
+ export declare class NeoPixel {
16
+ constructor(pin: number, options: NeoPixelOptions)
17
+ /** Set a single pixel's color (0–255 per channel) */
18
+ setPixel(index: number, r: number, g: number, b: number, w?: number): Result<void, NeoPixelError>
19
+ /** Set all pixels to the same color */
20
+ fill(r: number, g: number, b: number, w?: number): Result<void, NeoPixelError>
21
+ /** Transmit the pixel buffer to the strip */
22
+ show(): Result<void, NeoPixelError>
23
+ /** Turn off all pixels and transmit */
24
+ clear(): Result<void, NeoPixelError>
25
+ /** Release the RMT channel */
26
+ end(): Result<void, NeoPixelError>
27
+ }
@@ -0,0 +1,51 @@
1
+ import * as native from 'native:pin'
2
+
3
+ import type {Result} from '../result/types.js'
4
+ import type {AnalogReadOptions, Attenuation, PinError, PinMode} from './types.js'
5
+
6
+ // Public mode lookup. Kept as a runtime value (rather than inlined) because
7
+ // user code occasionally reads it to build a mode-picker UI on host-side
8
+ // tooling; the native:pin layer uses the numeric constants directly.
9
+ const PIN_MODE_INPUT = 0x01
10
+ const PIN_MODE_OUTPUT = 0x03
11
+ const PIN_MODE_INPUT_PULLUP = 0x05
12
+
13
+ export const NativePinModes = {
14
+ INPUT: PIN_MODE_INPUT,
15
+ OUTPUT: PIN_MODE_OUTPUT,
16
+ INPUT_PULLUP: PIN_MODE_INPUT_PULLUP,
17
+ } as const
18
+
19
+ const NativeAttenuation: Record<Attenuation, number> = {
20
+ '0db': 0,
21
+ '2.5db': 1,
22
+ '6db': 2,
23
+ '11db': 3,
24
+ } as const
25
+
26
+ // Wrappers translate the string/enum JS-idiom args to the numeric codes the
27
+ // native layer expects. Errors flow through unchanged: native:pin returns
28
+ // {ok, error:{name,message}} directly (see mik_pin.cpp), so no remapping.
29
+
30
+ export function pinMode(pin: number, mode: PinMode): Result<void, PinError> {
31
+ return native.pinMode(pin, NativePinModes[mode])
32
+ }
33
+
34
+ export function digitalWrite(pin: number, value: 0 | 1): Result<void, PinError> {
35
+ return native.digitalWrite(pin, value)
36
+ }
37
+
38
+ export function digitalRead(pin: number): 0 | 1 {
39
+ return native.digitalRead(pin) as 0 | 1
40
+ }
41
+
42
+ export function analogRead(pin: number, options?: AnalogReadOptions): Result<number, PinError> {
43
+ return native.analogRead(pin, NativeAttenuation[options?.attenuation ?? '11db'])
44
+ }
45
+
46
+ export function analogReadMillivolts(
47
+ pin: number,
48
+ options?: AnalogReadOptions,
49
+ ): Result<number, PinError> {
50
+ return native.analogReadMillivolts(pin, NativeAttenuation[options?.attenuation ?? '11db'])
51
+ }
@@ -0,0 +1,49 @@
1
+ import type {Result} from '../result/types.js'
2
+
3
+ export type PinMode = 'INPUT' | 'OUTPUT' | 'INPUT_PULLUP'
4
+
5
+ /**
6
+ * ADC attenuation setting, controls the measurable voltage range.
7
+ * - `'0db'` — 0–750 mV
8
+ * - `'2.5db'` — 0–1050 mV
9
+ * - `'6db'` — 0–1300 mV
10
+ * - `'11db'` — 0–2500 mV (default)
11
+ */
12
+ export type Attenuation = '0db' | '2.5db' | '6db' | '11db'
13
+
14
+ export interface AnalogReadOptions {
15
+ /** ADC attenuation. Defaults to `'11db'` (0–2500 mV range). */
16
+ attenuation?: Attenuation
17
+ }
18
+
19
+ export type PinError =
20
+ | {name: 'InvalidPin'; message: string}
21
+ | {name: 'SetModeFailed'; message: string}
22
+ | {name: 'WriteFailed'; message: string}
23
+ | {name: 'AdcInitFailed'; message: string}
24
+ | {name: 'AdcReadFailed'; message: string}
25
+ | {name: 'InvalidAdcPin'}
26
+
27
+ export declare function pinMode(pin: number, mode: PinMode): Result<void, PinError>
28
+
29
+ export declare function digitalWrite(pin: number, value: 0 | 1): Result<void, PinError>
30
+
31
+ export declare function digitalRead(pin: number): 0 | 1
32
+
33
+ /**
34
+ * Read the raw ADC value from a pin.
35
+ * @returns A Result containing a 12-bit integer (0–4095) proportional to the input voltage, or a PinError.
36
+ */
37
+ export declare function analogRead(
38
+ pin: number,
39
+ options?: AnalogReadOptions,
40
+ ): Result<number, PinError>
41
+
42
+ /**
43
+ * Read a calibrated voltage from a pin.
44
+ * @returns A Result containing the input voltage in millivolts, or a PinError.
45
+ */
46
+ export declare function analogReadMillivolts(
47
+ pin: number,
48
+ options?: AnalogReadOptions,
49
+ ): Result<number, PinError>
@@ -0,0 +1,32 @@
1
+ import {ok} from 'mikrojs/result'
2
+ import {Pwm as NativePwm} from 'native:pwm'
3
+
4
+ import type {Result} from '../result/types.js'
5
+ import type {PwmError, PwmOptions} from './types.js'
6
+
7
+ export class Pwm {
8
+ #native: InstanceType<typeof NativePwm>
9
+
10
+ constructor(pin: number, options: PwmOptions) {
11
+ this.#native = new NativePwm(pin, options.freq, options.duty ?? 0)
12
+ }
13
+
14
+ duty(value?: number): Result<number, PwmError> {
15
+ return this.#native.duty(value)
16
+ }
17
+
18
+ freq(value?: number): Result<number, PwmError> {
19
+ return this.#native.freq(value)
20
+ }
21
+
22
+ async fade(targetDuty: number, durationMs: number): Promise<Result<void, PwmError>> {
23
+ const result = this.#native.fade(targetDuty, durationMs)
24
+ if (!result.ok) return result
25
+ await result.value
26
+ return ok(undefined)
27
+ }
28
+
29
+ end(): Result<void, PwmError> {
30
+ return this.#native.end()
31
+ }
32
+ }
@@ -0,0 +1,29 @@
1
+ import type {Result} from '../result/types.js'
2
+
3
+ export interface PwmOptions {
4
+ /** Frequency in Hz */
5
+ freq: number
6
+ /** Initial duty cycle, 0.0–1.0. Defaults to 0. */
7
+ duty?: number
8
+ }
9
+
10
+ export type PwmError =
11
+ | {name: 'NoChannel'; message: string}
12
+ | {name: 'NoTimer'; message: string}
13
+ | {name: 'ConfigFailed'; message: string}
14
+ | {name: 'NotActive'}
15
+ | {name: 'DutyFailed'; message: string}
16
+ | {name: 'FreqFailed'; message: string}
17
+ | {name: 'FadeFailed'; message: string}
18
+
19
+ export declare class Pwm {
20
+ constructor(pin: number, options: PwmOptions)
21
+ /** Get or set duty cycle (0.0–1.0) */
22
+ duty(value?: number): Result<number, PwmError>
23
+ /** Get or set frequency in Hz */
24
+ freq(value?: number): Result<number, PwmError>
25
+ /** Hardware fade to target duty over duration. Returns promise with result. */
26
+ fade(targetDuty: number, durationMs: number): Promise<Result<void, PwmError>>
27
+ /** Stop PWM and release the channel */
28
+ end(): Result<void, PwmError>
29
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Byte-level protocol reader for mikrojs.
3
+ *
4
+ * Separate runtime module from `mikrojs/stream` so that apps which
5
+ * only need a buffered reader (a modem driver, a log framer, an HTTP
6
+ * body splitter) don't pay the load cost of the async-iterator
7
+ * transforms in `mikrojs/stream`. Both modules share the same
8
+ * single-consumer design philosophy; they're separated only for
9
+ * lazy-load efficiency.
10
+ */
11
+
12
+ /**
13
+ * A buffered byte reader over an async iterator of byte chunks.
14
+ *
15
+ * Wraps any `AsyncIterable<Uint8Array>` (typically `uart.read()`,
16
+ * eventually also TCP sockets) and exposes three primitives that
17
+ * share the same underlying byte buffer:
18
+ *
19
+ * - `readUntil(delimiter, {timeoutMs})` — pull chunks until the
20
+ * buffer contains `delimiter`, return everything before it and
21
+ * consume the delimiter. The AT-channel workhorse: passes
22
+ * `CRLF` to read a line, or `'> '` to wait for a prompt.
23
+ *
24
+ * - `readBytes(count, {timeoutMs})` — pull chunks until the buffer
25
+ * has at least `count` bytes, return the first `count` and keep
26
+ * the rest. Used for length-prefixed binary payloads (e.g.
27
+ * HTTP response bodies after `+SHREAD`).
28
+ *
29
+ * - `drain()` — discard any buffered bytes without touching the
30
+ * underlying iterator. Useful before sending a new command when
31
+ * stale bytes from a previous response might be left over.
32
+ *
33
+ * Both `readUntil` and `readBytes` throw `TimeoutError` (an Error
34
+ * with `name === 'TimeoutError'`) if the deadline expires before
35
+ * the request is satisfied, and `StreamClosed` (an Error with
36
+ * `name === 'StreamClosed'`) if the underlying iterator finishes
37
+ * before the request is satisfied. Callers may wrap those in their
38
+ * own Result type if they prefer structured error handling.
39
+ *
40
+ * Timeout semantics: when a pull races against a `setTimeout` and
41
+ * the timer wins, the pending `iter.next()` promise is kept so the
42
+ * NEXT pull resumes on the same chunk — bytes received during the
43
+ * timed-out window are not dropped. This matches the behavior of
44
+ * hand-written AT channels and is what you want for command retries.
45
+ *
46
+ * This reader is intentionally **single-consumer**. Interleaving
47
+ * concurrent `readUntil` / `readBytes` calls is not supported and
48
+ * not protected against — the caller is responsible for
49
+ * sequential ordering.
50
+ */
51
+ export class BufferedReader {
52
+ readonly #source: AsyncIterator<Uint8Array>
53
+ #buffer: Uint8Array
54
+ #pending: Promise<IteratorResult<Uint8Array>> | null
55
+
56
+ constructor(source: AsyncIterable<Uint8Array>) {
57
+ this.#source = source[Symbol.asyncIterator]() as AsyncIterator<Uint8Array>
58
+ this.#buffer = new Uint8Array(0)
59
+ this.#pending = null
60
+ }
61
+
62
+ async readUntil(delimiter: Uint8Array, options: {timeoutMs: number}): Promise<Uint8Array> {
63
+ if (delimiter.length === 0) {
64
+ throw new RangeError('BufferedReader.readUntil: delimiter must be non-empty')
65
+ }
66
+ const deadline = Date.now() + options.timeoutMs
67
+ while (true) {
68
+ const idx = indexOfBytes(this.#buffer, delimiter)
69
+ if (idx !== -1) {
70
+ const out = this.#buffer.slice(0, idx)
71
+ this.#buffer = this.#buffer.slice(idx + delimiter.length)
72
+ return out
73
+ }
74
+ await this.#pull(deadline)
75
+ }
76
+ }
77
+
78
+ async readBytes(count: number, options: {timeoutMs: number}): Promise<Uint8Array> {
79
+ if (count < 0) {
80
+ throw new RangeError('BufferedReader.readBytes: count must be non-negative')
81
+ }
82
+ if (count === 0) return new Uint8Array(0)
83
+ const deadline = Date.now() + options.timeoutMs
84
+ while (this.#buffer.length < count) {
85
+ await this.#pull(deadline)
86
+ }
87
+ const out = this.#buffer.slice(0, count)
88
+ this.#buffer = this.#buffer.slice(count)
89
+ return out
90
+ }
91
+
92
+ /**
93
+ * Discard buffered bytes. Leaves `#pending` alone so any in-flight
94
+ * `iter.next()` from a previously timed-out pull still gets
95
+ * consumed on the next read — we only want to drop stale bytes
96
+ * that were already delivered, not bytes the source hasn't sent
97
+ * yet.
98
+ */
99
+ drain(): void {
100
+ this.#buffer = new Uint8Array(0)
101
+ }
102
+
103
+ async #pull(deadline: number): Promise<void> {
104
+ const remaining = deadline - Date.now()
105
+ if (remaining <= 0) {
106
+ const err = new Error(`BufferedReader: timeout waiting for bytes`)
107
+ err.name = 'TimeoutError'
108
+ throw err
109
+ }
110
+
111
+ if (this.#pending === null) {
112
+ this.#pending = this.#source.next()
113
+ }
114
+ const pending = this.#pending
115
+
116
+ let timerId: ReturnType<typeof setTimeout> | undefined
117
+ try {
118
+ const result = await Promise.race([
119
+ pending.then((v): {kind: 'value'; v: IteratorResult<Uint8Array>} => ({kind: 'value', v})),
120
+ new Promise<{kind: 'timeout'}>((resolve) => {
121
+ timerId = setTimeout(() => resolve({kind: 'timeout'}), remaining)
122
+ }),
123
+ ])
124
+ if (result.kind === 'timeout') {
125
+ // Leave #pending intact so the next pull resumes on this chunk.
126
+ const err = new Error(`BufferedReader: timeout after ${remaining}ms waiting for bytes`)
127
+ err.name = 'TimeoutError'
128
+ throw err
129
+ }
130
+ this.#pending = null
131
+ if (result.v.done) {
132
+ const err = new Error('BufferedReader: stream closed before read satisfied')
133
+ err.name = 'StreamClosed'
134
+ throw err
135
+ }
136
+ this.#buffer = concatBytes(this.#buffer, result.v.value)
137
+ } finally {
138
+ if (timerId !== undefined) clearTimeout(timerId)
139
+ }
140
+ }
141
+ }
142
+
143
+ /** Find the first index of `needle` in `haystack`, or -1 if not found. */
144
+ function indexOfBytes(haystack: Uint8Array, needle: Uint8Array): number {
145
+ if (needle.length === 0) return 0
146
+ const limit = haystack.length - needle.length
147
+ for (let i = 0; i <= limit; i++) {
148
+ let ok = true
149
+ for (let j = 0; j < needle.length; j++) {
150
+ if (haystack[i + j] !== needle[j]) {
151
+ ok = false
152
+ break
153
+ }
154
+ }
155
+ if (ok) return i
156
+ }
157
+ return -1
158
+ }
159
+
160
+ function concatBytes(a: Uint8Array, b: Uint8Array): Uint8Array {
161
+ if (a.length === 0) return b
162
+ if (b.length === 0) return a
163
+ const out = new Uint8Array(a.length + b.length)
164
+ out.set(a, 0)
165
+ out.set(b, a.length)
166
+ return out
167
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Public types for `mikrojs/reader`.
3
+ *
4
+ * Runtime implementation lives in `./reader.ts` and is compiled to
5
+ * bytecode at firmware build time. This file is the declaration-only
6
+ * surface consumed by the `mikrojs/reader` re-export in
7
+ * `packages/mikrojs` and by user apps via their tsconfig.
8
+ */
9
+
10
+ /**
11
+ * A buffered byte reader over an async iterator of `Uint8Array`
12
+ * chunks. Offers three operations that share the same underlying
13
+ * buffer:
14
+ *
15
+ * - `readUntil(delimiter, {timeoutMs})` — return everything before
16
+ * the first occurrence of `delimiter`, consume the delimiter.
17
+ * - `readBytes(count, {timeoutMs})` — return exactly `count` bytes,
18
+ * pulling more source chunks as needed.
19
+ * - `drain()` — discard buffered bytes without affecting the
20
+ * underlying iterator.
21
+ *
22
+ * Throws an Error with `name === 'TimeoutError'` if the deadline
23
+ * expires before the request is satisfied, or `name === 'StreamClosed'`
24
+ * if the underlying iterator finishes before the request is satisfied.
25
+ *
26
+ * Single-consumer only: concurrent `readUntil` / `readBytes` calls on
27
+ * the same instance are unsupported.
28
+ */
29
+ export declare class BufferedReader {
30
+ constructor(source: AsyncIterable<Uint8Array>)
31
+ readUntil(delimiter: Uint8Array, options: {timeoutMs: number}): Promise<Uint8Array>
32
+ readBytes(count: number, options: {timeoutMs: number}): Promise<Uint8Array>
33
+ drain(): void
34
+ }
@@ -0,0 +1,44 @@
1
+ // Host-side shim for `native:result` used only in vitest (Node) where the
2
+ // mikrojs C runtime isn't available. Keep in sync with mik_result.cpp.
3
+
4
+ const proto = {
5
+ map(this: {ok: boolean; value?: unknown; error?: unknown}, fn: (v: unknown) => unknown) {
6
+ return this.ok ? ok(fn(this.value)) : this
7
+ },
8
+ mapErr(this: {ok: boolean; value?: unknown; error?: unknown}, fn: (e: unknown) => unknown) {
9
+ return this.ok ? this : err(fn(this.error))
10
+ },
11
+ andThen(this: {ok: boolean; value?: unknown; error?: unknown}, fn: (v: unknown) => unknown) {
12
+ return this.ok ? fn(this.value) : this
13
+ },
14
+ match(
15
+ this: {ok: boolean; value?: unknown; error?: unknown},
16
+ handlers: {ok: (v: unknown) => unknown; err: (e: unknown) => unknown},
17
+ ) {
18
+ return this.ok ? handlers.ok(this.value) : handlers.err(this.error)
19
+ },
20
+ orDefault(this: {ok: boolean; value?: unknown; error?: unknown}, defaultValue: unknown) {
21
+ return this.ok ? this.value : defaultValue
22
+ },
23
+ orPanic(this: {ok: boolean; value?: unknown; error?: unknown}, message: string) {
24
+ if (this.ok) return this.value
25
+ const panic = new Error(message)
26
+ panic.name = 'PanicError'
27
+ ;(panic as Error & {cause?: unknown}).cause = this.error
28
+ throw panic
29
+ },
30
+ }
31
+
32
+ export function ok(value?: unknown): unknown {
33
+ const r = Object.create(proto)
34
+ r.ok = true
35
+ if (arguments.length > 0) r.value = value
36
+ return r
37
+ }
38
+
39
+ export function err(error: unknown): unknown {
40
+ const r = Object.create(proto)
41
+ r.ok = false
42
+ r.error = error
43
+ return r
44
+ }
@@ -0,0 +1,26 @@
1
+ export {err, ok} from 'native:result'
2
+
3
+ export class PanicError extends Error {
4
+ constructor(message: string, options?: {cause?: unknown}) {
5
+ super(message, options)
6
+ this.name = 'PanicError'
7
+ }
8
+ }
9
+
10
+ export function defineError<D extends Record<string, (...args: any[]) => Record<string, unknown>>>(
11
+ _name: string,
12
+ variants: D,
13
+ ): {[K in keyof D & string]: (...args: Parameters<D[K]>) => {name: K} & ReturnType<D[K]>} {
14
+ const constructors: Record<string, (...args: unknown[]) => Record<string, unknown>> = {}
15
+ for (const key in variants) {
16
+ const factory = variants[key]!
17
+ constructors[key] = (...args: unknown[]) => {
18
+ const fields = (factory as (...a: unknown[]) => Record<string, unknown>)(...args)
19
+ fields.name = key
20
+ return fields
21
+ }
22
+ }
23
+ return constructors as unknown as {
24
+ [K in keyof D & string]: (...args: Parameters<D[K]>) => {name: K} & ReturnType<D[K]>
25
+ }
26
+ }