@reventlessdev/reventless-aws 3.0.0-alpha.272 → 3.0.0-alpha.274

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 (36) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/package.json +8 -7
  3. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +15 -13
  4. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +3 -2
  5. package/src/adapter/Api/Platform_UIFragments_Lambda.res +15 -13
  6. package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +3 -2
  7. package/src/adapter/EventCollector/EventCollectorChannel_Helpers.res +8 -1
  8. package/src/adapter/EventCollector/EventCollectorChannel_Helpers.res.mjs +3 -2
  9. package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res +15 -13
  10. package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res.mjs +3 -2
  11. package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res +6 -12
  12. package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs +4 -8
  13. package/src/adapter/QueryDb/QueryInterceptor_Provisioning.res +155 -0
  14. package/src/adapter/QueryDb/QueryInterceptor_Provisioning.res.mjs +92 -0
  15. package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res +4 -9
  16. package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res.mjs +8 -2
  17. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +32 -62
  18. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +7 -20
  19. package/src/adapter/StateTopic/StateTopic_AppSync.res +15 -13
  20. package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +3 -2
  21. package/src/adapter/Upload/Upload_Claim_S3.res +15 -13
  22. package/src/adapter/Upload/Upload_Claim_S3.res.mjs +3 -2
  23. package/src/adapter/Upload/Upload_Presign_S3.res +15 -13
  24. package/src/adapter/Upload/Upload_Presign_S3.res.mjs +3 -2
  25. package/src/components/Api/AppSync_Adapter.res +13 -9
  26. package/src/components/Api/AppSync_Adapter.res.mjs +4 -3
  27. package/src/util/Util_LambdaLogging.res +83 -21
  28. package/src/util/Util_LambdaLogging.res.mjs +21 -8
  29. package/src/util/Util_LogGroup_Adopting.res +480 -0
  30. package/src/util/Util_LogGroup_Adopting.res.mjs +365 -0
  31. package/src/util/Util_LogGroup_Adopting_Js.mjs +20 -0
  32. package/src/util/Util_LogRetention.res +11 -12
  33. package/tests/Util_LambdaLoggingTest.res +48 -0
  34. package/tests/Util_LambdaLoggingTest.res.mjs +30 -0
  35. package/tests/Util_LogGroup_AdoptingTest.res +169 -0
  36. package/tests/Util_LogGroup_AdoptingTest.res.mjs +208 -0
package/CHANGELOG.md CHANGED
@@ -3,6 +3,25 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 3.0.0-alpha.274 (2026-08-08)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **aws:** scope a managed log group by project as well as stack ([f3ea01a](https://github.com/ReventlessDev/reventless-core/commit/f3ea01a254465012becf6a27b401f10382439864))
11
+
12
+
13
+ # 3.0.0-alpha.273 (2026-08-08)
14
+
15
+ ### Bug Fixes
16
+
17
+ * **aws:** create a Lambda's log group before the function that writes to it ([8d1e459](https://github.com/ReventlessDev/reventless-core/commit/8d1e4591127489d2ae8e84e7ade3843a1a859eb6))
18
+ * **core:** attribute resources built after a builder's construct returns ([67d6d9c](https://github.com/ReventlessDev/reventless-core/commit/67d6d9c0ff3ff1d38cd9e8ad43a4b86121abf827))
19
+ ### Features
20
+
21
+ * **aws:** adopt an existing AppSync log group instead of failing on it ([ebed6c1](https://github.com/ReventlessDev/reventless-core/commit/ebed6c1ceb77b8deebff10a9c45715c107861f04))
22
+ * **core:** let an extension switch query interception on, and provision it ([4523b10](https://github.com/ReventlessDev/reventless-core/commit/4523b10597f1a028eedccc42391b250b25a6ddd3))
23
+
24
+
6
25
  # 3.0.0-alpha.272 (2026-08-08)
7
26
 
8
27
  ### Bug Fixes
package/package.json CHANGED
@@ -1,28 +1,29 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-aws",
3
- "version": "3.0.0-alpha.272",
3
+ "version": "3.0.0-alpha.274",
4
4
  "description": "AWS adapters for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "dependencies": {
7
7
  "@aws-sdk/client-appsync": "3.970.0",
8
8
  "@aws-sdk/client-cloudfront": "3.970.0",
9
+ "@aws-sdk/client-cloudwatch-logs": "3.970.0",
9
10
  "@aws-sdk/client-location": "3.970.0",
10
11
  "@aws-sdk/client-s3": "3.970.0",
11
12
  "@aws-sdk/s3-request-presigner": "3.970.0",
12
13
  "sury": "11.0.0-alpha.4",
13
14
  "uuid": "^13.0.0",
14
- "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
15
- "@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.66",
16
15
  "@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.4",
17
- "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
18
16
  "@reventlessdev/rescript-node": "2.0.0-alpha.2",
17
+ "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
18
+ "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
19
+ "@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.67",
19
20
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.18",
20
21
  "@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
22
+ "@reventlessdev/reventless-core": "3.0.0-alpha.217",
21
23
  "@reventlessdev/reventless-interop": "3.0.0-alpha.30",
22
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.80",
23
24
  "@reventlessdev/reventless-infra": "3.0.0-alpha.128",
24
- "@reventlessdev/reventless-spec": "3.0.0-alpha.102",
25
- "@reventlessdev/reventless-core": "3.0.0-alpha.216"
25
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.81",
26
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.102"
26
27
  },
27
28
  "devDependencies": {
28
29
  "rescript": "12.3.0",
@@ -135,6 +135,20 @@ let make = (
135
135
  ->Option.getOr([])
136
136
  ->Pulumi.Input.make
137
137
 
138
+ // Created before the function that writes to it — see
139
+ // `Util_LambdaLogging.makeManagedLogGroup` for why the ordering matters.
140
+ let logGroup = Util_LambdaLogging.makeManagedLogGroup(
141
+ ~name=name ++ "Lambda",
142
+ ~tags=AWS.Tags.make(
143
+ ~name=name ++ "LambdaLogGroup",
144
+ ~kind=ReventlessCore.ComponentType.Platform,
145
+ ~role=Logs,
146
+ ~scope=Platform,
147
+ ),
148
+ ~opts,
149
+ (),
150
+ )
151
+
138
152
  let lambda = Lambda.Function.make(
139
153
  ~name=name ++ "Lambda",
140
154
  ~args={
@@ -159,23 +173,11 @@ let make = (
159
173
  ]),
160
174
  }: Lambda.Function.functionEnvironment
161
175
  )->Pulumi.Input.make,
176
+ loggingConfig: ?Util_LambdaLogging.loggingConfigFor(logGroup),
162
177
  },
163
178
  ~opts,
164
179
  )
165
180
 
166
- Util_LambdaLogging.makeManagedLogGroup(
167
- ~name=name ++ "Lambda",
168
- ~lambdaName=lambda.name,
169
- ~tags=AWS.Tags.make(
170
- ~name=name ++ "LambdaLogGroup",
171
- ~kind=ReventlessCore.ComponentType.Platform,
172
- ~role=Logs,
173
- ~scope=Platform,
174
- ),
175
- ~opts,
176
- (),
177
- )
178
-
179
181
  let dataSourceRole = IAM.Role.makeWithDefaultPolicy(
180
182
  ~name=name ++ "DataSource",
181
183
  ~servicePrincipal=AWS.AppSync.principal->Pulumi.Output.make,
@@ -80,6 +80,7 @@ function make(api, pluginReadModelTableName, offloadBucketName, schemaReady, opt
80
80
  adminEntryJson
81
81
  ]]), false);
82
82
  let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
83
+ let logGroup = Util_LambdaLogging$ReventlessAws.makeManagedLogGroup(name + "Lambda", undefined, AWS_Tags$ReventlessAws.make(name + "LambdaLogGroup", "Platform", "Logs", "Platform", undefined, undefined, undefined, undefined), opts$1, undefined);
83
84
  let lambda = new (Aws.lambda.Function)(name + "Lambda", {
84
85
  handler: "index.handler",
85
86
  runtime: "nodejs22.x",
@@ -114,9 +115,9 @@ function make(api, pluginReadModelTableName, offloadBucketName, schemaReady, opt
114
115
  Util_LambdaLogging$ReventlessAws.logLevelEntry()
115
116
  ])
116
117
  },
117
- sourceCodeHash: match.sourceCodeHash
118
+ sourceCodeHash: match.sourceCodeHash,
119
+ loggingConfig: Util_LambdaLogging$ReventlessAws.loggingConfigFor(logGroup)
118
120
  }, opts$1);
119
- Util_LambdaLogging$ReventlessAws.makeManagedLogGroup(name + "Lambda", lambda.name, AWS_Tags$ReventlessAws.make(name + "LambdaLogGroup", "Platform", "Logs", "Platform", undefined, undefined, undefined, undefined), opts$1, undefined);
120
121
  let dataSourceRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name + "DataSource", Pulumi.output(AWS$ReventlessAws.AppSync.principal), AWS_Tags$ReventlessAws.make(name + "DataSource", "Platform", "Identity", "Platform", undefined, undefined, undefined, undefined), opts$1);
121
122
  Pulumi.all([
122
123
  lambda.arn,
@@ -93,6 +93,20 @@ let make = (
93
93
  ->Option.getOr([])
94
94
  ->Pulumi.Input.make
95
95
 
96
+ // Created before the function that writes to it — see
97
+ // `Util_LambdaLogging.makeManagedLogGroup` for why the ordering matters.
98
+ let logGroup = Util_LambdaLogging.makeManagedLogGroup(
99
+ ~name=name ++ "Lambda",
100
+ ~tags=AWS.Tags.make(
101
+ ~name=name ++ "LambdaLogGroup",
102
+ ~kind=ReventlessCore.ComponentType.Platform,
103
+ ~role=Logs,
104
+ ~scope=Platform,
105
+ ),
106
+ ~opts,
107
+ (),
108
+ )
109
+
96
110
  let lambda = Lambda.Function.make(
97
111
  ~name=name ++ "Lambda",
98
112
  ~args={
@@ -116,23 +130,11 @@ let make = (
116
130
  ]),
117
131
  }: Lambda.Function.functionEnvironment
118
132
  )->Pulumi.Input.make,
133
+ loggingConfig: ?Util_LambdaLogging.loggingConfigFor(logGroup),
119
134
  },
120
135
  ~opts,
121
136
  )
122
137
 
123
- Util_LambdaLogging.makeManagedLogGroup(
124
- ~name=name ++ "Lambda",
125
- ~lambdaName=lambda.name,
126
- ~tags=AWS.Tags.make(
127
- ~name=name ++ "LambdaLogGroup",
128
- ~kind=ReventlessCore.ComponentType.Platform,
129
- ~role=Logs,
130
- ~scope=Platform,
131
- ),
132
- ~opts,
133
- (),
134
- )
135
-
136
138
  let dataSourceRole = IAM.Role.makeWithDefaultPolicy(
137
139
  ~name=name ++ "DataSource",
138
140
  ~servicePrincipal=AWS.AppSync.principal->Pulumi.Output.make,
@@ -54,6 +54,7 @@ function make(api, uiFragmentRegistryTableName, schemaReady, opts) {
54
54
  ]]);
55
55
  let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Api/Platform_UIFragments_Lambda_Ops.res.mjs", packageDirs, undefined, false);
56
56
  let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
57
+ let logGroup = Util_LambdaLogging$ReventlessAws.makeManagedLogGroup(name + "Lambda", undefined, AWS_Tags$ReventlessAws.make(name + "LambdaLogGroup", "Platform", "Logs", "Platform", undefined, undefined, undefined, undefined), opts$1, undefined);
57
58
  let lambda = new (Aws.lambda.Function)(name + "Lambda", {
58
59
  handler: "index.handler",
59
60
  runtime: "nodejs22.x",
@@ -84,9 +85,9 @@ function make(api, uiFragmentRegistryTableName, schemaReady, opts) {
84
85
  Util_LambdaLogging$ReventlessAws.logLevelEntry()
85
86
  ])
86
87
  },
87
- sourceCodeHash: match.sourceCodeHash
88
+ sourceCodeHash: match.sourceCodeHash,
89
+ loggingConfig: Util_LambdaLogging$ReventlessAws.loggingConfigFor(logGroup)
88
90
  }, opts$1);
89
- Util_LambdaLogging$ReventlessAws.makeManagedLogGroup(name + "Lambda", lambda.name, AWS_Tags$ReventlessAws.make(name + "LambdaLogGroup", "Platform", "Logs", "Platform", undefined, undefined, undefined, undefined), opts$1, undefined);
90
91
  let dataSourceRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name + "DataSource", Pulumi.output(AWS$ReventlessAws.AppSync.principal), AWS_Tags$ReventlessAws.make(name + "DataSource", "Platform", "Identity", "Platform", undefined, undefined, undefined, undefined), opts$1);
91
92
  Pulumi.all([
92
93
  lambda.arn,
@@ -125,7 +125,14 @@ let connectLambda = (
125
125
  resources->ReventlessCore.Adapter.resourcesToResolvedOutput,
126
126
  )
127
127
  ->Pulumi.Output.all3
128
- ->Pulumi.Output.apply(((eventTopicResources, queueArns, resources)) => {
128
+ // Provisions the collector's role, policies and event-source mappings, so it
129
+ // carries the plugin's attribution across the apply — by the time this runs
130
+ // the builder's construct has returned and the ambient context is empty.
131
+ ->ReventlessCore.ResourceAttribution_Deploytime.applyAttributed(((
132
+ eventTopicResources,
133
+ queueArns,
134
+ resources,
135
+ )) => {
129
136
  log.debug(
130
137
  ~comp="EventCollector",
131
138
  `connectLambda ${name}: ${eventTopicResources->Array.length->Int.toString} topic resource(s), ${resources->Array.length->Int.toString} resource(s)`,
@@ -15,6 +15,7 @@ import * as Adapter_Helpers$ReventlessAws from "../Adapter_Helpers.res.mjs";
15
15
  import * as EventCollector$ReventlessCore from "@reventlessdev/reventless-core/src/components/EventCollector/EventCollector.res.mjs";
16
16
  import * as AdapterDeploytime$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/AdapterDeploytime.res.mjs";
17
17
  import * as Util_EventSourceMapping$ReventlessAws from "../../util/Util_EventSourceMapping.res.mjs";
18
+ import * as ResourceAttribution_Deploytime$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/ResourceAttribution_Deploytime.res.mjs";
18
19
 
19
20
  let log = Logger$ReventlessCore.fromEnv();
20
21
 
@@ -76,11 +77,11 @@ function esmTags(name, esmName) {
76
77
  }
77
78
 
78
79
  function connectLambda(lambda, name, lambdaRole, queues, eventTopics, resources, opts) {
79
- Pulumi.all([
80
+ ResourceAttribution_Deploytime$ReventlessCore.applyAttributed(Pulumi.all([
80
81
  toResources(eventTopics),
81
82
  Pulumi.all(queues.map(queue => queue.arn)),
82
83
  Adapter$ReventlessCore.resourcesToResolvedOutput(resources)
83
- ]).apply(param => {
84
+ ]), param => {
84
85
  let resources = param[2];
85
86
  let queueArns = param[1];
86
87
  let eventTopicResources = param[0];
@@ -118,6 +118,20 @@ let make = (
118
118
  ->Option.getOr([])
119
119
  ->Pulumi.Input.make
120
120
 
121
+ // Created before the function that writes to it — see
122
+ // `Util_LambdaLogging.makeManagedLogGroup` for why the ordering matters.
123
+ let logGroup = Util_LambdaLogging.makeManagedLogGroup(
124
+ ~name=name ++ "Lambda",
125
+ ~tags=AWS.Tags.make(
126
+ ~name=name ++ "LambdaLogGroup",
127
+ ~kind=ReventlessCore.ComponentType.Platform,
128
+ ~role=Logs,
129
+ ~scope=Platform,
130
+ ),
131
+ ~opts,
132
+ (),
133
+ )
134
+
121
135
  let lambda = Lambda.Function.make(
122
136
  ~name=name ++ "Lambda",
123
137
  ~args={
@@ -146,23 +160,11 @@ let make = (
146
160
  ]),
147
161
  }: Lambda.Function.functionEnvironment
148
162
  )->Pulumi.Input.make,
163
+ loggingConfig: ?Util_LambdaLogging.loggingConfigFor(logGroup),
149
164
  },
150
165
  ~opts,
151
166
  )
152
167
 
153
- Util_LambdaLogging.makeManagedLogGroup(
154
- ~name=name ++ "Lambda",
155
- ~lambdaName=lambda.name,
156
- ~tags=AWS.Tags.make(
157
- ~name=name ++ "LambdaLogGroup",
158
- ~kind=ReventlessCore.ComponentType.Platform,
159
- ~role=Logs,
160
- ~scope=Platform,
161
- ),
162
- ~opts,
163
- (),
164
- )
165
-
166
168
  let dataSourceRole = IAM.Role.makeWithDefaultPolicy(
167
169
  ~name=name ++ "DataSource",
168
170
  ~servicePrincipal=AWS.AppSync.principal->Pulumi.Output.make,
@@ -58,6 +58,7 @@ function make(api, placeIndexName, nameOpt, opts) {
58
58
  ]]);
59
59
  let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver_Ops.res.mjs", packageDirs, undefined, false);
60
60
  let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
61
+ let logGroup = Util_LambdaLogging$ReventlessAws.makeManagedLogGroup(name + "Lambda", undefined, AWS_Tags$ReventlessAws.make(name + "LambdaLogGroup", "Platform", "Logs", "Platform", undefined, undefined, undefined, undefined), opts$1, undefined);
61
62
  let lambda = new (Aws.lambda.Function)(name + "Lambda", {
62
63
  handler: "index.handler",
63
64
  runtime: "nodejs22.x",
@@ -88,9 +89,9 @@ function make(api, placeIndexName, nameOpt, opts) {
88
89
  Util_LambdaLogging$ReventlessAws.logLevelEntry()
89
90
  ])
90
91
  },
91
- sourceCodeHash: match.sourceCodeHash
92
+ sourceCodeHash: match.sourceCodeHash,
93
+ loggingConfig: Util_LambdaLogging$ReventlessAws.loggingConfigFor(logGroup)
92
94
  }, opts$1);
93
- Util_LambdaLogging$ReventlessAws.makeManagedLogGroup(name + "Lambda", lambda.name, AWS_Tags$ReventlessAws.make(name + "LambdaLogGroup", "Platform", "Logs", "Platform", undefined, undefined, undefined, undefined), opts$1, undefined);
94
95
  let dataSourceRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name + "DataSource", Pulumi.output(AWS$ReventlessAws.AppSync.principal), AWS_Tags$ReventlessAws.make(name + "DataSource", "Platform", "Identity", "Platform", undefined, undefined, undefined, undefined), opts$1);
95
96
  Pulumi.all([
96
97
  lambda.arn,
@@ -13,15 +13,6 @@ let log = ReventlessCore.Logger.fromEnv()
13
13
  type api = Types.AppSync.api
14
14
  type role = Types.AppSync.role
15
15
 
16
- type interceptorConfig = {
17
- dataSourceName: Pulumi.Input.t<string>,
18
- }
19
-
20
- /** Deploy-time config for the query interceptor. When set, all top-level Query
21
- resolvers become pipeline resolvers with an interceptor Lambda function
22
- preceding the DynamoDB query. Set this before calling `make`. */
23
- let queryInterceptorConfig: ref<option<interceptorConfig>> = ref(None)
24
-
25
16
  let interceptorCode = readModelName =>
26
17
  `import { util } from '@aws-appsync/utils';
27
18
  export function request(ctx) {
@@ -106,9 +97,12 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
106
97
  }
107
98
 
108
99
  // Creates either a unit resolver (no interceptor) or a pipeline resolver
109
- // (interceptor Lambda → DynamoDB query) depending on queryInterceptorConfig.
100
+ // (interceptor Lambda → DynamoDB query), depending on whether an extension
101
+ // switched interception on for this deployment. Provisioning the interceptor
102
+ // is the framework's job — see `QueryInterceptor_Provisioning`; the first
103
+ // Query resolver under a plugin creates it and the rest reuse it.
110
104
  let makeQueryResolver = (~resolverName, ~field, ~code) =>
111
- switch queryInterceptorConfig.contents {
105
+ switch QueryInterceptor_Provisioning.dataSourceName(~api, ~opts) {
112
106
  | None =>
113
107
  Resolver.makeUnitJsResolver(
114
108
  ~name=resolverName,
@@ -119,7 +113,7 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
119
113
  ~code,
120
114
  ~opts,
121
115
  )
122
- | Some({dataSourceName: interceptorDsName}) =>
116
+ | Some(interceptorDsName) =>
123
117
  let interceptorFn = Function.makeJs(
124
118
  ~name=resolverName ++ "Interceptor",
125
119
  ~api,
@@ -14,13 +14,10 @@ import * as Plugin_Helpers$ReventlessCore from "@reventlessdev/reventless-core/s
14
14
  import * as AppSync_Resolver_Functions$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/AppSync/AppSync_Resolver_Functions.res.mjs";
15
15
  import * as AppSync_Resolver_Retrying$ReventlessAws from "../Api/AppSync_Resolver_Retrying.res.mjs";
16
16
  import * as GraphQL_FragmentGenerator$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_FragmentGenerator.res.mjs";
17
+ import * as QueryInterceptor_Provisioning$ReventlessAws from "./QueryInterceptor_Provisioning.res.mjs";
17
18
 
18
19
  let log = Logger$ReventlessCore.fromEnv();
19
20
 
20
- let queryInterceptorConfig = {
21
- contents: undefined
22
- };
23
-
24
21
  function interceptorCode(readModelName) {
25
22
  return `import { util } from '@aws-appsync/utils';
26
23
  export function request(ctx) {
@@ -70,11 +67,11 @@ function make(name, api, apiRole, dataSourceName, indexes, subIdField, idResolve
70
67
  let includeIdParam = registryEntry !== undefined ? registryEntry.includeIdParam : true;
71
68
  let connectionSpec = registryEntry !== undefined ? registryEntry.connectionSpec : true;
72
69
  let makeQueryResolver = (resolverName, field, code) => {
73
- let match = queryInterceptorConfig.contents;
74
- if (match === undefined) {
70
+ let interceptorDsName = QueryInterceptor_Provisioning$ReventlessAws.dataSourceName(api, opts);
71
+ if (interceptorDsName === undefined) {
75
72
  return AppSync_Resolver_Retrying$ReventlessAws.makeUnitJsResolver(resolverName, api, dataSourceName, "Query", field, code, opts);
76
73
  }
77
- let interceptorFn = AppSync_Function$PulumiAws.makeJs(resolverName + "Interceptor", api, match.dataSourceName, interceptorCode(name$1), opts);
74
+ let interceptorFn = AppSync_Function$PulumiAws.makeJs(resolverName + "Interceptor", api, interceptorDsName, interceptorCode(name$1), opts);
78
75
  let queryFn = AppSync_Function$PulumiAws.makeJs(resolverName + "Query", api, dataSourceName, code, opts);
79
76
  return AppSync_Resolver_Retrying$ReventlessAws.makePipelineJsResolver(resolverName, api, "Query", field, AppSync_Resolver_Functions$PulumiAws.pipelinePassThrough, [
80
77
  interceptorFn,
@@ -238,7 +235,6 @@ let Resolver;
238
235
  export {
239
236
  Resolver,
240
237
  log,
241
- queryInterceptorConfig,
242
238
  interceptorCode,
243
239
  internalRowRequiredAttr,
244
240
  make,
@@ -0,0 +1,155 @@
1
+ /** Provisions the query-interceptor path on AppSync, when an extension has asked
2
+ for it via `ReventlessCore.QueryInterception.use`.
3
+
4
+ The framework owns everything provider-shaped here — the runtime, its
5
+ execution role, the AppSync Lambda data source and the role that lets AppSync
6
+ invoke it — because all of it needs the api handle and a service role, which
7
+ exist only inside the plugin build. An extension supplies none of it; see the
8
+ seam's own docstring for why that split is the point.
9
+
10
+ **One interceptor per api, not per read model.** A data source belongs to one
11
+ api, so it is memoised on the api handle *by identity* — the builders thread
12
+ a single `api` value through every read model that resolves against it, and
13
+ identity is what guarantees the data source ends up attached to the very api
14
+ the resolver names. Keying on anything looser (the owning plugin, say) would
15
+ be right until one key spanned two apis, and the failure then is a resolver
16
+ pointing at a data source that does not exist on its api — rejected at deploy
17
+ time, with nothing in the message about why.
18
+
19
+ The api's *contents* are an `Output` and are unknown at
20
+ resolver-construction time, which is why the ambient `ResourceAttribution`
21
+ plugin supplies the resource NAMES instead. That is presentation, not
22
+ identity: a second api under one plugin gets a suffixed name rather than a
23
+ shared data source. Substrate built outside any plugin construct (the admin
24
+ read models on the platform api) is named `Platform`.
25
+
26
+ The runtime is built through `RuntimeEnvironment_Lambda.makeFromCodeAsset`
27
+ rather than a bespoke `Lambda.Function.make`, which is what makes the seams
28
+ compose: that path bundles registered runtime extensions into the archive and
29
+ writes `RUNTIME_EXTENSIONS`, so an extension registering its interceptor in
30
+ `onColdStart` reaches this runtime without a second registration. */
31
+
32
+ open PulumiAws
33
+
34
+ // Memoised on the api handle itself — see the module docstring for why identity
35
+ // rather than a name. The value is the data-source name the resolver pipeline
36
+ // references.
37
+ let byApi: Map.t<Types.AppSync.api, Pulumi.Input.t<string>> = Map.make()
38
+
39
+ // How many interceptors a given owner already named, so a second api under one
40
+ // owner gets a distinct resource name instead of colliding with the first.
41
+ let namesUsed: dict<int> = Dict.make()
42
+
43
+ /** A stable, readable resource-name stem for the interceptor: the owning plugin,
44
+ or `Platform` for substrate provisioned outside any plugin construct. */
45
+ let nameFor = () => {
46
+ let owner = ReventlessCore.ResourceAttribution.current.contents.plugin->Option.getOr("Platform")
47
+ let used = namesUsed->Dict.get(owner)->Option.getOr(0)
48
+ namesUsed->Dict.set(owner, used + 1)
49
+ used == 0
50
+ ? owner ++ "QueryInterceptor"
51
+ : owner ++ "QueryInterceptor" ++ (used + 1)->Int.toString
52
+ }
53
+
54
+ let buildInterceptor = (~api: Types.AppSync.api, ~opts) => {
55
+ let name = nameFor()
56
+ let componentOpts: Pulumi.ComponentResource.options = {
57
+ parent: ?(opts: Pulumi.CustomResourceOptions.t).parent,
58
+ }
59
+
60
+ let packageDirs = Dict.fromArray([
61
+ (
62
+ "@reventlessdev/reventless-aws",
63
+ Util_Bundle.resolvePackageRoot("@reventlessdev/reventless-aws"),
64
+ ),
65
+ ])
66
+ let {code, sourceCodeHash} = Util_Bundle.buildCodeArchive(
67
+ ~entryPointModule="@reventlessdev/reventless-aws/src/adapter/QueryDb/QueryInterceptor_Lambda.res.mjs",
68
+ ~packageDirs,
69
+ )
70
+
71
+ // Sits in front of every read, so it is sized for a decision rather than for
72
+ // work: small memory, short timeout. A hook that needs longer than this is
73
+ // doing something a read path cannot afford anyway.
74
+ let runtime = RuntimeEnvironment_Lambda.makeFromCodeAsset(
75
+ ~name,
76
+ ~unitKind=ReventlessCore.Monitoring.Other("QueryInterceptor"),
77
+ ~componentKind=ReventlessCore.ComponentType.Plugin,
78
+ ~code,
79
+ ~sourceCodeHash,
80
+ ~memorySize=256,
81
+ ~timeout=10,
82
+ ~opts=componentOpts,
83
+ )
84
+ let lambdaArn = runtime.parts.lambda->Pulumi.Output.flatMap(lambda => lambda.arn)
85
+
86
+ let dataSourceRole = IAM.Role.makeWithDefaultPolicy(
87
+ ~name=name ++ "DataSource",
88
+ ~servicePrincipal=AWS.AppSync.principal->Pulumi.Output.make,
89
+ ~tags=AWS.Tags.make(
90
+ ~name=name ++ "DataSource",
91
+ ~kind=ReventlessCore.ComponentType.Plugin,
92
+ ~role=Identity,
93
+ ~scope=Plugin,
94
+ ),
95
+ ~opts,
96
+ )
97
+
98
+ let _ =
99
+ (lambdaArn, dataSourceRole.id)
100
+ ->Pulumi.Output.all2
101
+ ->Pulumi.Output.apply(((lambdaArn, dataSourceRoleId)) => {
102
+ open PolicyDocument
103
+ let _attach = IAM.RolePolicy.make(
104
+ ~name=name ++ "DataSource",
105
+ ~args={
106
+ IAM.RolePolicy.policy: PolicyDocument.make(
107
+ ~id=name ++ "DataSourcePolicy",
108
+ ~statements=[
109
+ {
110
+ sid: "AllowDataSourceInvokeInterceptor",
111
+ effect: Allow,
112
+ actions: Action("lambda:InvokeFunction"),
113
+ resources: Resource(lambdaArn),
114
+ },
115
+ ],
116
+ )
117
+ ->PolicyDocument.toJsonString
118
+ ->Pulumi.Input.make,
119
+ role: dataSourceRoleId->Pulumi.Input.make,
120
+ },
121
+ ~opts,
122
+ )
123
+ })
124
+
125
+ let dataSource = AppSync.DataSource.make(
126
+ ~name=name ++ "DataSource",
127
+ ~args={
128
+ type_: AWS_LAMBDA,
129
+ apiId: api->Pulumi.Output.flatMap(api => api.id)->Pulumi.Output.asInput,
130
+ lambdaConfig: {
131
+ AppSync.DataSource.functionArn: lambdaArn->Pulumi.Output.asInput,
132
+ }->Pulumi.Input.make,
133
+ serviceRoleArn: dataSourceRole.arn->Pulumi.Output.asInput,
134
+ },
135
+ ~opts=Some(opts),
136
+ )
137
+
138
+ dataSource.name->Pulumi.Output.asInput
139
+ }
140
+
141
+ /** The data source a Query resolver should put in front of its DynamoDB read, or
142
+ `None` when interception is off — in which case the caller builds the unit
143
+ resolver it always did and nothing here is provisioned. */
144
+ let dataSourceName = (~api: Types.AppSync.api, ~opts): option<Pulumi.Input.t<string>> =>
145
+ if !ReventlessCore.QueryInterception.isEnabled() {
146
+ None
147
+ } else {
148
+ switch byApi->Map.get(api) {
149
+ | Some(existing) => Some(existing)
150
+ | None =>
151
+ let created = buildInterceptor(~api, ~opts)
152
+ byApi->Map.set(api, created)
153
+ Some(created)
154
+ }
155
+ }
@@ -0,0 +1,92 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Aws from "@pulumi/aws";
4
+ import * as IAM$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/IAM.res.mjs";
5
+ import * as Output$Pulumi from "@reventlessdev/rescript-pulumi-pulumi/src/Output.res.mjs";
6
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
7
+ import * as Pulumi from "@pulumi/pulumi";
8
+ import * as AWS$ReventlessAws from "../AWS.res.mjs";
9
+ import * as AWS_Tags$ReventlessAws from "../AWS_Tags.res.mjs";
10
+ import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
11
+ import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
12
+ import * as QueryInterception$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/QueryInterception/QueryInterception.res.mjs";
13
+ import * as ResourceAttribution$ReventlessCore from "@reventlessdev/reventless-core/src/ResourceAttribution.res.mjs";
14
+ import * as RuntimeEnvironment_Lambda$ReventlessAws from "../Runtime/RuntimeEnvironment_Lambda.res.mjs";
15
+
16
+ let byApi = new Map();
17
+
18
+ let namesUsed = {};
19
+
20
+ function nameFor() {
21
+ let owner = Stdlib_Option.getOr(ResourceAttribution$ReventlessCore.current.contents.plugin, "Platform");
22
+ let used = Stdlib_Option.getOr(namesUsed[owner], 0);
23
+ namesUsed[owner] = used + 1 | 0;
24
+ if (used === 0) {
25
+ return owner + "QueryInterceptor";
26
+ } else {
27
+ return owner + "QueryInterceptor" + (used + 1 | 0).toString();
28
+ }
29
+ }
30
+
31
+ function buildInterceptor(api, opts) {
32
+ let name = nameFor();
33
+ let componentOpts_parent = opts.parent;
34
+ let componentOpts = {
35
+ parent: componentOpts_parent
36
+ };
37
+ let packageDirs = Object.fromEntries([[
38
+ "@reventlessdev/reventless-aws",
39
+ Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws")
40
+ ]]);
41
+ let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/QueryDb/QueryInterceptor_Lambda.res.mjs", packageDirs, undefined, undefined);
42
+ let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, {
43
+ TAG: "Other",
44
+ _0: "QueryInterceptor"
45
+ }, "Plugin", match.code, match.sourceCodeHash, undefined, 256, 10, undefined, undefined, undefined, undefined, undefined, componentOpts);
46
+ let lambdaArn = Output$Pulumi.flatMap(runtime.parts.lambda, lambda => lambda.arn);
47
+ let dataSourceRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name + "DataSource", Pulumi.output(AWS$ReventlessAws.AppSync.principal), AWS_Tags$ReventlessAws.make(name + "DataSource", "Plugin", "Identity", "Plugin", undefined, undefined, undefined, undefined), opts);
48
+ Pulumi.all([
49
+ lambdaArn,
50
+ dataSourceRole.id
51
+ ]).apply(param => {
52
+ new (Aws.iam.RolePolicy)(name + "DataSource", {
53
+ policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + "DataSourcePolicy", [{
54
+ Sid: "AllowDataSourceInvokeInterceptor",
55
+ Effect: "Allow",
56
+ Action: "lambda:InvokeFunction",
57
+ Resource: param[0]
58
+ }])),
59
+ role: param[1]
60
+ }, opts);
61
+ });
62
+ return new (Aws.appsync.DataSource)(name + "DataSource", {
63
+ type: "AWS_LAMBDA",
64
+ apiId: Output$Pulumi.flatMap(api, api => api.id),
65
+ lambdaConfig: {
66
+ functionArn: lambdaArn
67
+ },
68
+ serviceRoleArn: dataSourceRole.arn
69
+ }, opts).name;
70
+ }
71
+
72
+ function dataSourceName(api, opts) {
73
+ if (!QueryInterception$ReventlessCore.isEnabled()) {
74
+ return;
75
+ }
76
+ let existing = byApi.get(api);
77
+ if (existing !== undefined) {
78
+ return existing;
79
+ }
80
+ let created = buildInterceptor(api, opts);
81
+ byApi.set(api, created);
82
+ return created;
83
+ }
84
+
85
+ export {
86
+ byApi,
87
+ namesUsed,
88
+ nameFor,
89
+ buildInterceptor,
90
+ dataSourceName,
91
+ }
92
+ /* byApi Not a pure module */