@reventlessdev/reventless-aws 3.0.0-alpha.290 → 3.0.0-alpha.292
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 +15 -0
- package/package.json +11 -11
- package/src/Platform.res +115 -9
- package/src/Platform.res.mjs +101 -12
- package/src/adapter/Api/Platform_AdminScan_Ops.res +7 -3
- package/src/adapter/Api/Platform_AdminScan_Ops.res.mjs +2 -1
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +61 -1
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +29 -2
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res +248 -6
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res.mjs +181 -9
- package/src/plugin/runtime/PluginRuntime_Builder.res +16 -0
- package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +6 -0
- package/src/util/Util_ShellConfig.res +18 -3
- package/src/util/Util_ShellConfig.res.mjs +5 -1
- package/tests/Platform_ComponentDefinitions_Lambda_OpsTest.res +174 -0
- package/tests/Platform_ComponentDefinitions_Lambda_OpsTest.res.mjs +130 -0
- package/tests/Util_ShellConfigTest.res +41 -0
- package/tests/Util_ShellConfigTest.res.mjs +51 -5
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
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.292 (2026-08-13)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **aws:** re-detect once the Lambda that answers the handshake is up ([597e174](https://github.com/ReventlessDev/reventless-core/commit/597e174059e25461d66db35118c933d2ffce7e1f))
|
|
11
|
+
* **aws:** retain offloaded plugin structures so refs cannot dangle ([120a1a7](https://github.com/ReventlessDev/reventless-core/commit/120a1a7228087dd79f8bcfdd2fddc7b6dbdaf296))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# 3.0.0-alpha.291 (2026-08-13)
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* **aws:** bake the component manifest after every plugin stack is up ([2fff09a](https://github.com/ReventlessDev/reventless-core/commit/2fff09aa32b77cc8e33a65414c58c12026ca009e))
|
|
19
|
+
|
|
20
|
+
|
|
6
21
|
# 3.0.0-alpha.290 (2026-08-13)
|
|
7
22
|
|
|
8
23
|
### 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.292",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"dependencies": {
|
|
@@ -12,18 +12,18 @@
|
|
|
12
12
|
"@aws-sdk/s3-request-presigner": "3.970.0",
|
|
13
13
|
"sury": "11.0.0-alpha.4",
|
|
14
14
|
"uuid": "^13.0.0",
|
|
15
|
-
"@reventlessdev/rescript-
|
|
15
|
+
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.8",
|
|
16
16
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
17
|
-
"@reventlessdev/rescript-
|
|
18
|
-
"@reventlessdev/rescript-
|
|
19
|
-
"@reventlessdev/rescript-
|
|
20
|
-
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.
|
|
17
|
+
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.73",
|
|
18
|
+
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
19
|
+
"@reventlessdev/rescript-node": "2.0.0-alpha.5",
|
|
20
|
+
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
|
|
21
21
|
"@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
|
|
22
|
-
"@reventlessdev/reventless-core": "3.0.0-alpha.
|
|
23
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.
|
|
24
|
-
"@reventlessdev/reventless-interop": "3.0.0-alpha.
|
|
25
|
-
"@reventlessdev/reventless-
|
|
26
|
-
"@reventlessdev/reventless-
|
|
22
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.231",
|
|
23
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.139",
|
|
24
|
+
"@reventlessdev/reventless-interop": "3.0.0-alpha.31",
|
|
25
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.95",
|
|
26
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.112"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"rescript": "12.3.0",
|
package/src/Platform.res
CHANGED
|
@@ -1454,17 +1454,24 @@ module MakeWithConfig = (
|
|
|
1454
1454
|
// Also register the Plugin RM table with the AllAggregates Lambda runtime
|
|
1455
1455
|
// so its in-Lambda plugin status gate can read plugin status at command
|
|
1456
1456
|
// dispatch time (Part 2.3 of the resolver plan).
|
|
1457
|
-
|
|
1457
|
+
// Handed back so the host-UI half of this deploy can grant the bake its write
|
|
1458
|
+
// into the bucket it creates, and so the function name can be exported for
|
|
1459
|
+
// the post-deploy step. `None` when there is no Plugin RM to scan — there is
|
|
1460
|
+
// then no function, and nothing downstream to grant.
|
|
1461
|
+
let componentDefinitions = switch pluginReadModelTableName {
|
|
1458
1462
|
| Some(tableName) =>
|
|
1459
1463
|
AggregateRuntime_Builder_Single.setPluginReadModelTable(~name=tableName)
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1464
|
+
Some(
|
|
1465
|
+
Platform_ComponentDefinitions_Lambda.make(
|
|
1466
|
+
~api=platformApi,
|
|
1467
|
+
~pluginReadModelTableName=tableName,
|
|
1468
|
+
~offloadBucketName,
|
|
1469
|
+
~schemaReady=admin.adminSchemaPushed,
|
|
1470
|
+
~bakedManifest=?hostUiBundle->Option.flatMap(cfg => cfg.bakedManifest),
|
|
1471
|
+
~opts={},
|
|
1472
|
+
),
|
|
1466
1473
|
)
|
|
1467
|
-
| None =>
|
|
1474
|
+
| None => None
|
|
1468
1475
|
}
|
|
1469
1476
|
|
|
1470
1477
|
// Mount the Platform_UIFragments Lambda resolver — scans the UiFragments
|
|
@@ -2163,6 +2170,7 @@ module MakeWithConfig = (
|
|
|
2163
2170
|
Util_ShellConfig.fields(
|
|
2164
2171
|
~computed=withEvents,
|
|
2165
2172
|
~viewModes=?cfg.viewModes,
|
|
2173
|
+
~bakedManifest=?cfg.bakedManifest,
|
|
2166
2174
|
~shellConfig=?cfg.shellConfig,
|
|
2167
2175
|
)
|
|
2168
2176
|
->JSON.Encode.object
|
|
@@ -2202,6 +2210,50 @@ module MakeWithConfig = (
|
|
|
2202
2210
|
)
|
|
2203
2211
|
}
|
|
2204
2212
|
|
|
2213
|
+
// The bake's write, granted here because here is where both facts are
|
|
2214
|
+
// known: which bucket serves the shell, and that this deployment declared a
|
|
2215
|
+
// manifest at all. Scoped to the one key the deploy told the shell to fetch
|
|
2216
|
+
// — a function that may rewrite the bundle would be a much larger grant
|
|
2217
|
+
// than "keep one generated file current".
|
|
2218
|
+
//
|
|
2219
|
+
// Exported alongside so the post-deploy step has the function and the
|
|
2220
|
+
// target without reading either out of the deploy program.
|
|
2221
|
+
switch (componentDefinitions, cfg.bakedManifest) {
|
|
2222
|
+
| (Some(fn), Some(bake)) =>
|
|
2223
|
+
let manifestKey = bake.key->Option.getOr(
|
|
2224
|
+
ReventlessCore.Platform_BakedManifest.defaultKey,
|
|
2225
|
+
)
|
|
2226
|
+
let _ =
|
|
2227
|
+
(fn.roleId, bucketName)
|
|
2228
|
+
->Pulumi.Output.all2
|
|
2229
|
+
->Pulumi.Output.apply(((roleId, bucket)) => {
|
|
2230
|
+
open PulumiAws.PolicyDocument
|
|
2231
|
+
let _ = PulumiAws.IAM.RolePolicy.make(
|
|
2232
|
+
~name="PlatformUIDefinitionsBake",
|
|
2233
|
+
~args={
|
|
2234
|
+
PulumiAws.IAM.RolePolicy.policy: PulumiAws.PolicyDocument.make(
|
|
2235
|
+
~id="PlatformUIDefinitionsBakePolicy",
|
|
2236
|
+
~statements=[
|
|
2237
|
+
{
|
|
2238
|
+
sid: "AllowWriteBakedManifest",
|
|
2239
|
+
effect: Allow,
|
|
2240
|
+
actions: Actions(["s3:PutObject"]),
|
|
2241
|
+
resources: Resource(`arn:aws:s3:::${bucket}/${manifestKey}`),
|
|
2242
|
+
},
|
|
2243
|
+
],
|
|
2244
|
+
)
|
|
2245
|
+
->PulumiAws.PolicyDocument.toJsonString
|
|
2246
|
+
->Pulumi.Input.make,
|
|
2247
|
+
role: roleId->Pulumi.Input.make,
|
|
2248
|
+
},
|
|
2249
|
+
)
|
|
2250
|
+
})
|
|
2251
|
+
Pulumi.Pulumi.export("bakedManifestFunction", fn.functionName)
|
|
2252
|
+
Pulumi.Pulumi.export("bakedManifestBucket", bucketName)
|
|
2253
|
+
Pulumi.Pulumi.export("bakedManifestKey", Pulumi.Output.make(manifestKey))
|
|
2254
|
+
| _ => ()
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2205
2257
|
Pulumi.Pulumi.export("hostShellUrl", distributionUrl)
|
|
2206
2258
|
}
|
|
2207
2259
|
|
|
@@ -2244,11 +2296,34 @@ module MakeWithConfig = (
|
|
|
2244
2296
|
->Pulumi.Output.apply(o => o->Option.getOr("OFFLOAD_BUCKET_PENDING_PLATFORM_DEPLOY"))
|
|
2245
2297
|
| None => Pulumi.Output.make("OFFLOAD_BUCKET_PENDING_PLATFORM_DEPLOY")
|
|
2246
2298
|
}
|
|
2299
|
+
// The key this deploy wrote for the plugin's structure, exported below. The
|
|
2300
|
+
// Plugin read model is what the manifest bake reads and it is updated
|
|
2301
|
+
// asynchronously, so the bake needs to know which structure "current" means;
|
|
2302
|
+
// an equality check against the key the stack just wrote says that without
|
|
2303
|
+
// timestamps, and passes immediately for a plugin whose structure did not
|
|
2304
|
+
// change.
|
|
2305
|
+
let structureOffloadKey = ref(None)
|
|
2306
|
+
|
|
2247
2307
|
ReventlessCore.Plugin_Helpers.registerOffload((~store, ~bytes) => {
|
|
2248
2308
|
let hash = NodeCrypto.sha256Hex(bytes)
|
|
2249
2309
|
let key = "sha256/" ++ hash
|
|
2310
|
+
if store == "pluginStructures" {
|
|
2311
|
+
structureOffloadKey := Some(key)
|
|
2312
|
+
}
|
|
2250
2313
|
// Content-addressed: the name and key are the hash, so re-deploying an
|
|
2251
2314
|
// unchanged field writes the same object (idempotent, deduplicating).
|
|
2315
|
+
//
|
|
2316
|
+
// `retainOnDelete`: the only readers of these objects are the persisted
|
|
2317
|
+
// refs on the Plugin read model, and that row is updated asynchronously —
|
|
2318
|
+
// the deploy publishes a re-detect, the Plugin aggregate handles it, the
|
|
2319
|
+
// projection lands, minutes later. A changed field means a new hash, so
|
|
2320
|
+
// without this Pulumi DELETES the previous object as part of the same
|
|
2321
|
+
// update, and every reference still on the read model dangles until
|
|
2322
|
+
// registration catches up. S3 answers a GET for a deleted key with
|
|
2323
|
+
// AccessDenied (it masks the 404 unless the caller may ListBucket), so the
|
|
2324
|
+
// window shows up as an authorization failure in the manifest bake and in
|
|
2325
|
+
// the AutoUI definitions query. Content-addressed objects are immutable
|
|
2326
|
+
// and cost kilobytes; keeping them closes the window outright.
|
|
2252
2327
|
let _ = PulumiAws.S3.BucketObject.make(
|
|
2253
2328
|
~name="offload-" ++ hash,
|
|
2254
2329
|
~args={
|
|
@@ -2257,6 +2332,7 @@ module MakeWithConfig = (
|
|
|
2257
2332
|
content: Pulumi.Input.make(bytes),
|
|
2258
2333
|
contentType: Pulumi.Input.make("application/json"),
|
|
2259
2334
|
},
|
|
2335
|
+
~opts={retainOnDelete: true},
|
|
2260
2336
|
)
|
|
2261
2337
|
{Reventless.Offload.store, key, hash, bytes: bytes->String.length}
|
|
2262
2338
|
})
|
|
@@ -2309,6 +2385,23 @@ module MakeWithConfig = (
|
|
|
2309
2385
|
let pluginOutputs = pluginComponent->ReventlessCore.Component.outputs
|
|
2310
2386
|
ReventlessCore.Plugin_Helpers.exportPluginOutputs(pluginOutputs)
|
|
2311
2387
|
|
|
2388
|
+
// What the manifest bake compares the read model against. Keyed by the bare
|
|
2389
|
+
// plugin name because that is how the Plugin read model keys its rows — the
|
|
2390
|
+
// stack's own id carries the version, which the bake has no opinion about.
|
|
2391
|
+
switch structureOffloadKey.contents {
|
|
2392
|
+
| Some(key) =>
|
|
2393
|
+
Pulumi.Pulumi.export(
|
|
2394
|
+
"pluginStructureRef",
|
|
2395
|
+
pluginOutputs.id->Pulumi.Output.apply(id =>
|
|
2396
|
+
Dict.fromArray([
|
|
2397
|
+
("plugin", JSON.Encode.string(ReventlessCore.Plugin.name(id))),
|
|
2398
|
+
("key", JSON.Encode.string(key)),
|
|
2399
|
+
])->JSON.Encode.object
|
|
2400
|
+
),
|
|
2401
|
+
)
|
|
2402
|
+
| None => ()
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2312
2405
|
// ── Merged-API association (merged-api plan, Phase 4) ───────────────────
|
|
2313
2406
|
// Associate this plugin's source API with the platform's merged API —
|
|
2314
2407
|
// this replaces the SigV4 RegisterApiFragment handshake + reactive push +
|
|
@@ -2487,7 +2580,20 @@ module MakeWithConfig = (
|
|
|
2487
2580
|
// synchronously always saw the initial empty config, so the publish was skipped
|
|
2488
2581
|
// on every deploy. `pluginOutputs.heartbeat` derives from that same callback's
|
|
2489
2582
|
// output, so applying to it is the earliest point the config is populated.
|
|
2490
|
-
|
|
2583
|
+
//
|
|
2584
|
+
// The EventCollector Lambda is the other half of the gate because it is what
|
|
2585
|
+
// ANSWERS the handshake: the re-detect only asks the platform to re-run it,
|
|
2586
|
+
// and the definition that comes back is the one compiled into that Lambda
|
|
2587
|
+
// (PluginConnectExtension_Mapping's UnknownPluginDetected branch). Published
|
|
2588
|
+
// before it carries the new code, the plugin answers with the PREVIOUS
|
|
2589
|
+
// deploy's definition, `Connect` sees a definition it already holds and emits
|
|
2590
|
+
// nothing, and the row keeps the old structure — silently, and with no second
|
|
2591
|
+
// re-detect coming to correct it. Waiting on the heartbeat alone gated on a
|
|
2592
|
+
// Lambda that has no part in the answer.
|
|
2593
|
+
let redetectReady =
|
|
2594
|
+
(pluginOutputs.heartbeat, PluginRuntime_Builder.eventCollectorReadyRef.contents)
|
|
2595
|
+
->Pulumi.Output.all2
|
|
2596
|
+
let _ = redetectReady->Pulumi.Output.apply(_ => {
|
|
2491
2597
|
let hbConfig = PluginRuntime_Builder.heartbeatConfigRef.contents
|
|
2492
2598
|
switch (Pulumi.Pulumi.isDryRun(), hbConfig.epQueueUrl) {
|
|
2493
2599
|
| (true, _) => () // `pulumi preview` — no deploy-time side effects
|
package/src/Platform.res.mjs
CHANGED
|
@@ -16,12 +16,14 @@ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
|
16
16
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
17
17
|
import * as Plugin$ReventlessAws from "./components/Plugin.res.mjs";
|
|
18
18
|
import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
|
|
19
|
+
import * as Plugin$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin.res.mjs";
|
|
19
20
|
import * as AWS_Tags$ReventlessAws from "./adapter/AWS_Tags.res.mjs";
|
|
20
21
|
import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
|
|
21
22
|
import * as Scheduler$ReventlessAws from "./components/Scheduler.res.mjs";
|
|
22
23
|
import * as Aggregate$ReventlessCore from "@reventlessdev/reventless-core/src/components/Aggregate/Aggregate.res.mjs";
|
|
23
24
|
import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/components/Component.res.mjs";
|
|
24
25
|
import * as DcbBackend$ReventlessAws from "./adapter/DcbEventLog/DcbBackend.res.mjs";
|
|
26
|
+
import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
|
|
25
27
|
import * as ReadModel$ReventlessCore from "@reventlessdev/reventless-core/src/components/ReadModel/ReadModel.res.mjs";
|
|
26
28
|
import * as PluginSpec$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/lifecycle/PluginSpec.res.mjs";
|
|
27
29
|
import * as Util_Bundle$ReventlessAws from "./util/Util_Bundle.res.mjs";
|
|
@@ -70,6 +72,7 @@ import * as AutomationSlice_Builder$ReventlessAws from "./components/AutomationS
|
|
|
70
72
|
import * as EventTopicPublisher_SNS$ReventlessAws from "./adapter/EventTopic/EventTopicPublisher_SNS.res.mjs";
|
|
71
73
|
import * as ExtensionPointMapping$ReventlessInfra from "@reventlessdev/reventless-infra/src/types/ExtensionPointMapping.res.mjs";
|
|
72
74
|
import * as PgQueryResolver_Builder$ReventlessAws from "./adapter/QueryDb/PgQueryResolver_Builder.res.mjs";
|
|
75
|
+
import * as Platform_BakedManifest$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_BakedManifest.res.mjs";
|
|
73
76
|
import * as UiFragments_Projection$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments_Projection.res.mjs";
|
|
74
77
|
import * as Aggregate_Builder_Single$ReventlessAws from "./components/Aggregate_Builder_Single.res.mjs";
|
|
75
78
|
import * as ReadModel_Builder_Single$ReventlessAws from "./components/ReadModel_Builder_Single.res.mjs";
|
|
@@ -859,10 +862,7 @@ function MakeWithConfig(Config) {
|
|
|
859
862
|
restrictPublicBuckets: true
|
|
860
863
|
});
|
|
861
864
|
Pulumi$Pulumi.$$export("offloadBucket", offloadBucketName);
|
|
862
|
-
|
|
863
|
-
AggregateRuntime_Builder_Single$ReventlessAws.setPluginReadModelTable(pluginReadModelTableName);
|
|
864
|
-
Platform_ComponentDefinitions_Lambda$ReventlessAws.make(platformApi, pluginReadModelTableName, offloadBucketName, admin.adminSchemaPushed, {});
|
|
865
|
-
}
|
|
865
|
+
let componentDefinitions = pluginReadModelTableName !== undefined ? (AggregateRuntime_Builder_Single$ReventlessAws.setPluginReadModelTable(pluginReadModelTableName), Platform_ComponentDefinitions_Lambda$ReventlessAws.make(platformApi, pluginReadModelTableName, offloadBucketName, admin.adminSchemaPushed, Stdlib_Option.flatMap(hostUiBundle, cfg => cfg.bakedManifest), {})) : undefined;
|
|
866
866
|
let rm = admin.stateViewSlicesOutputs["UiFragments"];
|
|
867
867
|
if (rm !== undefined) {
|
|
868
868
|
let r$1 = rm.queryDb.resources[0];
|
|
@@ -1189,7 +1189,7 @@ function MakeWithConfig(Config) {
|
|
|
1189
1189
|
AppSync_EventsApi$ReventlessAws.clientNamespaceName
|
|
1190
1190
|
]
|
|
1191
1191
|
]) : computed;
|
|
1192
|
-
return JSON.stringify(Util_ShellConfig$ReventlessAws.fields(withEvents, hostUiBundle.viewModes, hostUiBundle.shellConfig));
|
|
1192
|
+
return JSON.stringify(Util_ShellConfig$ReventlessAws.fields(withEvents, hostUiBundle.viewModes, hostUiBundle.bakedManifest, hostUiBundle.shellConfig));
|
|
1193
1193
|
});
|
|
1194
1194
|
new (Aws.s3.BucketObject)("host-ui-config-json", {
|
|
1195
1195
|
bucket: bucketName,
|
|
@@ -1207,6 +1207,27 @@ function MakeWithConfig(Config) {
|
|
|
1207
1207
|
contentType: "application/json"
|
|
1208
1208
|
});
|
|
1209
1209
|
}
|
|
1210
|
+
let match$5 = hostUiBundle.bakedManifest;
|
|
1211
|
+
if (componentDefinitions !== undefined && match$5 !== undefined) {
|
|
1212
|
+
let manifestKey = Stdlib_Option.getOr(match$5.key, Platform_BakedManifest$ReventlessCore.defaultKey);
|
|
1213
|
+
Pulumi.all([
|
|
1214
|
+
componentDefinitions.roleId,
|
|
1215
|
+
bucketName
|
|
1216
|
+
]).apply(param => {
|
|
1217
|
+
new (Aws.iam.RolePolicy)("PlatformUIDefinitionsBake", {
|
|
1218
|
+
policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, "PlatformUIDefinitionsBakePolicy", [{
|
|
1219
|
+
Sid: "AllowWriteBakedManifest",
|
|
1220
|
+
Effect: "Allow",
|
|
1221
|
+
Action: ["s3:PutObject"],
|
|
1222
|
+
Resource: `arn:aws:s3:::` + param[1] + `/` + manifestKey
|
|
1223
|
+
}])),
|
|
1224
|
+
role: param[0]
|
|
1225
|
+
});
|
|
1226
|
+
});
|
|
1227
|
+
Pulumi$Pulumi.$$export("bakedManifestFunction", componentDefinitions.functionName);
|
|
1228
|
+
Pulumi$Pulumi.$$export("bakedManifestBucket", bucketName);
|
|
1229
|
+
Pulumi$Pulumi.$$export("bakedManifestKey", Pulumi.output(manifestKey));
|
|
1230
|
+
}
|
|
1210
1231
|
Pulumi$Pulumi.$$export("hostShellUrl", match$4.distributionUrl);
|
|
1211
1232
|
}
|
|
1212
1233
|
return Pulumi$Pulumi.getOutputs();
|
|
@@ -1228,14 +1249,22 @@ function MakeWithConfig(Config) {
|
|
|
1228
1249
|
hooksApiRef.contents = Platform_Casts$ReventlessAws.wrapHookedValue(targetApi);
|
|
1229
1250
|
hooksApiRoleRef.contents = Platform_Casts$ReventlessAws.wrapHookedValue(targetApiRole);
|
|
1230
1251
|
let offloadBucketName = platformStackRef !== undefined ? Primitive_option.valFromOption(platformStackRef).getOutput("offloadBucket").apply(o => Stdlib_Option.getOr(o, "OFFLOAD_BUCKET_PENDING_PLATFORM_DEPLOY")) : Pulumi.output("OFFLOAD_BUCKET_PENDING_PLATFORM_DEPLOY");
|
|
1252
|
+
let structureOffloadKey = {
|
|
1253
|
+
contents: undefined
|
|
1254
|
+
};
|
|
1231
1255
|
Plugin_Helpers$ReventlessCore.registerOffload((store, bytes) => {
|
|
1232
1256
|
let hash = NodeCrypto.sha256Hex(bytes);
|
|
1233
1257
|
let key = "sha256/" + hash;
|
|
1258
|
+
if (store === "pluginStructures") {
|
|
1259
|
+
structureOffloadKey.contents = key;
|
|
1260
|
+
}
|
|
1234
1261
|
new (Aws.s3.BucketObject)("offload-" + hash, {
|
|
1235
1262
|
bucket: offloadBucketName,
|
|
1236
1263
|
key: key,
|
|
1237
1264
|
content: bytes,
|
|
1238
1265
|
contentType: "application/json"
|
|
1266
|
+
}, {
|
|
1267
|
+
retainOnDelete: true
|
|
1239
1268
|
});
|
|
1240
1269
|
return {
|
|
1241
1270
|
store: store,
|
|
@@ -1253,6 +1282,19 @@ function MakeWithConfig(Config) {
|
|
|
1253
1282
|
Pulumi$Pulumi.$$export("_interopMeta", Plugin_Helpers$ReventlessCore.getInteropMeta());
|
|
1254
1283
|
let pluginOutputs = Component$ReventlessCore.outputs(pluginComponent);
|
|
1255
1284
|
Plugin_Helpers$ReventlessCore.exportPluginOutputs(pluginOutputs);
|
|
1285
|
+
let key = structureOffloadKey.contents;
|
|
1286
|
+
if (key !== undefined) {
|
|
1287
|
+
Pulumi$Pulumi.$$export("pluginStructureRef", pluginOutputs.id.apply(id => Object.fromEntries([
|
|
1288
|
+
[
|
|
1289
|
+
"plugin",
|
|
1290
|
+
Plugin$ReventlessCore.name(id)
|
|
1291
|
+
],
|
|
1292
|
+
[
|
|
1293
|
+
"key",
|
|
1294
|
+
key
|
|
1295
|
+
]
|
|
1296
|
+
])));
|
|
1297
|
+
}
|
|
1256
1298
|
if (platformStackRef !== undefined) {
|
|
1257
1299
|
let stackRef = Primitive_option.valFromOption(platformStackRef);
|
|
1258
1300
|
let defaultOutput = stackRef.getOutput("default");
|
|
@@ -1315,7 +1357,11 @@ function MakeWithConfig(Config) {
|
|
|
1315
1357
|
if (sel !== undefined) {
|
|
1316
1358
|
PgQueryResolver_Builder$ReventlessAws.provision(domainApi, sel, {});
|
|
1317
1359
|
}
|
|
1318
|
-
|
|
1360
|
+
let redetectReady = Pulumi.all([
|
|
1361
|
+
pluginOutputs.heartbeat,
|
|
1362
|
+
PluginRuntime_Builder$ReventlessAws.eventCollectorReadyRef.contents
|
|
1363
|
+
]);
|
|
1364
|
+
redetectReady.apply(param => {
|
|
1319
1365
|
let hbConfig = PluginRuntime_Builder$ReventlessAws.heartbeatConfigRef.contents;
|
|
1320
1366
|
let match = IndexJs.isDryRun();
|
|
1321
1367
|
let match$1 = hbConfig.epQueueUrl;
|
|
@@ -2098,10 +2144,7 @@ function Make($star) {
|
|
|
2098
2144
|
restrictPublicBuckets: true
|
|
2099
2145
|
});
|
|
2100
2146
|
Pulumi$Pulumi.$$export("offloadBucket", offloadBucketName);
|
|
2101
|
-
|
|
2102
|
-
AggregateRuntime_Builder_Single$ReventlessAws.setPluginReadModelTable(pluginReadModelTableName);
|
|
2103
|
-
Platform_ComponentDefinitions_Lambda$ReventlessAws.make(platformApi, pluginReadModelTableName, offloadBucketName, admin.adminSchemaPushed, {});
|
|
2104
|
-
}
|
|
2147
|
+
let componentDefinitions = pluginReadModelTableName !== undefined ? (AggregateRuntime_Builder_Single$ReventlessAws.setPluginReadModelTable(pluginReadModelTableName), Platform_ComponentDefinitions_Lambda$ReventlessAws.make(platformApi, pluginReadModelTableName, offloadBucketName, admin.adminSchemaPushed, Stdlib_Option.flatMap(hostUiBundle, cfg => cfg.bakedManifest), {})) : undefined;
|
|
2105
2148
|
let rm = admin.stateViewSlicesOutputs["UiFragments"];
|
|
2106
2149
|
if (rm !== undefined) {
|
|
2107
2150
|
let r$1 = rm.queryDb.resources[0];
|
|
@@ -2422,7 +2465,7 @@ function Make($star) {
|
|
|
2422
2465
|
AppSync_EventsApi$ReventlessAws.clientNamespaceName
|
|
2423
2466
|
]
|
|
2424
2467
|
]) : computed;
|
|
2425
|
-
return JSON.stringify(Util_ShellConfig$ReventlessAws.fields(withEvents, hostUiBundle.viewModes, hostUiBundle.shellConfig));
|
|
2468
|
+
return JSON.stringify(Util_ShellConfig$ReventlessAws.fields(withEvents, hostUiBundle.viewModes, hostUiBundle.bakedManifest, hostUiBundle.shellConfig));
|
|
2426
2469
|
});
|
|
2427
2470
|
new (Aws.s3.BucketObject)("host-ui-config-json", {
|
|
2428
2471
|
bucket: bucketName,
|
|
@@ -2440,6 +2483,27 @@ function Make($star) {
|
|
|
2440
2483
|
contentType: "application/json"
|
|
2441
2484
|
});
|
|
2442
2485
|
}
|
|
2486
|
+
let match$5 = hostUiBundle.bakedManifest;
|
|
2487
|
+
if (componentDefinitions !== undefined && match$5 !== undefined) {
|
|
2488
|
+
let manifestKey = Stdlib_Option.getOr(match$5.key, Platform_BakedManifest$ReventlessCore.defaultKey);
|
|
2489
|
+
Pulumi.all([
|
|
2490
|
+
componentDefinitions.roleId,
|
|
2491
|
+
bucketName
|
|
2492
|
+
]).apply(param => {
|
|
2493
|
+
new (Aws.iam.RolePolicy)("PlatformUIDefinitionsBake", {
|
|
2494
|
+
policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, "PlatformUIDefinitionsBakePolicy", [{
|
|
2495
|
+
Sid: "AllowWriteBakedManifest",
|
|
2496
|
+
Effect: "Allow",
|
|
2497
|
+
Action: ["s3:PutObject"],
|
|
2498
|
+
Resource: `arn:aws:s3:::` + param[1] + `/` + manifestKey
|
|
2499
|
+
}])),
|
|
2500
|
+
role: param[0]
|
|
2501
|
+
});
|
|
2502
|
+
});
|
|
2503
|
+
Pulumi$Pulumi.$$export("bakedManifestFunction", componentDefinitions.functionName);
|
|
2504
|
+
Pulumi$Pulumi.$$export("bakedManifestBucket", bucketName);
|
|
2505
|
+
Pulumi$Pulumi.$$export("bakedManifestKey", Pulumi.output(manifestKey));
|
|
2506
|
+
}
|
|
2443
2507
|
Pulumi$Pulumi.$$export("hostShellUrl", match$4.distributionUrl);
|
|
2444
2508
|
}
|
|
2445
2509
|
return Pulumi$Pulumi.getOutputs();
|
|
@@ -2461,14 +2525,22 @@ function Make($star) {
|
|
|
2461
2525
|
hooksApiRef.contents = Platform_Casts$ReventlessAws.wrapHookedValue(targetApi);
|
|
2462
2526
|
hooksApiRoleRef.contents = Platform_Casts$ReventlessAws.wrapHookedValue(targetApiRole);
|
|
2463
2527
|
let offloadBucketName = platformStackRef !== undefined ? Primitive_option.valFromOption(platformStackRef).getOutput("offloadBucket").apply(o => Stdlib_Option.getOr(o, "OFFLOAD_BUCKET_PENDING_PLATFORM_DEPLOY")) : Pulumi.output("OFFLOAD_BUCKET_PENDING_PLATFORM_DEPLOY");
|
|
2528
|
+
let structureOffloadKey = {
|
|
2529
|
+
contents: undefined
|
|
2530
|
+
};
|
|
2464
2531
|
Plugin_Helpers$ReventlessCore.registerOffload((store, bytes) => {
|
|
2465
2532
|
let hash = NodeCrypto.sha256Hex(bytes);
|
|
2466
2533
|
let key = "sha256/" + hash;
|
|
2534
|
+
if (store === "pluginStructures") {
|
|
2535
|
+
structureOffloadKey.contents = key;
|
|
2536
|
+
}
|
|
2467
2537
|
new (Aws.s3.BucketObject)("offload-" + hash, {
|
|
2468
2538
|
bucket: offloadBucketName,
|
|
2469
2539
|
key: key,
|
|
2470
2540
|
content: bytes,
|
|
2471
2541
|
contentType: "application/json"
|
|
2542
|
+
}, {
|
|
2543
|
+
retainOnDelete: true
|
|
2472
2544
|
});
|
|
2473
2545
|
return {
|
|
2474
2546
|
store: store,
|
|
@@ -2486,6 +2558,19 @@ function Make($star) {
|
|
|
2486
2558
|
Pulumi$Pulumi.$$export("_interopMeta", Plugin_Helpers$ReventlessCore.getInteropMeta());
|
|
2487
2559
|
let pluginOutputs = Component$ReventlessCore.outputs(pluginComponent);
|
|
2488
2560
|
Plugin_Helpers$ReventlessCore.exportPluginOutputs(pluginOutputs);
|
|
2561
|
+
let key = structureOffloadKey.contents;
|
|
2562
|
+
if (key !== undefined) {
|
|
2563
|
+
Pulumi$Pulumi.$$export("pluginStructureRef", pluginOutputs.id.apply(id => Object.fromEntries([
|
|
2564
|
+
[
|
|
2565
|
+
"plugin",
|
|
2566
|
+
Plugin$ReventlessCore.name(id)
|
|
2567
|
+
],
|
|
2568
|
+
[
|
|
2569
|
+
"key",
|
|
2570
|
+
key
|
|
2571
|
+
]
|
|
2572
|
+
])));
|
|
2573
|
+
}
|
|
2489
2574
|
if (platformStackRef !== undefined) {
|
|
2490
2575
|
let stackRef = Primitive_option.valFromOption(platformStackRef);
|
|
2491
2576
|
let defaultOutput = stackRef.getOutput("default");
|
|
@@ -2548,7 +2633,11 @@ function Make($star) {
|
|
|
2548
2633
|
if (sel !== undefined) {
|
|
2549
2634
|
PgQueryResolver_Builder$ReventlessAws.provision(domainApi, sel, {});
|
|
2550
2635
|
}
|
|
2551
|
-
|
|
2636
|
+
let redetectReady = Pulumi.all([
|
|
2637
|
+
pluginOutputs.heartbeat,
|
|
2638
|
+
PluginRuntime_Builder$ReventlessAws.eventCollectorReadyRef.contents
|
|
2639
|
+
]);
|
|
2640
|
+
redetectReady.apply(param => {
|
|
2552
2641
|
let hbConfig = PluginRuntime_Builder$ReventlessAws.heartbeatConfigRef.contents;
|
|
2553
2642
|
let match = IndexJs.isDryRun();
|
|
2554
2643
|
let match$1 = hbConfig.epQueueUrl;
|
|
@@ -108,13 +108,17 @@ let compareVersions = (a: string, b: string): int => {
|
|
|
108
108
|
// `nameVersionOf` reads the row's `name@version` string; `toEntry` builds the
|
|
109
109
|
// output entry for a surviving row (returning None drops a malformed row). The
|
|
110
110
|
// name/version split mirrors ReventlessCore.Plugin.name / compareVersions.
|
|
111
|
+
// Generic in what an entry is: the GraphQL fields collapse to encoded JSON, the
|
|
112
|
+
// bake collapses to the decoded structure it curates. One version-collapse rule
|
|
113
|
+
// either way — a second copy would be a second chance to disagree about which
|
|
114
|
+
// version of a plugin is the deployed one.
|
|
111
115
|
let latestByName = (
|
|
112
116
|
items: array<dict<JSON.t>>,
|
|
113
117
|
~nameVersionOf: dict<JSON.t> => option<string>,
|
|
114
|
-
~toEntry: (dict<JSON.t>, ~name: string) => option<
|
|
115
|
-
): array<
|
|
118
|
+
~toEntry: (dict<JSON.t>, ~name: string) => option<'entry>,
|
|
119
|
+
): array<'entry> => {
|
|
116
120
|
// bare plugin name -> (version, entry); highest version wins.
|
|
117
|
-
let latest: dict<(string,
|
|
121
|
+
let latest: dict<(string, 'entry)> = Dict.make()
|
|
118
122
|
items->Array.forEach(item =>
|
|
119
123
|
switch item->nameVersionOf {
|
|
120
124
|
| None => ()
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
|
+
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
4
5
|
import * as LibDynamodb from "@aws-sdk/lib-dynamodb";
|
|
5
6
|
import * as ClientDynamodb from "@aws-sdk/client-dynamodb";
|
|
6
7
|
|
|
@@ -83,7 +84,7 @@ function latestByName(items, nameVersionOf, toEntry) {
|
|
|
83
84
|
}
|
|
84
85
|
latest[name] = [
|
|
85
86
|
version,
|
|
86
|
-
entry
|
|
87
|
+
Primitive_option.valFromOption(entry)
|
|
87
88
|
];
|
|
88
89
|
});
|
|
89
90
|
return Object.values(latest).map(param => param[1]);
|
|
@@ -37,6 +37,22 @@ export function response(ctx) {
|
|
|
37
37
|
}
|
|
38
38
|
`->Pulumi.Input.make
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
What the platform needs back to finish wiring the bake.
|
|
42
|
+
|
|
43
|
+
The manifest is written into the host-UI bucket, and that bucket is created in a
|
|
44
|
+
later half of the same deploy — so the grant cannot be attached here. Handing the
|
|
45
|
+
role back is what lets the caller attach it at the point both facts are known,
|
|
46
|
+
rather than widening this function's write scope to "some bucket, later".
|
|
47
|
+
|
|
48
|
+
`functionName` is exported so the post-deploy step has something to invoke
|
|
49
|
+
without guessing at a generated name.
|
|
50
|
+
*/
|
|
51
|
+
type t = {
|
|
52
|
+
roleId: Pulumi.Output.t<string>,
|
|
53
|
+
functionName: Pulumi.Output.t<string>,
|
|
54
|
+
}
|
|
55
|
+
|
|
40
56
|
let make = (
|
|
41
57
|
~api: Pulumi.Output.t<AppSync.GraphQLApi.t>,
|
|
42
58
|
~pluginReadModelTableName: Pulumi.Output.t<string>,
|
|
@@ -47,8 +63,13 @@ let make = (
|
|
|
47
63
|
// first ships it. `Platform_ComponentDefinitions` predates the gate and is left
|
|
48
64
|
// ungated so this change adds no dependency edge to an already-deployed resource.
|
|
49
65
|
~schemaReady: Pulumi.Output.t<unit>,
|
|
66
|
+
// The deployment's include-list, when it declares one. Carried into the
|
|
67
|
+
// function's environment so the bake curates what the platform program
|
|
68
|
+
// declared and not what its caller asked for — the invocation supplies only
|
|
69
|
+
// where to write.
|
|
70
|
+
~bakedManifest: option<ReventlessInfra.Platform.bakedManifest>=?,
|
|
50
71
|
~opts: Pulumi.ComponentResource.options,
|
|
51
|
-
) => {
|
|
72
|
+
): t => {
|
|
52
73
|
let opts = opts->ReventlessCore.Util.Pulumi.ComponentResourceOptions.toCustomResourceOptions
|
|
53
74
|
let name = "PlatformUIDefinitions"
|
|
54
75
|
|
|
@@ -96,6 +117,16 @@ let make = (
|
|
|
96
117
|
actions: Actions(["s3:GetObject"]),
|
|
97
118
|
resources: Resource("arn:aws:s3:::" ++ offloadBucket ++ "/*"),
|
|
98
119
|
},
|
|
120
|
+
// Read-only on the bucket itself, purely so a key that is not there
|
|
121
|
+
// says so: S3 answers GET for a missing key with AccessDenied unless
|
|
122
|
+
// the caller may list the bucket, which reports a ref the handler
|
|
123
|
+
// cannot resolve as an IAM problem it does not have.
|
|
124
|
+
{
|
|
125
|
+
sid: "AllowOffloadList",
|
|
126
|
+
effect: Allow,
|
|
127
|
+
actions: Actions(["s3:ListBucket"]),
|
|
128
|
+
resources: Resource("arn:aws:s3:::" ++ offloadBucket),
|
|
129
|
+
},
|
|
99
130
|
],
|
|
100
131
|
)
|
|
101
132
|
->PolicyDocument.toJsonString
|
|
@@ -106,6 +137,28 @@ let make = (
|
|
|
106
137
|
)
|
|
107
138
|
})
|
|
108
139
|
|
|
140
|
+
// The include-list as the handler reads it. Encoded here rather than passed
|
|
141
|
+
// as a record so the deploy input and the runtime input are the same shape —
|
|
142
|
+
// `views`/`commands` absent means "every public component", which a record
|
|
143
|
+
// with explicit nulls would not say.
|
|
144
|
+
let bakeSelectionsJson = switch bakedManifest {
|
|
145
|
+
| None => ""
|
|
146
|
+
| Some(bake) =>
|
|
147
|
+
bake.components
|
|
148
|
+
->Array.map(sel => {
|
|
149
|
+
let entry = Dict.fromArray([("plugin", JSON.Encode.string(sel.plugin))])
|
|
150
|
+
let strings = (key, value) =>
|
|
151
|
+
value->Option.forEach(names =>
|
|
152
|
+
entry->Dict.set(key, names->Array.map(JSON.Encode.string)->JSON.Encode.array)
|
|
153
|
+
)
|
|
154
|
+
strings("views", sel.views)
|
|
155
|
+
strings("commands", sel.commands)
|
|
156
|
+
entry->JSON.Encode.object
|
|
157
|
+
})
|
|
158
|
+
->JSON.Encode.array
|
|
159
|
+
->JSON.stringify
|
|
160
|
+
}
|
|
161
|
+
|
|
109
162
|
let adminEntryJson =
|
|
110
163
|
ReventlessCore.Platform_ComponentDefinitionsApi.encodePluginStructureEntry(
|
|
111
164
|
~pluginId=ReventlessCore.Platform_Admin_Structure.pluginId,
|
|
@@ -167,6 +220,11 @@ let make = (
|
|
|
167
220
|
("Environment", Pulumi.Pulumi.getStackName()->Pulumi.Input.make),
|
|
168
221
|
("PLUGIN_RM_TABLE", pluginReadModelTableName->Pulumi.Output.asInput),
|
|
169
222
|
("OFFLOAD_BUCKET", offloadBucketName->Pulumi.Output.asInput),
|
|
223
|
+
// Absent declaration ⇒ empty string ⇒ a bake invocation fails saying
|
|
224
|
+
// this platform declares none, rather than writing an empty shop.
|
|
225
|
+
// An include-list is names only, so it stays far from the 4096-byte
|
|
226
|
+
// total the admin entry already pushed out of this dict.
|
|
227
|
+
("BAKE_SELECTIONS", bakeSelectionsJson->Pulumi.Input.make),
|
|
170
228
|
("NODE_OPTIONS", Util_Bundle.esmLoaderNodeOptions->Pulumi.Input.make),
|
|
171
229
|
("ESM_FALLBACK_DIRS", Util_Bundle.esmFallbackDirs->Pulumi.Input.make),
|
|
172
230
|
Util_LambdaLogging.logLevelEntry(),
|
|
@@ -256,4 +314,6 @@ let make = (
|
|
|
256
314
|
~opts,
|
|
257
315
|
)
|
|
258
316
|
)
|
|
317
|
+
|
|
318
|
+
{roleId: lambdaRole.id, functionName: lambda.name}
|
|
259
319
|
}
|