@reventlessdev/reventless-aws 3.0.0-alpha.207 → 3.0.0-alpha.209
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 +15 -0
- package/package.json +7 -7
- package/src/Platform.res +34 -475
- package/src/Platform.res.mjs +119 -610
- package/src/adapter/Api/ApiSchemaPush.res +24 -8
- package/src/adapter/Api/ApiSchemaPush.res.mjs +27 -1
- package/src/adapter/Runtime/AdminEventCollectorEntryPoint.mjs +7 -382
- package/src/adapter/Runtime/ApiSchemaPush_Runtime.mjs +8 -1
- package/src/components/Api/AppSync_Adapter.res +0 -91
- package/src/components/Api/AppSync_Adapter.res.mjs +0 -84
- package/src/plugin/runtime/PluginRuntime_Builder.res +0 -139
- package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +4 -80
- package/tests/ApiSchemaPushTest.res +8 -0
- package/tests/ApiSchemaPushTest.res.mjs +4 -0
- package/tests/MCP_LambdaTest.res +6 -4
- package/tests/MCP_LambdaTest.res.mjs +2 -2
|
@@ -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
|
};
|
|
@@ -408,9 +327,6 @@ export {
|
|
|
408
327
|
waitForSchemaActive,
|
|
409
328
|
getIntrospectionSdl,
|
|
410
329
|
deploySchemaWithRetry,
|
|
411
|
-
schemaLockSleep,
|
|
412
|
-
_schemaLockCounter,
|
|
413
|
-
withSchemaPushLock,
|
|
414
330
|
_client,
|
|
415
331
|
getClient,
|
|
416
332
|
_permissionToCognitoGroups,
|
|
@@ -3,38 +3,21 @@ let log = ReventlessCore.Logger.fromEnv()
|
|
|
3
3
|
type adminConfig = {
|
|
4
4
|
eventTopicArn: option<Pulumi.Output.t<string>>,
|
|
5
5
|
pluginReadModelTableName: option<Pulumi.Output.t<string>>,
|
|
6
|
-
// Dedicated PluginSchemaPersistence table holding deploy-time SDL fragments
|
|
7
|
-
// (rows keyed "deploy-schema:<name>"). The runtime schema stitch reads this
|
|
8
|
-
// durable source instead of the lifecycle-volatile Plugin RM Connected rows.
|
|
9
|
-
pluginSchemaPersistenceTableName: option<Pulumi.Output.t<string>>,
|
|
10
6
|
schedulerRoleArn: option<Pulumi.Output.t<string>>,
|
|
11
7
|
schedulerQueueArn: option<Pulumi.Output.t<string>>,
|
|
12
8
|
schedulerQueueName: option<Pulumi.Output.t<string>>,
|
|
13
9
|
appSyncApiId: option<Pulumi.Output.t<string>>,
|
|
14
10
|
clonerEnabled: bool,
|
|
15
|
-
// Reactive ApiFragmentRegistry single writer (2e), admin EventCollector only:
|
|
16
|
-
// the Platform AppSync id (split mode; unified → same as appSyncApiId), the
|
|
17
|
-
// ApiFragments StateViewSlice table it scans, the admin DCB command-topic FIFO
|
|
18
|
-
// URL it dispatches RecordApiFragmentPush to, and the split/unified mode flag.
|
|
19
|
-
platformApiId: option<Pulumi.Output.t<string>>,
|
|
20
|
-
apiFragmentRegistryTableName: option<Pulumi.Output.t<string>>,
|
|
21
|
-
adminDcbCmdTopicUrl: option<Pulumi.Output.t<string>>,
|
|
22
|
-
splitApi: bool,
|
|
23
11
|
}
|
|
24
12
|
|
|
25
13
|
let configRef: ref<adminConfig> = ref({
|
|
26
14
|
eventTopicArn: None,
|
|
27
15
|
pluginReadModelTableName: None,
|
|
28
|
-
pluginSchemaPersistenceTableName: None,
|
|
29
16
|
schedulerRoleArn: None,
|
|
30
17
|
schedulerQueueArn: None,
|
|
31
18
|
schedulerQueueName: None,
|
|
32
19
|
appSyncApiId: None,
|
|
33
20
|
clonerEnabled: false,
|
|
34
|
-
platformApiId: None,
|
|
35
|
-
apiFragmentRegistryTableName: None,
|
|
36
|
-
adminDcbCmdTopicUrl: None,
|
|
37
|
-
splitApi: false,
|
|
38
21
|
})
|
|
39
22
|
|
|
40
23
|
type sliceModulePaths = {
|
|
@@ -111,31 +94,21 @@ let setStateChangesConfig = (~sync=?, ~async=?, ()) => {
|
|
|
111
94
|
let registerConfig = (
|
|
112
95
|
~eventTopicArn=?,
|
|
113
96
|
~pluginReadModelTableName=?,
|
|
114
|
-
~pluginSchemaPersistenceTableName=?,
|
|
115
97
|
~schedulerRoleArn=?,
|
|
116
98
|
~schedulerQueueArn=?,
|
|
117
99
|
~schedulerQueueName=?,
|
|
118
100
|
~appSyncApiId=?,
|
|
119
101
|
~clonerEnabled=false,
|
|
120
|
-
~platformApiId=?,
|
|
121
|
-
~apiFragmentRegistryTableName=?,
|
|
122
|
-
~adminDcbCmdTopicUrl=?,
|
|
123
|
-
~splitApi=false,
|
|
124
102
|
(),
|
|
125
103
|
) =>
|
|
126
104
|
configRef := {
|
|
127
105
|
eventTopicArn,
|
|
128
106
|
pluginReadModelTableName,
|
|
129
|
-
pluginSchemaPersistenceTableName,
|
|
130
107
|
schedulerRoleArn,
|
|
131
108
|
schedulerQueueArn,
|
|
132
109
|
schedulerQueueName,
|
|
133
110
|
appSyncApiId,
|
|
134
111
|
clonerEnabled,
|
|
135
|
-
platformApiId,
|
|
136
|
-
apiFragmentRegistryTableName,
|
|
137
|
-
adminDcbCmdTopicUrl,
|
|
138
|
-
splitApi,
|
|
139
112
|
}
|
|
140
113
|
|
|
141
114
|
// PluginRuntime_Builder is a functor so that the caller can inject the EventCollectorChannel
|
|
@@ -318,10 +291,6 @@ module Make = (
|
|
|
318
291
|
config.schedulerQueueName->outputOrPlaceholder->Obj.magic,
|
|
319
292
|
config.appSyncApiId->outputOrPlaceholder->Obj.magic,
|
|
320
293
|
epEventTopicArnsOutput->Pulumi.Output.asInput->Obj.magic,
|
|
321
|
-
config.pluginSchemaPersistenceTableName->outputOrPlaceholder->Obj.magic,
|
|
322
|
-
config.platformApiId->outputOrPlaceholder->Obj.magic,
|
|
323
|
-
config.apiFragmentRegistryTableName->outputOrPlaceholder->Obj.magic,
|
|
324
|
-
config.adminDcbCmdTopicUrl->outputOrPlaceholder->Obj.magic,
|
|
325
294
|
])
|
|
326
295
|
->Pulumi.Output.apply(values => {
|
|
327
296
|
let queueUrl = values->Array.getUnsafe(0)
|
|
@@ -333,27 +302,13 @@ module Make = (
|
|
|
333
302
|
let schedQueueName = values->Array.getUnsafe(6)
|
|
334
303
|
let appSyncApiId = values->Array.getUnsafe(7)
|
|
335
304
|
let epEventTopicArns: array<string> = Obj.magic(values->Array.getUnsafe(8))
|
|
336
|
-
let schemaPersistenceTable = values->Array.getUnsafe(9)
|
|
337
|
-
let platformApiId = values->Array.getUnsafe(10)
|
|
338
|
-
let apiFragmentRegistryTable = values->Array.getUnsafe(11)
|
|
339
|
-
let adminDcbCmdTopicUrl = values->Array.getUnsafe(12)
|
|
340
305
|
|
|
341
306
|
let dict = Dict.make()
|
|
342
307
|
dict->Dict.set("queueUrl", JSON.Encode.string(queueUrl))
|
|
343
308
|
dict->Dict.set("pluginExtensionPointCmdTopicUrl", JSON.Encode.string(pluginEpCmdTopicUrl))
|
|
344
309
|
dict->Dict.set("eventTopicArn", JSON.Encode.string(topLevelEventTopicArn))
|
|
345
310
|
dict->Dict.set("pluginReadModelTableName", JSON.Encode.string(rmTable))
|
|
346
|
-
dict->Dict.set(
|
|
347
|
-
"pluginSchemaPersistenceTableName",
|
|
348
|
-
JSON.Encode.string(schemaPersistenceTable),
|
|
349
|
-
)
|
|
350
311
|
dict->Dict.set("appSyncApiId", JSON.Encode.string(appSyncApiId))
|
|
351
|
-
// Reactive ApiFragmentRegistry single writer (2e) — placeholders for
|
|
352
|
-
// plugin ECs / all-at-once platforms disable it in the mjs.
|
|
353
|
-
dict->Dict.set("platformApiId", JSON.Encode.string(platformApiId))
|
|
354
|
-
dict->Dict.set("apiFragmentRegistryTableName", JSON.Encode.string(apiFragmentRegistryTable))
|
|
355
|
-
dict->Dict.set("adminDcbCmdTopicUrl", JSON.Encode.string(adminDcbCmdTopicUrl))
|
|
356
|
-
dict->Dict.set("splitApi", JSON.Encode.bool(config.splitApi))
|
|
357
312
|
dict->Dict.set("clonerEnabled", JSON.Encode.bool(config.clonerEnabled))
|
|
358
313
|
dict->Dict.set("schedulerRoleArn", JSON.Encode.string(schedRoleArn))
|
|
359
314
|
dict->Dict.set("schedulerQueueArn", JSON.Encode.string(schedQueueArn))
|
|
@@ -643,100 +598,6 @@ module Make = (
|
|
|
643
598
|
| None => ()
|
|
644
599
|
}
|
|
645
600
|
|
|
646
|
-
// mkUpdateApiSchema reads each plugin's deploy-time SDL fragment from the
|
|
647
|
-
// dedicated PluginSchemaPersistence table (deploy-schema:<name> rows) to
|
|
648
|
-
// re-stitch the live schema. Grant Scan on that table too — it is owned by
|
|
649
|
-
// Platform.res, so the admin EC's default policy includes no perms on it.
|
|
650
|
-
switch config.pluginSchemaPersistenceTableName {
|
|
651
|
-
| Some(schemaTableOutput) =>
|
|
652
|
-
let policyJson =
|
|
653
|
-
schemaTableOutput->Pulumi.Output.apply(tableName =>
|
|
654
|
-
PulumiAws.PolicyDocument.make(
|
|
655
|
-
~id=`${name}PluginSchemaScanPolicy`,
|
|
656
|
-
~statements=[
|
|
657
|
-
{
|
|
658
|
-
sid: "AllowAdminScanPluginSchemaPersistence",
|
|
659
|
-
effect: Allow,
|
|
660
|
-
actions: Actions(["dynamodb:Scan"]),
|
|
661
|
-
resources: Resource("arn:aws:dynamodb:*:*:table/" ++ tableName),
|
|
662
|
-
},
|
|
663
|
-
],
|
|
664
|
-
)->PulumiAws.PolicyDocument.toJsonString
|
|
665
|
-
)
|
|
666
|
-
let _ = PulumiAws.IAM.RolePolicy.make(
|
|
667
|
-
~name=`${name}-pluginSchemaScan`,
|
|
668
|
-
~args={
|
|
669
|
-
policy: policyJson->Pulumi.Output.asInput,
|
|
670
|
-
role: runtime.parts.lambdaRole.id->Pulumi.Output.asInput,
|
|
671
|
-
},
|
|
672
|
-
)
|
|
673
|
-
| None => ()
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
// 2e: the reactive ApiFragmentRegistry single writer scans the ApiFragments
|
|
677
|
-
// StateViewSlice table to re-fold the registry on each ApiFragment* event.
|
|
678
|
-
// The table is owned by the admin DcbBuilder, so grant Scan explicitly.
|
|
679
|
-
switch config.apiFragmentRegistryTableName {
|
|
680
|
-
| Some(tableOutput) =>
|
|
681
|
-
let policyJson =
|
|
682
|
-
tableOutput->Pulumi.Output.apply(tableName =>
|
|
683
|
-
PulumiAws.PolicyDocument.make(
|
|
684
|
-
~id=`${name}ApiFragmentsScanPolicy`,
|
|
685
|
-
~statements=[
|
|
686
|
-
{
|
|
687
|
-
sid: "AllowAdminScanApiFragments",
|
|
688
|
-
effect: Allow,
|
|
689
|
-
actions: Actions(["dynamodb:Scan"]),
|
|
690
|
-
resources: Resource("arn:aws:dynamodb:*:*:table/" ++ tableName),
|
|
691
|
-
},
|
|
692
|
-
],
|
|
693
|
-
)->PulumiAws.PolicyDocument.toJsonString
|
|
694
|
-
)
|
|
695
|
-
let _ = PulumiAws.IAM.RolePolicy.make(
|
|
696
|
-
~name=`${name}-apiFragmentsScan`,
|
|
697
|
-
~args={
|
|
698
|
-
policy: policyJson->Pulumi.Output.asInput,
|
|
699
|
-
role: runtime.parts.lambdaRole.id->Pulumi.Output.asInput,
|
|
700
|
-
},
|
|
701
|
-
)
|
|
702
|
-
| None => ()
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
// 2e: the reactive push writes the outcome back with RecordApiFragmentPush,
|
|
706
|
-
// dispatched onto the admin DCB command-topic FIFO queue — grant
|
|
707
|
-
// sqs:SendMessage on it (queue URL → ARN, same derivation as
|
|
708
|
-
// publishToAggregates below).
|
|
709
|
-
switch config.adminDcbCmdTopicUrl {
|
|
710
|
-
| Some(urlOutput) =>
|
|
711
|
-
let policyJson =
|
|
712
|
-
urlOutput->Pulumi.Output.apply(url => {
|
|
713
|
-
let arn = switch url->String.split("/") {
|
|
714
|
-
| [_, _, host, acct, qn] =>
|
|
715
|
-
let region = host->String.split(".")->Array.get(1)->Option.getOr("eu-west-1")
|
|
716
|
-
`arn:aws:sqs:${region}:${acct}:${qn}`
|
|
717
|
-
| _ => url
|
|
718
|
-
}
|
|
719
|
-
PulumiAws.PolicyDocument.make(
|
|
720
|
-
~id=`${name}AdminDcbSendPolicy`,
|
|
721
|
-
~statements=[
|
|
722
|
-
{
|
|
723
|
-
sid: "AllowAdminDispatchRecordApiFragmentPush",
|
|
724
|
-
effect: Allow,
|
|
725
|
-
actions: Actions(["sqs:SendMessage"]),
|
|
726
|
-
resources: Resource(arn),
|
|
727
|
-
},
|
|
728
|
-
],
|
|
729
|
-
)->PulumiAws.PolicyDocument.toJsonString
|
|
730
|
-
})
|
|
731
|
-
let _ = PulumiAws.IAM.RolePolicy.make(
|
|
732
|
-
~name=`${name}-adminDcbSend`,
|
|
733
|
-
~args={
|
|
734
|
-
policy: policyJson->Pulumi.Output.asInput,
|
|
735
|
-
role: runtime.parts.lambdaRole.id->Pulumi.Output.asInput,
|
|
736
|
-
},
|
|
737
|
-
)
|
|
738
|
-
| None => ()
|
|
739
|
-
}
|
|
740
601
|
}
|
|
741
602
|
|
|
742
603
|
// Plugin EC sqs:SendMessage grants on the aggregate / StateChangeSlice
|
|
@@ -25,16 +25,11 @@ let configRef = {
|
|
|
25
25
|
contents: {
|
|
26
26
|
eventTopicArn: undefined,
|
|
27
27
|
pluginReadModelTableName: undefined,
|
|
28
|
-
pluginSchemaPersistenceTableName: undefined,
|
|
29
28
|
schedulerRoleArn: undefined,
|
|
30
29
|
schedulerQueueArn: undefined,
|
|
31
30
|
schedulerQueueName: undefined,
|
|
32
31
|
appSyncApiId: undefined,
|
|
33
|
-
clonerEnabled: false
|
|
34
|
-
platformApiId: undefined,
|
|
35
|
-
apiFragmentRegistryTableName: undefined,
|
|
36
|
-
adminDcbCmdTopicUrl: undefined,
|
|
37
|
-
splitApi: false
|
|
32
|
+
clonerEnabled: false
|
|
38
33
|
}
|
|
39
34
|
};
|
|
40
35
|
|
|
@@ -108,22 +103,16 @@ function setStateChangesConfig(sync, async, param) {
|
|
|
108
103
|
});
|
|
109
104
|
}
|
|
110
105
|
|
|
111
|
-
function registerConfig(eventTopicArn, pluginReadModelTableName,
|
|
106
|
+
function registerConfig(eventTopicArn, pluginReadModelTableName, schedulerRoleArn, schedulerQueueArn, schedulerQueueName, appSyncApiId, clonerEnabledOpt, param) {
|
|
112
107
|
let clonerEnabled = clonerEnabledOpt !== undefined ? clonerEnabledOpt : false;
|
|
113
|
-
let splitApi = splitApiOpt !== undefined ? splitApiOpt : false;
|
|
114
108
|
configRef.contents = {
|
|
115
109
|
eventTopicArn: eventTopicArn,
|
|
116
110
|
pluginReadModelTableName: pluginReadModelTableName,
|
|
117
|
-
pluginSchemaPersistenceTableName: pluginSchemaPersistenceTableName,
|
|
118
111
|
schedulerRoleArn: schedulerRoleArn,
|
|
119
112
|
schedulerQueueArn: schedulerQueueArn,
|
|
120
113
|
schedulerQueueName: schedulerQueueName,
|
|
121
114
|
appSyncApiId: appSyncApiId,
|
|
122
|
-
clonerEnabled: clonerEnabled
|
|
123
|
-
platformApiId: platformApiId,
|
|
124
|
-
apiFragmentRegistryTableName: apiFragmentRegistryTableName,
|
|
125
|
-
adminDcbCmdTopicUrl: adminDcbCmdTopicUrl,
|
|
126
|
-
splitApi: splitApi
|
|
115
|
+
clonerEnabled: clonerEnabled
|
|
127
116
|
};
|
|
128
117
|
}
|
|
129
118
|
|
|
@@ -209,11 +198,7 @@ function Make(EventCollectorChannel) {
|
|
|
209
198
|
outputOrPlaceholder(config.schedulerQueueArn),
|
|
210
199
|
outputOrPlaceholder(config.schedulerQueueName),
|
|
211
200
|
outputOrPlaceholder(config.appSyncApiId),
|
|
212
|
-
epEventTopicArnsOutput
|
|
213
|
-
outputOrPlaceholder(config.pluginSchemaPersistenceTableName),
|
|
214
|
-
outputOrPlaceholder(config.platformApiId),
|
|
215
|
-
outputOrPlaceholder(config.apiFragmentRegistryTableName),
|
|
216
|
-
outputOrPlaceholder(config.adminDcbCmdTopicUrl)
|
|
201
|
+
epEventTopicArnsOutput
|
|
217
202
|
]).apply(values => {
|
|
218
203
|
let queueUrl = values[0];
|
|
219
204
|
let pluginEpCmdTopicUrl = values[1];
|
|
@@ -224,21 +209,12 @@ function Make(EventCollectorChannel) {
|
|
|
224
209
|
let schedQueueName = values[6];
|
|
225
210
|
let appSyncApiId = values[7];
|
|
226
211
|
let epEventTopicArns = values[8];
|
|
227
|
-
let schemaPersistenceTable = values[9];
|
|
228
|
-
let platformApiId = values[10];
|
|
229
|
-
let apiFragmentRegistryTable = values[11];
|
|
230
|
-
let adminDcbCmdTopicUrl = values[12];
|
|
231
212
|
let dict = {};
|
|
232
213
|
dict["queueUrl"] = queueUrl;
|
|
233
214
|
dict["pluginExtensionPointCmdTopicUrl"] = pluginEpCmdTopicUrl;
|
|
234
215
|
dict["eventTopicArn"] = topLevelEventTopicArn;
|
|
235
216
|
dict["pluginReadModelTableName"] = rmTable;
|
|
236
|
-
dict["pluginSchemaPersistenceTableName"] = schemaPersistenceTable;
|
|
237
217
|
dict["appSyncApiId"] = appSyncApiId;
|
|
238
|
-
dict["platformApiId"] = platformApiId;
|
|
239
|
-
dict["apiFragmentRegistryTableName"] = apiFragmentRegistryTable;
|
|
240
|
-
dict["adminDcbCmdTopicUrl"] = adminDcbCmdTopicUrl;
|
|
241
|
-
dict["splitApi"] = config.splitApi;
|
|
242
218
|
dict["clonerEnabled"] = config.clonerEnabled;
|
|
243
219
|
dict["schedulerRoleArn"] = schedRoleArn;
|
|
244
220
|
dict["schedulerQueueArn"] = schedQueueArn;
|
|
@@ -383,58 +359,6 @@ function Make(EventCollectorChannel) {
|
|
|
383
359
|
role: runtime.parts.lambdaRole.id
|
|
384
360
|
});
|
|
385
361
|
}
|
|
386
|
-
let schemaTableOutput = config.pluginSchemaPersistenceTableName;
|
|
387
|
-
if (schemaTableOutput !== undefined) {
|
|
388
|
-
let policyJson$1 = schemaTableOutput.apply(tableName => PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + `PluginSchemaScanPolicy`, [{
|
|
389
|
-
Sid: "AllowAdminScanPluginSchemaPersistence",
|
|
390
|
-
Effect: "Allow",
|
|
391
|
-
Action: ["dynamodb:Scan"],
|
|
392
|
-
Resource: "arn:aws:dynamodb:*:*:table/" + tableName
|
|
393
|
-
}])));
|
|
394
|
-
new (Aws.iam.RolePolicy)(name + `-pluginSchemaScan`, {
|
|
395
|
-
policy: policyJson$1,
|
|
396
|
-
role: runtime.parts.lambdaRole.id
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
let tableOutput = config.apiFragmentRegistryTableName;
|
|
400
|
-
if (tableOutput !== undefined) {
|
|
401
|
-
let policyJson$2 = tableOutput.apply(tableName => PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + `ApiFragmentsScanPolicy`, [{
|
|
402
|
-
Sid: "AllowAdminScanApiFragments",
|
|
403
|
-
Effect: "Allow",
|
|
404
|
-
Action: ["dynamodb:Scan"],
|
|
405
|
-
Resource: "arn:aws:dynamodb:*:*:table/" + tableName
|
|
406
|
-
}])));
|
|
407
|
-
new (Aws.iam.RolePolicy)(name + `-apiFragmentsScan`, {
|
|
408
|
-
policy: policyJson$2,
|
|
409
|
-
role: runtime.parts.lambdaRole.id
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
let urlOutput = config.adminDcbCmdTopicUrl;
|
|
413
|
-
if (urlOutput !== undefined) {
|
|
414
|
-
let policyJson$3 = urlOutput.apply(url => {
|
|
415
|
-
let match = url.split("/");
|
|
416
|
-
let arn;
|
|
417
|
-
if (match.length !== 5) {
|
|
418
|
-
arn = url;
|
|
419
|
-
} else {
|
|
420
|
-
let host = match[2];
|
|
421
|
-
let acct = match[3];
|
|
422
|
-
let qn = match[4];
|
|
423
|
-
let region = Stdlib_Option.getOr(host.split(".")[1], "eu-west-1");
|
|
424
|
-
arn = `arn:aws:sqs:` + region + `:` + acct + `:` + qn;
|
|
425
|
-
}
|
|
426
|
-
return PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + `AdminDcbSendPolicy`, [{
|
|
427
|
-
Sid: "AllowAdminDispatchRecordApiFragmentPush",
|
|
428
|
-
Effect: "Allow",
|
|
429
|
-
Action: ["sqs:SendMessage"],
|
|
430
|
-
Resource: arn
|
|
431
|
-
}]));
|
|
432
|
-
});
|
|
433
|
-
new (Aws.iam.RolePolicy)(name + `-adminDcbSend`, {
|
|
434
|
-
policy: policyJson$3,
|
|
435
|
-
role: runtime.parts.lambdaRole.id
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
362
|
}
|
|
439
363
|
let aggregateNameSet = {};
|
|
440
364
|
context.extensions.forEach(ext => {
|
|
@@ -21,4 +21,12 @@ describe("ApiSchemaPush SideEffect Source (runtime materialisation)", () => {
|
|
|
21
21
|
let tags = Reventless.DcbTag.extractAllVariantNames(ApiSchemaPush.Source.eventSchema)
|
|
22
22
|
expect(tags)->toContain("ApiSchemaComputed")
|
|
23
23
|
})
|
|
24
|
+
|
|
25
|
+
testSync("Source.Id is a live module — the reflective id round-trip does not crash", () => {
|
|
26
|
+
// SideEffectHandler_Callback reads `Source.Id.schema` on the id-bearing registry events.
|
|
27
|
+
// A bare `module Id` alias compiles to `Id: undefined` in the Source record, so any access
|
|
28
|
+
// (makeFromString/toString/schema) throws "reading 'schema'"/undefined on a regression.
|
|
29
|
+
let id = "registry"->ApiSchemaPush.Source.Id.makeFromString
|
|
30
|
+
expect(id->ApiSchemaPush.Source.Id.toString)->toBe("registry")
|
|
31
|
+
})
|
|
24
32
|
})
|
|
@@ -11,6 +11,10 @@ globalThis.describe("ApiSchemaPush SideEffect Source (runtime materialisation)",
|
|
|
11
11
|
let tags = DcbTag$Reventless.extractAllVariantNames(ApiSchemaPush$ReventlessAws.Source.eventSchema);
|
|
12
12
|
globalThis.expect(tags).toContain("ApiSchemaComputed");
|
|
13
13
|
});
|
|
14
|
+
globalThis.test("Source.Id is a live module — the reflective id round-trip does not crash", () => {
|
|
15
|
+
let id = ApiSchemaPush$ReventlessAws.Source.Id.makeFromString("registry");
|
|
16
|
+
globalThis.expect(ApiSchemaPush$ReventlessAws.Source.Id.toString(id)).toBe("registry");
|
|
17
|
+
});
|
|
14
18
|
});
|
|
15
19
|
|
|
16
20
|
/* Not a pure module */
|
package/tests/MCP_LambdaTest.res
CHANGED
|
@@ -7,8 +7,9 @@ describe("MCP_Lambda.generateAdminConfig", () => {
|
|
|
7
7
|
~serverVersion="1.0.0",
|
|
8
8
|
~cloner=false,
|
|
9
9
|
)
|
|
10
|
-
// Admin has Plugin_Activate, Plugin_Deactivate, Plugin_Retire
|
|
11
|
-
|
|
10
|
+
// Admin has Plugin_Activate, Plugin_Deactivate, Plugin_Retire +
|
|
11
|
+
// ApiFragmentRegistry_Register/DeregisterApiFragment mutations
|
|
12
|
+
expect(config.tools)->toHaveLength(5)
|
|
12
13
|
})
|
|
13
14
|
|
|
14
15
|
testSync("produces additional clone tool when cloner=true", () => {
|
|
@@ -17,8 +18,9 @@ describe("MCP_Lambda.generateAdminConfig", () => {
|
|
|
17
18
|
~serverVersion="1.0.0",
|
|
18
19
|
~cloner=true,
|
|
19
20
|
)
|
|
20
|
-
// Plugin_Activate, Plugin_Deactivate, Plugin_Retire,
|
|
21
|
-
|
|
21
|
+
// Plugin_Activate, Plugin_Deactivate, Plugin_Retire,
|
|
22
|
+
// ApiFragmentRegistry_Register/DeregisterApiFragment, Clone
|
|
23
|
+
expect(config.tools)->toHaveLength(6)
|
|
22
24
|
})
|
|
23
25
|
|
|
24
26
|
testSync("produces resources from admin query entries", () => {
|
|
@@ -6,11 +6,11 @@ import * as MCP_Lambda$ReventlessAws from "../src/adapter/Mcp/MCP_Lambda.res.mjs
|
|
|
6
6
|
globalThis.describe("MCP_Lambda.generateAdminConfig", () => {
|
|
7
7
|
globalThis.test("produces tools from admin mutation entries (no cloner)", () => {
|
|
8
8
|
let config = MCP_Lambda$ReventlessAws.generateAdminConfig("test", "1.0.0", false, undefined, undefined);
|
|
9
|
-
globalThis.expect(config.tools).toHaveLength(
|
|
9
|
+
globalThis.expect(config.tools).toHaveLength(5);
|
|
10
10
|
});
|
|
11
11
|
globalThis.test("produces additional clone tool when cloner=true", () => {
|
|
12
12
|
let config = MCP_Lambda$ReventlessAws.generateAdminConfig("test", "1.0.0", true, undefined, undefined);
|
|
13
|
-
globalThis.expect(config.tools).toHaveLength(
|
|
13
|
+
globalThis.expect(config.tools).toHaveLength(6);
|
|
14
14
|
});
|
|
15
15
|
globalThis.test("produces resources from admin query entries", () => {
|
|
16
16
|
let config = MCP_Lambda$ReventlessAws.generateAdminConfig("test", "1.0.0", undefined, undefined, undefined);
|