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.
- package/README.md +7 -2
- package/dist/adapters/express.js +1 -1
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/fastify.js +1 -1
- package/dist/adapters/fastify.js.map +1 -1
- package/dist/adapters/nextjs-app.js +1 -1
- package/dist/adapters/nextjs-app.js.map +1 -1
- package/dist/adapters/nextjs-pages.js +1 -1
- package/dist/adapters/nextjs-pages.js.map +1 -1
- package/dist/discovery.d.ts +7 -1
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +18 -11
- package/dist/discovery.js.map +1 -1
- package/dist/process-change-hub.d.ts +87 -0
- package/dist/process-change-hub.d.ts.map +1 -0
- package/dist/process-change-hub.js +268 -0
- package/dist/process-change-hub.js.map +1 -0
- package/dist/process-change-relevance.d.ts +57 -0
- package/dist/process-change-relevance.d.ts.map +1 -0
- package/dist/process-change-relevance.js +101 -0
- package/dist/process-change-relevance.js.map +1 -0
- package/dist/stream-poller.d.ts +7 -0
- package/dist/stream-poller.d.ts.map +1 -1
- package/dist/stream-poller.js +288 -278
- package/dist/stream-poller.js.map +1 -1
- package/package.json +2 -2
package/dist/stream-poller.js
CHANGED
|
@@ -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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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 (
|
|
62
|
-
|
|
63
|
-
|
|
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,
|
|
119
|
+
const { db, prefix, sendEvent, rootId, baseDepth, maxDepth } = opts;
|
|
70
120
|
const col = db.collection(`${prefix}_processes`);
|
|
71
|
-
|
|
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
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
196
|
+
firstPoll = false;
|
|
197
|
+
if (logCleared) {
|
|
198
|
+
sendEvent({ type: 'log-clear' });
|
|
157
199
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
|
205
|
+
return driveByChanges(opts, read, (c) => treeRelevant(c, roots, members));
|
|
169
206
|
}
|
|
170
207
|
export function createMultiTreePoller(opts) {
|
|
171
|
-
const { db, prefix, sendEvent,
|
|
208
|
+
const { db, prefix, sendEvent, treeRoots, flatIds, maxDepth } = opts;
|
|
172
209
|
const col = db.collection(`${prefix}_processes`);
|
|
173
|
-
|
|
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
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
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
|
-
|
|
272
|
-
|
|
273
|
-
|
|
300
|
+
firstPoll = false;
|
|
301
|
+
for (const rid of logClearedRoots) {
|
|
302
|
+
sendEvent({ type: 'log-clear', rootId: rid });
|
|
274
303
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
|
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,
|
|
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
|
|
320
|
+
let members = new Set();
|
|
297
321
|
const lastCounts = new Map();
|
|
298
|
-
async function
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
|
342
|
+
return driveByChanges(opts, read, (c) => sessionRelevant(c, sessionId, members));
|
|
333
343
|
}
|
|
334
344
|
//# sourceMappingURL=stream-poller.js.map
|