@refraction-ui/react 0.6.0 → 0.8.0

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,165 @@
1
+ import { AnalyticsEvent, AnalyticsSink } from '../analytics/index.cjs';
2
+
3
+ /**
4
+ * Minimal structural shape of the bits of `@microsoft/applicationinsights-web`
5
+ * we use. Declared locally so the vendor lib stays an OPTIONAL peer with NO
6
+ * type-level hard dependency — it is only ever reached via dynamic import.
7
+ */
8
+ interface AppInsightsLike {
9
+ loadAppInsights?: () => unknown;
10
+ trackEvent: (event: {
11
+ name: string;
12
+ }, customProperties?: Record<string, unknown>) => void;
13
+ trackPageView: (pageView?: {
14
+ name?: string;
15
+ uri?: string;
16
+ properties?: Record<string, unknown>;
17
+ measurements?: Record<string, number>;
18
+ }) => void;
19
+ setAuthenticatedUserContext?: (authenticatedUserId: string, accountId?: string, storeInCookie?: boolean) => void;
20
+ clearAuthenticatedUserContext?: () => void;
21
+ flush?: (async?: boolean) => void;
22
+ }
23
+ /** Fan-out mode for the App Insights sink. */
24
+ type AppInsightsSinkMode = 'client-sdk' | 'http';
25
+ interface BaseOptions {
26
+ /** Consent categories this sink requires. Default `['analytics']`. */
27
+ consentCategories?: string[];
28
+ /** Stable sink name. Default `'app-insights'`. */
29
+ name?: string;
30
+ }
31
+ /**
32
+ * `client-sdk` mode — runs in the browser via `@microsoft/applicationinsights-web`.
33
+ *
34
+ * The vendor SDK is loaded **lazily via dynamic import** the first time a
35
+ * batch is delivered, so it never enters the bundle unless this sink is used.
36
+ * Callers may instead inject a ready instance (`appInsights`) or a custom
37
+ * `loadSdk` factory (used by tests with a mock transport — no network).
38
+ */
39
+ interface ClientSdkOptions extends BaseOptions {
40
+ mode: 'client-sdk';
41
+ /** App Insights connection string (preferred) or instrumentation key. */
42
+ connectionString?: string;
43
+ /** Legacy instrumentation key (used when `connectionString` is absent). */
44
+ instrumentationKey?: string;
45
+ /**
46
+ * Pre-constructed App Insights instance. When provided, no dynamic import
47
+ * or `loadAppInsights()` happens — used for DI/testing.
48
+ */
49
+ appInsights?: AppInsightsLike;
50
+ /**
51
+ * Custom async SDK factory. Overrides the built-in dynamic import of
52
+ * `@microsoft/applicationinsights-web`. Receives the resolved config.
53
+ */
54
+ loadSdk?: (config: {
55
+ connectionString?: string;
56
+ instrumentationKey?: string;
57
+ }) => Promise<AppInsightsLike>;
58
+ }
59
+ /**
60
+ * `http` mode — POSTs to the App Insights `/v2/track` ingestion endpoint.
61
+ * No browser library is loaded; server-relay friendly.
62
+ */
63
+ interface HttpOptions extends BaseOptions {
64
+ mode: 'http';
65
+ /** Instrumentation key (iKey) for the ingestion envelope. Required. */
66
+ instrumentationKey: string;
67
+ /**
68
+ * Ingestion endpoint base. Default the public global endpoint
69
+ * `https://dc.services.visualstudio.com`. Region endpoints are accepted.
70
+ * Events POST to `{endpoint}/v2/track`.
71
+ */
72
+ endpoint?: string;
73
+ /** Injected fetch (defaults to global fetch). */
74
+ fetchImpl?: typeof fetch;
75
+ /** Max retries for 429/5xx (exponential backoff). Default 3. */
76
+ maxRetries?: number;
77
+ /** Base backoff delay in ms. Default 500. */
78
+ backoffBaseMs?: number;
79
+ }
80
+ type AppInsightsSinkOptions = ClientSdkOptions | HttpOptions;
81
+ interface IngestEnvelope {
82
+ name: string;
83
+ time: string;
84
+ iKey: string;
85
+ tags: Record<string, string>;
86
+ data: {
87
+ baseType: 'EventData' | 'PageViewData';
88
+ baseData: {
89
+ ver: 2;
90
+ name: string;
91
+ properties: Record<string, string>;
92
+ measurements: Record<string, number>;
93
+ };
94
+ };
95
+ }
96
+ /**
97
+ * Build an App Insights ingestion envelope (Breeze schema) for one canonical
98
+ * event. `tags` carry session/user/operation correlation; `baseData` carries
99
+ * the custom event with the properties/measurements split.
100
+ */
101
+ declare function buildIngestEnvelope(ev: AnalyticsEvent, iKey: string): IngestEnvelope;
102
+ /**
103
+ * Create an Azure Application Insights {@link AnalyticsSink}.
104
+ *
105
+ * Dual-mode:
106
+ * - `client-sdk` — browser, lazy `@microsoft/applicationinsights-web`
107
+ * (`trackEvent` / `trackPageView`); the vendor lib is an OPTIONAL peer
108
+ * loaded only via dynamic import, never a hard/static dependency.
109
+ * - `http` — App Insights `/v2/track` ingestion endpoint; no browser lib,
110
+ * server-relay friendly.
111
+ *
112
+ * Both modes map the canonical envelope to App Insights custom events with a
113
+ * properties/measurements split and surface identity as
114
+ * `authenticatedUserId` / `anonymous`.
115
+ */
116
+ declare function createAppInsightsSink(options: AppInsightsSinkOptions): AnalyticsSink;
117
+
118
+ /**
119
+ * App Insights custom-event payload after mapping the canonical envelope.
120
+ *
121
+ * App Insights splits a custom event's bag into two dictionaries:
122
+ * - `properties` — string-valued dimensions (everything non-numeric)
123
+ * - `measurements`— numeric metrics (queryable/aggregatable in KQL)
124
+ *
125
+ * We additionally surface identity:
126
+ * - `authenticatedUserId` — present iff the envelope has `userId`
127
+ * - `anonymous` — `'true'` when no `userId`, else `'false'` (string for the
128
+ * properties bag; App Insights properties are always strings)
129
+ */
130
+ interface AppInsightsEvent {
131
+ /** App Insights custom-event name. */
132
+ name: string;
133
+ /** String-valued custom dimensions. */
134
+ properties: Record<string, string>;
135
+ /** Numeric custom metrics. */
136
+ measurements: Record<string, number>;
137
+ }
138
+ /**
139
+ * Derive the App Insights custom-event name for a canonical event.
140
+ *
141
+ * - `page` → `Page View: <name|path>` (also routed to `trackPageView` in the
142
+ * client-sdk mode; the name here is the http-mode/event fallback)
143
+ * - `screen`→ `Screen: <name>`
144
+ * - `identify` → `Identify`
145
+ * - `group` → `Group`
146
+ * - `alias` → `Alias`
147
+ * - `track` → the event name (or `track` when absent)
148
+ */
149
+ declare function eventName(ev: AnalyticsEvent): string;
150
+ /**
151
+ * Map a canonical {@link AnalyticsEvent} to an {@link AppInsightsEvent}.
152
+ *
153
+ * Properties bag composition:
154
+ * - canonical context: `app`, `env`, library name/version, page fields
155
+ * - envelope identity/routing: `messageId`, `anonymousId`, `sessionId`,
156
+ * `type`, `groupId?`, `previousId?`, `timestamp`, `schemaVersion`
157
+ * - identity surface: `authenticatedUserId` (iff `userId`), `anonymous`
158
+ * - the event's `properties` and `traits`, numbers → `measurements`
159
+ *
160
+ * The split is stable and lossless: every scalar lands in exactly one of the
161
+ * two dictionaries; nested objects are dot-flattened.
162
+ */
163
+ declare function mapEvent(ev: AnalyticsEvent): AppInsightsEvent;
164
+
165
+ export { type AppInsightsEvent, type AppInsightsLike, type AppInsightsSinkMode, type AppInsightsSinkOptions, type ClientSdkOptions, type HttpOptions, buildIngestEnvelope, createAppInsightsSink, eventName, mapEvent };
@@ -0,0 +1,165 @@
1
+ import { AnalyticsEvent, AnalyticsSink } from '../analytics/index.js';
2
+
3
+ /**
4
+ * Minimal structural shape of the bits of `@microsoft/applicationinsights-web`
5
+ * we use. Declared locally so the vendor lib stays an OPTIONAL peer with NO
6
+ * type-level hard dependency — it is only ever reached via dynamic import.
7
+ */
8
+ interface AppInsightsLike {
9
+ loadAppInsights?: () => unknown;
10
+ trackEvent: (event: {
11
+ name: string;
12
+ }, customProperties?: Record<string, unknown>) => void;
13
+ trackPageView: (pageView?: {
14
+ name?: string;
15
+ uri?: string;
16
+ properties?: Record<string, unknown>;
17
+ measurements?: Record<string, number>;
18
+ }) => void;
19
+ setAuthenticatedUserContext?: (authenticatedUserId: string, accountId?: string, storeInCookie?: boolean) => void;
20
+ clearAuthenticatedUserContext?: () => void;
21
+ flush?: (async?: boolean) => void;
22
+ }
23
+ /** Fan-out mode for the App Insights sink. */
24
+ type AppInsightsSinkMode = 'client-sdk' | 'http';
25
+ interface BaseOptions {
26
+ /** Consent categories this sink requires. Default `['analytics']`. */
27
+ consentCategories?: string[];
28
+ /** Stable sink name. Default `'app-insights'`. */
29
+ name?: string;
30
+ }
31
+ /**
32
+ * `client-sdk` mode — runs in the browser via `@microsoft/applicationinsights-web`.
33
+ *
34
+ * The vendor SDK is loaded **lazily via dynamic import** the first time a
35
+ * batch is delivered, so it never enters the bundle unless this sink is used.
36
+ * Callers may instead inject a ready instance (`appInsights`) or a custom
37
+ * `loadSdk` factory (used by tests with a mock transport — no network).
38
+ */
39
+ interface ClientSdkOptions extends BaseOptions {
40
+ mode: 'client-sdk';
41
+ /** App Insights connection string (preferred) or instrumentation key. */
42
+ connectionString?: string;
43
+ /** Legacy instrumentation key (used when `connectionString` is absent). */
44
+ instrumentationKey?: string;
45
+ /**
46
+ * Pre-constructed App Insights instance. When provided, no dynamic import
47
+ * or `loadAppInsights()` happens — used for DI/testing.
48
+ */
49
+ appInsights?: AppInsightsLike;
50
+ /**
51
+ * Custom async SDK factory. Overrides the built-in dynamic import of
52
+ * `@microsoft/applicationinsights-web`. Receives the resolved config.
53
+ */
54
+ loadSdk?: (config: {
55
+ connectionString?: string;
56
+ instrumentationKey?: string;
57
+ }) => Promise<AppInsightsLike>;
58
+ }
59
+ /**
60
+ * `http` mode — POSTs to the App Insights `/v2/track` ingestion endpoint.
61
+ * No browser library is loaded; server-relay friendly.
62
+ */
63
+ interface HttpOptions extends BaseOptions {
64
+ mode: 'http';
65
+ /** Instrumentation key (iKey) for the ingestion envelope. Required. */
66
+ instrumentationKey: string;
67
+ /**
68
+ * Ingestion endpoint base. Default the public global endpoint
69
+ * `https://dc.services.visualstudio.com`. Region endpoints are accepted.
70
+ * Events POST to `{endpoint}/v2/track`.
71
+ */
72
+ endpoint?: string;
73
+ /** Injected fetch (defaults to global fetch). */
74
+ fetchImpl?: typeof fetch;
75
+ /** Max retries for 429/5xx (exponential backoff). Default 3. */
76
+ maxRetries?: number;
77
+ /** Base backoff delay in ms. Default 500. */
78
+ backoffBaseMs?: number;
79
+ }
80
+ type AppInsightsSinkOptions = ClientSdkOptions | HttpOptions;
81
+ interface IngestEnvelope {
82
+ name: string;
83
+ time: string;
84
+ iKey: string;
85
+ tags: Record<string, string>;
86
+ data: {
87
+ baseType: 'EventData' | 'PageViewData';
88
+ baseData: {
89
+ ver: 2;
90
+ name: string;
91
+ properties: Record<string, string>;
92
+ measurements: Record<string, number>;
93
+ };
94
+ };
95
+ }
96
+ /**
97
+ * Build an App Insights ingestion envelope (Breeze schema) for one canonical
98
+ * event. `tags` carry session/user/operation correlation; `baseData` carries
99
+ * the custom event with the properties/measurements split.
100
+ */
101
+ declare function buildIngestEnvelope(ev: AnalyticsEvent, iKey: string): IngestEnvelope;
102
+ /**
103
+ * Create an Azure Application Insights {@link AnalyticsSink}.
104
+ *
105
+ * Dual-mode:
106
+ * - `client-sdk` — browser, lazy `@microsoft/applicationinsights-web`
107
+ * (`trackEvent` / `trackPageView`); the vendor lib is an OPTIONAL peer
108
+ * loaded only via dynamic import, never a hard/static dependency.
109
+ * - `http` — App Insights `/v2/track` ingestion endpoint; no browser lib,
110
+ * server-relay friendly.
111
+ *
112
+ * Both modes map the canonical envelope to App Insights custom events with a
113
+ * properties/measurements split and surface identity as
114
+ * `authenticatedUserId` / `anonymous`.
115
+ */
116
+ declare function createAppInsightsSink(options: AppInsightsSinkOptions): AnalyticsSink;
117
+
118
+ /**
119
+ * App Insights custom-event payload after mapping the canonical envelope.
120
+ *
121
+ * App Insights splits a custom event's bag into two dictionaries:
122
+ * - `properties` — string-valued dimensions (everything non-numeric)
123
+ * - `measurements`— numeric metrics (queryable/aggregatable in KQL)
124
+ *
125
+ * We additionally surface identity:
126
+ * - `authenticatedUserId` — present iff the envelope has `userId`
127
+ * - `anonymous` — `'true'` when no `userId`, else `'false'` (string for the
128
+ * properties bag; App Insights properties are always strings)
129
+ */
130
+ interface AppInsightsEvent {
131
+ /** App Insights custom-event name. */
132
+ name: string;
133
+ /** String-valued custom dimensions. */
134
+ properties: Record<string, string>;
135
+ /** Numeric custom metrics. */
136
+ measurements: Record<string, number>;
137
+ }
138
+ /**
139
+ * Derive the App Insights custom-event name for a canonical event.
140
+ *
141
+ * - `page` → `Page View: <name|path>` (also routed to `trackPageView` in the
142
+ * client-sdk mode; the name here is the http-mode/event fallback)
143
+ * - `screen`→ `Screen: <name>`
144
+ * - `identify` → `Identify`
145
+ * - `group` → `Group`
146
+ * - `alias` → `Alias`
147
+ * - `track` → the event name (or `track` when absent)
148
+ */
149
+ declare function eventName(ev: AnalyticsEvent): string;
150
+ /**
151
+ * Map a canonical {@link AnalyticsEvent} to an {@link AppInsightsEvent}.
152
+ *
153
+ * Properties bag composition:
154
+ * - canonical context: `app`, `env`, library name/version, page fields
155
+ * - envelope identity/routing: `messageId`, `anonymousId`, `sessionId`,
156
+ * `type`, `groupId?`, `previousId?`, `timestamp`, `schemaVersion`
157
+ * - identity surface: `authenticatedUserId` (iff `userId`), `anonymous`
158
+ * - the event's `properties` and `traits`, numbers → `measurements`
159
+ *
160
+ * The split is stable and lossless: every scalar lands in exactly one of the
161
+ * two dictionaries; nested objects are dot-flattened.
162
+ */
163
+ declare function mapEvent(ev: AnalyticsEvent): AppInsightsEvent;
164
+
165
+ export { type AppInsightsEvent, type AppInsightsLike, type AppInsightsSinkMode, type AppInsightsSinkOptions, type ClientSdkOptions, type HttpOptions, buildIngestEnvelope, createAppInsightsSink, eventName, mapEvent };
@@ -0,0 +1,219 @@
1
+ import { AnalyticsSink, AnalyticsEvent } from '../analytics/index.cjs';
2
+
3
+ /**
4
+ * @refraction-ui/analytics-sink-ga4 — option contracts.
5
+ *
6
+ * GA4 is "just a sink" in the epic's model (no privileged engine). This
7
+ * adapter implements the `AnalyticsSink` SPI from `@refraction-ui/analytics`
8
+ * in two interchangeable modes:
9
+ *
10
+ * - `client-sdk` — lazy-loads gtag.js in the browser (no hard vendor dep;
11
+ * the script is injected on first delivery only). Bridges Consent Mode.
12
+ * - `http` — GA4 Measurement Protocol (`/mp/collect`). No browser
13
+ * library is ever loaded — server-relay friendly.
14
+ *
15
+ * Default = `http` (protocol adapter, no vendor lib in the browser), matching
16
+ * the epic's "default = protocol adapters" stance.
17
+ */
18
+ /** Minimal gtag.js function signature (we never import the real types). */
19
+ type GtagFn = (...args: unknown[]) => void;
20
+ /** GA4 Consent Mode signal values. */
21
+ type ConsentState = 'granted' | 'denied';
22
+ /**
23
+ * GA4 Consent Mode bridge — maps our consent categories to the gtag
24
+ * `consent` command. Only used in `client-sdk` mode.
25
+ */
26
+ interface GA4ConsentBridge {
27
+ /**
28
+ * Default consent state pushed via `gtag('consent', 'default', …)` before
29
+ * the GA4 tag loads. Keys are GA4 consent types
30
+ * (`analytics_storage`, `ad_storage`, `ad_user_data`,
31
+ * `ad_personalization`, `functionality_storage`, …).
32
+ */
33
+ default?: Record<string, ConsentState>;
34
+ /**
35
+ * Maps a refraction consent category → the GA4 consent types it controls.
36
+ * When the router reports the sink may deliver (category granted) the
37
+ * adapter pushes `gtag('consent', 'update', { <types>: 'granted' })`.
38
+ * Example: `{ analytics: ['analytics_storage'] }`.
39
+ */
40
+ map?: Record<string, string[]>;
41
+ }
42
+ interface GA4CommonOptions {
43
+ /** GA4 Measurement ID, e.g. `G-XXXXXXXXXX`. */
44
+ measurementId: string;
45
+ /**
46
+ * Consent categories this sink requires. The router will not deliver until
47
+ * all are granted. Default: `['analytics']`.
48
+ */
49
+ consentCategories?: string[];
50
+ /** Stable sink name. Default `'ga4'`. */
51
+ name?: string;
52
+ }
53
+ /** `client-sdk` mode — runs gtag.js in the browser. */
54
+ interface GA4ClientSdkOptions extends GA4CommonOptions {
55
+ mode: 'client-sdk';
56
+ /**
57
+ * Inject the GA4 Consent Mode bridge. The default state (if any) is pushed
58
+ * before the tag loads; category grants drive `consent: update`.
59
+ */
60
+ consentMode?: GA4ConsentBridge;
61
+ /**
62
+ * Inject an existing gtag function (tests / apps that manage the tag
63
+ * themselves). When provided, the script loader is NOT used and **no**
64
+ * vendor script is injected.
65
+ */
66
+ gtag?: GtagFn;
67
+ /**
68
+ * Inject the script loader (tests / SSR-safe apps). Receives the gtag.js
69
+ * src URL; must resolve once the script has executed. Defaults to a DOM
70
+ * `<script>` injector (browser only).
71
+ */
72
+ scriptLoader?: (src: string) => Promise<void>;
73
+ /** Override the gtag.js base URL (testing). */
74
+ gtagSrcBase?: string;
75
+ }
76
+ /** `http` mode — GA4 Measurement Protocol. No browser library. */
77
+ interface GA4HttpOptions extends GA4CommonOptions {
78
+ mode?: 'http';
79
+ /** GA4 Measurement Protocol API secret (server-side credential). */
80
+ apiSecret: string;
81
+ /** Override the `/mp/collect` base URL (testing / EU endpoint). */
82
+ endpoint?: string;
83
+ /** Use the Measurement Protocol `/debug/mp/collect` validation endpoint. */
84
+ debug?: boolean;
85
+ /** Injected fetch (defaults to global fetch). Never loads a vendor lib. */
86
+ fetchImpl?: typeof fetch;
87
+ }
88
+ /** Discriminated union of the two modes. */
89
+ type GA4SinkOptions = GA4ClientSdkOptions | GA4HttpOptions;
90
+
91
+ /**
92
+ * `createGA4Sink` — the single entry point. Dispatches on `mode`:
93
+ * - `mode: 'client-sdk'` → lazy gtag.js adapter
94
+ * - `mode: 'http'` (default) → Measurement Protocol adapter, no vendor lib
95
+ *
96
+ * Default is `http` to honour the epic's "default = protocol adapters (no
97
+ * vendor libs in the browser)" stance. GA4 is just a sink — register it via
98
+ * `createAnalytics({ sinks: [createGA4Sink(...)] })` or `analytics.addSink`.
99
+ *
100
+ * Both factories are independent modules. The `http` factory has ZERO
101
+ * references to gtag.js / DOM-script code, and the `client-sdk` factory only
102
+ * touches the vendor script lazily (first delivery). Selecting one mode never
103
+ * executes the other's load path; consumers that only ever construct an
104
+ * `http` sink never run any vendor code.
105
+ */
106
+
107
+ declare function createGA4Sink(options: GA4SinkOptions): AnalyticsSink;
108
+
109
+ /**
110
+ * GA4 `http` adapter — Measurement Protocol (`/mp/collect`).
111
+ *
112
+ * Server-relay friendly: this path NEVER loads gtag.js or any browser
113
+ * library. It only POSTs JSON to the Measurement Protocol endpoint:
114
+ *
115
+ * POST {endpoint}/mp/collect?measurement_id={id}&api_secret={secret}
116
+ * Content-Type: application/json
117
+ * { client_id, user_id?, user_properties?, events: [{ name, params }] }
118
+ *
119
+ * GA4 Measurement Protocol constraints honoured here:
120
+ * - up to 25 events per request → batches are chunked.
121
+ * - `identify` envelopes carry no event; they still POST so user_id /
122
+ * user_properties propagate (events array may be empty for a user-props
123
+ * only ping, which GA4 accepts).
124
+ * - 2xx (incl. 204) = accepted. The MP endpoint always returns 2xx for
125
+ * well-formed requests; the `debug` endpoint returns validation messages.
126
+ */
127
+
128
+ /**
129
+ * Create the GA4 Measurement-Protocol (`http`) sink.
130
+ *
131
+ * No vendor library is loaded on this path under any circumstance.
132
+ */
133
+ declare function createGA4HttpSink(options: GA4HttpOptions): AnalyticsSink;
134
+
135
+ /**
136
+ * GA4 `client-sdk` adapter — lazy gtag.js.
137
+ *
138
+ * The vendor library is NEVER a hard dependency and is NEVER bundled. On the
139
+ * first delivery the adapter:
140
+ * 1. installs the gtag stub + dataLayer,
141
+ * 2. pushes the Consent Mode default (if a bridge is configured),
142
+ * 3. lazily injects `https://www.googletagmanager.com/gtag/js?id=<id>`
143
+ * via a `<script>` tag (or an injected loader for tests/SSR),
144
+ * 4. runs `gtag('js', Date)` + `gtag('config', <id>, { send_page_view:false })`.
145
+ *
146
+ * Subsequent deliveries map each canonical envelope through the shared mapper
147
+ * and call `gtag('set', 'user_properties', …)` / `gtag('set', { user_id })` /
148
+ * `gtag('event', name, params)`.
149
+ *
150
+ * Consent Mode bridge: `init` pushes `consent: default`; `deliver` is only
151
+ * reached when the router's consent gate already allows this sink, at which
152
+ * point `consent: update` is pushed for the mapped GA4 consent types.
153
+ */
154
+
155
+ /**
156
+ * Create the GA4 gtag.js (`client-sdk`) sink. The vendor script is dynamically
157
+ * loaded on first delivery only — there is no static import of any GA4/gtag
158
+ * library, so `http`-mode consumers never pull this code path.
159
+ */
160
+ declare function createGA4ClientSdkSink(options: GA4ClientSdkOptions): AnalyticsSink;
161
+
162
+ /**
163
+ * Canonical envelope → GA4 mapping.
164
+ *
165
+ * One mapper, two consumers: the `client-sdk` adapter feeds the result into
166
+ * `gtag('event', name, params)` / `gtag('set', ...)`, and the `http` adapter
167
+ * serialises it into a Measurement Protocol `/mp/collect` payload. Keeping the
168
+ * mapping in one place guarantees the two modes stay behaviourally identical.
169
+ *
170
+ * Identity / param mapping (issue #216, epic #213):
171
+ * anonymousId → client_id (GA4 device/browser id)
172
+ * userId → user_id (GA4 User-ID, cross-device)
173
+ * properties → event params (track/page/screen/group payload)
174
+ * identify → user_properties (traits become GA4 user properties)
175
+ * sessionId → session_id param (so GA4 sessionisation can align)
176
+ *
177
+ * GA4 event-name normalisation: GA4 recommends snake_case event names and
178
+ * forbids spaces. `page` → `page_view`, `screen` → `screen_view` (GA4
179
+ * Enhanced-Measurement parity); everything else is lower_snake_cased.
180
+ */
181
+
182
+ /** A GA4 event ready for gtag.js or the Measurement Protocol. */
183
+ interface GA4Event {
184
+ /** GA4 event name (snake_case, no spaces). */
185
+ name: string;
186
+ /** Event params (GA4 caps these; we pass them through verbatim). */
187
+ params: Record<string, unknown>;
188
+ }
189
+ /** The fully-mapped result for a single canonical envelope. */
190
+ interface GA4Mapped {
191
+ /** GA4 client_id (from anonymousId). Always present. */
192
+ clientId: string;
193
+ /** GA4 user_id (from userId). Present only after identify. */
194
+ userId?: string;
195
+ /**
196
+ * GA4 user_properties (from identify/group traits). Each value is wrapped
197
+ * in `{ value }` as the Measurement Protocol requires; the gtag adapter
198
+ * unwraps when it calls `gtag('set', 'user_properties', ...)`.
199
+ */
200
+ userProperties?: Record<string, {
201
+ value: unknown;
202
+ }>;
203
+ /**
204
+ * The GA4 event to send. `identify` calls carry no event (they only set
205
+ * user_id / user_properties), so this is optional.
206
+ */
207
+ event?: GA4Event;
208
+ }
209
+ /** Lower_snake_case an arbitrary event/trait name for GA4. */
210
+ declare function toGa4Name(name: string): string;
211
+ /** Map a Segment call type + name to its GA4 event name. */
212
+ declare function ga4EventName(ev: AnalyticsEvent): string | undefined;
213
+ /**
214
+ * Map one canonical envelope to its GA4 representation. Pure — no transport,
215
+ * no vendor lib; both the gtag and Measurement-Protocol adapters consume this.
216
+ */
217
+ declare function mapEvent(ev: AnalyticsEvent): GA4Mapped;
218
+
219
+ export { type ConsentState, type GA4ClientSdkOptions, type GA4ConsentBridge, type GA4Event, type GA4HttpOptions, type GA4Mapped, type GA4SinkOptions, type GtagFn, createGA4ClientSdkSink, createGA4HttpSink, createGA4Sink, ga4EventName, mapEvent, toGa4Name };