@reventlessdev/reventless-aws 3.0.0-alpha.209 → 3.0.0-alpha.210

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.
@@ -157,6 +157,77 @@ let getClient = () =>
157
157
  c
158
158
  }
159
159
 
160
+ // ── GetSourceApiAssociation — merged-API association status poll ──────────
161
+ // Merged-API deploys must fail loudly on MERGE_FAILED (plan
162
+ // merged-api-push-free-composition, Phase 0 finding: a failed merge silently
163
+ // keeps the last-good merged schema serving). After creating a
164
+ // SourceApiAssociation, poll until the initial merge lands.
165
+ type getSourceApiAssociationInput = {
166
+ associationId: string,
167
+ mergedApiIdentifier: string,
168
+ }
169
+ type getSourceApiAssociationCommand
170
+ type sourceApiAssociationSummary = {
171
+ sourceApiAssociationStatus: option<string>,
172
+ sourceApiAssociationStatusDetail: option<string>,
173
+ }
174
+ type getSourceApiAssociationResult = {sourceApiAssociation: option<sourceApiAssociationSummary>}
175
+
176
+ @module("@aws-sdk/client-appsync") @new
177
+ external makeGetSourceApiAssociationCommand: getSourceApiAssociationInput => getSourceApiAssociationCommand =
178
+ "GetSourceApiAssociationCommand"
179
+
180
+ @send
181
+ external sendGetSourceApiAssociation: (
182
+ appSyncClient,
183
+ getSourceApiAssociationCommand,
184
+ ) => promise<getSourceApiAssociationResult> = "send"
185
+
186
+ // Poll until the association reports MERGE_SUCCESS; throw with the AWS status
187
+ // detail on MERGE_FAILED / AUTO_MERGE_SCHEDULE_FAILED. Auto-merge lands in
188
+ // ~12 s (spike-measured), so 60 × 2 s bounds the wait at two minutes.
189
+ let rec waitForMergeSuccess = async (
190
+ client: appSyncClient,
191
+ ~associationId: string,
192
+ ~mergedApiIdentifier: string,
193
+ ~maxAttempts=60,
194
+ ~attempt=0,
195
+ ~delayMs=2000,
196
+ ) => {
197
+ let result = await client->sendGetSourceApiAssociation(
198
+ {associationId, mergedApiIdentifier}->makeGetSourceApiAssociationCommand,
199
+ )
200
+ let status =
201
+ result.sourceApiAssociation
202
+ ->Option.flatMap(a => a.sourceApiAssociationStatus)
203
+ ->Option.getOr("(no status)")
204
+ let detail =
205
+ result.sourceApiAssociation
206
+ ->Option.flatMap(a => a.sourceApiAssociationStatusDetail)
207
+ ->Option.getOr("(no details)")
208
+ switch status {
209
+ | "MERGE_SUCCESS" => ()
210
+ | "MERGE_FAILED" | "AUTO_MERGE_SCHEDULE_FAILED" =>
211
+ JsError.throwWithMessage(
212
+ `Source API association ${associationId} on ${mergedApiIdentifier} failed to merge (${status}): ${detail}`,
213
+ )
214
+ | _ if attempt >= maxAttempts =>
215
+ JsError.throwWithMessage(
216
+ `Source API association ${associationId} merge timed out after ${maxAttempts->Int.toString} attempts (status: ${status})`,
217
+ )
218
+ | _ =>
219
+ await Promise.make((resolve, _) => setTimeout(resolve, delayMs)->ignore)
220
+ await waitForMergeSuccess(
221
+ client,
222
+ ~associationId,
223
+ ~mergedApiIdentifier,
224
+ ~maxAttempts,
225
+ ~attempt=attempt + 1,
226
+ ~delayMs,
227
+ )
228
+ }
229
+ }
230
+
160
231
  // ── @aws_auth directive injection ─────────────────────────────────────────
161
232
  // Injects @aws_auth(cognito_groups: [...]) directives into SDL field strings
162
233
  // based on authorization metadata from schema entries.
@@ -433,13 +504,40 @@ let stitchWithAwsDirectives = (
433
504
  ->stampSharedIamTypes
434
505
  }
435
506
 
507
+ /**
508
+ Merged-mode plugin subgraph document: one fragment rendered standalone
509
+ (relay base types included, global `node` omitted — only the platform's
510
+ canonical base document carries `node`), with the same AppSync dialect as
511
+ `stitchWithAwsDirectives`. No `@canonical` stamps — plugin subgraphs stay
512
+ unstamped; the admin source's canonical definitions win on merge.
513
+ */
514
+ let stitchStandaloneWithAwsDirectives = (
515
+ ~fragment: Reventless.Plugin.apiSchemaFragment,
516
+ ): string => {
517
+ let sources = ReventlessCore.GraphQL_Stitcher.collectSubscriptionSources(
518
+ ~baseFragment=fragment,
519
+ ~pluginFragments=[],
520
+ )
521
+ ReventlessCore.GraphQL_Stitcher.stitchStandalone(~fragment)
522
+ ->AppSync_SdlDecorate.injectAwsSubscribe(~sources)
523
+ ->stampSharedIamTypes
524
+ }
525
+
436
526
  // ── Provider implementation ────────────────────────────────────────────────
437
527
 
438
528
  type api = AppSync.GraphQLApi.t
439
529
  type role = IAM.Role.t
440
530
 
441
- let makeApiResource = (
531
+ // Primary authentication mode every platform-created AppSync API uses. A
532
+ // merged API and its source APIs must share this primary mode — exported via
533
+ // StackReference (`mergedApiPrimaryAuth`) and asserted where associations are
534
+ // created (AppSync_MergedApi.assertCompatiblePrimaryAuth).
535
+ let primaryAuthenticationType = AppSync.GraphQLApi.AMAZON_COGNITO_USER_POOLS
536
+
537
+ let _makeApiResourceWith = (
442
538
  ~name: string,
539
+ ~schema: option<string>,
540
+ ~userPoolConfig: option<Pulumi.Output.t<AppSync.GraphQLApi.userPoolConfig>>,
443
541
  ~opts: Pulumi.ComponentResource.options,
444
542
  ): (Pulumi.Output.t<api>, Pulumi.Output.t<role>) => {
445
543
  let customOpts: Pulumi.CustomResourceOptions.t = {
@@ -456,13 +554,16 @@ let makeApiResource = (
456
554
  ~opts=Some(customOpts),
457
555
  )
458
556
 
459
- // Resolve the Cognito UserPool — cached inside Platform_Stack, so calling
460
- // from each API call site (DomainApi, PlatformApi) is safe. Returned as a
461
- // single Output that yields the {userPoolId, awsRegion, defaultAction}
462
- // record AppSync expects.
463
- let authConfigOut = Auth_Cognito.make(~name=`${name}-auth`)
464
- let userPoolConfigOut =
465
- authConfigOut->Pulumi.Output.apply((c: Auth_Cognito.authConfig) =>
557
+ // Resolve the Cognito UserPool — either supplied by the caller (plugin-stack
558
+ // source APIs read it from the platform's StackReference exports so they
559
+ // never provision pool/client resources of their own) or resolved via
560
+ // Auth_Cognito (cached inside Platform_Stack, so calling from each API call
561
+ // site DomainApi, PlatformApi — is safe). A single Output yielding the
562
+ // {userPoolId, awsRegion, defaultAction} record AppSync expects.
563
+ let userPoolConfigOut = switch userPoolConfig {
564
+ | Some(config) => config
565
+ | None =>
566
+ Auth_Cognito.make(~name=`${name}-auth`)->Pulumi.Output.apply((c: Auth_Cognito.authConfig) =>
466
567
  (
467
568
  {
468
569
  userPoolId: c.userPoolId,
@@ -471,13 +572,13 @@ let makeApiResource = (
471
572
  }: AppSync.GraphQLApi.userPoolConfig
472
573
  )
473
574
  )
575
+ }
474
576
 
475
577
  // Cognito as primary auth, AWS_IAM as additional provider for
476
578
  // server-to-server lambdas (heartbeat, Plugin_Connected emission) signed via
477
579
  // the existing IAM role.
478
580
  let apiArgs: AppSync.GraphQLApi.args = {
479
- authenticationType: AppSync.GraphQLApi
480
- .AMAZON_COGNITO_USER_POOLS->Pulumi.Input.make,
581
+ authenticationType: primaryAuthenticationType->Pulumi.Input.make,
481
582
  userPoolConfig: userPoolConfigOut->Pulumi.Output.asInput,
482
583
  additionalAuthenticationProviders: [
483
584
  (
@@ -486,12 +587,45 @@ let makeApiResource = (
486
587
  }: AppSync.GraphQLApi.additionalAuthenticationProvider
487
588
  )->Pulumi.Input.make,
488
589
  ]->Pulumi.Input.make,
590
+ schema: ?(schema->Option.map(Pulumi.Input.make)),
489
591
  }
490
592
  let graphQLApi = AppSync.GraphQLApi.make(~name, ~args=apiArgs, ~opts=Some(customOpts))
491
593
 
492
594
  (graphQLApi->Pulumi.Output.make, iamRole->Pulumi.Output.make)
493
595
  }
494
596
 
597
+ let makeApiResource = (
598
+ ~name: string,
599
+ ~opts: Pulumi.ComponentResource.options,
600
+ ): (Pulumi.Output.t<api>, Pulumi.Output.t<role>) =>
601
+ _makeApiResourceWith(~name, ~schema=None, ~userPoolConfig=None, ~opts)
602
+
603
+ // Merged-mode source API: same auth shape as makeApiResource but with a
604
+ // DECLARATIVE inline schema — the provider runs StartSchemaCreation + poll
605
+ // before the resource resolves, so resolvers chained on the API are ordered
606
+ // after the schema is ACTIVE without the push-path hook machinery. Not part
607
+ // of the Api_Adapter.Provider interface (Platform.res calls it directly on
608
+ // the merge path).
609
+ let makeSourceApiResource = (
610
+ ~name: string,
611
+ ~schema: string,
612
+ ~opts: Pulumi.ComponentResource.options,
613
+ ): (Pulumi.Output.t<api>, Pulumi.Output.t<role>) =>
614
+ _makeApiResourceWith(~name, ~schema=Some(schema), ~userPoolConfig=None, ~opts)
615
+
616
+ // Merged-mode PLUGIN source API: schema-less at creation (the plugin's
617
+ // standalone subgraph document is only computable during P.make(), so
618
+ // preResolversSchemaHook pushes it — the plugin's own API is a single writer
619
+ // by construction). The user pool comes from the platform's StackReference
620
+ // exports so the merged endpoint's Cognito primary auth matches across every
621
+ // source API without the plugin stack provisioning pool/client resources.
622
+ let makePluginSourceApiResource = (
623
+ ~name: string,
624
+ ~userPoolConfig: Pulumi.Output.t<AppSync.GraphQLApi.userPoolConfig>,
625
+ ~opts: Pulumi.ComponentResource.options,
626
+ ): (Pulumi.Output.t<api>, Pulumi.Output.t<role>) =>
627
+ _makeApiResourceWith(~name, ~schema=None, ~userPoolConfig=Some(userPoolConfig), ~opts)
628
+
495
629
  let generateFragment = (
496
630
  ~mutationEntries: array<ReventlessInfra.Api.mutationSchemaEntry>,
497
631
  ~queryEntries: array<ReventlessInfra.Api.querySchemaEntry>,
@@ -101,6 +101,35 @@ function getClient() {
101
101
  return c$1;
102
102
  }
103
103
 
104
+ async function waitForMergeSuccess(client, associationId, mergedApiIdentifier, maxAttemptsOpt, attemptOpt, delayMsOpt) {
105
+ let maxAttempts = maxAttemptsOpt !== undefined ? maxAttemptsOpt : 60;
106
+ let attempt = attemptOpt !== undefined ? attemptOpt : 0;
107
+ let delayMs = delayMsOpt !== undefined ? delayMsOpt : 2000;
108
+ let result = await client.send(new ClientAppsync.GetSourceApiAssociationCommand({
109
+ associationId: associationId,
110
+ mergedApiIdentifier: mergedApiIdentifier
111
+ }));
112
+ let status = Stdlib_Option.getOr(Stdlib_Option.flatMap(result.sourceApiAssociation, a => a.sourceApiAssociationStatus), "(no status)");
113
+ let detail = Stdlib_Option.getOr(Stdlib_Option.flatMap(result.sourceApiAssociation, a => a.sourceApiAssociationStatusDetail), "(no details)");
114
+ switch (status) {
115
+ case "AUTO_MERGE_SCHEDULE_FAILED" :
116
+ case "MERGE_FAILED" :
117
+ break;
118
+ case "MERGE_SUCCESS" :
119
+ return;
120
+ default:
121
+ if (attempt >= maxAttempts) {
122
+ return Stdlib_JsError.throwWithMessage(`Source API association ` + associationId + ` merge timed out after ` + maxAttempts.toString() + ` attempts (status: ` + status + `)`);
123
+ } else {
124
+ await new Promise((resolve, param) => {
125
+ setTimeout(resolve, delayMs);
126
+ });
127
+ return await waitForMergeSuccess(client, associationId, mergedApiIdentifier, maxAttempts, attempt + 1 | 0, delayMs);
128
+ }
129
+ }
130
+ return Stdlib_JsError.throwWithMessage(`Source API association ` + associationId + ` on ` + mergedApiIdentifier + ` failed to merge (` + status + `): ` + detail);
131
+ }
132
+
104
133
  function _permissionToCognitoGroups(permission) {
105
134
  if (typeof permission !== "object") {
106
135
  switch (permission) {
@@ -268,7 +297,12 @@ function stitchWithAwsDirectives(baseFragment, pluginFragments) {
268
297
  return AppSync_SdlDecorate$ReventlessAws.stampSharedIamTypes(AppSync_SdlDecorate$ReventlessAws.injectAwsSubscribe(GraphQL_Stitcher$ReventlessCore.stitch(baseFragment, pluginFragments), sources));
269
298
  }
270
299
 
271
- function makeApiResource(name, opts) {
300
+ function stitchStandaloneWithAwsDirectives(fragment) {
301
+ let sources = GraphQL_Stitcher$ReventlessCore.collectSubscriptionSources(fragment, []);
302
+ return AppSync_SdlDecorate$ReventlessAws.stampSharedIamTypes(AppSync_SdlDecorate$ReventlessAws.injectAwsSubscribe(GraphQL_Stitcher$ReventlessCore.stitchStandalone(fragment), sources));
303
+ }
304
+
305
+ function _makeApiResourceWith(name, schema, userPoolConfig, opts) {
272
306
  let customOpts_parent = opts.parent;
273
307
  let customOpts = {
274
308
  parent: customOpts_parent
@@ -276,18 +310,19 @@ function makeApiResource(name, opts) {
276
310
  let iamRole = new (Aws.iam.Role)(name + `-appsync-role`, {
277
311
  assumeRolePolicy: `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"appsync.amazonaws.com"},"Action":"sts:AssumeRole"}]}`
278
312
  }, customOpts);
279
- let authConfigOut = Auth_Cognito$ReventlessAws.make(name + `-auth`, undefined);
280
- let userPoolConfigOut = authConfigOut.apply(c => ({
281
- userPoolId: c.userPoolId,
282
- defaultAction: "ALLOW",
283
- awsRegion: c.region
284
- }));
313
+ let userPoolConfigOut = userPoolConfig !== undefined ? userPoolConfig : Auth_Cognito$ReventlessAws.make(name + `-auth`, undefined).apply(c => ({
314
+ userPoolId: c.userPoolId,
315
+ defaultAction: "ALLOW",
316
+ awsRegion: c.region
317
+ }));
318
+ let apiArgs_schema = Stdlib_Option.map(schema, prim => prim);
285
319
  let apiArgs_userPoolConfig = userPoolConfigOut;
286
320
  let apiArgs_additionalAuthenticationProviders = [{
287
321
  authenticationType: "AWS_IAM"
288
322
  }];
289
323
  let apiArgs = {
290
324
  authenticationType: "AMAZON_COGNITO_USER_POOLS",
325
+ schema: apiArgs_schema,
291
326
  userPoolConfig: apiArgs_userPoolConfig,
292
327
  additionalAuthenticationProviders: apiArgs_additionalAuthenticationProviders
293
328
  };
@@ -298,6 +333,18 @@ function makeApiResource(name, opts) {
298
333
  ];
299
334
  }
300
335
 
336
+ function makeApiResource(name, opts) {
337
+ return _makeApiResourceWith(name, undefined, undefined, opts);
338
+ }
339
+
340
+ function makeSourceApiResource(name, schema, opts) {
341
+ return _makeApiResourceWith(name, schema, undefined, opts);
342
+ }
343
+
344
+ function makePluginSourceApiResource(name, userPoolConfig, opts) {
345
+ return _makeApiResourceWith(name, undefined, userPoolConfig, opts);
346
+ }
347
+
301
348
  function generateFragment(mutationEntries, queryEntries) {
302
349
  let fragment = GraphQL_FragmentGenerator$ReventlessCore.generate(mutationEntries, queryEntries);
303
350
  return injectAwsAuth(fragment, mutationEntries, queryEntries);
@@ -319,6 +366,8 @@ function updateSchema(api, baseFragment, pluginFragments) {
319
366
 
320
367
  let stampSharedIamTypes = AppSync_SdlDecorate$ReventlessAws.stampSharedIamTypes;
321
368
 
369
+ let primaryAuthenticationType = "AMAZON_COGNITO_USER_POOLS";
370
+
322
371
  export {
323
372
  log,
324
373
  sha256Hex,
@@ -329,6 +378,7 @@ export {
329
378
  deploySchemaWithRetry,
330
379
  _client,
331
380
  getClient,
381
+ waitForMergeSuccess,
332
382
  _permissionToCognitoGroups,
333
383
  _formatGroupsDirective,
334
384
  _formatDualAuthDirective,
@@ -338,7 +388,12 @@ export {
338
388
  injectAwsAuth,
339
389
  injectAwsAuthAll,
340
390
  stitchWithAwsDirectives,
391
+ stitchStandaloneWithAwsDirectives,
392
+ primaryAuthenticationType,
393
+ _makeApiResourceWith,
341
394
  makeApiResource,
395
+ makeSourceApiResource,
396
+ makePluginSourceApiResource,
342
397
  generateFragment,
343
398
  updateSchema,
344
399
  }
@@ -0,0 +1,218 @@
1
+ // AppSync_MergedApi — merged-API construction for the push-free composition
2
+ // path (docs/plans/merged-api-push-free-composition.md, Phase 3).
3
+ //
4
+ // A merged API carries no schema of its own: source APIs contribute theirs via
5
+ // SourceApiAssociation and AWS composes the merged endpoint. The platform
6
+ // stack creates the merged API(s) plus the association(s) for its own
7
+ // source(s) (the admin/base canonical source); plugin stacks associate their
8
+ // source APIs against the exported merged-API ARN (Phase 4).
9
+
10
+ open PulumiAws
11
+
12
+ // ── Primary-auth contract ───────────────────────────────────────────────────
13
+ // A merged API and every associated source API must share the same PRIMARY
14
+ // authentication mode (AWS rejects incompatible associations). The platform
15
+ // exports the merged API's mode via StackReference (`mergedApiPrimaryAuth`);
16
+ // stacks creating a source API assert against it before associating.
17
+
18
+ let authenticationTypeName = (t: AppSync.GraphQLApi.authenticationType): string =>
19
+ switch t {
20
+ | API_KEY => "API_KEY"
21
+ | AWS_IAM => "AWS_IAM"
22
+ | AMAZON_COGNITO_USER_POOLS => "AMAZON_COGNITO_USER_POOLS"
23
+ | OPENID_CONNECT => "OPENID_CONNECT"
24
+ }
25
+
26
+ // The merged APIs share the platform-wide primary mode (Cognito primary,
27
+ // IAM secondary) — same shape the Phase-0 spike validated end-to-end.
28
+ let primaryAuthMode = authenticationTypeName(AppSync_Adapter.primaryAuthenticationType)
29
+
30
+ let assertCompatiblePrimaryAuth = (~sourceMode: string, ~mergedMode: string): unit =>
31
+ if sourceMode != mergedMode {
32
+ failwith(
33
+ `Merged-API primary-auth mismatch: the source API uses ${sourceMode} but the merged API ` ++
34
+ `expects ${mergedMode}. A source API must share the merged API's primary authentication ` ++
35
+ `mode — align the source API's authenticationType with the platform's ` ++ `\`mergedApiPrimaryAuth\` export before associating.`,
36
+ )
37
+ }
38
+
39
+ // ── Merged API + execution role ─────────────────────────────────────────────
40
+
41
+ type t = {
42
+ api: Pulumi.Output.t<AppSync.GraphQLApi.t>,
43
+ executionRole: IAM.Role.t,
44
+ }
45
+
46
+ let make = (~name: string, ~opts: Pulumi.ComponentResource.options): t => {
47
+ let customOpts: Pulumi.CustomResourceOptions.t = {
48
+ parent: ?opts.parent,
49
+ }
50
+
51
+ // Execution role AWS assumes to proxy merged-endpoint requests to the source
52
+ // APIs (appsync:SourceGraphQL) and to run schema merges
53
+ // (appsync:StartSchemaMerge — required for MANUAL_MERGE, harmless under
54
+ // AUTO_MERGE). Source APIs associate later from independent plugin stacks,
55
+ // so their ARNs cannot be enumerated here — the policy stays unscoped.
56
+ let executionRole = IAM.Role.make(
57
+ ~name=`${name}-merge-exec-role`,
58
+ ~args={
59
+ assumeRolePolicy: `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"appsync.amazonaws.com"},"Action":"sts:AssumeRole"}]}`->Pulumi.Input.make,
60
+ },
61
+ ~opts=Some(customOpts),
62
+ )
63
+ let _ = {
64
+ open PolicyDocument
65
+ IAM.RolePolicy.make(
66
+ ~name=`${name}-merge-exec-policy`,
67
+ ~args={
68
+ policy: PolicyDocument.make(
69
+ ~id=`${name}-merge-exec-policy`,
70
+ ~statements=[
71
+ {
72
+ sid: "AllowSourceGraphQLAndMerge",
73
+ effect: Allow,
74
+ actions: Actions(["appsync:SourceGraphQL", "appsync:StartSchemaMerge"]),
75
+ resources: AllResources,
76
+ },
77
+ ],
78
+ )
79
+ ->PolicyDocument.toJsonString
80
+ ->Pulumi.Input.make,
81
+ role: executionRole.id->Pulumi.Output.asInput,
82
+ },
83
+ ~opts=customOpts,
84
+ )
85
+ }
86
+
87
+ // Same auth shape as the source APIs (AppSync_Adapter.makeApiResource):
88
+ // Cognito primary + AWS_IAM secondary. Per-field multi-auth directives on
89
+ // the source schemas survive the merge verbatim (Phase-0 finding 4).
90
+ let authConfigOut = Auth_Cognito.make(~name=`${name}-auth`)
91
+ let userPoolConfigOut =
92
+ authConfigOut->Pulumi.Output.apply((
93
+ c: Auth_Cognito.authConfig,
94
+ ): AppSync.GraphQLApi.userPoolConfig => {
95
+ userPoolId: c.userPoolId,
96
+ awsRegion: c.region,
97
+ defaultAction: AppSync.GraphQLApi.ALLOW,
98
+ })
99
+
100
+ let mergedApi = AppSync.GraphQLApi.make(
101
+ ~name,
102
+ ~args={
103
+ authenticationType: AppSync_Adapter.primaryAuthenticationType->Pulumi.Input.make,
104
+ userPoolConfig: userPoolConfigOut->Pulumi.Output.asInput,
105
+ additionalAuthenticationProviders: [
106
+ (
107
+ {
108
+ authenticationType: AppSync.GraphQLApi.AWS_IAM->Pulumi.Input.make,
109
+ }: AppSync.GraphQLApi.additionalAuthenticationProvider
110
+ )->Pulumi.Input.make,
111
+ ]->Pulumi.Input.make,
112
+ apiType: AppSync.GraphQLApi.MERGED->Pulumi.Input.make,
113
+ mergedApiExecutionRoleArn: executionRole.arn->Pulumi.Output.asInput,
114
+ },
115
+ ~opts=Some(customOpts),
116
+ )
117
+
118
+ {api: mergedApi->Pulumi.Output.make, executionRole}
119
+ }
120
+
121
+ // ── Source association ──────────────────────────────────────────────────────
122
+
123
+ /** Associate a source API with the merged API under AUTO_MERGE. AWS
124
+ serializes association CREATES per merged API (409
125
+ ConcurrentModificationException) — the platform stack's own associations
126
+ are inherently sequential; concurrent first-time plugin-stack deploys need
127
+ retry-with-backoff (Phase 4). */
128
+ let associateSource = (
129
+ ~name: string,
130
+ ~mergedApi: t,
131
+ ~sourceApi: Pulumi.Output.t<AppSync.GraphQLApi.t>,
132
+ ~opts: Pulumi.ComponentResource.options,
133
+ ): AppSync.SourceApiAssociation.t => {
134
+ let customOpts: Pulumi.CustomResourceOptions.t = {
135
+ parent: ?opts.parent,
136
+ }
137
+ AppSync.SourceApiAssociation.make(
138
+ ~name,
139
+ ~args={
140
+ mergedApiId: mergedApi.api
141
+ ->Pulumi.Output.flatMap((a: AppSync.GraphQLApi.t) => a.id)
142
+ ->Pulumi.Output.asInput,
143
+ sourceApiId: sourceApi
144
+ ->Pulumi.Output.flatMap((a: AppSync.GraphQLApi.t) => a.id)
145
+ ->Pulumi.Output.asInput,
146
+ sourceApiAssociationConfigs: [
147
+ (
148
+ {
149
+ mergeType: AppSync.SourceApiAssociation.AUTO_MERGE->Pulumi.Input.make,
150
+ }: AppSync.SourceApiAssociation.sourceApiAssociationConfig
151
+ )->Pulumi.Input.make,
152
+ ]->Pulumi.Input.make,
153
+ },
154
+ ~opts=Some(customOpts),
155
+ )
156
+ }
157
+
158
+ /** Associate a source API against a merged API referenced by ARN — the
159
+ plugin-stack form, where the merged API is not a resource in this stack
160
+ but the platform's `domainMergedApiArn` / `platformMergedApiArn`
161
+ StackReference export. Same AUTO_MERGE + create-time 409 caveat as
162
+ `associateSource`. */
163
+ let associateSourceWithMergedArn = (
164
+ ~name: string,
165
+ ~mergedApiArn: Pulumi.Output.t<string>,
166
+ ~sourceApi: Pulumi.Output.t<AppSync.GraphQLApi.t>,
167
+ ~opts: Pulumi.ComponentResource.options,
168
+ ): AppSync.SourceApiAssociation.t => {
169
+ let customOpts: Pulumi.CustomResourceOptions.t = {
170
+ parent: ?opts.parent,
171
+ }
172
+ AppSync.SourceApiAssociation.make(
173
+ ~name,
174
+ ~args={
175
+ mergedApiArn: mergedApiArn->Pulumi.Output.asInput,
176
+ sourceApiId: sourceApi
177
+ ->Pulumi.Output.flatMap((a: AppSync.GraphQLApi.t) => a.id)
178
+ ->Pulumi.Output.asInput,
179
+ sourceApiAssociationConfigs: [
180
+ (
181
+ {
182
+ mergeType: AppSync.SourceApiAssociation.AUTO_MERGE->Pulumi.Input.make,
183
+ }: AppSync.SourceApiAssociation.sourceApiAssociationConfig
184
+ )->Pulumi.Input.make,
185
+ ]->Pulumi.Input.make,
186
+ },
187
+ ~opts=Some(customOpts),
188
+ )
189
+ }
190
+
191
+ /** Deploy-time merge gate: resolves once the association reports
192
+ MERGE_SUCCESS, fails the deploy loudly (with AWS's status detail) on
193
+ MERGE_FAILED. Fold the returned Output into an exported value so it is
194
+ consumed — a MERGE_FAILED then fails `pulumi up` instead of silently
195
+ serving the last-good merged schema (Phase-0 operational finding).
196
+ `mergedApiIdentifier` accepts the merged API's id or ARN. */
197
+ let mergeStatusGateWith = (
198
+ ~mergedApiIdentifier: Pulumi.Output.t<string>,
199
+ ~association: AppSync.SourceApiAssociation.t,
200
+ ): Pulumi.Output.t<unit> =>
201
+ (mergedApiIdentifier, association.associationId)
202
+ ->Pulumi.Output.all2
203
+ ->Pulumi.Output.flatMap(((identifier, associationId)) =>
204
+ AppSync_Adapter.waitForMergeSuccess(
205
+ AppSync_Adapter.getClient(),
206
+ ~associationId,
207
+ ~mergedApiIdentifier=identifier,
208
+ )->Pulumi.Output.fromPromise
209
+ )
210
+
211
+ let mergeStatusGate = (
212
+ ~mergedApi: t,
213
+ ~association: AppSync.SourceApiAssociation.t,
214
+ ): Pulumi.Output.t<unit> =>
215
+ mergeStatusGateWith(
216
+ ~mergedApiIdentifier=mergedApi.api->Pulumi.Output.flatMap((a: AppSync.GraphQLApi.t) => a.id),
217
+ ~association,
218
+ )
@@ -0,0 +1,122 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Pervasives from "@rescript/runtime/lib/es6/Pervasives.js";
4
+ import * as Aws from "@pulumi/aws";
5
+ import * as Output$Pulumi from "@reventlessdev/rescript-pulumi-pulumi/src/Output.res.mjs";
6
+ import * as Pulumi from "@pulumi/pulumi";
7
+ import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
8
+ import * as Auth_Cognito$ReventlessAws from "../../adapter/Auth/Auth_Cognito.res.mjs";
9
+ import * as AppSync_Adapter$ReventlessAws from "./AppSync_Adapter.res.mjs";
10
+
11
+ function authenticationTypeName(t) {
12
+ switch (t) {
13
+ case "API_KEY" :
14
+ return "API_KEY";
15
+ case "AWS_IAM" :
16
+ return "AWS_IAM";
17
+ case "AMAZON_COGNITO_USER_POOLS" :
18
+ return "AMAZON_COGNITO_USER_POOLS";
19
+ case "OPENID_CONNECT" :
20
+ return "OPENID_CONNECT";
21
+ }
22
+ }
23
+
24
+ let primaryAuthMode = authenticationTypeName(AppSync_Adapter$ReventlessAws.primaryAuthenticationType);
25
+
26
+ function assertCompatiblePrimaryAuth(sourceMode, mergedMode) {
27
+ if (sourceMode !== mergedMode) {
28
+ return Pervasives.failwith(`Merged-API primary-auth mismatch: the source API uses ` + sourceMode + ` but the merged API ` + (`expects ` + mergedMode + `. A source API must share the merged API's primary authentication `) + `mode — align the source API's authenticationType with the platform's \`mergedApiPrimaryAuth\` export before associating.`);
29
+ }
30
+ }
31
+
32
+ function make(name, opts) {
33
+ let customOpts_parent = opts.parent;
34
+ let customOpts = {
35
+ parent: customOpts_parent
36
+ };
37
+ let executionRole = new (Aws.iam.Role)(name + `-merge-exec-role`, {
38
+ assumeRolePolicy: `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"appsync.amazonaws.com"},"Action":"sts:AssumeRole"}]}`
39
+ }, customOpts);
40
+ new (Aws.iam.RolePolicy)(name + `-merge-exec-policy`, {
41
+ policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + `-merge-exec-policy`, [{
42
+ Sid: "AllowSourceGraphQLAndMerge",
43
+ Effect: "Allow",
44
+ Action: [
45
+ "appsync:SourceGraphQL",
46
+ "appsync:StartSchemaMerge"
47
+ ],
48
+ Resource: "*"
49
+ }])),
50
+ role: executionRole.id
51
+ }, customOpts);
52
+ let authConfigOut = Auth_Cognito$ReventlessAws.make(name + `-auth`, undefined);
53
+ let userPoolConfigOut = authConfigOut.apply(c => ({
54
+ userPoolId: c.userPoolId,
55
+ defaultAction: "ALLOW",
56
+ awsRegion: c.region
57
+ }));
58
+ let mergedApi = new (Aws.appsync.GraphQLApi)(name, {
59
+ authenticationType: AppSync_Adapter$ReventlessAws.primaryAuthenticationType,
60
+ userPoolConfig: userPoolConfigOut,
61
+ additionalAuthenticationProviders: [{
62
+ authenticationType: "AWS_IAM"
63
+ }],
64
+ apiType: "MERGED",
65
+ mergedApiExecutionRoleArn: executionRole.arn
66
+ }, customOpts);
67
+ return {
68
+ api: Pulumi.output(mergedApi),
69
+ executionRole: executionRole
70
+ };
71
+ }
72
+
73
+ function associateSource(name, mergedApi, sourceApi, opts) {
74
+ let customOpts_parent = opts.parent;
75
+ let customOpts = {
76
+ parent: customOpts_parent
77
+ };
78
+ return new (Aws.appsync.SourceApiAssociation)(name, {
79
+ mergedApiId: Output$Pulumi.flatMap(mergedApi.api, a => a.id),
80
+ sourceApiId: Output$Pulumi.flatMap(sourceApi, a => a.id),
81
+ sourceApiAssociationConfigs: [{
82
+ mergeType: "AUTO_MERGE"
83
+ }]
84
+ }, customOpts);
85
+ }
86
+
87
+ function associateSourceWithMergedArn(name, mergedApiArn, sourceApi, opts) {
88
+ let customOpts_parent = opts.parent;
89
+ let customOpts = {
90
+ parent: customOpts_parent
91
+ };
92
+ return new (Aws.appsync.SourceApiAssociation)(name, {
93
+ mergedApiArn: mergedApiArn,
94
+ sourceApiId: Output$Pulumi.flatMap(sourceApi, a => a.id),
95
+ sourceApiAssociationConfigs: [{
96
+ mergeType: "AUTO_MERGE"
97
+ }]
98
+ }, customOpts);
99
+ }
100
+
101
+ function mergeStatusGateWith(mergedApiIdentifier, association) {
102
+ return Output$Pulumi.flatMap(Pulumi.all([
103
+ mergedApiIdentifier,
104
+ association.associationId
105
+ ]), param => AppSync_Adapter$ReventlessAws.waitForMergeSuccess(AppSync_Adapter$ReventlessAws.getClient(), param[1], param[0], undefined, undefined, undefined));
106
+ }
107
+
108
+ function mergeStatusGate(mergedApi, association) {
109
+ return mergeStatusGateWith(Output$Pulumi.flatMap(mergedApi.api, a => a.id), association);
110
+ }
111
+
112
+ export {
113
+ authenticationTypeName,
114
+ primaryAuthMode,
115
+ assertCompatiblePrimaryAuth,
116
+ make,
117
+ associateSource,
118
+ associateSourceWithMergedArn,
119
+ mergeStatusGateWith,
120
+ mergeStatusGate,
121
+ }
122
+ /* primaryAuthMode Not a pure module */