diff --git a/packages/cli-server/src/source/effects.ts b/packages/cli-server/src/source/effects.ts index de7387ea..054f7db4 100644 --- a/packages/cli-server/src/source/effects.ts +++ b/packages/cli-server/src/source/effects.ts @@ -48,7 +48,6 @@ export async function runCliLoadSourceEffect(input: { }): Promise { const sourceConditions = [ eq(dataSources.organizationId, input.effect.organizationId), - eq(dataSources.name, input.effect.sourceKey), ]; if (input.effect.sourceProvider) { sourceConditions.push( @@ -56,7 +55,7 @@ export async function runCliLoadSourceEffect(input: { ); } - const row = await input.db.query.dataSources.findFirst({ + const rows = await input.db.query.dataSources.findMany({ columns: { id: true, name: true, @@ -69,14 +68,11 @@ export async function runCliLoadSourceEffect(input: { where: and(...sourceConditions), }); - if (!row) { - return { - kind: "not_found", - }; - } + const row = rows + .map((candidate) => createCliQuerySourceRecord(candidate)) + .find((source) => source?.sourceKey === input.effect.sourceKey); - const source = createCliQuerySourceRecord(row); - if (!source) { + if (!row) { return { kind: "not_found", }; @@ -84,7 +80,7 @@ export async function runCliLoadSourceEffect(input: { return { kind: "found", - source, + source: row, }; } diff --git a/packages/cli-server/src/source/model.test.ts b/packages/cli-server/src/source/model.test.ts index dbc07334..db3b0667 100644 --- a/packages/cli-server/src/source/model.test.ts +++ b/packages/cli-server/src/source/model.test.ts @@ -39,6 +39,10 @@ describe("source model", () => { expect(createCliSourceKey(" .. ")).toBeNull(); }); + it("derives safe source keys from display-style connection names", () => { + expect(createCliSourceKey("Slack - wordbricks")).toBe("slack-wordbricks"); + }); + it("exposes BigQuery as both query and API capable", () => { expect(getCliSourceInterfaceTypes("bigquery")).toEqual(["query", "api"]); }); diff --git a/packages/cli-server/src/source/model.ts b/packages/cli-server/src/source/model.ts index c43c9de9..de399670 100644 --- a/packages/cli-server/src/source/model.ts +++ b/packages/cli-server/src/source/model.ts @@ -18,15 +18,33 @@ import { isCliSourceKey } from "../identifiers"; type CliSourceInterfaceType = "query" | "api"; +const CLI_SOURCE_KEY_NORMALIZATION_PATTERN = /[^a-z0-9._-]+/g; +const CLI_SOURCE_KEY_BOUNDARY_PATTERN = /^[._-]+|[._-]+$/g; +const CLI_SOURCE_KEY_REPEAT_PATTERN = /[._-]{2,}/g; + +function normalizeCliSourceKey(name: string): string | null { + const normalized = name + .trim() + .toLowerCase() + .replaceAll(CLI_SOURCE_KEY_NORMALIZATION_PATTERN, "-") + .replaceAll(CLI_SOURCE_KEY_BOUNDARY_PATTERN, "") + .replaceAll(CLI_SOURCE_KEY_REPEAT_PATTERN, "-"); + + return normalized.length > 0 && isCliSourceKey(normalized) + ? normalized + : null; +} + // The backing table still stores the CLI-visible source identity in // data_sources.name. The CLI domain treats that normalized org-unique name as // the canonical sourceKey so the rest of the workflow code never reaches for // raw table field names. export function createCliSourceKey(name: string): string | null { const normalized = name.trim(); - return normalized.length > 0 && isCliSourceKey(normalized) - ? normalized - : null; + if (normalized.length > 0 && isCliSourceKey(normalized)) { + return normalized; + } + return normalizeCliSourceKey(normalized); } export function createCliSourceRecord(input: {