@reventlessdev/reventless-core 3.0.0-alpha.170 → 3.0.0-alpha.172
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 +14 -0
- package/package.json +14 -14
- package/src/components/ExtensionPoint/ExtensionPoint_Builder.res +5 -2
- package/src/components/ExtensionPoint/ExtensionPoint_Builder.res.mjs +3 -3
- package/src/components/Heartbeat/Heartbeat.res +2 -2
- package/src/components/Heartbeat/Heartbeat_Builder.res +2 -2
- package/src/components/Heartbeat/Heartbeat_Builder.res.mjs +2 -4
- package/src/components/Task/Task_Builder.res +6 -4
- package/src/components/Task/Task_Builder.res.mjs +4 -4
- package/src/plugin/component/Plugin_Builder.res +5 -1
- package/src/plugin/component/Plugin_Builder.res.mjs +1 -1
- package/src/plugin/component/Plugin_Helpers.res +6 -1
- package/src/plugin/connect/PluginExtensionPoint_Builder.res +2 -0
- package/src/plugin/connect/PluginExtensionPoint_Builder.res.mjs +3 -3
- package/src/plugin/connect/PluginExtensionPoint_Plugin.res +12 -5
- package/src/plugin/connect/PluginExtensionPoint_Plugin.res.mjs +8 -2
- package/tests/plugin/HeartbeatDisconnectGraceTest.res +98 -0
- package/tests/IdentityTest.res.mjs +0 -174
- package/tests/adapter/MonitoringTest.res.mjs +0 -82
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +0 -356
- package/tests/admin/Platform_UIFragmentsApiTest.res.mjs +0 -101
- package/tests/aggregate/AggregateCacheTest.res.mjs +0 -433
- package/tests/aggregate/AggregateCallbackTest.res.mjs +0 -143
- package/tests/aggregate/AggregateCausationTest.res.mjs +0 -47
- package/tests/aggregate/AggregateConflictTest.res.mjs +0 -386
- package/tests/aggregate/AggregateFixtures.res.mjs +0 -308
- package/tests/aggregate/AggregateRejectionTest.res.mjs +0 -166
- package/tests/aggregate/AggregateSnapshotTest.res.mjs +0 -645
- package/tests/api/ApiNoApiTest.res.mjs +0 -90
- package/tests/api/GraphQL_StitcherTest.res.mjs +0 -112
- package/tests/api/SimpleTest.res.mjs +0 -10
- package/tests/api/SuryToJsonSchemaTest.res.mjs +0 -674
- package/tests/commandgenerator/CommandGeneratorCallbackTest.res.mjs +0 -160
- package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +0 -197
- package/tests/commandtopic/CommandTopicCallbackFixtures.res.mjs +0 -152
- package/tests/commandtopic/CommandTopicCallbackTest.res.mjs +0 -106
- package/tests/commandtopic/CommandTopicHelpersRejectionTest.res.mjs +0 -109
- package/tests/counter/CounterCallbackTest.res.mjs +0 -140
- package/tests/counter/CounterFixtures.res.mjs +0 -67
- package/tests/dcb/DcbDecodeTest.res.mjs +0 -194
- package/tests/dcb/DcbEventLogOperationsTest.res.mjs +0 -268
- package/tests/dcb/DcbFixtures.res.mjs +0 -534
- package/tests/dcb/DcbScopeInferenceTest.res.mjs +0 -480
- package/tests/dcb/DcbStateChangeSliceTest.res.mjs +0 -540
- package/tests/dcb/DcbTagTest.res.mjs +0 -867
- package/tests/dcb/DcbValidationTest.res.mjs +0 -476
- package/tests/eventlog/EventLogFixtures.res.mjs +0 -196
- package/tests/eventlog/EventLogOperationsTest.res.mjs +0 -192
- package/tests/eventlog/EventLogRetryTest.res.mjs +0 -195
- package/tests/eventmapper/EventMapperCallbackTest.res.mjs +0 -141
- package/tests/eventmapper/EventMapperFixtures.res.mjs +0 -440
- package/tests/eventmapper/EventMapperTest.res.mjs +0 -83
- package/tests/eventtopic/EventTopicFixtures.res.mjs +0 -65
- package/tests/eventtopic/EventTopicOperationsTest.res.mjs +0 -71
- package/tests/extensionpoint/ExtensionPointCallbackTest.res.mjs +0 -94
- package/tests/extensionpoint/ExtensionPointFixtures.res.mjs +0 -205
- package/tests/extensionpoint/ExtensionPointOperationsTest.res.mjs +0 -304
- package/tests/ftp/FTPTest.res.mjs +0 -88
- package/tests/logger/LogFormatTest.res.mjs +0 -309
- package/tests/logger/LoggerLazyTest.res.mjs +0 -40
- package/tests/message/MessageTest.res.mjs +0 -218
- package/tests/message/MetaEnvelopeTest.res.mjs +0 -188
- package/tests/plugin/ManifestVisibilityTest.res.mjs +0 -215
- package/tests/plugin/PluginSpecExperiment.res.mjs +0 -31
- package/tests/plugin/PluginStructureTest.res.mjs +0 -419
- package/tests/plugin/PluginVersionTest.res.mjs +0 -30
- package/tests/plugin/SchemaWalkerTest.res.mjs +0 -160
- package/tests/plugin/StateChangeSlice/PsPlaceOrder.res.mjs +0 -62
- package/tests/plugin/StateChangeSlice/PsShipOrder.res.mjs +0 -69
- package/tests/plugin/StateViewSlice/PsAnnotatedView.res.mjs +0 -95
- package/tests/plugin/StateViewSlice/PsAvailableProductsView.res.mjs +0 -69
- package/tests/plugin/StateViewSlice/PsCustomersView.res.mjs +0 -69
- package/tests/plugin/StateViewSlice/PsOrdersView.res.mjs +0 -81
- package/tests/poc/StructuralSubtypingPoc.res.mjs +0 -61
- package/tests/projection/ProjectionOptimizeTest.res.mjs +0 -75
- package/tests/querydb/QueryDbFixtures.res.mjs +0 -135
- package/tests/querydb/QueryDbOperationsTest.res.mjs +0 -168
- package/tests/sideeffecthandler/SideEffectHandlerCallbackTest.res.mjs +0 -77
- package/tests/sideeffecthandler/SideEffectHandlerFixtures.res.mjs +0 -139
- package/tests/util/CommandPublisherTest.res.mjs +0 -78
- package/tests/util/CsvStreamTest.res.mjs +0 -51
- package/tests/util/LruTest.res.mjs +0 -105
- package/tests/util/MetricsTest.res.mjs +0 -53
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.172 (2026-07-17)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **plugin:** scale heartbeat disconnect grace and enforce single-source interval ([7ba8fb7](https://github.com/ReventlessDev/reventless-core/commit/7ba8fb7bfbe023aba7b538329d2489f939b7fcea))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 3.0.0-alpha.171 (2026-07-17)
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **runtime:** platform-supplied EP/task pod runtime floors ([37544d0](https://github.com/ReventlessDev/reventless-core/commit/37544d029d278bbcae6eeb0f583da07d6ce70f04))
|
|
18
|
+
|
|
19
|
+
|
|
6
20
|
# 3.0.0-alpha.170 (2026-07-16)
|
|
7
21
|
|
|
8
22
|
### Features
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-core",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.172",
|
|
4
4
|
"description": "Core package for Reventless framework",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -28,22 +28,22 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"sury": "11.0.0-alpha.4",
|
|
30
30
|
"uuid": "^13.0.0",
|
|
31
|
-
"@reventlessdev/rescript-effect": "0.1.0-alpha.
|
|
32
|
-
"@reventlessdev/rescript-hash-object": "1.2.0-alpha.
|
|
33
|
-
"@reventlessdev/rescript-
|
|
34
|
-
"@reventlessdev/rescript-
|
|
35
|
-
"@reventlessdev/rescript-node-streams": "1.1.0-alpha.
|
|
36
|
-
"@reventlessdev/rescript-
|
|
37
|
-
"@reventlessdev/
|
|
38
|
-
"@reventlessdev/
|
|
39
|
-
"@reventlessdev/reventless-interop": "3.0.0-alpha.
|
|
40
|
-
"@reventlessdev/
|
|
41
|
-
"@reventlessdev/
|
|
31
|
+
"@reventlessdev/rescript-effect": "0.1.0-alpha.29",
|
|
32
|
+
"@reventlessdev/rescript-hash-object": "1.2.0-alpha.13",
|
|
33
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.9",
|
|
34
|
+
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.17",
|
|
35
|
+
"@reventlessdev/rescript-node-streams": "1.1.0-alpha.13",
|
|
36
|
+
"@reventlessdev/rescript-fast-csv": "1.2.0-alpha.14",
|
|
37
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.102",
|
|
38
|
+
"@reventlessdev/rescript-uuid": "1.1.0-alpha.17",
|
|
39
|
+
"@reventlessdev/reventless-interop": "3.0.0-alpha.28",
|
|
40
|
+
"@reventlessdev/rescript-ssh2": "1.1.0-alpha.14",
|
|
41
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.79"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"rescript": "12.3.0",
|
|
45
45
|
"sury-ppx": "11.0.0-alpha.2",
|
|
46
|
-
"@reventlessdev/reventless-ppx": "1.0.0-alpha.
|
|
46
|
+
"@reventlessdev/reventless-ppx": "1.0.0-alpha.54"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"rescript": "12.3.0"
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"repository": {
|
|
56
56
|
"type": "git",
|
|
57
57
|
"url": "git+https://github.com/ReventlessDev/reventless-core.git",
|
|
58
|
-
"directory": "reventless/
|
|
58
|
+
"directory": "reventless/core"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "rescript build",
|
|
@@ -7,6 +7,7 @@ module Make = (
|
|
|
7
7
|
EventTopicAdapter: EventTopic_Adapter.Publisher,
|
|
8
8
|
ExtensionPointRuntimeBuilder: ExtensionPointRuntime_Builder.T
|
|
9
9
|
with module CommandTopicChannel := CommandTopicChannel,
|
|
10
|
+
Defaults: ReventlessInfra.RuntimeDefaults.T,
|
|
10
11
|
): ExtensionPoint.T => {
|
|
11
12
|
module Spec = Spec
|
|
12
13
|
|
|
@@ -92,8 +93,10 @@ module Make = (
|
|
|
92
93
|
commandTopic->ExtensionPointRuntimeBuilder.forCommandTopic(
|
|
93
94
|
~handler,
|
|
94
95
|
~connect=SpecificCommandTopic.connect(commandTopic, ~resources, ...),
|
|
95
|
-
~memorySize=runtime->ReventlessInfra.RuntimeHints.resolveMemory(
|
|
96
|
-
|
|
96
|
+
~memorySize=runtime->ReventlessInfra.RuntimeHints.resolveMemory(
|
|
97
|
+
~default=Defaults.memorySize,
|
|
98
|
+
),
|
|
99
|
+
~timeout=runtime->ReventlessInfra.RuntimeHints.resolveTimeout(~default=Defaults.timeout),
|
|
97
100
|
~specModulePath=Spec.moduleUrl,
|
|
98
101
|
~mappingsModulePath=Mappings.moduleUrl,
|
|
99
102
|
~publishToAggregatesQueueUrls,
|
|
@@ -17,7 +17,7 @@ import * as ExtensionPoint_Callback$ReventlessCore from "./ExtensionPoint_Callba
|
|
|
17
17
|
import * as ExtensionPoint_Operations$ReventlessCore from "./ExtensionPoint_Operations.res.mjs";
|
|
18
18
|
|
|
19
19
|
function Make(Spec) {
|
|
20
|
-
return Mappings => (RuntimeEnvironment => (CommandTopicChannel => (EventTopicAdapter => (ExtensionPointRuntimeBuilder => {
|
|
20
|
+
return Mappings => (RuntimeEnvironment => (CommandTopicChannel => (EventTopicAdapter => (ExtensionPointRuntimeBuilder => (Defaults => {
|
|
21
21
|
let name = Spec.name;
|
|
22
22
|
let commandSchema = Spec.commandSchema;
|
|
23
23
|
let eventSchema = Spec.eventSchema;
|
|
@@ -59,7 +59,7 @@ function Make(Spec) {
|
|
|
59
59
|
return Pulumi.output("");
|
|
60
60
|
}
|
|
61
61
|
});
|
|
62
|
-
ExtensionPointRuntimeBuilder.forCommandTopic(handler, none => SpecificCommandTopic.connect(none, resources, commandTopic), RuntimeHints$ReventlessInfra.resolveMemory(runtime,
|
|
62
|
+
ExtensionPointRuntimeBuilder.forCommandTopic(handler, none => SpecificCommandTopic.connect(none, resources, commandTopic), RuntimeHints$ReventlessInfra.resolveMemory(runtime, Defaults.memorySize), RuntimeHints$ReventlessInfra.resolveTimeout(runtime, Defaults.timeout), Spec.moduleUrl, Mappings.moduleUrl, publishToAggregatesQueueUrls, commandTopic);
|
|
63
63
|
let SpecificEventTopic = EventTopic_Builder$ReventlessCore.Make({
|
|
64
64
|
Id: Id$Reventless.$$String,
|
|
65
65
|
name: name,
|
|
@@ -106,7 +106,7 @@ function Make(Spec) {
|
|
|
106
106
|
outputs: Component$ReventlessCore.outputs,
|
|
107
107
|
operations: Component$ReventlessCore.operations
|
|
108
108
|
};
|
|
109
|
-
}))));
|
|
109
|
+
})))));
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
export {
|
|
@@ -11,13 +11,13 @@ module type T = {
|
|
|
11
11
|
let connect: (
|
|
12
12
|
~runtime: Runtime.environment<runtimeParts>,
|
|
13
13
|
~remoteChannel: CommandTopic_Adapter.remoteChannel,
|
|
14
|
-
~timeout: int
|
|
14
|
+
~timeout: int,
|
|
15
15
|
component,
|
|
16
16
|
) => unit
|
|
17
17
|
|
|
18
18
|
let makeHandler: (
|
|
19
19
|
~id: string,
|
|
20
|
-
~timeout: int
|
|
20
|
+
~timeout: int,
|
|
21
21
|
~publishToPluginExtensionPoint: CommandTopic.publishJsons,
|
|
22
22
|
) => Pulumi.Output.t<Runtime.eventHandler<unit, 'context, unit>>
|
|
23
23
|
|
|
@@ -5,7 +5,7 @@ module Make = (Runner: Heartbeat_Adapter.Runner): (
|
|
|
5
5
|
|
|
6
6
|
let construct = (_self, _name) => ()
|
|
7
7
|
|
|
8
|
-
let connect = (~runtime, ~remoteChannel, ~timeout
|
|
8
|
+
let connect = (~runtime, ~remoteChannel, ~timeout, heartbeat) => {
|
|
9
9
|
let heartbeatResource = heartbeat->Component.toPulumiResource
|
|
10
10
|
let name =
|
|
11
11
|
heartbeatResource.name
|
|
@@ -24,7 +24,7 @@ module Make = (Runner: Heartbeat_Adapter.Runner): (
|
|
|
24
24
|
let _ = heartbeat->Component.setOutputs(outputs)
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
let makeHandler = (~id, ~timeout
|
|
27
|
+
let makeHandler = (~id, ~timeout, ~publishToPluginExtensionPoint) => {
|
|
28
28
|
module Callback = Heartbeat_Callback.Make({
|
|
29
29
|
let publishToPluginExtensionPoint = publishToPluginExtensionPoint
|
|
30
30
|
let id = id
|
|
@@ -9,8 +9,7 @@ import * as Heartbeat_Callback$ReventlessCore from "./Heartbeat_Callback.res.mjs
|
|
|
9
9
|
|
|
10
10
|
function Make(Runner) {
|
|
11
11
|
let construct = (_self, _name) => {};
|
|
12
|
-
let connect = (runtime, remoteChannel,
|
|
13
|
-
let timeout = timeoutOpt !== undefined ? timeoutOpt : 10;
|
|
12
|
+
let connect = (runtime, remoteChannel, timeout, heartbeat) => {
|
|
14
13
|
let heartbeatResource = Component$ReventlessCore.toPulumiResource(heartbeat);
|
|
15
14
|
let name = ComponentType$ReventlessCore.name(Stdlib_Option.getOr(heartbeatResource.__name, "Unnamed"), Heartbeat$ReventlessCore.componentType);
|
|
16
15
|
let opts_parent = heartbeatResource;
|
|
@@ -24,8 +23,7 @@ function Make(Runner) {
|
|
|
24
23
|
};
|
|
25
24
|
Component$ReventlessCore.setOutputs(heartbeat, outputs);
|
|
26
25
|
};
|
|
27
|
-
let makeHandler = (id,
|
|
28
|
-
let timeout = timeoutOpt !== undefined ? timeoutOpt : 10;
|
|
26
|
+
let makeHandler = (id, timeout, publishToPluginExtensionPoint) => {
|
|
29
27
|
let Callback = Heartbeat_Callback$ReventlessCore.Make({
|
|
30
28
|
publishToPluginExtensionPoint: publishToPluginExtensionPoint,
|
|
31
29
|
id: id,
|
|
@@ -28,6 +28,7 @@ module Make = (
|
|
|
28
28
|
and type runtimeParts = RuntimeEnvironment.parts
|
|
29
29
|
and type context = TaskRuntimeBuilder.context,
|
|
30
30
|
SpecificSideEffectHandler: SideEffectHandler.T,
|
|
31
|
+
Defaults: ReventlessInfra.RuntimeDefaults.T,
|
|
31
32
|
): Task.T => {
|
|
32
33
|
module Spec = Spec
|
|
33
34
|
type component = Task.component
|
|
@@ -50,10 +51,11 @@ module Make = (
|
|
|
50
51
|
taskName,
|
|
51
52
|
) => {
|
|
52
53
|
let opts = {Pulumi.ComponentResource.parent: self->Component.toPulumiResource}
|
|
53
|
-
// Task bucket callbacks default to
|
|
54
|
-
// a plugin.json `runtime`
|
|
55
|
-
|
|
56
|
-
let
|
|
54
|
+
// Task bucket callbacks default to the platform-supplied task-pod floor (a
|
|
55
|
+
// large envelope for bulk import/export work); a plugin.json `runtime`
|
|
56
|
+
// override raises memory above it and replaces timeout.
|
|
57
|
+
let memorySize = ReventlessInfra.RuntimeHints.resolveMemory(runtime, ~default=Defaults.memorySize)
|
|
58
|
+
let timeout = ReventlessInfra.RuntimeHints.resolveTimeout(runtime, ~default=Defaults.timeout)
|
|
57
59
|
let allCommandTopics = allAggregates->Aggregate.allCommandTopics
|
|
58
60
|
|
|
59
61
|
// Each adapter chooses what string the Task runtime should treat as the
|
|
@@ -20,14 +20,14 @@ function pascalCase(s) {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
function Make(Spec) {
|
|
23
|
-
return RuntimeEnvironment => (EventCollectorChannel => (EventCollectorRuntimeBuilder => (TaskRuntimeBuilder => (TaskBucket => (SpecificSideEffectHandler => {
|
|
23
|
+
return RuntimeEnvironment => (EventCollectorChannel => (EventCollectorRuntimeBuilder => (TaskRuntimeBuilder => (TaskBucket => (SpecificSideEffectHandler => (Defaults => {
|
|
24
24
|
let make = (queryBucketName, scheduler, schedulerRoleUrn, publishToAggregates, queryEngine, resourceNaming, allAggregates, runtime, opts) => Component$ReventlessCore.make(ComponentType$ReventlessCore.toString(Task$ReventlessCore.componentType), Spec.name, (extra, extra$1) => {
|
|
25
25
|
let opts_parent = Component$ReventlessCore.toPulumiResource(extra);
|
|
26
26
|
let opts = {
|
|
27
27
|
parent: opts_parent
|
|
28
28
|
};
|
|
29
|
-
let memorySize = RuntimeHints$ReventlessInfra.resolveMemory(runtime,
|
|
30
|
-
let timeout = RuntimeHints$ReventlessInfra.resolveTimeout(runtime,
|
|
29
|
+
let memorySize = RuntimeHints$ReventlessInfra.resolveMemory(runtime, Defaults.memorySize);
|
|
30
|
+
let timeout = RuntimeHints$ReventlessInfra.resolveTimeout(runtime, Defaults.timeout);
|
|
31
31
|
let allCommandTopics = Aggregate$ReventlessCore.allCommandTopics(allAggregates);
|
|
32
32
|
let publishToAggregatesQueueUrls = Stdlib_Dict.mapValues(allAggregates, agg => Output$Pulumi.flatMap(agg.commandTopic, ct => {
|
|
33
33
|
let r = ct.resources[0];
|
|
@@ -107,7 +107,7 @@ function Make(Spec) {
|
|
|
107
107
|
make: make,
|
|
108
108
|
outputs: Component$ReventlessCore.outputs
|
|
109
109
|
};
|
|
110
|
-
})))));
|
|
110
|
+
}))))));
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
export {
|
|
@@ -887,7 +887,11 @@ module Make = (
|
|
|
887
887
|
| Some((_, pluginExtensionPointCommandTopicRemoteChannel)) =>
|
|
888
888
|
// Notify platform hook that the EP channel is available (AWS extracts SQS URL for bundled heartbeat)
|
|
889
889
|
Spec.hooks.onHeartbeatEpChannelAvailable->Option.forEach(hook =>
|
|
890
|
-
hook(
|
|
890
|
+
hook(
|
|
891
|
+
pluginExtensionPointCommandTopicRemoteChannel->Obj.magic,
|
|
892
|
+
~pluginId=id,
|
|
893
|
+
~heartbeatInterval,
|
|
894
|
+
)
|
|
891
895
|
)
|
|
892
896
|
heartbeat->PluginRuntimeBuilder.forPluginHeartbeat(
|
|
893
897
|
~handler,
|
|
@@ -545,7 +545,7 @@ function Make(Spec) {
|
|
|
545
545
|
let handler = SpecificHeartbeat.makeHandler(id, heartbeatInterval, publishToPluginExtensionPoint);
|
|
546
546
|
if (coreSetup !== undefined) {
|
|
547
547
|
let pluginExtensionPointCommandTopicRemoteChannel$1 = coreSetup[1];
|
|
548
|
-
Stdlib_Option.forEach(Spec.hooks.onHeartbeatEpChannelAvailable, hook => hook(pluginExtensionPointCommandTopicRemoteChannel$1, id));
|
|
548
|
+
Stdlib_Option.forEach(Spec.hooks.onHeartbeatEpChannelAvailable, hook => hook(pluginExtensionPointCommandTopicRemoteChannel$1, id, heartbeatInterval));
|
|
549
549
|
PluginRuntimeBuilder.forPluginHeartbeat(handler, none => SpecificHeartbeat.connect(none, pluginExtensionPointCommandTopicRemoteChannel$1, heartbeatInterval, heartbeat), undefined, undefined, heartbeat);
|
|
550
550
|
} else {
|
|
551
551
|
PluginRuntimeBuilder.forPluginHeartbeat(handler, param => {}, undefined, undefined, heartbeat);
|
|
@@ -957,7 +957,12 @@ type platformHooks = {
|
|
|
957
957
|
onDcbEventLogCreated?: unknown => unit,
|
|
958
958
|
onDcbCommandTopicCreated?: unknown => unit,
|
|
959
959
|
onDcbSlicesCreated?: unknown => unit,
|
|
960
|
-
|
|
960
|
+
// heartbeatInterval carries the plugin's heartbeat cadence so the AWS bundled
|
|
961
|
+
// heartbeat Lambda emits Heartbeat(interval) matching its EventBridge schedule
|
|
962
|
+
// rate. Both derive from the same heartbeatInterval; threading it here prevents
|
|
963
|
+
// the schedule-rate vs command-interval divergence that caused connect/disconnect
|
|
964
|
+
// flapping (schedule every 60min but Heartbeat(10) → 12min grace).
|
|
965
|
+
onHeartbeatEpChannelAvailable?: (unknown, ~pluginId: string, ~heartbeatInterval: int) => unit,
|
|
961
966
|
// ── Admin → Plugin coordination ────────────────────────────────────────
|
|
962
967
|
// Set by makePlatform after Admin.construct(); read by Plugin_Builder to
|
|
963
968
|
// wire the local admin connection path (in-memory). Starts as an empty
|
|
@@ -17,6 +17,7 @@ module Make = (
|
|
|
17
17
|
EventTopicAdapter: EventTopic_Adapter.Publisher,
|
|
18
18
|
ExtensionPointRuntimeBuilder: ExtensionPointRuntime_Builder.T
|
|
19
19
|
with module CommandTopicChannel := CommandTopicChannel,
|
|
20
|
+
Defaults: ReventlessInfra.RuntimeDefaults.T,
|
|
20
21
|
): ExtensionPoint.T => {
|
|
21
22
|
module PluginMappingInstance = PluginExtensionPoint_Plugin.Make(Spec)
|
|
22
23
|
|
|
@@ -41,5 +42,6 @@ module Make = (
|
|
|
41
42
|
CommandTopicChannel,
|
|
42
43
|
EventTopicAdapter,
|
|
43
44
|
ExtensionPointRuntimeBuilder,
|
|
45
|
+
Defaults,
|
|
44
46
|
)
|
|
45
47
|
}
|
|
@@ -6,7 +6,7 @@ import * as PluginExtensionPoint_Plugin$ReventlessCore from "./PluginExtensionPo
|
|
|
6
6
|
import * as PluginExtensionPoint_UiFragment$ReventlessCore from "./PluginExtensionPoint_UiFragment.res.mjs";
|
|
7
7
|
|
|
8
8
|
function Make(Spec) {
|
|
9
|
-
return RuntimeEnvironment => (CommandTopicChannel => (EventTopicAdapter => (ExtensionPointRuntimeBuilder => {
|
|
9
|
+
return RuntimeEnvironment => (CommandTopicChannel => (EventTopicAdapter => (ExtensionPointRuntimeBuilder => (Defaults => {
|
|
10
10
|
let PluginMappingInstance = PluginExtensionPoint_Plugin$ReventlessCore.Make(Spec);
|
|
11
11
|
let name = PluginMappingInstance.Mapping.delegateName;
|
|
12
12
|
let mappings = [
|
|
@@ -24,8 +24,8 @@ function Make(Spec) {
|
|
|
24
24
|
commandSchema: PluginExtensionPointSpec$ReventlessInfra.commandSchema,
|
|
25
25
|
eventSchema: PluginExtensionPointSpec$ReventlessInfra.eventSchema,
|
|
26
26
|
directiveSchema: PluginExtensionPointSpec$ReventlessInfra.directiveSchema
|
|
27
|
-
})(Mappings)(RuntimeEnvironment)(CommandTopicChannel)(EventTopicAdapter)(ExtensionPointRuntimeBuilder);
|
|
28
|
-
})));
|
|
27
|
+
})(Mappings)(RuntimeEnvironment)(CommandTopicChannel)(EventTopicAdapter)(ExtensionPointRuntimeBuilder)(Defaults);
|
|
28
|
+
}))));
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
let PluginExtensionPointSpec;
|
|
@@ -5,6 +5,14 @@ open Reventless.Plugin
|
|
|
5
5
|
|
|
6
6
|
module PluginExtensionPointSpec = ReventlessInfra.PluginExtensionPointSpec
|
|
7
7
|
|
|
8
|
+
// Grace before a plugin is marked Disconnected once heartbeats stop, expressed in
|
|
9
|
+
// whole minutes (schedules can only be created by minute). The margin scales with
|
|
10
|
+
// the interval so large cadences keep real headroom: a fixed +2 gave a 60-min
|
|
11
|
+
// heartbeat only ~3% slack, so a single late EventBridge beat + Lambda cold-start
|
|
12
|
+
// could trip a spurious disconnect. `+ max(2, interval / 2)` keeps ≥ +2 min for
|
|
13
|
+
// small intervals and ≥ +50% for large ones (5→7, 10→15, 60→90).
|
|
14
|
+
let disconnectGrace = interval => interval + max(2, interval / 2)
|
|
15
|
+
|
|
8
16
|
module type Spec = {
|
|
9
17
|
let runtimeOps: PluginRuntimeOperations.operations
|
|
10
18
|
let environment: string
|
|
@@ -145,10 +153,9 @@ module Make = (Spec: Spec) => {
|
|
|
145
153
|
switch cmd {
|
|
146
154
|
| PluginExtensionPointSpec.Heartbeat(interval) => [
|
|
147
155
|
PublishCommand(Plugin.name(id), Delegate.Heartbeat(Plugin.version(id))),
|
|
148
|
-
// Re-
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
HandleDirective(directiveHandler, CreateDisconnectSchedule(id, interval + 2)),
|
|
156
|
+
// Re-arm the disconnect schedule with an interval-scaled grace so a
|
|
157
|
+
// single late beat doesn't trip a spurious disconnect (see disconnectGrace).
|
|
158
|
+
HandleDirective(directiveHandler, CreateDisconnectSchedule(id, disconnectGrace(interval))),
|
|
152
159
|
]
|
|
153
160
|
| RedetectPlugin(interval) => [
|
|
154
161
|
// Deploy-time re-detect: drive Redetect (not Heartbeat) so an already-connected
|
|
@@ -156,7 +163,7 @@ module Make = (Spec: Spec) => {
|
|
|
156
163
|
// re-arm the disconnect schedule exactly like a heartbeat so liveness tracking
|
|
157
164
|
// continues from the deploy moment.
|
|
158
165
|
PublishCommand(Plugin.name(id), Delegate.Redetect(Plugin.version(id))),
|
|
159
|
-
HandleDirective(directiveHandler, CreateDisconnectSchedule(id, interval
|
|
166
|
+
HandleDirective(directiveHandler, CreateDisconnectSchedule(id, disconnectGrace(interval))),
|
|
160
167
|
]
|
|
161
168
|
| ConnectPlugin(pluginDefinition) =>
|
|
162
169
|
// Validate protocol versions declared by the connecting plugin.
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import * as S from "sury/src/S.res.mjs";
|
|
4
4
|
import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
5
5
|
import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
|
|
6
|
+
import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
|
|
6
7
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
7
8
|
import * as Effect from "effect/Effect";
|
|
8
9
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
@@ -17,6 +18,10 @@ import * as PluginsReadModelSpec$ReventlessCore from "../lifecycle/PluginsReadMo
|
|
|
17
18
|
import * as ExtensionPointMapping$ReventlessInfra from "@reventlessdev/reventless-infra/src/types/ExtensionPointMapping.res.mjs";
|
|
18
19
|
import * as PluginExtensionPointSpec$ReventlessInfra from "@reventlessdev/reventless-infra/src/types/PluginExtensionPointSpec.res.mjs";
|
|
19
20
|
|
|
21
|
+
function disconnectGrace(interval) {
|
|
22
|
+
return interval + Primitive_int.max(2, interval / 2 | 0) | 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
function Make(Spec) {
|
|
21
26
|
let forwardCommand = async (_id, command, extensionPointName, queryEngine) => {
|
|
22
27
|
let jsons = await queryEngine.scan(PluginSpec$ReventlessCore.name, [
|
|
@@ -151,7 +156,7 @@ function Make(Spec) {
|
|
|
151
156
|
_1: {
|
|
152
157
|
TAG: "CreateDisconnectSchedule",
|
|
153
158
|
_0: id,
|
|
154
|
-
_1: cmd._0
|
|
159
|
+
_1: disconnectGrace(cmd._0)
|
|
155
160
|
}
|
|
156
161
|
}
|
|
157
162
|
];
|
|
@@ -201,7 +206,7 @@ function Make(Spec) {
|
|
|
201
206
|
_1: {
|
|
202
207
|
TAG: "CreateDisconnectSchedule",
|
|
203
208
|
_0: id,
|
|
204
|
-
_1: cmd._0
|
|
209
|
+
_1: disconnectGrace(cmd._0)
|
|
205
210
|
}
|
|
206
211
|
}
|
|
207
212
|
];
|
|
@@ -369,6 +374,7 @@ export {
|
|
|
369
374
|
name,
|
|
370
375
|
Id,
|
|
371
376
|
PluginExtensionPointSpec,
|
|
377
|
+
disconnectGrace,
|
|
372
378
|
Make,
|
|
373
379
|
moduleUrl,
|
|
374
380
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Regression tests for the plugin-lifecycle heartbeat / disconnect margin.
|
|
2
|
+
//
|
|
3
|
+
// A plugin's liveness is scheduler-driven: every heartbeat re-arms a one-time
|
|
4
|
+
// disconnect schedule, and if heartbeats stop the schedule fires and marks the
|
|
5
|
+
// plugin Disconnected. Two independently-wired values must agree — the recurring
|
|
6
|
+
// heartbeat *schedule rate* and the *interval carried inside the Heartbeat command*
|
|
7
|
+
// (which drives CreateDisconnectSchedule). A deployed plugin once ran with schedule
|
|
8
|
+
// rate(60min) but a Heartbeat(10) command (grace ~12min), so it reconnected for
|
|
9
|
+
// ~12min after each hourly beat then sat Disconnected for the rest of the hour —
|
|
10
|
+
// it flapped. See docs/plans/plugin-heartbeat-disconnect-margin-hardening.md.
|
|
11
|
+
//
|
|
12
|
+
// These tests pin the two guarantees that the fix provides at this layer:
|
|
13
|
+
// 1. the disconnect grace scales with the interval (large cadences keep headroom);
|
|
14
|
+
// 2. the disconnect schedule the EP mapping arms is derived from the *same*
|
|
15
|
+
// interval the Heartbeat/RedetectPlugin command carries — never a constant.
|
|
16
|
+
// (The schedule-rate ⇄ command-interval single-source is additionally enforced at
|
|
17
|
+
// compile time now that Heartbeat_Builder's ~timeout is mandatory.)
|
|
18
|
+
|
|
19
|
+
open JestGlobals
|
|
20
|
+
|
|
21
|
+
module PluginExtensionPointSpec = ReventlessInfra.PluginExtensionPointSpec
|
|
22
|
+
open ReventlessInfra.ExtensionPointMapping
|
|
23
|
+
|
|
24
|
+
// The mapping functor only needs runtimeOps at directive-*execution* time; the
|
|
25
|
+
// pure mapIncomingCommand under test never touches it, so a stub Spec suffices
|
|
26
|
+
// (mirrors the Obj.magic stubbing already used in HeartbeatTest.res).
|
|
27
|
+
module TestMapping = PluginExtensionPoint_Plugin.Make({
|
|
28
|
+
let runtimeOps = Obj.magic()
|
|
29
|
+
let environment = "test"
|
|
30
|
+
let updateApiSchema = None
|
|
31
|
+
let manageSubscriptions = None
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// Pull the disconnect-schedule grace (in minutes) out of the actions a mapping
|
|
35
|
+
// emits for an incoming command, if any.
|
|
36
|
+
let graceFromActions = actions =>
|
|
37
|
+
actions->Array.findMap((action: commandAction<_, _>) =>
|
|
38
|
+
switch action {
|
|
39
|
+
| HandleDirective(_, PluginExtensionPointSpec.CreateDisconnectSchedule(_, timeout)) =>
|
|
40
|
+
Some(timeout)
|
|
41
|
+
| _ => None
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
let meta = Message.generateMeta(~service="test", ~user="test")
|
|
46
|
+
|
|
47
|
+
describe("disconnectGrace", () => {
|
|
48
|
+
// The observed-bug fix: the margin must scale, so a 60-min cadence keeps real
|
|
49
|
+
// headroom instead of the old fixed +2 (~3%).
|
|
50
|
+
testSync("scales with the interval (5→7, 10→15, 60→90)", () => {
|
|
51
|
+
expect(PluginExtensionPoint_Plugin.disconnectGrace(5))->toBe(7)
|
|
52
|
+
expect(PluginExtensionPoint_Plugin.disconnectGrace(10))->toBe(15)
|
|
53
|
+
expect(PluginExtensionPoint_Plugin.disconnectGrace(60))->toBe(90)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
testSync("always leaves at least a full interval of headroom past one beat", () => {
|
|
57
|
+
// Grace must exceed the interval (else a single beat's cadence trips a
|
|
58
|
+
// disconnect) and keep the historical ≥ +2 min floor for small intervals.
|
|
59
|
+
[1, 2, 5, 10, 30, 60, 120]->Array.forEach(interval => {
|
|
60
|
+
let grace = PluginExtensionPoint_Plugin.disconnectGrace(interval)
|
|
61
|
+
expect(grace > interval)->toBe(true)
|
|
62
|
+
expect(grace >= interval + 2)->toBe(true)
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
testSync("a 60-min cadence gets far more than the old fixed +2 grace", () => {
|
|
67
|
+
// The exact regression: interval 60 previously armed a 62-min disconnect.
|
|
68
|
+
expect(PluginExtensionPoint_Plugin.disconnectGrace(60) > 62)->toBe(true)
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
describe("PluginExtensionPoint mapping arms the disconnect schedule from the beat interval", () => {
|
|
73
|
+
testSync("Heartbeat(interval) → CreateDisconnectSchedule(_, disconnectGrace(interval))", () => {
|
|
74
|
+
[5, 10, 60]->Array.forEach(interval => {
|
|
75
|
+
let actions = TestMapping.PluginMapping.mapIncomingCommand(
|
|
76
|
+
"Test@1",
|
|
77
|
+
PluginExtensionPointSpec.Heartbeat(interval),
|
|
78
|
+
meta,
|
|
79
|
+
)
|
|
80
|
+
expect(actions->graceFromActions)->toEqual(
|
|
81
|
+
Some(PluginExtensionPoint_Plugin.disconnectGrace(interval)),
|
|
82
|
+
)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
testSync("RedetectPlugin(interval) arms the same interval-derived grace as a heartbeat", () => {
|
|
87
|
+
[5, 10, 60]->Array.forEach(interval => {
|
|
88
|
+
let actions = TestMapping.PluginMapping.mapIncomingCommand(
|
|
89
|
+
"Test@1",
|
|
90
|
+
PluginExtensionPointSpec.RedetectPlugin(interval),
|
|
91
|
+
meta,
|
|
92
|
+
)
|
|
93
|
+
expect(actions->graceFromActions)->toEqual(
|
|
94
|
+
Some(PluginExtensionPoint_Plugin.disconnectGrace(interval)),
|
|
95
|
+
)
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
})
|