diff --git a/.changeset/swift-swans-rush.md b/.changeset/swift-swans-rush.md new file mode 100644 index 00000000..918fa76e --- /dev/null +++ b/.changeset/swift-swans-rush.md @@ -0,0 +1,5 @@ +--- +"@proofkit/fmdapi": patch +--- + +Update removeFMTableNames function for proper type-safety diff --git a/packages/fmdapi/src/utils.ts b/packages/fmdapi/src/utils.ts index 093dc5e6..f5345415 100644 --- a/packages/fmdapi/src/utils.ts +++ b/packages/fmdapi/src/utils.ts @@ -1,31 +1,29 @@ -import type { S, L, U } from "ts-toolbelt"; import type * as z3 from "zod/v3"; import type * as z4 from "zod/v4/core"; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type TransformedFields> = U.Merge< - { - [Field in keyof T]: { - [Key in Field extends string - ? L.Last> - : Field]: T[Field]; - }; - }[keyof T] ->; +type StripFMTableName = K extends `${string}::${infer R}` + ? R + : K; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function removeFMTableNames>( +type TransformedFields = { + [K in keyof T as K extends string ? StripFMTableName : K]: T[K]; +}; + +export function removeFMTableNames( obj: T, ): TransformedFields { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const newObj: any = {}; + const newObj = {} as TransformedFields; for (const key in obj) { - if (key.includes("::")) { - const newKey = key.split("::")[1]; - newObj[newKey as keyof TransformedFields] = obj[key]; - } else { - newObj[key] = obj[key]; - } + const originalKey = key as keyof T; + const value = obj[originalKey]; + const mappedKey = ( + typeof key === "string" && key.includes("::") ? key.split("::")[1] : key + ) as keyof TransformedFields; + + // Use a temporary index signature cast to assign without any + (newObj as unknown as Record)[ + mappedKey as unknown as PropertyKey + ] = value as unknown; } return newObj; } diff --git a/packages/fmdapi/tests/removeFMTableNames.test.ts b/packages/fmdapi/tests/removeFMTableNames.test.ts new file mode 100644 index 00000000..1bfe3e7f --- /dev/null +++ b/packages/fmdapi/tests/removeFMTableNames.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from "vitest"; +import { removeFMTableNames } from "../src"; + +describe("removeFMTableNames", () => { + it("strips FM table prefixes from keys and preserves values", () => { + const input = { + "Customer::name": "Alice", + count: 2, + "X::flag": true, + } as const; + + const out = removeFMTableNames(input); + + expect(out).toEqual({ name: "Alice", count: 2, flag: true }); + expect("Customer::name" in (out as Record)).toBe(false); + expect("name" in out).toBe(true); + expect(out.name).toBe("Alice"); + expect(out.count).toBe(2); + expect(out.flag).toBe(true); + }); + + it("produces a type with stripped keys", () => { + type Input = { + "Customer::first_name": string; + last_name: string; + "Portal::recordId": number; + }; + + const input: Input = { + "Customer::first_name": "Bob", + last_name: "Builder", + "Portal::recordId": 42, + }; + const out = removeFMTableNames(input); + + // Type-level assertions via assignment + const first: string = out.first_name; + const last: string = out.last_name; + const recId: number = out.recordId; + expect(first).toBe("Bob"); + expect(last).toBe("Builder"); + expect(recId).toBe(42); + + // Old keys should not exist on the resulting type + // @ts-expect-error - old FM-prefixed key should not exist on result type + out["Customer::first_name"]; + // @ts-expect-error - old FM-prefixed key should not exist on result type + out["Portal::recordId"]; + }); +}); diff --git a/packages/fmdapi/tests/tsconfig.json b/packages/fmdapi/tests/tsconfig.json new file mode 100644 index 00000000..f4d49859 --- /dev/null +++ b/packages/fmdapi/tests/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "noEmit": true, + "types": ["vitest", "node"], + "isolatedModules": true + }, + "include": ["./**/*.ts"], + "exclude": ["../dist", "../schema"] +} diff --git a/packages/fmdapi/tests/tsconfig.typecheck.json b/packages/fmdapi/tests/tsconfig.typecheck.json new file mode 100644 index 00000000..16f5563e --- /dev/null +++ b/packages/fmdapi/tests/tsconfig.typecheck.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "strict": true, + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "react-jsx", + "skipLibCheck": true, + "noEmit": true, + "isolatedModules": true, + "types": ["node"] + }, + "files": ["./type/removeFMTableNames.typecheck.ts"] +}