@reventlessdev/reventless-aws 3.0.0-alpha.229 → 3.0.0-alpha.231

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 (33) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/package.json +5 -5
  3. package/src/adapter/Geocoder/Geocoder_AwsLocation.res +60 -141
  4. package/src/adapter/Geocoder/Geocoder_AwsLocation.res.mjs +52 -118
  5. package/src/adapter/Geocoder/Geocoder_AwsLocation_Ops.res +137 -0
  6. package/src/adapter/Geocoder/Geocoder_AwsLocation_Ops.res.mjs +124 -0
  7. package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +51 -6
  8. package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res +60 -0
  9. package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res.mjs +29 -1
  10. package/src/adapter/Runtime/HeartbeatEntryPoint.res +44 -0
  11. package/src/adapter/Runtime/HeartbeatEntryPoint.res.mjs +47 -0
  12. package/src/adapter/Runtime/PluginExtensionPointEntryPoint.res +234 -0
  13. package/src/adapter/Runtime/PluginExtensionPointEntryPoint.res.mjs +226 -0
  14. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res +49 -4
  15. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +22 -4
  16. package/src/adapter/Upload/Upload_Presign_S3.res +65 -164
  17. package/src/adapter/Upload/Upload_Presign_S3.res.mjs +56 -119
  18. package/src/adapter/Upload/Upload_Presign_S3_Ops.res +157 -0
  19. package/src/adapter/Upload/Upload_Presign_S3_Ops.res.mjs +120 -0
  20. package/src/components/InboundTranslationSlice_Builder.res +27 -1
  21. package/src/components/InboundTranslationSlice_Builder.res.mjs +15 -2
  22. package/src/plugin/runtime/PluginExtensionPointRuntime_Builder.res +1 -1
  23. package/src/plugin/runtime/PluginExtensionPointRuntime_Builder.res.mjs +1 -1
  24. package/src/plugin/runtime/PluginRuntime_Builder.res +59 -1
  25. package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +49 -2
  26. package/tests/DcbInboundTranslationRoutingTest.res +73 -0
  27. package/tests/DcbInboundTranslationRoutingTest.res.mjs +71 -0
  28. package/tests/EpInboundTestSlice.res +19 -0
  29. package/tests/EpInboundTestSlice.res.mjs +31 -0
  30. package/tests/EpInboundTestSliceTranslation.res +13 -0
  31. package/tests/EpInboundTestSliceTranslation.res.mjs +33 -0
  32. package/src/adapter/Runtime/HeartbeatEntryPoint.mjs +0 -38
  33. package/src/adapter/Runtime/PluginExtensionPointEntryPoint.mjs +0 -127
@@ -1,113 +1,16 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Aws from "@pulumi/aws";
4
- import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
5
4
  import * as IAM$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/IAM.res.mjs";
6
5
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
7
6
  import * as Pulumi from "@pulumi/pulumi";
8
7
  import * as Lambda$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/Lambda/Lambda.res.mjs";
9
8
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
10
9
  import * as AWS$ReventlessAws from "../AWS.res.mjs";
11
- import * as ClientS3 from "@aws-sdk/client-s3";
12
10
  import * as AWS_Tags$ReventlessAws from "../AWS_Tags.res.mjs";
13
11
  import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
12
+ import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
14
13
  import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Pulumi.res.mjs";
15
- import * as S3RequestPresigner from "@aws-sdk/s3-request-presigner";
16
-
17
- let getEnv = (function(k) { var v = process.env[k]; return (v === undefined || v === null || v === "") ? undefined : v; });
18
-
19
- let genUuid = (function() {
20
- return (globalThis.crypto && globalThis.crypto.randomUUID)
21
- ? globalThis.crypto.randomUUID()
22
- : require("crypto").randomUUID();
23
- });
24
-
25
- let decodeJwtSub = (function(header) {
26
- try {
27
- var token = header.indexOf("Bearer ") === 0 ? header.slice(7) : header;
28
- var parts = token.split(".");
29
- if (parts.length < 2) return undefined;
30
- var base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
31
- var json = Buffer.from(base64, "base64").toString("utf8");
32
- var claims = JSON.parse(json);
33
- return (claims && claims.sub) ? String(claims.sub) : undefined;
34
- } catch (_) { return undefined; }
35
- });
36
-
37
- function corsHeaders() {
38
- return Object.fromEntries([
39
- [
40
- "content-type",
41
- "application/json"
42
- ],
43
- [
44
- "access-control-allow-origin",
45
- "*"
46
- ],
47
- [
48
- "access-control-allow-methods",
49
- "POST,OPTIONS"
50
- ],
51
- [
52
- "access-control-allow-headers",
53
- "*"
54
- ]
55
- ]);
56
- }
57
-
58
- function identityPrefix(event) {
59
- let authHeader = Stdlib_Option.flatMap(event.headers, h => Stdlib_Option.orElse(h["authorization"], h["Authorization"]));
60
- let sub = Stdlib_Option.flatMap(authHeader, decodeJwtSub);
61
- if (sub !== undefined) {
62
- return sub + `/`;
63
- } else {
64
- return "";
65
- }
66
- }
67
-
68
- async function handlePresign(event, _context) {
69
- try {
70
- let bucket = Stdlib_Option.getOr(getEnv("UPLOAD_BUCKET"), "");
71
- let parsed = Stdlib_Option.getOr(Stdlib_JSON.Decode.object(JSON.parse(Stdlib_Option.getOr(event.body, "{}"))), {});
72
- let fileName = Stdlib_Option.getOr(Stdlib_Option.flatMap(parsed["fileName"], Stdlib_JSON.Decode.string), "upload");
73
- let contentType = Stdlib_Option.flatMap(parsed["contentType"], Stdlib_JSON.Decode.string);
74
- let servedPrefix = Stdlib_Option.getOr(getEnv("SERVED_PREFIX"), "uploads");
75
- let key = servedPrefix + `/` + identityPrefix(event) + genUuid() + `/` + fileName;
76
- let client = new ClientS3.S3Client();
77
- let command = new ClientS3.PutObjectCommand({
78
- Bucket: bucket,
79
- Key: key,
80
- ContentType: contentType
81
- });
82
- let uploadUrl = await S3RequestPresigner.getSignedUrl(client, command, {
83
- expiresIn: 300
84
- });
85
- let storageRef = `/` + key;
86
- return {
87
- statusCode: 200,
88
- headers: corsHeaders(),
89
- body: JSON.stringify(Object.fromEntries([
90
- [
91
- "uploadUrl",
92
- uploadUrl
93
- ],
94
- [
95
- "storageRef",
96
- storageRef
97
- ]
98
- ]))
99
- };
100
- } catch (exn) {
101
- return {
102
- statusCode: 400,
103
- headers: corsHeaders(),
104
- body: JSON.stringify(Object.fromEntries([[
105
- "error",
106
- "presign_failed"
107
- ]]))
108
- };
109
- }
110
- }
111
14
 
112
15
  function make(bucketName, corsOriginsOpt, servedPrefixOpt, opts) {
113
16
  let corsOrigins = corsOriginsOpt !== undefined ? corsOriginsOpt : ["*"];
@@ -118,28 +21,68 @@ function make(bucketName, corsOriginsOpt, servedPrefixOpt, opts) {
118
21
  bucketName.apply(b => {
119
22
  let arn = `arn:aws:s3:::` + b + `/*`;
120
23
  new (Aws.iam.RolePolicy)(serviceName + `Policy`, {
121
- policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, serviceName + `Policy`, [{
24
+ policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, serviceName + `Policy`, [
25
+ {
26
+ Sid: "AllowLambdaLogging",
27
+ Effect: "Allow",
28
+ Action: [
29
+ "logs:CreateLogGroup",
30
+ "logs:CreateLogStream",
31
+ "logs:PutLogEvents"
32
+ ],
33
+ Resource: "arn:aws:logs:*:*:*"
34
+ },
35
+ {
122
36
  Sid: "AllowUploadPut",
123
37
  Effect: "Allow",
124
38
  Action: "s3:PutObject",
125
39
  Resource: arn
126
- }])),
40
+ }
41
+ ])),
127
42
  role: lambdaRole.id
128
43
  }, opts$1 !== undefined ? Primitive_option.valFromOption(opts$1) : undefined);
129
44
  });
130
- let environment = {
131
- variables: Object.fromEntries([
132
- [
133
- "UPLOAD_BUCKET",
134
- bucketName
135
- ],
136
- [
137
- "SERVED_PREFIX",
138
- servedPrefix
139
- ]
140
- ])
141
- };
142
- let lambda = new (Aws.lambda.CallbackFunction)(serviceName, Lambda$PulumiAws.CallbackFunction.Args.make(handlePresign, lambdaRole, undefined, undefined, undefined, undefined, 30, undefined, undefined, undefined, AWS_Tags$ReventlessAws.make(serviceName, "Platform", "Runtime", "Platform", undefined, undefined, undefined, undefined), environment), opts$1 !== undefined ? Primitive_option.valFromOption(opts$1) : undefined);
45
+ let packageDirs = Object.fromEntries([[
46
+ "@reventlessdev/reventless-aws",
47
+ Util_Bundle$ReventlessAws.resolvePackageRoot("@reventlessdev/reventless-aws")
48
+ ]]);
49
+ let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Upload/Upload_Presign_S3_Ops.res.mjs", packageDirs, undefined);
50
+ let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
51
+ let lambda = new (Aws.lambda.Function)(serviceName, {
52
+ handler: "index.handler",
53
+ runtime: "nodejs22.x",
54
+ code: match.code,
55
+ role: lambdaRole.arn,
56
+ memorySize: 256,
57
+ timeout: 30,
58
+ layers: layers,
59
+ tags: AWS_Tags$ReventlessAws.make(serviceName, "Platform", "Runtime", "Platform", undefined, undefined, undefined, undefined),
60
+ environment: {
61
+ variables: Object.fromEntries([
62
+ [
63
+ "Environment",
64
+ Pulumi.getStack()
65
+ ],
66
+ [
67
+ "UPLOAD_BUCKET",
68
+ bucketName
69
+ ],
70
+ [
71
+ "SERVED_PREFIX",
72
+ servedPrefix
73
+ ],
74
+ [
75
+ "NODE_OPTIONS",
76
+ Util_Bundle$ReventlessAws.esmLoaderNodeOptions
77
+ ],
78
+ [
79
+ "ESM_FALLBACK_DIRS",
80
+ Util_Bundle$ReventlessAws.esmFallbackDirs
81
+ ]
82
+ ])
83
+ },
84
+ sourceCodeHash: match.sourceCodeHash
85
+ }, opts$1 !== undefined ? Primitive_option.valFromOption(opts$1) : undefined);
143
86
  let functionUrl = new (Aws.lambda.FunctionUrl)(serviceName + `Url`, {
144
87
  authorizationType: "NONE",
145
88
  functionName: lambda.name,
@@ -159,12 +102,6 @@ function make(bucketName, corsOriginsOpt, servedPrefixOpt, opts) {
159
102
  }
160
103
 
161
104
  export {
162
- getEnv,
163
- genUuid,
164
- decodeJwtSub,
165
- corsHeaders,
166
- identityPrefix,
167
- handlePresign,
168
105
  make,
169
106
  }
170
107
  /* @pulumi/aws Not a pure module */
@@ -0,0 +1,157 @@
1
+ // Runtime handler for the upload presign service — compiled, type-checked, and
2
+ // Pulumi-free so it can be shipped as an EntryPoint module (`Upload_Presign_S3`
3
+ // bundles it and re-exports `handler` from the code archive). Keeping it out of
4
+ // the deploy-time module is what avoids both the serialized-closure SDK skew and
5
+ // the deploy-time Pulumi import leaking into the Lambda's cold-start graph.
6
+ //
7
+ // Reads a JSON body `{fileName, contentType}`, derives a target key
8
+ // `uploads/<identity?>/<uuid>/<fileName>`, presigns a PUT to `UPLOAD_BUCKET`
9
+ // (expires in 300s), and returns `{uploadUrl, storageRef}`. A Bearer token, when
10
+ // present, is decoded (no signature verification here — the auth layer verifies;
11
+ // here we only namespace the storage key) and its `sub` namespaces the key.
12
+
13
+ // ── AWS SDK v3 bindings ─────────────────────────────────────────────────────
14
+
15
+ type s3Client
16
+
17
+ type putObjectInput = {
18
+ @as("Bucket") bucket: string,
19
+ @as("Key") key: string,
20
+ @as("ContentType") contentType?: string,
21
+ }
22
+ type putObjectCommand
23
+
24
+ @module("@aws-sdk/client-s3") @new external makeS3Client: unit => s3Client = "S3Client"
25
+
26
+ @module("@aws-sdk/client-s3") @new
27
+ external makePutObjectCommand: putObjectInput => putObjectCommand = "PutObjectCommand"
28
+
29
+ type presignOptions = {expiresIn: int}
30
+
31
+ @module("@aws-sdk/s3-request-presigner")
32
+ external getSignedUrl: (s3Client, putObjectCommand, presignOptions) => promise<string> =
33
+ "getSignedUrl"
34
+
35
+ // ── Node bindings (replacing the former `%raw` helpers with typed externals) ──
36
+
37
+ @val @scope("process") external processEnv: dict<string> = "env"
38
+
39
+ @module("node:crypto") external randomUUID: unit => string = "randomUUID"
40
+
41
+ type buffer
42
+ @val @scope("Buffer") external bufferFromBase64: (string, string) => buffer = "from"
43
+ @send external bufferToString: (buffer, string) => string = "toString"
44
+
45
+ // Read an env var, mapping "" / unset to None.
46
+ let getEnv = (k: string): option<string> =>
47
+ switch processEnv->Dict.get(k) {
48
+ | Some("") | None => None
49
+ | Some(v) => Some(v)
50
+ }
51
+
52
+ // ── Function URL event / response shapes (payload format 2.0) ────────────────
53
+
54
+ type functionUrlEvent = {
55
+ body?: string,
56
+ headers?: dict<string>,
57
+ }
58
+
59
+ type response = {
60
+ statusCode: int,
61
+ headers?: dict<string>,
62
+ body: string,
63
+ }
64
+
65
+ let corsHeaders = () =>
66
+ Dict.fromArray([
67
+ ("content-type", "application/json"),
68
+ ("access-control-allow-origin", "*"),
69
+ ("access-control-allow-methods", "POST,OPTIONS"),
70
+ ("access-control-allow-headers", "*"),
71
+ ])
72
+
73
+ // Decode a JWT payload's `sub` claim without verifying the signature.
74
+ let decodeJwtSub = (header: string): option<string> =>
75
+ try {
76
+ let token =
77
+ header->String.startsWith("Bearer ")
78
+ ? header->String.slice(~start=7, ~end=header->String.length)
79
+ : header
80
+ switch token->String.split(".")->Array.get(1) {
81
+ | Some(payload) =>
82
+ let base64 = payload->String.replaceAll("-", "+")->String.replaceAll("_", "/")
83
+ switch JSON.parseOrThrow(bufferFromBase64(base64, "base64")->bufferToString("utf8")) {
84
+ | Object(obj) => obj->Dict.get("sub")->Option.flatMap(JSON.Decode.string)
85
+ | _ => None
86
+ }
87
+ | None => None
88
+ }
89
+ } catch {
90
+ | _ => None
91
+ }
92
+
93
+ // Key prefix from the caller identity when a Bearer token is present.
94
+ let identityPrefix = (event: functionUrlEvent): string => {
95
+ let authHeader =
96
+ event.headers->Option.flatMap(h =>
97
+ h->Dict.get("authorization")->Option.orElse(h->Dict.get("Authorization"))
98
+ )
99
+ switch authHeader->Option.flatMap(decodeJwtSub) {
100
+ | Some(sub) => `${sub}/`
101
+ | None => ""
102
+ }
103
+ }
104
+
105
+ // ── Runtime handler ─────────────────────────────────────────────────────────
106
+
107
+ let handler = async (event: functionUrlEvent): response => {
108
+ try {
109
+ let bucket = getEnv("UPLOAD_BUCKET")->Option.getOr("")
110
+ let parsed =
111
+ event.body
112
+ ->Option.getOr("{}")
113
+ ->JSON.parseOrThrow
114
+ ->JSON.Decode.object
115
+ ->Option.getOr(Dict.make())
116
+ let fileName =
117
+ parsed->Dict.get("fileName")->Option.flatMap(JSON.Decode.string)->Option.getOr("upload")
118
+ let contentType = parsed->Dict.get("contentType")->Option.flatMap(JSON.Decode.string)
119
+
120
+ // The object key doubles as the served path segment: it is rooted at the
121
+ // served prefix (`SERVED_PREFIX`, e.g. `uploads`) so the CloudFront
122
+ // `{prefix}/*` behavior fronts it. Callers PUT to this exact key.
123
+ let servedPrefix = getEnv("SERVED_PREFIX")->Option.getOr("uploads")
124
+ let key = `${servedPrefix}/${identityPrefix(event)}${randomUUID()}/${fileName}`
125
+ let client = makeS3Client()
126
+ let command = makePutObjectCommand({bucket, key, contentType: ?contentType})
127
+ let uploadUrl = await getSignedUrl(client, command, {expiresIn: 300})
128
+
129
+ // The stored ref is a same-origin relative URL `/{key}`: the served bucket is
130
+ // fronted read-only by the UI's own CloudFront distribution under
131
+ // `{prefix}/*`, so a command stores this directly-renderable value and an
132
+ // `image`-semantic field thumbnails it with no post-processing and no public
133
+ // bucket. See [docs/plans/done/ui-served-buckets.md].
134
+ let storageRef = `/${key}`
135
+
136
+ {
137
+ statusCode: 200,
138
+ headers: corsHeaders(),
139
+ body: Dict.fromArray([
140
+ ("uploadUrl", JSON.Encode.string(uploadUrl)),
141
+ ("storageRef", JSON.Encode.string(storageRef)),
142
+ ])
143
+ ->JSON.Encode.object
144
+ ->JSON.stringify,
145
+ }
146
+ } catch {
147
+ | exn =>
148
+ Console.error2("UploadPresign: presign failed", exn)
149
+ {
150
+ statusCode: 400,
151
+ headers: corsHeaders(),
152
+ body: Dict.fromArray([("error", JSON.Encode.string("presign_failed"))])
153
+ ->JSON.Encode.object
154
+ ->JSON.stringify,
155
+ }
156
+ }
157
+ }
@@ -0,0 +1,120 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
4
+ import * as Nodecrypto from "node:crypto";
5
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
+ import * as ClientS3 from "@aws-sdk/client-s3";
7
+ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
8
+ import * as S3RequestPresigner from "@aws-sdk/s3-request-presigner";
9
+
10
+ function getEnv(k) {
11
+ let v = process.env[k];
12
+ if (v !== undefined && v !== "") {
13
+ return v;
14
+ }
15
+ }
16
+
17
+ function corsHeaders() {
18
+ return Object.fromEntries([
19
+ [
20
+ "content-type",
21
+ "application/json"
22
+ ],
23
+ [
24
+ "access-control-allow-origin",
25
+ "*"
26
+ ],
27
+ [
28
+ "access-control-allow-methods",
29
+ "POST,OPTIONS"
30
+ ],
31
+ [
32
+ "access-control-allow-headers",
33
+ "*"
34
+ ]
35
+ ]);
36
+ }
37
+
38
+ function decodeJwtSub(header) {
39
+ try {
40
+ let token = header.startsWith("Bearer ") ? header.slice(7, header.length) : header;
41
+ let payload = token.split(".")[1];
42
+ if (payload === undefined) {
43
+ return;
44
+ }
45
+ let base64 = payload.replaceAll("-", "+").replaceAll("_", "/");
46
+ let obj = JSON.parse(Buffer.from(base64, "base64").toString("utf8"));
47
+ if (typeof obj === "object" && obj !== null && !Array.isArray(obj)) {
48
+ return Stdlib_Option.flatMap(obj["sub"], Stdlib_JSON.Decode.string);
49
+ } else {
50
+ return;
51
+ }
52
+ } catch (exn) {
53
+ return;
54
+ }
55
+ }
56
+
57
+ function identityPrefix(event) {
58
+ let authHeader = Stdlib_Option.flatMap(event.headers, h => Stdlib_Option.orElse(h["authorization"], h["Authorization"]));
59
+ let sub = Stdlib_Option.flatMap(authHeader, decodeJwtSub);
60
+ if (sub !== undefined) {
61
+ return sub + `/`;
62
+ } else {
63
+ return "";
64
+ }
65
+ }
66
+
67
+ async function handler(event) {
68
+ try {
69
+ let bucket = Stdlib_Option.getOr(getEnv("UPLOAD_BUCKET"), "");
70
+ let parsed = Stdlib_Option.getOr(Stdlib_JSON.Decode.object(JSON.parse(Stdlib_Option.getOr(event.body, "{}"))), {});
71
+ let fileName = Stdlib_Option.getOr(Stdlib_Option.flatMap(parsed["fileName"], Stdlib_JSON.Decode.string), "upload");
72
+ let contentType = Stdlib_Option.flatMap(parsed["contentType"], Stdlib_JSON.Decode.string);
73
+ let servedPrefix = Stdlib_Option.getOr(getEnv("SERVED_PREFIX"), "uploads");
74
+ let key = servedPrefix + `/` + identityPrefix(event) + Nodecrypto.randomUUID() + `/` + fileName;
75
+ let client = new ClientS3.S3Client();
76
+ let command = new ClientS3.PutObjectCommand({
77
+ Bucket: bucket,
78
+ Key: key,
79
+ ContentType: contentType
80
+ });
81
+ let uploadUrl = await S3RequestPresigner.getSignedUrl(client, command, {
82
+ expiresIn: 300
83
+ });
84
+ let storageRef = `/` + key;
85
+ return {
86
+ statusCode: 200,
87
+ headers: corsHeaders(),
88
+ body: JSON.stringify(Object.fromEntries([
89
+ [
90
+ "uploadUrl",
91
+ uploadUrl
92
+ ],
93
+ [
94
+ "storageRef",
95
+ storageRef
96
+ ]
97
+ ]))
98
+ };
99
+ } catch (raw_exn) {
100
+ let exn = Primitive_exceptions.internalToException(raw_exn);
101
+ console.error("UploadPresign: presign failed", exn);
102
+ return {
103
+ statusCode: 400,
104
+ headers: corsHeaders(),
105
+ body: JSON.stringify(Object.fromEntries([[
106
+ "error",
107
+ "presign_failed"
108
+ ]]))
109
+ };
110
+ }
111
+ }
112
+
113
+ export {
114
+ getEnv,
115
+ corsHeaders,
116
+ decodeJwtSub,
117
+ identityPrefix,
118
+ handler,
119
+ }
120
+ /* node:crypto Not a pure module */
@@ -15,11 +15,37 @@ module Make = (Api: {
15
15
  Spec: Reventless.InboundTranslationSlice.Spec,
16
16
  Translation: Reventless.InboundTranslationSlice.Translation with module Spec := Spec,
17
17
  ): (ReventlessCore.InboundTranslationSlice.T with module Spec = Spec) => {
18
+ // Register spec + translation module paths so the shared DCB command Lambda's
19
+ // entry point can import them and route `__inboundTranslation` payloads to this
20
+ // slice's `receive` (mirrors StateChangeSlice_Builder's path registration).
21
+ PluginRuntime_Builder.registerInboundTranslationSliceSpec(
22
+ ~specName=Spec.name,
23
+ ~specPath=Util_Bundle.getModuleSpecifier(Spec.moduleUrl),
24
+ ~translationPath=Util_Bundle.getModuleSpecifier(Translation.moduleUrl),
25
+ )
18
26
  module InnerMake = Inner.Make(Spec, Translation)
19
27
  module Spec = Spec
20
28
  module Translation = Translation
21
29
  type component = InnerMake.component
22
30
  let queryDbName = InnerMake.queryDbName
23
- let make = InnerMake.make
31
+
32
+ let make = (~publishJsons, ~runtime=?, ~opts=?) => {
33
+ let component = InnerMake.make(~publishJsons, ~runtime?, ~opts?)
34
+ // The audit QueryDb's physical table name is Pulumi-generated, so it can only
35
+ // be read off the constructed component's outputs (resolved synchronously here,
36
+ // before forDcbCommandTopic builds HANDLER_CONFIG). Thread it so the entry
37
+ // point's Route 0 can persist the audit rows the in-process path writes inline.
38
+ let outputs: ReventlessInfra.InboundTranslationSlice.outputs =
39
+ component->ReventlessCore.Component.outputs
40
+ switch outputs.queryDb.resources->Array.get(0) {
41
+ | Some(tableResource) =>
42
+ PluginRuntime_Builder.registerInboundAuditTableName(
43
+ ~specName=Spec.name,
44
+ tableResource.name,
45
+ )
46
+ | None => ()
47
+ }
48
+ component
49
+ }
24
50
  }
25
51
  }
@@ -1,5 +1,8 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/components/Component.res.mjs";
4
+ import * as Util_Bundle$ReventlessAws from "../util/Util_Bundle.res.mjs";
5
+ import * as PluginRuntime_Builder$ReventlessAws from "../plugin/runtime/PluginRuntime_Builder.res.mjs";
3
6
  import * as QueryDbStorage_DynamoDb$ReventlessAws from "../adapter/QueryDb/QueryDbStorage_DynamoDb.res.mjs";
4
7
  import * as QueryDbResolvers_AppSync$ReventlessAws from "../adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs";
5
8
  import * as InboundTranslationSlice_Builder$ReventlessCore from "@reventlessdev/reventless-core/src/components/InboundTranslationSlice/InboundTranslationSlice_Builder.res.mjs";
@@ -11,12 +14,22 @@ function Make(Api) {
11
14
  make: QueryDbResolvers_AppSync$ReventlessAws.make
12
15
  })(Api);
13
16
  let Make$1 = Spec => (Translation => {
17
+ PluginRuntime_Builder$ReventlessAws.registerInboundTranslationSliceSpec(Spec.name, Util_Bundle$ReventlessAws.getModuleSpecifier(Spec.moduleUrl), Util_Bundle$ReventlessAws.getModuleSpecifier(Translation.moduleUrl));
14
18
  let InnerMake = Inner.Make(Spec)(Translation);
19
+ let make = (publishJsons, runtime, opts) => {
20
+ let component = InnerMake.make(publishJsons, runtime, opts);
21
+ let outputs = Component$ReventlessCore.outputs(component);
22
+ let tableResource = outputs.queryDb.resources[0];
23
+ if (tableResource !== undefined) {
24
+ PluginRuntime_Builder$ReventlessAws.registerInboundAuditTableName(Spec.name, tableResource.name);
25
+ }
26
+ return component;
27
+ };
15
28
  return {
16
29
  Spec: Spec,
17
30
  Translation: Translation,
18
31
  queryDbName: InnerMake.queryDbName,
19
- make: InnerMake.make
32
+ make: make
20
33
  };
21
34
  });
22
35
  return {
@@ -28,4 +41,4 @@ function Make(Api) {
28
41
  export {
29
42
  Make,
30
43
  }
31
- /* QueryDbStorage_DynamoDb-ReventlessAws Not a pure module */
44
+ /* Component-ReventlessCore Not a pure module */
@@ -96,7 +96,7 @@ let forCommandTopic = (
96
96
 
97
97
  // No user packages — all framework imports are in the Layer
98
98
  let {code, sourceCodeHash} = Util_Bundle.buildCodeArchive(
99
- ~entryPointModule="@reventlessdev/reventless-aws/src/adapter/Runtime/PluginExtensionPointEntryPoint.mjs",
99
+ ~entryPointModule="@reventlessdev/reventless-aws/src/adapter/Runtime/PluginExtensionPointEntryPoint.res.mjs",
100
100
  ~packageDirs=Dict.make(),
101
101
  )
102
102
 
@@ -65,7 +65,7 @@ function forCommandTopic(param, connect, memorySizeOpt, timeoutOpt, param$1, par
65
65
  return `{"queueUrl":"` + queueUrl + `","pluginReadModelTableName":"` + rmTable + `","schedulerRoleArn":"` + schedRoleArn + `","schedulerQueueArn":"` + schedQueueArn + `","schedulerQueueName":"` + schedQueueName + `","publishToAggregates":{` + publishToAggregatesJson + `}}`;
66
66
  });
67
67
  envVars["HANDLER_CONFIG"] = handlerConfigJson;
68
- let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/PluginExtensionPointEntryPoint.mjs", {}, undefined);
68
+ let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/PluginExtensionPointEntryPoint.res.mjs", {}, undefined);
69
69
  return connect(RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset(name, "CommandHandler", "ExtensionPoint", match.code, match.sourceCodeHash, envVars, memorySize, timeout, undefined, undefined, undefined, undefined, undefined, opts));
70
70
  }
71
71
 
@@ -43,6 +43,44 @@ let registerStateChangeSliceSpec = (~specPath: string, ~behaviorPath: string) =>
43
43
  let _ = registeredSliceModulePaths->Array.push({specPath, behaviorPath})
44
44
  }
45
45
 
46
+ // InboundTranslationSlice registration for the shared DCB command Lambda. Unlike
47
+ // StateChangeSlices (routed by command TAG), inbound slices are invoked directly
48
+ // by their AppSync mutation resolver with an `__inboundTranslation` payload — the
49
+ // entry point routes those to a `receive` handler built from the spec + translation
50
+ // modules. Registration is two-phase: the AWS `InboundTranslationSlice_Builder`
51
+ // functor registers the module paths at instantiation (spec/translation `moduleUrl`,
52
+ // like StateChangeSlice), and its wrapped `make` registers the audit QueryDb's
53
+ // resolved table-name Output once the component is constructed (the physical name
54
+ // is Pulumi-generated, so it can't be reconstructed at runtime). Both fire before
55
+ // `forDcbCommandTopic` reads them.
56
+ type inboundSliceReg = {
57
+ specPath: string,
58
+ translationPath: string,
59
+ mutable auditTableName: option<Pulumi.Output.t<string>>,
60
+ }
61
+ let registeredInboundSlices: dict<inboundSliceReg> = Dict.make()
62
+
63
+ let registerInboundTranslationSliceSpec = (
64
+ ~specName: string,
65
+ ~specPath: string,
66
+ ~translationPath: string,
67
+ ) =>
68
+ switch registeredInboundSlices->Dict.get(specName) {
69
+ | Some(reg) => registeredInboundSlices->Dict.set(specName, {...reg, specPath, translationPath})
70
+ | None =>
71
+ registeredInboundSlices->Dict.set(specName, {specPath, translationPath, auditTableName: None})
72
+ }
73
+
74
+ let registerInboundAuditTableName = (~specName: string, tableName: Pulumi.Output.t<string>) =>
75
+ switch registeredInboundSlices->Dict.get(specName) {
76
+ | Some(reg) => reg.auditTableName = Some(tableName)
77
+ | None =>
78
+ registeredInboundSlices->Dict.set(
79
+ specName,
80
+ {specPath: "", translationPath: "", auditTableName: Some(tableName)},
81
+ )
82
+ }
83
+
46
84
  /**
47
85
  Redundant with the seams that populate `dcbConfigRef` automatically:
48
86
  `pluginName` is set by `registerPluginName` (called from `Plugin_Builder.make`
@@ -731,7 +769,7 @@ module Make = (
731
769
 
732
770
  // Static re-export from Layer entry point — no esbuild, no user packages
733
771
  let {code, sourceCodeHash} = Util_Bundle.buildCodeArchive(
734
- ~entryPointModule="@reventlessdev/reventless-aws/src/adapter/Runtime/HeartbeatEntryPoint.mjs",
772
+ ~entryPointModule="@reventlessdev/reventless-aws/src/adapter/Runtime/HeartbeatEntryPoint.res.mjs",
735
773
  ~packageDirs=Dict.make(),
736
774
  )
737
775
 
@@ -768,8 +806,28 @@ module Make = (
768
806
  registeredSliceModulePaths
769
807
  ->Array.concat(dcbConfig.stateChangeSliceModulePaths)
770
808
  ->Array.map(({specPath, behaviorPath}) => (specPath, behaviorPath))
809
+ // Drop registrations that never received their module paths (a bare
810
+ // audit-table registration with no matching spec functor — shouldn't happen,
811
+ // but keeps a half-registered slice out of HANDLER_CONFIG).
812
+ let inboundSlices =
813
+ registeredInboundSlices
814
+ ->Dict.valuesToArray
815
+ ->Array.filterMap(reg =>
816
+ reg.specPath == ""
817
+ ? None
818
+ : Some(
819
+ (
820
+ {
821
+ StateChangeSliceRuntime_Builder_Single.specPath: reg.specPath,
822
+ translationPath: reg.translationPath,
823
+ auditTableName: reg.auditTableName,
824
+ }: StateChangeSliceRuntime_Builder_Single.inboundSlicePaths
825
+ ),
826
+ )
827
+ )
771
828
  StateChangeSliceRuntime_Builder_Single.forDcbCommandTopic(
772
829
  ~slicePaths,
830
+ ~inboundSlices,
773
831
  ~dcbTableName=dcbConfig.dcbTableName,
774
832
  ~pluginName=dcbConfig.pluginName,
775
833
  ~syncConfig=syncStateChangesConfigRef.contents,