You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Sibling to #1046/#1047 (Python). Mirrors the polyglot cadence established by #1041 → #1043 → #1045 for recv_event / send_event / postEvent.
Motivation
recvEvent (consumer side, drained once-per-message via JobController.recvEvent) and postEvent (producer/external side, fire-and-forget via mesh.jobs.postEvent) — both shipped in #1043 — cover the point-to-point event injection contract. They do not cover observer/fan-out patterns where a third party (or the commissioner itself, after the original commission/call returned) wants to watch a job's event stream without consuming from it.
Stream subscription mode adds a non-destructive read API:
Reads the registry's per-job event log (JobEvent ent table, (job_id, seq) indexed).
Resumes from a caller-supplied after cursor — late subscribers replay history; reconnecting subscribers resume from their last seq.
Long-polls the registry so idle subscribers don't busy-loop.
Server-supplied next_after watermark drives cursor advance even on empty-page poll cycles (avoids rescanning when server-side types filter excludes events).
This is the symmetric counterpart to postEvent on the producer side: producers push, observers read. Multiple subscribers can observe the same job's events independently — each has its own per-call cursor.
Iteration runs indefinitely until the caller breaks out of the for await loop or the registry returns JobNotFoundError. There is no automatic terminal-state detection — applications signal end with a sentinel event (matches Python behavior).
Summary
TypeScript vertical of MeshJob Stream subscription mode (#1032 follow-up):
mesh.jobs.subscribeEvents(jobId, { types, after, longPollSecs }) -> AsyncIterableIterator<JobEvent>.Sibling to #1046/#1047 (Python). Mirrors the polyglot cadence established by #1041 → #1043 → #1045 for
recv_event/send_event/postEvent.Motivation
recvEvent(consumer side, drained once-per-message viaJobController.recvEvent) andpostEvent(producer/external side, fire-and-forget viamesh.jobs.postEvent) — both shipped in #1043 — cover the point-to-point event injection contract. They do not cover observer/fan-out patterns where a third party (or the commissioner itself, after the originalcommission/callreturned) wants to watch a job's event stream without consuming from it.Stream subscription mode adds a non-destructive read API:
JobEventent table,(job_id, seq)indexed).aftercursor — late subscribers replay history; reconnecting subscribers resume from their last seq.next_afterwatermark drives cursor advance even on empty-page poll cycles (avoids rescanning when server-sidetypesfilter excludes events).JobEventobjects ({ seq, type, payload, posted_by, created_at, trace_context, job_id }).This is the symmetric counterpart to
postEventon the producer side: producers push, observers read. Multiple subscribers can observe the same job's events independently — each has its own per-call cursor.Surface
types: optional filter list (server-side filter via existingListJobEventsquery param).after: cursor; subscribers persist this to resume across restarts (default0).longPollSecs: controls per-requestwaitparameter. Default30.null≡ single immediate read (rarely needed).Iteration runs indefinitely until the caller
breaks out of thefor awaitloop or the registry returnsJobNotFoundError. There is no automatic terminal-state detection — applications signal end with a sentinel event (matches Python behavior).Scope
JsJobProxy.listEvents(after, types, timeoutSecs) -> Promise<[JobEvent[], bigint]>— thin pass-through to the Rust coreJobProxy::list_events(already shipped in MeshJob: Stream subscription — subscribe_events (Python) #1047). Returns a tuple[events, nextAfter]. Reusesparse_timeout_secsfrom ts: MeshJob event injection parity — recvEvent/sendEvent/postEvent #1043.mesh.jobs.subscribeEventsasync generator; uses the LRUJobProxycache shared withpostEvent(no new cache). Per-iterationseqtype-guard rejects booleans (Python parity).uc22_meshjob_ts— producer runningrun_until_donepushes 3 events; commissioner observer subscribes viasubscribeEventsfromafter=0and asserts each event seq/payload is observed end-to-end. Reuses existinglong-task-provider-ts/long-task-consumer-tsfixtures (extends withrun_until_done+commission_subscribe_observertools).Out of scope for this issue (separate vertical):
Acceptance
tsuite run --suite-path tests/integration --uc uc22_meshjob_ts --parallel 4→ baseline + 1 new tc27 pass.aftercursor semantics,nextAfterwatermark advance on empty pages, JobProxy cache reuse, bool-seq rejection.JobProxy::list_eventsships in main as of MeshJob: Stream subscription — subscribe_events (Python) #1047.