@walkeros/server-destination-amplitude 3.3.0-next-1776098542393

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.
package/README.md ADDED
@@ -0,0 +1,248 @@
1
+ # @walkeros/server-destination-amplitude
2
+
3
+ Server-side Amplitude destination for walkerOS. Sends events to Amplitude via
4
+ the `@amplitude/analytics-node` SDK with per-event identity, revenue, groups,
5
+ eventOptions, and consent support.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @walkeros/server-destination-amplitude
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ Minimal `flow.json` configuration:
16
+
17
+ ```json
18
+ {
19
+ "destinations": {
20
+ "amplitude": {
21
+ "package": "@walkeros/server-destination-amplitude",
22
+ "config": {
23
+ "settings": {
24
+ "apiKey": "YOUR_AMPLITUDE_API_KEY"
25
+ }
26
+ }
27
+ }
28
+ }
29
+ }
30
+ ```
31
+
32
+ ## Settings
33
+
34
+ | Setting | Type | Required | Default | Description |
35
+ | ------------------------------ | ---------------- | -------- | ------- | --------------------------------------- |
36
+ | `apiKey` | string | Yes | - | Amplitude project API key |
37
+ | `serverZone` | `'US'` \| `'EU'` | No | `'US'` | Data residency zone |
38
+ | `useBatch` | boolean | No | `false` | Use batch endpoint (higher rate limits) |
39
+ | `flushIntervalMillis` | number | No | `10000` | Flush interval in ms |
40
+ | `flushQueueSize` | number | No | `200` | Max queued events before flush |
41
+ | `flushMaxRetries` | number | No | `12` | Max retries on failed flush |
42
+ | `minIdLength` | number | No | - | Minimum length for user_id/device_id |
43
+ | `serverUrl` | string | No | - | Custom server URL for proxies |
44
+ | `optOut` | boolean | No | `false` | Initial opt-out state |
45
+ | `enableRequestBodyCompression` | boolean | No | `false` | Enable gzip compression |
46
+ | `identify` | MappingValue | No | - | Per-event identity resolution |
47
+ | `eventOptions` | MappingValue | No | - | Per-event EventOptions |
48
+ | `include` | string[] | No | - | Event sections as event_properties |
49
+
50
+ ## Identity (Per-Event EventOptions)
51
+
52
+ Unlike the browser SDK, the Node SDK has no stateful `setUserId()` or
53
+ `setDeviceId()`. Identity is passed per-event via `EventOptions` on every SDK
54
+ call.
55
+
56
+ ```json
57
+ {
58
+ "settings": {
59
+ "identify": {
60
+ "map": {
61
+ "user_id": "user.id",
62
+ "device_id": "user.device",
63
+ "session_id": "user.session"
64
+ }
65
+ }
66
+ }
67
+ }
68
+ ```
69
+
70
+ ### Identify Operations (User Properties)
71
+
72
+ Use per-rule mapping to set, increment, or clear user properties:
73
+
74
+ ```json
75
+ {
76
+ "mapping": {
77
+ "user": {
78
+ "login": {
79
+ "skip": true,
80
+ "settings": {
81
+ "identify": {
82
+ "map": {
83
+ "user_id": "data.user_id",
84
+ "set": {
85
+ "map": {
86
+ "plan": "data.plan",
87
+ "company": "data.company"
88
+ }
89
+ },
90
+ "setOnce": {
91
+ "map": { "first_login": "timestamp" }
92
+ },
93
+ "add": {
94
+ "map": { "login_count": { "value": 1 } }
95
+ }
96
+ }
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
102
+ }
103
+ ```
104
+
105
+ Supported identify operations: `set`, `setOnce`, `add`, `append`, `prepend`,
106
+ `preInsert`, `postInsert`, `remove`, `unset`, `clearAll`.
107
+
108
+ ## Revenue
109
+
110
+ ### Single Product
111
+
112
+ ```json
113
+ {
114
+ "mapping": {
115
+ "subscription": {
116
+ "renew": {
117
+ "skip": true,
118
+ "settings": {
119
+ "revenue": {
120
+ "map": {
121
+ "productId": "data.plan_id",
122
+ "price": "data.amount",
123
+ "revenueType": { "value": "renewal" },
124
+ "currency": { "key": "data.currency", "value": "USD" }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ }
130
+ }
131
+ }
132
+ ```
133
+
134
+ ### Multi-Product (Loop)
135
+
136
+ ```json
137
+ {
138
+ "settings": {
139
+ "revenue": {
140
+ "loop": [
141
+ "nested",
142
+ {
143
+ "map": {
144
+ "productId": "data.id",
145
+ "price": "data.price",
146
+ "quantity": { "key": "data.quantity", "value": 1 },
147
+ "revenueType": { "value": "purchase" }
148
+ }
149
+ }
150
+ ]
151
+ }
152
+ }
153
+ }
154
+ ```
155
+
156
+ Revenue fields: `productId`, `price`, `quantity`, `revenueType`, `currency`,
157
+ `revenue`, `receipt`, `receiptSig`, `eventProperties`.
158
+
159
+ ## Groups
160
+
161
+ ```json
162
+ {
163
+ "settings": {
164
+ "group": {
165
+ "map": {
166
+ "type": { "value": "company" },
167
+ "name": "data.company"
168
+ }
169
+ },
170
+ "groupIdentify": {
171
+ "map": {
172
+ "type": { "value": "company" },
173
+ "name": "data.company",
174
+ "set": {
175
+ "map": {
176
+ "industry": "data.industry",
177
+ "size": "data.employee_count"
178
+ }
179
+ }
180
+ }
181
+ }
182
+ }
183
+ }
184
+ ```
185
+
186
+ ## EventOptions (time, insert_id, ip)
187
+
188
+ Server-specific fields for offline/delayed events and deduplication:
189
+
190
+ ```json
191
+ {
192
+ "settings": {
193
+ "eventOptions": {
194
+ "map": {
195
+ "time": "timestamp",
196
+ "insert_id": "id",
197
+ "ip": "context.ip"
198
+ }
199
+ }
200
+ }
201
+ }
202
+ ```
203
+
204
+ ## Consent
205
+
206
+ Declare required consent in `config.consent`. The destination toggles
207
+ `amplitude.setOptOut()` when consent state changes.
208
+
209
+ ```json
210
+ {
211
+ "config": {
212
+ "consent": { "analytics": true },
213
+ "settings": { "apiKey": "..." }
214
+ }
215
+ }
216
+ ```
217
+
218
+ ## Destroy / Flush
219
+
220
+ The destination calls `amplitude.flush()` on destroy to ensure all buffered
221
+ events are sent before the process exits. This is critical for graceful server
222
+ shutdown and short-lived processes.
223
+
224
+ ## Mapping Settings
225
+
226
+ Per-rule mapping settings override destination-level settings:
227
+
228
+ | Setting | Type | Description |
229
+ | --------------- | ------------ | ------------------------------------------------ |
230
+ | `identify` | MappingValue | Per-event identity (overrides destination-level) |
231
+ | `revenue` | MappingValue | Revenue data (single or loop) |
232
+ | `group` | MappingValue | Group assignment `{ type, name }` |
233
+ | `groupIdentify` | MappingValue | Group properties |
234
+ | `eventOptions` | MappingValue | Per-rule EventOptions override |
235
+ | `include` | string[] | Per-rule include (replaces destination-level) |
236
+
237
+ ## SDK Call Mapping
238
+
239
+ | walkerOS | Amplitude SDK Call |
240
+ | ------------------------- | ------------------------------------------------------------- |
241
+ | Event push | `amplitude.track(eventType, props, eventOptions)` |
242
+ | `settings.identify` (ops) | `amplitude.identify(Identify, eventOptions)` |
243
+ | `settings.revenue` | `amplitude.revenue(Revenue, eventOptions)` |
244
+ | `settings.group` | `amplitude.setGroup(type, name, eventOptions)` |
245
+ | `settings.groupIdentify` | `amplitude.groupIdentify(type, name, Identify, eventOptions)` |
246
+ | Consent revoked | `amplitude.setOptOut(true)` |
247
+ | Consent granted | `amplitude.setOptOut(false)` |
248
+ | Destroy | `amplitude.flush()` |
package/dist/dev.d.mts ADDED
@@ -0,0 +1,287 @@
1
+ import * as _walkeros_core_dev from '@walkeros/core/dev';
2
+ import { z } from '@walkeros/core/dev';
3
+ import { Mapping as Mapping$1, Flow } from '@walkeros/core';
4
+ import { DestinationServer } from '@walkeros/server-core';
5
+
6
+ declare const SettingsSchema: z.ZodObject<{
7
+ apiKey: z.ZodString;
8
+ serverZone: z.ZodOptional<z.ZodEnum<{
9
+ US: "US";
10
+ EU: "EU";
11
+ }>>;
12
+ flushIntervalMillis: z.ZodOptional<z.ZodNumber>;
13
+ flushQueueSize: z.ZodOptional<z.ZodNumber>;
14
+ flushMaxRetries: z.ZodOptional<z.ZodNumber>;
15
+ useBatch: z.ZodOptional<z.ZodBoolean>;
16
+ minIdLength: z.ZodOptional<z.ZodNumber>;
17
+ serverUrl: z.ZodOptional<z.ZodString>;
18
+ optOut: z.ZodOptional<z.ZodBoolean>;
19
+ enableRequestBodyCompression: z.ZodOptional<z.ZodBoolean>;
20
+ identify: z.ZodOptional<z.ZodUnknown>;
21
+ eventOptions: z.ZodOptional<z.ZodUnknown>;
22
+ include: z.ZodOptional<z.ZodArray<z.ZodString>>;
23
+ }, z.core.$strip>;
24
+ type Settings$1 = z.infer<typeof SettingsSchema>;
25
+
26
+ declare const MappingSchema: z.ZodObject<{
27
+ identify: z.ZodOptional<z.ZodUnknown>;
28
+ revenue: z.ZodOptional<z.ZodUnknown>;
29
+ group: z.ZodOptional<z.ZodUnknown>;
30
+ groupIdentify: z.ZodOptional<z.ZodUnknown>;
31
+ eventOptions: z.ZodOptional<z.ZodUnknown>;
32
+ include: z.ZodOptional<z.ZodArray<z.ZodString>>;
33
+ }, z.core.$strip>;
34
+ type Mapping = z.infer<typeof MappingSchema>;
35
+
36
+ declare const settings: _walkeros_core_dev.JSONSchema;
37
+ declare const mapping: _walkeros_core_dev.JSONSchema;
38
+
39
+ type index$1_Mapping = Mapping;
40
+ declare const index$1_MappingSchema: typeof MappingSchema;
41
+ declare const index$1_SettingsSchema: typeof SettingsSchema;
42
+ declare const index$1_mapping: typeof mapping;
43
+ declare const index$1_settings: typeof settings;
44
+ declare namespace index$1 {
45
+ export { type index$1_Mapping as Mapping, index$1_MappingSchema as MappingSchema, type Settings$1 as Settings, index$1_SettingsSchema as SettingsSchema, index$1_mapping as mapping, index$1_settings as settings };
46
+ }
47
+
48
+ /**
49
+ * Amplitude SDK surface -- the subset of @amplitude/analytics-node the
50
+ * destination actually uses. Tests provide a mock via env.amplitude;
51
+ * production uses the real SDK import.
52
+ *
53
+ * Key difference from web: no setUserId/setDeviceId/setSessionId.
54
+ * Identity is per-event via EventOptions on every SDK call.
55
+ */
56
+ interface AmplitudeSDK {
57
+ init: (apiKey: string, options?: Record<string, unknown>) => {
58
+ promise: Promise<void>;
59
+ };
60
+ track: (eventType: string, eventProperties?: Record<string, unknown>, eventOptions?: EventOptions) => void;
61
+ identify: (identify: IdentifyInstance, eventOptions?: EventOptions) => void;
62
+ revenue: (revenue: RevenueInstance, eventOptions?: EventOptions) => void;
63
+ setGroup: (groupType: string, groupName: string | string[], eventOptions?: EventOptions) => void;
64
+ groupIdentify: (groupType: string, groupName: string, identify: IdentifyInstance, eventOptions?: EventOptions) => void;
65
+ setOptOut: (optOut: boolean) => void;
66
+ flush: () => {
67
+ promise: Promise<void>;
68
+ };
69
+ Identify: new () => IdentifyInstance;
70
+ Revenue: new () => RevenueInstance;
71
+ }
72
+ /**
73
+ * Per-event identity and metadata fields. Passed to every SDK call.
74
+ * Maps directly to Amplitude's EventOptions type from the Node SDK.
75
+ */
76
+ interface EventOptions {
77
+ user_id?: string;
78
+ device_id?: string;
79
+ session_id?: number;
80
+ time?: number;
81
+ insert_id?: string;
82
+ ip?: string;
83
+ city?: string;
84
+ country?: string;
85
+ region?: string;
86
+ language?: string;
87
+ platform?: string;
88
+ os_name?: string;
89
+ os_version?: string;
90
+ device_brand?: string;
91
+ device_manufacturer?: string;
92
+ device_model?: string;
93
+ app_version?: string;
94
+ carrier?: string;
95
+ user_agent?: string;
96
+ groups?: Record<string, unknown>;
97
+ plan?: Record<string, unknown>;
98
+ ingestion_metadata?: Record<string, unknown>;
99
+ partner_id?: string;
100
+ extra?: Record<string, unknown>;
101
+ }
102
+ /** Chainable Identify -- mirrors @amplitude/analytics-node Identify class. */
103
+ interface IdentifyInstance {
104
+ set: (property: string, value: unknown) => IdentifyInstance;
105
+ setOnce: (property: string, value: unknown) => IdentifyInstance;
106
+ add: (property: string, value: number) => IdentifyInstance;
107
+ append: (property: string, value: unknown) => IdentifyInstance;
108
+ prepend: (property: string, value: unknown) => IdentifyInstance;
109
+ preInsert: (property: string, value: unknown) => IdentifyInstance;
110
+ postInsert: (property: string, value: unknown) => IdentifyInstance;
111
+ remove: (property: string, value: unknown) => IdentifyInstance;
112
+ unset: (property: string) => IdentifyInstance;
113
+ clearAll: () => IdentifyInstance;
114
+ }
115
+ /** Chainable Revenue -- mirrors @amplitude/analytics-node Revenue class. */
116
+ interface RevenueInstance {
117
+ setProductId: (id: string) => RevenueInstance;
118
+ setPrice: (price: number) => RevenueInstance;
119
+ setQuantity: (quantity: number) => RevenueInstance;
120
+ setRevenueType: (type: string) => RevenueInstance;
121
+ setCurrency: (currency: string) => RevenueInstance;
122
+ setRevenue: (revenue: number) => RevenueInstance;
123
+ setReceipt: (receipt: string) => RevenueInstance;
124
+ setReceiptSig: (signature: string) => RevenueInstance;
125
+ setEventProperties: (properties: Record<string, unknown>) => RevenueInstance;
126
+ }
127
+ /**
128
+ * Destination-level settings. apiKey is required; all other fields
129
+ * are optional and passed through to the Amplitude Node SDK init().
130
+ */
131
+ interface Settings {
132
+ apiKey: string;
133
+ serverZone?: 'US' | 'EU';
134
+ flushIntervalMillis?: number;
135
+ flushQueueSize?: number;
136
+ flushMaxRetries?: number;
137
+ useBatch?: boolean;
138
+ minIdLength?: number;
139
+ serverUrl?: string;
140
+ logLevel?: number;
141
+ optOut?: boolean;
142
+ offline?: boolean;
143
+ enableRequestBodyCompression?: boolean;
144
+ plan?: Record<string, unknown>;
145
+ ingestionMetadata?: Record<string, unknown>;
146
+ /** walkerOS mapping value for per-event identity resolution. */
147
+ identify?: Mapping$1.Value;
148
+ /** walkerOS mapping value for per-event EventOptions (time, insert_id, ip, etc). */
149
+ eventOptions?: Mapping$1.Value;
150
+ /** Sections to include as event_properties (e.g., ['data', 'globals']). */
151
+ include?: string[];
152
+ }
153
+ /**
154
+ * Env -- optional SDK override. Production leaves this undefined and the
155
+ * destination falls back to the real @amplitude/analytics-node import.
156
+ * Tests provide a mock via env.amplitude.
157
+ */
158
+ interface Env extends DestinationServer.Env {
159
+ amplitude?: AmplitudeSDK;
160
+ }
161
+
162
+ declare const init: Env | undefined;
163
+ declare const push: Env;
164
+ declare const simulation: string[];
165
+
166
+ declare const env_init: typeof init;
167
+ declare const env_push: typeof push;
168
+ declare const env_simulation: typeof simulation;
169
+ declare namespace env {
170
+ export { env_init as init, env_push as push, env_simulation as simulation };
171
+ }
172
+
173
+ /**
174
+ * Extended step example type for Amplitude server destination.
175
+ * Settings and configInclude are read by the test runner and merged
176
+ * into the base destination configuration.
177
+ */
178
+ type AmplitudeStepExample = Flow.StepExample & {
179
+ settings?: Partial<Settings>;
180
+ configInclude?: string[];
181
+ };
182
+ /**
183
+ * Default event forwarding -- every walkerOS event becomes
184
+ * amplitude.track(event.name, event_properties). With no mapping and
185
+ * no destination-level include, event_properties is `{}`.
186
+ */
187
+ declare const defaultEventForwarding: AmplitudeStepExample;
188
+ /**
189
+ * Wildcard ignore -- walkerOS's standard way to drop events. The rule
190
+ * matches but does nothing. The destination fires zero SDK calls.
191
+ */
192
+ declare const wildcardIgnored: AmplitudeStepExample;
193
+ /**
194
+ * Destination-level settings.include flattens the walkerOS `data` section
195
+ * into prefixed event_properties on every push.
196
+ */
197
+ declare const destinationLevelInclude: AmplitudeStepExample;
198
+ /**
199
+ * Destination-level settings.identify resolves per-event identity.
200
+ * Unlike the web destination (which uses setUserId/setDeviceId/setSessionId),
201
+ * server-side identity goes into EventOptions passed to every SDK call.
202
+ *
203
+ * user.session is 's3ss10n' (string). The destination deterministically
204
+ * hashes non-numeric session strings via djb2.
205
+ * djb2('s3ss10n') = 394324160.
206
+ */
207
+ declare const destinationLevelIdentify: AmplitudeStepExample;
208
+ /**
209
+ * Per-event identify with the full operation vocabulary -- this is the
210
+ * "user login" pattern: set user_id, enrich user properties. `skip: true`
211
+ * suppresses the default amplitude.track() call because we're running
212
+ * identity side effects only.
213
+ *
214
+ * Server-side, user_id is passed via EventOptions on identify().
215
+ */
216
+ declare const userLoginIdentify: AmplitudeStepExample;
217
+ /**
218
+ * Single-product revenue -- resolves `settings.revenue` to one object and
219
+ * fires one amplitude.revenue() call. Note the `{ key: "data.currency",
220
+ * value: "EUR" }` fallback syntax: try data.currency, default to "EUR".
221
+ *
222
+ * The custom event has no data.currency, so the fallback fires.
223
+ * `skip: true` suppresses the default track().
224
+ */
225
+ declare const subscriptionRenewRevenue: AmplitudeStepExample;
226
+ /**
227
+ * Multi-product order -- the canonical Amplitude ecommerce pattern.
228
+ * `revenue.loop: ["nested", { map: ... }]` iterates event.nested and
229
+ * resolves one revenue item per entry. Each becomes a separate
230
+ * amplitude.revenue() call. The order-level track() fires once with
231
+ * include-based event_properties.
232
+ *
233
+ * The default "order complete" fixture has 3 nested entries: two
234
+ * products (ers, cc) and one gift. Products have `data.price`; the
235
+ * gift has only `data.name`. The `condition` on the loop inner value
236
+ * filters to products only (price must be present).
237
+ */
238
+ declare const orderCompleteMultiProduct: AmplitudeStepExample;
239
+ /**
240
+ * Group assignment + group properties. Typically used for B2B products
241
+ * where a user belongs to a company. Both SDK calls fire on the same rule.
242
+ */
243
+ declare const groupAssignmentWithProperties: AmplitudeStepExample;
244
+ /**
245
+ * EventOptions mapping -- `settings.eventOptions` maps walkerOS fields
246
+ * to Amplitude per-event metadata. Here `time` maps from the event
247
+ * timestamp and `insert_id` maps from the event id for deduplication.
248
+ */
249
+ declare const eventOptionsTimeInsertId: AmplitudeStepExample;
250
+ /**
251
+ * Consent revoked -> amplitude.setOptOut(true). The destination checks
252
+ * the consent keys declared in config.consent and toggles optOut
253
+ * accordingly (strict: all required keys must be granted).
254
+ *
255
+ * Uses the canonical StepExample.command='consent' pattern: the test
256
+ * runner dispatches via elb('walker consent', in) instead of pushing
257
+ * an event.
258
+ */
259
+ declare const consentRevokeOptOut: AmplitudeStepExample;
260
+ /**
261
+ * Consent granted -> amplitude.setOptOut(false).
262
+ */
263
+ declare const consentGrantOptIn: AmplitudeStepExample;
264
+
265
+ type step_AmplitudeStepExample = AmplitudeStepExample;
266
+ declare const step_consentGrantOptIn: typeof consentGrantOptIn;
267
+ declare const step_consentRevokeOptOut: typeof consentRevokeOptOut;
268
+ declare const step_defaultEventForwarding: typeof defaultEventForwarding;
269
+ declare const step_destinationLevelIdentify: typeof destinationLevelIdentify;
270
+ declare const step_destinationLevelInclude: typeof destinationLevelInclude;
271
+ declare const step_eventOptionsTimeInsertId: typeof eventOptionsTimeInsertId;
272
+ declare const step_groupAssignmentWithProperties: typeof groupAssignmentWithProperties;
273
+ declare const step_orderCompleteMultiProduct: typeof orderCompleteMultiProduct;
274
+ declare const step_subscriptionRenewRevenue: typeof subscriptionRenewRevenue;
275
+ declare const step_userLoginIdentify: typeof userLoginIdentify;
276
+ declare const step_wildcardIgnored: typeof wildcardIgnored;
277
+ declare namespace step {
278
+ export { type step_AmplitudeStepExample as AmplitudeStepExample, step_consentGrantOptIn as consentGrantOptIn, step_consentRevokeOptOut as consentRevokeOptOut, step_defaultEventForwarding as defaultEventForwarding, step_destinationLevelIdentify as destinationLevelIdentify, step_destinationLevelInclude as destinationLevelInclude, step_eventOptionsTimeInsertId as eventOptionsTimeInsertId, step_groupAssignmentWithProperties as groupAssignmentWithProperties, step_orderCompleteMultiProduct as orderCompleteMultiProduct, step_subscriptionRenewRevenue as subscriptionRenewRevenue, step_userLoginIdentify as userLoginIdentify, step_wildcardIgnored as wildcardIgnored };
279
+ }
280
+
281
+ declare const index_env: typeof env;
282
+ declare const index_step: typeof step;
283
+ declare namespace index {
284
+ export { index_env as env, index_step as step };
285
+ }
286
+
287
+ export { index as examples, index$1 as schemas };