Skip to content

Conversation

mariczne
Copy link
Contributor

@mariczne mariczne commented Aug 29, 2025

Why

  • Fallback emissions sometimes returned the raw payload, producing inconsistent shapes downstream.
  • Webhook handlers assumed payload shapes and could throw on edge cases (e.g., empty changedTablesById).
  • Fetching records, e.g. immediately fetching newly-created can intermittently fail; we shouldn’t fail the event because of that.

What changed

  • emitDefaultEvent now always emits { originalPayload } to keep a consistent shape.
  • Record & field emitters now guard payload shape before destructuring.
  • Introduced shared withRetry() in common/utils.mjs (exponential backoff) and used it for getRecord.
  • On repeated fetch failure, we emit with record.fields = {} instead of throwing.

Summary by CodeRabbit

  • Bug Fixes

    • Prevented errors when webhook payloads are empty or missing data.
    • Standardized default event emissions to include the original payload for consistency.
    • Improved handling of deleted records, including clearer summaries.
    • Graceful fallback when record details can’t be fetched, avoiding broken events.
  • Chores

    • Added an internal retry mechanism with exponential backoff to improve reliability of record retrieval.
    • Bumped package, action, and source metadata versions across the Airtable integration.

… retry getting record

- Always wrap fallback emissions as { originalPayload } for consistent shape
- Guard against missing/empty changedTablesById in field & record emitters
- Add shared withRetry() (exponential backoff,) in common/utils
- Use withRetry for getRecord in record emitter; on failure, emit empty fields
Copy link
Contributor

coderabbitai bot commented Aug 29, 2025

Walkthrough

Adds a retry utility with exponential backoff and exports it; integrates the retry into webhook record fetching. Webhook emitters defensively handle missing/empty payload.changedTablesById and normalize fallback emissions to { originalPayload }. Several action/source versions and package.json were bumped; no other public signatures changed.

Changes

Cohort / File(s) Summary
Utilities: Retry helper
components/airtable_oauth/common/utils.mjs
Adds sleep(ms) and withRetry(fn, { retries = 2, baseDelay = 500 } ) (exponential backoff) and exports withRetry.
Webhook emitters: Guards & normalized payloads
components/airtable_oauth/sources/common/common-webhook-field.mjs, components/airtable_oauth/sources/common/common-webhook-record.mjs, components/airtable_oauth/sources/common/common-webhook.mjs
Emitters now guard missing/empty payload?.changedTablesById and emit { originalPayload } with meta for fallback. Field/record emitters extract the first table entry defensively. Record emitter imports withRetry to wrap Airtable getRecord calls (logs on failure, uses {} fields). Deletion path emits originalPayload, tableId, and deletedRecordIds.
Actions & Sources: version bumps
components/airtable_oauth/actions/*/*.mjs, components/airtable_oauth/sources/*/*-*.mjs, components/airtable_oauth/package.json
Patch/version increments across many actions and sources and package.json (0.5.1 → 0.5.2); no functional or API changes aside from metadata.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant WH as Webhook Record Handler
  participant P as Incoming Payload
  participant AT as Airtable API
  participant EM as Event Emitter

  WH->>WH: emitEvent(P)
  alt payload?.changedTablesById missing/empty
    WH->>EM: emit({ originalPayload: P }, meta)
    note right of EM: Normalized fallback emission
  else
    WH->>WH: derive tableEntry => tableId, tableData
    alt destroyedRecordIds present
      WH->>EM: emit({ originalPayload: P, tableId, deletedRecordIds }, meta)
    else
      loop retry attempts (withRetry)
        WH->>AT: getRecord(tableId, recordId)
        AT-->>WH: record | error
      end
      alt fetch success
        WH->>EM: emit({ fields, originalPayload: P }, meta)
      else fetch failed
        WH->>EM: emit({ fields: {}, originalPayload: P }, meta)
      end
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • danhsiung
  • michelle0927
  • jcortes

Poem

I tap my paw, then wait—retry—
Backoff beats beneath the sky.
If tables hide, I still will tell,
Wrapping truth where payloads dwell.
Records fetched, or fields are bare—
I thump and emit with careful care. 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 85e5228 and 3be22a2.

📒 Files selected for processing (27)
  • components/airtable_oauth/actions/create-comment/create-comment.mjs (1 hunks)
  • components/airtable_oauth/actions/create-field/create-field.mjs (1 hunks)
  • components/airtable_oauth/actions/create-multiple-records/create-multiple-records.mjs (1 hunks)
  • components/airtable_oauth/actions/create-or-update-record/create-or-update-record.mjs (1 hunks)
  • components/airtable_oauth/actions/create-single-record/create-single-record.mjs (1 hunks)
  • components/airtable_oauth/actions/create-table/create-table.mjs (1 hunks)
  • components/airtable_oauth/actions/delete-record/delete-record.mjs (1 hunks)
  • components/airtable_oauth/actions/get-record-or-create/get-record-or-create.mjs (1 hunks)
  • components/airtable_oauth/actions/get-record/get-record.mjs (1 hunks)
  • components/airtable_oauth/actions/list-bases/list-bases.mjs (1 hunks)
  • components/airtable_oauth/actions/list-records-in-view/list-records-in-view.mjs (1 hunks)
  • components/airtable_oauth/actions/list-records/list-records.mjs (1 hunks)
  • components/airtable_oauth/actions/list-tables/list-tables.mjs (1 hunks)
  • components/airtable_oauth/actions/search-records/search-records.mjs (1 hunks)
  • components/airtable_oauth/actions/update-comment/update-comment.mjs (1 hunks)
  • components/airtable_oauth/actions/update-field/update-field.mjs (1 hunks)
  • components/airtable_oauth/actions/update-record/update-record.mjs (1 hunks)
  • components/airtable_oauth/actions/update-table/update-table.mjs (1 hunks)
  • components/airtable_oauth/package.json (1 hunks)
  • components/airtable_oauth/sources/new-field/new-field.mjs (1 hunks)
  • components/airtable_oauth/sources/new-modified-or-deleted-records-instant/new-modified-or-deleted-records-instant.mjs (1 hunks)
  • components/airtable_oauth/sources/new-modified-or-deleted-records/new-modified-or-deleted-records.mjs (1 hunks)
  • components/airtable_oauth/sources/new-or-modified-field/new-or-modified-field.mjs (1 hunks)
  • components/airtable_oauth/sources/new-or-modified-records-in-view/new-or-modified-records-in-view.mjs (1 hunks)
  • components/airtable_oauth/sources/new-or-modified-records/new-or-modified-records.mjs (1 hunks)
  • components/airtable_oauth/sources/new-records-in-view/new-records-in-view.mjs (1 hunks)
  • components/airtable_oauth/sources/new-records/new-records.mjs (1 hunks)
✅ Files skipped from review due to trivial changes (27)
  • components/airtable_oauth/actions/list-tables/list-tables.mjs
  • components/airtable_oauth/actions/get-record-or-create/get-record-or-create.mjs
  • components/airtable_oauth/sources/new-or-modified-field/new-or-modified-field.mjs
  • components/airtable_oauth/sources/new-field/new-field.mjs
  • components/airtable_oauth/sources/new-records-in-view/new-records-in-view.mjs
  • components/airtable_oauth/actions/list-records/list-records.mjs
  • components/airtable_oauth/actions/delete-record/delete-record.mjs
  • components/airtable_oauth/actions/create-or-update-record/create-or-update-record.mjs
  • components/airtable_oauth/actions/create-multiple-records/create-multiple-records.mjs
  • components/airtable_oauth/actions/update-comment/update-comment.mjs
  • components/airtable_oauth/actions/search-records/search-records.mjs
  • components/airtable_oauth/sources/new-or-modified-records-in-view/new-or-modified-records-in-view.mjs
  • components/airtable_oauth/actions/create-single-record/create-single-record.mjs
  • components/airtable_oauth/actions/create-comment/create-comment.mjs
  • components/airtable_oauth/actions/update-record/update-record.mjs
  • components/airtable_oauth/sources/new-or-modified-records/new-or-modified-records.mjs
  • components/airtable_oauth/sources/new-records/new-records.mjs
  • components/airtable_oauth/actions/update-field/update-field.mjs
  • components/airtable_oauth/actions/create-table/create-table.mjs
  • components/airtable_oauth/actions/update-table/update-table.mjs
  • components/airtable_oauth/package.json
  • components/airtable_oauth/actions/get-record/get-record.mjs
  • components/airtable_oauth/sources/new-modified-or-deleted-records/new-modified-or-deleted-records.mjs
  • components/airtable_oauth/sources/new-modified-or-deleted-records-instant/new-modified-or-deleted-records-instant.mjs
  • components/airtable_oauth/actions/list-bases/list-bases.mjs
  • components/airtable_oauth/actions/list-records-in-view/list-records-in-view.mjs
  • components/airtable_oauth/actions/create-field/create-field.mjs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: pnpm publish
  • GitHub Check: Publish TypeScript components
  • GitHub Check: Verify TypeScript components
  • GitHub Check: Lint Code Base
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • 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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbit in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbit 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:
    • @coderabbit gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbit read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbit help to get the list of available commands.

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • 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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@adolfo-pd adolfo-pd added the User submitted Submitted by a user label Aug 29, 2025
Copy link

vercel bot commented Aug 29, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
pipedream-docs Ignored Ignored Aug 29, 2025 1:12pm
pipedream-docs-redirect-do-not-edit Ignored Ignored Aug 29, 2025 1:12pm

@pipedream-component-development
Copy link
Collaborator

Thank you so much for submitting this! We've added it to our backlog to review, and our team has been notified.

@pipedream-component-development
Copy link
Collaborator

Thanks for submitting this PR! When we review PRs, we follow the Pipedream component guidelines. If you're not familiar, here's a quick checklist:

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: 3

🧹 Nitpick comments (4)
components/airtable_oauth/common/utils.mjs (1)

166-168: De-duplicate sleep helper across components

trustpilot/common/utils.mjs already defines sleep(ms). Consider centralizing this helper to avoid drift and duplication.

components/airtable_oauth/sources/common/common-webhook-field.mjs (1)

28-36: Good defensive guard; add a clearer summary for fallback events

Emitting a meta summary helps users distinguish normalized “raw” fallbacks in the UI and logs.

-      this.$emit({
-        originalPayload: payload,
-      }, this.generateMeta(payload));
+      const summary = "Raw webhook payload (field): empty or unknown changedTablesById";
+      this.$emit({
+        originalPayload: payload,
+      }, this.generateMeta(payload, summary));
components/airtable_oauth/sources/common/common-webhook-record.mjs (2)

14-22: Solid guard on empty payload shape; consider adding a summary

Matches field emitter behavior. Add a brief summary for better traceability.

-      this.$emit({
-        originalPayload: payload,
-      }, this.generateMeta(payload));
+      const summary = "Raw webhook payload (record): empty or unknown changedTablesById";
+      this.$emit({
+        originalPayload: payload,
+      }, this.generateMeta(payload, summary));

77-82: Include identifiers in the error log for easier triage

Add tableId and recordId to the log context.

-        console.log("Airtable getRecord failed; emitting empty fields", {
+        console.log("Airtable getRecord failed; emitting empty fields", {
           statusCode: err?.statusCode ?? err?.response?.status,
           message: err?.message,
-        });
+          tableId,
+          recordId,
+        });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e878c02 and 85e5228.

📒 Files selected for processing (4)
  • components/airtable_oauth/common/utils.mjs (1 hunks)
  • components/airtable_oauth/sources/common/common-webhook-field.mjs (1 hunks)
  • components/airtable_oauth/sources/common/common-webhook-record.mjs (3 hunks)
  • components/airtable_oauth/sources/common/common-webhook.mjs (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-07-24T02:06:47.016Z
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-07-24T02:06:47.016Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.

Applied to files:

  • components/airtable_oauth/sources/common/common-webhook-record.mjs
  • components/airtable_oauth/sources/common/common-webhook.mjs
🧬 Code graph analysis (3)
components/airtable_oauth/sources/common/common-webhook-record.mjs (1)
components/airtable_oauth/sources/common/common-webhook-field.mjs (4)
  • changed (28-28)
  • tableEntry (29-29)
  • tableId (37-40)
  • tableData (13-15)
components/airtable_oauth/sources/common/common-webhook-field.mjs (2)
components/airtable_oauth/sources/common/common-webhook-record.mjs (3)
  • changed (14-14)
  • tableEntry (15-15)
  • tableId (23-26)
components/airtable_oauth/common/utils.mjs (1)
  • tableId (142-142)
components/airtable_oauth/common/utils.mjs (1)
components/trustpilot/common/utils.mjs (1)
  • sleep (223-225)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Lint Code Base
  • GitHub Check: pnpm publish
  • GitHub Check: Publish TypeScript components
  • GitHub Check: Verify TypeScript components
🔇 Additional comments (2)
components/airtable_oauth/common/utils.mjs (1)

190-191: Exporting withRetry expands public API; verify no naming collisions

Confirm no other modules export a withRetry symbol in this package scope and update any barrels accordingly.

components/airtable_oauth/sources/common/common-webhook-record.mjs (1)

37-49: Deletion path looks good

Emits only IDs when records are destroyed and summarizes single vs multiple deletions. No issues.

Comment on lines +170 to +183
async function withRetry(fn, {
retries = 2, baseDelay = 500,
} = {}) {
let attempt = 0;
while (attempt <= retries) {
try {
return await fn();
} catch (err) {
if (attempt === retries) throw err;
await sleep(baseDelay * (2 ** attempt));
attempt += 1;
}
}
}
Copy link
Contributor

@coderabbitai coderabbitai bot Aug 29, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Retry policy retries on all errors; add shouldRetry, jitter, and max cap

Blindly retrying on 4xx (e.g., 404 for rapidly-deleted records) adds latency with no benefit. Add a shouldRetry predicate, jitter, and a maxDelay cap.

Apply:

-async function withRetry(fn, {
-  retries = 2, baseDelay = 500,
-} = {}) {
+async function withRetry(fn, {
+  retries = 2,
+  baseDelay = 500,
+  factor = 2,
+  maxDelay = 5_000,
+  jitter = 0.1,
+  shouldRetry,
+} = {}) {
   let attempt = 0;
   while (attempt <= retries) {
     try {
       return await fn();
     } catch (err) {
-      if (attempt === retries) throw err;
-      await sleep(baseDelay * (2 ** attempt));
-      attempt += 1;
+      const status = err?.statusCode ?? err?.response?.status;
+      const defaultShouldRetry = (e) => {
+        const s = e?.statusCode ?? e?.response?.status;
+        return s == null || s >= 500 || s === 429;
+      };
+      const should = (shouldRetry ?? defaultShouldRetry)(err, attempt);
+      if (!should || attempt === retries) throw err;
+      const backoff = Math.min(maxDelay, baseDelay * (factor ** attempt));
+      const rand = 1 + (Math.random() * 2 - 1) * jitter; // +/- jitter
+      const delay = Math.max(0, Math.floor(backoff * rand));
+      await sleep(delay);
+      attempt += 1;
     }
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function withRetry(fn, {
retries = 2, baseDelay = 500,
} = {}) {
let attempt = 0;
while (attempt <= retries) {
try {
return await fn();
} catch (err) {
if (attempt === retries) throw err;
await sleep(baseDelay * (2 ** attempt));
attempt += 1;
}
}
}
async function withRetry(fn, {
retries = 2,
baseDelay = 500,
factor = 2,
maxDelay = 5_000,
jitter = 0.1,
shouldRetry,
} = {}) {
let attempt = 0;
while (attempt <= retries) {
try {
return await fn();
} catch (err) {
const status = err?.statusCode ?? err?.response?.status;
const defaultShouldRetry = (e) => {
const s = e?.statusCode ?? e?.response?.status;
return s == null || s >= 500 || s === 429;
};
const should = (shouldRetry ?? defaultShouldRetry)(err, attempt);
if (!should || attempt === retries) throw err;
const backoff = Math.min(maxDelay, baseDelay * (factor ** attempt));
const rand = 1 + (Math.random() * 2 - 1) * jitter; // +/- jitter
const delay = Math.max(0, Math.floor(backoff * rand));
await sleep(delay);
attempt += 1;
}
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We might as well have a 404 when we fetch "too early" and I think this was the case at least once in our testing. Hence the retry

Copy link
Contributor

Choose a reason for hiding this comment

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

@mariczne That's a great point! If Airtable's API can return 404s for records that exist but aren't immediately available after webhook notifications (due to eventual consistency or propagation delays), then retrying on 404s makes perfect sense for this use case.

Your simpler retry implementation is appropriate here since the domain-specific behavior justifies retrying on all errors, including client errors like 404. Thanks for the clarification!


✏️ Learnings added
Learnt from: mariczne
PR: PipedreamHQ/pipedream#18221
File: components/airtable_oauth/common/utils.mjs:170-183
Timestamp: 2025-08-29T08:35:55.886Z
Learning: In Airtable webhook integrations, 404 errors can occur when fetching records "too early" after receiving webhook notifications, likely due to propagation delays between the webhook system and the API. Therefore, retrying on 404 errors is beneficial in this context.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines +65 to +76
let fields = {};
try {
const res = await withRetry(
() => this.airtable.getRecord({
baseId: this.baseId,
tableId,
recordId,
}),
);

fields = res?.fields ?? {};
} catch (err) {
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refine retries to avoid useless 4xx retries and add jitter

Pass shouldRetry to avoid retrying on 404/400s and to support 429/5xx/network only. This reduces latency when records are deleted between event and fetch.

-      try {
-        const res = await withRetry(
-          () => this.airtable.getRecord({
-            baseId: this.baseId,
-            tableId,
-            recordId,
-          }),
-        );
+      try {
+        const res = await withRetry(
+          () => this.airtable.getRecord({
+            baseId: this.baseId,
+            tableId,
+            recordId,
+          }),
+          {
+            retries: 2,
+            baseDelay: 500,
+            // Retry on 5xx / 429 / network only
+            shouldRetry: (err) => {
+              const status = err?.statusCode ?? err?.response?.status;
+              return status == null || status >= 500 || status === 429;
+            },
+          },
+        );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let fields = {};
try {
const res = await withRetry(
() => this.airtable.getRecord({
baseId: this.baseId,
tableId,
recordId,
}),
);
fields = res?.fields ?? {};
} catch (err) {
let fields = {};
try {
const res = await withRetry(
() => this.airtable.getRecord({
baseId: this.baseId,
tableId,
recordId,
}),
{
retries: 2,
baseDelay: 500,
// Retry on 5xx / 429 / network only
shouldRetry: (err) => {
const status = err?.statusCode ?? err?.response?.status;
return status == null || status >= 500 || status === 429;
},
},
);
fields = res?.fields ?? {};
} catch (err) {

Comment on lines +137 to 143
// Normalize the fallback so consumers always get a consistent structure
// matching our other emitters (which include originalPayload).
const meta = this.generateMeta(payload);
this.$emit(payload, meta);
this.$emit({
originalPayload: payload,
}, meta);
},
Copy link
Contributor

@coderabbitai coderabbitai bot Aug 29, 2025

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Potential breaking change: default emission now wrapped; provide BC path

Wrapping the payload under originalPayload can break existing workflows that read top-level fields. Consider dual-emitting the raw fields alongside originalPayload for a deprecation window.

-  const meta = this.generateMeta(payload);
-  this.$emit({
-    originalPayload: payload,
-  }, meta);
+  const meta = this.generateMeta(payload);
+  // Back-compat: spread raw keys for existing consumers; new consumers use originalPayload.
+  this.$emit({
+    originalPayload: payload,
+    ...payload,
+  }, meta);

If you prefer a hard switch, gate the spread behind an env flag (e.g., PD_AIRTABLE_EMIT_RAW_COMPAT !== "false") and plan a removal date in the changelog.


🏁 Script executed:

#!/bin/bash
set -e

echo "Listing files in components/airtable_oauth/sources/common:"
ls -1 components/airtable_oauth/sources/common

echo
echo "Inspecting all $emit calls in common directory:"
rg -n "\$emit" -C1 components/airtable_oauth/sources/common/*.mjs || true

echo
echo "Inspecting originalPayload usage in common directory:"
rg -n "originalPayload" -C1 components/airtable_oauth/sources/common/*.mjs || true

Length of output: 3278


Provide backward-compatibility by dual-emitting raw payload fields

The new emitters now nest the entire payload under originalPayload, dropping all top-level properties and breaking consumers that expect those fields. You must restore the old shape by spreading payload after originalPayload (or gate it behind an env flag for a deprecation window).

Update these emit calls:

  • components/airtable_oauth/sources/common/common-webhook.mjs (lines 140–143)
  • components/airtable_oauth/sources/common/common-webhook-record.mjs (lines 18, 43, 86)
  • components/airtable_oauth/sources/common/common-webhook-field.mjs (lines 32, 75)

Example diff for common-webhook.mjs:

- this.$emit({ originalPayload: payload }, meta);
+ this.$emit(
+   { originalPayload: payload, ...payload },
+   meta
+ );

If you prefer a toggle, wrap the spread in an env-flag check (e.g. PD_AIRTABLE_EMIT_RAW_COMPAT !== "false") and schedule its removal in your changelog.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Normalize the fallback so consumers always get a consistent structure
// matching our other emitters (which include originalPayload).
const meta = this.generateMeta(payload);
this.$emit(payload, meta);
this.$emit({
originalPayload: payload,
}, meta);
},
// Normalize the fallback so consumers always get a consistent structure
// matching our other emitters (which include originalPayload).
const meta = this.generateMeta(payload);
this.$emit(
{ originalPayload: payload, ...payload },
meta
);
},

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Isn't that what versioning is for?

Copy link
Contributor

Choose a reason for hiding this comment

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

@mariczne You're absolutely right! Versioning is the cleaner approach here.

If you're planning to bump the component version for this structural change, then the clean payload wrapping under originalPayload makes perfect sense without the backward compatibility complexity I suggested.

A version bump would:

  • Give users control over when to migrate
  • Avoid the dual-emission overhead
  • Keep the new code clean and consistent
  • Signal that this is an intentional breaking change

Are you planning to version this as a major update? If so, the current implementation is much better than my suggested dual-emission approach.

🤔 Hmm, you make a good point about versioning...
(\ /)
( ..)
o
(")(")


🧠 Learnings used
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-07-24T02:06:47.016Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.

Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-10-08T15:33:38.240Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
User submitted Submitted by a user
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants