---
name: ably-rest-field-name-normalization
description: |
  Debug silent data loss when the same entity arrives via both Ably real-time messages
  and REST API responses with different field names. Use when: (1) a value is unexpectedly
  `undefined` in JS despite data arriving successfully, (2) a notification or entity
  renders with "undefined" in its ID or key fields, (3) an Ably event handler and an
  API fetch handler both feed the same consumer but with different field names,
  (4) acknowledgment/dismiss/action logic silently skips because an ID is falsy.
  Covers Ably real-time + REST API integration patterns in BuyerKiosk.
author: Claude Code
version: 1.0.0
date: 2026-02-27
---

# Ably + REST API Field Name Normalization

## Problem

When a system receives the same entity from both Ably real-time messages and REST API
responses, field names often differ between the two sources. This causes **silent failures**
because JavaScript falsy checks (`if (id)`) skip critical logic paths without throwing
errors, making the bug extremely hard to diagnose.

## Context / Trigger Conditions

- A JS module consumes data from both Ably channel subscriptions AND REST API fetch calls
- An entity ID or key field is `undefined` despite data arriving successfully
- DOM elements render with "undefined" in their IDs (e.g., `alert-undefined`)
- Action handlers (dismiss, acknowledge, delete) silently skip their logic with no error
- Console debug logs show the data object has the value, but under a different key name
- Pattern: Ably message publishes with `{ alertId: 42 }` but REST API returns `{ id: 42 }`

## Root Cause

Ably message payloads are designed by the publisher (our code) and often use descriptive
field names like `alertId`, `userId`, `orderId`. REST API responses follow database
conventions and typically use `id` as the primary key field. When both data paths feed
the same consumer function, one path produces the expected field and the other produces
`undefined`.

The critical danger: JavaScript `if (alertId)` evaluates to `false` for `undefined`,
so code branches that depend on the ID silently take the "no-op" path instead of
throwing an error.

## Solution

### 1. Normalize at the boundary

In any adapter/sync module that bridges Ably events with REST API data, normalize
field names immediately when constructing the consumer payload:

```javascript
// BAD - assumes one naming convention
var notification = {
    id: 'alert-' + data.alertId,   // undefined when data comes from REST API
    alertId: data.alertId,          // undefined when data comes from REST API
};

// GOOD - handle both naming conventions
var alertId = data.alertId || data.id;  // Ably uses alertId, API uses id
var notification = {
    id: 'alert-' + alertId,
    alertId: alertId,
};
```

### 2. Add defensive logging

When the normalized value is still falsy, log a warning:

```javascript
var alertId = data.alertId || data.id;
if (!alertId) {
    console.warn('[Module] Received data with no alertId or id:', data);
    return;
}
```

### 3. Document the field mapping

In the adapter module's JSDoc, explicitly document which source uses which field names:

```javascript
/**
 * Enqueue alert to NotificationManager
 * @param {Object} data - Alert data from Ably (uses alertId) or API (uses id)
 */
```

### 4. Check ALL consumer functions

When fixing this pattern, audit every function that reads the normalized field.
In BuyerKiosk, the dismiss/acknowledge flow has multiple steps:

```
enqueue(data)       → needs alertId for notification.alertId
_render(notification) → passes alertId to onDismiss callback
dismiss(id, alertId)  → checks if (alertId) before calling _acknowledgeAlert
_acknowledgeAlert()   → sends POST /api/.../acknowledge
```

If `alertId` is undefined at step 1, ALL downstream steps silently fail.

## Verification

1. Check console for any IDs containing "undefined" (e.g., `alert-undefined`)
2. After dismiss/action, verify the expected API call fires in Network tab
3. Check the database for the expected record (e.g., acknowledgment row)
4. Add temporary `console.log` in the normalization function to confirm both
   Ably events AND API fetches produce valid IDs

## Example

**BuyerKiosk Spec 040 - System Alerts (AlertAblySync.js)**

The `enqueueAlert()` function receives data from two sources:
- Ably `alert:created` events: `{ alertId: 42, title: "...", ... }`
- REST API `/api/system-alerts/pending`: `{ id: 42, title: "...", ... }`

Fix applied at the normalization boundary:
```javascript
function enqueueAlert(data, deliveryMethod) {
    // Ably messages use 'alertId', API responses use 'id'
    var alertId = data.alertId || data.id;

    var notification = {
        id: 'alert-' + alertId,
        alertId: alertId,
        title: data.title,
        // ...
    };

    window.NotificationManager.enqueue(notification);
}
```

## Diagnostic Steps

If you suspect this bug:

1. **Check console for "undefined" in IDs**: Search for `alert-undefined`, `order-undefined`, etc.
2. **Compare Ably message payload vs API response**: They likely use different key names
3. **Look for `if (id)` or `if (alertId)` guards**: These silently skip when undefined
4. **Check the adapter/sync module**: The function that feeds both Ably events and API data
   to the same consumer is where the normalization should happen

## Notes

- This pattern applies to ANY Ably-integrated feature in BuyerKiosk, not just System Alerts
- The same issue can occur with WebSocket messages vs REST API responses
- Always prefer normalizing at the earliest boundary (adapter layer) rather than
  adding fallbacks in every consumer function
- When designing new Ably message payloads, consider matching the REST API field names
  to avoid this class of bug entirely
- Related: The `workspace.js` fallback fetch (`_fetchInitialPendingAlerts`) correctly
  used `alert.id`, which was a clue that the REST API used `id` not `alertId`
