@reventlessdev/reventless-aws 3.0.0-alpha.252 → 3.0.0-alpha.254
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 +28 -0
- package/package.json +8 -8
- package/src/Platform.res +159 -21
- package/src/Platform.res.mjs +120 -8
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res +5 -0
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res.mjs +3 -0
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res +5 -0
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res.mjs +3 -0
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +66 -8
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +16 -3
- package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res +5 -0
- package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +3 -0
- package/src/adapter/Task/TaskBucket_S3.res +11 -0
- package/src/adapter/Task/TaskBucket_S3.res.mjs +3 -0
- package/src/adapter/Upload/Upload_Claim_S3.res +361 -0
- package/src/adapter/Upload/Upload_Claim_S3.res.mjs +203 -0
- package/src/adapter/Upload/Upload_Claim_S3_Ops.res +279 -0
- package/src/adapter/Upload/Upload_Claim_S3_Ops.res.mjs +261 -0
- package/src/adapter/Upload/Upload_PendingTag.res +41 -0
- package/src/adapter/Upload/Upload_PendingTag.res.mjs +15 -0
- package/src/adapter/Upload/Upload_Presign_S3.res +12 -2
- package/src/adapter/Upload/Upload_Presign_S3.res.mjs +1 -0
- package/src/adapter/Upload/Upload_Presign_S3_Ops.res +17 -1
- package/src/adapter/Upload/Upload_Presign_S3_Ops.res.mjs +3 -1
- package/src/capability/Capability_ObjectStore_S3.res +42 -1
- package/src/capability/Capability_ObjectStore_S3.res.mjs +19 -1
- package/src/components/Api/AppSync_Adapter.res +36 -0
- package/src/components/Api/AppSync_Adapter.res.mjs +14 -0
- package/src/util/Util_LogRetention.res +86 -0
- package/src/util/Util_LogRetention.res.mjs +43 -0
- package/src/util/Util_StoreLayout.res +62 -9
- package/src/util/Util_StoreLayout.res.mjs +20 -2
- package/tests/Upload_ClaimTest.res +132 -0
- package/tests/Upload_ClaimTest.res.mjs +101 -0
- package/tests/Util_LogRetentionTest.res +122 -0
- package/tests/Util_LogRetentionTest.res.mjs +95 -0
- package/tests/Util_StoreLayoutTest.res +79 -3
- package/tests/Util_StoreLayoutTest.res.mjs +47 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,34 @@
|
|
|
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.254 (2026-08-02)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **aws,core,doc:** environment-tiered CloudWatch log retention and levels ([8032023](https://github.com/ReventlessDev/reventless-core/commit/803202305a7d352484b8df5e58df03c92ca58b5f))
|
|
11
|
+
* **aws,core,spec,seed-aws:** expire uploads nobody committed a reference to ([f63e84c](https://github.com/ReventlessDev/reventless-core/commit/f63e84c1a11cc350b799a6f69a2e7427cf1ea6e9))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# 3.0.0-alpha.253 (2026-08-01)
|
|
15
|
+
|
|
16
|
+
* feat(aws,core,spec)!: qualify store prefixes by plugin and refuse name collisions ([da39405](https://github.com/ReventlessDev/reventless-core/commit/da394059d9f8f981bf7adc79e2c1ce2b429e0267))
|
|
17
|
+
* feat(core,aws)!: name task buckets for their plugin and task ([05203cb](https://github.com/ReventlessDev/reventless-core/commit/05203cb3b0b019401a6f32cf78109f397b6799d5))
|
|
18
|
+
|
|
19
|
+
### BREAKING CHANGES
|
|
20
|
+
|
|
21
|
+
* objects minted under the old bare prefix are orphaned and their
|
|
22
|
+
refs unresolvable. The migration is `seed:reset` for the owning plugin, then
|
|
23
|
+
re-seed. Legacy-prefix grandfathering was considered and deliberately dropped —
|
|
24
|
+
it would have added a permanent prefix SET across the deploy argument, both store
|
|
25
|
+
configs, the presign IAM fan-out, the release scope check and the stack output,
|
|
26
|
+
to spare a disposable stack one wipe.
|
|
27
|
+
* a bucket name change is a replace. TaskBucket_S3 now sets
|
|
28
|
+
forceDestroy on disposable stacks (Util_StoreLayout.protectionFor), so pr-*
|
|
29
|
+
stacks recreate cleanly; protected stacks must have the task bucket drained by
|
|
30
|
+
hand before the first deploy after this, or the replace fails BucketNotEmpty.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
6
34
|
# 3.0.0-alpha.252 (2026-08-01)
|
|
7
35
|
|
|
8
36
|
### 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.254",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"dependencies": {
|
|
@@ -11,18 +11,18 @@
|
|
|
11
11
|
"@aws-sdk/s3-request-presigner": "3.970.0",
|
|
12
12
|
"sury": "11.0.0-alpha.4",
|
|
13
13
|
"uuid": "^13.0.0",
|
|
14
|
-
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.
|
|
15
|
-
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
14
|
+
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.1",
|
|
16
15
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
16
|
+
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
17
17
|
"@reventlessdev/rescript-node": "2.0.0-alpha.0",
|
|
18
|
-
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.
|
|
18
|
+
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.63",
|
|
19
19
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.18",
|
|
20
20
|
"@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
|
|
21
|
-
"@reventlessdev/reventless-
|
|
21
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.201",
|
|
22
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.118",
|
|
23
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.65",
|
|
22
24
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.30",
|
|
23
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.
|
|
24
|
-
"@reventlessdev/reventless-core": "3.0.0-alpha.199",
|
|
25
|
-
"@reventlessdev/reventless-postgres": "3.0.0-alpha.63"
|
|
25
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.93"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"rescript": "12.3.0",
|
package/src/Platform.res
CHANGED
|
@@ -157,6 +157,58 @@ module MakeWithConfig = (
|
|
|
157
157
|
->Pulumi.Config.get("stack")
|
|
158
158
|
->Option.map(stack => Pulumi.StackReference.make(stack))
|
|
159
159
|
|
|
160
|
+
// The declared object stores, as the upload claimer needs them: qualified
|
|
161
|
+
// name, physical bucket, served prefix.
|
|
162
|
+
//
|
|
163
|
+
// Stores are provisioned by the *platform* deploy; the claimer is wired into
|
|
164
|
+
// the *plugin* stack, because that is where the event log streams it reads
|
|
165
|
+
// exist. So the two halves meet here — across the stack reference in plugin
|
|
166
|
+
// mode, and through the platform's own in-process record when platform and
|
|
167
|
+
// plugin are one program.
|
|
168
|
+
//
|
|
169
|
+
// Read lazily (a thunk, not a value) for the monolithic case: `deployPlatform`
|
|
170
|
+
// fills `objectStoreEndpointsRef` as it provisions, and this is called during
|
|
171
|
+
// a later plugin build.
|
|
172
|
+
let claimStores = (): Pulumi.Output.t<array<Upload_Claim_S3.storeConfig>> =>
|
|
173
|
+
switch platformStackRef {
|
|
174
|
+
| Some(stackRef) =>
|
|
175
|
+
// The `objectStores` export: `{ "<plugin>.<store>": {bucketName, keyPrefix} }`.
|
|
176
|
+
// A platform that provisions none omits the export entirely, which reads
|
|
177
|
+
// as "no store to claim in" — the claimer then builds nothing, rather
|
|
178
|
+
// than building something that can reach nothing.
|
|
179
|
+
(stackRef->Pulumi.StackReference.getOutput("objectStores"): Pulumi.Output.t<option<JSON.t>>)
|
|
180
|
+
->Pulumi.Output.apply(objectStores =>
|
|
181
|
+
objectStores
|
|
182
|
+
->Option.flatMap(JSON.Decode.object)
|
|
183
|
+
->Option.getOr(Dict.make())
|
|
184
|
+
->Dict.toArray
|
|
185
|
+
->Array.filterMap(((qualified, v)) =>
|
|
186
|
+
v
|
|
187
|
+
->JSON.Decode.object
|
|
188
|
+
->Option.flatMap(o =>
|
|
189
|
+
switch (
|
|
190
|
+
o->Dict.get("bucketName")->Option.flatMap(JSON.Decode.string),
|
|
191
|
+
o->Dict.get("keyPrefix")->Option.flatMap(JSON.Decode.string),
|
|
192
|
+
) {
|
|
193
|
+
| (Some(bucketName), Some(servedPrefix)) =>
|
|
194
|
+
Some({Upload_Claim_S3.qualified: qualified, bucketName, servedPrefix})
|
|
195
|
+
| _ => None
|
|
196
|
+
}
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
| None =>
|
|
201
|
+
objectStoreEndpointsRef.contents
|
|
202
|
+
->Array.map(e =>
|
|
203
|
+
e.bucketName->Pulumi.Output.apply(bucketName => {
|
|
204
|
+
Upload_Claim_S3.qualified: e.store,
|
|
205
|
+
bucketName,
|
|
206
|
+
servedPrefix: e.keyPrefix,
|
|
207
|
+
})
|
|
208
|
+
)
|
|
209
|
+
->Pulumi.Output.all
|
|
210
|
+
}
|
|
211
|
+
|
|
160
212
|
// ── Merged-mode source SDL assembly (merged-api plan, Phase 3) ────────────
|
|
161
213
|
// Empty base fragment — no types, no mutations, no queries. Used by the
|
|
162
214
|
// plugin Api in split mode (so plugin schema has no core fields) and as the
|
|
@@ -172,7 +224,7 @@ module MakeWithConfig = (
|
|
|
172
224
|
// The upload service (route B) lives on the domain base, not the Admin-gated admin
|
|
173
225
|
// base: `Upload_Presign`/`Upload_Release` take the domain API's default
|
|
174
226
|
// `AllowAuthenticated` auth so any authenticated user can mint/release their own
|
|
175
|
-
// uploads. See [docs/plans/upload-release-path.md] § "Which API". (Split mode only —
|
|
227
|
+
// uploads. See [docs/plans/done/upload-release-path.md] § "Which API". (Split mode only —
|
|
176
228
|
// unified mode carries the admin base here instead; it declares no stores in practice,
|
|
177
229
|
// so the upload resolvers below are never created there.)
|
|
178
230
|
let domainBaseFragment = ReventlessCore.GraphQL_Stitcher.encode({
|
|
@@ -763,7 +815,7 @@ module MakeWithConfig = (
|
|
|
763
815
|
// Only active when the Events API resource exists (platform/monolithic mode).
|
|
764
816
|
subscriptionInfraHook: ?domainEventsApiOpt->Option.map(eventsApi =>
|
|
765
817
|
(params: ReventlessCore.Plugin_Helpers.subscriptionInfraParams) => {
|
|
766
|
-
let {allQueryDbs, allEventTopics, eventLogEntries, opts} = params
|
|
818
|
+
let {pluginName, allQueryDbs, allEventTopics, eventLogEntries, opts} = params
|
|
767
819
|
let customOpts =
|
|
768
820
|
opts->ReventlessCore.Util.Pulumi.ComponentResourceOptions.toCustomResourceOptions
|
|
769
821
|
// StateTopic Lambda per stream-enabled QueryDb — publishes row changes
|
|
@@ -799,19 +851,34 @@ module MakeWithConfig = (
|
|
|
799
851
|
let isSns =
|
|
800
852
|
EventTopicPublisher_SNS.snsRegistry->Set.has(entry.displayName) ||
|
|
801
853
|
EventTopicPublisher_SNS.snsRegistry->Set.has(entry.busKey)
|
|
802
|
-
let
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
854
|
+
let topicOutputs =
|
|
855
|
+
allEventTopics
|
|
856
|
+
->Dict.get(entry.displayName)
|
|
857
|
+
->Option.orElse(allEventTopics->Dict.get(entry.busKey))
|
|
858
|
+
isSns
|
|
859
|
+
? topicOutputs->Option.forEach(outputs =>
|
|
860
|
+
EventLogSubscription_AppSync.make(
|
|
861
|
+
~name=entry.displayName,
|
|
862
|
+
~topicName=entry.displayName,
|
|
863
|
+
~eventTopicOutputs=outputs,
|
|
864
|
+
~eventsApi,
|
|
865
|
+
~opts=customOpts,
|
|
866
|
+
)
|
|
867
|
+
)
|
|
868
|
+
: ()
|
|
869
|
+
// Register this event log with the upload claimer if — and only if —
|
|
870
|
+
// its events declare a `@storageRef` field. An event log that declares
|
|
871
|
+
// none is not registered, so a platform whose plugins declare no store
|
|
872
|
+
// provisions no claimer at all. `isSns` is the same synchronous answer
|
|
873
|
+
// the subscription above turns on, so the two cannot disagree about
|
|
874
|
+
// which channel an event log publishes through.
|
|
875
|
+
topicOutputs->Option.forEach(outputs =>
|
|
876
|
+
Upload_Claim_S3.make(
|
|
877
|
+
~plugin=pluginName,
|
|
878
|
+
~eventLogName=entry.displayName,
|
|
879
|
+
~eventSchema=entry.eventSchema,
|
|
812
880
|
~eventTopicOutputs=outputs,
|
|
813
|
-
~
|
|
814
|
-
~opts=customOpts,
|
|
881
|
+
~isStreamBacked=!isSns,
|
|
815
882
|
)
|
|
816
883
|
)
|
|
817
884
|
})
|
|
@@ -822,6 +889,9 @@ module MakeWithConfig = (
|
|
|
822
889
|
// empty because the hook hasn't run yet. Admin's hook fires synchronously,
|
|
823
890
|
// but the call site is unified here so admin and plugins share one path.
|
|
824
891
|
StateTopic_AppSync.finish(~eventsApi, ~opts={})
|
|
892
|
+
// Same timing constraint, same reason: the claimer's registry is filled
|
|
893
|
+
// by the loop just above and drained here.
|
|
894
|
+
Upload_Claim_S3.finish(~plugin=pluginName, ~stores=claimStores, ~opts=customOpts)
|
|
825
895
|
}
|
|
826
896
|
),
|
|
827
897
|
}
|
|
@@ -1063,12 +1133,15 @@ module MakeWithConfig = (
|
|
|
1063
1133
|
// omitted (byte-identical config.json).
|
|
1064
1134
|
geocoderPlaceIndex?: ReventlessInfra.Platform.geocoderIndex,
|
|
1065
1135
|
// Optional object store for direct-to-S3 uploads, as returned by
|
|
1066
|
-
// `Capability_ObjectStore_S3.make`. When set, the deploy
|
|
1067
|
-
//
|
|
1068
|
-
//
|
|
1069
|
-
//
|
|
1070
|
-
//
|
|
1071
|
-
//
|
|
1136
|
+
// `Capability_ObjectStore_S3.make`. When set, the deploy registers it with
|
|
1137
|
+
// the platform's one Upload service (`Upload_Presign_S3`) under the `uploads`
|
|
1138
|
+
// store key, and fronts the store read-only on the host-shell CloudFront
|
|
1139
|
+
// distribution under that prefix (a `{prefix}/*` ordered cache behavior +
|
|
1140
|
+
// origin + scoped BucketPolicy, private, OAC-read).
|
|
1141
|
+
//
|
|
1142
|
+
// No endpoint is written into config.json: under route B a caller mints
|
|
1143
|
+
// through the domain API's `Upload_Presign` mutation, naming the store, so
|
|
1144
|
+
// there is no per-store URL to publish or to resolve.
|
|
1072
1145
|
//
|
|
1073
1146
|
// One field replaces the former `enableUploads` / `uploadBucketName` /
|
|
1074
1147
|
// `servedBuckets` trio, which were three ways of saying one thing and left
|
|
@@ -1537,6 +1610,30 @@ module MakeWithConfig = (
|
|
|
1537
1610
|
: Array.concat(acc, [(plugin, store)])
|
|
1538
1611
|
)
|
|
1539
1612
|
|
|
1613
|
+
// Refuse two stores that cannot be told apart, BEFORE a single resource is
|
|
1614
|
+
// created. Left to the deploy, the same collision surfaces as a CloudFront
|
|
1615
|
+
// error about a duplicate path pattern — minutes in, naming a path pattern
|
|
1616
|
+
// rather than the two plugins — and only after the intermixed objects and
|
|
1617
|
+
// the over-broad presign grant have already been provisioned.
|
|
1618
|
+
//
|
|
1619
|
+
// Checked on the prefix, so it costs nothing to keep once prefixes are
|
|
1620
|
+
// qualified: a qualified prefix simply stops colliding, while the check
|
|
1621
|
+
// still guards any bare prefix that outlives the change.
|
|
1622
|
+
switch ReventlessCore.StorePrefixCollision.collisionsFor(
|
|
1623
|
+
~stores=declaredStores->Array.map(((plugin, store)) => {
|
|
1624
|
+
ReventlessCore.StorePrefixCollision.qualified: `${plugin}.${store}`,
|
|
1625
|
+
prefix: Util_StoreLayout.keyPrefixFor(~plugin, ~store),
|
|
1626
|
+
}),
|
|
1627
|
+
) {
|
|
1628
|
+
| [] => ()
|
|
1629
|
+
| collisions =>
|
|
1630
|
+
JsError.throwWithMessage(
|
|
1631
|
+
collisions
|
|
1632
|
+
->Array.map(ReventlessCore.StorePrefixCollision.collisionMessage)
|
|
1633
|
+
->Array.join("\n\n"),
|
|
1634
|
+
)
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1540
1637
|
if declaredStores->Array.length > 0 {
|
|
1541
1638
|
// The fail-open in `layoutFor` is only dangerous while it is silent: a
|
|
1542
1639
|
// production stack that is not on the prod list gets the shared layout and
|
|
@@ -1560,6 +1657,46 @@ module MakeWithConfig = (
|
|
|
1560
1657
|
// policy and one CDN origin however many stores live in it.
|
|
1561
1658
|
let storeBuckets: Dict.t<ReventlessInfra.Platform.objectStore> = Dict.make()
|
|
1562
1659
|
let storeBucketOrder: array<string> = []
|
|
1660
|
+
|
|
1661
|
+
// Which stores expire their never-claimed uploads, grouped by the bucket
|
|
1662
|
+
// they live in. Computed before any bucket is created, because a bucket
|
|
1663
|
+
// carries its lifecycle rules from birth and a shared-layout bucket is
|
|
1664
|
+
// created once for several stores — the first store through the loop below
|
|
1665
|
+
// would otherwise be the only one whose setting was ever read.
|
|
1666
|
+
//
|
|
1667
|
+
// Off unless a deployment names the store. See `Util_StoreLayout.pendingExpiryFor`
|
|
1668
|
+
// for why this is a deployment's setting and not a declaration's, and why
|
|
1669
|
+
// there is deliberately no default.
|
|
1670
|
+
let pendingExpiryConfig = Util_LocalConfig.get("pendingUploadExpiryDays")
|
|
1671
|
+
let pendingExpiryByBucket: Dict.t<array<Capability_ObjectStore_S3.pendingExpiry>> = Dict.make()
|
|
1672
|
+
declaredStores->Array.forEach(((plugin, store)) =>
|
|
1673
|
+
Util_StoreLayout.pendingExpiryFor(
|
|
1674
|
+
~config=pendingExpiryConfig,
|
|
1675
|
+
~store=`${plugin}.${store}`,
|
|
1676
|
+
)->Option.forEach(days => {
|
|
1677
|
+
let bucketName = Util_StoreLayout.bucketNameFor(
|
|
1678
|
+
~layout=storeLayout,
|
|
1679
|
+
~stack=stackName,
|
|
1680
|
+
~plugin,
|
|
1681
|
+
~store,
|
|
1682
|
+
)
|
|
1683
|
+
let prefix = Util_StoreLayout.keyPrefixFor(~plugin, ~store)
|
|
1684
|
+
pendingExpiryByBucket->Dict.set(
|
|
1685
|
+
bucketName,
|
|
1686
|
+
pendingExpiryByBucket
|
|
1687
|
+
->Dict.get(bucketName)
|
|
1688
|
+
->Option.getOr([])
|
|
1689
|
+
->Array.concat([{Capability_ObjectStore_S3.prefix, days}]),
|
|
1690
|
+
)
|
|
1691
|
+
log.info(
|
|
1692
|
+
~comp="Platform:deployPlatform",
|
|
1693
|
+
`object store ${plugin}.${store}: never-claimed uploads under ${prefix}/ expire after ` ++
|
|
1694
|
+
`${days->Int.toString} days. Claimed objects and objects minted before the claim ` ++
|
|
1695
|
+
`component existed carry no pending tag and are outside the rule.`,
|
|
1696
|
+
)
|
|
1697
|
+
})
|
|
1698
|
+
)
|
|
1699
|
+
|
|
1563
1700
|
let declaredStoreServices = declaredStores->Array.map(((plugin, store)) => {
|
|
1564
1701
|
let bucketName = Util_StoreLayout.bucketNameFor(
|
|
1565
1702
|
~layout=storeLayout,
|
|
@@ -1567,7 +1704,7 @@ module MakeWithConfig = (
|
|
|
1567
1704
|
~plugin,
|
|
1568
1705
|
~store,
|
|
1569
1706
|
)
|
|
1570
|
-
let keyPrefix = Util_StoreLayout.keyPrefixFor(~store)
|
|
1707
|
+
let keyPrefix = Util_StoreLayout.keyPrefixFor(~plugin, ~store)
|
|
1571
1708
|
let bucket = switch storeBuckets->Dict.get(bucketName) {
|
|
1572
1709
|
| Some(b) => b
|
|
1573
1710
|
| None =>
|
|
@@ -1584,6 +1721,7 @@ module MakeWithConfig = (
|
|
|
1584
1721
|
// disposable stack would leak exactly the buckets sharing exists to
|
|
1585
1722
|
// save. The two settings are opposite faces of one decision.
|
|
1586
1723
|
~forceDestroy=storeProtection == Unprotected,
|
|
1724
|
+
~expirePending=pendingExpiryByBucket->Dict.get(bucketName)->Option.getOr([]),
|
|
1587
1725
|
)
|
|
1588
1726
|
storeBuckets->Dict.set(bucketName, b)
|
|
1589
1727
|
storeBucketOrder->Array.push(bucketName)
|
package/src/Platform.res.mjs
CHANGED
|
@@ -35,6 +35,7 @@ import * as EventLogBackend$ReventlessAws from "./adapter/EventLog/EventLogBacke
|
|
|
35
35
|
import * as Platform_Admin$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_Admin.res.mjs";
|
|
36
36
|
import * as PluginBehavior$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/lifecycle/PluginBehavior.res.mjs";
|
|
37
37
|
import * as Plugin_Helpers$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin_Helpers.res.mjs";
|
|
38
|
+
import * as Upload_Claim_S3$ReventlessAws from "./adapter/Upload/Upload_Claim_S3.res.mjs";
|
|
38
39
|
import * as PgProjectionFeed$ReventlessAws from "./adapter/Postgres/PgProjectionFeed.res.mjs";
|
|
39
40
|
import * as Util_LocalConfig$ReventlessAws from "./util/Util_LocalConfig.res.mjs";
|
|
40
41
|
import * as Util_StoreLayout$ReventlessAws from "./util/Util_StoreLayout.res.mjs";
|
|
@@ -59,6 +60,7 @@ import * as Geocoder_AwsLocation$ReventlessAws from "./adapter/Geocoder/Geocoder
|
|
|
59
60
|
import * as QueryEngine_DynamoDb$ReventlessAws from "./adapter/QueryEngine/QueryEngine_DynamoDb.res.mjs";
|
|
60
61
|
import * as PluginRuntime_Builder$ReventlessAws from "./plugin/runtime/PluginRuntime_Builder.res.mjs";
|
|
61
62
|
import * as PluginsReadModelSpec$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/lifecycle/PluginsReadModelSpec.res.mjs";
|
|
63
|
+
import * as StorePrefixCollision$ReventlessCore from "@reventlessdev/reventless-core/src/util/StorePrefixCollision.res.mjs";
|
|
62
64
|
import * as ExtensionPoint_Builder$ReventlessAws from "./components/ExtensionPoint_Builder.res.mjs";
|
|
63
65
|
import * as StateViewSlice_Builder$ReventlessAws from "./components/StateViewSlice_Builder.res.mjs";
|
|
64
66
|
import * as Task_Builder_PerBucket$ReventlessAws from "./components/Task_Builder_PerBucket.res.mjs";
|
|
@@ -155,6 +157,30 @@ function MakeWithConfig(Config) {
|
|
|
155
157
|
contents: "Domain"
|
|
156
158
|
};
|
|
157
159
|
let platformStackRef = Stdlib_Option.map(new Pulumi.Config("platform").get("stack"), stack => new Pulumi.StackReference(stack));
|
|
160
|
+
let claimStores = () => {
|
|
161
|
+
if (platformStackRef !== undefined) {
|
|
162
|
+
return Primitive_option.valFromOption(platformStackRef).getOutput("objectStores").apply(objectStores => Stdlib_Array.filterMap(Object.entries(Stdlib_Option.getOr(Stdlib_Option.flatMap(objectStores, Stdlib_JSON.Decode.object), {})), param => {
|
|
163
|
+
let qualified = param[0];
|
|
164
|
+
return Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(param[1]), o => {
|
|
165
|
+
let match = Stdlib_Option.flatMap(o["bucketName"], Stdlib_JSON.Decode.string);
|
|
166
|
+
let match$1 = Stdlib_Option.flatMap(o["keyPrefix"], Stdlib_JSON.Decode.string);
|
|
167
|
+
if (match !== undefined && match$1 !== undefined) {
|
|
168
|
+
return {
|
|
169
|
+
qualified: qualified,
|
|
170
|
+
bucketName: match,
|
|
171
|
+
servedPrefix: match$1
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}));
|
|
176
|
+
} else {
|
|
177
|
+
return Pulumi.all(objectStoreEndpointsRef.contents.map(e => e.bucketName.apply(bucketName => ({
|
|
178
|
+
qualified: e.store,
|
|
179
|
+
bucketName: bucketName,
|
|
180
|
+
servedPrefix: e.keyPrefix
|
|
181
|
+
}))));
|
|
182
|
+
}
|
|
183
|
+
};
|
|
158
184
|
let domainBaseFragment = GraphQL_Stitcher$ReventlessCore.encode({
|
|
159
185
|
types: Platform_AdminApi$ReventlessCore.uploadTypes,
|
|
160
186
|
mutations: Platform_AdminApi$ReventlessCore.uploadMutationFields,
|
|
@@ -456,6 +482,7 @@ function MakeWithConfig(Config) {
|
|
|
456
482
|
let hooks_subscriptionInfraHook = Stdlib_Option.map(domainEventsApiOpt, eventsApi => (params => {
|
|
457
483
|
let allEventTopics = params.allEventTopics;
|
|
458
484
|
let allQueryDbs = params.allQueryDbs;
|
|
485
|
+
let pluginName = params.pluginName;
|
|
459
486
|
let customOpts = Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions(params.opts);
|
|
460
487
|
Stdlib_Dict.forEachWithKey(allQueryDbs, (_queryDbOutputs, readModelName) => {
|
|
461
488
|
if (!QueryDbStorage_DynamoDbStream$ReventlessAws.streamRegistry.has(readModelName)) {
|
|
@@ -466,10 +493,14 @@ function MakeWithConfig(Config) {
|
|
|
466
493
|
});
|
|
467
494
|
params.eventLogEntries.forEach(entry => {
|
|
468
495
|
let isSns = EventTopicPublisher_SNS$ReventlessAws.snsRegistry.has(entry.displayName) || EventTopicPublisher_SNS$ReventlessAws.snsRegistry.has(entry.busKey);
|
|
469
|
-
let
|
|
470
|
-
|
|
496
|
+
let topicOutputs = Stdlib_Option.orElse(allEventTopics[entry.displayName], allEventTopics[entry.busKey]);
|
|
497
|
+
if (isSns) {
|
|
498
|
+
Stdlib_Option.forEach(topicOutputs, outputs => EventLogSubscription_AppSync$ReventlessAws.make(entry.displayName, entry.displayName, outputs, eventsApi, customOpts));
|
|
499
|
+
}
|
|
500
|
+
Stdlib_Option.forEach(topicOutputs, outputs => Upload_Claim_S3$ReventlessAws.make(pluginName, entry.displayName, entry.eventSchema, outputs, !isSns));
|
|
471
501
|
});
|
|
472
502
|
StateTopic_AppSync$ReventlessAws.finish(eventsApi, {});
|
|
503
|
+
Upload_Claim_S3$ReventlessAws.finish(pluginName, claimStores, undefined, customOpts);
|
|
473
504
|
}));
|
|
474
505
|
let hooks_preResolversSchemaHook = (name, version, pluginFragment) => {
|
|
475
506
|
let sdl = AppSync_Adapter$ReventlessAws.stitchStandaloneWithAwsDirectives(pluginFragment);
|
|
@@ -904,6 +935,17 @@ function MakeWithConfig(Config) {
|
|
|
904
935
|
]]);
|
|
905
936
|
}
|
|
906
937
|
});
|
|
938
|
+
let collisions = StorePrefixCollision$ReventlessCore.collisionsFor(declaredStores.map(param => {
|
|
939
|
+
let store = param[1];
|
|
940
|
+
let plugin = param[0];
|
|
941
|
+
return {
|
|
942
|
+
qualified: plugin + `.` + store,
|
|
943
|
+
prefix: Util_StoreLayout$ReventlessAws.keyPrefixFor(plugin, store)
|
|
944
|
+
};
|
|
945
|
+
}));
|
|
946
|
+
if (collisions.length !== 0) {
|
|
947
|
+
Stdlib_JsError.throwWithMessage(collisions.map(StorePrefixCollision$ReventlessCore.collisionMessage).join("\n\n"));
|
|
948
|
+
}
|
|
907
949
|
if (declaredStores.length !== 0) {
|
|
908
950
|
let tmp;
|
|
909
951
|
tmp = storeLayout === "PerStore" ? "bucket per store" : "shared bucket, {store}/… prefixes";
|
|
@@ -913,11 +955,26 @@ function MakeWithConfig(Config) {
|
|
|
913
955
|
}
|
|
914
956
|
let storeBuckets = {};
|
|
915
957
|
let storeBucketOrder = [];
|
|
958
|
+
let pendingExpiryConfig = Util_LocalConfig$ReventlessAws.get("pendingUploadExpiryDays");
|
|
959
|
+
let pendingExpiryByBucket = {};
|
|
960
|
+
declaredStores.forEach(param => {
|
|
961
|
+
let store = param[1];
|
|
962
|
+
let plugin = param[0];
|
|
963
|
+
Stdlib_Option.forEach(Util_StoreLayout$ReventlessAws.pendingExpiryFor(pendingExpiryConfig, plugin + `.` + store), days => {
|
|
964
|
+
let bucketName = Util_StoreLayout$ReventlessAws.bucketNameFor(storeLayout, stackName, plugin, store);
|
|
965
|
+
let prefix = Util_StoreLayout$ReventlessAws.keyPrefixFor(plugin, store);
|
|
966
|
+
pendingExpiryByBucket[bucketName] = Stdlib_Option.getOr(pendingExpiryByBucket[bucketName], []).concat([{
|
|
967
|
+
prefix: prefix,
|
|
968
|
+
days: days
|
|
969
|
+
}]);
|
|
970
|
+
log.info("Platform:deployPlatform", undefined, `object store ` + plugin + `.` + store + `: never-claimed uploads under ` + prefix + `/ expire after ` + (days.toString() + ` days. Claimed objects and objects minted before the claim `) + `component existed carry no pending tag and are outside the rule.`);
|
|
971
|
+
});
|
|
972
|
+
});
|
|
916
973
|
let declaredStoreServices = declaredStores.map(param => {
|
|
917
974
|
let store = param[1];
|
|
918
975
|
let plugin = param[0];
|
|
919
976
|
let bucketName = Util_StoreLayout$ReventlessAws.bucketNameFor(storeLayout, stackName, plugin, store);
|
|
920
|
-
let keyPrefix = Util_StoreLayout$ReventlessAws.keyPrefixFor(store);
|
|
977
|
+
let keyPrefix = Util_StoreLayout$ReventlessAws.keyPrefixFor(plugin, store);
|
|
921
978
|
let b = storeBuckets[bucketName];
|
|
922
979
|
let bucket;
|
|
923
980
|
if (b !== undefined) {
|
|
@@ -925,7 +982,7 @@ function MakeWithConfig(Config) {
|
|
|
925
982
|
} else {
|
|
926
983
|
let tmp;
|
|
927
984
|
tmp = storeLayout === "PerStore" ? plugin : undefined;
|
|
928
|
-
let b$1 = Capability_ObjectStore_S3$ReventlessAws.make(bucketName, keyPrefix, tmp, storeProtection === "Protected", storeProtection === "Unprotected", undefined, undefined);
|
|
985
|
+
let b$1 = Capability_ObjectStore_S3$ReventlessAws.make(bucketName, keyPrefix, tmp, storeProtection === "Protected", storeProtection === "Unprotected", Stdlib_Option.getOr(pendingExpiryByBucket[bucketName], []), undefined, undefined);
|
|
929
986
|
storeBuckets[bucketName] = b$1;
|
|
930
987
|
storeBucketOrder.push(bucketName);
|
|
931
988
|
bucket = b$1;
|
|
@@ -1312,6 +1369,30 @@ function Make($star) {
|
|
|
1312
1369
|
contents: "Domain"
|
|
1313
1370
|
};
|
|
1314
1371
|
let platformStackRef = Stdlib_Option.map(new Pulumi.Config("platform").get("stack"), stack => new Pulumi.StackReference(stack));
|
|
1372
|
+
let claimStores = () => {
|
|
1373
|
+
if (platformStackRef !== undefined) {
|
|
1374
|
+
return Primitive_option.valFromOption(platformStackRef).getOutput("objectStores").apply(objectStores => Stdlib_Array.filterMap(Object.entries(Stdlib_Option.getOr(Stdlib_Option.flatMap(objectStores, Stdlib_JSON.Decode.object), {})), param => {
|
|
1375
|
+
let qualified = param[0];
|
|
1376
|
+
return Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(param[1]), o => {
|
|
1377
|
+
let match = Stdlib_Option.flatMap(o["bucketName"], Stdlib_JSON.Decode.string);
|
|
1378
|
+
let match$1 = Stdlib_Option.flatMap(o["keyPrefix"], Stdlib_JSON.Decode.string);
|
|
1379
|
+
if (match !== undefined && match$1 !== undefined) {
|
|
1380
|
+
return {
|
|
1381
|
+
qualified: qualified,
|
|
1382
|
+
bucketName: match,
|
|
1383
|
+
servedPrefix: match$1
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
});
|
|
1387
|
+
}));
|
|
1388
|
+
} else {
|
|
1389
|
+
return Pulumi.all(objectStoreEndpointsRef.contents.map(e => e.bucketName.apply(bucketName => ({
|
|
1390
|
+
qualified: e.store,
|
|
1391
|
+
bucketName: bucketName,
|
|
1392
|
+
servedPrefix: e.keyPrefix
|
|
1393
|
+
}))));
|
|
1394
|
+
}
|
|
1395
|
+
};
|
|
1315
1396
|
let domainBaseFragment = GraphQL_Stitcher$ReventlessCore.encode({
|
|
1316
1397
|
types: Platform_AdminApi$ReventlessCore.uploadTypes,
|
|
1317
1398
|
mutations: Platform_AdminApi$ReventlessCore.uploadMutationFields,
|
|
@@ -1613,6 +1694,7 @@ function Make($star) {
|
|
|
1613
1694
|
let hooks_subscriptionInfraHook = Stdlib_Option.map(domainEventsApiOpt, eventsApi => (params => {
|
|
1614
1695
|
let allEventTopics = params.allEventTopics;
|
|
1615
1696
|
let allQueryDbs = params.allQueryDbs;
|
|
1697
|
+
let pluginName = params.pluginName;
|
|
1616
1698
|
let customOpts = Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions(params.opts);
|
|
1617
1699
|
Stdlib_Dict.forEachWithKey(allQueryDbs, (_queryDbOutputs, readModelName) => {
|
|
1618
1700
|
if (!QueryDbStorage_DynamoDbStream$ReventlessAws.streamRegistry.has(readModelName)) {
|
|
@@ -1623,10 +1705,14 @@ function Make($star) {
|
|
|
1623
1705
|
});
|
|
1624
1706
|
params.eventLogEntries.forEach(entry => {
|
|
1625
1707
|
let isSns = EventTopicPublisher_SNS$ReventlessAws.snsRegistry.has(entry.displayName) || EventTopicPublisher_SNS$ReventlessAws.snsRegistry.has(entry.busKey);
|
|
1626
|
-
let
|
|
1627
|
-
|
|
1708
|
+
let topicOutputs = Stdlib_Option.orElse(allEventTopics[entry.displayName], allEventTopics[entry.busKey]);
|
|
1709
|
+
if (isSns) {
|
|
1710
|
+
Stdlib_Option.forEach(topicOutputs, outputs => EventLogSubscription_AppSync$ReventlessAws.make(entry.displayName, entry.displayName, outputs, eventsApi, customOpts));
|
|
1711
|
+
}
|
|
1712
|
+
Stdlib_Option.forEach(topicOutputs, outputs => Upload_Claim_S3$ReventlessAws.make(pluginName, entry.displayName, entry.eventSchema, outputs, !isSns));
|
|
1628
1713
|
});
|
|
1629
1714
|
StateTopic_AppSync$ReventlessAws.finish(eventsApi, {});
|
|
1715
|
+
Upload_Claim_S3$ReventlessAws.finish(pluginName, claimStores, undefined, customOpts);
|
|
1630
1716
|
}));
|
|
1631
1717
|
let hooks_preResolversSchemaHook = (name, version, pluginFragment) => {
|
|
1632
1718
|
let sdl = AppSync_Adapter$ReventlessAws.stitchStandaloneWithAwsDirectives(pluginFragment);
|
|
@@ -2038,6 +2124,17 @@ function Make($star) {
|
|
|
2038
2124
|
]]);
|
|
2039
2125
|
}
|
|
2040
2126
|
});
|
|
2127
|
+
let collisions = StorePrefixCollision$ReventlessCore.collisionsFor(declaredStores.map(param => {
|
|
2128
|
+
let store = param[1];
|
|
2129
|
+
let plugin = param[0];
|
|
2130
|
+
return {
|
|
2131
|
+
qualified: plugin + `.` + store,
|
|
2132
|
+
prefix: Util_StoreLayout$ReventlessAws.keyPrefixFor(plugin, store)
|
|
2133
|
+
};
|
|
2134
|
+
}));
|
|
2135
|
+
if (collisions.length !== 0) {
|
|
2136
|
+
Stdlib_JsError.throwWithMessage(collisions.map(StorePrefixCollision$ReventlessCore.collisionMessage).join("\n\n"));
|
|
2137
|
+
}
|
|
2041
2138
|
if (declaredStores.length !== 0) {
|
|
2042
2139
|
let tmp;
|
|
2043
2140
|
tmp = storeLayout === "PerStore" ? "bucket per store" : "shared bucket, {store}/… prefixes";
|
|
@@ -2047,11 +2144,26 @@ function Make($star) {
|
|
|
2047
2144
|
}
|
|
2048
2145
|
let storeBuckets = {};
|
|
2049
2146
|
let storeBucketOrder = [];
|
|
2147
|
+
let pendingExpiryConfig = Util_LocalConfig$ReventlessAws.get("pendingUploadExpiryDays");
|
|
2148
|
+
let pendingExpiryByBucket = {};
|
|
2149
|
+
declaredStores.forEach(param => {
|
|
2150
|
+
let store = param[1];
|
|
2151
|
+
let plugin = param[0];
|
|
2152
|
+
Stdlib_Option.forEach(Util_StoreLayout$ReventlessAws.pendingExpiryFor(pendingExpiryConfig, plugin + `.` + store), days => {
|
|
2153
|
+
let bucketName = Util_StoreLayout$ReventlessAws.bucketNameFor(storeLayout, stackName, plugin, store);
|
|
2154
|
+
let prefix = Util_StoreLayout$ReventlessAws.keyPrefixFor(plugin, store);
|
|
2155
|
+
pendingExpiryByBucket[bucketName] = Stdlib_Option.getOr(pendingExpiryByBucket[bucketName], []).concat([{
|
|
2156
|
+
prefix: prefix,
|
|
2157
|
+
days: days
|
|
2158
|
+
}]);
|
|
2159
|
+
log.info("Platform:deployPlatform", undefined, `object store ` + plugin + `.` + store + `: never-claimed uploads under ` + prefix + `/ expire after ` + (days.toString() + ` days. Claimed objects and objects minted before the claim `) + `component existed carry no pending tag and are outside the rule.`);
|
|
2160
|
+
});
|
|
2161
|
+
});
|
|
2050
2162
|
let declaredStoreServices = declaredStores.map(param => {
|
|
2051
2163
|
let store = param[1];
|
|
2052
2164
|
let plugin = param[0];
|
|
2053
2165
|
let bucketName = Util_StoreLayout$ReventlessAws.bucketNameFor(storeLayout, stackName, plugin, store);
|
|
2054
|
-
let keyPrefix = Util_StoreLayout$ReventlessAws.keyPrefixFor(store);
|
|
2166
|
+
let keyPrefix = Util_StoreLayout$ReventlessAws.keyPrefixFor(plugin, store);
|
|
2055
2167
|
let b = storeBuckets[bucketName];
|
|
2056
2168
|
let bucket;
|
|
2057
2169
|
if (b !== undefined) {
|
|
@@ -2059,7 +2171,7 @@ function Make($star) {
|
|
|
2059
2171
|
} else {
|
|
2060
2172
|
let tmp;
|
|
2061
2173
|
tmp = storeLayout === "PerStore" ? plugin : undefined;
|
|
2062
|
-
let b$1 = Capability_ObjectStore_S3$ReventlessAws.make(bucketName, keyPrefix, tmp, storeProtection === "Protected", storeProtection === "Unprotected", undefined, undefined);
|
|
2174
|
+
let b$1 = Capability_ObjectStore_S3$ReventlessAws.make(bucketName, keyPrefix, tmp, storeProtection === "Protected", storeProtection === "Unprotected", Stdlib_Option.getOr(pendingExpiryByBucket[bucketName], []), undefined, undefined);
|
|
2063
2175
|
storeBuckets[bucketName] = b$1;
|
|
2064
2176
|
storeBucketOrder.push(bucketName);
|
|
2065
2177
|
bucket = b$1;
|
|
@@ -298,6 +298,11 @@ let finish = () =>
|
|
|
298
298
|
}
|
|
299
299
|
})
|
|
300
300
|
)
|
|
301
|
+
// First-class log-level override → LOG_LEVEL, winning over any generic
|
|
302
|
+
// envVars entry. When unset, makeFromCodeAsset applies the tier default.
|
|
303
|
+
cfg.logLevel->Option.forEach(level =>
|
|
304
|
+
envVars->Dict.set("LOG_LEVEL", level->Pulumi.Output.make->Pulumi.Output.asInput)
|
|
305
|
+
)
|
|
301
306
|
|
|
302
307
|
// Build AssetArchive: static re-export + user packages
|
|
303
308
|
let {code, sourceCodeHash} = Util_Bundle.buildCodeArchive(
|
|
@@ -197,6 +197,9 @@ function finish() {
|
|
|
197
197
|
return;
|
|
198
198
|
}
|
|
199
199
|
}));
|
|
200
|
+
Stdlib_Option.forEach(cfg.logLevel, level => {
|
|
201
|
+
envVars["LOG_LEVEL"] = Pulumi.output(level);
|
|
202
|
+
});
|
|
200
203
|
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs", packageDirs, undefined);
|
|
201
204
|
Stdlib_Option.forEach(cfg.sqsBatchSize, CommandTopicChannel_SQS_Sync$ReventlessAws.setBatchSize);
|
|
202
205
|
let vpcConfig = pgSelection !== undefined ? pgSelection.securityGroupId.apply(sgId => ({
|
|
@@ -276,6 +276,11 @@ let finish = () =>
|
|
|
276
276
|
}
|
|
277
277
|
})
|
|
278
278
|
)
|
|
279
|
+
// First-class log-level override → LOG_LEVEL, winning over any generic
|
|
280
|
+
// envVars entry. When unset, makeFromCodeAsset applies the tier default.
|
|
281
|
+
cfg.logLevel->Option.forEach(level =>
|
|
282
|
+
envVars->Dict.set("LOG_LEVEL", level->Pulumi.Output.make->Pulumi.Output.asInput)
|
|
283
|
+
)
|
|
279
284
|
|
|
280
285
|
// Build AssetArchive: static re-export + user packages
|
|
281
286
|
let {code, sourceCodeHash} = Util_Bundle.buildCodeArchive(
|
|
@@ -186,6 +186,9 @@ function finish() {
|
|
|
186
186
|
return;
|
|
187
187
|
}
|
|
188
188
|
}));
|
|
189
|
+
Stdlib_Option.forEach(cfg.logLevel, level => {
|
|
190
|
+
envVars["LOG_LEVEL"] = Pulumi.output(level);
|
|
191
|
+
});
|
|
189
192
|
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs", packageDirs, undefined);
|
|
190
193
|
Stdlib_Option.forEach(cfg.sqsBatchSize, CommandTopicChannel_SQS_Async$ReventlessAws.setBatchSize);
|
|
191
194
|
let vpcConfig = pgSelection !== undefined ? pgSelection.securityGroupId.apply(sgId => ({
|