Skip to content

Conversation

@adityachoudhari26
Copy link
Contributor

@adityachoudhari26 adityachoudhari26 commented Apr 16, 2025

Summary by CodeRabbit

  • Refactor

    • Streamlined release target computations by removing intermediate replacement utilities and directly computing targets via new builder classes.
    • Simplified worker and API handler logic by eliminating redundant selector replacements and consolidating exit hook dispatching.
    • Enhanced database schema with new relations to better represent computed resources for environments and deployments.
    • Modularized selector replacement logic into private hooks for improved clarity and maintainability.
  • Chores

    • Removed deprecated utility functions and files related to release target replacement and exit hook dispatching.
    • Cleaned up imports and exports to reflect removal of obsolete modules.
  • Bug Fixes

    • Improved consistency in release target updates and exit event dispatching to prevent stale resource-deployment associations.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Apr 16, 2025

Caution

Review failed

The pull request is closed.

"""

Walkthrough

This change removes the legacy replaceReleaseTargets utility and all related direct release target manipulation logic across resource, environment, and deployment workers. Instead, it introduces a new ResourceBuilder class in the selector compute module, which orchestrates release target recomputation for resources in a workspace, coordinating environment, deployment, and policy computations. Workers and API endpoints are refactored to use this new selector-based approach, simplifying the control flow and eliminating explicit resource and policy selector recomputation. Several schema and relation definitions are updated to support these new patterns, and related obsolete files and exports are deleted.

Changes

File(s) Change Summary
apps/event-worker/src/utils/replace-release-targets.ts,
packages/job-dispatch/src/resource/replace-release-targets.ts,
apps/event-worker/src/workers/updated-resources/dispatch-exit-hooks.ts
Deleted legacy utilities for replacing release targets and dispatching exit hooks.
apps/event-worker/src/workers/new-deployment.ts,
apps/event-worker/src/workers/new-resource.ts,
apps/event-worker/src/workers/update-deployment.ts,
apps/event-worker/src/workers/update-environment.ts,
apps/event-worker/src/workers/update-resource-variable.ts,
apps/event-worker/src/workers/updated-resources/index.ts
Refactored workers to use the new selector-based ResourceBuilder for release target computation; removed explicit resource and policy selector recomputation and direct calls to legacy utilities.
apps/pty-proxy/src/controller/agent-socket.ts,
apps/webservice/src/app/api/v1/resources/[resourceId]/route.ts,
apps/webservice/src/app/api/v1/resources/route.ts,
packages/job-dispatch/src/resource/handle-provider-scan.ts
Removed imports and usage of legacy release target replacement utilities and selector recomputation logic from resource update and creation flows.
packages/db/src/schema/deployment.ts,
packages/db/src/schema/environment-relations.ts
Added and updated ORM relations for computed deployment and environment resources to support new selector compute flows.
packages/db/src/selectors/compute/compute.ts Added resources(resources: schema.Resource[]): ResourceBuilder to ComputeBuilder for resource-scoped computations; updated method signatures for environments and deployments.
packages/db/src/selectors/compute/resource-builder.ts New file: Introduced ResourceBuilder to manage resource-scoped release target computations and related hooks.
packages/db/src/selectors/compute/deployment-builder.ts,
packages/db/src/selectors/compute/environment-builder.ts,
packages/db/src/selectors/compute/policy-builder.ts
Refactored to extract inline async functions into private methods for modularity; replaced ReplaceBuilder usage with explicit transactional logic.
packages/db/src/selectors/compute/replace-builder.ts Deleted ReplaceBuilder class that previously encapsulated transactional replace operations.
packages/job-dispatch/src/resource/index.ts Removed export of the deleted replace-release-targets module.

Sequence Diagram(s)

sequenceDiagram
    participant Worker
    participant ComputeBuilder
    participant ResourceBuilder
    participant DB
    participant PolicyBuilder

    Worker->>ComputeBuilder: .resources([resourceId])
    ComputeBuilder->>ResourceBuilder: new ResourceBuilder(tx, [resourceId])
    ResourceBuilder->>DB: Validate resources in one workspace
    ResourceBuilder->>ResourceBuilder: _preHook()
    ResourceBuilder->>DB: Delete previous release targets for resources
    ResourceBuilder->>DB: Query environments, deployments, selectors
    ResourceBuilder->>DB: Insert new release targets
    ResourceBuilder->>ResourceBuilder: _postHook()
    ResourceBuilder->>PolicyBuilder: Trigger policy selector recomputation
    ResourceBuilder-->>Worker: Return new release targets
    Worker->>DB: Enqueue evaluation jobs for release targets
Loading

Possibly related PRs

Suggested reviewers

  • jsbroks

Poem

In the warren of code, old paths swept away,
A Builder emerges to brighten the day.
No more tangled hooks or targets replaced—
Now selectors compute, and logic is graced.
Relations are mapped, the schema anew,
The rabbits all cheer, for the system just grew!
🐇✨
"""


📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae254d5 and 5d0287c.

📒 Files selected for processing (1)
  • apps/event-worker/src/workers/new-deployment.ts (2 hunks)

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

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

Documentation and Community

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🔭 Outside diff range comments (2)
packages/job-dispatch/src/resource/handle-provider-scan.ts (1)

47-48: 💡 Verification agent

🧩 Analysis chain

Verify that event triggers maintain the same behavior.

The code now relies on the event queue system (NewResource and UpdatedResource channels) to trigger subsequent processing. Ensure that the ResourceBuilder is invoked appropriately when these events are processed.


🏁 Script executed:

#!/bin/bash
# Let's check how NewResource and UpdatedResource events are handled

# Look for event handlers for NewResource channel
echo "Checking NewResource event handlers:"
rg -A 5 "Channel.NewResource" --glob "apps/event-worker/src/**/*.ts"

# Look for event handlers for UpdatedResource channel
echo "Checking UpdatedResource event handlers:"
rg -A 5 "Channel.UpdatedResource" --glob "apps/event-worker/src/**/*.ts"

# Check if these handlers use the ResourceBuilder
echo "Checking for ResourceBuilder usage in event handlers:"
rg -A 5 "ResourceBuilder" --glob "apps/event-worker/src/**/*.ts"

Length of output: 2293


Ensure ResourceBuilder is invoked when processing queued events

The handlers for Channel.NewResource and Channel.UpdatedResource currently process incoming resources but don’t call ResourceBuilder, which may break existing build logic. After you fetch or compute the resource, import and invoke ResourceBuilder.build(resource) (or the appropriate method) in both workers.

Files to update:
- apps/event-worker/src/workers/new-resource.ts
- apps/event-worker/src/workers/updated-resources/index.ts

Example diff in each worker:

 import { createWorker } from "../lib/worker";
+import { ResourceBuilder } from "packages/resource-builder";

 export const newResourceWorker = createWorker(
   Channel.NewResource,
   async ({ data: resource }) => {
+    await ResourceBuilder.build(resource);
     const releaseTargets = await selector()
       .compute()
       .resources([resource.id])
       .releaseTargets();
     …

Make the analogous change in updatedResourceWorker so that every new or updated resource triggers ResourceBuilder.

apps/webservice/src/app/api/v1/resources/route.ts (1)

85-86: 💡 Verification agent

🧩 Analysis chain

Verify that event triggers maintain the same behavior.

The code now relies on the event queue system (NewResource and UpdatedResource channels) to trigger subsequent processing. Ensure that the ResourceBuilder is invoked appropriately when these events are processed.


🏁 Script executed:

#!/bin/bash
# Let's check the connection between these queue events and the ResourceBuilder

# First, let's verify how these events are processed
echo "Checking queue processors for NewResource and UpdatedResource:"
rg "Channel\.(NewResource|UpdatedResource)" --glob "apps/event-worker/src/workers/**/*.ts"

# Then, check if the ResourceBuilder is used in the processing
echo "Checking ResourceBuilder usage in related workers:"
rg "ResourceBuilder" --glob "apps/event-worker/src/workers/**/*.ts"

# Let's also check for any potentially missed selector.resources() calls
echo "Checking for selector.resources() usage:"
rg "selector.*resources\(" --glob "apps/**/*.ts" --glob "packages/**/*.ts"

Length of output: 1020


Ensure ResourceBuilder is invoked in your queue processors

It looks like you’ve wired up the NewResource and UpdatedResource channels to newResourceWorker and updatedResourceWorker (in apps/event-worker/src/workers/index.ts), but neither handler actually calls the ResourceBuilder. To preserve your existing post‐insert/update logic, please integrate the builder in both places:

• apps/event-worker/src/workers/new-resource.ts
• apps/event-worker/src/workers/updated-resources/index.ts

For example, at the end of each handler:

 import { /* … */ } from '...';
+ import { ResourceBuilder } from 'path/to/ResourceBuilder';
 
 export const newResourceWorker = async (job) => {
   // existing logic…
+  await ResourceBuilder.build(job.data);
 };

And similarly in the updated‐resources worker. Don’t forget to add or update tests to cover these flows.

🧹 Nitpick comments (13)
apps/event-worker/src/workers/update-resource-variable.ts (1)

41-49: Consider running the read & recompute in the same transaction

db.query.resource.findFirst and the subsequent selector().compute() call run in two separate implicit transactions.
Between these calls another worker could mutate release targets, resulting in a stale currentDeployments list and therefore missed or spurious exit‑hooks. Executing both steps inside an explicit transaction (and passing the same tx to selector()) would guarantee consistency.

packages/db/src/schema/environment-relations.ts (1)

24-25: Explicitly declare the join fields for computedResources

For many‑to‑one relations Drizzle can usually infer the FK, but being explicit avoids silent breakage when the FK or PK changes:

-  computedResources: many(computedEnvironmentResource),
+  computedResources: many(computedEnvironmentResource, {
+    fields: [environment.id],
+    references: [computedEnvironmentResource.environmentId],
+  }),

It also improves IntelliSense & type‑safety.

apps/event-worker/src/workers/new-deployment.ts (2)

34-37: Guard against empty resource list

If resourceIds ends up empty (e.g., no computed resources yet), .resources([]) may throw or generate a no‑op SQL that still triggers hooks. Early‑returning here avoids pointless work and queue noise.

if (resourceIds.length === 0) {
  log.debug("No resources to upsert release targets for");
  return;
}

34-41: Pass a transaction to keep reads & writes consistent

Similar to the update‑resource‑variable worker, consider wrapping:

  1. the environment.findMany read, and
  2. the selector().compute().resources(...).releaseTargets().replace() mutation

in a single transaction (db.transaction()), then supply the tx to selector(tx).
This prevents race conditions where other workers alter selectors between the read and the write.

packages/db/src/selectors/compute/deployment-builder.ts (2)

19-23: Guard against empty ids to avoid invalid IN () SQL

If this.ids is empty the generated WHERE IN () clause can throw in some SQL engines.
A quick early‑return prevents an unnecessary round‑trip.

-private async _deletePrevious(tx: Tx) {
-  await tx
-    .delete(SCHEMA.computedDeploymentResource)
-    .where(inArray(SCHEMA.computedDeploymentResource.deploymentId, this.ids));
+private async _deletePrevious(tx: Tx) {
+  if (this.ids.length === 0) return;
+  await tx
+    .delete(SCHEMA.computedDeploymentResource)
+    .where(inArray(SCHEMA.computedDeploymentResource.deploymentId, this.ids));
 }

31-50: N + 1 query pattern—consider batching for scalability

_values performs one SELECT … FROM resource per deployment.
In a workspace with hundreds of deployments this becomes an N + 1 pattern and slows down the replace cycle.

Options:

  1. Build one combined query that evaluates all selectors in SQL (preferred, but complex).
  2. Chunk deployments and execute with Promise.allSettled + concurrency limit to avoid flooding the pool.
  3. Cache selector → resource set mappings inside the loop.

Even a modest optimisation (e.g. p‑map with concurrency 10) would reduce DB stress noticeably.

apps/event-worker/src/workers/update-environment.ts (1)

74-81: Duplicate exit‑hook events

exitedResources may contain duplicates because currentResources mirrors currentReleaseTargets one‑to‑one.
As with the resource worker, deduplicate before dispatching to avoid event storms.

-const exitedResources = currentResources.filter(…);
+const exitedResources = _.uniqBy(
+  currentResources.filter(…),
+  "id",
+);
packages/db/src/selectors/compute/resource-builder.ts (3)

10-17: Avoid defaulting to an empty workspaceId – use undefined and assert later

"" is a valid (albeit unusual) string value; using it to represent “not‑initialised” can mask accidental bugs during debugging or type‑narrowing. Prefer undefined or make the field private workspaceId!: string; and rely on the type‑checker to ensure it’s always set by _preHook.

-  private workspaceId: string;
+  private workspaceId!: string;
...
-    this.workspaceId = "";

38-42: Possible N+1 delete & missed deduplication

DELETE … WHERE id IN (<array>) is fine, but if the caller accidentally provides duplicates you still only issue one statement. However the query planner may build a very large IN (…) list.
Use Array.from(new Set(this.resourceIds)) to deduplicate, or switch to DELETE FROM … WHERE resource_id = ANY($1::uuid[]).


94-97: Policy recomputation may become a hotspot

Triggering a full workspace policy recomputation (policyComputer.releaseTargetSelectors().replace()) for every resource‑level change can lead to quadratic behaviour in busy workspaces.
Consider debouncing via a job queue or at least limiting the recomputation to policies that reference one of this.resourceIds.

apps/event-worker/src/workers/update-deployment.ts (1)

60-61: Dispatching exit hooks outside a DB transaction may violate ordering guarantees

dispatchExitHooks fires after the replacement transaction has already committed (good), but there is still a window where the worker crashes after committing but before dispatching, causing hooks to be dropped.

Consider publishing the exit‑hook events inside the same transaction to a durable outbox table and letting a separate relay push them to the event bus (transactional outbox pattern).

packages/db/src/selectors/compute/policy-builder.ts (2)

16-28: Inefficient deletion strategy

You first fetch targets only to delete rows whose policyTargetId matches.
Postgres supports DELETE … USING:

DELETE FROM computed_policy_target_release_target c
USING policy_target p
WHERE p.policy_id = ANY($1)
  AND c.policy_target_id = p.id;

which avoids the round‑trip and the large IN (…) list.
Consider porting this pattern with Drizzle’s sql.raw.


117-161: Potentially heavy per‑target queries – batch instead

For each policy target you run an independent three‑way join over releaseTarget × resource × deployment × environment.
On large workspaces this becomes O(n × joins). You can:

  1. Build a single SELECT … WHERE policy_target_id IN (…) with OR‑combined selector predicates.
  2. Or at least run the inner loop in batches instead of Promise.all(targets.map(...)).

This will materially reduce round‑trips and planning time.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 12e599c and 1cc7a30.

📒 Files selected for processing (22)
  • apps/event-worker/src/utils/replace-release-targets.ts (0 hunks)
  • apps/event-worker/src/workers/new-deployment.ts (2 hunks)
  • apps/event-worker/src/workers/new-resource.ts (1 hunks)
  • apps/event-worker/src/workers/update-deployment.ts (3 hunks)
  • apps/event-worker/src/workers/update-environment.ts (2 hunks)
  • apps/event-worker/src/workers/update-resource-variable.ts (2 hunks)
  • apps/event-worker/src/workers/updated-resources/dispatch-exit-hooks.ts (0 hunks)
  • apps/event-worker/src/workers/updated-resources/index.ts (2 hunks)
  • apps/pty-proxy/src/controller/agent-socket.ts (1 hunks)
  • apps/webservice/src/app/api/v1/resources/[resourceId]/route.ts (1 hunks)
  • apps/webservice/src/app/api/v1/resources/route.ts (1 hunks)
  • packages/db/src/schema/deployment.ts (3 hunks)
  • packages/db/src/schema/environment-relations.ts (3 hunks)
  • packages/db/src/selectors/compute/compute.ts (2 hunks)
  • packages/db/src/selectors/compute/deployment-builder.ts (2 hunks)
  • packages/db/src/selectors/compute/environment-builder.ts (2 hunks)
  • packages/db/src/selectors/compute/policy-builder.ts (2 hunks)
  • packages/db/src/selectors/compute/replace-builder.ts (1 hunks)
  • packages/db/src/selectors/compute/resource-builder.ts (1 hunks)
  • packages/job-dispatch/src/resource/handle-provider-scan.ts (1 hunks)
  • packages/job-dispatch/src/resource/index.ts (0 hunks)
  • packages/job-dispatch/src/resource/replace-release-targets.ts (0 hunks)
💤 Files with no reviewable changes (4)
  • packages/job-dispatch/src/resource/index.ts
  • packages/job-dispatch/src/resource/replace-release-targets.ts
  • apps/event-worker/src/workers/updated-resources/dispatch-exit-hooks.ts
  • apps/event-worker/src/utils/replace-release-targets.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{ts,tsx}`: **Note on Error Handling:** Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error...

**/*.{ts,tsx}: Note on Error Handling:
Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error handling. These are acceptable as long as they maintain clarity and predictability.

  • packages/db/src/selectors/compute/replace-builder.ts
  • packages/db/src/selectors/compute/compute.ts
  • apps/webservice/src/app/api/v1/resources/route.ts
  • apps/pty-proxy/src/controller/agent-socket.ts
  • apps/event-worker/src/workers/new-resource.ts
  • packages/job-dispatch/src/resource/handle-provider-scan.ts
  • packages/db/src/schema/deployment.ts
  • apps/event-worker/src/workers/new-deployment.ts
  • packages/db/src/schema/environment-relations.ts
  • apps/event-worker/src/workers/update-resource-variable.ts
  • apps/event-worker/src/workers/updated-resources/index.ts
  • packages/db/src/selectors/compute/resource-builder.ts
  • packages/db/src/selectors/compute/policy-builder.ts
  • packages/db/src/selectors/compute/deployment-builder.ts
  • apps/event-worker/src/workers/update-environment.ts
  • apps/webservice/src/app/api/v1/resources/[resourceId]/route.ts
  • packages/db/src/selectors/compute/environment-builder.ts
  • apps/event-worker/src/workers/update-deployment.ts
🧬 Code Graph Analysis (11)
packages/db/src/selectors/compute/replace-builder.ts (1)
packages/db/src/common.ts (1)
  • Tx (22-22)
packages/db/src/selectors/compute/compute.ts (1)
packages/db/src/selectors/compute/resource-builder.ts (1)
  • ResourceBuilder (10-109)
apps/event-worker/src/workers/new-resource.ts (3)
packages/db/src/selectors/compute/resource-builder.ts (1)
  • releaseTargets (99-108)
packages/db/src/selectors/index.ts (1)
  • selector (18-18)
packages/db/src/schema/resource.ts (1)
  • resource (59-87)
packages/db/src/schema/deployment.ts (1)
packages/db/src/schema/resource.ts (1)
  • resource (59-87)
apps/event-worker/src/workers/new-deployment.ts (5)
packages/db/src/selectors/compute/compute.ts (1)
  • environments (20-22)
packages/db/src/selectors/query/builder.ts (1)
  • environments (28-33)
packages/db/src/client.ts (1)
  • db (15-15)
packages/db/src/selectors/compute/resource-builder.ts (1)
  • releaseTargets (99-108)
packages/db/src/selectors/index.ts (1)
  • selector (18-18)
packages/db/src/selectors/compute/resource-builder.ts (6)
packages/db/src/common.ts (1)
  • Tx (22-22)
packages/db/src/selectors/compute/compute.ts (1)
  • resources (40-42)
packages/db/src/selectors/compute/environment-builder.ts (1)
  • WorkspaceEnvironmentBuilder (85-139)
packages/db/src/selectors/compute/deployment-builder.ts (1)
  • WorkspaceDeploymentBuilder (83-137)
packages/db/src/selectors/compute/policy-builder.ts (1)
  • WorkspacePolicyBuilder (91-175)
packages/db/src/selectors/compute/replace-builder.ts (1)
  • ReplaceBuilder (6-31)
packages/db/src/selectors/compute/policy-builder.ts (4)
packages/db/src/common.ts (1)
  • Tx (22-22)
packages/db/src/selectors/query/builder.ts (1)
  • QueryBuilder (19-45)
packages/db/src/selectors/compute/resource-builder.ts (1)
  • releaseTargets (99-108)
packages/db/src/selectors/compute/replace-builder.ts (1)
  • ReplaceBuilder (6-31)
packages/db/src/selectors/compute/deployment-builder.ts (4)
packages/db/src/common.ts (1)
  • Tx (22-22)
packages/db/src/selectors/compute/compute.ts (2)
  • deployments (28-30)
  • resources (40-42)
packages/db/src/selectors/query/builder.ts (2)
  • deployments (21-26)
  • resources (35-40)
packages/db/src/selectors/compute/replace-builder.ts (1)
  • ReplaceBuilder (6-31)
apps/event-worker/src/workers/update-environment.ts (4)
packages/db/src/client.ts (1)
  • db (15-15)
packages/db/src/schema/environment.ts (1)
  • environment (59-84)
packages/db/src/selectors/index.ts (1)
  • selector (18-18)
packages/events/src/index.ts (1)
  • getQueue (28-34)
packages/db/src/selectors/compute/environment-builder.ts (3)
packages/db/src/common.ts (1)
  • Tx (22-22)
packages/db/src/selectors/query/builder.ts (1)
  • resources (35-40)
packages/db/src/selectors/compute/replace-builder.ts (1)
  • ReplaceBuilder (6-31)
apps/event-worker/src/workers/update-deployment.ts (3)
packages/db/src/client.ts (1)
  • db (15-15)
packages/db/src/selectors/index.ts (1)
  • selector (18-18)
packages/events/src/index.ts (1)
  • getQueue (28-34)
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Typecheck
  • GitHub Check: Lint
  • GitHub Check: build (linux/amd64)
🔇 Additional comments (25)
packages/db/src/selectors/compute/compute.ts (2)

11-11: LGTM: New import for ResourceBuilder from a relative path.

The new import correctly references the resource-builder.js file that contains the ResourceBuilder class.


39-42: LGTM: Added new method for resource-specific computations.

The new resources(ids: string[]) method follows the established pattern of other builder methods in this class (environments, deployments, policies), providing a consistent interface for resource-specific selector computations.

packages/db/src/selectors/compute/replace-builder.ts (5)

10-10: LGTM: Added preHook parameter to the constructor.

Adding this lifecycle hook parameter allows for operations to be performed before the main database operations, enhancing the flexibility of the ReplaceBuilder.


13-13: LGTM: Added postHook parameter to the constructor.

Adding this lifecycle hook parameter allows for operations to be performed after the main database operations, completing the pre/post hook pattern.


18-18: LGTM: Calling preHook before database operations.

The preHook is now properly awaited before proceeding with deletion of previous entries, allowing for any necessary setup operations.


21-22: LGTM: Early return for empty values.

This optimization returns an empty array when there are no values to insert, avoiding an unnecessary database operation.


27-28: LGTM: Calling postHook and returning results.

The postHook is properly awaited after the database insertion, and the results are correctly returned from the transaction.

packages/job-dispatch/src/resource/handle-provider-scan.ts (2)

4-4: LGTM: Simplified import for upsertResources.

The import for upsertResources has been simplified, removing unnecessary imports that are no longer used after the refactoring.


35-38: Implementation follows the new selector pattern.

The resource upsert operations are now using the simplified approach without explicit selector computations. This aligns with the refactoring to move release target updates to the ResourceBuilder in the selector utility.

apps/webservice/src/app/api/v1/resources/route.ts (2)

5-5: LGTM: Simplified imports.

The imports have been simplified to remove the selector and replaceReleaseTargetsAndDispatchExitHooks utilities that are no longer used, keeping only the necessary imports for the refactored implementation.

Also applies to: 9-9


72-75: Implementation follows the new selector pattern.

The resource upsert operations now use the simplified approach without explicit selector computations. This aligns with the refactoring to move release target updates to the ResourceBuilder in the selector utility.

apps/pty-proxy/src/controller/agent-socket.ts (2)

12-12: Import simplification reflects architectural improvements

The simplified import statement is part of the larger refactoring effort that removes explicit resource selector and release target manipulation from this class. By removing the selector import, the code now relies on the event queue system to handle release target computation through the new ResourceBuilder class.


128-146: Simplification of resource update logic improves separation of concerns

The updateResource method has been simplified to focus solely on its core responsibilities: upserting resources, adding the updated resource to the event queue, and maintaining internal state. The previous implementation likely contained explicit selector recomputation and release target replacement logic that has been removed.

This change properly delegates the complex computation of release targets to the event worker system, adhering to better separation of concerns.

apps/webservice/src/app/api/v1/resources/[resourceId]/route.ts (2)

5-5: Import simplification reflects architectural improvements

The imports have been updated to remove reference to selector and release target replacement utilities, aligning with the architectural changes in this PR where release target computation is now handled by the new ResourceBuilder class.

Also applies to: 8-8


101-107: PATCH handler simplification improves API endpoint focus

The PATCH handler has been simplified to focus on its primary responsibility: upserting resources and queuing update events. The removed code likely contained explicit selector recomputation and release target replacement logic.

This change properly delegates the complex computation of release targets to the event worker system, resulting in a cleaner and more maintainable API endpoint with better separation of concerns.

apps/event-worker/src/workers/new-resource.ts (2)

21-25: Improved release target computation with fluent selector API

The worker now uses a concise and elegant fluent interface to compute release targets directly for the specific resource. This replaces more complex logic that likely involved separate steps for recomputing environment and deployment resource selectors.

The new approach leverages the ResourceBuilder class (referenced in the AI summary) which encapsulates the release target computation logic, making the worker code more focused and maintainable.


26-26: Consistent pattern for handling release targets

The updated code maintains the same pattern for mapping release targets to evaluation jobs, ensuring backwards compatibility while benefiting from the improved release target computation logic.

packages/db/src/schema/deployment.ts (2)

115-115: Enhanced deployment relations support resource computation

Adding the computedResources relation to the deploymentRelations establishes a proper many-to-many relationship between deployments and resources through the computedDeploymentResource table. This provides the necessary schema support for the new selector-based approach to resource management.


131-143: Complete bidirectional relation mapping improves ORM functionality

The new computedDeploymentResourceRelations establishes proper bidirectional relationships between the junction table and both the deployment and resource tables. This enables the ORM to navigate relationships in both directions, which is essential for the ResourceBuilder to efficiently coordinate resource-related computations.

This schema enhancement provides the foundation for the refactored release target computation approach.

packages/db/src/schema/environment-relations.ts (1)

37-49: Verify this relation is re‑exported

computedEnvironmentResourceRelations is defined here but consumers must be able to import it from packages/db/src/schema/index.ts (or the relevant barrel). Make sure it is re‑exported, otherwise downstream selectors will fail at runtime.

packages/db/src/selectors/compute/policy-builder.ts (1)

30-75: Null selector handling may yield invalid SQL

qb.resources().where(t.resourceSelector).sql() blindly receives null.
Unless WhereBuilder.where() explicitly converts null into a tautology, the generated SQL becomes … AND (NULL) which Postgres treats as false, silently excluding rows.

Either:

const resourceCond =
  t.resourceSelector == null ? sql`TRUE` : qb.resources().where(t.resourceSelector).sql();

or guard earlier.

Same applies to deployment/environment selectors below.

packages/db/src/selectors/compute/environment-builder.ts (4)

17-55: Well-structured refactoring of EnvironmentBuilder with clear method separation

The extraction of inline anonymous functions into properly named private methods significantly improves code readability and maintainability. The implementation correctly:

  1. Deletes previous computed environment resources
  2. Computes new environment-resource mappings based on selectors
  3. Provides hooks for pre and post-processing

60-64: Clean implementation of ReplaceBuilder integration

These parameter updates correctly wire the newly extracted private methods to the ReplaceBuilder, providing a more maintainable and explicit approach compared to inline anonymous functions.


94-128: Good consistency in WorkspaceEnvironmentBuilder implementation

The WorkspaceEnvironmentBuilder follows the same pattern as EnvironmentBuilder, maintaining consistency across the codebase. The workspace-scoped implementation correctly:

  1. Retrieves environments in the workspace with getEnvsInWorkspace
  2. Deletes computed resources for those environments
  3. Computes new mappings based on resource selectors
  4. Provides pre and post hooks similar to EnvironmentBuilder

133-137: Consistent method references for WorkspaceEnvironmentBuilder

The update to use the new private methods maintains consistency with the EnvironmentBuilder implementation and improves readability over inline anonymous functions.

Comment on lines 25 to 32
const environments = await db.query.environment.findMany({
where: eq(schema.environment.systemId, job.data.systemId),
with: { computedResources: true },
});

const computedDeploymentResources = await db
.select()
.from(schema.computedDeploymentResource)
.innerJoin(
schema.resource,
eq(schema.computedDeploymentResource.resourceId, schema.resource.id),
)
.where(eq(schema.computedDeploymentResource.deploymentId, job.data.id));
const resources = computedDeploymentResources.map((r) => r.resource);

const releaseTargetPromises = resources.map(async (r) =>
replaceReleaseTargets(db, r),
const resourceIds = environments.flatMap((e) =>
e.computedResources.map((r) => r.resourceId),
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Deduplicate resourceIds to avoid redundant recomputations

Multiple environments often reference the same resource. Passing duplicates down the compute chain may:

  • perform identical heavy SQL work several times, and
  • cause releaseTargets().replace() to execute unnecessary INSERT/DELETE statements.
-const resourceIds = environments.flatMap((e) =>
-  e.computedResources.map((r) => r.resourceId),
-);
+const resourceIds = Array.from(
+  new Set(
+    environments.flatMap((e) =>
+      e.computedResources.map((r) => r.resourceId),
+    ),
+  ),
+);

This small change can drastically cut database load in large systems.

📝 Committable suggestion

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

Suggested change
const environments = await db.query.environment.findMany({
where: eq(schema.environment.systemId, job.data.systemId),
with: { computedResources: true },
});
const computedDeploymentResources = await db
.select()
.from(schema.computedDeploymentResource)
.innerJoin(
schema.resource,
eq(schema.computedDeploymentResource.resourceId, schema.resource.id),
)
.where(eq(schema.computedDeploymentResource.deploymentId, job.data.id));
const resources = computedDeploymentResources.map((r) => r.resource);
const releaseTargetPromises = resources.map(async (r) =>
replaceReleaseTargets(db, r),
const resourceIds = environments.flatMap((e) =>
e.computedResources.map((r) => r.resourceId),
);
const environments = await db.query.environment.findMany({
where: eq(schema.environment.systemId, job.data.systemId),
with: { computedResources: true },
});
const resourceIds = Array.from(
new Set(
environments.flatMap((e) =>
e.computedResources.map((r) => r.resourceId),
),
),
);

Comment on lines 41 to 45
name: `${rt.resourceId}-${rt.environmentId}-${rt.deploymentId}`,
data: rt,
}));
await getQueue(Channel.EvaluateReleaseTarget).addBulk(jobs);

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Job‑name format is inconsistent with other workers and may cause duplicate‐key collisions

Other workers (e.g. update‑environment.ts) use the database‑generated rt.id as the BullMQ job name, while this worker concatenates three columns.
If both patterns target the same queue (Channel.EvaluateReleaseTarget) BullMQ will treat the two variants as distinct jobs for the same release target, producing duplicates or collisions depending on the queue’s jobId strategy.

-const jobs = rts.map((rt) => ({
-  name: `${rt.resourceId}-${rt.environmentId}-${rt.deploymentId}`,
-  data: rt,
-}));
+const jobs = rts.map((rt) => ({
+  name: rt.id,            // keeps one canonical identifier across workers
+  data: rt,
+}));
📝 Committable suggestion

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

Suggested change
const jobs = rts.map((rt) => ({
name: `${rt.resourceId}-${rt.environmentId}-${rt.deploymentId}`,
data: rt,
}));
await getQueue(Channel.EvaluateReleaseTarget).addBulk(jobs);
const jobs = rts.map((rt) => ({
name: rt.id, // keeps one canonical identifier across workers
data: rt,
}));
await getQueue(Channel.EvaluateReleaseTarget).addBulk(jobs);

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (7)
apps/event-worker/src/workers/updated-resources/index.ts (2)

33-33: ⚠️ Potential issue

Fix potential duplicate events by deduplicating deployments.

currentDeployments is built with a simple .map(), meaning a deployment that owns multiple release targets will appear multiple times in the array. This can lead to duplicate "deployment.resource.removed" events being dispatched.

-const currentDeployments = currentReleaseTargets.map((rt) => rt.deployment);
+const currentDeployments = _.uniqBy(
+  currentReleaseTargets.map((rt) => rt.deployment),
+  "id",
+);

40-43: ⚠️ Potential issue

Use consistent job naming to prevent queue collisions.

The job naming pattern (${rt.resourceId}-${rt.environmentId}-${rt.deploymentId}) differs from other workers that use the database-generated rt.id as the BullMQ job name. This inconsistency could cause duplicate jobs or collisions in the queue.

-const jobs = rts.map((rt) => ({
-  name: `${rt.resourceId}-${rt.environmentId}-${rt.deploymentId}`,
-  data: rt,
-}));
+const jobs = rts.map((rt) => ({
+  name: rt.id,            // keeps one canonical identifier across workers
+  data: rt,
+}));
packages/db/src/selectors/compute/resource-builder.ts (2)

48-51: ⚠️ Potential issue

Potential race condition in resource matching deployment logic.

The check against computedDeploymentResource may exclude deployments with valid selectors if the related CDR rows weren't yet computed. Consider performing the join based on the actual selector condition rather than relying on the pre-computed table.

-const isResourceMatchingDeployment = or(
-  isNull(SCHEMA.deployment.resourceSelector),
-  eq(SCHEMA.computedDeploymentResource.resourceId, SCHEMA.resource.id),
-);
+// Use a more direct approach that evaluates the selector
+// This is a simplified example - actual implementation would depend on your query builder
+const isResourceMatchingDeployment = or(
+  isNull(SCHEMA.deployment.resourceSelector),
+  /* Your query builder expression to directly evaluate deployment.resourceSelector against resource */
+);

21-31: 💡 Verification agent

🧩 Analysis chain

Avoid nested transactions in recomputeResourceSelectors.

The replace() method of the environment and deployment builders likely creates nested transactions, which can lead to performance issues with savepoints in PostgreSQL. Consider passing the transaction object explicitly to prevent nesting.

Run this to verify if the builders' replace() methods create nested transactions:


🏁 Script executed:

#!/bin/bash
# Check if replace() methods create nested transactions
rg -A 5 "replace\(\)" --type=ts packages/db/src/selectors/compute/

Length of output: 1677


🏁 Script executed:

#!/bin/bash
# Display the ReplaceBuilder implementation to confirm nested transaction usage
cat packages/db/src/selectors/compute/replace-builder.ts

Length of output: 1036


🏁 Script executed:

#!/bin/bash
# Display the WorkspaceEnvironmentBuilder and WorkspaceDeploymentBuilder implementations
sed -n '1,200p' packages/db/src/selectors/compute/resource-builder.ts

Length of output: 3622


Prevent nested transactions in recomputeResourceSelectors

We’ve confirmed that ReplaceBuilder.replace() always wraps its logic in a new tx.transaction(), so calling it inside the releaseTargets transaction creates nested savepoints in PostgreSQL. To avoid this overhead:

• In packages/db/src/selectors/compute/replace-builder.ts, add an inline variant (e.g. replaceInline(tx: Tx)) that runs the same hooks and insert/delete logic without the tx.transaction(...) wrapper.
• In packages/db/src/selectors/compute/resource-builder.ts, update recomputeResourceSelectors to pass the existing tx into this inline method instead of calling .replace().
• Ensure both WorkspaceEnvironmentBuilder and WorkspaceDeploymentBuilder expose the new inline API.

Example diff for resource-builder.ts:

--- a/packages/db/src/selectors/compute/resource-builder.ts
+++ b/packages/db/src/selectors/compute/resource-builder.ts
   async recomputeResourceSelectors(tx: Tx) {
     const envComputer = new WorkspaceEnvironmentBuilder(tx, this.workspaceId);
     const deploymentComputer = new WorkspaceDeploymentBuilder(
       tx,
       this.workspaceId,
     );
-    await Promise.all([
-      envComputer.resourceSelectors().replace(),
-      deploymentComputer.resourceSelectors().replace(),
-    ]);
+    await Promise.all([
+      envComputer.resourceSelectors().replaceInline(tx),
+      deploymentComputer.resourceSelectors().replaceInline(tx),
+    ]);
   }
apps/event-worker/src/workers/update-deployment.ts (2)

49-54: 🛠️ Refactor suggestion

Refine exited resources calculation to prevent false positives.

The current approach might misclassify resources as "exited" if they appear in release targets for other deployments. Filter by this deployment's ID when checking for exited resources.

-const exitedResources = currentResources.filter(
-  (r) =>
-    !rts.some(
-      (rt) => rt.resourceId === r.id && rt.deploymentId === data.id,
-    ),
-);
+// More efficient approach using a Set for look-up
+const remainingResourceIds = new Set(
+  rts.filter((rt) => rt.deploymentId === data.id)
+     .map((rt) => rt.resourceId)
+);
+const exitedResources = currentResources.filter(
+  (r) => !remainingResourceIds.has(r.id)
+);

45-48: ⚠️ Potential issue

Compute release targets for newly selected resources.

The code only recomputes release targets for resources that were already attached to the deployment (currentResources). If the updated selector adds new resources, they won't be processed until another job touches them.

-const rts = await selector()
-  .compute()
-  .resources(currentResources)
-  .releaseTargets();
+// Get all resources that should be matched by the new selector
+const newlyMatchedResources = await db.query.resource.findMany({
+  where: and(
+    eq(schema.resource.workspaceId, data.workspaceId),
+    // Use an appropriate query builder to apply the resourceSelector
+    selector().queryBuilder().resources().where(resourceSelector).sql()
+  ),
+});
+// Combine current and newly matched resources for complete computation
+const allResources = _.uniqBy([...currentResources, ...newlyMatchedResources], 'id');
+const rts = await selector()
+  .compute()
+  .resources(allResources)
+  .releaseTargets();
apps/event-worker/src/workers/update-environment.ts (1)

65-68: ⚠️ Potential issue

Include newly matched resources in computation.

The computation only includes resources that were already attached to the environment. If the updated selector matches new resources, they won't be processed until another job touches them.

-const rts = await selector()
-  .compute()
-  .resources(currentResources)
-  .releaseTargets();
+// Also get resources newly matched by the updated selector
+const newlyMatchedResources = await db.query.resource.findMany({
+  where: and(
+    eq(schema.resource.workspaceId, environment.workspaceId),
+    // Use an appropriate query builder to apply the resourceSelector
+    selector().queryBuilder().resources().where(environment.resourceSelector).sql()
+  ),
+});
+// Combine current and newly matched resources for complete computation
+const allResources = _.uniqBy([...currentResources, ...newlyMatchedResources], 'id');
+const rts = await selector()
+  .compute()
+  .resources(allResources)
+  .releaseTargets();
🧹 Nitpick comments (2)
packages/db/src/selectors/compute/resource-builder.ts (1)

9-19: Add early return for empty resources array.

If this.resources is empty, the code will attempt to extract the workspace ID from an empty array, which might cause issues. Consider adding an early return or check for empty resources.

constructor(
  private readonly tx: Tx,
  private readonly resources: SCHEMA.Resource[],
) {
+  if (resources.length === 0) {
+    throw new Error("At least one resource is required");
+  }
  const workspaceIds = new Set(this.resources.map((r) => r.workspaceId));
  if (workspaceIds.size !== 1)
    throw new Error("All resources must be in the same workspace");
  this.workspaceId = Array.from(workspaceIds)[0]!;
}
apps/event-worker/src/workers/update-environment.ts (1)

73-79: Optimize exited resources calculation with Set.

The current filtering approach can be inefficient for larger arrays. Consider using a Set for O(1) lookups instead of nested array filtering.

-const exitedResources = currentResources.filter(
-  (r) =>
-    !rts.some(
-      (rt) =>
-        rt.resourceId === r.id && rt.environmentId === environment.id,
-    ),
-);
+// More efficient approach using a Set for look-up
+const remainingResourceIds = new Set(
+  rts.filter((rt) => rt.environmentId === environment.id)
+     .map((rt) => rt.resourceId)
+);
+const exitedResources = currentResources.filter(
+  (r) => !remainingResourceIds.has(r.id)
+);
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5713612 and 00cb1fb.

📒 Files selected for processing (8)
  • apps/event-worker/src/workers/new-deployment.ts (2 hunks)
  • apps/event-worker/src/workers/new-resource.ts (1 hunks)
  • apps/event-worker/src/workers/update-deployment.ts (3 hunks)
  • apps/event-worker/src/workers/update-environment.ts (2 hunks)
  • apps/event-worker/src/workers/update-resource-variable.ts (2 hunks)
  • apps/event-worker/src/workers/updated-resources/index.ts (2 hunks)
  • packages/db/src/selectors/compute/compute.ts (3 hunks)
  • packages/db/src/selectors/compute/resource-builder.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/event-worker/src/workers/new-resource.ts
  • packages/db/src/selectors/compute/compute.ts
  • apps/event-worker/src/workers/update-resource-variable.ts
  • apps/event-worker/src/workers/new-deployment.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{ts,tsx}`: **Note on Error Handling:** Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error...

**/*.{ts,tsx}: Note on Error Handling:
Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error handling. These are acceptable as long as they maintain clarity and predictability.

  • apps/event-worker/src/workers/update-environment.ts
  • packages/db/src/selectors/compute/resource-builder.ts
  • apps/event-worker/src/workers/updated-resources/index.ts
  • apps/event-worker/src/workers/update-deployment.ts
🧬 Code Graph Analysis (2)
apps/event-worker/src/workers/update-environment.ts (4)
packages/db/src/client.ts (1)
  • db (15-15)
packages/db/src/schema/environment.ts (1)
  • environment (59-84)
packages/db/src/selectors/index.ts (1)
  • selector (18-18)
packages/events/src/index.ts (1)
  • getQueue (28-34)
packages/db/src/selectors/compute/resource-builder.ts (5)
packages/db/src/common.ts (1)
  • Tx (22-22)
packages/db/src/selectors/compute/compute.ts (1)
  • resources (41-43)
packages/db/src/selectors/compute/environment-builder.ts (1)
  • WorkspaceEnvironmentBuilder (85-139)
packages/db/src/selectors/compute/deployment-builder.ts (1)
  • WorkspaceDeploymentBuilder (83-137)
packages/db/src/selectors/compute/policy-builder.ts (1)
  • WorkspacePolicyBuilder (91-175)
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: Lint
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Typecheck
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
🔇 Additional comments (5)
apps/event-worker/src/workers/updated-resources/index.ts (2)

9-20: LGTM! Inline function to dispatch exit hooks.

The new approach to dispatch exit hooks as independent promises with allSettled is a good improvement that allows the system to continue even if some event dispatches fail.


35-38: LGTM! Clean selector-based approach.

Using the selector API with the new resource-scoped computation is cleaner and more maintainable than the previous approach with explicit release target manipulation.

packages/db/src/selectors/compute/resource-builder.ts (1)

98-112: LGTM! Atomic transaction with clear phases.

Good structuring of the transaction with clear phases: recompute selectors, delete existing targets, find matches, insert new targets, and update policies. This ensures atomicity and prevents partial updates.

apps/event-worker/src/workers/update-deployment.ts (1)

40-45: LGTM! Clean resource fetching approach.

Using the join with with: { resource: true } is a clean approach to fetch the release targets and their associated resources in a single query.

apps/event-worker/src/workers/update-environment.ts (1)

59-64: LGTM! Effective resource/release target query.

The approach to query current release targets with their resources is clean and avoids unnecessary database round-trips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6d3ea3d and ecf630f.

📒 Files selected for processing (1)
  • packages/db/src/selectors/compute/environment-builder.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{ts,tsx}`: **Note on Error Handling:** Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error...

**/*.{ts,tsx}: Note on Error Handling:
Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error handling. These are acceptable as long as they maintain clarity and predictability.

  • packages/db/src/selectors/compute/environment-builder.ts
🔇 Additional comments (1)
packages/db/src/selectors/compute/environment-builder.ts (1)

19-28: Guard against empty environmentIds before generating IN (...) clause

If this.environments is an empty array, the inArray(...) call will emit an IN () predicate.
Depending on the SQL dialect / Drizzle implementation, this can either:

  • produce invalid SQL (WHERE ... IN ()) and throw at runtime, or
  • silently match zero rows but still create an unnecessary statement.

A lightweight defensive guard prevents both issues and saves an unnecessary round–trip:

-await tx
-  .delete(SCHEMA.computedEnvironmentResource)
-  .where(
-    inArray(
-      SCHEMA.computedEnvironmentResource.environmentId,
-      this.environmentIds,
-    ),
-  );
+if (this.environmentIds.length) {
+  await tx
+    .delete(SCHEMA.computedEnvironmentResource)
+    .where(
+      inArray(
+        SCHEMA.computedEnvironmentResource.environmentId,
+        this.environmentIds,
+      ),
+    );
+}

Please double‑check how drizzle-orm handles IN () and adjust accordingly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/db/src/selectors/compute/resource-builder.ts (2)

30-32: Consider adding an early check for empty resources array

While the implementation handles an empty result set later (line 118), consider adding an early return in releaseTargets() if this.resourceIds.length === 0 to avoid unnecessary database operations.

 releaseTargets() {
   return this.tx.transaction(async (tx) => {
+    if (this.resourceIds.length === 0) return [];
     await this.deleteExistingReleaseTargets(tx);
     const vals = await this.findMatchingEnvironmentDeploymentPairs(tx);
     if (vals.length === 0) return [];

1-131: Consider adding error handling for database operations

While the coding guidelines don't strictly enforce try/catch blocks, consider adding some error handling for the critical database operations, especially when multiple operations need to succeed together.

For example, if recomputePolicyReleaseTargets fails after successfully inserting release targets, you might want to log the error or take specific recovery actions.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ecf630f and 6564bf2.

📒 Files selected for processing (2)
  • packages/db/src/selectors/compute/compute.ts (3 hunks)
  • packages/db/src/selectors/compute/resource-builder.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/db/src/selectors/compute/compute.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{ts,tsx}`: **Note on Error Handling:** Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error...

**/*.{ts,tsx}: Note on Error Handling:
Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error handling. These are acceptable as long as they maintain clarity and predictability.

  • packages/db/src/selectors/compute/resource-builder.ts
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: Typecheck
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Lint
🔇 Additional comments (5)
packages/db/src/selectors/compute/resource-builder.ts (5)

20-28: LGTM: Good constructor implementation with proper validation

The constructor correctly validates that all resources belong to the same workspace and extracts the workspace ID. The error message is clear and descriptive.


40-60: Good documentation for complex matching logic

The detailed documentation for findMatchingEnvironmentDeploymentPairs clearly explains the matching conditions and the relationships between the tables being joined. The note about assuming up-to-date computed environment resource selectors is particularly helpful.


61-107: Verify deployment selector matching logic still meets requirements

The previous review raised concerns about the LEFT JOIN with computedDeploymentResource potentially filtering out deployments unintentionally. Although marked as resolved, please verify that this implementation correctly handles the following scenario:

  • A deployment with a non-null selector but no matching computedDeploymentResource row yet

This edge case could occur during race conditions where selector computation is still in progress.


109-112: Good use of existing policy builder

Leveraging the WorkspacePolicyBuilder for policy-related computations maintains separation of concerns and avoids duplicating logic.


114-130: Well-structured transaction flow with proper handling of empty results

The main releaseTargets method:

  1. Properly uses a transaction for atomic operations
  2. Correctly handles the case of no matching pairs
  3. Uses onConflictDoNothing to handle potential duplicates gracefully
  4. Returns the results to the caller

This structure ensures data consistency and handles edge cases appropriately.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/db/src/selectors/compute/policy-builder.ts (1)

57-61: Guard against NULL selectors when building dynamic predicates

qb.resources().where(t.resourceSelector).sql() (and the deployment/environment equivalents) will be called even when t.resourceSelector is null, which may produce an invalid SQL fragment depending on QueryBuilder’s implementation.
Consider short‑circuiting or generating a tautology (TRUE) when a selector is null.

Also applies to: 132-136

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab0fb8f and de8d23f.

📒 Files selected for processing (5)
  • packages/db/src/selectors/compute/compute.ts (3 hunks)
  • packages/db/src/selectors/compute/deployment-builder.ts (1 hunks)
  • packages/db/src/selectors/compute/policy-builder.ts (2 hunks)
  • packages/db/src/selectors/compute/replace-builder.ts (0 hunks)
  • packages/db/src/selectors/compute/resource-builder.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/db/src/selectors/compute/replace-builder.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/db/src/selectors/compute/compute.ts
  • packages/db/src/selectors/compute/resource-builder.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{ts,tsx}`: **Note on Error Handling:** Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error...

**/*.{ts,tsx}: Note on Error Handling:
Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error handling. These are acceptable as long as they maintain clarity and predictability.

  • packages/db/src/selectors/compute/deployment-builder.ts
  • packages/db/src/selectors/compute/policy-builder.ts
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Lint

Comment on lines 28 to 46
const promises = deployments.map(async (d) => {
const { system } = d;
const { workspaceId } = system;
if (d.resourceSelector == null) return [];
const resources = await tx.query.resource.findMany({
where: and(
eq(SCHEMA.resource.workspaceId, workspaceId),
this._queryBuilder.resources().where(d.resourceSelector).sql(),
),
});

const fulfilled = await Promise.all(promises);
return fulfilled.flat();
},
);
return resources.map((r) => ({
deploymentId: d.id,
resourceId: r.id,
}));
});

const fulfilled = await Promise.all(promises);
return fulfilled.flat();
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Concurrent queries on the same transaction may dead‑lock the pg client

Promise.all() fires multiple queries in parallel against a single tx (one connection).
pg (used under the hood by Drizzle) does not support overlapping queries on the same client and will throw
Error: Client has already been released / cannot run multiple commands on the same socket.

Refactor to run the queries sequentially (or batch them in SQL):

-    const promises = deployments.map(async (d) => {
-      …                        // same for workspace variant
-    });
-
-    const fulfilled = await Promise.all(promises);
-    return fulfilled.flat();
+    const matches: Array<{ deploymentId: string; resourceId: string }> = [];
+    for (const d of deployments) {
+      if (d.resourceSelector == null) continue;
+      const res = await tx.query.resource.findMany({
+        where: and(
+          eq(SCHEMA.resource.workspaceId, d.system.workspaceId),
+          this._queryBuilder.resources().where(d.resourceSelector).sql(),
+        ),
+      });
+      matches.push(
+        ...res.map((r) => ({ deploymentId: d.id, resourceId: r.id })),
+      );
+    }
+    return matches;

(The same pattern applies to lines 102‑118).

Also applies to: 102-118

Comment on lines +34 to +72
const targetPromises = this.targets.map(async (t) => {
if (
t.resourceSelector == null &&
t.deploymentSelector == null &&
t.environmentSelector == null
)
return [];
const releaseTargets = await tx
.select()
.from(SCHEMA.releaseTarget)
.innerJoin(
SCHEMA.resource,
eq(SCHEMA.releaseTarget.resourceId, SCHEMA.resource.id),
)
.innerJoin(
SCHEMA.deployment,
eq(SCHEMA.releaseTarget.deploymentId, SCHEMA.deployment.id),
)
.innerJoin(
SCHEMA.environment,
eq(SCHEMA.releaseTarget.environmentId, SCHEMA.environment.id),
)
.where(
and(
qb.resources().where(t.resourceSelector).sql(),
qb.deployments().where(t.deploymentSelector).sql(),
qb.environments().where(t.environmentSelector).sql(),
),
);
},
async (tx) => {
const targets = await tx
.select()
.from(SCHEMA.policyTarget)
.where(inArray(SCHEMA.policyTarget.policyId, this.ids));

const qb = new QueryBuilder(tx);
const targetPromises = targets.map(async (t) => {
if (
t.resourceSelector == null &&
t.deploymentSelector == null &&
t.environmentSelector == null
)
return [];
const releaseTargets = await tx
.select()
.from(SCHEMA.releaseTarget)
.innerJoin(
SCHEMA.resource,
eq(SCHEMA.releaseTarget.resourceId, SCHEMA.resource.id),
)
.innerJoin(
SCHEMA.deployment,
eq(SCHEMA.releaseTarget.deploymentId, SCHEMA.deployment.id),
)
.innerJoin(
SCHEMA.environment,
eq(SCHEMA.releaseTarget.environmentId, SCHEMA.environment.id),
)
.where(
and(
qb.resources().where(t.resourceSelector).sql(),
qb.deployments().where(t.deploymentSelector).sql(),
qb.environments().where(t.environmentSelector).sql(),
),
);
return releaseTargets.map((rt) => ({
policyTargetId: t.id,
releaseTargetId: rt.release_target.id,
}));
});

return releaseTargets.map((rt) => ({
policyTargetId: t.id,
releaseTargetId: rt.release_target.id,
}));
});
const fulfilled = await Promise.all(targetPromises);
return fulfilled.flat();
}
Copy link
Contributor

@coderabbitai coderabbitai bot Apr 17, 2025

Choose a reason for hiding this comment

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

💡 Verification agent

❓ Verification inconclusive

Serialise selector evaluations to avoid connection contention

Promise.all() runs selector queries in parallel on the single transaction tx, risking the same concurrency problem described for the deployment builder.
Replace the parallel map with a for…of loop (or merge the logic into a single SQL statement with OR’ed predicates).

This change applies to both the per‑policy section (34‑72) and the workspace variant (115‑145).

Also applies to: 115-145


Serialise selector evaluations to avoid connection contention
Kysely transactions share a single connection and will error if you fire overlapping queries on the same tx. Running your selector queries in parallel via Promise.all risks “already a query in progress” or similar driver errors. Switch to sequential iteration (for…of) or merge into one SQL statement with OR‑ed predicates.

Affected locations:

  • packages/db/src/selectors/compute/policy-builder.ts: lines 34–72
  • packages/db/src/selectors/compute/policy-builder.ts (workspace variant): lines 115–145

Example sequential refactor:

const results: { policyTargetId: string; releaseTargetId: string }[] = [];
for (const t of this.targets) {
  if (!t.resourceSelector && !t.deploymentSelector && !t.environmentSelector) {
    continue;
  }
  const releaseTargets = await tx
    .select()
    .from(SCHEMA.releaseTarget)
    // …same joins…
    .where(
      and(
        qb.resources().where(t.resourceSelector).sql(),
        qb.deployments().where(t.deploymentSelector).sql(),
        qb.environments().where(t.environmentSelector).sql(),
      ),
    );

  for (const rt of releaseTargets) {
    results.push({
      policyTargetId: t.id,
      releaseTargetId: rt.release_target.id,
    });
  }
}
return results;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tested this and it didn't seem to cause any issues and I tested with two targets in the system

Copy link
Contributor

Choose a reason for hiding this comment

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

@adityachoudhari26 Thanks for testing this and sharing your results. If it's working fine with multiple targets in your testing environment, that's good to know. The theoretical concern about transaction connection contention is more significant with larger numbers of concurrent queries, but if your testing shows the current implementation is stable for your use cases, that's what matters most.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
apps/event-worker/src/workers/update-deployment.ts (1)

50-53: New resources added by the updated selector are still not covered
This was raised earlier and remains: computing release targets only for currentResources ignores any new resources now matched by resourceSelector, leading to an inconsistent state until another worker touches them.
Please compute over the selector‑derived resource set instead of the pre‑existing one.

🧹 Nitpick comments (1)
apps/event-worker/src/workers/update-deployment.ts (1)

60-60: Consider awaiting dispatchExitHooks to avoid unhandled rejections

dispatchExitHooks returns a promise. If it rejects, the worker will still resolve (the error is swallowed), potentially missing critical exit‑hook processing. Add await so that failures surface.

- await dispatchExitHooks(data, exitedResources);
+ await dispatchExitHooks(data, exitedResources);
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3691408 and 48f218d.

📒 Files selected for processing (7)
  • apps/event-worker/src/workers/new-deployment.ts (2 hunks)
  • apps/event-worker/src/workers/update-deployment.ts (3 hunks)
  • apps/event-worker/src/workers/update-environment.ts (2 hunks)
  • apps/event-worker/src/workers/update-resource-variable.ts (2 hunks)
  • apps/webservice/src/app/api/v1/resources/[resourceId]/route.ts (2 hunks)
  • apps/webservice/src/app/api/v1/resources/route.ts (2 hunks)
  • packages/job-dispatch/src/resource/handle-provider-scan.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/webservice/src/app/api/v1/resources/[resourceId]/route.ts
  • apps/event-worker/src/workers/update-resource-variable.ts
  • apps/event-worker/src/workers/new-deployment.ts
  • apps/event-worker/src/workers/update-environment.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{ts,tsx}`: **Note on Error Handling:** Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error...

**/*.{ts,tsx}: Note on Error Handling:
Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error handling. These are acceptable as long as they maintain clarity and predictability.

  • packages/job-dispatch/src/resource/handle-provider-scan.ts
  • apps/webservice/src/app/api/v1/resources/route.ts
  • apps/event-worker/src/workers/update-deployment.ts
🧬 Code Graph Analysis (3)
packages/job-dispatch/src/resource/handle-provider-scan.ts (2)
packages/job-dispatch/src/resource/delete.ts (1)
  • deleteResources (55-74)
packages/db/src/selectors/index.ts (1)
  • selector (18-18)
apps/webservice/src/app/api/v1/resources/route.ts (2)
packages/db/src/selectors/index.ts (1)
  • selector (18-18)
packages/events/src/index.ts (1)
  • getQueue (28-34)
apps/event-worker/src/workers/update-deployment.ts (3)
packages/db/src/client.ts (1)
  • db (15-15)
packages/db/src/selectors/index.ts (1)
  • selector (18-18)
packages/events/src/index.ts (1)
  • getQueue (28-34)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Typecheck
  • GitHub Check: Lint
🔇 Additional comments (1)
apps/event-worker/src/workers/update-deployment.ts (1)

40-49: resources(currentResources) expects IDs, not full objects

Unless the compute builder overloads the method, passing full Resource objects will result in a type/runtime error or an unnecessary object‑to‑ID coercion.

- const rts = await computeBuilder
-   .resources(currentResources)
+ const rts = await computeBuilder
+   .resources(currentResources.map((r) => r.id))
   .releaseTargets();


const insertJobs = insertedResources.map((r) => ({ name: r.id, data: r }));
const updateJobs = updatedResources.map((r) => ({ name: r.id, data: r }));
const deleted = await deleteResources(tx, toDelete);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

deleteResources() currently returns void, so deleted is always undefined

The returned object { all: [...], deleted } will therefore expose an undefined value and may break consumers expecting a list/count of deleted resources.

- const deleted = await deleteResources(tx, toDelete);
+ await deleteResources(tx, toDelete);

If callers need this information, change deleteResources() to return the affected resources and update its implementation accordingly.

📝 Committable suggestion

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

Suggested change
const deleted = await deleteResources(tx, toDelete);
await deleteResources(tx, toDelete);

Comment on lines 48 to 50
await selector(tx).compute().allResourceSelectors(workspaceId);

await selector().compute().allResourceSelectors(workspaceId);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Running selector computation twice is costly and probably redundant

allResourceSelectors(workspaceId) performs non‑trivial DB writes.
Invoking it inside the same transaction (selector(tx) …) and again outside the transaction (selector() …) doubles the work, increases lock contention, and may introduce race conditions if the second call starts before the outer transaction commits.

Unless you have a very specific consistency requirement, one call should be enough:

- await selector(tx).compute().allResourceSelectors(workspaceId);
- await selector().compute().allResourceSelectors(workspaceId);
+ await selector(tx).compute().allResourceSelectors(workspaceId);

If you truly need a post‑commit recomputation, consider a follow‑up job instead of an immediate synchronous call.

📝 Committable suggestion

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

Suggested change
await selector(tx).compute().allResourceSelectors(workspaceId);
await selector().compute().allResourceSelectors(workspaceId);
await selector(tx).compute().allResourceSelectors(workspaceId);

Comment on lines +85 to +91
selector()
.compute()
.allResourceSelectors(workspaceId)
.then(() => {
getQueue(Channel.NewResource).addBulk(insertJobs);
getQueue(Channel.UpdatedResource).addBulk(updateJobs);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Un‑awaited promise hides failures and may drop follow‑up jobs

The .then() chain is fire‑and‑forget; any rejection in allResourceSelectors() or addBulk() will be swallowed, and the request will still return HTTP 200.
Wrap the whole sequence in await (inside the existing try / catch) so that errors propagate and you log a consistent response.

- selector()
-   .compute()
-   .allResourceSelectors(workspaceId)
-   .then(() => {
-     getQueue(Channel.NewResource).addBulk(insertJobs);
-     getQueue(Channel.UpdatedResource).addBulk(updateJobs);
-   });
+ await selector().compute().allResourceSelectors(workspaceId);
+ await Promise.all([
+   getQueue(Channel.NewResource).addBulk(insertJobs),
+   getQueue(Channel.UpdatedResource).addBulk(updateJobs),
+ ]);
📝 Committable suggestion

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

Suggested change
selector()
.compute()
.allResourceSelectors(workspaceId)
.then(() => {
getQueue(Channel.NewResource).addBulk(insertJobs);
getQueue(Channel.UpdatedResource).addBulk(updateJobs);
});
await selector().compute().allResourceSelectors(workspaceId);
await Promise.all([
getQueue(Channel.NewResource).addBulk(insertJobs),
getQueue(Channel.UpdatedResource).addBulk(updateJobs),
]);

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/job-dispatch/src/resource/handle-provider-scan.ts (1)

46-46: ⚠️ Potential issue

deleteResources() function returns void, making deleted always undefined

The deleteResources() function doesn't return any value, but you're assigning its result to a deleted variable that's later included in the returned object. This will cause deleted to always be undefined, potentially breaking consumers expecting a list or count of deleted resources.

- const deleted = await deleteResources(tx, toDelete);
+ await deleteResources(tx, toDelete);

And then update the return statement accordingly:

- return { all: [...insertedResources, ...updatedResources], deleted };
+ return { all: [...insertedResources, ...updatedResources] };

If you need to track deleted resources, consider modifying the deleteResources() function to return the deleted resources.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 48f218d and 742377e.

📒 Files selected for processing (2)
  • package.json (1 hunks)
  • packages/job-dispatch/src/resource/handle-provider-scan.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • package.json
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{ts,tsx}`: **Note on Error Handling:** Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error...

**/*.{ts,tsx}: Note on Error Handling:
Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error handling. These are acceptable as long as they maintain clarity and predictability.

  • packages/job-dispatch/src/resource/handle-provider-scan.ts
🧬 Code Graph Analysis (1)
packages/job-dispatch/src/resource/handle-provider-scan.ts (2)
packages/job-dispatch/src/resource/delete.ts (1)
  • deleteResources (55-74)
packages/db/src/selectors/index.ts (1)
  • selector (18-18)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Typecheck
  • GitHub Check: Lint
  • GitHub Check: build (linux/amd64)

await Promise.all(promises);
await cb.allPolicies(workspaceId).releaseTargetSelectors().replace();

await selector().compute().allResourceSelectors(workspaceId);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider using the existing transaction for selector computation

The current implementation creates a new transaction when calling selector().compute().allResourceSelectors(workspaceId). Since you already have an active transaction tx, it would be more efficient to reuse it rather than creating a new one.

- await selector().compute().allResourceSelectors(workspaceId);
+ await selector(tx).compute().allResourceSelectors(workspaceId);

Using the existing transaction will:

  1. Reduce database lock contention
  2. Improve performance by avoiding unnecessary transaction overhead
  3. Ensure data consistency within the same transaction
  4. Prevent potential race conditions if the second call starts before the outer transaction commits
📝 Committable suggestion

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

Suggested change
await selector().compute().allResourceSelectors(workspaceId);
await selector(tx).compute().allResourceSelectors(workspaceId);

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/db/src/selectors/compute/resource-builder.ts (1)

133-250: Consider reducing code duplication between Resource and WorkspaceResourceBuilder.

Both classes share very similar implementation for findMatchingEnvironmentDeploymentPairs and other methods. Consider extracting common logic to a base class or shared utility functions.

+// Create a helper function or base class with shared logic
+function findMatchingPairs(tx: Tx, resourceIds: string[]) {
+  const isResourceMatchingEnvironment = eq(
+    SCHEMA.computedEnvironmentResource.resourceId,
+    SCHEMA.resource.id,
+  );
+  // ... rest of the implementation
+  return tx.select()...
+}

 // Then in both classes:
 private findMatchingEnvironmentDeploymentPairs(tx: Tx, resourceIds?: string[]) {
-  // Duplicate implementation
+  return findMatchingPairs(tx, resourceIds ?? this.resourceIds);
 }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 742377e and b1789d3.

📒 Files selected for processing (6)
  • apps/event-worker/src/workers/update-deployment.ts (3 hunks)
  • apps/event-worker/src/workers/update-environment.ts (2 hunks)
  • packages/db/src/schema/deployment.ts (4 hunks)
  • packages/db/src/schema/environment-relations.ts (3 hunks)
  • packages/db/src/selectors/compute/compute.ts (3 hunks)
  • packages/db/src/selectors/compute/resource-builder.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/db/src/schema/environment-relations.ts
  • packages/db/src/schema/deployment.ts
  • packages/db/src/selectors/compute/compute.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{ts,tsx}`: **Note on Error Handling:** Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error...

**/*.{ts,tsx}: Note on Error Handling:
Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error handling. These are acceptable as long as they maintain clarity and predictability.

  • apps/event-worker/src/workers/update-deployment.ts
  • apps/event-worker/src/workers/update-environment.ts
  • packages/db/src/selectors/compute/resource-builder.ts
🧬 Code Graph Analysis (1)
apps/event-worker/src/workers/update-environment.ts (4)
packages/db/src/schema/environment.ts (1)
  • environment (59-84)
packages/db/src/client.ts (1)
  • db (15-15)
packages/db/src/selectors/index.ts (1)
  • selector (18-18)
packages/events/src/index.ts (1)
  • getQueue (28-34)
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Typecheck
  • GitHub Check: Lint
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
🔇 Additional comments (19)
apps/event-worker/src/workers/update-deployment.ts (8)

3-3: LGTM: Import selection reflects refactored approach.

Import of eq and selector from @ctrlplane/db supports the shift to selector-based utility.


12-23: LGTM: Simplified exit hook dispatch function.

The function no longer needs a database transaction parameter, aligning with the new selector-based approach.


25-29: LGTM: Updated comments accurately reflect new implementation.

The documentation has been updated to describe the new workflow:

  1. Query current release targets with resources
  2. Recompute release targets for current resources

40-46: LGTM: More efficient query with eager loading.

Using findFirst with nested with clauses efficiently loads the deployment with its system and release targets (including resources) in a single query.


47-51: LGTM: Clear extraction of current state.

Extracting current release targets and resources before recomputation provides a clean baseline for comparison.


52-61: LGTM: Workspace-scoped recomputation of release targets.

Using allResources(workspaceId) ensures all affected resources are included in the recomputation, not just those previously attached to the deployment.


56-61: Fix for the past issue with exited resources calculation.

The filter now correctly checks both resource ID and deployment ID, preventing false positives when a resource appears in multiple deployments.


65-65: LGTM: Simplified exit hook invocation.

Exit hooks are now dispatched directly without a transaction parameter, consistent with the simplified function signature.

apps/event-worker/src/workers/update-environment.ts (6)

4-4: LGTM: Import selection reflects refactored approach.

Import of eq and selector from @ctrlplane/db supports the shift to selector-based utility.


56-57: LGTM: Early return for unchanged selectors.

Efficiently skips processing when resource selectors haven't changed.


59-65: LGTM: Efficient query with eager loading.

Using findFirst with nested with clauses efficiently loads the environment with its system and release targets (including resources) in a single query.


66-68: LGTM: Clear extraction of current state.

Extracting current release targets and resources before recomputation provides a clean baseline for comparison.


69-75: LGTM: Comprehensive recomputation approach.

The approach correctly:

  1. Computes environment resource selectors first
  2. Then computes release targets for all resources in the workspace

This ensures complete and consistent recomputation, including any newly matched resources.


80-86: LGTM: Proper identification of exited resources.

The filter correctly checks both resource ID and environment ID when determining which resources have exited.

packages/db/src/selectors/compute/resource-builder.ts (5)

1-6: LGTM: Clear imports and dependencies.

The imports are well-organized, focusing on database expressions and necessary schema elements.


7-131: LGTM: Well-designed ResourceBuilder class.

The ResourceBuilder class effectively:

  1. Validates resources belong to same workspace
  2. Deletes existing release targets
  3. Computes new matches via complex but correct joining logic
  4. Updates policy release targets
  5. Handles everything in a single transaction

The implementation aligns with the PR objectives to consolidate release target computation in the selector utility.


61-107: Resource matching logic correctly handles deployment selectors.

The query joins resources with computed environment resources, environments, deployments, and (optionally) computed deployment resources. It correctly handles deployments with null selectors as matching all environment resources.


114-130: LGTM: Transactional release target update.

The method effectively orchestrates all steps within a transaction, ensuring atomicity:

  1. Delete existing targets
  2. Find new matches
  3. Insert new targets
  4. Recompute policy release targets

228-249: LGTM: Proper workspace-level release target computation.

The WorkspaceResourceBuilder implementation correctly:

  1. Fetches all resources in the workspace
  2. Recomputes all their release targets in one transaction
  3. Updates policies afterward

This enables the efficient workspace-scoped computation seen in the worker implementations.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/rule-engine/src/utils/merge-policies.ts (1)

45-47: Updated structure for deploymentVersionSelector

This change modifies the structure of the merged policy object to match the new pattern used throughout the system. The nested structure aligns with the broader refactoring effort to standardize selector handling.

Consider adding a comment explaining why this nesting structure is necessary, which would help future developers understand the design decision.

  return {
    ...merged,
+   // Nest deploymentVersionSelector to match the expected structure in the selector API
    deploymentVersionSelector: {
      deploymentVersionSelector: mergedVersionSelector,
    },
  };
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 41e3331 and 576bd88.

📒 Files selected for processing (2)
  • packages/rule-engine/src/manager/version-manager.ts (2 hunks)
  • packages/rule-engine/src/utils/merge-policies.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{ts,tsx}`: **Note on Error Handling:** Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error...

**/*.{ts,tsx}: Note on Error Handling:
Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error handling. These are acceptable as long as they maintain clarity and predictability.

  • packages/rule-engine/src/utils/merge-policies.ts
  • packages/rule-engine/src/manager/version-manager.ts
🧬 Code Graph Analysis (1)
packages/rule-engine/src/manager/version-manager.ts (2)
packages/db/src/selectors/query/deployment-selector.ts (1)
  • sql (35-39)
packages/db/src/selectors/index.ts (1)
  • selector (18-18)
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Typecheck
  • GitHub Check: Lint
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
🔇 Additional comments (3)
packages/rule-engine/src/manager/version-manager.ts (3)

10-10: Good addition of the selector import

This import addition aligns with the PR objective of moving release target operations to the selector utility.


123-127: Improved approach using selector API to generate SQL

The selector API provides a more consistent and maintainable way to generate SQL conditions compared to the previous helper function approach. This change aligns well with the broader refactoring effort to centralize selector logic.


134-134: Properly integrating the generated SQL

Using the SQL generated by the selector API in the query's where clause is a good replacement for the previous approach using schema.deploymentVersionMatchesCondition.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🔭 Outside diff range comments (1)
apps/event-worker/src/workers/update-environment.ts (1)

35-37: 🛠️ Refactor suggestion

Capture & log rejected exit‑hook dispatches

Re‑use the logging strategy proposed for the other workers so failures inside handleEvent don’t disappear.

♻️ Duplicate comments (4)
apps/event-worker/src/workers/new-deployment.ts (1)

39-41: 🛠️ Refactor suggestion

Deduplicate resources to avoid redundant recomputations.

Multiple environments often reference the same resource. Passing duplicates down the compute chain may:

  • Perform identical heavy SQL work multiple times
  • Cause unnecessary INSERT/DELETE statements during release target computation
-const resources = environments.flatMap((e) =>
-  e.computedResources.map((r) => r.resource),
-);
+const resources = Array.from(
+  new Set(
+    environments.flatMap((e) =>
+      e.computedResources.map((r) => r.resource),
+    ),
+  ),
+);
apps/event-worker/src/workers/updated-resources/index.ts (1)

9-9: Job‑ID format inconsistency still present
Past feedback suggested using the canonical rt.id as the BullMQ job name to avoid duplicate‑key collisions between workers.
dispatchEvaluateJobs() still constructs the composite
"${rt.resourceId}-${rt.environmentId}-${rt.deploymentId}".

If other workers (or future refactors) enqueue the same release‑target with a different jobId, BullMQ will treat them as independent jobs for the same work item.

Consider switching dispatchEvaluateJobs to name: rt.id (or a hash thereof) to keep a single authoritative identifier.
(See previous review for a diff.)

Also applies to: 43-43

apps/event-worker/src/workers/update-resource-variable.ts (1)

61-63: Potential job‑name collision carried over

dispatchEvaluateJobs(rts) inherits the composite job‑name format.
See earlier comment in updated-resources/index.ts – using rt.id remains the recommended fix.

apps/event-worker/src/workers/update-environment.ts (1)

84-86: Job‑name format inconsistency

Same remark as in the other workers regarding dispatchEvaluateJobs and the composite job‑name.

🧹 Nitpick comments (1)
apps/event-worker/src/workers/update-resource-variable.ts (1)

64-68: Micro‑nit: drop lodash for deduplication

For a small one‑liner you can avoid the lodash dependency:

const exitedDeployments = [
  ...new Map(
    currentDeployments
      .filter((d) => !rts.some((rt) => rt.deploymentId === d.id))
      .map((d) => [d.id, d]),
  ).values(),
];

Not critical, but shaving unnecessary dependencies keeps bundle size lean.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0ec1a5 and ae254d5.

📒 Files selected for processing (9)
  • apps/event-worker/src/utils/dispatch-evaluate-jobs.ts (1 hunks)
  • apps/event-worker/src/workers/new-deployment-version.ts (2 hunks)
  • apps/event-worker/src/workers/new-deployment.ts (2 hunks)
  • apps/event-worker/src/workers/new-resource.ts (2 hunks)
  • apps/event-worker/src/workers/update-deployment-variable.ts (2 hunks)
  • apps/event-worker/src/workers/update-deployment.ts (3 hunks)
  • apps/event-worker/src/workers/update-environment.ts (3 hunks)
  • apps/event-worker/src/workers/update-resource-variable.ts (2 hunks)
  • apps/event-worker/src/workers/updated-resources/index.ts (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/event-worker/src/utils/dispatch-evaluate-jobs.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/event-worker/src/workers/update-deployment.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{ts,tsx}`: **Note on Error Handling:** Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error...

**/*.{ts,tsx}: Note on Error Handling:
Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error handling. These are acceptable as long as they maintain clarity and predictability.

  • apps/event-worker/src/workers/new-deployment-version.ts
  • apps/event-worker/src/workers/update-deployment-variable.ts
  • apps/event-worker/src/workers/new-resource.ts
  • apps/event-worker/src/workers/new-deployment.ts
  • apps/event-worker/src/workers/update-resource-variable.ts
  • apps/event-worker/src/workers/update-environment.ts
  • apps/event-worker/src/workers/updated-resources/index.ts
🧬 Code Graph Analysis (3)
apps/event-worker/src/workers/new-deployment-version.ts (1)
apps/event-worker/src/utils/dispatch-evaluate-jobs.ts (1)
  • dispatchEvaluateJobs (5-11)
apps/event-worker/src/workers/update-deployment-variable.ts (1)
apps/event-worker/src/utils/dispatch-evaluate-jobs.ts (1)
  • dispatchEvaluateJobs (5-11)
apps/event-worker/src/workers/update-resource-variable.ts (7)
packages/logger/src/index.ts (1)
  • logger (48-48)
packages/db/src/schema/deployment.ts (1)
  • deployment (69-93)
packages/job-dispatch/src/events/index.ts (1)
  • handleEvent (8-9)
packages/db/src/schema/resource.ts (1)
  • resource (59-87)
packages/db/src/selectors/index.ts (1)
  • selector (18-18)
packages/db/src/client.ts (1)
  • db (15-15)
apps/event-worker/src/utils/dispatch-evaluate-jobs.ts (1)
  • dispatchEvaluateJobs (5-11)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Typecheck
  • GitHub Check: Lint
  • GitHub Check: build (linux/amd64)
🔇 Additional comments (13)
apps/event-worker/src/workers/new-deployment-version.ts (2)

4-6: Clean import refactoring.

The removal of getQueue import and addition of dispatchEvaluateJobs utility import aligns with the refactoring goal of centralizing release target evaluation job dispatching.


23-23: Good simplification by using the utility function.

Replacing direct queue management with the dispatchEvaluateJobs utility function reduces code duplication and makes the worker more focused on its core responsibility.

apps/event-worker/src/workers/update-deployment-variable.ts (2)

4-6: Clean import refactoring.

The removal of getQueue import and addition of dispatchEvaluateJobs utility import aligns with the refactoring goal of centralizing release target evaluation job dispatching.


32-32: Good simplification by using the utility function.

Replacing direct queue management with the dispatchEvaluateJobs utility function reduces code duplication and makes the worker more focused on its core responsibility.

apps/event-worker/src/workers/new-resource.ts (3)

1-6: Proper import organization.

Good job changing schema to a type-only import and removing unused imports. Adding the necessary imports for the selector API and the dispatch utility function supports the new implementation approach.


8-13: Well-structured helper function for recomputation.

The new recomputeReleaseTargets helper function effectively uses the selector API to compute release targets for a specific resource. The implementation follows a clear pattern:

  1. Create a compute builder
  2. Compute all resource selectors for the workspace
  3. Return release targets for the specific resource

This separation of concerns improves maintainability and readability.


30-31: Simplified worker logic.

The worker implementation is now much cleaner and more focused, delegating the recomputation and job dispatching to specialized utility functions.

apps/event-worker/src/workers/new-deployment.ts (4)

1-1: Good import addition.

Adding the selector import supports the refactored implementation that uses the selector API.


9-16: Well-structured helper function for recomputation.

The new recomputeReleaseTargets helper function effectively uses the selector API to compute release targets for a deployment and a list of resources. The implementation follows a clear pattern:

  1. Create a compute builder
  2. Compute resource selectors for the deployment
  3. Return release targets for the given resources

This separation of concerns improves maintainability and readability.


34-37: Improved query efficiency.

The updated query fetches environments by systemId with their computed resources and associated resource entities in a single database call, which is more efficient than the previous approach.


43-43: Clean usage of helper function.

Good job using the new helper function to compute release targets for the deployment and resources.

apps/event-worker/src/workers/updated-resources/index.ts (1)

45-48: Duplicate‑event issue fixed – nice!

_.uniqBy on currentDeployments guarantees each deployment is considered once, eliminating the duplicate
“deployment.resource.removed” events highlighted in a previous review.
No further action required.

apps/event-worker/src/workers/update-environment.ts (1)

39-47: Verify the breadth of recomputation to avoid superfluous evaluations

computeBuilder.allResources(workspaceId).releaseTargets() returns every release‑target in the workspace,
but only the environment’s selectors were recomputed (computeBuilder.environments([environment]).resourceSelectors()).

If other environments live in the same workspace their release‑targets will be enqueued as evaluation jobs
even though nothing changed for them, potentially wasting compute and queue bandwidth.

Please double‑check that releaseTargets() internally filters to the touched environment, or limit the scope explicitly:

return computeBuilder
  .resources(currentResources)   // or a derived list
  .releaseTargets();

Comment on lines +12 to +23
const dispatchExitHooks = async (
deployments: SCHEMA.Deployment[],
exitedResource: SCHEMA.Resource,
) => {
const events = deployments.map((deployment) => ({
action: "deployment.resource.removed" as const,
payload: { deployment, resource: exitedResource },
}));

const handleEventPromises = events.map(handleEvent);
await Promise.allSettled(handleEventPromises);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Surface & log failures from handleEvent instead of silently swallowing them

Promise.allSettled() is great for keeping the worker alive, but any rejection is currently discarded.
If an exit‑hook fails you won’t see it in the logs and downstream invariants may silently drift.

-const handleEventPromises = events.map(handleEvent);
-await Promise.allSettled(handleEventPromises);
+const results = await Promise.allSettled(events.map(handleEvent));
+results
+  .filter((r): r is PromiseRejectedResult => r.status === "rejected")
+  .forEach((r) =>
+    logger.warn({ err: r.reason }, "exit‑hook dispatch failed"),
+  );

This preserves fire‑and‑forget semantics while still giving operators visibility into failures.
(You'll need to import logger at the top of the file.)

📝 Committable suggestion

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

Suggested change
const dispatchExitHooks = async (
deployments: SCHEMA.Deployment[],
exitedResource: SCHEMA.Resource,
) => {
const events = deployments.map((deployment) => ({
action: "deployment.resource.removed" as const,
payload: { deployment, resource: exitedResource },
}));
const handleEventPromises = events.map(handleEvent);
await Promise.allSettled(handleEventPromises);
};
const dispatchExitHooks = async (
deployments: SCHEMA.Deployment[],
exitedResource: SCHEMA.Resource,
) => {
const events = deployments.map((deployment) => ({
action: "deployment.resource.removed" as const,
payload: { deployment, resource: exitedResource },
}));
const results = await Promise.allSettled(events.map(handleEvent));
results
.filter((r): r is PromiseRejectedResult => r.status === "rejected")
.forEach((r) =>
logger.warn({ err: r.reason }, "exit‑hook dispatch failed"),
);
};

Comment on lines +14 to +25
const dispatchExitHooks = async (
deployments: schema.Deployment[],
exitedResource: schema.Resource,
) => {
const events = deployments.map((deployment) => ({
action: "deployment.resource.removed" as const,
payload: { deployment, resource: exitedResource },
}));

const handleEventPromises = events.map(handleEvent);
await Promise.allSettled(handleEventPromises);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Log and examine individual exit‑hook failures

Same observation as in the updated‑resources worker: rejections from handleEvent are suppressed.
Apply the same logging pattern so operational issues don’t get lost.

@adityachoudhari26 adityachoudhari26 merged commit 733f40e into main Apr 18, 2025
3 of 10 checks passed
@adityachoudhari26 adityachoudhari26 deleted the move-upsert-release-targets branch April 18, 2025 18:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants