@reventlessdev/reventless-aws 3.0.0-alpha.210 → 3.0.0-alpha.211
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 +7 -0
- package/package.json +6 -6
- package/src/Platform.res +116 -849
- package/src/Platform.res.mjs +75 -710
- package/src/adapter/QueryDb/PgQueryResolver_Builder.res +4 -47
- package/src/adapter/QueryDb/PgQueryResolver_Builder.res.mjs +1 -30
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res +0 -11
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs +0 -5
- package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res +6 -7
- package/src/components/Api/AppSync_Adapter.res +13 -105
- package/src/components/Api/AppSync_Adapter.res.mjs +0 -52
- package/src/components/Api/AppSync_MergedApi.res +1 -1
- package/src/components/Api/AppSync_SdlDecorate.res +6 -65
- package/src/components/Api/AppSync_SdlDecorate.res.mjs +1 -31
- package/src/components/Plugin.res.mjs +1 -2
- package/tests/AppSync_AdapterTest.res +23 -15
- package/tests/AppSync_AdapterTest.res.mjs +18 -16
- package/tests/AppSync_SdlDecorateTest.res +9 -89
- package/tests/AppSync_SdlDecorateTest.res.mjs +7 -72
- package/tests/MCP_LambdaTest.res +4 -6
- package/tests/MCP_LambdaTest.res.mjs +2 -2
- package/src/adapter/Api/ApiFragmentDeregistration.res +0 -138
- package/src/adapter/Api/ApiFragmentDeregistration.res.mjs +0 -108
- package/src/adapter/Api/ApiSchemaPush.res +0 -79
- package/src/adapter/Api/ApiSchemaPush.res.mjs +0 -64
- package/src/adapter/Api/Platform_ApiFragments_Lambda.res +0 -222
- package/src/adapter/Api/Platform_ApiFragments_Lambda.res.mjs +0 -202
- package/src/adapter/QueryDb/NodeResolver_AppSync.res +0 -71
- package/src/adapter/QueryDb/NodeResolver_AppSync.res.mjs +0 -44
- package/src/adapter/Runtime/ApiSchemaPush_Runtime.mjs +0 -204
- package/tests/ApiSchemaPushTest.res +0 -32
- package/tests/ApiSchemaPushTest.res.mjs +0 -20
package/src/Platform.res
CHANGED
|
@@ -9,16 +9,10 @@
|
|
|
9
9
|
// module App = MyPlugin.Make(Platform)
|
|
10
10
|
//
|
|
11
11
|
// Custom config:
|
|
12
|
-
// module Platform = Platform.MakeWithConfig({
|
|
13
|
-
// let splitApi = false; let mergedApi = false; let cloner = true
|
|
14
|
-
// })
|
|
12
|
+
// module Platform = Platform.MakeWithConfig({let splitApi = false; let cloner = true})
|
|
15
13
|
|
|
16
14
|
let log = ReventlessCore.Logger.fromEnv()
|
|
17
15
|
|
|
18
|
-
// Deploy-time schema-push shrink-guard threshold (deploy-time analogue of the
|
|
19
|
-
// runtime RUNTIME_SCHEMA_SHRINK_THRESHOLD in AdminEventCollectorEntryPoint.mjs).
|
|
20
|
-
// A push whose stitched SDL has fewer than (threshold × live) root fields is
|
|
21
|
-
// refused as a likely stale concurrent-deploy stitch. Default 0.5; override via
|
|
22
16
|
// API config ref — populated during MakeWithConfig so slice builders
|
|
23
17
|
// can access api/apiRole outside the functor constraint.
|
|
24
18
|
//
|
|
@@ -53,17 +47,6 @@ let getSplitApiOutputs = () => splitApiOutputsRef.contents
|
|
|
53
47
|
module MakeWithConfig = (
|
|
54
48
|
Config: {
|
|
55
49
|
let splitApi: bool
|
|
56
|
-
/** Merged-API composition (docs/plans/merged-api-push-free-composition.md).
|
|
57
|
-
`true` (the default — `Make()` sets it): the platform creates AppSync
|
|
58
|
-
MERGED APIs and the platform-owned APIs are ordinary SOURCE APIs with
|
|
59
|
-
declarative schemas; plugin stacks (deployPlugin) create their own
|
|
60
|
-
source API and associate it against the platform's exported merged-API
|
|
61
|
-
ARN. No fragment registration, no reactive schema push. `false` keeps
|
|
62
|
-
the legacy push path until its Phase-5 retirement — switching an
|
|
63
|
-
already-deployed platform in either direction requires wiping its
|
|
64
|
-
stacks. Supported with deployPlatform + deployPlugin (makePlatform
|
|
65
|
-
predates the merge path); all stacks of one platform must agree. */
|
|
66
|
-
let mergedApi: bool
|
|
67
50
|
let cloner: bool
|
|
68
51
|
let commandHandlerConfig: ReventlessCore.Runtime.commandHandlerConfigs
|
|
69
52
|
/** B2.3c platform toggle. When `Some`, every DCB EventLog is Postgres-backed
|
|
@@ -118,12 +101,7 @@ module MakeWithConfig = (
|
|
|
118
101
|
// Lambda (DynamoDB scan) — keep it off the Postgres selection like the other
|
|
119
102
|
// admin stores.
|
|
120
103
|
QueryDbBackend.exempt(ReventlessCore.UiFragments.name)
|
|
121
|
-
// The ApiFragments read model backs the Platform_ApiFragments status query
|
|
122
|
-
// (DynamoDB scan) and the schema-push SideEffect — admin store, off Postgres too.
|
|
123
|
-
QueryDbBackend.exempt(ReventlessCore.ApiFragmentsReadModelSpec.name)
|
|
124
104
|
})
|
|
125
|
-
let mergedApiMode = Config.mergedApi
|
|
126
|
-
|
|
127
105
|
type api = Types.AppSync.api
|
|
128
106
|
type role = Types.AppSync.role
|
|
129
107
|
type apiTarget = Domain | Platform
|
|
@@ -141,228 +119,112 @@ module MakeWithConfig = (
|
|
|
141
119
|
->Pulumi.Config.get("stack")
|
|
142
120
|
->Option.map(stack => Pulumi.StackReference.make(stack))
|
|
143
121
|
|
|
144
|
-
// Helper: build a phantom GraphQLApi.t from resolved id + endpoint strings.
|
|
145
|
-
let makePhantomApi = (apiId, apiEndpoint): PulumiAws.AppSync.GraphQLApi.t => ({
|
|
146
|
-
id: Pulumi.Output.make(apiId),
|
|
147
|
-
arn: Pulumi.Output.make(""),
|
|
148
|
-
name: Pulumi.Output.make(""),
|
|
149
|
-
uris: Pulumi.Output.make(({graphQL: apiEndpoint, realtime: ""}: PulumiAws.AppSync.GraphQLApi.uris)),
|
|
150
|
-
}: PulumiAws.AppSync.GraphQLApi.t)
|
|
151
|
-
|
|
152
|
-
// Helper: build a phantom IAM.Role.t from a resolved role ARN.
|
|
153
|
-
let makePhantomRole = (apiRoleArn): PulumiAws.IAM.Role.t => {
|
|
154
|
-
let roleName =
|
|
155
|
-
apiRoleArn->String.split("/")->Array.at(-1)->Option.getOr(apiRoleArn)
|
|
156
|
-
({
|
|
157
|
-
arn: Pulumi.Output.make(apiRoleArn),
|
|
158
|
-
id: Pulumi.Output.make(roleName),
|
|
159
|
-
name: Pulumi.Output.make(roleName),
|
|
160
|
-
}: PulumiAws.IAM.Role.t)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
122
|
// ── Merged-mode source SDL assembly (merged-api plan, Phase 3) ────────────
|
|
164
123
|
// Empty base fragment — no types, no mutations, no queries. Used by the
|
|
165
124
|
// plugin Api in split mode (so plugin schema has no core fields) and as the
|
|
166
125
|
// base of the split-mode Domain source document below.
|
|
167
|
-
|
|
126
|
+
// Split-mode Domain base fragment. No component fields — the Domain merged
|
|
127
|
+
// API's fields come from plugin sources; this document only anchors the
|
|
128
|
+
// canonical relay base types. `Platform_ping` exists because a GraphQL
|
|
129
|
+
// schema cannot have an empty Query type (unresolved → null; spike
|
|
130
|
+
// precedent). The global `node` query is NOT emitted on AWS — see the
|
|
131
|
+
// "Relay node resolution" section of the merged-api plan (never resolved on
|
|
132
|
+
// any deployed AWS platform, zero consumers; the Node interface and global
|
|
133
|
+
// IDs stay).
|
|
134
|
+
let domainBaseFragment = ReventlessCore.GraphQL_Stitcher.encode({
|
|
168
135
|
types: [],
|
|
169
136
|
mutations: [],
|
|
170
|
-
queries: [],
|
|
137
|
+
queries: [" Platform_ping: String"],
|
|
171
138
|
subscriptions: [],
|
|
172
139
|
subscriptionSources: [],
|
|
173
140
|
})
|
|
174
141
|
|
|
175
142
|
// The canonical base document for a merged API's platform-owned source:
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
// (divergence is shadowed, not MERGE_FAILED — Phase-0 finding 1).
|
|
143
|
+
// rendered standalone (relay base types included, no global `node` query),
|
|
144
|
+
// AWS-dialect decorated, and `@canonical`-stamped so the platform-owned
|
|
145
|
+
// shared types win over every plugin source's standalone copy (divergence
|
|
146
|
+
// is shadowed, not MERGE_FAILED — Phase-0 finding 1).
|
|
181
147
|
let assembleCanonicalSourceSdl = (~baseFragment): string =>
|
|
182
|
-
AppSync_Adapter.
|
|
148
|
+
AppSync_Adapter.stitchStandaloneWithAwsDirectives(~fragment=baseFragment)
|
|
183
149
|
->AppSync_SdlDecorate.stampCanonicalTypes
|
|
184
150
|
|
|
185
|
-
// Admin base as a source-API document —
|
|
186
|
-
//
|
|
151
|
+
// Admin base as a source-API document — auth-decorated (all fields Admin,
|
|
152
|
+
// Cognito-only; the deploy-time SigV4 system-caller fields died with the
|
|
153
|
+
// fragment registry) plus the canonical stamp.
|
|
187
154
|
let adminSourceSdl = (): string =>
|
|
188
155
|
assembleCanonicalSourceSdl(
|
|
189
156
|
~baseFragment=AppSync_Adapter.injectAwsAuthAll(
|
|
190
157
|
ReventlessCore.AdminApi.baseFragment(~cloner=Config.cloner),
|
|
191
158
|
~group="Admin",
|
|
192
|
-
~iamFieldNames=ReventlessCore.AdminApi.systemCallerFieldNames,
|
|
193
159
|
),
|
|
194
160
|
)
|
|
195
161
|
|
|
196
|
-
// Split-mode Domain source document: relay base +
|
|
197
|
-
// merged API's canonical owner (plugin fields come from plugin sources).
|
|
198
|
-
let
|
|
162
|
+
// Split-mode Domain source document: relay base types + Platform_ping — the
|
|
163
|
+
// Domain merged API's canonical owner (plugin fields come from plugin sources).
|
|
164
|
+
let domainBaseSourceSdl = (): string => assembleCanonicalSourceSdl(~baseFragment=domainBaseFragment)
|
|
199
165
|
|
|
200
166
|
let (domainApi, domainApiRole, platformApi, platformApiRole) = switch platformStackRef {
|
|
201
167
|
| None =>
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
relayBaseSourceSdl()
|
|
209
|
-
} else {
|
|
210
|
-
adminSourceSdl()
|
|
211
|
-
}
|
|
212
|
-
AppSync_Adapter.makeSourceApiResource(~name="DomainApi", ~schema, ~opts={})
|
|
168
|
+
// The Domain API is an ordinary GRAPHQL source API with a DECLARATIVE
|
|
169
|
+
// schema. Unified: it carries the admin base (the canonical document).
|
|
170
|
+
// Split: it carries only the relay base document; the admin base lives on
|
|
171
|
+
// the Platform source API created in deployPlatform.
|
|
172
|
+
let schema = if Config.splitApi {
|
|
173
|
+
domainBaseSourceSdl()
|
|
213
174
|
} else {
|
|
214
|
-
|
|
175
|
+
adminSourceSdl()
|
|
215
176
|
}
|
|
177
|
+
let (api, role) = AppSync_Adapter.makeSourceApiResource(~name="DomainApi", ~schema, ~opts={})
|
|
216
178
|
// In platform/monolithic mode the platform API is not yet known — it is created
|
|
217
179
|
// during deployPlatform/makePlatform and the ref is updated there.
|
|
218
180
|
(api, role, api, role)
|
|
219
181
|
| Some(stackRef) =>
|
|
220
|
-
// Plugin mode
|
|
221
|
-
//
|
|
182
|
+
// Plugin mode (merged-api plan, Phase 4): the plugin stack owns a real
|
|
183
|
+
// SOURCE API — the single writer for its subgraph schema and resolvers.
|
|
184
|
+
// It fills all four API slots (resolver wiring is target-agnostic here;
|
|
185
|
+
// apiTarget only decides WHICH merged API the association in deployPlugin
|
|
186
|
+
// points at). The user pool comes from the platform's exports so Cognito
|
|
187
|
+
// primary auth matches across all sources of the merged endpoint — the
|
|
188
|
+
// plugin stack provisions no pool/client.
|
|
222
189
|
//
|
|
223
190
|
// In ESM mode, Pulumi exports are inside the "default" output.
|
|
224
191
|
// Try top-level field first (CJS), fall back to "default".<field> (ESM).
|
|
225
192
|
let defaultOutput: Pulumi.Output.t<option<JSON.t>> =
|
|
226
193
|
stackRef->Pulumi.StackReference.getOutput("default")
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
let
|
|
230
|
-
stackRef->Pulumi.StackReference.getOutput("
|
|
231
|
-
let
|
|
232
|
-
|
|
233
|
-
let domainApiRoleArnOutput: Pulumi.Output.t<option<string>> =
|
|
234
|
-
stackRef->Pulumi.StackReference.getOutput("domainApiRoleArn")
|
|
235
|
-
|
|
236
|
-
let phantomApi: Types.AppSync.api =
|
|
237
|
-
(domainApiIdOutput, domainApiEndpointOutput, defaultOutput)
|
|
194
|
+
let cognitoPoolIdOutput: Pulumi.Output.t<option<string>> =
|
|
195
|
+
stackRef->Pulumi.StackReference.getOutput("cognitoUserPoolId")
|
|
196
|
+
let cognitoRegionOutput: Pulumi.Output.t<option<string>> =
|
|
197
|
+
stackRef->Pulumi.StackReference.getOutput("cognitoRegion")
|
|
198
|
+
let userPoolConfig =
|
|
199
|
+
(cognitoPoolIdOutput, cognitoRegionOutput, defaultOutput)
|
|
238
200
|
->Pulumi.Output.all3
|
|
239
|
-
->Pulumi.Output.apply(((
|
|
240
|
-
let apiId = switch directId {
|
|
241
|
-
| Some(id) => id
|
|
242
|
-
| None =>
|
|
243
|
-
default
|
|
244
|
-
->Option.flatMap(d => d->JSON.Decode.object)
|
|
245
|
-
->Option.flatMap(d => d->Dict.get("domainApiId"))
|
|
246
|
-
->Option.flatMap(v => v->JSON.Decode.string)
|
|
247
|
-
->Option.getOrThrow
|
|
248
|
-
}
|
|
249
|
-
let apiEndpoint = switch directEndpoint {
|
|
250
|
-
| Some(ep) => ep
|
|
251
|
-
| None =>
|
|
252
|
-
default
|
|
253
|
-
->Option.flatMap(d => d->JSON.Decode.object)
|
|
254
|
-
->Option.flatMap(d => d->Dict.get("domainApiEndpoint"))
|
|
255
|
-
->Option.flatMap(v => v->JSON.Decode.string)
|
|
256
|
-
->Option.getOrThrow(
|
|
257
|
-
~message="Platform stack does not export 'domainApiEndpoint' — redeploy the platform stack first",
|
|
258
|
-
)
|
|
259
|
-
}
|
|
260
|
-
makePhantomApi(apiId, apiEndpoint)
|
|
261
|
-
})
|
|
262
|
-
let phantomRole: Types.AppSync.role =
|
|
263
|
-
(domainApiRoleArnOutput, defaultOutput)
|
|
264
|
-
->Pulumi.Output.all2
|
|
265
|
-
->Pulumi.Output.apply(((direct, default)) => {
|
|
266
|
-
let domainApiRoleArn = switch direct {
|
|
267
|
-
| Some(arn) => arn
|
|
268
|
-
| None =>
|
|
269
|
-
default
|
|
270
|
-
->Option.flatMap(d => d->JSON.Decode.object)
|
|
271
|
-
->Option.flatMap(d => d->Dict.get("domainApiRoleArn"))
|
|
272
|
-
->Option.flatMap(v => v->JSON.Decode.string)
|
|
273
|
-
->Option.getOrThrow
|
|
274
|
-
}
|
|
275
|
-
makePhantomRole(domainApiRoleArn)
|
|
276
|
-
})
|
|
277
|
-
|
|
278
|
-
// ── Platform API (admin / Platform_Sync* resolvers) ─────────────────────
|
|
279
|
-
let platformApiIdOutput: Pulumi.Output.t<option<string>> =
|
|
280
|
-
stackRef->Pulumi.StackReference.getOutput("platformApiId")
|
|
281
|
-
let platformApiEndpointOutput: Pulumi.Output.t<option<string>> =
|
|
282
|
-
stackRef->Pulumi.StackReference.getOutput("platformApiEndpoint")
|
|
283
|
-
let platformApiRoleArnOutput: Pulumi.Output.t<option<string>> =
|
|
284
|
-
stackRef->Pulumi.StackReference.getOutput("platformApiRoleArn")
|
|
285
|
-
|
|
286
|
-
let phantomPlatformApi: Types.AppSync.api =
|
|
287
|
-
(platformApiIdOutput, platformApiEndpointOutput, defaultOutput)
|
|
288
|
-
->Pulumi.Output.all3
|
|
289
|
-
->Pulumi.Output.apply(((directPlatId, directPlatEndpoint, default)) => {
|
|
290
|
-
let getFromDefault = key =>
|
|
291
|
-
default
|
|
292
|
-
->Option.flatMap(d => d->JSON.Decode.object)
|
|
293
|
-
->Option.flatMap(d => d->Dict.get(key))
|
|
294
|
-
->Option.flatMap(v => v->JSON.Decode.string)
|
|
295
|
-
let platformApiId =
|
|
296
|
-
directPlatId
|
|
297
|
-
->Option.orElse(getFromDefault("platformApiId"))
|
|
298
|
-
->Option.getOrThrow
|
|
299
|
-
let platformApiEndpoint =
|
|
300
|
-
directPlatEndpoint
|
|
301
|
-
->Option.orElse(getFromDefault("platformApiEndpoint"))
|
|
302
|
-
->Option.getOrThrow
|
|
303
|
-
makePhantomApi(platformApiId, platformApiEndpoint)
|
|
304
|
-
})
|
|
305
|
-
let phantomPlatformRole: Types.AppSync.role =
|
|
306
|
-
(platformApiRoleArnOutput, defaultOutput)
|
|
307
|
-
->Pulumi.Output.all2
|
|
308
|
-
->Pulumi.Output.apply(((directPlat, default)) => {
|
|
201
|
+
->Pulumi.Output.apply(((directPoolId, directRegion, default)) => {
|
|
309
202
|
let getFromDefault = key =>
|
|
310
203
|
default
|
|
311
204
|
->Option.flatMap(d => d->JSON.Decode.object)
|
|
312
205
|
->Option.flatMap(d => d->Dict.get(key))
|
|
313
206
|
->Option.flatMap(v => v->JSON.Decode.string)
|
|
314
|
-
let
|
|
315
|
-
|
|
316
|
-
->Option.orElse(getFromDefault("
|
|
317
|
-
->Option.getOrThrow
|
|
318
|
-
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
if mergedApiMode {
|
|
322
|
-
// Merged mode (merged-api plan, Phase 4): the plugin stack owns a real
|
|
323
|
-
// SOURCE API — the single writer for its subgraph schema and resolvers.
|
|
324
|
-
// It fills all four API slots (resolver wiring is target-agnostic here;
|
|
325
|
-
// apiTarget only decides WHICH merged API the association in
|
|
326
|
-
// deployPlugin points at). The user pool comes from the platform's
|
|
327
|
-
// exports so Cognito primary auth matches across all sources of the
|
|
328
|
-
// merged endpoint — the plugin stack provisions no pool/client.
|
|
329
|
-
let cognitoPoolIdOutput: Pulumi.Output.t<option<string>> =
|
|
330
|
-
stackRef->Pulumi.StackReference.getOutput("cognitoUserPoolId")
|
|
331
|
-
let cognitoRegionOutput: Pulumi.Output.t<option<string>> =
|
|
332
|
-
stackRef->Pulumi.StackReference.getOutput("cognitoRegion")
|
|
333
|
-
let userPoolConfig =
|
|
334
|
-
(cognitoPoolIdOutput, cognitoRegionOutput, defaultOutput)
|
|
335
|
-
->Pulumi.Output.all3
|
|
336
|
-
->Pulumi.Output.apply(((directPoolId, directRegion, default)) => {
|
|
337
|
-
let getFromDefault = key =>
|
|
338
|
-
default
|
|
339
|
-
->Option.flatMap(d => d->JSON.Decode.object)
|
|
340
|
-
->Option.flatMap(d => d->Dict.get(key))
|
|
341
|
-
->Option.flatMap(v => v->JSON.Decode.string)
|
|
342
|
-
let userPoolId =
|
|
343
|
-
directPoolId
|
|
344
|
-
->Option.orElse(getFromDefault("cognitoUserPoolId"))
|
|
345
|
-
->Option.getOrThrow(
|
|
346
|
-
~message="Platform stack does not export 'cognitoUserPoolId' — redeploy the platform stack first",
|
|
347
|
-
)
|
|
348
|
-
let awsRegion = directRegion->Option.orElse(getFromDefault("cognitoRegion"))
|
|
349
|
-
(
|
|
350
|
-
{
|
|
351
|
-
userPoolId,
|
|
352
|
-
?awsRegion,
|
|
353
|
-
defaultAction: PulumiAws.AppSync.GraphQLApi.ALLOW,
|
|
354
|
-
}: PulumiAws.AppSync.GraphQLApi.userPoolConfig
|
|
207
|
+
let userPoolId =
|
|
208
|
+
directPoolId
|
|
209
|
+
->Option.orElse(getFromDefault("cognitoUserPoolId"))
|
|
210
|
+
->Option.getOrThrow(
|
|
211
|
+
~message="Platform stack does not export 'cognitoUserPoolId' — redeploy the platform stack first",
|
|
355
212
|
)
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
213
|
+
let awsRegion = directRegion->Option.orElse(getFromDefault("cognitoRegion"))
|
|
214
|
+
(
|
|
215
|
+
{
|
|
216
|
+
userPoolId,
|
|
217
|
+
?awsRegion,
|
|
218
|
+
defaultAction: PulumiAws.AppSync.GraphQLApi.ALLOW,
|
|
219
|
+
}: PulumiAws.AppSync.GraphQLApi.userPoolConfig
|
|
220
|
+
)
|
|
221
|
+
})
|
|
222
|
+
let (api, role) = AppSync_Adapter.makePluginSourceApiResource(
|
|
223
|
+
~name="PluginSourceApi",
|
|
224
|
+
~userPoolConfig,
|
|
225
|
+
~opts={},
|
|
226
|
+
)
|
|
227
|
+
(api, role, api, role)
|
|
366
228
|
}
|
|
367
229
|
|
|
368
230
|
// Expose api/apiRole as Platform.T value bindings so DCB slice builders
|
|
@@ -686,30 +548,8 @@ module MakeWithConfig = (
|
|
|
686
548
|
ReventlessCore.UiFragments,
|
|
687
549
|
ReventlessCore.UiFragments_Projection,
|
|
688
550
|
)
|
|
689
|
-
//
|
|
690
|
-
//
|
|
691
|
-
|
|
692
|
-
// (emptyBaseFragment — the empty split-mode plugin base — is defined above,
|
|
693
|
-
// next to the merged-mode source SDL assembly that shares it.)
|
|
694
|
-
|
|
695
|
-
module Api = {
|
|
696
|
-
module Make = (
|
|
697
|
-
FragmentConfig: {
|
|
698
|
-
let baseFragment: ReventlessInfra.Api.schemaFragment
|
|
699
|
-
},
|
|
700
|
-
): ReventlessInfra.Api.T => {
|
|
701
|
-
module Builder = ReventlessCore.Api_Builder.Make(AppSync_Adapter)
|
|
702
|
-
// In split mode, the plugin API uses an empty base fragment so plugin schema
|
|
703
|
-
// has no core fields. In unified mode, use the provided base fragment as-is.
|
|
704
|
-
let effectiveBaseFragment = if Config.splitApi {
|
|
705
|
-
emptyBaseFragment
|
|
706
|
-
} else {
|
|
707
|
-
FragmentConfig.baseFragment
|
|
708
|
-
}
|
|
709
|
-
let make = (~name, ~opts=?) =>
|
|
710
|
-
Builder.make(~name, ~baseFragment=effectiveBaseFragment, ~opts?)
|
|
711
|
-
}
|
|
712
|
-
}
|
|
551
|
+
// (domainBaseFragment — the split-mode Domain source base — is defined above,
|
|
552
|
+
// next to the merged-mode source SDL assembly that uses it.)
|
|
713
553
|
|
|
714
554
|
// ── Typed identity casts — see Platform_Casts.res for rationale ─────────
|
|
715
555
|
open Platform_Casts
|
|
@@ -753,153 +593,6 @@ module MakeWithConfig = (
|
|
|
753
593
|
| None => resolveHookedApi()
|
|
754
594
|
}
|
|
755
595
|
|
|
756
|
-
// ── Phase 3: staged-deploy API-fragment registration ────────────────────────
|
|
757
|
-
// When a plugin deploys against an ALREADY-RUNNING platform (plugin-stack mode),
|
|
758
|
-
// it registers its API-schema fragment with the platform via the Platform API
|
|
759
|
-
// (SigV4 system caller) instead of writing deploy-schema:* rows and pushing the
|
|
760
|
-
// schema itself. The platform-side reactive single writer (Plan 2e) stitches and
|
|
761
|
-
// pushes; this waits on the push status so resolver creation (gated on the
|
|
762
|
-
// returned Output) proceeds only once the plugin's fields are ACTIVE — or fails
|
|
763
|
-
// with the stitch error. Used only in plugin-stack mode; makePlatform (all-at-once)
|
|
764
|
-
// keeps the direct deploy-time push (the reactive writer is dormant there).
|
|
765
|
-
@val external registerWaiterSetTimeout: (unit => unit, int) => unit = "setTimeout"
|
|
766
|
-
let deployWaiterDelay = (ms: int): promise<unit> =>
|
|
767
|
-
Promise.make((resolve, _) => registerWaiterSetTimeout(() => resolve(), ms))
|
|
768
|
-
|
|
769
|
-
// Poll the platform's Platform_ApiFragments status query until the plugin's row
|
|
770
|
-
// shows a push that landed at/after our registration time (`sinceAt`) — a fresh
|
|
771
|
-
// "ok" means our fields are live; a fresh "error" surfaces the stitch failure. An
|
|
772
|
-
// older "ok" is a stale prior deploy's and is ignored (ISO strings sort lexically).
|
|
773
|
-
let waitForApiFragmentPush = async (~endpoint, ~region, ~pluginId, ~sinceAt) => {
|
|
774
|
-
let query = "query { Platform_ApiFragments { pluginId pushStatus pushMessage pushedAt } }"
|
|
775
|
-
let maxAttempts = 90 // ~3 min at 2s intervals
|
|
776
|
-
let rec loop = async attempt =>
|
|
777
|
-
if attempt >= maxAttempts {
|
|
778
|
-
JsError.throwWithMessage(
|
|
779
|
-
`Timed out (>3min) waiting for the platform to push ${pluginId}'s API fragment — check the AdminEventCollector Lambda logs.`,
|
|
780
|
-
)
|
|
781
|
-
} else {
|
|
782
|
-
let data = await Util_AppSync_Caller.sendQuery(~endpoint, ~region, ~queryString=query)
|
|
783
|
-
let row =
|
|
784
|
-
data
|
|
785
|
-
->Option.flatMap(JSON.Decode.object)
|
|
786
|
-
->Option.flatMap(d => d->Dict.get("Platform_ApiFragments"))
|
|
787
|
-
->Option.flatMap(JSON.Decode.array)
|
|
788
|
-
->Option.getOr([])
|
|
789
|
-
->Array.find(entry =>
|
|
790
|
-
entry
|
|
791
|
-
->JSON.Decode.object
|
|
792
|
-
->Option.flatMap(e => e->Dict.get("pluginId"))
|
|
793
|
-
->Option.flatMap(JSON.Decode.string) == Some(pluginId)
|
|
794
|
-
)
|
|
795
|
-
->Option.flatMap(JSON.Decode.object)
|
|
796
|
-
switch row {
|
|
797
|
-
| Some(e) =>
|
|
798
|
-
let field = k => e->Dict.get(k)->Option.flatMap(JSON.Decode.string)->Option.getOr("")
|
|
799
|
-
let status = field("pushStatus")
|
|
800
|
-
let fresh = field("pushedAt") >= sinceAt
|
|
801
|
-
if fresh && status == "ok" {
|
|
802
|
-
log.info(~comp="registerFragmentViaApi", `${pluginId} schema push confirmed ACTIVE`)
|
|
803
|
-
} else if fresh && status == "error" {
|
|
804
|
-
JsError.throwWithMessage(`Schema push failed for ${pluginId}: ${field("pushMessage")}`)
|
|
805
|
-
} else {
|
|
806
|
-
await deployWaiterDelay(2000)
|
|
807
|
-
await loop(attempt + 1)
|
|
808
|
-
}
|
|
809
|
-
| None =>
|
|
810
|
-
await deployWaiterDelay(2000)
|
|
811
|
-
await loop(attempt + 1)
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
await loop(0)
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
let registerFragmentViaApi = (
|
|
818
|
-
~name: string,
|
|
819
|
-
~fragment: Reventless.Plugin.apiSchemaFragment,
|
|
820
|
-
~apiTargetName: string,
|
|
821
|
-
): Pulumi.Output.t<unit> => {
|
|
822
|
-
// Platform_RegisterApiFragment is an admin systemCallable mutation on the Platform
|
|
823
|
-
// API (regardless of the plugin's apiTarget, which is passed as the enum argument).
|
|
824
|
-
let platformApi = switch apiConfigRef.contents {
|
|
825
|
-
| Some({platformApi}) => platformApi
|
|
826
|
-
| None => domainApi
|
|
827
|
-
}
|
|
828
|
-
let region =
|
|
829
|
-
Pulumi.Config.make(Some("aws"))->Pulumi.Config.get("region")->Option.getOr("unknown")
|
|
830
|
-
let endpointOutput =
|
|
831
|
-
platformApi
|
|
832
|
-
->Pulumi.Output.flatMap(api => api.uris)
|
|
833
|
-
->Pulumi.Output.apply(uris => uris.graphQL)
|
|
834
|
-
// Deregister-on-destroy: a dynamic resource whose `delete` handler sends
|
|
835
|
-
// Platform_DeregisterApiFragment when the plugin stack is destroyed (final
|
|
836
|
-
// retirement). It never replaces on a version bump, so supersession does NOT
|
|
837
|
-
// deregister — only a genuine `pulumi destroy` removes the fields.
|
|
838
|
-
let _ = ApiFragmentDeregistration.make(
|
|
839
|
-
~name=`${name}ApiFragmentRegistration`,
|
|
840
|
-
~pluginId=name,
|
|
841
|
-
~endpoint=endpointOutput->Pulumi.Output.asInput,
|
|
842
|
-
~region,
|
|
843
|
-
)
|
|
844
|
-
endpointOutput
|
|
845
|
-
->Pulumi.Output.flatMap(endpoint => {
|
|
846
|
-
let run = async () => {
|
|
847
|
-
let at = Date.make()->Date.toISOString
|
|
848
|
-
// The registry is a SINGLETON aggregate — the mutation `id` arg is the fixed
|
|
849
|
-
// constant, `pluginId` (a payload field) carries the plugin name.
|
|
850
|
-
let variables = {
|
|
851
|
-
"id": "registry",
|
|
852
|
-
"pluginId": name,
|
|
853
|
-
"fragment": {"encoded": fragment.encoded, "protocol": fragment.protocol},
|
|
854
|
-
"apiTarget": Util_AppSync_Caller.graphqlEnum(apiTargetName),
|
|
855
|
-
"at": at,
|
|
856
|
-
}
|
|
857
|
-
let selection = "{ __typename ... on CommandAccepted { eventCount } ... on CommandRejected { errorCode errorDetail } }"
|
|
858
|
-
log.info(
|
|
859
|
-
~comp="registerFragmentViaApi",
|
|
860
|
-
`Registering API fragment for ${name} (${apiTargetName}) via ${endpoint}`,
|
|
861
|
-
)
|
|
862
|
-
let result = await Util_AppSync_Caller.sendMutation(
|
|
863
|
-
~endpoint,
|
|
864
|
-
~region,
|
|
865
|
-
~mutation="Platform_ApiFragmentRegistry_RegisterApiFragment",
|
|
866
|
-
~selection,
|
|
867
|
-
~variables,
|
|
868
|
-
)
|
|
869
|
-
let outcome =
|
|
870
|
-
result
|
|
871
|
-
->Option.flatMap(JSON.Decode.object)
|
|
872
|
-
->Option.flatMap(d => d->Dict.get("Platform_ApiFragmentRegistry_RegisterApiFragment"))
|
|
873
|
-
->Option.flatMap(JSON.Decode.object)
|
|
874
|
-
switch outcome {
|
|
875
|
-
| Some(cmd) =>
|
|
876
|
-
let typename =
|
|
877
|
-
cmd->Dict.get("__typename")->Option.flatMap(JSON.Decode.string)->Option.getOr("")
|
|
878
|
-
switch typename {
|
|
879
|
-
| "CommandRejected" =>
|
|
880
|
-
let code =
|
|
881
|
-
cmd->Dict.get("errorCode")->Option.flatMap(JSON.Decode.string)->Option.getOr("unknown")
|
|
882
|
-
JsError.throwWithMessage(`RegisterApiFragment rejected for ${name}: ${code}`)
|
|
883
|
-
| "CommandAccepted" =>
|
|
884
|
-
// eventCount 0 ⇒ idempotent no-op (unchanged fragment) — fields already live,
|
|
885
|
-
// proceed without waiting; >0 ⇒ a real change, wait for the reactive push.
|
|
886
|
-
let eventCount =
|
|
887
|
-
cmd->Dict.get("eventCount")->Option.flatMap(JSON.Decode.float)->Option.getOr(0.0)
|
|
888
|
-
if eventCount > 0.0 {
|
|
889
|
-
await waitForApiFragmentPush(~endpoint, ~region, ~pluginId=name, ~sinceAt=at)
|
|
890
|
-
} else {
|
|
891
|
-
log.info(~comp="registerFragmentViaApi", `${name} fragment unchanged — no push needed`)
|
|
892
|
-
}
|
|
893
|
-
| _ => await waitForApiFragmentPush(~endpoint, ~region, ~pluginId=name, ~sinceAt=at)
|
|
894
|
-
}
|
|
895
|
-
| None =>
|
|
896
|
-
// Unparseable result (mutation may have failed) — fall back to polling.
|
|
897
|
-
await waitForApiFragmentPush(~endpoint, ~region, ~pluginId=name, ~sinceAt=at)
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
run()->Pulumi.Output.fromPromise
|
|
901
|
-
})
|
|
902
|
-
}
|
|
903
596
|
|
|
904
597
|
let hooks: ReventlessCore.Plugin_Helpers.platformHooks = {
|
|
905
598
|
// AWS uses Interstack for admin extension points — leave ref at empty dict.
|
|
@@ -946,136 +639,40 @@ module MakeWithConfig = (
|
|
|
946
639
|
// makePlatform / deployPlatform before Admin.construct fires, is visible):
|
|
947
640
|
// - split mode: platformApi from splitApiOutputsRef
|
|
948
641
|
// - unified mode / not-yet-populated: domainApi
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
"split mode but the PlatformApi is not available at hook time — SKIPPING the admin schema push to avoid clobbering the DomainApi (pushing admin-base here would wipe every plugin field)",
|
|
977
|
-
)
|
|
978
|
-
adminBarrier
|
|
979
|
-
| Some(targetApi) =>
|
|
980
|
-
let adminBaseFragment = AppSync_Adapter.injectAwsAuthAll(
|
|
981
|
-
ReventlessCore.AdminApi.baseFragment(~cloner=Config.cloner),
|
|
982
|
-
~group="Admin",
|
|
983
|
-
// The ApiFragmentRegistry register/deregister mutations + the Platform_ApiFragments
|
|
984
|
-
// status query are invoked by the plugin/standalone deploy as a SigV4 system caller, so
|
|
985
|
-
// they carry the dual-auth (@aws_cognito_user_pools @aws_iam) directive.
|
|
986
|
-
~iamFieldNames=ReventlessCore.AdminApi.systemCallerFieldNames,
|
|
987
|
-
)
|
|
988
|
-
let sdl = AppSync_Adapter.stitchWithAwsDirectives(
|
|
989
|
-
~baseFragment=adminBaseFragment,
|
|
990
|
-
~pluginFragments=[],
|
|
991
|
-
)
|
|
992
|
-
(targetApi, adminBarrier)
|
|
993
|
-
->Pulumi.Output.all2
|
|
994
|
-
->Pulumi.Output.flatMap(((api, _)) =>
|
|
995
|
-
api.id->Pulumi.Output.flatMap(apiId => {
|
|
996
|
-
log.info(~comp="preAdminResolversSchemaHook", `Pushing admin schema to ${apiId}`)
|
|
997
|
-
let client = AppSync_Adapter.getClient()
|
|
998
|
-
client
|
|
999
|
-
->AppSync_Adapter.startSchemaCreationRetrying({apiId, definition: sdl})
|
|
1000
|
-
->Promise.then(async _ => {
|
|
1001
|
-
log.info(
|
|
1002
|
-
~comp="preAdminResolversSchemaHook",
|
|
1003
|
-
"startSchemaCreation called, waiting for ACTIVE",
|
|
1004
|
-
)
|
|
1005
|
-
await AppSync_Adapter.waitForSchemaActive(client, apiId)
|
|
1006
|
-
log.info(~comp="preAdminResolversSchemaHook", "schema is ACTIVE")
|
|
1007
|
-
})
|
|
1008
|
-
->Pulumi.Output.fromPromise
|
|
1009
|
-
})
|
|
1010
|
-
)
|
|
1011
|
-
}
|
|
1012
|
-
},
|
|
1013
|
-
|
|
1014
|
-
// Staged deploy (deployPlugin against a running platform): register the
|
|
1015
|
-
// plugin's fragment via the Platform API (SigV4); the reactive ApiSchemaPush
|
|
1016
|
-
// SideEffect stitches + pushes cumulatively. The legacy all-at-once
|
|
1017
|
-
// deploy-schema:* write+scan+push path was retired in Phase 4b (makePlatform
|
|
1018
|
-
// with plugins is no longer supported on AWS).
|
|
1019
|
-
preResolversSchemaHook: (~name, ~version, pluginFragment) =>
|
|
1020
|
-
if mergedApiMode {
|
|
1021
|
-
// Merged mode (merged-api plan, Phase 4): push the plugin's standalone
|
|
1022
|
-
// subgraph document to the plugin's OWN source API — a single writer
|
|
1023
|
-
// by construction, so no SigV4 registration, no reactive stitcher, no
|
|
1024
|
-
// waiter, no drift check, no destroy-path deregistration. The returned
|
|
1025
|
-
// Output gates resolver creation (as on the push path) and deployPlugin
|
|
1026
|
-
// additionally sequences the SourceApiAssociation behind it (the
|
|
1027
|
-
// initial merge needs the source schema present). Under AUTO_MERGE
|
|
1028
|
-
// every later schema update here re-merges automatically.
|
|
1029
|
-
let sdl = AppSync_Adapter.stitchStandaloneWithAwsDirectives(~fragment=pluginFragment)
|
|
1030
|
-
let pushed =
|
|
1031
|
-
domainApi->Pulumi.Output.flatMap(api =>
|
|
1032
|
-
api.id->Pulumi.Output.flatMap(apiId => {
|
|
1033
|
-
log.info(
|
|
1034
|
-
~comp="preResolversSchemaHook",
|
|
1035
|
-
`Pushing subgraph schema for ${name}@${version} to source API ${apiId}`,
|
|
1036
|
-
)
|
|
1037
|
-
let client = AppSync_Adapter.getClient()
|
|
1038
|
-
client
|
|
1039
|
-
->AppSync_Adapter.startSchemaCreationRetrying({apiId, definition: sdl})
|
|
1040
|
-
->Promise.then(async _ => {
|
|
1041
|
-
await AppSync_Adapter.waitForSchemaActive(client, apiId)
|
|
1042
|
-
log.info(~comp="preResolversSchemaHook", "subgraph schema is ACTIVE")
|
|
1043
|
-
})
|
|
1044
|
-
->Pulumi.Output.fromPromise
|
|
642
|
+
// Admin base SDL is DECLARATIVE on the source API resource
|
|
643
|
+
// (makeSourceApiResource) — the provider runs StartSchemaCreation + poll
|
|
644
|
+
// before the resource resolves, so admin resolvers chained on the API are
|
|
645
|
+
// already ordered after the schema is ACTIVE. No push.
|
|
646
|
+
preAdminResolversSchemaHook: (~adminBarrier) => adminBarrier,
|
|
647
|
+
|
|
648
|
+
// Push the plugin's standalone subgraph document to the plugin's OWN
|
|
649
|
+
// source API — a single writer by construction. The returned Output gates
|
|
650
|
+
// resolver creation and deployPlugin additionally sequences the
|
|
651
|
+
// SourceApiAssociation behind it (the initial merge needs the source
|
|
652
|
+
// schema present). Under AUTO_MERGE every later schema update here
|
|
653
|
+
// re-merges automatically.
|
|
654
|
+
preResolversSchemaHook: (~name, ~version, pluginFragment) => {
|
|
655
|
+
let sdl = AppSync_Adapter.stitchStandaloneWithAwsDirectives(~fragment=pluginFragment)
|
|
656
|
+
let pushed =
|
|
657
|
+
domainApi->Pulumi.Output.flatMap(api =>
|
|
658
|
+
api.id->Pulumi.Output.flatMap(apiId => {
|
|
659
|
+
log.info(
|
|
660
|
+
~comp="preResolversSchemaHook",
|
|
661
|
+
`Pushing subgraph schema for ${name}@${version} to source API ${apiId}`,
|
|
662
|
+
)
|
|
663
|
+
let client = AppSync_Adapter.getClient()
|
|
664
|
+
client
|
|
665
|
+
->AppSync_Adapter.startSchemaCreationRetrying({apiId, definition: sdl})
|
|
666
|
+
->Promise.then(async _ => {
|
|
667
|
+
await AppSync_Adapter.waitForSchemaActive(client, apiId)
|
|
668
|
+
log.info(~comp="preResolversSchemaHook", "subgraph schema is ACTIVE")
|
|
1045
669
|
})
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
pushed
|
|
1049
|
-
} else {
|
|
1050
|
-
log.info(
|
|
1051
|
-
~comp="preResolversSchemaHook",
|
|
1052
|
-
`Pushing schema for plugin ${name}@${version} to AppSync`,
|
|
1053
|
-
)
|
|
1054
|
-
|
|
1055
|
-
// Capture deploy target synchronously — deployPlugin resets currentDeployTarget to
|
|
1056
|
-
// Domain after P.make() returns, before any Pulumi.Output async callbacks run.
|
|
1057
|
-
let capturedDeployTarget = currentDeployTarget.contents
|
|
1058
|
-
|
|
1059
|
-
switch platformStackRef {
|
|
1060
|
-
| Some(_) =>
|
|
1061
|
-
// Phase 3 — staged deploy against a running platform (deployPlugin): register the
|
|
1062
|
-
// fragment via the Platform API; the reactive single writer (2e) stitches + pushes.
|
|
1063
|
-
let apiTargetName = switch capturedDeployTarget {
|
|
1064
|
-
| Domain => "Domain"
|
|
1065
|
-
| Platform => "Platform"
|
|
1066
|
-
}
|
|
1067
|
-
registerFragmentViaApi(~name, ~fragment=pluginFragment, ~apiTargetName)
|
|
1068
|
-
| None =>
|
|
1069
|
-
// makePlatform(~plugins=[…]) all-at-once deploy-schema push retired in Phase 4b.
|
|
1070
|
-
// Staged deployPlatform + deployPlugin (register + reactive SideEffect push) is the
|
|
1071
|
-
// sole supported AWS deploy path. deployPlatform deploys no plugins, so this hook only
|
|
1072
|
-
// fires for makePlatform-with-plugins — no longer supported on AWS.
|
|
1073
|
-
failwith(
|
|
1074
|
-
"makePlatform(~plugins=[…]) is no longer supported on AWS — deploy the platform with " ++
|
|
1075
|
-
"deployPlatform and each plugin with deployPlugin (staged register + reactive push).",
|
|
670
|
+
->Pulumi.Output.fromPromise
|
|
671
|
+
})
|
|
1076
672
|
)
|
|
1077
|
-
|
|
1078
|
-
|
|
673
|
+
mergedSchemaPushedRef := Some(pushed)
|
|
674
|
+
pushed
|
|
675
|
+
},
|
|
1079
676
|
// DCB EventLog created hook — extracts DynamoDB table name for DCB CommandTopic Lambda handler.
|
|
1080
677
|
// Postgres-backed DCB logs (B2.3c) create no table, so there is no resource to read:
|
|
1081
678
|
// the DCB command Lambda derives its `dcb_event.log_name` from the plugin name and
|
|
@@ -1258,49 +855,6 @@ module MakeWithConfig = (
|
|
|
1258
855
|
PluginReadModelMappings,
|
|
1259
856
|
)
|
|
1260
857
|
|
|
1261
|
-
// Admin-internal ApiFragmentRegistry singleton aggregate — the platform API-schema
|
|
1262
|
-
// fragment registry (docs/plans/event-sourced-fragment-registries.md). Threaded into
|
|
1263
|
-
// Admin.construct's ~aggregates so registerAdminAggregateMutations wires
|
|
1264
|
-
// Platform_ApiFragmentRegistry_RegisterApiFragment / _DeregisterApiFragment via the
|
|
1265
|
-
// standard CommandGenerator auto-flow (RecordApiFragmentPush carries `@noApi`). IAM
|
|
1266
|
-
// dual-auth for the SigV4 deploy caller comes from AdminApi.systemCallerFieldNames.
|
|
1267
|
-
module ApiFragmentRegistryAggregate: (
|
|
1268
|
-
ReventlessInfra.Aggregate.T with type api = Types.AppSync.api
|
|
1269
|
-
) = Aggregate_Builder_Single.Make(
|
|
1270
|
-
ReventlessCore.ApiFragmentRegistrySpec,
|
|
1271
|
-
ReventlessCore.ApiFragmentRegistryBehavior,
|
|
1272
|
-
ReventlessInfra.NoEventMappings.Make(ReventlessCore.ApiFragmentRegistrySpec),
|
|
1273
|
-
)
|
|
1274
|
-
|
|
1275
|
-
// Admin ApiFragments read model — per-plugin status rows off the aggregate's event
|
|
1276
|
-
// topic (DynamoDB stream), backing the Platform_ApiFragments status query.
|
|
1277
|
-
module ApiFragmentsReadModelMappings: Reventless.Projection.Mappings
|
|
1278
|
-
with module Target := ReventlessCore.ApiFragmentsReadModelSpec = {
|
|
1279
|
-
module M = ReventlessCore.ApiFragmentsProjection.Mappings
|
|
1280
|
-
module type Mapping = M.Mapping
|
|
1281
|
-
let moduleUrl: string = ReventlessCore.ApiFragmentsProjection.moduleUrl
|
|
1282
|
-
let mappings: array<module(Mapping)> = ReventlessCore.ApiFragmentsProjection.mappings
|
|
1283
|
-
}
|
|
1284
|
-
// NoResolver builder: the ApiFragments RM's query surface is the dedicated
|
|
1285
|
-
// Platform_ApiFragments Lambda (declared in baseFragment), NOT an auto-generated
|
|
1286
|
-
// Connection resolver — so it must not emit AppSync query resolvers (they'd orphan
|
|
1287
|
-
// against the static pushed baseFragment: "No field named apiFragments on type Query").
|
|
1288
|
-
// Still stream-projects the table from the aggregate's event stream. Mirrors local's
|
|
1289
|
-
// MakeNoResolver.
|
|
1290
|
-
module ApiFragmentsReadModel = ReadModel_Builder_NoResolver_Stream.Make(
|
|
1291
|
-
ReventlessCore.ApiFragmentsReadModelSpec,
|
|
1292
|
-
ApiFragmentsReadModelMappings,
|
|
1293
|
-
)
|
|
1294
|
-
|
|
1295
|
-
// Admin SideEffectHandler hosting the reactive schema-push side effect (ApiSchemaPush).
|
|
1296
|
-
// Subscribes to the ApiFragmentRegistry aggregate's DynamoDB stream (single-shard for a
|
|
1297
|
-
// singleton → naturally serialized, no concurrent StartSchemaCreation) and, on each
|
|
1298
|
-
// ApiSchemaComputed, stitches + pushes the schema per target API, then records the
|
|
1299
|
-
// outcome via RecordApiFragmentPush. Wired in deployPlatform (the staged path where the
|
|
1300
|
-
// deploy caller fires RegisterApiFragment); makePlatform builds the schema directly and
|
|
1301
|
-
// never triggers it.
|
|
1302
|
-
module AdminApiSchemaPushHandler = SideEffectHandler_Single.Make()
|
|
1303
|
-
|
|
1304
858
|
module type PluginMaker = {
|
|
1305
859
|
let make: unit => Plugin.component
|
|
1306
860
|
}
|
|
@@ -1429,193 +983,14 @@ module MakeWithConfig = (
|
|
|
1429
983
|
// In unified mode, makePlatform is a no-op (schema stitching handled by events).
|
|
1430
984
|
let makePlatform = (~version, ~plugins: array<module(PluginMaker)>) => {
|
|
1431
985
|
log.info(~comp="Platform", `v${version}`)
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
)
|
|
1439
|
-
}
|
|
1440
|
-
// Create scheduler and populate platform context refs so Plugin_Builder
|
|
1441
|
-
// can read them without app plugins having to pass them through.
|
|
1442
|
-
let scheduler = makeScheduler()
|
|
1443
|
-
hooks.scheduler := Some(scheduler)
|
|
1444
|
-
hooks.api := Some(domainApi->wrapHookedValue)
|
|
1445
|
-
hooks.apiRole := Some(domainApiRole->wrapHookedValue)
|
|
1446
|
-
|
|
1447
|
-
// Phase 2: create the Platform API resource early — before Admin.construct —
|
|
1448
|
-
// so admin resolvers are attached to the correct API in split mode.
|
|
1449
|
-
// In unified mode this is the same resource as the Domain API.
|
|
1450
|
-
let (platformApi, platformApiRole) = if Config.splitApi {
|
|
1451
|
-
AppSync_Adapter.makeApiResource(~name="PlatformApi", ~opts={})
|
|
1452
|
-
} else {
|
|
1453
|
-
(domainApi, domainApiRole)
|
|
1454
|
-
}
|
|
1455
|
-
|
|
1456
|
-
// Admin DCB mutation resolvers bind to the Platform API (split mode) or the shared api
|
|
1457
|
-
// (unified). Recorded now that the Platform API resource exists, so the admin's deferred
|
|
1458
|
-
// dcbConnectFn (via ~onAdminApi) targets it rather than the Domain/deploy-target hooksApiRef.
|
|
1459
|
-
hooks.adminApi := Some(platformApi->wrapHookedValue)
|
|
1460
|
-
|
|
1461
|
-
// Update splitApiOutputsRef and apiConfig with the now-known Platform API.
|
|
1462
|
-
if Config.splitApi {
|
|
1463
|
-
splitApiOutputsRef := Some({platformApi, platformApiRole})
|
|
1464
|
-
switch apiConfigRef.contents {
|
|
1465
|
-
| Some(c) =>
|
|
1466
|
-
apiConfigRef := Some({
|
|
1467
|
-
domainApi: c.domainApi,
|
|
1468
|
-
domainApiRole: c.domainApiRole,
|
|
1469
|
-
platformApi: platformApi,
|
|
1470
|
-
platformApiRole: platformApiRole,
|
|
1471
|
-
})
|
|
1472
|
-
| None => ()
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
// Phase 2: Admin resolvers go on the Platform API (platformApi) in split mode,
|
|
1477
|
-
// on the shared Domain API in unified mode.
|
|
1478
|
-
let admin = Admin.construct(
|
|
1479
|
-
~version,
|
|
1480
|
-
~extensionPoints=[],
|
|
1481
|
-
~aggregates=[module(PluginAggregate), module(ApiFragmentRegistryAggregate)],
|
|
1482
|
-
~readModels=[module(PluginReadModel), module(ApiFragmentsReadModel)],
|
|
1483
|
-
~scheduler,
|
|
1484
|
-
~resourceNaming=Util_ResourceNaming.operations,
|
|
1485
|
-
~api=platformApi,
|
|
1486
|
-
~apiRole=platformApiRole,
|
|
1487
|
-
~stateChangeSlices=[module(UiFragmentRegistrySlice)],
|
|
1488
|
-
~stateViewSlices=[module(UiFragmentsViewSlice)],
|
|
1489
|
-
~automationSlices=[],
|
|
1490
|
-
~outboundTranslationSlices=[],
|
|
1491
|
-
~inboundTranslationSlices=[],
|
|
1492
|
-
)
|
|
1493
|
-
|
|
1494
|
-
// Mount the Platform_ComponentDefinitions Lambda resolver on the Platform API
|
|
1495
|
-
// (split mode) or Domain API (unified mode — platformApi == domainApi above).
|
|
1496
|
-
// Also register the Plugin RM table with the AllAggregates Lambda runtime
|
|
1497
|
-
// so its in-Lambda plugin status gate can read plugin status at command
|
|
1498
|
-
// dispatch time (Part 2.3 of the resolver plan).
|
|
1499
|
-
switch admin.readModelsOutputs->Dict.get("Plugins") {
|
|
1500
|
-
| Some(pluginRm) =>
|
|
1501
|
-
switch pluginRm.queryDb.resources->Array.get(0) {
|
|
1502
|
-
| Some(r) =>
|
|
1503
|
-
Platform_ComponentDefinitions_Lambda.make(
|
|
1504
|
-
~api=platformApi,
|
|
1505
|
-
~pluginReadModelTableName=r.name,
|
|
1506
|
-
~opts={},
|
|
1507
|
-
)
|
|
1508
|
-
AggregateRuntime_Builder_Single.setPluginReadModelTable(~name=r.name)
|
|
1509
|
-
| None => ()
|
|
1510
|
-
}
|
|
1511
|
-
| None => ()
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
// Mount the Platform_UIFragments Lambda resolver — scans the UiFragments
|
|
1515
|
-
// StateViewSlice table provisioned above and returns one entry per registered
|
|
1516
|
-
// plugin UI.
|
|
1517
|
-
switch admin.stateViewSlicesOutputs->Dict.get("UiFragments") {
|
|
1518
|
-
| Some(rm) =>
|
|
1519
|
-
switch rm.queryDb.resources->Array.get(0) {
|
|
1520
|
-
| Some(r) =>
|
|
1521
|
-
Platform_UIFragments_Lambda.make(
|
|
1522
|
-
~api=platformApi,
|
|
1523
|
-
~uiFragmentRegistryTableName=r.name,
|
|
1524
|
-
~schemaReady=admin.adminSchemaPushed,
|
|
1525
|
-
~opts={},
|
|
1526
|
-
)
|
|
1527
|
-
| None => ()
|
|
1528
|
-
}
|
|
1529
|
-
| None => ()
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
// Mount the Platform_ApiFragments Lambda resolver — scans the ApiFragments
|
|
1533
|
-
// StateViewSlice table and returns the push-status row per plugin (the deploy
|
|
1534
|
-
// waiter polls this). The query field is in the pushed admin base but was
|
|
1535
|
-
// unresolved on AWS until now.
|
|
1536
|
-
switch admin.readModelsOutputs->Dict.get("ApiFragments") {
|
|
1537
|
-
| Some(rm) =>
|
|
1538
|
-
switch rm.queryDb.resources->Array.get(0) {
|
|
1539
|
-
| Some(r) =>
|
|
1540
|
-
Platform_ApiFragments_Lambda.make(
|
|
1541
|
-
~api=platformApi,
|
|
1542
|
-
~apiFragmentRegistryTableName=r.name,
|
|
1543
|
-
~schemaReady=admin.adminSchemaPushed,
|
|
1544
|
-
~opts={},
|
|
1545
|
-
)
|
|
1546
|
-
| None => ()
|
|
1547
|
-
}
|
|
1548
|
-
| None => ()
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
// Build each plugin.
|
|
1552
|
-
let pluginComponents = plugins->Array.map(plugin => {
|
|
1553
|
-
module P = unpack(plugin)
|
|
1554
|
-
P.make()
|
|
1555
|
-
})
|
|
1556
|
-
|
|
1557
|
-
// Note: StateTopic_AppSync.finish runs from inside subscriptionInfraHook
|
|
1558
|
-
// (Phase 4 wiring above), not here — Plugin_Builder fires the hook from
|
|
1559
|
-
// within a Pulumi.Output.apply chain, so finishing synchronously here would
|
|
1560
|
-
// see an empty registry.
|
|
1561
|
-
|
|
1562
|
-
// Export first plugin's outputs (monolithic mode = typically single plugin).
|
|
1563
|
-
switch pluginComponents->Array.get(0) {
|
|
1564
|
-
| Some(pluginComponent) =>
|
|
1565
|
-
let pluginOutputs = pluginComponent->ReventlessCore.Component.outputs
|
|
1566
|
-
ReventlessCore.Plugin_Helpers.exportPluginOutputs(pluginOutputs)
|
|
1567
|
-
| None => ()
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
// B2.3d: provision the Postgres change-feed relay (monolithic mode — all plugins
|
|
1571
|
-
// built above, so the DcbBackend relay registry is complete).
|
|
1572
|
-
provisionPgChangeFeedRelay()
|
|
1573
|
-
|
|
1574
|
-
// B3.2b: provision the shared PgQueryResolver Lambda + AppSync data source for
|
|
1575
|
-
// Postgres-backed read models (monolithic mode). Runs after construct so the
|
|
1576
|
-
// resolver-binding registry (QueryDbResolvers_Lambda.make) and the ReadModel
|
|
1577
|
-
// spec-module registry are complete; it fulfils the deferred `dataSourceName`
|
|
1578
|
-
// the Postgres storage maker handed to the (schema-pushed) resolvers. App read
|
|
1579
|
-
// model resolvers attach to the Domain API. Plugin-stack mode is deferred —
|
|
1580
|
-
// it needs a per-stack data-source name to avoid collisions on the shared API.
|
|
1581
|
-
switch QueryDbBackend.get() {
|
|
1582
|
-
| Some(sel) => PgQueryResolver_Builder.provision(~api=domainApi, ~selection=sel, ~opts={})
|
|
1583
|
-
| None => ()
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
|
-
// Admin schema push is fired by preAdminResolversSchemaHook from inside
|
|
1587
|
-
// Admin.construct (with createResolvers gated on it). Only exports below.
|
|
1588
|
-
if Config.splitApi {
|
|
1589
|
-
// Platform API exports (split mode).
|
|
1590
|
-
Pulumi.Pulumi.export("platformApiId", platformApi->Pulumi.Output.flatMap(api => api.id))
|
|
1591
|
-
Pulumi.Pulumi.export(
|
|
1592
|
-
"platformApiEndpoint",
|
|
1593
|
-
platformApi->Pulumi.Output.flatMap(api =>
|
|
1594
|
-
api.uris->Pulumi.Output.apply(uris => uris.graphQL)
|
|
1595
|
-
),
|
|
1596
|
-
)
|
|
1597
|
-
Pulumi.Pulumi.export("platformApiRoleArn", platformApiRole->Pulumi.Output.flatMap(role => role.arn))
|
|
1598
|
-
} else {
|
|
1599
|
-
// Platform API exports (unified mode — same resource as Domain API).
|
|
1600
|
-
Pulumi.Pulumi.export("platformApiId", domainApi->Pulumi.Output.flatMap(api => api.id))
|
|
1601
|
-
Pulumi.Pulumi.export(
|
|
1602
|
-
"platformApiEndpoint",
|
|
1603
|
-
domainApi->Pulumi.Output.flatMap(api =>
|
|
1604
|
-
api.uris->Pulumi.Output.apply(uris => uris.graphQL)
|
|
1605
|
-
),
|
|
1606
|
-
)
|
|
1607
|
-
Pulumi.Pulumi.export("platformApiRoleArn", domainApiRole->Pulumi.Output.flatMap(role => role.arn))
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
// Domain API exports.
|
|
1611
|
-
Pulumi.Pulumi.export("domainApiId", domainApi->Pulumi.Output.flatMap(api => api.id))
|
|
1612
|
-
Pulumi.Pulumi.export(
|
|
1613
|
-
"domainApiEndpoint",
|
|
1614
|
-
domainApi->Pulumi.Output.flatMap(api =>
|
|
1615
|
-
api.uris->Pulumi.Output.apply(uris => uris.graphQL)
|
|
1616
|
-
),
|
|
986
|
+
let _ = plugins
|
|
987
|
+
// Retired with the merged-API cutover: on AWS the platform deploys with
|
|
988
|
+
// deployPlatform (staged) and each plugin with deployPlugin — makePlatform
|
|
989
|
+
// predates the merge path and never gained merged wiring.
|
|
990
|
+
failwith(
|
|
991
|
+
"makePlatform is not supported on AWS — deploy the platform with deployPlatform and " ++
|
|
992
|
+
"each plugin with deployPlugin (merged-API composition).",
|
|
1617
993
|
)
|
|
1618
|
-
Pulumi.Pulumi.export("domainApiRoleArn", domainApiRole->Pulumi.Output.flatMap(role => role.arn))
|
|
1619
994
|
}
|
|
1620
995
|
|
|
1621
996
|
// Optional host UI shell bundle: a static SPA (e.g. reventless-ui's host-shell)
|
|
@@ -1652,11 +1027,7 @@ module MakeWithConfig = (
|
|
|
1652
1027
|
// On the merge path the Platform API is an ordinary GRAPHQL source API
|
|
1653
1028
|
// carrying the admin canonical document declaratively.
|
|
1654
1029
|
let (platformApi, platformApiRole) = if Config.splitApi {
|
|
1655
|
-
|
|
1656
|
-
AppSync_Adapter.makeSourceApiResource(~name="PlatformApi", ~schema=adminSourceSdl(), ~opts={})
|
|
1657
|
-
} else {
|
|
1658
|
-
AppSync_Adapter.makeApiResource(~name="PlatformApi", ~opts={})
|
|
1659
|
-
}
|
|
1030
|
+
AppSync_Adapter.makeSourceApiResource(~name="PlatformApi", ~schema=adminSourceSdl(), ~opts={})
|
|
1660
1031
|
} else {
|
|
1661
1032
|
(domainApi, domainApiRole)
|
|
1662
1033
|
}
|
|
@@ -1666,7 +1037,7 @@ module MakeWithConfig = (
|
|
|
1666
1037
|
// Plugin stacks associate their own source APIs against the exported
|
|
1667
1038
|
// merged-API ARN (Phase 4); `pulumi destroy` of a plugin stack deletes
|
|
1668
1039
|
// its association — retirement by construction.
|
|
1669
|
-
let mergedOutputs =
|
|
1040
|
+
let mergedOutputs = {
|
|
1670
1041
|
AppSync_MergedApi.assertCompatiblePrimaryAuth(
|
|
1671
1042
|
~sourceMode=AppSync_MergedApi.authenticationTypeName(
|
|
1672
1043
|
AppSync_Adapter.primaryAuthenticationType,
|
|
@@ -1718,8 +1089,6 @@ module MakeWithConfig = (
|
|
|
1718
1089
|
platformMergeGate: gate,
|
|
1719
1090
|
})
|
|
1720
1091
|
}
|
|
1721
|
-
} else {
|
|
1722
|
-
None
|
|
1723
1092
|
}
|
|
1724
1093
|
|
|
1725
1094
|
// Admin DCB mutation resolvers bind to the Platform API (split mode) or the shared api
|
|
@@ -1759,8 +1128,8 @@ module MakeWithConfig = (
|
|
|
1759
1128
|
// (Heartbeat, ForwardCommand). None here keeps the deploy-time path
|
|
1760
1129
|
// unchanged; the .mjs entry point supplies a real implementation.
|
|
1761
1130
|
let manageSubscriptions = None
|
|
1762
|
-
//
|
|
1763
|
-
//
|
|
1131
|
+
// No runtime schema writer exists under merged-API composition — every
|
|
1132
|
+
// source API is its own single writer.
|
|
1764
1133
|
let updateApiSchema = None
|
|
1765
1134
|
})
|
|
1766
1135
|
|
|
@@ -1769,8 +1138,8 @@ module MakeWithConfig = (
|
|
|
1769
1138
|
let admin = Admin.construct(
|
|
1770
1139
|
~version,
|
|
1771
1140
|
~extensionPoints=[module(PluginExtensionPoint)],
|
|
1772
|
-
~aggregates=[module(PluginAggregate)
|
|
1773
|
-
~readModels=[module(PluginReadModel)
|
|
1141
|
+
~aggregates=[module(PluginAggregate)],
|
|
1142
|
+
~readModels=[module(PluginReadModel)],
|
|
1774
1143
|
~scheduler,
|
|
1775
1144
|
~resourceNaming=Util_ResourceNaming.operations,
|
|
1776
1145
|
~api=platformApi,
|
|
@@ -1782,79 +1151,6 @@ module MakeWithConfig = (
|
|
|
1782
1151
|
~inboundTranslationSlices=[],
|
|
1783
1152
|
)
|
|
1784
1153
|
|
|
1785
|
-
// Reactive schema-push SideEffect (docs/plans/event-sourced-fragment-registries.md §
|
|
1786
|
-
// Reactive writer design). Host ApiSchemaPush on an admin SideEffectHandler subscribed to
|
|
1787
|
-
// the ApiFragmentRegistry aggregate's event topic (its DynamoDB stream). Deploy-derived
|
|
1788
|
-
// config the runtime-pure push reads at invocation time is injected as Lambda env
|
|
1789
|
-
// (~extraEnvVars); ~targets grants SQS send to the aggregate command topic (for the
|
|
1790
|
-
// RecordApiFragmentPush write-back). Only in deployPlatform (the staged path where the
|
|
1791
|
-
// deploy caller fires RegisterApiFragment); makePlatform pushes the schema directly.
|
|
1792
|
-
let apiSchemaPushEventTopics = ReventlessCore.Aggregate.allEventTopics(admin.aggregatesOutputs)
|
|
1793
|
-
let apiSchemaPushCmdTopics = ReventlessCore.Aggregate.allCommandTopics(admin.aggregatesOutputs)
|
|
1794
|
-
// MUST be a `switch`, NOT `->Option.map(...)->Option.getOr(...)`. The map/getOr form
|
|
1795
|
-
// materialises an `option<Pulumi.Output.t<string>>`, and wrapping a Pulumi Output in a
|
|
1796
|
-
// ReScript option collapses the nested Output to `undefined` at runtime (the documented
|
|
1797
|
-
// "option(Pulumi.Output.t) doesn't work" pitfall). That made API_SCHEMA_PUSH_CMD_TOPIC_URL
|
|
1798
|
-
// resolve to undefined → Pulumi dropped the env var → the ApiSchemaPush runtime logged
|
|
1799
|
-
// "no command-topic URL configured — skipping" and never pushed/recorded, so the deploy
|
|
1800
|
-
// waiter timed out. Verified via local `pulumi preview`: map/getOr → isValidOutput=false;
|
|
1801
|
-
// switch → isValidOutput=true.
|
|
1802
|
-
let apiSchemaPushCmdTopicUrl = switch admin.aggregatesOutputs->Dict.get(
|
|
1803
|
-
ReventlessCore.ApiFragmentRegistrySpec.name,
|
|
1804
|
-
) {
|
|
1805
|
-
| Some(agg) =>
|
|
1806
|
-
agg.commandTopic->Pulumi.Output.flatMap(ct =>
|
|
1807
|
-
switch ct.resources->Array.get(0) {
|
|
1808
|
-
| Some(r) => r.id
|
|
1809
|
-
| None => Pulumi.Output.make("")
|
|
1810
|
-
}
|
|
1811
|
-
)
|
|
1812
|
-
| None => Pulumi.Output.make("")
|
|
1813
|
-
}
|
|
1814
|
-
let apiSchemaPushEnv = Dict.fromArray([
|
|
1815
|
-
("API_SCHEMA_PUSH_DOMAIN_API_ID", domainApiId->Pulumi.Output.asInput),
|
|
1816
|
-
(
|
|
1817
|
-
"API_SCHEMA_PUSH_PLATFORM_API_ID",
|
|
1818
|
-
platformApi->Pulumi.Output.flatMap(api => api.id)->Pulumi.Output.asInput,
|
|
1819
|
-
),
|
|
1820
|
-
(
|
|
1821
|
-
"API_SCHEMA_PUSH_SPLIT_API",
|
|
1822
|
-
Pulumi.Output.make(Config.splitApi ? "true" : "false")->Pulumi.Output.asInput,
|
|
1823
|
-
),
|
|
1824
|
-
(
|
|
1825
|
-
"API_SCHEMA_PUSH_CLONER",
|
|
1826
|
-
Pulumi.Output.make(Config.cloner ? "true" : "false")->Pulumi.Output.asInput,
|
|
1827
|
-
),
|
|
1828
|
-
("API_SCHEMA_PUSH_CMD_TOPIC_URL", apiSchemaPushCmdTopicUrl->Pulumi.Output.asInput),
|
|
1829
|
-
])
|
|
1830
|
-
// ApiSchemaPush ignores the injected queryEngine (it self-wires push + write-back from
|
|
1831
|
-
// env); an empty one satisfies the SideEffectHandler contract. `scheduler`/`queryEngine`
|
|
1832
|
-
// are Outputs, so make() runs inside their apply; finish() (which builds the shared
|
|
1833
|
-
// Lambda from the registered handlers) is registered on apiSchemaPushCmdTopics
|
|
1834
|
-
// immediately after make() so it runs AFTER make's own deferred forEventCollector apply
|
|
1835
|
-
// on that same Output (Pulumi runs same-Output apply callbacks in registration order).
|
|
1836
|
-
let apiSchemaPushQueryEngine = QueryEngine.DynamoDb.make(Dict.make())
|
|
1837
|
-
let _ =
|
|
1838
|
-
(scheduler, apiSchemaPushQueryEngine)
|
|
1839
|
-
->Pulumi.Output.all2
|
|
1840
|
-
->Pulumi.Output.apply(((sched, qe)) => {
|
|
1841
|
-
let _ = AdminApiSchemaPushHandler.make(
|
|
1842
|
-
~name="AdminApiSchemaPush",
|
|
1843
|
-
~sideEffects=[module(ApiSchemaPush)],
|
|
1844
|
-
~allEventTopics=apiSchemaPushEventTopics,
|
|
1845
|
-
~allCommandTopics=apiSchemaPushCmdTopics,
|
|
1846
|
-
~targets=[ReventlessCore.ApiFragmentRegistrySpec.name],
|
|
1847
|
-
~queryEngine=qe,
|
|
1848
|
-
~scheduler=sched,
|
|
1849
|
-
~resourceNaming=Util_ResourceNaming.operations,
|
|
1850
|
-
~extraEnvVars=apiSchemaPushEnv,
|
|
1851
|
-
~opts={},
|
|
1852
|
-
)
|
|
1853
|
-
let _ = apiSchemaPushCmdTopics->Pulumi.Output.apply(_ =>
|
|
1854
|
-
SideEffectHandlerRuntime_Builder_Single.finish()
|
|
1855
|
-
)
|
|
1856
|
-
})
|
|
1857
|
-
|
|
1858
1154
|
// Extract Plugin RM table name as Output.t<string>.
|
|
1859
1155
|
// IMPORTANT: Do NOT use option<Pulumi.Output.t<…>> — Pulumi Outputs use property
|
|
1860
1156
|
// lifting, which breaks ReScript's internal option encoding (BS_PRIVATE_NESTED_SOME_NONE).
|
|
@@ -1940,25 +1236,6 @@ module MakeWithConfig = (
|
|
|
1940
1236
|
| None => ()
|
|
1941
1237
|
}
|
|
1942
1238
|
|
|
1943
|
-
// Mount the Platform_ApiFragments Lambda resolver — scans the ApiFragments
|
|
1944
|
-
// StateViewSlice table and returns the push-status row per plugin (the deploy
|
|
1945
|
-
// waiter polls this). The query field is in the pushed admin base but was
|
|
1946
|
-
// unresolved on AWS until now.
|
|
1947
|
-
switch admin.readModelsOutputs->Dict.get("ApiFragments") {
|
|
1948
|
-
| Some(rm) =>
|
|
1949
|
-
switch rm.queryDb.resources->Array.get(0) {
|
|
1950
|
-
| Some(r) =>
|
|
1951
|
-
Platform_ApiFragments_Lambda.make(
|
|
1952
|
-
~api=platformApi,
|
|
1953
|
-
~apiFragmentRegistryTableName=r.name,
|
|
1954
|
-
~schemaReady=admin.adminSchemaPushed,
|
|
1955
|
-
~opts={},
|
|
1956
|
-
)
|
|
1957
|
-
| None => ()
|
|
1958
|
-
}
|
|
1959
|
-
| None => ()
|
|
1960
|
-
}
|
|
1961
|
-
|
|
1962
1239
|
// Note: StateTopic_AppSync.finish runs from inside subscriptionInfraHook
|
|
1963
1240
|
// (Phase 4 wiring above), not here — see the note in deployPlatform.
|
|
1964
1241
|
|
|
@@ -2314,8 +1591,8 @@ module MakeWithConfig = (
|
|
|
2314
1591
|
// 409s (AWS serializes association creates per merged API) surface as a
|
|
2315
1592
|
// deploy failure — retry concurrent FIRST-TIME plugin deploys; steady-
|
|
2316
1593
|
// state schema updates never re-create the association.
|
|
2317
|
-
switch
|
|
2318
|
-
|
|
|
1594
|
+
switch platformStackRef {
|
|
1595
|
+
| Some(stackRef) =>
|
|
2319
1596
|
let defaultOutput: Pulumi.Output.t<option<JSON.t>> =
|
|
2320
1597
|
stackRef->Pulumi.StackReference.getOutput("default")
|
|
2321
1598
|
let getMergedExport = (key: string): Pulumi.Output.t<string> => {
|
|
@@ -2402,19 +1679,10 @@ module MakeWithConfig = (
|
|
|
2402
1679
|
provisionPgChangeFeedRelay()
|
|
2403
1680
|
|
|
2404
1681
|
// B3.2b/c: provision this plugin stack's PgQueryResolver Lambda + AppSync data
|
|
2405
|
-
// source for its Postgres-backed read models (registered during P.make()).
|
|
2406
|
-
//
|
|
2407
|
-
// data source, so plugin stacks don't collide. The node(id) resolver is skipped
|
|
2408
|
-
// in plugin-stack mode — it's a single shared Query.node field only one stack
|
|
2409
|
-
// may own (monolithic-only; see PgQueryResolver_Builder.provision).
|
|
1682
|
+
// source for its Postgres-backed read models (registered during P.make()).
|
|
1683
|
+
// Resolvers attach to this plugin's own source API.
|
|
2410
1684
|
switch QueryDbBackend.get() {
|
|
2411
|
-
| Some(sel) =>
|
|
2412
|
-
PgQueryResolver_Builder.provision(
|
|
2413
|
-
~api=domainApi,
|
|
2414
|
-
~selection=sel,
|
|
2415
|
-
~opts={},
|
|
2416
|
-
~createNodeResolver=false,
|
|
2417
|
-
)
|
|
1685
|
+
| Some(sel) => PgQueryResolver_Builder.provision(~api=domainApi, ~selection=sel, ~opts={})
|
|
2418
1686
|
| None => ()
|
|
2419
1687
|
}
|
|
2420
1688
|
|
|
@@ -2475,7 +1743,6 @@ module Make = (): (
|
|
|
2475
1743
|
) => {
|
|
2476
1744
|
include MakeWithConfig({
|
|
2477
1745
|
let splitApi = true
|
|
2478
|
-
let mergedApi = true
|
|
2479
1746
|
let cloner = false
|
|
2480
1747
|
let commandHandlerConfig: ReventlessCore.Runtime.commandHandlerConfigs = {}
|
|
2481
1748
|
let pgConnection = None
|