optio-api 0.2.10 → 0.2.11

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.
@@ -1,288 +1,312 @@
1
1
  import { ObjectId } from 'mongodb';
2
2
  import { metadataFilterToMongo } from './metadata-filter-query.js';
3
3
  import { andScope } from './access-scope.js';
4
- export function createListPoller(opts) {
5
- const { db, prefix, sendEvent, onError, metadataFilter, scope } = opts;
6
- const col = db.collection(`${prefix}_processes`);
7
- const filter = andScope(metadataFilterToMongo(metadataFilter), scope ?? null);
8
- let interval = null;
9
- let lastSnapshot = '';
10
- async function poll() {
4
+ import { LIST_PROJECTION, listRelevant, treeRelevant, sessionRelevant, } from './process-change-relevance.js';
5
+ import { getProcessChangeHub } from './process-change-hub.js';
6
+ /**
7
+ * Run `read` once on start, then whenever the hub reports a change `rule`
8
+ * finds relevant (the hub throttles to one read a second, and polls once a
9
+ * second where change streams are unavailable). Reads never overlap: a refresh
10
+ * during a read runs one more read after it. While a read is in flight every
11
+ * change counts as relevant, since `rule` judges by the last read's members
12
+ * and the read in flight may have missed the change. A failed read stops the
13
+ * stream and calls `onError`.
14
+ *
15
+ * Spec: docs/2026-10-09-api-change-streams-design.md
16
+ */
17
+ function driveByChanges(opts, read, rule) {
18
+ const hub = opts.hub ?? getProcessChangeHub(opts.db, opts.prefix);
19
+ let stopped = false;
20
+ let reading = false;
21
+ let again = false;
22
+ async function run() {
23
+ if (stopped)
24
+ return;
25
+ if (reading) {
26
+ again = true;
27
+ return;
28
+ }
29
+ reading = true;
11
30
  try {
12
- const allProcs = await col.find(filter).sort({ depth: 1, order: 1, _id: 1 }).toArray();
13
- const snapshot = JSON.stringify(allProcs.map((p) => ({
14
- id: p._id,
15
- state: p.status?.state,
16
- percent: p.progress?.percent,
17
- message: p.progress?.message,
18
- supportsResume: p.supportsResume ?? false,
19
- hasSavedState: p.hasSavedState ?? false,
20
- supportsResurrect: p.supportsResurrect ?? false,
21
- hasUnsavedWork: p.hasUnsavedWork ?? false,
22
- resurrecting: p.resurrecting ?? false,
23
- autoResumeScheduled: p.autoResumeScheduled ?? false,
24
- browserOpenRequests: p.browserOpenRequests ?? [],
25
- })));
26
- if (snapshot !== lastSnapshot) {
27
- lastSnapshot = snapshot;
28
- sendEvent({
29
- type: 'update',
30
- processes: allProcs.map((p) => ({
31
- _id: p._id.toString(),
32
- processId: p.processId,
33
- name: p.name,
34
- status: p.status,
35
- progress: p.progress,
36
- cancellable: p.cancellable,
37
- special: p.special,
38
- warning: p.warning,
39
- metadata: p.metadata,
40
- depth: p.depth ?? 0,
41
- supportsResume: p.supportsResume ?? false,
42
- hasSavedState: p.hasSavedState ?? false,
43
- supportsResurrect: p.supportsResurrect ?? false,
44
- hasUnsavedWork: p.hasUnsavedWork ?? false,
45
- resurrecting: p.resurrecting ?? false,
46
- autoResumeScheduled: p.autoResumeScheduled ?? false,
47
- browserOpenRequests: p.browserOpenRequests ?? [],
48
- })),
49
- });
50
- }
31
+ do {
32
+ again = false;
33
+ await read();
34
+ } while (again && !stopped);
51
35
  }
52
36
  catch {
53
37
  stop();
54
- onError();
38
+ opts.onError();
39
+ }
40
+ finally {
41
+ reading = false;
55
42
  }
56
43
  }
44
+ const sub = {
45
+ isRelevant: (c) => reading || rule(c),
46
+ refresh: () => { void run(); },
47
+ };
57
48
  function start() {
58
- interval = setInterval(poll, 1000);
49
+ stopped = false;
50
+ hub.subscribe(sub).then(() => run(), () => {
51
+ if (stopped)
52
+ return;
53
+ stop();
54
+ opts.onError();
55
+ });
59
56
  }
60
57
  function stop() {
61
- if (interval) {
62
- clearInterval(interval);
63
- interval = null;
64
- }
58
+ if (stopped)
59
+ return;
60
+ stopped = true;
61
+ hub.unsubscribe(sub);
65
62
  }
66
63
  return { start, stop };
67
64
  }
65
+ export function createListPoller(opts) {
66
+ const { db, prefix, sendEvent, metadataFilter, scope } = opts;
67
+ const col = db.collection(`${prefix}_processes`);
68
+ const filter = andScope(metadataFilterToMongo(metadataFilter), scope ?? null);
69
+ let members = new Set();
70
+ let lastSnapshot = '';
71
+ async function read() {
72
+ const allProcs = await col
73
+ .find(filter, { projection: LIST_PROJECTION })
74
+ .sort({ depth: 1, order: 1, _id: 1 })
75
+ .toArray();
76
+ members = new Set(allProcs.map((p) => String(p._id)));
77
+ const snapshot = JSON.stringify(allProcs.map((p) => ({
78
+ id: p._id,
79
+ state: p.status?.state,
80
+ percent: p.progress?.percent,
81
+ message: p.progress?.message,
82
+ supportsResume: p.supportsResume ?? false,
83
+ hasSavedState: p.hasSavedState ?? false,
84
+ supportsResurrect: p.supportsResurrect ?? false,
85
+ hasUnsavedWork: p.hasUnsavedWork ?? false,
86
+ resurrecting: p.resurrecting ?? false,
87
+ autoResumeScheduled: p.autoResumeScheduled ?? false,
88
+ browserOpenRequests: p.browserOpenRequests ?? [],
89
+ })));
90
+ if (snapshot !== lastSnapshot) {
91
+ lastSnapshot = snapshot;
92
+ sendEvent({
93
+ type: 'update',
94
+ processes: allProcs.map((p) => ({
95
+ _id: p._id.toString(),
96
+ processId: p.processId,
97
+ name: p.name,
98
+ status: p.status,
99
+ progress: p.progress,
100
+ cancellable: p.cancellable,
101
+ special: p.special,
102
+ warning: p.warning,
103
+ metadata: p.metadata,
104
+ depth: p.depth ?? 0,
105
+ supportsResume: p.supportsResume ?? false,
106
+ hasSavedState: p.hasSavedState ?? false,
107
+ supportsResurrect: p.supportsResurrect ?? false,
108
+ hasUnsavedWork: p.hasUnsavedWork ?? false,
109
+ resurrecting: p.resurrecting ?? false,
110
+ autoResumeScheduled: p.autoResumeScheduled ?? false,
111
+ browserOpenRequests: p.browserOpenRequests ?? [],
112
+ })),
113
+ });
114
+ }
115
+ }
116
+ return driveByChanges(opts, read, (c) => listRelevant(c, members));
117
+ }
68
118
  export function createTreePoller(opts) {
69
- const { db, prefix, sendEvent, onError, rootId, baseDepth, maxDepth } = opts;
119
+ const { db, prefix, sendEvent, rootId, baseDepth, maxDepth } = opts;
70
120
  const col = db.collection(`${prefix}_processes`);
71
- let interval = null;
121
+ const roots = new Set([rootId]);
122
+ let members = new Set();
72
123
  let lastSnapshot = '';
73
124
  const lastLogCounts = new Map();
74
125
  let firstPoll = true;
75
- async function poll() {
76
- try {
77
- const filter = { rootId: new ObjectId(rootId) };
78
- if (maxDepth !== undefined) {
79
- filter.depth = { $lte: baseDepth + maxDepth };
80
- }
81
- const allProcs = await col.find(filter).sort({ depth: 1, order: 1 }).toArray();
82
- const snapshot = JSON.stringify(allProcs.map((p) => ({
83
- id: p._id, status: p.status, progress: p.progress,
84
- widgetData: p.widgetData, uiWidget: p.uiWidget,
85
- supportsResume: p.supportsResume ?? false,
86
- hasSavedState: p.hasSavedState ?? false,
87
- supportsResurrect: p.supportsResurrect ?? false,
88
- hasUnsavedWork: p.hasUnsavedWork ?? false,
89
- resurrecting: p.resurrecting ?? false,
90
- autoResumeScheduled: p.autoResumeScheduled ?? false,
91
- browserOpenRequests: p.browserOpenRequests ?? [],
92
- metadata: p.metadata,
93
- })));
94
- if (snapshot !== lastSnapshot) {
95
- lastSnapshot = snapshot;
96
- sendEvent({
97
- type: 'update',
98
- processes: allProcs.map((p) => ({
99
- _id: p._id.toString(),
100
- parentId: p.parentId?.toString() ?? null,
101
- rootId: p.rootId?.toString() ?? null,
102
- name: p.name,
103
- status: p.status,
104
- progress: p.progress,
105
- cancellable: p.cancellable ?? false,
106
- depth: p.depth,
107
- order: p.order,
108
- widgetData: p.widgetData,
109
- uiWidget: p.uiWidget,
110
- supportsResume: p.supportsResume ?? false,
111
- hasSavedState: p.hasSavedState ?? false,
112
- supportsResurrect: p.supportsResurrect ?? false,
113
- hasUnsavedWork: p.hasUnsavedWork ?? false,
114
- resurrecting: p.resurrecting ?? false,
115
- autoResumeScheduled: p.autoResumeScheduled ?? false,
116
- browserOpenRequests: p.browserOpenRequests ?? [],
117
- metadata: p.metadata,
118
- })),
119
- });
126
+ async function read() {
127
+ const filter = { rootId: new ObjectId(rootId) };
128
+ if (maxDepth !== undefined) {
129
+ filter.depth = { $lte: baseDepth + maxDepth };
130
+ }
131
+ const allProcs = await col.find(filter).sort({ depth: 1, order: 1 }).toArray();
132
+ members = new Set(allProcs.map((p) => String(p._id)));
133
+ const snapshot = JSON.stringify(allProcs.map((p) => ({
134
+ id: p._id, status: p.status, progress: p.progress,
135
+ widgetData: p.widgetData, uiWidget: p.uiWidget,
136
+ supportsResume: p.supportsResume ?? false,
137
+ hasSavedState: p.hasSavedState ?? false,
138
+ supportsResurrect: p.supportsResurrect ?? false,
139
+ hasUnsavedWork: p.hasUnsavedWork ?? false,
140
+ resurrecting: p.resurrecting ?? false,
141
+ autoResumeScheduled: p.autoResumeScheduled ?? false,
142
+ browserOpenRequests: p.browserOpenRequests ?? [],
143
+ metadata: p.metadata,
144
+ })));
145
+ if (snapshot !== lastSnapshot) {
146
+ lastSnapshot = snapshot;
147
+ sendEvent({
148
+ type: 'update',
149
+ processes: allProcs.map((p) => ({
150
+ _id: p._id.toString(),
151
+ parentId: p.parentId?.toString() ?? null,
152
+ rootId: p.rootId?.toString() ?? null,
153
+ name: p.name,
154
+ status: p.status,
155
+ progress: p.progress,
156
+ cancellable: p.cancellable ?? false,
157
+ depth: p.depth,
158
+ order: p.order,
159
+ widgetData: p.widgetData,
160
+ uiWidget: p.uiWidget,
161
+ supportsResume: p.supportsResume ?? false,
162
+ hasSavedState: p.hasSavedState ?? false,
163
+ supportsResurrect: p.supportsResurrect ?? false,
164
+ hasUnsavedWork: p.hasUnsavedWork ?? false,
165
+ resurrecting: p.resurrecting ?? false,
166
+ autoResumeScheduled: p.autoResumeScheduled ?? false,
167
+ browserOpenRequests: p.browserOpenRequests ?? [],
168
+ metadata: p.metadata,
169
+ })),
170
+ });
171
+ }
172
+ // Detect log changes
173
+ let logCleared = false;
174
+ const newLogEntries = [];
175
+ for (const p of allProcs) {
176
+ const pid = p._id.toString();
177
+ const logLen = (p.log ?? []).length;
178
+ const lastLen = lastLogCounts.get(pid) ?? 0;
179
+ if (logLen < lastLen) {
180
+ logCleared = true;
181
+ lastLogCounts.set(pid, 0);
120
182
  }
121
- // Detect log changes
122
- let logCleared = false;
123
- const newLogEntries = [];
124
- for (const p of allProcs) {
125
- const pid = p._id.toString();
126
- const logLen = (p.log ?? []).length;
127
- const lastLen = lastLogCounts.get(pid) ?? 0;
128
- if (logLen < lastLen) {
129
- logCleared = true;
130
- lastLogCounts.set(pid, 0);
131
- }
132
- const effectiveLastLen = lastLogCounts.get(pid) ?? 0;
133
- if (logLen > effectiveLastLen) {
134
- const entries = (p.log ?? []).slice(firstPoll ? 0 : effectiveLastLen);
135
- for (const entry of entries) {
136
- newLogEntries.push({
137
- ...entry,
138
- processId: pid,
139
- processLabel: p.name,
140
- });
141
- }
142
- lastLogCounts.set(pid, logLen);
183
+ const effectiveLastLen = lastLogCounts.get(pid) ?? 0;
184
+ if (logLen > effectiveLastLen) {
185
+ const entries = (p.log ?? []).slice(firstPoll ? 0 : effectiveLastLen);
186
+ for (const entry of entries) {
187
+ newLogEntries.push({
188
+ ...entry,
189
+ processId: pid,
190
+ processLabel: p.name,
191
+ });
143
192
  }
144
- }
145
- firstPoll = false;
146
- if (logCleared) {
147
- sendEvent({ type: 'log-clear' });
148
- }
149
- if (newLogEntries.length > 0) {
150
- newLogEntries.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
151
- sendEvent({ type: 'log', entries: newLogEntries });
193
+ lastLogCounts.set(pid, logLen);
152
194
  }
153
195
  }
154
- catch {
155
- stop();
156
- onError();
196
+ firstPoll = false;
197
+ if (logCleared) {
198
+ sendEvent({ type: 'log-clear' });
157
199
  }
158
- }
159
- function start() {
160
- interval = setInterval(poll, 1000);
161
- }
162
- function stop() {
163
- if (interval) {
164
- clearInterval(interval);
165
- interval = null;
200
+ if (newLogEntries.length > 0) {
201
+ newLogEntries.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
202
+ sendEvent({ type: 'log', entries: newLogEntries });
166
203
  }
167
204
  }
168
- return { start, stop };
205
+ return driveByChanges(opts, read, (c) => treeRelevant(c, roots, members));
169
206
  }
170
207
  export function createMultiTreePoller(opts) {
171
- const { db, prefix, sendEvent, onError, treeRoots, flatIds, maxDepth } = opts;
208
+ const { db, prefix, sendEvent, treeRoots, flatIds, maxDepth } = opts;
172
209
  const col = db.collection(`${prefix}_processes`);
173
- let interval = null;
210
+ const roots = new Set(treeRoots.map((r) => r.rootId.toString()));
211
+ let members = new Set();
174
212
  let lastSnapshot = '';
175
213
  const lastLogCounts = new Map();
176
214
  let firstPoll = true;
177
- async function poll() {
178
- try {
179
- const branches = [];
180
- if (treeRoots.length > 0) {
181
- branches.push({
182
- $or: treeRoots.map((r) => {
183
- const f = { rootId: r.rootId };
184
- if (maxDepth !== undefined) {
185
- f.depth = { $lte: r.baseDepth + maxDepth };
186
- }
187
- return f;
188
- }),
189
- });
190
- }
191
- if (flatIds.length > 0) {
192
- branches.push({ _id: { $in: flatIds } });
215
+ async function read() {
216
+ const branches = [];
217
+ if (treeRoots.length > 0) {
218
+ branches.push({
219
+ $or: treeRoots.map((r) => {
220
+ const f = { rootId: r.rootId };
221
+ if (maxDepth !== undefined) {
222
+ f.depth = { $lte: r.baseDepth + maxDepth };
223
+ }
224
+ return f;
225
+ }),
226
+ });
227
+ }
228
+ if (flatIds.length > 0) {
229
+ branches.push({ _id: { $in: flatIds } });
230
+ }
231
+ if (branches.length === 0)
232
+ return;
233
+ const filter = branches.length === 1 ? branches[0] : { $or: branches };
234
+ const allProcs = await col.find(filter).sort({ depth: 1, order: 1 }).toArray();
235
+ members = new Set([...allProcs.map((p) => String(p._id)), ...flatIds.map(String)]);
236
+ const snapshot = JSON.stringify(allProcs.map((p) => ({
237
+ id: p._id, status: p.status, progress: p.progress,
238
+ widgetData: p.widgetData, uiWidget: p.uiWidget,
239
+ supportsResume: p.supportsResume ?? false,
240
+ hasSavedState: p.hasSavedState ?? false,
241
+ supportsResurrect: p.supportsResurrect ?? false,
242
+ hasUnsavedWork: p.hasUnsavedWork ?? false,
243
+ resurrecting: p.resurrecting ?? false,
244
+ autoResumeScheduled: p.autoResumeScheduled ?? false,
245
+ browserOpenRequests: p.browserOpenRequests ?? [],
246
+ metadata: p.metadata,
247
+ })));
248
+ if (snapshot !== lastSnapshot) {
249
+ lastSnapshot = snapshot;
250
+ sendEvent({
251
+ type: 'update',
252
+ processes: allProcs.map((p) => ({
253
+ _id: p._id.toString(),
254
+ parentId: p.parentId?.toString() ?? null,
255
+ rootId: p.rootId?.toString() ?? null,
256
+ processId: p.processId,
257
+ name: p.name,
258
+ status: p.status,
259
+ progress: p.progress,
260
+ cancellable: p.cancellable ?? false,
261
+ depth: p.depth,
262
+ order: p.order,
263
+ widgetData: p.widgetData,
264
+ uiWidget: p.uiWidget,
265
+ supportsResume: p.supportsResume ?? false,
266
+ hasSavedState: p.hasSavedState ?? false,
267
+ supportsResurrect: p.supportsResurrect ?? false,
268
+ hasUnsavedWork: p.hasUnsavedWork ?? false,
269
+ resurrecting: p.resurrecting ?? false,
270
+ autoResumeScheduled: p.autoResumeScheduled ?? false,
271
+ browserOpenRequests: p.browserOpenRequests ?? [],
272
+ metadata: p.metadata,
273
+ })),
274
+ });
275
+ }
276
+ const logClearedRoots = new Set();
277
+ const newLogEntries = [];
278
+ for (const p of allProcs) {
279
+ const pid = p._id.toString();
280
+ const logLen = (p.log ?? []).length;
281
+ const lastLen = lastLogCounts.get(pid) ?? 0;
282
+ if (logLen < lastLen) {
283
+ logClearedRoots.add(p.rootId?.toString() ?? '');
284
+ lastLogCounts.set(pid, 0);
193
285
  }
194
- if (branches.length === 0)
195
- return;
196
- const filter = branches.length === 1 ? branches[0] : { $or: branches };
197
- const allProcs = await col.find(filter).sort({ depth: 1, order: 1 }).toArray();
198
- const snapshot = JSON.stringify(allProcs.map((p) => ({
199
- id: p._id, status: p.status, progress: p.progress,
200
- widgetData: p.widgetData, uiWidget: p.uiWidget,
201
- supportsResume: p.supportsResume ?? false,
202
- hasSavedState: p.hasSavedState ?? false,
203
- supportsResurrect: p.supportsResurrect ?? false,
204
- hasUnsavedWork: p.hasUnsavedWork ?? false,
205
- resurrecting: p.resurrecting ?? false,
206
- autoResumeScheduled: p.autoResumeScheduled ?? false,
207
- browserOpenRequests: p.browserOpenRequests ?? [],
208
- metadata: p.metadata,
209
- })));
210
- if (snapshot !== lastSnapshot) {
211
- lastSnapshot = snapshot;
212
- sendEvent({
213
- type: 'update',
214
- processes: allProcs.map((p) => ({
215
- _id: p._id.toString(),
216
- parentId: p.parentId?.toString() ?? null,
286
+ const effectiveLastLen = lastLogCounts.get(pid) ?? 0;
287
+ if (logLen > effectiveLastLen) {
288
+ const entries = (p.log ?? []).slice(firstPoll ? 0 : effectiveLastLen);
289
+ for (const entry of entries) {
290
+ newLogEntries.push({
291
+ ...entry,
292
+ processId: pid,
293
+ processLabel: p.name,
217
294
  rootId: p.rootId?.toString() ?? null,
218
- processId: p.processId,
219
- name: p.name,
220
- status: p.status,
221
- progress: p.progress,
222
- cancellable: p.cancellable ?? false,
223
- depth: p.depth,
224
- order: p.order,
225
- widgetData: p.widgetData,
226
- uiWidget: p.uiWidget,
227
- supportsResume: p.supportsResume ?? false,
228
- hasSavedState: p.hasSavedState ?? false,
229
- supportsResurrect: p.supportsResurrect ?? false,
230
- hasUnsavedWork: p.hasUnsavedWork ?? false,
231
- resurrecting: p.resurrecting ?? false,
232
- autoResumeScheduled: p.autoResumeScheduled ?? false,
233
- browserOpenRequests: p.browserOpenRequests ?? [],
234
- metadata: p.metadata,
235
- })),
236
- });
237
- }
238
- const logClearedRoots = new Set();
239
- const newLogEntries = [];
240
- for (const p of allProcs) {
241
- const pid = p._id.toString();
242
- const logLen = (p.log ?? []).length;
243
- const lastLen = lastLogCounts.get(pid) ?? 0;
244
- if (logLen < lastLen) {
245
- logClearedRoots.add(p.rootId?.toString() ?? '');
246
- lastLogCounts.set(pid, 0);
247
- }
248
- const effectiveLastLen = lastLogCounts.get(pid) ?? 0;
249
- if (logLen > effectiveLastLen) {
250
- const entries = (p.log ?? []).slice(firstPoll ? 0 : effectiveLastLen);
251
- for (const entry of entries) {
252
- newLogEntries.push({
253
- ...entry,
254
- processId: pid,
255
- processLabel: p.name,
256
- rootId: p.rootId?.toString() ?? null,
257
- });
258
- }
259
- lastLogCounts.set(pid, logLen);
295
+ });
260
296
  }
261
- }
262
- firstPoll = false;
263
- for (const rid of logClearedRoots) {
264
- sendEvent({ type: 'log-clear', rootId: rid });
265
- }
266
- if (newLogEntries.length > 0) {
267
- newLogEntries.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
268
- sendEvent({ type: 'log', entries: newLogEntries });
297
+ lastLogCounts.set(pid, logLen);
269
298
  }
270
299
  }
271
- catch {
272
- stop();
273
- onError();
300
+ firstPoll = false;
301
+ for (const rid of logClearedRoots) {
302
+ sendEvent({ type: 'log-clear', rootId: rid });
274
303
  }
275
- }
276
- function start() {
277
- interval = setInterval(poll, 1000);
278
- }
279
- function stop() {
280
- if (interval) {
281
- clearInterval(interval);
282
- interval = null;
304
+ if (newLogEntries.length > 0) {
305
+ newLogEntries.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
306
+ sendEvent({ type: 'log', entries: newLogEntries });
283
307
  }
284
308
  }
285
- return { start, stop };
309
+ return driveByChanges(opts, read, (c) => treeRelevant(c, roots, members));
286
310
  }
287
311
  /**
288
312
  * Poll-backed session-events feed. Each ~1s tick reads processes whose
@@ -290,45 +314,31 @@ export function createMultiTreePoller(opts) {
290
314
  * sessionEvents (deduped by length high-water mark per process). Read-only.
291
315
  */
292
316
  export function createSessionEventsPoller(opts) {
293
- const { db, prefix, sessionId, sendEvent, onError, scope } = opts;
317
+ const { db, prefix, sessionId, sendEvent, scope } = opts;
294
318
  const col = db.collection(`${prefix}_processes`);
295
319
  const filter = andScope({ originatingSessionId: sessionId }, scope ?? null);
296
- let interval = null;
320
+ let members = new Set();
297
321
  const lastCounts = new Map();
298
- async function poll() {
299
- try {
300
- const procs = await col
301
- .find(filter)
302
- .project({ sessionEvents: 1 })
303
- .toArray();
304
- for (const p of procs) {
305
- const pid = p._id.toString();
306
- const events = (p.sessionEvents ?? []);
307
- const seen = lastCounts.get(pid) ?? 0;
308
- if (events.length > seen) {
309
- sendEvent({
310
- type: 'session-events',
311
- processId: pid,
312
- events: events.slice(seen),
313
- });
314
- lastCounts.set(pid, events.length);
315
- }
322
+ async function read() {
323
+ const procs = await col
324
+ .find(filter)
325
+ .project({ sessionEvents: 1 })
326
+ .toArray();
327
+ members = new Set(procs.map((p) => String(p._id)));
328
+ for (const p of procs) {
329
+ const pid = p._id.toString();
330
+ const events = (p.sessionEvents ?? []);
331
+ const seen = lastCounts.get(pid) ?? 0;
332
+ if (events.length > seen) {
333
+ sendEvent({
334
+ type: 'session-events',
335
+ processId: pid,
336
+ events: events.slice(seen),
337
+ });
338
+ lastCounts.set(pid, events.length);
316
339
  }
317
340
  }
318
- catch {
319
- stop();
320
- onError();
321
- }
322
- }
323
- function start() {
324
- interval = setInterval(poll, 1000);
325
- }
326
- function stop() {
327
- if (interval) {
328
- clearInterval(interval);
329
- interval = null;
330
- }
331
341
  }
332
- return { start, stop };
342
+ return driveByChanges(opts, read, (c) => sessionRelevant(c, sessionId, members));
333
343
  }
334
344
  //# sourceMappingURL=stream-poller.js.map