@reventlessdev/reventless-aws 3.0.0-alpha.167
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 +1564 -0
- package/README.md +62 -0
- package/__mocks__/emptyModule.js +1 -0
- package/all-licenses.csv +565 -0
- package/docker-compose.dynamodb-local.yml +12 -0
- package/jest.setup.cjs +5 -0
- package/licenses-analysis.ods +0 -0
- package/package.json +73 -0
- package/rescript.json +39 -0
- package/reventless-licenses-analysis.xls +0 -0
- package/scripts/run-integration-tests.sh +43 -0
- package/src/Platform.res +1940 -0
- package/src/Platform.res.mjs +2281 -0
- package/src/Platform_Casts.res +106 -0
- package/src/Platform_Casts.res.mjs +13 -0
- package/src/Platform_Stack.res +153 -0
- package/src/Platform_Stack.res.mjs +121 -0
- package/src/Types.res +39 -0
- package/src/Types.res.mjs +21 -0
- package/src/adapter/AWS.res +96 -0
- package/src/adapter/AWS.res.mjs +186 -0
- package/src/adapter/AWS_Tags.res +16 -0
- package/src/adapter/AWS_Tags.res.mjs +48 -0
- package/src/adapter/Adapter_Helpers.res +19 -0
- package/src/adapter/Adapter_Helpers.res.mjs +37 -0
- package/src/adapter/Api/AppSync_EventsApi.res +172 -0
- package/src/adapter/Api/AppSync_EventsApi.res.mjs +96 -0
- package/src/adapter/Api/AppSync_Resolver_Native.res +93 -0
- package/src/adapter/Api/AppSync_Resolver_Native.res.mjs +67 -0
- package/src/adapter/Api/AppSync_Resolver_Retrying.res +653 -0
- package/src/adapter/Api/AppSync_Resolver_Retrying.res.mjs +462 -0
- package/src/adapter/Api/CommandSubscriptionResolvers_AppSync.res +29 -0
- package/src/adapter/Api/CommandSubscriptionResolvers_AppSync.res.mjs +15 -0
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +238 -0
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +179 -0
- package/src/adapter/Api/Platform_UIFragments_Lambda.res +229 -0
- package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +245 -0
- package/src/adapter/Auth/Auth_Cognito.res +209 -0
- package/src/adapter/Auth/Auth_Cognito.res.mjs +135 -0
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers.res +2 -0
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers.res.mjs +12 -0
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync.res +251 -0
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync.res.mjs +126 -0
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync_NoOp.res +20 -0
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync_NoOp.res.mjs +23 -0
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync_Runtime.res +1 -0
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync_Runtime.res.mjs +11 -0
- package/src/adapter/CommandGenerator/InboundTranslationResolvers_AppSync.res +95 -0
- package/src/adapter/CommandGenerator/InboundTranslationResolvers_AppSync.res.mjs +50 -0
- package/src/adapter/CommandTopic/CommandTopicChannel.res +6 -0
- package/src/adapter/CommandTopic/CommandTopicChannel.res.mjs +21 -0
- package/src/adapter/CommandTopic/CommandTopicChannel_Helpers.res +196 -0
- package/src/adapter/CommandTopic/CommandTopicChannel_Helpers.res.mjs +145 -0
- package/src/adapter/CommandTopic/CommandTopicChannel_SQS.res +91 -0
- package/src/adapter/CommandTopic/CommandTopicChannel_SQS.res.mjs +74 -0
- package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Async.res +69 -0
- package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Async.res.mjs +62 -0
- package/src/adapter/CommandTopic/CommandTopicChannel_SQS_FIFO.res +60 -0
- package/src/adapter/CommandTopic/CommandTopicChannel_SQS_FIFO.res.mjs +56 -0
- package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Runtime.res +73 -0
- package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Runtime.res.mjs +65 -0
- package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Sync.res +88 -0
- package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Sync.res.mjs +76 -0
- package/src/adapter/CommandTopic/CommandTopicRemoteChannel.res +1 -0
- package/src/adapter/CommandTopic/CommandTopicRemoteChannel.res.mjs +9 -0
- package/src/adapter/CommandTopic/CommandTopicRemoteChannel_SQS.res +7 -0
- package/src/adapter/CommandTopic/CommandTopicRemoteChannel_SQS.res.mjs +17 -0
- package/src/adapter/Counter/CounterHandler_DynamoDbStream.res +103 -0
- package/src/adapter/Counter/CounterHandler_DynamoDbStream.res.mjs +68 -0
- package/src/adapter/Counter/CounterHandler_DynamoDbStream_Runtime.res +107 -0
- package/src/adapter/Counter/CounterHandler_DynamoDbStream_Runtime.res.mjs +145 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage.res +1 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage.res.mjs +9 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb.res +84 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb.res.mjs +52 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res +1436 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res.mjs +1143 -0
- package/src/adapter/EventCollector/EventCollectorChannel.res +6 -0
- package/src/adapter/EventCollector/EventCollectorChannel.res.mjs +21 -0
- package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream.res +63 -0
- package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream.res.mjs +56 -0
- package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream_Runtime.res +20 -0
- package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream_Runtime.res.mjs +32 -0
- package/src/adapter/EventCollector/EventCollectorChannel_Helpers.res +266 -0
- package/src/adapter/EventCollector/EventCollectorChannel_Helpers.res.mjs +166 -0
- package/src/adapter/EventCollector/EventCollectorChannel_SQS.res +81 -0
- package/src/adapter/EventCollector/EventCollectorChannel_SQS.res.mjs +54 -0
- package/src/adapter/EventCollector/EventCollectorChannel_SQS_FIFO.res +59 -0
- package/src/adapter/EventCollector/EventCollectorChannel_SQS_FIFO.res.mjs +45 -0
- package/src/adapter/EventCollector/EventCollectorChannel_SQS_Runtime.res +91 -0
- package/src/adapter/EventCollector/EventCollectorChannel_SQS_Runtime.res.mjs +89 -0
- package/src/adapter/EventLog/EventLogStorage.res +3 -0
- package/src/adapter/EventLog/EventLogStorage.res.mjs +15 -0
- package/src/adapter/EventLog/EventLogStorage_DynamoDb.res +25 -0
- package/src/adapter/EventLog/EventLogStorage_DynamoDb.res.mjs +34 -0
- package/src/adapter/EventLog/EventLogStorage_DynamoDbStream.res +26 -0
- package/src/adapter/EventLog/EventLogStorage_DynamoDbStream.res.mjs +35 -0
- package/src/adapter/EventLog/EventLogStorage_DynamoDb_Runtime.res +121 -0
- package/src/adapter/EventLog/EventLogStorage_DynamoDb_Runtime.res.mjs +137 -0
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +265 -0
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +185 -0
- package/src/adapter/EventTopic/EventTopicPublisher.res +6 -0
- package/src/adapter/EventTopic/EventTopicPublisher.res.mjs +21 -0
- package/src/adapter/EventTopic/EventTopicPublisher_DynamoDbStream.res +25 -0
- package/src/adapter/EventTopic/EventTopicPublisher_DynamoDbStream.res.mjs +28 -0
- package/src/adapter/EventTopic/EventTopicPublisher_SNS.res +40 -0
- package/src/adapter/EventTopic/EventTopicPublisher_SNS.res.mjs +39 -0
- package/src/adapter/EventTopic/EventTopicPublisher_SNS_FIFO.res +39 -0
- package/src/adapter/EventTopic/EventTopicPublisher_SNS_FIFO.res.mjs +38 -0
- package/src/adapter/EventTopic/EventTopicPublisher_SNS_Runtime.res +5 -0
- package/src/adapter/EventTopic/EventTopicPublisher_SNS_Runtime.res.mjs +18 -0
- package/src/adapter/Mcp/MCP_Lambda.res +502 -0
- package/src/adapter/Mcp/MCP_Lambda.res.mjs +289 -0
- package/src/adapter/QueryDb/NodeResolver_AppSync.res +71 -0
- package/src/adapter/QueryDb/NodeResolver_AppSync.res.mjs +44 -0
- package/src/adapter/QueryDb/QueryDbResolvers.res +2 -0
- package/src/adapter/QueryDb/QueryDbResolvers.res.mjs +12 -0
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res +434 -0
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs +243 -0
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync_NoOp.res +21 -0
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync_NoOp.res.mjs +14 -0
- package/src/adapter/QueryDb/QueryDbResolvers_NoOp.res +21 -0
- package/src/adapter/QueryDb/QueryDbResolvers_NoOp.res.mjs +14 -0
- package/src/adapter/QueryDb/QueryDbStorage.res +5 -0
- package/src/adapter/QueryDb/QueryDbStorage.res.mjs +18 -0
- package/src/adapter/QueryDb/QueryDbStorage_DynamoDb.res +115 -0
- package/src/adapter/QueryDb/QueryDbStorage_DynamoDb.res.mjs +100 -0
- package/src/adapter/QueryDb/QueryDbStorage_DynamoDbStream.res +52 -0
- package/src/adapter/QueryDb/QueryDbStorage_DynamoDbStream.res.mjs +38 -0
- package/src/adapter/QueryDb/QueryDbStorage_DynamoDb_Runtime.res +271 -0
- package/src/adapter/QueryDb/QueryDbStorage_DynamoDb_Runtime.res.mjs +306 -0
- package/src/adapter/QueryDb/QueryInterceptor_Lambda.res +20 -0
- package/src/adapter/QueryDb/QueryInterceptor_Lambda.res.mjs +22 -0
- package/src/adapter/QueryEngine/QueryEngine.res +3 -0
- package/src/adapter/QueryEngine/QueryEngine.res.mjs +12 -0
- package/src/adapter/QueryEngine/QueryEngine_DynamoDb.res +184 -0
- package/src/adapter/QueryEngine/QueryEngine_DynamoDb.res.mjs +221 -0
- package/src/adapter/Runtime/AdminEventCollectorEntryPoint.mjs +879 -0
- package/src/adapter/Runtime/AggregateEntryPoint.mjs +313 -0
- package/src/adapter/Runtime/AggregateRuntime_Builder_Micro.res +327 -0
- package/src/adapter/Runtime/AggregateRuntime_Builder_Micro.res.mjs +239 -0
- package/src/adapter/Runtime/AggregateRuntime_Builder_Micro_Async.res +331 -0
- package/src/adapter/Runtime/AggregateRuntime_Builder_Micro_Async.res.mjs +240 -0
- package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate.res +247 -0
- package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate.res.mjs +185 -0
- package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate_Async.res +244 -0
- package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate_Async.res.mjs +185 -0
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res +347 -0
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res.mjs +244 -0
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res +285 -0
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res.mjs +212 -0
- package/src/adapter/Runtime/AutomationSliceEntryPoint.mjs +124 -0
- package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res +187 -0
- package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res.mjs +147 -0
- package/src/adapter/Runtime/CounterEntryPoint.mjs +91 -0
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +265 -0
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_PerEventCollector.res +122 -0
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_PerEventCollector.res.mjs +89 -0
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res +193 -0
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res.mjs +137 -0
- package/src/adapter/Runtime/EventMapperEntryPoint.mjs +97 -0
- package/src/adapter/Runtime/ExtensionPointEntryPoint.mjs +101 -0
- package/src/adapter/Runtime/ExtensionPointRuntime_Builder_PerExtensionPoint.res +83 -0
- package/src/adapter/Runtime/ExtensionPointRuntime_Builder_PerExtensionPoint.res.mjs +59 -0
- package/src/adapter/Runtime/HandlerFactoryHelpers.mjs +142 -0
- package/src/adapter/Runtime/HeartbeatEntryPoint.mjs +38 -0
- package/src/adapter/Runtime/PluginExtensionPointEntryPoint.mjs +137 -0
- package/src/adapter/Runtime/ReadModelEntryPoint.mjs +143 -0
- package/src/adapter/Runtime/RuntimeEnvironment.res +1 -0
- package/src/adapter/Runtime/RuntimeEnvironment.res.mjs +9 -0
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +254 -0
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +169 -0
- package/src/adapter/Runtime/SideEffectEntryPoint.mjs +81 -0
- package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res +178 -0
- package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res.mjs +129 -0
- package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res +166 -0
- package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +87 -0
- package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +136 -0
- package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res +344 -0
- package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res.mjs +242 -0
- package/src/adapter/Runtime/TaskBucketEntryPoint.mjs +150 -0
- package/src/adapter/Runtime/TaskRuntime_Builder_PerBucket.res +123 -0
- package/src/adapter/Runtime/TaskRuntime_Builder_PerBucket.res.mjs +74 -0
- package/src/adapter/ScheduledPublisher/ScheduledPublisher.res +2 -0
- package/src/adapter/ScheduledPublisher/ScheduledPublisher.res.mjs +12 -0
- package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents.res +54 -0
- package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents.res.mjs +41 -0
- package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents_Runtime.res +97 -0
- package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents_Runtime.res.mjs +85 -0
- package/src/adapter/StateTopic/StateTopic_AppSync.res +418 -0
- package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +295 -0
- package/src/adapter/Task/TaskBucket.res +1 -0
- package/src/adapter/Task/TaskBucket.res.mjs +9 -0
- package/src/adapter/Task/TaskBucket_S3.res +157 -0
- package/src/adapter/Task/TaskBucket_S3.res.mjs +108 -0
- package/src/adapter/Task/TaskBucket_S3_Runtime.res +11 -0
- package/src/adapter/Task/TaskBucket_S3_Runtime.res.mjs +15 -0
- package/src/components/Aggregate_Builder.res +1 -0
- package/src/components/Aggregate_Builder.res.mjs +25 -0
- package/src/components/Aggregate_Builder_Micro.res +63 -0
- package/src/components/Aggregate_Builder_Micro.res.mjs +80 -0
- package/src/components/Aggregate_Builder_NoResolver.res +61 -0
- package/src/components/Aggregate_Builder_NoResolver.res.mjs +80 -0
- package/src/components/Aggregate_Builder_PerAggregate.res +57 -0
- package/src/components/Aggregate_Builder_PerAggregate.res.mjs +80 -0
- package/src/components/Aggregate_Builder_Single.res +59 -0
- package/src/components/Aggregate_Builder_Single.res.mjs +80 -0
- package/src/components/Aggregate_Builder_Single_Async.res +59 -0
- package/src/components/Aggregate_Builder_Single_Async.res.mjs +80 -0
- package/src/components/Api/AppSync_Adapter.res +500 -0
- package/src/components/Api/AppSync_Adapter.res.mjs +396 -0
- package/src/components/AutomationSlice_Builder.res +52 -0
- package/src/components/AutomationSlice_Builder.res.mjs +72 -0
- package/src/components/AutomationSlice_Builder_Bundled.res.mjs +70 -0
- package/src/components/Counter_Builder.res +10 -0
- package/src/components/Counter_Builder.res.mjs +16 -0
- package/src/components/DataCleaner.res +170 -0
- package/src/components/DataCleaner.res.mjs +2 -0
- package/src/components/DcbEventLog_Builder.res +8 -0
- package/src/components/DcbEventLog_Builder.res.mjs +14 -0
- package/src/components/ExtensionPoint_Builder.res +16 -0
- package/src/components/ExtensionPoint_Builder.res.mjs +37 -0
- package/src/components/ForeignReadModel_Builder.res +19 -0
- package/src/components/ForeignReadModel_Builder.res.mjs +48 -0
- package/src/components/InboundTranslationSlice_Builder.res +25 -0
- package/src/components/InboundTranslationSlice_Builder.res.mjs +31 -0
- package/src/components/OutboundTranslationSlice_Builder.res +60 -0
- package/src/components/OutboundTranslationSlice_Builder.res.mjs +76 -0
- package/src/components/OutboundTranslationSlice_Builder_Bundled.res.mjs +75 -0
- package/src/components/Plugin.res +29 -0
- package/src/components/Plugin.res.mjs +63 -0
- package/src/components/ReadModel_Builder.res +1 -0
- package/src/components/ReadModel_Builder.res.mjs +19 -0
- package/src/components/ReadModel_Builder_NoResolver.res +55 -0
- package/src/components/ReadModel_Builder_NoResolver.res.mjs +64 -0
- package/src/components/ReadModel_Builder_NoResolver_Stream.res +60 -0
- package/src/components/ReadModel_Builder_NoResolver_Stream.res.mjs +64 -0
- package/src/components/ReadModel_Builder_PerReadModel.res +53 -0
- package/src/components/ReadModel_Builder_PerReadModel.res.mjs +66 -0
- package/src/components/ReadModel_Builder_Single.res +52 -0
- package/src/components/ReadModel_Builder_Single.res.mjs +66 -0
- package/src/components/ReadModel_Builder_Single_Stream.res +60 -0
- package/src/components/ReadModel_Builder_Single_Stream.res.mjs +66 -0
- package/src/components/Scheduler.res +1 -0
- package/src/components/Scheduler.res.mjs +13 -0
- package/src/components/SideEffectHandler.res +1 -0
- package/src/components/SideEffectHandler.res.mjs +19 -0
- package/src/components/SideEffectHandler_PerSideEffectHandler.res +13 -0
- package/src/components/SideEffectHandler_PerSideEffectHandler.res.mjs +52 -0
- package/src/components/SideEffectHandler_Single.res +54 -0
- package/src/components/SideEffectHandler_Single.res.mjs +60 -0
- package/src/components/StateChangeSlice_Builder.res +17 -0
- package/src/components/StateChangeSlice_Builder.res.mjs +23 -0
- package/src/components/StateViewSlice_Builder.res +46 -0
- package/src/components/StateViewSlice_Builder.res.mjs +68 -0
- package/src/components/StateViewSlice_Builder_Bundled.res.mjs +69 -0
- package/src/components/StateViewSlice_Builder_Stream.res +50 -0
- package/src/components/StateViewSlice_Builder_Stream.res.mjs +68 -0
- package/src/components/Task_Builder.res +1 -0
- package/src/components/Task_Builder.res.mjs +28 -0
- package/src/components/Task_Builder_PerBucket.res +22 -0
- package/src/components/Task_Builder_PerBucket.res.mjs +63 -0
- package/src/components/Vpc.res +214 -0
- package/src/components/Vpc.res.mjs +117 -0
- package/src/components/Vpc.resi +22 -0
- package/src/errors/AppSync_Error.res +36 -0
- package/src/errors/AppSync_Error.res.mjs +38 -0
- package/src/errors/Cognito_Error.res +36 -0
- package/src/errors/Cognito_Error.res.mjs +38 -0
- package/src/errors/DynamoDb_Error.res +75 -0
- package/src/errors/DynamoDb_Error.res.mjs +86 -0
- package/src/errors/Kinesis_Error.res +36 -0
- package/src/errors/Kinesis_Error.res.mjs +38 -0
- package/src/errors/SNS_Error.res +34 -0
- package/src/errors/SNS_Error.res.mjs +38 -0
- package/src/errors/SQS_Error.res +40 -0
- package/src/errors/SQS_Error.res.mjs +41 -0
- package/src/plugin/cloner/ClonerRunner.res +1 -0
- package/src/plugin/cloner/ClonerRunner.res.mjs +9 -0
- package/src/plugin/cloner/ClonerRunner_Fargate.res +362 -0
- package/src/plugin/cloner/ClonerRunner_Fargate.res.mjs +246 -0
- package/src/plugin/cloner/ClonerRunner_Fargate_Runtime.res +42 -0
- package/src/plugin/cloner/ClonerRunner_Fargate_Runtime.res.mjs +45 -0
- package/src/plugin/heartbeat/HeartbeatRunner.res +1 -0
- package/src/plugin/heartbeat/HeartbeatRunner.res.mjs +9 -0
- package/src/plugin/heartbeat/HeartbeatRunner_CloudWatchEvents.res +96 -0
- package/src/plugin/heartbeat/HeartbeatRunner_CloudWatchEvents.res.mjs +69 -0
- package/src/plugin/runtime/PluginExtensionPointRuntime_Builder.res +115 -0
- package/src/plugin/runtime/PluginExtensionPointRuntime_Builder.res.mjs +88 -0
- package/src/plugin/runtime/PluginRuntimeOperations.res +5 -0
- package/src/plugin/runtime/PluginRuntimeOperations.res.mjs +14 -0
- package/src/plugin/runtime/PluginRuntime_Builder.res +769 -0
- package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +464 -0
- package/src/plugin/runtime/Util_PluginMessage_Runtime.res +13 -0
- package/src/plugin/runtime/Util_PluginMessage_Runtime.res.mjs +19 -0
- package/src/plugin/stack/Plugin_ExtensionPoint_Builder.res +28 -0
- package/src/plugin/stack/Plugin_ExtensionPoint_Builder.res.mjs +67 -0
- package/src/plugin/stack/Plugin_Stack.res +439 -0
- package/src/plugin/stack/Plugin_Stack.res.mjs +263 -0
- package/src/util/Util.res +29 -0
- package/src/util/Util.res.mjs +93 -0
- package/src/util/Util_AppSync.res +36 -0
- package/src/util/Util_AppSync.res.mjs +35 -0
- package/src/util/Util_AppSync_Caller.res +213 -0
- package/src/util/Util_AppSync_Caller.res.mjs +170 -0
- package/src/util/Util_Bundle.res +255 -0
- package/src/util/Util_Bundle.res.mjs +179 -0
- package/src/util/Util_Cloudwatch.res +14 -0
- package/src/util/Util_Cloudwatch.res.mjs +19 -0
- package/src/util/Util_CognitoGroupUser.res +39 -0
- package/src/util/Util_CognitoGroupUser.res.mjs +32 -0
- package/src/util/Util_CognitoGroupUser_Runtime.res +65 -0
- package/src/util/Util_CognitoGroupUser_Runtime.res.mjs +50 -0
- package/src/util/Util_Cognito_Runtime.res +52 -0
- package/src/util/Util_Cognito_Runtime.res.mjs +35 -0
- package/src/util/Util_DeadLetterQueue.res +154 -0
- package/src/util/Util_DeadLetterQueue.res.mjs +158 -0
- package/src/util/Util_DynamoDb.res +210 -0
- package/src/util/Util_DynamoDb.res.mjs +218 -0
- package/src/util/Util_DynamoDbStream.res +138 -0
- package/src/util/Util_DynamoDbStream.res.mjs +134 -0
- package/src/util/Util_DynamoDbStream_Runtime.res +94 -0
- package/src/util/Util_DynamoDbStream_Runtime.res.mjs +131 -0
- package/src/util/Util_DynamoDb_Runtime.res +216 -0
- package/src/util/Util_DynamoDb_Runtime.res.mjs +257 -0
- package/src/util/Util_DynamoDb_TableManager.res +23 -0
- package/src/util/Util_DynamoDb_TableManager.res.mjs +35 -0
- package/src/util/Util_DynamoDb_TableManager.resi +3 -0
- package/src/util/Util_Env.res +5 -0
- package/src/util/Util_Env.res.mjs +2 -0
- package/src/util/Util_EventSourceMapping.res +49 -0
- package/src/util/Util_EventSourceMapping.res.mjs +32 -0
- package/src/util/Util_HostUiDomain.res +28 -0
- package/src/util/Util_HostUiDomain.res.mjs +26 -0
- package/src/util/Util_IAM_Role.res +14 -0
- package/src/util/Util_IAM_Role.res.mjs +28 -0
- package/src/util/Util_Kinesis_Runtime.res +20 -0
- package/src/util/Util_Kinesis_Runtime.res.mjs +24 -0
- package/src/util/Util_Lambda.res +39 -0
- package/src/util/Util_Lambda.res.mjs +45 -0
- package/src/util/Util_LocalConfig.res +119 -0
- package/src/util/Util_LocalConfig.res.mjs +112 -0
- package/src/util/Util_ResourceNaming.res +15 -0
- package/src/util/Util_ResourceNaming.res.mjs +26 -0
- package/src/util/Util_S3.res +17 -0
- package/src/util/Util_S3.res.mjs +30 -0
- package/src/util/Util_S3_Runtime.res +11 -0
- package/src/util/Util_S3_Runtime.res.mjs +15 -0
- package/src/util/Util_SNS.res +33 -0
- package/src/util/Util_SNS.res.mjs +50 -0
- package/src/util/Util_SNS_FIFO.res +21 -0
- package/src/util/Util_SNS_FIFO.res.mjs +27 -0
- package/src/util/Util_SNS_Runtime.res +33 -0
- package/src/util/Util_SNS_Runtime.res.mjs +27 -0
- package/src/util/Util_SQS.res +70 -0
- package/src/util/Util_SQS.res.mjs +83 -0
- package/src/util/Util_SQS_FIFO.res +16 -0
- package/src/util/Util_SQS_FIFO.res.mjs +28 -0
- package/src/util/Util_SQS_Runtime.res +169 -0
- package/src/util/Util_SQS_Runtime.res.mjs +150 -0
- package/src/util/Util_SesPolicy_Runtime.res +54 -0
- package/src/util/Util_SesPolicy_Runtime.res.mjs +55 -0
- package/src/util/Util_StaticBundle.res +124 -0
- package/src/util/Util_StaticBundle.res.mjs +132 -0
- package/src/util/Util_Vpc.res +22 -0
- package/src/util/Util_Vpc.res.mjs +27 -0
- package/tests/AppSync_AdapterTest.res +396 -0
- package/tests/AppSync_AdapterTest.res.mjs +326 -0
- package/tests/AppSync_Resolver_RetryingTest.res +59 -0
- package/tests/AppSync_Resolver_RetryingTest.res.mjs +66 -0
- package/tests/Auth_CognitoTest.res +203 -0
- package/tests/Auth_CognitoTest.res.mjs +248 -0
- package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res +602 -0
- package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res.mjs +742 -0
- package/tests/EventLogStorage_DynamoDb_RuntimeTest.res +57 -0
- package/tests/EventLogStorage_DynamoDb_RuntimeTest.res.mjs +68 -0
- package/tests/MCP_LambdaTest.res +101 -0
- package/tests/MCP_LambdaTest.res.mjs +56 -0
- package/tests/Util_HostUiDomainTest.res +90 -0
- package/tests/Util_HostUiDomainTest.res.mjs +50 -0
- package/tests/Util_SQS_RuntimeTest.res +46 -0
- package/tests/Util_SQS_RuntimeTest.res.mjs +45 -0
- package/tests/Util_StaticBundleTest.res +136 -0
- package/tests/Util_StaticBundleTest.res.mjs +149 -0
- package/tests/integration/DcbCommandTopicEntryPoint_IntegrationTest.res +101 -0
- package/tests/integration/DcbCommandTopicEntryPoint_IntegrationTest.res.mjs +91 -0
- package/tests/integration/DcbEventLogStorage_DynamoDb_IntegrationTest.res +315 -0
- package/tests/integration/DcbEventLogStorage_DynamoDb_IntegrationTest.res.mjs +435 -0
- package/tests/integration/DcbIntegrationHarness.res +102 -0
- package/tests/integration/DcbIntegrationHarness.res.mjs +163 -0
- package/tests/integration/EpTestSlice.res +28 -0
- package/tests/integration/EpTestSlice.res.mjs +43 -0
- package/tests/integration/EpTestSliceBehavior.res +18 -0
- package/tests/integration/EpTestSliceBehavior.res.mjs +35 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
// Aggregate Lambda entry point.
|
|
2
|
+
// At cold start: reads HANDLER_CONFIG, dynamically imports Spec+Behavior modules,
|
|
3
|
+
// wires EventLog_Operations, Aggregate_Callback, CommandTopic_Callback.
|
|
4
|
+
// Handles both SQS CommandTopic events (Route 2) and AppSync direct invocation (Route 1).
|
|
5
|
+
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import { patchSpecId, makeQueueRef, log, pluginName } from "./HandlerFactoryHelpers.mjs";
|
|
8
|
+
import { tag as requestContextTag } from "@reventlessdev/reventless-core/src/RequestContext.res.mjs";
|
|
9
|
+
import { Make as eventLogOperationsMake } from "@reventlessdev/reventless-core/src/components/EventLog/EventLog_Operations.res.mjs";
|
|
10
|
+
import { Make as aggregateCallbackMake } from "@reventlessdev/reventless-core/src/components/Aggregate/Aggregate_Callback.res.mjs";
|
|
11
|
+
import { Make as commandTopicCallbackMake } from "@reventlessdev/reventless-core/src/components/CommandTopic/CommandTopic_Callback.res.mjs";
|
|
12
|
+
import { makeGenerateCommand } from "@reventlessdev/reventless-core/src/components/CommandGenerator/CommandGenerator_Callback.res.mjs";
|
|
13
|
+
import { commandOutcomeToJson, runInlineAndCollect } from "@reventlessdev/reventless-core/src/components/CommandTopic/CommandTopic_Helpers.res.mjs";
|
|
14
|
+
import { append, replay, replayStream, appendStream } from "@reventlessdev/reventless-aws/src/adapter/EventLog/EventLogStorage_DynamoDb_Runtime.res.mjs";
|
|
15
|
+
import { handleQueueEvent, publishJsons as sqsPublishJsons } from "@reventlessdev/reventless-aws/src/adapter/CommandTopic/CommandTopicChannel_SQS_Runtime.res.mjs";
|
|
16
|
+
import { DynamoDBClient, ScanCommand } from "@aws-sdk/client-dynamodb";
|
|
17
|
+
import { unmarshall } from "@aws-sdk/util-dynamodb";
|
|
18
|
+
|
|
19
|
+
const dynamicImport = (specifier) => import('/var/task/node_modules/' + specifier);
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Plugin status gate — mirrors CommandGeneratorResolvers_GraphQL.checkPluginStatus.
|
|
23
|
+
// Rejects mutations on plugins whose Plugin RM `status` is not `Connected`,
|
|
24
|
+
// returning the split error codes from docs/analysis/plugin-lifecycle-tiers.md:
|
|
25
|
+
// Disconnected → PluginUnavailable (tier 1, retryable)
|
|
26
|
+
// Inactive → PluginInactive (tier 2, admin-controlled)
|
|
27
|
+
// Retired → PluginRetired (tier 2, admin-archived version)
|
|
28
|
+
// Platform_* admin mutations bypass the gate. Disabled silently if
|
|
29
|
+
// PLUGIN_RM_TABLE_NAME env var is not set.
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
const PLUGIN_RM_TABLE_NAME = process.env["PLUGIN_RM_TABLE_NAME"];
|
|
33
|
+
const PLUGIN_RM_CACHE_TTL_MS = parseInt(process.env["PLUGIN_RM_CACHE_TTL_MS"] || "5000", 10);
|
|
34
|
+
const ddbClient = PLUGIN_RM_TABLE_NAME ? new DynamoDBClient({}) : null;
|
|
35
|
+
|
|
36
|
+
let pluginStatusCache = null;
|
|
37
|
+
let pluginStatusCacheExpiresAt = 0;
|
|
38
|
+
|
|
39
|
+
async function loadPluginStatuses() {
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
if (pluginStatusCache !== null && now < pluginStatusCacheExpiresAt) {
|
|
42
|
+
return pluginStatusCache;
|
|
43
|
+
}
|
|
44
|
+
const dict = {};
|
|
45
|
+
let exclusiveStartKey = undefined;
|
|
46
|
+
do {
|
|
47
|
+
const res = await ddbClient.send(new ScanCommand({
|
|
48
|
+
TableName: PLUGIN_RM_TABLE_NAME,
|
|
49
|
+
Limit: 1000,
|
|
50
|
+
ExclusiveStartKey: exclusiveStartKey,
|
|
51
|
+
}));
|
|
52
|
+
for (const raw of res.Items || []) {
|
|
53
|
+
try {
|
|
54
|
+
const item = unmarshall(raw);
|
|
55
|
+
if (typeof item.name === "string" && typeof item.status === "string") {
|
|
56
|
+
dict[item.name] = item.status;
|
|
57
|
+
}
|
|
58
|
+
} catch (_) {}
|
|
59
|
+
}
|
|
60
|
+
exclusiveStartKey = res.LastEvaluatedKey;
|
|
61
|
+
} while (exclusiveStartKey);
|
|
62
|
+
pluginStatusCache = dict;
|
|
63
|
+
pluginStatusCacheExpiresAt = now + PLUGIN_RM_CACHE_TTL_MS;
|
|
64
|
+
return dict;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function extractFieldName(event) {
|
|
68
|
+
// AppSync resolver template populates `event.meta.info` with
|
|
69
|
+
// `${parentTypeName}.${fieldName}` (see AppSync_Resolver_Functions.invokeCommandGenerator).
|
|
70
|
+
const info = event?.meta?.info;
|
|
71
|
+
if (typeof info !== "string") return null;
|
|
72
|
+
const dot = info.indexOf(".");
|
|
73
|
+
return dot >= 0 ? info.slice(dot + 1) : info;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function pluginRejection(msgId, errorCode, detail) {
|
|
77
|
+
return {
|
|
78
|
+
__typename: "CommandRejected",
|
|
79
|
+
msgId,
|
|
80
|
+
errorCode,
|
|
81
|
+
errorDetail: detail,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function checkPluginStatus(event) {
|
|
86
|
+
if (ddbClient === null) return null;
|
|
87
|
+
const fieldName = extractFieldName(event);
|
|
88
|
+
if (fieldName === null) return null;
|
|
89
|
+
const underscore = fieldName.indexOf("_");
|
|
90
|
+
const pluginPrefix = underscore >= 0 ? fieldName.slice(0, underscore) : fieldName;
|
|
91
|
+
if (pluginPrefix === "Platform") return null;
|
|
92
|
+
let statuses;
|
|
93
|
+
try {
|
|
94
|
+
statuses = await loadPluginStatuses();
|
|
95
|
+
} catch (err) {
|
|
96
|
+
log.warn("checkPluginStatus: failed to load Plugin RM statuses", { comp: "AggregateEntryPoint", detail: err?.message ?? String(err) });
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const status = statuses[pluginPrefix];
|
|
100
|
+
const msgId = "00000000-0000-0000-0000-000000000000";
|
|
101
|
+
const detailPrefix = `Mutation.${fieldName}`;
|
|
102
|
+
switch (status) {
|
|
103
|
+
case undefined:
|
|
104
|
+
case "Connected":
|
|
105
|
+
return null;
|
|
106
|
+
case "Disconnected":
|
|
107
|
+
return pluginRejection(msgId, "PluginUnavailable", `${detailPrefix}: plugin is disconnected`);
|
|
108
|
+
case "Inactive":
|
|
109
|
+
return pluginRejection(msgId, "PluginInactive", `${detailPrefix}: plugin is inactive`);
|
|
110
|
+
case "Retired":
|
|
111
|
+
return pluginRejection(msgId, "PluginRetired", `${detailPrefix}: plugin version has been retired`);
|
|
112
|
+
default:
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Build the per-aggregate parts shared by Route 1 (AppSync direct invoke) and
|
|
118
|
+
// Route 2 (SQS event source): the in-process command handler (used both as the
|
|
119
|
+
// inline dispatch target and as the SQS message handler) plus the resolved
|
|
120
|
+
// queue ref for the fire-and-forget async path.
|
|
121
|
+
function buildAggregateParts(specModule, behaviorModule, eventLogTableName, queueUrl) {
|
|
122
|
+
const patchedSpec = patchSpecId(specModule);
|
|
123
|
+
const resolvedTable = { name: eventLogTableName };
|
|
124
|
+
const rawStorageOps = {
|
|
125
|
+
append: append(resolvedTable),
|
|
126
|
+
replay: replay(resolvedTable),
|
|
127
|
+
replayStream: replayStream(resolvedTable),
|
|
128
|
+
appendStream: appendStream(resolvedTable),
|
|
129
|
+
};
|
|
130
|
+
const eventLogOps = eventLogOperationsMake(patchedSpec)({
|
|
131
|
+
Spec: patchedSpec,
|
|
132
|
+
EventTopic: { Spec: patchedSpec },
|
|
133
|
+
eventTopic: { publish: async () => {} },
|
|
134
|
+
storage: rawStorageOps,
|
|
135
|
+
});
|
|
136
|
+
const aggregateCallback = aggregateCallbackMake(patchedSpec)(behaviorModule)({
|
|
137
|
+
Spec: patchedSpec,
|
|
138
|
+
EventLog: { Spec: patchedSpec },
|
|
139
|
+
eventLog: eventLogOps,
|
|
140
|
+
});
|
|
141
|
+
const commandTopicCallback = commandTopicCallbackMake(patchedSpec)({
|
|
142
|
+
Spec: patchedSpec,
|
|
143
|
+
commandsHandler: aggregateCallback.handleCommands,
|
|
144
|
+
});
|
|
145
|
+
const resolvedQueue = makeQueueRef(queueUrl);
|
|
146
|
+
return {
|
|
147
|
+
patchedSpec,
|
|
148
|
+
handleJsonCommands: commandTopicCallback.handleJsonCommands,
|
|
149
|
+
sqsHandler: handleQueueEvent(resolvedQueue, commandTopicCallback.handleJsonCommands),
|
|
150
|
+
resolvedQueue,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function buildCommandGeneratorHandler(parts, dispatchMode) {
|
|
155
|
+
const { patchedSpec, handleJsonCommands, resolvedQueue } = parts;
|
|
156
|
+
const publishJsons = sqsPublishJsons(resolvedQueue, "SQS_FIFO");
|
|
157
|
+
// Sync (default): dispatch inline so the AppSync resolver gets a typed
|
|
158
|
+
// Accepted/Rejected outcome. Async: fire-and-forget to SQS, return Pending —
|
|
159
|
+
// the SQS event source (Route 2) processes the command later.
|
|
160
|
+
const publishJsonsAndWait = dispatchMode === "async"
|
|
161
|
+
? undefined
|
|
162
|
+
: (jsons) => runInlineAndCollect(jsons, handleJsonCommands);
|
|
163
|
+
// Positional args after ReScript compiles labeled params:
|
|
164
|
+
// (publishJsons, publishJsonsAndWait, serviceName, commandSchema, componentKind, stripIdFromParams)
|
|
165
|
+
// The `undefined` for `stripIdFromParams` must be passed explicitly here —
|
|
166
|
+
// omitting it shifts everything left, sending the schema object through the
|
|
167
|
+
// serviceName slot (string-concat then throws "Cannot convert object to
|
|
168
|
+
// primitive value" once the log line tries to print "CommandGenerator(<schema>)").
|
|
169
|
+
const generateCommand = makeGenerateCommand(publishJsons, publishJsonsAndWait, patchedSpec.name, patchedSpec.commandSchema, "Aggregate", undefined);
|
|
170
|
+
return (event, _context) => {
|
|
171
|
+
// Add identity fallback: use payload.identity if present, otherwise construct from meta.user.
|
|
172
|
+
const identity = (event.identity != null && typeof event.identity === 'object')
|
|
173
|
+
? event.identity
|
|
174
|
+
: {
|
|
175
|
+
userId: event.meta?.user ?? "anonymous",
|
|
176
|
+
username: event.meta?.user ?? "anonymous",
|
|
177
|
+
groups: [],
|
|
178
|
+
provider: { TAG: "Custom", _0: "aws" },
|
|
179
|
+
};
|
|
180
|
+
// CommandGenerator.meta declares ip as array<string> (X-Forwarded-For chain).
|
|
181
|
+
// The AppSync resolver template sends a single string (or null) from
|
|
182
|
+
// identity.sourceIp — normalize to array so generateCommand's
|
|
183
|
+
// `payload.meta.ip->Array.shift` doesn't crash on null/non-array values.
|
|
184
|
+
const rawIp = event.meta?.ip;
|
|
185
|
+
const ip = rawIp == null ? [] : Array.isArray(rawIp) ? rawIp : [rawIp];
|
|
186
|
+
const meta = { ...event.meta, ip };
|
|
187
|
+
return generateCommand({ ...event, meta, identity });
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Set at handler entry; read by runEffect to tag logs with the Lambda request id.
|
|
192
|
+
let _currentRequestId = "unknown";
|
|
193
|
+
|
|
194
|
+
function runEffect(correlationId, effect) {
|
|
195
|
+
let e = effect
|
|
196
|
+
// Promote correlationId/requestId/plugin to top-level JSON log fields —
|
|
197
|
+
// decoded by EffectLogger.install() from Effect log annotations. Harmless
|
|
198
|
+
// no-op if the unified logger isn't installed.
|
|
199
|
+
.pipe(Effect.annotateLogs("correlationId", correlationId || "unknown"))
|
|
200
|
+
.pipe(Effect.annotateLogs("requestId", _currentRequestId));
|
|
201
|
+
if (pluginName) e = e.pipe(Effect.annotateLogs("plugin", pluginName));
|
|
202
|
+
return e
|
|
203
|
+
.pipe(Effect.provideService(requestContextTag, { correlationId: correlationId || "unknown" }))
|
|
204
|
+
.pipe(Effect.runPromise);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function groupBySource(records) {
|
|
208
|
+
const dict = {};
|
|
209
|
+
records.forEach(record => {
|
|
210
|
+
const arn = record.eventSourceARN;
|
|
211
|
+
const existing = dict[arn] || [];
|
|
212
|
+
dict[arn] = existing.concat([record]);
|
|
213
|
+
});
|
|
214
|
+
return dict;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function extractCorrelationId(records) {
|
|
218
|
+
const first = records[0];
|
|
219
|
+
if (!first) return undefined;
|
|
220
|
+
const body = first.body;
|
|
221
|
+
if (body == null) return undefined;
|
|
222
|
+
try {
|
|
223
|
+
const parsed = JSON.parse(body);
|
|
224
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
225
|
+
const meta = parsed.meta;
|
|
226
|
+
if (meta && typeof meta === "object" && !Array.isArray(meta)) {
|
|
227
|
+
const cid = meta.correlationId;
|
|
228
|
+
if (typeof cid === "string") return cid;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
} catch (_) {}
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// DISPATCH_MODE controls whether AppSync direct-invoke commands are run inline
|
|
236
|
+
// (sync, default) or pushed to SQS for asynchronous processing (async).
|
|
237
|
+
// Aggregate_Builder_Single bundles this entry point with sync; the async
|
|
238
|
+
// variant (Aggregate_Builder_Single_Async) sets DISPATCH_MODE=async.
|
|
239
|
+
const DISPATCH_MODE = process.env["DISPATCH_MODE"] === "async" ? "async" : "sync";
|
|
240
|
+
|
|
241
|
+
async function buildAllHandlers() {
|
|
242
|
+
const configStr = process.env["HANDLER_CONFIG"] || '{"handlers":[]}';
|
|
243
|
+
const config = JSON.parse(configStr);
|
|
244
|
+
const cmdTopicHandlers = {};
|
|
245
|
+
const cmdGenHandlers = {};
|
|
246
|
+
await Promise.all(config.handlers.map(async h => {
|
|
247
|
+
const specModule = await dynamicImport(h.specModule);
|
|
248
|
+
const behaviorModule = await dynamicImport(h.behaviorModule);
|
|
249
|
+
const parts = buildAggregateParts(specModule, behaviorModule, h.eventLogTable, h.queueUrl);
|
|
250
|
+
cmdTopicHandlers[h.queueArn] = parts.sqsHandler;
|
|
251
|
+
cmdGenHandlers[specModule.name] = buildCommandGeneratorHandler(parts, DISPATCH_MODE);
|
|
252
|
+
}));
|
|
253
|
+
return [cmdTopicHandlers, cmdGenHandlers];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const initPromise = buildAllHandlers();
|
|
257
|
+
|
|
258
|
+
export async function handler(event, context) {
|
|
259
|
+
_currentRequestId = context?.awsRequestId || "unknown";
|
|
260
|
+
const [cmdTopicHandlers, cmdGenHandlers] = await initPromise;
|
|
261
|
+
|
|
262
|
+
// Route 1: AppSync direct invocation
|
|
263
|
+
if (event.command != null && event.arguments != null) {
|
|
264
|
+
// Plugin status gate (Part 2.3) — checked before dispatching to the
|
|
265
|
+
// per-aggregate handler so a deactivated plugin's mutations are rejected
|
|
266
|
+
// without touching the EventLog or CommandTopic.
|
|
267
|
+
const rejection = await checkPluginStatus(event);
|
|
268
|
+
if (rejection !== null) {
|
|
269
|
+
log.warn("plugin status gate rejected: " + rejection.errorCode, { comp: "CommandGenerator" });
|
|
270
|
+
return rejection;
|
|
271
|
+
}
|
|
272
|
+
const entries = Object.entries(cmdGenHandlers);
|
|
273
|
+
const len = entries.length;
|
|
274
|
+
let i = 0;
|
|
275
|
+
let result = null;
|
|
276
|
+
let found = false;
|
|
277
|
+
while (i < len && !found) {
|
|
278
|
+
const [name, cmdGenHandler] = entries[i];
|
|
279
|
+
try {
|
|
280
|
+
const r = await runEffect(undefined, cmdGenHandler(event, context));
|
|
281
|
+
log.debug("processed command via " + name, { comp: "CommandGenerator" });
|
|
282
|
+
result = r;
|
|
283
|
+
found = true;
|
|
284
|
+
} catch (e) {
|
|
285
|
+
log.debug("handler '" + name + "' threw, trying next", { comp: "CommandGenerator", detail: e && e.message ? e.message : String(e) });
|
|
286
|
+
i = i + 1;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (!found) {
|
|
290
|
+
log.warn("no handler matched command", { comp: "CommandGenerator" });
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
// generateCommand returns a ReScript commandOutcome variant
|
|
294
|
+
// (Accepted/Rejected/Pending) compiled to a `{TAG, ...}` JS object. AppSync
|
|
295
|
+
// resolves the CommandResult union by `__typename` — convert to that shape.
|
|
296
|
+
return commandOutcomeToJson(result);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Route 2: SQS CommandTopic events
|
|
300
|
+
const records = event.Records || [];
|
|
301
|
+
const correlationId = extractCorrelationId(records);
|
|
302
|
+
const grouped = groupBySource(records);
|
|
303
|
+
await Promise.all(Object.entries(grouped).map(async ([arn, subRecords]) => {
|
|
304
|
+
const cmdHandler = cmdTopicHandlers[arn];
|
|
305
|
+
if (cmdHandler !== undefined) {
|
|
306
|
+
log.debug("found handler for CommandTopic " + arn, { comp: "AggregateRuntime" });
|
|
307
|
+
await runEffect(correlationId, cmdHandler({ Records: subRecords }, context));
|
|
308
|
+
} else {
|
|
309
|
+
log.warn("no handler found: " + arn, { comp: "AggregateRuntime" });
|
|
310
|
+
}
|
|
311
|
+
}));
|
|
312
|
+
return "";
|
|
313
|
+
}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
module CommandTopicChannel = CommandTopicChannel.SQS_Sync
|
|
2
|
+
module EventCollectorChannel = EventCollectorChannel.DynamoDbStream
|
|
3
|
+
module RuntimeEnvironment = RuntimeEnvironment.Lambda
|
|
4
|
+
|
|
5
|
+
let log = ReventlessCore.Logger.fromEnv()
|
|
6
|
+
|
|
7
|
+
type context = PulumiAws.Lambda.context
|
|
8
|
+
type runtimeParts = Util.Lambda.runtimeParts
|
|
9
|
+
|
|
10
|
+
type aggregateInfo = {
|
|
11
|
+
specModulePath: string,
|
|
12
|
+
behaviorModulePath: string,
|
|
13
|
+
eventLogTableName: Pulumi.Output.t<string>,
|
|
14
|
+
mappingsModulePath: option<string>,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let aggregateInfos: dict<aggregateInfo> = Dict.make()
|
|
18
|
+
|
|
19
|
+
let registerAggregate = (
|
|
20
|
+
~aggregateName,
|
|
21
|
+
~specModulePath,
|
|
22
|
+
~behaviorModulePath,
|
|
23
|
+
~eventLogTableName,
|
|
24
|
+
~mappingsModulePath=?,
|
|
25
|
+
) =>
|
|
26
|
+
aggregateInfos->Dict.set(
|
|
27
|
+
aggregateName,
|
|
28
|
+
{specModulePath, behaviorModulePath, eventLogTableName, mappingsModulePath},
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
type storedSpec = {
|
|
32
|
+
aggregateName: string,
|
|
33
|
+
aggregateResource: Pulumi.Resource.t,
|
|
34
|
+
queueUrl: Pulumi.Output.t<string>,
|
|
35
|
+
queueArn: Pulumi.Output.t<string>,
|
|
36
|
+
commandTopicConnects: array<ReventlessCore.Runtime.connect<runtimeParts>>,
|
|
37
|
+
commandGeneratorConnects: array<ReventlessCore.Runtime.connect<runtimeParts>>,
|
|
38
|
+
eventCollectorChannelSpec: option<
|
|
39
|
+
ReventlessCore.EventCollector_Adapter.channelSpec<
|
|
40
|
+
EventCollectorChannel.callbackEvent,
|
|
41
|
+
context,
|
|
42
|
+
EventCollectorChannel.channelParts,
|
|
43
|
+
>,
|
|
44
|
+
>,
|
|
45
|
+
commandTopicMemorySize: int,
|
|
46
|
+
commandTopicTimeout: int,
|
|
47
|
+
commandGeneratorMemorySize: int,
|
|
48
|
+
commandGeneratorTimeout: int,
|
|
49
|
+
eventCollectorMemorySize: int,
|
|
50
|
+
eventCollectorTimeout: int,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let storedSpecs: dict<storedSpec> = Dict.make()
|
|
54
|
+
|
|
55
|
+
let getStoredSpec = (aggregateName, aggregateResource) =>
|
|
56
|
+
storedSpecs
|
|
57
|
+
->Dict.get(aggregateName)
|
|
58
|
+
->Option.getOr({
|
|
59
|
+
aggregateName,
|
|
60
|
+
aggregateResource,
|
|
61
|
+
queueUrl: ""->Pulumi.Output.make,
|
|
62
|
+
queueArn: ""->Pulumi.Output.make,
|
|
63
|
+
commandTopicConnects: [],
|
|
64
|
+
commandGeneratorConnects: [],
|
|
65
|
+
eventCollectorChannelSpec: None,
|
|
66
|
+
commandTopicMemorySize: 0,
|
|
67
|
+
commandTopicTimeout: 0,
|
|
68
|
+
commandGeneratorMemorySize: 0,
|
|
69
|
+
commandGeneratorTimeout: 0,
|
|
70
|
+
eventCollectorMemorySize: 0,
|
|
71
|
+
eventCollectorTimeout: 0,
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
let forCommandGenerator: ReventlessCore.Runtime.forComponent<
|
|
75
|
+
ReventlessCore.CommandGenerator.effectEventHandler<context>,
|
|
76
|
+
runtimeParts,
|
|
77
|
+
ReventlessCore.CommandGenerator.component,
|
|
78
|
+
> = (
|
|
79
|
+
~handler as _,
|
|
80
|
+
~connect,
|
|
81
|
+
~memorySize=1024,
|
|
82
|
+
~timeout=30,
|
|
83
|
+
commandGenerator,
|
|
84
|
+
) => {
|
|
85
|
+
let resource = commandGenerator->ReventlessCore.Component.toPulumiResource
|
|
86
|
+
switch resource.parent {
|
|
87
|
+
| Some(aggregateResource) =>
|
|
88
|
+
let aggregateName = aggregateResource.name->Option.getOr("Unnamed")
|
|
89
|
+
let spec = getStoredSpec(aggregateName, aggregateResource)
|
|
90
|
+
storedSpecs->Dict.set(aggregateName, {
|
|
91
|
+
...spec,
|
|
92
|
+
commandGeneratorConnects: spec.commandGeneratorConnects->Array.concat([connect]),
|
|
93
|
+
commandGeneratorMemorySize: Math.Int.max(spec.commandGeneratorMemorySize, memorySize),
|
|
94
|
+
commandGeneratorTimeout: Math.Int.max(spec.commandGeneratorTimeout, timeout),
|
|
95
|
+
})
|
|
96
|
+
| None => ()
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let forCommandTopic: ReventlessCore.Runtime.forComponent<
|
|
101
|
+
ReventlessCore.Runtime.effectHandler<
|
|
102
|
+
CommandTopicChannel.callbackEvent,
|
|
103
|
+
context,
|
|
104
|
+
unit,
|
|
105
|
+
string,
|
|
106
|
+
>,
|
|
107
|
+
runtimeParts,
|
|
108
|
+
ReventlessCore.CommandTopic.component<'op>,
|
|
109
|
+
> = (
|
|
110
|
+
~handler as _,
|
|
111
|
+
~connect,
|
|
112
|
+
~memorySize=1024,
|
|
113
|
+
~timeout=30,
|
|
114
|
+
commandTopic,
|
|
115
|
+
) => {
|
|
116
|
+
let commandTopicResource = commandTopic->ReventlessCore.Component.toPulumiResource
|
|
117
|
+
switch commandTopicResource.parent {
|
|
118
|
+
| Some(aggregateResource) =>
|
|
119
|
+
let aggregateName = aggregateResource.name->Option.getOr("Unnamed")
|
|
120
|
+
let channel = commandTopic->ReventlessCore.CommandTopic_Adapter.channel
|
|
121
|
+
let channelParts: Util.SQS.channelParts = Obj.magic(channel.parts)
|
|
122
|
+
let queue = channelParts.queue
|
|
123
|
+
|
|
124
|
+
let spec = getStoredSpec(aggregateName, aggregateResource)
|
|
125
|
+
storedSpecs->Dict.set(aggregateName, {
|
|
126
|
+
...spec,
|
|
127
|
+
queueUrl: queue.id,
|
|
128
|
+
queueArn: queue.arn,
|
|
129
|
+
commandTopicConnects: spec.commandTopicConnects->Array.concat([connect]),
|
|
130
|
+
commandTopicMemorySize: Math.Int.max(spec.commandTopicMemorySize, memorySize),
|
|
131
|
+
commandTopicTimeout: Math.Int.max(spec.commandTopicTimeout, timeout),
|
|
132
|
+
})
|
|
133
|
+
| None =>
|
|
134
|
+
let name = commandTopicResource.name->Option.getOr("Unnamed")
|
|
135
|
+
JsError.throwWithMessage(
|
|
136
|
+
`forCommandTopic(micro): commandTopic ${name} has no Aggregate parent`,
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let forEventCollector: ReventlessCore.Runtime.forEventCollector<
|
|
142
|
+
ReventlessCore.Runtime.effectHandler<
|
|
143
|
+
EventCollectorChannel.callbackEvent,
|
|
144
|
+
context,
|
|
145
|
+
unit,
|
|
146
|
+
string,
|
|
147
|
+
>,
|
|
148
|
+
ReventlessCore.EventCollector.component,
|
|
149
|
+
> = (
|
|
150
|
+
~handler as _,
|
|
151
|
+
~eventTopics,
|
|
152
|
+
~resources,
|
|
153
|
+
~memorySize=2048,
|
|
154
|
+
~timeout=180,
|
|
155
|
+
eventCollector,
|
|
156
|
+
) => {
|
|
157
|
+
let eventCollectorResource = eventCollector->ReventlessCore.Component.toPulumiResource
|
|
158
|
+
let channel = eventCollector->ReventlessCore.EventCollector_Adapter.channel
|
|
159
|
+
switch eventCollectorResource.parent->Option.flatMap(parent => parent.parent) {
|
|
160
|
+
| Some(aggregateResource) =>
|
|
161
|
+
let aggregateName = aggregateResource.name->Option.getOr("Unnamed")
|
|
162
|
+
let spec = getStoredSpec(aggregateName, aggregateResource)
|
|
163
|
+
storedSpecs->Dict.set(aggregateName, {
|
|
164
|
+
...spec,
|
|
165
|
+
eventCollectorChannelSpec: Some({channel, eventTopics, resources}),
|
|
166
|
+
eventCollectorMemorySize: Math.Int.max(spec.eventCollectorMemorySize, memorySize),
|
|
167
|
+
eventCollectorTimeout: Math.Int.max(spec.eventCollectorTimeout, timeout),
|
|
168
|
+
})
|
|
169
|
+
| None =>
|
|
170
|
+
let name = eventCollectorResource.name->Option.getOr("Unnamed")
|
|
171
|
+
JsError.throwWithMessage(
|
|
172
|
+
`forEventCollector(micro): eventCollector ${name} has no Aggregate parent`,
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let finished = ref(false)
|
|
178
|
+
|
|
179
|
+
let finish = () =>
|
|
180
|
+
if !finished.contents {
|
|
181
|
+
let specs = storedSpecs->Dict.valuesToArray
|
|
182
|
+
if specs->Array.length > 0 {
|
|
183
|
+
specs->Array.forEach(spec => {
|
|
184
|
+
switch aggregateInfos->Dict.get(spec.aggregateName) {
|
|
185
|
+
| Some(info) =>
|
|
186
|
+
let aggregateOpts = {
|
|
187
|
+
Pulumi.ComponentResource.parent: spec.aggregateResource,
|
|
188
|
+
}
|
|
189
|
+
let baseName =
|
|
190
|
+
spec.aggregateName->ReventlessCore.ComponentType.name(
|
|
191
|
+
ReventlessCore.Aggregate.componentType,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
// Collect user packages (shared by CmdTopic and CmdGen)
|
|
195
|
+
let packageDirs: dict<string> = Dict.make()
|
|
196
|
+
let specPkg = Util_Bundle.extractPackageName(info.specModulePath)
|
|
197
|
+
let behaviorPkg = Util_Bundle.extractPackageName(info.behaviorModulePath)
|
|
198
|
+
packageDirs->Dict.set(specPkg, Util_Bundle.resolvePackageRoot(specPkg))
|
|
199
|
+
packageDirs->Dict.set(behaviorPkg, Util_Bundle.resolvePackageRoot(behaviorPkg))
|
|
200
|
+
|
|
201
|
+
let specModule =
|
|
202
|
+
info.specModulePath->JSON.stringifyAny->Option.getOr(`""`)
|
|
203
|
+
let behaviorModule =
|
|
204
|
+
info.behaviorModulePath->JSON.stringifyAny->Option.getOr(`""`)
|
|
205
|
+
|
|
206
|
+
// --- CommandTopic Lambda ---
|
|
207
|
+
let cmdTopicHandlerConfigOutput =
|
|
208
|
+
Pulumi.Output.all3((info.eventLogTableName, spec.queueUrl, spec.queueArn))
|
|
209
|
+
->Pulumi.Output.apply(((table, queueUrl, queueArn)) =>
|
|
210
|
+
`{"handlers":[{"specModule":${specModule},"behaviorModule":${behaviorModule},"eventLogTable":"${table}","queueUrl":"${queueUrl}","queueArn":"${queueArn}"}]}`
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
let cmdTopicEnvVars: dict<Pulumi.Input.t<string>> = Dict.make()
|
|
214
|
+
cmdTopicEnvVars->Dict.set("HANDLER_CONFIG", cmdTopicHandlerConfigOutput->Pulumi.Output.asInput)
|
|
215
|
+
|
|
216
|
+
let {code: cmdTopicCode, sourceCodeHash: cmdTopicSourceCodeHash} = Util_Bundle.buildCodeArchive(
|
|
217
|
+
~entryPointModule="@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs",
|
|
218
|
+
~packageDirs,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
let cmdTopicName = baseName ++ "CmdHandler"
|
|
222
|
+
let cmdTopicRuntime = RuntimeEnvironment_Lambda.makeFromCodeAsset(
|
|
223
|
+
~name=cmdTopicName,
|
|
224
|
+
~code=cmdTopicCode,
|
|
225
|
+
~sourceCodeHash=cmdTopicSourceCodeHash,
|
|
226
|
+
~envVars=cmdTopicEnvVars,
|
|
227
|
+
~memorySize=Math.Int.max(spec.commandTopicMemorySize, 1024),
|
|
228
|
+
~timeout=Math.Int.max(spec.commandTopicTimeout, 30),
|
|
229
|
+
~opts=aggregateOpts,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
spec.commandTopicConnects->Array.forEach(connect => connect(~runtime=cmdTopicRuntime))
|
|
233
|
+
|
|
234
|
+
// --- CommandGenerator Lambda ---
|
|
235
|
+
if spec.commandGeneratorConnects->Array.length > 0 {
|
|
236
|
+
let cmdGenHandlerConfigOutput =
|
|
237
|
+
Pulumi.Output.all3((info.eventLogTableName, spec.queueUrl, spec.queueArn))
|
|
238
|
+
->Pulumi.Output.apply(((table, queueUrl, queueArn)) =>
|
|
239
|
+
`{"handlers":[{"specModule":${specModule},"behaviorModule":${behaviorModule},"eventLogTable":"${table}","queueUrl":"${queueUrl}","queueArn":"${queueArn}"}]}`
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
let cmdGenEnvVars: dict<Pulumi.Input.t<string>> = Dict.make()
|
|
243
|
+
cmdGenEnvVars->Dict.set("HANDLER_CONFIG", cmdGenHandlerConfigOutput->Pulumi.Output.asInput)
|
|
244
|
+
|
|
245
|
+
let {code: cmdGenCode, sourceCodeHash: cmdGenSourceCodeHash} = Util_Bundle.buildCodeArchive(
|
|
246
|
+
~entryPointModule="@reventlessdev/reventless-aws/src/adapter/Runtime/AggregateEntryPoint.mjs",
|
|
247
|
+
~packageDirs,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
let cmdGenName = baseName ++ "CmdGen"
|
|
251
|
+
let cmdGenRuntime = RuntimeEnvironment_Lambda.makeFromCodeAsset(
|
|
252
|
+
~name=cmdGenName,
|
|
253
|
+
~code=cmdGenCode,
|
|
254
|
+
~sourceCodeHash=cmdGenSourceCodeHash,
|
|
255
|
+
~envVars=cmdGenEnvVars,
|
|
256
|
+
~memorySize=Math.Int.max(spec.commandGeneratorMemorySize, 1024),
|
|
257
|
+
~timeout=Math.Int.max(spec.commandGeneratorTimeout, 30),
|
|
258
|
+
~opts=aggregateOpts,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
spec.commandGeneratorConnects->Array.forEach(connect =>
|
|
262
|
+
connect(~runtime=cmdGenRuntime)
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// --- EventMapper Lambda ---
|
|
267
|
+
switch (spec.eventCollectorChannelSpec, info.mappingsModulePath) {
|
|
268
|
+
| (Some(channelSpec), Some(mappingsModulePath)) =>
|
|
269
|
+
let evtMapperEnvVars: dict<Pulumi.Input.t<string>> = Dict.make()
|
|
270
|
+
|
|
271
|
+
let targetSpecModule =
|
|
272
|
+
info.specModulePath->JSON.stringifyAny->Option.getOr(`""`)
|
|
273
|
+
let mappingsModuleJson =
|
|
274
|
+
mappingsModulePath->JSON.stringifyAny->Option.getOr(`""`)
|
|
275
|
+
|
|
276
|
+
let handlerConfigJson =
|
|
277
|
+
spec.queueUrl
|
|
278
|
+
->Pulumi.Output.apply(queueUrl =>
|
|
279
|
+
`{"targetSpecModule":${targetSpecModule},"mappingsModule":${mappingsModuleJson},"queueUrl":"${queueUrl}"}`
|
|
280
|
+
)
|
|
281
|
+
evtMapperEnvVars->Dict.set("HANDLER_CONFIG", handlerConfigJson->Pulumi.Output.asInput)
|
|
282
|
+
|
|
283
|
+
let evtMapperPackageDirs: dict<string> = Dict.make()
|
|
284
|
+
let specPkg = Util_Bundle.extractPackageName(info.specModulePath)
|
|
285
|
+
let mappingsPkg = Util_Bundle.extractPackageName(mappingsModulePath)
|
|
286
|
+
evtMapperPackageDirs->Dict.set(specPkg, Util_Bundle.resolvePackageRoot(specPkg))
|
|
287
|
+
evtMapperPackageDirs->Dict.set(mappingsPkg, Util_Bundle.resolvePackageRoot(mappingsPkg))
|
|
288
|
+
|
|
289
|
+
let {code: evtMapperCode, sourceCodeHash: evtMapperSourceCodeHash} = Util_Bundle.buildCodeArchive(
|
|
290
|
+
~entryPointModule="@reventlessdev/reventless-aws/src/adapter/Runtime/EventMapperEntryPoint.mjs",
|
|
291
|
+
~packageDirs=evtMapperPackageDirs,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
let evtMapperName = baseName ++ "EventMapper"
|
|
295
|
+
let evtMapperRuntime = RuntimeEnvironment_Lambda.makeFromCodeAsset(
|
|
296
|
+
~name=evtMapperName,
|
|
297
|
+
~code=evtMapperCode,
|
|
298
|
+
~sourceCodeHash=evtMapperSourceCodeHash,
|
|
299
|
+
~envVars=evtMapperEnvVars,
|
|
300
|
+
~memorySize=Math.Int.max(spec.eventCollectorMemorySize, 2048),
|
|
301
|
+
~timeout=Math.Int.max(spec.eventCollectorTimeout, 180),
|
|
302
|
+
~opts=aggregateOpts,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
let _connectResources = EventCollectorChannel.connect(
|
|
306
|
+
~name=evtMapperName,
|
|
307
|
+
~channelSpecs=[channelSpec],
|
|
308
|
+
~runtime=evtMapperRuntime,
|
|
309
|
+
~opts=aggregateOpts,
|
|
310
|
+
)
|
|
311
|
+
| (Some(_), None) =>
|
|
312
|
+
log.warn(
|
|
313
|
+
~comp="AggregateRuntime_Builder_Micro",
|
|
314
|
+
`eventCollector registered for ${spec.aggregateName} but no mappingsModulePath — skipping EventMapper Lambda`,
|
|
315
|
+
)
|
|
316
|
+
| _ => ()
|
|
317
|
+
}
|
|
318
|
+
| None =>
|
|
319
|
+
log.warn(
|
|
320
|
+
~comp="AggregateRuntime_Builder_Micro",
|
|
321
|
+
`no handler registered for ${spec.aggregateName}`,
|
|
322
|
+
)
|
|
323
|
+
}
|
|
324
|
+
})
|
|
325
|
+
}
|
|
326
|
+
finished := true
|
|
327
|
+
}
|