@reventlessdev/reventless-aws 3.0.0-alpha.345 → 3.0.0-alpha.347
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 +54 -0
- package/package.json +19 -12
- package/rescript.json +2 -1
- package/run-bake-manifest.mjs +3 -0
- package/run-deploy-app.mjs +3 -0
- package/run-provision-accounts.mjs +3 -0
- package/run-provision-admin.mjs +3 -0
- package/scripts/BakeManifest.res +395 -0
- package/scripts/BakeManifest.res.mjs +487 -0
- package/scripts/DeployApp.res +708 -0
- package/scripts/DeployApp.res.mjs +1015 -0
- package/scripts/DeployManifest.res +84 -0
- package/scripts/DeployManifest.res.mjs +112 -0
- package/scripts/ProvisionAccounts.res +339 -0
- package/scripts/ProvisionAccounts.res.mjs +351 -0
- package/scripts/ProvisionAdmin.res +275 -0
- package/scripts/ProvisionAdmin.res.mjs +289 -0
- package/scripts/ProvisionCognito.res +149 -0
- package/scripts/ProvisionCognito.res.mjs +98 -0
- package/scripts/ProvisionIdentity.res +76 -11
- package/scripts/ProvisionIdentity.res.mjs +72 -7
- package/scripts/ProvisionProvider.res +104 -0
- package/scripts/ProvisionProvider.res.mjs +123 -0
- package/scripts/PulumiCli.res +43 -0
- package/scripts/PulumiCli.res.mjs +106 -0
- package/src/Platform.res +51 -4
- package/src/Platform.res.mjs +32 -14
- package/src/Platform_Stack.res +119 -9
- package/src/Platform_Stack.res.mjs +42 -4
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +1 -5
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +1 -1
- package/src/adapter/Api/Platform_UIFragments_Lambda.res +1 -5
- package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +1 -2
- package/src/adapter/Auth/Auth_ActiveRoleStore.res +1 -5
- package/src/adapter/Auth/Auth_ActiveRoleStore.res.mjs +1 -2
- package/src/adapter/Auth/Auth_ActiveRoleTrigger.res +1 -5
- package/src/adapter/Auth/Auth_ActiveRoleTrigger.res.mjs +1 -2
- package/src/adapter/Auth/Auth_LoginIdentifier.res +66 -0
- package/src/adapter/Auth/Auth_LoginIdentifier.res.mjs +70 -0
- package/src/adapter/Auth/Auth_SignUpMode.res +78 -0
- package/src/adapter/Auth/Auth_SignUpMode.res.mjs +56 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res +52 -31
- package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res.mjs +75 -44
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +1 -5
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +1 -2
- package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res +1 -5
- package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res.mjs +1 -2
- package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res +5 -4
- package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res.mjs +1 -1
- package/src/adapter/Postgres/PgMigration_Builder.res +1 -1
- package/src/adapter/Postgres/PgMigration_Builder.res.mjs +1 -2
- package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res +5 -0
- package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res.mjs +5 -1
- package/src/adapter/Runtime/CommandGeneratorEntryPoint_Ops.res +2 -0
- package/src/adapter/Runtime/CommandGeneratorEntryPoint_Ops.res.mjs +2 -2
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +15 -6
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res +51 -30
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res.mjs +31 -14
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +1 -5
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +1 -1
- package/src/adapter/StateTopic/StateTopic_AppSync.res +1 -5
- package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +1 -1
- package/src/adapter/Task/TaskBucket_S3.res +1 -3
- package/src/adapter/Task/TaskBucket_S3.res.mjs +1 -1
- package/src/adapter/Upload/Upload_Claim_S3.res +1 -5
- package/src/adapter/Upload/Upload_Claim_S3.res.mjs +1 -1
- package/src/adapter/Upload/Upload_Presign_S3.res +1 -5
- package/src/adapter/Upload/Upload_Presign_S3.res.mjs +1 -2
- package/src/capability/Capability_Geocoding_AwsLocation.res +5 -1
- package/src/capability/Capability_Geocoding_AwsLocation.res.mjs +7 -1
- package/src/components/Api/AppSync_Adapter.res +56 -72
- package/src/components/Api/AppSync_Adapter.res.mjs +26 -60
- package/src/components/Api/AppSync_SdlDecorate.res +82 -1
- package/src/components/Api/AppSync_SdlDecorate.res.mjs +45 -0
- package/src/plugin/cloner/ClonerRunner_Fargate.res +1 -5
- package/src/plugin/cloner/ClonerRunner_Fargate.res.mjs +1 -1
- package/src/plugin/stack/Plugin_Stack.res +4 -0
- package/src/plugin/stack/Plugin_Stack.res.mjs +2 -0
- package/src/util/Util_DeadLetterQueue.res +1 -5
- package/src/util/Util_DeadLetterQueue.res.mjs +1 -2
- package/src/util/Util_StoreLayout.res +15 -2
- package/src/util/Util_StoreLayout.res.mjs +11 -3
- package/tests/AppSync_AdapterTest.res +83 -0
- package/tests/AppSync_AdapterTest.res.mjs +69 -0
- package/tests/Auth_LoginIdentifierTest.res +51 -0
- package/tests/Auth_LoginIdentifierTest.res.mjs +51 -0
- package/tests/Auth_SignUpModeTest.res +56 -0
- package/tests/Auth_SignUpModeTest.res.mjs +53 -0
- package/tests/BakeManifestTest.res +204 -0
- package/tests/BakeManifestTest.res.mjs +259 -0
- package/tests/DcbColdStartPartitionTest.res +32 -0
- package/tests/DcbColdStartPartitionTest.res.mjs +33 -0
- package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res +88 -0
- package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res.mjs +112 -0
- package/tests/DcbUnresolvedPartitionSlice.res +28 -0
- package/tests/DcbUnresolvedPartitionSlice.res.mjs +38 -0
- package/tests/DeployAppTest.res +90 -0
- package/tests/DeployAppTest.res.mjs +133 -0
- package/tests/LambdaLayerLookupTest.res +104 -0
- package/tests/LambdaLayerLookupTest.res.mjs +117 -0
- package/tests/ProvisionAccountsTest.res +168 -0
- package/tests/ProvisionAccountsTest.res.mjs +184 -0
- package/tests/ProvisionAdminTest.res +55 -0
- package/tests/ProvisionAdminTest.res.mjs +66 -0
- package/tests/ProvisionIdentityTest.res +89 -3
- package/tests/ProvisionIdentityTest.res.mjs +66 -1
- package/tests/Util_OwnerScopeEnvTest.res +25 -0
- package/tests/Util_OwnerScopeEnvTest.res.mjs +13 -0
- package/tests/Util_StoreLayoutTest.res +6 -0
- package/tests/Util_StoreLayoutTest.res.mjs +7 -4
- package/tests/dcbColdStartPartition.mjs +66 -0
- package/tests/setup/layerArn.cjs +5 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
4
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
|
+
import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
|
|
6
|
+
import * as BakeManifest$ReventlessAws from "../scripts/BakeManifest.res.mjs";
|
|
7
|
+
import * as DeployManifest$ReventlessAws from "../scripts/DeployManifest.res.mjs";
|
|
8
|
+
|
|
9
|
+
function json(text) {
|
|
10
|
+
return JSON.parse(text);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
globalThis.describe("DeployManifest", () => {
|
|
14
|
+
globalThis.test("resolves every folder against the manifest's own folder", () => {
|
|
15
|
+
let resolved = Stdlib_Result.map(DeployManifest$ReventlessAws.parseString(`
|
|
16
|
+
region: eu-west-1
|
|
17
|
+
platform:
|
|
18
|
+
path: platform-aws
|
|
19
|
+
name: shop-platform
|
|
20
|
+
stack-defaults:
|
|
21
|
+
platform:messagingEmailProvider: log
|
|
22
|
+
plugins:
|
|
23
|
+
- name: catalog
|
|
24
|
+
path: catalog-aws
|
|
25
|
+
depends-on: []
|
|
26
|
+
- name: ordering
|
|
27
|
+
path: ordering-aws
|
|
28
|
+
`), m => DeployManifest$ReventlessAws.resolve(m, "/repo/shop/deploy-manifest.yaml"));
|
|
29
|
+
globalThis.expect(resolved).toEqual({
|
|
30
|
+
TAG: "Ok",
|
|
31
|
+
_0: {
|
|
32
|
+
file: "/repo/shop/deploy-manifest.yaml",
|
|
33
|
+
region: "eu-west-1",
|
|
34
|
+
platform: {
|
|
35
|
+
name: "shop-platform",
|
|
36
|
+
dir: "/repo/shop/platform-aws",
|
|
37
|
+
stackDefaults: Object.fromEntries([[
|
|
38
|
+
"platform:messagingEmailProvider",
|
|
39
|
+
"log"
|
|
40
|
+
]])
|
|
41
|
+
},
|
|
42
|
+
plugins: [
|
|
43
|
+
{
|
|
44
|
+
name: "catalog",
|
|
45
|
+
dir: "/repo/shop/catalog-aws",
|
|
46
|
+
stackDefaults: {}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "ordering",
|
|
50
|
+
dir: "/repo/shop/ordering-aws",
|
|
51
|
+
stackDefaults: {}
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
globalThis.test("refuses a manifest without a platform", () => {
|
|
58
|
+
globalThis.expect(Stdlib_Result.isError(DeployManifest$ReventlessAws.parseString("plugins: []"))).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
globalThis.describe("BakeManifest.parseArgs", () => {
|
|
63
|
+
globalThis.test("reads the stack, the manifest and the deploy's start", () => {
|
|
64
|
+
globalThis.expect(BakeManifest$ReventlessAws.parseArgs([
|
|
65
|
+
"--stack",
|
|
66
|
+
"alpha",
|
|
67
|
+
"--manifest",
|
|
68
|
+
"m.yaml",
|
|
69
|
+
"--since",
|
|
70
|
+
"2026-09-16T10:00:00Z"
|
|
71
|
+
])).toEqual({
|
|
72
|
+
TAG: "Ok",
|
|
73
|
+
_0: {
|
|
74
|
+
manifest: "m.yaml",
|
|
75
|
+
stack: "alpha",
|
|
76
|
+
since: "2026-09-16T10:00:00Z",
|
|
77
|
+
help: false
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
globalThis.test("an empty --since is no instant", () => {
|
|
82
|
+
globalThis.expect(Stdlib_Result.map(BakeManifest$ReventlessAws.parseArgs([
|
|
83
|
+
"--since",
|
|
84
|
+
""
|
|
85
|
+
]), a => a.since)).toEqual({
|
|
86
|
+
TAG: "Ok",
|
|
87
|
+
_0: undefined
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
globalThis.test("an unknown flag refuses", () => {
|
|
91
|
+
globalThis.expect(BakeManifest$ReventlessAws.parseArgs([
|
|
92
|
+
"--stak",
|
|
93
|
+
"alpha"
|
|
94
|
+
])).toEqual({
|
|
95
|
+
TAG: "Error",
|
|
96
|
+
_0: `unknown argument "--stak"`
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
globalThis.describe("BakeManifest reads the stacks", () => {
|
|
102
|
+
globalThis.test("a platform without a bake function declares no bake", () => {
|
|
103
|
+
globalThis.expect(BakeManifest$ReventlessAws.targetOf(Stdlib_Option.getOr(Stdlib_JSON.Decode.object(JSON.parse(`{"bakedManifestBucket": "b", "bakedManifestFunction": ""}`)), {}))).toEqual(undefined);
|
|
104
|
+
});
|
|
105
|
+
globalThis.test("a platform with one names the function, bucket and key", () => {
|
|
106
|
+
globalThis.expect(BakeManifest$ReventlessAws.targetOf(Stdlib_Option.getOr(Stdlib_JSON.Decode.object(JSON.parse(`{"bakedManifestFunction": "fn", "bakedManifestBucket": "b", "bakedManifestKey": "manifest.json"}`)), {}))).toEqual({
|
|
107
|
+
functionName: "fn",
|
|
108
|
+
bucket: "b",
|
|
109
|
+
key: "manifest.json"
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
globalThis.test("a plugin stack's structure key is read from pluginStructureRef", () => {
|
|
113
|
+
globalThis.expect(BakeManifest$ReventlessAws.structureRefOf(Stdlib_Option.getOr(Stdlib_JSON.Decode.object(JSON.parse(`{"pluginStructureRef": {"plugin": "Catalog", "key": "sha256/abc"}}`)), {}))).toEqual([
|
|
114
|
+
"Catalog",
|
|
115
|
+
"sha256/abc"
|
|
116
|
+
]);
|
|
117
|
+
});
|
|
118
|
+
globalThis.test("the payload carries the deploy's start only when there is one", () => {
|
|
119
|
+
let target = {
|
|
120
|
+
functionName: "fn",
|
|
121
|
+
bucket: "b",
|
|
122
|
+
key: "k"
|
|
123
|
+
};
|
|
124
|
+
let expect_ = Object.fromEntries([[
|
|
125
|
+
"Catalog",
|
|
126
|
+
"sha256/abc"
|
|
127
|
+
]]);
|
|
128
|
+
globalThis.expect(JSON.stringify(BakeManifest$ReventlessAws.payload(target, expect_, undefined))).toBe(`{"bake":true,"bucket":"b","key":"k","expect":{"Catalog":"sha256/abc"}}`);
|
|
129
|
+
globalThis.expect(JSON.stringify(BakeManifest$ReventlessAws.payload(target, expect_, "2026-09-16T10:00:00Z"))).toBe(`{"bake":true,"bucket":"b","key":"k","expect":{"Catalog":"sha256/abc"},"since":"2026-09-16T10:00:00Z"}`);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
function pending(state) {
|
|
134
|
+
return JSON.parse(`[{"baked": false, "pending": ["Catalog"], "registrations": [
|
|
135
|
+
{"plugin": "Catalog", "state": "` + state + `", "expected": "sha256/new", "found": "sha256/old", "writtenAt": null}
|
|
136
|
+
]}]`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let baked = JSON.parse(`[
|
|
140
|
+
{"baked": true, "bucket": "b", "key": "manifest.json", "plugins": 2, "bytes": 512},
|
|
141
|
+
{"summary": true, "plugins": 1, "registered": 1, "unchanged": 0, "matched": 0, "registrations": [
|
|
142
|
+
{"plugin": "Catalog", "state": "registered", "expected": "sha256/new", "found": "sha256/new", "writtenAt": "2026-09-16T10:01:00Z"}
|
|
143
|
+
]}
|
|
144
|
+
]`);
|
|
145
|
+
|
|
146
|
+
globalThis.describe("BakeManifest.readAnswer", () => {
|
|
147
|
+
globalThis.test("a written manifest reports its files and what convergence proved", () => {
|
|
148
|
+
let match = BakeManifest$ReventlessAws.readAnswer(baked);
|
|
149
|
+
if (match.TAG === "Ok") {
|
|
150
|
+
let match$1 = match._0;
|
|
151
|
+
if (match$1.TAG === "Baked") {
|
|
152
|
+
globalThis.expect([
|
|
153
|
+
match$1.written.map(w => [
|
|
154
|
+
w.key,
|
|
155
|
+
w.bytes
|
|
156
|
+
]),
|
|
157
|
+
Stdlib_Option.map(match$1.summary, s => s.registered)
|
|
158
|
+
]).toEqual([
|
|
159
|
+
[[
|
|
160
|
+
"manifest.json",
|
|
161
|
+
512
|
|
162
|
+
]],
|
|
163
|
+
1
|
|
164
|
+
]);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
globalThis.expect("not baked").toBe("baked");
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
globalThis.expect("not baked").toBe("baked");
|
|
171
|
+
});
|
|
172
|
+
globalThis.test("a pending answer names the plugins not yet arrived", () => {
|
|
173
|
+
let match = BakeManifest$ReventlessAws.readAnswer(pending("behind"));
|
|
174
|
+
if (match.TAG === "Ok") {
|
|
175
|
+
let match$1 = match._0;
|
|
176
|
+
if (match$1.TAG === "Baked") {
|
|
177
|
+
globalThis.expect("not pending").toBe("pending");
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
globalThis.expect(match$1.pending).toEqual(["Catalog"]);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
globalThis.expect("not pending").toBe("pending");
|
|
184
|
+
});
|
|
185
|
+
globalThis.test("the report line carries both keys and the row's date", () => {
|
|
186
|
+
globalThis.expect(BakeManifest$ReventlessAws.registrationLine({
|
|
187
|
+
plugin: "Catalog",
|
|
188
|
+
state: "missing",
|
|
189
|
+
expected: "sha256/new",
|
|
190
|
+
found: undefined,
|
|
191
|
+
writtenAt: undefined
|
|
192
|
+
})).toBe(" Catalog: missing — expected sha256/new, row holds no row (written never)");
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
globalThis.describe("BakeManifest.converge", () => {
|
|
197
|
+
let run = async (answers, attempts) => {
|
|
198
|
+
let asked = {
|
|
199
|
+
contents: 0
|
|
200
|
+
};
|
|
201
|
+
let invoke = async () => {
|
|
202
|
+
let answer = answers[Math.min(asked.contents, answers.length - 1 | 0)];
|
|
203
|
+
asked.contents = asked.contents + 1 | 0;
|
|
204
|
+
return {
|
|
205
|
+
TAG: "Ok",
|
|
206
|
+
_0: answer
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
let outcome = await BakeManifest$ReventlessAws.converge(invoke, async () => {}, param => {}, attempts);
|
|
210
|
+
return [
|
|
211
|
+
Stdlib_Result.isOk(outcome),
|
|
212
|
+
asked.contents
|
|
213
|
+
];
|
|
214
|
+
};
|
|
215
|
+
globalThis.test("asks again while a plugin is behind, then succeeds", async () => {
|
|
216
|
+
globalThis.expect(await run([
|
|
217
|
+
pending("behind"),
|
|
218
|
+
pending("behind"),
|
|
219
|
+
baked
|
|
220
|
+
], 5)).toEqual([
|
|
221
|
+
true,
|
|
222
|
+
3
|
|
223
|
+
]);
|
|
224
|
+
});
|
|
225
|
+
globalThis.test("waits for a missing registration", async () => {
|
|
226
|
+
globalThis.expect(await run([
|
|
227
|
+
pending("missing"),
|
|
228
|
+
baked
|
|
229
|
+
], 5)).toEqual([
|
|
230
|
+
true,
|
|
231
|
+
2
|
|
232
|
+
]);
|
|
233
|
+
});
|
|
234
|
+
globalThis.test("stops at once on a registration that diverged", async () => {
|
|
235
|
+
globalThis.expect(await run([
|
|
236
|
+
pending("diverged"),
|
|
237
|
+
baked
|
|
238
|
+
], 5)).toEqual([
|
|
239
|
+
false,
|
|
240
|
+
1
|
|
241
|
+
]);
|
|
242
|
+
});
|
|
243
|
+
globalThis.test("gives up when the attempts run out", async () => {
|
|
244
|
+
globalThis.expect(await run([pending("behind")], 3)).toEqual([
|
|
245
|
+
false,
|
|
246
|
+
3
|
|
247
|
+
]);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
let Bake;
|
|
252
|
+
|
|
253
|
+
export {
|
|
254
|
+
Bake,
|
|
255
|
+
json,
|
|
256
|
+
pending,
|
|
257
|
+
baked,
|
|
258
|
+
}
|
|
259
|
+
/* Not a pure module */
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// The DCB command Lambda refuses to start when its partition keys cannot be
|
|
2
|
+
// derived, and says why on every invocation.
|
|
3
|
+
//
|
|
4
|
+
// Deploy derives the same keys from the same slices, so this only fails when the
|
|
5
|
+
// framework in the Lambda layer differs from the one deploy used. Carrying on
|
|
6
|
+
// would file events under the wrong keys; refusing sends messages to the DLQ.
|
|
7
|
+
|
|
8
|
+
open JestGlobals
|
|
9
|
+
|
|
10
|
+
type afterFailedStartup = {messages: array<string>, unhandled: array<string>}
|
|
11
|
+
|
|
12
|
+
@module("./dcbColdStartPartition.mjs")
|
|
13
|
+
external invokeAfterFailedStartup: unit => promise<afterFailedStartup> = "invokeAfterFailedStartup"
|
|
14
|
+
|
|
15
|
+
@module("./dcbColdStartPartition.mjs")
|
|
16
|
+
external startWithUnresolvedSlice: unit => promise<string> = "startWithUnresolvedSlice"
|
|
17
|
+
|
|
18
|
+
describe("DcbCommandTopicEntryPoint cold start", () => {
|
|
19
|
+
test("a slice whose partition cannot be inferred stops startup, naming it", async () => {
|
|
20
|
+
let message = await startWithUnresolvedSlice()
|
|
21
|
+
expect(message->String.includes("LinkGadget"))->toBe(true)
|
|
22
|
+
expect(message->String.includes("@partitionTag"))->toBe(true)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test("after a failed startup every invocation rethrows the same error", async () => {
|
|
26
|
+
let {messages, unhandled} = await invokeAfterFailedStartup()
|
|
27
|
+
expect(messages->Array.length)->toBe(2)
|
|
28
|
+
expect(messages->Array.includes("resolved"))->toBe(false)
|
|
29
|
+
expect(messages->Array.getUnsafe(0))->toBe(messages->Array.getUnsafe(1))
|
|
30
|
+
expect(unhandled)->toEqual([])
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as DcbColdStartPartitionMjs from "./dcbColdStartPartition.mjs";
|
|
4
|
+
|
|
5
|
+
function invokeAfterFailedStartup(prim) {
|
|
6
|
+
return DcbColdStartPartitionMjs.invokeAfterFailedStartup();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function startWithUnresolvedSlice(prim) {
|
|
10
|
+
return DcbColdStartPartitionMjs.startWithUnresolvedSlice();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
globalThis.describe("DcbCommandTopicEntryPoint cold start", () => {
|
|
14
|
+
globalThis.test("a slice whose partition cannot be inferred stops startup, naming it", async () => {
|
|
15
|
+
let message = await DcbColdStartPartitionMjs.startWithUnresolvedSlice();
|
|
16
|
+
globalThis.expect(message.includes("LinkGadget")).toBe(true);
|
|
17
|
+
globalThis.expect(message.includes("@partitionTag")).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
globalThis.test("after a failed startup every invocation rethrows the same error", async () => {
|
|
20
|
+
let match = await DcbColdStartPartitionMjs.invokeAfterFailedStartup();
|
|
21
|
+
let messages = match.messages;
|
|
22
|
+
globalThis.expect(messages.length).toBe(2);
|
|
23
|
+
globalThis.expect(messages.includes("resolved")).toBe(false);
|
|
24
|
+
globalThis.expect(messages[0]).toBe(messages[1]);
|
|
25
|
+
globalThis.expect(match.unhandled).toEqual([]);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
invokeAfterFailedStartup,
|
|
31
|
+
startWithUnresolvedSlice,
|
|
32
|
+
}
|
|
33
|
+
/* Not a pure module */
|
|
@@ -259,6 +259,94 @@ describe("Runtime.buildScanFilter — excludes fence sentinels (Issue 12)", () =
|
|
|
259
259
|
})
|
|
260
260
|
})
|
|
261
261
|
|
|
262
|
+
describe("Runtime.derivePartitionKey — each event type under its slice's key", () => {
|
|
263
|
+
// Two entities in one log. AddressLinked declares addressId first, so a
|
|
264
|
+
// positional default would file it under the reference.
|
|
265
|
+
let partitionTag = Reventless.DcbTag.ByEventType(
|
|
266
|
+
Dict.fromArray([("OrderPlaced", "orderId"), ("AddressLinked", "customerId")]),
|
|
267
|
+
)
|
|
268
|
+
let thrownMessage = f =>
|
|
269
|
+
try {
|
|
270
|
+
let _ = f()
|
|
271
|
+
""
|
|
272
|
+
} catch {
|
|
273
|
+
| JsExn(err) => JsExn.message(err)->Option.getOr("")
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
testSync("files an event under its own type's key, not its first tag", () =>
|
|
277
|
+
expect(
|
|
278
|
+
Runtime.derivePartitionKey(
|
|
279
|
+
~partitionTag,
|
|
280
|
+
~eventType="AddressLinked",
|
|
281
|
+
[tag("addressId", "a1"), tag("customerId", "c1")],
|
|
282
|
+
),
|
|
283
|
+
)->toBe("customerId:c1")
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
testSync("another type in the same log keeps its own key", () =>
|
|
287
|
+
expect(
|
|
288
|
+
Runtime.derivePartitionKey(
|
|
289
|
+
~partitionTag,
|
|
290
|
+
~eventType="OrderPlaced",
|
|
291
|
+
[tag("customerId", "c1"), tag("orderId", "o1")],
|
|
292
|
+
),
|
|
293
|
+
)->toBe("orderId:o1")
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
testSync("an event type no slice writes throws instead of guessing", () =>
|
|
297
|
+
expect(
|
|
298
|
+
thrownMessage(
|
|
299
|
+
() =>
|
|
300
|
+
Runtime.derivePartitionKey(
|
|
301
|
+
~partitionTag,
|
|
302
|
+
~eventType="Unknown",
|
|
303
|
+
[tag("customerId", "c1")],
|
|
304
|
+
),
|
|
305
|
+
)->String.includes("Unknown has no partition key"),
|
|
306
|
+
)->toBe(true)
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
testSync("an event missing its partition tag throws instead of guessing", () =>
|
|
310
|
+
expect(
|
|
311
|
+
thrownMessage(
|
|
312
|
+
() =>
|
|
313
|
+
Runtime.derivePartitionKey(
|
|
314
|
+
~partitionTag,
|
|
315
|
+
~eventType="AddressLinked",
|
|
316
|
+
[tag("addressId", "a1")],
|
|
317
|
+
),
|
|
318
|
+
)->String.includes("carries no customerId tag"),
|
|
319
|
+
)->toBe(true)
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
testSync("a conditional append puts and fences the event under that key", () => {
|
|
323
|
+
let linked: ReventlessCore.DcbEventLog_Adapter.rawStoredEvent = {
|
|
324
|
+
eventType: "AddressLinked",
|
|
325
|
+
data: JSON.Object(Dict.make()),
|
|
326
|
+
tags: [tag("addressId", "a1"), tag("customerId", "c1")],
|
|
327
|
+
meta: testMeta(),
|
|
328
|
+
}
|
|
329
|
+
let cond: Reventless.DcbTag.appendCondition = {
|
|
330
|
+
query: [{tags: [tag("customerId", "c1")], eventTypes: ["AddressLinked"]}],
|
|
331
|
+
after: "50",
|
|
332
|
+
}
|
|
333
|
+
let ids =
|
|
334
|
+
Runtime.buildConditionalTransactItems(table, [linked], cond, "100", ~partitionTag)
|
|
335
|
+
->Array.filterMap(
|
|
336
|
+
it =>
|
|
337
|
+
switch (it.put, it.update) {
|
|
338
|
+
| (Some(p), _) => p.item->JSON.Decode.object->Option.flatMap(i => i->Dict.get("id"))
|
|
339
|
+
| (_, Some(u)) => u.key->Dict.get("id")
|
|
340
|
+
| _ => None
|
|
341
|
+
},
|
|
342
|
+
)
|
|
343
|
+
->Array.filterMap(JSON.Decode.string)
|
|
344
|
+
expect(ids->Array.includes("customerId:c1"))->toBe(true)
|
|
345
|
+
expect(ids->Array.includes("fence#customerId:c1"))->toBe(true)
|
|
346
|
+
expect(ids->Array.some(id => id->String.includes("addressId")))->toBe(false)
|
|
347
|
+
})
|
|
348
|
+
})
|
|
349
|
+
|
|
262
350
|
describe("Runtime.buildEventPuts", () => {
|
|
263
351
|
let event = (eventType, tags): ReventlessCore.DcbEventLog_Adapter.rawStoredEvent => {
|
|
264
352
|
eventType,
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
4
4
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
5
|
+
import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
5
6
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
7
|
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
8
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
7
9
|
import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
|
|
8
10
|
import * as DcbEventLogStorage_DynamoDb_Runtime$ReventlessAws from "../src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res.mjs";
|
|
9
11
|
|
|
@@ -314,6 +316,116 @@ globalThis.describe("Runtime.buildScanFilter — excludes fence sentinels (Issue
|
|
|
314
316
|
});
|
|
315
317
|
});
|
|
316
318
|
|
|
319
|
+
globalThis.describe("Runtime.derivePartitionKey — each event type under its slice's key", () => {
|
|
320
|
+
let partitionTag = {
|
|
321
|
+
TAG: "ByEventType",
|
|
322
|
+
_0: Object.fromEntries([
|
|
323
|
+
[
|
|
324
|
+
"OrderPlaced",
|
|
325
|
+
"orderId"
|
|
326
|
+
],
|
|
327
|
+
[
|
|
328
|
+
"AddressLinked",
|
|
329
|
+
"customerId"
|
|
330
|
+
]
|
|
331
|
+
])
|
|
332
|
+
};
|
|
333
|
+
let thrownMessage = f => {
|
|
334
|
+
try {
|
|
335
|
+
f();
|
|
336
|
+
return "";
|
|
337
|
+
} catch (raw_err) {
|
|
338
|
+
let err = Primitive_exceptions.internalToException(raw_err);
|
|
339
|
+
if (err.RE_EXN_ID === "JsExn") {
|
|
340
|
+
return Stdlib_Option.getOr(Stdlib_JsExn.message(err._1), "");
|
|
341
|
+
}
|
|
342
|
+
throw err;
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
globalThis.test("files an event under its own type's key, not its first tag", () => {
|
|
346
|
+
globalThis.expect(DcbEventLogStorage_DynamoDb_Runtime$ReventlessAws.derivePartitionKey(partitionTag, "AddressLinked", [
|
|
347
|
+
{
|
|
348
|
+
key: "addressId",
|
|
349
|
+
value: "a1"
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
key: "customerId",
|
|
353
|
+
value: "c1"
|
|
354
|
+
}
|
|
355
|
+
])).toBe("customerId:c1");
|
|
356
|
+
});
|
|
357
|
+
globalThis.test("another type in the same log keeps its own key", () => {
|
|
358
|
+
globalThis.expect(DcbEventLogStorage_DynamoDb_Runtime$ReventlessAws.derivePartitionKey(partitionTag, "OrderPlaced", [
|
|
359
|
+
{
|
|
360
|
+
key: "customerId",
|
|
361
|
+
value: "c1"
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
key: "orderId",
|
|
365
|
+
value: "o1"
|
|
366
|
+
}
|
|
367
|
+
])).toBe("orderId:o1");
|
|
368
|
+
});
|
|
369
|
+
globalThis.test("an event type no slice writes throws instead of guessing", () => {
|
|
370
|
+
globalThis.expect(thrownMessage(() => DcbEventLogStorage_DynamoDb_Runtime$ReventlessAws.derivePartitionKey(partitionTag, "Unknown", [{
|
|
371
|
+
key: "customerId",
|
|
372
|
+
value: "c1"
|
|
373
|
+
}])).includes("Unknown has no partition key")).toBe(true);
|
|
374
|
+
});
|
|
375
|
+
globalThis.test("an event missing its partition tag throws instead of guessing", () => {
|
|
376
|
+
globalThis.expect(thrownMessage(() => DcbEventLogStorage_DynamoDb_Runtime$ReventlessAws.derivePartitionKey(partitionTag, "AddressLinked", [{
|
|
377
|
+
key: "addressId",
|
|
378
|
+
value: "a1"
|
|
379
|
+
}])).includes("carries no customerId tag")).toBe(true);
|
|
380
|
+
});
|
|
381
|
+
globalThis.test("a conditional append puts and fences the event under that key", () => {
|
|
382
|
+
let linked_data = {};
|
|
383
|
+
let linked_tags = [
|
|
384
|
+
{
|
|
385
|
+
key: "addressId",
|
|
386
|
+
value: "a1"
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
key: "customerId",
|
|
390
|
+
value: "c1"
|
|
391
|
+
}
|
|
392
|
+
];
|
|
393
|
+
let linked_meta = testMeta();
|
|
394
|
+
let linked = {
|
|
395
|
+
eventType: "AddressLinked",
|
|
396
|
+
data: linked_data,
|
|
397
|
+
tags: linked_tags,
|
|
398
|
+
meta: linked_meta
|
|
399
|
+
};
|
|
400
|
+
let cond_query = [{
|
|
401
|
+
eventTypes: ["AddressLinked"],
|
|
402
|
+
tags: [{
|
|
403
|
+
key: "customerId",
|
|
404
|
+
value: "c1"
|
|
405
|
+
}]
|
|
406
|
+
}];
|
|
407
|
+
let cond_after = "50";
|
|
408
|
+
let cond = {
|
|
409
|
+
query: cond_query,
|
|
410
|
+
after: cond_after
|
|
411
|
+
};
|
|
412
|
+
let ids = Stdlib_Array.filterMap(Stdlib_Array.filterMap(DcbEventLogStorage_DynamoDb_Runtime$ReventlessAws.buildConditionalTransactItems(table, [linked], cond, "100", partitionTag, undefined), it => {
|
|
413
|
+
let match = it.Put;
|
|
414
|
+
let match$1 = it.Update;
|
|
415
|
+
if (match !== undefined) {
|
|
416
|
+
return Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(match.Item), i => i["id"]);
|
|
417
|
+
} else if (match$1 !== undefined) {
|
|
418
|
+
return match$1.Key["id"];
|
|
419
|
+
} else {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
}), Stdlib_JSON.Decode.string);
|
|
423
|
+
globalThis.expect(ids.includes("customerId:c1")).toBe(true);
|
|
424
|
+
globalThis.expect(ids.includes("fence#customerId:c1")).toBe(true);
|
|
425
|
+
globalThis.expect(ids.some(id => id.includes("addressId"))).toBe(false);
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
|
|
317
429
|
globalThis.describe("Runtime.buildEventPuts", () => {
|
|
318
430
|
let event = (eventType, tags) => ({
|
|
319
431
|
eventType: eventType,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// A slice whose partition cannot be inferred, for `DcbColdStartPartitionTest`.
|
|
2
|
+
// It writes two ids and reads both off an event nothing in its boundary writes,
|
|
3
|
+
// so neither can be told apart from a reference.
|
|
4
|
+
//
|
|
5
|
+
// Explicit `@s.matches(...)` form: reventless-ppx is not wired into this package.
|
|
6
|
+
|
|
7
|
+
@schema
|
|
8
|
+
type consumedEvent = GadgetImported({gadgetId: string, supplierId: string})
|
|
9
|
+
|
|
10
|
+
@schema
|
|
11
|
+
type command =
|
|
12
|
+
| LinkGadget({
|
|
13
|
+
gadgetId: @s.matches(Reventless.DcbTag.string) string,
|
|
14
|
+
supplierId: @s.matches(Reventless.DcbTag.string) string,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
@schema
|
|
18
|
+
type error = AlreadyLinked
|
|
19
|
+
|
|
20
|
+
@schema
|
|
21
|
+
type event =
|
|
22
|
+
| GadgetLinked({
|
|
23
|
+
gadgetId: @s.matches(Reventless.DcbTag.string) string,
|
|
24
|
+
supplierId: @s.matches(Reventless.DcbTag.string) string,
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
let name = "LinkGadget"
|
|
28
|
+
let moduleUrl = "cold-start-test://LinkGadget"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Sury from "sury";
|
|
4
|
+
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
5
|
+
|
|
6
|
+
let consumedEventSchema = Sury.$schema(s => ({
|
|
7
|
+
TAG: "GadgetImported",
|
|
8
|
+
gadgetId: s.m(Sury.string),
|
|
9
|
+
supplierId: s.m(Sury.string)
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
let commandSchema = Sury.$schema(s => ({
|
|
13
|
+
TAG: "LinkGadget",
|
|
14
|
+
gadgetId: s.m(DcbTag$Reventless.string),
|
|
15
|
+
supplierId: s.m(DcbTag$Reventless.string)
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
let errorSchema = Sury.literal("AlreadyLinked");
|
|
19
|
+
|
|
20
|
+
let eventSchema = Sury.$schema(s => ({
|
|
21
|
+
TAG: "GadgetLinked",
|
|
22
|
+
gadgetId: s.m(DcbTag$Reventless.string),
|
|
23
|
+
supplierId: s.m(DcbTag$Reventless.string)
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
let name = "LinkGadget";
|
|
27
|
+
|
|
28
|
+
let moduleUrl = "cold-start-test://LinkGadget";
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
consumedEventSchema,
|
|
32
|
+
commandSchema,
|
|
33
|
+
errorSchema,
|
|
34
|
+
eventSchema,
|
|
35
|
+
name,
|
|
36
|
+
moduleUrl,
|
|
37
|
+
}
|
|
38
|
+
/* consumedEventSchema Not a pure module */
|