Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 1 addition & 44 deletions apps/event-queue/src/repository/in-memory/resource.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type { Tx } from "@ctrlplane/db";
import type { FullResource } from "@ctrlplane/events";
import _ from "lodash";
import { isPresent } from "ts-is-present";

import { and, eq, isNull } from "@ctrlplane/db";
import { eq } from "@ctrlplane/db";
import { db as dbClient } from "@ctrlplane/db/client";
import * as schema from "@ctrlplane/db/schema";

Expand All @@ -14,39 +12,6 @@ type InMemoryResourceRepositoryOptions = {
tx?: Tx;
};

const getInitialEntities = async (workspaceId: string) => {
const dbResult = await dbClient
.select()
.from(schema.resource)
.leftJoin(
schema.resourceMetadata,
eq(schema.resource.id, schema.resourceMetadata.resourceId),
)
.where(
and(
eq(schema.resource.workspaceId, workspaceId),
isNull(schema.resource.deletedAt),
),
);

return _.chain(dbResult)
.groupBy((row) => row.resource.id)
.map((group) => {
const [first] = group;
if (first == null) return null;
const { resource } = first;
const metadata = Object.fromEntries(
group
.map((r) => r.resource_metadata)
.filter(isPresent)
.map((m) => [m.key, m.value]),
);
return { ...resource, metadata };
})
.value()
.filter(isPresent);
};

export class InMemoryResourceRepository implements Repository<FullResource> {
private entities: Map<string, FullResource>;
private db: Tx;
Expand All @@ -57,14 +22,6 @@ export class InMemoryResourceRepository implements Repository<FullResource> {
this.entities.set(entity.id, entity);
this.db = opts.tx ?? dbClient;
}
static async create(workspaceId: string) {
const initialEntities = await getInitialEntities(workspaceId);
const inMemoryResourceRepository = new InMemoryResourceRepository({
initialEntities,
tx: dbClient,
});
return inMemoryResourceRepository;
}

get(id: string) {
return this.entities.get(id) ?? null;
Expand Down
37 changes: 3 additions & 34 deletions apps/event-queue/src/selector/in-memory/deployment-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { FullResource } from "@ctrlplane/events";
import _ from "lodash";
import { isPresent } from "ts-is-present";

import { and, eq, inArray, isNull } from "@ctrlplane/db";
import { and, eq, inArray } from "@ctrlplane/db";
import { db as dbClient } from "@ctrlplane/db/client";
import * as schema from "@ctrlplane/db/schema";
import { logger } from "@ctrlplane/logger";
Expand Down Expand Up @@ -59,38 +59,7 @@ export class InMemoryDeploymentResourceSelector
return this.matches;
}

static async create(workspaceId: string) {
const allEntitiesDbResult = await dbClient
.select()
.from(schema.resource)
.leftJoin(
schema.resourceMetadata,
eq(schema.resource.id, schema.resourceMetadata.resourceId),
)
.where(
and(
eq(schema.resource.workspaceId, workspaceId),
isNull(schema.resource.deletedAt),
),
);

const allEntities = _.chain(allEntitiesDbResult)
.groupBy((row) => row.resource.id)
.map((group) => {
const [first] = group;
if (first == null) return null;
const { resource } = first;
const metadata = Object.fromEntries(
group
.map((r) => r.resource_metadata)
.filter(isPresent)
.map((m) => [m.key, m.value]),
);
return { ...resource, metadata };
})
.value()
.filter(isPresent);

static async create(workspaceId: string, initialEntities: FullResource[]) {
const allSelectors = await dbClient
.select()
.from(schema.deployment)
Expand All @@ -103,7 +72,7 @@ export class InMemoryDeploymentResourceSelector

const inMemoryDeploymentResourceSelector =
new InMemoryDeploymentResourceSelector({
initialEntities: allEntities,
initialEntities,
initialSelectors: allSelectors,
});

Expand Down
37 changes: 3 additions & 34 deletions apps/event-queue/src/selector/in-memory/environment-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { FullResource } from "@ctrlplane/events";
import _ from "lodash";
import { isPresent } from "ts-is-present";

import { and, eq, inArray, isNull } from "@ctrlplane/db";
import { and, eq, inArray } from "@ctrlplane/db";
import { db as dbClient } from "@ctrlplane/db/client";
import * as schema from "@ctrlplane/db/schema";
import { logger } from "@ctrlplane/logger";
Expand Down Expand Up @@ -59,38 +59,7 @@ export class InMemoryEnvironmentResourceSelector
return this.matches;
}

static async create(workspaceId: string) {
const allEntitiesDbResult = await dbClient
.select()
.from(schema.resource)
.leftJoin(
schema.resourceMetadata,
eq(schema.resource.id, schema.resourceMetadata.resourceId),
)
.where(
and(
eq(schema.resource.workspaceId, workspaceId),
isNull(schema.resource.deletedAt),
),
);

const allEntities = _.chain(allEntitiesDbResult)
.groupBy((row) => row.resource.id)
.map((group) => {
const [first] = group;
if (first == null) return null;
const { resource } = first;
const metadata = Object.fromEntries(
group
.map((r) => r.resource_metadata)
.filter(isPresent)
.map((m) => [m.key, m.value]),
);
return { ...resource, metadata };
})
.value()
.filter(isPresent);

static async create(workspaceId: string, initialEntities: FullResource[]) {
const allSelectors = await dbClient
.select()
.from(schema.environment)
Expand All @@ -103,7 +72,7 @@ export class InMemoryEnvironmentResourceSelector

const inMemoryEnvironmentResourceSelector =
new InMemoryEnvironmentResourceSelector({
initialEntities: allEntities,
initialEntities,
initialSelectors: allSelectors,
});

Expand Down
65 changes: 56 additions & 9 deletions apps/event-queue/src/workspace/workspace.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
import type { FullResource } from "@ctrlplane/events";
import _ from "lodash";
import { isPresent } from "ts-is-present";

import { and, eq, isNull } from "@ctrlplane/db";
import { db as dbClient } from "@ctrlplane/db/client";
import * as schema from "@ctrlplane/db/schema";
import { logger } from "@ctrlplane/logger";

import type { ResourceRelationshipManager } from "../relationships/resource-relationship-manager.js";
Expand Down Expand Up @@ -40,16 +47,52 @@ type WorkspaceOptions = {
repository: WorkspaceRepository;
};

const createSelectorManager = async (id: string) => {
const getInitialResources = async (id: string): Promise<FullResource[]> => {
const dbResult = await dbClient
.select()
.from(schema.resource)
.leftJoin(
schema.resourceMetadata,
eq(schema.resource.id, schema.resourceMetadata.resourceId),
)
.where(
and(
eq(schema.resource.workspaceId, id),
isNull(schema.resource.deletedAt),
),
);

return _.chain(dbResult)
.groupBy((row) => row.resource.id)
.map((group) => {
const [first] = group;
if (first == null) return null;
const { resource } = first;
const metadata = Object.fromEntries(
group
.map((r) => r.resource_metadata)
.filter(isPresent)
.map((m) => [m.key, m.value]),
);
return { ...resource, metadata };
})
.value()
.filter(isPresent);
Comment on lines +66 to +80
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 | 🔴 Critical

Bug: metadata lookup uses wrong property name

row.resource_metadata will always be undefined—Drizzle returns camelCase keys (row.resourceMetadata). As written, we drop every metadata row and seed selectors/repositories with empty metadata. That breaks any selector predicates relying on metadata and risks wiping metadata when the in-memory repository writes back. Please swap this to row.resourceMetadata.
Apply the following diff:

-          .map((r) => r.resource_metadata)
+          .map((r) => r.resourceMetadata)
📝 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
.groupBy((row) => row.resource.id)
.map((group) => {
const [first] = group;
if (first == null) return null;
const { resource } = first;
const metadata = Object.fromEntries(
group
.map((r) => r.resource_metadata)
.filter(isPresent)
.map((m) => [m.key, m.value]),
);
return { ...resource, metadata };
})
.value()
.filter(isPresent);
.groupBy((row) => row.resource.id)
.map((group) => {
const [first] = group;
if (first == null) return null;
const { resource } = first;
const metadata = Object.fromEntries(
group
.map((r) => r.resourceMetadata)
.filter(isPresent)
.map((m) => [m.key, m.value]),
);
return { ...resource, metadata };
})
.value()
.filter(isPresent);
🤖 Prompt for AI Agents
In apps/event-queue/src/workspace/workspace.ts around lines 66 to 80, the code
reads row.resource_metadata which is always undefined because Drizzle returns
camelCase keys; replace usages of row.resource_metadata with
row.resourceMetadata so metadata entries are correctly collected (update the
.map and any related filters to use row.resourceMetadata and ensure
isPresent/type checks still apply).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is straight up wrong lol

};

const createSelectorManager = async (
id: string,
initialResources: FullResource[],
) => {
log.info(`Creating selector manager for workspace ${id}`);

const [
deploymentResourceSelector,
environmentResourceSelector,
policyTargetReleaseTargetSelector,
] = await Promise.all([
InMemoryDeploymentResourceSelector.create(id),
InMemoryEnvironmentResourceSelector.create(id),
InMemoryDeploymentResourceSelector.create(id, initialResources),
InMemoryEnvironmentResourceSelector.create(id, initialResources),
InMemoryPolicyTargetReleaseTargetSelector.create(id),
]);

Expand All @@ -63,26 +106,29 @@ const createSelectorManager = async (id: string) => {
});
};

const createRepository = async (id: string) => {
const createRepository = async (
id: string,
initialResources: FullResource[],
) => {
log.info(`Creating repository for workspace ${id}`);

const [
inMemoryReleaseTargetRepository,
inMemoryReleaseRepository,
inMemoryResourceRepository,
inMemoryVersionReleaseRepository,
] = await Promise.all([
InMemoryReleaseTargetRepository.create(id),
InMemoryReleaseRepository.create(id),
InMemoryResourceRepository.create(id),
InMemoryVersionReleaseRepository.create(id),
]);

return new WorkspaceRepository({
versionRepository: new DbVersionRepository(id),
environmentRepository: new DbEnvironmentRepository(id),
deploymentRepository: new DbDeploymentRepository(id),
resourceRepository: inMemoryResourceRepository,
resourceRepository: new InMemoryResourceRepository({
initialEntities: initialResources,
}),
resourceVariableRepository: new DbResourceVariableRepository(id),
policyRepository: new DbPolicyRepository(id),
jobAgentRepository: new DbJobAgentRepository(id),
Expand All @@ -106,9 +152,10 @@ const createRepository = async (id: string) => {

export class Workspace {
static async load(id: string) {
const initialResources = await getInitialResources(id);
const [selectorManager, repository] = await Promise.all([
createSelectorManager(id),
createRepository(id),
createSelectorManager(id, initialResources),
createRepository(id, initialResources),
]);

const ws = new Workspace({ id, selectorManager, repository });
Expand Down
Loading