@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.
- package/CHANGELOG.md +20 -0
- package/package.json +7 -7
- package/src/Platform.res +18 -4
- package/src/Platform.res.mjs +24 -4
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +39 -4
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +34 -13
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res +108 -20
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res.mjs +99 -17
- package/src/adapter/QueryDb/PgQueryResolver_Lambda.res +41 -3
- package/src/adapter/QueryDb/PgQueryResolver_Lambda.res.mjs +27 -6
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res +34 -4
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs +16 -5
- package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res +10 -0
- package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res.mjs +4 -1
- package/src/adapter/Runtime/StateTopicPublish.mjs +38 -5
- package/src/adapter/StateTopic/StateTopic_AppSync.res +49 -1
- package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +28 -3
- package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res +73 -2
- package/src/adapter/StateTopic/StateTopic_AppSync_Ops.res.mjs +39 -4
- package/src/util/Util_DynamoDbStream.res +15 -4
- package/src/util/Util_DynamoDbStream.res.mjs +15 -5
- package/src/util/Util_ShellConfig.res +22 -2
- package/src/util/Util_ShellConfig.res.mjs +11 -0
- package/tests/PgQueryResolver_LambdaTest.res +16 -1
- package/tests/PgQueryResolver_LambdaTest.res.mjs +46 -32
- package/tests/Platform_ComponentDefinitions_Lambda_OpsTest.res +111 -0
- package/tests/Platform_ComponentDefinitions_Lambda_OpsTest.res.mjs +67 -0
- package/tests/QueryDbResolvers_AppSyncTest.res.mjs +3 -3
- package/tests/StateChangeDescriptorParityTest.res +82 -1
- package/tests/StateChangeDescriptorParityTest.res.mjs +97 -1
- package/tests/Util_DynamoDbStreamTest.res +43 -0
- package/tests/Util_DynamoDbStreamTest.res.mjs +41 -0
- package/tests/Util_ShellConfigTest.res +107 -0
- package/tests/Util_ShellConfigTest.res.mjs +100 -0
- package/tests/stateChangeDescriptorParity.mjs +72 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
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.303 (2026-08-16)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **core:** exclude retired rows from reads a caller may not widen ([662f31a](https://github.com/ReventlessDev/reventless-core/commit/662f31abb717bda5154199d349da6dcf8e2d3e78))
|
|
11
|
+
* **core:** let [@retired](https://github.com/retired) name a lifecycle state, not only a boolean ([6bb346b](https://github.com/ReventlessDev/reventless-core/commit/6bb346b4f6a5f33826fc24537953482a76067177))
|
|
12
|
+
* **core:** mark the state that retires a row, and allow more than one ([cb1461f](https://github.com/ReventlessDev/reventless-core/commit/cb1461f024d3ca3b53fd9c8b010a054e3fcc4555))
|
|
13
|
+
* **core:** withhold a retired row's payload from the live channel ([82c62db](https://github.com/ReventlessDev/reventless-core/commit/82c62db00b87f7135378496e36601b544d4d2b62))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# 3.0.0-alpha.302 (2026-08-15)
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* **aws:** let a stream resource answer for its own stream ([4617773](https://github.com/ReventlessDev/reventless-core/commit/461777349f10ebc83085d59907bac5b5537429ed))
|
|
21
|
+
### Features
|
|
22
|
+
|
|
23
|
+
* **aws:** serve one baked manifest per journey ([16d6307](https://github.com/ReventlessDev/reventless-core/commit/16d63071f059cf1fc7025f9be9206dda51f71053))
|
|
24
|
+
|
|
25
|
+
|
|
6
26
|
# 3.0.0-alpha.301 (2026-08-15)
|
|
7
27
|
|
|
8
28
|
### Bug Fixes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-aws",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.303",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"dependencies": {
|
|
@@ -14,16 +14,16 @@
|
|
|
14
14
|
"uuid": "^13.0.0",
|
|
15
15
|
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.11",
|
|
16
16
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
17
|
-
"@reventlessdev/rescript-node": "2.0.0-alpha.7",
|
|
18
17
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
19
|
-
"@reventlessdev/rescript-
|
|
18
|
+
"@reventlessdev/rescript-node": "2.0.0-alpha.7",
|
|
19
|
+
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.77",
|
|
20
20
|
"@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
|
|
21
|
-
"@reventlessdev/reventless-core": "3.0.0-alpha.
|
|
21
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.237",
|
|
22
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.143",
|
|
22
23
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.31",
|
|
23
24
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
|
|
24
|
-
"@reventlessdev/reventless-postgres": "3.0.0-alpha.
|
|
25
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.
|
|
26
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.142"
|
|
25
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.101",
|
|
26
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.115"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"rescript": "12.3.0",
|
package/src/Platform.res
CHANGED
|
@@ -2221,9 +2221,14 @@ module MakeWithConfig = (
|
|
|
2221
2221
|
// target without reading either out of the deploy program.
|
|
2222
2222
|
switch (componentDefinitions, cfg.bakedManifest) {
|
|
2223
2223
|
| (Some(fn), Some(bake)) =>
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2224
|
+
// Every file the declaration produces: the default journey's, which is
|
|
2225
|
+
// what the shell's `manifestUrl` points at, and one per declared journey.
|
|
2226
|
+
// Resolved through the same function the bake and `config.json` use, so
|
|
2227
|
+
// a grant cannot be scoped to a key nothing writes.
|
|
2228
|
+
let manifestKeys =
|
|
2229
|
+
ReventlessCore.Platform_BakedManifest.files(~config=bake)->Array.map(((key, _)) => key)
|
|
2230
|
+
let manifestKey =
|
|
2231
|
+
manifestKeys->Array.get(0)->Option.getOr(ReventlessCore.Platform_BakedManifest.defaultKey)
|
|
2227
2232
|
let _ =
|
|
2228
2233
|
(fn.roleId, bucketName)
|
|
2229
2234
|
->Pulumi.Output.all2
|
|
@@ -2239,7 +2244,16 @@ module MakeWithConfig = (
|
|
|
2239
2244
|
sid: "AllowWriteBakedManifest",
|
|
2240
2245
|
effect: Allow,
|
|
2241
2246
|
actions: Actions(["s3:PutObject"]),
|
|
2242
|
-
|
|
2247
|
+
// A single key stays a bare `Resource` string: the two
|
|
2248
|
+
// forms mean the same thing to IAM, and a deployment that
|
|
2249
|
+
// declares no journey should not see its policy rewritten
|
|
2250
|
+
// for a field it does not use.
|
|
2251
|
+
resources: switch manifestKeys->Array.map(key =>
|
|
2252
|
+
`arn:aws:s3:::${bucket}/${key}`
|
|
2253
|
+
) {
|
|
2254
|
+
| [only] => Resource(only)
|
|
2255
|
+
| arns => Resources(arns)
|
|
2256
|
+
},
|
|
2243
2257
|
},
|
|
2244
2258
|
],
|
|
2245
2259
|
)
|
package/src/Platform.res.mjs
CHANGED
|
@@ -1210,17 +1210,27 @@ function MakeWithConfig(Config) {
|
|
|
1210
1210
|
}
|
|
1211
1211
|
let match$5 = hostUiBundle.bakedManifest;
|
|
1212
1212
|
if (componentDefinitions !== undefined && match$5 !== undefined) {
|
|
1213
|
-
let
|
|
1213
|
+
let manifestKeys = Platform_BakedManifest$ReventlessCore.files(match$5).map(param => param[0]);
|
|
1214
|
+
let manifestKey = Stdlib_Option.getOr(manifestKeys[0], Platform_BakedManifest$ReventlessCore.defaultKey);
|
|
1214
1215
|
Pulumi.all([
|
|
1215
1216
|
componentDefinitions.roleId,
|
|
1216
1217
|
bucketName
|
|
1217
1218
|
]).apply(param => {
|
|
1219
|
+
let bucket = param[1];
|
|
1220
|
+
let arns = manifestKeys.map(key => `arn:aws:s3:::` + bucket + `/` + key);
|
|
1221
|
+
let tmp;
|
|
1222
|
+
if (arns.length !== 1) {
|
|
1223
|
+
tmp = arns;
|
|
1224
|
+
} else {
|
|
1225
|
+
let only = arns[0];
|
|
1226
|
+
tmp = only;
|
|
1227
|
+
}
|
|
1218
1228
|
new (Aws.iam.RolePolicy)("PlatformUIDefinitionsBake", {
|
|
1219
1229
|
policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, "PlatformUIDefinitionsBakePolicy", [{
|
|
1220
1230
|
Sid: "AllowWriteBakedManifest",
|
|
1221
1231
|
Effect: "Allow",
|
|
1222
1232
|
Action: ["s3:PutObject"],
|
|
1223
|
-
Resource:
|
|
1233
|
+
Resource: tmp
|
|
1224
1234
|
}])),
|
|
1225
1235
|
role: param[0]
|
|
1226
1236
|
});
|
|
@@ -2490,17 +2500,27 @@ function Make($star) {
|
|
|
2490
2500
|
}
|
|
2491
2501
|
let match$5 = hostUiBundle.bakedManifest;
|
|
2492
2502
|
if (componentDefinitions !== undefined && match$5 !== undefined) {
|
|
2493
|
-
let
|
|
2503
|
+
let manifestKeys = Platform_BakedManifest$ReventlessCore.files(match$5).map(param => param[0]);
|
|
2504
|
+
let manifestKey = Stdlib_Option.getOr(manifestKeys[0], Platform_BakedManifest$ReventlessCore.defaultKey);
|
|
2494
2505
|
Pulumi.all([
|
|
2495
2506
|
componentDefinitions.roleId,
|
|
2496
2507
|
bucketName
|
|
2497
2508
|
]).apply(param => {
|
|
2509
|
+
let bucket = param[1];
|
|
2510
|
+
let arns = manifestKeys.map(key => `arn:aws:s3:::` + bucket + `/` + key);
|
|
2511
|
+
let tmp;
|
|
2512
|
+
if (arns.length !== 1) {
|
|
2513
|
+
tmp = arns;
|
|
2514
|
+
} else {
|
|
2515
|
+
let only = arns[0];
|
|
2516
|
+
tmp = only;
|
|
2517
|
+
}
|
|
2498
2518
|
new (Aws.iam.RolePolicy)("PlatformUIDefinitionsBake", {
|
|
2499
2519
|
policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, "PlatformUIDefinitionsBakePolicy", [{
|
|
2500
2520
|
Sid: "AllowWriteBakedManifest",
|
|
2501
2521
|
Effect: "Allow",
|
|
2502
2522
|
Action: ["s3:PutObject"],
|
|
2503
|
-
Resource:
|
|
2523
|
+
Resource: tmp
|
|
2504
2524
|
}])),
|
|
2505
2525
|
role: param[0]
|
|
2506
2526
|
});
|
|
@@ -141,10 +141,8 @@ let make = (
|
|
|
141
141
|
// as a record so the deploy input and the runtime input are the same shape —
|
|
142
142
|
// `views`/`commands` absent means "every public component", which a record
|
|
143
143
|
// with explicit nulls would not say.
|
|
144
|
-
let
|
|
145
|
-
|
|
146
|
-
| Some(bake) =>
|
|
147
|
-
bake.components
|
|
144
|
+
let encodeSelections = (components: array<ReventlessCore.Platform_BakedManifest.selection>) =>
|
|
145
|
+
components
|
|
148
146
|
->Array.map(sel => {
|
|
149
147
|
let entry = Dict.fromArray([("plugin", JSON.Encode.string(sel.plugin))])
|
|
150
148
|
let strings = (key, value) =>
|
|
@@ -157,6 +155,39 @@ let make = (
|
|
|
157
155
|
entry->JSON.Encode.object
|
|
158
156
|
})
|
|
159
157
|
->JSON.Encode.array
|
|
158
|
+
|
|
159
|
+
let bakeSelectionsJson = switch bakedManifest {
|
|
160
|
+
| None => ""
|
|
161
|
+
| Some(bake) =>
|
|
162
|
+
bake.components
|
|
163
|
+
->Array.map(ReventlessCore.Platform_BakedManifest.toSelection)
|
|
164
|
+
->encodeSelections
|
|
165
|
+
->JSON.stringify
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// The journeys, in a variable of their own rather than folded into the one
|
|
169
|
+
// above: a deployment that declares none then puts the same string in the same
|
|
170
|
+
// place it always did, and its function is not replaced for a field it does
|
|
171
|
+
// not use.
|
|
172
|
+
//
|
|
173
|
+
// Each carries the key the DECLARATION resolves to, taken from the function
|
|
174
|
+
// that also decides the IAM grant and the URL `config.json` publishes. A key
|
|
175
|
+
// the handler derived for itself would be a fourth derivation of one name, and
|
|
176
|
+
// the way it fails is a file written where nothing fetches it.
|
|
177
|
+
let bakeJourneysJson = switch bakedManifest->Option.mapOr([], config =>
|
|
178
|
+
ReventlessCore.Platform_BakedManifest.journeyFiles(~config)
|
|
179
|
+
) {
|
|
180
|
+
| [] => ""
|
|
181
|
+
| journeys =>
|
|
182
|
+
journeys
|
|
183
|
+
->Array.map(j =>
|
|
184
|
+
Dict.fromArray([
|
|
185
|
+
("group", JSON.Encode.string(j.group)),
|
|
186
|
+
("key", JSON.Encode.string(j.key)),
|
|
187
|
+
("components", j.selections->encodeSelections),
|
|
188
|
+
])->JSON.Encode.object
|
|
189
|
+
)
|
|
190
|
+
->JSON.Encode.array
|
|
160
191
|
->JSON.stringify
|
|
161
192
|
}
|
|
162
193
|
|
|
@@ -226,6 +257,10 @@ let make = (
|
|
|
226
257
|
// An include-list is names only, so it stays far from the 4096-byte
|
|
227
258
|
// total the admin entry already pushed out of this dict.
|
|
228
259
|
("BAKE_SELECTIONS", bakeSelectionsJson->Pulumi.Input.make),
|
|
260
|
+
// One curated surface per audience beside the default one. Empty for
|
|
261
|
+
// a deployment that declares none, which is every deployment that
|
|
262
|
+
// predates journeys.
|
|
263
|
+
("BAKE_JOURNEYS", bakeJourneysJson->Pulumi.Input.make),
|
|
229
264
|
("NODE_OPTIONS", Util_Bundle.esmLoaderNodeOptions->Pulumi.Input.make),
|
|
230
265
|
("ESM_FALLBACK_DIRS", Util_Bundle.esmFallbackDirs->Pulumi.Input.make),
|
|
231
266
|
Util_LambdaLogging.logLevelEntry(),
|
|
@@ -13,6 +13,7 @@ import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
|
|
|
13
13
|
import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Pulumi.res.mjs";
|
|
14
14
|
import * as Util_LambdaLogging$ReventlessAws from "../../util/Util_LambdaLogging.res.mjs";
|
|
15
15
|
import * as AppSync_Resolver_Native$ReventlessAws from "./AppSync_Resolver_Native.res.mjs";
|
|
16
|
+
import * as Platform_BakedManifest$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_BakedManifest.res.mjs";
|
|
16
17
|
import * as Platform_Admin_Structure$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_Admin_Structure.res.mjs";
|
|
17
18
|
import * as Platform_ComponentDefinitionsApi$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_ComponentDefinitionsApi.res.mjs";
|
|
18
19
|
|
|
@@ -77,19 +78,35 @@ function make(api, pluginReadModelTableName, offloadBucketName, schemaReady, bak
|
|
|
77
78
|
role: lambdaRole.id
|
|
78
79
|
}, opts$1);
|
|
79
80
|
});
|
|
80
|
-
let
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
81
|
+
let encodeSelections = components => components.map(sel => {
|
|
82
|
+
let entry = Object.fromEntries([[
|
|
83
|
+
"plugin",
|
|
84
|
+
sel.plugin
|
|
85
|
+
]]);
|
|
86
|
+
let strings = (key, value) => Stdlib_Option.forEach(value, names => {
|
|
87
|
+
entry[key] = names.map(prim => prim);
|
|
88
|
+
});
|
|
89
|
+
strings("views", sel.views);
|
|
90
|
+
strings("commands", sel.commands);
|
|
91
|
+
strings("derived", sel.derived);
|
|
92
|
+
return entry;
|
|
93
|
+
});
|
|
94
|
+
let bakeSelectionsJson = bakedManifest !== undefined ? JSON.stringify(encodeSelections(bakedManifest.components.map(Platform_BakedManifest$ReventlessCore.toSelection))) : "";
|
|
95
|
+
let journeys = Stdlib_Option.mapOr(bakedManifest, [], Platform_BakedManifest$ReventlessCore.journeyFiles);
|
|
96
|
+
let bakeJourneysJson = journeys.length !== 0 ? JSON.stringify(journeys.map(j => Object.fromEntries([
|
|
97
|
+
[
|
|
98
|
+
"group",
|
|
99
|
+
j.group
|
|
100
|
+
],
|
|
101
|
+
[
|
|
102
|
+
"key",
|
|
103
|
+
j.key
|
|
104
|
+
],
|
|
105
|
+
[
|
|
106
|
+
"components",
|
|
107
|
+
encodeSelections(j.selections)
|
|
108
|
+
]
|
|
109
|
+
]))) : "";
|
|
93
110
|
let adminEntryJson = JSON.stringify(Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry(Platform_Admin_Structure$ReventlessCore.pluginId, undefined, Platform_Admin_Structure$ReventlessCore.structure));
|
|
94
111
|
let packageDirs = Object.fromEntries([[
|
|
95
112
|
"@reventlessdev/reventless-aws",
|
|
@@ -128,6 +145,10 @@ function make(api, pluginReadModelTableName, offloadBucketName, schemaReady, bak
|
|
|
128
145
|
"BAKE_SELECTIONS",
|
|
129
146
|
bakeSelectionsJson
|
|
130
147
|
],
|
|
148
|
+
[
|
|
149
|
+
"BAKE_JOURNEYS",
|
|
150
|
+
bakeJourneysJson
|
|
151
|
+
],
|
|
131
152
|
[
|
|
132
153
|
"NODE_OPTIONS",
|
|
133
154
|
Util_Bundle$ReventlessAws.esmLoaderNodeOptions
|
|
@@ -90,6 +90,24 @@ let mapMembers = (obj: dict<JSON.t>, key: string, f: dict<JSON.t> => unit): unit
|
|
|
90
90
|
)
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
// Read-path shim for the `statusField` → `lifecycleField` rename. A structure
|
|
94
|
+
// persisted before the rename carries only the old key, and this handler serves
|
|
95
|
+
// raw JSON, so the resolver would find no `lifecycleField` and answer null until
|
|
96
|
+
// the plugin re-registers. That is not a crash — the SDL declares a nullable
|
|
97
|
+
// `String`, not a `[T!]!` that would null-propagate to the root — but it is a
|
|
98
|
+
// silent degradation: command-menu filtering, board columns, group sections,
|
|
99
|
+
// progress trackers and state diagrams go quiet while lists keep rendering.
|
|
100
|
+
//
|
|
101
|
+
// Delete a release later, once no structure predating the rename can be reached.
|
|
102
|
+
// It closes a window; it does not support two names.
|
|
103
|
+
let fillLifecycleField = (component: dict<JSON.t>): unit =>
|
|
104
|
+
switch (component->Dict.get("lifecycleField"), component->Dict.get("statusField")) {
|
|
105
|
+
| (None | Some(JSON.Null), Some(legacy)) => component->Dict.set("lifecycleField", legacy)
|
|
106
|
+
| _ => ()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let readSideCollections = ["readModels", "stateViewSlices"]
|
|
110
|
+
|
|
93
111
|
let healStructure = (structure: dict<JSON.t>): dict<JSON.t> => {
|
|
94
112
|
let out = Dict.fromArray(structure->Dict.toArray)
|
|
95
113
|
// The collections themselves are `[T!]!` too, so an absent one is the same
|
|
@@ -103,6 +121,9 @@ let healStructure = (structure: dict<JSON.t>): dict<JSON.t> => {
|
|
|
103
121
|
)
|
|
104
122
|
})
|
|
105
123
|
)
|
|
124
|
+
readSideCollections->Array.forEach(collection =>
|
|
125
|
+
out->mapMembers(collection, fillLifecycleField)
|
|
126
|
+
)
|
|
106
127
|
out
|
|
107
128
|
}
|
|
108
129
|
|
|
@@ -288,6 +309,18 @@ let structureOf = (item: dict<JSON.t>, ~name as _: string): option<(
|
|
|
288
309
|
| _ => None
|
|
289
310
|
}
|
|
290
311
|
|
|
312
|
+
// One curated surface per audience, beside the default one. Only what the write
|
|
313
|
+
// needs travels: the file's key, resolved by the deploy so that the grant, the
|
|
314
|
+
// URL in `config.json` and the object written here are one string, and the
|
|
315
|
+
// include-list that decides its contents. The group the journey serves is
|
|
316
|
+
// carried too, purely so the invocation's report names the audience a file was
|
|
317
|
+
// written for.
|
|
318
|
+
type journey = {
|
|
319
|
+
group: string,
|
|
320
|
+
key: string,
|
|
321
|
+
selections: array<ReventlessCore.Platform_BakedManifest.selection>,
|
|
322
|
+
}
|
|
323
|
+
|
|
291
324
|
let bakeSelection = (json: JSON.t): option<ReventlessCore.Platform_BakedManifest.selection> => {
|
|
292
325
|
let strings = (o, key) =>
|
|
293
326
|
o
|
|
@@ -309,6 +342,9 @@ let bakeSelection = (json: JSON.t): option<ReventlessCore.Platform_BakedManifest
|
|
|
309
342
|
)
|
|
310
343
|
}
|
|
311
344
|
|
|
345
|
+
let decodeSelections = (json: JSON.t): array<ReventlessCore.Platform_BakedManifest.selection> =>
|
|
346
|
+
json->JSON.Decode.array->Option.getOr([])->Array.filterMap(bakeSelection)
|
|
347
|
+
|
|
312
348
|
// The declaration travels as an env var because it is deploy input — stated once
|
|
313
349
|
// in the platform program, beside the bucket it is written to. The target travels
|
|
314
350
|
// in the invocation payload instead: the bucket only exists inside the host-UI
|
|
@@ -319,14 +355,44 @@ let bakeSelections = (): array<ReventlessCore.Platform_BakedManifest.selection>
|
|
|
319
355
|
switch NodeProcess.env->Dict.get("BAKE_SELECTIONS") {
|
|
320
356
|
| None | Some("") => []
|
|
321
357
|
| Some(raw) =>
|
|
322
|
-
switch raw->JSON.parseOrThrow
|
|
323
|
-
|
|
|
324
|
-
| None => []
|
|
358
|
+
switch raw->JSON.parseOrThrow {
|
|
359
|
+
| json => json->decodeSelections
|
|
325
360
|
| exception _ =>
|
|
326
361
|
JsError.throwWithMessage("baked manifest: BAKE_SELECTIONS is not a JSON array")
|
|
327
362
|
}
|
|
328
363
|
}
|
|
329
364
|
|
|
365
|
+
let bakeJourney = (json: JSON.t): option<journey> =>
|
|
366
|
+
json
|
|
367
|
+
->JSON.Decode.object
|
|
368
|
+
->Option.flatMap(o =>
|
|
369
|
+
switch (
|
|
370
|
+
o->Dict.get("group")->Option.flatMap(JSON.Decode.string),
|
|
371
|
+
o->Dict.get("key")->Option.flatMap(JSON.Decode.string),
|
|
372
|
+
) {
|
|
373
|
+
| (Some(group), Some(key)) =>
|
|
374
|
+
Some({
|
|
375
|
+
group,
|
|
376
|
+
key,
|
|
377
|
+
selections: o->Dict.get("components")->Option.mapOr([], decodeSelections),
|
|
378
|
+
})
|
|
379
|
+
| _ => None
|
|
380
|
+
}
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
// Absent for every deployment that declares one audience, which is what the
|
|
384
|
+
// declaration meant before journeys existed and what it still means.
|
|
385
|
+
let bakeJourneys = (): array<journey> =>
|
|
386
|
+
switch NodeProcess.env->Dict.get("BAKE_JOURNEYS") {
|
|
387
|
+
| None | Some("") => []
|
|
388
|
+
| Some(raw) =>
|
|
389
|
+
switch raw->JSON.parseOrThrow->JSON.Decode.array {
|
|
390
|
+
| Some(entries) => entries->Array.filterMap(bakeJourney)
|
|
391
|
+
| None => []
|
|
392
|
+
| exception _ => JsError.throwWithMessage("baked manifest: BAKE_JOURNEYS is not a JSON array")
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
330
396
|
type bakeTarget = {bucket: string, key: string}
|
|
331
397
|
|
|
332
398
|
let bakeTargetOf = (event: JSON.t): option<bakeTarget> =>
|
|
@@ -417,27 +483,49 @@ let runBake = async (
|
|
|
417
483
|
"so there is nothing to write and a shell pointed at the file would find none.",
|
|
418
484
|
)
|
|
419
485
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
486
|
+
// The default journey first and always — the caller supplies its key, because
|
|
487
|
+
// the caller is what knows where the shell it configured fetches from — then
|
|
488
|
+
// one file per declared journey, under the key the deploy resolved.
|
|
489
|
+
let files = Array.concat(
|
|
490
|
+
[(None, target.key, selections)],
|
|
491
|
+
bakeJourneys()->Array.map(j => (Some(j.group), j.key, j.selections)),
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
// Every file is curated before any is written, for the reason the in-memory
|
|
495
|
+
// platform curates before it writes: a declaration naming a component that
|
|
496
|
+
// does not exist fails the bake, and failing it halfway would leave the bucket
|
|
497
|
+
// serving one audience's file beside a stale copy of another's.
|
|
498
|
+
let curated = files->Array.map(((group, key, selections)) => (
|
|
499
|
+
group,
|
|
500
|
+
key,
|
|
501
|
+
selections,
|
|
502
|
+
switch ReventlessCore.Platform_BakedManifest.curate(~structures, ~selections) {
|
|
503
|
+
| Error(e) => JsError.throwWithMessage(ReventlessCore.Platform_BakedManifest.describe(e))
|
|
504
|
+
| Ok(manifest) => JSON.stringify(manifest, ~space=2)
|
|
505
|
+
},
|
|
506
|
+
))
|
|
507
|
+
|
|
508
|
+
await Promise.all(
|
|
509
|
+
curated->Array.map(async ((group, key, selections, body)) => {
|
|
510
|
+
let _ = await AwsSdk.S3.PutObjectCommand.make({
|
|
511
|
+
bucket: target.bucket,
|
|
512
|
+
key,
|
|
513
|
+
body: AwsSdk.S3.PutObjectCommand.bodyFromString(body),
|
|
514
|
+
contentType: "application/json",
|
|
515
|
+
})->AwsSdk.S3.PutObjectCommand.send
|
|
516
|
+
let report = Dict.fromArray([
|
|
433
517
|
("baked", JSON.Encode.bool(true)),
|
|
434
518
|
("bucket", JSON.Encode.string(target.bucket)),
|
|
435
|
-
("key", JSON.Encode.string(
|
|
519
|
+
("key", JSON.Encode.string(key)),
|
|
436
520
|
("plugins", JSON.Encode.int(selections->Array.length)),
|
|
437
521
|
("bytes", JSON.Encode.int(body->String.length)),
|
|
438
|
-
])
|
|
439
|
-
|
|
440
|
-
|
|
522
|
+
])
|
|
523
|
+
// Only a journey's entry carries the audience it was written for, so the
|
|
524
|
+
// default one reports exactly what it reported before journeys existed.
|
|
525
|
+
group->Option.forEach(g => report->Dict.set("group", JSON.Encode.string(g)))
|
|
526
|
+
report->JSON.Encode.object
|
|
527
|
+
}),
|
|
528
|
+
)
|
|
441
529
|
}
|
|
442
530
|
|
|
443
531
|
let handler = async (event: JSON.t): array<JSON.t> => {
|
|
@@ -148,6 +148,23 @@ function mapMembers(obj, key, f) {
|
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
function fillLifecycleField(component) {
|
|
152
|
+
let match = component["lifecycleField"];
|
|
153
|
+
let match$1 = component["statusField"];
|
|
154
|
+
if (match !== undefined && match !== null) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (match$1 !== undefined) {
|
|
158
|
+
component["lifecycleField"] = match$1;
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let readSideCollections = [
|
|
164
|
+
"readModels",
|
|
165
|
+
"stateViewSlices"
|
|
166
|
+
];
|
|
167
|
+
|
|
151
168
|
function healStructure(structure) {
|
|
152
169
|
let out = Object.fromEntries(Object.entries(structure));
|
|
153
170
|
fillLists(out, healByCollection.map(param => param[0]));
|
|
@@ -161,6 +178,7 @@ function healStructure(structure) {
|
|
|
161
178
|
});
|
|
162
179
|
});
|
|
163
180
|
});
|
|
181
|
+
readSideCollections.forEach(collection => mapMembers(out, collection, fillLifecycleField));
|
|
164
182
|
return out;
|
|
165
183
|
}
|
|
166
184
|
|
|
@@ -268,6 +286,10 @@ function bakeSelection(json) {
|
|
|
268
286
|
})));
|
|
269
287
|
}
|
|
270
288
|
|
|
289
|
+
function decodeSelections(json) {
|
|
290
|
+
return Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_JSON.Decode.array(json), []), bakeSelection);
|
|
291
|
+
}
|
|
292
|
+
|
|
271
293
|
function bakeSelections() {
|
|
272
294
|
let raw = process.env["BAKE_SELECTIONS"];
|
|
273
295
|
if (raw === undefined) {
|
|
@@ -276,14 +298,45 @@ function bakeSelections() {
|
|
|
276
298
|
if (raw === "") {
|
|
277
299
|
return [];
|
|
278
300
|
}
|
|
301
|
+
let json;
|
|
302
|
+
try {
|
|
303
|
+
json = JSON.parse(raw);
|
|
304
|
+
} catch (exn) {
|
|
305
|
+
return Stdlib_JsError.throwWithMessage("baked manifest: BAKE_SELECTIONS is not a JSON array");
|
|
306
|
+
}
|
|
307
|
+
return decodeSelections(json);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function bakeJourney(json) {
|
|
311
|
+
return Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(json), o => {
|
|
312
|
+
let match = Stdlib_Option.flatMap(o["group"], Stdlib_JSON.Decode.string);
|
|
313
|
+
let match$1 = Stdlib_Option.flatMap(o["key"], Stdlib_JSON.Decode.string);
|
|
314
|
+
if (match !== undefined && match$1 !== undefined) {
|
|
315
|
+
return {
|
|
316
|
+
group: match,
|
|
317
|
+
key: match$1,
|
|
318
|
+
selections: Stdlib_Option.mapOr(o["components"], [], decodeSelections)
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function bakeJourneys() {
|
|
325
|
+
let raw = process.env["BAKE_JOURNEYS"];
|
|
326
|
+
if (raw === undefined) {
|
|
327
|
+
return [];
|
|
328
|
+
}
|
|
329
|
+
if (raw === "") {
|
|
330
|
+
return [];
|
|
331
|
+
}
|
|
279
332
|
let entries;
|
|
280
333
|
try {
|
|
281
334
|
entries = Stdlib_JSON.Decode.array(JSON.parse(raw));
|
|
282
335
|
} catch (exn) {
|
|
283
|
-
return Stdlib_JsError.throwWithMessage("baked manifest:
|
|
336
|
+
return Stdlib_JsError.throwWithMessage("baked manifest: BAKE_JOURNEYS is not a JSON array");
|
|
284
337
|
}
|
|
285
338
|
if (entries !== undefined) {
|
|
286
|
-
return Stdlib_Array.filterMap(entries,
|
|
339
|
+
return Stdlib_Array.filterMap(entries, bakeJourney);
|
|
287
340
|
} else {
|
|
288
341
|
return [];
|
|
289
342
|
}
|
|
@@ -336,18 +389,37 @@ async function runBake(target, structures) {
|
|
|
336
389
|
if (selections.length === 0) {
|
|
337
390
|
Stdlib_JsError.throwWithMessage("baked manifest: BAKE_SELECTIONS is empty — this platform declares no bake, so there is nothing to write and a shell pointed at the file would find none.");
|
|
338
391
|
}
|
|
339
|
-
let
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
392
|
+
let files = [[
|
|
393
|
+
undefined,
|
|
394
|
+
target.key,
|
|
395
|
+
selections
|
|
396
|
+
]].concat(bakeJourneys().map(j => [
|
|
397
|
+
j.group,
|
|
398
|
+
j.key,
|
|
399
|
+
j.selections
|
|
400
|
+
]));
|
|
401
|
+
let curated = files.map(param => {
|
|
402
|
+
let selections = param[2];
|
|
403
|
+
let e = Platform_BakedManifest$ReventlessCore.curate(structures, selections);
|
|
404
|
+
let tmp;
|
|
405
|
+
tmp = e.TAG === "Ok" ? JSON.stringify(e._0, undefined, 2) : Stdlib_JsError.throwWithMessage(Platform_BakedManifest$ReventlessCore.describe(e._0));
|
|
406
|
+
return [
|
|
407
|
+
param[0],
|
|
408
|
+
param[1],
|
|
409
|
+
selections,
|
|
410
|
+
tmp
|
|
411
|
+
];
|
|
412
|
+
});
|
|
413
|
+
return await Promise.all(curated.map(async param => {
|
|
414
|
+
let body = param[3];
|
|
415
|
+
let key = param[1];
|
|
416
|
+
await S3$AwsSdk.PutObjectCommand.send(new ClientS3.PutObjectCommand({
|
|
417
|
+
Bucket: target.bucket,
|
|
418
|
+
Key: key,
|
|
419
|
+
Body: body,
|
|
420
|
+
ContentType: "application/json"
|
|
421
|
+
}));
|
|
422
|
+
let report = Object.fromEntries([
|
|
351
423
|
[
|
|
352
424
|
"baked",
|
|
353
425
|
true
|
|
@@ -358,17 +430,22 @@ async function runBake(target, structures) {
|
|
|
358
430
|
],
|
|
359
431
|
[
|
|
360
432
|
"key",
|
|
361
|
-
|
|
433
|
+
key
|
|
362
434
|
],
|
|
363
435
|
[
|
|
364
436
|
"plugins",
|
|
365
|
-
|
|
437
|
+
param[2].length
|
|
366
438
|
],
|
|
367
439
|
[
|
|
368
440
|
"bytes",
|
|
369
441
|
body.length
|
|
370
442
|
]
|
|
371
|
-
])
|
|
443
|
+
]);
|
|
444
|
+
Stdlib_Option.forEach(param[0], g => {
|
|
445
|
+
report["group"] = g;
|
|
446
|
+
});
|
|
447
|
+
return report;
|
|
448
|
+
}));
|
|
372
449
|
}
|
|
373
450
|
|
|
374
451
|
async function handler(event) {
|
|
@@ -424,6 +501,8 @@ export {
|
|
|
424
501
|
healByCollection,
|
|
425
502
|
fillLists,
|
|
426
503
|
mapMembers,
|
|
504
|
+
fillLifecycleField,
|
|
505
|
+
readSideCollections,
|
|
427
506
|
healStructure,
|
|
428
507
|
isPublicQueryable,
|
|
429
508
|
filterStructure,
|
|
@@ -433,7 +512,10 @@ export {
|
|
|
433
512
|
isComplete,
|
|
434
513
|
structureOf,
|
|
435
514
|
bakeSelection,
|
|
515
|
+
decodeSelections,
|
|
436
516
|
bakeSelections,
|
|
517
|
+
bakeJourney,
|
|
518
|
+
bakeJourneys,
|
|
437
519
|
bakeTargetOf,
|
|
438
520
|
bakeExpectations,
|
|
439
521
|
structureRefKey,
|