optio-api 0.2.9 → 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 +39 -7
- package/dist/_generated/optio-engine.d.ts +8 -0
- package/dist/_generated/optio-engine.d.ts.map +1 -1
- package/dist/_generated/optio-engine.js +3 -0
- package/dist/_generated/optio-engine.js.map +1 -1
- package/dist/access-scope.d.ts +53 -0
- package/dist/access-scope.d.ts.map +1 -0
- package/dist/access-scope.js +110 -0
- package/dist/access-scope.js.map +1 -0
- package/dist/adapters/express.d.ts +5 -0
- package/dist/adapters/express.d.ts.map +1 -1
- package/dist/adapters/express.js +35 -23
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/fastify.d.ts +5 -1
- package/dist/adapters/fastify.d.ts.map +1 -1
- package/dist/adapters/fastify.js +74 -33
- package/dist/adapters/fastify.js.map +1 -1
- package/dist/adapters/nextjs-app.d.ts +5 -0
- package/dist/adapters/nextjs-app.d.ts.map +1 -1
- package/dist/adapters/nextjs-app.js +44 -15
- package/dist/adapters/nextjs-app.js.map +1 -1
- package/dist/adapters/nextjs-pages.d.ts +5 -0
- package/dist/adapters/nextjs-pages.d.ts.map +1 -1
- package/dist/adapters/nextjs-pages.js +41 -15
- package/dist/adapters/nextjs-pages.js.map +1 -1
- package/dist/auth.d.ts +48 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +44 -0
- package/dist/auth.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/handlers.d.ts +44 -14
- package/dist/handlers.d.ts.map +1 -1
- package/dist/handlers.js +78 -20
- package/dist/handlers.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.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 +13 -1
- package/dist/stream-poller.d.ts.map +1 -1
- package/dist/stream-poller.js +290 -260
- package/dist/stream-poller.js.map +1 -1
- package/package.json +2 -2
package/dist/stream-poller.js
CHANGED
|
@@ -1,269 +1,312 @@
|
|
|
1
1
|
import { ObjectId } from 'mongodb';
|
|
2
2
|
import { metadataFilterToMongo } from './metadata-filter-query.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
import { andScope } from './access-scope.js';
|
|
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;
|
|
10
30
|
try {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
percent: p.progress?.percent,
|
|
16
|
-
message: p.progress?.message,
|
|
17
|
-
supportsResume: p.supportsResume ?? false,
|
|
18
|
-
hasSavedState: p.hasSavedState ?? false,
|
|
19
|
-
autoResumeScheduled: p.autoResumeScheduled ?? false,
|
|
20
|
-
browserOpenRequests: p.browserOpenRequests ?? [],
|
|
21
|
-
})));
|
|
22
|
-
if (snapshot !== lastSnapshot) {
|
|
23
|
-
lastSnapshot = snapshot;
|
|
24
|
-
sendEvent({
|
|
25
|
-
type: 'update',
|
|
26
|
-
processes: allProcs.map((p) => ({
|
|
27
|
-
_id: p._id.toString(),
|
|
28
|
-
processId: p.processId,
|
|
29
|
-
name: p.name,
|
|
30
|
-
status: p.status,
|
|
31
|
-
progress: p.progress,
|
|
32
|
-
cancellable: p.cancellable,
|
|
33
|
-
special: p.special,
|
|
34
|
-
warning: p.warning,
|
|
35
|
-
metadata: p.metadata,
|
|
36
|
-
depth: p.depth ?? 0,
|
|
37
|
-
supportsResume: p.supportsResume ?? false,
|
|
38
|
-
hasSavedState: p.hasSavedState ?? false,
|
|
39
|
-
autoResumeScheduled: p.autoResumeScheduled ?? false,
|
|
40
|
-
browserOpenRequests: p.browserOpenRequests ?? [],
|
|
41
|
-
})),
|
|
42
|
-
});
|
|
43
|
-
}
|
|
31
|
+
do {
|
|
32
|
+
again = false;
|
|
33
|
+
await read();
|
|
34
|
+
} while (again && !stopped);
|
|
44
35
|
}
|
|
45
36
|
catch {
|
|
46
37
|
stop();
|
|
47
|
-
onError();
|
|
38
|
+
opts.onError();
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
reading = false;
|
|
48
42
|
}
|
|
49
43
|
}
|
|
44
|
+
const sub = {
|
|
45
|
+
isRelevant: (c) => reading || rule(c),
|
|
46
|
+
refresh: () => { void run(); },
|
|
47
|
+
};
|
|
50
48
|
function start() {
|
|
51
|
-
|
|
49
|
+
stopped = false;
|
|
50
|
+
hub.subscribe(sub).then(() => run(), () => {
|
|
51
|
+
if (stopped)
|
|
52
|
+
return;
|
|
53
|
+
stop();
|
|
54
|
+
opts.onError();
|
|
55
|
+
});
|
|
52
56
|
}
|
|
53
57
|
function stop() {
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
if (stopped)
|
|
59
|
+
return;
|
|
60
|
+
stopped = true;
|
|
61
|
+
hub.unsubscribe(sub);
|
|
58
62
|
}
|
|
59
63
|
return { start, stop };
|
|
60
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
|
+
}
|
|
61
118
|
export function createTreePoller(opts) {
|
|
62
|
-
const { db, prefix, sendEvent,
|
|
119
|
+
const { db, prefix, sendEvent, rootId, baseDepth, maxDepth } = opts;
|
|
63
120
|
const col = db.collection(`${prefix}_processes`);
|
|
64
|
-
|
|
121
|
+
const roots = new Set([rootId]);
|
|
122
|
+
let members = new Set();
|
|
65
123
|
let lastSnapshot = '';
|
|
66
124
|
const lastLogCounts = new Map();
|
|
67
125
|
let firstPoll = true;
|
|
68
|
-
async function
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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);
|
|
107
182
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
lastLogCounts.set(pid, 0);
|
|
118
|
-
}
|
|
119
|
-
const effectiveLastLen = lastLogCounts.get(pid) ?? 0;
|
|
120
|
-
if (logLen > effectiveLastLen) {
|
|
121
|
-
const entries = (p.log ?? []).slice(firstPoll ? 0 : effectiveLastLen);
|
|
122
|
-
for (const entry of entries) {
|
|
123
|
-
newLogEntries.push({
|
|
124
|
-
...entry,
|
|
125
|
-
processId: pid,
|
|
126
|
-
processLabel: p.name,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
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
|
+
});
|
|
130
192
|
}
|
|
131
|
-
|
|
132
|
-
firstPoll = false;
|
|
133
|
-
if (logCleared) {
|
|
134
|
-
sendEvent({ type: 'log-clear' });
|
|
135
|
-
}
|
|
136
|
-
if (newLogEntries.length > 0) {
|
|
137
|
-
newLogEntries.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
138
|
-
sendEvent({ type: 'log', entries: newLogEntries });
|
|
193
|
+
lastLogCounts.set(pid, logLen);
|
|
139
194
|
}
|
|
140
195
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
196
|
+
firstPoll = false;
|
|
197
|
+
if (logCleared) {
|
|
198
|
+
sendEvent({ type: 'log-clear' });
|
|
144
199
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
function stop() {
|
|
150
|
-
if (interval) {
|
|
151
|
-
clearInterval(interval);
|
|
152
|
-
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 });
|
|
153
203
|
}
|
|
154
204
|
}
|
|
155
|
-
return
|
|
205
|
+
return driveByChanges(opts, read, (c) => treeRelevant(c, roots, members));
|
|
156
206
|
}
|
|
157
207
|
export function createMultiTreePoller(opts) {
|
|
158
|
-
const { db, prefix, sendEvent,
|
|
208
|
+
const { db, prefix, sendEvent, treeRoots, flatIds, maxDepth } = opts;
|
|
159
209
|
const col = db.collection(`${prefix}_processes`);
|
|
160
|
-
|
|
210
|
+
const roots = new Set(treeRoots.map((r) => r.rootId.toString()));
|
|
211
|
+
let members = new Set();
|
|
161
212
|
let lastSnapshot = '';
|
|
162
213
|
const lastLogCounts = new Map();
|
|
163
214
|
let firstPoll = true;
|
|
164
|
-
async function
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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);
|
|
180
285
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
hasSavedState: p.hasSavedState ?? false,
|
|
190
|
-
autoResumeScheduled: p.autoResumeScheduled ?? false,
|
|
191
|
-
browserOpenRequests: p.browserOpenRequests ?? [],
|
|
192
|
-
metadata: p.metadata,
|
|
193
|
-
})));
|
|
194
|
-
if (snapshot !== lastSnapshot) {
|
|
195
|
-
lastSnapshot = snapshot;
|
|
196
|
-
sendEvent({
|
|
197
|
-
type: 'update',
|
|
198
|
-
processes: allProcs.map((p) => ({
|
|
199
|
-
_id: p._id.toString(),
|
|
200
|
-
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,
|
|
201
294
|
rootId: p.rootId?.toString() ?? null,
|
|
202
|
-
|
|
203
|
-
name: p.name,
|
|
204
|
-
status: p.status,
|
|
205
|
-
progress: p.progress,
|
|
206
|
-
cancellable: p.cancellable ?? false,
|
|
207
|
-
depth: p.depth,
|
|
208
|
-
order: p.order,
|
|
209
|
-
widgetData: p.widgetData,
|
|
210
|
-
uiWidget: p.uiWidget,
|
|
211
|
-
supportsResume: p.supportsResume ?? false,
|
|
212
|
-
hasSavedState: p.hasSavedState ?? false,
|
|
213
|
-
autoResumeScheduled: p.autoResumeScheduled ?? false,
|
|
214
|
-
browserOpenRequests: p.browserOpenRequests ?? [],
|
|
215
|
-
metadata: p.metadata,
|
|
216
|
-
})),
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
const logClearedRoots = new Set();
|
|
220
|
-
const newLogEntries = [];
|
|
221
|
-
for (const p of allProcs) {
|
|
222
|
-
const pid = p._id.toString();
|
|
223
|
-
const logLen = (p.log ?? []).length;
|
|
224
|
-
const lastLen = lastLogCounts.get(pid) ?? 0;
|
|
225
|
-
if (logLen < lastLen) {
|
|
226
|
-
logClearedRoots.add(p.rootId?.toString() ?? '');
|
|
227
|
-
lastLogCounts.set(pid, 0);
|
|
228
|
-
}
|
|
229
|
-
const effectiveLastLen = lastLogCounts.get(pid) ?? 0;
|
|
230
|
-
if (logLen > effectiveLastLen) {
|
|
231
|
-
const entries = (p.log ?? []).slice(firstPoll ? 0 : effectiveLastLen);
|
|
232
|
-
for (const entry of entries) {
|
|
233
|
-
newLogEntries.push({
|
|
234
|
-
...entry,
|
|
235
|
-
processId: pid,
|
|
236
|
-
processLabel: p.name,
|
|
237
|
-
rootId: p.rootId?.toString() ?? null,
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
lastLogCounts.set(pid, logLen);
|
|
295
|
+
});
|
|
241
296
|
}
|
|
242
|
-
|
|
243
|
-
firstPoll = false;
|
|
244
|
-
for (const rid of logClearedRoots) {
|
|
245
|
-
sendEvent({ type: 'log-clear', rootId: rid });
|
|
246
|
-
}
|
|
247
|
-
if (newLogEntries.length > 0) {
|
|
248
|
-
newLogEntries.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
249
|
-
sendEvent({ type: 'log', entries: newLogEntries });
|
|
297
|
+
lastLogCounts.set(pid, logLen);
|
|
250
298
|
}
|
|
251
299
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
300
|
+
firstPoll = false;
|
|
301
|
+
for (const rid of logClearedRoots) {
|
|
302
|
+
sendEvent({ type: 'log-clear', rootId: rid });
|
|
255
303
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
function stop() {
|
|
261
|
-
if (interval) {
|
|
262
|
-
clearInterval(interval);
|
|
263
|
-
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 });
|
|
264
307
|
}
|
|
265
308
|
}
|
|
266
|
-
return
|
|
309
|
+
return driveByChanges(opts, read, (c) => treeRelevant(c, roots, members));
|
|
267
310
|
}
|
|
268
311
|
/**
|
|
269
312
|
* Poll-backed session-events feed. Each ~1s tick reads processes whose
|
|
@@ -271,44 +314,31 @@ export function createMultiTreePoller(opts) {
|
|
|
271
314
|
* sessionEvents (deduped by length high-water mark per process). Read-only.
|
|
272
315
|
*/
|
|
273
316
|
export function createSessionEventsPoller(opts) {
|
|
274
|
-
const { db, prefix, sessionId, sendEvent,
|
|
317
|
+
const { db, prefix, sessionId, sendEvent, scope } = opts;
|
|
275
318
|
const col = db.collection(`${prefix}_processes`);
|
|
276
|
-
|
|
319
|
+
const filter = andScope({ originatingSessionId: sessionId }, scope ?? null);
|
|
320
|
+
let members = new Set();
|
|
277
321
|
const lastCounts = new Map();
|
|
278
|
-
async function
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
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);
|
|
296
339
|
}
|
|
297
340
|
}
|
|
298
|
-
catch {
|
|
299
|
-
stop();
|
|
300
|
-
onError();
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
function start() {
|
|
304
|
-
interval = setInterval(poll, 1000);
|
|
305
341
|
}
|
|
306
|
-
|
|
307
|
-
if (interval) {
|
|
308
|
-
clearInterval(interval);
|
|
309
|
-
interval = null;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
return { start, stop };
|
|
342
|
+
return driveByChanges(opts, read, (c) => sessionRelevant(c, sessionId, members));
|
|
313
343
|
}
|
|
314
344
|
//# sourceMappingURL=stream-poller.js.map
|