Node.js data action failing on MongoDB aggregation pipeline when mapping CXone flow variables

Building a custom data action in CXone Studio requires a Node.js service to handle the MongoDB aggregation pipeline. The flow passes a customer_tier string and a last_login timestamp, and the backend needs to group sessions while calculating a churn score. Mongoose connection pooling sits at a size of 10, and the circuit breaker wires up to retry on ECONNRESET or replica set step-downs. The real headache shows up when dynamic type coercion maps the flow input to the $match stage. CXone sends everything as strings, so casting happens inside the pipeline builder. The query profiler keeps showing full collection scans instead of index usage. It’s really frustrating when the execution plan ignores the obvious compound index.

Here is how the pipeline payload gets constructed before POSTing to the data action endpoint. A quick validation loop checks the incoming JSON schema against the defined constraints. If the types mismatch, a 400 response fires immediately. The pipeline array looks like this when it hits the database:
const pipeline = [{ $match: { tier: { $eq: String(req.body.tier) }, last_login: { $gte: new Date(req.body.last_login) } } }, { $group: { _id: null, avg_score: { $avg: '$engagement' } } }]
Index usage on tier and last_login should trigger automatically. The profiler output instead returns stage: COLLSCAN every single time. Switching to $expr with $toDate was attempted to fix the coercion, but latency spiked and the scan persisted. Don’t think the wrapper should be touching BSON types during serialization.

Tracing the request through the middleware stack reveals the retry logic handles timeout spikes without dropping connections. Audit trail logs capture the exact payload hash before execution. JSON schema validation passes cleanly, yet the aggregation stage ignores the type cast entirely. Pre-parsing the variables in the route handler before building the pipeline object was another attempt. The circuit breaker trips after the second retry because the query takes too long on the scan. Running explain('executionStats') on the debug endpoint shows the exact same plan with zero index usage.

Environment and steps I’ve run through:

  • CXone Data Action configured with Node.js 18 runtime and custom timeout of 8000ms
  • Mongoose 7.6 with maxPoolSize: 10 and serverSelectionTimeoutMS: 5000
  • Circuit breaker using opossum with errorThresholdPercentage: 50 and timeout: 3000
  • JSON Schema validator checking customer_tier as string and last_login as ISO date
  • MongoDB 6.0 replica set with compound index on tier and last_login
  • Query profiler enabled at level 2 to capture execution stats per request
  • Retry loop capped at 3 attempts with exponential backoff for MongoServerSelectionError

The profiler JSON keeps returning "executionStages": { "stage": "COLLSCAN", "nReturned": 42, "docsExamined": 15000 } even when the filter should only touch a handful of records. Maybe the $eq operator refuses to cooperate with string coercion inside the aggregation framework. The Mongoose schema definition might be overriding the raw pipeline structure. Checking the BSON serializer settings tomorrow.