@macula-io/ts 0.18.0 → 0.20.0

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/dist/session.d.ts DELETED
@@ -1,489 +0,0 @@
1
- import { DhtRecordType, type DhtRecord } from "./dht.js";
2
- import type { AdvertiseDirectOptions, DirectDialTarget } from "./directdial.js";
3
- import { Identity } from "./identity.js";
4
- import type { PublishOptions, PubsubEvent, SubscribeOptions } from "./pubsub.js";
5
- import { type BytesOutput, type JsonValue } from "./rpc.js";
6
- import { Ucan } from "./ucan.js";
7
- /** Options for Session.call()/callWithUcan(). */
8
- export interface CallOptions {
9
- /** How long to wait for a RESULT/ERROR before giving up, in
10
- * milliseconds. Also becomes the wire's own `deadline_ms` (now +
11
- * this) -- see rpc.ts's DEFAULT_CALL_TIMEOUT_MS for why both share
12
- * one number. */
13
- deadlineMs?: number;
14
- /** The realm this CALL is scoped to, as a 64-character lowercase (or
15
- * uppercase, case-insensitive) hex string -- 32 bytes, the same
16
- * hex-string convention DhtRecord's own key/version/signature fields
17
- * already use (dht.ts), not native.*'s raw-byte Uint8Array (this
18
- * class converts internally -- see realmBytesFromHex below). Omitted
19
- * means the all-zero realm, the same default every mesh operation in
20
- * this SDK used exclusively before this option existed, and what
21
- * macula-go's own realm32OrZero (cabi/main.go) falls back to when no
22
- * realm pointer is given.
23
- *
24
- * Must match whatever realm the target procedure is actually served
25
- * under, or this CALL comes back `unknown_next_peer` even for a
26
- * procedure genuinely advertised elsewhere -- realm is an exact-match
27
- * routing key, not a hierarchy or a default-realm fallback. This
28
- * class's own `serve()` always advertises under the all-zero realm
29
- * (a separate, later gap -- see README.md's "What's explicitly not
30
- * yet implemented"); calling with a non-zero realm only reaches a
31
- * provider serving under that same realm through some other means. */
32
- realm?: string;
33
- /** How bytes in the RESULT payload come back: "hex" (the default) or
34
- * "tagged" -- see rpc.ts's BytesOutput. */
35
- bytes?: BytesOutput;
36
- }
37
- /** Options for Session.serve(). */
38
- export interface ServeOptions {
39
- /** How bytes in each inbound CALL's payload reach the handler: "hex"
40
- * (the default) or "tagged" -- see rpc.ts's BytesOutput. */
41
- bytes?: BytesOutput;
42
- }
43
- /** Decodes CallOptions.realm/PublishOptions.realm/SubscribeOptions.realm's
44
- * public hex-string convention into the 32-byte Uint8Array native.*
45
- * already accepts for its own `realm` parameter on sessionCall/
46
- * sessionCallWithUcan/sessionPublish/sessionSubscribeStart -- cabi/rpc.go,
47
- * cabi/pubsub.go, and addon/binding.cc were, on inspection, already fully
48
- * wired for an optional realm all the way through (ReadOptionalRealm in
49
- * binding.cc, realm32OrZero in cabi/main.go); this class's own methods
50
- * were the only place still hardcoding `undefined`. Kept as a hex string
51
- * at the PUBLIC surface specifically to match DhtRecord's existing
52
- * convention, while reusing that already-working raw-byte plumbing
53
- * beneath it unchanged, rather than re-threading the FFI boundary itself
54
- * as a second, redundant string convention alongside it.
55
- * `undefined` in, `undefined` out -- the all-zero-realm default. */
56
- export declare function realmBytesFromHex(realm: string | undefined): Uint8Array | undefined;
57
- export declare class Session {
58
- #private;
59
- private constructor();
60
- /** Dials host:port and completes the full CONNECT/HELLO handshake
61
- * against a real macula-station, via macula-go's connection.Connect
62
- * (WebPKI trust -- standard CA-bundle validation, matching what the
63
- * production fleet actually presents; Pinned/Insecure trust modes
64
- * aren't exposed yet).
65
- *
66
- * This is real network I/O -- a QUIC dial plus a signed round trip,
67
- * bounded by macula-go's own ~30s handshake timeout -- so it's async
68
- * on both sides of the FFI boundary (see addon/binding.cc's
69
- * ConnectWorker): awaiting this never blocks Node's event loop.
70
- *
71
- * `identity` must stay non-disposed for the life of the returned
72
- * Session -- close() needs it again to sign GOODBYE. */
73
- static connect(host: string, port: number, identity: Identity): Promise<Session>;
74
- /** The address this session's underlying QUIC connection is with. */
75
- get remoteAddr(): string;
76
- /** The station's HELLO-verified 32-byte NodeID (Ed25519 public key)
77
- * -- proof, beyond "connect() didn't throw", that this is a real,
78
- * application-layer-verified session and not just a QUIC/TLS
79
- * handshake: frame.Verify already checked this NodeID's signature
80
- * over the HELLO frame inside connect(), this just surfaces it. */
81
- get stationNodeId(): Uint8Array;
82
- /** Sends a signed GOODBYE and closes the connection. Safe to call
83
- * more than once -- a second call is a no-op, matching Identity's
84
- * dispose() convention. `identity` must be the same (non-disposed)
85
- * identity used to open this session; Close needs it to sign
86
- * GOODBYE. Like connect(), this is real network I/O (a drain sleep
87
- * plus a write) and runs off the main thread on the native side.
88
- *
89
- * Stops an active subscribe() FIRST, if there is one, sending its
90
- * UNSUBSCRIBE over the still-open connection before that connection
91
- * goes away -- unlike every other resource this SDK hands out (an
92
- * Identity, an unstopped serve() loop), an unstopped subscription
93
- * left dangling here does not just leak memory: its background reader
94
- * goroutine holds a live Napi::ThreadSafeFunction, which deliberately
95
- * keeps Node's event loop alive on its own (see subscribe()'s own doc
96
- * -- a program that does nothing but subscribe() and wait needs
97
- * exactly this to stay alive for events to arrive at all). Verified
98
- * live: closing a Session out from under an active subscription
99
- * without this hung the process forever, not merely leaked a handle
100
- * -- close() closing it first is what makes "forgot to call the
101
- * returned stop()" fail safe instead of fail hung. */
102
- close(identity: Identity, reason?: string): Promise<void>;
103
- /** Caller role: sends a signed CALL for `procedure` and waits for the
104
- * matching RESULT or ERROR (macula-go's connection.Session.Call).
105
- * `payload` is JSON, converted to a cbor.Value on the Go side
106
- * (cabi/wirevalue.go) -- see rpc.ts's JsonValue for the wire's own
107
- * restrictions (no booleans; bytes go in as `{"$bytes": base64}` and
108
- * come back as `opts.bytes` asks, hex by default).
109
- *
110
- * Resolves with the RESULT's payload on success. Rejects with a
111
- * MaculaCallError (rpc.ts) when a real BOLT#4 ERROR frame came back
112
- * instead -- e.g. `unknown_next_peer` for a procedure nobody has
113
- * advertised -- carrying its code/name/retryable triple rather than
114
- * a generic message; rejects with a plain Error for everything that
115
- * isn't a wire-level answer at all (a local timeout, a dead session,
116
- * a payload the wire can't represent).
117
- *
118
- * Real network I/O -- a signed frame out and a wait for the reply,
119
- * up to opts.deadlineMs -- so this runs off the main thread on the
120
- * native side, like connect()/close(). Do not call this
121
- * concurrently with an active serve() on the SAME Session: both read
122
- * frames off the one shared control stream, and macula-go's own
123
- * ServeOneCall/Call docs both warn that mixing roles on one
124
- * connection races (an unrelated frame arriving first is discarded,
125
- * not queued) -- open a second Session for the other role instead,
126
- * exactly what this SDK's own live test does. */
127
- call(procedure: string, payload: JsonValue, opts?: CallOptions): Promise<JsonValue>;
128
- /** Caller role: call(), attaching `ucanToken` to the outgoing CALL
129
- * (macula-go's `connection.Session.CallWithUCAN`) -- for invoking a
130
- * procedure a provider has gated behind a `ucan.Policy.Required`
131
- * policy on its own side (this SDK does not implement that provider
132
- * side itself -- see ucan.ts's own module doc). `ucanToken` may be a
133
- * `Ucan` (as returned by `Ucan.mint()`/`Ucan.decode()` -- its `.token`
134
- * is attached) or a raw token string directly.
135
- *
136
- * Same resolve/reject shape as `call()` in every other respect: the
137
- * RESULT's payload on success, a `MaculaCallError` for a real BOLT#4
138
- * ERROR frame (e.g. `unauthorized` for a token that fails the
139
- * provider's policy check, or `unknown_next_peer` for a procedure
140
- * nobody has advertised), a plain `Error` for anything that never got
141
- * a wire-level answer at all.
142
- *
143
- * Deliberately attaches whatever token bytes it is given with NO local
144
- * check relating this Session's own identity to `ucanToken`'s `aud`
145
- * claim -- see ucan.ts's own module doc for why: macula's UCAN gate is
146
- * a bearer-token check (signature + expiry against the token's
147
- * issuer), and the real wire-level gate never looks at the caller's
148
- * identity against `aud` either. A client-side guard here would both
149
- * reject configurations the mesh accepts fine and misrepresent a
150
- * security property that isn't actually enforced.
151
- *
152
- * Real network I/O, off the main thread on the native side, and
153
- * subject to the identical same-Session exclusivity rule as `call()`
154
- * (`#requireHandleNotServing`) -- both end up on the same shared
155
- * control stream. */
156
- callWithUcan(procedure: string, payload: JsonValue, ucanToken: string | Ucan, opts?: CallOptions): Promise<JsonValue>;
157
- /** Provider role: advertises `procedure` (macula-go's
158
- * connection.Session.Advertise) and answers inbound CALLs against it
159
- * forever, invoking `handler` for each one (payload in, reply or
160
- * thrown error out -- `handler` may be async), until the returned
161
- * stop function is called. A thrown/rejected `handler` becomes a
162
- * BOLT#4 UnknownError reply carrying the thrown value's message as
163
- * detail (matching macula-go's connection/serve.go, which maps every
164
- * handler error to that one code); a `handler` that panics on the Go
165
- * side instead (not reachable from here -- there is no Go code
166
- * between this and the JS handler) would map to
167
- * TemporaryRelayFailure, per that same file.
168
- *
169
- * Only one serve() registration is allowed per Session at a time --
170
- * see call()'s own doc on why mixing roles (or two server loops) on
171
- * one connection is unsafe, not just inadvisable; open a second
172
- * Session for a second procedure instead of trying to serve two
173
- * procedures off one.
174
- *
175
- * `opts.bytes` picks how bytes in each inbound CALL's payload reach
176
- * `handler`: "hex" (the default) or "tagged" (rpc.ts's BytesOutput).
177
- * A reply follows the same JsonValue rules as any payload, so a
178
- * tagged value can be returned as it arrived.
179
- *
180
- * The returned stop function is async: it unadvertises the
181
- * procedure (a real network write) and waits for the current poll
182
- * tick to finish (up to rpc.ts's SERVE_POLL_MS) before resolving --
183
- * there is no way to interrupt a Go-side wait already in flight, the
184
- * same bounded-latency shape macula-go's own ServeForever has
185
- * internally. */
186
- serve(procedure: string, handler: (payload: JsonValue) => JsonValue | Promise<JsonValue>, opts?: ServeOptions): Promise<() => Promise<void>>;
187
- /** DHT: returns every record of `recordType` currently visible from
188
- * the station this Session is connected to (macula-go's
189
- * dht.FindRecordsByType, via a signed CALL to `_dht.find_records_by_type`
190
- * under the DHT's own reserved realm -- threaded internally, this
191
- * method never touches a realm itself). Coverage depends on that
192
- * station's own view of the mesh, not a global guarantee. Neither
193
- * this nor findRecord/findRecords verifies a returned record's
194
- * signature or checks its expiry -- see DhtRecord's own doc (dht.ts).
195
- *
196
- * Real network I/O, off the main thread on the native side, exactly
197
- * like call() -- and subject to the same same-Session exclusivity
198
- * rule as call() (see #requireHandleNotServing's own doc): do not
199
- * call this while serve() is active on the same Session. */
200
- findRecordsByType(recordType: DhtRecordType | number): Promise<DhtRecord[]>;
201
- /** DHT: returns every record stored at `key` -- the full
202
- * signer-deduped multiset at that storage key (macula-go's
203
- * dht.FindRecords), e.g. every procedure_advertisement for one
204
- * procedure, not just the first one found. `key` must be exactly 32
205
- * bytes -- see dht/record.go's ProcedureKey/StationEndpointKey/
206
- * ContentKey (macula-go) for how those are derived from the thing
207
- * being looked up. Same I/O and exclusivity notes as
208
- * findRecordsByType(). */
209
- findRecords(key: Uint8Array): Promise<DhtRecord[]>;
210
- /** DHT: returns ONE record by storage key (macula-go's
211
- * dht.FindRecord). Resolves `null` when none exists -- mirrors
212
- * macula-go's own dht.ErrNotFound, translated to a value instead of a
213
- * thrown error since "not found" is an expected, routine outcome
214
- * here, not exceptional. Same I/O and exclusivity notes as
215
- * findRecordsByType(). */
216
- findRecord(key: Uint8Array): Promise<DhtRecord | null>;
217
- /** DHT: builds the realm-qualified discovery URI (macula-go's
218
- * dht.DiscoveryURI), then builds (dht.NewProcedureAdvertisement),
219
- * signs, and stores a procedure_advertisement naming this Session's
220
- * own Identity as `procedure`'s advertiser and `servingStation` (32
221
- * bytes -- a station's NodeID, typically this Session's own
222
- * `stationNodeId`) as the station that serves it.
223
- *
224
- * `realm` should be the SAME realm `procedure` is (or will be) served
225
- * under via `serve()` -- defaults to the all-zero realm, matching
226
- * `call()`'s own default (see CallOptions.realm's own doc: `call()`/
227
- * `callWithUcan()`/`publish()`/`subscribe()` now all take an optional
228
- * realm; `serve()`/`advertise` remain all-zero-realm-only for this
229
- * slice, unchanged). This method
230
- * builds the qualified URI itself (dht.DiscoveryURI) rather than
231
- * taking a pre-qualified string, since NewProcedureAdvertisement's own
232
- * doc is explicit that "the advertiser and the resolver must derive
233
- * the identical URI or the DHT storage key will not agree" -- a
234
- * caller-supplied pre-qualified string invites exactly that class of
235
- * bug (verified directly: an earlier draft of this SDK's own live
236
- * test got this wrong by hand-qualifying the URI itself, and its
237
- * following findRecord() came back not-found until this method built
238
- * the URI internally instead).
239
- *
240
- * Wraps macula-go's REAL constructor rather than a generic
241
- * JSON-payload builder deliberately -- see cabi/dht.go's own doc on
242
- * why: two of this record type's payload fields (advertiser_node,
243
- * serving_station) are raw 32-byte pubkeys that must be actual CBOR
244
- * byte strings for a real resolver to read, and this SDK's generic
245
- * JSON<->cbor.Value conversion (rpc.ts's JsonValue, wirevalue.go) has
246
- * no way to guarantee that on its own: it produces them only when
247
- * every caller tags both as `{"$bytes": base64}`, and a plain string
248
- * silently becomes CBOR text. Typed arguments rule that mistake out
249
- * (see DhtRecord's own doc). `ttlMs` defaults to DHT_DEFAULT_TTL_MS
250
- * (48h). Resolves with the signed record actually stored. Same I/O
251
- * and exclusivity notes as findRecordsByType(). */
252
- putProcedureAdvertisement(procedure: string, servingStation: Uint8Array, opts?: {
253
- realm?: Uint8Array;
254
- ttlMs?: number;
255
- }): Promise<DhtRecord>;
256
- /** DHT: builds (macula-go's dht.NewContentAnnouncement), signs, and
257
- * stores a content_announcement naming this Session's own Identity as
258
- * `mcid`'s (34 bytes) announcer, reachable at `endpoint` (a dialable
259
- * seed URL, e.g. "https://host:4433" -- NOT a station_endpoint's
260
- * split host/port). Same reasoning as putProcedureAdvertisement()'s
261
- * own doc for wrapping macula-go's real constructor instead of a
262
- * generic JSON-payload builder (announcer_node/mcid are the same kind
263
- * of raw-byte field). `ttlMs` defaults to DHT_DEFAULT_TTL_MS (48h).
264
- * Same I/O and exclusivity notes as findRecordsByType(). */
265
- putContentAnnouncement(mcid: Uint8Array, endpoint: string, ttlMs?: number): Promise<DhtRecord>;
266
- /** Direct-dial (caller side): finds `procedure`'s currently-advertised
267
- * serving station and its dialable host/port (macula-go's
268
- * `directdial.Resolve`), via this Session used only to query the DHT
269
- * -- it does not need to be connected to the station that ends up
270
- * serving `procedure`. Every advertisement that verifies is a
271
- * candidate, and the DHT is asked again until one's station endpoint
272
- * resolves or `opts.deadlineMs` passes (10 s when unset, macula-go's
273
- * `directdial.DefaultResolveTimeout`); a `procedure` nobody ever called
274
- * `advertiseDirect()` for rejects once that time is up (a plain `Error`
275
- * wrapping macula-go's `ErrProcedureNotAdvertised`), never a hang.
276
- *
277
- * `opts.realm` must match whatever realm `procedure` was
278
- * `advertiseDirect()`d under, or the discovery URI the two sides
279
- * derive disagrees and this rejects the same way as if nothing was
280
- * ever advertised at all -- see `AdvertiseDirectOptions.realm`'s own
281
- * doc (directdial.ts). `callDirect()`/`callDirectWithUcan()` call this
282
- * internally; it's exposed on its own for callers that just want the
283
- * resolved station/host/port without also dialing and calling it (e.g.
284
- * diagnostics).
285
- *
286
- * Real network I/O, off the main thread on the native side, subject to
287
- * the same same-Session exclusivity rule as `call()`/the DHT methods
288
- * (`#requireHandleNotServing`). */
289
- resolveDirect(procedure: string, opts?: {
290
- realm?: string;
291
- deadlineMs?: number;
292
- }): Promise<DirectDialTarget>;
293
- /** Direct-dial (caller side): resolves `procedure`'s provider (via
294
- * `resolveDirect()`, through this Session) and calls it there, in one
295
- * hop, in a SEPARATE connection macula-go opens, application-layer-pins
296
- * against the resolved station identity, and closes again, entirely
297
- * internally (macula-go's `directdial.Call`) -- this Session's own
298
- * connection is never touched beyond the DHT lookup. The provider must
299
- * have `advertiseDirect()`d `procedure` (or the Erlang/Rust/Go
300
- * equivalent) -- a plain `serve()`-side `Advertise` alone publishes no
301
- * discoverable DHT record, and this rejects with `ErrProcedureNotAdvertised`.
302
- *
303
- * Same resolve/reject shape as `call()`: the RESULT's payload on
304
- * success, a `MaculaCallError` (rpc.ts) for a real BOLT#4 ERROR frame
305
- * from the resolved provider, a plain `Error` for a resolve failure, a
306
- * dial failure, an identity-trust violation (the dialed peer proved a
307
- * DIFFERENT identity than the DHT chain resolved), or anything else
308
- * that never got a wire-level answer at all.
309
- *
310
- * Real network I/O (DHT lookups, a fresh QUIC dial, then the CALL
311
- * itself) -- off the main thread on the native side, subject to the
312
- * same same-Session exclusivity rule as `call()` for THIS Session's own
313
- * DHT-querying use (the separate dialed connection this opens
314
- * internally is not this Session and is never exposed as one). */
315
- callDirect(procedure: string, payload: JsonValue, opts?: CallOptions): Promise<JsonValue>;
316
- /** Direct-dial (caller side): `callDirect()`, attaching `ucanToken` to
317
- * the outgoing CALL (macula-go's `directdial.CallWithUCAN`) -- for
318
- * reaching a direct-dial-advertised procedure a provider has gated
319
- * behind a `ucan.Policy.Required` policy. Every hecate-om capability is
320
- * advertised via `advertiseDirect()` specifically so it's reachable
321
- * ONLY this way -- plain `callDirect()` cannot resolve or attach a
322
- * token to it. Same `ucanToken` shape, no-audience-check, and
323
- * resolve/reject conventions as `callWithUcan()` (see both that
324
- * method's and ucan.ts's own doc for why: macula's UCAN gate is a
325
- * bearer-token check, not an audience match).
326
- *
327
- * Same I/O and exclusivity notes as `callDirect()`. */
328
- callDirectWithUcan(procedure: string, payload: JsonValue, ucanToken: string | Ucan, opts?: CallOptions): Promise<JsonValue>;
329
- /** Direct-dial (provider side): publishes `procedure` as
330
- * direct-dial-reachable at THIS Session's own currently-connected
331
- * station (macula-go's `directdial.AdvertiseDirect`) -- a plain
332
- * ADVERTISE (so an inbound CALL routed here via the DHT-resolved path
333
- * still has something to route to -- a real bug macula-go fixed live
334
- * 2026-08-30: skipping this let resolve+dial complete cleanly against a
335
- * station with nothing registered to answer, `ServeOneCall` never
336
- * seeing it) plus a signed `procedure_advertisement` DHT record naming
337
- * this Session's own station.
338
- *
339
- * Unlike `serve()`'s own internal advertise (still all-zero-realm-only
340
- * in this SDK -- see `serve()`'s own doc), this method threads
341
- * `opts.realm` all the way through, matching `directdial.AdvertiseDirect`
342
- * itself: `resolveDirect()`/`callDirect()`/`callDirectWithUcan()` only
343
- * ever reach a procedure `advertiseDirect()`d under the EXACT SAME
344
- * realm.
345
- *
346
- * A station's registration for a procedure does not survive the
347
- * connection that sent it being replaced -- a long-lived provider needs
348
- * to call this again on its own schedule; see `keepAdvertisedDirect()`
349
- * (directdial.ts) for that loop, built on top of this method rather
350
- * than duplicating macula-go's own `KeepAdvertisedDirect` here.
351
- *
352
- * **Must NOT be called on a Session that is also actively
353
- * `serve()`-ing** -- enforced by the same `#requireHandleNotServing`
354
- * guard `call()`/the DHT methods use, for the identical reason: this
355
- * method's own `PutRecord` CALL reads a RESULT off the same shared
356
- * control stream `serve()`'s poll loop is also reading, and the two
357
- * would race (matches `directdial.AdvertiseDirect`'s own doc). A
358
- * provider that also serves `procedure` needs a SEPARATE Session (and
359
- * identity -- this fleet enforces one connection per identity, kicking
360
- * whichever connects second) to call this on.
361
- *
362
- * Real network I/O (a fire-and-forget ADVERTISE write plus a signed
363
- * PutRecord CALL) -- off the main thread on the native side. */
364
- advertiseDirect(procedure: string, opts?: AdvertiseDirectOptions): Promise<void>;
365
- /** Pubsub: sends a signed PUBLISH for `topic` (macula-go's
366
- * connection.Session.Publish, which also attaches the end-to-end
367
- * publisher_sig a relayed EVENT needs to survive beyond one hop --
368
- * see that method's own doc, not reimplemented here). `payload`
369
- * follows the same JsonValue rules as call()'s payload (no boolean,
370
- * embedded bytes as `{"$bytes": base64}`). Fire-and-forget: Publish's
371
- * own doc is explicit that no reply is expected on the wire, so the
372
- * returned Promise resolving only means this Session's own frame was
373
- * encoded, signed, and sent -- never that any subscriber received it
374
- * (macula-go's own live test for this, TestLivePubSubRoundTrip,
375
- * observes a subscriber's own publish arriving back at it rather than
376
- * asserting it as a hard guarantee, for the same reason).
377
- *
378
- * Deliberately NOT guarded by the same-Session exclusivity rule
379
- * call()/serve()/subscribe()/the DHT methods share (see
380
- * #requireHandleNotServing's own doc) -- publish() only ever writes,
381
- * never reads off the shared control stream, so it does not race a
382
- * concurrent serve()/subscribe() the way those do, and can run safely
383
- * on the SAME Session a subscribe() of its own is active on -- exactly
384
- * what a subscriber publishing to (and receiving) its own topic needs.
385
- *
386
- * `opts.realm` (see CallOptions.realm's own doc for the hex-string
387
- * format and exact-match semantics) scopes which realm this EVENT is
388
- * published under -- omitted means the all-zero realm, this SDK's
389
- * sole default before this option existed. A subscribe() only
390
- * receives this event if its own realm matches exactly.
391
- *
392
- * Real network I/O (one signed frame write) -- runs off the main
393
- * thread on the native side, like every other network-touching method
394
- * here. */
395
- publish(topic: string, payload: JsonValue, opts?: PublishOptions): Promise<void>;
396
- /** Pubsub: sends a signed SUBSCRIBE for `topic`, then delivers every
397
- * inbound EVENT for it to `handler` -- macula-go's own
398
- * connection.Session.RunSubscriber (connection/subscriber.go) drives
399
- * the actual read loop on the Go side, in a background goroutine, NOT
400
- * reimplemented on top of a hand-rolled poll here (see cabi/pubsub.go's
401
- * own doc for why RunSubscriber specifically, over the lower-level
402
- * RecvEvent). Delivery is Go-driven, not JS-driven: unlike serve()'s
403
- * poll loop, nothing on this side calls into the native layer
404
- * repeatedly to ask "did anything arrive yet" -- the addon calls INTO
405
- * this handler asynchronously, via a Napi::ThreadSafeFunction wired to
406
- * that background goroutine, whenever an EVENT actually shows up.
407
- *
408
- * Only one subscribe() (and no active serve()) is allowed per Session
409
- * at a time -- same reasoning as serve()'s own one-at-a-time rule
410
- * (#requireHandleNotServing's own doc): the background reader and any
411
- * other read off this Session's shared control stream would race.
412
- * Open a second Session for a second topic (or to serve/call
413
- * concurrently) instead.
414
- *
415
- * Resolves with an async stop() function once the initial SUBSCRIBE
416
- * has been sent and the background reader has started. stop() sends
417
- * the matching UNSUBSCRIBE and does not resolve until the Go-side
418
- * reader goroutine has genuinely exited -- calling it and awaiting the
419
- * result is the actual guarantee that no further `handler` call can
420
- * happen afterward, not just that one was requested.
421
- *
422
- * Real network I/O (the initial SUBSCRIBE send, and stop()'s
423
- * UNSUBSCRIBE) -- both run off the main thread on the native side.
424
- *
425
- * `opts.realm` (see CallOptions.realm's own doc for the hex-string
426
- * format and exact-match semantics) scopes which realm this SUBSCRIBE
427
- * listens on -- omitted means the all-zero realm, this SDK's sole
428
- * default before this option existed. Only an EVENT published under
429
- * the SAME realm is ever delivered to `handler`.
430
- *
431
- * `opts.bytes` picks how bytes in each event's payload reach
432
- * `handler`: "hex" (the default) or "tagged" (rpc.ts's BytesOutput).
433
- *
434
- * If the underlying connection dies (or any other transport error
435
- * ends the background reader) rather than the returned stop() being
436
- * called, this subscription tears itself down automatically -- the
437
- * native handle is released and this Session is left closable and
438
- * reusable for a fresh subscribe()/serve()/call() -- and, if
439
- * provided, `opts.onClosed` is called once with the error. Verified
440
- * live that, without this, such a subscription went silent forever
441
- * (no further events, no error) and left this Session's handle
442
- * permanently open even after close(). */
443
- subscribe(topic: string, handler: (evt: PubsubEvent) => void, opts?: SubscribeOptions): Promise<() => Promise<void>>;
444
- /** Content transfer: stores `data` (macula-go's content.Put, on this
445
- * Session's own fresh dedicated QUIC stream -- Session.
446
- * OpenDedicatedStream on the Go side, NOT the shared control stream
447
- * call()/serve()/the DHT methods/subscribe() all read from), chunking
448
- * automatically above manifest.DefaultChunkSize (256 KiB) and
449
- * returning the hex-encoded mcid it's now addressable by. `name` is
450
- * used ONLY on the chunked path (attached to the resulting manifest)
451
- * -- a single-block put ignores it entirely, matching content.Put's
452
- * own documented behavior; leave it unset for small blobs.
453
- *
454
- * NOT durable object storage -- see content.ts's own module doc: a
455
- * station may forget this content later, and there is no list/delete
456
- * operation. Treat this as "hand these bytes to a peer once."
457
- *
458
- * Because Put opens its own dedicated stream instead of reading the
459
- * shared control stream, this is, unlike call()/serve()/the DHT
460
- * methods/subscribe(), never subject to Session's same-Session
461
- * exclusivity guard (#requireHandleNotServing) -- it can run
462
- * concurrently with an active serve()/subscribe() (or another
463
- * putContent()/getContent()) on the same Session without racing.
464
- *
465
- * Real network I/O (one or more signed CALLs on the new stream) --
466
- * runs off the main thread on the native side, like every other
467
- * network-touching method here. */
468
- putContent(data: Uint8Array, name?: string): Promise<{
469
- mcid: string;
470
- }>;
471
- /** Content transfer: fetches and verifies (macula-go's content.Get,
472
- * on its own fresh dedicated QUIC stream, same reasoning as
473
- * putContent() -- including content.Get's own client-side hash
474
- * re-check against `mcid`: a station may only be relaying content it
475
- * doesn't itself store, so its answer is never trusted blindly) the
476
- * content addressed by `mcid` (the hex string putContent() returned).
477
- *
478
- * Rejects with ContentNotFoundError (content.ts) specifically when
479
- * the station reports it doesn't know this mcid -- an expected,
480
- * routine outcome for a one-time transfer mechanism with no
481
- * durability guarantee, not a transport failure; every other failure
482
- * (a bad session, a malformed mcid, a real transport error) rejects
483
- * with a plain Error instead.
484
- *
485
- * Same dedicated-stream, no-exclusivity-guard reasoning as
486
- * putContent() -- safe alongside an active serve()/subscribe() on the
487
- * same Session. Real network I/O, runs off the main thread. */
488
- getContent(mcid: string): Promise<Uint8Array>;
489
- }