@reventlessdev/reventless-aws 3.0.0-alpha.219 → 3.0.0-alpha.221

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 (164) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/package.json +8 -8
  3. package/src/Platform.res.mjs +4 -0
  4. package/src/Platform_Stack.res +6 -0
  5. package/src/Platform_Stack.res.mjs +3 -1
  6. package/src/adapter/AWS_Tags.res +127 -12
  7. package/src/adapter/AWS_Tags.res.mjs +58 -17
  8. package/src/adapter/Api/AppSync_SourceApiAssociation_Retrying.res +26 -1
  9. package/src/adapter/Api/AppSync_SourceApiAssociation_Retrying.res.mjs +12 -1
  10. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +13 -1
  11. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +3 -4
  12. package/src/adapter/Api/Platform_UIFragments_Lambda.res +13 -1
  13. package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +3 -4
  14. package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync.res +12 -0
  15. package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync.res.mjs +4 -2
  16. package/src/adapter/CommandGenerator/InboundTranslationResolvers_AppSync.res +6 -0
  17. package/src/adapter/CommandGenerator/InboundTranslationResolvers_AppSync.res.mjs +2 -1
  18. package/src/adapter/CommandTopic/CommandTopicChannel_SQS.res +2 -2
  19. package/src/adapter/CommandTopic/CommandTopicChannel_SQS.res.mjs +2 -2
  20. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Async.res +2 -2
  21. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Async.res.mjs +2 -2
  22. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_FIFO.res +2 -2
  23. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_FIFO.res.mjs +2 -2
  24. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Sync.res +2 -2
  25. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Sync.res.mjs +2 -2
  26. package/src/adapter/Counter/CounterHandler_DynamoDbStream.res +1 -0
  27. package/src/adapter/Counter/CounterHandler_DynamoDbStream.res.mjs +1 -1
  28. package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb.res +1 -1
  29. package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb.res.mjs +1 -2
  30. package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream.res +1 -1
  31. package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream.res.mjs +1 -1
  32. package/src/adapter/EventCollector/EventCollectorChannel_SQS.res +2 -2
  33. package/src/adapter/EventCollector/EventCollectorChannel_SQS.res.mjs +2 -2
  34. package/src/adapter/EventCollector/EventCollectorChannel_SQS_FIFO.res +2 -2
  35. package/src/adapter/EventCollector/EventCollectorChannel_SQS_FIFO.res.mjs +2 -2
  36. package/src/adapter/EventLog/EventLogStorage.res +3 -3
  37. package/src/adapter/EventLog/EventLogStorage.res.mjs +3 -3
  38. package/src/adapter/EventLog/EventLogStorage_DynamoDb.res +2 -2
  39. package/src/adapter/EventLog/EventLogStorage_DynamoDb.res.mjs +2 -2
  40. package/src/adapter/EventLog/EventLogStorage_DynamoDbStream.res +2 -2
  41. package/src/adapter/EventLog/EventLogStorage_DynamoDbStream.res.mjs +2 -2
  42. package/src/adapter/EventLog/EventLogStorage_Postgres.res +1 -1
  43. package/src/adapter/EventLog/EventLogStorage_Postgres.res.mjs +1 -1
  44. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +7 -1
  45. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +2 -2
  46. package/src/adapter/EventTopic/EventTopicPublisher_DynamoDbStream.res +1 -1
  47. package/src/adapter/EventTopic/EventTopicPublisher_DynamoDbStream.res.mjs +1 -1
  48. package/src/adapter/EventTopic/EventTopicPublisher_SNS.res +2 -2
  49. package/src/adapter/EventTopic/EventTopicPublisher_SNS.res.mjs +2 -2
  50. package/src/adapter/EventTopic/EventTopicPublisher_SNS_FIFO.res +2 -2
  51. package/src/adapter/EventTopic/EventTopicPublisher_SNS_FIFO.res.mjs +2 -2
  52. package/src/adapter/Mcp/MCP_Lambda.res.mjs +1 -1
  53. package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res +7 -0
  54. package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs +4 -2
  55. package/src/adapter/Postgres/PgMigration_Builder.res +1 -0
  56. package/src/adapter/Postgres/PgMigration_Builder.res.mjs +1 -1
  57. package/src/adapter/QueryDb/PgQueryResolver_Builder.res +7 -0
  58. package/src/adapter/QueryDb/PgQueryResolver_Builder.res.mjs +4 -2
  59. package/src/adapter/QueryDb/QueryDbStorage.res +6 -6
  60. package/src/adapter/QueryDb/QueryDbStorage.res.mjs +6 -6
  61. package/src/adapter/QueryDb/QueryDbStorage_DynamoDb.res +2 -2
  62. package/src/adapter/QueryDb/QueryDbStorage_DynamoDb.res.mjs +2 -2
  63. package/src/adapter/QueryDb/QueryDbStorage_DynamoDbStream.res +2 -2
  64. package/src/adapter/QueryDb/QueryDbStorage_DynamoDbStream.res.mjs +2 -2
  65. package/src/adapter/QueryDb/QueryDbStorage_Postgres.res +1 -1
  66. package/src/adapter/QueryDb/QueryDbStorage_Postgres.res.mjs +1 -1
  67. package/src/adapter/Runtime/AdminEventCollectorEntryPoint.mjs +24 -41
  68. package/src/adapter/Runtime/AggregateEntryPoint.mjs +52 -45
  69. package/src/adapter/Runtime/AggregateRuntime_Builder_Micro.res +19 -2
  70. package/src/adapter/Runtime/AggregateRuntime_Builder_Micro.res.mjs +8 -5
  71. package/src/adapter/Runtime/AggregateRuntime_Builder_Micro_Async.res +19 -2
  72. package/src/adapter/Runtime/AggregateRuntime_Builder_Micro_Async.res.mjs +8 -5
  73. package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate.res +9 -1
  74. package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate.res.mjs +4 -2
  75. package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate_Async.res +9 -1
  76. package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate_Async.res.mjs +4 -2
  77. package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res +9 -1
  78. package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res.mjs +4 -2
  79. package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res +9 -1
  80. package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res.mjs +4 -2
  81. package/src/adapter/Runtime/AutomationSliceEntryPoint.mjs +36 -22
  82. package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res +1 -0
  83. package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res.mjs +1 -1
  84. package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +39 -42
  85. package/src/adapter/Runtime/EventCollectorRuntime_Builder_PerEventCollector.res +7 -1
  86. package/src/adapter/Runtime/EventCollectorRuntime_Builder_PerEventCollector.res.mjs +4 -2
  87. package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res +19 -1
  88. package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res.mjs +5 -2
  89. package/src/adapter/Runtime/EventMapperEntryPoint.mjs +25 -23
  90. package/src/adapter/Runtime/ExtensionPointEntryPoint.mjs +25 -41
  91. package/src/adapter/Runtime/ExtensionPointRuntime_Builder_PerExtensionPoint.res +1 -0
  92. package/src/adapter/Runtime/ExtensionPointRuntime_Builder_PerExtensionPoint.res.mjs +1 -1
  93. package/src/adapter/Runtime/HandlerFactoryHelpers.mjs +126 -0
  94. package/src/adapter/Runtime/PluginExtensionPointEntryPoint.mjs +24 -41
  95. package/src/adapter/Runtime/ReadModelEntryPoint.mjs +33 -24
  96. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +52 -2
  97. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +25 -7
  98. package/src/adapter/Runtime/SideEffectEntryPoint.mjs +25 -24
  99. package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res +9 -1
  100. package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res.mjs +4 -2
  101. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res +1 -0
  102. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +1 -1
  103. package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +26 -21
  104. package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res +2 -1
  105. package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res.mjs +2 -2
  106. package/src/adapter/Runtime/TaskRuntime_Builder_PerBucket.res +1 -0
  107. package/src/adapter/Runtime/TaskRuntime_Builder_PerBucket.res.mjs +1 -1
  108. package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents.res +12 -0
  109. package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents.res.mjs +4 -2
  110. package/src/adapter/StateTopic/StateTopic_AppSync.res +7 -1
  111. package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +2 -2
  112. package/src/adapter/Task/TaskBucket_S3.res +1 -0
  113. package/src/adapter/Task/TaskBucket_S3.res.mjs +7 -1
  114. package/src/components/Aggregate_Builder_Micro.res.mjs +2 -0
  115. package/src/components/Aggregate_Builder_NoResolver.res.mjs +2 -0
  116. package/src/components/Aggregate_Builder_PerAggregate.res.mjs +2 -0
  117. package/src/components/Aggregate_Builder_Single.res.mjs +2 -0
  118. package/src/components/Aggregate_Builder_Single_Async.res.mjs +2 -0
  119. package/src/components/Api/AppSync_Adapter.res +12 -0
  120. package/src/components/Api/AppSync_Adapter.res.mjs +6 -2
  121. package/src/components/Api/AppSync_MergedApi.res +12 -0
  122. package/src/components/Api/AppSync_MergedApi.res.mjs +5 -2
  123. package/src/components/AutomationSlice_Builder.res.mjs +2 -0
  124. package/src/components/ExtensionPoint_Builder.res.mjs +2 -0
  125. package/src/components/ForeignReadModel_Builder.res.mjs +4 -0
  126. package/src/components/OutboundTranslationSlice_Builder.res.mjs +2 -0
  127. package/src/components/Plugin.res.mjs +2 -0
  128. package/src/components/ReadModel_Builder_NoResolver.res.mjs +2 -0
  129. package/src/components/ReadModel_Builder_NoResolver_Stream.res.mjs +2 -0
  130. package/src/components/ReadModel_Builder_PerReadModel.res.mjs +2 -0
  131. package/src/components/ReadModel_Builder_Single.res.mjs +2 -0
  132. package/src/components/ReadModel_Builder_Single_Stream.res.mjs +2 -0
  133. package/src/components/SideEffectHandler_PerSideEffectHandler.res.mjs +6 -0
  134. package/src/components/SideEffectHandler_Single.res.mjs +4 -0
  135. package/src/components/StateViewSlice_Builder.res.mjs +2 -0
  136. package/src/components/StateViewSlice_Builder_Stream.res.mjs +2 -0
  137. package/src/components/Task_Builder_PerBucket.res.mjs +4 -0
  138. package/src/components/Vpc.res +10 -1
  139. package/src/components/Vpc.res.mjs +3 -1
  140. package/src/plugin/cloner/ClonerRunner_Fargate.res +26 -1
  141. package/src/plugin/cloner/ClonerRunner_Fargate.res.mjs +9 -6
  142. package/src/plugin/heartbeat/HeartbeatRunner_CloudWatchEvents.res +6 -0
  143. package/src/plugin/heartbeat/HeartbeatRunner_CloudWatchEvents.res.mjs +4 -1
  144. package/src/plugin/runtime/PluginExtensionPointRuntime_Builder.res +1 -0
  145. package/src/plugin/runtime/PluginExtensionPointRuntime_Builder.res.mjs +1 -1
  146. package/src/plugin/runtime/PluginRuntime_Builder.res +2 -0
  147. package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +2 -2
  148. package/src/plugin/stack/Plugin_ExtensionPoint_Builder.res.mjs +4 -0
  149. package/src/plugin/stack/Plugin_Stack.res +26 -1
  150. package/src/plugin/stack/Plugin_Stack.res.mjs +8 -3
  151. package/src/util/Util_CognitoGroupUser.res +6 -0
  152. package/src/util/Util_CognitoGroupUser.res.mjs +3 -1
  153. package/src/util/Util_DeadLetterQueue.res +8 -2
  154. package/src/util/Util_DeadLetterQueue.res.mjs +3 -4
  155. package/src/util/Util_LogAttribution.res +31 -0
  156. package/src/util/Util_LogAttribution.res.mjs +27 -0
  157. package/tests/AWS_TagsTest.res +236 -0
  158. package/tests/AWS_TagsTest.res.mjs +153 -0
  159. package/tests/AppSync_SourceApiAssociation_RetryingTest.res +26 -0
  160. package/tests/AppSync_SourceApiAssociation_RetryingTest.res.mjs +20 -0
  161. package/tests/HandlerFactoryDispatchTest.res +190 -0
  162. package/tests/HandlerFactoryDispatchTest.res.mjs +135 -0
  163. package/tests/integration/AggregateEntryPoint_IntegrationTest.res +5 -3
  164. package/tests/integration/AggregateEntryPoint_IntegrationTest.res.mjs +5 -3
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as Aws from "@pulumi/aws";
4
4
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
5
+ import * as AWS_Tags$ReventlessAws from "../adapter/AWS_Tags.res.mjs";
5
6
  import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
6
7
 
7
8
  function addUserGroup(name, userPoolId) {
@@ -21,7 +22,8 @@ function makeAddRemoveUserToGroupPolicy(name, userPoolArn, opts) {
21
22
  "cognito-idp:AdminRemoveUserFromGroup"
22
23
  ],
23
24
  Resource: userPoolArn
24
- }]))
25
+ }])),
26
+ tags: AWS_Tags$ReventlessAws.make(name + "AddRemoveUserToGroup", "Platform", "Identity", "Platform", undefined, undefined, undefined, undefined)
25
27
  }, opts !== undefined ? Primitive_option.valFromOption(opts) : undefined));
26
28
  }
27
29
 
@@ -26,6 +26,12 @@ let opts = {Pulumi.CustomResourceOptions.parent: queue->PulumiAws.SQS.Queue.toRe
26
26
  let lambdaRole = IAM.Role.makeWithDefaultPolicy(
27
27
  ~name,
28
28
  ~servicePrincipal=AWS.Lambda.principal->Pulumi.Output.make,
29
+ ~tags=AWS.Tags.make(
30
+ ~name,
31
+ ~kind=ReventlessCore.ComponentType.Plugin,
32
+ ~role=Identity,
33
+ ~scope=Plugin,
34
+ ),
29
35
  ~opts,
30
36
  )
31
37
 
@@ -58,7 +64,7 @@ let handler = Lambda.Function.make(
58
64
  memorySize: 128->Pulumi.Input.make,
59
65
  timeout: 30->Pulumi.Input.make,
60
66
  layers,
61
- tags: AWS.Tags.make(~name, ReventlessCore.CommandTopic.componentType),
67
+ tags: AWS.Tags.make(~name, ~kind=ReventlessCore.ComponentType.Plugin, ~role=DeadLetter, ~scope=Plugin),
62
68
  environment: (
63
69
  {
64
70
  Lambda.Function.variables: Dict.fromArray([
@@ -77,7 +83,7 @@ ReventlessCore.Monitoring.notify(
77
83
  ~name,
78
84
  ~component=Util_Lambda.functionToResource(
79
85
  handler,
80
- ~tags=AWS.Tags.make(~name, ReventlessCore.CommandTopic.componentType)->Pulumi.Output.fromInput,
86
+ ~tags=AWS.Tags.make(~name, ~kind=ReventlessCore.ComponentType.Plugin, ~role=DeadLetter, ~scope=Plugin)->Pulumi.Output.fromInput,
81
87
  ),
82
88
  )
83
89
 
@@ -11,7 +11,6 @@ import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/sr
11
11
  import * as Monitoring$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Monitoring/Monitoring.res.mjs";
12
12
  import * as Util_Bundle$ReventlessAws from "./Util_Bundle.res.mjs";
13
13
  import * as Util_Lambda$ReventlessAws from "./Util_Lambda.res.mjs";
14
- import * as CommandTopic$ReventlessCore from "@reventlessdev/reventless-core/src/components/CommandTopic/CommandTopic.res.mjs";
15
14
  import * as Util_EventSourceMapping$ReventlessAws from "./Util_EventSourceMapping.res.mjs";
16
15
 
17
16
  let name = "DeadLetterQueue";
@@ -36,7 +35,7 @@ let opts = {
36
35
  parent: opts_parent
37
36
  };
38
37
 
39
- let lambdaRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name, Pulumi.output(AWS$ReventlessAws.Lambda.principal), opts);
38
+ let lambdaRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name, Pulumi.output(AWS$ReventlessAws.Lambda.principal), AWS_Tags$ReventlessAws.make(name, "Plugin", "Identity", "Plugin", undefined, undefined, undefined, undefined), opts);
40
39
 
41
40
  let entryPointCode = `export const handler = async (event) => {
42
41
  console.error("DEAD LETTER ITEM:", JSON.stringify(event));
@@ -60,7 +59,7 @@ let handler = new (Aws.lambda.Function)(name, {
60
59
  memorySize: 128,
61
60
  timeout: 30,
62
61
  layers: layers,
63
- tags: AWS_Tags$ReventlessAws.make(name, CommandTopic$ReventlessCore.componentType),
62
+ tags: AWS_Tags$ReventlessAws.make(name, "Plugin", "DeadLetter", "Plugin", undefined, undefined, undefined, undefined),
64
63
  environment: {
65
64
  variables: Object.fromEntries([[
66
65
  "Environment",
@@ -70,7 +69,7 @@ let handler = new (Aws.lambda.Function)(name, {
70
69
  sourceCodeHash: sourceCodeHash
71
70
  }, opts);
72
71
 
73
- Monitoring$ReventlessCore.notify("DeadLetterSink", name, Util_Lambda$ReventlessAws.functionToResource(AWS_Tags$ReventlessAws.make(name, CommandTopic$ReventlessCore.componentType), handler));
72
+ Monitoring$ReventlessCore.notify("DeadLetterSink", name, Util_Lambda$ReventlessAws.functionToResource(AWS_Tags$ReventlessAws.make(name, "Plugin", "DeadLetter", "Plugin", undefined, undefined, undefined, undefined), handler));
74
73
 
75
74
  let lambda = Pulumi.output(handler);
76
75
 
@@ -0,0 +1,31 @@
1
+ // Deploy-time log attribution baked into HANDLER_CONFIG.
2
+ //
3
+ // The deployed entry points annotate each invocation with `comp` and `plugin`
4
+ // (HandlerFactoryHelpers.runEffect). `comp` a shell can usually derive itself
5
+ // from the spec name it loads; `plugin` it cannot — resolution goes through
6
+ // LogPrefix's component→plugin registry, which `Plugin_Builder.construct`
7
+ // populates at **deploy** time and which is empty inside a Lambda. And the
8
+ // Lambda-name fallback (`derivePluginFromLambdaName`) only works for Lambdas
9
+ // named `<Plugin>Plugin<Suffix>`: it yields nothing for a shared multi-plugin
10
+ // Lambda (`AllAggregatesCmdHandler`, `AllReadModels`) or a per-component one
11
+ // (`<Aggregate>Aggr`) — exactly the cases where the field matters most.
12
+ //
13
+ // So resolve it here, where the registry exists, and ship it in the config.
14
+ // See docs/plans/entrypoint-dispatch-parity-and-latency-fields.md.
15
+
16
+ /** `,"plugin":"<Name>"` for a HANDLER_CONFIG entry, or `""` when the comp
17
+ resolves to no plugin (the shell then falls back to the Lambda name). */
18
+ let pluginFragment = (~comp: string): string =>
19
+ switch ReventlessCore.Logger.resolvePlugin(~comp, ()) {
20
+ | Some(plugin) => `,"plugin":${plugin->JSON.Encode.string->JSON.stringify}`
21
+ | None => ""
22
+ }
23
+
24
+ /** `,"comp":"<Kind(Name)>"` for a HANDLER_CONFIG entry. For the shells that
25
+ cannot name the element themselves — a read model or side-effect handler is
26
+ known to its shell only by module path, while the aggregate/slice shells read
27
+ the name off the spec they load. */
28
+ let compFragment = (~comp: string): string => `,"comp":${comp->JSON.Encode.string->JSON.stringify}`
29
+
30
+ /** Both fragments, in the order the entry points expect. */
31
+ let fragments = (~comp: string): string => compFragment(~comp) ++ pluginFragment(~comp)
@@ -0,0 +1,27 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
4
+
5
+ function pluginFragment(comp) {
6
+ let plugin = Logger$ReventlessCore.resolvePlugin(comp, undefined);
7
+ if (plugin !== undefined) {
8
+ return `,"plugin":` + JSON.stringify(plugin);
9
+ } else {
10
+ return "";
11
+ }
12
+ }
13
+
14
+ function compFragment(comp) {
15
+ return `,"comp":` + JSON.stringify(comp);
16
+ }
17
+
18
+ function fragments(comp) {
19
+ return compFragment(comp) + pluginFragment(comp);
20
+ }
21
+
22
+ export {
23
+ pluginFragment,
24
+ compFragment,
25
+ fragments,
26
+ }
27
+ /* Logger-ReventlessCore Not a pure module */
@@ -0,0 +1,236 @@
1
+ open JestGlobals
2
+
3
+ // Guards the resource-attribution tag schema. A downstream inventory answers
4
+ // "which model component owns this resource, in what role, at what scope" purely
5
+ // from these keys, so the key set and its namespacing are load-bearing.
6
+ // See docs/plans/resource-attribution-tag-schema.md.
7
+
8
+ module Attribution = ReventlessCore.ResourceAttribution
9
+
10
+ // Pulumi.Input.make is %identity, so the tags value is the underlying dict.
11
+ external toDict: Pulumi.Input.t<dict<string>> => dict<string> = "%identity"
12
+
13
+ let tagsFor = (
14
+ ~name,
15
+ ~kind,
16
+ ~role,
17
+ ~scope: Attribution.Scope.t=Component,
18
+ ~component=?,
19
+ ~plugin=?,
20
+ ~platform=?,
21
+ ) =>
22
+ AWS_Tags.make(~name, ~kind, ~role, ~scope, ~component?, ~plugin?, ~platform?)->toDict
23
+
24
+ describe("AWS_Tags — key set", () => {
25
+ testSync("emits every reventless: fact exactly once, plus the bare Name", () => {
26
+ let keys =
27
+ tagsFor(
28
+ ~name="ProductsQueryDb",
29
+ ~kind=ReventlessCore.ComponentType.ReadModel,
30
+ ~role=QueryDb,
31
+ ~plugin="Catalog",
32
+ ~platform="online-shop",
33
+ )
34
+ ->Dict.keysToArray
35
+ ->Array.toSorted(String.compare)
36
+ expect(keys)->toEqual([
37
+ "Name",
38
+ "reventless:component",
39
+ "reventless:environment",
40
+ "reventless:kind",
41
+ "reventless:platform",
42
+ "reventless:plugin",
43
+ "reventless:role",
44
+ "reventless:scope",
45
+ ])
46
+ })
47
+
48
+ testSync("the dropped bare duplicates stay dropped", () => {
49
+ // `Type` and `Plugin` were exact duplicates of reventless:kind / :plugin, and
50
+ // `Environment` was a framework fact wearing an un-namespaced name. One
51
+ // authoritative name per fact — `Name` is the sole bare key, because the AWS
52
+ // console reads that literal spelling for its resource-name column.
53
+ let tags = tagsFor(~name="Products", ~kind=ReventlessCore.ComponentType.ReadModel, ~role=QueryDb)
54
+ expect(tags->Dict.get("Type"))->toEqual(None)
55
+ expect(tags->Dict.get("Plugin"))->toEqual(None)
56
+ expect(tags->Dict.get("Environment"))->toEqual(None)
57
+ expect(tags->Dict.get("Name"))->toEqual(Some("Products"))
58
+ })
59
+ })
60
+
61
+ describe("AWS_Tags — role and kind are independent facts", () => {
62
+ testSync("one component kind spans several piece roles", () => {
63
+ let kind = ReventlessCore.ComponentType.Aggregate
64
+ let eventLog = tagsFor(~name="OrderEventLog", ~kind, ~role=EventLog)
65
+ let commandTopic = tagsFor(~name="OrderCmdTopic", ~kind, ~role=CommandTopic)
66
+ expect(eventLog->Dict.get("reventless:kind"))->toEqual(Some("Aggregate"))
67
+ expect(commandTopic->Dict.get("reventless:kind"))->toEqual(Some("Aggregate"))
68
+ expect(eventLog->Dict.get("reventless:role"))->toEqual(Some("EventLog"))
69
+ expect(commandTopic->Dict.get("reventless:role"))->toEqual(Some("CommandTopic"))
70
+ })
71
+
72
+ testSync("one piece role spans several component kinds — the Lambda mis-tag", () => {
73
+ // Regression guard for the defect this schema exists to fix: every runtime
74
+ // Lambda used to report kind=role=CommandTopic regardless of what it ran.
75
+ let aggregate = tagsFor(
76
+ ~name="AllAggregatesCmdHandler",
77
+ ~kind=ReventlessCore.ComponentType.Aggregate,
78
+ ~role=Runtime,
79
+ )
80
+ let projection = tagsFor(
81
+ ~name="ProductsProjection",
82
+ ~kind=ReventlessCore.ComponentType.StateViewSlice,
83
+ ~role=Runtime,
84
+ )
85
+ expect(aggregate->Dict.get("reventless:role"))->toEqual(Some("Runtime"))
86
+ expect(projection->Dict.get("reventless:role"))->toEqual(Some("Runtime"))
87
+ expect(aggregate->Dict.get("reventless:kind"))->toEqual(Some("Aggregate"))
88
+ expect(projection->Dict.get("reventless:kind"))->toEqual(Some("StateViewSlice"))
89
+ // …and neither reports the old hardcoded lie.
90
+ expect(aggregate->Dict.get("reventless:kind") == Some("CommandTopic"))->toBe(false)
91
+ })
92
+ })
93
+
94
+ describe("AWS_Tags — owner overrides the piece adapter's own kind", () => {
95
+ // The piece adapters pass their OWN ComponentType as ~kind, which names the
96
+ // piece and so collapses onto ~role. A QueryDb is instantiated by six different
97
+ // component kinds, so without the owner a reader cannot tell a ReadModel's
98
+ // table from a StateViewSlice's. See docs/plans/resource-attribution-owner-kind.md.
99
+ let queryDbTagsFor = (~owner=?) =>
100
+ AWS_Tags.make(
101
+ ~name="ProductsQueryDb",
102
+ ~kind=ReventlessCore.ComponentType.QueryDb,
103
+ ~role=QueryDb,
104
+ ~owner?,
105
+ )->toDict
106
+
107
+ testSync("without an owner, kind still names the piece", () => {
108
+ let tags = queryDbTagsFor()
109
+ expect(tags->Dict.get("reventless:kind"))->toEqual(Some("QueryDB"))
110
+ expect(tags->Dict.get("reventless:role"))->toEqual(Some("QueryDb"))
111
+ })
112
+
113
+ testSync("two QueryDb tables under different owners differ in kind", () => {
114
+ let readModel = queryDbTagsFor(
115
+ ~owner={kind: ReventlessCore.ComponentType.ReadModel, name: "Products"},
116
+ )
117
+ let stateView = queryDbTagsFor(
118
+ ~owner={kind: ReventlessCore.ComponentType.StateViewSlice, name: "ProductAvailability"},
119
+ )
120
+ expect(readModel->Dict.get("reventless:kind"))->toEqual(Some("ReadModel"))
121
+ expect(stateView->Dict.get("reventless:kind"))->toEqual(Some("StateViewSlice"))
122
+ // …while both still report the same piece role.
123
+ expect(readModel->Dict.get("reventless:role"))->toEqual(Some("QueryDb"))
124
+ expect(stateView->Dict.get("reventless:role"))->toEqual(Some("QueryDb"))
125
+ })
126
+
127
+ testSync("the owner also supplies the component stem, not the resource name", () => {
128
+ let tags = queryDbTagsFor(
129
+ ~owner={kind: ReventlessCore.ComponentType.ReadModel, name: "Products"},
130
+ )
131
+ expect(tags->Dict.get("Name"))->toEqual(Some("ProductsQueryDb"))
132
+ expect(tags->Dict.get("reventless:component"))->toEqual(Some("Products"))
133
+ })
134
+
135
+ testSync("a Plugin-kinded owner attributes substrate to the plugin", () => {
136
+ // A plugin IS a model element, so shared substrate is owned by it rather than
137
+ // by nothing. The owner implies plugin scope even though the piece adapter
138
+ // passed no ~scope, and it names the plugin.
139
+ let tags =
140
+ AWS_Tags.make(
141
+ ~name="OrderingDcbEventTopic",
142
+ ~kind=ReventlessCore.ComponentType.EventTopic,
143
+ ~role=EventTopic,
144
+ ~owner={kind: ReventlessCore.ComponentType.Plugin, name: "Ordering"},
145
+ )->toDict
146
+ expect(tags->Dict.get("reventless:kind"))->toEqual(Some("Plugin"))
147
+ expect(tags->Dict.get("reventless:scope"))->toEqual(Some("plugin"))
148
+ expect(tags->Dict.get("reventless:plugin"))->toEqual(Some("Ordering"))
149
+ expect(tags->Dict.get("reventless:component"))->toEqual(Some(""))
150
+ // …and the piece it plays is still legible.
151
+ expect(tags->Dict.get("reventless:role"))->toEqual(Some("EventTopic"))
152
+ })
153
+
154
+ testSync("an owner does not leak a component onto plugin substrate", () => {
155
+ let tags =
156
+ AWS_Tags.make(
157
+ ~name="OrderingDcbEventLog",
158
+ ~kind=ReventlessCore.ComponentType.DcbEventLog,
159
+ ~role=DcbEventLog,
160
+ ~scope=Plugin,
161
+ ~owner={kind: ReventlessCore.ComponentType.Aggregate, name: "Order"},
162
+ )->toDict
163
+ expect(tags->Dict.get("reventless:component"))->toEqual(Some(""))
164
+ })
165
+ })
166
+
167
+ describe("AWS_Tags — scope", () => {
168
+ testSync("component scope names the component, defaulting to the resource name", () => {
169
+ let tags = tagsFor(~name="Products", ~kind=ReventlessCore.ComponentType.ReadModel, ~role=QueryDb)
170
+ expect(tags->Dict.get("reventless:scope"))->toEqual(Some("component"))
171
+ expect(tags->Dict.get("reventless:component"))->toEqual(Some("Products"))
172
+ })
173
+
174
+ testSync("an explicit component stem survives a suffixed resource name", () => {
175
+ let tags = tagsFor(
176
+ ~name="ProductsStateTopic",
177
+ ~kind=ReventlessCore.ComponentType.ReadModel,
178
+ ~role=StateTopic,
179
+ ~component="Products",
180
+ )
181
+ expect(tags->Dict.get("Name"))->toEqual(Some("ProductsStateTopic"))
182
+ expect(tags->Dict.get("reventless:component"))->toEqual(Some("Products"))
183
+ })
184
+
185
+ testSync("plugin substrate carries no component, but keeps its plugin", () => {
186
+ let tags = tagsFor(
187
+ ~name="OrderingDcbEventLog",
188
+ ~kind=ReventlessCore.ComponentType.DcbEventLog,
189
+ ~role=DcbEventLog,
190
+ ~scope=Plugin,
191
+ ~plugin="Ordering",
192
+ )
193
+ expect(tags->Dict.get("reventless:scope"))->toEqual(Some("plugin"))
194
+ expect(tags->Dict.get("reventless:component"))->toEqual(Some(""))
195
+ expect(tags->Dict.get("reventless:plugin"))->toEqual(Some("Ordering"))
196
+ })
197
+
198
+ testSync("platform substrate carries neither component nor plugin", () => {
199
+ let tags = tagsFor(
200
+ ~name="UIFragmentsLambda",
201
+ ~kind=ReventlessCore.ComponentType.ReadModel,
202
+ ~role=Runtime,
203
+ ~scope=Platform,
204
+ ~plugin="Ordering",
205
+ ~platform="online-shop",
206
+ )
207
+ expect(tags->Dict.get("reventless:scope"))->toEqual(Some("platform"))
208
+ expect(tags->Dict.get("reventless:component"))->toEqual(Some(""))
209
+ expect(tags->Dict.get("reventless:plugin"))->toEqual(Some(""))
210
+ expect(tags->Dict.get("reventless:platform"))->toEqual(Some("online-shop"))
211
+ })
212
+ })
213
+
214
+ describe("AWS_Tags — plugin attribution", () => {
215
+ testSync("falls back to the ambient plugin published by Plugin_Builder", () => {
216
+ // Adapters sit far below the builder that knows the plugin name, so the
217
+ // ambient context is how a DynamoDB table learns it belongs to Ordering.
218
+ let previous = Attribution.enter(~platform="online-shop", ~plugin="Ordering")
219
+ let tags = tagsFor(~name="OrderEventLog", ~kind=ReventlessCore.ComponentType.Aggregate, ~role=EventLog)
220
+ Attribution.restore(previous)
221
+ expect(tags->Dict.get("reventless:plugin"))->toEqual(Some("Ordering"))
222
+ expect(tags->Dict.get("reventless:platform"))->toEqual(Some("online-shop"))
223
+ })
224
+
225
+ testSync("an explicit plugin beats the ambient one", () => {
226
+ let previous = Attribution.enter(~platform="online-shop", ~plugin="Ordering")
227
+ let tags = tagsFor(
228
+ ~name="CatalogTable",
229
+ ~kind=ReventlessCore.ComponentType.ReadModel,
230
+ ~role=QueryDb,
231
+ ~plugin="Catalog",
232
+ )
233
+ Attribution.restore(previous)
234
+ expect(tags->Dict.get("reventless:plugin"))->toEqual(Some("Catalog"))
235
+ })
236
+ })
@@ -0,0 +1,153 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
4
+ import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
5
+ import * as AWS_Tags$ReventlessAws from "../src/adapter/AWS_Tags.res.mjs";
6
+ import * as ResourceAttribution$ReventlessCore from "@reventlessdev/reventless-core/src/ResourceAttribution.res.mjs";
7
+
8
+ function tagsFor(name, kind, role, scopeOpt, component, plugin, platform) {
9
+ let scope = scopeOpt !== undefined ? scopeOpt : "Component";
10
+ return AWS_Tags$ReventlessAws.make(name, kind, role, scope, component, undefined, plugin, platform);
11
+ }
12
+
13
+ globalThis.describe("AWS_Tags — key set", () => {
14
+ globalThis.test("emits every reventless: fact exactly once, plus the bare Name", () => {
15
+ let keys = Object.keys(tagsFor("ProductsQueryDb", "ReadModel", "QueryDb", undefined, undefined, "Catalog", "online-shop")).toSorted(Primitive_string.compare);
16
+ globalThis.expect(keys).toEqual([
17
+ "Name",
18
+ "reventless:component",
19
+ "reventless:environment",
20
+ "reventless:kind",
21
+ "reventless:platform",
22
+ "reventless:plugin",
23
+ "reventless:role",
24
+ "reventless:scope"
25
+ ]);
26
+ });
27
+ globalThis.test("the dropped bare duplicates stay dropped", () => {
28
+ let tags = tagsFor("Products", "ReadModel", "QueryDb", undefined, undefined, undefined, undefined);
29
+ globalThis.expect(tags["Type"]).toEqual(undefined);
30
+ globalThis.expect(tags["Plugin"]).toEqual(undefined);
31
+ globalThis.expect(tags["Environment"]).toEqual(undefined);
32
+ globalThis.expect(tags["Name"]).toEqual("Products");
33
+ });
34
+ });
35
+
36
+ globalThis.describe("AWS_Tags — role and kind are independent facts", () => {
37
+ globalThis.test("one component kind spans several piece roles", () => {
38
+ let eventLog = tagsFor("OrderEventLog", "Aggregate", "EventLog", undefined, undefined, undefined, undefined);
39
+ let commandTopic = tagsFor("OrderCmdTopic", "Aggregate", "CommandTopic", undefined, undefined, undefined, undefined);
40
+ globalThis.expect(eventLog["reventless:kind"]).toEqual("Aggregate");
41
+ globalThis.expect(commandTopic["reventless:kind"]).toEqual("Aggregate");
42
+ globalThis.expect(eventLog["reventless:role"]).toEqual("EventLog");
43
+ globalThis.expect(commandTopic["reventless:role"]).toEqual("CommandTopic");
44
+ });
45
+ globalThis.test("one piece role spans several component kinds — the Lambda mis-tag", () => {
46
+ let aggregate = tagsFor("AllAggregatesCmdHandler", "Aggregate", "Runtime", undefined, undefined, undefined, undefined);
47
+ let projection = tagsFor("ProductsProjection", "StateViewSlice", "Runtime", undefined, undefined, undefined, undefined);
48
+ globalThis.expect(aggregate["reventless:role"]).toEqual("Runtime");
49
+ globalThis.expect(projection["reventless:role"]).toEqual("Runtime");
50
+ globalThis.expect(aggregate["reventless:kind"]).toEqual("Aggregate");
51
+ globalThis.expect(projection["reventless:kind"]).toEqual("StateViewSlice");
52
+ globalThis.expect(Primitive_object.equal(aggregate["reventless:kind"], "CommandTopic")).toBe(false);
53
+ });
54
+ });
55
+
56
+ globalThis.describe("AWS_Tags — owner overrides the piece adapter's own kind", () => {
57
+ let queryDbTagsFor = owner => AWS_Tags$ReventlessAws.make("ProductsQueryDb", "QueryDb", "QueryDb", undefined, undefined, owner, undefined, undefined);
58
+ globalThis.test("without an owner, kind still names the piece", () => {
59
+ let tags = queryDbTagsFor(undefined);
60
+ globalThis.expect(tags["reventless:kind"]).toEqual("QueryDB");
61
+ globalThis.expect(tags["reventless:role"]).toEqual("QueryDb");
62
+ });
63
+ globalThis.test("two QueryDb tables under different owners differ in kind", () => {
64
+ let readModel = queryDbTagsFor({
65
+ kind: "ReadModel",
66
+ name: "Products"
67
+ });
68
+ let stateView = queryDbTagsFor({
69
+ kind: "StateViewSlice",
70
+ name: "ProductAvailability"
71
+ });
72
+ globalThis.expect(readModel["reventless:kind"]).toEqual("ReadModel");
73
+ globalThis.expect(stateView["reventless:kind"]).toEqual("StateViewSlice");
74
+ globalThis.expect(readModel["reventless:role"]).toEqual("QueryDb");
75
+ globalThis.expect(stateView["reventless:role"]).toEqual("QueryDb");
76
+ });
77
+ globalThis.test("the owner also supplies the component stem, not the resource name", () => {
78
+ let tags = queryDbTagsFor({
79
+ kind: "ReadModel",
80
+ name: "Products"
81
+ });
82
+ globalThis.expect(tags["Name"]).toEqual("ProductsQueryDb");
83
+ globalThis.expect(tags["reventless:component"]).toEqual("Products");
84
+ });
85
+ globalThis.test("a Plugin-kinded owner attributes substrate to the plugin", () => {
86
+ let tags = AWS_Tags$ReventlessAws.make("OrderingDcbEventTopic", "EventTopic", "EventTopic", undefined, undefined, {
87
+ kind: "Plugin",
88
+ name: "Ordering"
89
+ }, undefined, undefined);
90
+ globalThis.expect(tags["reventless:kind"]).toEqual("Plugin");
91
+ globalThis.expect(tags["reventless:scope"]).toEqual("plugin");
92
+ globalThis.expect(tags["reventless:plugin"]).toEqual("Ordering");
93
+ globalThis.expect(tags["reventless:component"]).toEqual("");
94
+ globalThis.expect(tags["reventless:role"]).toEqual("EventTopic");
95
+ });
96
+ globalThis.test("an owner does not leak a component onto plugin substrate", () => {
97
+ let tags = AWS_Tags$ReventlessAws.make("OrderingDcbEventLog", "DcbEventLog", "DcbEventLog", "Plugin", undefined, {
98
+ kind: "Aggregate",
99
+ name: "Order"
100
+ }, undefined, undefined);
101
+ globalThis.expect(tags["reventless:component"]).toEqual("");
102
+ });
103
+ });
104
+
105
+ globalThis.describe("AWS_Tags — scope", () => {
106
+ globalThis.test("component scope names the component, defaulting to the resource name", () => {
107
+ let tags = tagsFor("Products", "ReadModel", "QueryDb", undefined, undefined, undefined, undefined);
108
+ globalThis.expect(tags["reventless:scope"]).toEqual("component");
109
+ globalThis.expect(tags["reventless:component"]).toEqual("Products");
110
+ });
111
+ globalThis.test("an explicit component stem survives a suffixed resource name", () => {
112
+ let tags = tagsFor("ProductsStateTopic", "ReadModel", "StateTopic", undefined, "Products", undefined, undefined);
113
+ globalThis.expect(tags["Name"]).toEqual("ProductsStateTopic");
114
+ globalThis.expect(tags["reventless:component"]).toEqual("Products");
115
+ });
116
+ globalThis.test("plugin substrate carries no component, but keeps its plugin", () => {
117
+ let tags = tagsFor("OrderingDcbEventLog", "DcbEventLog", "DcbEventLog", "Plugin", undefined, "Ordering", undefined);
118
+ globalThis.expect(tags["reventless:scope"]).toEqual("plugin");
119
+ globalThis.expect(tags["reventless:component"]).toEqual("");
120
+ globalThis.expect(tags["reventless:plugin"]).toEqual("Ordering");
121
+ });
122
+ globalThis.test("platform substrate carries neither component nor plugin", () => {
123
+ let tags = tagsFor("UIFragmentsLambda", "ReadModel", "Runtime", "Platform", undefined, "Ordering", "online-shop");
124
+ globalThis.expect(tags["reventless:scope"]).toEqual("platform");
125
+ globalThis.expect(tags["reventless:component"]).toEqual("");
126
+ globalThis.expect(tags["reventless:plugin"]).toEqual("");
127
+ globalThis.expect(tags["reventless:platform"]).toEqual("online-shop");
128
+ });
129
+ });
130
+
131
+ globalThis.describe("AWS_Tags — plugin attribution", () => {
132
+ globalThis.test("falls back to the ambient plugin published by Plugin_Builder", () => {
133
+ let previous = ResourceAttribution$ReventlessCore.enter("online-shop", "Ordering");
134
+ let tags = tagsFor("OrderEventLog", "Aggregate", "EventLog", undefined, undefined, undefined, undefined);
135
+ ResourceAttribution$ReventlessCore.restore(previous);
136
+ globalThis.expect(tags["reventless:plugin"]).toEqual("Ordering");
137
+ globalThis.expect(tags["reventless:platform"]).toEqual("online-shop");
138
+ });
139
+ globalThis.test("an explicit plugin beats the ambient one", () => {
140
+ let previous = ResourceAttribution$ReventlessCore.enter("online-shop", "Ordering");
141
+ let tags = tagsFor("CatalogTable", "ReadModel", "QueryDb", undefined, undefined, "Catalog", undefined);
142
+ ResourceAttribution$ReventlessCore.restore(previous);
143
+ globalThis.expect(tags["reventless:plugin"]).toEqual("Catalog");
144
+ });
145
+ });
146
+
147
+ let Attribution;
148
+
149
+ export {
150
+ Attribution,
151
+ tagsFor,
152
+ }
153
+ /* Not a pure module */
@@ -110,3 +110,29 @@ describe("AppSync_SourceApiAssociation_Retrying.idsFromComposite", () => {
110
110
  expect(Assoc.idsFromComposite("just-one-value")->Option.isNone)->toBe(true)
111
111
  })
112
112
  })
113
+
114
+ // pulumi.dynamic.Resource defines a resource property per KEY of the props
115
+ // object, so the output-only fields must be present (with an undefined value)
116
+ // or `association.associationId` is a bare `undefined` and the merge gate
117
+ // calls GetSourceApiAssociation without an id.
118
+ describe("AppSync_SourceApiAssociation_Retrying.resourcePropsOf", () => {
119
+ let keysOf: Assoc.resourceProps => array<string> = p => Object.keysToArray(Obj.magic(p))
120
+ let props = Assoc.resourcePropsOf({
121
+ mergedApiIdentifier: Obj.magic("merged-arn"),
122
+ sourceApiIdentifier: Obj.magic("src123"),
123
+ mergeType: Obj.magic("AUTO_MERGE"),
124
+ })
125
+
126
+ testSync("declares arn + associationId as output-only keys", () => {
127
+ expect(props->keysOf->Array.includes("arn"))->toBe(true)
128
+ expect(props->keysOf->Array.includes("associationId"))->toBe(true)
129
+ expect(props.arn->Nullable.toOption->Option.isNone)->toBe(true)
130
+ expect(props.associationId->Nullable.toOption->Option.isNone)->toBe(true)
131
+ })
132
+
133
+ testSync("passes the three real inputs through", () => {
134
+ expect((props.mergedApiIdentifier->Obj.magic: string))->toBe("merged-arn")
135
+ expect((props.sourceApiIdentifier->Obj.magic: string))->toBe("src123")
136
+ expect((props.mergeType->Obj.magic: string))->toBe("AUTO_MERGE")
137
+ })
138
+ })
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as JestGlobals from "@reventlessdev/rescript-jest/src/JestGlobals.res.mjs";
4
4
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
5
6
  import * as AppSync_SourceApiAssociation_Retrying$ReventlessAws from "../src/adapter/Api/AppSync_SourceApiAssociation_Retrying.res.mjs";
6
7
 
7
8
  function mkErr(name, message) {
@@ -124,6 +125,25 @@ globalThis.describe("AppSync_SourceApiAssociation_Retrying.idsFromComposite", ()
124
125
  });
125
126
  });
126
127
 
128
+ globalThis.describe("AppSync_SourceApiAssociation_Retrying.resourcePropsOf", () => {
129
+ let props = AppSync_SourceApiAssociation_Retrying$ReventlessAws.resourcePropsOf({
130
+ mergedApiIdentifier: "merged-arn",
131
+ sourceApiIdentifier: "src123",
132
+ mergeType: "AUTO_MERGE"
133
+ });
134
+ globalThis.test("declares arn + associationId as output-only keys", () => {
135
+ globalThis.expect(Object.keys(props).includes("arn")).toBe(true);
136
+ globalThis.expect(Object.keys(props).includes("associationId")).toBe(true);
137
+ globalThis.expect(Stdlib_Option.isNone(Primitive_option.fromNullable(props.arn))).toBe(true);
138
+ globalThis.expect(Stdlib_Option.isNone(Primitive_option.fromNullable(props.associationId))).toBe(true);
139
+ });
140
+ globalThis.test("passes the three real inputs through", () => {
141
+ globalThis.expect(props.mergedApiIdentifier).toBe("merged-arn");
142
+ globalThis.expect(props.sourceApiIdentifier).toBe("src123");
143
+ globalThis.expect(props.mergeType).toBe("AUTO_MERGE");
144
+ });
145
+ });
146
+
127
147
  let Assoc;
128
148
 
129
149
  export {