-
Notifications
You must be signed in to change notification settings - Fork 5.3k
[Components] plaid - new components #16586
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎ 3 Skipped Deployments
|
""" WalkthroughThis update introduces a comprehensive Plaid integration, adding new Plaid API actions, webhook sources, utility modules, and constants. The Plaid app component is refactored to use the official SDK, with robust prop definitions and methods for API interaction, error handling, and pagination. Webhook sources and test events are implemented for real-time event handling. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Action
participant PlaidApp
participant PlaidAPI
User->>Action: Trigger action (e.g., get-transactions)
Action->>PlaidApp: Call method (e.g., getTransactions)
PlaidApp->>PlaidAPI: Make API request
PlaidAPI-->>PlaidApp: Return API response
PlaidApp-->>Action: Return processed data
Action-->>User: Output result
sequenceDiagram
participant PlaidAPI
participant WebhookSource
participant System
PlaidAPI-->>WebhookSource: Send webhook event
WebhookSource->>WebhookSource: Check event relevance
WebhookSource->>System: Emit event with metadata
Assessment against linked issues
Suggested labels
Poem
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
components/plaid/actions/create-access-token/create-access-token.mjsOops! Something went wrong! :( ESLint: 8.57.1 Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'jsonc-eslint-parser' imported from /eslint.config.mjs components/plaid/actions/create-sandbox-public-token/create-sandbox-public-token.mjsOops! Something went wrong! :( ESLint: 8.57.1 Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'jsonc-eslint-parser' imported from /eslint.config.mjs components/plaid/actions/create-user/create-user.mjsOops! Something went wrong! :( ESLint: 8.57.1 Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'jsonc-eslint-parser' imported from /eslint.config.mjs
Tip ⚡️ Faster reviews with caching
Enjoy the performance boost—your workflow just got faster. ✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this 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
♻️ Duplicate comments (1)
components/plaid/sources/new-accounts-available-instant/new-accounts-available-instant.mjs (1)
24-30
: Same deduplication concern as other sourceSee the note about using
event_id
instead ofDate.now()
for deterministic IDs. Applying the same change here keeps the sources consistent.
🧹 Nitpick comments (18)
components/plaid/common/constants.mjs (1)
4-7
: Consider Named Exports for Tree-Shaking
Using a default export object may hinder tree-shaking. Consider switching to named exports:-export default { - DEFAULT_LIMIT, - DEFAULT_MAX, -}; +export { DEFAULT_LIMIT, DEFAULT_MAX };components/plaid/common/sandbox-institutions.mjs (3)
1-3
: Add File-Level Documentation
This file defines a large static map of sandbox institutions. Adding a JSDoc header or inline comment explaining its purpose and usage will improve developer onboarding and maintainability.
2-21
: Group Institutions by Category
The non-OAuth banks (INS_109508–INS_109512) would benefit from a comment block separating them from other categories. For example:+// ── Non-OAuth sandbox institutions ──────────────────────────────────────── INS_109508: { … }, INS_109509: { … }, …
22-70
: Add Section Comments for Clarity
Institutions span Canadian, UK, OAuth, and degraded/unsupported cases. Adding comments before each logical group will make future updates easier to navigate.components/plaid/sources/common/events.mjs (3)
1-5
: Freeze WEBHOOK_TYPE for Immutability
Protect these constants from unintended mutation by applyingObject.freeze
. E.g.:-const WEBHOOK_TYPE = { … }; +const WEBHOOK_TYPE = Object.freeze({ … });
7-26
: Freeze WEBHOOK_CODE and Nested Objects
Similarly, lock down the nestedWEBHOOK_CODE
object and its sub-objects:-const WEBHOOK_CODE = { … }; +const WEBHOOK_CODE = Object.freeze({ … });
28-31
: Consider Named Exports over Default Export
Using named exports (export { WEBHOOK_TYPE, WEBHOOK_CODE };
) improves tree-shaking and makes individual constants easier to import directly.components/plaid/package.json (1)
16-17
: Verify Dependency Versions and Compatibility
Ensure the newly addedplaid@^33.0.0
SDK and upgraded@pipedream/platform@^3.0.3
are compatible and available. You can run:npm view plaid@^33.0.0 version npm view @pipedream/platform@^3.0.3 versioncomponents/plaid/sources/new-accounts-available-instant/test-event.mjs (1)
8-8
: Remove stray line number.There appears to be a stray character/number "8" at the end of the file that should be removed.
"webhook_type": "ITEM" }; -8
components/plaid/common/utils.mjs (1)
1-17
: Consider adding JSDoc comments to utility functions.Adding JSDoc comments would improve code maintainability by providing clear documentation about parameters, return values, and usage examples.
+/** + * Collects all items from an async iterable into an array + * @param {AsyncIterable} iterations - The async iterable to collect from + * @returns {Promise<Array>} Array containing all items from the iterable + */ async function iterate(iterations) { const items = []; for await (const item of iterations) { items.push(item); } return items; } +/** + * Safely accesses nested properties in an object using a dot-separated path + * @param {Object} obj - The object to access properties from + * @param {string} propertyString - Dot-separated path to the property (e.g., "user.address.zipCode") + * @returns {*} The value at the specified path or undefined if the path doesn't exist + */ function getNestedProperty(obj, propertyString) { const properties = propertyString.split("."); return properties.reduce((prev, curr) => prev?.[curr], obj); }components/plaid/actions/get-transactions/get-transactions.mjs (3)
68-80
: Consider simplifying conditional options handling.The conditional logic for building the options object is well-structured, but could be more concise.
Consider using object property shorthand when the property name matches the variable name:
- if (includeOriginalDescription !== undefined) { - options.include_original_description = includeOriginalDescription; - } + if (includeOriginalDescription !== undefined) { + options.include_original_description = includeOriginalDescription; + }While the implementation is correct, this is just a stylistic suggestion.
88-91
: Simplify conditional spread for options.The conditional spread operator with logical AND could be simplified for better readability.
- ...(Object.keys(options).length > 0 && { - options, - }), + ...(Object.keys(options).length ? { options } : {}),This alternative is more explicit about the intent and avoids potential confusion with the logical AND operator.
95-96
: Consider handling empty transactions array more directly.The current implementation handles the possibility of
transactions
being undefined, which may not be necessary if the paginate method always returns an array.- const transactionCount = transactions?.length || 0; + const transactionCount = transactions.length;If there's a possibility that
transactions
could be undefined or null from the paginate method, then the current implementation is correct. Otherwise, this simplification would be cleaner.components/plaid/sources/common/webhook.mjs (3)
61-62
: Remove console.log statement in production code.Console logging the createLinkToken response may not be appropriate for production code.
- console.log("createLinkToken response", response);
Consider using a debug log level if you need to retain this for troubleshooting, or remove it entirely for production code.
85-87
: Uncomment or remove HTTP response handling.There are commented-out lines related to HTTP response handling. These should either be uncommented or removed to maintain clean code.
Either uncomment and use:
http.respond({ status: 200, });Or remove the commented code entirely if it's not needed.
71-73
: Document the intention of isEventRelevant method.The
isEventRelevant
method always returns true, which means all webhook events will be processed by default. This is likely intended to be overridden by extending components.Add a comment to clarify the intention:
isEventRelevant() { + // Base implementation accepts all events; override in extending components to filter events return true; },
components/plaid/sources/new-event-instant/new-event-instant.mjs (1)
14-21
: Consider using a more unique event ID.The current ID generation uses a timestamp (
Date.now()
), which could potentially cause collisions if multiple events are processed at the exact same millisecond.Consider incorporating the webhook type and code into the ID for better uniqueness:
generateMeta(resource) { const ts = Date.now(); return { - id: ts, + id: `${resource.webhook_type}.${resource.webhook_code}-${ts}`, summary: `New Event: ${resource.webhook_type}.${resource.webhook_code}`, ts, }; },This would ensure that even if two different events arrive at the same millisecond, they would still have unique IDs.
components/plaid/sources/sync-updates-available-instant/sync-updates-available-instant.mjs (1)
20-27
: Use webhook-provided identifiers for deterministic deduping
generateMeta
relies onDate.now()
for bothid
andts
. When two webhooks arrive in the same millisecond the IDs will collide, causing dropped events in “unique” dedupe mode.
Plaid webhook payloads include anevent_id
; prefer that when available, falling back to the timestamp only if not present:generateMeta(resource) { - const ts = Date.now(); - return { - id: ts, - summary: `New Event: ${resource.webhook_type}.${resource.webhook_code}`, - ts, - }; + const ts = Date.now(); + return { + id: resource.event_id ?? ts, + summary: `New Event: ${resource.webhook_type}.${resource.webhook_code}`, + ts, + }; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (17)
components/plaid/actions/create-access-token/create-access-token.mjs
(1 hunks)components/plaid/actions/create-sandbox-public-token/create-sandbox-public-token.mjs
(1 hunks)components/plaid/actions/get-real-time-balance/get-real-time-balance.mjs
(1 hunks)components/plaid/actions/get-transactions/get-transactions.mjs
(1 hunks)components/plaid/common/constants.mjs
(1 hunks)components/plaid/common/sandbox-institutions.mjs
(1 hunks)components/plaid/common/utils.mjs
(1 hunks)components/plaid/package.json
(2 hunks)components/plaid/plaid.app.mjs
(1 hunks)components/plaid/sources/common/events.mjs
(1 hunks)components/plaid/sources/common/webhook.mjs
(1 hunks)components/plaid/sources/new-accounts-available-instant/new-accounts-available-instant.mjs
(1 hunks)components/plaid/sources/new-accounts-available-instant/test-event.mjs
(1 hunks)components/plaid/sources/new-event-instant/new-event-instant.mjs
(1 hunks)components/plaid/sources/new-event-instant/test-event.mjs
(1 hunks)components/plaid/sources/sync-updates-available-instant/sync-updates-available-instant.mjs
(1 hunks)components/plaid/sources/sync-updates-available-instant/test-event.mjs
(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (4)
- GitHub Check: pnpm publish
- GitHub Check: Verify TypeScript components
- GitHub Check: Lint Code Base
- GitHub Check: Publish TypeScript components
🔇 Additional comments (17)
components/plaid/common/constants.mjs (1)
1-2
: Constants Naming and Purpose Clear
TheDEFAULT_LIMIT
andDEFAULT_MAX
constants follow standard uppercase naming and appropriately centralize pagination settings.components/plaid/package.json (1)
3-3
: Version Bump Looks Good
Updating to0.7.0
correctly reflects the addition of new features; this follows semantic versioning for a minor release.components/plaid/sources/sync-updates-available-instant/test-event.mjs (1)
1-8
: Test Fixture Matches Event Schema
The structure (environment
, flags,item_id
,webhook_code
,webhook_type
) aligns with the webhook event constants. This test event will correctly simulate a"SYNC_UPDATES_AVAILABLE"
transactions webhook.components/plaid/sources/new-event-instant/test-event.mjs (1)
1-8
: Test event looks good and follows Plaid webhook format.The exported object correctly models a Plaid webhook event with the appropriate fields needed for testing the "New Event (Instant)" source. The payload includes all required fields for a webhook update acknowledgment event.
components/plaid/sources/new-accounts-available-instant/test-event.mjs (1)
1-7
: Test event structure looks good for NEW_ACCOUNTS_AVAILABLE webhook.The payload correctly structures a Plaid webhook event for new accounts becoming available, with all the necessary fields.
components/plaid/common/utils.mjs (2)
1-7
: Good implementation of async iteration utility.The
iterate
function correctly collects all items from an async iterable into an array, which is useful for handling paginated responses from the Plaid API.
9-12
: Effective utility for safely accessing nested properties.The
getNestedProperty
function uses optional chaining to safely access nested object properties, which is ideal for handling complex nested response structures from Plaid.components/plaid/actions/get-real-time-balance/get-real-time-balance.mjs (2)
9-29
: Props are well-defined with appropriate descriptions.The props include the required access token and optional account IDs with clear labels and descriptions. The dynamic account ID options based on the access token provide a good user experience.
4-8
: Version suggests this is a new component.The version "0.0.1" indicates this is the first release of this component. Ensure adequate testing has been performed, especially with the Plaid sandbox environment.
Have you tested this component with real Plaid sandbox credentials to verify it works as expected?
components/plaid/actions/create-access-token/create-access-token.mjs (1)
1-32
: Good implementation of the Plaid token exchange action.This action properly implements the exchange of a public token for an access token using the Plaid API. The structure follows Pipedream's conventions with clear property definitions and a straightforward implementation.
components/plaid/actions/get-transactions/get-transactions.mjs (1)
1-100
: Overall well-structured transaction retrieval action.The action correctly implements transaction retrieval with proper pagination support and parameter handling. The code is well-organized with appropriate property definitions and validation rules.
components/plaid/sources/common/webhook.mjs (2)
54-58
: Review hardcoded products array vs. configurable products props.The webhook is configured with hardcoded products ("transactions" and "assets"), but the component also accepts product-related props that aren't being used in the Link token creation.
Consider whether the hardcoded products should be replaced with the configurable products from props:
- products: [ - "transactions", - "assets", - ], + products: this.products,If the intention is to always use these specific products regardless of the props, consider removing the product-related props or adding a comment explaining why they're not used in this context.
1-93
: Solid foundation for Plaid webhook event handling.Overall, this common webhook base component provides a good foundation for handling Plaid webhook events. It correctly sets up the webhook endpoint, defines methods that can be overridden by extending components, and provides a consistent structure for event processing.
components/plaid/sources/new-event-instant/new-event-instant.mjs (1)
1-24
: Clean implementation of webhook event source.This is a well-implemented specific webhook source that correctly extends the common webhook component. It provides appropriate metadata generation for its events and includes a sample event for testing.
components/plaid/actions/create-sandbox-public-token/create-sandbox-public-token.mjs (1)
86-95
: Pass a single payload object tocreateSandboxPublicToken
makeRequest
expectsargs
to be the first positional argument in the SDK call, but spreading the payload (lines 86-95) directly into the wrapper call also spreads it into the options bag understood bymakeRequest
.
A safer pattern is:-const response = await app.createSandboxPublicToken({ - institution_id: institutionId, - initial_products: initialProducts, - ...(Object.keys(options).length > 0 && { options }), - ...(userToken && { user_token: userToken }), -}); +const payload = { + institution_id: institutionId, + initial_products: initialProducts, + ...(Object.keys(options).length && { options }), + ...(userToken && { user_token: userToken }), +}; + +const response = await app.createSandboxPublicToken(payload);This guarantees that the wrapper receives exactly the structure it forward-calls with, safeguarding against accidental argument shadowing.
components/plaid/sources/new-accounts-available-instant/new-accounts-available-instant.mjs (1)
18-23
: Guard against undefined constantsIf
events.WEBHOOK_CODE[webhookType]
is missing (e.g. future Plaid change or typo), accessing.NEW_ACCOUNTS_AVAILABLE
will throw.
Defensively test before dereferencing:const webhookType = events.WEBHOOK_TYPE.ITEM; -const webhookCode = events.WEBHOOK_CODE[webhookType].NEW_ACCOUNTS_AVAILABLE; +const webhookCode = + events.WEBHOOK_CODE?.[webhookType]?.NEW_ACCOUNTS_AVAILABLE;Return
false
early when the constant is absent to avoid runtime exceptions.components/plaid/plaid.app.mjs (1)
217-260
: Pagination loop can spin indefinitely if the API ignoresoffset
offset
is incremented blindly (line 259) without checking whether the next page actually yielded different resources.
If Plaid ever ignoresoffset
for a particular endpoint, the loop will never exit.
Store the last batch’s first resource ID or use the SDK’snext_cursor
(for/transactions/sync
-style endpoints) when available.
components/plaid/actions/get-real-time-balance/get-real-time-balance.mjs
Show resolved
Hide resolved
components/plaid/actions/create-sandbox-public-token/create-sandbox-public-token.mjs
Outdated
Show resolved
Hide resolved
d876bb2
to
6f29da2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
components/plaid/plaid.app.mjs (2)
161-164
: ThemakeRequest
method has a signature mismatch issueThe
plaid-node
v10+ library expects methods to be called with(request, options?)
, but the current implementation passesargs
andotherOptions
as separate parameters, which may lead to API call failures.
175-178
: Error handling needs improvement for network failuresThe current error handling will throw an exception when trying to stringify
error.response.data
iferror.response
is undefined (which happens with network errors or timeouts).
🧹 Nitpick comments (4)
components/plaid/plaid.app.mjs (2)
229-277
: Pagination implementation looks robust but could be optimizedThe pagination implementation via the generator function is well-structured, but it always fetches fixed-size batches (DEFAULT_LIMIT) even when the requested max is smaller. This could be optimized for cases where users only need a few records.
Consider adjusting the count parameter based on the remaining items needed:
- options: { - ...resourcesFnArgs?.options, - count: constants.DEFAULT_LIMIT, - offset, - }, + options: { + ...resourcesFnArgs?.options, + count: Math.min(constants.DEFAULT_LIMIT, max - resourcesCount), + offset, + },
222-227
: The createUser method should be documented with parameter detailsThe
createUser
method wrapper is missing JSDoc comments describing the expected parameters, unlike some of the other methods. This would be helpful for developers using this method.Consider adding JSDoc comments:
+ /** + * Creates a new user in Plaid + * @param {Object} args - The request parameters + * @param {string} args.client_user_id - A unique ID representing the end user + * @param {Object} [args.consumer_report_user_identity] - Optional identity information + * @returns {Promise<Object>} The created user object + */ createUser(args = {}) { return this.makeRequest({ method: "userCreate", ...args, }); },components/plaid/actions/create-user/create-user.mjs (2)
41-45
: Consider adding validation for phone number formatWhile the description mentions the E.164 format for phone numbers, there's no validation to ensure users input correctly formatted numbers.
Consider adding a simple regex validation to ensure phone numbers match the E.164 format:
phoneNumbers: { type: "string[]", label: "Phone Numbers", description: "The user's phone number, in E.164 format: +{countrycode}{number}. For example: `+14157452130`. Phone numbers provided in other formats will be parsed on a best-effort basis. Phone number input is validated against valid number ranges; number strings that do not match a real-world phone numbering scheme may cause the request to fail, even in the Sandbox test environment.", + validate: (value) => { + const e164Regex = /^\+[1-9]\d{1,14}$/; + const invalidNumbers = value.filter(num => !e164Regex.test(num.trim())); + if (invalidNumbers.length > 0) { + return `The following numbers don't match E.164 format: ${invalidNumbers.join(', ')}`; + } + return true; + }, },
57-61
: Add date format validation for Date of BirthThe Date of Birth field specifies a format (
yyyy-mm-dd
), but there's no validation to ensure users input dates in this format.Adding validation would improve user experience by catching errors early:
dateOfBirth: { type: "string", label: "Date of Birth", description: "To be provided in the format `yyyy-mm-dd`. This field is required for all Plaid Check customers.", + validate: (value) => { + const dateRegex = /^\d{4}-\d{2}-\d{2}$/; + if (!dateRegex.test(value)) { + return "Date must be in the format yyyy-mm-dd"; + } + const date = new Date(value); + if (isNaN(date.getTime())) { + return "Invalid date"; + } + return true; + }, },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (18)
components/plaid/actions/create-access-token/create-access-token.mjs
(1 hunks)components/plaid/actions/create-sandbox-public-token/create-sandbox-public-token.mjs
(1 hunks)components/plaid/actions/create-user/create-user.mjs
(1 hunks)components/plaid/actions/get-real-time-balance/get-real-time-balance.mjs
(1 hunks)components/plaid/actions/get-transactions/get-transactions.mjs
(1 hunks)components/plaid/common/constants.mjs
(1 hunks)components/plaid/common/sandbox-institutions.mjs
(1 hunks)components/plaid/common/utils.mjs
(1 hunks)components/plaid/package.json
(2 hunks)components/plaid/plaid.app.mjs
(1 hunks)components/plaid/sources/common/events.mjs
(1 hunks)components/plaid/sources/common/webhook.mjs
(1 hunks)components/plaid/sources/new-accounts-available-instant/new-accounts-available-instant.mjs
(1 hunks)components/plaid/sources/new-accounts-available-instant/test-event.mjs
(1 hunks)components/plaid/sources/new-event-instant/new-event-instant.mjs
(1 hunks)components/plaid/sources/new-event-instant/test-event.mjs
(1 hunks)components/plaid/sources/sync-updates-available-instant/sync-updates-available-instant.mjs
(1 hunks)components/plaid/sources/sync-updates-available-instant/test-event.mjs
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- components/plaid/sources/new-event-instant/test-event.mjs
🚧 Files skipped from review as they are similar to previous changes (15)
- components/plaid/sources/sync-updates-available-instant/test-event.mjs
- components/plaid/common/constants.mjs
- components/plaid/sources/new-accounts-available-instant/test-event.mjs
- components/plaid/common/sandbox-institutions.mjs
- components/plaid/package.json
- components/plaid/actions/get-transactions/get-transactions.mjs
- components/plaid/common/utils.mjs
- components/plaid/sources/common/events.mjs
- components/plaid/actions/create-access-token/create-access-token.mjs
- components/plaid/actions/create-sandbox-public-token/create-sandbox-public-token.mjs
- components/plaid/sources/common/webhook.mjs
- components/plaid/sources/new-event-instant/new-event-instant.mjs
- components/plaid/sources/sync-updates-available-instant/sync-updates-available-instant.mjs
- components/plaid/sources/new-accounts-available-instant/new-accounts-available-instant.mjs
- components/plaid/actions/get-real-time-balance/get-real-time-balance.mjs
🔇 Additional comments (4)
components/plaid/plaid.app.mjs (1)
11-127
: Comprehensive propDefinitions for Plaid integrationThe propDefinitions are well-structured with appropriate types, labels, and descriptions. The dynamic options for accountId and the extensive product lists provide a good foundation for the Plaid integration components.
components/plaid/actions/create-user/create-user.mjs (3)
3-24
: Well-structured component with clear descriptionsThe component is properly structured with appropriate key, name, description, and props. The toggle for consumer report identity with
reloadProps: true
is a good implementation of dynamic form behavior.
107-129
: Well-structured request payload constructionThe way the request payload is constructed, conditionally including the consumer report user identity, is clean and readable. The spread operator with the ternary expression is an elegant way to handle the conditional inclusion.
131-133
: Good use of export summary and return valueThe component correctly exports a human-readable summary with the created user ID and returns the full API response, following best practices for action components.
6f29da2
to
285b035
Compare
/approve |
There was a problem hiding this 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
♻️ Duplicate comments (3)
components/plaid/plaid.app.mjs (3)
161-164
:client[method]
signature mismatch riskThe Plaid SDK v10+ defines methods with the signature
(request, options?)
, but the current implementation passesargs
(which contains all parameters) andotherOptions
(which is always undefined with the current usage).const client = this.getClient(); try { - const response = await client[method](args, otherOptions); + const response = await client[method](request, options);
175-178
: Avoid throwing whenerror.response
is undefinedThis error handling will break if
error.response
is undefined, which happens with network errors or timeouts. The code will crash trying to stringify undefined.} catch (error) { console.error("Error making request", error.response); - throw new Error(JSON.stringify(error.response.data, null, 2)); + const safeMsg = error.response?.data + ? JSON.stringify(error.response.data, null, 2) + : error.message; + throw new Error(safeMsg); }
149-150
:⚠️ Potential issueIncorrect parameter destructuring in makeRequest method
The function signature destructures parameters incorrectly. The
method
parameter is part of the main object, but you're also spreading...args
which would include all other properties. This creates confusion about what parameters should be passed.- async makeRequest({ - method, debug = false, otherOptions, ...args - } = {}) { + async makeRequest({ + method, debug = false, request = {}, options + } = {}) {
🧹 Nitpick comments (2)
components/plaid/plaid.app.mjs (2)
258-263
: Simplify date comparison logicThe date comparison logic can be simplified for better readability.
- const isDateGreater = - lastDateAt - && Date.parse(resource[dateField]) >= Date.parse(lastDateAt); - - if (!lastDateAt || isDateGreater) { + if (!lastDateAt || Date.parse(resource[dateField]) >= Date.parse(lastDateAt)) { yield resource; resourcesCount += 1; }
238-245
: Avoid potential reference modificationWhen merging options, the current implementation might modify the original
resourcesFnArgs.options
reference. Use spread operator on both objects for safety.const response = await resourcesFn({ ...resourcesFnArgs, options: { - ...resourcesFnArgs?.options, + ...(resourcesFnArgs?.options || {}), count: constants.DEFAULT_LIMIT, offset, }, });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (18)
components/plaid/actions/create-access-token/create-access-token.mjs
(1 hunks)components/plaid/actions/create-sandbox-public-token/create-sandbox-public-token.mjs
(1 hunks)components/plaid/actions/create-user/create-user.mjs
(1 hunks)components/plaid/actions/get-real-time-balance/get-real-time-balance.mjs
(1 hunks)components/plaid/actions/get-transactions/get-transactions.mjs
(1 hunks)components/plaid/common/constants.mjs
(1 hunks)components/plaid/common/sandbox-institutions.mjs
(1 hunks)components/plaid/common/utils.mjs
(1 hunks)components/plaid/package.json
(2 hunks)components/plaid/plaid.app.mjs
(1 hunks)components/plaid/sources/common/events.mjs
(1 hunks)components/plaid/sources/common/webhook.mjs
(1 hunks)components/plaid/sources/new-accounts-available-instant/new-accounts-available-instant.mjs
(1 hunks)components/plaid/sources/new-accounts-available-instant/test-event.mjs
(1 hunks)components/plaid/sources/new-event-instant/new-event-instant.mjs
(1 hunks)components/plaid/sources/new-event-instant/test-event.mjs
(1 hunks)components/plaid/sources/sync-updates-available-instant/sync-updates-available-instant.mjs
(1 hunks)components/plaid/sources/sync-updates-available-instant/test-event.mjs
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- components/plaid/sources/new-event-instant/test-event.mjs
🚧 Files skipped from review as they are similar to previous changes (16)
- components/plaid/common/constants.mjs
- components/plaid/package.json
- components/plaid/common/sandbox-institutions.mjs
- components/plaid/sources/sync-updates-available-instant/test-event.mjs
- components/plaid/sources/new-accounts-available-instant/test-event.mjs
- components/plaid/actions/get-transactions/get-transactions.mjs
- components/plaid/actions/create-access-token/create-access-token.mjs
- components/plaid/common/utils.mjs
- components/plaid/actions/get-real-time-balance/get-real-time-balance.mjs
- components/plaid/sources/common/webhook.mjs
- components/plaid/sources/common/events.mjs
- components/plaid/sources/sync-updates-available-instant/sync-updates-available-instant.mjs
- components/plaid/sources/new-event-instant/new-event-instant.mjs
- components/plaid/actions/create-sandbox-public-token/create-sandbox-public-token.mjs
- components/plaid/actions/create-user/create-user.mjs
- components/plaid/sources/new-accounts-available-instant/new-accounts-available-instant.mjs
⏰ Context from checks skipped due to timeout of 90000ms (4)
- GitHub Check: pnpm publish
- GitHub Check: Verify TypeScript components
- GitHub Check: Lint Code Base
- GitHub Check: Publish TypeScript components
285b035
to
9eaa060
Compare
/approve |
There was a problem hiding this 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
♻️ Duplicate comments (6)
components/plaid/plaid.app.mjs (6)
161-164
:client[method]
signature mismatch riskThe implementation of
makeRequest
method passes arguments incorrectly to the Plaid SDK methods. According to the Plaid Node SDK v10+, the method signature is(request, options?)
but your implementation passesargs
(containing all parameters) andotherOptions
(which is always undefined).This can lead to runtime errors or unexpected behavior when calling Plaid API methods.
async makeRequest({ method, debug = false, otherOptions, ...args } = {}) { // ... const client = this.getClient(); try { - const response = await client[method](args, otherOptions); + const response = await client[method](args);Consider restructuring
makeRequest
to match the SDK signature:async makeRequest({ - method, debug = false, otherOptions, ...args + method, debug = false, request = {} } = {}) { // ... const client = this.getClient(); try { - const response = await client[method](args, otherOptions); + const response = await client[method](request);
175-177
: Avoid throwing whenerror.response
is undefinedYour error handling in
makeRequest
will crash when network errors occur becauseerror.response
may be undefined for network failures or timeouts, causingJSON.stringify(error.response.data)
to throw another error.} catch (error) { console.error("Error making request", error.response); - throw new Error(JSON.stringify(error.response.data, null, 2)); + const safeMsg = error.response?.data + ? JSON.stringify(error.response.data, null, 2) + : error.message; + throw new Error(safeMsg); }
129-144
: Add validation for required authentication parametersThe
getConf()
method extracts credentials from$auth
but doesn't validate their presence, which could lead to cryptic errors later if any required values are missing.getConf() { const { client_id: clientId, client_secret: clientSecret, environment: basePath, } = this.$auth; + + // Validate required auth parameters + if (!clientId || !clientSecret || !basePath) { + throw new Error("Missing required authentication parameters. Please check your credentials."); + } return new Configuration({ basePath, baseOptions: { headers: { "PLAID-CLIENT-ID": clientId, "PLAID-SECRET": clientSecret, }, }, }); }
169-173
: Improve error handling for non-200 responsesYour error handling for non-200 responses logs the full response object but the error message only includes minimal information. Including more error details would make debugging easier.
if (response.status !== 200) { console.log("Status error", response); - throw new Error(`Status error: ${response.status} ${response.statusText}`); + const errorData = response.data ? JSON.stringify(response.data, null, 2) : ''; + throw new Error(`Status error: ${response.status} ${response.statusText}${errorData ? `\nDetails: ${errorData}` : ''}`); }
180-185
: Refactor wrapper methods to match updated makeRequest signatureAfter fixing the
makeRequest
method signature as suggested above, all these wrapper methods need updating to pass parameters correctly.createSandboxPublicToken(args = {}) { return this.makeRequest({ method: "sandboxPublicTokenCreate", - ...args, + request: args, }); }This change pattern should be applied to all wrapper methods:
exchangePublicToken
getAccountsBalance
getTransactions
getAccounts
createLinkToken
getLinkToken
createUser
Also applies to: 186-191, 192-197, 198-203, 204-209, 210-215, 216-221, 222-227
247-247
: Add safety check for utils.getNestedPropertyThe call to
utils.getNestedProperty(response, resourceName)
could fail if the response is undefined or not an object.- const nextResources = utils.getNestedProperty(response, resourceName); + const nextResources = response ? utils.getNestedProperty(response, resourceName) : [];
🧹 Nitpick comments (3)
components/plaid/plaid.app.mjs (3)
249-252
: Improve early return condition clarity for empty resourcesThe condition to check for empty resources is redundant with the null-check - they can be combined for clearer code.
- const nextResources = utils.getNestedProperty(response, resourceName); - - if (!nextResources?.length) { + const nextResources = response ? utils.getNestedProperty(response, resourceName) : []; + + if (!nextResources || nextResources.length === 0) { console.log("No more resources found"); return; }
254-267
: Add resource date validation and improve variable namingThe date comparison logic using
isDateGreater
could be improved with better variable naming and type checking to prevent potential issues.for (const resource of nextResources) { - const isDateGreater = - lastDateAt - && Date.parse(resource[dateField]) >= Date.parse(lastDateAt); + // Skip resources with invalid dates if filtering by date + if (lastDateAt && !resource[dateField]) { + continue; + } + + const resourceDate = resource[dateField] ? Date.parse(resource[dateField]) : null; + const filterDate = lastDateAt ? Date.parse(lastDateAt) : null; + const isAfterFilterDate = resourceDate && filterDate && resourceDate >= filterDate; - if (!lastDateAt || isDateGreater) { + if (!lastDateAt || isAfterFilterDate) { yield resource; resourcesCount += 1; } if (resourcesCount >= max) { console.log("Reached max resources"); return; } }
255-257
: Improve date parsing logic in getIterationsThe date parsing logic in
getIterations
will throw for invalid dates and doesn't handle timezone issues, which can lead to inconsistent filtering.- const isDateGreater = - lastDateAt - && Date.parse(resource[dateField]) >= Date.parse(lastDateAt); + const parseDate = (dateStr) => { + try { + return dateStr ? new Date(dateStr).getTime() : null; + } catch (e) { + console.warn(`Invalid date format: ${dateStr}`); + return null; + } + }; + + const resourceDate = parseDate(resource[dateField]); + const cutoffDate = parseDate(lastDateAt); + + const isDateGreater = lastDateAt && resourceDate && cutoffDate && resourceDate >= cutoffDate;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (18)
components/plaid/actions/create-access-token/create-access-token.mjs
(1 hunks)components/plaid/actions/create-sandbox-public-token/create-sandbox-public-token.mjs
(1 hunks)components/plaid/actions/create-user/create-user.mjs
(1 hunks)components/plaid/actions/get-real-time-balance/get-real-time-balance.mjs
(1 hunks)components/plaid/actions/get-transactions/get-transactions.mjs
(1 hunks)components/plaid/common/constants.mjs
(1 hunks)components/plaid/common/sandbox-institutions.mjs
(1 hunks)components/plaid/common/utils.mjs
(1 hunks)components/plaid/package.json
(2 hunks)components/plaid/plaid.app.mjs
(1 hunks)components/plaid/sources/common/events.mjs
(1 hunks)components/plaid/sources/common/webhook.mjs
(1 hunks)components/plaid/sources/new-accounts-available-instant/new-accounts-available-instant.mjs
(1 hunks)components/plaid/sources/new-accounts-available-instant/test-event.mjs
(1 hunks)components/plaid/sources/new-event-instant/new-event-instant.mjs
(1 hunks)components/plaid/sources/new-event-instant/test-event.mjs
(1 hunks)components/plaid/sources/sync-updates-available-instant/sync-updates-available-instant.mjs
(1 hunks)components/plaid/sources/sync-updates-available-instant/test-event.mjs
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (17)
- components/plaid/sources/sync-updates-available-instant/test-event.mjs
- components/plaid/common/constants.mjs
- components/plaid/package.json
- components/plaid/sources/new-accounts-available-instant/test-event.mjs
- components/plaid/sources/new-event-instant/test-event.mjs
- components/plaid/sources/common/events.mjs
- components/plaid/common/sandbox-institutions.mjs
- components/plaid/actions/get-transactions/get-transactions.mjs
- components/plaid/common/utils.mjs
- components/plaid/actions/create-user/create-user.mjs
- components/plaid/actions/get-real-time-balance/get-real-time-balance.mjs
- components/plaid/actions/create-access-token/create-access-token.mjs
- components/plaid/actions/create-sandbox-public-token/create-sandbox-public-token.mjs
- components/plaid/sources/sync-updates-available-instant/sync-updates-available-instant.mjs
- components/plaid/sources/new-event-instant/new-event-instant.mjs
- components/plaid/sources/new-accounts-available-instant/new-accounts-available-instant.mjs
- components/plaid/sources/common/webhook.mjs
⏰ Context from checks skipped due to timeout of 90000ms (4)
- GitHub Check: Verify TypeScript components
- GitHub Check: pnpm publish
- GitHub Check: Publish TypeScript components
- GitHub Check: Lint Code Base
async *getIterations({ | ||
resourcesFn, resourcesFnArgs, resourceName, | ||
lastDateAt, dateField, | ||
max = constants.DEFAULT_MAX, | ||
}) { | ||
let offset = 0; | ||
let resourcesCount = 0; | ||
|
||
while (true) { | ||
const response = | ||
await resourcesFn({ | ||
...resourcesFnArgs, | ||
options: { | ||
...resourcesFnArgs?.options, | ||
count: constants.DEFAULT_LIMIT, | ||
offset, | ||
}, | ||
}); | ||
|
||
const nextResources = utils.getNestedProperty(response, resourceName); | ||
|
||
if (!nextResources?.length) { | ||
console.log("No more resources found"); | ||
return; | ||
} | ||
|
||
for (const resource of nextResources) { | ||
const isDateGreater = | ||
lastDateAt | ||
&& Date.parse(resource[dateField]) >= Date.parse(lastDateAt); | ||
|
||
if (!lastDateAt || isDateGreater) { | ||
yield resource; | ||
resourcesCount += 1; | ||
} | ||
|
||
if (resourcesCount >= max) { | ||
console.log("Reached max resources"); | ||
return; | ||
} | ||
} | ||
|
||
if (nextResources.length < constants.DEFAULT_LIMIT) { | ||
console.log("No next page found"); | ||
return; | ||
} | ||
|
||
offset += constants.DEFAULT_LIMIT; | ||
} | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling to getIterations method
The getIterations
method makes API calls but doesn't include try/catch blocks, which could cause the iterator to fail without providing clear error messages.
Add error handling to the getIterations
method:
async *getIterations({
resourcesFn, resourcesFnArgs, resourceName,
lastDateAt, dateField,
max = constants.DEFAULT_MAX,
}) {
let offset = 0;
let resourcesCount = 0;
while (true) {
+ try {
const response =
await resourcesFn({
...resourcesFnArgs,
options: {
...resourcesFnArgs?.options,
count: constants.DEFAULT_LIMIT,
offset,
},
});
const nextResources = response ? utils.getNestedProperty(response, resourceName) : [];
if (!nextResources?.length) {
console.log("No more resources found");
return;
}
for (const resource of nextResources) {
// ... (existing code)
}
if (nextResources.length < constants.DEFAULT_LIMIT) {
console.log("No next page found");
return;
}
offset += constants.DEFAULT_LIMIT;
+ } catch (error) {
+ console.error("Error in getIterations:", error);
+ throw new Error(`Failed to iterate resources: ${error.message}`);
+ }
}
}
📝 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.
async *getIterations({ | |
resourcesFn, resourcesFnArgs, resourceName, | |
lastDateAt, dateField, | |
max = constants.DEFAULT_MAX, | |
}) { | |
let offset = 0; | |
let resourcesCount = 0; | |
while (true) { | |
const response = | |
await resourcesFn({ | |
...resourcesFnArgs, | |
options: { | |
...resourcesFnArgs?.options, | |
count: constants.DEFAULT_LIMIT, | |
offset, | |
}, | |
}); | |
const nextResources = utils.getNestedProperty(response, resourceName); | |
if (!nextResources?.length) { | |
console.log("No more resources found"); | |
return; | |
} | |
for (const resource of nextResources) { | |
const isDateGreater = | |
lastDateAt | |
&& Date.parse(resource[dateField]) >= Date.parse(lastDateAt); | |
if (!lastDateAt || isDateGreater) { | |
yield resource; | |
resourcesCount += 1; | |
} | |
if (resourcesCount >= max) { | |
console.log("Reached max resources"); | |
return; | |
} | |
} | |
if (nextResources.length < constants.DEFAULT_LIMIT) { | |
console.log("No next page found"); | |
return; | |
} | |
offset += constants.DEFAULT_LIMIT; | |
} | |
}, | |
async *getIterations({ | |
resourcesFn, resourcesFnArgs, resourceName, | |
lastDateAt, dateField, | |
max = constants.DEFAULT_MAX, | |
}) { | |
let offset = 0; | |
let resourcesCount = 0; | |
while (true) { | |
try { | |
const response = | |
await resourcesFn({ | |
...resourcesFnArgs, | |
options: { | |
...resourcesFnArgs?.options, | |
count: constants.DEFAULT_LIMIT, | |
offset, | |
}, | |
}); | |
const nextResources = response | |
? utils.getNestedProperty(response, resourceName) | |
: []; | |
if (!nextResources?.length) { | |
console.log("No more resources found"); | |
return; | |
} | |
for (const resource of nextResources) { | |
const isDateGreater = | |
lastDateAt && | |
Date.parse(resource[dateField]) >= Date.parse(lastDateAt); | |
if (!lastDateAt || isDateGreater) { | |
yield resource; | |
resourcesCount += 1; | |
} | |
if (resourcesCount >= max) { | |
console.log("Reached max resources"); | |
return; | |
} | |
} | |
if (nextResources.length < constants.DEFAULT_LIMIT) { | |
console.log("No next page found"); | |
return; | |
} | |
offset += constants.DEFAULT_LIMIT; | |
} catch (error) { | |
console.error("Error in getIterations:", error); | |
throw new Error(`Failed to iterate resources: ${error.message}`); | |
} | |
} | |
}, |
async options({ accessToken }) { | ||
if (!accessToken) { | ||
return []; | ||
} | ||
|
||
const { accounts } = await this.getAccounts({ | ||
access_token: accessToken, | ||
}); | ||
return accounts.map(({ | ||
account_id: value, name: label, | ||
}) => ({ | ||
label, | ||
value, | ||
})); | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling to account options function
The async options
function in accountId
prop definition makes an API call but doesn't include try/catch for error handling, which could silently fail and confuse users.
async options({ accessToken }) {
if (!accessToken) {
return [];
}
+ try {
const { accounts } = await this.getAccounts({
access_token: accessToken,
});
return accounts.map(({
account_id: value, name: label,
}) => ({
label,
value,
}));
+ } catch (error) {
+ console.error("Error fetching account options:", error);
+ return [];
+ }
},
📝 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.
async options({ accessToken }) { | |
if (!accessToken) { | |
return []; | |
} | |
const { accounts } = await this.getAccounts({ | |
access_token: accessToken, | |
}); | |
return accounts.map(({ | |
account_id: value, name: label, | |
}) => ({ | |
label, | |
value, | |
})); | |
}, | |
async options({ accessToken }) { | |
if (!accessToken) { | |
return []; | |
} | |
try { | |
const { accounts } = await this.getAccounts({ | |
access_token: accessToken, | |
}); | |
return accounts.map(({ | |
account_id: value, name: label, | |
}) => ({ | |
label, | |
value, | |
})); | |
} catch (error) { | |
console.error("Error fetching account options:", error); | |
return []; | |
} | |
}, |
async makeRequest({ | ||
method, debug = false, otherOptions, ...args | ||
} = {}) { | ||
if (debug) { | ||
console.log("Request args", args); | ||
console.log("Request otherOptions", otherOptions); | ||
console.log("Request method", method); | ||
} | ||
|
||
if (!method) { | ||
throw new Error("Method is required"); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve makeRequest parameter handling
The makeRequest
method uses both destructured parameters and rest parameters, which makes the signature confusing. Also, the debug
parameter is used for logging but there's no way to set it to true from the wrapper methods.
async makeRequest({
- method, debug = false, otherOptions, ...args
+ method,
+ request = {},
+ options = {},
+ debug = false
} = {}) {
if (debug) {
- console.log("Request args", args);
- console.log("Request otherOptions", otherOptions);
+ console.log("Request payload", request);
+ console.log("Request options", options);
console.log("Request method", method);
}
if (!method) {
throw new Error("Method is required");
}
📝 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.
async makeRequest({ | |
method, debug = false, otherOptions, ...args | |
} = {}) { | |
if (debug) { | |
console.log("Request args", args); | |
console.log("Request otherOptions", otherOptions); | |
console.log("Request method", method); | |
} | |
if (!method) { | |
throw new Error("Method is required"); | |
} | |
async makeRequest({ | |
method, | |
request = {}, | |
options = {}, | |
debug = false | |
} = {}) { | |
if (debug) { | |
console.log("Request payload", request); | |
console.log("Request options", options); | |
console.log("Request method", method); | |
} | |
if (!method) { | |
throw new Error("Method is required"); | |
} | |
// …rest of method… | |
} |
WHY
Resolves #15144
Summary by CodeRabbit
New Features
Chores