@reventlessdev/reventless-aws 3.0.0-alpha.208 → 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.
- package/CHANGELOG.md +17 -0
- package/package.json +7 -7
- package/src/Platform.res +478 -545
- package/src/Platform.res.mjs +393 -717
- package/src/adapter/Runtime/AdminEventCollectorEntryPoint.mjs +7 -382
- package/src/adapter/Runtime/ApiSchemaPush_Runtime.mjs +8 -1
- package/src/components/Api/AppSync_Adapter.res +144 -101
- package/src/components/Api/AppSync_Adapter.res.mjs +62 -91
- package/src/components/Api/AppSync_MergedApi.res +218 -0
- package/src/components/Api/AppSync_MergedApi.res.mjs +122 -0
- package/src/components/Api/AppSync_SdlDecorate.res +47 -0
- package/src/components/Api/AppSync_SdlDecorate.res.mjs +39 -0
- package/src/plugin/runtime/PluginRuntime_Builder.res +0 -139
- package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +4 -80
- package/tests/AppSync_AdapterTest.res +176 -0
- package/tests/AppSync_AdapterTest.res.mjs +134 -0
- package/tests/AppSync_SdlDecorateTest.res +27 -0
- package/tests/AppSync_SdlDecorateTest.res.mjs +20 -0
- package/tests/MCP_LambdaTest.res +6 -4
- package/tests/MCP_LambdaTest.res.mjs +2 -2
|
@@ -146,97 +146,6 @@ let deploySchemaWithRetry = (
|
|
|
146
146
|
() => startSchemaCreation(client, {apiId, definition})->Promise.then(_ => Promise.resolve()),
|
|
147
147
|
)->Effect.retry(AppSync_Error.retrySchedule)
|
|
148
148
|
|
|
149
|
-
// ── Schema-push serialization lock ──────────────────────────────────────────
|
|
150
|
-
//
|
|
151
|
-
// Plugin/service stacks share one AppSync API, and StartSchemaCreation REPLACES
|
|
152
|
-
// the whole schema. When two stacks scan the deploy-schema table, stitch, and
|
|
153
|
-
// push concurrently, a push built from a stale scan (missing a peer's not-yet-
|
|
154
|
-
// written fragment) clobbers the peer's fields — orphaning their resolvers
|
|
155
|
-
// (NotFoundException: No field named X). The shrink guard only catches
|
|
156
|
-
// catastrophic (>threshold) drops, not a single dropped field.
|
|
157
|
-
//
|
|
158
|
-
// This lease serialises scan→stitch→push across stacks via a conditional-write
|
|
159
|
-
// lock row in the shared PluginSchemaPersistence table. A push that holds the
|
|
160
|
-
// lease always scans a table already containing every prior push's fragment, so
|
|
161
|
-
// the last push is complete and nothing is clobbered. The lease carries a TTL so
|
|
162
|
-
// a crashed holder cannot deadlock the table; if the lock cannot be acquired
|
|
163
|
-
// within maxWaitMs we proceed best-effort rather than fail the deploy.
|
|
164
|
-
|
|
165
|
-
@val external schemaLockSetTimeout: (unit => unit, int) => unit = "setTimeout"
|
|
166
|
-
let schemaLockSleep = (ms: int): promise<unit> =>
|
|
167
|
-
Promise.make((resolve, _) => schemaLockSetTimeout(() => resolve(), ms))
|
|
168
|
-
let _schemaLockCounter = ref(0)
|
|
169
|
-
|
|
170
|
-
let withSchemaPushLock = async (
|
|
171
|
-
~tableName: string,
|
|
172
|
-
~apiId: string,
|
|
173
|
-
~leaseMs: int=120000,
|
|
174
|
-
~maxWaitMs: int=180000,
|
|
175
|
-
fn: unit => promise<'a>,
|
|
176
|
-
): 'a => {
|
|
177
|
-
open AwsSdk.DynamoDb.DocumentClient
|
|
178
|
-
let lockId = `schema-push-lock:${apiId}`
|
|
179
|
-
_schemaLockCounter := _schemaLockCounter.contents + 1
|
|
180
|
-
let holder = `${apiId}#${Date.now()->Float.toString}#${_schemaLockCounter.contents->Int.toString}`
|
|
181
|
-
|
|
182
|
-
let acquire = async () => {
|
|
183
|
-
let deadline = Date.now() +. maxWaitMs->Int.toFloat
|
|
184
|
-
let acquired = ref(false)
|
|
185
|
-
while !acquired.contents {
|
|
186
|
-
let now = Date.now()
|
|
187
|
-
let ok =
|
|
188
|
-
await PutCommand.make({
|
|
189
|
-
PutCommand.tableName,
|
|
190
|
-
item: Dict.fromArray([
|
|
191
|
-
("id", lockId->JSON.Encode.string),
|
|
192
|
-
("holder", holder->JSON.Encode.string),
|
|
193
|
-
("expiresAt", (now +. leaseMs->Int.toFloat)->JSON.Encode.float),
|
|
194
|
-
])->JSON.Encode.object,
|
|
195
|
-
conditionExpression: "attribute_not_exists(id) OR expiresAt < :now",
|
|
196
|
-
expressionAttributeValues: Dict.fromArray([(":now", now->JSON.Encode.float)]),
|
|
197
|
-
})
|
|
198
|
-
->PutCommand.send
|
|
199
|
-
->Promise.thenResolve(_ => true)
|
|
200
|
-
->Promise.catch(_ => Promise.resolve(false))
|
|
201
|
-
if ok {
|
|
202
|
-
acquired := true
|
|
203
|
-
} else if Date.now() > deadline {
|
|
204
|
-
log.warn(
|
|
205
|
-
~comp="AppSync_Adapter",
|
|
206
|
-
`schema-push lock for ${apiId} not acquired within ${maxWaitMs->Int.toString}ms — proceeding best-effort`,
|
|
207
|
-
)
|
|
208
|
-
acquired := true
|
|
209
|
-
} else {
|
|
210
|
-
await schemaLockSleep(1000)
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
let release = async () => {
|
|
216
|
-
let _ =
|
|
217
|
-
await DeleteCommand.make({
|
|
218
|
-
DeleteCommand.tableName,
|
|
219
|
-
key: Dict.fromArray([("id", lockId->JSON.Encode.string)]),
|
|
220
|
-
conditionExpression: "holder = :holder",
|
|
221
|
-
expressionAttributeValues: Dict.fromArray([(":holder", holder->JSON.Encode.string)]),
|
|
222
|
-
})
|
|
223
|
-
->DeleteCommand.send
|
|
224
|
-
->Promise.thenResolve(_ => ())
|
|
225
|
-
->Promise.catch(_ => Promise.resolve()) // expired lease stolen by a peer — leave theirs intact
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
await acquire()
|
|
229
|
-
try {
|
|
230
|
-
let r = await fn()
|
|
231
|
-
let _ = await release()
|
|
232
|
-
r
|
|
233
|
-
} catch {
|
|
234
|
-
| exn =>
|
|
235
|
-
let _ = await release()
|
|
236
|
-
throw(exn)
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
149
|
// Lazy singleton AppSync client (runtime only)
|
|
241
150
|
let _client: ref<option<appSyncClient>> = ref(None)
|
|
242
151
|
let getClient = () =>
|
|
@@ -248,6 +157,77 @@ let getClient = () =>
|
|
|
248
157
|
c
|
|
249
158
|
}
|
|
250
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
|
+
|
|
251
231
|
// ── @aws_auth directive injection ─────────────────────────────────────────
|
|
252
232
|
// Injects @aws_auth(cognito_groups: [...]) directives into SDL field strings
|
|
253
233
|
// based on authorization metadata from schema entries.
|
|
@@ -524,13 +504,40 @@ let stitchWithAwsDirectives = (
|
|
|
524
504
|
->stampSharedIamTypes
|
|
525
505
|
}
|
|
526
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
|
+
|
|
527
526
|
// ── Provider implementation ────────────────────────────────────────────────
|
|
528
527
|
|
|
529
528
|
type api = AppSync.GraphQLApi.t
|
|
530
529
|
type role = IAM.Role.t
|
|
531
530
|
|
|
532
|
-
|
|
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 = (
|
|
533
538
|
~name: string,
|
|
539
|
+
~schema: option<string>,
|
|
540
|
+
~userPoolConfig: option<Pulumi.Output.t<AppSync.GraphQLApi.userPoolConfig>>,
|
|
534
541
|
~opts: Pulumi.ComponentResource.options,
|
|
535
542
|
): (Pulumi.Output.t<api>, Pulumi.Output.t<role>) => {
|
|
536
543
|
let customOpts: Pulumi.CustomResourceOptions.t = {
|
|
@@ -547,13 +554,16 @@ let makeApiResource = (
|
|
|
547
554
|
~opts=Some(customOpts),
|
|
548
555
|
)
|
|
549
556
|
|
|
550
|
-
// Resolve the Cognito UserPool —
|
|
551
|
-
//
|
|
552
|
-
//
|
|
553
|
-
//
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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) =>
|
|
557
567
|
(
|
|
558
568
|
{
|
|
559
569
|
userPoolId: c.userPoolId,
|
|
@@ -562,13 +572,13 @@ let makeApiResource = (
|
|
|
562
572
|
}: AppSync.GraphQLApi.userPoolConfig
|
|
563
573
|
)
|
|
564
574
|
)
|
|
575
|
+
}
|
|
565
576
|
|
|
566
577
|
// Cognito as primary auth, AWS_IAM as additional provider for
|
|
567
578
|
// server-to-server lambdas (heartbeat, Plugin_Connected emission) signed via
|
|
568
579
|
// the existing IAM role.
|
|
569
580
|
let apiArgs: AppSync.GraphQLApi.args = {
|
|
570
|
-
authenticationType:
|
|
571
|
-
.AMAZON_COGNITO_USER_POOLS->Pulumi.Input.make,
|
|
581
|
+
authenticationType: primaryAuthenticationType->Pulumi.Input.make,
|
|
572
582
|
userPoolConfig: userPoolConfigOut->Pulumi.Output.asInput,
|
|
573
583
|
additionalAuthenticationProviders: [
|
|
574
584
|
(
|
|
@@ -577,12 +587,45 @@ let makeApiResource = (
|
|
|
577
587
|
}: AppSync.GraphQLApi.additionalAuthenticationProvider
|
|
578
588
|
)->Pulumi.Input.make,
|
|
579
589
|
]->Pulumi.Input.make,
|
|
590
|
+
schema: ?(schema->Option.map(Pulumi.Input.make)),
|
|
580
591
|
}
|
|
581
592
|
let graphQLApi = AppSync.GraphQLApi.make(~name, ~args=apiArgs, ~opts=Some(customOpts))
|
|
582
593
|
|
|
583
594
|
(graphQLApi->Pulumi.Output.make, iamRole->Pulumi.Output.make)
|
|
584
595
|
}
|
|
585
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
|
+
|
|
586
629
|
let generateFragment = (
|
|
587
630
|
~mutationEntries: array<ReventlessInfra.Api.mutationSchemaEntry>,
|
|
588
631
|
~queryEntries: array<ReventlessInfra.Api.querySchemaEntry>,
|
|
@@ -10,15 +10,12 @@ import * as Stdlib_String from "@rescript/runtime/lib/es6/Stdlib_String.js";
|
|
|
10
10
|
import * as Effect$1 from "effect/Effect";
|
|
11
11
|
import * as Pulumi from "@pulumi/pulumi";
|
|
12
12
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
13
|
-
import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
|
|
14
13
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
15
14
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
16
|
-
import * as LibDynamodb from "@aws-sdk/lib-dynamodb";
|
|
17
15
|
import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
|
|
18
16
|
import * as ClientAppsync from "@aws-sdk/client-appsync";
|
|
19
17
|
import * as Auth_Cognito$ReventlessAws from "../../adapter/Auth/Auth_Cognito.res.mjs";
|
|
20
18
|
import * as AppSync_Error$ReventlessAws from "../../errors/AppSync_Error.res.mjs";
|
|
21
|
-
import * as DynamoDb_DocumentClient$AwsSdk from "@reventlessdev/rescript-aws-sdk/src/DynamoDb_DocumentClient.res.mjs";
|
|
22
19
|
import * as GraphQL_Stitcher$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_Stitcher.res.mjs";
|
|
23
20
|
import * as AppSync_SdlDecorate$ReventlessAws from "./AppSync_SdlDecorate.res.mjs";
|
|
24
21
|
import * as GraphQL_FragmentGenerator$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_FragmentGenerator.res.mjs";
|
|
@@ -90,84 +87,6 @@ function deploySchemaWithRetry(client, apiId, definition) {
|
|
|
90
87
|
})).then(param => Promise.resolve())), AppSync_Error$ReventlessAws.retrySchedule);
|
|
91
88
|
}
|
|
92
89
|
|
|
93
|
-
function schemaLockSleep(ms) {
|
|
94
|
-
return new Promise((resolve, param) => {
|
|
95
|
-
setTimeout(() => resolve(), ms);
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
let _schemaLockCounter = {
|
|
100
|
-
contents: 0
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
async function withSchemaPushLock(tableName, apiId, leaseMsOpt, maxWaitMsOpt, fn) {
|
|
104
|
-
let leaseMs = leaseMsOpt !== undefined ? leaseMsOpt : 120000;
|
|
105
|
-
let maxWaitMs = maxWaitMsOpt !== undefined ? maxWaitMsOpt : 180000;
|
|
106
|
-
let lockId = `schema-push-lock:` + apiId;
|
|
107
|
-
_schemaLockCounter.contents = _schemaLockCounter.contents + 1 | 0;
|
|
108
|
-
let holder = apiId + `#` + Date.now().toString() + `#` + _schemaLockCounter.contents.toString();
|
|
109
|
-
let acquire = async () => {
|
|
110
|
-
let deadline = Date.now() + maxWaitMs;
|
|
111
|
-
let acquired = false;
|
|
112
|
-
while (!acquired) {
|
|
113
|
-
let now = Date.now();
|
|
114
|
-
let ok = await Stdlib_Promise.$$catch(DynamoDb_DocumentClient$AwsSdk.PutCommand.send(new LibDynamodb.PutCommand({
|
|
115
|
-
Item: Object.fromEntries([
|
|
116
|
-
[
|
|
117
|
-
"id",
|
|
118
|
-
lockId
|
|
119
|
-
],
|
|
120
|
-
[
|
|
121
|
-
"holder",
|
|
122
|
-
holder
|
|
123
|
-
],
|
|
124
|
-
[
|
|
125
|
-
"expiresAt",
|
|
126
|
-
now + leaseMs
|
|
127
|
-
]
|
|
128
|
-
]),
|
|
129
|
-
TableName: tableName,
|
|
130
|
-
ConditionExpression: "attribute_not_exists(id) OR expiresAt < :now",
|
|
131
|
-
ExpressionAttributeValues: Object.fromEntries([[
|
|
132
|
-
":now",
|
|
133
|
-
now
|
|
134
|
-
]])
|
|
135
|
-
})).then(param => true), param => Promise.resolve(false));
|
|
136
|
-
if (ok) {
|
|
137
|
-
acquired = true;
|
|
138
|
-
} else if (Date.now() > deadline) {
|
|
139
|
-
log.warn("AppSync_Adapter", undefined, `schema-push lock for ` + apiId + ` not acquired within ` + maxWaitMs.toString() + `ms — proceeding best-effort`);
|
|
140
|
-
acquired = true;
|
|
141
|
-
} else {
|
|
142
|
-
await schemaLockSleep(1000);
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
};
|
|
146
|
-
let release = async () => {
|
|
147
|
-
await Stdlib_Promise.$$catch(DynamoDb_DocumentClient$AwsSdk.DeleteCommand.send(new LibDynamodb.DeleteCommand({
|
|
148
|
-
TableName: tableName,
|
|
149
|
-
Key: Object.fromEntries([[
|
|
150
|
-
"id",
|
|
151
|
-
lockId
|
|
152
|
-
]]),
|
|
153
|
-
ConditionExpression: "holder = :holder",
|
|
154
|
-
ExpressionAttributeValues: Object.fromEntries([[
|
|
155
|
-
":holder",
|
|
156
|
-
holder
|
|
157
|
-
]])
|
|
158
|
-
})).then(param => {}), param => Promise.resolve());
|
|
159
|
-
};
|
|
160
|
-
await acquire();
|
|
161
|
-
try {
|
|
162
|
-
let r = await fn();
|
|
163
|
-
await release();
|
|
164
|
-
return r;
|
|
165
|
-
} catch (exn) {
|
|
166
|
-
await release();
|
|
167
|
-
throw exn;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
90
|
let _client = {
|
|
172
91
|
contents: undefined
|
|
173
92
|
};
|
|
@@ -182,6 +101,35 @@ function getClient() {
|
|
|
182
101
|
return c$1;
|
|
183
102
|
}
|
|
184
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
|
+
|
|
185
133
|
function _permissionToCognitoGroups(permission) {
|
|
186
134
|
if (typeof permission !== "object") {
|
|
187
135
|
switch (permission) {
|
|
@@ -349,7 +297,12 @@ function stitchWithAwsDirectives(baseFragment, pluginFragments) {
|
|
|
349
297
|
return AppSync_SdlDecorate$ReventlessAws.stampSharedIamTypes(AppSync_SdlDecorate$ReventlessAws.injectAwsSubscribe(GraphQL_Stitcher$ReventlessCore.stitch(baseFragment, pluginFragments), sources));
|
|
350
298
|
}
|
|
351
299
|
|
|
352
|
-
function
|
|
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) {
|
|
353
306
|
let customOpts_parent = opts.parent;
|
|
354
307
|
let customOpts = {
|
|
355
308
|
parent: customOpts_parent
|
|
@@ -357,18 +310,19 @@ function makeApiResource(name, opts) {
|
|
|
357
310
|
let iamRole = new (Aws.iam.Role)(name + `-appsync-role`, {
|
|
358
311
|
assumeRolePolicy: `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"appsync.amazonaws.com"},"Action":"sts:AssumeRole"}]}`
|
|
359
312
|
}, customOpts);
|
|
360
|
-
let
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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);
|
|
366
319
|
let apiArgs_userPoolConfig = userPoolConfigOut;
|
|
367
320
|
let apiArgs_additionalAuthenticationProviders = [{
|
|
368
321
|
authenticationType: "AWS_IAM"
|
|
369
322
|
}];
|
|
370
323
|
let apiArgs = {
|
|
371
324
|
authenticationType: "AMAZON_COGNITO_USER_POOLS",
|
|
325
|
+
schema: apiArgs_schema,
|
|
372
326
|
userPoolConfig: apiArgs_userPoolConfig,
|
|
373
327
|
additionalAuthenticationProviders: apiArgs_additionalAuthenticationProviders
|
|
374
328
|
};
|
|
@@ -379,6 +333,18 @@ function makeApiResource(name, opts) {
|
|
|
379
333
|
];
|
|
380
334
|
}
|
|
381
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
|
+
|
|
382
348
|
function generateFragment(mutationEntries, queryEntries) {
|
|
383
349
|
let fragment = GraphQL_FragmentGenerator$ReventlessCore.generate(mutationEntries, queryEntries);
|
|
384
350
|
return injectAwsAuth(fragment, mutationEntries, queryEntries);
|
|
@@ -400,6 +366,8 @@ function updateSchema(api, baseFragment, pluginFragments) {
|
|
|
400
366
|
|
|
401
367
|
let stampSharedIamTypes = AppSync_SdlDecorate$ReventlessAws.stampSharedIamTypes;
|
|
402
368
|
|
|
369
|
+
let primaryAuthenticationType = "AMAZON_COGNITO_USER_POOLS";
|
|
370
|
+
|
|
403
371
|
export {
|
|
404
372
|
log,
|
|
405
373
|
sha256Hex,
|
|
@@ -408,11 +376,9 @@ export {
|
|
|
408
376
|
waitForSchemaActive,
|
|
409
377
|
getIntrospectionSdl,
|
|
410
378
|
deploySchemaWithRetry,
|
|
411
|
-
schemaLockSleep,
|
|
412
|
-
_schemaLockCounter,
|
|
413
|
-
withSchemaPushLock,
|
|
414
379
|
_client,
|
|
415
380
|
getClient,
|
|
381
|
+
waitForMergeSuccess,
|
|
416
382
|
_permissionToCognitoGroups,
|
|
417
383
|
_formatGroupsDirective,
|
|
418
384
|
_formatDualAuthDirective,
|
|
@@ -422,7 +388,12 @@ export {
|
|
|
422
388
|
injectAwsAuth,
|
|
423
389
|
injectAwsAuthAll,
|
|
424
390
|
stitchWithAwsDirectives,
|
|
391
|
+
stitchStandaloneWithAwsDirectives,
|
|
392
|
+
primaryAuthenticationType,
|
|
393
|
+
_makeApiResourceWith,
|
|
425
394
|
makeApiResource,
|
|
395
|
+
makeSourceApiResource,
|
|
396
|
+
makePluginSourceApiResource,
|
|
426
397
|
generateFragment,
|
|
427
398
|
updateSchema,
|
|
428
399
|
}
|