Skip to content

RudderStack Cloud Mode destination in Survicate#4907

Open
anna-belka wants to merge 12 commits intorudderlabs:developfrom
Survicate:survicate-cloud-mode
Open

RudderStack Cloud Mode destination in Survicate#4907
anna-belka wants to merge 12 commits intorudderlabs:developfrom
Survicate:survicate-cloud-mode

Conversation

@anna-belka
Copy link

@anna-belka anna-belka commented Jan 15, 2026

What are the changes introduced in this PR?

This PR introduces the Survicate Cloud Mode Destination integration for RudderStack. The implementation transforms RudderStack events (identify, group, and track) into Survicate API format for integration with the Survicate Contacts service.

What is the related Linear task?

Resolves INT-XXX

Please explain the objectives of your changes below

The objective is to enable RudderStack users to seamlessly send user identification, group association, and event tracking data to Survicate's Contacts service. The integration:

Processes three event types:

identify: Sends user identification data with traits and selected context
group: Associates users with groups including group-specific traits
track: Tracks user events with event names and properties
Ensures data consistency by always including: userId, timestamp, messageId and filtered context (only locale, campaign, userAgent)

Validates required fields to ensure data integrity at the Contacts service

Flattens nested structures by extracting context traits and merging them into the payload

Any changes to existing capabilities/behaviour, mention the reason & what are the changes ?

N

Any new dependencies introduced with this change?

N

Any new generic utility introduced or modified. Please explain the changes.

N

Any technical or performance related pointers to consider with the change?

N


Developer checklist

  • My code follows the style guidelines of this project

  • No breaking changes are being introduced.

  • All related docs linked with the PR?

  • All changes manually tested?

  • Any documentation changes needed with this change?

  • Is the PR limited to 10 file changes?

  • Is the PR limited to one linear task?

  • Are relevant unit and component test-cases added in new readability format?

Reviewer checklist

  • Is the type of change in the PR title appropriate as per the changes?

  • Verified that there are no credentials or confidential data exposed with the changes.

@contributor-support
Copy link

Thank you @anna-belka for contributing this PR.
Please sign the Contributor License Agreement (CLA) before merging.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'auto_resolve_threads'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

Adds a new Survicate destination integration: configuration constants, Zod-based types for runtime validation, and transformation logic that maps identify, group, and track RudderStack events to Survicate POST requests via a router and per-event handlers.

Changes

Cohort / File(s) Summary
Configuration
src/v0/destinations/survicate/config.ts
New module exporting BASE_URL, ENDPOINT_CONFIG (identify/group/track endpoints + method/content-type), DEST_TYPE, and HEADERS_CONFIG.
Types & Validation
src/v0/destinations/survicate/types.ts
Adds Zod schemas and TypeScript types: SurvicateDestinationConfigSchema, SurvicateMessageSchema, runtime types (SurvicateDestinationConfig, SurvicateMessage, SurvicateDestination, SurvicateRouterRequest), and payload interfaces IdentifyPayload, GroupPayload, TrackPayload.
Transformation & Routing
src/v0/destinations/survicate/transform.ts
New router and per-event handlers (identify, group, track), process and processRouterDest exports; input validation, payload construction, header assembly, and routing to Survicate endpoints.

Sequence Diagram(s)

sequenceDiagram
    participant Event as RudderStack Event
    participant Router as Survicate Router
    participant Handler as Event Handler
    participant API as Survicate API

    Event->>Router: process(event)
    Router->>Router: validate destinationKey & message type

    alt identify
        Router->>Handler: route to identify handler
        Handler->>Handler: validate user_id, flatten traits, build payload
        Handler->>API: POST /endpoint/rudder-stack/identify (headers + body)
        API-->>Handler: response
    else group
        Router->>Handler: route to group handler
        Handler->>Handler: validate user_id & group_id, build payload
        Handler->>API: POST /endpoint/rudder-stack/group
        API-->>Handler: response
    else track
        Router->>Handler: route to track handler
        Handler->>Handler: validate user_id & event, build payload
        Handler->>API: POST /endpoint/rudder-stack/track
        API-->>Handler: response
    end

    Router-->>Event: return formatted request(s)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: introducing a Survicate Cloud Mode destination integration for RudderStack, which aligns with the changeset.
Description check ✅ Passed The PR description follows the template structure with all required sections completed, including objectives, explanations of changes, dependency status, and developer/reviewer checklists.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@devops-github-rudderstack
Copy link
Contributor

This PR is considered to be stale. It has been open for 20 days with no further activity thus it is going to be closed in 7 days. To avoid such a case please consider removing the stale label manually or add a comment to the PR.

@anna-belka
Copy link
Author

This PR is considered to be stale. It has been open for 20 days with no further activity thus it is going to be closed in 7 days. To avoid such a case please consider removing the stale label manually or add a comment to the PR.

@devops-github-rudderstack we still working on it. Please do not close this PR.

@devops-github-rudderstack
Copy link
Contributor

This PR is considered to be stale. It has been open for 20 days with no further activity thus it is going to be closed in 7 days. To avoid such a case please consider removing the stale label manually or add a comment to the PR.

@anna-belka anna-belka marked this pull request as ready for review March 10, 2026 09:26
@anna-belka anna-belka requested a review from a team as a code owner March 10, 2026 09:26
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/v0/destinations/survicate/transform.ts`:
- Around line 169-176: processTrackEvent is only forwarding message.properties,
message_id and timestamp, so track payloads miss the filtered context and
flattened context.traits that identify/group include; update processTrackEvent
to attach the same filtered context block (e.g., context: filteredContext) and
merge filteredContext.traits into the payload.properties (preserving existing
properties) so locale, campaign, userAgent and user traits are sent with
message, referencing the same filtering/flattening helpers used by
identify/group to keep behavior consistent.
- Around line 48-50: The merge of message.context.traits into payload (in
transform.ts) can overwrite canonical fields like user_id, group_id, timestamp,
and message_id; before calling Object.assign(payload, message.context.traits)
(and the analogous merge at the other occurrence around lines 113-115) filter
out reserved keys ["user_id","group_id","timestamp","message_id"] from
message.context.traits or re-apply
payload.user_id/payload.group_id/payload.timestamp/payload.message_id after the
merge so trait values cannot replace canonical IDs; update the merge in the
transform function(s) to use the filtered traits object or reassign reserved
fields afterward.

In `@src/v0/destinations/survicate/types.ts`:
- Around line 25-31: The SurvicateMessage Zod schema currently uses snake_case
field names (user_id, group_id, message_id) which mismatch RudderStack's
camelCase fields; update the schema inside the SurvicateMessage definition to
use camelCase names (userId, groupId, messageId) and any other camelCase fields
(e.g., anonymousId, originalTimestamp) to match incoming messages, or
alternatively add a normalization step at the message entry point (before
validation) that maps snake_case keys to camelCase; adjust any code referencing
the old snake_case keys to use the new camelCase symbols so validation and
downstream transforms (the SurvicateMessage schema consumer) work correctly.
- Around line 32-38: Update the Zod records to the Zod 4 two-argument form:
replace occurrences of z.record(z.any()) for the fields properties, traits,
context.traits, and context.campaign with z.record(z.string(), z.unknown()) so
the schemas in the Survicate type definitions (the properties and traits
top-level fields and the context object’s traits and campaign fields) use a key
schema and a value schema compatible with Zod v4.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c273f488-edcc-4930-afae-010ef25954c0

📥 Commits

Reviewing files that changed from the base of the PR and between 32471e3 and 459b0b7.

⛔ Files ignored due to path filters (7)
  • src/v0/destinations/survicate/README.md is excluded by !**/*.md
  • test/integrations/component.test.ts is excluded by !**/test/**
  • test/integrations/destinations/survicate/processor/data.ts is excluded by !**/test/**
  • test/integrations/destinations/survicate/processor/group.ts is excluded by !**/test/**
  • test/integrations/destinations/survicate/processor/identify.ts is excluded by !**/test/**
  • test/integrations/destinations/survicate/processor/track.ts is excluded by !**/test/**
  • test/integrations/destinations/survicate/router/data.ts is excluded by !**/test/**
📒 Files selected for processing (3)
  • src/v0/destinations/survicate/config.ts
  • src/v0/destinations/survicate/transform.ts
  • src/v0/destinations/survicate/types.ts

@github-actions github-actions bot removed the Stale label Mar 11, 2026
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/v0/destinations/survicate/types.ts (1)

25-30: ⚠️ Potential issue | 🟠 Major

Switch this message schema to the standard camelCase RudderStack fields.

user_id, group_id, and message_id do not match the normal incoming message contract (userId, groupId, messageId). With the current schema plus the matching snake_case reads in src/v0/destinations/survicate/transform.ts, valid identified/group events will be treated as anonymous or lose messageId unless some unseen normalization runs first. Normalize before validation, or update this schema and the downstream accessors together.

Patch sketch for this file
 export const SurvicateMessageSchema = z
   .object({
     type: z.enum(['identify', 'group', 'track']),
-    user_id: z.string().optional(),
+    userId: z.string().optional(),
     anonymousId: z.string().optional(),
-    group_id: z.string().optional(),
+    groupId: z.string().optional(),
     event: z.string().optional(),
-    message_id: z.string().optional(),
+    messageId: z.string().optional(),
     originalTimestamp: z.string().optional(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/v0/destinations/survicate/types.ts` around lines 25 - 30, Change the
Survicate message Zod schema to use standard RudderStack camelCase fields
(userId, groupId, messageId) instead of snake_case (user_id, group_id,
message_id) so validation matches incoming messages; update any downstream
accessors in src/v0/destinations/survicate/transform.ts that read
message_id/user_id/group_id to read messageId/userId/groupId (or alternatively
add a normalization step that converts snake_case to camelCase before calling
the existing schema/transform functions), ensuring the symbols type (the Zod
schema object in types.ts) and the transform functions refer to the same field
names.
🧹 Nitpick comments (1)
src/v0/destinations/survicate/types.ts (1)

58-67: Align the exported identify/group payload types with the payloads actually built.

processIdentifyEvent and processGroupEvent add timestamp, message_id, and optional context, but IdentifyPayload/GroupPayload do not model those fields and the implementation currently falls back to Record<string, any>. Either make these interfaces match the real request shape and use them in transform.ts, or drop them to avoid a stale public type surface.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/v0/destinations/survicate/types.ts` around lines 58 - 67, The exported
IdentifyPayload and GroupPayload interfaces are out-of-date: update
IdentifyPayload and GroupPayload to include the fields added by
processIdentifyEvent/processGroupEvent (timestamp: string | number, message_id:
string, and optional context: Record<string, any>) so the types model the actual
request shape used in transform.ts, and then use these interfaces from
transform.ts instead of falling back to Record<string, any>; alternatively, if
you prefer not to expose them, remove these stale exports so there is no
mismatch between the public type surface and the implementations in
processIdentifyEvent/processGroupEvent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/v0/destinations/survicate/types.ts`:
- Around line 30-31: Update the Zod schema that defines message_id and
originalTimestamp so they are required at validation time: change the
definitions for message_id and originalTimestamp from z.string().optional() to
z.string() in the Survicate event schema (the object where message_id and
originalTimestamp are currently defined) so events missing these stamped values
are rejected during validation rather than later.

---

Duplicate comments:
In `@src/v0/destinations/survicate/types.ts`:
- Around line 25-30: Change the Survicate message Zod schema to use standard
RudderStack camelCase fields (userId, groupId, messageId) instead of snake_case
(user_id, group_id, message_id) so validation matches incoming messages; update
any downstream accessors in src/v0/destinations/survicate/transform.ts that read
message_id/user_id/group_id to read messageId/userId/groupId (or alternatively
add a normalization step that converts snake_case to camelCase before calling
the existing schema/transform functions), ensuring the symbols type (the Zod
schema object in types.ts) and the transform functions refer to the same field
names.

---

Nitpick comments:
In `@src/v0/destinations/survicate/types.ts`:
- Around line 58-67: The exported IdentifyPayload and GroupPayload interfaces
are out-of-date: update IdentifyPayload and GroupPayload to include the fields
added by processIdentifyEvent/processGroupEvent (timestamp: string | number,
message_id: string, and optional context: Record<string, any>) so the types
model the actual request shape used in transform.ts, and then use these
interfaces from transform.ts instead of falling back to Record<string, any>;
alternatively, if you prefer not to expose them, remove these stale exports so
there is no mismatch between the public type surface and the implementations in
processIdentifyEvent/processGroupEvent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0c3a1d26-f9ce-4207-ba4b-436d1d15553c

📥 Commits

Reviewing files that changed from the base of the PR and between 459b0b7 and a724959.

📒 Files selected for processing (1)
  • src/v0/destinations/survicate/types.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
src/v0/destinations/survicate/types.ts (1)

30-31: ⚠️ Potential issue | 🟠 Major

Require message_id and originalTimestamp at the schema boundary.

All three handlers stamp these into the outbound payload, but the schema still allows them to be absent. That means invalid events can pass validation and reach Survicate with undefined audit fields instead of failing fast here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/v0/destinations/survicate/types.ts` around lines 30 - 31, The schema
currently marks message_id and originalTimestamp as optional, allowing invalid
events through; update the Zod schema in src/v0/destinations/survicate/types.ts
so that message_id and originalTimestamp are required (change their definitions
from z.string().optional() to z.string()), ensuring the outbound payload
validation (the schema used by the Survicate handlers) fails fast when those
audit fields are missing.
src/v0/destinations/survicate/transform.ts (1)

35-45: ⚠️ Potential issue | 🔴 Critical

Use a single message-field convention across all handlers.

These branches currently mix user_id/group_id/message_id with userId/groupId/messageId. As written, a normal RudderStack message can either fail the guard checks or produce an outbound payload with missing IDs. Normalize once at the boundary and read the same keys everywhere.

Suggested fix
-  if (!message.user_id) {
+  if (!message.userId) {
     throw new InstrumentationError(
       'Anonymous identify calls are not supported. userId is required.',
     );
   }

   const payload: Record<string, any> = {
     user_id: message.userId,
     timestamp: message.originalTimestamp,
-    message_id: message.message_id,
+    message_id: message.messageId,
   };
   const payload: Record<string, any> = {
-    user_id: message.user_id,
-    group_id: message.group_id,
+    user_id: message.userId,
+    group_id: message.groupId,
     traits: message.traits || {},
     timestamp: message.originalTimestamp,
-    message_id: message.message_id,
+    message_id: message.messageId,
   };

Update src/v0/destinations/survicate/types.ts to the same convention in the same change so validation and runtime access stay aligned.

Also applies to: 102-119, 174-190

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/v0/destinations/survicate/transform.ts` around lines 35 - 45, Normalize
incoming Rudder message ID fields to a single convention at the transformer
boundary: map message.user_id → message.userId, message.group_id →
message.groupId, message.message_id → message.messageId (or vice‑versa if you
prefer snake_case) immediately when the transform starts, then use only that
canonical key (message.userId/message.groupId/message.messageId) throughout the
handler (guards, payload building, and any branches that currently read mixed
keys); also update the destination type definitions used for validation to the
same convention so runtime access and typing align.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/v0/destinations/survicate/transform.ts`:
- Around line 35-45: Normalize incoming Rudder message ID fields to a single
convention at the transformer boundary: map message.user_id → message.userId,
message.group_id → message.groupId, message.message_id → message.messageId (or
vice‑versa if you prefer snake_case) immediately when the transform starts, then
use only that canonical key (message.userId/message.groupId/message.messageId)
throughout the handler (guards, payload building, and any branches that
currently read mixed keys); also update the destination type definitions used
for validation to the same convention so runtime access and typing align.

In `@src/v0/destinations/survicate/types.ts`:
- Around line 30-31: The schema currently marks message_id and originalTimestamp
as optional, allowing invalid events through; update the Zod schema in
src/v0/destinations/survicate/types.ts so that message_id and originalTimestamp
are required (change their definitions from z.string().optional() to
z.string()), ensuring the outbound payload validation (the schema used by the
Survicate handlers) fails fast when those audit fields are missing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e2bc78b3-26cb-480e-8934-088b0d560c53

📥 Commits

Reviewing files that changed from the base of the PR and between a724959 and 55dd8ea.

⛔ Files ignored due to path filters (1)
  • test/integrations/destinations/survicate/router/data.ts is excluded by !**/test/**
📒 Files selected for processing (2)
  • src/v0/destinations/survicate/transform.ts
  • src/v0/destinations/survicate/types.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
src/v0/destinations/survicate/transform.ts (3)

105-114: Consider extracting shared context enrichment logic.

The context property extraction (locale, campaign, userAgent) is duplicated across all three event processors (lines 105-114, 189-198, 278-285). Similarly, the reserved-key trait filtering appears three times.

Extracting these into helper functions would reduce duplication and ensure consistency if the logic needs to change:

Example helper functions
function extractContextData(context: SurvicateMessage['context']): Record<string, any> | undefined {
  if (!context) return undefined;
  const data: Record<string, any> = {};
  if (context.locale) data.locale = context.locale;
  if (context.campaign) data.campaign = context.campaign;
  if (context.userAgent) data.userAgent = context.userAgent;
  return Object.keys(data).length > 0 ? data : undefined;
}

function filterReservedTraits(
  traits: Record<string, unknown> | undefined,
  reserved: string[],
): Record<string, any> {
  if (!traits) return {};
  const filtered: Record<string, any> = {};
  for (const [k, v] of Object.entries(traits)) {
    if (!reserved.includes(k)) filtered[k] = v;
  }
  return filtered;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/v0/destinations/survicate/transform.ts` around lines 105 - 114, Extract
the duplicated context enrichment and reserved-trait filtering into helpers
(e.g. create extractContextData(context: SurvicateMessage['context']):
Record<string, any> | undefined and filterReservedTraits(traits, reservedKeys):
Record<string, any>), then replace the repeated blocks that build
payload.context from msg.context and the three trait-filtering loops with calls
to these helpers (references: payload.context assignment, msg.context, the trait
filtering logic around traits/reservedKeys in each event processor). Ensure
helpers return undefined/empty when no data so existing checks
(Object.keys(...).length) can be simplified or removed.

76-82: Manual field checks are redundant after schema validation.

Lines 77-82 check for messageId and originalTimestamp, but these fields are already required in SurvicateMessageSchema (lines 34-35 in types.ts). The SurvicateMessageSchema.parse(msg) call at line 67 would throw a ZodError before reaching these checks if the fields were missing.

If the intent is to provide user-friendly InstrumentationError messages rather than Zod's default errors, consider catching the ZodError and re-throwing with a custom message. Otherwise, these checks can be removed to reduce redundancy.

The same observation applies to identical checks in processGroupEvent (lines 160-166) and processTrackEvent (lines 244-250).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/v0/destinations/survicate/transform.ts` around lines 76 - 82, The
explicit checks for messageId and originalTimestamp after calling
SurvicateMessageSchema.parse(msg) are redundant because
SurvicateMessageSchema.parse(msg) will already throw a ZodError if those
required fields are missing; remove the manual checks that throw new
InstrumentationError(ERR_MESSAGE_ID_REQUIRED) and new
InstrumentationError(ERR_ORIG_TS_REQUIRED) from the transform function (the
block after SurvicateMessageSchema.parse) and the equivalent blocks in
processGroupEvent and processTrackEvent, or alternatively wrap
SurvicateMessageSchema.parse(msg) in a try/catch and convert the ZodError into
user-friendly InstrumentationError messages if you prefer custom errors.

91-103: Consider filtering camelCase variants of reserved keys.

The reserved key list only includes snake_case identifiers (user_id, group_id, etc.). If context.traits contains camelCase equivalents like userId or messageId, they won't be filtered and could appear in the outgoing payload alongside the snake_case canonical fields.

While this won't overwrite the primary identifiers (since Object.assign happens after they're set), it may send redundant data to Survicate.

Suggested improvement
-    const reserved = ['user_id', 'group_id', 'timestamp', 'message_id'];
+    const reserved = [
+      'user_id', 'group_id', 'timestamp', 'message_id',
+      'userId', 'groupId', 'originalTimestamp', 'messageId',
+    ];

This pattern applies to the same filtering logic in processGroupEvent (line 179) and processTrackEvent (line 263) as well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/v0/destinations/survicate/transform.ts` around lines 91 - 103, The
current filtering only checks snake_case reserved keys (the reserved array and
loop over msg.context.traits before Object.assign(payload, filtered)), so
camelCase variants like userId/messageId slip through; update the filter logic
used in this transform block and the same patterns in processGroupEvent and
processTrackEvent to detect and exclude camelCase equivalents (e.g., userId,
groupId, messageId, timestamp) by normalizing trait keys (for example map keys
to a canonical form such as lowercased and underscores removed or explicitly
check both snake_case and camelCase variants) before adding them to filtered,
ensuring Object.assign(payload, filtered) never includes those reserved
variants.
src/v0/destinations/survicate/types.ts (1)

56-59: Consider explicit type parameters for router request type.

The SurvicateRouterRequest relies on default values for the Connection and Metadata type parameters. For consistency with other destinations like PostHog (which uses RouterTransformationRequestData<PostHogMessage, PostHogDestination, undefined, Metadata>), you may want to be explicit:

 export type SurvicateRouterRequest = RouterTransformationRequestData<
   SurvicateMessage,
-  SurvicateDestination
+  SurvicateDestination,
+  undefined,
+  Metadata
 >;

This would also require importing Metadata from ../../../types.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/v0/destinations/survicate/types.ts` around lines 56 - 59, The
SurvicateRouterRequest currently uses default generics; update its declaration
to explicitly pass the Connection/Metadata type parameters by changing the type
alias for SurvicateRouterRequest to use
RouterTransformationRequestData<SurvicateMessage, SurvicateDestination,
undefined, Metadata> and add an import for Metadata from ../../../types so the
Metadata symbol is available for the type argument.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/v0/destinations/survicate/transform.ts`:
- Around line 105-114: Extract the duplicated context enrichment and
reserved-trait filtering into helpers (e.g. create extractContextData(context:
SurvicateMessage['context']): Record<string, any> | undefined and
filterReservedTraits(traits, reservedKeys): Record<string, any>), then replace
the repeated blocks that build payload.context from msg.context and the three
trait-filtering loops with calls to these helpers (references: payload.context
assignment, msg.context, the trait filtering logic around traits/reservedKeys in
each event processor). Ensure helpers return undefined/empty when no data so
existing checks (Object.keys(...).length) can be simplified or removed.
- Around line 76-82: The explicit checks for messageId and originalTimestamp
after calling SurvicateMessageSchema.parse(msg) are redundant because
SurvicateMessageSchema.parse(msg) will already throw a ZodError if those
required fields are missing; remove the manual checks that throw new
InstrumentationError(ERR_MESSAGE_ID_REQUIRED) and new
InstrumentationError(ERR_ORIG_TS_REQUIRED) from the transform function (the
block after SurvicateMessageSchema.parse) and the equivalent blocks in
processGroupEvent and processTrackEvent, or alternatively wrap
SurvicateMessageSchema.parse(msg) in a try/catch and convert the ZodError into
user-friendly InstrumentationError messages if you prefer custom errors.
- Around line 91-103: The current filtering only checks snake_case reserved keys
(the reserved array and loop over msg.context.traits before
Object.assign(payload, filtered)), so camelCase variants like userId/messageId
slip through; update the filter logic used in this transform block and the same
patterns in processGroupEvent and processTrackEvent to detect and exclude
camelCase equivalents (e.g., userId, groupId, messageId, timestamp) by
normalizing trait keys (for example map keys to a canonical form such as
lowercased and underscores removed or explicitly check both snake_case and
camelCase variants) before adding them to filtered, ensuring
Object.assign(payload, filtered) never includes those reserved variants.

In `@src/v0/destinations/survicate/types.ts`:
- Around line 56-59: The SurvicateRouterRequest currently uses default generics;
update its declaration to explicitly pass the Connection/Metadata type
parameters by changing the type alias for SurvicateRouterRequest to use
RouterTransformationRequestData<SurvicateMessage, SurvicateDestination,
undefined, Metadata> and add an import for Metadata from ../../../types so the
Metadata symbol is available for the type argument.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 29558b72-f05d-4e0c-9572-24df3d015448

📥 Commits

Reviewing files that changed from the base of the PR and between 55dd8ea and e5200bd.

⛔ Files ignored due to path filters (5)
  • src/v0/destinations/survicate/README.md is excluded by !**/*.md
  • test/integrations/destinations/survicate/processor/group.ts is excluded by !**/test/**
  • test/integrations/destinations/survicate/processor/identify.ts is excluded by !**/test/**
  • test/integrations/destinations/survicate/processor/track.ts is excluded by !**/test/**
  • test/integrations/destinations/survicate/router/data.ts is excluded by !**/test/**
📒 Files selected for processing (2)
  • src/v0/destinations/survicate/transform.ts
  • src/v0/destinations/survicate/types.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/v0/destinations/survicate/types.ts (2)

26-48: Previous feedback addressed; consider adding user identifier validation.

The schema correctly addresses the prior review comments:

  • Field names are now camelCase
  • Uses two-argument z.record() form
  • messageId and originalTimestamp are required

One potential improvement: the schema allows both userId and anonymousId to be absent. If the Survicate API requires at least one user identifier, consider adding a refinement:

💡 Optional refinement to enforce user identifier
 export const SurvicateMessageSchema = z
   .object({
     type: z.enum(['identify', 'group', 'track']),
     userId: z.string().optional(),
     anonymousId: z.string().optional(),
     // ... rest of schema
   })
-  .passthrough();
+  .passthrough()
+  .refine(
+    (msg) => msg.userId || msg.anonymousId,
+    { message: 'Either userId or anonymousId must be provided' }
+  );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/v0/destinations/survicate/types.ts` around lines 26 - 48,
SurvicateMessageSchema currently allows both userId and anonymousId to be
missing; update SurvicateMessageSchema to enforce that at least one identifier
is present by adding a refinement (e.g., .refine or .superRefine) on
SurvicateMessageSchema that checks either data.userId or data.anonymousId is
truthy and returns a clear validation message if both are absent; ensure the
refinement references the existing schema object so other passthrough behavior
remains unchanged.

63-81: Consider using unknown instead of any for stricter type safety.

The snake_case field names are appropriate here since these interfaces represent outbound payloads to the Survicate API. However, the any type in index signatures and Record types bypasses TypeScript's type checking.

💡 Suggested improvement for type safety
 export interface IdentifyPayload {
   user_id?: string;
-  [key: string]: any;
+  [key: string]: unknown;
 }

 export interface GroupPayload {
   user_id?: string;
   group_id?: string;
-  [key: string]: any;
+  [key: string]: unknown;
 }

 export interface TrackPayload {
   user_id?: string;
   event?: string;
-  properties?: Record<string, any>;
+  properties?: Record<string, unknown>;
   timestamp?: string;
   message_id?: string;
 }

This change would require explicit type assertions or narrowing in the transformation code when accessing dynamic properties, but it provides better compile-time safety against unintended type coercions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/v0/destinations/survicate/types.ts` around lines 63 - 81, Replace the
permissive any types in the outbound Survicate payload interfaces
(IdentifyPayload, GroupPayload, TrackPayload) with unknown for index signatures
and property records to tighten type safety; update IdentifyPayload and
GroupPayload index signatures from [key: string]: any to [key: string]: unknown
and change TrackPayload properties?: Record<string, any> to properties?:
Record<string, unknown>, then adjust the transformation code where these
payloads are read or written to perform explicit type narrowing/casts or runtime
checks so the compiler is satisfied.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/v0/destinations/survicate/types.ts`:
- Around line 26-48: SurvicateMessageSchema currently allows both userId and
anonymousId to be missing; update SurvicateMessageSchema to enforce that at
least one identifier is present by adding a refinement (e.g., .refine or
.superRefine) on SurvicateMessageSchema that checks either data.userId or
data.anonymousId is truthy and returns a clear validation message if both are
absent; ensure the refinement references the existing schema object so other
passthrough behavior remains unchanged.
- Around line 63-81: Replace the permissive any types in the outbound Survicate
payload interfaces (IdentifyPayload, GroupPayload, TrackPayload) with unknown
for index signatures and property records to tighten type safety; update
IdentifyPayload and GroupPayload index signatures from [key: string]: any to
[key: string]: unknown and change TrackPayload properties?: Record<string, any>
to properties?: Record<string, unknown>, then adjust the transformation code
where these payloads are read or written to perform explicit type
narrowing/casts or runtime checks so the compiler is satisfied.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 58738f56-adeb-4efc-bc3d-043e4ca61a43

📥 Commits

Reviewing files that changed from the base of the PR and between e5200bd and 5ef5596.

⛔ Files ignored due to path filters (1)
  • test/integrations/destinations/survicate/router/data.ts is excluded by !**/test/**
📒 Files selected for processing (2)
  • src/v0/destinations/survicate/transform.ts
  • src/v0/destinations/survicate/types.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/v0/destinations/survicate/transform.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants