@reventlessdev/reventless-aws 3.0.0-alpha.271 → 3.0.0-alpha.273
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 +19 -0
- package/package.json +9 -8
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +20 -16
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +7 -7
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res +14 -5
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res.mjs +10 -3
- package/src/adapter/Api/Platform_UIFragments_Lambda.res +15 -13
- package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +3 -2
- package/src/adapter/EventCollector/EventCollectorChannel_Helpers.res +8 -1
- package/src/adapter/EventCollector/EventCollectorChannel_Helpers.res.mjs +3 -2
- package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res +15 -13
- package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res.mjs +3 -2
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res +6 -12
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs +4 -8
- package/src/adapter/QueryDb/QueryInterceptor_Provisioning.res +155 -0
- package/src/adapter/QueryDb/QueryInterceptor_Provisioning.res.mjs +92 -0
- package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res +4 -9
- package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res.mjs +8 -2
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +32 -62
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +7 -20
- package/src/adapter/StateTopic/StateTopic_AppSync.res +15 -13
- package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +3 -2
- package/src/adapter/Upload/Upload_Claim_S3.res +15 -13
- package/src/adapter/Upload/Upload_Claim_S3.res.mjs +3 -2
- package/src/adapter/Upload/Upload_Presign_S3.res +15 -13
- package/src/adapter/Upload/Upload_Presign_S3.res.mjs +3 -2
- package/src/components/Api/AppSync_Adapter.res +13 -9
- package/src/components/Api/AppSync_Adapter.res.mjs +4 -3
- package/src/util/Util_LambdaLogging.res +69 -21
- package/src/util/Util_LambdaLogging.res.mjs +21 -8
- package/src/util/Util_LogGroup_Adopting.res +480 -0
- package/src/util/Util_LogGroup_Adopting.res.mjs +365 -0
- package/src/util/Util_LogGroup_Adopting_Js.mjs +20 -0
- package/src/util/Util_LogRetention.res +11 -12
- package/tests/Util_LambdaLoggingTest.res +36 -0
- package/tests/Util_LambdaLoggingTest.res.mjs +22 -0
- package/tests/Util_LogGroup_AdoptingTest.res +169 -0
- package/tests/Util_LogGroup_AdoptingTest.res.mjs +208 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
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.273 (2026-08-08)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **aws:** create a Lambda's log group before the function that writes to it ([8d1e459](https://github.com/ReventlessDev/reventless-core/commit/8d1e4591127489d2ae8e84e7ade3843a1a859eb6))
|
|
11
|
+
* **core:** attribute resources built after a builder's construct returns ([67d6d9c](https://github.com/ReventlessDev/reventless-core/commit/67d6d9c0ff3ff1d38cd9e8ad43a4b86121abf827))
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
* **aws:** adopt an existing AppSync log group instead of failing on it ([ebed6c1](https://github.com/ReventlessDev/reventless-core/commit/ebed6c1ceb77b8deebff10a9c45715c107861f04))
|
|
15
|
+
* **core:** let an extension switch query interception on, and provision it ([4523b10](https://github.com/ReventlessDev/reventless-core/commit/4523b10597f1a028eedccc42391b250b25a6ddd3))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# 3.0.0-alpha.272 (2026-08-08)
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* **aws:** ship the admin structure as a code asset, not an env var ([93bc8a4](https://github.com/ReventlessDev/reventless-core/commit/93bc8a4891b54efeb13b99845a96f990dea6d41c))
|
|
23
|
+
|
|
24
|
+
|
|
6
25
|
# 3.0.0-alpha.271 (2026-08-08)
|
|
7
26
|
|
|
8
27
|
**Note:** Version bump only for package @reventlessdev/reventless-aws
|
package/package.json
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-aws",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.273",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@aws-sdk/client-appsync": "3.970.0",
|
|
8
8
|
"@aws-sdk/client-cloudfront": "3.970.0",
|
|
9
|
+
"@aws-sdk/client-cloudwatch-logs": "3.970.0",
|
|
9
10
|
"@aws-sdk/client-location": "3.970.0",
|
|
10
11
|
"@aws-sdk/client-s3": "3.970.0",
|
|
11
12
|
"@aws-sdk/s3-request-presigner": "3.970.0",
|
|
12
13
|
"sury": "11.0.0-alpha.4",
|
|
13
14
|
"uuid": "^13.0.0",
|
|
14
15
|
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.4",
|
|
15
|
-
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
16
|
-
"@reventlessdev/rescript-node": "2.0.0-alpha.2",
|
|
17
|
-
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.66",
|
|
18
16
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
19
|
-
"@reventlessdev/
|
|
17
|
+
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.67",
|
|
18
|
+
"@reventlessdev/rescript-node": "2.0.0-alpha.2",
|
|
19
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
20
|
+
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.18",
|
|
21
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.128",
|
|
22
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.217",
|
|
20
23
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.30",
|
|
21
24
|
"@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
|
|
22
|
-
"@reventlessdev/reventless-postgres": "3.0.0-alpha.
|
|
23
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.128",
|
|
24
|
-
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.18",
|
|
25
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.81",
|
|
25
26
|
"@reventlessdev/reventless-spec": "3.0.0-alpha.102"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
@@ -115,14 +115,17 @@ let make = (
|
|
|
115
115
|
// Bundle reventless-aws (the compiled `_Ops` handler lives inside it) and
|
|
116
116
|
// re-export its `handler`; buildCodeArchive also ships the ESM resolve-hook so
|
|
117
117
|
// the handler's bare @aws-sdk/* specifiers resolve from the managed runtime.
|
|
118
|
-
// The admin entry
|
|
119
|
-
//
|
|
118
|
+
// The admin entry rides the archive as `adminEntry.json` rather than an env var:
|
|
119
|
+
// it alone encodes to ~3 KB, which together with the other variables exceeds the
|
|
120
|
+
// 4096-byte UpdateFunctionConfiguration limit. Its content participates in
|
|
121
|
+
// `sourceCodeHash`, so a change to the admin structure redeploys the code.
|
|
120
122
|
let packageDirs = Dict.fromArray([
|
|
121
123
|
("@reventlessdev/reventless-aws", Util_Bundle.resolvePackageRoot("@reventlessdev/reventless-aws")),
|
|
122
124
|
])
|
|
123
125
|
let {code, sourceCodeHash} = Util_Bundle.buildCodeArchive(
|
|
124
126
|
~entryPointModule="@reventlessdev/reventless-aws/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res.mjs",
|
|
125
127
|
~packageDirs,
|
|
128
|
+
~extraStringAssets=Dict.fromArray([("adminEntry.json", adminEntryJson)]),
|
|
126
129
|
~bundleRuntimeExtensions=false,
|
|
127
130
|
)
|
|
128
131
|
|
|
@@ -132,6 +135,20 @@ let make = (
|
|
|
132
135
|
->Option.getOr([])
|
|
133
136
|
->Pulumi.Input.make
|
|
134
137
|
|
|
138
|
+
// Created before the function that writes to it — see
|
|
139
|
+
// `Util_LambdaLogging.makeManagedLogGroup` for why the ordering matters.
|
|
140
|
+
let logGroup = Util_LambdaLogging.makeManagedLogGroup(
|
|
141
|
+
~name=name ++ "Lambda",
|
|
142
|
+
~tags=AWS.Tags.make(
|
|
143
|
+
~name=name ++ "LambdaLogGroup",
|
|
144
|
+
~kind=ReventlessCore.ComponentType.Platform,
|
|
145
|
+
~role=Logs,
|
|
146
|
+
~scope=Platform,
|
|
147
|
+
),
|
|
148
|
+
~opts,
|
|
149
|
+
(),
|
|
150
|
+
)
|
|
151
|
+
|
|
135
152
|
let lambda = Lambda.Function.make(
|
|
136
153
|
~name=name ++ "Lambda",
|
|
137
154
|
~args={
|
|
@@ -150,30 +167,17 @@ let make = (
|
|
|
150
167
|
("Environment", Pulumi.Pulumi.getStackName()->Pulumi.Input.make),
|
|
151
168
|
("PLUGIN_RM_TABLE", pluginReadModelTableName->Pulumi.Output.asInput),
|
|
152
169
|
("OFFLOAD_BUCKET", offloadBucketName->Pulumi.Output.asInput),
|
|
153
|
-
("ADMIN_ENTRY_JSON", adminEntryJson->Pulumi.Input.make),
|
|
154
170
|
("NODE_OPTIONS", Util_Bundle.esmLoaderNodeOptions->Pulumi.Input.make),
|
|
155
171
|
("ESM_FALLBACK_DIRS", Util_Bundle.esmFallbackDirs->Pulumi.Input.make),
|
|
156
172
|
Util_LambdaLogging.logLevelEntry(),
|
|
157
173
|
]),
|
|
158
174
|
}: Lambda.Function.functionEnvironment
|
|
159
175
|
)->Pulumi.Input.make,
|
|
176
|
+
loggingConfig: ?Util_LambdaLogging.loggingConfigFor(logGroup),
|
|
160
177
|
},
|
|
161
178
|
~opts,
|
|
162
179
|
)
|
|
163
180
|
|
|
164
|
-
Util_LambdaLogging.makeManagedLogGroup(
|
|
165
|
-
~name=name ++ "Lambda",
|
|
166
|
-
~lambdaName=lambda.name,
|
|
167
|
-
~tags=AWS.Tags.make(
|
|
168
|
-
~name=name ++ "LambdaLogGroup",
|
|
169
|
-
~kind=ReventlessCore.ComponentType.Platform,
|
|
170
|
-
~role=Logs,
|
|
171
|
-
~scope=Platform,
|
|
172
|
-
),
|
|
173
|
-
~opts,
|
|
174
|
-
(),
|
|
175
|
-
)
|
|
176
|
-
|
|
177
181
|
let dataSourceRole = IAM.Role.makeWithDefaultPolicy(
|
|
178
182
|
~name=name ++ "DataSource",
|
|
179
183
|
~servicePrincipal=AWS.AppSync.principal->Pulumi.Output.make,
|
|
@@ -75,8 +75,12 @@ function make(api, pluginReadModelTableName, offloadBucketName, schemaReady, opt
|
|
|
75
75
|
"@reventlessdev/reventless-aws",
|
|
76
76
|
Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws")
|
|
77
77
|
]]);
|
|
78
|
-
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res.mjs", packageDirs,
|
|
78
|
+
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Api/Platform_ComponentDefinitions_Lambda_Ops.res.mjs", packageDirs, Object.fromEntries([[
|
|
79
|
+
"adminEntry.json",
|
|
80
|
+
adminEntryJson
|
|
81
|
+
]]), false);
|
|
79
82
|
let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
|
|
83
|
+
let logGroup = Util_LambdaLogging$ReventlessAws.makeManagedLogGroup(name + "Lambda", undefined, AWS_Tags$ReventlessAws.make(name + "LambdaLogGroup", "Platform", "Logs", "Platform", undefined, undefined, undefined, undefined), opts$1, undefined);
|
|
80
84
|
let lambda = new (Aws.lambda.Function)(name + "Lambda", {
|
|
81
85
|
handler: "index.handler",
|
|
82
86
|
runtime: "nodejs22.x",
|
|
@@ -100,10 +104,6 @@ function make(api, pluginReadModelTableName, offloadBucketName, schemaReady, opt
|
|
|
100
104
|
"OFFLOAD_BUCKET",
|
|
101
105
|
offloadBucketName
|
|
102
106
|
],
|
|
103
|
-
[
|
|
104
|
-
"ADMIN_ENTRY_JSON",
|
|
105
|
-
adminEntryJson
|
|
106
|
-
],
|
|
107
107
|
[
|
|
108
108
|
"NODE_OPTIONS",
|
|
109
109
|
Util_Bundle$ReventlessAws.esmLoaderNodeOptions
|
|
@@ -115,9 +115,9 @@ function make(api, pluginReadModelTableName, offloadBucketName, schemaReady, opt
|
|
|
115
115
|
Util_LambdaLogging$ReventlessAws.logLevelEntry()
|
|
116
116
|
])
|
|
117
117
|
},
|
|
118
|
-
sourceCodeHash: match.sourceCodeHash
|
|
118
|
+
sourceCodeHash: match.sourceCodeHash,
|
|
119
|
+
loggingConfig: Util_LambdaLogging$ReventlessAws.loggingConfigFor(logGroup)
|
|
119
120
|
}, opts$1);
|
|
120
|
-
Util_LambdaLogging$ReventlessAws.makeManagedLogGroup(name + "Lambda", lambda.name, AWS_Tags$ReventlessAws.make(name + "LambdaLogGroup", "Platform", "Logs", "Platform", undefined, undefined, undefined, undefined), opts$1, undefined);
|
|
121
121
|
let dataSourceRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name + "DataSource", Pulumi.output(AWS$ReventlessAws.AppSync.principal), AWS_Tags$ReventlessAws.make(name + "DataSource", "Platform", "Identity", "Platform", undefined, undefined, undefined, undefined), opts$1);
|
|
122
122
|
Pulumi.all([
|
|
123
123
|
lambda.arn,
|
|
@@ -68,17 +68,26 @@ let toEntryWith = (~filter: bool, item: dict<JSON.t>, ~name: string): option<JSO
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
|
|
71
|
-
// The built-in Platform_Admin entry,
|
|
72
|
-
//
|
|
73
|
-
//
|
|
71
|
+
// The built-in Platform_Admin entry, written at deploy time as an asset file in
|
|
72
|
+
// the code archive (the admin never Connects to itself, so its structure never
|
|
73
|
+
// enters the Plugin read model). It rides the archive rather than an env var
|
|
74
|
+
// because it alone is ~3 KB and the function's variables must fit AWS's 4096-byte
|
|
75
|
+
// UpdateFunctionConfiguration limit. process.cwd() is /var/task in Lambda, where
|
|
76
|
+
// the archive is extracted.
|
|
74
77
|
//
|
|
75
78
|
// Shared by both fields. The admin structure has no Internal components and no
|
|
76
79
|
// extension points, so its filtered and complete encodings carry the same
|
|
77
80
|
// components; the structure-level fields the complete entry adds are absent from
|
|
78
81
|
// this JSON and resolve to null, which is what `extensionPoints: None` means.
|
|
79
82
|
let adminEntry: option<JSON.t> =
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
try {
|
|
84
|
+
switch NodePath.join([NodeProcess.cwd(), "adminEntry.json"])
|
|
85
|
+
->NodeFs.readFileSync
|
|
86
|
+
->JSON.parseOrThrow {
|
|
87
|
+
| JSON.Null => None
|
|
88
|
+
| json => Some(json)
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
82
91
|
| _ => None
|
|
83
92
|
}
|
|
84
93
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Nodefs from "node:fs";
|
|
3
4
|
import * as S3$AwsSdk from "@reventlessdev/rescript-aws-sdk/src/S3.res.mjs";
|
|
5
|
+
import * as Nodepath from "node:path";
|
|
4
6
|
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
5
7
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
8
|
import * as Offload$Reventless from "@reventlessdev/reventless-spec/src/semantic/Offload.res.mjs";
|
|
@@ -50,9 +52,14 @@ function toEntryWith(filter, item, name) {
|
|
|
50
52
|
return entry;
|
|
51
53
|
}
|
|
52
54
|
|
|
53
|
-
let
|
|
55
|
+
let adminEntry;
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
try {
|
|
58
|
+
let json = JSON.parse(Nodefs.readFileSync(Nodepath.join(process.cwd(), "adminEntry.json"), "utf8"));
|
|
59
|
+
adminEntry = json === null ? undefined : json;
|
|
60
|
+
} catch (exn) {
|
|
61
|
+
adminEntry = undefined;
|
|
62
|
+
}
|
|
56
63
|
|
|
57
64
|
function resolveStructure(fetch, item) {
|
|
58
65
|
let refJson = Stdlib_Option.flatMap(Stdlib_Option.flatMap(item["structure"], Stdlib_JSON.Decode.object), o => o[Offload$Reventless.sentinelKey]);
|
|
@@ -112,4 +119,4 @@ export {
|
|
|
112
119
|
isComplete,
|
|
113
120
|
handler,
|
|
114
121
|
}
|
|
115
|
-
/*
|
|
122
|
+
/* adminEntry Not a pure module */
|
|
@@ -93,6 +93,20 @@ let make = (
|
|
|
93
93
|
->Option.getOr([])
|
|
94
94
|
->Pulumi.Input.make
|
|
95
95
|
|
|
96
|
+
// Created before the function that writes to it — see
|
|
97
|
+
// `Util_LambdaLogging.makeManagedLogGroup` for why the ordering matters.
|
|
98
|
+
let logGroup = Util_LambdaLogging.makeManagedLogGroup(
|
|
99
|
+
~name=name ++ "Lambda",
|
|
100
|
+
~tags=AWS.Tags.make(
|
|
101
|
+
~name=name ++ "LambdaLogGroup",
|
|
102
|
+
~kind=ReventlessCore.ComponentType.Platform,
|
|
103
|
+
~role=Logs,
|
|
104
|
+
~scope=Platform,
|
|
105
|
+
),
|
|
106
|
+
~opts,
|
|
107
|
+
(),
|
|
108
|
+
)
|
|
109
|
+
|
|
96
110
|
let lambda = Lambda.Function.make(
|
|
97
111
|
~name=name ++ "Lambda",
|
|
98
112
|
~args={
|
|
@@ -116,23 +130,11 @@ let make = (
|
|
|
116
130
|
]),
|
|
117
131
|
}: Lambda.Function.functionEnvironment
|
|
118
132
|
)->Pulumi.Input.make,
|
|
133
|
+
loggingConfig: ?Util_LambdaLogging.loggingConfigFor(logGroup),
|
|
119
134
|
},
|
|
120
135
|
~opts,
|
|
121
136
|
)
|
|
122
137
|
|
|
123
|
-
Util_LambdaLogging.makeManagedLogGroup(
|
|
124
|
-
~name=name ++ "Lambda",
|
|
125
|
-
~lambdaName=lambda.name,
|
|
126
|
-
~tags=AWS.Tags.make(
|
|
127
|
-
~name=name ++ "LambdaLogGroup",
|
|
128
|
-
~kind=ReventlessCore.ComponentType.Platform,
|
|
129
|
-
~role=Logs,
|
|
130
|
-
~scope=Platform,
|
|
131
|
-
),
|
|
132
|
-
~opts,
|
|
133
|
-
(),
|
|
134
|
-
)
|
|
135
|
-
|
|
136
138
|
let dataSourceRole = IAM.Role.makeWithDefaultPolicy(
|
|
137
139
|
~name=name ++ "DataSource",
|
|
138
140
|
~servicePrincipal=AWS.AppSync.principal->Pulumi.Output.make,
|
|
@@ -54,6 +54,7 @@ function make(api, uiFragmentRegistryTableName, schemaReady, opts) {
|
|
|
54
54
|
]]);
|
|
55
55
|
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Api/Platform_UIFragments_Lambda_Ops.res.mjs", packageDirs, undefined, false);
|
|
56
56
|
let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
|
|
57
|
+
let logGroup = Util_LambdaLogging$ReventlessAws.makeManagedLogGroup(name + "Lambda", undefined, AWS_Tags$ReventlessAws.make(name + "LambdaLogGroup", "Platform", "Logs", "Platform", undefined, undefined, undefined, undefined), opts$1, undefined);
|
|
57
58
|
let lambda = new (Aws.lambda.Function)(name + "Lambda", {
|
|
58
59
|
handler: "index.handler",
|
|
59
60
|
runtime: "nodejs22.x",
|
|
@@ -84,9 +85,9 @@ function make(api, uiFragmentRegistryTableName, schemaReady, opts) {
|
|
|
84
85
|
Util_LambdaLogging$ReventlessAws.logLevelEntry()
|
|
85
86
|
])
|
|
86
87
|
},
|
|
87
|
-
sourceCodeHash: match.sourceCodeHash
|
|
88
|
+
sourceCodeHash: match.sourceCodeHash,
|
|
89
|
+
loggingConfig: Util_LambdaLogging$ReventlessAws.loggingConfigFor(logGroup)
|
|
88
90
|
}, opts$1);
|
|
89
|
-
Util_LambdaLogging$ReventlessAws.makeManagedLogGroup(name + "Lambda", lambda.name, AWS_Tags$ReventlessAws.make(name + "LambdaLogGroup", "Platform", "Logs", "Platform", undefined, undefined, undefined, undefined), opts$1, undefined);
|
|
90
91
|
let dataSourceRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name + "DataSource", Pulumi.output(AWS$ReventlessAws.AppSync.principal), AWS_Tags$ReventlessAws.make(name + "DataSource", "Platform", "Identity", "Platform", undefined, undefined, undefined, undefined), opts$1);
|
|
91
92
|
Pulumi.all([
|
|
92
93
|
lambda.arn,
|
|
@@ -125,7 +125,14 @@ let connectLambda = (
|
|
|
125
125
|
resources->ReventlessCore.Adapter.resourcesToResolvedOutput,
|
|
126
126
|
)
|
|
127
127
|
->Pulumi.Output.all3
|
|
128
|
-
|
|
128
|
+
// Provisions the collector's role, policies and event-source mappings, so it
|
|
129
|
+
// carries the plugin's attribution across the apply — by the time this runs
|
|
130
|
+
// the builder's construct has returned and the ambient context is empty.
|
|
131
|
+
->ReventlessCore.ResourceAttribution_Deploytime.applyAttributed(((
|
|
132
|
+
eventTopicResources,
|
|
133
|
+
queueArns,
|
|
134
|
+
resources,
|
|
135
|
+
)) => {
|
|
129
136
|
log.debug(
|
|
130
137
|
~comp="EventCollector",
|
|
131
138
|
`connectLambda ${name}: ${eventTopicResources->Array.length->Int.toString} topic resource(s), ${resources->Array.length->Int.toString} resource(s)`,
|
|
@@ -15,6 +15,7 @@ import * as Adapter_Helpers$ReventlessAws from "../Adapter_Helpers.res.mjs";
|
|
|
15
15
|
import * as EventCollector$ReventlessCore from "@reventlessdev/reventless-core/src/components/EventCollector/EventCollector.res.mjs";
|
|
16
16
|
import * as AdapterDeploytime$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/AdapterDeploytime.res.mjs";
|
|
17
17
|
import * as Util_EventSourceMapping$ReventlessAws from "../../util/Util_EventSourceMapping.res.mjs";
|
|
18
|
+
import * as ResourceAttribution_Deploytime$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/ResourceAttribution_Deploytime.res.mjs";
|
|
18
19
|
|
|
19
20
|
let log = Logger$ReventlessCore.fromEnv();
|
|
20
21
|
|
|
@@ -76,11 +77,11 @@ function esmTags(name, esmName) {
|
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
function connectLambda(lambda, name, lambdaRole, queues, eventTopics, resources, opts) {
|
|
79
|
-
Pulumi.all([
|
|
80
|
+
ResourceAttribution_Deploytime$ReventlessCore.applyAttributed(Pulumi.all([
|
|
80
81
|
toResources(eventTopics),
|
|
81
82
|
Pulumi.all(queues.map(queue => queue.arn)),
|
|
82
83
|
Adapter$ReventlessCore.resourcesToResolvedOutput(resources)
|
|
83
|
-
])
|
|
84
|
+
]), param => {
|
|
84
85
|
let resources = param[2];
|
|
85
86
|
let queueArns = param[1];
|
|
86
87
|
let eventTopicResources = param[0];
|
|
@@ -118,6 +118,20 @@ let make = (
|
|
|
118
118
|
->Option.getOr([])
|
|
119
119
|
->Pulumi.Input.make
|
|
120
120
|
|
|
121
|
+
// Created before the function that writes to it — see
|
|
122
|
+
// `Util_LambdaLogging.makeManagedLogGroup` for why the ordering matters.
|
|
123
|
+
let logGroup = Util_LambdaLogging.makeManagedLogGroup(
|
|
124
|
+
~name=name ++ "Lambda",
|
|
125
|
+
~tags=AWS.Tags.make(
|
|
126
|
+
~name=name ++ "LambdaLogGroup",
|
|
127
|
+
~kind=ReventlessCore.ComponentType.Platform,
|
|
128
|
+
~role=Logs,
|
|
129
|
+
~scope=Platform,
|
|
130
|
+
),
|
|
131
|
+
~opts,
|
|
132
|
+
(),
|
|
133
|
+
)
|
|
134
|
+
|
|
121
135
|
let lambda = Lambda.Function.make(
|
|
122
136
|
~name=name ++ "Lambda",
|
|
123
137
|
~args={
|
|
@@ -146,23 +160,11 @@ let make = (
|
|
|
146
160
|
]),
|
|
147
161
|
}: Lambda.Function.functionEnvironment
|
|
148
162
|
)->Pulumi.Input.make,
|
|
163
|
+
loggingConfig: ?Util_LambdaLogging.loggingConfigFor(logGroup),
|
|
149
164
|
},
|
|
150
165
|
~opts,
|
|
151
166
|
)
|
|
152
167
|
|
|
153
|
-
Util_LambdaLogging.makeManagedLogGroup(
|
|
154
|
-
~name=name ++ "Lambda",
|
|
155
|
-
~lambdaName=lambda.name,
|
|
156
|
-
~tags=AWS.Tags.make(
|
|
157
|
-
~name=name ++ "LambdaLogGroup",
|
|
158
|
-
~kind=ReventlessCore.ComponentType.Platform,
|
|
159
|
-
~role=Logs,
|
|
160
|
-
~scope=Platform,
|
|
161
|
-
),
|
|
162
|
-
~opts,
|
|
163
|
-
(),
|
|
164
|
-
)
|
|
165
|
-
|
|
166
168
|
let dataSourceRole = IAM.Role.makeWithDefaultPolicy(
|
|
167
169
|
~name=name ++ "DataSource",
|
|
168
170
|
~servicePrincipal=AWS.AppSync.principal->Pulumi.Output.make,
|
|
@@ -58,6 +58,7 @@ function make(api, placeIndexName, nameOpt, opts) {
|
|
|
58
58
|
]]);
|
|
59
59
|
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver_Ops.res.mjs", packageDirs, undefined, false);
|
|
60
60
|
let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
|
|
61
|
+
let logGroup = Util_LambdaLogging$ReventlessAws.makeManagedLogGroup(name + "Lambda", undefined, AWS_Tags$ReventlessAws.make(name + "LambdaLogGroup", "Platform", "Logs", "Platform", undefined, undefined, undefined, undefined), opts$1, undefined);
|
|
61
62
|
let lambda = new (Aws.lambda.Function)(name + "Lambda", {
|
|
62
63
|
handler: "index.handler",
|
|
63
64
|
runtime: "nodejs22.x",
|
|
@@ -88,9 +89,9 @@ function make(api, placeIndexName, nameOpt, opts) {
|
|
|
88
89
|
Util_LambdaLogging$ReventlessAws.logLevelEntry()
|
|
89
90
|
])
|
|
90
91
|
},
|
|
91
|
-
sourceCodeHash: match.sourceCodeHash
|
|
92
|
+
sourceCodeHash: match.sourceCodeHash,
|
|
93
|
+
loggingConfig: Util_LambdaLogging$ReventlessAws.loggingConfigFor(logGroup)
|
|
92
94
|
}, opts$1);
|
|
93
|
-
Util_LambdaLogging$ReventlessAws.makeManagedLogGroup(name + "Lambda", lambda.name, AWS_Tags$ReventlessAws.make(name + "LambdaLogGroup", "Platform", "Logs", "Platform", undefined, undefined, undefined, undefined), opts$1, undefined);
|
|
94
95
|
let dataSourceRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name + "DataSource", Pulumi.output(AWS$ReventlessAws.AppSync.principal), AWS_Tags$ReventlessAws.make(name + "DataSource", "Platform", "Identity", "Platform", undefined, undefined, undefined, undefined), opts$1);
|
|
95
96
|
Pulumi.all([
|
|
96
97
|
lambda.arn,
|
|
@@ -13,15 +13,6 @@ let log = ReventlessCore.Logger.fromEnv()
|
|
|
13
13
|
type api = Types.AppSync.api
|
|
14
14
|
type role = Types.AppSync.role
|
|
15
15
|
|
|
16
|
-
type interceptorConfig = {
|
|
17
|
-
dataSourceName: Pulumi.Input.t<string>,
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** Deploy-time config for the query interceptor. When set, all top-level Query
|
|
21
|
-
resolvers become pipeline resolvers with an interceptor Lambda function
|
|
22
|
-
preceding the DynamoDB query. Set this before calling `make`. */
|
|
23
|
-
let queryInterceptorConfig: ref<option<interceptorConfig>> = ref(None)
|
|
24
|
-
|
|
25
16
|
let interceptorCode = readModelName =>
|
|
26
17
|
`import { util } from '@aws-appsync/utils';
|
|
27
18
|
export function request(ctx) {
|
|
@@ -106,9 +97,12 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
|
|
|
106
97
|
}
|
|
107
98
|
|
|
108
99
|
// Creates either a unit resolver (no interceptor) or a pipeline resolver
|
|
109
|
-
// (interceptor Lambda → DynamoDB query) depending on
|
|
100
|
+
// (interceptor Lambda → DynamoDB query), depending on whether an extension
|
|
101
|
+
// switched interception on for this deployment. Provisioning the interceptor
|
|
102
|
+
// is the framework's job — see `QueryInterceptor_Provisioning`; the first
|
|
103
|
+
// Query resolver under a plugin creates it and the rest reuse it.
|
|
110
104
|
let makeQueryResolver = (~resolverName, ~field, ~code) =>
|
|
111
|
-
switch
|
|
105
|
+
switch QueryInterceptor_Provisioning.dataSourceName(~api, ~opts) {
|
|
112
106
|
| None =>
|
|
113
107
|
Resolver.makeUnitJsResolver(
|
|
114
108
|
~name=resolverName,
|
|
@@ -119,7 +113,7 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
|
|
|
119
113
|
~code,
|
|
120
114
|
~opts,
|
|
121
115
|
)
|
|
122
|
-
| Some(
|
|
116
|
+
| Some(interceptorDsName) =>
|
|
123
117
|
let interceptorFn = Function.makeJs(
|
|
124
118
|
~name=resolverName ++ "Interceptor",
|
|
125
119
|
~api,
|
|
@@ -14,13 +14,10 @@ import * as Plugin_Helpers$ReventlessCore from "@reventlessdev/reventless-core/s
|
|
|
14
14
|
import * as AppSync_Resolver_Functions$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/AppSync/AppSync_Resolver_Functions.res.mjs";
|
|
15
15
|
import * as AppSync_Resolver_Retrying$ReventlessAws from "../Api/AppSync_Resolver_Retrying.res.mjs";
|
|
16
16
|
import * as GraphQL_FragmentGenerator$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_FragmentGenerator.res.mjs";
|
|
17
|
+
import * as QueryInterceptor_Provisioning$ReventlessAws from "./QueryInterceptor_Provisioning.res.mjs";
|
|
17
18
|
|
|
18
19
|
let log = Logger$ReventlessCore.fromEnv();
|
|
19
20
|
|
|
20
|
-
let queryInterceptorConfig = {
|
|
21
|
-
contents: undefined
|
|
22
|
-
};
|
|
23
|
-
|
|
24
21
|
function interceptorCode(readModelName) {
|
|
25
22
|
return `import { util } from '@aws-appsync/utils';
|
|
26
23
|
export function request(ctx) {
|
|
@@ -70,11 +67,11 @@ function make(name, api, apiRole, dataSourceName, indexes, subIdField, idResolve
|
|
|
70
67
|
let includeIdParam = registryEntry !== undefined ? registryEntry.includeIdParam : true;
|
|
71
68
|
let connectionSpec = registryEntry !== undefined ? registryEntry.connectionSpec : true;
|
|
72
69
|
let makeQueryResolver = (resolverName, field, code) => {
|
|
73
|
-
let
|
|
74
|
-
if (
|
|
70
|
+
let interceptorDsName = QueryInterceptor_Provisioning$ReventlessAws.dataSourceName(api, opts);
|
|
71
|
+
if (interceptorDsName === undefined) {
|
|
75
72
|
return AppSync_Resolver_Retrying$ReventlessAws.makeUnitJsResolver(resolverName, api, dataSourceName, "Query", field, code, opts);
|
|
76
73
|
}
|
|
77
|
-
let interceptorFn = AppSync_Function$PulumiAws.makeJs(resolverName + "Interceptor", api,
|
|
74
|
+
let interceptorFn = AppSync_Function$PulumiAws.makeJs(resolverName + "Interceptor", api, interceptorDsName, interceptorCode(name$1), opts);
|
|
78
75
|
let queryFn = AppSync_Function$PulumiAws.makeJs(resolverName + "Query", api, dataSourceName, code, opts);
|
|
79
76
|
return AppSync_Resolver_Retrying$ReventlessAws.makePipelineJsResolver(resolverName, api, "Query", field, AppSync_Resolver_Functions$PulumiAws.pipelinePassThrough, [
|
|
80
77
|
interceptorFn,
|
|
@@ -238,7 +235,6 @@ let Resolver;
|
|
|
238
235
|
export {
|
|
239
236
|
Resolver,
|
|
240
237
|
log,
|
|
241
|
-
queryInterceptorConfig,
|
|
242
238
|
interceptorCode,
|
|
243
239
|
internalRowRequiredAttr,
|
|
244
240
|
make,
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/** Provisions the query-interceptor path on AppSync, when an extension has asked
|
|
2
|
+
for it via `ReventlessCore.QueryInterception.use`.
|
|
3
|
+
|
|
4
|
+
The framework owns everything provider-shaped here — the runtime, its
|
|
5
|
+
execution role, the AppSync Lambda data source and the role that lets AppSync
|
|
6
|
+
invoke it — because all of it needs the api handle and a service role, which
|
|
7
|
+
exist only inside the plugin build. An extension supplies none of it; see the
|
|
8
|
+
seam's own docstring for why that split is the point.
|
|
9
|
+
|
|
10
|
+
**One interceptor per api, not per read model.** A data source belongs to one
|
|
11
|
+
api, so it is memoised on the api handle *by identity* — the builders thread
|
|
12
|
+
a single `api` value through every read model that resolves against it, and
|
|
13
|
+
identity is what guarantees the data source ends up attached to the very api
|
|
14
|
+
the resolver names. Keying on anything looser (the owning plugin, say) would
|
|
15
|
+
be right until one key spanned two apis, and the failure then is a resolver
|
|
16
|
+
pointing at a data source that does not exist on its api — rejected at deploy
|
|
17
|
+
time, with nothing in the message about why.
|
|
18
|
+
|
|
19
|
+
The api's *contents* are an `Output` and are unknown at
|
|
20
|
+
resolver-construction time, which is why the ambient `ResourceAttribution`
|
|
21
|
+
plugin supplies the resource NAMES instead. That is presentation, not
|
|
22
|
+
identity: a second api under one plugin gets a suffixed name rather than a
|
|
23
|
+
shared data source. Substrate built outside any plugin construct (the admin
|
|
24
|
+
read models on the platform api) is named `Platform`.
|
|
25
|
+
|
|
26
|
+
The runtime is built through `RuntimeEnvironment_Lambda.makeFromCodeAsset`
|
|
27
|
+
rather than a bespoke `Lambda.Function.make`, which is what makes the seams
|
|
28
|
+
compose: that path bundles registered runtime extensions into the archive and
|
|
29
|
+
writes `RUNTIME_EXTENSIONS`, so an extension registering its interceptor in
|
|
30
|
+
`onColdStart` reaches this runtime without a second registration. */
|
|
31
|
+
|
|
32
|
+
open PulumiAws
|
|
33
|
+
|
|
34
|
+
// Memoised on the api handle itself — see the module docstring for why identity
|
|
35
|
+
// rather than a name. The value is the data-source name the resolver pipeline
|
|
36
|
+
// references.
|
|
37
|
+
let byApi: Map.t<Types.AppSync.api, Pulumi.Input.t<string>> = Map.make()
|
|
38
|
+
|
|
39
|
+
// How many interceptors a given owner already named, so a second api under one
|
|
40
|
+
// owner gets a distinct resource name instead of colliding with the first.
|
|
41
|
+
let namesUsed: dict<int> = Dict.make()
|
|
42
|
+
|
|
43
|
+
/** A stable, readable resource-name stem for the interceptor: the owning plugin,
|
|
44
|
+
or `Platform` for substrate provisioned outside any plugin construct. */
|
|
45
|
+
let nameFor = () => {
|
|
46
|
+
let owner = ReventlessCore.ResourceAttribution.current.contents.plugin->Option.getOr("Platform")
|
|
47
|
+
let used = namesUsed->Dict.get(owner)->Option.getOr(0)
|
|
48
|
+
namesUsed->Dict.set(owner, used + 1)
|
|
49
|
+
used == 0
|
|
50
|
+
? owner ++ "QueryInterceptor"
|
|
51
|
+
: owner ++ "QueryInterceptor" ++ (used + 1)->Int.toString
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let buildInterceptor = (~api: Types.AppSync.api, ~opts) => {
|
|
55
|
+
let name = nameFor()
|
|
56
|
+
let componentOpts: Pulumi.ComponentResource.options = {
|
|
57
|
+
parent: ?(opts: Pulumi.CustomResourceOptions.t).parent,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let packageDirs = Dict.fromArray([
|
|
61
|
+
(
|
|
62
|
+
"@reventlessdev/reventless-aws",
|
|
63
|
+
Util_Bundle.resolvePackageRoot("@reventlessdev/reventless-aws"),
|
|
64
|
+
),
|
|
65
|
+
])
|
|
66
|
+
let {code, sourceCodeHash} = Util_Bundle.buildCodeArchive(
|
|
67
|
+
~entryPointModule="@reventlessdev/reventless-aws/src/adapter/QueryDb/QueryInterceptor_Lambda.res.mjs",
|
|
68
|
+
~packageDirs,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
// Sits in front of every read, so it is sized for a decision rather than for
|
|
72
|
+
// work: small memory, short timeout. A hook that needs longer than this is
|
|
73
|
+
// doing something a read path cannot afford anyway.
|
|
74
|
+
let runtime = RuntimeEnvironment_Lambda.makeFromCodeAsset(
|
|
75
|
+
~name,
|
|
76
|
+
~unitKind=ReventlessCore.Monitoring.Other("QueryInterceptor"),
|
|
77
|
+
~componentKind=ReventlessCore.ComponentType.Plugin,
|
|
78
|
+
~code,
|
|
79
|
+
~sourceCodeHash,
|
|
80
|
+
~memorySize=256,
|
|
81
|
+
~timeout=10,
|
|
82
|
+
~opts=componentOpts,
|
|
83
|
+
)
|
|
84
|
+
let lambdaArn = runtime.parts.lambda->Pulumi.Output.flatMap(lambda => lambda.arn)
|
|
85
|
+
|
|
86
|
+
let dataSourceRole = IAM.Role.makeWithDefaultPolicy(
|
|
87
|
+
~name=name ++ "DataSource",
|
|
88
|
+
~servicePrincipal=AWS.AppSync.principal->Pulumi.Output.make,
|
|
89
|
+
~tags=AWS.Tags.make(
|
|
90
|
+
~name=name ++ "DataSource",
|
|
91
|
+
~kind=ReventlessCore.ComponentType.Plugin,
|
|
92
|
+
~role=Identity,
|
|
93
|
+
~scope=Plugin,
|
|
94
|
+
),
|
|
95
|
+
~opts,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
let _ =
|
|
99
|
+
(lambdaArn, dataSourceRole.id)
|
|
100
|
+
->Pulumi.Output.all2
|
|
101
|
+
->Pulumi.Output.apply(((lambdaArn, dataSourceRoleId)) => {
|
|
102
|
+
open PolicyDocument
|
|
103
|
+
let _attach = IAM.RolePolicy.make(
|
|
104
|
+
~name=name ++ "DataSource",
|
|
105
|
+
~args={
|
|
106
|
+
IAM.RolePolicy.policy: PolicyDocument.make(
|
|
107
|
+
~id=name ++ "DataSourcePolicy",
|
|
108
|
+
~statements=[
|
|
109
|
+
{
|
|
110
|
+
sid: "AllowDataSourceInvokeInterceptor",
|
|
111
|
+
effect: Allow,
|
|
112
|
+
actions: Action("lambda:InvokeFunction"),
|
|
113
|
+
resources: Resource(lambdaArn),
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
)
|
|
117
|
+
->PolicyDocument.toJsonString
|
|
118
|
+
->Pulumi.Input.make,
|
|
119
|
+
role: dataSourceRoleId->Pulumi.Input.make,
|
|
120
|
+
},
|
|
121
|
+
~opts,
|
|
122
|
+
)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
let dataSource = AppSync.DataSource.make(
|
|
126
|
+
~name=name ++ "DataSource",
|
|
127
|
+
~args={
|
|
128
|
+
type_: AWS_LAMBDA,
|
|
129
|
+
apiId: api->Pulumi.Output.flatMap(api => api.id)->Pulumi.Output.asInput,
|
|
130
|
+
lambdaConfig: {
|
|
131
|
+
AppSync.DataSource.functionArn: lambdaArn->Pulumi.Output.asInput,
|
|
132
|
+
}->Pulumi.Input.make,
|
|
133
|
+
serviceRoleArn: dataSourceRole.arn->Pulumi.Output.asInput,
|
|
134
|
+
},
|
|
135
|
+
~opts=Some(opts),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
dataSource.name->Pulumi.Output.asInput
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The data source a Query resolver should put in front of its DynamoDB read, or
|
|
142
|
+
`None` when interception is off — in which case the caller builds the unit
|
|
143
|
+
resolver it always did and nothing here is provisioned. */
|
|
144
|
+
let dataSourceName = (~api: Types.AppSync.api, ~opts): option<Pulumi.Input.t<string>> =>
|
|
145
|
+
if !ReventlessCore.QueryInterception.isEnabled() {
|
|
146
|
+
None
|
|
147
|
+
} else {
|
|
148
|
+
switch byApi->Map.get(api) {
|
|
149
|
+
| Some(existing) => Some(existing)
|
|
150
|
+
| None =>
|
|
151
|
+
let created = buildInterceptor(~api, ~opts)
|
|
152
|
+
byApi->Map.set(api, created)
|
|
153
|
+
Some(created)
|
|
154
|
+
}
|
|
155
|
+
}
|