@reventlessdev/reventless-local 3.0.0-alpha.113 → 3.0.0-alpha.115
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 +21 -0
- package/package.json +8 -8
- package/src/Platform.res +25 -50
- package/src/Platform.res.mjs +33 -160
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res +10 -1
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res.mjs +9 -1
- package/src/adapter/EventLog/EventLogStorage_InMemory.res +1 -1
- package/src/adapter/EventLog/EventLogStorage_InMemory.res.mjs +1 -1
- package/src/adapter/EventLog/EventLogStorage_Sqlite.res +16 -3
- package/src/adapter/EventLog/EventLogStorage_Sqlite.res.mjs +35 -7
- package/src/adapter/LocalBus.res +23 -3
- package/src/adapter/LocalBus.res.mjs +100 -66
- package/src/adapter/QueryDb/QueryDbStorage_InMemory.res +99 -7
- package/src/adapter/QueryDb/QueryDbStorage_InMemory.res.mjs +97 -13
- package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res +24 -1
- package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res.mjs +19 -4
- package/src/adapter/SqliteDriver.res +6 -1
- package/src/adapter/SqliteDriver.res.mjs +5 -1
- package/src/components/DcbEventLog_Builder.res +7 -1
- package/src/components/DcbEventLog_Builder.res.mjs +3 -3
- package/src/test/Mocks/MockEventLogStorage.res +1 -1
- package/src/test/Mocks/MockEventLogStorage.res.mjs +4 -1
- package/tests/adapter/BackendParityTest.res +64 -0
- package/tests/adapter/BackendParityTest.res.mjs +45 -0
- package/tests/adapter/EventLogStorageSqliteTest.res +4 -2
- package/tests/adapter/EventLogStorageSqliteTest.res.mjs +6 -1
- package/tests/adapter/LocalBusPubSubTest.res +31 -0
- package/tests/adapter/LocalBusPubSubTest.res.mjs +28 -0
- package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res +2 -0
- package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res.mjs +3 -0
- package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res +1 -0
- package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res.mjs +1 -0
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res.mjs +4 -2
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceFixtures.res +2 -0
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceFixtures.res.mjs +4 -2
package/src/adapter/LocalBus.res
CHANGED
|
@@ -265,7 +265,19 @@ module Impl = (C: BusConfig): T => {
|
|
|
265
265
|
let drainLoop = Effect.scoped(
|
|
266
266
|
PubSub.subscribe(hub)->Effect.flatMap(queue =>
|
|
267
267
|
Stream.fromQueue(queue)->Stream.runForEach(msg =>
|
|
268
|
-
|
|
268
|
+
// A throwing/rejecting handler must not (a) leave `done_` uncalled —
|
|
269
|
+
// the publisher's countdown would never reach zero and every publish
|
|
270
|
+
// on this topic would hang — nor (b) kill this drain fiber, which
|
|
271
|
+
// would stop consuming while the subscriber stays counted, hanging all
|
|
272
|
+
// future publishes too. So: convert a rejection into a typed error
|
|
273
|
+
// (`tryPromise`), log-and-recover (`catchAll`) so the stream keeps
|
|
274
|
+
// draining, and run `done_` via `ensuring` so the countdown always
|
|
275
|
+
// advances regardless of outcome.
|
|
276
|
+
Effect.tryPromise(~catch=e => e, () => handler(msg.service, msg.meta, msg.json))
|
|
277
|
+
->Effect.catchAll(err =>
|
|
278
|
+
Effect.sync(() => Console.error2("[LocalBus] subscriber handler failed:", err))
|
|
279
|
+
)
|
|
280
|
+
->Effect.ensuring(msg.done_)
|
|
269
281
|
)
|
|
270
282
|
),
|
|
271
283
|
)
|
|
@@ -372,7 +384,11 @@ module Impl = (C: BusConfig): T => {
|
|
|
372
384
|
let pending = queue.contents
|
|
373
385
|
queue.contents = []
|
|
374
386
|
pending->Array.forEach(json => {
|
|
375
|
-
let _ =
|
|
387
|
+
let _ =
|
|
388
|
+
handler(json, ())->Promise.catch(e => {
|
|
389
|
+
Console.error2("[LocalBus] parked command handler failed:", e)
|
|
390
|
+
Promise.resolve()
|
|
391
|
+
})
|
|
376
392
|
})
|
|
377
393
|
| None => ()
|
|
378
394
|
}
|
|
@@ -414,7 +430,11 @@ module Impl = (C: BusConfig): T => {
|
|
|
414
430
|
// and done_ is called immediately. This prevents a deadlock where publishEvent on the
|
|
415
431
|
// EP topic would block until the downstream aggregate command chain completes.
|
|
416
432
|
let makeFireAndForgetHandler = handler => (_, _, json) => {
|
|
417
|
-
let _ =
|
|
433
|
+
let _ =
|
|
434
|
+
handler(json, ())->Promise.catch(e => {
|
|
435
|
+
Console.error2("[LocalBus] fire-and-forget handler failed:", e)
|
|
436
|
+
Promise.resolve()
|
|
437
|
+
})
|
|
418
438
|
Promise.resolve()
|
|
419
439
|
}
|
|
420
440
|
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Effect from "@reventlessdev/rescript-effect/src/Effect.res.mjs";
|
|
3
4
|
import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
4
5
|
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
5
6
|
import * as Queue from "effect/Queue";
|
|
6
7
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
7
|
-
import * as Effect from "effect/Effect";
|
|
8
|
+
import * as Effect$1 from "effect/Effect";
|
|
8
9
|
import * as PubSub from "effect/PubSub";
|
|
9
10
|
import * as Stream from "effect/Stream";
|
|
11
|
+
import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
|
|
10
12
|
import * as Deferred from "effect/Deferred";
|
|
11
13
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
12
14
|
|
|
@@ -110,9 +112,9 @@ function Impl(C) {
|
|
|
110
112
|
};
|
|
111
113
|
let makeHub = () => {
|
|
112
114
|
if (capacity !== undefined) {
|
|
113
|
-
return Effect.runSync(PubSub.bounded(capacity));
|
|
115
|
+
return Effect$1.runSync(PubSub.bounded(capacity));
|
|
114
116
|
} else {
|
|
115
|
-
return Effect.runSync(PubSub.unbounded());
|
|
117
|
+
return Effect$1.runSync(PubSub.unbounded());
|
|
116
118
|
}
|
|
117
119
|
};
|
|
118
120
|
let subscribeToEvents = (topicName, handler) => {
|
|
@@ -127,8 +129,10 @@ function Impl(C) {
|
|
|
127
129
|
}
|
|
128
130
|
let n = Stdlib_Option.getOr(subscriberCounts.contents[topicName], 0);
|
|
129
131
|
subscriberCounts.contents[topicName] = n + 1 | 0;
|
|
130
|
-
let drainLoop = Effect.scoped(Effect.flatMap(PubSub.subscribe(hub), queue => Stream.runForEach(Stream.fromQueue(queue), msg => Effect.
|
|
131
|
-
|
|
132
|
+
let drainLoop = Effect$1.scoped(Effect$1.flatMap(PubSub.subscribe(hub), queue => Stream.runForEach(Stream.fromQueue(queue), msg => Effect$1.ensuring(Effect$1.catchAll(Effect.tryPromise(e => e, () => handler(msg.service, msg.meta, msg.json)), err => Effect$1.sync(() => {
|
|
133
|
+
console.error("[LocalBus] subscriber handler failed:", err);
|
|
134
|
+
})), msg.done_))));
|
|
135
|
+
Effect$1.runFork(drainLoop);
|
|
132
136
|
};
|
|
133
137
|
let subscribeToEventStream = topicName => {
|
|
134
138
|
let h = eventHubs.contents[topicName];
|
|
@@ -140,10 +144,10 @@ function Impl(C) {
|
|
|
140
144
|
eventHubs.contents[topicName] = h$1;
|
|
141
145
|
hub = h$1;
|
|
142
146
|
}
|
|
143
|
-
return Effect.map(Effect.acquireRelease(Effect.flatMap(Effect.sync(() => {
|
|
147
|
+
return Effect$1.map(Effect$1.acquireRelease(Effect$1.flatMap(Effect$1.sync(() => {
|
|
144
148
|
let n = Stdlib_Option.getOr(subscriberCounts.contents[topicName], 0);
|
|
145
149
|
subscriberCounts.contents[topicName] = n + 1 | 0;
|
|
146
|
-
}), () => PubSub.subscribe(hub)), (queue, _exit) => Effect.zipRight(Effect.sync(() => {
|
|
150
|
+
}), () => PubSub.subscribe(hub)), (queue, _exit) => Effect$1.zipRight(Effect$1.sync(() => {
|
|
147
151
|
let n = Stdlib_Option.getOr(subscriberCounts.contents[topicName], 1);
|
|
148
152
|
subscriberCounts.contents[topicName] = n - 1 | 0;
|
|
149
153
|
}), Queue.shutdown(queue))), queue => Stream.fromQueue(queue));
|
|
@@ -161,17 +165,17 @@ function Impl(C) {
|
|
|
161
165
|
if (n === 0) {
|
|
162
166
|
return;
|
|
163
167
|
}
|
|
164
|
-
let allDone = Effect.runSync(Deferred.make());
|
|
168
|
+
let allDone = Effect$1.runSync(Deferred.make());
|
|
165
169
|
let remaining = {
|
|
166
170
|
contents: n
|
|
167
171
|
};
|
|
168
|
-
let done_ = Effect.flatMap(Effect.sync(() => {
|
|
172
|
+
let done_ = Effect$1.flatMap(Effect$1.sync(() => {
|
|
169
173
|
remaining.contents = remaining.contents - 1 | 0;
|
|
170
174
|
}), () => {
|
|
171
175
|
if (remaining.contents === 0) {
|
|
172
|
-
return Effect.map(Deferred.succeed(allDone, undefined), param => {});
|
|
176
|
+
return Effect$1.map(Deferred.succeed(allDone, undefined), param => {});
|
|
173
177
|
} else {
|
|
174
|
-
return Effect.succeed();
|
|
178
|
+
return Effect$1.succeed();
|
|
175
179
|
}
|
|
176
180
|
});
|
|
177
181
|
let msg = {
|
|
@@ -180,10 +184,10 @@ function Impl(C) {
|
|
|
180
184
|
json: json,
|
|
181
185
|
done_: done_
|
|
182
186
|
};
|
|
183
|
-
let publishAndWait = capacity !== undefined ? Effect.flatMap(PubSub.publish(hub$1, msg), param => Deferred.await(allDone)) : Effect.zipRight(Effect.sync(() => {
|
|
184
|
-
Effect.runSync(PubSub.publish(hub$1, msg));
|
|
187
|
+
let publishAndWait = capacity !== undefined ? Effect$1.flatMap(PubSub.publish(hub$1, msg), param => Deferred.await(allDone)) : Effect$1.zipRight(Effect$1.sync(() => {
|
|
188
|
+
Effect$1.runSync(PubSub.publish(hub$1, msg));
|
|
185
189
|
}), Deferred.await(allDone));
|
|
186
|
-
await Effect.runPromise(publishAndWait);
|
|
190
|
+
await Effect$1.runPromise(publishAndWait);
|
|
187
191
|
};
|
|
188
192
|
let dispatchCommand = async (channelName, json) => {
|
|
189
193
|
let handler = commandHandlers.contents[channelName];
|
|
@@ -212,7 +216,10 @@ function Impl(C) {
|
|
|
212
216
|
let pending = queue.contents;
|
|
213
217
|
queue.contents = [];
|
|
214
218
|
pending.forEach(json => {
|
|
215
|
-
handler(json, undefined)
|
|
219
|
+
Stdlib_Promise.$$catch(handler(json, undefined), e => {
|
|
220
|
+
console.error("[LocalBus] parked command handler failed:", e);
|
|
221
|
+
return Promise.resolve();
|
|
222
|
+
});
|
|
216
223
|
});
|
|
217
224
|
};
|
|
218
225
|
let registerQueryDb = (name, ops) => {
|
|
@@ -252,7 +259,10 @@ function Impl(C) {
|
|
|
252
259
|
contents: {}
|
|
253
260
|
};
|
|
254
261
|
let makeFireAndForgetHandler = handler => ((param, param$1, json) => {
|
|
255
|
-
handler(json, undefined)
|
|
262
|
+
Stdlib_Promise.$$catch(handler(json, undefined), e => {
|
|
263
|
+
console.error("[LocalBus] fire-and-forget handler failed:", e);
|
|
264
|
+
return Promise.resolve();
|
|
265
|
+
});
|
|
256
266
|
return Promise.resolve();
|
|
257
267
|
});
|
|
258
268
|
let registerEventCollectorHandler = (ecName, handler) => {
|
|
@@ -273,10 +283,10 @@ function Impl(C) {
|
|
|
273
283
|
eventCollectorPendingTopics.contents[ecName] = pending;
|
|
274
284
|
};
|
|
275
285
|
let reset = () => {
|
|
276
|
-
let shutdownAll = Effect.map(Effect.all(Object.values(eventHubs.contents).map(hub => PubSub.shutdown(hub)), {
|
|
286
|
+
let shutdownAll = Effect$1.map(Effect$1.all(Object.values(eventHubs.contents).map(hub => PubSub.shutdown(hub)), {
|
|
277
287
|
concurrency: "unbounded"
|
|
278
288
|
}), param => {});
|
|
279
|
-
Effect.runSync(shutdownAll);
|
|
289
|
+
Effect$1.runSync(shutdownAll);
|
|
280
290
|
eventHubs.contents = {};
|
|
281
291
|
subscriberCounts.contents = {};
|
|
282
292
|
commandHandlers.contents = {};
|
|
@@ -342,7 +352,7 @@ function Make($star) {
|
|
|
342
352
|
let dcbEventLogReadRegistry = {
|
|
343
353
|
contents: {}
|
|
344
354
|
};
|
|
345
|
-
let makeHub = () => Effect.runSync(PubSub.unbounded());
|
|
355
|
+
let makeHub = () => Effect$1.runSync(PubSub.unbounded());
|
|
346
356
|
let subscribeToEvents = (topicName, handler) => {
|
|
347
357
|
let h = eventHubs.contents[topicName];
|
|
348
358
|
let hub;
|
|
@@ -355,8 +365,10 @@ function Make($star) {
|
|
|
355
365
|
}
|
|
356
366
|
let n = Stdlib_Option.getOr(subscriberCounts.contents[topicName], 0);
|
|
357
367
|
subscriberCounts.contents[topicName] = n + 1 | 0;
|
|
358
|
-
let drainLoop = Effect.scoped(Effect.flatMap(PubSub.subscribe(hub), queue => Stream.runForEach(Stream.fromQueue(queue), msg => Effect.
|
|
359
|
-
|
|
368
|
+
let drainLoop = Effect$1.scoped(Effect$1.flatMap(PubSub.subscribe(hub), queue => Stream.runForEach(Stream.fromQueue(queue), msg => Effect$1.ensuring(Effect$1.catchAll(Effect.tryPromise(e => e, () => handler(msg.service, msg.meta, msg.json)), err => Effect$1.sync(() => {
|
|
369
|
+
console.error("[LocalBus] subscriber handler failed:", err);
|
|
370
|
+
})), msg.done_))));
|
|
371
|
+
Effect$1.runFork(drainLoop);
|
|
360
372
|
};
|
|
361
373
|
let subscribeToEventStream = topicName => {
|
|
362
374
|
let h = eventHubs.contents[topicName];
|
|
@@ -368,10 +380,10 @@ function Make($star) {
|
|
|
368
380
|
eventHubs.contents[topicName] = h$1;
|
|
369
381
|
hub = h$1;
|
|
370
382
|
}
|
|
371
|
-
return Effect.map(Effect.acquireRelease(Effect.flatMap(Effect.sync(() => {
|
|
383
|
+
return Effect$1.map(Effect$1.acquireRelease(Effect$1.flatMap(Effect$1.sync(() => {
|
|
372
384
|
let n = Stdlib_Option.getOr(subscriberCounts.contents[topicName], 0);
|
|
373
385
|
subscriberCounts.contents[topicName] = n + 1 | 0;
|
|
374
|
-
}), () => PubSub.subscribe(hub)), (queue, _exit) => Effect.zipRight(Effect.sync(() => {
|
|
386
|
+
}), () => PubSub.subscribe(hub)), (queue, _exit) => Effect$1.zipRight(Effect$1.sync(() => {
|
|
375
387
|
let n = Stdlib_Option.getOr(subscriberCounts.contents[topicName], 1);
|
|
376
388
|
subscriberCounts.contents[topicName] = n - 1 | 0;
|
|
377
389
|
}), Queue.shutdown(queue))), queue => Stream.fromQueue(queue));
|
|
@@ -389,17 +401,17 @@ function Make($star) {
|
|
|
389
401
|
if (n === 0) {
|
|
390
402
|
return;
|
|
391
403
|
}
|
|
392
|
-
let allDone = Effect.runSync(Deferred.make());
|
|
404
|
+
let allDone = Effect$1.runSync(Deferred.make());
|
|
393
405
|
let remaining = {
|
|
394
406
|
contents: n
|
|
395
407
|
};
|
|
396
|
-
let done_ = Effect.flatMap(Effect.sync(() => {
|
|
408
|
+
let done_ = Effect$1.flatMap(Effect$1.sync(() => {
|
|
397
409
|
remaining.contents = remaining.contents - 1 | 0;
|
|
398
410
|
}), () => {
|
|
399
411
|
if (remaining.contents === 0) {
|
|
400
|
-
return Effect.map(Deferred.succeed(allDone, undefined), param => {});
|
|
412
|
+
return Effect$1.map(Deferred.succeed(allDone, undefined), param => {});
|
|
401
413
|
} else {
|
|
402
|
-
return Effect.succeed();
|
|
414
|
+
return Effect$1.succeed();
|
|
403
415
|
}
|
|
404
416
|
});
|
|
405
417
|
let msg = {
|
|
@@ -408,10 +420,10 @@ function Make($star) {
|
|
|
408
420
|
json: json,
|
|
409
421
|
done_: done_
|
|
410
422
|
};
|
|
411
|
-
let publishAndWait = Effect.zipRight(Effect.sync(() => {
|
|
412
|
-
Effect.runSync(PubSub.publish(hub$1, msg));
|
|
423
|
+
let publishAndWait = Effect$1.zipRight(Effect$1.sync(() => {
|
|
424
|
+
Effect$1.runSync(PubSub.publish(hub$1, msg));
|
|
413
425
|
}), Deferred.await(allDone));
|
|
414
|
-
await Effect.runPromise(publishAndWait);
|
|
426
|
+
await Effect$1.runPromise(publishAndWait);
|
|
415
427
|
};
|
|
416
428
|
let dispatchCommand = async (channelName, json) => {
|
|
417
429
|
let handler = commandHandlers.contents[channelName];
|
|
@@ -440,7 +452,10 @@ function Make($star) {
|
|
|
440
452
|
let pending = queue.contents;
|
|
441
453
|
queue.contents = [];
|
|
442
454
|
pending.forEach(json => {
|
|
443
|
-
handler(json, undefined)
|
|
455
|
+
Stdlib_Promise.$$catch(handler(json, undefined), e => {
|
|
456
|
+
console.error("[LocalBus] parked command handler failed:", e);
|
|
457
|
+
return Promise.resolve();
|
|
458
|
+
});
|
|
444
459
|
});
|
|
445
460
|
};
|
|
446
461
|
let registerQueryDb = (name, ops) => {
|
|
@@ -480,7 +495,10 @@ function Make($star) {
|
|
|
480
495
|
contents: {}
|
|
481
496
|
};
|
|
482
497
|
let makeFireAndForgetHandler = handler => ((param, param$1, json) => {
|
|
483
|
-
handler(json, undefined)
|
|
498
|
+
Stdlib_Promise.$$catch(handler(json, undefined), e => {
|
|
499
|
+
console.error("[LocalBus] fire-and-forget handler failed:", e);
|
|
500
|
+
return Promise.resolve();
|
|
501
|
+
});
|
|
484
502
|
return Promise.resolve();
|
|
485
503
|
});
|
|
486
504
|
let registerEventCollectorHandler = (ecName, handler) => {
|
|
@@ -501,10 +519,10 @@ function Make($star) {
|
|
|
501
519
|
eventCollectorPendingTopics.contents[ecName] = pending;
|
|
502
520
|
};
|
|
503
521
|
let reset = () => {
|
|
504
|
-
let shutdownAll = Effect.map(Effect.all(Object.values(eventHubs.contents).map(hub => PubSub.shutdown(hub)), {
|
|
522
|
+
let shutdownAll = Effect$1.map(Effect$1.all(Object.values(eventHubs.contents).map(hub => PubSub.shutdown(hub)), {
|
|
505
523
|
concurrency: "unbounded"
|
|
506
524
|
}), param => {});
|
|
507
|
-
Effect.runSync(shutdownAll);
|
|
525
|
+
Effect$1.runSync(shutdownAll);
|
|
508
526
|
eventHubs.contents = {};
|
|
509
527
|
subscriberCounts.contents = {};
|
|
510
528
|
commandHandlers.contents = {};
|
|
@@ -570,7 +588,7 @@ function MakeSilent($star) {
|
|
|
570
588
|
let dcbEventLogReadRegistry = {
|
|
571
589
|
contents: {}
|
|
572
590
|
};
|
|
573
|
-
let makeHub = () => Effect.runSync(PubSub.unbounded());
|
|
591
|
+
let makeHub = () => Effect$1.runSync(PubSub.unbounded());
|
|
574
592
|
let subscribeToEvents = (topicName, handler) => {
|
|
575
593
|
let h = eventHubs.contents[topicName];
|
|
576
594
|
let hub;
|
|
@@ -583,8 +601,10 @@ function MakeSilent($star) {
|
|
|
583
601
|
}
|
|
584
602
|
let n = Stdlib_Option.getOr(subscriberCounts.contents[topicName], 0);
|
|
585
603
|
subscriberCounts.contents[topicName] = n + 1 | 0;
|
|
586
|
-
let drainLoop = Effect.scoped(Effect.flatMap(PubSub.subscribe(hub), queue => Stream.runForEach(Stream.fromQueue(queue), msg => Effect.
|
|
587
|
-
|
|
604
|
+
let drainLoop = Effect$1.scoped(Effect$1.flatMap(PubSub.subscribe(hub), queue => Stream.runForEach(Stream.fromQueue(queue), msg => Effect$1.ensuring(Effect$1.catchAll(Effect.tryPromise(e => e, () => handler(msg.service, msg.meta, msg.json)), err => Effect$1.sync(() => {
|
|
605
|
+
console.error("[LocalBus] subscriber handler failed:", err);
|
|
606
|
+
})), msg.done_))));
|
|
607
|
+
Effect$1.runFork(drainLoop);
|
|
588
608
|
};
|
|
589
609
|
let subscribeToEventStream = topicName => {
|
|
590
610
|
let h = eventHubs.contents[topicName];
|
|
@@ -596,10 +616,10 @@ function MakeSilent($star) {
|
|
|
596
616
|
eventHubs.contents[topicName] = h$1;
|
|
597
617
|
hub = h$1;
|
|
598
618
|
}
|
|
599
|
-
return Effect.map(Effect.acquireRelease(Effect.flatMap(Effect.sync(() => {
|
|
619
|
+
return Effect$1.map(Effect$1.acquireRelease(Effect$1.flatMap(Effect$1.sync(() => {
|
|
600
620
|
let n = Stdlib_Option.getOr(subscriberCounts.contents[topicName], 0);
|
|
601
621
|
subscriberCounts.contents[topicName] = n + 1 | 0;
|
|
602
|
-
}), () => PubSub.subscribe(hub)), (queue, _exit) => Effect.zipRight(Effect.sync(() => {
|
|
622
|
+
}), () => PubSub.subscribe(hub)), (queue, _exit) => Effect$1.zipRight(Effect$1.sync(() => {
|
|
603
623
|
let n = Stdlib_Option.getOr(subscriberCounts.contents[topicName], 1);
|
|
604
624
|
subscriberCounts.contents[topicName] = n - 1 | 0;
|
|
605
625
|
}), Queue.shutdown(queue))), queue => Stream.fromQueue(queue));
|
|
@@ -617,17 +637,17 @@ function MakeSilent($star) {
|
|
|
617
637
|
if (n === 0) {
|
|
618
638
|
return;
|
|
619
639
|
}
|
|
620
|
-
let allDone = Effect.runSync(Deferred.make());
|
|
640
|
+
let allDone = Effect$1.runSync(Deferred.make());
|
|
621
641
|
let remaining = {
|
|
622
642
|
contents: n
|
|
623
643
|
};
|
|
624
|
-
let done_ = Effect.flatMap(Effect.sync(() => {
|
|
644
|
+
let done_ = Effect$1.flatMap(Effect$1.sync(() => {
|
|
625
645
|
remaining.contents = remaining.contents - 1 | 0;
|
|
626
646
|
}), () => {
|
|
627
647
|
if (remaining.contents === 0) {
|
|
628
|
-
return Effect.map(Deferred.succeed(allDone, undefined), param => {});
|
|
648
|
+
return Effect$1.map(Deferred.succeed(allDone, undefined), param => {});
|
|
629
649
|
} else {
|
|
630
|
-
return Effect.succeed();
|
|
650
|
+
return Effect$1.succeed();
|
|
631
651
|
}
|
|
632
652
|
});
|
|
633
653
|
let msg = {
|
|
@@ -636,10 +656,10 @@ function MakeSilent($star) {
|
|
|
636
656
|
json: json,
|
|
637
657
|
done_: done_
|
|
638
658
|
};
|
|
639
|
-
let publishAndWait = Effect.zipRight(Effect.sync(() => {
|
|
640
|
-
Effect.runSync(PubSub.publish(hub$1, msg));
|
|
659
|
+
let publishAndWait = Effect$1.zipRight(Effect$1.sync(() => {
|
|
660
|
+
Effect$1.runSync(PubSub.publish(hub$1, msg));
|
|
641
661
|
}), Deferred.await(allDone));
|
|
642
|
-
await Effect.runPromise(publishAndWait);
|
|
662
|
+
await Effect$1.runPromise(publishAndWait);
|
|
643
663
|
};
|
|
644
664
|
let dispatchCommand = async (channelName, json) => {
|
|
645
665
|
let handler = commandHandlers.contents[channelName];
|
|
@@ -668,7 +688,10 @@ function MakeSilent($star) {
|
|
|
668
688
|
let pending = queue.contents;
|
|
669
689
|
queue.contents = [];
|
|
670
690
|
pending.forEach(json => {
|
|
671
|
-
handler(json, undefined)
|
|
691
|
+
Stdlib_Promise.$$catch(handler(json, undefined), e => {
|
|
692
|
+
console.error("[LocalBus] parked command handler failed:", e);
|
|
693
|
+
return Promise.resolve();
|
|
694
|
+
});
|
|
672
695
|
});
|
|
673
696
|
};
|
|
674
697
|
let registerQueryDb = (name, ops) => {
|
|
@@ -708,7 +731,10 @@ function MakeSilent($star) {
|
|
|
708
731
|
contents: {}
|
|
709
732
|
};
|
|
710
733
|
let makeFireAndForgetHandler = handler => ((param, param$1, json) => {
|
|
711
|
-
handler(json, undefined)
|
|
734
|
+
Stdlib_Promise.$$catch(handler(json, undefined), e => {
|
|
735
|
+
console.error("[LocalBus] fire-and-forget handler failed:", e);
|
|
736
|
+
return Promise.resolve();
|
|
737
|
+
});
|
|
712
738
|
return Promise.resolve();
|
|
713
739
|
});
|
|
714
740
|
let registerEventCollectorHandler = (ecName, handler) => {
|
|
@@ -729,10 +755,10 @@ function MakeSilent($star) {
|
|
|
729
755
|
eventCollectorPendingTopics.contents[ecName] = pending;
|
|
730
756
|
};
|
|
731
757
|
let reset = () => {
|
|
732
|
-
let shutdownAll = Effect.map(Effect.all(Object.values(eventHubs.contents).map(hub => PubSub.shutdown(hub)), {
|
|
758
|
+
let shutdownAll = Effect$1.map(Effect$1.all(Object.values(eventHubs.contents).map(hub => PubSub.shutdown(hub)), {
|
|
733
759
|
concurrency: "unbounded"
|
|
734
760
|
}), param => {});
|
|
735
|
-
Effect.runSync(shutdownAll);
|
|
761
|
+
Effect$1.runSync(shutdownAll);
|
|
736
762
|
eventHubs.contents = {};
|
|
737
763
|
subscriberCounts.contents = {};
|
|
738
764
|
commandHandlers.contents = {};
|
|
@@ -799,7 +825,7 @@ function MakeBounded(C) {
|
|
|
799
825
|
let dcbEventLogReadRegistry = {
|
|
800
826
|
contents: {}
|
|
801
827
|
};
|
|
802
|
-
let makeHub = () => Effect.runSync(PubSub.bounded(capacity));
|
|
828
|
+
let makeHub = () => Effect$1.runSync(PubSub.bounded(capacity));
|
|
803
829
|
let subscribeToEvents = (topicName, handler) => {
|
|
804
830
|
let h = eventHubs.contents[topicName];
|
|
805
831
|
let hub;
|
|
@@ -812,8 +838,10 @@ function MakeBounded(C) {
|
|
|
812
838
|
}
|
|
813
839
|
let n = Stdlib_Option.getOr(subscriberCounts.contents[topicName], 0);
|
|
814
840
|
subscriberCounts.contents[topicName] = n + 1 | 0;
|
|
815
|
-
let drainLoop = Effect.scoped(Effect.flatMap(PubSub.subscribe(hub), queue => Stream.runForEach(Stream.fromQueue(queue), msg => Effect.
|
|
816
|
-
|
|
841
|
+
let drainLoop = Effect$1.scoped(Effect$1.flatMap(PubSub.subscribe(hub), queue => Stream.runForEach(Stream.fromQueue(queue), msg => Effect$1.ensuring(Effect$1.catchAll(Effect.tryPromise(e => e, () => handler(msg.service, msg.meta, msg.json)), err => Effect$1.sync(() => {
|
|
842
|
+
console.error("[LocalBus] subscriber handler failed:", err);
|
|
843
|
+
})), msg.done_))));
|
|
844
|
+
Effect$1.runFork(drainLoop);
|
|
817
845
|
};
|
|
818
846
|
let subscribeToEventStream = topicName => {
|
|
819
847
|
let h = eventHubs.contents[topicName];
|
|
@@ -825,10 +853,10 @@ function MakeBounded(C) {
|
|
|
825
853
|
eventHubs.contents[topicName] = h$1;
|
|
826
854
|
hub = h$1;
|
|
827
855
|
}
|
|
828
|
-
return Effect.map(Effect.acquireRelease(Effect.flatMap(Effect.sync(() => {
|
|
856
|
+
return Effect$1.map(Effect$1.acquireRelease(Effect$1.flatMap(Effect$1.sync(() => {
|
|
829
857
|
let n = Stdlib_Option.getOr(subscriberCounts.contents[topicName], 0);
|
|
830
858
|
subscriberCounts.contents[topicName] = n + 1 | 0;
|
|
831
|
-
}), () => PubSub.subscribe(hub)), (queue, _exit) => Effect.zipRight(Effect.sync(() => {
|
|
859
|
+
}), () => PubSub.subscribe(hub)), (queue, _exit) => Effect$1.zipRight(Effect$1.sync(() => {
|
|
832
860
|
let n = Stdlib_Option.getOr(subscriberCounts.contents[topicName], 1);
|
|
833
861
|
subscriberCounts.contents[topicName] = n - 1 | 0;
|
|
834
862
|
}), Queue.shutdown(queue))), queue => Stream.fromQueue(queue));
|
|
@@ -845,17 +873,17 @@ function MakeBounded(C) {
|
|
|
845
873
|
if (n === 0) {
|
|
846
874
|
return;
|
|
847
875
|
}
|
|
848
|
-
let allDone = Effect.runSync(Deferred.make());
|
|
876
|
+
let allDone = Effect$1.runSync(Deferred.make());
|
|
849
877
|
let remaining = {
|
|
850
878
|
contents: n
|
|
851
879
|
};
|
|
852
|
-
let done_ = Effect.flatMap(Effect.sync(() => {
|
|
880
|
+
let done_ = Effect$1.flatMap(Effect$1.sync(() => {
|
|
853
881
|
remaining.contents = remaining.contents - 1 | 0;
|
|
854
882
|
}), () => {
|
|
855
883
|
if (remaining.contents === 0) {
|
|
856
|
-
return Effect.map(Deferred.succeed(allDone, undefined), param => {});
|
|
884
|
+
return Effect$1.map(Deferred.succeed(allDone, undefined), param => {});
|
|
857
885
|
} else {
|
|
858
|
-
return Effect.succeed();
|
|
886
|
+
return Effect$1.succeed();
|
|
859
887
|
}
|
|
860
888
|
});
|
|
861
889
|
let msg = {
|
|
@@ -864,8 +892,8 @@ function MakeBounded(C) {
|
|
|
864
892
|
json: json,
|
|
865
893
|
done_: done_
|
|
866
894
|
};
|
|
867
|
-
let publishAndWait = Effect.flatMap(PubSub.publish(Primitive_option.valFromOption(hub), msg), param => Deferred.await(allDone));
|
|
868
|
-
await Effect.runPromise(publishAndWait);
|
|
895
|
+
let publishAndWait = Effect$1.flatMap(PubSub.publish(Primitive_option.valFromOption(hub), msg), param => Deferred.await(allDone));
|
|
896
|
+
await Effect$1.runPromise(publishAndWait);
|
|
869
897
|
};
|
|
870
898
|
let dispatchCommand = async (channelName, json) => {
|
|
871
899
|
let handler = commandHandlers.contents[channelName];
|
|
@@ -894,7 +922,10 @@ function MakeBounded(C) {
|
|
|
894
922
|
let pending = queue.contents;
|
|
895
923
|
queue.contents = [];
|
|
896
924
|
pending.forEach(json => {
|
|
897
|
-
handler(json, undefined)
|
|
925
|
+
Stdlib_Promise.$$catch(handler(json, undefined), e => {
|
|
926
|
+
console.error("[LocalBus] parked command handler failed:", e);
|
|
927
|
+
return Promise.resolve();
|
|
928
|
+
});
|
|
898
929
|
});
|
|
899
930
|
};
|
|
900
931
|
let registerQueryDb = (name, ops) => {
|
|
@@ -934,7 +965,10 @@ function MakeBounded(C) {
|
|
|
934
965
|
contents: {}
|
|
935
966
|
};
|
|
936
967
|
let makeFireAndForgetHandler = handler => ((param, param$1, json) => {
|
|
937
|
-
handler(json, undefined)
|
|
968
|
+
Stdlib_Promise.$$catch(handler(json, undefined), e => {
|
|
969
|
+
console.error("[LocalBus] fire-and-forget handler failed:", e);
|
|
970
|
+
return Promise.resolve();
|
|
971
|
+
});
|
|
938
972
|
return Promise.resolve();
|
|
939
973
|
});
|
|
940
974
|
let registerEventCollectorHandler = (ecName, handler) => {
|
|
@@ -955,10 +989,10 @@ function MakeBounded(C) {
|
|
|
955
989
|
eventCollectorPendingTopics.contents[ecName] = pending;
|
|
956
990
|
};
|
|
957
991
|
let reset = () => {
|
|
958
|
-
let shutdownAll = Effect.map(Effect.all(Object.values(eventHubs.contents).map(hub => PubSub.shutdown(hub)), {
|
|
992
|
+
let shutdownAll = Effect$1.map(Effect$1.all(Object.values(eventHubs.contents).map(hub => PubSub.shutdown(hub)), {
|
|
959
993
|
concurrency: "unbounded"
|
|
960
994
|
}), param => {});
|
|
961
|
-
Effect.runSync(shutdownAll);
|
|
995
|
+
Effect$1.runSync(shutdownAll);
|
|
962
996
|
eventHubs.contents = {};
|
|
963
997
|
subscriberCounts.contents = {};
|
|
964
998
|
commandHandlers.contents = {};
|
|
@@ -1008,4 +1042,4 @@ export {
|
|
|
1008
1042
|
MakeSilent,
|
|
1009
1043
|
MakeBounded,
|
|
1010
1044
|
}
|
|
1011
|
-
/*
|
|
1045
|
+
/* Effect Not a pure module */
|
|
@@ -87,6 +87,60 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
87
87
|
allItems.contents = flattenStore(store.contents)
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
// TTL parity with the SQLite backend (which filters on `expires_at`). We
|
|
91
|
+
// record an absolute expiry (epoch seconds, the same value SQLite stores) per
|
|
92
|
+
// (partition, sub-key) and drop expired entries lazily on read — a session
|
|
93
|
+
// without reads never spends cycles expiring, matching SQLite's read-time
|
|
94
|
+
// `notExpiredClause`. Previously `~ttl` was ignored entirely in memory.
|
|
95
|
+
let expiries: ref<dict<dict<float>>> = ref(Dict.make())
|
|
96
|
+
let nowSecs = () => Date.now() /. 1000.0
|
|
97
|
+
let recordExpiry = (id: string, subKey: string, ttl: option<int>) =>
|
|
98
|
+
switch ttl {
|
|
99
|
+
| Some(t) =>
|
|
100
|
+
let subExp = switch expiries.contents->Dict.get(id) {
|
|
101
|
+
| Some(m) => m
|
|
102
|
+
| None =>
|
|
103
|
+
let m = Dict.make()
|
|
104
|
+
expiries.contents->Dict.set(id, m)
|
|
105
|
+
m
|
|
106
|
+
}
|
|
107
|
+
subExp->Dict.set(subKey, Int.toFloat(t))
|
|
108
|
+
| None =>
|
|
109
|
+
switch expiries.contents->Dict.get(id) {
|
|
110
|
+
| Some(m) => m->Dict.delete(subKey)
|
|
111
|
+
| None => ()
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
let purgeExpired = () => {
|
|
115
|
+
let now = nowSecs()
|
|
116
|
+
let removedAny = ref(false)
|
|
117
|
+
expiries.contents
|
|
118
|
+
->Dict.toArray
|
|
119
|
+
->Array.forEach(((id, subExp)) =>
|
|
120
|
+
subExp
|
|
121
|
+
->Dict.toArray
|
|
122
|
+
->Array.forEach(((subKey, exp)) =>
|
|
123
|
+
if exp <= now {
|
|
124
|
+
switch store.contents->Dict.get(id) {
|
|
125
|
+
| Some(sm) =>
|
|
126
|
+
if sm->Dict.get(subKey)->Option.isSome {
|
|
127
|
+
sm->Dict.delete(subKey)
|
|
128
|
+
removedAny := true
|
|
129
|
+
}
|
|
130
|
+
if sm->Dict.keysToArray->Array.length == 0 {
|
|
131
|
+
store.contents->Dict.delete(id)
|
|
132
|
+
}
|
|
133
|
+
| None => ()
|
|
134
|
+
}
|
|
135
|
+
subExp->Dict.delete(subKey)
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
if removedAny.contents {
|
|
140
|
+
syncAll()
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
90
144
|
let getOrCreateSubMap = (partitionKey: string): dict<JSON.t> =>
|
|
91
145
|
switch store.contents->Dict.get(partitionKey) {
|
|
92
146
|
| Some(m) => m
|
|
@@ -96,11 +150,15 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
96
150
|
m
|
|
97
151
|
}
|
|
98
152
|
|
|
99
|
-
let load: QueryDb.load<string, JSON.t> = async id =>
|
|
153
|
+
let load: QueryDb.load<string, JSON.t> = async id => {
|
|
154
|
+
purgeExpired()
|
|
100
155
|
Ok(sortedItems(store.contents, id))
|
|
156
|
+
}
|
|
101
157
|
|
|
102
|
-
let loadStream: QueryDb.loadStream<string, JSON.t> = id =>
|
|
158
|
+
let loadStream: QueryDb.loadStream<string, JSON.t> = id => {
|
|
159
|
+
purgeExpired()
|
|
103
160
|
sortedItems(store.contents, id)->Stream.fromIterable
|
|
161
|
+
}
|
|
104
162
|
|
|
105
163
|
// Entity key matches the AWS StateTopic Lambda output (Phase 1):
|
|
106
164
|
// single-key tables → partition value; composite tables → `pk-sk`.
|
|
@@ -129,20 +187,22 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
129
187
|
Bus.publishStateChange(~name, ~descriptor)
|
|
130
188
|
}
|
|
131
189
|
|
|
132
|
-
let save: QueryDb.save<string, JSON.t> = async (id, state, _saveMode,
|
|
190
|
+
let save: QueryDb.save<string, JSON.t> = async (id, state, _saveMode, ttl) => {
|
|
133
191
|
let subKey = getSubKey(state, subIdField)
|
|
134
192
|
let subMap = getOrCreateSubMap(id)
|
|
135
193
|
subMap->Dict.set(subKey, state)
|
|
194
|
+
recordExpiry(id, subKey, ttl)
|
|
136
195
|
syncAll()
|
|
137
196
|
publishUpdated(id, state)
|
|
138
197
|
Ok()
|
|
139
198
|
}
|
|
140
199
|
|
|
141
200
|
let saveBatch: QueryDb.saveBatch<string, JSON.t> = async batch => {
|
|
142
|
-
batch->Array.forEach(((id, state,
|
|
201
|
+
batch->Array.forEach(((id, state, ttl)) => {
|
|
143
202
|
let subKey = getSubKey(state, subIdField)
|
|
144
203
|
let subMap = getOrCreateSubMap(id)
|
|
145
204
|
subMap->Dict.set(subKey, state)
|
|
205
|
+
recordExpiry(id, subKey, ttl)
|
|
146
206
|
})
|
|
147
207
|
syncAll()
|
|
148
208
|
// Notify subscribers for each saved item
|
|
@@ -150,7 +210,33 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
150
210
|
Ok()
|
|
151
211
|
}
|
|
152
212
|
|
|
153
|
-
|
|
213
|
+
// Atomic-ish counter, mirroring DynamoDB's `ADD #fieldName :inc` on key {id}:
|
|
214
|
+
// read the field on the partition-key item (counters are single-state, so the
|
|
215
|
+
// implicit sub-key ""), add `inc`, persist, and return the NEW total. The
|
|
216
|
+
// previous `Ok(inc)` echoed the increment and never touched the store, so the
|
|
217
|
+
// running total was wrong and `loadStream` never reflected the counter.
|
|
218
|
+
let count: QueryDb.count<string> = async (id, fieldName, inc) => {
|
|
219
|
+
let subMap = getOrCreateSubMap(id)
|
|
220
|
+
let existing = subMap->Dict.get("")->Option.flatMap(JSON.Decode.object)
|
|
221
|
+
let current =
|
|
222
|
+
existing
|
|
223
|
+
->Option.flatMap(o => o->Dict.get(fieldName))
|
|
224
|
+
->Option.flatMap(JSON.Decode.float)
|
|
225
|
+
->Option.mapOr(0, Float.toInt)
|
|
226
|
+
let next = current + inc
|
|
227
|
+
let obj = Dict.make()
|
|
228
|
+
switch existing {
|
|
229
|
+
| Some(o) => o->Dict.toArray->Array.forEach(((k, v)) => obj->Dict.set(k, v))
|
|
230
|
+
| None => ()
|
|
231
|
+
}
|
|
232
|
+
obj->Dict.set("id", JSON.Encode.string(id))
|
|
233
|
+
obj->Dict.set(fieldName, JSON.Encode.int(next))
|
|
234
|
+
let newItem = JSON.Encode.object(obj)
|
|
235
|
+
subMap->Dict.set("", newItem)
|
|
236
|
+
syncAll()
|
|
237
|
+
publishUpdated(id, newItem)
|
|
238
|
+
Ok(next)
|
|
239
|
+
}
|
|
154
240
|
|
|
155
241
|
let delete: QueryDb.delete<string> = async (id, subIdOpt) => {
|
|
156
242
|
switch subIdOpt {
|
|
@@ -215,8 +301,14 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
215
301
|
}
|
|
216
302
|
|
|
217
303
|
Bus.registerQueryDb(name, ops)
|
|
218
|
-
Bus.registerQueryDbScan(name, () =>
|
|
219
|
-
|
|
304
|
+
Bus.registerQueryDbScan(name, () => {
|
|
305
|
+
purgeExpired()
|
|
306
|
+
allItems.contents
|
|
307
|
+
})
|
|
308
|
+
Bus.registerQueryDbStream(name, () => {
|
|
309
|
+
purgeExpired()
|
|
310
|
+
allItems.contents->Stream.fromIterable
|
|
311
|
+
})
|
|
220
312
|
|
|
221
313
|
{
|
|
222
314
|
resources: [],
|