@mikrojs/native 0.6.0-pr-72.g464c29c → 0.6.1

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 CHANGED
@@ -50,6 +50,7 @@ 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
53
54
  )
54
55
 
55
56
  add_library(mikrojs STATIC
@@ -80,7 +81,7 @@ endif()
80
81
  include(cmake/mikrojs_bytecode.cmake)
81
82
  mikrojs_generate_bytecode(
82
83
  RUNTIME_DIR "${CMAKE_CURRENT_SOURCE_DIR}/runtime"
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
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
84
85
  MODULE_PREFIX "mikrojs"
85
86
  SYMBOL_PREFIX "mikrojs"
86
87
  TARGET gen_bytecode
@@ -161,6 +162,7 @@ if(BUILD_TESTING)
161
162
  test/stream_test.cpp
162
163
  test/runtime_recycle_test.cpp
163
164
  test/udp_test.cpp
165
+ test/observable_test.cpp
164
166
  )
165
167
 
166
168
  target_link_libraries(mikrojs_tests PRIVATE mikrojs)
@@ -30,7 +30,8 @@ typedef struct MIKConfig {
30
30
  uint32_t mem_reserved;
31
31
  uint32_t fs_read_max; /* 0 = keep runtime default (65536) */
32
32
  char entry_point[128];
33
- char wifi_country[3]; /* Two-letter country code + NUL, e.g. "NO" */
33
+ char wifi_country[3]; /* Two-letter country code + NUL, e.g. "NO" */
34
+ char wifi_hostname[64]; /* DHCP hostname; empty = use mikrojs-<device-id> default */
34
35
  } MIKConfig;
35
36
 
36
37
  void MIK_DefaultConfig(MIKConfig* config);
@@ -215,6 +215,9 @@ 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
+
218
221
  bool mik__repl_is_evaluating(void);
219
222
 
220
223
  /* 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-72.g464c29c",
3
+ "version": "0.6.1",
4
4
  "description": "Mikro.js C++ runtime library and Node.js native addon",
5
5
  "keywords": [
6
6
  "esp32",
@@ -56,6 +56,8 @@
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",
59
61
  "./runtime/pin/types": "./runtime/pin/types.ts",
60
62
  "./runtime/pwm/types": "./runtime/pwm/types.ts",
61
63
  "./runtime/reader/types": "./runtime/reader/types.ts",
@@ -76,7 +78,7 @@
76
78
  "cmake-js": "^8.0.0",
77
79
  "node-addon-api": "^8.7.0",
78
80
  "node-gyp-build": "^4.8.4",
79
- "@mikrojs/quickjs": "0.6.0-pr-72.g464c29c"
81
+ "@mikrojs/quickjs": "0.6.1"
80
82
  },
81
83
  "devDependencies": {
82
84
  "@swc/core": "^1.15.30",
@@ -1,3 +1,4 @@
1
+ import {lazyEvent} from 'mikrojs/observable/lazy'
1
2
  import {err, ok} from 'mikrojs/result'
2
3
  import {Ble as NativeBle} from 'native:ble'
3
4
 
@@ -9,8 +10,9 @@ import type {
9
10
  BleError,
10
11
  Characteristic,
11
12
  CharacteristicProperty,
13
+ ConnectionInfo,
14
+ MtuInfo,
12
15
  Peripheral,
13
- PeripheralEventMap,
14
16
  Service,
15
17
  } from './types.js'
16
18
  import {parseUuid} from './uuid.js'
@@ -219,13 +221,9 @@ const peripheral: Peripheral = {
219
221
  return ok(handle)
220
222
  },
221
223
 
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
- },
224
+ onConnect: lazyEvent<ConnectionInfo>(native, 'connect'),
225
+ onDisconnect: lazyEvent<ConnectionInfo>(native, 'disconnect'),
226
+ onMtu: lazyEvent<MtuInfo>(native, 'mtu'),
229
227
  }
230
228
 
231
229
  export {ble, peripheral}
@@ -1,3 +1,4 @@
1
+ import type {Observable} from '../observable/types.js'
1
2
  import type {Result} from '../result/types.js'
2
3
 
3
4
  /** Advertising interval range in milliseconds. */
@@ -171,23 +172,15 @@ export interface MtuInfo {
171
172
  mtu: number
172
173
  }
173
174
 
174
- export interface PeripheralEventMap {
175
- connect: (info: ConnectionInfo) => void
176
- disconnect: (info: ConnectionInfo) => void
177
- mtu: (info: MtuInfo) => void
178
- }
179
-
180
175
  export interface Peripheral {
181
176
  /** Start advertising. Returns a handle whose `stop()` ends this session. */
182
177
  advertise(options?: AdvertiseOptions): Promise<Result<AdvertiseHandle, BleError>>
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
178
+ /** Emits when a central connects. Subscribers are dispatched on the JS loop thread. */
179
+ readonly onConnect: Observable<ConnectionInfo>
180
+ /** Emits when a central disconnects. */
181
+ readonly onDisconnect: Observable<ConnectionInfo>
182
+ /** Emits when a connected central renegotiates its ATT MTU. */
183
+ readonly onMtu: Observable<MtuInfo>
191
184
  }
192
185
 
193
186
  export declare const ble: Ble
@@ -11,6 +11,10 @@ declare module 'mikrojs/kv/shared' {
11
11
  export {KVError, makeCreateValue, type NativeKvFns} from './kv/shared.js'
12
12
  }
13
13
 
14
+ declare module 'mikrojs/observable/lazy' {
15
+ export {lazyEvent} from './observable/lazy.js'
16
+ }
17
+
14
18
  declare module 'native:cbor' {
15
19
  import type {CborError} from '@mikrojs/native/runtime/cbor/types'
16
20
  import type {Result} from 'mikrojs/result'
@@ -25,6 +29,10 @@ declare module 'native:result' {
25
29
  export function err<E>(error: E): ErrResult<E>
26
30
  }
27
31
 
32
+ declare module 'native:observable' {
33
+ export {Observable} from '@mikrojs/native/runtime/observable/types'
34
+ }
35
+
28
36
  declare module 'native:sys' {
29
37
  import type {JsMemoryUsage} from './sys/types.js'
30
38
  export function evalScript(code: string): Promise<{value: unknown}>
@@ -407,7 +415,6 @@ declare module 'native:wifi' {
407
415
  // Network config
408
416
  mac(): R<string>
409
417
  getHostname(): string | undefined
410
- setHostname(hostname: string): R<void>
411
418
  getIpConfig(): R<{ip: string; netmask: string; gateway: string; dns: string} | undefined>
412
419
  setIpConfig(opts: {
413
420
  ip?: string
@@ -446,7 +453,6 @@ declare module 'native:wifi' {
446
453
  getPowerSave(): string
447
454
  setPowerSave(mode: string): R<void>
448
455
  getCountry(): string | undefined
449
- setCountry(cc: string): R<void>
450
456
  }
451
457
 
452
458
  export declare const Wifi: {
@@ -0,0 +1,39 @@
1
+ import {Observable} from 'native:observable'
2
+
3
+ import type {Subscriber} from './types.js'
4
+
5
+ /* Lazy-attach Observable: native.on is only called once a JS subscriber
6
+ * appears, and native.off runs when the last one unsubscribes. Code paths
7
+ * that import the host module (wifi/ble/...) but don't subscribe take the
8
+ * exact same internal-RAM path as before observables existed — important
9
+ * because mbedTLS handshake needs ~16 KB contiguous internal SRAM and each
10
+ * eager closure pinned at module load chips into that headroom. */
11
+
12
+ interface NativeEventSource {
13
+ on(event: string, listener: (...args: unknown[]) => void): void
14
+ off(event: string, listener: (...args: unknown[]) => void): void
15
+ }
16
+
17
+ export function lazyEvent<T>(source: NativeEventSource, eventName: string): Observable<T> {
18
+ const subscribers: Subscriber<T>[] = []
19
+ function handler(value: unknown): void {
20
+ /* Snapshot so unsubscribes during dispatch don't shift indices. */
21
+ const snapshot = subscribers.slice()
22
+ for (const s of snapshot) {
23
+ if (!s.closed) s.next(value as T)
24
+ }
25
+ }
26
+ return new Observable<T>((sub) => {
27
+ subscribers.push(sub)
28
+ if (subscribers.length === 1) {
29
+ source.on(eventName, handler)
30
+ }
31
+ sub.addTeardown(() => {
32
+ const i = subscribers.indexOf(sub)
33
+ if (i >= 0) subscribers.splice(i, 1)
34
+ if (subscribers.length === 0) {
35
+ source.off(eventName, handler)
36
+ }
37
+ })
38
+ })
39
+ }
@@ -0,0 +1,220 @@
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
+ }
@@ -0,0 +1 @@
1
+ export {Observable} from 'native:observable'
@@ -0,0 +1,144 @@
1
+ /* Operators for `Observable.pipe(...)`. Each is a factory returning a
2
+ * function `(source) => Observable`. Composition is pure pipe — no method
3
+ * chaining on Observable itself.
4
+ *
5
+ * M0 scope: operators apply to non-fallible streams (`Err = never`). For
6
+ * fallible streams (`Observable<Ok, Err>` with Err != never), corresponding
7
+ * Result-aware operators (`mapOk`, `filterOk`, ...) ship when a concrete
8
+ * consumer asks. Today no module produces fallible event streams.
9
+ *
10
+ * Errors: throws inside operator transforms or finalize callbacks are caught
11
+ * at the dispatch boundary, isolated to that subscriber, and re-thrown
12
+ * asynchronously via setTimeout(0). The synchronous producer keeps going
13
+ * (the bad value is dropped, sibling subscribers untouched), and on device
14
+ * the eventual uncaught throw halts the runtime via the existing
15
+ * unhandled-rejection path. Stream errors are panics.
16
+ *
17
+ * See `.claude/plans/observable.md` for the full design.
18
+ */
19
+
20
+ import {Observable as NativeObservable} from 'native:observable'
21
+
22
+ import type {Observable as ObservableT} from './types.js'
23
+
24
+ /* `native:observable` resolves only inside the runtime build; outside
25
+ * (twoslash, host typecheck without internal.d.ts) it falls back to `any`,
26
+ * which collapses pipe/operator inference at use sites. Pin the type to the
27
+ * declared class in `./types.ts` so consumers always see the typed shape. */
28
+ const Observable = NativeObservable as unknown as typeof ObservableT
29
+ type Observable<Ok, Err = never> = ObservableT<Ok, Err>
30
+
31
+ /* Catch a thrown error and re-throw it on the next tick. The synchronous
32
+ * caller keeps going; the error eventually surfaces as an uncaught
33
+ * exception. */
34
+ function panicAsync(err: unknown): void {
35
+ setTimeout(() => {
36
+ throw err
37
+ }, 0)
38
+ }
39
+
40
+ /* Map values through a transform. Throws inside `fn` are caught and
41
+ * scheduled to re-throw on the next tick (panic). The bad value is dropped
42
+ * for that subscription; sibling subscriptions are unaffected. */
43
+ export const map =
44
+ <A, B>(fn: (value: A) => B) =>
45
+ (source: Observable<A>): Observable<B> =>
46
+ new Observable<B>((sub) => {
47
+ const upstream = source.subscribe({
48
+ next: (value) => {
49
+ let next: B
50
+ try {
51
+ next = fn(value)
52
+ } catch (err) {
53
+ panicAsync(err)
54
+ return
55
+ }
56
+ sub.next(next)
57
+ },
58
+ complete: () => sub.complete(),
59
+ })
60
+ sub.addTeardown(() => upstream.unsubscribe())
61
+ })
62
+
63
+ /* Pass through values matching `pred`. `pred` errors panic asynchronously. */
64
+ export const filter =
65
+ <A>(pred: (value: A) => boolean) =>
66
+ (source: Observable<A>): Observable<A> =>
67
+ new Observable<A>((sub) => {
68
+ const upstream = source.subscribe({
69
+ next: (value) => {
70
+ let keep: boolean
71
+ try {
72
+ keep = pred(value)
73
+ } catch (err) {
74
+ panicAsync(err)
75
+ return
76
+ }
77
+ if (keep) sub.next(value)
78
+ },
79
+ complete: () => sub.complete(),
80
+ })
81
+ sub.addTeardown(() => upstream.unsubscribe())
82
+ })
83
+
84
+ /* Take at most n values, then complete. n <= 0 completes immediately. */
85
+ export const take =
86
+ (n: number) =>
87
+ <A>(source: Observable<A>): Observable<A> =>
88
+ new Observable<A>((sub) => {
89
+ if (n <= 0) {
90
+ sub.complete()
91
+ return
92
+ }
93
+ let remaining = n
94
+ const upstream = source.subscribe({
95
+ next: (value) => {
96
+ if (remaining <= 0) return
97
+ remaining--
98
+ sub.next(value)
99
+ if (remaining === 0) sub.complete()
100
+ },
101
+ complete: () => sub.complete(),
102
+ })
103
+ sub.addTeardown(() => upstream.unsubscribe())
104
+ })
105
+
106
+ /* Stop emitting when `notifier` emits its first value. Notifier completing
107
+ * without emitting is NOT a trigger — primary keeps going. */
108
+ export const takeUntil =
109
+ (notifier: Observable<unknown, unknown>) =>
110
+ <A>(source: Observable<A>): Observable<A> =>
111
+ new Observable<A>((sub) => {
112
+ const upstream = source.subscribe({
113
+ next: (value) => sub.next(value),
114
+ complete: () => sub.complete(),
115
+ })
116
+ const notifierSub = notifier.subscribe({
117
+ next: () => sub.complete(),
118
+ })
119
+ sub.addTeardown(() => {
120
+ notifierSub.unsubscribe()
121
+ upstream.unsubscribe()
122
+ })
123
+ })
124
+
125
+ /* Run `fn` when the subscription ends for any reason (unsubscribe or
126
+ * natural completion). Throws inside `fn` are caught and panic
127
+ * asynchronously, so subsequent teardowns still run. RxJS naming. */
128
+ export const finalize =
129
+ (fn: () => void) =>
130
+ <A>(source: Observable<A>): Observable<A> =>
131
+ new Observable<A>((sub) => {
132
+ const upstream = source.subscribe({
133
+ next: (value) => sub.next(value),
134
+ complete: () => sub.complete(),
135
+ })
136
+ sub.addTeardown(() => {
137
+ upstream.unsubscribe()
138
+ try {
139
+ fn()
140
+ } catch (err) {
141
+ panicAsync(err)
142
+ }
143
+ })
144
+ })
@@ -0,0 +1,80 @@
1
+ import type {Result} from '../result/types.js'
2
+
3
+ /* Push-shaped, composable event stream. See observable.md (worktree branch)
4
+ * for the full design. */
5
+
6
+ export type NextArg<Ok, Err> = [Err] extends [never] ? Ok : Result<Ok, Err>
7
+
8
+ export type Observer<Ok, Err = never> = [Err] extends [never]
9
+ ? {next?: (value: Ok) => void; complete?: () => void}
10
+ : {next?: (value: Result<Ok, Err>) => void; complete?: () => void}
11
+
12
+ export type NextFn<Ok, Err = never> = [Err] extends [never]
13
+ ? (value: Ok) => void
14
+ : (value: Result<Ok, Err>) => void
15
+
16
+ export interface Subscriber<Ok, Err = never> {
17
+ next(value: NextArg<Ok, Err>): void
18
+ complete(): void
19
+ addTeardown(fn: () => void): void
20
+ readonly closed: boolean
21
+ }
22
+
23
+ export type SubscribeCallback<Ok, Err> = (subscriber: Subscriber<Ok, Err>) => void
24
+
25
+ export interface Subscription {
26
+ unsubscribe(): void
27
+ }
28
+
29
+ export type OperatorFunction<TIn, EIn, TOut, EOut> = (
30
+ source: Observable<TIn, EIn>,
31
+ ) => Observable<TOut, EOut>
32
+
33
+ export declare class Observable<Ok, Err = never> {
34
+ constructor(cb: SubscribeCallback<Ok, Err>)
35
+ subscribe(observer?: Observer<Ok, Err> | NextFn<Ok, Err>): Subscription
36
+
37
+ pipe(): Observable<Ok, Err>
38
+ pipe<A>(op1: OperatorFunction<Ok, Err, A, Err>): Observable<A, Err>
39
+ pipe<A, B>(
40
+ op1: OperatorFunction<Ok, Err, A, Err>,
41
+ op2: OperatorFunction<A, Err, B, Err>,
42
+ ): Observable<B, Err>
43
+ pipe<A, B, C>(
44
+ op1: OperatorFunction<Ok, Err, A, Err>,
45
+ op2: OperatorFunction<A, Err, B, Err>,
46
+ op3: OperatorFunction<B, Err, C, Err>,
47
+ ): Observable<C, Err>
48
+ pipe<A, B, C, D>(
49
+ op1: OperatorFunction<Ok, Err, A, Err>,
50
+ op2: OperatorFunction<A, Err, B, Err>,
51
+ op3: OperatorFunction<B, Err, C, Err>,
52
+ op4: OperatorFunction<C, Err, D, Err>,
53
+ ): Observable<D, Err>
54
+ pipe<A, B, C, D, E>(
55
+ op1: OperatorFunction<Ok, Err, A, Err>,
56
+ op2: OperatorFunction<A, Err, B, Err>,
57
+ op3: OperatorFunction<B, Err, C, Err>,
58
+ op4: OperatorFunction<C, Err, D, Err>,
59
+ op5: OperatorFunction<D, Err, E, Err>,
60
+ ): Observable<E, Err>
61
+ pipe<A, B, C, D, E, F>(
62
+ op1: OperatorFunction<Ok, Err, A, Err>,
63
+ op2: OperatorFunction<A, Err, B, Err>,
64
+ op3: OperatorFunction<B, Err, C, Err>,
65
+ op4: OperatorFunction<C, Err, D, Err>,
66
+ op5: OperatorFunction<D, Err, E, Err>,
67
+ op6: OperatorFunction<E, Err, F, Err>,
68
+ ): Observable<F, Err>
69
+
70
+ static from<X, E>(p: Promise<Result<X, E>>): Observable<X, E>
71
+ static from<T>(p: Promise<T>): Observable<T, never>
72
+ static from<T>(it: Iterable<T>): Observable<T, never>
73
+ static from<Ok, Err>(o: Observable<Ok, Err>): Observable<Ok, Err>
74
+
75
+ static withEmitters<Ok, Err = never>(): {
76
+ observable: Observable<Ok, Err>
77
+ next: (value: NextArg<Ok, Err>) => void
78
+ complete: () => void
79
+ }
80
+ }