@reventlessdev/reventless-aws 3.0.0-alpha.175 → 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 +22 -0
- package/package.json +10 -10
- package/src/Platform.res +127 -31
- package/src/Platform.res.mjs +226 -44
- package/src/adapter/DcbEventLog/DcbBackend.res +2 -2
- package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream.res +4 -1
- package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream.res.mjs +1 -1
- package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream_Runtime.res +14 -4
- package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream_Runtime.res.mjs +19 -14
- package/src/adapter/EventLog/EventLogBackend.res +82 -0
- package/src/adapter/EventLog/EventLogBackend.res.mjs +53 -0
- package/src/adapter/EventLog/EventLogStorage.res +15 -0
- package/src/adapter/EventLog/EventLogStorage.res.mjs +20 -1
- package/src/adapter/EventLog/EventLogStorage_Postgres.res +37 -0
- package/src/adapter/EventLog/EventLogStorage_Postgres.res.mjs +21 -0
- package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res +26 -11
- package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs +15 -6
- package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res +54 -0
- package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res.mjs +23 -0
- package/src/adapter/Postgres/PgProjectionFeed.res +100 -0
- package/src/adapter/Postgres/PgProjectionFeed.res.mjs +54 -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 +335 -0
- package/src/adapter/QueryDb/PgQueryResolver_Lambda.res.mjs +252 -0
- package/src/adapter/QueryDb/QueryDbBackend.res +52 -0
- package/src/adapter/QueryDb/QueryDbBackend.res.mjs +44 -0
- package/src/adapter/QueryDb/QueryDbResolvers.res +49 -0
- package/src/adapter/QueryDb/QueryDbResolvers.res.mjs +20 -1
- package/src/adapter/QueryDb/QueryDbResolvers_Lambda.res +264 -0
- package/src/adapter/QueryDb/QueryDbResolvers_Lambda.res.mjs +164 -0
- package/src/adapter/QueryDb/QueryDbStorage.res +44 -0
- package/src/adapter/QueryDb/QueryDbStorage.res.mjs +34 -1
- package/src/adapter/QueryDb/QueryDbStorage_Postgres.res +47 -0
- package/src/adapter/QueryDb/QueryDbStorage_Postgres.res.mjs +21 -0
- package/src/adapter/QueryDb/QueryDbStorage_Postgres_Runtime.res +19 -0
- package/src/adapter/QueryDb/QueryDbStorage_Postgres_Runtime.res.mjs +15 -0
- package/src/adapter/QueryEngine/QueryEngine_DynamoDb.res +12 -1
- package/src/adapter/QueryEngine/QueryEngine_DynamoDb.res.mjs +2 -1
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res +83 -3
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res.mjs +46 -3
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res +83 -3
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res.mjs +48 -3
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res +130 -5
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res.mjs +66 -6
- package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.mjs +15 -10
- package/src/adapter/Runtime/PgQueryResolverEntryPoint.mjs +88 -0
- package/src/adapter/Runtime/ReadModelEntryPoint.mjs +32 -15
- package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +26 -12
- package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res +138 -8
- package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res.mjs +76 -11
- package/src/components/Aggregate_Builder_Single.res +12 -5
- package/src/components/Aggregate_Builder_Single.res.mjs +12 -6
- package/src/components/Aggregate_Builder_Single_Async.res +12 -5
- package/src/components/Aggregate_Builder_Single_Async.res.mjs +12 -6
- package/src/components/ReadModel_Builder_NoResolver.res.mjs +1 -1
- package/src/components/ReadModel_Builder_NoResolver_Stream.res +13 -4
- package/src/components/ReadModel_Builder_NoResolver_Stream.res.mjs +14 -9
- package/src/components/ReadModel_Builder_Single.res +14 -5
- package/src/components/ReadModel_Builder_Single.res.mjs +15 -12
- package/src/components/ReadModel_Builder_Single_Stream.res +14 -5
- package/src/components/ReadModel_Builder_Single_Stream.res.mjs +15 -12
- package/src/components/StateViewSlice_Builder.res +10 -4
- package/src/components/StateViewSlice_Builder.res.mjs +8 -10
- package/src/components/StateViewSlice_Builder_Stream.res +10 -4
- package/src/components/StateViewSlice_Builder_Stream.res.mjs +8 -10
- package/src/plugin/runtime/PluginRuntime_Builder.res +12 -0
- package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +4 -0
- package/tests/EventCollectorChannelStreamRuntimeTest.res +55 -0
- package/tests/EventCollectorChannelStreamRuntimeTest.res.mjs +78 -0
- package/tests/PgChangeFeedRelay_IntegrationTest.res +115 -4
- package/tests/PgChangeFeedRelay_IntegrationTest.res.mjs +84 -0
- package/tests/PgPipeline_IntegrationTest.res +208 -0
- package/tests/PgPipeline_IntegrationTest.res.mjs +260 -0
- package/tests/PgQueryResolver_LambdaTest.res +308 -0
- package/tests/PgQueryResolver_LambdaTest.res.mjs +392 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Aws from "@pulumi/aws";
|
|
4
|
+
import * as SQS_Queue$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/SQS/SQS_Queue.res.mjs";
|
|
5
|
+
import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
|
|
6
|
+
import * as Util_DeadLetterQueue$ReventlessAws from "../../util/Util_DeadLetterQueue.res.mjs";
|
|
7
|
+
import * as Util_EventSourceMapping$ReventlessAws from "../../util/Util_EventSourceMapping.res.mjs";
|
|
8
|
+
|
|
9
|
+
let feedQueues = [];
|
|
10
|
+
|
|
11
|
+
function getFeedQueues() {
|
|
12
|
+
return feedQueues;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function makeQueue(name, scope, includeClassic, includeDcb, opts) {
|
|
16
|
+
let queue = new (Aws.sqs.Queue)(name, {
|
|
17
|
+
redrivePolicy: Util_DeadLetterQueue$ReventlessAws.queue.arn.apply(dlqArn => SQS_Queue$PulumiAws.RedrivePolicy.make(dlqArn, 5)),
|
|
18
|
+
visibilityTimeoutSeconds: 120,
|
|
19
|
+
sqsManagedSseEnabled: false
|
|
20
|
+
}, opts);
|
|
21
|
+
feedQueues.push({
|
|
22
|
+
scope: scope,
|
|
23
|
+
includeClassic: includeClassic,
|
|
24
|
+
includeDcb: includeDcb,
|
|
25
|
+
url: queue.id,
|
|
26
|
+
arn: queue.arn
|
|
27
|
+
});
|
|
28
|
+
return queue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function connect(name, queue, lambda, lambdaRole, opts) {
|
|
32
|
+
Util_EventSourceMapping$ReventlessAws.subscribeSqs(lambda, name, queue, undefined, opts);
|
|
33
|
+
queue.arn.apply(queueArn => new (Aws.iam.RolePolicy)(name + `Receive`, {
|
|
34
|
+
policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + `ReceivePolicy`, [{
|
|
35
|
+
Sid: "AllowReceiveFeedQueue",
|
|
36
|
+
Effect: "Allow",
|
|
37
|
+
Action: [
|
|
38
|
+
"sqs:ReceiveMessage",
|
|
39
|
+
"sqs:DeleteMessage",
|
|
40
|
+
"sqs:GetQueueAttributes"
|
|
41
|
+
],
|
|
42
|
+
Resource: queueArn
|
|
43
|
+
}])),
|
|
44
|
+
role: lambdaRole.id
|
|
45
|
+
}, opts));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export {
|
|
49
|
+
feedQueues,
|
|
50
|
+
getFeedQueues,
|
|
51
|
+
makeQueue,
|
|
52
|
+
connect,
|
|
53
|
+
}
|
|
54
|
+
/* @pulumi/aws Not a pure module */
|
|
@@ -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 */
|