@reventlessdev/reventless-aws 3.0.0-alpha.265 → 3.0.0-alpha.267

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.
@@ -0,0 +1,227 @@
1
+ // AWS Location Service geocoder, mounted on the platform GraphQL API.
2
+ //
3
+ // Deploy-time only: `make` provisions one compiled-EntryPoint Lambda (a plain
4
+ // `Lambda.Function` whose code archive re-exports `handler` from the compiled,
5
+ // type-checked runtime module `Geocoder_AwsLocation_Resolver_Ops` and ships the
6
+ // shared ESM resolve-hook loader), an IAM execution role scoped to CloudWatch Logs
7
+ // + `geo:SearchPlaceIndexForText` on the target place index, an AppSync Lambda data
8
+ // source, and one resolver — `Query.geocode` — on the platform API.
9
+ //
10
+ // There is no Function URL and no anonymous surface: authentication is the platform
11
+ // API's Cognito authorizer. This is the *client* door of the geocoding capability
12
+ // (D9 half 2), replacing the public Function URL the browser used to call. The
13
+ // unattended slice path reaches the same place index through the SDK directly
14
+ // (`Geocoder_AwsLocation_Backend`), so one capability, two doors — the same shape
15
+ // the object store already has with `Upload_Presign` and `Offload.resolve`.
16
+ //
17
+ // Why an EntryPoint and not a Pulumi `CallbackFunction`: same SDK-skew reason as the
18
+ // upload service — a serialized closure bakes the deploy machine's version-specific
19
+ // AWS SDK internals into the archive but then resolves `@smithy/*`/`@aws-sdk/*`
20
+ // transitives from independently-versioned layer/runtime sources that disagree at
21
+ // cold start. Shipping the compiled `_Ops` module with bare `@aws-sdk/*` imports,
22
+ // resolved through the resolve-hook, loads one internally consistent SDK. The
23
+ // runtime logic lives in [Geocoder_AwsLocation_Resolver_Ops.res].
24
+
25
+ open PulumiAws
26
+
27
+ type serviceOutputs = {resources: array<Pulumi.Output.t<string>>}
28
+
29
+ // JS resolver code (APPSYNC_JS runtime): forward the caller's arguments to the
30
+ // Lambda. CORS and auth belong to the API, not to this code. No identity is
31
+ // forwarded — geocoding is not scoped per caller (any authenticated user may
32
+ // resolve any address), unlike the upload service which namespaces objects by the
33
+ // verified `sub`.
34
+ let invokeCode: Pulumi.Input.t<string> =
35
+ `import { util } from '@aws-appsync/utils';
36
+ export function request(ctx) {
37
+ return { operation: 'Invoke', payload: { arguments: ctx.args } };
38
+ }
39
+ export function response(ctx) {
40
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
41
+ return ctx.result;
42
+ }
43
+ `->Pulumi.Input.make
44
+
45
+ let make = (
46
+ ~api: Pulumi.Output.t<AppSync.GraphQLApi.t>,
47
+ ~placeIndexName: Pulumi.Input.t<string>,
48
+ ~name: string="GeocodeService",
49
+ ~opts: Pulumi.ComponentResource.options,
50
+ ): serviceOutputs => {
51
+ let opts = opts->ReventlessCore.Util.Pulumi.ComponentResourceOptions.toCustomResourceOptions
52
+
53
+ let lambdaRole = IAM.Role.makeWithDefaultPolicy(
54
+ ~name=name ++ "Lambda",
55
+ ~servicePrincipal=AWS.Lambda.principal->Pulumi.Output.make,
56
+ ~tags=AWS.Tags.make(
57
+ ~name=name ++ "Lambda",
58
+ ~kind=ReventlessCore.ComponentType.Platform,
59
+ ~role=Identity,
60
+ ~scope=Platform,
61
+ ),
62
+ ~opts,
63
+ )
64
+
65
+ // CloudWatch Logs (so failures are observable) plus least-privilege
66
+ // `geo:SearchPlaceIndexForText` on the one place index — the same grant the
67
+ // Function URL flavour carried, now on a resolver-invoked Lambda.
68
+ let _policy =
69
+ placeIndexName
70
+ ->Pulumi.Output.fromInput
71
+ ->Pulumi.Output.apply(idx => {
72
+ let arn = `arn:aws:geo:*:*:place-index/${idx}`
73
+ let _ = IAM.RolePolicy.make(
74
+ ~name=name ++ "LambdaPolicy",
75
+ ~args={
76
+ policy: PolicyDocument.make(
77
+ ~id=name ++ "LambdaPolicy",
78
+ ~statements=[
79
+ {
80
+ sid: "AllowLambdaLogging",
81
+ effect: Allow,
82
+ actions: Actions(["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"]),
83
+ resources: Resource("arn:aws:logs:*:*:*"),
84
+ },
85
+ {
86
+ sid: "AllowGeocode",
87
+ effect: Allow,
88
+ actions: Action("geo:SearchPlaceIndexForText"),
89
+ resources: Resource(arn),
90
+ },
91
+ ],
92
+ )
93
+ ->PolicyDocument.toJsonString
94
+ ->Pulumi.Input.make,
95
+ role: lambdaRole.id->Pulumi.Output.asInput,
96
+ },
97
+ ~opts,
98
+ )
99
+ })
100
+
101
+ // Bundle reventless-aws (the compiled `_Ops` handler lives inside it) and
102
+ // re-export its `handler`; buildCodeArchive also ships the ESM resolve-hook.
103
+ let packageDirs = Dict.fromArray([
104
+ (
105
+ "@reventlessdev/reventless-aws",
106
+ Util_Bundle.resolvePackageRoot("@reventlessdev/reventless-aws"),
107
+ ),
108
+ ])
109
+ let {code, sourceCodeHash} = Util_Bundle.buildCodeArchive(
110
+ ~entryPointModule="@reventlessdev/reventless-aws/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver_Ops.res.mjs",
111
+ ~packageDirs,
112
+ )
113
+
114
+ let layers =
115
+ Lambda.reventlessLayerArn
116
+ ->Option.map(arn => [arn->Pulumi.Input.make])
117
+ ->Option.getOr([])
118
+ ->Pulumi.Input.make
119
+
120
+ let lambda = Lambda.Function.make(
121
+ ~name=name ++ "Lambda",
122
+ ~args={
123
+ handler: "index.handler"->Pulumi.Input.make,
124
+ runtime: "nodejs22.x"->Pulumi.Input.make,
125
+ code: code->Pulumi.Input.make,
126
+ sourceCodeHash: sourceCodeHash->Pulumi.Input.make,
127
+ role: lambdaRole.arn->Pulumi.Output.asInput,
128
+ memorySize: 256->Pulumi.Input.make,
129
+ timeout: 30->Pulumi.Input.make,
130
+ layers,
131
+ tags: AWS.Tags.make(
132
+ ~name=name ++ "Lambda",
133
+ ~kind=ReventlessCore.ComponentType.Platform,
134
+ ~role=Runtime,
135
+ ~scope=Platform,
136
+ ),
137
+ environment: (
138
+ {
139
+ Lambda.Function.variables: Dict.fromArray([
140
+ ("Environment", Pulumi.Pulumi.getStackName()->Pulumi.Input.make),
141
+ ("PLACE_INDEX_NAME", placeIndexName),
142
+ ("NODE_OPTIONS", Util_Bundle.esmLoaderNodeOptions->Pulumi.Input.make),
143
+ ("ESM_FALLBACK_DIRS", Util_Bundle.esmFallbackDirs->Pulumi.Input.make),
144
+ Util_LambdaLogging.logLevelEntry(),
145
+ ]),
146
+ }: Lambda.Function.functionEnvironment
147
+ )->Pulumi.Input.make,
148
+ },
149
+ ~opts,
150
+ )
151
+
152
+ Util_LambdaLogging.makeManagedLogGroup(
153
+ ~name=name ++ "Lambda",
154
+ ~lambdaName=lambda.name,
155
+ ~tags=AWS.Tags.make(
156
+ ~name=name ++ "LambdaLogGroup",
157
+ ~kind=ReventlessCore.ComponentType.Platform,
158
+ ~role=Logs,
159
+ ~scope=Platform,
160
+ ),
161
+ ~opts,
162
+ (),
163
+ )
164
+
165
+ let dataSourceRole = IAM.Role.makeWithDefaultPolicy(
166
+ ~name=name ++ "DataSource",
167
+ ~servicePrincipal=AWS.AppSync.principal->Pulumi.Output.make,
168
+ ~tags=AWS.Tags.make(
169
+ ~name=name ++ "DataSource",
170
+ ~kind=ReventlessCore.ComponentType.Platform,
171
+ ~role=Identity,
172
+ ~scope=Platform,
173
+ ),
174
+ ~opts,
175
+ )
176
+
177
+ let _ =
178
+ (lambda.arn, dataSourceRole.id)
179
+ ->Pulumi.Output.all2
180
+ ->Pulumi.Output.apply(((lambdaArn, dataSourceRoleId)) => {
181
+ let _attach = IAM.RolePolicy.make(
182
+ ~name=name ++ "DataSource",
183
+ ~args={
184
+ policy: PolicyDocument.make(
185
+ ~id=name ++ "DataSourcePolicy",
186
+ ~statements=[
187
+ {
188
+ sid: "AllowDataSourceInvokeLambda",
189
+ effect: Allow,
190
+ actions: Action("lambda:InvokeFunction"),
191
+ resources: Resource(lambdaArn),
192
+ },
193
+ ],
194
+ )
195
+ ->PolicyDocument.toJsonString
196
+ ->Pulumi.Input.make,
197
+ role: dataSourceRoleId->Pulumi.Input.make,
198
+ },
199
+ ~opts,
200
+ )
201
+ })
202
+
203
+ let dataSource = AppSync.DataSource.make(
204
+ ~name=name ++ "DataSource",
205
+ ~args={
206
+ type_: AWS_LAMBDA,
207
+ apiId: api->Pulumi.Output.flatMap(api => api.id)->Pulumi.Output.asInput,
208
+ lambdaConfig: {
209
+ AppSync.DataSource.functionArn: lambda.arn->Pulumi.Output.asInput,
210
+ }->Pulumi.Input.make,
211
+ serviceRoleArn: dataSourceRole.arn->Pulumi.Output.asInput,
212
+ },
213
+ ~opts=Some(opts),
214
+ )
215
+
216
+ let _geocodeResolver = AppSync_Resolver_Native.makeUnitJsResolver(
217
+ ~name=name ++ "Geocode",
218
+ ~api,
219
+ ~dataSourceName=dataSource.name->Pulumi.Output.asInput,
220
+ ~type_="Query"->Pulumi.Input.make,
221
+ ~field="geocode"->Pulumi.Input.make,
222
+ ~code=invokeCode,
223
+ ~opts,
224
+ )
225
+
226
+ {resources: [lambda.arn]}
227
+ }
@@ -0,0 +1,127 @@
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 Lambda$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/Lambda/Lambda.res.mjs";
9
+ import * as AWS$ReventlessAws from "../AWS.res.mjs";
10
+ import * as AWS_Tags$ReventlessAws from "../AWS_Tags.res.mjs";
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";
13
+ import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Pulumi.res.mjs";
14
+ import * as Util_LambdaLogging$ReventlessAws from "../../util/Util_LambdaLogging.res.mjs";
15
+ import * as AppSync_Resolver_Native$ReventlessAws from "../Api/AppSync_Resolver_Native.res.mjs";
16
+
17
+ let invokeCode = `import { util } from '@aws-appsync/utils';
18
+ export function request(ctx) {
19
+ return { operation: 'Invoke', payload: { arguments: ctx.args } };
20
+ }
21
+ export function response(ctx) {
22
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
23
+ return ctx.result;
24
+ }
25
+ `;
26
+
27
+ function make(api, placeIndexName, nameOpt, opts) {
28
+ let name = nameOpt !== undefined ? nameOpt : "GeocodeService";
29
+ let opts$1 = Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions(opts);
30
+ let lambdaRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name + "Lambda", Pulumi.output(AWS$ReventlessAws.Lambda.principal), AWS_Tags$ReventlessAws.make(name + "Lambda", "Platform", "Identity", "Platform", undefined, undefined, undefined, undefined), opts$1);
31
+ placeIndexName.apply(idx => {
32
+ let arn = `arn:aws:geo:*:*:place-index/` + idx;
33
+ new (Aws.iam.RolePolicy)(name + "LambdaPolicy", {
34
+ policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + "LambdaPolicy", [
35
+ {
36
+ Sid: "AllowLambdaLogging",
37
+ Effect: "Allow",
38
+ Action: [
39
+ "logs:CreateLogGroup",
40
+ "logs:CreateLogStream",
41
+ "logs:PutLogEvents"
42
+ ],
43
+ Resource: "arn:aws:logs:*:*:*"
44
+ },
45
+ {
46
+ Sid: "AllowGeocode",
47
+ Effect: "Allow",
48
+ Action: "geo:SearchPlaceIndexForText",
49
+ Resource: arn
50
+ }
51
+ ])),
52
+ role: lambdaRole.id
53
+ }, opts$1);
54
+ });
55
+ let packageDirs = Object.fromEntries([[
56
+ "@reventlessdev/reventless-aws",
57
+ Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws")
58
+ ]]);
59
+ let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver_Ops.res.mjs", packageDirs, undefined);
60
+ let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
61
+ let lambda = new (Aws.lambda.Function)(name + "Lambda", {
62
+ handler: "index.handler",
63
+ runtime: "nodejs22.x",
64
+ code: match.code,
65
+ role: lambdaRole.arn,
66
+ memorySize: 256,
67
+ timeout: 30,
68
+ layers: layers,
69
+ tags: AWS_Tags$ReventlessAws.make(name + "Lambda", "Platform", "Runtime", "Platform", undefined, undefined, undefined, undefined),
70
+ environment: {
71
+ variables: Object.fromEntries([
72
+ [
73
+ "Environment",
74
+ Pulumi.getStack()
75
+ ],
76
+ [
77
+ "PLACE_INDEX_NAME",
78
+ placeIndexName
79
+ ],
80
+ [
81
+ "NODE_OPTIONS",
82
+ Util_Bundle$ReventlessAws.esmLoaderNodeOptions
83
+ ],
84
+ [
85
+ "ESM_FALLBACK_DIRS",
86
+ Util_Bundle$ReventlessAws.esmFallbackDirs
87
+ ],
88
+ Util_LambdaLogging$ReventlessAws.logLevelEntry()
89
+ ])
90
+ },
91
+ sourceCodeHash: match.sourceCodeHash
92
+ }, 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
+ 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
+ Pulumi.all([
96
+ lambda.arn,
97
+ dataSourceRole.id
98
+ ]).apply(param => {
99
+ new (Aws.iam.RolePolicy)(name + "DataSource", {
100
+ policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + "DataSourcePolicy", [{
101
+ Sid: "AllowDataSourceInvokeLambda",
102
+ Effect: "Allow",
103
+ Action: "lambda:InvokeFunction",
104
+ Resource: param[0]
105
+ }])),
106
+ role: param[1]
107
+ }, opts$1);
108
+ });
109
+ let dataSource = new (Aws.appsync.DataSource)(name + "DataSource", {
110
+ type: "AWS_LAMBDA",
111
+ apiId: Output$Pulumi.flatMap(api, api => api.id),
112
+ lambdaConfig: {
113
+ functionArn: lambda.arn
114
+ },
115
+ serviceRoleArn: dataSourceRole.arn
116
+ }, opts$1);
117
+ AppSync_Resolver_Native$ReventlessAws.makeUnitJsResolver(name + "Geocode", api, dataSource.name, "Query", "geocode", invokeCode, opts$1);
118
+ return {
119
+ resources: [lambda.arn]
120
+ };
121
+ }
122
+
123
+ export {
124
+ invokeCode,
125
+ make,
126
+ }
127
+ /* @pulumi/aws Not a pure module */
@@ -0,0 +1,55 @@
1
+ // Runtime handler for the geocoder's client door — compiled, type-checked, and
2
+ // Pulumi-free so it can be shipped as an EntryPoint module
3
+ // (`Geocoder_AwsLocation_Resolver` bundles it and attaches it as the platform API's
4
+ // `Query.geocode` Lambda data source). Keeping it out of the deploy-time module
5
+ // avoids both the serialized-closure SDK skew and a deploy-time Pulumi import
6
+ // leaking into the Lambda's cold-start graph.
7
+ //
8
+ // Invoked by an AppSync resolver, not a Function URL: the platform API's Cognito
9
+ // authorizer has already authenticated the caller, so there is no token to decode
10
+ // and no anonymous surface. The SDK call, the `[lng, lat]` order and the relevance
11
+ // handling all live in `Geocoder_AwsLocation_Backend`, shared with the unattended
12
+ // slice path so one module owns the AWS Location call.
13
+ //
14
+ // Returns the ranked candidates as `[{label, lat, lng, relevance?}]`. A no-match is
15
+ // an empty array — a browser search box degrades to "no results". A service failure
16
+ // throws, which the resolver's response mapper turns into a GraphQL error rather
17
+ // than an empty answer: the client half of D2's status contract, so a browser can
18
+ // tell "no such address" from "the geocoder is down" the way the Function URL's
19
+ // `200`/`502` split did.
20
+
21
+ let candidateJson = (c: Reventless.Geocoding.candidate): JSON.t =>
22
+ Dict.fromArray(
23
+ Array.concat(
24
+ [
25
+ ("label", JSON.Encode.string(c.label)),
26
+ ("lat", JSON.Encode.float(c.point.lat)),
27
+ ("lng", JSON.Encode.float(c.point.lng)),
28
+ ],
29
+ switch c.relevance {
30
+ | Some(rel) => [("relevance", JSON.Encode.float(rel))]
31
+ | None => []
32
+ },
33
+ ),
34
+ )->JSON.Encode.object
35
+
36
+ // Only `text` is read; `ctx.identity` is forwarded by the resolver but the geocoder
37
+ // does not scope by caller, so the handler ignores it.
38
+ type geocodeArgs = {text?: string}
39
+ type appSyncEvent = {arguments?: geocodeArgs}
40
+
41
+ let handler = async (event: appSyncEvent): array<JSON.t> => {
42
+ let indexName = switch NodeProcess.env->Dict.get("PLACE_INDEX_NAME") {
43
+ | Some("") | None => ""
44
+ | Some(v) => v
45
+ }
46
+ let text = event.arguments->Option.flatMap(a => a.text)->Option.getOr("")
47
+ switch await Geocoder_AwsLocation_Backend.search(~indexName, ~text) {
48
+ | Ok(candidates) => candidates->Array.map(candidateJson)
49
+ // The provider answered and had nothing — a true, final empty answer.
50
+ | Error(NoMatch) => []
51
+ // Misconfigured (no index) or the service failed: throw so `ctx.error` is set and
52
+ // the caller sees an error, never a silent empty list it would read as "no match".
53
+ | Error(Unavailable(msg)) => JsError.throwWithMessage(`geocoder unavailable: ${msg}`)
54
+ }
55
+ }
@@ -0,0 +1,48 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
5
+ import * as Geocoder_AwsLocation_Backend$ReventlessAws from "./Geocoder_AwsLocation_Backend.res.mjs";
6
+
7
+ function candidateJson(c) {
8
+ let rel = c.relevance;
9
+ return Object.fromEntries([
10
+ [
11
+ "label",
12
+ c.label
13
+ ],
14
+ [
15
+ "lat",
16
+ c.point.lat
17
+ ],
18
+ [
19
+ "lng",
20
+ c.point.lng
21
+ ]
22
+ ].concat(rel !== undefined ? [[
23
+ "relevance",
24
+ rel
25
+ ]] : []));
26
+ }
27
+
28
+ async function handler(event) {
29
+ let v = process.env["PLACE_INDEX_NAME"];
30
+ let indexName = v !== undefined && v !== "" ? v : "";
31
+ let text = Stdlib_Option.getOr(Stdlib_Option.flatMap(event.arguments, a => a.text), "");
32
+ let candidates = await Geocoder_AwsLocation_Backend$ReventlessAws.search(indexName, text, undefined);
33
+ if (candidates.TAG === "Ok") {
34
+ return candidates._0.map(candidateJson);
35
+ }
36
+ let msg = candidates._0;
37
+ if (typeof msg !== "object") {
38
+ return [];
39
+ } else {
40
+ return Stdlib_JsError.throwWithMessage(`geocoder unavailable: ` + msg._0);
41
+ }
42
+ }
43
+
44
+ export {
45
+ candidateJson,
46
+ handler,
47
+ }
48
+ /* Geocoder_AwsLocation_Backend-ReventlessAws Not a pure module */
@@ -0,0 +1,53 @@
1
+ // Guards the geocode resolver handler's contract — the client door of the
2
+ // geocoding capability (D9 half 2), which replaced the Function URL. The Function
3
+ // URL served two callers through one `200`/`502` body-vs-status split; this handler
4
+ // serves only the browser, through GraphQL, so the contract is simpler:
5
+ //
6
+ // a value returned — an answer (a possibly-empty candidate list)
7
+ // a thrown error — no answer (the resolver's response mapper turns it into a
8
+ // GraphQL error, so the client degrades rather than reading
9
+ // an empty list as "no such address")
10
+ //
11
+ // The two arms below are the ones reachable without a live AWS Location call: an
12
+ // unset index throws (misconfiguration is not a verdict on the address), and an
13
+ // empty query returns `[]` (nothing was asked). The success path and the thrown
14
+ // service-failure path both need the SDK and stay unasserted here — see the plan's
15
+ // Verification note.
16
+
17
+ open JestGlobals
18
+
19
+ let setIndex = v => NodeProcess.env->Dict.set("PLACE_INDEX_NAME", v)
20
+ let clearIndex = () => NodeProcess.env->Dict.delete("PLACE_INDEX_NAME")
21
+
22
+ describe("Geocoder_AwsLocation_Resolver_Ops.handler contract", () => {
23
+ test("an unset PLACE_INDEX_NAME throws — a misconfiguration, not a verdict", async () => {
24
+ clearIndex()
25
+ let threw = switch await Geocoder_AwsLocation_Resolver_Ops.handler({
26
+ arguments: {text: "10 Downing Street"},
27
+ }) {
28
+ | _ => false
29
+ | exception _ => true
30
+ }
31
+ // Returning `[]` here would tell the browser this address does not exist, when
32
+ // in fact the deployment has no geocoder — the client must see an error and be
33
+ // able to distinguish the two, which is the whole point of throwing.
34
+ expect(threw)->toBe(true)
35
+ clearIndex()
36
+ })
37
+
38
+ test("an empty query is an answer — nothing was asked, so `[]`", async () => {
39
+ setIndex("some-index")
40
+ let results = await Geocoder_AwsLocation_Resolver_Ops.handler({arguments: {text: ""}})
41
+ // The empty text short-circuits in the shared backend before any SDK call, so
42
+ // this arm is reachable in a unit test and returns a true, final empty answer.
43
+ expect(results->Array.length)->toBe(0)
44
+ clearIndex()
45
+ })
46
+
47
+ test("a missing text argument is treated as an empty query", async () => {
48
+ setIndex("some-index")
49
+ let results = await Geocoder_AwsLocation_Resolver_Ops.handler({arguments: {}})
50
+ expect(results->Array.length)->toBe(0)
51
+ clearIndex()
52
+ })
53
+ })
@@ -0,0 +1,55 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
4
+ import * as Geocoder_AwsLocation_Resolver_Ops$ReventlessAws from "../src/adapter/Geocoder/Geocoder_AwsLocation_Resolver_Ops.res.mjs";
5
+
6
+ function setIndex(v) {
7
+ process.env["PLACE_INDEX_NAME"] = v;
8
+ }
9
+
10
+ function clearIndex() {
11
+ Stdlib_Dict.$$delete(process.env, "PLACE_INDEX_NAME");
12
+ }
13
+
14
+ globalThis.describe("Geocoder_AwsLocation_Resolver_Ops.handler contract", () => {
15
+ globalThis.test("an unset PLACE_INDEX_NAME throws — a misconfiguration, not a verdict", async () => {
16
+ Stdlib_Dict.$$delete(process.env, "PLACE_INDEX_NAME");
17
+ let threw;
18
+ try {
19
+ await Geocoder_AwsLocation_Resolver_Ops$ReventlessAws.handler({
20
+ arguments: {
21
+ text: "10 Downing Street"
22
+ }
23
+ });
24
+ threw = false;
25
+ } catch (exn) {
26
+ threw = true;
27
+ }
28
+ globalThis.expect(threw).toBe(true);
29
+ return Stdlib_Dict.$$delete(process.env, "PLACE_INDEX_NAME");
30
+ });
31
+ globalThis.test("an empty query is an answer — nothing was asked, so `[]`", async () => {
32
+ setIndex("some-index");
33
+ let results = await Geocoder_AwsLocation_Resolver_Ops$ReventlessAws.handler({
34
+ arguments: {
35
+ text: ""
36
+ }
37
+ });
38
+ globalThis.expect(results.length).toBe(0);
39
+ return Stdlib_Dict.$$delete(process.env, "PLACE_INDEX_NAME");
40
+ });
41
+ globalThis.test("a missing text argument is treated as an empty query", async () => {
42
+ setIndex("some-index");
43
+ let results = await Geocoder_AwsLocation_Resolver_Ops$ReventlessAws.handler({
44
+ arguments: {}
45
+ });
46
+ globalThis.expect(results.length).toBe(0);
47
+ return Stdlib_Dict.$$delete(process.env, "PLACE_INDEX_NAME");
48
+ });
49
+ });
50
+
51
+ export {
52
+ setIndex,
53
+ clearIndex,
54
+ }
55
+ /* Not a pure module */