@reventlessdev/reventless-aws 3.0.0-alpha.301 → 3.0.0-alpha.303

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/package.json +7 -7
  3. package/src/Platform.res +18 -4
  4. package/src/Platform.res.mjs +24 -4
  5. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +39 -4
  6. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +34 -13
  7. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res +108 -20
  8. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res.mjs +99 -17
  9. package/src/adapter/QueryDb/PgQueryResolver_Lambda.res +41 -3
  10. package/src/adapter/QueryDb/PgQueryResolver_Lambda.res.mjs +27 -6
  11. package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res +34 -4
  12. package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs +16 -5
  13. package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res +10 -0
  14. package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res.mjs +4 -1
  15. package/src/adapter/Runtime/StateTopicPublish.mjs +38 -5
  16. package/src/adapter/StateTopic/StateTopic_AppSync.res +49 -1
  17. package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +28 -3
  18. package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res +73 -2
  19. package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res.mjs +39 -4
  20. package/src/util/Util_DynamoDbStream.res +15 -4
  21. package/src/util/Util_DynamoDbStream.res.mjs +15 -5
  22. package/src/util/Util_ShellConfig.res +22 -2
  23. package/src/util/Util_ShellConfig.res.mjs +11 -0
  24. package/tests/PgQueryResolver_LambdaTest.res +16 -1
  25. package/tests/PgQueryResolver_LambdaTest.res.mjs +46 -32
  26. package/tests/Platform_ComponentDefinitions_Lambda_OpsTest.res +111 -0
  27. package/tests/Platform_ComponentDefinitions_Lambda_OpsTest.res.mjs +67 -0
  28. package/tests/QueryDbResolvers_AppSyncTest.res.mjs +3 -3
  29. package/tests/StateChangeDescriptorParityTest.res +82 -1
  30. package/tests/StateChangeDescriptorParityTest.res.mjs +97 -1
  31. package/tests/Util_DynamoDbStreamTest.res +43 -0
  32. package/tests/Util_DynamoDbStreamTest.res.mjs +41 -0
  33. package/tests/Util_ShellConfigTest.res +107 -0
  34. package/tests/Util_ShellConfigTest.res.mjs +100 -0
  35. package/tests/stateChangeDescriptorParity.mjs +72 -3
@@ -1,9 +1,12 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as JestGlobals from "@reventlessdev/rescript-jest/src/JestGlobals.res.mjs";
4
+ import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
5
+ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
4
6
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
7
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
6
8
  import * as Util_ShellConfig$ReventlessAws from "../src/util/Util_ShellConfig.res.mjs";
9
+ import * as Platform_BakedManifest$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_BakedManifest.res.mjs";
7
10
 
8
11
  let computed = [
9
12
  [
@@ -107,6 +110,103 @@ globalThis.describe("Util_ShellConfig.fields — bakedManifest", () => {
107
110
  });
108
111
  });
109
112
 
113
+ globalThis.describe("Util_ShellConfig.fields — journeys", () => {
114
+ let withJourneys = journeys => ({
115
+ components: [{
116
+ plugin: "Catalog",
117
+ views: ["Products"],
118
+ commands: []
119
+ }],
120
+ journeys: journeys
121
+ });
122
+ let shopper_components = [{
123
+ plugin: "Catalog",
124
+ views: ["Products"],
125
+ commands: []
126
+ }];
127
+ let shopper = {
128
+ group: "Shopper",
129
+ components: shopper_components
130
+ };
131
+ let fulfilment_components = [{
132
+ plugin: "Ordering",
133
+ views: ["Orders"],
134
+ commands: ["ShipOrder"]
135
+ }];
136
+ let fulfilment_key = "fulfilment.json";
137
+ let fulfilment = {
138
+ group: "Fulfilment",
139
+ components: fulfilment_components,
140
+ key: fulfilment_key
141
+ };
142
+ globalThis.test("a bake declaring no journeys writes no map", () => {
143
+ let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, {
144
+ components: [{
145
+ plugin: "Catalog",
146
+ views: ["Products"],
147
+ commands: []
148
+ }]
149
+ }, undefined);
150
+ globalThis.expect(Stdlib_Option.isNone(out["journeyManifestUrls"])).toBe(true);
151
+ });
152
+ globalThis.test("an empty journeys array is the same as none", () => {
153
+ let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, withJourneys([]), undefined);
154
+ globalThis.expect(Stdlib_Option.isNone(out["journeyManifestUrls"])).toBe(true);
155
+ });
156
+ globalThis.test("keeps manifestUrl as the default journey", () => {
157
+ let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, withJourneys([shopper]), undefined);
158
+ globalThis.expect(get(out, "manifestUrl")).toEqual("/component-manifest.json");
159
+ });
160
+ globalThis.test("maps each declared group to its own file", () => {
161
+ let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, withJourneys([
162
+ shopper,
163
+ fulfilment
164
+ ]), undefined);
165
+ globalThis.expect(get(out, "journeyManifestUrls")).toEqual(Object.fromEntries([
166
+ [
167
+ "Shopper",
168
+ "/component-manifest-shopper.json"
169
+ ],
170
+ [
171
+ "Fulfilment",
172
+ "/fulfilment.json"
173
+ ]
174
+ ]));
175
+ });
176
+ globalThis.test("publishes the URL of every key the bake writes, exactly once", () => {
177
+ let bake = withJourneys([
178
+ shopper,
179
+ fulfilment
180
+ ]);
181
+ let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, bake, undefined);
182
+ let published = Stdlib_Option.mapOr(Stdlib_JSON.Decode.string(get(out, "manifestUrl")), [], url => [url]).concat(Stdlib_Array.filterMap(Object.values(Stdlib_Option.getOr(Stdlib_JSON.Decode.object(get(out, "journeyManifestUrls")), {})), Stdlib_JSON.Decode.string));
183
+ globalThis.expect(published).toEqual(Platform_BakedManifest$ReventlessCore.files(bake).map(param => "/" + param[0]));
184
+ });
185
+ globalThis.test("a shellConfig journey map fails the deploy rather than redirecting it", () => {
186
+ let failure;
187
+ try {
188
+ Util_ShellConfig$ReventlessAws.fields(computed, undefined, withJourneys([shopper]), Object.fromEntries([[
189
+ "journeyManifestUrls",
190
+ {}
191
+ ]]));
192
+ failure = undefined;
193
+ } catch (raw_message) {
194
+ let message = Primitive_exceptions.internalToException(raw_message);
195
+ if (message.RE_EXN_ID === "Failure") {
196
+ failure = message._1;
197
+ } else {
198
+ throw message;
199
+ }
200
+ }
201
+ if (failure !== undefined) {
202
+ globalThis.expect(failure.includes("journeyManifestUrls")).toBe(true);
203
+ return;
204
+ } else {
205
+ return JestGlobals.fail("a shellConfig key redirecting the computed journey map must fail the deploy");
206
+ }
207
+ });
208
+ });
209
+
110
210
  globalThis.describe("Util_ShellConfig.fields — shellConfig passthrough", () => {
111
211
  globalThis.test("shell-owned keys land verbatim, under the computed ones", () => {
112
212
  let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, undefined, Object.fromEntries([
@@ -49,6 +49,59 @@ const CASES = [
49
49
  entityKey: "p3",
50
50
  state: { id: "p3", blob: "x".repeat(70 * 1024), updatedAt: "2026-05-19T12:00:00Z" },
51
51
  },
52
+ // Retirement: the row is withdrawn, so every implementation must publish the
53
+ // metadata-only shape — no state, and no sortKeyValue either, even though this
54
+ // row carries an updatedAt that the same row would publish while live.
55
+ {
56
+ name: "retired row degrades to metadata only",
57
+ changeKind: "Updated",
58
+ entityKey: "p4",
59
+ state: { id: "p4", name: "Widget", archived: true, updatedAt: "2026-05-19T12:00:00Z" },
60
+ retiredField: "archived",
61
+ },
62
+ // The control: same field declared, flag false, so nothing changes. Without
63
+ // this, an implementation that dropped the payload unconditionally whenever a
64
+ // retiredField was configured would still pass the case above.
65
+ {
66
+ name: "live row with a retirement flag carries the row",
67
+ changeKind: "Updated",
68
+ entityKey: "p5",
69
+ state: { id: "p5", name: "Widget", archived: false, updatedAt: "2026-05-19T12:00:00Z" },
70
+ retiredField: "archived",
71
+ },
72
+ // The state form, and the reason it is a set: a lifecycle may end in more than
73
+ // one way, and both ways withdraw the row identically. Two cases rather than
74
+ // one so an implementation comparing against only the first member fails.
75
+ {
76
+ name: "row in the first retired state degrades to metadata only",
77
+ changeKind: "Updated",
78
+ entityKey: "p6",
79
+ state: { id: "p6", name: "Widget", shelfStatus: "Archived", updatedAt: "2026-05-19T12:00:00Z" },
80
+ retiredField: "shelfStatus",
81
+ retiredValues: ["Archived", "Discontinued"],
82
+ },
83
+ {
84
+ name: "row in the second retired state degrades to metadata only",
85
+ changeKind: "Updated",
86
+ entityKey: "p7",
87
+ state: {
88
+ id: "p7",
89
+ name: "Widget",
90
+ shelfStatus: "Discontinued",
91
+ updatedAt: "2026-05-19T12:00:00Z",
92
+ },
93
+ retiredField: "shelfStatus",
94
+ retiredValues: ["Archived", "Discontinued"],
95
+ },
96
+ // The state-form control: a live state on the same declared lifecycle.
97
+ {
98
+ name: "row in a live state carries the row",
99
+ changeKind: "Updated",
100
+ entityKey: "p8",
101
+ state: { id: "p8", name: "Widget", shelfStatus: "Listed", updatedAt: "2026-05-19T12:00:00Z" },
102
+ retiredField: "shelfStatus",
103
+ retiredValues: ["Archived", "Discontinued"],
104
+ },
52
105
  {
53
106
  name: "delete carries no row",
54
107
  changeKind: "Removed",
@@ -79,10 +132,17 @@ export async function buildAll() {
79
132
  return { ...descriptor, seq: "<seq>" };
80
133
  };
81
134
 
82
- return CASES.map(({ name, changeKind, entityKey, state }) => ({
135
+ return CASES.map(({ name, changeKind, entityKey, state, retiredField, retiredValues }) => ({
83
136
  name,
84
137
  local: normalise(
85
- local.make(changeKind, entityKey, state === undefined ? undefined : state, local.nextSequence()),
138
+ local.make(
139
+ changeKind,
140
+ entityKey,
141
+ state === undefined ? undefined : state,
142
+ local.nextSequence(),
143
+ retiredField,
144
+ retiredValues,
145
+ ),
86
146
  ),
87
147
  relay: normalise(
88
148
  relay.makeDescriptor(
@@ -93,10 +153,19 @@ export async function buildAll() {
93
153
  // OldImage, which makeDescriptor then drops.
94
154
  state === undefined ? { id: entityKey } : state,
95
155
  "49590300000000016818000000",
156
+ retiredField,
157
+ retiredValues,
96
158
  ),
97
159
  ),
98
160
  postgres: normalise(
99
- postgres.makeDescriptor({ changeKind, entityKey, state, seq: postgres.nextSequence() }),
161
+ postgres.makeDescriptor({
162
+ changeKind,
163
+ entityKey,
164
+ state,
165
+ seq: postgres.nextSequence(),
166
+ retiredField,
167
+ retiredValues,
168
+ }),
100
169
  ),
101
170
  }));
102
171
  }