@reventlessdev/reventless-core 3.0.0-alpha.177 → 3.0.0-alpha.178

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 CHANGED
@@ -3,6 +3,13 @@
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.178 (2026-07-22)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **core:** derive the disconnect schedule's rule name in one place ([6324a5b](https://github.com/ReventlessDev/reventless-core/commit/6324a5bc678a375688ec98571423f164b09375ae))
11
+
12
+
6
13
  # 3.0.0-alpha.177 (2026-07-22)
7
14
 
8
15
  * refactor!: retire the stale "Admin" vocabulary for platform-owned resources ([2770bd9](https://github.com/ReventlessDev/reventless-core/commit/2770bd96f309142bdfc65b9f36bd1a3ef60641bd))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-core",
3
- "version": "3.0.0-alpha.177",
3
+ "version": "3.0.0-alpha.178",
4
4
  "description": "Core package for Reventless framework",
5
5
  "license": "Apache-2.0",
6
6
  "jest": {
@@ -29,16 +29,16 @@
29
29
  "sury": "11.0.0-alpha.4",
30
30
  "uuid": "^13.0.0",
31
31
  "@reventlessdev/rescript-effect": "0.1.0-alpha.30",
32
- "@reventlessdev/rescript-hash-object": "1.2.0-alpha.13",
33
32
  "@reventlessdev/rescript-fast-csv": "1.2.0-alpha.14",
34
- "@reventlessdev/rescript-node-streams": "1.1.0-alpha.13",
33
+ "@reventlessdev/rescript-hash-object": "1.2.0-alpha.13",
35
34
  "@reventlessdev/rescript-jest": "1.0.0-alpha.9",
36
- "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.17",
35
+ "@reventlessdev/rescript-node-streams": "1.1.0-alpha.13",
37
36
  "@reventlessdev/rescript-ssh2": "1.1.0-alpha.14",
37
+ "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.17",
38
38
  "@reventlessdev/rescript-uuid": "1.1.0-alpha.17",
39
- "@reventlessdev/reventless-spec": "3.0.0-alpha.80",
40
39
  "@reventlessdev/reventless-interop": "3.0.0-alpha.29",
41
- "@reventlessdev/reventless-infra": "3.0.0-alpha.104"
40
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.104",
41
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.80"
42
42
  },
43
43
  "devDependencies": {
44
44
  "rescript": "12.3.0",
@@ -29,6 +29,13 @@ module type Spec = {
29
29
  }
30
30
 
31
31
  module Make = (Spec: Spec) => {
32
+ // Single owner of the disconnect schedule's rule name. Create and delete must
33
+ // derive it identically — they previously did not (create prefixed the stack,
34
+ // delete did not), so every delete targeted a name that was never created and
35
+ // the rule survived. The stack prefix itself is load-bearing: several stacks
36
+ // share one EventBridge bus, and an unprefixed plugin id would collide.
37
+ let disconnectScheduleName = id => Spec.environment ++ ("-" ++ id)
38
+
32
39
  let forwardCommand = async (
33
40
  _id,
34
41
  command,
@@ -104,7 +111,7 @@ module Make = (Spec: Spec) => {
104
111
  switch directive {
105
112
  | PluginExtensionPointSpec.CreateDisconnectSchedule(id, timeout) =>
106
113
  await createSchedule({
107
- name: Spec.environment ++ ("-" ++ id),
114
+ name: id->disconnectScheduleName,
108
115
  rate: timeout->ScheduleOps.minutesFromNow,
109
116
  payload: {
110
117
  Message.id,
@@ -114,7 +121,7 @@ module Make = (Spec: Spec) => {
114
121
  ->Message.encodeCommand'(S.string, PluginExtensionPointSpec.commandSchema)
115
122
  ->JSON.stringify,
116
123
  })
117
- | DeleteDisconnectSchedule(id) => await deleteSchedule(id)
124
+ | DeleteDisconnectSchedule(id) => await deleteSchedule(id->disconnectScheduleName)
118
125
  | ForwardCommand({id, command, extensionPointName}) =>
119
126
  await forwardCommand(id, command, extensionPointName, queryEngine)
120
127
  | DoConnectPlugin(pluginDef) =>
@@ -23,6 +23,7 @@ function disconnectGrace(interval) {
23
23
  }
24
24
 
25
25
  function Make(Spec) {
26
+ let disconnectScheduleName = id => Spec.environment + ("-" + id);
26
27
  let forwardCommand = async (_id, command, extensionPointName, queryEngine) => {
27
28
  let jsons = await queryEngine.scan(PluginSpec$ReventlessCore.name, [
28
29
  [
@@ -81,7 +82,7 @@ function Make(Spec) {
81
82
  case "CreateDisconnectSchedule" :
82
83
  let id = directive._0;
83
84
  return await createSchedule({
84
- name: Spec.environment + ("-" + id),
85
+ name: disconnectScheduleName(id),
85
86
  rate: ScheduleOps$ReventlessCore.minutesFromNow(directive._1),
86
87
  payload: JSON.stringify(Message$ReventlessCore.encodeCommand$p({
87
88
  id: id,
@@ -90,7 +91,7 @@ function Make(Spec) {
90
91
  }, S.string, PluginExtensionPointSpec$ReventlessInfra.commandSchema))
91
92
  });
92
93
  case "DeleteDisconnectSchedule" :
93
- return await deleteSchedule(directive._0);
94
+ return await deleteSchedule(disconnectScheduleName(directive._0));
94
95
  case "DoConnectPlugin" :
95
96
  let fn = Spec.manageSubscriptions;
96
97
  if (fn !== undefined) {
@@ -355,6 +356,7 @@ function Make(Spec) {
355
356
  mapOutgoingEvent: mapOutgoingEvent
356
357
  });
357
358
  return {
359
+ disconnectScheduleName: disconnectScheduleName,
358
360
  forwardCommand: forwardCommand,
359
361
  directiveHandler: directiveHandler,
360
362
  PluginMapping: PluginMapping,
@@ -0,0 +1,66 @@
1
+ // Guards the disconnect schedule's rule name. Create and delete must derive it
2
+ // identically: they previously did not — create prefixed `Spec.environment`,
3
+ // delete did not — so every delete targeted a rule that had never been created
4
+ // and the real rule survived. The leak is invisible at runtime, because the
5
+ // delete reports success against a name AWS simply doesn't have.
6
+
7
+ open JestGlobals
8
+
9
+ let created: ref<array<string>> = ref([])
10
+ let deleted: ref<array<string>> = ref([])
11
+
12
+ module DisconnectSpec = {
13
+ let runtimeOps: PluginRuntimeOperations.operations = {
14
+ messagePublish: {
15
+ sendMessageToChannel: async (~channelId as _, ~messageBody as _) => (),
16
+ },
17
+ }
18
+ let environment = "teststack"
19
+ let updateApiSchema = None
20
+ let manageSubscriptions = None
21
+ }
22
+
23
+ module EP = PluginExtensionPoint_Plugin.Make(DisconnectSpec)
24
+
25
+ let createSchedule: Reventless.Schedule.create = async schedule => {
26
+ created := created.contents->Array.concat([schedule.name])
27
+ }
28
+
29
+ let deleteSchedule: Reventless.Schedule.delete = async name => {
30
+ deleted := deleted.contents->Array.concat([name])
31
+ }
32
+
33
+ let queryEngine: Reventless.QueryEngine.operations = {
34
+ scan: async (~readModelName as _, ~filterConfigs as _, ~limit as _) => [],
35
+ query: async (
36
+ ~readModelName as _,
37
+ ~key as _=?,
38
+ ~id as _,
39
+ ~subIdConfig as _=?,
40
+ ~filterConfigs as _=?,
41
+ ~ascending as _=?,
42
+ ~limit as _=?,
43
+ ) => [],
44
+ }
45
+
46
+ let pluginId = "Catalog_1.0.0-alpha.169"
47
+
48
+ let handle = directive => EP.directiveHandler(createSchedule, deleteSchedule, queryEngine, directive)
49
+
50
+ describe("plugin disconnect schedule", () => {
51
+ test("create and delete address the same rule name", async () => {
52
+ created := []
53
+ deleted := []
54
+ await handle(ReventlessInfra.PluginExtensionPointSpec.CreateDisconnectSchedule(pluginId, 5))
55
+ await handle(DeleteDisconnectSchedule(pluginId))
56
+ expect(deleted.contents)->toEqual(created.contents)
57
+ })
58
+
59
+ test("the rule name is namespaced by stack", async () => {
60
+ created := []
61
+ await handle(ReventlessInfra.PluginExtensionPointSpec.CreateDisconnectSchedule(pluginId, 5))
62
+ // Several stacks share one EventBridge bus, so a bare plugin id would
63
+ // collide across them.
64
+ expect(created.contents)->toEqual(["teststack-" ++ pluginId])
65
+ })
66
+ })