wenay-common2 1.0.74 → 1.0.76

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/doc/ROADMAP.md CHANGED
@@ -139,12 +139,13 @@ control channel; `authorize` = server-side `canExposeEndpoint`), `createWebRtcCo
139
139
  `acceptWebRtcDirect` (RTCPeerConnection injected as a runtime factory, structural types, no lib.dom),
140
140
  and `serveReplayChannel`/`channelReplayRemote` (replay wire over any ordered channel — the datachannel
141
141
  path bypasses the RPC core by design). Oracle `replay/route-webrtc.test.ts` drives promotion, endpoint
142
- denial, session rejection, and server revoke over both an in-proc hub and a real Socket.IO/RPC wire.
143
- Still open: browser/Node WebRTC glue (step 10 now a one-line `rtc` factory plus media re-emit) and
144
- the account-map lifecycle integration (step 7). Media-side candidates (2026-07-10, after the demo
145
- stand stress test): a real `transport:'webrtc'` media track SDP over the existing signal hub,
146
- media bypassing the socket relay for call-grade smoothness; and `MediaStreamTrackProcessor` capture
147
- for true 30fps (`grabFrame`'s ~50ms serial latency caps snapshot capture at ~15-20fps).
142
+ denial, session rejection, server revoke, and an encoded `Media` `Uint8Array` frame over both the
143
+ direct datachannel and relay fallback. v1.0.76 completes step 10 with a portable binary replay codec
144
+ and the browser/Node `rtc` factory seam; it also completes step 7 with the app-level
145
+ `noStrict(accountMap)` + `createStoreManager` selected-mirror lifecycle oracle. Media-side candidates
146
+ remain a real `transport:'webrtc'` native media track/SFU for call-grade smoothness and
147
+ `MediaStreamTrackProcessor` capture for true 30fps (`grabFrame`'s ~50ms serial latency caps snapshot
148
+ capture at ~15-20fps); they are performance adapters, not route-coordinator prerequisites.
148
149
 
149
150
  Consumption-layer candidates (2026-07-10, author's ask):
150
151
  - ✅ **Call system** — SHIPPED in v1.0.74: `Peer.createCallManager` (ring/accept/decline/hangup as
@@ -0,0 +1,10 @@
1
+ # 1.0.75
2
+
3
+ - Stable RPC path identity: `func`, `space`, and `strict` cache child proxies per client instance, semantic surface, and collision-safe path; `all === func`, dotted keys stay distinct, and the same client keeps identity through schema refresh, reauth, and transient reconnect.
4
+ - Additive hub lifecycle listeners: `connectListen(cb)` / `disconnectListen(cb)` return individual off functions while legacy `onConnect(fn | null)` / `onDisconnect(fn | null)` retain their single-slot behavior. Internal recovery is independent from every public callback slot.
5
+ - Server-side HELLO barrier serializes concurrent STRICT/CALL/PIPE behind async auth resolution, so reconnect cannot resubscribe against a pre-auth facade; compatibility with older servers and clients is unchanged.
6
+ - Transient Socket.IO reconnect through `createRpcClientHub` now preserves logical deduped RPC Listen subscriptions and their local consumers, retires the dead physical generation, and creates exactly one replacement wire subscription after the new handshake. Unsubscribing offline prevents resurrection.
7
+ - Replay subscribers on RPC remotes recover automatically from their own last delivered `seq`: live is restored first, racing envelopes queue, then frame/since catch-up is ordered and deduplicated. Queue policy keeps an ungated live line; producer-declared mini-frames/keyframes may cover a raw seq jump without a logical data gap.
8
+ - Sacred queues remain exact: a retained journal replays every missing envelope, while eviction/no-keyframe is a terminal `onError`; queued events after the hole are never emitted as a false continuation.
9
+ - Hard boundaries stay hard: `close()` / `dispose()` and hub `connect()` / `setToken()` terminate the old generation. Pending or attempted ordinary RPC/pipe calls fail on disconnect and are never retried, avoiding duplicated side effects.
10
+ - Add real Socket.IO reconnect coverage for stable proxy identity, deduped multi-consumer recovery, repeated reconnect generations, sacred eviction, offline dispose, token rotation, and multicast lifecycle listeners.
@@ -0,0 +1,4 @@
1
+ # 1.0.76
2
+
3
+ - Complete the route-coordinator account lifecycle target: `noStrict` runtime account maps compose with `Observe.createStoreManager` to start/stop selected replay mirrors independently; add the focused oracle.
4
+ - Preserve `Uint8Array` payloads in replay-over-datachannel with a portable binary codec, so encoded `Media` frames survive relay → direct WebRTC → relay hand-offs byte-for-byte; extend the real signaling oracle.
@@ -150,16 +150,27 @@ createRpcServerAuto(opts) // canonical: nested object
150
150
  // path stays byte-for-byte (Pkt.MAP only grows additively), plus line/frameLine/since/keyframe/frame — so
151
151
  // upgrading listen -> replayListen is a declaration-site-only change; replaySubscribe(client.func.key) works as is.
152
152
  // opts.replayOpts {pending?, highWater?, lowWater?, pollMs?} arms the per-connection lag gate on frameLine
153
- // (consumer picks policy 'queue'|'frame' at subscribe time); the replay `line` is never throttled (no seq holes).
153
+ // (consumer picks policy 'queue'|'frame' at subscribe time); the replay `line` stays ungated for connected queue-policy live delivery.
154
154
  createRpcServer(opts) // lower-level core
155
155
  createRpcServerAutoDetect(opts) // + legacy/v2 protocol auto-detection (createRpcServerAutoWithProtocolDetection)
156
156
  createRpcServerInProc(...) // in-process fast path (no socket)
157
157
  // clients
158
- createRpcClientHub(opts) + rpc // multiplexing client hub: connect(token)/reauth(token)/onConnect
159
- // alias: hub.setToken->connect
158
+ createRpcClientHub(opts) + rpc // multiplexing client hub: connect(token)/reauth(token)/onConnect/onDisconnect + connectListen/disconnectListen
159
+ // alias: hub.setToken->connect. onConnect/onDisconnect are legacy single-slot setters; the additive
160
+ // *Listen registries return per-listener off functions and cannot overwrite each other or internal recovery.
161
+ // A transient disconnect/reconnect of the SAME Socket.IO object keeps this client generation: after
162
+ // the new route/auth handshake, each active deduped logical Listen gets exactly one new physical attempt.
163
+ // Local consumers and their off handles survive; a consumer removed offline is not resurrected.
164
+ // connect()/setToken() hard-rotates the socket/client generation; close()/dispose() is terminal.
160
165
  client members: func (proxy) · strict (schema-safe) · schema() · auth() · reauth() · onDisconnect()
161
166
  close(reason?, {socketAlive?}) · ready() · init(obj?) · api.subscriptions()
162
167
  // alias: dispose->close · readyStrict->ready · initStrict->init
168
+ // func/space/strict child proxies are cached per createRpcClient instance + surface + lossless RPC path:
169
+ // c.func.a.b === c.func.a.b and c.all === c.func; dotted keys remain distinct from segmented paths.
170
+ // Identity survives strict-schema refresh, reauth and transient reconnect. Hard token rotation creates
171
+ // a fresh client/cache; incompatible wait/surface semantics never share a proxy.
172
+ // Only Listen subscriptions are transport-resumed. Pending/attempted ordinary RPC and pipe calls reject
173
+ // on disconnect and are NEVER replayed: retrying an arbitrary command could repeat side effects.
163
174
  noStrict(obj) / isNoStrict(obj) // dynamic (no-schema) subtree
164
175
  endCallback(fn) // alias: rpcEndCallback
165
176
  // subscription primitives (rare/manual; createRpcServerAuto/createRpcClientHub are the normal path)
@@ -458,28 +469,36 @@ withReplayListen(base, {current?, frame?, history?, getSince?, onJournal?, now?,
458
469
  // sugar `current: 'last'` — single-entity lines: keyframe = last journaled envelope, no hand-kept state.
459
470
  // Source 2 (mini-frame): `frame` lambda — gets the raw tail, returns a state-equivalent compact
460
471
  // (last-per-entity, gap aggregate...); cost ~ touched entities, wins over keyframe while the journal covers.
461
- // Line classes FOLLOW from declared lambdas (no mode flags): current+frame = condensable; current only =
462
- // snapshot-recoverable; neither = sacred queue — full tail only, evicted -> frame() THROWS (loud, never silent loss).
472
+ // Line classes FOLLOW from declared lambdas (no mode flags): current+frame = condensable with keyframe fallback;
473
+ // current only = exact retained tail + keyframe fallback; frame without current condenses only while the tail
474
+ // is retained and still fails on eviction; neither = sacred exact queue — eviction THROWS terminally.
463
475
  // Triggers: reconnect (`since`), client pull (own timer — replaces any server-side interval mode), server gate drain.
464
476
  // The transport sees ONLY seq; entity keys/skip rules live in producer lambdas (hint = opaque per-subscriber pass-through).
465
477
  exposeReplay(replay) <-> replaySubscribe(remote, cb, {since?, onSeq?, staleMs?, onStale?, skewMs?, now?, policy?, hint?}) -> off // wire pair over the EXISTING rpc: line = plain Listen, since/keyframe/frame = plain methods
466
478
  // NORMAL PATH: createRpcServerAuto exposes replay listens automatically (see rpc section) — exposeReplay stays
467
479
  // as the manual/custom-transport path. replaySubscribe prefers `frame` when the server has it (one round trip,
468
480
  // server picks tail/mini-frame/keyframe; sacred throw -> onError), falls back to since/keyframe on old servers.
469
- // policy: 'queue' (default — socket buffers everything, nothing skipped) | 'frame' (subscribes frameLine when
470
- // present: on lag the server drops and recovers via frame(lastSent) — mini-frame, no backlog). A non-envelope
481
+ // policy: 'queue' (default — ungated connected live line) | 'frame' (subscribes frameLine when present: on lag
482
+ // the server may drop and recover via a state-equivalent frame(lastSent)). Catch-up is shared by both policies
483
+ // and may use a producer mini-frame/keyframe, so a covered raw seq jump is not a logical data gap. A non-envelope
471
484
  // on the line (RPC_STOP — e.g. the gate's loud sacred failure) surfaces via onError + off, never silence.
472
485
  // hint reaches the frame lambda on catch-up and on every explicit frame(seq, hint) call (pull); the push-gate's
473
486
  // drain recovery uses the line's DEFAULT condensation — client-specific rules/pace = the pull path.
474
- // off.ready (catch-up done) · off.seq() (reconnect point) · off.isStale()/off.lastTs(); reconnect = call again with {since: prev.seq()}
487
+ // off.ready (first handover done, or terminal error/teardown) · off.seq() (last honestly delivered coordinate) · off.isStale()/off.lastTs().
488
+ // Hub-managed RPC remotes resume automatically after transient reconnect from off.seq(): live is restored first,
489
+ // racing envelopes queue, then frame/since catch-up is sorted+deduped. Transport-agnostic remotes without RPC
490
+ // lifecycle metadata still reconnect by creating a new subscriber with {since: prev.seq()}.
475
491
  replayRouteSubscribe(remote, cb, {label?, since?, onSeq?, onError?, onRoute?}) -> off & {ready, switch(nextRemote, {label?, since?, reset?, policy?, hint?}), seq(), label(), active()}
476
492
  // transport hand-off helper: old route remains live, replacement subscribes+catches up from seq, then old closes; overlap is seq-deduped. Use for relay -> direct and direct -> relay over any ordered ReplayRemote.
477
493
  // DELIVERY CONTRACT (guaranteed, not best-effort): the subscriber's cb sees ONE uniform stream —
478
494
  // first delivery = the snapshot (keyframe as an event of the SAME type; store: root patch),
479
- // then only strictly-newer events, seq-ascending, no gaps, no dups. Live events racing ahead of the
495
+ // then only strictly-newer events, seq-ascending and deduped. A raw seq jump is valid only when a
496
+ // producer-declared mini-frame/keyframe is state-equivalent to the omitted range; sacred retained tails
497
+ // are exact. Live events racing ahead of the
480
498
  // keyframe over the wire are queued during catch-up and seq-deduped — they can NEVER arrive first.
481
499
  // With {since: K}: same fold, journal tail after K instead of a keyframe (evicted -> keyframe fallback,
482
- // visible to the client as a seq jump > +1). Requires an ORDERED transport (socket.io / TCP / in-proc).
500
+ // visible as a covered seq jump > +1). A sacred eviction instead calls onError, closes the subscriber and
501
+ // never drains post-gap live events as a false continuation. Requires an ORDERED transport (socket.io / TCP / in-proc).
483
502
  // Net effect: one client fold `state = apply(state, event)` handles cold start, reconnect,
484
503
  // conflation recovery and archive playback identically — snapshot is not a special case.
485
504
  // FRESHNESS (staleMs/onStale — an option, not consumer boilerplate): delivery is consistent but silent
@@ -496,7 +515,8 @@ createRouteCoordinator({connect, policy?, shadow?, catchUpTimeoutMs?}) -> coordi
496
515
  // LAYERING: a CONNECTOR is a pure transport (no route decisions) — {info: {label, kind: 'relay'|'direct',
497
516
  // binary?, ordered?, reliable?}, open() -> ReplayRemote, close(), state(), metrics?() -> {rtt?, pending?},
498
517
  // onFail?}; the COORDINATOR alone owns promotion/fallback (state machine + policy); data continuity is
499
- // replayRouteSubscribe underneath, so ANY route change is gap-free and dup-free by the seq contract.
518
+ // replayRouteSubscribe underneath, so ANY route change is deduped and has no uncovered logical gap;
519
+ // a producer-authorized frame/keyframe may still jump raw seq.
500
520
  // WebRTC/NAT is deliberately absent here: a future datachannel is just another RouteConnector.
501
521
  // connect(ref, kind) -> RouteConnector — transport factory per pair and kind (called per activation).
502
522
  // policy hooks run BEFORE any transport action; absent hook = allowed, present hook must return true:
@@ -531,9 +551,11 @@ createSignalHub({authorize?}) -> {register(account) -> {send, signals, close}, r
531
551
  // one server-side policy point for endpoint material AND calls.
532
552
  createWebRtcConnector({port, rtc, self, peer, pair, session?, label?, openTimeoutMs?}) -> RouteConnector
533
553
  // the direct connector for createRouteCoordinator: open() drives offer/answer/ICE through the signal
534
- // port, waits for the datachannel, returns a replay wire over it. RTCPeerConnection is NOT bundled:
535
- // rtc is a runtime factory — browser `() => new RTCPeerConnection(cfg)`, Node werift/node-datachannel,
536
- // tests an in-proc fake (RtcPeerConnection/RtcDataChannel are structural types, no lib.dom).
554
+ // port, waits for the datachannel, returns a replay wire over it. RTCPeerConnection is NOT bundled:
555
+ // rtc is a runtime factory — browser `() => new RTCPeerConnection(cfg)`, Node werift/node-datachannel,
556
+ // tests an in-proc fake (RtcPeerConnection/RtcDataChannel are structural types, no lib.dom).
557
+ // The replay channel encodes Uint8Array explicitly, preserving Media frames byte-for-byte instead
558
+ // of JSON's numeric-key object conversion; native tracks/SFU are optional performance adapters.
537
559
  // revoke/close signals and channel death (incl. DURING open) fail loudly -> coordinator auto-fallback.
538
560
  acceptWebRtcDirect({port, rtc, self, serve, accept?}) -> close()
539
561
  // responder side: on offer, negotiates answer/ICE and serves serve(env) (exposeReplay(...) as is) into
@@ -630,7 +652,7 @@ openHistory(storage, live?) -> {at({seq?|ts?}?), subscribe(cb, {since?|ts?, onSe
630
652
  // seamless rewind->live: create the line with getSince reading the same storage («memory outside»); else the gap closes with a keyframe jump (still consistent)
631
653
  storeReplayAt(storage, {seq?|ts?}?) -> snapshot | undefined // store time machine: bit-exact state at any archived moment (same applyStorePatch mechanism)
632
654
  ```
633
- > Killer property: a lagging/late/stalled consumer never gets a queue backlog evicted seq / full outgoing buffer -> fresh keyframe + live from it.
655
+ > Killer property for state/frame lines: a lagging/late/stalled consumer can replace backlog with a state-equivalent frame/keyframe. A sacred queue deliberately does not: its retained tail is exact, and eviction is a terminal error rather than silent loss.
634
656
  > Files: `src/Common/events/replay-{listen,wire,conflate,history,index}.ts` + `src/Common/Observe/store-{replay,offline}.ts`;
635
657
  > everything is additive (the canonical Listen surface gained only `registerListenOn`/`ListenOnBrand`; exposeStore/mirror untouched).
636
658
  > Oracles: `npx ts-node replay/<f>.ts` — replay-listen / store-replay / offline-store / socket-replay / offline-store-socket / conflate / conflate-socket / coalesce / history / staleness / canvas-socket (raw bytes) / video-socket.demo;
@@ -682,4 +704,4 @@ const facade = {
682
704
  // createRpcServerAuto({ object: facade, ... }) exposes accountChanged/accountChangedPaths/btcChanged
683
705
  // as normal RPC Listen subscriptions. This is a notification stream, not a full
684
706
  // automatic snapshot mirror; send/read snapshots explicitly via facade methods.
685
- ```
707
+ ```