@reventlessdev/reventless-aws 3.0.0-alpha.310 → 3.0.0-alpha.311

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.311 (2026-08-19)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **aws:** decode the plugin definition asset instead of casting it ([5ac8a75](https://github.com/ReventlessDev/reventless-core/commit/5ac8a758673da63479049ba17658c8f91c5d72c5))
11
+
12
+
6
13
  # 3.0.0-alpha.310 (2026-08-18)
7
14
 
8
15
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-aws",
3
- "version": "3.0.0-alpha.310",
3
+ "version": "3.0.0-alpha.311",
4
4
  "description": "AWS adapters for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "dependencies": {
@@ -13,17 +13,17 @@
13
13
  "sury": "11.0.0-rc.1",
14
14
  "uuid": "^13.0.0",
15
15
  "@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.12",
16
- "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
17
- "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
18
16
  "@reventlessdev/rescript-node": "2.0.0-alpha.8",
17
+ "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
19
18
  "@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.1",
19
+ "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
20
20
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
21
21
  "@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
22
22
  "@reventlessdev/reventless-core": "3.0.0-alpha.240",
23
- "@reventlessdev/reventless-infra": "3.0.0-alpha.146",
24
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.104",
25
23
  "@reventlessdev/reventless-interop": "3.0.0-alpha.32",
26
- "@reventlessdev/reventless-spec": "3.0.0-alpha.118"
24
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.118",
25
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.104",
26
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.146"
27
27
  },
28
28
  "devDependencies": {
29
29
  "rescript": "12.3.0",
@@ -12,8 +12,8 @@
12
12
  // Plugin_Callback wiring, and the Lambda dispatch boundary.
13
13
  //
14
14
  // Runtime-pure: no `open PulumiAws` values — Pulumi appears in type positions
15
- // only (erased). The two `%identity` casts below sit on documented JSON
16
- // contracts (same pattern as AggregateEntryPoint_Ops.asConnectionConfig).
15
+ // only (erased). The one `%identity` cast below sits on a documented JSON
16
+ // contract (same pattern as AggregateEntryPoint_Ops.asConnectionConfig).
17
17
 
18
18
  // ── Shim bindings (HandlerFactoryHelpers.mjs) ───────────────────────────────
19
19
  // The structured-log + Effect dispatch boundary shared by every deployed entry
@@ -325,8 +325,8 @@ let projectPluginRow = (row: JSON.t): option<pluginProjection> =>
325
325
 
326
326
  // A pluginDefinition viewed as a projection (the manage logic reads only the
327
327
  // shared subset; `status` is never read for the acting plugin). Array/option
328
- // fields are js_nullable-normalized the definition may come from a plain
329
- // JSON.parse of pluginDefinition.json.
328
+ // fields stay js_nullable-normalized: a definition arriving over the wire, rather
329
+ // than from loadPluginDefinition's decode, still carries null for an absent field.
330
330
  let projectionOfDefinition = (d: Reventless.Plugin.pluginDefinition): pluginProjection => {
331
331
  id: d.id,
332
332
  status: "",
@@ -671,16 +671,16 @@ let warnSkippedExtension = (ext: extensionConfig) =>
671
671
 
672
672
  let assetPath = (fileName: string): string => NodePath.join([NodeProcess.cwd(), fileName])
673
673
 
674
- // pluginDefinition.json is written at deploy time as a stable JSON literal
675
- // matching Reventless.Plugin.pluginDefinitionSchema (PluginRuntime_Builder), so
676
- // the parsed object IS the record shape cast at the documented contract (the
677
- // AggregateEntryPoint_Ops.asConnectionConfig pattern). js_nullable option
678
- // fields may hold null; consumers normalize via jsOption.
679
- external asPluginDefinition: JSON.t => Reventless.Plugin.pluginDefinition = "%identity"
680
-
674
+ // pluginDefinition.json is written through the schema's encoder (Plugin_Helpers),
675
+ // so it holds the wire form, which is not the record's runtime shape: an @offload
676
+ // field is {"$offload": …} on the wire and Offloaded(…) in ReScript. Decode it
677
+ // back through the same schema — a cast leaves the wire form in a value typed as
678
+ // the record, and the next encode of it (the Connect handshake) throws.
681
679
  let loadPluginDefinition = (): Reventless.Plugin.pluginDefinition =>
682
680
  try {
683
- assetPath("pluginDefinition.json")->NodeFs.readFileSync->JSON.parseOrThrow->asPluginDefinition
681
+ assetPath("pluginDefinition.json")
682
+ ->NodeFs.readFileSync
683
+ ->Reventless.Util_Sury.fromJsonString(Reventless.Plugin.pluginDefinitionSchema)
684
684
  } catch {
685
685
  | exn =>
686
686
  JsError.throwWithMessage(
@@ -8,8 +8,10 @@ import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
8
8
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
9
9
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
10
10
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
11
+ import * as Plugin$Reventless from "@reventlessdev/reventless-spec/src/components/Plugin.res.mjs";
11
12
  import * as SNS_Helpers$AwsSdk from "@reventlessdev/rescript-aws-sdk/src/SNS_Helpers.res.mjs";
12
13
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
14
+ import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
13
15
  import * as HandlerFactoryHelpersMjs from "./HandlerFactoryHelpers.mjs";
14
16
  import * as Plugin_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin_Callback.res.mjs";
15
17
  import * as Util_PluginMessage_Runtime$ReventlessAws from "../../plugin/runtime/Util_PluginMessage_Runtime.res.mjs";
@@ -519,7 +521,7 @@ function assetPath(fileName) {
519
521
 
520
522
  function loadPluginDefinition() {
521
523
  try {
522
- return JSON.parse(Nodefs.readFileSync(assetPath("pluginDefinition.json"), "utf8"));
524
+ return Util_Sury$Reventless.fromJsonString(Nodefs.readFileSync(assetPath("pluginDefinition.json"), "utf8"), Plugin$Reventless.pluginDefinitionSchema);
523
525
  } catch (raw_exn) {
524
526
  let exn = Primitive_exceptions.internalToException(raw_exn);
525
527
  return Stdlib_JsError.throwWithMessage("Failed to load pluginDefinition.json from asset zip: " + exnMessage(exn));
@@ -0,0 +1,104 @@
1
+ // Guards the write/read symmetry of the `pluginDefinition.json` asset.
2
+ //
3
+ // The deploy side writes it with the schema's encoder (Plugin_Helpers); the
4
+ // runtime side (EventCollectorEntryPoint_Ops.loadPluginDefinition) reads it back
5
+ // and republishes the value inside the Connect handshake. Those two have to be
6
+ // the same schema in opposite directions.
7
+ //
8
+ // They stopped being that once a field carried @offload, whose wire form is
9
+ // deliberately untagged: {"$offload": …} on the wire, Offloaded(…) in ReScript.
10
+ // The read side cast the parsed JSON instead of decoding it, so the wire form
11
+ // survived inside a value typed as the record and the *next* encode — publishing
12
+ // ConnectPlugin — threw. Registration then froze at the previous version with the
13
+ // deploy reporting success, so this asserts the round trip rather than the read.
14
+
15
+ open JestGlobals
16
+
17
+ let offloadedFragment: Reventless.Plugin.pluginDefinition = {
18
+ id: "Catalog@1.0.0-alpha.215",
19
+ name: "Catalog",
20
+ version: "1.0.0-alpha.215",
21
+ extensionPoints: [],
22
+ extensions: [],
23
+ eventCollector: "arn:aws:sqs:eu-west-1:1:CatalogPluginEventColl",
24
+ extensionProtocols: [],
25
+ apiSchemaFragment: Some(
26
+ Offloaded({
27
+ store: "pluginApiFragments",
28
+ key: "sha256/a3db053a9ca51d63982811b0c1faa42cbb83ef6011ea140841375c7b1c9e9695",
29
+ hash: "a3db053a9ca51d63982811b0c1faa42cbb83ef6011ea140841375c7b1c9e9695",
30
+ bytes: 14594,
31
+ }),
32
+ ),
33
+ apiTarget: Some("Domain"),
34
+ structure: None,
35
+ dcbEventLog: None,
36
+ kind: Domain,
37
+ }
38
+
39
+ let inlineFragment: Reventless.Plugin.pluginDefinition = {
40
+ ...offloadedFragment,
41
+ apiSchemaFragment: Some(Inline({encoded: "{\"types\":[]}", protocol: "1"})),
42
+ }
43
+
44
+ // The asset as it lands in the code archive — the deploy side, verbatim.
45
+ let writeAsset = (def: Reventless.Plugin.pluginDefinition): string =>
46
+ def->Reventless.Util_Sury.toJsonString(Reventless.Plugin.pluginDefinitionSchema)
47
+
48
+ // The runtime side, through the real entry point: loadPluginDefinition resolves
49
+ // the asset against process.cwd(), so the file has to be laid down where it looks
50
+ // rather than the decode re-implemented here — re-implementing it is what would
51
+ // let a cast come back green.
52
+ let readAsset = (json: string): Reventless.Plugin.pluginDefinition => {
53
+ let dir = NodeFs.mkdtempSync(NodePath.join([NodeOs.tmpdir(), "plugindef-"]))
54
+ let previousCwd = NodeProcess.cwd()
55
+ NodeFs.writeFileSync(NodePath.join([dir, "pluginDefinition.json"]), json)
56
+ NodeProcess.chdir(dir)
57
+ let restore = () => {
58
+ NodeProcess.chdir(previousCwd)
59
+ NodeFs.rmSync(dir, {recursive: true, force: true})
60
+ }
61
+ switch EventCollectorEntryPoint_Ops.loadPluginDefinition() {
62
+ | def =>
63
+ restore()
64
+ def
65
+ | exception exn =>
66
+ restore()
67
+ throw(exn)
68
+ }
69
+ }
70
+
71
+ describe("pluginDefinition.json write/read symmetry", () => {
72
+ testSync("an offloaded field reaches the runtime as Offloaded, not as its wire form", () => {
73
+ let def = offloadedFragment->writeAsset->readAsset
74
+ switch def.apiSchemaFragment {
75
+ | Some(Offloaded({store, bytes})) =>
76
+ expect(store)->toBe("pluginApiFragments")
77
+ expect(bytes)->toBe(14594)
78
+ | Some(Inline(_)) => fail("offloaded fragment decoded as Inline")
79
+ | None => fail("offloaded fragment decoded as None")
80
+ }
81
+ })
82
+
83
+ testSync("a definition read back from the asset can be published to the Connect handshake", () => {
84
+ // The encode the old cast broke. Both arms, because only Offloaded diverges
85
+ // between wire and runtime shape and an Inline-only test would stay green.
86
+ [offloadedFragment, inlineFragment]->Array.forEach(def => {
87
+ let published =
88
+ ReventlessInfra.PluginExtensionPointSpec.ConnectPlugin(def->writeAsset->readAsset)
89
+ ->Reventless.Util_Sury.toJson(ReventlessInfra.PluginExtensionPointSpec.commandSchema)
90
+ expect(published->JSON.Decode.object->Option.isSome)->toBe(true)
91
+ })
92
+ })
93
+
94
+ testSync("the asset round-trips byte-identically, so no stored message shifts", () => {
95
+ let once = offloadedFragment->writeAsset
96
+ expect(once->readAsset->writeAsset)->toBe(once)
97
+ })
98
+
99
+ testSync("an absent optional reaches the runtime as None, not as null", () => {
100
+ let def = offloadedFragment->writeAsset->readAsset
101
+ expect(def.structure->Option.isNone)->toBe(true)
102
+ expect(def.dcbEventLog->Option.isNone)->toBe(true)
103
+ })
104
+ })
@@ -0,0 +1,129 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Nodefs from "node:fs";
4
+ import * as Nodeos from "node:os";
5
+ import * as Nodepath from "node:path";
6
+ import * as JestGlobals from "@reventlessdev/rescript-jest/src/JestGlobals.res.mjs";
7
+ import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
8
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
9
+ import * as Plugin$Reventless from "@reventlessdev/reventless-spec/src/components/Plugin.res.mjs";
10
+ import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
11
+ import * as PluginExtensionPointSpec$ReventlessInfra from "@reventlessdev/reventless-infra/src/types/PluginExtensionPointSpec.res.mjs";
12
+ import * as EventCollectorEntryPoint_Ops$ReventlessAws from "../src/adapter/Runtime/EventCollectorEntryPoint_Ops.res.mjs";
13
+
14
+ let offloadedFragment = {
15
+ id: "Catalog@1.0.0-alpha.215",
16
+ name: "Catalog",
17
+ version: "1.0.0-alpha.215",
18
+ extensionPoints: [],
19
+ extensions: [],
20
+ eventCollector: "arn:aws:sqs:eu-west-1:1:CatalogPluginEventColl",
21
+ extensionProtocols: [],
22
+ apiSchemaFragment: {
23
+ TAG: "Offloaded",
24
+ _0: {
25
+ store: "pluginApiFragments",
26
+ key: "sha256/a3db053a9ca51d63982811b0c1faa42cbb83ef6011ea140841375c7b1c9e9695",
27
+ hash: "a3db053a9ca51d63982811b0c1faa42cbb83ef6011ea140841375c7b1c9e9695",
28
+ bytes: 14594
29
+ }
30
+ },
31
+ apiTarget: "Domain",
32
+ structure: undefined,
33
+ dcbEventLog: undefined,
34
+ kind: "Domain"
35
+ };
36
+
37
+ let inlineFragment = {
38
+ id: offloadedFragment.id,
39
+ name: offloadedFragment.name,
40
+ version: offloadedFragment.version,
41
+ extensionPoints: offloadedFragment.extensionPoints,
42
+ extensions: offloadedFragment.extensions,
43
+ eventCollector: offloadedFragment.eventCollector,
44
+ extensionProtocols: offloadedFragment.extensionProtocols,
45
+ apiSchemaFragment: {
46
+ TAG: "Inline",
47
+ _0: {
48
+ encoded: "{\"types\":[]}",
49
+ protocol: "1"
50
+ }
51
+ },
52
+ apiTarget: offloadedFragment.apiTarget,
53
+ structure: offloadedFragment.structure,
54
+ dcbEventLog: offloadedFragment.dcbEventLog,
55
+ kind: offloadedFragment.kind
56
+ };
57
+
58
+ function writeAsset(def) {
59
+ return Util_Sury$Reventless.toJsonString(def, Plugin$Reventless.pluginDefinitionSchema, undefined);
60
+ }
61
+
62
+ function readAsset(json) {
63
+ let dir = Nodefs.mkdtempSync(Nodepath.join(Nodeos.tmpdir(), "plugindef-"));
64
+ let previousCwd = process.cwd();
65
+ Nodefs.writeFileSync(Nodepath.join(dir, "pluginDefinition.json"), json, "utf8");
66
+ process.chdir(dir);
67
+ let restore = () => {
68
+ process.chdir(previousCwd);
69
+ Nodefs.rmSync(dir, {
70
+ recursive: true,
71
+ force: true
72
+ });
73
+ };
74
+ let def;
75
+ try {
76
+ def = EventCollectorEntryPoint_Ops$ReventlessAws.loadPluginDefinition();
77
+ } catch (exn) {
78
+ restore();
79
+ throw exn;
80
+ }
81
+ restore();
82
+ return def;
83
+ }
84
+
85
+ globalThis.describe("pluginDefinition.json write/read symmetry", () => {
86
+ globalThis.test("an offloaded field reaches the runtime as Offloaded, not as its wire form", () => {
87
+ let def = readAsset(Util_Sury$Reventless.toJsonString(offloadedFragment, Plugin$Reventless.pluginDefinitionSchema, undefined));
88
+ let match = def.apiSchemaFragment;
89
+ if (match === undefined) {
90
+ return JestGlobals.fail("offloaded fragment decoded as None");
91
+ }
92
+ if (match.TAG === "Inline") {
93
+ return JestGlobals.fail("offloaded fragment decoded as Inline");
94
+ }
95
+ let match$1 = match._0;
96
+ globalThis.expect(match$1.store).toBe("pluginApiFragments");
97
+ globalThis.expect(match$1.bytes).toBe(14594);
98
+ });
99
+ globalThis.test("a definition read back from the asset can be published to the Connect handshake", () => {
100
+ [
101
+ offloadedFragment,
102
+ inlineFragment
103
+ ].forEach(def => {
104
+ let published = Util_Sury$Reventless.toJson({
105
+ TAG: "ConnectPlugin",
106
+ _0: readAsset(Util_Sury$Reventless.toJsonString(def, Plugin$Reventless.pluginDefinitionSchema, undefined))
107
+ }, PluginExtensionPointSpec$ReventlessInfra.commandSchema);
108
+ globalThis.expect(Stdlib_Option.isSome(Stdlib_JSON.Decode.object(published))).toBe(true);
109
+ });
110
+ });
111
+ globalThis.test("the asset round-trips byte-identically, so no stored message shifts", () => {
112
+ let once = Util_Sury$Reventless.toJsonString(offloadedFragment, Plugin$Reventless.pluginDefinitionSchema, undefined);
113
+ let def = readAsset(once);
114
+ globalThis.expect(Util_Sury$Reventless.toJsonString(def, Plugin$Reventless.pluginDefinitionSchema, undefined)).toBe(once);
115
+ });
116
+ globalThis.test("an absent optional reaches the runtime as None, not as null", () => {
117
+ let def = readAsset(Util_Sury$Reventless.toJsonString(offloadedFragment, Plugin$Reventless.pluginDefinitionSchema, undefined));
118
+ globalThis.expect(Stdlib_Option.isNone(def.structure)).toBe(true);
119
+ globalThis.expect(Stdlib_Option.isNone(def.dcbEventLog)).toBe(true);
120
+ });
121
+ });
122
+
123
+ export {
124
+ offloadedFragment,
125
+ inlineFragment,
126
+ writeAsset,
127
+ readAsset,
128
+ }
129
+ /* Not a pure module */