@reventlessdev/reventless-aws 3.0.0-alpha.176 → 3.0.0-alpha.177
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 +9 -0
- package/package.json +6 -6
- package/src/Platform.res +12 -0
- package/src/Platform.res.mjs +9 -0
- package/src/adapter/QueryDb/PgQueryResolver_Builder.res +281 -0
- package/src/adapter/QueryDb/PgQueryResolver_Builder.res.mjs +189 -0
- package/src/adapter/QueryDb/PgQueryResolver_Lambda.res +152 -9
- package/src/adapter/QueryDb/PgQueryResolver_Lambda.res.mjs +115 -4
- package/src/adapter/QueryDb/QueryDbResolvers.res +6 -5
- package/src/adapter/QueryDb/QueryDbResolvers.res.mjs +6 -3
- package/src/adapter/QueryDb/QueryDbResolvers_Lambda.res +264 -0
- package/src/adapter/QueryDb/QueryDbResolvers_Lambda.res.mjs +164 -0
- package/src/adapter/QueryDb/QueryDbStorage_Postgres.res +5 -1
- package/src/adapter/QueryDb/QueryDbStorage_Postgres.res.mjs +3 -3
- package/src/adapter/Runtime/PgQueryResolverEntryPoint.mjs +88 -0
- package/tests/PgQueryResolver_LambdaTest.res +104 -2
- package/tests/PgQueryResolver_LambdaTest.res.mjs +144 -11
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,15 @@
|
|
|
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.177 (2026-07-06)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **reventless-aws:** AppSync read path for Postgres read models (B3.2b) ([a6dd6cb](https://github.com/ReventlessDev/reventless-core/commit/a6dd6cbddfc65410bf96da49bb2603c88670aced))
|
|
11
|
+
* **reventless-aws:** cross-table @resolves/[@resolves](https://github.com/resolves)Many + node resolvers for Postgres reads (B3.2c) ([713b6ab](https://github.com/ReventlessDev/reventless-core/commit/713b6ab736715209e7198b2b4ab609c5447adef5))
|
|
12
|
+
* **reventless-postgres,reventless-aws:** items (sub-id connection) resolver for Postgres reads (B3.2c) ([2477220](https://github.com/ReventlessDev/reventless-core/commit/247722062913f5d0bcb5895b152f05033d8297d8))
|
|
13
|
+
|
|
14
|
+
|
|
6
15
|
# 3.0.0-alpha.176 (2026-07-06)
|
|
7
16
|
|
|
8
17
|
### 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.177",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"dependencies": {
|
|
@@ -9,16 +9,16 @@
|
|
|
9
9
|
"sury": "11.0.0-alpha.4",
|
|
10
10
|
"uuid": "^13.0.0",
|
|
11
11
|
"@reventlessdev/rescript-aws-sdk": "2.2.0-alpha.20",
|
|
12
|
-
"@reventlessdev/rescript-effect": "0.1.0-alpha.25",
|
|
13
12
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.6",
|
|
13
|
+
"@reventlessdev/rescript-effect": "0.1.0-alpha.25",
|
|
14
14
|
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.45",
|
|
15
15
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.14",
|
|
16
16
|
"@reventlessdev/rescript-uuid": "1.1.0-alpha.14",
|
|
17
|
-
"@reventlessdev/reventless-
|
|
17
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.143",
|
|
18
18
|
"@reventlessdev/reventless-infra": "3.0.0-alpha.89",
|
|
19
|
-
"@reventlessdev/reventless-postgres": "3.0.0-alpha.
|
|
20
|
-
"@reventlessdev/reventless-
|
|
21
|
-
"@reventlessdev/reventless-
|
|
19
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.6",
|
|
20
|
+
"@reventlessdev/reventless-interop": "3.0.0-alpha.24",
|
|
21
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.67"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"rescript": "^12.3.0",
|
package/src/Platform.res
CHANGED
|
@@ -1470,6 +1470,18 @@ module MakeWithConfig = (
|
|
|
1470
1470
|
// built above, so the DcbBackend relay registry is complete).
|
|
1471
1471
|
provisionPgChangeFeedRelay()
|
|
1472
1472
|
|
|
1473
|
+
// B3.2b: provision the shared PgQueryResolver Lambda + AppSync data source for
|
|
1474
|
+
// Postgres-backed read models (monolithic mode). Runs after construct so the
|
|
1475
|
+
// resolver-binding registry (QueryDbResolvers_Lambda.make) and the ReadModel
|
|
1476
|
+
// spec-module registry are complete; it fulfils the deferred `dataSourceName`
|
|
1477
|
+
// the Postgres storage maker handed to the (schema-pushed) resolvers. App read
|
|
1478
|
+
// model resolvers attach to the Domain API. Plugin-stack mode is deferred —
|
|
1479
|
+
// it needs a per-stack data-source name to avoid collisions on the shared API.
|
|
1480
|
+
switch QueryDbBackend.get() {
|
|
1481
|
+
| Some(sel) => PgQueryResolver_Builder.provision(~api=domainApi, ~selection=sel, ~opts={})
|
|
1482
|
+
| None => ()
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1473
1485
|
// Admin schema push is fired by preAdminResolversSchemaHook from inside
|
|
1474
1486
|
// Admin.construct (with createResolvers gated on it). Only exports below.
|
|
1475
1487
|
if Config.splitApi {
|
package/src/Platform.res.mjs
CHANGED
|
@@ -61,6 +61,7 @@ import * as Task_Builder_PerBucket$ReventlessAws from "./components/Task_Builder
|
|
|
61
61
|
import * as AutomationSlice_Builder$ReventlessAws from "./components/AutomationSlice_Builder.res.mjs";
|
|
62
62
|
import * as EventTopicPublisher_SNS$ReventlessAws from "./adapter/EventTopic/EventTopicPublisher_SNS.res.mjs";
|
|
63
63
|
import * as ExtensionPointMapping$ReventlessInfra from "@reventlessdev/reventless-infra/src/types/ExtensionPointMapping.res.mjs";
|
|
64
|
+
import * as PgQueryResolver_Builder$ReventlessAws from "./adapter/QueryDb/PgQueryResolver_Builder.res.mjs";
|
|
64
65
|
import * as Aggregate_Builder_Single$ReventlessAws from "./components/Aggregate_Builder_Single.res.mjs";
|
|
65
66
|
import * as ReadModel_Builder_Single$ReventlessAws from "./components/ReadModel_Builder_Single.res.mjs";
|
|
66
67
|
import * as StateChangeSlice_Builder$ReventlessAws from "./components/StateChangeSlice_Builder.res.mjs";
|
|
@@ -981,6 +982,10 @@ function MakeWithConfig(Config) {
|
|
|
981
982
|
Plugin_Helpers$ReventlessCore.exportPluginOutputs(Component$ReventlessCore.outputs(Primitive_option.valFromOption(pluginComponent)));
|
|
982
983
|
}
|
|
983
984
|
provisionPgChangeFeedRelay();
|
|
985
|
+
let sel = QueryDbBackend$ReventlessAws.get();
|
|
986
|
+
if (sel !== undefined) {
|
|
987
|
+
PgQueryResolver_Builder$ReventlessAws.provision(domainApi, sel, {});
|
|
988
|
+
}
|
|
984
989
|
if (Config.splitApi) {
|
|
985
990
|
Pulumi$Pulumi.$$export("platformApiId", Output$Pulumi.flatMap(platformApi, api => api.id));
|
|
986
991
|
Pulumi$Pulumi.$$export("platformApiEndpoint", Output$Pulumi.flatMap(platformApi, api => api.uris.apply(uris => uris.GRAPHQL)));
|
|
@@ -2185,6 +2190,10 @@ function Make($star) {
|
|
|
2185
2190
|
Plugin_Helpers$ReventlessCore.exportPluginOutputs(Component$ReventlessCore.outputs(Primitive_option.valFromOption(pluginComponent)));
|
|
2186
2191
|
}
|
|
2187
2192
|
provisionPgChangeFeedRelay();
|
|
2193
|
+
let sel = QueryDbBackend$ReventlessAws.get();
|
|
2194
|
+
if (sel !== undefined) {
|
|
2195
|
+
PgQueryResolver_Builder$ReventlessAws.provision(domainApi, sel, {});
|
|
2196
|
+
}
|
|
2188
2197
|
Pulumi$Pulumi.$$export("platformApiId", Output$Pulumi.flatMap(platformApi, api => api.id));
|
|
2189
2198
|
Pulumi$Pulumi.$$export("platformApiEndpoint", Output$Pulumi.flatMap(platformApi, api => api.uris.apply(uris => uris.GRAPHQL)));
|
|
2190
2199
|
Pulumi$Pulumi.$$export("platformApiRoleArn", Output$Pulumi.flatMap(platformApiRole, role => role.arn));
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
// Deploy-time provisioner for the shared Postgres GraphQL-read Lambda (B3.2b).
|
|
2
|
+
//
|
|
3
|
+
// Postgres-backed read models have no per-table AppSync data source, so instead
|
|
4
|
+
// of the direct DynamoDB resolvers there is ONE in-VPC "PgQueryResolver" Lambda
|
|
5
|
+
// (PgQueryResolverEntryPoint.mjs → PgQueryResolver_Lambda.dispatch) registered
|
|
6
|
+
// as a single AppSync Lambda data source. Every Postgres read model's resolvers
|
|
7
|
+
// (QueryDbResolvers_Lambda) are thin APPSYNC_JS `Invoke` templates pointing at
|
|
8
|
+
// this one data source.
|
|
9
|
+
//
|
|
10
|
+
// Ordering wrinkle: `QueryDbStorage_Postgres.make` runs DURING construct and
|
|
11
|
+
// must return the data source name, but the Lambda's code bundle needs the set
|
|
12
|
+
// of Postgres read models (their spec packages), known only AFTER construct. So
|
|
13
|
+
// the data source name is a DEFERRED Output (`dataSourceName`), fulfilled by
|
|
14
|
+
// `provision` — which the platform calls in makePlatform once every plugin is
|
|
15
|
+
// built (alongside provisionPgChangeFeedRelay). The resolvers themselves are
|
|
16
|
+
// created even later (inside the schema-pushed `resourcesMaker`), so they read
|
|
17
|
+
// the name after `provision` has resolved it.
|
|
18
|
+
|
|
19
|
+
open PulumiAws
|
|
20
|
+
|
|
21
|
+
let log = ReventlessCore.Logger.fromEnv()
|
|
22
|
+
|
|
23
|
+
// -- Deferred data source name ----------------------------------------------
|
|
24
|
+
// Created once at module load. `QueryDbStorage_Postgres.make` returns this for
|
|
25
|
+
// every Postgres read model (one shared data source); `provision` resolves it.
|
|
26
|
+
let resolveDataSourceName: ref<string => unit> = ref(_ => ())
|
|
27
|
+
let dataSourceNamePromise: promise<string> = Promise.make((resolve, _reject) =>
|
|
28
|
+
resolveDataSourceName := resolve
|
|
29
|
+
)
|
|
30
|
+
let dataSourceName: Pulumi.Output.t<string> = Pulumi.Output.fromPromise(dataSourceNamePromise)
|
|
31
|
+
|
|
32
|
+
// -- Resolver-binding registry ----------------------------------------------
|
|
33
|
+
// Populated by QueryDbResolvers_Lambda.make during construct (per Postgres read
|
|
34
|
+
// model): the bits `provision` bakes into the Lambda env config that the entry
|
|
35
|
+
// point can't get from the spec module (they come from the deploy-time
|
|
36
|
+
// queryFieldNamesRegistry). specModulePath is looked up from
|
|
37
|
+
// EventCollectorRuntime_Builder_Single.readModelInfos at provision time.
|
|
38
|
+
type resolverEntry = {
|
|
39
|
+
readModelName: string,
|
|
40
|
+
labelField: string,
|
|
41
|
+
includeIdParam: bool,
|
|
42
|
+
}
|
|
43
|
+
let entries: dict<resolverEntry> = Dict.make()
|
|
44
|
+
let register = (entry: resolverEntry): unit =>
|
|
45
|
+
entries->Dict.set(entry.readModelName, entry)
|
|
46
|
+
|
|
47
|
+
// Relay node type → read-model-name, populated per RM by QueryDbResolvers_Lambda.
|
|
48
|
+
// Baked into the env config so the entry point can serve the shared node(id).
|
|
49
|
+
let nodeTypes: dict<string> = Dict.make()
|
|
50
|
+
let registerNodeType = (~typeName: string, ~readModelName: string): unit =>
|
|
51
|
+
nodeTypes->Dict.set(typeName, readModelName)
|
|
52
|
+
|
|
53
|
+
// -- Provision --------------------------------------------------------------
|
|
54
|
+
let bool = b => b ? "true" : "false"
|
|
55
|
+
|
|
56
|
+
// One `node(id: ID!)` resolver on the shared data source → Invoke {kind:"node"}.
|
|
57
|
+
let nodeResolverCode =
|
|
58
|
+
`import { util } from '@aws-appsync/utils';
|
|
59
|
+
export function request(ctx) {
|
|
60
|
+
const id = ctx.identity;
|
|
61
|
+
return {
|
|
62
|
+
operation: 'Invoke',
|
|
63
|
+
payload: {
|
|
64
|
+
readModelName: '',
|
|
65
|
+
kind: 'node',
|
|
66
|
+
arguments: ctx.args,
|
|
67
|
+
identity: id != null && id.sub != null
|
|
68
|
+
? { userId: id.sub, username: id.username, groups: id.claims?.['cognito:groups'] ?? [], claims: id.claims, provider: 'Cognito' }
|
|
69
|
+
: id != null
|
|
70
|
+
? { userArn: id.userArn ?? null, accountId: id.accountId ?? null, username: id.username ?? null, provider: 'IAM' }
|
|
71
|
+
: null
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export function response(ctx) {
|
|
76
|
+
if (ctx.error) util.error(ctx.error.message, ctx.error.type);
|
|
77
|
+
return ctx.result;
|
|
78
|
+
}
|
|
79
|
+
`->Pulumi.Input.make
|
|
80
|
+
|
|
81
|
+
// Serialize the shared connection config into the env-config JSON (same shape
|
|
82
|
+
// EventCollectorRuntime_Builder_Single bakes into HANDLER_CONFIG).
|
|
83
|
+
let pgConnectionJson = (cc: PgConnection.connectionConfig): string =>
|
|
84
|
+
[
|
|
85
|
+
("host", cc.host->JSON.Encode.string),
|
|
86
|
+
("port", cc.port->Int.toFloat->JSON.Encode.float),
|
|
87
|
+
("database", cc.database->JSON.Encode.string),
|
|
88
|
+
("username", cc.username->JSON.Encode.string),
|
|
89
|
+
("secretArn", cc.secretArn->JSON.Encode.string),
|
|
90
|
+
]
|
|
91
|
+
->Dict.fromArray
|
|
92
|
+
->JSON.Encode.object
|
|
93
|
+
->JSON.stringify
|
|
94
|
+
|
|
95
|
+
let provision = (
|
|
96
|
+
~api: Pulumi.Output.t<AppSync.GraphQLApi.t>,
|
|
97
|
+
~selection: QueryDbBackend.selection,
|
|
98
|
+
~opts: Pulumi.ComponentResource.options,
|
|
99
|
+
): unit => {
|
|
100
|
+
// Join the resolver-binding registry (labelField/includeIdParam) with the
|
|
101
|
+
// ReadModel runtime registry (specModulePath, pgBacked). Only read models
|
|
102
|
+
// that both got Lambda resolvers AND are Postgres-backed are handled here.
|
|
103
|
+
let handlers =
|
|
104
|
+
entries
|
|
105
|
+
->Dict.valuesToArray
|
|
106
|
+
->Array.filterMap(entry =>
|
|
107
|
+
switch EventCollectorRuntime_Builder_Single.readModelInfos->Dict.get(entry.readModelName) {
|
|
108
|
+
| Some(info) if info.pgBacked => Some((entry, info.specModulePath))
|
|
109
|
+
| _ => None
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
if handlers->Array.length == 0 {
|
|
113
|
+
// No Postgres read models with Lambda resolvers — nothing to provision.
|
|
114
|
+
()
|
|
115
|
+
} else {
|
|
116
|
+
// makeFromCodeAsset takes ComponentResource.options (the original); the raw
|
|
117
|
+
// IAM / DataSource resources take the CustomResourceOptions projection.
|
|
118
|
+
let customOpts =
|
|
119
|
+
opts->ReventlessCore.Util.Pulumi.ComponentResourceOptions.toCustomResourceOptions
|
|
120
|
+
let name = "PgQueryResolver"
|
|
121
|
+
|
|
122
|
+
// Bundle the entry point plus each read model's spec package (imported at
|
|
123
|
+
// Lambda init for indexes / subIdField / schema → capability).
|
|
124
|
+
let packageDirs: dict<string> = Dict.make()
|
|
125
|
+
handlers->Array.forEach(((_entry, specModulePath)) => {
|
|
126
|
+
let pkg = Util_Bundle.extractPackageName(specModulePath)
|
|
127
|
+
packageDirs->Dict.set(pkg, Util_Bundle.resolvePackageRoot(pkg))
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// Env config: shared pgConnection + one handler per read model. Sorted for
|
|
131
|
+
// deploy-stable output. specModule/labelField/includeIdParam are baked;
|
|
132
|
+
// indexes/subIdField/authorization come from the imported spec at runtime.
|
|
133
|
+
let handlerJsons =
|
|
134
|
+
handlers
|
|
135
|
+
->Array.toSorted(((a, _), (b, _)) => String.compare(a.readModelName, b.readModelName))
|
|
136
|
+
->Array.map(((entry, specModulePath)) => {
|
|
137
|
+
let specModule = specModulePath->JSON.stringifyAny->Option.getOr(`""`)
|
|
138
|
+
`{"readModelName":"${entry.readModelName}","specModule":${specModule},"labelField":"${entry.labelField}","includeIdParam":${bool(
|
|
139
|
+
entry.includeIdParam,
|
|
140
|
+
)}}`
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// Relay node type → read-model map, baked so the entry point can serve node(id).
|
|
144
|
+
let nodeTypesJson =
|
|
145
|
+
nodeTypes
|
|
146
|
+
->Dict.toArray
|
|
147
|
+
->Array.toSorted(((a, _), (b, _)) => String.compare(a, b))
|
|
148
|
+
->Array.map(((typeName, rm)) => `"${typeName}":"${rm}"`)
|
|
149
|
+
->Array.join(",")
|
|
150
|
+
|
|
151
|
+
let queryResolverConfig =
|
|
152
|
+
selection.connectionConfig->Pulumi.Output.apply(cc =>
|
|
153
|
+
`{"pgConnection":${pgConnectionJson(cc)},"handlers":[${handlerJsons->Array.join(
|
|
154
|
+
",",
|
|
155
|
+
)}],"nodeTypes":{${nodeTypesJson}}}`
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
let envVars: dict<Pulumi.Input.t<string>> = Dict.make()
|
|
159
|
+
envVars->Dict.set("QUERY_RESOLVER_CONFIG", queryResolverConfig->Pulumi.Output.asInput)
|
|
160
|
+
envVars->Dict.set("Environment", Pulumi.Pulumi.getStackName()->Pulumi.Input.make)
|
|
161
|
+
|
|
162
|
+
let {code, sourceCodeHash} = Util_Bundle.buildCodeArchive(
|
|
163
|
+
~entryPointModule="@reventlessdev/reventless-aws/src/adapter/Runtime/PgQueryResolverEntryPoint.mjs",
|
|
164
|
+
~packageDirs,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
// In-VPC on the DB-access security group + private subnets (reach RDS).
|
|
168
|
+
let vpcConfig =
|
|
169
|
+
selection.securityGroupId
|
|
170
|
+
->Pulumi.Output.apply(sgId =>
|
|
171
|
+
(
|
|
172
|
+
{
|
|
173
|
+
Lambda.Function.subnetIds: selection.subnetIds->Pulumi.Input.make,
|
|
174
|
+
securityGroupIds: [sgId->Pulumi.Input.make]->Pulumi.Input.make,
|
|
175
|
+
}: Lambda.Function.vpcConfig
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
->Pulumi.Output.asInput
|
|
179
|
+
|
|
180
|
+
let runtime = RuntimeEnvironment_Lambda.makeFromCodeAsset(
|
|
181
|
+
~name,
|
|
182
|
+
~code,
|
|
183
|
+
~sourceCodeHash,
|
|
184
|
+
~envVars,
|
|
185
|
+
~memorySize=512,
|
|
186
|
+
~timeout=30,
|
|
187
|
+
~vpcConfig,
|
|
188
|
+
~opts,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
// Read the RDS-managed secret so PgRuntime can resolve the DB password.
|
|
192
|
+
let _ = selection.connectionConfig->Pulumi.Output.apply(cc => {
|
|
193
|
+
open PolicyDocument
|
|
194
|
+
IAM.RolePolicy.make(
|
|
195
|
+
~name=name ++ "-pgSecret",
|
|
196
|
+
~args={
|
|
197
|
+
policy: PolicyDocument.make(
|
|
198
|
+
~id=name ++ "-pgSecretPolicy",
|
|
199
|
+
~statements=[
|
|
200
|
+
{
|
|
201
|
+
sid: "AllowGetPgSecret",
|
|
202
|
+
effect: Allow,
|
|
203
|
+
actions: Action("secretsmanager:GetSecretValue"),
|
|
204
|
+
resources: Resource(cc.secretArn),
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
)
|
|
208
|
+
->PolicyDocument.toJsonString
|
|
209
|
+
->Pulumi.Input.make,
|
|
210
|
+
role: runtime.parts.lambdaRole.id->Pulumi.Output.asInput,
|
|
211
|
+
},
|
|
212
|
+
)
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
// AppSync → Lambda data source (one, shared by every Postgres RM's resolvers).
|
|
216
|
+
let dataSourceRole = IAM.Role.makeWithDefaultPolicy(
|
|
217
|
+
~name=name ++ "DataSource",
|
|
218
|
+
~servicePrincipal=AWS.AppSync.principal->Pulumi.Output.make,
|
|
219
|
+
~opts=customOpts,
|
|
220
|
+
)
|
|
221
|
+
let lambdaArn = runtime.parts.lambda->Pulumi.Output.flatMap(l => l.arn)
|
|
222
|
+
let _ =
|
|
223
|
+
(lambdaArn, dataSourceRole.id)
|
|
224
|
+
->Pulumi.Output.all2
|
|
225
|
+
->Pulumi.Output.apply(((arn, dsRoleId)) => {
|
|
226
|
+
open PolicyDocument
|
|
227
|
+
IAM.RolePolicy.make(
|
|
228
|
+
~name=name ++ "DataSourceInvoke",
|
|
229
|
+
~args={
|
|
230
|
+
policy: PolicyDocument.make(
|
|
231
|
+
~id=name ++ "DataSourceInvokePolicy",
|
|
232
|
+
~statements=[
|
|
233
|
+
{
|
|
234
|
+
sid: "AllowDataSourceInvokeLambda",
|
|
235
|
+
effect: Allow,
|
|
236
|
+
actions: Action("lambda:InvokeFunction"),
|
|
237
|
+
resources: Resource(arn),
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
)
|
|
241
|
+
->PolicyDocument.toJsonString
|
|
242
|
+
->Pulumi.Input.make,
|
|
243
|
+
role: dsRoleId->Pulumi.Input.make,
|
|
244
|
+
},
|
|
245
|
+
)
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
let dataSource = AppSync.DataSource.make(
|
|
249
|
+
~name=name ++ "DataSource",
|
|
250
|
+
~args={
|
|
251
|
+
type_: AWS_LAMBDA,
|
|
252
|
+
apiId: api->Pulumi.Output.flatMap(a => a.id)->Pulumi.Output.asInput,
|
|
253
|
+
lambdaConfig: {
|
|
254
|
+
AppSync.DataSource.functionArn: lambdaArn->Pulumi.Output.asInput,
|
|
255
|
+
}->Pulumi.Input.make,
|
|
256
|
+
serviceRoleArn: dataSourceRole.arn->Pulumi.Output.asInput,
|
|
257
|
+
},
|
|
258
|
+
~opts=Some(customOpts),
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
// Shared node(id) resolver (B3.2c) — one for the whole API, dispatched by
|
|
262
|
+
// typeName in the Lambda. Only when some entity registered a node type
|
|
263
|
+
// (else the `node` field isn't in the schema). Mirrors NodeResolver_AppSync,
|
|
264
|
+
// which likewise creates the node resolver directly at deploy time.
|
|
265
|
+
if nodeTypes->Dict.toArray->Array.length > 0 {
|
|
266
|
+
let _nodeResolver = AppSync_Resolver_Retrying.makeUnitJsResolver(
|
|
267
|
+
~name=name ++ "NodeResolver",
|
|
268
|
+
~api,
|
|
269
|
+
~dataSourceName=dataSource.name->Pulumi.Output.asInput,
|
|
270
|
+
~type_="Query"->Pulumi.Input.make,
|
|
271
|
+
~field="node"->Pulumi.Input.make,
|
|
272
|
+
~code=nodeResolverCode,
|
|
273
|
+
~opts=customOpts,
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Fulfil the deferred name the Postgres storage maker handed to resolvers.
|
|
278
|
+
let _ = dataSource.name->Pulumi.Output.apply(n => resolveDataSourceName.contents(n))
|
|
279
|
+
log.info(~comp="PgQueryResolver_Builder", `provisioned for ${handlers->Array.length->Int.toString} read model(s)`)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Aws from "@pulumi/aws";
|
|
4
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
5
|
+
import * as IAM$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/IAM.res.mjs";
|
|
6
|
+
import * as Output$Pulumi from "@reventlessdev/rescript-pulumi-pulumi/src/Output.res.mjs";
|
|
7
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
8
|
+
import * as Pulumi from "@pulumi/pulumi";
|
|
9
|
+
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
10
|
+
import * as AWS$ReventlessAws from "../AWS.res.mjs";
|
|
11
|
+
import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
|
|
12
|
+
import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
|
|
13
|
+
import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
|
|
14
|
+
import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Pulumi.res.mjs";
|
|
15
|
+
import * as AppSync_Resolver_Retrying$ReventlessAws from "../Api/AppSync_Resolver_Retrying.res.mjs";
|
|
16
|
+
import * as RuntimeEnvironment_Lambda$ReventlessAws from "../Runtime/RuntimeEnvironment_Lambda.res.mjs";
|
|
17
|
+
import * as EventCollectorRuntime_Builder_Single$ReventlessAws from "../Runtime/EventCollectorRuntime_Builder_Single.res.mjs";
|
|
18
|
+
|
|
19
|
+
let log = Logger$ReventlessCore.fromEnv();
|
|
20
|
+
|
|
21
|
+
let resolveDataSourceName = {
|
|
22
|
+
contents: param => {}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
let dataSourceNamePromise = new Promise((resolve, _reject) => {
|
|
26
|
+
resolveDataSourceName.contents = resolve;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
let entries = {};
|
|
30
|
+
|
|
31
|
+
function register(entry) {
|
|
32
|
+
entries[entry.readModelName] = entry;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let nodeTypes = {};
|
|
36
|
+
|
|
37
|
+
function registerNodeType(typeName, readModelName) {
|
|
38
|
+
nodeTypes[typeName] = readModelName;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function bool(b) {
|
|
42
|
+
if (b) {
|
|
43
|
+
return "true";
|
|
44
|
+
} else {
|
|
45
|
+
return "false";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let nodeResolverCode = `import { util } from '@aws-appsync/utils';
|
|
50
|
+
export function request(ctx) {
|
|
51
|
+
const id = ctx.identity;
|
|
52
|
+
return {
|
|
53
|
+
operation: 'Invoke',
|
|
54
|
+
payload: {
|
|
55
|
+
readModelName: '',
|
|
56
|
+
kind: 'node',
|
|
57
|
+
arguments: ctx.args,
|
|
58
|
+
identity: id != null && id.sub != null
|
|
59
|
+
? { userId: id.sub, username: id.username, groups: id.claims?.['cognito:groups'] ?? [], claims: id.claims, provider: 'Cognito' }
|
|
60
|
+
: id != null
|
|
61
|
+
? { userArn: id.userArn ?? null, accountId: id.accountId ?? null, username: id.username ?? null, provider: 'IAM' }
|
|
62
|
+
: null
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export function response(ctx) {
|
|
67
|
+
if (ctx.error) util.error(ctx.error.message, ctx.error.type);
|
|
68
|
+
return ctx.result;
|
|
69
|
+
}
|
|
70
|
+
`;
|
|
71
|
+
|
|
72
|
+
function pgConnectionJson(cc) {
|
|
73
|
+
return JSON.stringify(Object.fromEntries([
|
|
74
|
+
[
|
|
75
|
+
"host",
|
|
76
|
+
cc.host
|
|
77
|
+
],
|
|
78
|
+
[
|
|
79
|
+
"port",
|
|
80
|
+
cc.port
|
|
81
|
+
],
|
|
82
|
+
[
|
|
83
|
+
"database",
|
|
84
|
+
cc.database
|
|
85
|
+
],
|
|
86
|
+
[
|
|
87
|
+
"username",
|
|
88
|
+
cc.username
|
|
89
|
+
],
|
|
90
|
+
[
|
|
91
|
+
"secretArn",
|
|
92
|
+
cc.secretArn
|
|
93
|
+
]
|
|
94
|
+
]));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function provision(api, selection, opts) {
|
|
98
|
+
let handlers = Stdlib_Array.filterMap(Object.values(entries), entry => {
|
|
99
|
+
let info = EventCollectorRuntime_Builder_Single$ReventlessAws.readModelInfos[entry.readModelName];
|
|
100
|
+
if (info !== undefined && info.pgBacked) {
|
|
101
|
+
return [
|
|
102
|
+
entry,
|
|
103
|
+
info.specModulePath
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
if (handlers.length === 0) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
let customOpts = Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions(opts);
|
|
111
|
+
let name = "PgQueryResolver";
|
|
112
|
+
let packageDirs = {};
|
|
113
|
+
handlers.forEach(param => {
|
|
114
|
+
let pkg = Util_Bundle$ReventlessAws.extractPackageName(param[1]);
|
|
115
|
+
packageDirs[pkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(pkg);
|
|
116
|
+
});
|
|
117
|
+
let handlerJsons = handlers.toSorted((param, param$1) => Primitive_string.compare(param[0].readModelName, param$1[0].readModelName)).map(param => {
|
|
118
|
+
let entry = param[0];
|
|
119
|
+
let specModule = Stdlib_Option.getOr(JSON.stringify(param[1]), `""`);
|
|
120
|
+
return `{"readModelName":"` + entry.readModelName + `","specModule":` + specModule + `,"labelField":"` + entry.labelField + `","includeIdParam":` + (
|
|
121
|
+
entry.includeIdParam ? "true" : "false"
|
|
122
|
+
) + `}`;
|
|
123
|
+
});
|
|
124
|
+
let nodeTypesJson = Object.entries(nodeTypes).toSorted((param, param$1) => Primitive_string.compare(param[0], param$1[0])).map(param => `"` + param[0] + `":"` + param[1] + `"`).join(",");
|
|
125
|
+
let queryResolverConfig = selection.connectionConfig.apply(cc => `{"pgConnection":` + pgConnectionJson(cc) + `,"handlers":[` + handlerJsons.join(",") + `],"nodeTypes":{` + nodeTypesJson + `}}`);
|
|
126
|
+
let envVars = {};
|
|
127
|
+
envVars["QUERY_RESOLVER_CONFIG"] = queryResolverConfig;
|
|
128
|
+
envVars["Environment"] = Pulumi.getStack();
|
|
129
|
+
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/PgQueryResolverEntryPoint.mjs", packageDirs, undefined);
|
|
130
|
+
let vpcConfig = selection.securityGroupId.apply(sgId => ({
|
|
131
|
+
subnetIds: selection.subnetIds,
|
|
132
|
+
securityGroupIds: [sgId]
|
|
133
|
+
}));
|
|
134
|
+
let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, 512, 30, undefined, undefined, undefined, undefined, vpcConfig, opts);
|
|
135
|
+
selection.connectionConfig.apply(cc => new (Aws.iam.RolePolicy)(name + "-pgSecret", {
|
|
136
|
+
policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + "-pgSecretPolicy", [{
|
|
137
|
+
Sid: "AllowGetPgSecret",
|
|
138
|
+
Effect: "Allow",
|
|
139
|
+
Action: "secretsmanager:GetSecretValue",
|
|
140
|
+
Resource: cc.secretArn
|
|
141
|
+
}])),
|
|
142
|
+
role: runtime.parts.lambdaRole.id
|
|
143
|
+
}));
|
|
144
|
+
let dataSourceRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name + "DataSource", Pulumi.output(AWS$ReventlessAws.AppSync.principal), customOpts);
|
|
145
|
+
let lambdaArn = Output$Pulumi.flatMap(runtime.parts.lambda, l => l.arn);
|
|
146
|
+
Pulumi.all([
|
|
147
|
+
lambdaArn,
|
|
148
|
+
dataSourceRole.id
|
|
149
|
+
]).apply(param => new (Aws.iam.RolePolicy)(name + "DataSourceInvoke", {
|
|
150
|
+
policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + "DataSourceInvokePolicy", [{
|
|
151
|
+
Sid: "AllowDataSourceInvokeLambda",
|
|
152
|
+
Effect: "Allow",
|
|
153
|
+
Action: "lambda:InvokeFunction",
|
|
154
|
+
Resource: param[0]
|
|
155
|
+
}])),
|
|
156
|
+
role: param[1]
|
|
157
|
+
}));
|
|
158
|
+
let dataSource = new (Aws.appsync.DataSource)(name + "DataSource", {
|
|
159
|
+
type: "AWS_LAMBDA",
|
|
160
|
+
apiId: Output$Pulumi.flatMap(api, a => a.id),
|
|
161
|
+
lambdaConfig: {
|
|
162
|
+
functionArn: lambdaArn
|
|
163
|
+
},
|
|
164
|
+
serviceRoleArn: dataSourceRole.arn
|
|
165
|
+
}, customOpts);
|
|
166
|
+
if (Object.entries(nodeTypes).length !== 0) {
|
|
167
|
+
AppSync_Resolver_Retrying$ReventlessAws.makeUnitJsResolver(name + "NodeResolver", api, dataSource.name, "Query", "node", nodeResolverCode, customOpts);
|
|
168
|
+
}
|
|
169
|
+
dataSource.name.apply(n => resolveDataSourceName.contents(n));
|
|
170
|
+
log.info("PgQueryResolver_Builder", undefined, `provisioned for ` + handlers.length.toString() + ` read model(s)`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let dataSourceName = dataSourceNamePromise;
|
|
174
|
+
|
|
175
|
+
export {
|
|
176
|
+
log,
|
|
177
|
+
resolveDataSourceName,
|
|
178
|
+
dataSourceNamePromise,
|
|
179
|
+
dataSourceName,
|
|
180
|
+
entries,
|
|
181
|
+
register,
|
|
182
|
+
nodeTypes,
|
|
183
|
+
registerNodeType,
|
|
184
|
+
bool,
|
|
185
|
+
nodeResolverCode,
|
|
186
|
+
pgConnectionJson,
|
|
187
|
+
provision,
|
|
188
|
+
}
|
|
189
|
+
/* log Not a pure module */
|