@reventlessdev/reventless-aws 3.0.0-alpha.172 → 3.0.0-alpha.173

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/package.json +6 -6
  3. package/src/Platform.res.mjs +5 -5
  4. package/src/adapter/Counter/CounterHandler_DynamoDbStream.res.mjs +1 -1
  5. package/src/adapter/DcbEventLog/DcbEventLogStorage_Postgres_Runtime.res +26 -0
  6. package/src/adapter/DcbEventLog/DcbEventLogStorage_Postgres_Runtime.res.mjs +15 -0
  7. package/src/adapter/EventLog/EventLogStorage_Postgres_Runtime.res +23 -0
  8. package/src/adapter/EventLog/EventLogStorage_Postgres_Runtime.res.mjs +14 -0
  9. package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res +181 -0
  10. package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs +135 -0
  11. package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res +64 -0
  12. package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res.mjs +36 -0
  13. package/src/adapter/Runtime/AggregateEntryPoint.mjs +27 -9
  14. package/src/adapter/Runtime/AggregateRuntime_Builder_Micro.res.mjs +3 -3
  15. package/src/adapter/Runtime/AggregateRuntime_Builder_Micro_Async.res.mjs +3 -3
  16. package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate.res.mjs +1 -1
  17. package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate_Async.res.mjs +1 -1
  18. package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res.mjs +1 -1
  19. package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res.mjs +1 -1
  20. package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res.mjs +1 -1
  21. package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +20 -6
  22. package/src/adapter/Runtime/EventCollectorRuntime_Builder_PerEventCollector.res.mjs +1 -1
  23. package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res.mjs +1 -1
  24. package/src/adapter/Runtime/ExtensionPointRuntime_Builder_PerExtensionPoint.res.mjs +1 -1
  25. package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.mjs +52 -0
  26. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +38 -0
  27. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +20 -2
  28. package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res.mjs +1 -1
  29. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +1 -1
  30. package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res.mjs +1 -1
  31. package/src/adapter/Runtime/TaskRuntime_Builder_PerBucket.res.mjs +1 -1
  32. package/src/components/Api/AppSync_Adapter.res +72 -8
  33. package/src/components/Api/AppSync_Adapter.res.mjs +45 -6
  34. package/src/plugin/runtime/PluginExtensionPointRuntime_Builder.res.mjs +1 -1
  35. package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +2 -2
  36. package/tests/AppSync_AdapterTest.res +140 -0
  37. package/tests/AppSync_AdapterTest.res.mjs +182 -6
  38. package/tests/PgChangeFeedRelay_RuntimeTest.res +44 -0
  39. package/tests/PgChangeFeedRelay_RuntimeTest.res.mjs +70 -0
@@ -173,7 +173,7 @@ function finish() {
173
173
  cmdTopicEnvVars["HANDLER_CONFIG"] = cmdTopicHandlerConfigOutput;
174
174
  let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs", packageDirs, undefined);
175
175
  let cmdTopicName = baseName + "CmdHandler";
176
- let cmdTopicRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(cmdTopicName, match.code, match.sourceCodeHash, cmdTopicEnvVars, Math.max(spec.commandTopicMemorySize, 1024), Math.max(spec.commandTopicTimeout, 30), undefined, undefined, undefined, undefined, aggregateOpts);
176
+ let cmdTopicRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(cmdTopicName, match.code, match.sourceCodeHash, cmdTopicEnvVars, Math.max(spec.commandTopicMemorySize, 1024), Math.max(spec.commandTopicTimeout, 30), undefined, undefined, undefined, undefined, undefined, aggregateOpts);
177
177
  spec.commandTopicConnects.forEach(connect => connect(cmdTopicRuntime));
178
178
  if (spec.commandGeneratorConnects.length !== 0) {
179
179
  let cmdGenHandlerConfigOutput = Pulumi.all([
@@ -185,7 +185,7 @@ function finish() {
185
185
  cmdGenEnvVars["HANDLER_CONFIG"] = cmdGenHandlerConfigOutput;
186
186
  let match$1 = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs", packageDirs, undefined);
187
187
  let cmdGenName = baseName + "CmdGen";
188
- let cmdGenRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(cmdGenName, match$1.code, match$1.sourceCodeHash, cmdGenEnvVars, Math.max(spec.commandGeneratorMemorySize, 1024), Math.max(spec.commandGeneratorTimeout, 30), undefined, undefined, undefined, undefined, aggregateOpts);
188
+ let cmdGenRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(cmdGenName, match$1.code, match$1.sourceCodeHash, cmdGenEnvVars, Math.max(spec.commandGeneratorMemorySize, 1024), Math.max(spec.commandGeneratorTimeout, 30), undefined, undefined, undefined, undefined, undefined, aggregateOpts);
189
189
  spec.commandGeneratorConnects.forEach(connect => connect(cmdGenRuntime));
190
190
  }
191
191
  let match$2 = spec.eventCollectorChannelSpec;
@@ -208,7 +208,7 @@ function finish() {
208
208
  evtMapperPackageDirs[mappingsPkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(mappingsPkg);
209
209
  let match$4 = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/EventMapperEntryPoint.mjs", evtMapperPackageDirs, undefined);
210
210
  let evtMapperName = baseName + "EventMapper";
211
- let evtMapperRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(evtMapperName, match$4.code, match$4.sourceCodeHash, evtMapperEnvVars, Math.max(spec.eventCollectorMemorySize, 2048), Math.max(spec.eventCollectorTimeout, 180), undefined, undefined, undefined, undefined, aggregateOpts);
211
+ let evtMapperRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(evtMapperName, match$4.code, match$4.sourceCodeHash, evtMapperEnvVars, Math.max(spec.eventCollectorMemorySize, 2048), Math.max(spec.eventCollectorTimeout, 180), undefined, undefined, undefined, undefined, undefined, aggregateOpts);
212
212
  EventCollectorChannel_DynamoDbStream$ReventlessAws.connect(evtMapperName, [match$2], evtMapperRuntime, aggregateOpts);
213
213
  });
214
214
  }
@@ -173,7 +173,7 @@ function finish() {
173
173
  cmdTopicEnvVars["HANDLER_CONFIG"] = cmdTopicHandlerConfigOutput;
174
174
  let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs", packageDirs, undefined);
175
175
  let cmdTopicName = baseName + "CmdHandler";
176
- let cmdTopicRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(cmdTopicName, match.code, match.sourceCodeHash, cmdTopicEnvVars, Math.max(spec.commandTopicMemorySize, 1024), Math.max(spec.commandTopicTimeout, 30), undefined, undefined, undefined, undefined, aggregateOpts);
176
+ let cmdTopicRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(cmdTopicName, match.code, match.sourceCodeHash, cmdTopicEnvVars, Math.max(spec.commandTopicMemorySize, 1024), Math.max(spec.commandTopicTimeout, 30), undefined, undefined, undefined, undefined, undefined, aggregateOpts);
177
177
  spec.commandTopicConnects.forEach(connect => connect(cmdTopicRuntime));
178
178
  if (spec.commandGeneratorConnects.length !== 0) {
179
179
  let cmdGenHandlerConfigOutput = Pulumi.all([
@@ -186,7 +186,7 @@ function finish() {
186
186
  cmdGenEnvVars["DISPATCH_MODE"] = "async";
187
187
  let match$1 = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs", packageDirs, undefined);
188
188
  let cmdGenName = baseName + "CmdGen";
189
- let cmdGenRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(cmdGenName, match$1.code, match$1.sourceCodeHash, cmdGenEnvVars, Math.max(spec.commandGeneratorMemorySize, 1024), Math.max(spec.commandGeneratorTimeout, 30), undefined, undefined, undefined, undefined, aggregateOpts);
189
+ let cmdGenRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(cmdGenName, match$1.code, match$1.sourceCodeHash, cmdGenEnvVars, Math.max(spec.commandGeneratorMemorySize, 1024), Math.max(spec.commandGeneratorTimeout, 30), undefined, undefined, undefined, undefined, undefined, aggregateOpts);
190
190
  spec.commandGeneratorConnects.forEach(connect => connect(cmdGenRuntime));
191
191
  }
192
192
  let match$2 = spec.eventCollectorChannelSpec;
@@ -209,7 +209,7 @@ function finish() {
209
209
  evtMapperPackageDirs[mappingsPkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(mappingsPkg);
210
210
  let match$4 = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/EventMapperEntryPoint.mjs", evtMapperPackageDirs, undefined);
211
211
  let evtMapperName = baseName + "EventMapper";
212
- let evtMapperRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(evtMapperName, match$4.code, match$4.sourceCodeHash, evtMapperEnvVars, Math.max(spec.eventCollectorMemorySize, 2048), Math.max(spec.eventCollectorTimeout, 180), undefined, undefined, undefined, undefined, aggregateOpts);
212
+ let evtMapperRuntime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(evtMapperName, match$4.code, match$4.sourceCodeHash, evtMapperEnvVars, Math.max(spec.eventCollectorMemorySize, 2048), Math.max(spec.eventCollectorTimeout, 180), undefined, undefined, undefined, undefined, undefined, aggregateOpts);
213
213
  EventCollectorChannel_DynamoDbStream$ReventlessAws.connect(evtMapperName, [match$2], evtMapperRuntime, aggregateOpts);
214
214
  });
215
215
  }
@@ -152,7 +152,7 @@ function finish() {
152
152
  packageDirs[specPkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(specPkg);
153
153
  packageDirs[behaviorPkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(behaviorPkg);
154
154
  let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs", packageDirs, undefined);
155
- let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(lambdaName, match.code, match.sourceCodeHash, envVars, spec.memorySize, spec.timeout, undefined, undefined, undefined, undefined, aggregateOpts);
155
+ let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(lambdaName, match.code, match.sourceCodeHash, envVars, spec.memorySize, spec.timeout, undefined, undefined, undefined, undefined, undefined, aggregateOpts);
156
156
  spec.connects.forEach(connect => connect(runtime));
157
157
  let channelSpecs = Stdlib_Option.mapOr(spec.eventCollectorChannelSpec, [], cs => [cs]);
158
158
  EventCollectorChannel_DynamoDbStream$ReventlessAws.connect(name, channelSpecs, runtime, aggregateOpts);
@@ -152,7 +152,7 @@ function finish() {
152
152
  packageDirs[specPkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(specPkg);
153
153
  packageDirs[behaviorPkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(behaviorPkg);
154
154
  let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs", packageDirs, undefined);
155
- let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, spec.memorySize, spec.timeout, undefined, undefined, undefined, undefined, aggregateOpts);
155
+ let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, spec.memorySize, spec.timeout, undefined, undefined, undefined, undefined, undefined, aggregateOpts);
156
156
  spec.connects.forEach(connect => connect(runtime));
157
157
  let channelSpecs = Stdlib_Option.mapOr(spec.eventCollectorChannelSpec, [], cs => [cs]);
158
158
  EventCollectorChannel_DynamoDbStream$ReventlessAws.connect(name, channelSpecs, runtime, aggregateOpts);
@@ -187,7 +187,7 @@ function finish() {
187
187
  }));
188
188
  let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs", packageDirs, undefined);
189
189
  Stdlib_Option.forEach(cfg.sqsBatchSize, CommandTopicChannel_SQS_Sync$ReventlessAws.setBatchSize);
190
- let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllAggregatesCmdHandler", match.code, match.sourceCodeHash, envVars, cfg.memorySize, cfg.timeout, cfg.reservedConcurrency, cfg.ephemeralStorageMb, cfg.logRetentionDays, undefined, opts);
190
+ let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllAggregatesCmdHandler", match.code, match.sourceCodeHash, envVars, cfg.memorySize, cfg.timeout, cfg.reservedConcurrency, cfg.ephemeralStorageMb, cfg.logRetentionDays, undefined, undefined, opts);
191
191
  let tableName$1 = pluginRmTableName.contents;
192
192
  if (tableName$1 !== undefined) {
193
193
  Pulumi.all([
@@ -174,7 +174,7 @@ function finish() {
174
174
  }));
175
175
  let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs", packageDirs, undefined);
176
176
  Stdlib_Option.forEach(cfg.sqsBatchSize, CommandTopicChannel_SQS_Async$ReventlessAws.setBatchSize);
177
- let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllAggregatesAsyncCmdHandler", match.code, match.sourceCodeHash, envVars, cfg.memorySize, cfg.timeout, cfg.reservedConcurrency, cfg.ephemeralStorageMb, cfg.logRetentionDays, undefined, opts);
177
+ let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllAggregatesAsyncCmdHandler", match.code, match.sourceCodeHash, envVars, cfg.memorySize, cfg.timeout, cfg.reservedConcurrency, cfg.ephemeralStorageMb, cfg.logRetentionDays, undefined, undefined, opts);
178
178
  specs.forEach(param => {
179
179
  param.connects.forEach(connect => connect(runtime));
180
180
  });
@@ -116,7 +116,7 @@ function finish() {
116
116
  let envVars = {};
117
117
  envVars["HANDLER_CONFIG"] = handlerConfigOutput;
118
118
  let match$1 = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AutomationSliceEntryPoint.mjs", packageDirs, undefined);
119
- let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllAutomationSlices", match$1.code, match$1.sourceCodeHash, envVars, match[0], match[1], undefined, undefined, undefined, undefined, opts);
119
+ let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllAutomationSlices", match$1.code, match$1.sourceCodeHash, envVars, match[0], match[1], undefined, undefined, undefined, undefined, undefined, opts);
120
120
  let channelSpecs = storedSpecs.map(param => param.channelSpec);
121
121
  EventCollectorChannel_DynamoDbStream$ReventlessAws.connect("AllAutomationSlices", channelSpecs, runtime, opts);
122
122
  } else {
@@ -22,6 +22,7 @@ import { makeGenerateCommand } from "@reventlessdev/reventless-core/src/componen
22
22
  import { commandOutcomeToJson, runInlineAndCollect } from "@reventlessdev/reventless-core/src/components/CommandTopic/CommandTopic_Helpers.res.mjs";
23
23
  import { json as jsonSchema } from "sury/src/S.res.mjs";
24
24
  import { read, append, readStream } from "@reventlessdev/reventless-aws/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res.mjs";
25
+ import { opsFor as pgDcbEventLogOpsFor } from "@reventlessdev/reventless-aws/src/adapter/DcbEventLog/DcbEventLogStorage_Postgres_Runtime.res.mjs";
25
26
  import { handleQueueEvent, publishJsons as sqsPublishJsons } from "@reventlessdev/reventless-aws/src/adapter/CommandTopic/CommandTopicChannel_SQS_Runtime.res.mjs";
26
27
 
27
28
  const dynamicImport = (specifier) => import('/var/task/node_modules/' + specifier);
@@ -90,12 +91,25 @@ function getIdStringSchema() {
90
91
  // can drive the real `cmdGenHandler` end-to-end against DynamoDB Local.
91
92
  export async function buildHandlersForConfig(config, opts = {}) {
92
93
  const loadModule = opts.loadModule ?? dynamicImport;
93
- const resolvedTable = { name: config.dcbEventLogTableName };
94
- const rawStorageOps = {
95
- read: read(resolvedTable),
96
- append: append(resolvedTable),
97
- readStream: readStream(resolvedTable),
98
- };
94
+ // `pgConnection`, when present in HANDLER_CONFIG, selects the Postgres DCB
95
+ // runtime (dcbEventLogTableName doubles as the `dcb_event.log_name`); absence
96
+ // keeps the DynamoDB path byte-identical. NB: this swaps only the *storage*
97
+ // ops — event propagation (within-plugin projections + cross-plugin SNS) is
98
+ // stream-driven on AWS and is handled separately by the change-feed relay
99
+ // (see docs/plans/aws-postgres-change-feed-bridge.md), not by this Lambda.
100
+ const rawStorageOps = config.pgConnection
101
+ ? (() => {
102
+ const pgOps = pgDcbEventLogOpsFor(config.pgConnection, config.dcbEventLogTableName);
103
+ return { read: pgOps.read, append: pgOps.append, readStream: pgOps.readStream };
104
+ })()
105
+ : (() => {
106
+ const resolvedTable = { name: config.dcbEventLogTableName };
107
+ return {
108
+ read: read(resolvedTable),
109
+ append: append(resolvedTable),
110
+ readStream: readStream(resolvedTable),
111
+ };
112
+ })();
99
113
 
100
114
  const handlersByType = {};
101
115
  const sharedDcbEventLogOps = dcbEventLogOperationsMake({
@@ -61,7 +61,7 @@ function forEventCollector(param, eventTopics, resources, memorySizeOpt, timeout
61
61
  packageDirs[specPkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(specPkg);
62
62
  packageDirs[mappingsPkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(mappingsPkg);
63
63
  let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/ReadModelEntryPoint.mjs", packageDirs, undefined);
64
- let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, opts);
64
+ let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, undefined, opts);
65
65
  EventCollectorChannel_DynamoDbStream$ReventlessAws.connect(name, [{
66
66
  channel: channel,
67
67
  eventTopics: eventTopics,
@@ -108,7 +108,7 @@ function finish() {
108
108
  let envVars = {};
109
109
  envVars["HANDLER_CONFIG"] = handlerConfigOutput;
110
110
  let match$1 = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/ReadModelEntryPoint.mjs", packageDirs, undefined);
111
- let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllReadModels", match$1.code, match$1.sourceCodeHash, envVars, match[0], match[1], undefined, undefined, undefined, undefined, opts);
111
+ let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllReadModels", match$1.code, match$1.sourceCodeHash, envVars, match[0], match[1], undefined, undefined, undefined, undefined, undefined, opts);
112
112
  let channelSpecs = storedSpecs.map(param => param.channelSpec);
113
113
  EventCollectorChannel_DynamoDbStream$ReventlessAws.connect("AllReadModels", channelSpecs, runtime, opts);
114
114
  } else {
@@ -39,7 +39,7 @@ function forCommandTopic(param, connect, memorySizeOpt, timeoutOpt, specModuleUr
39
39
  packageDirs[specPkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(specPkg);
40
40
  packageDirs[mappingsPkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(mappingsPkg);
41
41
  let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/ExtensionPointEntryPoint.mjs", packageDirs, undefined);
42
- return connect(RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, opts));
42
+ return connect(RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, undefined, opts));
43
43
  }
44
44
 
45
45
  function finish() {
@@ -0,0 +1,52 @@
1
+ // PgChangeFeedRelay Lambda entry point (scheduled poll — B2.3).
2
+ //
3
+ // Triggered on a schedule (EventBridge rate rule). Each invocation drains every
4
+ // Postgres DCB log listed in HANDLER_CONFIG from its checkpoint and relays the
5
+ // events, transformed into the {id, meta, event} shape, onto the plugin
6
+ // EventCollector SQS queue — which then fans out to projections, aggregate
7
+ // command topics, and the cross-plugin SNS EventTopic.
8
+ // See docs/plans/aws-postgres-change-feed-bridge.md.
9
+ //
10
+ // HANDLER_CONFIG shape:
11
+ // { "logs": [ { "pgConnection": {host,port,database,username,secretArn},
12
+ // "logName": string, // dcb_event.log_name
13
+ // "subscriber": string, // dcb_subscription checkpoint key
14
+ // "targetQueueUrl": string, // plugin EventCollector SQS URL
15
+ // "partitionTag"?: <derivedPartitionTag> } ] }
16
+
17
+ import { relay } from "@reventlessdev/reventless-aws/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res.mjs";
18
+ import { sendMessage } from "@reventlessdev/reventless-aws/src/util/Util_SQS_Runtime.res.mjs";
19
+ import { makeQueueRef, log } from "./HandlerFactoryHelpers.mjs";
20
+
21
+ // Standard (non-FIFO) EventCollector queue — no message group id needed. The
22
+ // SQS handler parses each body straight back to JSON (Util_SQS_Runtime.parseSqsRecord).
23
+ function makeSendBatch(targetQueueUrl) {
24
+ const queue = makeQueueRef(targetQueueUrl);
25
+ return (jsons) =>
26
+ Promise.all(jsons.map((json) => sendMessage(queue, undefined, JSON.stringify(json)))).then(
27
+ () => {}
28
+ );
29
+ }
30
+
31
+ export async function handler(_event, _context) {
32
+ const configStr = process.env["HANDLER_CONFIG"] || '{"logs":[]}';
33
+ const config = JSON.parse(configStr);
34
+ const logs = config.logs || [];
35
+
36
+ await Promise.all(
37
+ logs.map(async (l) => {
38
+ const sendBatch = makeSendBatch(l.targetQueueUrl);
39
+ try {
40
+ const count = await relay(l.pgConnection, l.logName, l.subscriber, l.partitionTag, sendBatch);
41
+ log.debug("relayed " + count + " event(s) for " + l.logName, { comp: "PgChangeFeedRelay" });
42
+ } catch (err) {
43
+ // Log and continue — a failed log this tick is retried next tick from its
44
+ // unchanged checkpoint (at-least-once; projections are idempotent).
45
+ log.warn("relay failed for " + l.logName + ": " + (err?.message ?? String(err)), {
46
+ comp: "PgChangeFeedRelay",
47
+ });
48
+ }
49
+ })
50
+ );
51
+ return "";
52
+ }
@@ -78,6 +78,11 @@ let makeFromCodeAsset: (
78
78
  Lambda's log group (command-handler Lambdas only). Requires a managed log
79
79
  group, so it only takes effect when `~logRetentionDays` is also set. */
80
80
  ~dcbMetrics: bool=?,
81
+ /** Place the Lambda in a VPC (needed to reach RDS/managed Postgres). When set,
82
+ the execution role also gets the EC2 network-interface permissions AWS
83
+ requires for VPC Lambdas. Build it from `PgConnection.{securityGroupId,
84
+ subnetIds}`. */
85
+ ~vpcConfig: Pulumi.Input.t<PulumiAws.Lambda.Function.vpcConfig>=?,
81
86
  ~opts: Pulumi.ComponentResource.options=?,
82
87
  ) => ReventlessCore.Runtime.environment<parts> = (
83
88
  ~name,
@@ -90,6 +95,7 @@ let makeFromCodeAsset: (
90
95
  ~ephemeralStorageMb=?,
91
96
  ~logRetentionDays=?,
92
97
  ~dcbMetrics=false,
98
+ ~vpcConfig=?,
93
99
  ~opts=?,
94
100
  ) => {
95
101
  open PulumiAws
@@ -102,6 +108,37 @@ let makeFromCodeAsset: (
102
108
  ~opts?,
103
109
  )
104
110
 
111
+ // VPC Lambdas must be able to manage ENIs in the target subnets. Attach the
112
+ // standard EC2 network-interface permissions (resource "*", as ENIs have no
113
+ // predictable ARN) whenever the function is VPC-placed.
114
+ vpcConfig->Option.forEach(_ => {
115
+ let _ = PulumiAws.IAM.RolePolicy.make(
116
+ ~name=`${name}VpcAccess`,
117
+ ~args={
118
+ policy: PolicyDocument.make(
119
+ ~id=`${name}VpcAccessPolicy`,
120
+ ~statements=[
121
+ {
122
+ sid: "AllowVpcEni",
123
+ effect: Allow,
124
+ actions: Actions([
125
+ "ec2:CreateNetworkInterface",
126
+ "ec2:DescribeNetworkInterfaces",
127
+ "ec2:DeleteNetworkInterface",
128
+ "ec2:AssignPrivateIpAddresses",
129
+ "ec2:UnassignPrivateIpAddresses",
130
+ ]),
131
+ resources: Resource("*"),
132
+ },
133
+ ],
134
+ )
135
+ ->PolicyDocument.toJsonString
136
+ ->Pulumi.Input.make,
137
+ role: lambdaRole.id->Pulumi.Output.asInput,
138
+ },
139
+ )
140
+ })
141
+
105
142
  // Create additional IAM policies registered by consumers.
106
143
  additionalIamPolicies->Array.forEach(({suffix, actions, resourceArn}) => {
107
144
  let _ = resourceArn->Pulumi.Output.apply(arn => {
@@ -160,6 +197,7 @@ let makeFromCodeAsset: (
160
197
  ),
161
198
  layers,
162
199
  tags,
200
+ vpcConfig: ?vpcConfig,
163
201
  environment: ({Lambda.Function.variables: variables}: Lambda.Function.functionEnvironment)
164
202
  ->Pulumi.Input.make,
165
203
  },
@@ -41,13 +41,30 @@ function make(name, handler, memorySizeOpt, timeoutOpt, opts) {
41
41
  };
42
42
  }
43
43
 
44
- function makeFromCodeAsset(name, code, sourceCodeHash, envVarsOpt, memorySizeOpt, timeoutOpt, reservedConcurrency, ephemeralStorageMb, logRetentionDays, dcbMetricsOpt, opts) {
44
+ function makeFromCodeAsset(name, code, sourceCodeHash, envVarsOpt, memorySizeOpt, timeoutOpt, reservedConcurrency, ephemeralStorageMb, logRetentionDays, dcbMetricsOpt, vpcConfig, opts) {
45
45
  let envVars = envVarsOpt !== undefined ? envVarsOpt : ({});
46
46
  let memorySize = memorySizeOpt !== undefined ? memorySizeOpt : Runtime$ReventlessCore.CommandHandlerDefaults.memorySize;
47
47
  let timeout = timeoutOpt !== undefined ? timeoutOpt : Runtime$ReventlessCore.CommandHandlerDefaults.timeout;
48
48
  let dcbMetrics = dcbMetricsOpt !== undefined ? dcbMetricsOpt : false;
49
49
  let opts$1 = Stdlib_Option.map(opts, Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions);
50
50
  let lambdaRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name, Pulumi.output(AWS$ReventlessAws.Lambda.principal), opts$1);
51
+ Stdlib_Option.forEach(vpcConfig, param => {
52
+ new (Aws.iam.RolePolicy)(name + `VpcAccess`, {
53
+ policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + `VpcAccessPolicy`, [{
54
+ Sid: "AllowVpcEni",
55
+ Effect: "Allow",
56
+ Action: [
57
+ "ec2:CreateNetworkInterface",
58
+ "ec2:DescribeNetworkInterfaces",
59
+ "ec2:DeleteNetworkInterface",
60
+ "ec2:AssignPrivateIpAddresses",
61
+ "ec2:UnassignPrivateIpAddresses"
62
+ ],
63
+ Resource: "*"
64
+ }])),
65
+ role: lambdaRole.id
66
+ });
67
+ });
51
68
  additionalIamPolicies.forEach(param => {
52
69
  let actions = param.actions;
53
70
  let suffix = param.suffix;
@@ -91,7 +108,8 @@ function makeFromCodeAsset(name, code, sourceCodeHash, envVarsOpt, memorySizeOpt
91
108
  reservedConcurrentExecutions: Stdlib_Option.map(reservedConcurrency, prim => prim),
92
109
  ephemeralStorage: Stdlib_Option.map(ephemeralStorageMb, mb => ({
93
110
  size: mb
94
- }))
111
+ })),
112
+ vpcConfig: vpcConfig
95
113
  }, opts$1 !== undefined ? Primitive_option.valFromOption(opts$1) : undefined);
96
114
  Stdlib_Option.forEach(logRetentionDays, days => {
97
115
  let logGroup = new (Aws.cloudwatch.LogGroup)(name + `LogGroup`, {
@@ -100,7 +100,7 @@ function finish() {
100
100
  let envVars = {};
101
101
  envVars["HANDLER_CONFIG"] = handlerConfigOutput;
102
102
  let match$1 = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/SideEffectEntryPoint.mjs", packageDirs, undefined);
103
- let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllSideEffectHandlers", match$1.code, match$1.sourceCodeHash, envVars, match[0], match[1], undefined, undefined, undefined, undefined, opts);
103
+ let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllSideEffectHandlers", match$1.code, match$1.sourceCodeHash, envVars, match[0], match[1], undefined, undefined, undefined, undefined, undefined, opts);
104
104
  let channelSpecs = storedSpecs.map(param => param.channelSpec);
105
105
  EventCollectorChannel_DynamoDbStream$ReventlessAws.connect("AllSideEffectHandlers", channelSpecs, runtime, opts);
106
106
  } else {
@@ -66,7 +66,7 @@ function forDcbCommandTopic(slicePaths, dcbTableName, pluginName, syncConfig, as
66
66
  packageDirs["@reventlessdev/reventless-core"] = Util_Bundle$ReventlessAws.resolvePackageRoot("@reventlessdev/reventless-core");
67
67
  let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs", packageDirs, undefined);
68
68
  Stdlib_Option.forEach(cfg.sqsBatchSize, CommandTopicChannel_SQS$ReventlessAws.setBatchSize);
69
- let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, cfg.memorySize, cfg.timeout, cfg.reservedConcurrency, cfg.ephemeralStorageMb, cfg.logRetentionDays, true, opts);
69
+ let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, match.code, match.sourceCodeHash, envVars, cfg.memorySize, cfg.timeout, cfg.reservedConcurrency, cfg.ephemeralStorageMb, cfg.logRetentionDays, true, undefined, opts);
70
70
  queue.arn.apply(queueArn => new (Aws.iam.RolePolicy)(name + `-sqsSend`, {
71
71
  policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + `-sqsSendPolicy`, [{
72
72
  Sid: "AllowSqsSend",
@@ -117,7 +117,7 @@ function buildLambda(parent, handlerOutputs, packageDirs, channelSpecs, memorySi
117
117
  packageDirs["@reventlessdev/reventless-aws"] = Util_Bundle$ReventlessAws.resolvePackageRoot("@reventlessdev/reventless-aws");
118
118
  packageDirs["@reventlessdev/reventless-core"] = Util_Bundle$ReventlessAws.resolvePackageRoot("@reventlessdev/reventless-core");
119
119
  let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/StateViewSliceEntryPoint.mjs", packageDirs, undefined);
120
- let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllStateViewSlices", match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, opts);
120
+ let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllStateViewSlices", match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, undefined, opts);
121
121
  EventCollectorChannel_DynamoDbStream$ReventlessAws.connect("AllStateViewSlices", channelSpecs, runtime, opts);
122
122
  }
123
123
 
@@ -29,7 +29,7 @@ function forBucketCallback(param, connect, memorySizeOpt, timeoutOpt, name, call
29
29
  let pkg = Util_Bundle$ReventlessAws.extractPackageName(callbackModulePath);
30
30
  packageDirs[pkg] = Util_Bundle$ReventlessAws.resolvePackageRoot(pkg);
31
31
  let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/TaskBucketEntryPoint.mjs", packageDirs, undefined);
32
- let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(fullName, match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, {
32
+ let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(fullName, match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, undefined, {
33
33
  parent: resource
34
34
  });
35
35
  if (schedulerConfig !== undefined) {
@@ -266,6 +266,21 @@ let getClient = () =>
266
266
  // reaching request is already authenticated). `AllowGroups([])` and
267
267
  // `DenyAll` emit a sentinel `__deny_all__` group that no Cognito user can
268
268
  // belong to — effectively blocking the field at the API layer.
269
+ //
270
+ // ── Dual-auth (Cognito + IAM) for deploy-time system callers ────────────────
271
+ // A field flagged `iamCallable` (via `mutationSchemaEntry`/`querySchemaEntry`)
272
+ // must be reachable by BOTH the console UI (Cognito) and a deploy-time system
273
+ // caller signing SigV4 with ambient AWS credentials (`Util_AppSync_Caller`,
274
+ // the AWS_IAM additional auth provider). `@aws_auth(...)` is the single-mode
275
+ // directive form and does not admit IAM on a multi-auth API. For these fields
276
+ // we emit the multi-auth form instead:
277
+ // @aws_cognito_user_pools(cognito_groups: [...]) @aws_iam
278
+ // The Cognito arm preserves the field's existing group gating (or, when the
279
+ // field carries no Cognito group restriction, a bare `@aws_cognito_user_pools`
280
+ // keeps it open to any authenticated Cognito user); the `@aws_iam` arm admits
281
+ // the system caller. IAM access must be constrained by the API resource policy
282
+ // and a least-privilege deploy-role policy — see
283
+ // `docs/guides/appsync-iam-system-caller.md`.
269
284
 
270
285
  let _permissionToCognitoGroups = (
271
286
  permission: Reventless.Authorization.permission,
@@ -282,6 +297,20 @@ let _formatGroupsDirective = (groups: array<string>): string => {
282
297
  `@aws_auth(cognito_groups: [${quoted}])`
283
298
  }
284
299
 
300
+ // Multi-auth directive for a field that must accept BOTH Cognito and IAM.
301
+ // `groups=Some([...])` preserves Cognito group gating; `groups=None` keeps the
302
+ // field open to any authenticated Cognito user. `@aws_iam` admits the
303
+ // deploy-time SigV4 system caller. See the dual-auth note above.
304
+ let _formatDualAuthDirective = (groups: option<array<string>>): string => {
305
+ let cognito = switch groups {
306
+ | Some(g) =>
307
+ let quoted = g->Array.map(x => `"${x}"`)->Array.join(", ")
308
+ `@aws_cognito_user_pools(cognito_groups: [${quoted}])`
309
+ | None => `@aws_cognito_user_pools`
310
+ }
311
+ `${cognito} @aws_iam`
312
+ }
313
+
285
314
  let injectAwsAuth = (
286
315
  fragment: Reventless.Plugin.apiSchemaFragment,
287
316
  ~mutationEntries: array<ReventlessInfra.Api.mutationSchemaEntry>,
@@ -292,8 +321,16 @@ let injectAwsAuth = (
292
321
  // Build authorization lookup from mutation entries: fieldName -> groups.
293
322
  // Spec-level `fieldPermissions` takes precedence over the legacy
294
323
  // `authorization.group` per-entry single-group form.
324
+ // Fields opted into deploy-time IAM invocation (dual-auth). Membership here
325
+ // switches a field from the single-mode `@aws_auth` form to the multi-auth
326
+ // `@aws_cognito_user_pools(...) @aws_iam` form.
327
+ let iamFields: Dict.t<bool> = Dict.make()
328
+
295
329
  let mutationAuthMap: Dict.t<array<string>> = Dict.make()
296
330
  mutationEntries->Array.forEach(entry => {
331
+ if entry.iamCallable->Option.getOr(false) {
332
+ entry.fieldNames->Array.forEach(fieldName => iamFields->Dict.set(fieldName, true))
333
+ }
297
334
  switch entry.authorization {
298
335
  | Some({group}) =>
299
336
  entry.fieldNames->Array.forEach(fieldName =>
@@ -319,6 +356,10 @@ let injectAwsAuth = (
319
356
  // Spec-level `permission` takes precedence over the legacy authorization.
320
357
  let queryAuthMap: Dict.t<array<string>> = Dict.make()
321
358
  queryEntries->Array.forEach(entry => {
359
+ if entry.iamCallable->Option.getOr(false) {
360
+ iamFields->Dict.set(entry.singleFieldName, true)
361
+ iamFields->Dict.set(entry.listFieldName, true)
362
+ }
322
363
  switch entry.authorization {
323
364
  | Some({group}) =>
324
365
  queryAuthMap->Dict.set(entry.singleFieldName, [group])
@@ -341,17 +382,27 @@ let injectAwsAuth = (
341
382
 
342
383
  let augmentedMutations = parts.mutations->Array.map(field => {
343
384
  let fieldName = ReventlessCore.GraphQL_Stitcher.extractLeadingName(field)
344
- switch mutationAuthMap->Dict.get(fieldName) {
345
- | Some(groups) => `${field}\n ${_formatGroupsDirective(groups)}`
346
- | None => field
385
+ let groups = mutationAuthMap->Dict.get(fieldName)
386
+ if iamFields->Dict.get(fieldName)->Option.getOr(false) {
387
+ `${field}\n ${_formatDualAuthDirective(groups)}`
388
+ } else {
389
+ switch groups {
390
+ | Some(groups) => `${field}\n ${_formatGroupsDirective(groups)}`
391
+ | None => field
392
+ }
347
393
  }
348
394
  })
349
395
 
350
396
  let augmentedQueries = parts.queries->Array.map(field => {
351
397
  let fieldName = ReventlessCore.GraphQL_Stitcher.extractLeadingName(field)
352
- switch queryAuthMap->Dict.get(fieldName) {
353
- | Some(groups) => `${field} ${_formatGroupsDirective(groups)}`
354
- | None => field
398
+ let groups = queryAuthMap->Dict.get(fieldName)
399
+ if iamFields->Dict.get(fieldName)->Option.getOr(false) {
400
+ `${field} ${_formatDualAuthDirective(groups)}`
401
+ } else {
402
+ switch groups {
403
+ | Some(groups) => `${field} ${_formatGroupsDirective(groups)}`
404
+ | None => field
405
+ }
355
406
  }
356
407
  })
357
408
 
@@ -374,17 +425,30 @@ let injectAwsAuth = (
374
425
  // Injects @aws_auth with the given group on ALL mutation, query, and subscription
375
426
  // fields in a fragment. Used for the base fragment where all fields share the
376
427
  // same authorization group.
428
+ //
429
+ // `~iamFieldNames` opts the named mutation/query fields into deploy-time IAM
430
+ // (dual-auth): those fields emit `@aws_cognito_user_pools(cognito_groups:
431
+ // ["<group>"]) @aws_iam` instead of the single-mode `@aws_auth(...)`, keeping
432
+ // the same Cognito group gating while also admitting the SigV4 system caller.
433
+ // Subscriptions are never IAM-marked (the deploy caller does not subscribe).
377
434
  let injectAwsAuthAll = (
378
435
  fragment: Reventless.Plugin.apiSchemaFragment,
379
436
  ~group: string,
437
+ ~iamFieldNames: array<string>=[],
380
438
  ): Reventless.Plugin.apiSchemaFragment => {
381
439
  let parts = ReventlessCore.GraphQL_Stitcher.decode(fragment)
440
+ let isIam = (field: string): bool =>
441
+ iamFieldNames->Array.includes(ReventlessCore.GraphQL_Stitcher.extractLeadingName(field))
382
442
 
383
443
  let augmentedMutations = parts.mutations->Array.map(field =>
384
- `${field}\n @aws_auth(cognito_groups: ["${group}"])`
444
+ isIam(field)
445
+ ? `${field}\n ${_formatDualAuthDirective(Some([group]))}`
446
+ : `${field}\n @aws_auth(cognito_groups: ["${group}"])`
385
447
  )
386
448
  let augmentedQueries = parts.queries->Array.map(field =>
387
- `${field} @aws_auth(cognito_groups: ["${group}"])`
449
+ isIam(field)
450
+ ? `${field} ${_formatDualAuthDirective(Some([group]))}`
451
+ : `${field} @aws_auth(cognito_groups: ["${group}"])`
388
452
  )
389
453
  let augmentedSubscriptions = parts.subscriptions->Array.map(field =>
390
454
  `${field}\n @aws_auth(cognito_groups: ["${group}"])`
@@ -204,10 +204,27 @@ function _formatGroupsDirective(groups) {
204
204
  return `@aws_auth(cognito_groups: [` + quoted + `])`;
205
205
  }
206
206
 
207
+ function _formatDualAuthDirective(groups) {
208
+ let cognito;
209
+ if (groups !== undefined) {
210
+ let quoted = groups.map(x => `"` + x + `"`).join(", ");
211
+ cognito = `@aws_cognito_user_pools(cognito_groups: [` + quoted + `])`;
212
+ } else {
213
+ cognito = `@aws_cognito_user_pools`;
214
+ }
215
+ return cognito + ` @aws_iam`;
216
+ }
217
+
207
218
  function injectAwsAuth(fragment, mutationEntries, queryEntries) {
208
219
  let parts = GraphQL_Stitcher$ReventlessCore.decode(fragment);
220
+ let iamFields = {};
209
221
  let mutationAuthMap = {};
210
222
  mutationEntries.forEach(entry => {
223
+ if (Stdlib_Option.getOr(entry.iamCallable, false)) {
224
+ entry.fieldNames.forEach(fieldName => {
225
+ iamFields[fieldName] = true;
226
+ });
227
+ }
211
228
  let match = entry.authorization;
212
229
  if (match !== undefined) {
213
230
  let group = match.group;
@@ -232,6 +249,10 @@ function injectAwsAuth(fragment, mutationEntries, queryEntries) {
232
249
  });
233
250
  let queryAuthMap = {};
234
251
  queryEntries.forEach(entry => {
252
+ if (Stdlib_Option.getOr(entry.iamCallable, false)) {
253
+ iamFields[entry.singleFieldName] = true;
254
+ iamFields[entry.listFieldName] = true;
255
+ }
235
256
  let match = entry.authorization;
236
257
  if (match !== undefined) {
237
258
  let group = match.group;
@@ -255,7 +276,9 @@ function injectAwsAuth(fragment, mutationEntries, queryEntries) {
255
276
  let augmentedMutations = parts.mutations.map(field => {
256
277
  let fieldName = GraphQL_Stitcher$ReventlessCore.extractLeadingName(field);
257
278
  let groups = mutationAuthMap[fieldName];
258
- if (groups !== undefined) {
279
+ if (Stdlib_Option.getOr(iamFields[fieldName], false)) {
280
+ return field + `\n ` + _formatDualAuthDirective(groups);
281
+ } else if (groups !== undefined) {
259
282
  return field + `\n ` + _formatGroupsDirective(groups);
260
283
  } else {
261
284
  return field;
@@ -264,7 +287,9 @@ function injectAwsAuth(fragment, mutationEntries, queryEntries) {
264
287
  let augmentedQueries = parts.queries.map(field => {
265
288
  let fieldName = GraphQL_Stitcher$ReventlessCore.extractLeadingName(field);
266
289
  let groups = queryAuthMap[fieldName];
267
- if (groups !== undefined) {
290
+ if (Stdlib_Option.getOr(iamFields[fieldName], false)) {
291
+ return field + ` ` + _formatDualAuthDirective(groups);
292
+ } else if (groups !== undefined) {
268
293
  return field + ` ` + _formatGroupsDirective(groups);
269
294
  } else {
270
295
  return field;
@@ -294,10 +319,23 @@ function injectAwsAuth(fragment, mutationEntries, queryEntries) {
294
319
  };
295
320
  }
296
321
 
297
- function injectAwsAuthAll(fragment, group) {
322
+ function injectAwsAuthAll(fragment, group, iamFieldNamesOpt) {
323
+ let iamFieldNames = iamFieldNamesOpt !== undefined ? iamFieldNamesOpt : [];
298
324
  let parts = GraphQL_Stitcher$ReventlessCore.decode(fragment);
299
- let augmentedMutations = parts.mutations.map(field => field + `\n @aws_auth(cognito_groups: ["` + group + `"])`);
300
- let augmentedQueries = parts.queries.map(field => field + ` @aws_auth(cognito_groups: ["` + group + `"])`);
325
+ let augmentedMutations = parts.mutations.map(field => {
326
+ if (iamFieldNames.includes(GraphQL_Stitcher$ReventlessCore.extractLeadingName(field))) {
327
+ return field + `\n ` + _formatDualAuthDirective([group]);
328
+ } else {
329
+ return field + `\n @aws_auth(cognito_groups: ["` + group + `"])`;
330
+ }
331
+ });
332
+ let augmentedQueries = parts.queries.map(field => {
333
+ if (iamFieldNames.includes(GraphQL_Stitcher$ReventlessCore.extractLeadingName(field))) {
334
+ return field + ` ` + _formatDualAuthDirective([group]);
335
+ } else {
336
+ return field + ` @aws_auth(cognito_groups: ["` + group + `"])`;
337
+ }
338
+ });
301
339
  let augmentedSubscriptions = parts.subscriptions.map(field => field + `\n @aws_auth(cognito_groups: ["` + group + `"])`);
302
340
  let encoded = JSON.stringify(Object.fromEntries([
303
341
  [
@@ -359,7 +397,7 @@ function generateFragment(mutationEntries, queryEntries) {
359
397
  }
360
398
 
361
399
  function updateSchema(api, baseFragment, pluginFragments) {
362
- let augmentedBaseFragment = injectAwsAuthAll(baseFragment, "Admin");
400
+ let augmentedBaseFragment = injectAwsAuthAll(baseFragment, "Admin", undefined);
363
401
  let sdl = GraphQL_Stitcher$ReventlessCore.stitch(augmentedBaseFragment, pluginFragments);
364
402
  let resultPromise = {
365
403
  contents: Promise.resolve()
@@ -387,6 +425,7 @@ export {
387
425
  getClient,
388
426
  _permissionToCognitoGroups,
389
427
  _formatGroupsDirective,
428
+ _formatDualAuthDirective,
390
429
  injectAwsAuth,
391
430
  injectAwsAuthAll,
392
431
  makeApiResource,