1import * as GeoRoutes from "@/AWS/GeoRoutes";
2import * as Lambda from "@/AWS/Lambda";
3import * as Cause from "effect/Cause";
4import * as Duration from "effect/Duration";
5import * as Effect from "effect/Effect";
6import * as Layer from "effect/Layer";
7import { HttpServerRequest } from "effect/http/HttpServerRequest";
8import * as HttpServerResponse from "effect/http/HttpServerResponse";
9import path from "pathe";
10
11const main = path.resolve(import.meta.dirname, "handler.ts");
12
13export class GeoRoutesTestFunction extends Lambda.Function<Lambda.Function>()(
14 "GeoRoutesTestFunction",
15) {}
16
17export default GeoRoutesTestFunction.make(
18 {
19 main,
20 functionUrl: true,
21 // Geo calls fan out to upstream providers and can exceed Lambda's 3s default.
22 timeout: Duration.seconds(30),
23 },
24 Effect.gen(function* () {
25 const calculateRoutes = yield* GeoRoutes.CalculateRoutes();
26 const calculateIsolines = yield* GeoRoutes.CalculateIsolines();
27 const calculateRouteMatrix = yield* GeoRoutes.CalculateRouteMatrix();
28 const optimizeWaypoints = yield* GeoRoutes.OptimizeWaypoints();
29 const snapToRoads = yield* GeoRoutes.SnapToRoads();
30
31 return {
32 fetch: Effect.gen(function* () {
33 const request = yield* HttpServerRequest;
34 const pathname = new URL(request.originalUrl).pathname;
35
36 // Cheap readiness route — no AWS call.
37 if (request.method === "GET" && pathname === "/ping") {
38 return yield* HttpServerResponse.json({ ok: true });
39 }
40
41 if (request.method === "GET" && pathname === "/calculate-routes") {
42 const result = yield* calculateRoutes({
43 // [longitude, latitude] — two points in Seattle.
44 Origin: [-122.339, 47.61],
45 Destination: [-122.201, 47.61],
46 TravelMode: "Car",
47 });
48 const routes = result.Routes ?? [];
49 return yield* HttpServerResponse.json({
50 count: routes.length,
51 distance: routes[0]?.Summary?.Distance,
52 duration: routes[0]?.Summary?.Duration,
53 });
54 }
55
56 if (request.method === "GET" && pathname === "/calculate-isolines") {
57 const result = yield* calculateIsolines({
58 // [longitude, latitude] — downtown Seattle.
59 Origin: [-122.339, 47.61],
60 // 10-minute drive-time isochrone.
61 Thresholds: { Time: [600] },
62 TravelMode: "Car",
63 });
64 const isolines = result.Isolines ?? [];
65 return yield* HttpServerResponse.json({
66 count: isolines.length,
67 geometryCount: isolines[0]?.Geometries?.length ?? 0,
68 pricingBucket: result.PricingBucket,
69 });
70 }
71
72 if (
73 request.method === "GET" &&
74 pathname === "/calculate-route-matrix"
75 ) {
76 const result = yield* calculateRouteMatrix({
77 // [longitude, latitude] — 2x2 matrix within the Seattle area.
78 Origins: [
79 { Position: [-122.339, 47.61] },
80 { Position: [-122.335, 47.608] },
81 ],
82 Destinations: [
83 { Position: [-122.201, 47.61] },
84 { Position: [-122.313, 47.62] },
85 ],
86 RoutingBoundary: {
87 Geometry: {
88 // Radius in meters — covers all origins and destinations.
89 Circle: { Center: [-122.3, 47.61], Radius: 30_000 },
90 },
91 },
92 });
93 const matrix = result.RouteMatrix ?? [];
94 return yield* HttpServerResponse.json({
95 rows: matrix.length,
96 columns: matrix[0]?.length ?? 0,
97 firstDuration: matrix[0]?.[0]?.Duration,
98 errorCount: result.ErrorCount,
99 pricingBucket: result.PricingBucket,
100 });
101 }
102
103 if (request.method === "GET" && pathname === "/optimize-waypoints") {
104 const result = yield* optimizeWaypoints({
105 // [longitude, latitude] — Seattle loop with two stops.
106 Origin: [-122.339, 47.61],
107 Destination: [-122.201, 47.61],
108 Waypoints: [
109 { Id: "stop-1", Position: [-122.335, 47.608] },
110 { Id: "stop-2", Position: [-122.313, 47.62] },
111 ],
112 });
113 return yield* HttpServerResponse.json({
114 optimizedCount: result.OptimizedWaypoints?.length ?? 0,
115 order: (result.OptimizedWaypoints ?? []).map((w) => w.Id),
116 distance: result.Distance,
117 duration: result.Duration,
118 pricingBucket: result.PricingBucket,
119 });
120 }
121
122 if (request.method === "GET" && pathname === "/snap-to-roads") {
123 const result = yield* snapToRoads({
124 // [longitude, latitude] — a short GPS trace along a Seattle street.
125 TracePoints: [
126 { Position: [-122.339, 47.61] },
127 { Position: [-122.337, 47.609] },
128 { Position: [-122.335, 47.608] },
129 ],
130 TravelMode: "Car",
131 });
132 return yield* HttpServerResponse.json({
133 snappedCount: result.SnappedTracePoints?.length ?? 0,
134 firstConfidence: result.SnappedTracePoints?.[0]?.Confidence,
135 pricingBucket: result.PricingBucket,
136 });
137 }
138
139 return yield* HttpServerResponse.json(
140 { error: "Not found", method: request.method, pathname },
141 { status: 404 },
142 );
143 }).pipe(
144 // Surface the typed error (tag + message) instead of an opaque 500 so
145 // test failures show the real cause in the response body.
146 Effect.catchCause((cause) =>
147 HttpServerResponse.json(
148 { error: Cause.pretty(cause) },
149 { status: 500 },
150 ),
151 ),
152 ),
153 };
154 }).pipe(
155 Effect.provide(
156 Layer.mergeAll(
157 GeoRoutes.CalculateRoutesHttp,
158 GeoRoutes.CalculateIsolinesHttp,
159 GeoRoutes.CalculateRouteMatrixHttp,
160 GeoRoutes.OptimizeWaypointsHttp,
161 GeoRoutes.SnapToRoadsHttp,
162 ),
163 ),
164 ),
165);