@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
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
// ApiSchemaPush — the admin reactive schema-push SideEffect
|
|
2
|
-
// (docs/plans/event-sourced-fragment-registries.md § Reactive writer design).
|
|
3
|
-
//
|
|
4
|
-
// Source = the ApiFragmentRegistry singleton aggregate. On every ApiSchemaComputed
|
|
5
|
-
// { snapshot } (emitted by the aggregate behaviour alongside each ApiFragment* fact,
|
|
6
|
-
// carrying the WHOLE consistent per-plugin fragment set), this hands the snapshot to
|
|
7
|
-
// the runtime-pure push engine (ApiSchemaPush_Runtime.mjs), which stitches one
|
|
8
|
-
// AWS-decorated schema per target API, pushes each behind the shrink guard, and writes
|
|
9
|
-
// the outcome back via RecordApiFragmentPush.
|
|
10
|
-
//
|
|
11
|
-
// The generic SideEffect.T context (`queryEngine`) is unused: this bespoke platform
|
|
12
|
-
// side effect self-acquires its config (API ids, split flag, command-topic URL) from
|
|
13
|
-
// Lambda env injected by the admin SideEffectHandler (~extraEnvVars). The push module
|
|
14
|
-
// is loaded via dynamic import so no @aws-sdk / provider code is statically captured
|
|
15
|
-
// beyond what the SideEffectHandler Lambda already bundles (runtime-purity — see
|
|
16
|
-
// reference_pulumi_leaks_into_lambda_runtime_graph).
|
|
17
|
-
module Spec = ReventlessCore.ApiFragmentRegistrySpec
|
|
18
|
-
|
|
19
|
-
// Source is built EXPLICITLY rather than aliasing/including the spec. SideEffectHandler_Callback
|
|
20
|
-
// reads Source.{name,eventSchema,Id.schema} reflectively off this module's compiled export at
|
|
21
|
-
// Lambda cold start, but the spec uses these only at the TYPE level, so ReScript dead-shakes
|
|
22
|
-
// them: a bare `module Source = Spec` alias erases the whole binding to `let Source;`, and even
|
|
23
|
-
// the spec's own @@reventless.spec-injected `module Id` compiles to `let Id;` (undefined) — the
|
|
24
|
-
// cross-module runtime reflection is invisible to ReScript's optimiser (deploy-time is fine
|
|
25
|
-
// because Platform.res hand-wires these values). Both surfaced on deploy as "Cannot read
|
|
26
|
-
// properties of undefined (reading 'eventSchema' / 'schema')". Forwarding name/eventSchema (real
|
|
27
|
-
// spec exports) and re-binding Id (String — the singleton registry id, matching Platform.res's
|
|
28
|
-
// hand-wiring) materialises all three, while the type aliases keep full transparency for the
|
|
29
|
-
// pattern match below.
|
|
30
|
-
module Source = {
|
|
31
|
-
type event = Spec.event
|
|
32
|
-
type fragmentSnapshotEntry = Spec.fragmentSnapshotEntry
|
|
33
|
-
// `include` (not `module Id = Reventless.Id.String`) — a bare module alias compiles to
|
|
34
|
-
// `Id: undefined` in the Source record (Id is only type-projected via Source.Id.t), and
|
|
35
|
-
// SideEffectHandler_Callback's `Source.Id.schema` reflective read crashes at cold start.
|
|
36
|
-
// `include` materialises the Id module's runtime values into the record.
|
|
37
|
-
module Id = {
|
|
38
|
-
include Reventless.Id.String
|
|
39
|
-
}
|
|
40
|
-
let name = Spec.name
|
|
41
|
-
let eventSchema = Spec.eventSchema
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
let moduleUrl: string = %raw(`import.meta.url`)
|
|
45
|
-
|
|
46
|
-
type jsEntry = {pluginId: string, encoded: string, protocol: string, apiTarget: string}
|
|
47
|
-
|
|
48
|
-
let pushApiSchema: array<jsEntry> => promise<unit> = %raw(`
|
|
49
|
-
async function (snapshot) {
|
|
50
|
-
const mod = await import("@reventlessdev/reventless-aws/src/adapter/Runtime/ApiSchemaPush_Runtime.mjs");
|
|
51
|
-
await mod.pushApiSchema(snapshot);
|
|
52
|
-
}
|
|
53
|
-
`)
|
|
54
|
-
|
|
55
|
-
let execute = async (
|
|
56
|
-
_id: Source.Id.t,
|
|
57
|
-
_meta: Reventless.Message.meta,
|
|
58
|
-
event: Source.event,
|
|
59
|
-
_queryEngine: Reventless.QueryEngine.operations,
|
|
60
|
-
) =>
|
|
61
|
-
switch event {
|
|
62
|
-
| ApiSchemaComputed({snapshot}) =>
|
|
63
|
-
let jsSnapshot = snapshot->Array.map((e: Source.fragmentSnapshotEntry) => {
|
|
64
|
-
pluginId: e.pluginId,
|
|
65
|
-
encoded: e.encoded,
|
|
66
|
-
protocol: e.protocol,
|
|
67
|
-
apiTarget: switch e.apiTarget {
|
|
68
|
-
| Reventless.Plugin.Domain => "Domain"
|
|
69
|
-
| Reventless.Plugin.Platform => "Platform"
|
|
70
|
-
},
|
|
71
|
-
})
|
|
72
|
-
await pushApiSchema(jsSnapshot)
|
|
73
|
-
// Fact events are handled via the ApiSchemaComputed echo (which carries the
|
|
74
|
-
// consistent snapshot); the write-back and lifecycle facts are no-ops here.
|
|
75
|
-
| ApiFragmentRegistered(_)
|
|
76
|
-
| ApiFragmentUpdated(_)
|
|
77
|
-
| ApiFragmentDeregistered(_)
|
|
78
|
-
| ApiFragmentPushRecorded(_) => ()
|
|
79
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
-
|
|
3
|
-
import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
|
|
4
|
-
import * as ApiFragmentRegistrySpec$ReventlessCore from "@reventlessdev/reventless-core/src/admin/ApiFragmentRegistry/ApiFragmentRegistrySpec.res.mjs";
|
|
5
|
-
|
|
6
|
-
let Id_schema = Id$Reventless.$$String.schema;
|
|
7
|
-
|
|
8
|
-
let Id_make = Id$Reventless.$$String.make;
|
|
9
|
-
|
|
10
|
-
let Id_makeFromString = Id$Reventless.$$String.makeFromString;
|
|
11
|
-
|
|
12
|
-
let Id_toString = Id$Reventless.$$String.toString;
|
|
13
|
-
|
|
14
|
-
let Id_cmp = Id$Reventless.$$String.cmp;
|
|
15
|
-
|
|
16
|
-
let Id = {
|
|
17
|
-
schema: Id_schema,
|
|
18
|
-
make: Id_make,
|
|
19
|
-
makeFromString: Id_makeFromString,
|
|
20
|
-
toString: Id_toString,
|
|
21
|
-
cmp: Id_cmp
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
let Source = {
|
|
25
|
-
Id: Id,
|
|
26
|
-
name: ApiFragmentRegistrySpec$ReventlessCore.name,
|
|
27
|
-
eventSchema: ApiFragmentRegistrySpec$ReventlessCore.eventSchema
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
let moduleUrl = import.meta.url;
|
|
31
|
-
|
|
32
|
-
let pushApiSchema = (async function (snapshot) {
|
|
33
|
-
const mod = await import("@reventlessdev/reventless-aws/src/adapter/Runtime/ApiSchemaPush_Runtime.mjs");
|
|
34
|
-
await mod.pushApiSchema(snapshot);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
async function execute(_id, _meta, event, _queryEngine) {
|
|
38
|
-
if (event.TAG !== "ApiSchemaComputed") {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
let jsSnapshot = event.snapshot.map(e => {
|
|
42
|
-
let match = e.apiTarget;
|
|
43
|
-
let tmp;
|
|
44
|
-
tmp = match === "Domain" ? "Domain" : "Platform";
|
|
45
|
-
return {
|
|
46
|
-
pluginId: e.pluginId,
|
|
47
|
-
encoded: e.encoded,
|
|
48
|
-
protocol: e.protocol,
|
|
49
|
-
apiTarget: tmp
|
|
50
|
-
};
|
|
51
|
-
});
|
|
52
|
-
return await pushApiSchema(jsSnapshot);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let Spec;
|
|
56
|
-
|
|
57
|
-
export {
|
|
58
|
-
Spec,
|
|
59
|
-
Source,
|
|
60
|
-
moduleUrl,
|
|
61
|
-
pushApiSchema,
|
|
62
|
-
execute,
|
|
63
|
-
}
|
|
64
|
-
/* moduleUrl Not a pure module */
|
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
// AWS resolver for the `Platform_ApiFragments` admin GraphQL query — the deploy-facing
|
|
2
|
-
// push-status surface of the API-schema fragment registry.
|
|
3
|
-
//
|
|
4
|
-
// Backed by a Lambda DataSource that scans the ApiFragments StateViewSlice table (one item per
|
|
5
|
-
// plugin name that has registered an API-schema fragment). The persisted state carries the same
|
|
6
|
-
// status fields the in-memory adapter's `Platform_ApiFragmentsApi.encodeApiFragmentEntry`
|
|
7
|
-
// projects (pluginId, apiTarget, pushStatus, pushMessage, pushedAt, registeredAt, updatedAt) —
|
|
8
|
-
// all plain/bare-string primitives in DynamoDB — so the handler projects them as-is. The encoded
|
|
9
|
-
// SDL is deliberately NOT exposed (the deploy waiter needs status only). The registry is keyed by
|
|
10
|
-
// bare plugin name, so no name@version collapse is required (mirrors the ApiFragments slice).
|
|
11
|
-
|
|
12
|
-
open PulumiAws
|
|
13
|
-
|
|
14
|
-
let makeHandlerCode = (~tableName as _: string): string =>
|
|
15
|
-
`
|
|
16
|
-
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
17
|
-
import { DynamoDBDocumentClient, ScanCommand } from "@aws-sdk/lib-dynamodb";
|
|
18
|
-
|
|
19
|
-
const client = DynamoDBDocumentClient.from(new DynamoDBClient({}));
|
|
20
|
-
const TABLE = process.env.API_FRAGMENT_RM_TABLE;
|
|
21
|
-
|
|
22
|
-
export async function handler() {
|
|
23
|
-
if (!TABLE) {
|
|
24
|
-
console.error("Platform_ApiFragments: API_FRAGMENT_RM_TABLE env var not set");
|
|
25
|
-
return [];
|
|
26
|
-
}
|
|
27
|
-
const items = [];
|
|
28
|
-
let exclusiveStartKey;
|
|
29
|
-
do {
|
|
30
|
-
const out = await client.send(new ScanCommand({
|
|
31
|
-
TableName: TABLE,
|
|
32
|
-
Limit: 1000,
|
|
33
|
-
ExclusiveStartKey: exclusiveStartKey,
|
|
34
|
-
}));
|
|
35
|
-
if (out.Items) items.push(...out.Items);
|
|
36
|
-
exclusiveStartKey = out.LastEvaluatedKey;
|
|
37
|
-
} while (exclusiveStartKey);
|
|
38
|
-
|
|
39
|
-
// Status-only projection, byte-identical to Platform_ApiFragmentsApi.encodeApiFragmentEntry.
|
|
40
|
-
// The registry is keyed by bare plugin name; split("@")[0] is a defensive no-op against any
|
|
41
|
-
// legacy name@version row.
|
|
42
|
-
return items
|
|
43
|
-
.filter((item) => item && item.pluginId)
|
|
44
|
-
.map((item) => ({
|
|
45
|
-
pluginId: String(item.pluginId).split("@")[0],
|
|
46
|
-
apiTarget: item.apiTarget || "Domain",
|
|
47
|
-
pushStatus: item.pushStatus || "pending",
|
|
48
|
-
pushMessage: item.pushMessage || "",
|
|
49
|
-
pushedAt: item.pushedAt || "",
|
|
50
|
-
registeredAt: item.registeredAt || "",
|
|
51
|
-
updatedAt: item.updatedAt || "",
|
|
52
|
-
}));
|
|
53
|
-
}
|
|
54
|
-
`
|
|
55
|
-
|
|
56
|
-
let resolverCode = `
|
|
57
|
-
import { util } from '@aws-appsync/utils';
|
|
58
|
-
export function request(ctx) {
|
|
59
|
-
return { operation: 'Invoke', payload: {} };
|
|
60
|
-
}
|
|
61
|
-
export function response(ctx) {
|
|
62
|
-
if (ctx.error) util.error(ctx.error.message, ctx.error.type);
|
|
63
|
-
return ctx.result;
|
|
64
|
-
}
|
|
65
|
-
`->Pulumi.Input.make
|
|
66
|
-
|
|
67
|
-
let make = (
|
|
68
|
-
~api: Pulumi.Output.t<AppSync.GraphQLApi.t>,
|
|
69
|
-
~apiFragmentRegistryTableName: Pulumi.Output.t<string>,
|
|
70
|
-
// Resolves when the admin-base schema push has completed and the API is ACTIVE
|
|
71
|
-
// (Platform_Admin.adminSchemaPushed). CreateResolver is gated on it so it never
|
|
72
|
-
// races StartSchemaCreation on this first-ever-deployed field.
|
|
73
|
-
~schemaReady: Pulumi.Output.t<unit>,
|
|
74
|
-
~opts: Pulumi.ComponentResource.options,
|
|
75
|
-
) => {
|
|
76
|
-
let opts = opts->ReventlessCore.Util.Pulumi.ComponentResourceOptions.toCustomResourceOptions
|
|
77
|
-
let name = "PlatformApiFragments"
|
|
78
|
-
|
|
79
|
-
let lambdaRole = IAM.Role.makeWithDefaultPolicy(
|
|
80
|
-
~name=name ++ "Lambda",
|
|
81
|
-
~servicePrincipal=AWS.Lambda.principal->Pulumi.Output.make,
|
|
82
|
-
~opts,
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
let _ =
|
|
86
|
-
apiFragmentRegistryTableName->Pulumi.Output.apply(tableName => {
|
|
87
|
-
open PolicyDocument
|
|
88
|
-
let _rolePolicy = IAM.RolePolicy.make(
|
|
89
|
-
~name=name ++ "LambdaPolicy",
|
|
90
|
-
~args={
|
|
91
|
-
IAM.RolePolicy.policy: PolicyDocument.make(
|
|
92
|
-
~id=name ++ "LambdaPolicy",
|
|
93
|
-
~statements=[
|
|
94
|
-
{
|
|
95
|
-
sid: "AllowLambdaLogging",
|
|
96
|
-
effect: Allow,
|
|
97
|
-
actions: Action("logs:*"),
|
|
98
|
-
resources: Resource("arn:aws:logs:*:*:*"),
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
sid: "AllowScanApiFragmentsTable",
|
|
102
|
-
effect: Allow,
|
|
103
|
-
actions: Actions(["dynamodb:Scan"]),
|
|
104
|
-
resources: Resource("arn:aws:dynamodb:*:*:table/" ++ tableName),
|
|
105
|
-
},
|
|
106
|
-
],
|
|
107
|
-
)
|
|
108
|
-
->PolicyDocument.toJsonString
|
|
109
|
-
->Pulumi.Input.make,
|
|
110
|
-
role: lambdaRole.id->Pulumi.Output.asInput,
|
|
111
|
-
},
|
|
112
|
-
~opts,
|
|
113
|
-
)
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
let archiveContents: dict<Pulumi.Archive.assetOrArchive> = Dict.make()
|
|
117
|
-
let handlerCodeStub = makeHandlerCode(~tableName="")
|
|
118
|
-
archiveContents->Dict.set(
|
|
119
|
-
"index.mjs",
|
|
120
|
-
Pulumi.Asset.stringAsset(handlerCodeStub)->Pulumi.Archive.assetToAssetOrArchive,
|
|
121
|
-
)
|
|
122
|
-
// ESM self-containment: the handler imports @aws-sdk/* bare specifiers the
|
|
123
|
-
// nodejs22.x runtime provides only under /var/runtime — unreachable from
|
|
124
|
-
// /var/task ESM without the resolver hook. Ship the loader + set its env vars.
|
|
125
|
-
let loaderHash = Util_Bundle.addEsmLoaderAssets(archiveContents)
|
|
126
|
-
let code = Pulumi.Archive.assetArchive(archiveContents)
|
|
127
|
-
let sourceCodeHash = Util_Bundle.hashString(handlerCodeStub ++ "\n---\n" ++ loaderHash)
|
|
128
|
-
|
|
129
|
-
let layers =
|
|
130
|
-
Lambda.reventlessLayerArn
|
|
131
|
-
->Option.map(arn => [arn->Pulumi.Input.make])
|
|
132
|
-
->Option.getOr([])
|
|
133
|
-
->Pulumi.Input.make
|
|
134
|
-
|
|
135
|
-
let lambda = Lambda.Function.make(
|
|
136
|
-
~name=name ++ "Lambda",
|
|
137
|
-
~args={
|
|
138
|
-
handler: "index.handler"->Pulumi.Input.make,
|
|
139
|
-
runtime: "nodejs22.x"->Pulumi.Input.make,
|
|
140
|
-
code: code->Pulumi.Input.make,
|
|
141
|
-
sourceCodeHash: sourceCodeHash->Pulumi.Input.make,
|
|
142
|
-
role: lambdaRole.arn->Pulumi.Output.asInput,
|
|
143
|
-
memorySize: 512->Pulumi.Input.make,
|
|
144
|
-
timeout: 30->Pulumi.Input.make,
|
|
145
|
-
layers,
|
|
146
|
-
tags: AWS.Tags.make(~name=name ++ "Lambda", ReventlessCore.ReadModel.componentType),
|
|
147
|
-
environment: (
|
|
148
|
-
{
|
|
149
|
-
Lambda.Function.variables: Dict.fromArray([
|
|
150
|
-
("Environment", Pulumi.Pulumi.getStackName()->Pulumi.Input.make),
|
|
151
|
-
("API_FRAGMENT_RM_TABLE", apiFragmentRegistryTableName->Pulumi.Output.asInput),
|
|
152
|
-
("NODE_OPTIONS", Util_Bundle.esmLoaderNodeOptions->Pulumi.Input.make),
|
|
153
|
-
("ESM_FALLBACK_DIRS", Util_Bundle.esmFallbackDirs->Pulumi.Input.make),
|
|
154
|
-
]),
|
|
155
|
-
}: Lambda.Function.functionEnvironment
|
|
156
|
-
)->Pulumi.Input.make,
|
|
157
|
-
},
|
|
158
|
-
~opts,
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
let dataSourceRole = IAM.Role.makeWithDefaultPolicy(
|
|
162
|
-
~name=name ++ "DataSource",
|
|
163
|
-
~servicePrincipal=AWS.AppSync.principal->Pulumi.Output.make,
|
|
164
|
-
~opts,
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
let _ =
|
|
168
|
-
(lambda.arn, dataSourceRole.id)
|
|
169
|
-
->Pulumi.Output.all2
|
|
170
|
-
->Pulumi.Output.apply(((lambdaArn, dataSourceRoleId)) => {
|
|
171
|
-
open PolicyDocument
|
|
172
|
-
let _attach = IAM.RolePolicy.make(
|
|
173
|
-
~name=name ++ "DataSource",
|
|
174
|
-
~args={
|
|
175
|
-
IAM.RolePolicy.policy: PolicyDocument.make(
|
|
176
|
-
~id=name ++ "DataSourcePolicy",
|
|
177
|
-
~statements=[
|
|
178
|
-
{
|
|
179
|
-
sid: "AllowDataSourceInvokeLambda",
|
|
180
|
-
effect: Allow,
|
|
181
|
-
actions: Action("lambda:InvokeFunction"),
|
|
182
|
-
resources: Resource(lambdaArn),
|
|
183
|
-
},
|
|
184
|
-
],
|
|
185
|
-
)
|
|
186
|
-
->PolicyDocument.toJsonString
|
|
187
|
-
->Pulumi.Input.make,
|
|
188
|
-
role: dataSourceRoleId->Pulumi.Input.make,
|
|
189
|
-
},
|
|
190
|
-
~opts,
|
|
191
|
-
)
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
let dataSource = AppSync.DataSource.make(
|
|
195
|
-
~name=name ++ "DataSource",
|
|
196
|
-
~args={
|
|
197
|
-
type_: AWS_LAMBDA,
|
|
198
|
-
apiId: api->Pulumi.Output.flatMap(api => api.id)->Pulumi.Output.asInput,
|
|
199
|
-
lambdaConfig: {
|
|
200
|
-
AppSync.DataSource.functionArn: lambda.arn->Pulumi.Output.asInput,
|
|
201
|
-
}->Pulumi.Input.make,
|
|
202
|
-
serviceRoleArn: dataSourceRole.arn->Pulumi.Output.asInput,
|
|
203
|
-
},
|
|
204
|
-
~opts=Some(opts),
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
// Create the resolver only after the admin schema push is ACTIVE — otherwise
|
|
208
|
-
// CreateResolver races StartSchemaCreation and fails with "No field named
|
|
209
|
-
// Platform_ApiFragments found on type Query" the first time this field ships.
|
|
210
|
-
let _resolver =
|
|
211
|
-
schemaReady->Pulumi.Output.apply(() =>
|
|
212
|
-
AppSync_Resolver_Native.makeUnitJsResolver(
|
|
213
|
-
~name=name ++ "Resolver",
|
|
214
|
-
~api,
|
|
215
|
-
~dataSourceName=dataSource.name->Pulumi.Output.asInput,
|
|
216
|
-
~type_="Query"->Pulumi.Input.make,
|
|
217
|
-
~field="Platform_ApiFragments"->Pulumi.Input.make,
|
|
218
|
-
~code=resolverCode,
|
|
219
|
-
~opts,
|
|
220
|
-
)
|
|
221
|
-
)
|
|
222
|
-
}
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
-
|
|
3
|
-
import * as Aws from "@pulumi/aws";
|
|
4
|
-
import * as IAM$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/IAM.res.mjs";
|
|
5
|
-
import * as Output$Pulumi from "@reventlessdev/rescript-pulumi-pulumi/src/Output.res.mjs";
|
|
6
|
-
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
7
|
-
import * as Pulumi from "@pulumi/pulumi";
|
|
8
|
-
import * as Lambda$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/Lambda/Lambda.res.mjs";
|
|
9
|
-
import * as AWS$ReventlessAws from "../AWS.res.mjs";
|
|
10
|
-
import * as AWS_Tags$ReventlessAws from "../AWS_Tags.res.mjs";
|
|
11
|
-
import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
|
|
12
|
-
import * as ReadModel$ReventlessCore from "@reventlessdev/reventless-core/src/components/ReadModel/ReadModel.res.mjs";
|
|
13
|
-
import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
|
|
14
|
-
import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Pulumi.res.mjs";
|
|
15
|
-
import * as AppSync_Resolver_Native$ReventlessAws from "./AppSync_Resolver_Native.res.mjs";
|
|
16
|
-
|
|
17
|
-
function makeHandlerCode(param) {
|
|
18
|
-
return `
|
|
19
|
-
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
20
|
-
import { DynamoDBDocumentClient, ScanCommand } from "@aws-sdk/lib-dynamodb";
|
|
21
|
-
|
|
22
|
-
const client = DynamoDBDocumentClient.from(new DynamoDBClient({}));
|
|
23
|
-
const TABLE = process.env.API_FRAGMENT_RM_TABLE;
|
|
24
|
-
|
|
25
|
-
export async function handler() {
|
|
26
|
-
if (!TABLE) {
|
|
27
|
-
console.error("Platform_ApiFragments: API_FRAGMENT_RM_TABLE env var not set");
|
|
28
|
-
return [];
|
|
29
|
-
}
|
|
30
|
-
const items = [];
|
|
31
|
-
let exclusiveStartKey;
|
|
32
|
-
do {
|
|
33
|
-
const out = await client.send(new ScanCommand({
|
|
34
|
-
TableName: TABLE,
|
|
35
|
-
Limit: 1000,
|
|
36
|
-
ExclusiveStartKey: exclusiveStartKey,
|
|
37
|
-
}));
|
|
38
|
-
if (out.Items) items.push(...out.Items);
|
|
39
|
-
exclusiveStartKey = out.LastEvaluatedKey;
|
|
40
|
-
} while (exclusiveStartKey);
|
|
41
|
-
|
|
42
|
-
// Status-only projection, byte-identical to Platform_ApiFragmentsApi.encodeApiFragmentEntry.
|
|
43
|
-
// The registry is keyed by bare plugin name; split("@")[0] is a defensive no-op against any
|
|
44
|
-
// legacy name@version row.
|
|
45
|
-
return items
|
|
46
|
-
.filter((item) => item && item.pluginId)
|
|
47
|
-
.map((item) => ({
|
|
48
|
-
pluginId: String(item.pluginId).split("@")[0],
|
|
49
|
-
apiTarget: item.apiTarget || "Domain",
|
|
50
|
-
pushStatus: item.pushStatus || "pending",
|
|
51
|
-
pushMessage: item.pushMessage || "",
|
|
52
|
-
pushedAt: item.pushedAt || "",
|
|
53
|
-
registeredAt: item.registeredAt || "",
|
|
54
|
-
updatedAt: item.updatedAt || "",
|
|
55
|
-
}));
|
|
56
|
-
}
|
|
57
|
-
`;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
let resolverCode = `
|
|
61
|
-
import { util } from '@aws-appsync/utils';
|
|
62
|
-
export function request(ctx) {
|
|
63
|
-
return { operation: 'Invoke', payload: {} };
|
|
64
|
-
}
|
|
65
|
-
export function response(ctx) {
|
|
66
|
-
if (ctx.error) util.error(ctx.error.message, ctx.error.type);
|
|
67
|
-
return ctx.result;
|
|
68
|
-
}
|
|
69
|
-
`;
|
|
70
|
-
|
|
71
|
-
function make(api, apiFragmentRegistryTableName, schemaReady, opts) {
|
|
72
|
-
let opts$1 = Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions(opts);
|
|
73
|
-
let name = "PlatformApiFragments";
|
|
74
|
-
let lambdaRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name + "Lambda", Pulumi.output(AWS$ReventlessAws.Lambda.principal), opts$1);
|
|
75
|
-
apiFragmentRegistryTableName.apply(tableName => {
|
|
76
|
-
new (Aws.iam.RolePolicy)(name + "LambdaPolicy", {
|
|
77
|
-
policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + "LambdaPolicy", [
|
|
78
|
-
{
|
|
79
|
-
Sid: "AllowLambdaLogging",
|
|
80
|
-
Effect: "Allow",
|
|
81
|
-
Action: "logs:*",
|
|
82
|
-
Resource: "arn:aws:logs:*:*:*"
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
Sid: "AllowScanApiFragmentsTable",
|
|
86
|
-
Effect: "Allow",
|
|
87
|
-
Action: ["dynamodb:Scan"],
|
|
88
|
-
Resource: "arn:aws:dynamodb:*:*:table/" + tableName
|
|
89
|
-
}
|
|
90
|
-
])),
|
|
91
|
-
role: lambdaRole.id
|
|
92
|
-
}, opts$1);
|
|
93
|
-
});
|
|
94
|
-
let archiveContents = {};
|
|
95
|
-
let handlerCodeStub = `
|
|
96
|
-
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
97
|
-
import { DynamoDBDocumentClient, ScanCommand } from "@aws-sdk/lib-dynamodb";
|
|
98
|
-
|
|
99
|
-
const client = DynamoDBDocumentClient.from(new DynamoDBClient({}));
|
|
100
|
-
const TABLE = process.env.API_FRAGMENT_RM_TABLE;
|
|
101
|
-
|
|
102
|
-
export async function handler() {
|
|
103
|
-
if (!TABLE) {
|
|
104
|
-
console.error("Platform_ApiFragments: API_FRAGMENT_RM_TABLE env var not set");
|
|
105
|
-
return [];
|
|
106
|
-
}
|
|
107
|
-
const items = [];
|
|
108
|
-
let exclusiveStartKey;
|
|
109
|
-
do {
|
|
110
|
-
const out = await client.send(new ScanCommand({
|
|
111
|
-
TableName: TABLE,
|
|
112
|
-
Limit: 1000,
|
|
113
|
-
ExclusiveStartKey: exclusiveStartKey,
|
|
114
|
-
}));
|
|
115
|
-
if (out.Items) items.push(...out.Items);
|
|
116
|
-
exclusiveStartKey = out.LastEvaluatedKey;
|
|
117
|
-
} while (exclusiveStartKey);
|
|
118
|
-
|
|
119
|
-
// Status-only projection, byte-identical to Platform_ApiFragmentsApi.encodeApiFragmentEntry.
|
|
120
|
-
// The registry is keyed by bare plugin name; split("@")[0] is a defensive no-op against any
|
|
121
|
-
// legacy name@version row.
|
|
122
|
-
return items
|
|
123
|
-
.filter((item) => item && item.pluginId)
|
|
124
|
-
.map((item) => ({
|
|
125
|
-
pluginId: String(item.pluginId).split("@")[0],
|
|
126
|
-
apiTarget: item.apiTarget || "Domain",
|
|
127
|
-
pushStatus: item.pushStatus || "pending",
|
|
128
|
-
pushMessage: item.pushMessage || "",
|
|
129
|
-
pushedAt: item.pushedAt || "",
|
|
130
|
-
registeredAt: item.registeredAt || "",
|
|
131
|
-
updatedAt: item.updatedAt || "",
|
|
132
|
-
}));
|
|
133
|
-
}
|
|
134
|
-
`;
|
|
135
|
-
archiveContents["index.mjs"] = new (Pulumi.asset.StringAsset)(handlerCodeStub);
|
|
136
|
-
let loaderHash = Util_Bundle$ReventlessAws.addEsmLoaderAssets(archiveContents);
|
|
137
|
-
let code = new (Pulumi.asset.AssetArchive)(archiveContents);
|
|
138
|
-
let sourceCodeHash = Util_Bundle$ReventlessAws.hashString(handlerCodeStub + "\n---\n" + loaderHash);
|
|
139
|
-
let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
|
|
140
|
-
let lambda = new (Aws.lambda.Function)(name + "Lambda", {
|
|
141
|
-
handler: "index.handler",
|
|
142
|
-
runtime: "nodejs22.x",
|
|
143
|
-
code: code,
|
|
144
|
-
role: lambdaRole.arn,
|
|
145
|
-
memorySize: 512,
|
|
146
|
-
timeout: 30,
|
|
147
|
-
layers: layers,
|
|
148
|
-
tags: AWS_Tags$ReventlessAws.make(name + "Lambda", ReadModel$ReventlessCore.componentType),
|
|
149
|
-
environment: {
|
|
150
|
-
variables: Object.fromEntries([
|
|
151
|
-
[
|
|
152
|
-
"Environment",
|
|
153
|
-
Pulumi.getStack()
|
|
154
|
-
],
|
|
155
|
-
[
|
|
156
|
-
"API_FRAGMENT_RM_TABLE",
|
|
157
|
-
apiFragmentRegistryTableName
|
|
158
|
-
],
|
|
159
|
-
[
|
|
160
|
-
"NODE_OPTIONS",
|
|
161
|
-
Util_Bundle$ReventlessAws.esmLoaderNodeOptions
|
|
162
|
-
],
|
|
163
|
-
[
|
|
164
|
-
"ESM_FALLBACK_DIRS",
|
|
165
|
-
Util_Bundle$ReventlessAws.esmFallbackDirs
|
|
166
|
-
]
|
|
167
|
-
])
|
|
168
|
-
},
|
|
169
|
-
sourceCodeHash: sourceCodeHash
|
|
170
|
-
}, opts$1);
|
|
171
|
-
let dataSourceRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name + "DataSource", Pulumi.output(AWS$ReventlessAws.AppSync.principal), opts$1);
|
|
172
|
-
Pulumi.all([
|
|
173
|
-
lambda.arn,
|
|
174
|
-
dataSourceRole.id
|
|
175
|
-
]).apply(param => {
|
|
176
|
-
new (Aws.iam.RolePolicy)(name + "DataSource", {
|
|
177
|
-
policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + "DataSourcePolicy", [{
|
|
178
|
-
Sid: "AllowDataSourceInvokeLambda",
|
|
179
|
-
Effect: "Allow",
|
|
180
|
-
Action: "lambda:InvokeFunction",
|
|
181
|
-
Resource: param[0]
|
|
182
|
-
}])),
|
|
183
|
-
role: param[1]
|
|
184
|
-
}, opts$1);
|
|
185
|
-
});
|
|
186
|
-
let dataSource = new (Aws.appsync.DataSource)(name + "DataSource", {
|
|
187
|
-
type: "AWS_LAMBDA",
|
|
188
|
-
apiId: Output$Pulumi.flatMap(api, api => api.id),
|
|
189
|
-
lambdaConfig: {
|
|
190
|
-
functionArn: lambda.arn
|
|
191
|
-
},
|
|
192
|
-
serviceRoleArn: dataSourceRole.arn
|
|
193
|
-
}, opts$1);
|
|
194
|
-
schemaReady.apply(() => AppSync_Resolver_Native$ReventlessAws.makeUnitJsResolver(name + "Resolver", api, dataSource.name, "Query", "Platform_ApiFragments", resolverCode, opts$1));
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
export {
|
|
198
|
-
makeHandlerCode,
|
|
199
|
-
resolverCode,
|
|
200
|
-
make,
|
|
201
|
-
}
|
|
202
|
-
/* @pulumi/aws Not a pure module */
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
// AppSync Relay Node resolver.
|
|
2
|
-
// Creates a pipeline resolver for node(id: ID!) that decodes the global ID
|
|
3
|
-
// and routes to the correct DynamoDB table based on entity type.
|
|
4
|
-
//
|
|
5
|
-
// Usage: call `make` at platform deploy time after all QueryDb components are built,
|
|
6
|
-
// passing the collected (typeName, dataSourceName) pairs.
|
|
7
|
-
|
|
8
|
-
open PulumiAws.AppSync
|
|
9
|
-
|
|
10
|
-
type nodeTypeEntry = {
|
|
11
|
-
typeName: string,
|
|
12
|
-
dataSourceName: Pulumi.Input.t<string>,
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Tracks registered entity types for the node resolver pipeline.
|
|
16
|
-
let nodeTypeEntries: ref<array<nodeTypeEntry>> = ref([])
|
|
17
|
-
|
|
18
|
-
let registerNodeType = (~typeName: string, ~dataSourceName: Pulumi.Input.t<string>) =>
|
|
19
|
-
nodeTypeEntries.contents->Array.push({typeName, dataSourceName})
|
|
20
|
-
|
|
21
|
-
let make = (
|
|
22
|
-
~api: Types.AppSync.api,
|
|
23
|
-
~opts: Pulumi.ComponentResource.options,
|
|
24
|
-
) => {
|
|
25
|
-
let entries = nodeTypeEntries.contents
|
|
26
|
-
if entries->Array.length == 0 {
|
|
27
|
-
[]
|
|
28
|
-
} else {
|
|
29
|
-
let noneDataSource = DataSource.makeNoneDataSource(
|
|
30
|
-
~name="NodeResolverNone",
|
|
31
|
-
~api,
|
|
32
|
-
~opts=ReventlessCore.Util.Pulumi.ComponentResourceOptions.toCustomResourceOptions(opts),
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
// First pipeline function: decode global ID (NONE datasource)
|
|
36
|
-
let decodeFn = Function.makeJs(
|
|
37
|
-
~name="NodeDecodeGlobalId",
|
|
38
|
-
~api,
|
|
39
|
-
~dataSource=noneDataSource.name->Pulumi.Output.asInput,
|
|
40
|
-
~code=Resolver.Functions.nodeDecodeGlobalId,
|
|
41
|
-
~opts=ReventlessCore.Util.Pulumi.ComponentResourceOptions.toCustomResourceOptions(opts),
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
// Per-type pipeline functions: each checks ctx.stash.typeName and fetches if matching
|
|
45
|
-
let typeFunctions = entries->Array.map(entry => {
|
|
46
|
-
Function.makeJs(
|
|
47
|
-
~name="NodeGet" ++ entry.typeName,
|
|
48
|
-
~api,
|
|
49
|
-
~dataSource=entry.dataSourceName,
|
|
50
|
-
~code=Resolver.Functions.nodeGetItemForType(~typeName=entry.typeName),
|
|
51
|
-
~opts=ReventlessCore.Util.Pulumi.ComponentResourceOptions.toCustomResourceOptions(opts),
|
|
52
|
-
)
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
let allFunctions = Array.concat([decodeFn], typeFunctions)
|
|
56
|
-
|
|
57
|
-
let resolver = Resolver.makePipelineJsResolver(
|
|
58
|
-
~name="NodeResolver",
|
|
59
|
-
~api,
|
|
60
|
-
~type_="Query"->Pulumi.Input.make,
|
|
61
|
-
~field="node"->Pulumi.Input.make,
|
|
62
|
-
~code=Resolver.Functions.pipelinePassThrough,
|
|
63
|
-
~functions=allFunctions,
|
|
64
|
-
~opts=ReventlessCore.Util.Pulumi.ComponentResourceOptions.toCustomResourceOptions(opts),
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
[resolver]->Array.map(Util_AppSync.toResource)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
let reset = () => nodeTypeEntries.contents = []
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
-
|
|
3
|
-
import * as AppSync_Function$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/AppSync/AppSync_Function.res.mjs";
|
|
4
|
-
import * as AppSync_Resolver$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/AppSync/AppSync_Resolver.res.mjs";
|
|
5
|
-
import * as Util_AppSync$ReventlessAws from "../../util/Util_AppSync.res.mjs";
|
|
6
|
-
import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Pulumi.res.mjs";
|
|
7
|
-
import * as AppSync_DataSource$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/AppSync/AppSync_DataSource.res.mjs";
|
|
8
|
-
import * as AppSync_Resolver_Functions$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/AppSync/AppSync_Resolver_Functions.res.mjs";
|
|
9
|
-
|
|
10
|
-
let nodeTypeEntries = {
|
|
11
|
-
contents: []
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
function registerNodeType(typeName, dataSourceName) {
|
|
15
|
-
nodeTypeEntries.contents.push({
|
|
16
|
-
typeName: typeName,
|
|
17
|
-
dataSourceName: dataSourceName
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function make(api, opts) {
|
|
22
|
-
let entries = nodeTypeEntries.contents;
|
|
23
|
-
if (entries.length === 0) {
|
|
24
|
-
return [];
|
|
25
|
-
}
|
|
26
|
-
let noneDataSource = AppSync_DataSource$PulumiAws.makeNoneDataSource("NodeResolverNone", api, Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions(opts));
|
|
27
|
-
let decodeFn = AppSync_Function$PulumiAws.makeJs("NodeDecodeGlobalId", api, noneDataSource.name, AppSync_Resolver_Functions$PulumiAws.nodeDecodeGlobalId, Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions(opts));
|
|
28
|
-
let typeFunctions = entries.map(entry => AppSync_Function$PulumiAws.makeJs("NodeGet" + entry.typeName, api, entry.dataSourceName, AppSync_Resolver_Functions$PulumiAws.nodeGetItemForType(entry.typeName), Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions(opts)));
|
|
29
|
-
let allFunctions = [decodeFn].concat(typeFunctions);
|
|
30
|
-
let resolver = AppSync_Resolver$PulumiAws.makePipelineJsResolver("NodeResolver", api, "Query", "node", AppSync_Resolver_Functions$PulumiAws.pipelinePassThrough, allFunctions, Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions(opts));
|
|
31
|
-
return [resolver].map(Util_AppSync$ReventlessAws.toResource);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function reset() {
|
|
35
|
-
nodeTypeEntries.contents = [];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export {
|
|
39
|
-
nodeTypeEntries,
|
|
40
|
-
registerNodeType,
|
|
41
|
-
make,
|
|
42
|
-
reset,
|
|
43
|
-
}
|
|
44
|
-
/* AppSync_Function-PulumiAws Not a pure module */
|