@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
|
@@ -53,30 +53,10 @@ let registerNodeType = (~typeName: string, ~readModelName: string): unit =>
|
|
|
53
53
|
// -- Provision --------------------------------------------------------------
|
|
54
54
|
let bool = b => b ? "true" : "false"
|
|
55
55
|
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const id = ctx.identity;
|
|
61
|
-
return {
|
|
62
|
-
operation: 'Invoke',
|
|
63
|
-
payload: {
|
|
64
|
-
readModelName: '',
|
|
65
|
-
kind: 'node',
|
|
66
|
-
arguments: ctx.args,
|
|
67
|
-
identity: id != null && id.sub != null
|
|
68
|
-
? { userId: id.sub, username: id.username, groups: id.claims?.['cognito:groups'] ?? [], claims: id.claims, provider: 'Cognito' }
|
|
69
|
-
: id != null
|
|
70
|
-
? { userArn: id.userArn ?? null, accountId: id.accountId ?? null, username: id.username ?? null, provider: 'IAM' }
|
|
71
|
-
: null
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
export function response(ctx) {
|
|
76
|
-
if (ctx.error) util.error(ctx.error.message, ctx.error.type);
|
|
77
|
-
return ctx.result;
|
|
78
|
-
}
|
|
79
|
-
`->Pulumi.Input.make
|
|
56
|
+
// (The shared `node(id: ID!)` resolver was removed with the Relay-node
|
|
57
|
+
// resolution of the merged-api plan: the root field is no longer emitted on
|
|
58
|
+
// AWS. The Pg Lambda's runtime `handleNode` + the nodeTypes env plumbing stay
|
|
59
|
+
// inert pending a future consumer.)
|
|
80
60
|
|
|
81
61
|
// Serialize the shared connection config into the env-config JSON (same shape
|
|
82
62
|
// EventCollectorRuntime_Builder_Single bakes into HANDLER_CONFIG).
|
|
@@ -87,13 +67,6 @@ let provision = (
|
|
|
87
67
|
~api: Pulumi.Output.t<AppSync.GraphQLApi.t>,
|
|
88
68
|
~selection: QueryDbBackend.selection,
|
|
89
69
|
~opts: Pulumi.ComponentResource.options,
|
|
90
|
-
// The shared node(id) resolver is a single resolver on the API's Query.node
|
|
91
|
-
// field. In plugin-stack mode each plugin stack deploys independently onto one
|
|
92
|
-
// shared API, so only ONE stack may own that field — and a per-plugin node
|
|
93
|
-
// resolver only knows its own plugin's types anyway. So node is provisioned in
|
|
94
|
-
// monolithic mode only; plugin stacks pass false. (Node is not wired on the
|
|
95
|
-
// DynamoDB path either — it stays a monolithic-only capability for now.)
|
|
96
|
-
~createNodeResolver: bool=true,
|
|
97
70
|
): unit => {
|
|
98
71
|
// Join the resolver-binding registry (labelField/includeIdParam) with the
|
|
99
72
|
// ReadModel runtime registry (specModulePath, pgBacked). Only read models
|
|
@@ -257,22 +230,6 @@ let provision = (
|
|
|
257
230
|
~opts=Some(customOpts),
|
|
258
231
|
)
|
|
259
232
|
|
|
260
|
-
// Shared node(id) resolver (B3.2c) — one for the whole API, dispatched by
|
|
261
|
-
// typeName in the Lambda. Only when some entity registered a node type
|
|
262
|
-
// (else the `node` field isn't in the schema) AND in monolithic mode
|
|
263
|
-
// (plugin stacks would collide on the single Query.node field).
|
|
264
|
-
if createNodeResolver && nodeTypes->Dict.toArray->Array.length > 0 {
|
|
265
|
-
let _nodeResolver = AppSync_Resolver_Retrying.makeUnitJsResolver(
|
|
266
|
-
~name=name ++ "NodeResolver",
|
|
267
|
-
~api,
|
|
268
|
-
~dataSourceName=dataSource.name->Pulumi.Output.asInput,
|
|
269
|
-
~type_="Query"->Pulumi.Input.make,
|
|
270
|
-
~field="node"->Pulumi.Input.make,
|
|
271
|
-
~code=nodeResolverCode,
|
|
272
|
-
~opts=customOpts,
|
|
273
|
-
)
|
|
274
|
-
}
|
|
275
|
-
|
|
276
233
|
// Fulfil the deferred name the Postgres storage maker handed to resolvers.
|
|
277
234
|
let _ = dataSource.name->Pulumi.Output.apply(n => resolveDataSourceName.contents(n))
|
|
278
235
|
log.info(~comp="PgQueryResolver_Builder", `provisioned for ${handlers->Array.length->Int.toString} read model(s)`)
|
|
@@ -13,7 +13,6 @@ import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/sr
|
|
|
13
13
|
import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
|
|
14
14
|
import * as PgConnection$ReventlessAws from "../Postgres/PgConnection.res.mjs";
|
|
15
15
|
import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Pulumi.res.mjs";
|
|
16
|
-
import * as AppSync_Resolver_Retrying$ReventlessAws from "../Api/AppSync_Resolver_Retrying.res.mjs";
|
|
17
16
|
import * as RuntimeEnvironment_Lambda$ReventlessAws from "../Runtime/RuntimeEnvironment_Lambda.res.mjs";
|
|
18
17
|
import * as EventCollectorRuntime_Builder_Single$ReventlessAws from "../Runtime/EventCollectorRuntime_Builder_Single.res.mjs";
|
|
19
18
|
|
|
@@ -47,35 +46,11 @@ function bool(b) {
|
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
let nodeResolverCode = `import { util } from '@aws-appsync/utils';
|
|
51
|
-
export function request(ctx) {
|
|
52
|
-
const id = ctx.identity;
|
|
53
|
-
return {
|
|
54
|
-
operation: 'Invoke',
|
|
55
|
-
payload: {
|
|
56
|
-
readModelName: '',
|
|
57
|
-
kind: 'node',
|
|
58
|
-
arguments: ctx.args,
|
|
59
|
-
identity: id != null && id.sub != null
|
|
60
|
-
? { userId: id.sub, username: id.username, groups: id.claims?.['cognito:groups'] ?? [], claims: id.claims, provider: 'Cognito' }
|
|
61
|
-
: id != null
|
|
62
|
-
? { userArn: id.userArn ?? null, accountId: id.accountId ?? null, username: id.username ?? null, provider: 'IAM' }
|
|
63
|
-
: null
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
export function response(ctx) {
|
|
68
|
-
if (ctx.error) util.error(ctx.error.message, ctx.error.type);
|
|
69
|
-
return ctx.result;
|
|
70
|
-
}
|
|
71
|
-
`;
|
|
72
|
-
|
|
73
49
|
function pgConnectionJson(cc) {
|
|
74
50
|
return JSON.stringify(PgConnection$ReventlessAws.connectionConfigToJson(undefined, cc));
|
|
75
51
|
}
|
|
76
52
|
|
|
77
|
-
function provision(api, selection, opts
|
|
78
|
-
let createNodeResolver = createNodeResolverOpt !== undefined ? createNodeResolverOpt : true;
|
|
53
|
+
function provision(api, selection, opts) {
|
|
79
54
|
let handlers = Stdlib_Array.filterMap(Object.values(entries), entry => {
|
|
80
55
|
let info = EventCollectorRuntime_Builder_Single$ReventlessAws.readModelInfos[entry.readModelName];
|
|
81
56
|
if (info !== undefined && info.pgBacked) {
|
|
@@ -147,9 +122,6 @@ function provision(api, selection, opts, createNodeResolverOpt) {
|
|
|
147
122
|
},
|
|
148
123
|
serviceRoleArn: dataSourceRole.arn
|
|
149
124
|
}, customOpts);
|
|
150
|
-
if (createNodeResolver && Object.entries(nodeTypes).length !== 0) {
|
|
151
|
-
AppSync_Resolver_Retrying$ReventlessAws.makeUnitJsResolver(name + "NodeResolver", api, dataSource.name, "Query", "node", nodeResolverCode, customOpts);
|
|
152
|
-
}
|
|
153
125
|
dataSource.name.apply(n => resolveDataSourceName.contents(n));
|
|
154
126
|
log.info("PgQueryResolver_Builder", undefined, `provisioned for ` + handlers.length.toString() + ` read model(s)`);
|
|
155
127
|
}
|
|
@@ -166,7 +138,6 @@ export {
|
|
|
166
138
|
nodeTypes,
|
|
167
139
|
registerNodeType,
|
|
168
140
|
bool,
|
|
169
|
-
nodeResolverCode,
|
|
170
141
|
pgConnectionJson,
|
|
171
142
|
provision,
|
|
172
143
|
}
|
|
@@ -105,17 +105,6 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
|
|
|
105
105
|
| None => true
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
// Resolve returnTypeName for Relay Node type registry
|
|
109
|
-
let returnTypeName = switch registryEntry {
|
|
110
|
-
| Some({returnTypeName: rt}) => rt
|
|
111
|
-
| None => name
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Register entity type in the Relay Node type registry for node(id: ID!) resolution
|
|
115
|
-
if includeIdParam {
|
|
116
|
-
NodeResolver_AppSync.registerNodeType(~typeName=returnTypeName, ~dataSourceName)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
108
|
// Creates either a unit resolver (no interceptor) or a pipeline resolver
|
|
120
109
|
// (interceptor Lambda → DynamoDB query) depending on queryInterceptorConfig.
|
|
121
110
|
let makeQueryResolver = (~resolverName, ~field, ~code) =>
|
|
@@ -11,7 +11,6 @@ import * as Util_DynamoDb$ReventlessAws from "../../util/Util_DynamoDb.res.mjs";
|
|
|
11
11
|
import * as Util_QueryDb$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_QueryDb.res.mjs";
|
|
12
12
|
import * as AppSync_DataSource$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/AppSync/AppSync_DataSource.res.mjs";
|
|
13
13
|
import * as Plugin_Helpers$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin_Helpers.res.mjs";
|
|
14
|
-
import * as NodeResolver_AppSync$ReventlessAws from "./NodeResolver_AppSync.res.mjs";
|
|
15
14
|
import * as AppSync_Resolver_Functions$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/AppSync/AppSync_Resolver_Functions.res.mjs";
|
|
16
15
|
import * as AppSync_Resolver_Retrying$ReventlessAws from "../Api/AppSync_Resolver_Retrying.res.mjs";
|
|
17
16
|
import * as GraphQL_FragmentGenerator$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_FragmentGenerator.res.mjs";
|
|
@@ -70,10 +69,6 @@ function make(name, api, apiRole, dataSourceName, indexes, subIdField, idResolve
|
|
|
70
69
|
let fieldNameForSingle = registryEntry !== undefined ? registryEntry.singleFieldName : AppSync_Resolver_Functions$PulumiAws.uncapitalize(name$1);
|
|
71
70
|
let includeIdParam = registryEntry !== undefined ? registryEntry.includeIdParam : true;
|
|
72
71
|
let connectionSpec = registryEntry !== undefined ? registryEntry.connectionSpec : true;
|
|
73
|
-
let returnTypeName = registryEntry !== undefined ? registryEntry.returnTypeName : name$1;
|
|
74
|
-
if (includeIdParam) {
|
|
75
|
-
NodeResolver_AppSync$ReventlessAws.registerNodeType(returnTypeName, dataSourceName);
|
|
76
|
-
}
|
|
77
72
|
let makeQueryResolver = (resolverName, field, code) => {
|
|
78
73
|
let match = queryInterceptorConfig.contents;
|
|
79
74
|
if (match === undefined) {
|
|
@@ -15,7 +15,7 @@ let sideEffectInfos: dict<sideEffectInfo> = Dict.make()
|
|
|
15
15
|
let registerSideEffectHandler = (~sideEffectHandlerName, ~sideEffectModulePaths) =>
|
|
16
16
|
sideEffectInfos->Dict.set(sideEffectHandlerName, {sideEffectModulePaths: sideEffectModulePaths})
|
|
17
17
|
|
|
18
|
-
// Extra Lambda env vars contributed by bespoke side effects
|
|
18
|
+
// Extra Lambda env vars contributed by bespoke side effects.
|
|
19
19
|
// The shared "AllSideEffectHandlers" Lambda is built once in finish(); all registered
|
|
20
20
|
// entries are merged onto its env there. Deploy-derived config only — never overrides
|
|
21
21
|
// HANDLER_CONFIG.
|
|
@@ -176,12 +176,11 @@ let finish = () =>
|
|
|
176
176
|
~opts,
|
|
177
177
|
)
|
|
178
178
|
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
// AllowAdminStartSchemaCreation grant (PluginRuntime_Builder).
|
|
179
|
+
// Grant AppSync schema perms to the shared side-effect-handler Lambda
|
|
180
|
+
// role ONLY when a bespoke side effect registered extra-env config —
|
|
181
|
+
// Task-only deployments keep the narrow default perimeter. (The last
|
|
182
|
+
// such contributor, the admin ApiSchemaPush, was retired with the
|
|
183
|
+
// merged-API cutover; the mechanism stays for future bespoke effects.)
|
|
185
184
|
if extraEnvVarsAll->Dict.keysToArray->Array.length > 0 {
|
|
186
185
|
let _ = PulumiAws.IAM.RolePolicy.make(
|
|
187
186
|
~name="AllSideEffectHandlers-appsyncSchemaPush",
|
|
@@ -94,58 +94,6 @@ let rec waitForSchemaActive = async (client, apiId, ~maxAttempts=30, ~attempt=0)
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
// GetIntrospectionSchema — fetch the live schema as an SDL string. Used by the
|
|
98
|
-
// deploy-time drift check in preResolversSchemaHook to detect a live schema that
|
|
99
|
-
// was clobbered out-of-band by a runtime re-stitch (the stored deploy hash does
|
|
100
|
-
// not reflect such clobbers). Returns "" when the API has no schema or when
|
|
101
|
-
// introspection fails — the caller decides how to treat an empty result.
|
|
102
|
-
type getIntrospectionSchemaInput = {apiId: string, format: string}
|
|
103
|
-
type getIntrospectionSchemaCommand
|
|
104
|
-
type schemaBlob
|
|
105
|
-
type getIntrospectionSchemaResult = {schema: option<schemaBlob>}
|
|
106
|
-
|
|
107
|
-
@module("@aws-sdk/client-appsync") @new
|
|
108
|
-
external makeGetIntrospectionSchemaCommand: getIntrospectionSchemaInput => getIntrospectionSchemaCommand =
|
|
109
|
-
"GetIntrospectionSchemaCommand"
|
|
110
|
-
|
|
111
|
-
@send
|
|
112
|
-
external sendGetIntrospection: (
|
|
113
|
-
appSyncClient,
|
|
114
|
-
getIntrospectionSchemaCommand,
|
|
115
|
-
) => promise<getIntrospectionSchemaResult> = "send"
|
|
116
|
-
|
|
117
|
-
// resp.schema is a Uint8Array of the SDL text; decode it to UTF-8.
|
|
118
|
-
type nodeBuffer
|
|
119
|
-
@val @scope("Buffer") external bufferFrom: schemaBlob => nodeBuffer = "from"
|
|
120
|
-
@send external bufferToString: (nodeBuffer, string) => string = "toString"
|
|
121
|
-
|
|
122
|
-
let getIntrospectionSdl = async (client: appSyncClient, apiId: string): string => {
|
|
123
|
-
try {
|
|
124
|
-
let resp = await client->sendGetIntrospection(
|
|
125
|
-
{apiId, format: "SDL"}->makeGetIntrospectionSchemaCommand,
|
|
126
|
-
)
|
|
127
|
-
switch resp.schema {
|
|
128
|
-
| Some(blob) => bufferFrom(blob)->bufferToString("utf-8")
|
|
129
|
-
| None => ""
|
|
130
|
-
}
|
|
131
|
-
} catch {
|
|
132
|
-
| exn =>
|
|
133
|
-
let msg = exn->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr("unknown")
|
|
134
|
-
log.warn(~comp="AppSync_Adapter", `getIntrospectionSdl failed for ${apiId}: ${msg}`)
|
|
135
|
-
""
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
let deploySchemaWithRetry = (
|
|
140
|
-
client: appSyncClient,
|
|
141
|
-
apiId: string,
|
|
142
|
-
definition: string,
|
|
143
|
-
): Effect.t<unit, AppSync_Error.t, unit> =>
|
|
144
|
-
Effect.tryPromise(
|
|
145
|
-
~catch=AppSync_Error.classify,
|
|
146
|
-
() => startSchemaCreation(client, {apiId, definition})->Promise.then(_ => Promise.resolve()),
|
|
147
|
-
)->Effect.retry(AppSync_Error.retrySchedule)
|
|
148
|
-
|
|
149
97
|
// Lazy singleton AppSync client (runtime only)
|
|
150
98
|
let _client: ref<option<appSyncClient>> = ref(None)
|
|
151
99
|
let getClient = () =>
|
|
@@ -484,32 +432,16 @@ let injectAwsAuthAll = (
|
|
|
484
432
|
AppSync_SdlDecorate.injectAwsAuthAll(fragment, ~group, ~iamFieldNames)
|
|
485
433
|
|
|
486
434
|
/**
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
): string => {
|
|
498
|
-
let sources = ReventlessCore.GraphQL_Stitcher.collectSubscriptionSources(
|
|
499
|
-
~baseFragment,
|
|
500
|
-
~pluginFragments,
|
|
501
|
-
)
|
|
502
|
-
ReventlessCore.GraphQL_Stitcher.stitch(~baseFragment, ~pluginFragments)
|
|
503
|
-
->AppSync_SdlDecorate.injectAwsSubscribe(~sources)
|
|
504
|
-
->stampSharedIamTypes
|
|
505
|
-
}
|
|
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.
|
|
435
|
+
One source-API document rendered standalone (relay base types included; the
|
|
436
|
+
global `node` query is not emitted on AWS — see the merged-api plan's "Relay
|
|
437
|
+
node resolution" section), decorated with the AppSync dialect:
|
|
438
|
+
`@aws_subscribe` on mutation-sourced subscription fields (from the fragment's
|
|
439
|
+
neutral `subscriptionSources` metadata) and `@aws_cognito_user_pools @aws_iam`
|
|
440
|
+
on the shared traversal types. Every AWS source-API document — the platform's
|
|
441
|
+
canonical documents and each plugin's subgraph — assembles through here so the
|
|
442
|
+
dialect is applied uniformly. No `@canonical` stamps here — the platform adds
|
|
443
|
+
them to its canonical documents only; plugin subgraphs stay unstamped and the
|
|
444
|
+
canonical definitions win on merge.
|
|
513
445
|
*/
|
|
514
446
|
let stitchStandaloneWithAwsDirectives = (
|
|
515
447
|
~fragment: Reventless.Plugin.apiSchemaFragment,
|
|
@@ -634,30 +566,6 @@ let generateFragment = (
|
|
|
634
566
|
injectAwsAuth(fragment, ~mutationEntries, ~queryEntries)
|
|
635
567
|
}
|
|
636
568
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
~pluginFragments: array<Reventless.Plugin.apiSchemaFragment>,
|
|
641
|
-
): promise<unit> => {
|
|
642
|
-
// Inject @aws_auth(cognito_groups: ["Admin"]) into all base fragment fields.
|
|
643
|
-
// The base fragment contains core Plugin aggregate queries/mutations — all Admin-only.
|
|
644
|
-
// Plugin fragments already have @aws_auth injected via generateFragment.
|
|
645
|
-
let augmentedBaseFragment = injectAwsAuthAll(baseFragment, ~group="Admin")
|
|
646
|
-
// Shared traversal types + @aws_subscribe are stamped once on the assembled
|
|
647
|
-
// SDL (post-stitch, post-dedupe) — see stitchWithAwsDirectives.
|
|
648
|
-
let sdl = stitchWithAwsDirectives(~baseFragment=augmentedBaseFragment, ~pluginFragments)
|
|
649
|
-
// Resolve the API ID from the Output chain. In mock mode (tests) and in Lambda runtime
|
|
650
|
-
// (where the Output is backed by already-known values), this completes synchronously.
|
|
651
|
-
// The resulting promise wraps the AppSync SDK call.
|
|
652
|
-
let resultPromise: ref<promise<unit>> = ref(Promise.resolve())
|
|
653
|
-
let _ =
|
|
654
|
-
api
|
|
655
|
-
->Pulumi.Output.apply(graphQLApi =>
|
|
656
|
-
graphQLApi.id->Pulumi.Output.apply(apiId => {
|
|
657
|
-
let effect = deploySchemaWithRetry(getClient(), apiId, sdl)
|
|
658
|
-
let p = effect->Effect.runPromise
|
|
659
|
-
resultPromise.contents = p
|
|
660
|
-
})
|
|
661
|
-
)
|
|
662
|
-
resultPromise.contents
|
|
663
|
-
}
|
|
569
|
+
// (updateSchema — the whole-replace stitched-schema push — was retired with
|
|
570
|
+
// the merged-API cutover; every source API owns its schema declaratively or
|
|
571
|
+
// via its own single-writer subgraph push in preResolversSchemaHook.)
|
|
@@ -4,14 +4,12 @@ import * as Effect from "@reventlessdev/rescript-effect/src/Effect.res.mjs";
|
|
|
4
4
|
import * as Aws from "@pulumi/aws";
|
|
5
5
|
import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
6
6
|
import * as Nodecrypto from "node:crypto";
|
|
7
|
-
import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
8
7
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
9
8
|
import * as Stdlib_String from "@rescript/runtime/lib/es6/Stdlib_String.js";
|
|
10
9
|
import * as Effect$1 from "effect/Effect";
|
|
11
10
|
import * as Pulumi from "@pulumi/pulumi";
|
|
12
11
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
13
12
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
14
|
-
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
15
13
|
import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
|
|
16
14
|
import * as ClientAppsync from "@aws-sdk/client-appsync";
|
|
17
15
|
import * as Auth_Cognito$ReventlessAws from "../../adapter/Auth/Auth_Cognito.res.mjs";
|
|
@@ -60,33 +58,6 @@ async function waitForSchemaActive(client, apiId, maxAttemptsOpt, attemptOpt) {
|
|
|
60
58
|
}
|
|
61
59
|
}
|
|
62
60
|
|
|
63
|
-
async function getIntrospectionSdl(client, apiId) {
|
|
64
|
-
try {
|
|
65
|
-
let resp = await client.send(new ClientAppsync.GetIntrospectionSchemaCommand({
|
|
66
|
-
apiId: apiId,
|
|
67
|
-
format: "SDL"
|
|
68
|
-
}));
|
|
69
|
-
let blob = resp.schema;
|
|
70
|
-
if (blob !== undefined) {
|
|
71
|
-
return Buffer.from(Primitive_option.valFromOption(blob)).toString("utf-8");
|
|
72
|
-
} else {
|
|
73
|
-
return "";
|
|
74
|
-
}
|
|
75
|
-
} catch (raw_exn) {
|
|
76
|
-
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
77
|
-
let msg = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn), Stdlib_JsExn.message), "unknown");
|
|
78
|
-
log.warn("AppSync_Adapter", undefined, `getIntrospectionSdl failed for ` + apiId + `: ` + msg);
|
|
79
|
-
return "";
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function deploySchemaWithRetry(client, apiId, definition) {
|
|
84
|
-
return Effect$1.retry(Effect.tryPromise(AppSync_Error$ReventlessAws.classify, () => client.send(new ClientAppsync.StartSchemaCreationCommand({
|
|
85
|
-
apiId: apiId,
|
|
86
|
-
definition: definition
|
|
87
|
-
})).then(param => Promise.resolve())), AppSync_Error$ReventlessAws.retrySchedule);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
61
|
let _client = {
|
|
91
62
|
contents: undefined
|
|
92
63
|
};
|
|
@@ -292,11 +263,6 @@ function injectAwsAuthAll(fragment, group, iamFieldNamesOpt) {
|
|
|
292
263
|
return AppSync_SdlDecorate$ReventlessAws.injectAwsAuthAll(fragment, group, iamFieldNames);
|
|
293
264
|
}
|
|
294
265
|
|
|
295
|
-
function stitchWithAwsDirectives(baseFragment, pluginFragments) {
|
|
296
|
-
let sources = GraphQL_Stitcher$ReventlessCore.collectSubscriptionSources(baseFragment, pluginFragments);
|
|
297
|
-
return AppSync_SdlDecorate$ReventlessAws.stampSharedIamTypes(AppSync_SdlDecorate$ReventlessAws.injectAwsSubscribe(GraphQL_Stitcher$ReventlessCore.stitch(baseFragment, pluginFragments), sources));
|
|
298
|
-
}
|
|
299
|
-
|
|
300
266
|
function stitchStandaloneWithAwsDirectives(fragment) {
|
|
301
267
|
let sources = GraphQL_Stitcher$ReventlessCore.collectSubscriptionSources(fragment, []);
|
|
302
268
|
return AppSync_SdlDecorate$ReventlessAws.stampSharedIamTypes(AppSync_SdlDecorate$ReventlessAws.injectAwsSubscribe(GraphQL_Stitcher$ReventlessCore.stitchStandalone(fragment), sources));
|
|
@@ -350,20 +316,6 @@ function generateFragment(mutationEntries, queryEntries) {
|
|
|
350
316
|
return injectAwsAuth(fragment, mutationEntries, queryEntries);
|
|
351
317
|
}
|
|
352
318
|
|
|
353
|
-
function updateSchema(api, baseFragment, pluginFragments) {
|
|
354
|
-
let augmentedBaseFragment = injectAwsAuthAll(baseFragment, "Admin", undefined);
|
|
355
|
-
let sdl = stitchWithAwsDirectives(augmentedBaseFragment, pluginFragments);
|
|
356
|
-
let resultPromise = {
|
|
357
|
-
contents: Promise.resolve()
|
|
358
|
-
};
|
|
359
|
-
api.apply(graphQLApi => graphQLApi.id.apply(apiId => {
|
|
360
|
-
let effect = deploySchemaWithRetry(getClient(), apiId, sdl);
|
|
361
|
-
let p = Effect$1.runPromise(effect);
|
|
362
|
-
resultPromise.contents = p;
|
|
363
|
-
}));
|
|
364
|
-
return resultPromise.contents;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
319
|
let stampSharedIamTypes = AppSync_SdlDecorate$ReventlessAws.stampSharedIamTypes;
|
|
368
320
|
|
|
369
321
|
let primaryAuthenticationType = "AMAZON_COGNITO_USER_POOLS";
|
|
@@ -374,8 +326,6 @@ export {
|
|
|
374
326
|
startSchemaCreation,
|
|
375
327
|
startSchemaCreationRetrying,
|
|
376
328
|
waitForSchemaActive,
|
|
377
|
-
getIntrospectionSdl,
|
|
378
|
-
deploySchemaWithRetry,
|
|
379
329
|
_client,
|
|
380
330
|
getClient,
|
|
381
331
|
waitForMergeSuccess,
|
|
@@ -387,7 +337,6 @@ export {
|
|
|
387
337
|
stampSharedIamTypes,
|
|
388
338
|
injectAwsAuth,
|
|
389
339
|
injectAwsAuthAll,
|
|
390
|
-
stitchWithAwsDirectives,
|
|
391
340
|
stitchStandaloneWithAwsDirectives,
|
|
392
341
|
primaryAuthenticationType,
|
|
393
342
|
_makeApiResourceWith,
|
|
@@ -395,6 +344,5 @@ export {
|
|
|
395
344
|
makeSourceApiResource,
|
|
396
345
|
makePluginSourceApiResource,
|
|
397
346
|
generateFragment,
|
|
398
|
-
updateSchema,
|
|
399
347
|
}
|
|
400
348
|
/* log Not a pure module */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// AppSync_MergedApi — merged-API construction for the push-free composition
|
|
2
|
-
// path (docs/plans/merged-api-push-free-composition.md, Phase 3).
|
|
2
|
+
// path (docs/plans/done/merged-api-push-free-composition.md, Phase 3).
|
|
3
3
|
//
|
|
4
4
|
// A merged API carries no schema of its own: source APIs contribute theirs via
|
|
5
5
|
// SourceApiAssociation and AWS composes the merged endpoint. The platform
|
|
@@ -119,19 +119,14 @@ let injectAwsAuthAll = (
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
// Shared traversal types every callable surface reaches — `PageInfo` (relay
|
|
122
|
-
// connections, injected by the stitcher)
|
|
123
|
-
// returns
|
|
124
|
-
// (
|
|
125
|
-
// waiter polls via SigV4 — without the type-level `@aws_iam` the SigV4 caller reaches the
|
|
126
|
-
// query field but is "Not Authorized to access <field> on type Platform_ApiFragmentEntry").
|
|
127
|
-
// Stamped once on the ASSEMBLED SDL: per-fragment stamping would race the stitcher's
|
|
128
|
-
// first-wins dedupe against unstamped sibling copies.
|
|
122
|
+
// connections, injected by the stitcher) and the `CommandResult` members
|
|
123
|
+
// (mutation returns). Stamped once on the ASSEMBLED SDL so an IAM system
|
|
124
|
+
// caller (`@@reventless.systemCallable` fields) can traverse them.
|
|
129
125
|
let sharedIamTypeNames = [
|
|
130
126
|
"PageInfo",
|
|
131
127
|
"CommandAccepted",
|
|
132
128
|
"CommandRejected",
|
|
133
129
|
"CommandPending",
|
|
134
|
-
"Platform_ApiFragmentEntry",
|
|
135
130
|
]
|
|
136
131
|
|
|
137
132
|
let stampSharedIamTypes = (sdl: string): string =>
|
|
@@ -186,60 +181,6 @@ let stampCanonicalTypes = (sdl: string): string =>
|
|
|
186
181
|
})
|
|
187
182
|
->Array.join("\n")
|
|
188
183
|
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
// API, as the bare string "Domain" / "Platform"), it produces one fully
|
|
193
|
-
// AppSync-decorated SDL push plan per API. It reuses the core planner for the
|
|
194
|
-
// neutral base-selection + stitch, then applies the AppSync dialect exactly as the
|
|
195
|
-
// deploy path's `stitchWithAwsDirectives`:
|
|
196
|
-
// - the admin base is auth-decorated (group "Admin" + dual-auth on the system-
|
|
197
|
-
// caller field names) BEFORE stitching, so the Platform/unified base carries
|
|
198
|
-
// @aws_auth/@aws_iam (the empty Domain-split base needs none);
|
|
199
|
-
// - @aws_subscribe is injected from the neutral subscriptionSources metadata;
|
|
200
|
-
// - the shared traversal types are stamped once on the assembled SDL.
|
|
201
|
-
// Plugin fragments already carry their own per-field @aws_auth (baked at
|
|
202
|
-
// generateFragment time), so they stitch in as-is. `api` comes back as the bare
|
|
203
|
-
// string "PlatformApi" / "DomainApi" for the mjs to map onto the AppSync ids.
|
|
204
|
-
type targetedFragmentInput = {
|
|
205
|
-
encoded: string,
|
|
206
|
-
protocol: string,
|
|
207
|
-
// "Domain" | "Platform" — the bare-string runtime form of Reventless.Plugin.apiTarget.
|
|
208
|
-
target: string,
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
type awsPushPlan = {
|
|
212
|
-
// "PlatformApi" | "DomainApi" — the bare-string runtime form of GraphQL_PushPlanner.planTarget.
|
|
213
|
-
api: string,
|
|
214
|
-
sdl: string,
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
let planAwsPushes = (
|
|
218
|
-
~rawAdminBase: Reventless.Plugin.apiSchemaFragment,
|
|
219
|
-
~iamFieldNames: array<string>,
|
|
220
|
-
~fragments: array<targetedFragmentInput>,
|
|
221
|
-
~splitApi: bool,
|
|
222
|
-
): array<awsPushPlan> => {
|
|
223
|
-
let authBase = injectAwsAuthAll(rawAdminBase, ~group="Admin", ~iamFieldNames)
|
|
224
|
-
let targeted = fragments->Array.map((f): ReventlessCore.GraphQL_PushPlanner.targetedFragment => {
|
|
225
|
-
fragment: {encoded: f.encoded, protocol: f.protocol},
|
|
226
|
-
target: switch f.target {
|
|
227
|
-
| "Platform" => Platform
|
|
228
|
-
| _ => Domain
|
|
229
|
-
},
|
|
230
|
-
})
|
|
231
|
-
let allFrags = targeted->Array.map(t => t.fragment)
|
|
232
|
-
let sources = ReventlessCore.GraphQL_Stitcher.collectSubscriptionSources(
|
|
233
|
-
~baseFragment=rawAdminBase,
|
|
234
|
-
~pluginFragments=allFrags,
|
|
235
|
-
)
|
|
236
|
-
ReventlessCore.GraphQL_PushPlanner.planPushes(~adminBase=authBase, ~fragments=targeted, ~splitApi)
|
|
237
|
-
->Array.map(plan => {
|
|
238
|
-
let sdl = plan.sdl->injectAwsSubscribe(~sources)->stampSharedIamTypes
|
|
239
|
-
let api = switch plan.api {
|
|
240
|
-
| PlatformApi => "PlatformApi"
|
|
241
|
-
| DomainApi => "DomainApi"
|
|
242
|
-
}
|
|
243
|
-
{api, sdl}
|
|
244
|
-
})
|
|
245
|
-
}
|
|
184
|
+
// (The reactive push planner — planAwsPushes, the AWS counterpart of core
|
|
185
|
+
// GraphQL_PushPlanner — was retired with the merged-API cutover: source APIs
|
|
186
|
+
// are single writers, AWS composes the merged endpoint, nothing re-stitches.)
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
4
4
|
import * as Stdlib_String from "@rescript/runtime/lib/es6/Stdlib_String.js";
|
|
5
5
|
import * as GraphQL_Stitcher$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_Stitcher.res.mjs";
|
|
6
|
-
import * as GraphQL_PushPlanner$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_PushPlanner.res.mjs";
|
|
7
6
|
|
|
8
7
|
function injectAwsSubscribe(sdl, sources) {
|
|
9
8
|
if (sources.length === 0) {
|
|
@@ -80,8 +79,7 @@ let sharedIamTypeNames = [
|
|
|
80
79
|
"PageInfo",
|
|
81
80
|
"CommandAccepted",
|
|
82
81
|
"CommandRejected",
|
|
83
|
-
"CommandPending"
|
|
84
|
-
"Platform_ApiFragmentEntry"
|
|
82
|
+
"CommandPending"
|
|
85
83
|
];
|
|
86
84
|
|
|
87
85
|
function stampSharedIamTypes(sdl) {
|
|
@@ -125,33 +123,6 @@ function stampCanonicalTypes(sdl) {
|
|
|
125
123
|
}).join("\n");
|
|
126
124
|
}
|
|
127
125
|
|
|
128
|
-
function planAwsPushes(rawAdminBase, iamFieldNames, fragments, splitApi) {
|
|
129
|
-
let authBase = injectAwsAuthAll(rawAdminBase, "Admin", iamFieldNames);
|
|
130
|
-
let targeted = fragments.map(f => {
|
|
131
|
-
let match = f.target;
|
|
132
|
-
let tmp = match === "Platform" ? "Platform" : "Domain";
|
|
133
|
-
return {
|
|
134
|
-
fragment: {
|
|
135
|
-
encoded: f.encoded,
|
|
136
|
-
protocol: f.protocol
|
|
137
|
-
},
|
|
138
|
-
target: tmp
|
|
139
|
-
};
|
|
140
|
-
});
|
|
141
|
-
let allFrags = targeted.map(t => t.fragment);
|
|
142
|
-
let sources = GraphQL_Stitcher$ReventlessCore.collectSubscriptionSources(rawAdminBase, allFrags);
|
|
143
|
-
return GraphQL_PushPlanner$ReventlessCore.planPushes(authBase, targeted, splitApi).map(plan => {
|
|
144
|
-
let sdl = stampSharedIamTypes(injectAwsSubscribe(plan.sdl, sources));
|
|
145
|
-
let match = plan.api;
|
|
146
|
-
let api;
|
|
147
|
-
api = match === "DomainApi" ? "DomainApi" : "PlatformApi";
|
|
148
|
-
return {
|
|
149
|
-
api: api,
|
|
150
|
-
sdl: sdl
|
|
151
|
-
};
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
126
|
export {
|
|
156
127
|
injectAwsSubscribe,
|
|
157
128
|
formatDualAuthDirective,
|
|
@@ -160,6 +131,5 @@ export {
|
|
|
160
131
|
stampSharedIamTypes,
|
|
161
132
|
canonicalTypeNames,
|
|
162
133
|
stampCanonicalTypes,
|
|
163
|
-
planAwsPushes,
|
|
164
134
|
}
|
|
165
135
|
/* GraphQL_Stitcher-ReventlessCore Not a pure module */
|
|
@@ -28,8 +28,7 @@ function Make(HooksConfig) {
|
|
|
28
28
|
hooks: HooksConfig.hooks
|
|
29
29
|
})({})({
|
|
30
30
|
makeApiResource: AppSync_Adapter$ReventlessAws.makeApiResource,
|
|
31
|
-
generateFragment: AppSync_Adapter$ReventlessAws.generateFragment
|
|
32
|
-
updateSchema: AppSync_Adapter$ReventlessAws.updateSchema
|
|
31
|
+
generateFragment: AppSync_Adapter$ReventlessAws.generateFragment
|
|
33
32
|
})({
|
|
34
33
|
make: RuntimeEnvironment_Lambda$ReventlessAws.make,
|
|
35
34
|
groupBySource: RuntimeEnvironment_Lambda$ReventlessAws.groupBySource,
|