1import type * as Effect from "effect/Effect";
2import type * as Stream from "effect/Stream";
3import {
4 consumeBusEvents,
5 type EventRecord,
6 type EventRouteProps,
7} from "../EventBridge/EventSource.ts";
8
9/**
10 * The `detail` payload Amazon Location Service delivers to EventBridge.
11 * Geofence events describe an ENTER/EXIT transition; device position events
12 * describe an accepted position update on a tracker with EventBridge
13 * publishing enabled. Fields not shared by every event kind are optional
14 * (the schema grows over time).
15 */
16export interface TrackerEventDetail {
17 /**
18 * Geofence events: `ENTER` or `EXIT`. Device position events: `UPDATE`.
19 */
20 EventType?: string;
21 /** The device the event is about. */
22 DeviceId?: string;
23 /** Geofence events: the geofence that was entered or exited. */
24 GeofenceId?: string;
25 /** The position sample that triggered the event ([longitude, latitude]). */
26 Position?: number[];
27 /** When the device reported the position. */
28 SampleTime?: string;
29 /** Additional event fields (the schema grows over time). */
30 [key: string]: unknown;
31}
32
33/** An Amazon Location EventBridge event delivered to the handler. */
34export type TrackerEvent = EventRecord<TrackerEventDetail>;
35
36/** Which Amazon Location notifications to subscribe to. */
37export type TrackerEventKind = "geofence-event" | "device-position-event";
38
39const DETAIL_TYPES: Record<TrackerEventKind, string> = {
40 "geofence-event": "Location Geofence Event",
41 "device-position-event": "Location Device Position Event",
42};
43
44export interface TrackerEventSourceProps extends EventRouteProps {
45 /**
46 * Logical id for the backing EventBridge rule.
47 * @default "LocationTrackerEvents"
48 */
49 id?: string;
50 /**
51 * Which notifications to subscribe to: geofence ENTER/EXIT events
52 * (emitted when a linked tracker's positions breach a geofence) and/or
53 * raw device position updates (emitted by trackers with
54 * `eventBridgeEnabled: true`).
55 * @default ["geofence-event"]
56 */
57 kinds?: readonly TrackerEventKind[];
58 /**
59 * Restrict to events about specific resources (matched against the
60 * event's top-level `resources`, which includes the tracker and geofence
61 * collection ARNs).
62 */
63 resourceArns?: readonly string[];
64}
65
66/**
67 * Event source connecting Amazon Location Service tracking notifications to
68 * the hosting compute. Location publishes geofence ENTER/EXIT events (for
69 * trackers linked to a geofence collection via {@link TrackerConsumer}) and
70 * raw device position updates (for trackers with `eventBridgeEnabled: true`)
71 * to the account's default EventBridge bus (source `aws.geo`); this
72 * subscribes the host Function to those events.
73 *
74 * Location publishes to EventBridge automatically — no additional resource
75 * is created besides the EventBridge rule targeting the host. Provide the
76 * host-specific implementation layer (e.g. `AWS.Lambda.EventSource`) on the
77 * Function effect.
78 *
79 * ### Consuming Tracker Events
80 * **Example:** React to Geofence Breaches
81 * ```typescript
82 * import * as AWS from "alchemy/AWS";
83 *
84 * export default AlertFunction.make(
85 * { main: import.meta.url },
86 * Effect.gen(function* () {
87 * yield* AWS.Location.consumeTrackerEvents(
88 * { kinds: ["geofence-event"] },
89 * (events) =>
90 * Stream.runForEach(events, (event) =>
91 * Effect.log(
92 * `${event.detail.DeviceId} ${event.detail.EventType} ${event.detail.GeofenceId}`,
93 * ),
94 * ),
95 * );
96 * return {};
97 * }).pipe(Effect.provide(AWS.Lambda.EventSource)),
98 * );
99 * ```
100 */
101export const consumeTrackerEvents = <StreamReq = never, Req = never>(
102 props: TrackerEventSourceProps,
103 process: (
104 events: Stream.Stream<TrackerEvent, never, StreamReq>,
105 ) => Effect.Effect<void, never, Req>,
106) =>
107 consumeBusEvents(
108 props.id ?? "LocationTrackerEvents",
109 {
110 source: ["aws.geo"],
111 "detail-type": (props.kinds ?? (["geofence-event"] as const)).map(
112 (kind) => DETAIL_TYPES[kind],
113 ),
114 ...(props.resourceArns !== undefined
115 ? { resources: [...props.resourceArns] }
116 : {}),
117 },
118 { description: props.description, state: props.state },
119 process,
120 );