@reventlessdev/reventless-local 3.0.0-alpha.317 → 3.0.0-alpha.319
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/CHANGELOG.md +16 -0
- package/package.json +10 -10
- package/src/LocalPlatformRegistry.res +26 -1
- package/src/LocalPlatformRegistry.res.mjs +25 -5
- package/src/Platform.res +27 -2
- package/src/Platform.res.mjs +24 -4
- package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res +38 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res.mjs +29 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res +87 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res.mjs +76 -0
- package/src/adapter/DcbEventLog/LocalDcbEventLogStorage.res +8 -1
- package/src/adapter/DcbEventLog/LocalDcbEventLogStorage.res.mjs +17 -4
- package/src/adapter/EventLog/EventLogStorage_InMemory.res +49 -2
- package/src/adapter/EventLog/EventLogStorage_InMemory.res.mjs +57 -21
- package/src/adapter/EventLog/EventLogStorage_Sqlite.res +85 -0
- package/src/adapter/EventLog/EventLogStorage_Sqlite.res.mjs +50 -0
- package/src/adapter/EventLog/LocalEventLogStorage.res +3 -0
- package/src/adapter/EventLog/LocalEventLogStorage.res.mjs +6 -2
- package/src/adapter/LocalBus.res +32 -5
- package/src/adapter/LocalBus.res.mjs +27 -7
- package/src/adapter/LocalEventLogRead.res +477 -0
- package/src/adapter/LocalEventLogRead.res.mjs +507 -0
- package/src/adapter/LocalEventTap.res +18 -80
- package/src/adapter/LocalEventTap.res.mjs +19 -108
- package/src/adapter/LocalFeed.res +433 -0
- package/src/adapter/LocalFeed.res.mjs +419 -0
- package/src/adapter/LocalLogFeed.res +217 -0
- package/src/adapter/LocalLogFeed.res.mjs +295 -0
- package/src/adapter/LocalPgEventLogRead.res +85 -0
- package/src/adapter/LocalPgEventLogRead.res.mjs +91 -0
- package/src/adapter/LocalPlatformObserve.res +456 -0
- package/src/adapter/LocalPlatformObserve.res.mjs +505 -0
- package/src/adapter/LocalTapLines.res +149 -0
- package/src/adapter/LocalTapLines.res.mjs +232 -0
- package/src/adapter/PlatformGraphQL_Server.res +36 -3
- package/src/adapter/PlatformGraphQL_Server.res.mjs +15 -1
- package/src/adapter/ProjectionCheckpoint.res.mjs +3 -3
- package/src/components/AutomationSlice_Builder.res.mjs +1 -1
- package/src/components/LocalAutomationSchedules.res.mjs +1 -1
- package/src/components/LocalAutomationSweep.res.mjs +1 -1
- package/src/reexport/CommandTopic.res.mjs +5 -2
- package/tests/LocalPlatformRegistryTest.res +17 -0
- package/tests/LocalPlatformRegistryTest.res.mjs +14 -2
- package/tests/LocalPlatformStartTest.res.mjs +1 -1
- package/tests/adapter/GraphQLWsFixtures.mjs +46 -0
- package/tests/adapter/LocalEventLogReadTest.res +539 -0
- package/tests/adapter/LocalEventLogReadTest.res.mjs +466 -0
- package/tests/adapter/LocalFeedTest.res +314 -0
- package/tests/adapter/LocalFeedTest.res.mjs +329 -0
- package/tests/adapter/LocalPlatformObserveTest.res +165 -0
- package/tests/adapter/LocalPlatformObserveTest.res.mjs +183 -0
- package/tests/adapter/LocalTapLinesTest.res +207 -0
- package/tests/adapter/LocalTapLinesTest.res.mjs +274 -0
- package/tests/adapter/PlatformSubscriptionsTest.res +123 -0
- package/tests/adapter/PlatformSubscriptionsTest.res.mjs +132 -0
- package/tests/components/automationslice/AutomationSliceCallbackTest.res +80 -0
- package/tests/components/automationslice/AutomationSliceCallbackTest.res.mjs +81 -0
- package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res +17 -0
- package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res.mjs +24 -0
- package/tests/components/automationslice/AutomationSliceSelfDeadlockTest.res +30 -0
- package/tests/components/automationslice/AutomationSliceSelfDeadlockTest.res.mjs +28 -0
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res +58 -0
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res.mjs +71 -19
- package/tests/components/outboundtranslationslice/OutboundTranslationSlicePiiTest.res.mjs +7 -7
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Pervasives from "@rescript/runtime/lib/es6/Pervasives.js";
|
|
4
|
+
import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
5
|
+
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
6
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
7
|
+
import * as Stdlib_Float from "@rescript/runtime/lib/es6/Stdlib_Float.js";
|
|
8
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
9
|
+
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
10
|
+
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
11
|
+
import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
|
|
12
|
+
|
|
13
|
+
function metaOfFlat(flat) {
|
|
14
|
+
return Object.fromEntries(Stdlib_Array.filterMap(Message$ReventlessCore.metaKeys, k => Stdlib_Option.map(flat[k], v => [
|
|
15
|
+
k,
|
|
16
|
+
v
|
|
17
|
+
])));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function storedOfFlat(log, position, entityId, payload) {
|
|
21
|
+
let flat = Stdlib_Option.getOr(Stdlib_JSON.Decode.object(payload), {});
|
|
22
|
+
let str = k => Stdlib_Option.flatMap(flat[k], Stdlib_JSON.Decode.string);
|
|
23
|
+
let meta = metaOfFlat(flat);
|
|
24
|
+
let t = str("recordedAt");
|
|
25
|
+
return {
|
|
26
|
+
log: log,
|
|
27
|
+
position: position,
|
|
28
|
+
eventType: Stdlib_Option.getOr(str("event"), "unknown"),
|
|
29
|
+
entityId: entityId,
|
|
30
|
+
tags: [],
|
|
31
|
+
payload: Stdlib_Option.getOr(flat["data"], null),
|
|
32
|
+
meta: meta,
|
|
33
|
+
recordedAt: t !== undefined ? t : Stdlib_Option.getOr(str("time"), "")
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function numberOf(v) {
|
|
38
|
+
if (v === undefined) {
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
switch (typeof v) {
|
|
42
|
+
case "string" :
|
|
43
|
+
return Stdlib_Option.getOr(Stdlib_Float.fromString(v), 0);
|
|
44
|
+
case "number" :
|
|
45
|
+
return v;
|
|
46
|
+
default:
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let readers = {};
|
|
52
|
+
|
|
53
|
+
let pluginOfLog = {};
|
|
54
|
+
|
|
55
|
+
function register(log, kind, readBack) {
|
|
56
|
+
readers[log] = {
|
|
57
|
+
kind: kind,
|
|
58
|
+
readBack: readBack
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function notePlugin(log, plugin) {
|
|
63
|
+
pluginOfLog[log] = plugin;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function pluginOf(log) {
|
|
67
|
+
return pluginOfLog[log];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function logs() {
|
|
71
|
+
return Object.keys(readers).toSorted(Primitive_string.compare);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function positionToString(p) {
|
|
75
|
+
return p.toString();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let notes = new Map();
|
|
79
|
+
|
|
80
|
+
function note(log, msgId, position) {
|
|
81
|
+
notes.set(msgId, [
|
|
82
|
+
log,
|
|
83
|
+
position
|
|
84
|
+
]);
|
|
85
|
+
if (notes.size <= 10000) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
let match = notes.keys().next();
|
|
89
|
+
if (match.done) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
let oldest = match.value;
|
|
93
|
+
if (oldest !== undefined) {
|
|
94
|
+
notes.delete(oldest);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function take(msgId) {
|
|
100
|
+
let found = notes.get(msgId);
|
|
101
|
+
if (found !== undefined) {
|
|
102
|
+
notes.delete(msgId);
|
|
103
|
+
return found;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function resetForTests() {
|
|
108
|
+
Object.keys(readers).forEach(k => Stdlib_Dict.$$delete(readers, k));
|
|
109
|
+
Object.keys(pluginOfLog).forEach(k => Stdlib_Dict.$$delete(pluginOfLog, k));
|
|
110
|
+
notes.clear();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function recordToJson(r) {
|
|
114
|
+
return Object.fromEntries([
|
|
115
|
+
[
|
|
116
|
+
"log",
|
|
117
|
+
r.log
|
|
118
|
+
],
|
|
119
|
+
[
|
|
120
|
+
"position",
|
|
121
|
+
r.position
|
|
122
|
+
],
|
|
123
|
+
[
|
|
124
|
+
"eventType",
|
|
125
|
+
r.eventType
|
|
126
|
+
],
|
|
127
|
+
[
|
|
128
|
+
"tags",
|
|
129
|
+
r.tags.map(t => Object.fromEntries([
|
|
130
|
+
[
|
|
131
|
+
"key",
|
|
132
|
+
t.key
|
|
133
|
+
],
|
|
134
|
+
[
|
|
135
|
+
"value",
|
|
136
|
+
t.value
|
|
137
|
+
]
|
|
138
|
+
]))
|
|
139
|
+
],
|
|
140
|
+
[
|
|
141
|
+
"payload",
|
|
142
|
+
r.payload
|
|
143
|
+
],
|
|
144
|
+
[
|
|
145
|
+
"meta",
|
|
146
|
+
r.meta
|
|
147
|
+
],
|
|
148
|
+
[
|
|
149
|
+
"recordedAt",
|
|
150
|
+
r.recordedAt
|
|
151
|
+
]
|
|
152
|
+
].concat(Stdlib_Option.mapOr(r.plugin, [], p => [[
|
|
153
|
+
"plugin",
|
|
154
|
+
p
|
|
155
|
+
]])).concat(Stdlib_Option.mapOr(r.entityId, [], e => [[
|
|
156
|
+
"entityId",
|
|
157
|
+
e
|
|
158
|
+
]])));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function filterOfJson(json) {
|
|
162
|
+
let d = Stdlib_Option.getOr(Stdlib_Option.flatMap(json, Stdlib_JSON.Decode.object), {});
|
|
163
|
+
let s = k => Stdlib_Option.flatMap(d[k], Stdlib_JSON.Decode.string);
|
|
164
|
+
let a = k => Stdlib_Option.map(Stdlib_Option.flatMap(d[k], Stdlib_JSON.Decode.array), items => Stdlib_Array.filterMap(items, Stdlib_JSON.Decode.string));
|
|
165
|
+
return {
|
|
166
|
+
plugins: a("plugins"),
|
|
167
|
+
logs: a("logs"),
|
|
168
|
+
eventTypes: a("eventTypes"),
|
|
169
|
+
entityId: s("entityId"),
|
|
170
|
+
tagKey: s("tagKey"),
|
|
171
|
+
tagValue: s("tagValue"),
|
|
172
|
+
msgId: s("msgId"),
|
|
173
|
+
correlationId: s("correlationId"),
|
|
174
|
+
causationId: s("causationId"),
|
|
175
|
+
user: s("user"),
|
|
176
|
+
timeFrom: s("timeFrom"),
|
|
177
|
+
timeTo: s("timeTo")
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function metaString(meta, key) {
|
|
182
|
+
return Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(meta), d => d[key]), Stdlib_JSON.Decode.string);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function selectsLog(f, log, plugin) {
|
|
186
|
+
if (Stdlib_Option.mapOr(f.logs, true, logs => logs.includes(log))) {
|
|
187
|
+
return Stdlib_Option.mapOr(f.plugins, true, plugins => Stdlib_Option.mapOr(plugin, false, p => plugins.includes(p)));
|
|
188
|
+
} else {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function matches(f, r) {
|
|
194
|
+
let equalsMeta = (wanted, key) => Stdlib_Option.mapOr(wanted, true, w => Primitive_object.equal(metaString(r.meta, key), w));
|
|
195
|
+
let match = f.tagKey;
|
|
196
|
+
let match$1 = f.tagValue;
|
|
197
|
+
let tagMatch = match !== undefined ? (
|
|
198
|
+
match$1 !== undefined ? r.tags.some(t => {
|
|
199
|
+
if (t.key === match) {
|
|
200
|
+
return t.value === match$1;
|
|
201
|
+
} else {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}) : r.tags.some(t => t.key === match)
|
|
205
|
+
) : (
|
|
206
|
+
match$1 !== undefined ? r.tags.some(t => t.value === match$1) : true
|
|
207
|
+
);
|
|
208
|
+
let id = f.entityId;
|
|
209
|
+
let entityMatch = id !== undefined ? Primitive_object.equal(r.entityId, id) || r.tags.some(t => t.value === id) : true;
|
|
210
|
+
if (tagMatch && entityMatch && Stdlib_Option.mapOr(f.eventTypes, true, types => types.includes(r.eventType)) && equalsMeta(f.msgId, "msgId") && equalsMeta(f.correlationId, "correlationId") && equalsMeta(f.causationId, "causationId") && equalsMeta(f.user, "user") && Stdlib_Option.mapOr(f.timeFrom, true, from => r.recordedAt >= from)) {
|
|
211
|
+
return Stdlib_Option.mapOr(f.timeTo, true, to_ => r.recordedAt <= to_);
|
|
212
|
+
} else {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function toBase64Url(s) {
|
|
218
|
+
return btoa(s).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function fromBase64Url(s) {
|
|
222
|
+
let b = s.replaceAll("-", "+").replaceAll("_", "/");
|
|
223
|
+
return atob(b + "=".repeat((4 - b.length % 4 | 0) % 4));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function encodeCursor(states) {
|
|
227
|
+
return toBase64Url(JSON.stringify(Object.fromEntries(states.map(param => {
|
|
228
|
+
let state = param[1];
|
|
229
|
+
let tmp;
|
|
230
|
+
if (typeof state !== "object") {
|
|
231
|
+
tmp = "done";
|
|
232
|
+
} else {
|
|
233
|
+
let p = state._0;
|
|
234
|
+
tmp = p.TAG === "Below" ? Object.fromEntries([[
|
|
235
|
+
"below",
|
|
236
|
+
p._0
|
|
237
|
+
]]) : Object.fromEntries([[
|
|
238
|
+
"atMost",
|
|
239
|
+
p._0
|
|
240
|
+
]]);
|
|
241
|
+
}
|
|
242
|
+
return [
|
|
243
|
+
param[0],
|
|
244
|
+
tmp
|
|
245
|
+
];
|
|
246
|
+
}))));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function decodeCursor(cursor) {
|
|
250
|
+
let d;
|
|
251
|
+
try {
|
|
252
|
+
d = Stdlib_JSON.Decode.object(JSON.parse(fromBase64Url(cursor)));
|
|
253
|
+
} catch (exn) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (d === undefined) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
let out = {};
|
|
260
|
+
Object.entries(d).forEach(param => {
|
|
261
|
+
let v = param[1];
|
|
262
|
+
let log = param[0];
|
|
263
|
+
if (v === null || Array.isArray(v)) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
switch (typeof v) {
|
|
267
|
+
case "string" :
|
|
268
|
+
if (v !== "done") {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
out[log] = "Done";
|
|
272
|
+
return;
|
|
273
|
+
case "object" :
|
|
274
|
+
let match = Stdlib_Option.flatMap(v["below"], Stdlib_JSON.Decode.float);
|
|
275
|
+
let match$1 = Stdlib_Option.flatMap(v["atMost"], Stdlib_JSON.Decode.float);
|
|
276
|
+
if (match !== undefined) {
|
|
277
|
+
out[log] = {
|
|
278
|
+
TAG: "Until",
|
|
279
|
+
_0: {
|
|
280
|
+
TAG: "Below",
|
|
281
|
+
_0: match
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
return;
|
|
285
|
+
} else if (match$1 !== undefined) {
|
|
286
|
+
out[log] = {
|
|
287
|
+
TAG: "Until",
|
|
288
|
+
_0: {
|
|
289
|
+
TAG: "AtMost",
|
|
290
|
+
_0: match$1
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
return;
|
|
294
|
+
} else {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
default:
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
return out;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function toRecord(s, r) {
|
|
305
|
+
return {
|
|
306
|
+
plugin: s.plugin,
|
|
307
|
+
log: r.log,
|
|
308
|
+
position: r.position.toString(),
|
|
309
|
+
eventType: r.eventType,
|
|
310
|
+
entityId: r.entityId,
|
|
311
|
+
tags: r.tags,
|
|
312
|
+
payload: r.payload,
|
|
313
|
+
meta: r.meta,
|
|
314
|
+
recordedAt: r.recordedAt
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function newerFirst(param, param$1) {
|
|
319
|
+
let b = param$1[1];
|
|
320
|
+
let sb = param$1[0];
|
|
321
|
+
let a = param[1];
|
|
322
|
+
let sa = param[0];
|
|
323
|
+
if (a.recordedAt !== b.recordedAt) {
|
|
324
|
+
if (a.recordedAt > b.recordedAt) {
|
|
325
|
+
return -1;
|
|
326
|
+
} else {
|
|
327
|
+
return 1;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
let pa = Stdlib_Option.getOr(sa.plugin, "");
|
|
331
|
+
let pb = Stdlib_Option.getOr(sb.plugin, "");
|
|
332
|
+
if (pa !== pb) {
|
|
333
|
+
return Primitive_string.compare(pa, pb);
|
|
334
|
+
} else if (sa.log !== sb.log) {
|
|
335
|
+
return Primitive_string.compare(sa.log, sb.log);
|
|
336
|
+
} else {
|
|
337
|
+
return b.position - a.position;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function read(filter, first, after) {
|
|
342
|
+
let pageSize = Math.min(Math.max(Stdlib_Option.getOr(first, 50), 0), 1000);
|
|
343
|
+
let incoming = Stdlib_Option.flatMap(after, decodeCursor);
|
|
344
|
+
let sources = Stdlib_Array.filterMap(logs(), log => {
|
|
345
|
+
let plugin = pluginOfLog[log];
|
|
346
|
+
let match = readers[log];
|
|
347
|
+
let match$1 = selectsLog(filter, log, plugin);
|
|
348
|
+
if (match === undefined) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (!match$1) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
let start = incoming !== undefined ? Stdlib_Option.getOr(incoming[log], "Done") : ({
|
|
355
|
+
TAG: "Until",
|
|
356
|
+
_0: {
|
|
357
|
+
TAG: "AtMost",
|
|
358
|
+
_0: Pervasives.infinity
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
if (typeof start !== "object") {
|
|
362
|
+
return;
|
|
363
|
+
} else {
|
|
364
|
+
return {
|
|
365
|
+
log: log,
|
|
366
|
+
plugin: plugin,
|
|
367
|
+
reader: match,
|
|
368
|
+
scan: Stdlib_Option.isSome(incoming) ? start._0 : undefined,
|
|
369
|
+
fetched: false,
|
|
370
|
+
exhausted: false,
|
|
371
|
+
buffer: [],
|
|
372
|
+
state: Stdlib_Option.isSome(incoming) ? start : "Done"
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
let batch = Math.max(pageSize + 1 | 0, 50);
|
|
377
|
+
let fetch = async s => {
|
|
378
|
+
let rows = await s.reader.readBack(s.scan, batch, filter.correlationId);
|
|
379
|
+
if (!s.fetched) {
|
|
380
|
+
s.fetched = true;
|
|
381
|
+
if (Stdlib_Option.isNone(incoming)) {
|
|
382
|
+
let newest = rows[0];
|
|
383
|
+
s.state = newest !== undefined ? ({
|
|
384
|
+
TAG: "Until",
|
|
385
|
+
_0: {
|
|
386
|
+
TAG: "AtMost",
|
|
387
|
+
_0: newest.position
|
|
388
|
+
}
|
|
389
|
+
}) : "Done";
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
let oldest = rows.at(-1);
|
|
393
|
+
if (oldest !== undefined) {
|
|
394
|
+
s.scan = {
|
|
395
|
+
TAG: "Below",
|
|
396
|
+
_0: oldest.position
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
if (rows.length < batch) {
|
|
400
|
+
s.exhausted = true;
|
|
401
|
+
}
|
|
402
|
+
s.buffer = s.buffer.concat(rows.filter(r => matches(filter, r)));
|
|
403
|
+
};
|
|
404
|
+
let headOf = async s => {
|
|
405
|
+
let head = s.buffer[0];
|
|
406
|
+
if (head !== undefined) {
|
|
407
|
+
return head;
|
|
408
|
+
} else if (s.exhausted) {
|
|
409
|
+
return;
|
|
410
|
+
} else {
|
|
411
|
+
await fetch(s);
|
|
412
|
+
return await headOf(s);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
let newest = async () => {
|
|
416
|
+
let heads = [];
|
|
417
|
+
for (let i = 0, i_finish = sources.length; i < i_finish; ++i) {
|
|
418
|
+
let s = sources[i];
|
|
419
|
+
let r = await headOf(s);
|
|
420
|
+
if (r !== undefined) {
|
|
421
|
+
heads.push([
|
|
422
|
+
s,
|
|
423
|
+
r
|
|
424
|
+
]);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return heads.toSorted(newerFirst)[0];
|
|
428
|
+
};
|
|
429
|
+
let stateNow = () => sources.map(s => [
|
|
430
|
+
s.log,
|
|
431
|
+
s.state
|
|
432
|
+
]);
|
|
433
|
+
let edges = [];
|
|
434
|
+
let stop = false;
|
|
435
|
+
while (!stop && edges.length < pageSize) {
|
|
436
|
+
let match = await newest();
|
|
437
|
+
if (match !== undefined) {
|
|
438
|
+
let r = match[1];
|
|
439
|
+
let s = match[0];
|
|
440
|
+
s.buffer = s.buffer.slice(1, s.buffer.length);
|
|
441
|
+
s.state = s.exhausted && s.buffer.length === 0 ? "Done" : ({
|
|
442
|
+
TAG: "Until",
|
|
443
|
+
_0: {
|
|
444
|
+
TAG: "Below",
|
|
445
|
+
_0: r.position
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
edges.push([
|
|
449
|
+
toRecord(s, r),
|
|
450
|
+
encodeCursor(stateNow())
|
|
451
|
+
]);
|
|
452
|
+
} else {
|
|
453
|
+
stop = true;
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
let hasNextPage = Stdlib_Option.isSome(await newest());
|
|
457
|
+
sources.forEach(s => {
|
|
458
|
+
if (s.exhausted && s.buffer.length === 0) {
|
|
459
|
+
s.state = "Done";
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
return {
|
|
464
|
+
edges: edges,
|
|
465
|
+
hasNextPage: hasNextPage,
|
|
466
|
+
endCursor: edges.length !== 0 || Stdlib_Option.isSome(incoming) ? encodeCursor(stateNow()) : undefined
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
let maxNotes = 10000;
|
|
471
|
+
|
|
472
|
+
let defaultPageSize = 50;
|
|
473
|
+
|
|
474
|
+
let maxPageSize = 1000;
|
|
475
|
+
|
|
476
|
+
export {
|
|
477
|
+
metaOfFlat,
|
|
478
|
+
storedOfFlat,
|
|
479
|
+
numberOf,
|
|
480
|
+
readers,
|
|
481
|
+
pluginOfLog,
|
|
482
|
+
register,
|
|
483
|
+
notePlugin,
|
|
484
|
+
pluginOf,
|
|
485
|
+
logs,
|
|
486
|
+
positionToString,
|
|
487
|
+
maxNotes,
|
|
488
|
+
notes,
|
|
489
|
+
note,
|
|
490
|
+
take,
|
|
491
|
+
resetForTests,
|
|
492
|
+
recordToJson,
|
|
493
|
+
filterOfJson,
|
|
494
|
+
metaString,
|
|
495
|
+
selectsLog,
|
|
496
|
+
matches,
|
|
497
|
+
toBase64Url,
|
|
498
|
+
fromBase64Url,
|
|
499
|
+
encodeCursor,
|
|
500
|
+
decodeCursor,
|
|
501
|
+
defaultPageSize,
|
|
502
|
+
maxPageSize,
|
|
503
|
+
toRecord,
|
|
504
|
+
newerFirst,
|
|
505
|
+
read,
|
|
506
|
+
}
|
|
507
|
+
/* notes Not a pure module */
|
|
@@ -7,12 +7,13 @@
|
|
|
7
7
|
// what let it stop being configured. The noisy half (a line per event on stdout)
|
|
8
8
|
// stays opt-in. `REVENTLESS_EVENT_TAP=off` disables the socket too.
|
|
9
9
|
// See docs/plans/done/one-local-platform-one-store.md.
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
//
|
|
11
|
+
// The socket is a `LocalFeed`: each reader has a bounded queue and may send a
|
|
12
|
+
// filter line on connect. See docs/plans/the-local-platform-serves-what-is-watched.md.
|
|
12
13
|
|
|
13
14
|
let envVar = "REVENTLESS_EVENT_TAP"
|
|
14
15
|
|
|
15
|
-
type setting =
|
|
16
|
+
type setting = LocalFeed.setting =
|
|
16
17
|
| Off
|
|
17
18
|
| Ephemeral
|
|
18
19
|
| Fixed(int)
|
|
@@ -25,16 +26,7 @@ let envValue = (~env: dict<string>): option<string> =>
|
|
|
25
26
|
A port only when the value IS one in the unprivileged range: the runner has
|
|
26
27
|
passed `ndjson` since the tap existed, and someone writing `=1` means "on", not
|
|
27
28
|
port 1. Everything else — unset included — takes the ephemeral default. */
|
|
28
|
-
let settingFromEnv = (~env=NodeProcess.env): setting =>
|
|
29
|
-
switch envValue(~env) {
|
|
30
|
-
| Some("off") | Some("false") | Some("none") => Off
|
|
31
|
-
| Some(v) =>
|
|
32
|
-
switch Int.fromString(v) {
|
|
33
|
-
| Some(p) if p >= 1024 && p <= 65535 => Fixed(p)
|
|
34
|
-
| _ => Ephemeral
|
|
35
|
-
}
|
|
36
|
-
| None => Ephemeral
|
|
37
|
-
}
|
|
29
|
+
let settingFromEnv = (~env=NodeProcess.env): setting => LocalFeed.settingOf(envValue(~env))
|
|
38
30
|
|
|
39
31
|
/** Whether to ALSO write each line to stdout. Opt-in and unchanged: this is the
|
|
40
32
|
half with a visible cost, and it exists for the child whose stdout the runner
|
|
@@ -45,33 +37,26 @@ let stdoutEnabled = (~env=NodeProcess.env): bool =>
|
|
|
45
37
|
| Some(_) => true
|
|
46
38
|
}
|
|
47
39
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
40
|
+
/** The feed the socket serves. A filter line narrows it by plugin, event type and
|
|
41
|
+
correlation id; the other filter fields are about log lines and are ignored
|
|
42
|
+
here. */
|
|
43
|
+
let feed = LocalFeed.make(~name="EventTap", ~honours=[Plugins, EventTypes, CorrelationId], ())
|
|
51
44
|
|
|
52
45
|
/** The port the socket is actually bound to, known only once `listen` calls back —
|
|
53
46
|
which is why {!start} takes `~onBound` rather than the caller reading this
|
|
54
47
|
straight after. `None` until then, and if the bind fails. */
|
|
55
|
-
let port = () =>
|
|
48
|
+
let port = () => feed->LocalFeed.port
|
|
56
49
|
|
|
57
50
|
/** Whether anyone is reading, so the hot path can skip building a line nobody
|
|
58
51
|
wants. */
|
|
59
|
-
let hasReaders = () =>
|
|
52
|
+
let hasReaders = () => feed->LocalFeed.hasReaders
|
|
60
53
|
|
|
61
|
-
|
|
62
|
-
|
|
54
|
+
/** Sends a line with what a filter can ask about it. */
|
|
55
|
+
let publish = (item: LocalFeed.item): unit => feed->LocalFeed.publish(item)
|
|
63
56
|
|
|
64
57
|
/** Guarded per socket: a write to a closed peer throws, and one dead reader must
|
|
65
58
|
not cost the others their events or the platform its run. */
|
|
66
|
-
let broadcast = (line: string): unit =>
|
|
67
|
-
if hasReaders() {
|
|
68
|
-
connections.contents->Array.forEach(socket =>
|
|
69
|
-
switch socket->NodeNet.write(line ++ "\n") {
|
|
70
|
-
| _ => ()
|
|
71
|
-
| exception _ => drop(socket)
|
|
72
|
-
}
|
|
73
|
-
)
|
|
74
|
-
}
|
|
59
|
+
let broadcast = (line: string): unit => feed->LocalFeed.broadcast(line)
|
|
75
60
|
|
|
76
61
|
/** Binds the tap socket and reports the port it got.
|
|
77
62
|
|
|
@@ -80,60 +65,13 @@ let broadcast = (line: string): unit =>
|
|
|
80
65
|
failure mode: a platform whose diagnostic socket cannot bind must still serve,
|
|
81
66
|
and simply carries no tap port. */
|
|
82
67
|
let start = (~env=NodeProcess.env, ~onBound: int => unit=_ => (), ()): unit =>
|
|
83
|
-
|
|
84
|
-
| (Off, _) | (_, Some(_)) => ()
|
|
85
|
-
| (setting, None) =>
|
|
86
|
-
let requested = switch setting {
|
|
87
|
-
| Fixed(p) => p
|
|
88
|
-
// 0 lets the OS pick, so parallel platforms in one directory never collide and
|
|
89
|
-
// no unrelated service can be advertised as ours.
|
|
90
|
-
| Off | Ephemeral => 0
|
|
91
|
-
}
|
|
92
|
-
let s = NodeNet.createServer(socket => {
|
|
93
|
-
socket->NodeNet.setEncoding("utf8")
|
|
94
|
-
socket->NodeNet.onSocketError(_ => drop(socket))
|
|
95
|
-
socket->NodeNet.onSocketClose(() => drop(socket))
|
|
96
|
-
connections := connections.contents->Array.concat([socket])
|
|
97
|
-
})
|
|
98
|
-
s->NodeNet.onServerError(err => {
|
|
99
|
-
log.warn(
|
|
100
|
-
~comp="EventTap",
|
|
101
|
-
`could not listen: ${err->JsExn.message->Option.getOr("unknown error")}`,
|
|
102
|
-
)
|
|
103
|
-
server := None
|
|
104
|
-
listeningPort := None
|
|
105
|
-
})
|
|
106
|
-
// A diagnostic listener must never be why the process stays alive.
|
|
107
|
-
s->NodeNet.unref
|
|
108
|
-
s->NodeNet.listen(requested, "127.0.0.1", () =>
|
|
109
|
-
switch s->NodeNet.address->Nullable.toOption {
|
|
110
|
-
| Some(addr) =>
|
|
111
|
-
let bound = addr["port"]
|
|
112
|
-
listeningPort := Some(bound)
|
|
113
|
-
log.info(~comp="EventTap", `listening on 127.0.0.1:${bound->Int.toString}`)
|
|
114
|
-
onBound(bound)
|
|
115
|
-
| None => ()
|
|
116
|
-
}
|
|
117
|
-
)
|
|
118
|
-
server := Some(s)
|
|
119
|
-
}
|
|
68
|
+
feed->LocalFeed.start(~setting=settingFromEnv(~env), ~onBound, ())
|
|
120
69
|
|
|
121
70
|
/** Test seam: forgets connections and server without closing anything. */
|
|
122
|
-
let resetForTests = () =>
|
|
123
|
-
connections := []
|
|
124
|
-
server := None
|
|
125
|
-
listeningPort := None
|
|
126
|
-
}
|
|
71
|
+
let resetForTests = () => feed->LocalFeed.resetForTests
|
|
127
72
|
|
|
128
73
|
let addConnectionForTests = (socket: NodeNet.socket) =>
|
|
129
|
-
|
|
74
|
+
feed->LocalFeed.addConnectionForTests(socket)
|
|
130
75
|
|
|
131
76
|
/** Test seam: actually releases the port, so a suite can bind it again. */
|
|
132
|
-
let stopForTests = (): promise<unit> =>
|
|
133
|
-
switch server.contents {
|
|
134
|
-
| None => Promise.resolve()
|
|
135
|
-
| Some(s) =>
|
|
136
|
-
connections.contents->Array.forEach(NodeNet.destroySocket)
|
|
137
|
-
resetForTests()
|
|
138
|
-
Promise.make((resolve, _reject) => s->NodeNet.closeServer(() => resolve()))
|
|
139
|
-
}
|
|
77
|
+
let stopForTests = (): promise<unit> => feed->LocalFeed.stopForTests
|