Repository: rudderlabs/rudder-transformer
Pull request #4343: refactor: update customerio_audience to use RudderRecordV2Schema
Author: login: koladilip
Created: 2025-05-21T23:31:00Z | Merged: 2025-05-26T14:44:56Z

## Description

This PR updates the customerio_audience destination to use the RudderRecordV2Schema and RudderRecordV2 types.

## Changes

- Add RecordAction enum in rudderEvents.ts for better type safety (renamed from RecordActionType)
- Update RudderRecordV2Schema to make identifiers optional and rudderId required
- Remove z.null() from identifiers type in RudderRecordV2Schema
- Update customerio_audience destination to use RudderRecordV2Schema
- Ensure identifiers remains required for customerio_audience
- Update tests to handle new schema requirements

## Testing

All tests are passing, including the component tests for customerio_audience destination.

Resolve INT-3700


Diff:
diff --git a/src/types/rudderEvents.ts b/src/types/rudderEvents.ts
index 29ba4fe478..9fe2b19f76 100644
--- a/src/types/rudderEvents.ts
+++ b/src/types/rudderEvents.ts
@@ -47,6 +47,38 @@ export const RudderRecordV1Schema = z.object({
 
 export type RudderRecordV1 = z.infer<typeof RudderRecordV1Schema>;
 
+/**
+ * Enum for record actions
+ */
+export enum RecordAction {
+  INSERT = 'insert',
+  UPDATE = 'update',
+  DELETE = 'delete',
+}
+
+export const RudderRecordV2Schema = RudderMessageSchema.extend({
+  type: z.literal('record'),
+  action: z.nativeEnum(RecordAction),
+  fields: z.record(z.string(), z.any()).optional(),
+  identifiers: z.record(z.string(), z.union([z.string(), z.number()])).optional(),
+  recordId: z.string().optional(),
+  rudderId: z.string(),
+  context: z
+    .object({
+      sources: z
+        .object({
+          job_id: z.string(),
+          version: z.string(),
+          job_run_id: z.string(),
+          task_run_id: z.string(),
+        })
+        .optional(),
+    })
+    .optional(),
+});
+
+export type RudderRecordV2 = z.infer<typeof RudderRecordV2Schema>;
+
 /**
  * Metadata structure for messages
  */
diff --git a/src/v0/destinations/customerio_audience/transform.ts b/src/v0/destinations/customerio_audience/transform.ts
index 4bbef334af..6182d232ed 100644
--- a/src/v0/destinations/customerio_audience/transform.ts
+++ b/src/v0/destinations/customerio_audience/transform.ts
@@ -1,10 +1,10 @@
 import {
-  SegmentAction,
   CustomerIOConnection,
   CustomerIODestination,
   CustomerIORouterRequest,
   ProcessedEvent,
 } from './type';
+import { RecordAction } from '../../../types/rudderEvents';
 import { batchResponseBuilder, createEventChunk } from './utils';
 import { handleRtTfSingleEventError } from '../../util';
 
@@ -42,11 +42,11 @@ const processRouterDest = async (inputs: CustomerIORouterRequest[], reqMetadata:
 
   // Split successful events into delete and insert/update lists
   const deleteRespList = successfulEvents
-    .filter((event) => event.eventAction === SegmentAction.DELETE)
+    .filter((event) => event.eventAction === RecordAction.DELETE)
     .map(({ payload, metadata }) => ({ payload, metadata }));
 
   const insertOrUpdateRespList = successfulEvents
-    .filter((event) => event.eventAction !== SegmentAction.DELETE)
+    .filter((event) => event.eventAction !== RecordAction.DELETE)
     .map(({ payload, metadata }) => ({ payload, metadata }));
 
   const batchSuccessfulRespList = batchResponseBuilder(
diff --git a/src/v0/destinations/customerio_audience/type.ts b/src/v0/destinations/customerio_audience/type.ts
index cfffa6608b..9197dd6259 100644
--- a/src/v0/destinations/customerio_audience/type.ts
+++ b/src/v0/destinations/customerio_audience/type.ts
@@ -4,10 +4,11 @@ import {
   Connection,
   Destination,
   DestinationConnectionConfig,
-  MessageTypeSchema,
   Metadata,
   RouterTransformationRequestData,
+  RudderRecordV2Schema,
 } from '../../../types';
+import { RecordAction } from '../../../types/rudderEvents';
 
 import {
   BatchedRequest,
@@ -37,11 +38,7 @@ export type SegmentationHeaders = {
   Authorization: string;
 };
 
-export const SegmentAction = {
-  INSERT: 'insert',
-  UPDATE: 'update',
-  DELETE: 'delete',
-} as const;
+// We use RudderRecordV2's action type directly
 
 export const CustomerIODestinationConfigSchema = z
   .object({
@@ -63,33 +60,18 @@ export const CustomerIOConnectionConfigSchema = z
 
 export type CustomerIOConnectionConfig = z.infer<typeof CustomerIOConnectionConfigSchema>;
 
-const SegmentActionSchema = z.nativeEnum(SegmentAction);
-
-// Message type specific to CustomerIO
-export const CustomerIOMessageSchema = z
-  .object({
-    type: z.literal(MessageTypeSchema.enum.record),
-    action: SegmentActionSchema,
-    identifiers: z
-      .record(z.string(), z.union([z.string(), z.number()]))
-      .superRefine((identifiers, ctx) => {
-        if (Object.keys(identifiers).length === 0) {
-          ctx.addIssue({
-            code: z.ZodIssueCode.custom,
-            message: 'cannot be empty',
-          });
-        } else if (Object.keys(identifiers).length !== 1) {
-          ctx.addIssue({
-            code: z.ZodIssueCode.custom,
-            message: 'only one identifier is supported',
-          });
-        }
-      }),
-  })
-  .passthrough();
-
-export type SegmentActionType = z.infer<typeof SegmentActionSchema>;
-
+// Message type specific to CustomerIO, based on RudderRecordV2Schema
+export const CustomerIOMessageSchema = RudderRecordV2Schema.extend({
+  // Override the identifiers field with CustomerIO-specific validation
+  // For CustomerIO, identifiers is required and must contain exactly one identifier
+  identifiers: z
+    .record(z.string().min(1), z.union([z.string(), z.number()]))
+    .refine((ids) => Object.keys(ids).length === 1, {
+      message: 'exactly one identifier is supported',
+    }),
+});
+
+// CustomerIOMessage type derived directly from the schema
 export type CustomerIOMessage = z.infer<typeof CustomerIOMessageSchema>;
 
 // Final exported types using generics from base types
@@ -121,5 +103,5 @@ export type CustomerIOBatchedRequest = BatchedRequest<
 export type CustomerIOBatchedRequestBody = BatchedRequestBody<SegmentationPayload>;
 
 export type ProcessedEvent = RespList & {
-  eventAction: SegmentActionType;
+  eventAction: RecordAction;
 };
diff --git a/src/v0/destinations/customerio_audience/utils.test.ts b/src/v0/destinations/customerio_audience/utils.test.ts
index c940e0d380..2922d84bf6 100644
--- a/src/v0/destinations/customerio_audience/utils.test.ts
+++ b/src/v0/destinations/customerio_audience/utils.test.ts
@@ -68,6 +68,8 @@ describe('utils', () => {
                 type: 'record',
                 action: 'insert',
                 identifiers: { email: 'test@test.com' },
+                rudderId: 'test-rudder-id',
+                messageId: 'test-message-id',
               },
               connection: {
                 config: {
@@ -92,6 +94,8 @@ describe('utils', () => {
                 type: 'record',
                 action: 'insert',
                 identifiers: { email: 'test@test.com' },
+                rudderId: 'test-rudder-id',
+                messageId: 'test-message-id',
               },
               connection: {
                 config: {
@@ -134,6 +138,8 @@ describe('utils', () => {
                 type: 'record',
                 action: 'insert',
                 identifiers: { email: 'test@test.com' },
+                rudderId: 'test-rudder-id',
+                messageId: 'test-message-id',
               },
               connection: {
                 config: {
diff --git a/src/v0/destinations/customerio_audience/utils.ts b/src/v0/destinations/customerio_audience/utils.ts
index ed80f6816a..be5f701094 100644
--- a/src/v0/destinations/customerio_audience/utils.ts
+++ b/src/v0/destinations/customerio_audience/utils.ts
@@ -15,12 +15,11 @@ import {
   SegmentationParam,
   SegmentationPayload,
   CustomerIOBatchResponse,
-  SegmentActionType,
   CustomerIOConnectionConfigSchema,
   CustomerIOMessageSchema,
   ProcessedEvent,
 } from './type';
-import { Metadata } from '../../../types';
+import { Metadata, RecordAction } from '../../../types/rudderEvents';
 
 const getIdType = (connection: CustomerIOConnection): string =>
   connection.config.destination.identifierMappings[0]?.to || DEFAULT_ID_TYPE;
@@ -126,7 +125,8 @@ export const batchResponseBuilder = (
   return [...insertResponses, ...deleteResponses];
 };
 
-const getEventAction = (event: CustomerIORouterRequest): string => event.message.action;
+const getEventAction = (event: CustomerIORouterRequest): RecordAction =>
+  event.message.action as RecordAction;
 
 const validateEvent = (event: CustomerIORouterRequest): boolean => {
   const { message, connection } = event;
@@ -155,6 +155,6 @@ export const createEventChunk = (event: CustomerIORouterRequest): ProcessedEvent
   return {
     payload: { ids: [id] },
     metadata: event.metadata,
-    eventAction: eventAction as SegmentActionType,
+    eventAction,
   };
 };
diff --git a/test/integrations/destinations/customerio_audience/router/data.ts b/test/integrations/destinations/customerio_audience/router/data.ts
index f9964a79ec..319ed2971a 100644
--- a/test/integrations/destinations/customerio_audience/router/data.ts
+++ b/test/integrations/destinations/customerio_audience/router/data.ts
@@ -1,4 +1,4 @@
-import { generateMetadata, generateRecordPayload, overrideDestination } from '../../../testUtils';
+import { generateMetadata, generateRecordPayload } from '../../../testUtils';
 import { defaultMockFns } from '../mocks';
 import {
   destType,
@@ -296,7 +296,7 @@ export const data = [
               metadata: [generateMetadata(9)],
               batched: false,
               statusCode: 400,
-              error: 'type: Invalid literal value, expected "record"',
+              error: 'type: Invalid literal value, expected "record"; rudderId: Required',
               statTags: RouterInstrumentationErrorStatTags,
               destination,
             },
@@ -304,7 +304,7 @@ export const data = [
               metadata: [generateMetadata(10)],
               batched: false,
               statusCode: 400,
-              error: 'identifiers: cannot be empty',
+              error: 'identifiers: exactly one identifier is supported',
               statTags: RouterInstrumentationErrorStatTags,
               destination,
             },
@@ -312,7 +312,7 @@ export const data = [
               metadata: [generateMetadata(11)],
               batched: false,
               statusCode: 400,
-              error: 'identifiers: only one identifier is supported',
+              error: 'identifiers: exactly one identifier is supported',
               statTags: RouterInstrumentationErrorStatTags,
               destination,
             },


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>src/v0/destinations/customerio_audience/type.ts (2)</summary><blockquote>

`70-85`: **Simplify and harden the identifier-count check**  
The current `superRefine` block is correct but a bit verbose, and it still accepts objects with blank keys or `undefined` values.  
You can compress the logic and make intent clearer by using a single `refine` plus key/value checks:

```diff
-  identifiers: z
-    .record(z.string(), z.union([z.string(), z.number()]))
-    .superRefine((identifiers, ctx) => {
-      if (Object.keys(identifiers).length === 0) {
-        ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'cannot be empty' });
-      } else if (Object.keys(identifiers).length !== 1) {
-        ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'only one identifier is supported' });
-      }
-    }),
+  identifiers: z
+    .record(z.string().min(1), z.union([z.string(), z.number()]))
+    .refine((ids) => Object.keys(ids).length === 1, {
+      message: 'exactly one identifier is supported',
+    }),
```

This keeps the same runtime guarantees while improving readability.

---

`88-94`: **Derive the TypeScript type directly from the schema to avoid drift**  
`CustomerIOMessage` is manually reconstructed with `Omit`, which can fall out of sync if the schema evolves.  Leveraging `z.infer` keeps the type definition single-sourced:

```diff
-export type CustomerIOMessage = Omit<RudderRecordV2, 'messageId' | 'identifiers'> & {
-  messageId?: string;
-  identifiers: Record<string, string | number>;
-};
+export type CustomerIOMessage = z.infer<typeof CustomerIOMessageSchema>;
```

This eliminates duplication and ensures strict correspondence between runtime validation and compile-time typing.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 20ccf22add29df9df526cd53103b5a68b77aac20 and 8384d6bb41320fc1c2e2d09eddc681b17ce855f2.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `test/integrations/destinations/customerio_audience/router/data.ts` is excluded by `!**/test/**`

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `src/types/rudderEvents.ts` (1 hunks)
* `src/v0/destinations/customerio_audience/transform.ts` (2 hunks)
* `src/v0/destinations/customerio_audience/type.ts` (4 hunks)
* `src/v0/destinations/customerio_audience/utils.test.ts` (3 hunks)
* `src/v0/destinations/customerio_audience/utils.ts` (3 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>src/v0/destinations/customerio_audience/utils.ts (1)</summary><blockquote>

<details>
<summary>src/v0/destinations/customerio_audience/type.ts (1)</summary>

* `CustomerIORouterRequest` (102-106)

</details>

</blockquote></details>
<details>
<summary>src/v0/destinations/customerio_audience/type.ts (1)</summary><blockquote>

<details>
<summary>src/types/rudderEvents.ts (2)</summary>

* `RudderRecordV2Schema` (64-86)
* `RudderRecordV2` (88-88)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (9)</summary>

* GitHub Check: Build Transformer Docker Image - PR / Build Transformer Docker Image ARM64
* GitHub Check: Build Transformer Docker Image - PR / Build Transformer Docker Image AMD64
* GitHub Check: Build User Transformer Docker Image - PR / Build Transformer Docker Image ARM64
* GitHub Check: Build User Transformer Docker Image - PR / Build Transformer Docker Image AMD64
* GitHub Check: Code Coverage
* GitHub Check: UT Tests
* GitHub Check: test_and_publish
* GitHub Check: Analyze (javascript-typescript)
* GitHub Check: Analyze (go)

</details>

<details>
<summary>🔇 Additional comments (12)</summary><blockquote>

<details>
<summary>src/types/rudderEvents.ts (1)</summary>

`50-89`: **Well-structured implementation of RecordActionType and RudderRecordV2Schema**

The introduction of the `RecordActionType` enum and `RudderRecordV2Schema` provides a consistent typing structure for record actions. The schema correctly makes identifiers optional while requiring rudderId, which aligns with the PR objectives.

Good use of JSDoc comments explaining the purpose of these new types. The schema is properly defined using zod with appropriate type constraints and passthrough to allow additional properties.

</details>
<details>
<summary>src/v0/destinations/customerio_audience/utils.test.ts (3)</summary>

`71-71`: **Added required rudderId field to test case**

Correctly added the required `rudderId` field to the test message to match the new schema requirements in RudderRecordV2Schema.

---

`96-96`: **Added required rudderId field to test case**

Correctly added the required `rudderId` field to the test message to match the new schema requirements in RudderRecordV2Schema.

---

`139-139`: **Added required rudderId field to test case**

Correctly added the required `rudderId` field to the test message to match the new schema requirements in RudderRecordV2Schema.

</details>
<details>
<summary>src/v0/destinations/customerio_audience/transform.ts (3)</summary>

`7-7`: **Updated import to use RecordActionType from rudderEvents**

Correctly imported the centralized `RecordActionType` enum from rudderEvents.ts instead of using a local type.

---

`45-45`: **Updated filtering to use RecordActionType.DELETE**

Correctly updated the filter condition to use the centralized RecordActionType enum instead of the previous SegmentAction enum.

---

`49-49`: **Updated filtering to use RecordActionType.DELETE**

Correctly updated the filter condition to use the centralized RecordActionType enum instead of the previous SegmentAction enum.

</details>
<details>
<summary>src/v0/destinations/customerio_audience/utils.ts (3)</summary>

`22-22`: **Updated import to use RecordActionType from rudderEvents**

Correctly imported the centralized `RecordActionType` enum from rudderEvents.ts instead of using a local type.

---

`128-129`: **Updated getEventAction to return RecordActionType**

The function is now correctly typed to return RecordActionType. This assumes that the action in the message is a valid RecordActionType value, which should be verified by the validation logic in the validateEvent function.

---

`158-158`: **Removed unnecessary cast to SegmentActionType**

The explicit cast to SegmentActionType has been removed, allowing the function to use the RecordActionType returned by getEventAction directly. This is a cleaner approach that leverages TypeScript's type inference.

</details>
<details>
<summary>src/v0/destinations/customerio_audience/type.ts (2)</summary>

`65-68`: **Optional `messageId` may break downstream expectations**  
`messageId` is made optional here solely “to maintain compatibility with existing tests”.  In production pipelines, `messageId` is often relied upon for idempotency and de-dupe logic; silently omitting it can lead to duplicate event ingestion or tracing gaps.

Please double-check:

1. Whether any downstream service (router, DLQ, warehouse writer, etc.) still assumes the field is always present.  
2. If the field is optional only during back-fill, consider guarding writes with an explicit default (e.g. `crypto.randomUUID()`).

[tag: request_verification]

---

`125-126`: **👍 Enum swap looks good**  
`eventAction` now references the shared `RecordActionType`, aligning this file with the new Rudder v2 event model. Nice consolidation!

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T23:33:34Z
user: login: vinayteki95
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T06:45:21Z
user: login: vinayteki95
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T06:47:08Z
user: login: koladilip
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T08:47:17Z
user: login: koladilip
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T08:47:33Z
user: login: vinayteki95
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T12:28:30Z
user: login: manish339k
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-26T14:39:10Z

Review comments:
diff_hunk: @@ -63,33 +60,18 @@ export const CustomerIOConnectionConfigSchema = z
 
 export type CustomerIOConnectionConfig = z.infer<typeof CustomerIOConnectionConfigSchema>;
 
-const SegmentActionSchema = z.nativeEnum(SegmentAction);
-
-// Message type specific to CustomerIO
-export const CustomerIOMessageSchema = z
-  .object({
-    type: z.literal(MessageTypeSchema.enum.record),
-    action: SegmentActionSchema,
-    identifiers: z
-      .record(z.string(), z.union([z.string(), z.number()]))
-      .superRefine((identifiers, ctx) => {
-        if (Object.keys(identifiers).length === 0) {
-          ctx.addIssue({
-            code: z.ZodIssueCode.custom,
-            message: 'cannot be empty',
-          });
-        } else if (Object.keys(identifiers).length !== 1) {
-          ctx.addIssue({
-            code: z.ZodIssueCode.custom,
-            message: 'only one identifier is supported',
-          });
-        }
-      }),
-  })
-  .passthrough();
-
-export type SegmentActionType = z.infer<typeof SegmentActionSchema>;
-
+// Message type specific to CustomerIO, based on RudderRecordV2Schema
+export const CustomerIOMessageSchema = RudderRecordV2Schema.extend({
+  // Override the identifiers field with CustomerIO-specific validation
+  // For CustomerIO, identifiers is required and must contain exactly one identifier
+  identifiers: z
+    .record(z.string().min(1), z.union([z.string(), z.number()]))
+    .refine((ids) => Object.keys(ids).length === 1, {
path: src/v0/destinations/customerio_audience/type.ts
user: login: vinayteki95
body: Mainly to make it clear what the refine function receives as its argument.
```suggestion
    .refine((identifiersObject) => Object.keys(identifiersObject).length === 1, {
```
created_at: 2025-05-23T06:45:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -47,6 +47,41 @@ export const RudderRecordV1Schema = z.object({
 
 export type RudderRecordV1 = z.infer<typeof RudderRecordV1Schema>;
 
+/**
+ * Enum for record actions
+ */
+export enum RecordAction {
+  INSERT = 'insert',
+  UPDATE = 'update',
+  DELETE = 'delete',
+}
+
+export const RudderRecordV2Schema = z
+  .object({
+    type: z.literal('record'),
path: src/types/rudderEvents.ts
user: login: vinayteki95
body: According to the customerio documentation, `userId` and `channel` should also be part of this type?
Should this be part of every record ?

https://www.rudderstack.com/docs/destinations/reverse-etl-destinations/customerio-audience/connect-retl-source/#record-event-structure 
created_at: 2025-05-23T06:47:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -63,33 +60,18 @@ export const CustomerIOConnectionConfigSchema = z
 
 export type CustomerIOConnectionConfig = z.infer<typeof CustomerIOConnectionConfigSchema>;
 
-const SegmentActionSchema = z.nativeEnum(SegmentAction);
-
-// Message type specific to CustomerIO
-export const CustomerIOMessageSchema = z
-  .object({
-    type: z.literal(MessageTypeSchema.enum.record),
-    action: SegmentActionSchema,
-    identifiers: z
-      .record(z.string(), z.union([z.string(), z.number()]))
-      .superRefine((identifiers, ctx) => {
-        if (Object.keys(identifiers).length === 0) {
-          ctx.addIssue({
-            code: z.ZodIssueCode.custom,
-            message: 'cannot be empty',
-          });
-        } else if (Object.keys(identifiers).length !== 1) {
-          ctx.addIssue({
-            code: z.ZodIssueCode.custom,
-            message: 'only one identifier is supported',
-          });
-        }
-      }),
-  })
-  .passthrough();
-
-export type SegmentActionType = z.infer<typeof SegmentActionSchema>;
-
+// Message type specific to CustomerIO, based on RudderRecordV2Schema
+export const CustomerIOMessageSchema = RudderRecordV2Schema.extend({
+  // Override the identifiers field with CustomerIO-specific validation
+  // For CustomerIO, identifiers is required and must contain exactly one identifier
+  identifiers: z
+    .record(z.string().min(1), z.union([z.string(), z.number()]))
+    .refine((ids) => Object.keys(ids).length === 1, {
path: src/v0/destinations/customerio_audience/type.ts
user: login: koladilip
body: Done
created_at: 2025-05-23T08:47:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -47,6 +47,41 @@ export const RudderRecordV1Schema = z.object({
 
 export type RudderRecordV1 = z.infer<typeof RudderRecordV1Schema>;
 
+/**
+ * Enum for record actions
+ */
+export enum RecordAction {
+  INSERT = 'insert',
+  UPDATE = 'update',
+  DELETE = 'delete',
+}
+
+export const RudderRecordV2Schema = z
+  .object({
+    type: z.literal('record'),
path: src/types/rudderEvents.ts
user: login: koladilip
body: Addressed
created_at: 2025-05-23T08:47:33Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-21T23:31:06Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Summary by CodeRabbit

- **Refactor**
  - Unified event action types by adopting a standardized action enum across related features.
  - Updated validation schemas to extend from a common base, ensuring consistency and reducing redundancy.
  - Simplified identifier requirements for certain event messages to enforce exactly one identifier.
- **Tests**
  - Enhanced test cases to include additional required properties, improving test coverage and reliability.
## Summary by CodeRabbit

- **Refactor**
  - Unified event action and message types to use a standardized schema for improved consistency across event processing.
  - Updated validation and typing for event messages to align with the new schema, ensuring stricter identifier requirements.
- **Tests**
  - Enhanced test cases to include new required fields in event messages, ensuring comprehensive test coverage.

## Walkthrough

This update introduces a unified `RecordAction` enum and a new `RudderRecordV2Schema` for event typing and validation. The customer.io audience integration is refactored to use these new types and schemas, replacing legacy custom types. Related test cases and utility functions are updated accordingly for consistency.

## Changes

| File(s)                                                                                  | Change Summary                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
|------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `src/types/rudderEvents.ts`                                                              | Added `RecordAction` enum with members `insert`, `update`, and `delete`. Added `RudderRecordV2Schema` (Zod schema for Rudder V2 record events) and `RudderRecordV2` type alias. No modifications to existing types.                                                                                                                                                                                                                                           |
| `src/v0/destinations/customerio_audience/type.ts`                                        | Removed legacy `SegmentAction` enum and schema. Refactored `CustomerIOMessageSchema` to extend `RudderRecordV2Schema` with an overridden `identifiers` field enforcing exactly one identifier. Updated related types to use `RecordAction` instead of `SegmentActionType`.                                                                                                                                                                   |
| `src/v0/destinations/customerio_audience/transform.ts`                                   | Replaced `SegmentAction` with `RecordAction` for filtering event actions. Updated import paths and filtering logic accordingly. No changes to function signatures or control flow.                                                                                                                                                                                                                                                        |
| `src/v0/destinations/customerio_audience/utils.ts`                                       | Updated imports and typings: replaced `SegmentActionType` with `RecordAction`. Modified `getEventAction` to return `RecordAction` explicitly. Adjusted `createEventChunk` to use the new type without casting.                                                                                                                                                                                                                           |
| `src/v0/destinations/customerio_audience/utils.test.ts`                                  | Updated test cases to add required `rudderId` and `messageId` properties to event message objects. No other logic or assertion changes.                                                                                                                                                                                                                                                                                                |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Router as Event Router
    participant Utils as Utils
    participant Schema as RudderRecordV2Schema

    Router->>Utils: processRouterDest(event)
    Utils->>Schema: Validate event with RudderRecordV2Schema
    Schema-->>Utils: Validated event with RecordAction
    Utils-->>Router: Processed event using unified action type
```

## Suggested reviewers

- sivashanmukh
- ItsSudip
- vinayteki95

---

**Suggestions for Improvement:**

- Consider adding migration notes or deprecation warnings for downstream consumers relying on the legacy `SegmentAction` types to ease transition.
- Update documentation and API references to reflect the new `RecordAction` enum and `RudderRecordV2Schema`.
- Expand test coverage to include validation failure scenarios for the new schema, especially around the single-identifier constraint in `identifiers`.
- Review other destination integrations for opportunities to unify event typing with the new schema for consistency across the codebase.

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 0fd99e88c575b1ba422a952a189cba3749bdc9d0 and 9882fba04e8ed84f64ccbe87981b081057219b15.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `test/integrations/destinations/customerio_audience/router/data.ts` is excluded by `!**/test/**`

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `src/types/rudderEvents.ts` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* src/types/rudderEvents.ts

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (9)</summary>

* GitHub Check: Build User Transformer Docker Image - PR / Build Transformer Docker Image ARM64
* GitHub Check: Build User Transformer Docker Image - PR / Build Transformer Docker Image AMD64
* GitHub Check: Build Transformer Docker Image - PR / Build Transformer Docker Image AMD64
* GitHub Check: Build Transformer Docker Image - PR / Build Transformer Docker Image ARM64
* GitHub Check: Code Coverage
* GitHub Check: UT Tests
* GitHub Check: test_and_publish
* GitHub Check: Analyze (javascript-typescript)
* GitHub Check: Analyze (go)

</details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=rudderlabs/rudder-transformer&utm_content=4343):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: devops-github-rudderstack
created_at: 2025-05-21T23:37:01Z
body: Allure Test reports for this run are available at:
- Allure Report: [View Report](https://rudderlabs.github.io/test-reports/rudder-transformer/4343/index.html)
author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-05-21T23:37:38Z
body: ## [Codecov](https://app.codecov.io/gh/rudderlabs/rudder-transformer/pull/4343?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=rudderlabs) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 91.12%. Comparing base [(`4a18dc9`)](https://app.codecov.io/gh/rudderlabs/rudder-transformer/commit/4a18dc99a37cb25476fbe0b7a4e68b3a96caf77f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=rudderlabs) to head [(`9882fba`)](https://app.codecov.io/gh/rudderlabs/rudder-transformer/commit/9882fba04e8ed84f64ccbe87981b081057219b15?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=rudderlabs).
> Report is 16 commits behind head on develop.

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff             @@
##           develop    #4343      +/-   ##
===========================================
+ Coverage    91.10%   91.12%   +0.01%     
===========================================
  Files          636      636              
  Lines        33378    33385       +7     
  Branches      7888     7894       +6     
===========================================
+ Hits         30410    30421      +11     
+ Misses        2729     2695      -34     
- Partials       239      269      +30     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/rudderlabs/rudder-transformer/pull/4343?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=rudderlabs).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=rudderlabs).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: devops-github-rudderstack
created_at: 2025-05-21T23:51:02Z
body: Allure Test reports for this run are available at:
- Allure Report: [View Report](https://rudderlabs.github.io/test-reports/rudder-transformer/4343/index.html)
author_association: CONTRIBUTOR
user: login: devops-github-rudderstack
created_at: 2025-05-22T06:26:12Z
body: Allure Test reports for this run are available at:
- Allure Report: [View Report](https://rudderlabs.github.io/test-reports/rudder-transformer/4343/index.html)
author_association: CONTRIBUTOR
user: login: koladilip
created_at: 2025-05-22T10:20:18Z
body: I've implemented the CodeRabbit suggestions to improve the code quality:

1. Simplified and hardened the identifier-count check in `CustomerIOMessageSchema`:
   - Replaced the verbose `superRefine` block with a more concise `refine` method
   - Added validation to ensure keys are not empty strings using `z.string().min(1)`
   - Simplified the validation logic to a single check for exactly one identifier
   - Improved the error message to be more consistent

2. Derived the TypeScript type directly from the schema to avoid drift:
   - Used `z.infer<typeof CustomerIOMessageSchema>` instead of manually reconstructing the type with `Omit`
   - This ensures the type definition stays in sync with the schema
   - Removed the now-unused import of `RudderRecordV2`

All tests are passing, including the component tests for customerio_audience destination.
author_association: CONTRIBUTOR
user: login: koladilip
created_at: 2025-05-22T10:20:32Z
body: I've implemented the CodeRabbit suggestions to improve the code quality:

1. Simplified and hardened the identifier-count check in `CustomerIOMessageSchema`:
   - Replaced the verbose `superRefine` block with a more concise `refine` method
   - Added validation to ensure keys are not empty strings using `z.string().min(1)`
   - Simplified the validation logic to a single check for exactly one identifier
   - Improved the error message to be more consistent

2. Derived the TypeScript type directly from the schema to avoid drift:
   - Used `z.infer<typeof CustomerIOMessageSchema>` instead of manually reconstructing the type with `Omit`
   - This ensures the type definition stays in sync with the schema
   - Removed the now-unused import of `RudderRecordV2`

All tests are passing, including the component tests for customerio_audience destination.
author_association: CONTRIBUTOR
user: login: devops-github-rudderstack
created_at: 2025-05-22T10:20:39Z
body: Allure Test reports for this run are available at:
- Allure Report: [View Report](https://rudderlabs.github.io/test-reports/rudder-transformer/4343/index.html)
author_association: CONTRIBUTOR
user: login: devops-github-rudderstack
created_at: 2025-05-23T07:05:09Z
body: Allure Test reports for this run are available at:
- Allure Report: [View Report](https://rudderlabs.github.io/test-reports/rudder-transformer/4343/index.html)
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-05-23T07:06:17Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=rudderlabs_rudder-transformer&pullRequest=4343) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=rudderlabs_rudder-transformer&pullRequest=4343&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=rudderlabs_rudder-transformer&pullRequest=4343&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=rudderlabs_rudder-transformer&pullRequest=4343&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=rudderlabs_rudder-transformer&pullRequest=4343&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=rudderlabs_rudder-transformer&pullRequest=4343&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=rudderlabs_rudder-transformer&pullRequest=4343)


author_association: NONE