-
Notifications
You must be signed in to change notification settings - Fork 2
✨ server: add queue on user creation #619
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
base: main
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: d21e7e7 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
WalkthroughAdds a BullMQ-backed Alchemy job queue and worker, moves Alchemy webhook subscriber updates to queued jobs, introduces WebhookNotReadyError and retry handling in auth flows, integrates worker lifecycle into server startup/shutdown, and adds tests and test mocks. Changes
Sequence DiagramsequenceDiagram
participant Client
participant AuthServer as Auth Server
participant Queue as Job Queue (BullMQ)
participant Redis
participant Worker as Queue Worker
participant AlchemyAPI as Alchemy API
Client->>AuthServer: POST /register (webauthn)
AuthServer->>AuthServer: createCredential(credentialId, options)
AuthServer->>Queue: add(ADD_SUBSCRIBER, { account, webhookId })
Queue->>Redis: persist job
Queue-->>AuthServer: job enqueued
AuthServer-->>Client: 200 OK
Worker->>Redis: fetch job
Worker->>Worker: startSpan("alchemy.processor")
Worker->>AlchemyAPI: PATCH /v2/.../subscribe (X-Alchemy-Token)
alt API Success
AlchemyAPI-->>Worker: 200 OK
Worker->>Worker: addBreadcrumb
Worker->>Redis: mark completed
else API Failure
AlchemyAPI-->>Worker: non-OK
Worker->>Worker: captureException (with job data)
Worker->>Redis: mark failed
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
Summary of ChangesHello @aguxez, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a robust queueing system for handling Alchemy webhook subscription updates. By integrating BullMQ, the application now processes these updates asynchronously, moving the task from the main request-response cycle to a background worker. This change enhances the system's reliability and performance by preventing blocking operations and providing built-in retry capabilities for external API calls, particularly when a new user credential is created. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
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.
Code Review
This pull request introduces a background job queue using BullMQ and Redis to handle Alchemy webhook subscription updates upon user creation. This change refactors the createCredential function to offload the synchronous API call to Alchemy into an asynchronous job. This improves the endpoint's response time and adds resilience through automatic retries on failure. The changes include new files for the queue, worker, and constants, along with corresponding tests. My review provides suggestions to improve the robustness of the queue worker, enhance test coverage for error scenarios, and increase code conciseness.
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: 6
Fix all issues with AI Agents 🤖
In @server/queues/alchemyQueue.ts:
- Line 61: Replace the direct process.env access in the conditional that checks
DISABLE_WORKERS inside alchemyQueue.ts: instead accept a configuration value
(e.g., a boolean like disableWorkers) injected from startup config and use that
variable in the if check instead of process.env.DISABLE_WORKERS; modify the
module API (exported initializer or constructor function) to receive the config
from the app bootstrap where env is loaded once, and update any call sites to
pass the injected config value. Ensure the unique symbol referenced is the
conditional that currently uses process.env.DISABLE_WORKERS and the module's
public initialization function (or top-level export) is updated to accept the
config.
- Around line 18-23: The code directly reads process.env to build the Redis
connection (the const connection) and uses Number() for the port; change this to
accept a Redis config object injected at module initialization (e.g., add a
factory function like createAlchemyQueue(redisConfig) or export a setter that
receives redisConfig) and construct the connection from that injected config
instead of process.env, and parse the port with Number.parseInt(redisConfig.port
?? "6379", 10) rather than Number(); update all uses of the existing connection
constant to obtain it from the factory/initializer you add.
- Around line 38-57: The span.setStatus call in processor currently uses the
magic number 2; replace this with a named Sentry status constant (e.g.,
SpanStatus or Sentry.SpanStatus/SpanStatusType) to improve clarity: import the
appropriate enum from the Sentry package used in the project and call
span.setStatus({ code: SpanStatus.InternalError }) or the equivalent named
member instead of using the literal 2; update the import and the span.setStatus
invocation in the processor function and ensure any type references (Span,
startSpan) remain compatible.
In @server/test/queues/alchemyQueue.test.ts:
- Around line 39-66: The test contains an unnecessary type assertion on the
headers expectation; in the AlchemyQueue test where you call processor and
assert fetch was called, remove the "as unknown as Record<string, string>" cast
on the expect.objectContaining({ "X-Alchemy-Token": "mock-token" }) expression
so the matcher is used directly; keep the rest of the
expect.objectContaining(...) structure intact and run the tests to ensure
type/compile issues are resolved.
In @server/test/utils/createCredential.test.ts:
- Around line 59-71: Remove the unnecessary type assertion on the
expect.stringMatching call in the test: in the "should add a job to alchemyQueue
when credential is created" case, update the assertion that checks
alchemyQueue.add was called with AlchemyJob.ADD_SUBSCRIBER so the account
property uses expect.stringMatching(/^0x/) directly (remove the "as unknown as
string" cast); this change touches the test around createCredential,
mockContext, credentialId and the alchemyQueue.add expectation.
In @server/utils/createCredential.ts:
- Around line 54-56: The captureException call for the
alchemyQueue.add(AlchemyJob.ADD_SUBSCRIBER, { account, webhookId }) promise
rejection should include the same error-level metadata used elsewhere; update
the anonymous catch handler to call captureException(error, { level: "error" })
so the AlchemyJob.ADD_SUBSCRIBER error is logged with a consistent severity
level like the Sardine capture.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (7)
.changeset/short-cars-return.mdserver/queues/alchemyQueue.tsserver/queues/constants.tsserver/script/openapi.tsserver/test/queues/alchemyQueue.test.tsserver/test/utils/createCredential.test.tsserver/utils/createCredential.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/.changeset/*.md
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use a lowercase sentence in the imperative present tense for changeset summaries
Files:
.changeset/short-cars-return.md
server/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/**/*.ts: Usec.varobject to pass strongly-typed data between Hono middleware and route handlers; do not usec.set
All request validation (headers, body, params) must be handled by@hono/valibot-validatormiddleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwingnew HTTPException()for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLintplugin:n/recommendedconfiguration
Enforce Drizzle ORM best practices using ESLintplugin:drizzle/allconfiguration, including requiringwhereclauses forupdateanddeleteoperations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not accessprocess.envdirectly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; useasync/awaitcorrectly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Files:
server/queues/alchemyQueue.tsserver/queues/constants.tsserver/test/utils/createCredential.test.tsserver/utils/createCredential.tsserver/test/queues/alchemyQueue.test.tsserver/script/openapi.ts
**/*.{js,ts,tsx,jsx,sol}
📄 CodeRabbit inference engine (AGENTS.md)
Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No
anytype.
Files:
server/queues/alchemyQueue.tsserver/queues/constants.tsserver/test/utils/createCredential.test.tsserver/utils/createCredential.tsserver/test/queues/alchemyQueue.test.tsserver/script/openapi.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Omit redundant type names in variable declarations - let the type system explain itself
**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas usingtype User = v.Input<typeof UserSchema>instead of manually defining interfaces
Files:
server/queues/alchemyQueue.tsserver/queues/constants.tsserver/test/utils/createCredential.test.tsserver/utils/createCredential.tsserver/test/queues/alchemyQueue.test.tsserver/script/openapi.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Preferconstfor all variable declarations by default; only useletif the variable's value will be reassigned
Declare each variable on its own line with its ownconstorletkeyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always useimport type { ... }for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), andfor...ofloops over traditional syntax
Do not use abbreviations or cryptic names; write out full words likeerror,parameters,requestinstead oferr,params,req
UseNumber.parseInt()instead of the globalparseInt()function when parsing numbers
All classes called withnewmust use PascalCase
UseBuffer.from(),Buffer.alloc(), orBuffer.allocUnsafe()instead of the deprecatednew Buffer()
Use@ts-expect-errorinstead of@ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like-or:
Do not include the type in a variable's name; let the static type system do its job (e.g., useconst user: Usernotconst userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...
Files:
server/queues/alchemyQueue.tsserver/queues/constants.tsserver/test/utils/createCredential.test.tsserver/utils/createCredential.tsserver/test/queues/alchemyQueue.test.tsserver/script/openapi.ts
server/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - usec.var.db
Files:
server/queues/alchemyQueue.tsserver/queues/constants.tsserver/test/utils/createCredential.test.tsserver/utils/createCredential.tsserver/test/queues/alchemyQueue.test.tsserver/script/openapi.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
For files with a single
defaultexport, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word
Files:
server/queues/alchemyQueue.tsserver/queues/constants.tsserver/test/utils/createCredential.test.tsserver/utils/createCredential.tsserver/test/queues/alchemyQueue.test.tsserver/script/openapi.ts
🧠 Learnings (4)
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.
Applied to files:
.changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Applied to files:
server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services
Applied to files:
server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres
Applied to files:
server/script/openapi.ts
🧬 Code graph analysis (4)
server/queues/alchemyQueue.ts (1)
server/queues/constants.ts (2)
QueueName(1-3)AlchemyJob(7-9)
server/test/utils/createCredential.test.ts (3)
server/utils/createCredential.ts (1)
createCredential(24-63)server/queues/alchemyQueue.ts (1)
alchemyQueue(30-30)server/queues/constants.ts (1)
AlchemyJob(7-9)
server/utils/createCredential.ts (2)
server/queues/alchemyQueue.ts (1)
alchemyQueue(30-30)server/queues/constants.ts (1)
AlchemyJob(7-9)
server/test/queues/alchemyQueue.test.ts (2)
server/queues/constants.ts (1)
AlchemyJob(7-9)server/queues/alchemyQueue.ts (2)
AlchemyJobData(11-16)processor(38-57)
🔇 Additional comments (11)
server/script/openapi.ts (1)
25-26: LGTM: Environment configuration updated for queue support.The Redis URL format and worker disabling flag are appropriate for the OpenAPI generation script context. These changes align with the new queue infrastructure while preventing worker initialization during spec generation.
.changeset/short-cars-return.md (1)
1-5: LGTM: Changeset follows conventions.The changeset format and description are correct, using lowercase imperative present tense as required by coding guidelines.
server/test/utils/createCredential.test.ts (2)
9-50: LGTM: Comprehensive mock setup.The mocks appropriately cover all dependencies required for testing the queue integration, including database, Sentry, cookies, and external services.
73-81: LGTM: Error handling properly tested.The test correctly validates that queue failures are captured via Sentry, ensuring observability of background job issues.
server/utils/createCredential.ts (1)
1-22: LGTM: Imports properly organized.The import changes correctly add queue dependencies and organize external libraries before relative paths as required by coding guidelines.
server/test/queues/alchemyQueue.test.ts (2)
8-32: LGTM: Mock setup properly simulates dependencies.The mocks correctly simulate Alchemy API headers, webhook IDs, Sentry tracing, and HTTP responses needed for processor testing.
68-77: LGTM: Unknown job handling properly tested.The test correctly validates that unknown job names are gracefully ignored while maintaining observability through Sentry spans.
server/queues/constants.ts (1)
1-11: LGTM! Well-structured constants and types.The use of
as constwith derived type aliases provides excellent type safety for queue and job identifiers throughout the application. The naming conventions are clear and consistent.server/queues/alchemyQueue.ts (3)
8-16: LGTM! Clear interface definition.The JSDoc comments follow the lowercase convention and provide helpful context for the data structure.
25-30: Queue setup looks good once connection configuration is addressed.The queue initialization is properly structured. The comment effectively explains the purpose of the queue.
62-87: Excellent Worker configuration and observability.The Worker setup demonstrates good practices:
- Rate limiting with reasonable defaults
- Comprehensive error handling with Sentry integration
- Breadcrumbs on lifecycle events for debugging
32c8a33 to
0dc7771
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: 3
🤖 Fix all issues with AI Agents
In @server/queues/alchemyQueue.ts:
- Around line 10-14: The code directly reads process.env to set disableWorkers
and Redis configuration; refactor to remove direct env access by creating a
factory initializer (e.g., createAlchemyQueue) that accepts an
AlchemyQueueConfig containing disableWorkers and a redis config object, use that
config to construct the Redis connection or host/port and to instantiate the
Queue (QueueName.ALCHEMY) and worker conditional logic, and update all places
using the disableWorkers variable or inline Redis creation to use the injected
config instead so configuration is loaded once at startup and passed in.
In @server/test/queues/alchemyQueue.test.ts:
- Line 54: Remove the redundant type assertion on the headers expectation in the
test: replace the line using headers: expect.objectContaining({
"X-Alchemy-Token": "mock-token" }) as Record<string, string> with headers:
expect.objectContaining({ "X-Alchemy-Token": "mock-token" }) (i.e., delete the
"as Record<string, string>" cast) so the matcher types from
expect.objectContaining() are used directly.
- Around line 68-77: The test should be updated to expect the processor to throw
for unknown job names (the processor's default path throws Error("Unknown job
name: unknown")). Replace the current await processor(job) with await
expect(processor(job)).rejects.toThrow("Unknown job name: unknown"); and keep
the existing assertions that startSpan was called with expect.objectContaining({
name: "alchemy.processor", op: "queue.process" }) and that fetch was not called,
so the test aligns with the processor function's behavior.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (7)
.changeset/short-cars-return.mdserver/queues/alchemyQueue.tsserver/queues/constants.tsserver/script/openapi.tsserver/test/queues/alchemyQueue.test.tsserver/test/utils/createCredential.test.tsserver/utils/createCredential.ts
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/**/*.ts: Usec.varobject to pass strongly-typed data between Hono middleware and route handlers; do not usec.set
All request validation (headers, body, params) must be handled by@hono/valibot-validatormiddleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwingnew HTTPException()for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLintplugin:n/recommendedconfiguration
Enforce Drizzle ORM best practices using ESLintplugin:drizzle/allconfiguration, including requiringwhereclauses forupdateanddeleteoperations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not accessprocess.envdirectly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; useasync/awaitcorrectly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Files:
server/test/utils/createCredential.test.tsserver/queues/constants.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.tsserver/script/openapi.tsserver/test/queues/alchemyQueue.test.ts
**/*.{js,ts,tsx,jsx,sol}
📄 CodeRabbit inference engine (AGENTS.md)
Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No
anytype.
Files:
server/test/utils/createCredential.test.tsserver/queues/constants.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.tsserver/script/openapi.tsserver/test/queues/alchemyQueue.test.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Omit redundant type names in variable declarations - let the type system explain itself
**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas usingtype User = v.Input<typeof UserSchema>instead of manually defining interfaces
Files:
server/test/utils/createCredential.test.tsserver/queues/constants.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.tsserver/script/openapi.tsserver/test/queues/alchemyQueue.test.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Preferconstfor all variable declarations by default; only useletif the variable's value will be reassigned
Declare each variable on its own line with its ownconstorletkeyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always useimport type { ... }for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), andfor...ofloops over traditional syntax
Do not use abbreviations or cryptic names; write out full words likeerror,parameters,requestinstead oferr,params,req
UseNumber.parseInt()instead of the globalparseInt()function when parsing numbers
All classes called withnewmust use PascalCase
UseBuffer.from(),Buffer.alloc(), orBuffer.allocUnsafe()instead of the deprecatednew Buffer()
Use@ts-expect-errorinstead of@ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like-or:
Do not include the type in a variable's name; let the static type system do its job (e.g., useconst user: Usernotconst userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...
Files:
server/test/utils/createCredential.test.tsserver/queues/constants.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.tsserver/script/openapi.tsserver/test/queues/alchemyQueue.test.ts
server/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - usec.var.db
Files:
server/test/utils/createCredential.test.tsserver/queues/constants.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.tsserver/script/openapi.tsserver/test/queues/alchemyQueue.test.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
For files with a single
defaultexport, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word
Files:
server/test/utils/createCredential.test.tsserver/queues/constants.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.tsserver/script/openapi.tsserver/test/queues/alchemyQueue.test.ts
**/.changeset/*.md
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use a lowercase sentence in the imperative present tense for changeset summaries
Files:
.changeset/short-cars-return.md
🧠 Learnings (6)
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Applied to files:
server/queues/alchemyQueue.tsserver/script/openapi.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Applied to files:
server/queues/alchemyQueue.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`
Applied to files:
server/utils/createCredential.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services
Applied to files:
server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres
Applied to files:
server/script/openapi.ts
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.
Applied to files:
.changeset/short-cars-return.md
🧬 Code graph analysis (3)
server/queues/alchemyQueue.ts (1)
server/queues/constants.ts (2)
QueueName(1-3)AlchemyJob(7-9)
server/utils/createCredential.ts (2)
server/queues/alchemyQueue.ts (1)
alchemyQueue(38-38)server/queues/constants.ts (1)
AlchemyJob(7-9)
server/test/queues/alchemyQueue.test.ts (2)
server/queues/constants.ts (1)
AlchemyJob(7-9)server/queues/alchemyQueue.ts (2)
AlchemyJobData(19-24)processor(46-73)
🪛 GitHub Actions: test
server/queues/alchemyQueue.ts
[error] 68-68: Unknown job name: unknown. Step: nx run @exactly/server:test:vi (vitest run).
server/test/queues/alchemyQueue.test.ts
[error] 1-1: getaddrinfo EAI_AGAIN redis (DNS resolution failure for Redis) during tests. Step: nx run @exactly/server:test:vi (vitest run).
🪛 GitHub Check: test
server/queues/alchemyQueue.ts
[failure] 68-68: test/queues/alchemyQueue.test.ts > alchemyQueue worker processor > should ignore unknown job names
Error: Unknown job name: unknown
❯ queues/alchemyQueue.ts:68:17
❯ test/queues/alchemyQueue.test.ts:22:7
❯ Module.processor queues/alchemyQueue.ts:47:10
❯ test/queues/alchemyQueue.test.ts:70:11
🔇 Additional comments (11)
server/script/openapi.ts (1)
25-26: LGTM! Environment configuration updated appropriately.The REDIS_URL now uses a proper connection string format, and the DISABLE_WORKERS flag aligns with the queue worker system introduced in this PR.
.changeset/short-cars-return.md (1)
1-5: LGTM! Changeset follows guidelines.The changeset correctly uses a patch-level bump and the description follows the required format: lowercase imperative present tense.
server/test/utils/createCredential.test.ts (2)
9-50: LGTM! Comprehensive mock setup.The test properly mocks all external dependencies including the queue, database, Sentry, and network calls. The use of
{ spy: true }for Sentry allows verification of captureException calls while preserving the module structure.
73-82: LGTM! Error handling test is correct.The test properly validates that exceptions from queue operations are captured by Sentry, ensuring observability of background job failures.
server/utils/createCredential.ts (2)
1-22: LGTM! Imports updated for queue-based architecture.The import changes correctly separate Sentry concerns (
setUserfrom core,captureExceptionfrom node) and introduce the necessary queue infrastructure. All imports use relative paths as required.
24-61: LGTM! Queue-based architecture improves reliability.The refactor from direct HTTP calls to background job enqueuing is well-implemented. Using
Promise.allfor parallel execution of non-blocking operations is appropriate, and the queue approach enables retry logic and better observability for webhook subscriptions.server/queues/constants.ts (1)
1-11: LGTM! Clean constant definitions.The const objects with
as constand derived type unions provide a type-safe, enum-like pattern for queue and job names. The structure is clear and easily extensible for future queues or job types.server/test/queues/alchemyQueue.test.ts (2)
39-66: Good test coverage for the happy path.The test correctly validates the fetch call payload, HTTP method, headers, and Sentry span invocation for the
ADD_SUBSCRIBERjob.
79-95: Good coverage for API failure scenario.This test validates that non-OK responses from the Alchemy API result in proper error propagation with the expected message format.
server/queues/alchemyQueue.ts (2)
46-73: Processor implementation is well-structured.The switch statement with a
defaultcase that throws for unknown job names is a robust pattern. The Sentry span wrapping provides good observability. The inline comment/* this is an error code */clarifies the magic number.
77-102: Worker initialization with lifecycle handlers looks good.The event handlers for
failed,completed, andactiveprovide appropriate observability via Sentry breadcrumbs and exception capture. The limiter configuration (max: 10, duration: 1000) provides rate limiting.One minor note: the
new Worker(...)result is discarded. If you need to gracefully shut down the worker during application termination, consider storing a reference.
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: 5
🤖 Fix all issues with AI Agents
In @server/queues/alchemyQueue.ts:
- Around line 65-69: Update the test "should ignore unknown job names" to assert
that an error is thrown instead of expecting silent ignore: call the function
that processes jobs with an unknown job.name and change the assertion to expect
a rejection/throw (e.g., use expect(...).rejects.toThrow or expect(() =>
...).toThrow) for the error message matching `Unknown job name: ${job.name}` (or
assert span.setStatus was called with code 2 and the same message) so the test
aligns with the implementation that throws the Error constructed in the default
branch (errorMessage) and calls span.setStatus.
- Around line 78-102: The Worker instance created for QueueName.ALCHEMY is not
stored, preventing graceful shutdown; assign the new Worker(...) to a variable
(e.g., alchemyWorker) where it's instantiated (the call using QueueName.ALCHEMY,
processor, connection, limiter) and export or register that reference so
shutdown logic can call alchemyWorker?.close() (or await alchemyWorker.close())
during application termination to allow in-flight jobs to finish.
In @server/test/queues/alchemyQueue.test.ts:
- Around line 68-77: The test incorrectly assumes processor(job) resolves for
unknown job names; instead replace the direct await call with a Jest rejection
assertion: call await expect(processor(job)).rejects.toThrow() (keeping the
subsequent assertions about startSpan and fetch if still relevant) so the test
expects the processor to throw on an unknown job name; reference the processor
function, the job variable (name: "unknown"), startSpan and fetch in the updated
expectation.
- Around line 39-66: Remove the unnecessary type assertion on the headers
expectation in the test: in the "should call Alchemy API to update webhook
addresses" test where you call expect(fetch).toHaveBeenCalledWith(...), delete
the trailing "as Record<string, string>" after expect.objectContaining({
"X-Alchemy-Token": "mock-token" }) so the matcher reads
expect.objectContaining({ headers: expect.objectContaining({ "X-Alchemy-Token":
"mock-token" }) }), leaving the rest of the assertion (Job<AlchemyJobData>,
AlchemyJob.ADD_SUBSCRIBER, processor, and startSpan expectations) unchanged.
In @server/test/utils/createCredential.test.ts:
- Around line 59-71: Remove the unnecessary type assertion on the
expect.stringMatching result in the test for createCredential: modify the
assertion in the alchemyQueue.add expectation to use
expect.stringMatching(/^0x/) without "as string" (target the call to
alchemyQueue.add in the "should add a job to alchemyQueue when credential is
created" test that asserts AlchemyJob.ADD_SUBSCRIBER and objectContaining {
account: expect.stringMatching(/^0x/), webhookId: "webhook-id" }).
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (7)
.changeset/short-cars-return.mdserver/queues/alchemyQueue.tsserver/queues/constants.tsserver/script/openapi.tsserver/test/queues/alchemyQueue.test.tsserver/test/utils/createCredential.test.tsserver/utils/createCredential.ts
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/**/*.ts: Usec.varobject to pass strongly-typed data between Hono middleware and route handlers; do not usec.set
All request validation (headers, body, params) must be handled by@hono/valibot-validatormiddleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwingnew HTTPException()for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLintplugin:n/recommendedconfiguration
Enforce Drizzle ORM best practices using ESLintplugin:drizzle/allconfiguration, including requiringwhereclauses forupdateanddeleteoperations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not accessprocess.envdirectly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; useasync/awaitcorrectly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Files:
server/test/queues/alchemyQueue.test.tsserver/queues/constants.tsserver/test/utils/createCredential.test.tsserver/utils/createCredential.tsserver/queues/alchemyQueue.tsserver/script/openapi.ts
**/*.{js,ts,tsx,jsx,sol}
📄 CodeRabbit inference engine (AGENTS.md)
Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No
anytype.
Files:
server/test/queues/alchemyQueue.test.tsserver/queues/constants.tsserver/test/utils/createCredential.test.tsserver/utils/createCredential.tsserver/queues/alchemyQueue.tsserver/script/openapi.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Omit redundant type names in variable declarations - let the type system explain itself
**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas usingtype User = v.Input<typeof UserSchema>instead of manually defining interfaces
Files:
server/test/queues/alchemyQueue.test.tsserver/queues/constants.tsserver/test/utils/createCredential.test.tsserver/utils/createCredential.tsserver/queues/alchemyQueue.tsserver/script/openapi.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Preferconstfor all variable declarations by default; only useletif the variable's value will be reassigned
Declare each variable on its own line with its ownconstorletkeyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always useimport type { ... }for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), andfor...ofloops over traditional syntax
Do not use abbreviations or cryptic names; write out full words likeerror,parameters,requestinstead oferr,params,req
UseNumber.parseInt()instead of the globalparseInt()function when parsing numbers
All classes called withnewmust use PascalCase
UseBuffer.from(),Buffer.alloc(), orBuffer.allocUnsafe()instead of the deprecatednew Buffer()
Use@ts-expect-errorinstead of@ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like-or:
Do not include the type in a variable's name; let the static type system do its job (e.g., useconst user: Usernotconst userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...
Files:
server/test/queues/alchemyQueue.test.tsserver/queues/constants.tsserver/test/utils/createCredential.test.tsserver/utils/createCredential.tsserver/queues/alchemyQueue.tsserver/script/openapi.ts
server/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - usec.var.db
Files:
server/test/queues/alchemyQueue.test.tsserver/queues/constants.tsserver/test/utils/createCredential.test.tsserver/utils/createCredential.tsserver/queues/alchemyQueue.tsserver/script/openapi.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
For files with a single
defaultexport, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word
Files:
server/test/queues/alchemyQueue.test.tsserver/queues/constants.tsserver/test/utils/createCredential.test.tsserver/utils/createCredential.tsserver/queues/alchemyQueue.tsserver/script/openapi.ts
**/.changeset/*.md
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use a lowercase sentence in the imperative present tense for changeset summaries
Files:
.changeset/short-cars-return.md
🧠 Learnings (6)
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.
Applied to files:
.changeset/short-cars-return.md
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`
Applied to files:
server/utils/createCredential.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Applied to files:
server/queues/alchemyQueue.tsserver/script/openapi.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Applied to files:
server/queues/alchemyQueue.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services
Applied to files:
server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres
Applied to files:
server/script/openapi.ts
🧬 Code graph analysis (3)
server/test/queues/alchemyQueue.test.ts (3)
.maestro/maestro.d.ts (1)
Response(19-19)server/queues/constants.ts (1)
AlchemyJob(7-9)server/queues/alchemyQueue.ts (2)
AlchemyJobData(19-24)processor(46-73)
server/test/utils/createCredential.test.ts (3)
server/utils/createCredential.ts (1)
createCredential(24-61)server/queues/alchemyQueue.ts (1)
alchemyQueue(38-38)server/queues/constants.ts (1)
AlchemyJob(7-9)
server/utils/createCredential.ts (2)
server/queues/alchemyQueue.ts (1)
alchemyQueue(38-38)server/queues/constants.ts (1)
AlchemyJob(7-9)
🪛 GitHub Actions: test
server/test/queues/alchemyQueue.test.ts
[error] 22-22: Test failed: Unknown job name: unknown
server/queues/alchemyQueue.ts
[error] 68-68: Unknown job name: unknown
🪛 GitHub Check: test
server/queues/alchemyQueue.ts
[failure] 68-68: test/queues/alchemyQueue.test.ts > alchemyQueue worker processor > should ignore unknown job names
Error: Unknown job name: unknown
❯ queues/alchemyQueue.ts:68:17
❯ test/queues/alchemyQueue.test.ts:22:7
❯ Module.processor queues/alchemyQueue.ts:47:10
❯ test/queues/alchemyQueue.test.ts:70:11
🔇 Additional comments (7)
server/script/openapi.ts (1)
25-26: LGTM: Environment configuration aligns with queue system requirements.The Redis URL now uses the full connection string format required by ioredis/BullMQ, and
DISABLE_WORKERSprevents background worker processes during OpenAPI spec generation. These changes are appropriate for the script context..changeset/short-cars-return.md (1)
1-5: LGTM: Changeset follows conventions.The changeset format is correct, and the description follows the required lowercase imperative present tense style.
server/test/utils/createCredential.test.ts (1)
73-81: LGTM: Error handling test is comprehensive.The test correctly verifies that queue failures are captured via Sentry's
captureException, ensuring resilient error handling in the credential creation flow.server/queues/constants.ts (1)
1-11: LGTM: Clean constant definitions following TypeScript best practices.The use of
as constwith type extraction via indexed access types is idiomatic and type-safe. The naming is clear and follows conventions.server/test/queues/alchemyQueue.test.ts (1)
79-95: LGTM: API failure test validates error propagation.The test correctly verifies that non-OK responses from the Alchemy API result in the processor rejecting with the appropriate error message.
server/queues/alchemyQueue.ts (2)
46-73: LGTM - Processor implementation is well-structured.The switch statement pattern with a
defaultcase that throws for unknown job names is a good defensive approach for catching typos and ensuring new job types are explicitly handled. The Sentry span tracing provides good observability.
19-24: LGTM - Interface is well-documented.The
AlchemyJobDatainterface provides clear documentation for the job payload structure.
0dc7771 to
63b777f
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #619 +/- ##
==========================================
- Coverage 44.77% 41.48% -3.29%
==========================================
Files 176 54 -122
Lines 5244 2555 -2689
Branches 1624 567 -1057
==========================================
- Hits 2348 1060 -1288
+ Misses 2783 1426 -1357
+ Partials 113 69 -44
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
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: 1
🤖 Fix all issues with AI Agents
In @server/queues/alchemyQueue.ts:
- Around line 60-62: The default branch in the processor currently only sets
span.setStatus({ code: 2, message: `unknown job name: ${job.name}` }) but
doesn't throw, so unknown job names silently succeed; change the default in the
switch inside processor to throw a new Error(`unknown job name: ${job.name}`)
after setting the span status so the job fails fast (keep the span.setStatus
call), and update the test in server/test/queues/alchemyQueue.test.ts to expect
processor(job) to reject with that error.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (7)
.changeset/short-cars-return.mdserver/queues/alchemyQueue.tsserver/queues/constants.tsserver/script/openapi.tsserver/test/queues/alchemyQueue.test.tsserver/test/utils/createCredential.test.tsserver/utils/createCredential.ts
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/**/*.ts: Usec.varobject to pass strongly-typed data between Hono middleware and route handlers; do not usec.set
All request validation (headers, body, params) must be handled by@hono/valibot-validatormiddleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwingnew HTTPException()for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLintplugin:n/recommendedconfiguration
Enforce Drizzle ORM best practices using ESLintplugin:drizzle/allconfiguration, including requiringwhereclauses forupdateanddeleteoperations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not accessprocess.envdirectly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; useasync/awaitcorrectly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Files:
server/script/openapi.tsserver/test/utils/createCredential.test.tsserver/queues/constants.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.tsserver/test/queues/alchemyQueue.test.ts
**/*.{js,ts,tsx,jsx,sol}
📄 CodeRabbit inference engine (AGENTS.md)
Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No
anytype.
Files:
server/script/openapi.tsserver/test/utils/createCredential.test.tsserver/queues/constants.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.tsserver/test/queues/alchemyQueue.test.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Omit redundant type names in variable declarations - let the type system explain itself
**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas usingtype User = v.Input<typeof UserSchema>instead of manually defining interfaces
Files:
server/script/openapi.tsserver/test/utils/createCredential.test.tsserver/queues/constants.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.tsserver/test/queues/alchemyQueue.test.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Preferconstfor all variable declarations by default; only useletif the variable's value will be reassigned
Declare each variable on its own line with its ownconstorletkeyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always useimport type { ... }for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), andfor...ofloops over traditional syntax
Do not use abbreviations or cryptic names; write out full words likeerror,parameters,requestinstead oferr,params,req
UseNumber.parseInt()instead of the globalparseInt()function when parsing numbers
All classes called withnewmust use PascalCase
UseBuffer.from(),Buffer.alloc(), orBuffer.allocUnsafe()instead of the deprecatednew Buffer()
Use@ts-expect-errorinstead of@ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like-or:
Do not include the type in a variable's name; let the static type system do its job (e.g., useconst user: Usernotconst userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...
Files:
server/script/openapi.tsserver/test/utils/createCredential.test.tsserver/queues/constants.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.tsserver/test/queues/alchemyQueue.test.ts
server/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - usec.var.db
Files:
server/script/openapi.tsserver/test/utils/createCredential.test.tsserver/queues/constants.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.tsserver/test/queues/alchemyQueue.test.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
For files with a single
defaultexport, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word
Files:
server/script/openapi.tsserver/test/utils/createCredential.test.tsserver/queues/constants.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.tsserver/test/queues/alchemyQueue.test.ts
**/.changeset/*.md
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use a lowercase sentence in the imperative present tense for changeset summaries
Files:
.changeset/short-cars-return.md
🧠 Learnings (6)
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services
Applied to files:
server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres
Applied to files:
server/script/openapi.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Applied to files:
server/script/openapi.tsserver/queues/alchemyQueue.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Applied to files:
server/queues/alchemyQueue.ts
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.
Applied to files:
.changeset/short-cars-return.md
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`
Applied to files:
server/utils/createCredential.ts
🧬 Code graph analysis (4)
server/test/utils/createCredential.test.ts (2)
server/queues/alchemyQueue.ts (1)
alchemyQueue(33-33)server/queues/constants.ts (1)
AlchemyJob(7-9)
server/queues/alchemyQueue.ts (1)
server/queues/constants.ts (2)
QueueName(1-3)AlchemyJob(7-9)
server/utils/createCredential.ts (2)
server/queues/alchemyQueue.ts (1)
alchemyQueue(33-33)server/queues/constants.ts (1)
AlchemyJob(7-9)
server/test/queues/alchemyQueue.test.ts (2)
server/queues/constants.ts (1)
AlchemyJob(7-9)server/queues/alchemyQueue.ts (2)
AlchemyJobData(14-19)processor(41-65)
🔇 Additional comments (7)
server/script/openapi.ts (1)
25-26: LGTM!The updated
REDIS_URLformat andDISABLE_WORKERSflag are appropriate for the OpenAPI spec generation script. Disabling workers prevents unnecessary Redis connections during build-time spec generation. Based on learnings, this script context (not application code) is acceptable for directprocess.envusage.server/queues/constants.ts (1)
1-11: LGTM!Well-structured constants with proper type derivation using
as const. The pattern of deriving types from constant objects ensures type safety and maintains a single source of truth..changeset/short-cars-return.md (1)
1-5: LGTM!Changeset follows guidelines with lowercase imperative present tense description. Patch level is appropriate for this additive feature.
server/test/utils/createCredential.test.ts (1)
52-82: LGTM!Good test coverage for the queue integration: verifies job enqueueing on credential creation and proper error capture when the queue fails.
server/test/queues/alchemyQueue.test.ts (2)
68-77: Test expectation should change if processor is updated.This test currently expects unknown jobs to be silently ignored, which matches the current implementation. However, if the processor is updated to throw on unknown job names (as recommended in the
alchemyQueue.tsreview), update this test to expect the error:it("should throw an error for unknown job names", async () => { const job = { name: "unknown", data: {} } as unknown as Job<AlchemyJobData>; await expect(processor(job)).rejects.toThrow("unknown job name: unknown"); expect(fetch).not.toHaveBeenCalled(); });
39-66: LGTM!Good test coverage for the processor: verifies correct API call construction for
ADD_SUBSCRIBERjobs, proper Sentry span creation, and error handling when the Alchemy API fails.Also applies to: 79-95
server/utils/createCredential.ts (1)
21-22: LGTM!Good refactoring from synchronous HTTP calls to queue-based processing. The fire-and-forget pattern with error capture is appropriate for non-critical webhook subscription updates, and the queue provides retry capability on failures.
Also applies to: 54-54
|
@cursor review |
PR SummaryCursor Bugbot is generating a summary for commit 63b777f. Configure here. |
PR SummaryIntroduces background processing for Alchemy webhook address updates and wires it into credential creation.
Written by Cursor Bugbot for commit 63b777f. This will update automatically on new commits. Configure here. |
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.
This is the final PR Bugbot will review for you during this billing cycle
Your free Bugbot reviews will reset on February 2
Details
You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
63b777f to
03cbfe4
Compare
03cbfe4 to
54b79be
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: 6
🤖 Fix all issues with AI agents
In @server/queues/alchemyQueue.ts:
- Line 9: The file directly reads process.env.DISABLE_WORKERS into the const
disableWorkers which violates the rule to not access process.env in application
code; change the module to accept configuration via dependency injection (e.g.,
export a factory like createAlchemyQueue(config) or accept a Config parameter)
and replace direct usage of disableWorkers with config.disableWorkers (or
equivalent) throughout the file so configuration is loaded once at startup and
passed in rather than read from process.env inside this module.
- Around line 21-26: The code directly reads process.env to build the Redis
`connection` object and uses Number() for port parsing; change this by
introducing a factory function (e.g., export function
createRedisConnection(redisConfig: { url?: string; host?: string; port?: string
| number })) that takes the preloaded config instead of accessing process.env
inside this module, and inside that function build the connection: if
redisConfig.url use new Redis(redisConfig.url, { maxRetriesPerRequest: null }),
otherwise use { host: redisConfig.host ?? "localhost", port:
Number.parseInt(String(redisConfig.port ?? "6379"), 10) }; update call sites to
pass the startup-loaded config into createRedisConnection instead of relying on
environment access here.
- Around line 41-68: The span.setStatus calls in processor (inside the
AlchemyJob.ADD_SUBSCRIBER case and the default case) use the magic number 2;
replace that literal with a named constant or the Sentry enum (e.g., import
SpanStatusCode from "@sentry/node" and use SpanStatusCode.Error) or define a
local constant like SPAN_STATUS_ERROR and use it in both span.setStatus calls to
improve readability and consistency.
- Around line 72-100: The Worker instance created for QueueName.ALCHEMY is not
stored, preventing graceful shutdown; assign it to a module-scoped variable
(e.g., let alchemyWorker: Worker | undefined = new Worker(...) or create it then
set alchemyWorker = worker) so you can call alchemyWorker?.close() during
application termination/shutdown handling; ensure the variable is exported or
accessible by your existing shutdown routine and keep existing event handlers
(.on("failed"/"completed"/"active"/"error")) attached to the stored worker.
In @server/test/queues/alchemyQueue.test.ts:
- Line 54: The test assertion includes an unnecessary type assertion on the
headers matcher; remove the trailing "as Record<string, string>" from the
headers line so the matcher uses expect.objectContaining({ "X-Alchemy-Token":
"mock-token" }) directly (update the headers property in the assertion where
expect.objectContaining is used).
In @server/test/utils/createCredential.test.ts:
- Line 67: Remove the redundant type assertion on the account matcher: locate
the object assigning account: expect.stringMatching(/^0x/) as string and simply
use account: expect.stringMatching(/^0x/); the expect.stringMatching call
already returns the correct matcher type so drop the "as string" cast to clean
up the test.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (7)
.changeset/short-cars-return.mdserver/queues/alchemyQueue.tsserver/queues/constants.tsserver/script/openapi.tsserver/test/queues/alchemyQueue.test.tsserver/test/utils/createCredential.test.tsserver/utils/createCredential.ts
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/**/*.ts: Usec.varobject to pass strongly-typed data between Hono middleware and route handlers; do not usec.set
All request validation (headers, body, params) must be handled by@hono/valibot-validatormiddleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwingnew HTTPException()for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLintplugin:n/recommendedconfiguration
Enforce Drizzle ORM best practices using ESLintplugin:drizzle/allconfiguration, including requiringwhereclauses forupdateanddeleteoperations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not accessprocess.envdirectly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; useasync/awaitcorrectly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Files:
server/test/utils/createCredential.test.tsserver/script/openapi.tsserver/queues/constants.tsserver/test/queues/alchemyQueue.test.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.ts
**/*.{js,ts,tsx,jsx,sol}
📄 CodeRabbit inference engine (AGENTS.md)
Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No
anytype.
Files:
server/test/utils/createCredential.test.tsserver/script/openapi.tsserver/queues/constants.tsserver/test/queues/alchemyQueue.test.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Omit redundant type names in variable declarations - let the type system explain itself
**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas usingtype User = v.Input<typeof UserSchema>instead of manually defining interfaces
Files:
server/test/utils/createCredential.test.tsserver/script/openapi.tsserver/queues/constants.tsserver/test/queues/alchemyQueue.test.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Preferconstfor all variable declarations by default; only useletif the variable's value will be reassigned
Declare each variable on its own line with its ownconstorletkeyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always useimport type { ... }for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), andfor...ofloops over traditional syntax
Do not use abbreviations or cryptic names; write out full words likeerror,parameters,requestinstead oferr,params,req
UseNumber.parseInt()instead of the globalparseInt()function when parsing numbers
All classes called withnewmust use PascalCase
UseBuffer.from(),Buffer.alloc(), orBuffer.allocUnsafe()instead of the deprecatednew Buffer()
Use@ts-expect-errorinstead of@ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like-or:
Do not include the type in a variable's name; let the static type system do its job (e.g., useconst user: Usernotconst userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...
Files:
server/test/utils/createCredential.test.tsserver/script/openapi.tsserver/queues/constants.tsserver/test/queues/alchemyQueue.test.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.ts
server/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - usec.var.db
Files:
server/test/utils/createCredential.test.tsserver/script/openapi.tsserver/queues/constants.tsserver/test/queues/alchemyQueue.test.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
For files with a single
defaultexport, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word
Files:
server/test/utils/createCredential.test.tsserver/script/openapi.tsserver/queues/constants.tsserver/test/queues/alchemyQueue.test.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.ts
**/.changeset/*.md
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use a lowercase sentence in the imperative present tense for changeset summaries
Files:
.changeset/short-cars-return.md
🧠 Learnings (6)
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services
Applied to files:
server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres
Applied to files:
server/script/openapi.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Applied to files:
server/script/openapi.tsserver/queues/alchemyQueue.ts
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.
Applied to files:
.changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Applied to files:
server/queues/alchemyQueue.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`
Applied to files:
server/utils/createCredential.ts
🧬 Code graph analysis (3)
server/test/queues/alchemyQueue.test.ts (1)
server/queues/alchemyQueue.ts (2)
AlchemyJobData(14-19)processor(41-68)
server/queues/alchemyQueue.ts (1)
server/queues/constants.ts (2)
QueueName(1-3)AlchemyJob(7-9)
server/utils/createCredential.ts (2)
server/queues/alchemyQueue.ts (1)
alchemyQueue(33-33)server/queues/constants.ts (1)
AlchemyJob(7-9)
🔇 Additional comments (12)
server/queues/constants.ts (1)
1-11: LGTM! Clean constants definition.The use of
as constwith derived enum types is idiomatic TypeScript, providing strong type safety for queue and job names.server/script/openapi.ts (1)
25-26: LGTM! Appropriate environment configuration for OpenAPI generation.The explicit Redis URL and
DISABLE_WORKERSflag correctly prevent worker initialization during spec generation while maintaining the zero-config local dev environment..changeset/short-cars-return.md (1)
1-5: LGTM! Changeset follows the required format.The summary correctly uses lowercase imperative present tense as per coding guidelines.
server/test/utils/createCredential.test.ts (1)
1-58: LGTM! Comprehensive test coverage with proper mocking.The test suite correctly validates both the happy path (job enqueuing) and error handling (exception capture), with well-isolated mocks.
Also applies to: 72-82
server/test/queues/alchemyQueue.test.ts (1)
1-53: LGTM! Comprehensive test coverage for queue processor.The test suite validates all critical paths: successful webhook updates, unknown job handling, and API failure scenarios. The tests correctly expect the processor to throw errors for edge cases.
Also applies to: 55-90
server/utils/createCredential.ts (4)
6-7: LGTM!The Sentry import changes appropriately separate
setUserfrom@sentry/coreandcaptureExceptionfrom@sentry/node, following the modular structure of the Sentry SDK.
14-14: LGTM!The database import addition is necessary for the database operations and follows the project's import conventions.
21-22: LGTM!The queue-related imports are correctly added to support the new asynchronous job enqueueing approach, replacing the previous synchronous HTTP call.
54-54: Consider consistent error capture options.The
captureExceptioncall at line 54 doesn't include aleveloption, while the similar sardine error capture at line 56 specifies{ level: "error" }. For consistency, consider adding the same error level to both non-critical async operations.🔎 Suggested consistency improvement
- alchemyQueue.add(AlchemyJob.ADD_SUBSCRIBER, { account, webhookId }).catch(captureException), + alchemyQueue.add(AlchemyJob.ADD_SUBSCRIBER, { account, webhookId }).catch((error: unknown) => + captureException(error, { level: "error" }), + ),Likely an incorrect or invalid review comment.
server/queues/alchemyQueue.ts (3)
1-6: LGTM!The imports are correctly structured with proper type imports and necessary dependencies for the queue implementation.
11-19: LGTM!The
AlchemyJobDatainterface is well-documented with clear JSDoc comments explaining each field's purpose.
28-33: LGTM!The queue is correctly instantiated with proper documentation explaining its purpose for offloading webhook subscription updates and enabling retries.
54b79be to
61cb4bb
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: 2
🤖 Fix all issues with AI agents
In @server/test/queues/alchemyQueue.test.ts:
- Line 54: The test contains an unnecessary type assertion on the headers
matcher: remove the trailing "as Record<string, string>" from the
expect.objectContaining call (the matcher returned by expect.objectContaining({
"X-Alchemy-Token": "mock-token" }) is already correctly typed); update the line
using the headers matcher (the expression starting with
expect.objectContaining({ "X-Alchemy-Token": "mock-token" })) to omit the cast
and run tests to confirm no type errors remain.
In @server/test/utils/createCredential.test.ts:
- Line 67: Remove the redundant type assertion on the account matcher: replace
the property value "expect.stringMatching(/^0x/) as string" with just
"expect.stringMatching(/^0x/)" in the createCredential.test.ts test (the object
property named account), since expect.stringMatching already returns the correct
matcher type.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (7)
.changeset/short-cars-return.mdserver/queues/alchemyQueue.tsserver/queues/constants.tsserver/script/openapi.tsserver/test/queues/alchemyQueue.test.tsserver/test/utils/createCredential.test.tsserver/utils/createCredential.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/.changeset/*.md
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use a lowercase sentence in the imperative present tense for changeset summaries
Files:
.changeset/short-cars-return.md
server/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/**/*.ts: Usec.varobject to pass strongly-typed data between Hono middleware and route handlers; do not usec.set
All request validation (headers, body, params) must be handled by@hono/valibot-validatormiddleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwingnew HTTPException()for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLintplugin:n/recommendedconfiguration
Enforce Drizzle ORM best practices using ESLintplugin:drizzle/allconfiguration, including requiringwhereclauses forupdateanddeleteoperations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not accessprocess.envdirectly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; useasync/awaitcorrectly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Files:
server/queues/alchemyQueue.tsserver/script/openapi.tsserver/utils/createCredential.tsserver/test/queues/alchemyQueue.test.tsserver/queues/constants.tsserver/test/utils/createCredential.test.ts
**/*.{js,ts,tsx,jsx,sol}
📄 CodeRabbit inference engine (AGENTS.md)
Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No
anytype.
Files:
server/queues/alchemyQueue.tsserver/script/openapi.tsserver/utils/createCredential.tsserver/test/queues/alchemyQueue.test.tsserver/queues/constants.tsserver/test/utils/createCredential.test.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Omit redundant type names in variable declarations - let the type system explain itself
**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas usingtype User = v.Input<typeof UserSchema>instead of manually defining interfaces
Files:
server/queues/alchemyQueue.tsserver/script/openapi.tsserver/utils/createCredential.tsserver/test/queues/alchemyQueue.test.tsserver/queues/constants.tsserver/test/utils/createCredential.test.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Preferconstfor all variable declarations by default; only useletif the variable's value will be reassigned
Declare each variable on its own line with its ownconstorletkeyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always useimport type { ... }for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), andfor...ofloops over traditional syntax
Do not use abbreviations or cryptic names; write out full words likeerror,parameters,requestinstead oferr,params,req
UseNumber.parseInt()instead of the globalparseInt()function when parsing numbers
All classes called withnewmust use PascalCase
UseBuffer.from(),Buffer.alloc(), orBuffer.allocUnsafe()instead of the deprecatednew Buffer()
Use@ts-expect-errorinstead of@ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like-or:
Do not include the type in a variable's name; let the static type system do its job (e.g., useconst user: Usernotconst userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...
Files:
server/queues/alchemyQueue.tsserver/script/openapi.tsserver/utils/createCredential.tsserver/test/queues/alchemyQueue.test.tsserver/queues/constants.tsserver/test/utils/createCredential.test.ts
server/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - usec.var.db
Files:
server/queues/alchemyQueue.tsserver/script/openapi.tsserver/utils/createCredential.tsserver/test/queues/alchemyQueue.test.tsserver/queues/constants.tsserver/test/utils/createCredential.test.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
For files with a single
defaultexport, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word
Files:
server/queues/alchemyQueue.tsserver/script/openapi.tsserver/utils/createCredential.tsserver/test/queues/alchemyQueue.test.tsserver/queues/constants.tsserver/test/utils/createCredential.test.ts
🧠 Learnings (6)
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.
Applied to files:
.changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Applied to files:
server/queues/alchemyQueue.tsserver/script/openapi.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Applied to files:
server/queues/alchemyQueue.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services
Applied to files:
server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres
Applied to files:
server/script/openapi.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`
Applied to files:
server/utils/createCredential.ts
🧬 Code graph analysis (4)
server/queues/alchemyQueue.ts (1)
server/queues/constants.ts (2)
QueueName(1-3)AlchemyJob(7-9)
server/utils/createCredential.ts (2)
server/queues/alchemyQueue.ts (1)
alchemyQueue(33-33)server/queues/constants.ts (1)
AlchemyJob(7-9)
server/test/queues/alchemyQueue.test.ts (3)
.maestro/maestro.d.ts (1)
Response(19-19)server/queues/constants.ts (1)
AlchemyJob(7-9)server/queues/alchemyQueue.ts (2)
AlchemyJobData(14-19)processor(41-68)
server/test/utils/createCredential.test.ts (3)
server/utils/createCredential.ts (1)
createCredential(24-61)server/queues/alchemyQueue.ts (1)
alchemyQueue(33-33)server/queues/constants.ts (1)
AlchemyJob(7-9)
🔇 Additional comments (9)
server/script/openapi.ts (1)
25-26: LGTM! Environment configuration aligns with new queue infrastructure.The Redis URL now provides a proper connection string for local development, and the DISABLE_WORKERS flag enables control over worker initialization during spec generation.
.changeset/short-cars-return.md (1)
1-5: LGTM! Changeset follows guidelines.The description uses lowercase imperative present tense as required by the coding guidelines.
server/test/utils/createCredential.test.ts (1)
1-82: LGTM! Test coverage is comprehensive.The tests appropriately cover both the success path (job enqueuing) and error path (exception capture), with thorough mocking of dependencies.
server/queues/constants.ts (1)
1-11: LGTM! Clean constants definition.The constants follow TypeScript best practices with
as constassertions and proper type derivation. Naming conventions are correct throughout.server/test/queues/alchemyQueue.test.ts (1)
1-90: LGTM! Comprehensive test coverage for the queue processor.The tests cover all critical paths: successful processing, unknown job handling, and API failure scenarios. The mocking strategy is thorough and appropriate.
server/utils/createCredential.ts (1)
46-59: Queue-based approach for webhook subscription looks good.The switch from synchronous HTTP PATCH to asynchronous queue processing improves reliability by enabling retries on API failures and avoids blocking the main request flow. The
Promise.allcorrectly handles the concurrent operations with appropriate error capture.server/queues/alchemyQueue.ts (3)
60-64: Unknown job handling now correctly throws - good fix.The default case properly sets the span status and throws an error, ensuring unknown jobs fail fast rather than silently succeeding. This addresses the previous feedback about silent failures.
98-99: Error handler addition prevents process crashes - good fix.The
.on("error")handler correctly captures Redis connection failures and internal errors, preventing uncaught exceptions from crashing the process. This follows the pattern used elsewhere in the codebase (e.g.,analytics.on("error", ...)insegment.ts).
41-68: Processor implementation is well-structured.The processor correctly:
- Uses Sentry tracing with appropriate span attributes
- Handles the ADD_SUBSCRIBER job type with proper API call
- Throws on non-ok responses with status details
- Throws on unknown job names for fail-fast behavior
f9b7be5 to
5e59af2
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: 3
🤖 Fix all issues with AI agents
In @.changeset/short-cars-return.md:
- Line 5: Update the changelog summary in .changeset/short-cars-return.md by
removing the redundant "server:" prefix so the summary reads "add queue on user
creation" (i.e., replace "server: add queue on user creation" with "add queue on
user creation").
In @server/test/queues/alchemyQueue.test.ts:
- Line 54: Remove the redundant type assertion by deleting "as Record<string,
string>" from the headers matcher so it reads headers: expect.objectContaining({
"X-Alchemy-Token": "mock-token" }); leave the expect.objectContaining() usage
intact (in the test that sets headers) since Vitest will infer the matcher types
correctly.
In @server/test/utils/createCredential.test.ts:
- Line 67: The test includes a redundant type assertion on the account property:
remove the unnecessary "as string" cast and rely on expect.stringMatching(/^0x/)
directly (i.e., replace "account: expect.stringMatching(/^0x/) as string" with
"account: expect.stringMatching(/^0x/)" in the test using
expect.stringMatching).
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (7)
.changeset/short-cars-return.mdserver/queues/alchemyQueue.tsserver/queues/constants.tsserver/script/openapi.tsserver/test/queues/alchemyQueue.test.tsserver/test/utils/createCredential.test.tsserver/utils/createCredential.ts
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/**/*.ts: Usec.varobject to pass strongly-typed data between Hono middleware and route handlers; do not usec.set
All request validation (headers, body, params) must be handled by@hono/valibot-validatormiddleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwingnew HTTPException()for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLintplugin:n/recommendedconfiguration
Enforce Drizzle ORM best practices using ESLintplugin:drizzle/allconfiguration, including requiringwhereclauses forupdateanddeleteoperations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not accessprocess.envdirectly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; useasync/awaitcorrectly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Files:
server/script/openapi.tsserver/queues/constants.tsserver/test/queues/alchemyQueue.test.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.tsserver/test/utils/createCredential.test.ts
**/*.{js,ts,tsx,jsx,sol}
📄 CodeRabbit inference engine (AGENTS.md)
Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No
anytype.
Files:
server/script/openapi.tsserver/queues/constants.tsserver/test/queues/alchemyQueue.test.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.tsserver/test/utils/createCredential.test.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Omit redundant type names in variable declarations - let the type system explain itself
**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas usingtype User = v.Input<typeof UserSchema>instead of manually defining interfaces
Files:
server/script/openapi.tsserver/queues/constants.tsserver/test/queues/alchemyQueue.test.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.tsserver/test/utils/createCredential.test.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Preferconstfor all variable declarations by default; only useletif the variable's value will be reassigned
Declare each variable on its own line with its ownconstorletkeyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always useimport type { ... }for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), andfor...ofloops over traditional syntax
Do not use abbreviations or cryptic names; write out full words likeerror,parameters,requestinstead oferr,params,req
UseNumber.parseInt()instead of the globalparseInt()function when parsing numbers
All classes called withnewmust use PascalCase
UseBuffer.from(),Buffer.alloc(), orBuffer.allocUnsafe()instead of the deprecatednew Buffer()
Use@ts-expect-errorinstead of@ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like-or:
Do not include the type in a variable's name; let the static type system do its job (e.g., useconst user: Usernotconst userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...
Files:
server/script/openapi.tsserver/queues/constants.tsserver/test/queues/alchemyQueue.test.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.tsserver/test/utils/createCredential.test.ts
server/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - usec.var.db
Files:
server/script/openapi.tsserver/queues/constants.tsserver/test/queues/alchemyQueue.test.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.tsserver/test/utils/createCredential.test.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
For files with a single
defaultexport, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word
Files:
server/script/openapi.tsserver/queues/constants.tsserver/test/queues/alchemyQueue.test.tsserver/queues/alchemyQueue.tsserver/utils/createCredential.tsserver/test/utils/createCredential.test.ts
**/.changeset/*.md
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use a lowercase sentence in the imperative present tense for changeset summaries
Files:
.changeset/short-cars-return.md
🧠 Learnings (6)
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services
Applied to files:
server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres
Applied to files:
server/script/openapi.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Applied to files:
server/script/openapi.tsserver/queues/alchemyQueue.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Applied to files:
server/queues/alchemyQueue.ts
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.
Applied to files:
.changeset/short-cars-return.md
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`
Applied to files:
server/utils/createCredential.ts
🧬 Code graph analysis (4)
server/test/queues/alchemyQueue.test.ts (3)
.maestro/maestro.d.ts (1)
Response(19-19)server/queues/constants.ts (1)
AlchemyJob(7-9)server/queues/alchemyQueue.ts (2)
AlchemyJobData(15-20)processor(42-69)
server/queues/alchemyQueue.ts (1)
server/queues/constants.ts (2)
QueueName(1-3)AlchemyJob(7-9)
server/utils/createCredential.ts (2)
server/queues/alchemyQueue.ts (1)
alchemyQueue(34-34)server/queues/constants.ts (1)
AlchemyJob(7-9)
server/test/utils/createCredential.test.ts (4)
.maestro/maestro.d.ts (1)
Response(19-19)server/utils/createCredential.ts (1)
createCredential(24-61)server/queues/alchemyQueue.ts (1)
alchemyQueue(34-34)server/queues/constants.ts (1)
AlchemyJob(7-9)
🔇 Additional comments (5)
server/script/openapi.ts (1)
25-26: LGTM!The REDIS_URL change to a full URL format is more explicit, and adding DISABLE_WORKERS prevents the queue worker from starting during OpenAPI spec generation, which is appropriate for this tooling script.
server/test/utils/createCredential.test.ts (1)
73-81: LGTM!The exception handling test correctly verifies that queue failures are captured via Sentry's
captureException, ensuring observability for background job enqueue failures.server/queues/constants.ts (1)
1-11: LGTM!Clean constants definition following TypeScript best practices. The use of
as constfor literal types and indexed access types for enums ensures type safety throughout the queue system.server/test/queues/alchemyQueue.test.ts (2)
68-71: LGTM!The test correctly validates that the processor throws an error for unknown job names, aligning with the processor's default case behavior.
73-89: LGTM!Excellent addition of the API failure test case. This ensures the processor correctly handles and reports non-ok responses from the Alchemy API, improving resilience and observability.
5e59af2 to
fdc90ad
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: 1
🤖 Fix all issues with AI agents
In `@server/test/queues/alchemyQueue.test.ts`:
- Around line 1-7: Add the Redis mock import at the top of the test module to
prevent real Redis connection during module initialization: insert an import
statement for the test Redis mock (e.g., import "../mocks/redis";) as the very
first import in alchemyQueue.test.ts so it runs before importing the module that
instantiates Redis (the module that exports processor and
AlchemyJob/AlchemyJobData from ../../queues/alchemyQueue), ensuring ioredis-mock
is used during tests.
♻️ Duplicate comments (11)
server/script/openapi.ts (1)
28-29: Environment variable additions follow existing pattern in this CLI script.These new
process.envassignments are consistent with the existing pattern in this OpenAPI generation script (lines 7-27). While the coding guidelines discourage directprocess.envaccess in application code, CLI scripts that bootstrap test/generation environments are a common exception. TheDISABLE_WORKERS="true"correctly prevents worker instantiation during spec generation.server/test/utils/createCredential.test.ts (4)
14-14: Use lowercase comment text.As per coding guidelines, all code comments must be lowercase.
♻️ Proposed fix
-// Mock dependencies +// mock dependencies
57-57: Use lowercase comment text.As per coding guidelines.
♻️ Proposed fix
-// Mock global fetch to avoid actual network calls +// mock global fetch to avoid actual network calls
80-80: Remove unnecessary type assertion.The
as stringcast is redundant—expect.stringMatching()returns the correct matcher type.♻️ Proposed fix
- account: expect.stringMatching(/^0x/) as string, + account: expect.stringMatching(/^0x/),
97-100: Remove unnecessary type assertion.The
as Record<string, unknown>cast is redundant—expect.objectContaining()handles matcher types correctly.♻️ Proposed fix
extra: expect.objectContaining({ job: AlchemyJob.ADD_SUBSCRIBER, webhookId: "webhook-id", - }) as Record<string, unknown>, + }),server/test/queues/alchemyQueue.test.ts (2)
9-9: Use lowercase comment text.As per coding guidelines, all code comments must be lowercase.
♻️ Proposed fix
-// Mock dependencies +// mock dependencies
54-54: Remove unnecessary type assertion.The
as Record<string, string>cast is redundant—expect.objectContaining()handles matcher types correctly.♻️ Proposed fix
- headers: expect.objectContaining({ "X-Alchemy-Token": "mock-token" }) as Record<string, string>, + headers: expect.objectContaining({ "X-Alchemy-Token": "mock-token" }),server/utils/createCredential.ts (1)
57-63: Conditional guard is correct but silent skip during startup race remains a concern.The implementation properly guards against undefined
webhookIdand includes comprehensive error context (level: "error",extra: { job, account, webhookId }). However, credentials created beforewebhookIdinitialization completes will silently skip the ADD_SUBSCRIBER job with no recovery mechanism.Consider logging when
webhookIdis undefined so missed subscriptions are observable:🔍 Suggested observability improvement
webhookId ? alchemyQueue .add(AlchemyJob.ADD_SUBSCRIBER, { account, webhookId }) .catch((error: unknown) => captureException(error, { level: "error", extra: { job: AlchemyJob.ADD_SUBSCRIBER, account, webhookId } }), ) - : undefined, + : captureException(new Error("webhookId not initialized"), { + level: "warning", + extra: { account }, + }),server/queues/alchemyQueue.ts (3)
8-10: Directprocess.envaccess violates coding guidelines.Per guidelines: "Do not access
process.envdirectly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context."The
DISABLE_WORKERSflag should be loaded at startup and injected.
104-106: Parallel shutdown creates a race condition.BullMQ requires sequential shutdown: worker first, then queue, then Redis connection. The current
Promise.allapproach may terminate the Redis connection before Worker/Queue finish their cleanup (releasing locks, updating job states).🐛 Proposed fix for sequential shutdown
export async function close() { - await Promise.all([connection.quit(), alchemyWorker?.close() ?? Promise.resolve(), alchemyQueue.close()]); + await alchemyWorker?.close(); + await alchemyQueue.close(); + await connection.quit(); }
22-23: Load REDIS_URL at startup and pass through dependency injection, notprocess.envdirectly.Lines 22-23 directly access
process.env.REDIS_URL, which violates the guideline to load configuration and secrets once at startup and pass them through dependency injection or context. The same issue exists on line 9 withprocess.env.DISABLE_WORKERS. Move environment variable loading to a configuration module initialized at startup, then inject the connection or URL into the queue setup.
838c475 to
e936214
Compare
e936214 to
733df9f
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: 1
🤖 Fix all issues with AI agents
In `@server/queues/alchemyQueue.ts`:
- Around line 22-23: Update the ioredis mock to export a default (change
server/test/mocks/redis.ts to vi.mock("ioredis", () => ({ default: Redis })))
and remove direct process.env access in module scope: stop reading
process.env.REDIS_URL and process.env.DISABLE_WORKERS inside
server/queues/alchemyQueue.ts (and any other modules) and instead accept the
values via parameters or a startup context that is injected into the
queue/worker initializer; replace the module-level `const connection = new
Redis(process.env.REDIS_URL, ...)` with a function or factory (e.g.,
createAlchemyQueue or initAlchemyQueue) that takes redisUrl (and disableWorkers
flag) and constructs `new Redis(redisUrl, { maxRetriesPerRequest: null })`, so
all environment values are loaded once at app startup and passed in rather than
read at import time.
733df9f to
d9c51d7
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.
d9c51d7 to
302a354
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: 2
🤖 Fix all issues with AI agents
In `@server/queues/alchemyQueue.ts`:
- Around line 38-43: The function getAlchemyQueue uses an explicit
null/undefined check and assignment for the module-scoped _alchemyQueue; replace
that pattern with the nullish coalescing assignment operator so the code
becomes: use `_alchemyQueue ??= new Queue(QueueName.ALCHEMY, { connection:
getConnection() });` inside getAlchemyQueue and then return _alchemyQueue. This
keeps the same behavior while satisfying ESLint.
In `@server/utils/redis.ts`:
- Around line 5-11: getRedis currently reads process.env.REDIS_URL directly
which breaks the config-once/DI guideline; update getRedis to accept an optional
redisUrl parameter (e.g., function getRedis(redisUrl?: string): Redis) and use
that value first, falling back to process.env.REDIS_URL only if the parameter is
undefined, or alternatively add a comment above getRedis explaining this
deliberate runtime env access for lazy singleton testability; reference the
getRedis function and the redis variable/Redis constructor in your change so
tests can inject a URL instead of relying on process.env.
81c87fd to
f909d98
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.
f909d98 to
12559ec
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: 4
🤖 Fix all issues with AI agents
In `@server/api/auth/authentication.ts`:
- Line 11: Reorder the imports so that the import from "hono/cookie" (setCookie,
setSignedCookie) appears before the import from "hono-openapi/valibot" to
satisfy the perfectionist/sort-imports linter rule; locate the import lines that
reference setCookie and setSignedCookie and move that entire import statement
above the import(s) from "hono-openapi/valibot" (e.g., the validateBody or
related valibot symbols) so import ordering is corrected.
- Around line 340-350: Replace the fragile string-name check for the custom
error with an instanceof check: import the WebhookNotReadyError class from the
module that defines it (e.g., createCredential) and update the catch block to
test "if (error instanceof WebhookNotReadyError)" before calling
captureException and returning the 503 response via c.json; keep the existing
captureException call and response body intact so behavior doesn't change
besides the safer type check.
In `@server/api/auth/registration.ts`:
- Around line 372-379: Replace the fragile name-check in the registration error
handler with a proper class check: import the WebhookNotReadyError symbol
alongside createCredential (from the module that exports it) and change the
condition from `error instanceof Error && error.name === "WebhookNotReadyError"`
to `error instanceof WebhookNotReadyError`; keep the same captureException and
response behavior when that instance check matches.
In `@server/queues/alchemyQueue.ts`:
- Around line 125-134: The close() function currently spin-waits on the boolean
isInitializing using a setTimeout loop which can race and is inefficient;
replace this with a Promise-based synchronization: introduce an initPromise set
when initialization starts (e.g., in initializeWorker or wherever alchemyWorker
is created) and resolve it when initialization completes, then have close()
await initPromise instead of looping, and finally call await
Promise.all([alchemyWorker?.close() ?? Promise.resolve(), _alchemyQueue?.close()
?? Promise.resolve()]) and await connection?.quit() as before so close()
reliably waits for initialization to finish before closing.
12559ec to
2ce113f
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: 3
🤖 Fix all issues with AI agents
In `@server/queues/alchemyQueue.ts`:
- Around line 78-86: The initializeWorker function can leave isInitializing true
if new Worker(...) throws; wrap the worker creation and handler attachment in a
try/finally so isInitializing is reset in the finally block, and ensure you only
attach event handlers/releases (for example those referencing alchemyWorker or
close()) after alchemyWorker is successfully created; update references to
alchemyWorker, isInitializing, initializeWorker, and any handler attachment
logic accordingly so failures don’t leave isInitializing stuck true.
In `@server/test/mocks/sentry.ts`:
- Around line 6-19: The mock for `@sentry/node` is missing the withScope export
used by server/hooks/activity.ts; add a withScope property to the object
returned by vi.mock that accepts a callback and invokes it with a mock scope
(e.g., an object with setContext/setUser/setTag or minimal methods used by the
code) so tests can call withScope(fn) and fn receives a scope; ensure the mock
uses vi.fn() for withScope to preserve spyability and mirrors the existing style
(similar to startSpan.mockImplementation).
In `@server/utils/createCredential.ts`:
- Around line 73-79: The queued job payload in createCredential.ts includes
credentialId but the AlchemyJobData type and the ADD_SUBSCRIBER processor
(alchemyQueue.ts) only expect account and webhookId; either remove credentialId
from the payload when calling getAlchemyQueue().add(AlchemyJob.ADD_SUBSCRIBER,
...) or update the AlchemyJobData type to include credentialId and adjust the
processor to destructure/use it for tracing; update the code path that calls
getAlchemyQueue and the AlchemyJobData declaration accordingly so the payload
and type/processor stay aligned (referencing getAlchemyQueue,
AlchemyJob.ADD_SUBSCRIBER, AlchemyJobData, and the ADD_SUBSCRIBER handler in
alchemyQueue.ts).
2ce113f to
e5a9612
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.
| @@ -358,16 +358,25 @@ export default new Hono() | |||
| await redis.del(sessionId); | |||
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.
🔴 Session deleted before createCredential call makes retry mechanism ineffective in registration flow
When WebhookNotReadyError is thrown during registration, the user receives a 503 response asking them to retry. However, the session has already been deleted in the finally block at line 358, so the retry will fail with a "no registration" error.
Click to expand
Flow analysis
- User submits registration
- Attestation is verified in the try block (lines 311-353)
- Session is deleted in the finally block (line 358):
await redis.del(sessionId); createCredentialis called (line 362)- If
WebhookNotReadyErroris thrown (whenwebhookIdis undefined), user gets 503 with "service temporarily unavailable, please retry" message - User retries, but session is gone, so they get "no registration" error at line 308
Actual vs Expected
Actual: Session is deleted before createCredential is called, so if WebhookNotReadyError is thrown, the user cannot retry because their session is gone.
Expected: Session should be preserved when WebhookNotReadyError is thrown so the user can actually retry the registration.
Impact
Users who attempt to register when the Alchemy webhook is not yet initialized will receive a 503 error asking them to retry, but the retry will always fail because the session has been deleted. This defeats the purpose of the retry mechanism added in this PR.
Recommendation: Move the redis.del(sessionId) call to after createCredential succeeds, or only delete the session when not throwing WebhookNotReadyError. For example, add a separate try-catch around createCredential that only deletes the session on success or non-retriable errors.
Was this helpful? React with 👍 or 👎 to provide feedback.
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: 1
🤖 Fix all issues with AI agents
In `@server/queues/alchemyQueue.ts`:
- Around line 5-6: The import order is reversed and fails the
perfectionist/sort-imports rule; move the import of AlchemyJob and QueueName
from "./constants" so it appears before the import of headers from
"../utils/alchemy" (i.e., import { AlchemyJob, QueueName } from "./constants"
before import { headers } from "../utils/alchemy") to satisfy the linter.
e5a9612 to
d21e7e7
Compare
| function getConnection(): Redis { | ||
| if (!connection) { | ||
| if (!process.env.REDIS_URL) throw new Error("REDIS_URL environment variable is not set"); | ||
| connection = new Redis(process.env.REDIS_URL, { maxRetriesPerRequest: null }); |
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.
Bug: The shared Redis connection uses maxRetriesPerRequest: null, which is suitable for Workers but causes Queue operations to hang indefinitely during outages instead of failing fast.
Severity: MEDIUM
Suggested Fix
Create two separate Redis connections. One for the Queue with a fail-fast setting like maxRetriesPerRequest: 1, and a separate one for the Worker with maxRetriesPerRequest: null to allow it to wait indefinitely for reconnections. Update the getAlchemyQueue and initializeWorker functions to use their respective connections.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: server/queues/alchemyQueue.ts#L18
Potential issue: A single Redis connection is created with `maxRetriesPerRequest: null`
and shared by both the BullMQ `Queue` and `Worker`. While this setting is correct for
the `Worker`, it is incorrect for the `Queue`. According to BullMQ best practices,
`Queue` operations like `queue.add()` should fail fast if Redis is unavailable. The
current configuration will cause these operations to retry indefinitely, leading to an
accumulation of unresolved promises in memory during a Redis outage. This can cause
memory pressure and delays error reporting, although existing `.catch()` handlers
prevent the user-facing request from failing.
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: 2
🤖 Fix all issues with AI agents
In `@server/test/mocks/alchemy.ts`:
- Around line 12-20: The TypeScript error is caused by calls to
mockResolvedValue() with no argument; update the mock so Promise-returning mocks
use mockResolvedValue(undefined) and void-returning functions use plain vi.fn():
specifically change getAlchemyQueue().add and its inner close to use
mockResolvedValue(undefined) (or pass undefined) if they return promises, but
change initializeWorker to vi.fn() (no mockResolvedValue) because
initializeWorker returns void; likewise review top-level close/processor and
replace mockResolvedValue() with mockResolvedValue(undefined) only where they
are async/Promise-returning.
In `@server/utils/createCredential.ts`:
- Around line 25-34: Remove the JSDoc comment block above the
WebhookNotReadyError class: delete the three-line comment describing the error
so only the class declaration (export class WebhookNotReadyError extends Error {
... }) and its constructor remain; leave the class name and message intact (they
are self-documenting) and ensure no other comments are added around the
WebhookNotReadyError symbol or its constructor.
| /** | ||
| * error thrown when alchemy webhook is not yet initialized. | ||
| * signals that credential creation should be retried after webhook setup completes. | ||
| */ | ||
| export class WebhookNotReadyError extends Error { | ||
| constructor() { | ||
| super("alchemy webhook not initialized yet, retry credential creation"); | ||
| this.name = "WebhookNotReadyError"; | ||
| } | ||
| } |
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 | 🟠 Major
Remove JSDoc comment block.
Per coding guidelines, explanatory comments and JSDoc are not allowed. The error class name and message are self-documenting.
Proposed fix
-/**
- * error thrown when alchemy webhook is not yet initialized.
- * signals that credential creation should be retried after webhook setup completes.
- */
export class WebhookNotReadyError extends Error {
constructor() {
super("alchemy webhook not initialized yet, retry credential creation");
this.name = "WebhookNotReadyError";
}
}🤖 Prompt for AI Agents
In `@server/utils/createCredential.ts` around lines 25 - 34, Remove the JSDoc
comment block above the WebhookNotReadyError class: delete the three-line
comment describing the error so only the class declaration (export class
WebhookNotReadyError extends Error { ... }) and its constructor remain; leave
the class name and message intact (they are self-documenting) and ensure no
other comments are added around the WebhookNotReadyError symbol or its
constructor.
c71e562 to
d4d2407
Compare
closes #177
Summary by CodeRabbit
New Features
Bug Fixes
Improvements
Tests