@reventlessdev/reventless-aws 3.0.0-alpha.176 → 3.0.0-alpha.178
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 +23 -0
- package/docker-compose.postgres.yml +21 -0
- package/package.json +7 -6
- package/scripts/run-pg-integration-tests.sh +58 -0
- package/src/Platform.res +49 -0
- package/src/Platform.res.mjs +48 -2
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +7 -1
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +10 -1
- package/src/adapter/Api/Platform_UIFragments_Lambda.res +7 -1
- package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +10 -1
- package/src/adapter/Counter/CounterHandler_DynamoDbStream.res +6 -21
- package/src/adapter/Counter/CounterHandler_DynamoDbStream.res.mjs +2 -17
- package/src/adapter/DcbEventLog/DcbBackend.res +4 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Postgres.res +2 -2
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Postgres.res.mjs +7 -1
- package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res +1 -11
- package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs +2 -24
- package/src/adapter/Postgres/PgConnection.res +70 -10
- package/src/adapter/Postgres/PgConnection.res.mjs +48 -3
- package/src/adapter/Postgres/PgMigration_Builder.res +127 -0
- package/src/adapter/Postgres/PgMigration_Builder.res.mjs +51 -0
- package/src/adapter/QueryDb/PgQueryResolver_Builder.res +279 -0
- package/src/adapter/QueryDb/PgQueryResolver_Builder.res.mjs +170 -0
- package/src/adapter/QueryDb/PgQueryResolver_Lambda.res +202 -11
- package/src/adapter/QueryDb/PgQueryResolver_Lambda.res.mjs +150 -9
- package/src/adapter/QueryDb/QueryDbBackend.res +8 -0
- package/src/adapter/QueryDb/QueryDbBackend.res.mjs +4 -1
- package/src/adapter/QueryDb/QueryDbResolvers.res +6 -5
- package/src/adapter/QueryDb/QueryDbResolvers.res.mjs +6 -3
- package/src/adapter/QueryDb/QueryDbResolvers_Lambda.res +288 -0
- package/src/adapter/QueryDb/QueryDbResolvers_Lambda.res.mjs +170 -0
- package/src/adapter/QueryDb/QueryDbStorage.res +6 -1
- package/src/adapter/QueryDb/QueryDbStorage.res.mjs +2 -1
- package/src/adapter/QueryDb/QueryDbStorage_Postgres.res +5 -1
- package/src/adapter/QueryDb/QueryDbStorage_Postgres.res.mjs +3 -3
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res +3 -14
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res.mjs +2 -25
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res +3 -14
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res.mjs +2 -25
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +7 -1
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res +78 -15
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res.mjs +43 -26
- package/src/adapter/Runtime/PgMigrationEntryPoint.mjs +40 -0
- package/src/adapter/Runtime/PgQueryResolverEntryPoint.mjs +88 -0
- package/src/adapter/Runtime/ReadModelEntryPoint.mjs +17 -5
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +10 -0
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +3 -0
- package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res +9 -14
- package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +2 -25
- package/src/adapter/Runtime/StateTopicPublish.mjs +204 -0
- package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +17 -3
- package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res +86 -17
- package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res.mjs +50 -33
- package/src/adapter/StateTopic/StateTopic_AppSync.res +7 -1
- package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +10 -1
- package/src/plugin/cloner/ClonerRunner_Fargate.res +7 -1
- package/src/plugin/cloner/ClonerRunner_Fargate.res.mjs +10 -1
- package/src/util/Util_Bundle.res +71 -1
- package/src/util/Util_Bundle.res.mjs +49 -1
- package/tests/PgChangeFeedRelay_IntegrationTest.res +3 -3
- package/tests/PgConnectionJsonTest.res +84 -0
- package/tests/PgConnectionJsonTest.res.mjs +68 -0
- package/tests/PgMigrationEntryPoint_IntegrationTest.res +82 -0
- package/tests/PgMigrationEntryPoint_IntegrationTest.res.mjs +63 -0
- package/tests/PgPipeline_IntegrationTest.res +3 -1
- package/tests/PgQueryResolver_LambdaTest.res +163 -2
- package/tests/PgQueryResolver_LambdaTest.res.mjs +226 -14
- package/tests/StateTopicPublishTest.res +162 -0
- package/tests/StateTopicPublishTest.res.mjs +158 -0
- package/tests/Util_BundleEsmLoaderTest.res +57 -0
- package/tests/Util_BundleEsmLoaderTest.res.mjs +44 -0
|
@@ -32,6 +32,8 @@ function isPostgres() {
|
|
|
32
32
|
return Stdlib_Option.isSome(selectionRef.contents);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
let postgresStreamRegistry = new Set();
|
|
36
|
+
|
|
35
37
|
export {
|
|
36
38
|
selectionRef,
|
|
37
39
|
set,
|
|
@@ -40,5 +42,6 @@ export {
|
|
|
40
42
|
exempt,
|
|
41
43
|
isPostgresFor,
|
|
42
44
|
isPostgres,
|
|
45
|
+
postgresStreamRegistry,
|
|
43
46
|
}
|
|
44
|
-
/*
|
|
47
|
+
/* postgresStreamRegistry Not a pure module */
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
module AppSync = QueryDbResolvers_AppSync
|
|
2
2
|
module NoOp = QueryDbResolvers_NoOp
|
|
3
|
+
module Lambda = QueryDbResolvers_Lambda
|
|
3
4
|
|
|
4
|
-
// B3.
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// B3.2b: Postgres-backed read models have no DynamoDB data source — their
|
|
6
|
+
// GraphQL Query fields are served by the shared PgQueryResolver Lambda data
|
|
7
|
+
// source (`QueryDbResolvers_Lambda`, Invoke templates → PgQueryResolver_Lambda).
|
|
8
|
+
// DynamoDB-backed (incl. admin-exempt) read models keep the direct resolver set.
|
|
8
9
|
module Selectable = {
|
|
9
10
|
type api = QueryDbResolvers_AppSync_NoOp.api
|
|
10
11
|
type role = QueryDbResolvers_AppSync_NoOp.role
|
|
@@ -21,7 +22,7 @@ module Selectable = {
|
|
|
21
22
|
~opts,
|
|
22
23
|
) =>
|
|
23
24
|
if QueryDbBackend.isPostgresFor(name) {
|
|
24
|
-
|
|
25
|
+
QueryDbResolvers_Lambda.make(
|
|
25
26
|
~name,
|
|
26
27
|
~api,
|
|
27
28
|
~apiRole,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as QueryDbBackend$ReventlessAws from "./QueryDbBackend.res.mjs";
|
|
4
|
+
import * as QueryDbResolvers_Lambda$ReventlessAws from "./QueryDbResolvers_Lambda.res.mjs";
|
|
4
5
|
import * as QueryDbResolvers_AppSync$ReventlessAws from "./QueryDbResolvers_AppSync.res.mjs";
|
|
5
|
-
import * as QueryDbResolvers_AppSync_NoOp$ReventlessAws from "./QueryDbResolvers_AppSync_NoOp.res.mjs";
|
|
6
6
|
|
|
7
7
|
function make(name, api, apiRole, dataSourceName, indexes, subIdField, idResolverConfigs, idsResolverConfigs, authorization, opts) {
|
|
8
8
|
if (QueryDbBackend$ReventlessAws.isPostgresFor(name)) {
|
|
9
|
-
return
|
|
9
|
+
return QueryDbResolvers_Lambda$ReventlessAws.make(name, api, apiRole, dataSourceName, indexes, subIdField, idResolverConfigs, idsResolverConfigs, authorization, opts);
|
|
10
10
|
} else {
|
|
11
11
|
return QueryDbResolvers_AppSync$ReventlessAws.make(name, api, apiRole, dataSourceName, indexes, subIdField, idResolverConfigs, idsResolverConfigs, authorization, opts);
|
|
12
12
|
}
|
|
@@ -20,9 +20,12 @@ let AppSync;
|
|
|
20
20
|
|
|
21
21
|
let NoOp;
|
|
22
22
|
|
|
23
|
+
let Lambda;
|
|
24
|
+
|
|
23
25
|
export {
|
|
24
26
|
AppSync,
|
|
25
27
|
NoOp,
|
|
28
|
+
Lambda,
|
|
26
29
|
Selectable,
|
|
27
30
|
}
|
|
28
|
-
/*
|
|
31
|
+
/* QueryDbBackend-ReventlessAws Not a pure module */
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
// AppSync resolvers for Postgres-backed read models (B3.2b) — the Lambda-data-
|
|
2
|
+
// source parallel to QueryDbResolvers_AppSync's direct DynamoDB resolvers.
|
|
3
|
+
//
|
|
4
|
+
// Every Query field becomes a thin APPSYNC_JS unit resolver whose `Invoke`
|
|
5
|
+
// template carries { readModelName, kind, index?, arguments, identity } to the
|
|
6
|
+
// ONE shared PgQueryResolver Lambda (PgQueryResolver_Builder), which dispatches
|
|
7
|
+
// via PgQueryResolver_Lambda.dispatch. `dataSourceName` is the shared data
|
|
8
|
+
// source's (deferred) name, the same for every Postgres read model.
|
|
9
|
+
//
|
|
10
|
+
// Field names / includeIdParam / connectionSpec come from the same registry the
|
|
11
|
+
// AppSync path reads, so the SDL emitted by GraphQL_FragmentGenerator stays in
|
|
12
|
+
// lockstep. Kinds covered: getById, list (connection), index, byIds. Deferred to
|
|
13
|
+
// B3.2c: items (sub-id connection), @resolves/@resolvesMany, node, auth-table.
|
|
14
|
+
|
|
15
|
+
module Resolver = AppSync_Resolver_Retrying
|
|
16
|
+
open Reventless.ReadModel
|
|
17
|
+
|
|
18
|
+
type api = Types.AppSync.api
|
|
19
|
+
type role = Types.AppSync.role
|
|
20
|
+
|
|
21
|
+
// Identity block shared by every Invoke template (Cognito vs IAM), matching the
|
|
22
|
+
// shape PgQueryResolver_Lambda decodes (Reventless.Identity.t).
|
|
23
|
+
let identityBlock = `id != null && id.sub != null
|
|
24
|
+
? { userId: id.sub, username: id.username, groups: id.claims?.['cognito:groups'] ?? [], claims: id.claims, provider: 'Cognito' }
|
|
25
|
+
: id != null
|
|
26
|
+
? { userArn: id.userArn ?? null, accountId: id.accountId ?? null, username: id.username ?? null, provider: 'IAM' }
|
|
27
|
+
: null`
|
|
28
|
+
|
|
29
|
+
let invokeTemplate = (
|
|
30
|
+
~readModelName: string,
|
|
31
|
+
~kind: string,
|
|
32
|
+
~index: option<string>=?,
|
|
33
|
+
~authTable: option<string>=?,
|
|
34
|
+
~authGroup: option<string>=?,
|
|
35
|
+
) => {
|
|
36
|
+
let indexFrag = switch index {
|
|
37
|
+
| Some(ix) => `\n index: '${ix}',`
|
|
38
|
+
| None => ""
|
|
39
|
+
}
|
|
40
|
+
let authFrag = switch (authTable, authGroup) {
|
|
41
|
+
| (Some(t), Some(g)) => `\n authTable: '${t}',\n authGroup: '${g}',`
|
|
42
|
+
| _ => ""
|
|
43
|
+
}
|
|
44
|
+
`import { util } from '@aws-appsync/utils';
|
|
45
|
+
export function request(ctx) {
|
|
46
|
+
const id = ctx.identity;
|
|
47
|
+
return {
|
|
48
|
+
operation: 'Invoke',
|
|
49
|
+
payload: {
|
|
50
|
+
readModelName: '${readModelName}',
|
|
51
|
+
kind: '${kind}',${indexFrag}${authFrag}
|
|
52
|
+
arguments: ctx.args,
|
|
53
|
+
identity: ${identityBlock}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function response(ctx) {
|
|
58
|
+
if (ctx.error) util.error(ctx.error.message, ctx.error.type);
|
|
59
|
+
return ctx.result;
|
|
60
|
+
}
|
|
61
|
+
`->Pulumi.Input.make
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Cross-table field resolver (@resolves/@resolvesMany, B3.2c): on a parent
|
|
65
|
+
// entity type's field, carrying the parent object (ctx.source) + baked target
|
|
66
|
+
// metadata (`extra`). `sourceType` is the parent read model (used only for auth
|
|
67
|
+
// scoping in dispatch); `target` is the target read model's binding key.
|
|
68
|
+
let invokeFieldTemplate = (~sourceType: string, ~kind: string, ~target: string, ~extra: string) =>
|
|
69
|
+
`import { util } from '@aws-appsync/utils';
|
|
70
|
+
export function request(ctx) {
|
|
71
|
+
const id = ctx.identity;
|
|
72
|
+
return {
|
|
73
|
+
operation: 'Invoke',
|
|
74
|
+
payload: {
|
|
75
|
+
readModelName: '${sourceType}',
|
|
76
|
+
kind: '${kind}',
|
|
77
|
+
target: '${target}',
|
|
78
|
+
source: ctx.source,
|
|
79
|
+
arguments: ctx.args,${extra}
|
|
80
|
+
identity: ${identityBlock}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export function response(ctx) {
|
|
85
|
+
if (ctx.error) util.error(ctx.error.message, ctx.error.type);
|
|
86
|
+
return ctx.result;
|
|
87
|
+
}
|
|
88
|
+
`->Pulumi.Input.make
|
|
89
|
+
|
|
90
|
+
let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
|
|
91
|
+
~name: string,
|
|
92
|
+
~api: api,
|
|
93
|
+
~apiRole as _: role,
|
|
94
|
+
~dataSourceName,
|
|
95
|
+
~indexes: array<indexConfig>,
|
|
96
|
+
~subIdField,
|
|
97
|
+
~idResolverConfigs: array<idResolverConfig>,
|
|
98
|
+
~idsResolverConfigs: array<idsResolverConfig>,
|
|
99
|
+
~authorization as _: Reventless.Authorization.permission,
|
|
100
|
+
~opts,
|
|
101
|
+
) => {
|
|
102
|
+
let dataSourceName = dataSourceName->Pulumi.Output.asInput
|
|
103
|
+
let name = name->String.capitalize
|
|
104
|
+
let registryEntry = ReventlessCore.Plugin_Helpers.queryFieldNamesRegistry->Dict.get(name)
|
|
105
|
+
|
|
106
|
+
let fieldNameForSingle = switch registryEntry {
|
|
107
|
+
| Some({singleFieldName}) => singleFieldName
|
|
108
|
+
| None => name->Resolver.Functions.uncapitalize
|
|
109
|
+
}
|
|
110
|
+
let includeIdParam = switch registryEntry {
|
|
111
|
+
| Some({includeIdParam}) => includeIdParam
|
|
112
|
+
| None => true
|
|
113
|
+
}
|
|
114
|
+
let connectionSpec = switch registryEntry {
|
|
115
|
+
| Some({connectionSpec}) => connectionSpec
|
|
116
|
+
| None => true
|
|
117
|
+
}
|
|
118
|
+
let fieldNameForAll = switch registryEntry {
|
|
119
|
+
| Some({listFieldName}) => listFieldName
|
|
120
|
+
| None => name ++ "s"
|
|
121
|
+
}
|
|
122
|
+
let labelField = switch registryEntry {
|
|
123
|
+
| Some({labelField: ?lf}) => lf->Option.getOr("id")
|
|
124
|
+
| None => "id"
|
|
125
|
+
}
|
|
126
|
+
let returnTypeName = switch registryEntry {
|
|
127
|
+
| Some({returnTypeName: rt}) => rt
|
|
128
|
+
| None => name
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Register the binding info the shared Lambda's env config needs (the entry
|
|
132
|
+
// point gets indexes/subIdField/schema from the spec module; labelField and
|
|
133
|
+
// includeIdParam come from this deploy-time registry). Keyed by spec name.
|
|
134
|
+
PgQueryResolver_Builder.register({readModelName: name, labelField, includeIdParam})
|
|
135
|
+
|
|
136
|
+
// Relay node type → this read model (for the shared node(id) resolver, B3.2c).
|
|
137
|
+
// Only entities addressable by id participate in node resolution.
|
|
138
|
+
if includeIdParam {
|
|
139
|
+
PgQueryResolver_Builder.registerNodeType(~typeName=returnTypeName, ~readModelName=name)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let mkResolver = (~resolverName, ~field, ~kind, ~index=?, ~authTable=?, ~authGroup=?) =>
|
|
143
|
+
Resolver.makeUnitJsResolver(
|
|
144
|
+
~name=resolverName,
|
|
145
|
+
~api,
|
|
146
|
+
~dataSourceName,
|
|
147
|
+
~type_="Query"->Pulumi.Input.make,
|
|
148
|
+
~field=field->Pulumi.Input.make,
|
|
149
|
+
~code=invokeTemplate(~readModelName=name, ~kind, ~index?, ~authTable?, ~authGroup?),
|
|
150
|
+
~opts,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
let stripLeadingBy = s =>
|
|
154
|
+
if s->String.startsWith("by") && s->String.length > 2 {
|
|
155
|
+
s->String.slice(~start=2, ~end=s->String.length)
|
|
156
|
+
} else {
|
|
157
|
+
s
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Resolvers are deferred into resourcesMaker (created inside the schema-pushed
|
|
161
|
+
// builderOutputs.apply, like the AppSync path) — so they only exist after the
|
|
162
|
+
// fields are ACTIVE and after PgQueryResolver_Builder.provision has resolved
|
|
163
|
+
// the shared data source name.
|
|
164
|
+
let resourcesMaker: ReventlessCore.QueryDb.resolversResourcesMaker = _allQueryDbs => {
|
|
165
|
+
// getById (or listAll when the read model has no id param, e.g. singletons).
|
|
166
|
+
let byId = mkResolver(
|
|
167
|
+
~resolverName=fieldNameForSingle->String.capitalize,
|
|
168
|
+
~field=fieldNameForSingle,
|
|
169
|
+
~kind=includeIdParam ? "getById" : "list",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
// Main list — Relay connection (or legacy list when connectionSpec=false).
|
|
173
|
+
let all = mkResolver(
|
|
174
|
+
~resolverName=fieldNameForAll->String.capitalize,
|
|
175
|
+
~field=fieldNameForAll,
|
|
176
|
+
~kind=connectionSpec ? "list" : "list",
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
// Sub-id connection — {single}Items(id, filter, …); only when subId configured.
|
|
180
|
+
let items = switch subIdField {
|
|
181
|
+
| Some(_) => [
|
|
182
|
+
mkResolver(
|
|
183
|
+
~resolverName=fieldNameForSingle->String.capitalize ++ "Items",
|
|
184
|
+
~field=fieldNameForSingle ++ "Items",
|
|
185
|
+
~kind="items",
|
|
186
|
+
),
|
|
187
|
+
]
|
|
188
|
+
| None => []
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Batched-by-ids — single-key projections only (mirrors the SDL).
|
|
192
|
+
let byIds = if includeIdParam && subIdField === None {
|
|
193
|
+
let byIdsField = fieldNameForAll ++ "ByIds"
|
|
194
|
+
[
|
|
195
|
+
mkResolver(
|
|
196
|
+
~resolverName=byIdsField->String.capitalize,
|
|
197
|
+
~field=byIdsField,
|
|
198
|
+
~kind="byIds",
|
|
199
|
+
),
|
|
200
|
+
]
|
|
201
|
+
} else {
|
|
202
|
+
[]
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Per-index equality queries: {single}By{Index}. A group-restricted index
|
|
206
|
+
// (indexConfig.authorization) bakes the auth-table pipeline params — the
|
|
207
|
+
// Lambda verifies group membership + ownership against the auth read model.
|
|
208
|
+
let byIndex = indexes->Array.map((ic: indexConfig) => {
|
|
209
|
+
let index = ic.index
|
|
210
|
+
let resolverName =
|
|
211
|
+
fieldNameForSingle->String.capitalize ++ "By" ++ index->stripLeadingBy->String.capitalize
|
|
212
|
+
let field = fieldNameForSingle ++ "By" ++ index->stripLeadingBy->String.capitalize
|
|
213
|
+
switch ic.authorization {
|
|
214
|
+
| Some({tableName, group}) =>
|
|
215
|
+
mkResolver(
|
|
216
|
+
~resolverName,
|
|
217
|
+
~field,
|
|
218
|
+
~kind="index",
|
|
219
|
+
~index,
|
|
220
|
+
~authTable=tableName->String.capitalize,
|
|
221
|
+
~authGroup=group,
|
|
222
|
+
)
|
|
223
|
+
| None => mkResolver(~resolverName, ~field, ~kind="index", ~index)
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
// @resolves — single cross-table field resolver on this (parent) type. The
|
|
228
|
+
// target's binding key is its capitalized spec name (mirrors how the binding
|
|
229
|
+
// registry is keyed); the parent object flows via ctx.source.
|
|
230
|
+
let idResolvers = idResolverConfigs->Array.map(config => {
|
|
231
|
+
let {source: {idField, subId, resolvedField}, target} = config
|
|
232
|
+
let targetKey = target.tableName->String.capitalize
|
|
233
|
+
let (field, multi) = switch resolvedField {
|
|
234
|
+
| Single(f) => (f, false)
|
|
235
|
+
| Multi(f) => (f, true)
|
|
236
|
+
}
|
|
237
|
+
let targetFrag = switch target.idField {
|
|
238
|
+
| Index(ix) => `\n targetIndex: '${ix}',\n targetIndexIdField: '${ix}',`
|
|
239
|
+
| IndexWithId(ix, idf) => `\n targetIndex: '${ix}',\n targetIndexIdField: '${idf}',`
|
|
240
|
+
| Id => ""
|
|
241
|
+
}
|
|
242
|
+
let subIdFrag = switch subId {
|
|
243
|
+
| Field(f) => `\n sourceSubId: { kind: 'field', name: '${f}' },`
|
|
244
|
+
| Argument(a) => `\n sourceSubId: { kind: 'arg', name: '${a}' },`
|
|
245
|
+
| NoSubId => ""
|
|
246
|
+
}
|
|
247
|
+
let extra =
|
|
248
|
+
`\n sourceIdField: '${idField}',\n multi: ${multi ? "true" : "false"},` ++
|
|
249
|
+
targetFrag ++
|
|
250
|
+
subIdFrag
|
|
251
|
+
Resolver.makeUnitJsResolver(
|
|
252
|
+
~name=name ++ field->String.capitalize,
|
|
253
|
+
~api,
|
|
254
|
+
~dataSourceName,
|
|
255
|
+
~type_=name->Pulumi.Input.make,
|
|
256
|
+
~field=field->Pulumi.Input.make,
|
|
257
|
+
~code=invokeFieldTemplate(~sourceType=name, ~kind="resolveOne", ~target=targetKey, ~extra),
|
|
258
|
+
~opts,
|
|
259
|
+
)
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
// @resolvesMany — batch cross-table field resolver (BatchGet by ids).
|
|
263
|
+
let idsResolvers = idsResolverConfigs->Array.map(config => {
|
|
264
|
+
let {source: {idsField, resolvedField}, target} = config
|
|
265
|
+
let targetKey = target.tableName->String.capitalize
|
|
266
|
+
let extra = `\n sourceIdsField: '${idsField}',`
|
|
267
|
+
Resolver.makeUnitJsResolver(
|
|
268
|
+
~name=name ++ resolvedField->String.capitalize,
|
|
269
|
+
~api,
|
|
270
|
+
~dataSourceName,
|
|
271
|
+
~type_=name->Pulumi.Input.make,
|
|
272
|
+
~field=resolvedField->Pulumi.Input.make,
|
|
273
|
+
~code=invokeFieldTemplate(~sourceType=name, ~kind="resolveMany", ~target=targetKey, ~extra),
|
|
274
|
+
~opts,
|
|
275
|
+
)
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
[byId, all]
|
|
279
|
+
->Array.concat(items)
|
|
280
|
+
->Array.concat(byIds)
|
|
281
|
+
->Array.concat(byIndex)
|
|
282
|
+
->Array.concat(idResolvers)
|
|
283
|
+
->Array.concat(idsResolvers)
|
|
284
|
+
->Array.map(Util.AppSync.toResourceNative)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
{ReventlessCore.QueryDb_Adapter.resources: [], resourcesMaker}
|
|
288
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
|
+
import * as Stdlib_String from "@rescript/runtime/lib/es6/Stdlib_String.js";
|
|
5
|
+
import * as Util_AppSync$ReventlessAws from "../../util/Util_AppSync.res.mjs";
|
|
6
|
+
import * as Plugin_Helpers$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin_Helpers.res.mjs";
|
|
7
|
+
import * as AppSync_Resolver_Functions$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/AppSync/AppSync_Resolver_Functions.res.mjs";
|
|
8
|
+
import * as PgQueryResolver_Builder$ReventlessAws from "./PgQueryResolver_Builder.res.mjs";
|
|
9
|
+
import * as AppSync_Resolver_Retrying$ReventlessAws from "../Api/AppSync_Resolver_Retrying.res.mjs";
|
|
10
|
+
|
|
11
|
+
let identityBlock = `id != null && id.sub != null
|
|
12
|
+
? { userId: id.sub, username: id.username, groups: id.claims?.['cognito:groups'] ?? [], claims: id.claims, provider: 'Cognito' }
|
|
13
|
+
: id != null
|
|
14
|
+
? { userArn: id.userArn ?? null, accountId: id.accountId ?? null, username: id.username ?? null, provider: 'IAM' }
|
|
15
|
+
: null`;
|
|
16
|
+
|
|
17
|
+
function invokeTemplate(readModelName, kind, index, authTable, authGroup) {
|
|
18
|
+
let indexFrag = index !== undefined ? `\n index: '` + index + `',` : "";
|
|
19
|
+
let authFrag = authTable !== undefined && authGroup !== undefined ? `\n authTable: '` + authTable + `',\n authGroup: '` + authGroup + `',` : "";
|
|
20
|
+
return `import { util } from '@aws-appsync/utils';
|
|
21
|
+
export function request(ctx) {
|
|
22
|
+
const id = ctx.identity;
|
|
23
|
+
return {
|
|
24
|
+
operation: 'Invoke',
|
|
25
|
+
payload: {
|
|
26
|
+
readModelName: '` + readModelName + `',
|
|
27
|
+
kind: '` + kind + `',` + indexFrag + authFrag + `
|
|
28
|
+
arguments: ctx.args,
|
|
29
|
+
identity: ` + identityBlock + `
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function response(ctx) {
|
|
34
|
+
if (ctx.error) util.error(ctx.error.message, ctx.error.type);
|
|
35
|
+
return ctx.result;
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function invokeFieldTemplate(sourceType, kind, target, extra) {
|
|
41
|
+
return `import { util } from '@aws-appsync/utils';
|
|
42
|
+
export function request(ctx) {
|
|
43
|
+
const id = ctx.identity;
|
|
44
|
+
return {
|
|
45
|
+
operation: 'Invoke',
|
|
46
|
+
payload: {
|
|
47
|
+
readModelName: '` + sourceType + `',
|
|
48
|
+
kind: '` + kind + `',
|
|
49
|
+
target: '` + target + `',
|
|
50
|
+
source: ctx.source,
|
|
51
|
+
arguments: ctx.args,` + extra + `
|
|
52
|
+
identity: ` + identityBlock + `
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function response(ctx) {
|
|
57
|
+
if (ctx.error) util.error(ctx.error.message, ctx.error.type);
|
|
58
|
+
return ctx.result;
|
|
59
|
+
}
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function make(name, api, param, dataSourceName, indexes, subIdField, idResolverConfigs, idsResolverConfigs, param$1, opts) {
|
|
64
|
+
let name$1 = Stdlib_String.capitalize(name);
|
|
65
|
+
let registryEntry = Plugin_Helpers$ReventlessCore.queryFieldNamesRegistry[name$1];
|
|
66
|
+
let fieldNameForSingle = registryEntry !== undefined ? registryEntry.singleFieldName : AppSync_Resolver_Functions$PulumiAws.uncapitalize(name$1);
|
|
67
|
+
let includeIdParam = registryEntry !== undefined ? registryEntry.includeIdParam : true;
|
|
68
|
+
let fieldNameForAll = registryEntry !== undefined ? registryEntry.listFieldName : name$1 + "s";
|
|
69
|
+
let labelField = registryEntry !== undefined ? Stdlib_Option.getOr(registryEntry.labelField, "id") : "id";
|
|
70
|
+
let returnTypeName = registryEntry !== undefined ? registryEntry.returnTypeName : name$1;
|
|
71
|
+
PgQueryResolver_Builder$ReventlessAws.register({
|
|
72
|
+
readModelName: name$1,
|
|
73
|
+
labelField: labelField,
|
|
74
|
+
includeIdParam: includeIdParam
|
|
75
|
+
});
|
|
76
|
+
if (includeIdParam) {
|
|
77
|
+
PgQueryResolver_Builder$ReventlessAws.registerNodeType(returnTypeName, name$1);
|
|
78
|
+
}
|
|
79
|
+
let mkResolver = (resolverName, field, kind, index, authTable, authGroup) => AppSync_Resolver_Retrying$ReventlessAws.makeUnitJsResolver(resolverName, api, dataSourceName, "Query", field, invokeTemplate(name$1, kind, index, authTable, authGroup), opts);
|
|
80
|
+
let stripLeadingBy = s => {
|
|
81
|
+
if (s.startsWith("by") && s.length > 2) {
|
|
82
|
+
return s.slice(2, s.length);
|
|
83
|
+
} else {
|
|
84
|
+
return s;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
let resourcesMaker = _allQueryDbs => {
|
|
88
|
+
let byId = mkResolver(Stdlib_String.capitalize(fieldNameForSingle), fieldNameForSingle, includeIdParam ? "getById" : "list", undefined, undefined, undefined);
|
|
89
|
+
let all = mkResolver(Stdlib_String.capitalize(fieldNameForAll), fieldNameForAll, "list", undefined, undefined, undefined);
|
|
90
|
+
let items = subIdField !== undefined ? [mkResolver(Stdlib_String.capitalize(fieldNameForSingle) + "Items", fieldNameForSingle + "Items", "items", undefined, undefined, undefined)] : [];
|
|
91
|
+
let byIds;
|
|
92
|
+
if (includeIdParam && subIdField === undefined) {
|
|
93
|
+
let byIdsField = fieldNameForAll + "ByIds";
|
|
94
|
+
byIds = [mkResolver(Stdlib_String.capitalize(byIdsField), byIdsField, "byIds", undefined, undefined, undefined)];
|
|
95
|
+
} else {
|
|
96
|
+
byIds = [];
|
|
97
|
+
}
|
|
98
|
+
let byIndex = indexes.map(ic => {
|
|
99
|
+
let index = ic.index;
|
|
100
|
+
let resolverName = Stdlib_String.capitalize(fieldNameForSingle) + "By" + Stdlib_String.capitalize(stripLeadingBy(index));
|
|
101
|
+
let field = fieldNameForSingle + "By" + Stdlib_String.capitalize(stripLeadingBy(index));
|
|
102
|
+
let match = ic.authorization;
|
|
103
|
+
if (match !== undefined) {
|
|
104
|
+
return mkResolver(resolverName, field, "index", index, Stdlib_String.capitalize(match.tableName), match.group);
|
|
105
|
+
} else {
|
|
106
|
+
return mkResolver(resolverName, field, "index", index, undefined, undefined);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
let idResolvers = idResolverConfigs.map(config => {
|
|
110
|
+
let target = config.target;
|
|
111
|
+
let match = config.source;
|
|
112
|
+
let resolvedField = match.resolvedField;
|
|
113
|
+
let subId = match.subId;
|
|
114
|
+
let targetKey = Stdlib_String.capitalize(target.tableName);
|
|
115
|
+
let match$1;
|
|
116
|
+
match$1 = resolvedField.TAG === "Single" ? [
|
|
117
|
+
resolvedField._0,
|
|
118
|
+
false
|
|
119
|
+
] : [
|
|
120
|
+
resolvedField._0,
|
|
121
|
+
true
|
|
122
|
+
];
|
|
123
|
+
let field = match$1[0];
|
|
124
|
+
let ix = target.idField;
|
|
125
|
+
let targetFrag;
|
|
126
|
+
if (typeof ix !== "object") {
|
|
127
|
+
targetFrag = "";
|
|
128
|
+
} else if (ix.TAG === "Index") {
|
|
129
|
+
let ix$1 = ix._0;
|
|
130
|
+
targetFrag = `\n targetIndex: '` + ix$1 + `',\n targetIndexIdField: '` + ix$1 + `',`;
|
|
131
|
+
} else {
|
|
132
|
+
targetFrag = `\n targetIndex: '` + ix._0 + `',\n targetIndexIdField: '` + ix._1 + `',`;
|
|
133
|
+
}
|
|
134
|
+
let subIdFrag;
|
|
135
|
+
subIdFrag = typeof subId !== "object" ? "" : (
|
|
136
|
+
subId.TAG === "Field" ? `\n sourceSubId: { kind: 'field', name: '` + subId._0 + `' },` : `\n sourceSubId: { kind: 'arg', name: '` + subId._0 + `' },`
|
|
137
|
+
);
|
|
138
|
+
let extra = `\n sourceIdField: '` + match.idField + `',\n multi: ` + (
|
|
139
|
+
match$1[1] ? "true" : "false"
|
|
140
|
+
) + `,` + targetFrag + subIdFrag;
|
|
141
|
+
return AppSync_Resolver_Retrying$ReventlessAws.makeUnitJsResolver(name$1 + Stdlib_String.capitalize(field), api, dataSourceName, name$1, field, invokeFieldTemplate(name$1, "resolveOne", targetKey, extra), opts);
|
|
142
|
+
});
|
|
143
|
+
let idsResolvers = idsResolverConfigs.map(config => {
|
|
144
|
+
let match = config.source;
|
|
145
|
+
let resolvedField = match.resolvedField;
|
|
146
|
+
let targetKey = Stdlib_String.capitalize(config.target.tableName);
|
|
147
|
+
let extra = `\n sourceIdsField: '` + match.idsField + `',`;
|
|
148
|
+
return AppSync_Resolver_Retrying$ReventlessAws.makeUnitJsResolver(name$1 + Stdlib_String.capitalize(resolvedField), api, dataSourceName, name$1, resolvedField, invokeFieldTemplate(name$1, "resolveMany", targetKey, extra), opts);
|
|
149
|
+
});
|
|
150
|
+
return [
|
|
151
|
+
byId,
|
|
152
|
+
all
|
|
153
|
+
].concat(items).concat(byIds).concat(byIndex).concat(idResolvers).concat(idsResolvers).map(Util_AppSync$ReventlessAws.toResourceNative);
|
|
154
|
+
};
|
|
155
|
+
return {
|
|
156
|
+
resources: [],
|
|
157
|
+
resourcesMaker: resourcesMaker
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let Resolver;
|
|
162
|
+
|
|
163
|
+
export {
|
|
164
|
+
Resolver,
|
|
165
|
+
identityBlock,
|
|
166
|
+
invokeTemplate,
|
|
167
|
+
invokeFieldTemplate,
|
|
168
|
+
make,
|
|
169
|
+
}
|
|
170
|
+
/* Util_AppSync-ReventlessAws Not a pure module */
|
|
@@ -9,7 +9,11 @@ module Postgres = QueryDbStorage_Postgres
|
|
|
9
9
|
// platform when a `PgConnection` is supplied — and dispatch per read model:
|
|
10
10
|
// Postgres (no table, no data source) unless the name is admin-exempt. The Stream
|
|
11
11
|
// selector falls back to the streamed DynamoDB maker; on Postgres there is no
|
|
12
|
-
// stream
|
|
12
|
+
// stream, so live updates (B3.3) are published from the projection Lambda after
|
|
13
|
+
// each save/delete instead. SelectableStream records subscription-enabled
|
|
14
|
+
// Postgres read models in `QueryDbBackend.postgresStreamRegistry` (a leaf module,
|
|
15
|
+
// to avoid a build cycle) so the projection-Lambda runtime builder knows which
|
|
16
|
+
// handlers need the AppSync-Events publish wiring.
|
|
13
17
|
module Selectable = {
|
|
14
18
|
type api = QueryDbStorage_Postgres.api
|
|
15
19
|
type role = QueryDbStorage_Postgres.role
|
|
@@ -42,6 +46,7 @@ module SelectableStream = {
|
|
|
42
46
|
~opts,
|
|
43
47
|
) =>
|
|
44
48
|
if QueryDbBackend.isPostgresFor(name) {
|
|
49
|
+
QueryDbBackend.postgresStreamRegistry->Set.add(name)
|
|
45
50
|
Postgres.make(~name, ~indexes, ~subIdField?, ~ttl?, ~api, ~apiRole, ~opts)
|
|
46
51
|
} else {
|
|
47
52
|
DynamoDbStream.make(~name, ~indexes, ~subIdField?, ~ttl?, ~api, ~apiRole, ~opts)
|
|
@@ -19,6 +19,7 @@ let Selectable = {
|
|
|
19
19
|
|
|
20
20
|
function make$1(name, indexes, subIdField, ttl, api, apiRole, opts) {
|
|
21
21
|
if (QueryDbBackend$ReventlessAws.isPostgresFor(name)) {
|
|
22
|
+
QueryDbBackend$ReventlessAws.postgresStreamRegistry.add(name);
|
|
22
23
|
return QueryDbStorage_Postgres$ReventlessAws.make(name, indexes, subIdField, ttl, api, apiRole, opts);
|
|
23
24
|
} else {
|
|
24
25
|
return QueryDbStorage_DynamoDbStream$ReventlessAws.make(name, indexes, subIdField, ttl, api, apiRole, opts);
|
|
@@ -48,4 +49,4 @@ export {
|
|
|
48
49
|
Selectable,
|
|
49
50
|
SelectableStream,
|
|
50
51
|
}
|
|
51
|
-
/*
|
|
52
|
+
/* QueryDbBackend-ReventlessAws Not a pure module */
|
|
@@ -37,7 +37,11 @@ let make: ReventlessCore.QueryDb_Adapter.storageMaker<api, role> = (
|
|
|
37
37
|
}
|
|
38
38
|
{
|
|
39
39
|
ReventlessCore.QueryDb_Adapter.resources: [],
|
|
40
|
-
|
|
40
|
+
// B3.2b: the shared PgQueryResolver Lambda data source. Deferred — resolved
|
|
41
|
+
// by PgQueryResolver_Builder.provision after every plugin is built (the
|
|
42
|
+
// resolvers that read it are created even later, inside the schema-pushed
|
|
43
|
+
// resourcesMaker). Same value for every Postgres read model.
|
|
44
|
+
dataSourceName: PgQueryResolver_Builder.dataSourceName,
|
|
41
45
|
operations,
|
|
42
46
|
}
|
|
43
47
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
-
import * as Pulumi from "@pulumi/pulumi";
|
|
4
3
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
5
4
|
import * as QueryDbBackend$ReventlessAws from "./QueryDbBackend.res.mjs";
|
|
5
|
+
import * as PgQueryResolver_Builder$ReventlessAws from "./PgQueryResolver_Builder.res.mjs";
|
|
6
6
|
import * as QueryDbStorage_Postgres_Runtime$ReventlessAws from "./QueryDbStorage_Postgres_Runtime.res.mjs";
|
|
7
7
|
|
|
8
8
|
function make(name, indexes, subIdField, param, param$1, param$2, param$3) {
|
|
@@ -10,7 +10,7 @@ function make(name, indexes, subIdField, param, param$1, param$2, param$3) {
|
|
|
10
10
|
let operations = match !== undefined ? match.connectionConfig.apply(config => QueryDbStorage_Postgres_Runtime$ReventlessAws.opsFor(config, name, indexes, subIdField)) : Stdlib_JsError.throwWithMessage("QueryDbStorage_Postgres.make called without a QueryDbBackend selection");
|
|
11
11
|
return {
|
|
12
12
|
resources: [],
|
|
13
|
-
dataSourceName:
|
|
13
|
+
dataSourceName: PgQueryResolver_Builder$ReventlessAws.dataSourceName,
|
|
14
14
|
operations: operations
|
|
15
15
|
};
|
|
16
16
|
}
|
|
@@ -18,4 +18,4 @@ function make(name, indexes, subIdField, param, param$1, param$2, param$3) {
|
|
|
18
18
|
export {
|
|
19
19
|
make,
|
|
20
20
|
}
|
|
21
|
-
/*
|
|
21
|
+
/* QueryDbBackend-ReventlessAws Not a pure module */
|
|
@@ -214,20 +214,9 @@ let finish = () =>
|
|
|
214
214
|
let pgSelection = EventLogBackend.get()
|
|
215
215
|
let pgConnectionFragment = switch pgSelection {
|
|
216
216
|
| Some(sel) =>
|
|
217
|
-
sel.connectionConfig->Pulumi.Output.apply(cc =>
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
("host", cc.host->JSON.Encode.string),
|
|
221
|
-
("port", cc.port->Int.toFloat->JSON.Encode.float),
|
|
222
|
-
("database", cc.database->JSON.Encode.string),
|
|
223
|
-
("username", cc.username->JSON.Encode.string),
|
|
224
|
-
("secretArn", cc.secretArn->JSON.Encode.string),
|
|
225
|
-
]
|
|
226
|
-
->Dict.fromArray
|
|
227
|
-
->JSON.Encode.object
|
|
228
|
-
->JSON.stringify
|
|
229
|
-
`,"pgConnection":${pgConnectionJson}`
|
|
230
|
-
})
|
|
217
|
+
sel.connectionConfig->Pulumi.Output.apply(cc =>
|
|
218
|
+
`,"pgConnection":${cc->PgConnection.connectionConfigToJson->JSON.stringify}`
|
|
219
|
+
)
|
|
231
220
|
| None => Pulumi.Output.make("")
|
|
232
221
|
}
|
|
233
222
|
|
|
@@ -10,6 +10,7 @@ import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/
|
|
|
10
10
|
import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/components/Component.res.mjs";
|
|
11
11
|
import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
|
|
12
12
|
import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
|
|
13
|
+
import * as PgConnection$ReventlessAws from "../Postgres/PgConnection.res.mjs";
|
|
13
14
|
import * as EventLogBackend$ReventlessAws from "../EventLog/EventLogBackend.res.mjs";
|
|
14
15
|
import * as RuntimeEnvironment_Lambda$ReventlessAws from "./RuntimeEnvironment_Lambda.res.mjs";
|
|
15
16
|
import * as CommandTopicChannel_SQS_Sync$ReventlessAws from "../CommandTopic/CommandTopicChannel_SQS_Sync.res.mjs";
|
|
@@ -156,31 +157,7 @@ function finish() {
|
|
|
156
157
|
let handlerOutputs = [];
|
|
157
158
|
let packageDirs = {};
|
|
158
159
|
let pgSelection = EventLogBackend$ReventlessAws.get();
|
|
159
|
-
let pgConnectionFragment = pgSelection !== undefined ? pgSelection.connectionConfig.apply(cc =>
|
|
160
|
-
let pgConnectionJson = JSON.stringify(Object.fromEntries([
|
|
161
|
-
[
|
|
162
|
-
"host",
|
|
163
|
-
cc.host
|
|
164
|
-
],
|
|
165
|
-
[
|
|
166
|
-
"port",
|
|
167
|
-
cc.port
|
|
168
|
-
],
|
|
169
|
-
[
|
|
170
|
-
"database",
|
|
171
|
-
cc.database
|
|
172
|
-
],
|
|
173
|
-
[
|
|
174
|
-
"username",
|
|
175
|
-
cc.username
|
|
176
|
-
],
|
|
177
|
-
[
|
|
178
|
-
"secretArn",
|
|
179
|
-
cc.secretArn
|
|
180
|
-
]
|
|
181
|
-
]));
|
|
182
|
-
return `,"pgConnection":` + pgConnectionJson;
|
|
183
|
-
}) : Pulumi.output("");
|
|
160
|
+
let pgConnectionFragment = pgSelection !== undefined ? pgSelection.connectionConfig.apply(cc => `,"pgConnection":` + JSON.stringify(PgConnection$ReventlessAws.connectionConfigToJson(undefined, cc))) : Pulumi.output("");
|
|
184
161
|
specs.forEach(spec => {
|
|
185
162
|
let info = aggregateInfos[spec.aggregateName];
|
|
186
163
|
if (info === undefined) {
|