From 8e67a998cf866840b9549d3bc88e5ee09fb203fe Mon Sep 17 00:00:00 2001 From: Charlie Cooper Date: Wed, 23 Aug 2023 11:13:08 +0100 Subject: [PATCH 1/2] create hooks for join tables --- src/hooks/index.ts | 9 +++- src/hooks/joinTable.ts | 95 +++++++++++++++++++++++++++++++++++++++ src/hooks/table.ts | 92 +++++-------------------------------- src/pgMeta/fetchTables.ts | 85 +++++++++++++++++++++++++++++++++++ src/utils/index.ts | 2 + 5 files changed, 200 insertions(+), 83 deletions(-) create mode 100644 src/hooks/joinTable.ts create mode 100644 src/pgMeta/fetchTables.ts diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 370682d..d537104 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,9 +1,14 @@ +import { fetchTables } from "../pgMeta/fetchTables"; import type { HookFile } from "../types"; import { parseTableFiles } from "./table"; +import { parseJoinTableFiles } from "./joinTable"; import { parseViewFiles } from "./view"; export const parseHookFiles = async (): Promise => { - const tableFiles = await parseTableFiles(); + const { tables, joinTables } = await fetchTables(); + + const tableFiles = await parseTableFiles(tables); + const joinTableFiles = await parseJoinTableFiles(joinTables); const viewFiles = await parseViewFiles(); - return [...tableFiles, ...viewFiles]; + return [...tableFiles, ...joinTableFiles, ...viewFiles]; }; diff --git a/src/hooks/joinTable.ts b/src/hooks/joinTable.ts new file mode 100644 index 0000000..aa893e0 --- /dev/null +++ b/src/hooks/joinTable.ts @@ -0,0 +1,95 @@ +import prettier from "prettier"; +import comment from "../comment"; +import { TableResponse, TablesResponse } from "../pgMeta/fetchTables"; +import type { File, HookFile } from "../types"; +import { DIRECTORY, parseNameFormats } from "../utils"; + +const mapJoinTableToFile = async ( + joinTable: TableResponse +): Promise => { + const joinTableFormats = parseNameFormats(joinTable.name); + + const tableOneFormats = parseNameFormats( + joinTable.relationships.at(0)!.targetTableName + ); + const tableTwoFormats = parseNameFormats( + joinTable.relationships.at(1)!.targetTableName + ); + + const tableOneFetchFunctionName = `fetch${tableTwoFormats.pascalCasePlural}For${tableOneFormats.pascalCase}`; + const tableTwoFetchFunctionName = `fetch${tableOneFormats.pascalCasePlural}For${tableTwoFormats.pascalCase}`; + + const content = ` + ${comment} + + import { supabase } from "../supabase"; + import { Database } from "../types"; + + type ${tableOneFormats.pascalCase}Row = Database["public"]["Tables"]["${tableOneFormats.name}"]["Row"] + type ${tableTwoFormats.pascalCase}Row = Database["public"]["Tables"]["${tableTwoFormats.name}"]["Row"] + + const use${joinTableFormats.pascalCasePlural} = () => { + const ${tableOneFetchFunctionName} = async(id: ${tableOneFormats.pascalCase}Row["id"]) => { + try { + const { data, error } = await supabase + .from("${tableOneFormats.name}") + .select("*, ${tableTwoFormats.name}(*)") + .eq("id", id); + if (error) { + throw error; + } + return data + } catch (error) { + console.error("Error fetching", error); + } + }; + + const ${tableTwoFetchFunctionName} = async(id: ${tableTwoFormats.pascalCase}Row["id"]) => { + try { + const { data, error } = await supabase + .from("${tableTwoFormats.name}") + .select("*, ${tableOneFormats.name}(*)") + .eq("id", id); + if (error) { + throw error; + } + return data + } catch (error) { + console.error("Error fetching", error); + } + }; + + return { ${tableOneFetchFunctionName}, ${tableTwoFetchFunctionName} }; + }; + + export default use${joinTableFormats.pascalCasePlural}; + `; + + const formattedContent = await prettier.format(content, { + parser: "typescript", + }); + + const file: File = { + fileName: `use${joinTableFormats.pascalCasePlural}`, + content: formattedContent, + }; + const usage = `const { ${tableOneFetchFunctionName} } = use${joinTableFormats.pascalCasePlural}();`; + + return { + file, + location: `${DIRECTORY}/hooks/${file.fileName}.ts`, + type: "HOOK", + entity: "TABLE", + usage, + }; +}; + +export const parseJoinTableFiles = async ( + joinTables: TablesResponse +): Promise => { + const joinTableHookPromises = + joinTables.map>(mapJoinTableToFile); + const joinTableFiles = await Promise.all(joinTableHookPromises); + + return joinTableFiles; +}; diff --git a/src/hooks/table.ts b/src/hooks/table.ts index affa075..4d6ec92 100644 --- a/src/hooks/table.ts +++ b/src/hooks/table.ts @@ -1,83 +1,11 @@ -import axios from "axios"; import prettier from "prettier"; import comment from "../comment"; import type { File, HookFile } from "../types"; -import { DIRECTORY, log, parseNameFormats } from "../utils"; - -export type ColumnResponse = { - tableId: number; - schema: string; - table: string; - id: string; - ordinalPosition: number; - name: string; - defaultValue: unknown; - dataType: string; - format: string; - isIdentity: boolean; - identityGeneration: "ALWAYS" | "BY DEFAULT" | null; - isGenerated: boolean; - isNullable: boolean; - isUpdatable: boolean; - isUnique: boolean; - enums: string[]; - check: string | null; - comment: string | null; -}; - -export type TableResponse = { - id: number; - schema: string; - name: string; - rlsEnabled: boolean; - rlsForced: boolean; - replicaIdentity: "DEFAULT" | "INDEX" | "FULL" | "NOTHING"; - bytes: number; - size: string; - liveRowsEstimate: number; - deadRowsEstimate: number; - comment: string | null; - columns?: ColumnResponse[]; - primaryKeys: { - schema: string; - tableName: string; - name: string; - tableId: number; - }[]; - relationships: { - id: number; - constraintName: string; - sourceSchema: string; - sourceTableName: string; - sourceColumnName: string; - targetTableSchema: string; - targetTableName: string; - targetColumnName: string; - }[]; -}; +import { DIRECTORY, parseNameFormats } from "../utils"; +import { TableResponse, TablesResponse } from "../pgMeta/fetchTables"; -export type TablesResponse = TableResponse[]; - -const parseTableNames = async (): Promise => { - const tablesResponse = await axios.get( - `${process.env.BACKENGINE_BASE_URL}/api/v1/projects/${process.env.BACKENGINE_PROJECT_ID}/pg-meta/tables`, - { - headers: { - Authorization: `Bearer ${process.env.BACKENGINE_API_KEY}`, - Accept: "application/json", - }, - } - ); - log("Fetched table metadata"); - - const publicTables = tablesResponse.data - .filter((table) => table.schema === "public") - .filter(({ primaryKeys }) => primaryKeys.some(({ name }) => name === "id")); - - return publicTables.map((table) => table.name); -}; - -const mapTableToFile = async (tableName: string): Promise => { +const mapTableToFile = async (table: TableResponse): Promise => { + const { name: tableName } = table; const { pascalCase, pascalCasePlural, camelCase, camelCasePlural } = parseNameFormats(tableName); @@ -190,9 +118,11 @@ const mapTableToFile = async (tableName: string): Promise => { }; }; -export const parseTableFiles = async (): Promise => { - const tableNames = await parseTableNames(); - const hookPromises = tableNames.map>(mapTableToFile); - const files = await Promise.all(hookPromises); - return files; +export const parseTableFiles = async ( + tables: TablesResponse +): Promise => { + const tableHookPromises = tables.map>(mapTableToFile); + const tableFiles = await Promise.all(tableHookPromises); + + return tableFiles; }; diff --git a/src/pgMeta/fetchTables.ts b/src/pgMeta/fetchTables.ts new file mode 100644 index 0000000..7e8de22 --- /dev/null +++ b/src/pgMeta/fetchTables.ts @@ -0,0 +1,85 @@ +import axios from "axios"; +import { log } from "../utils"; + +export type ColumnResponse = { + tableId: number; + schema: string; + table: string; + id: string; + ordinalPosition: number; + name: string; + defaultValue: unknown; + dataType: string; + format: string; + isIdentity: boolean; + identityGeneration: "ALWAYS" | "BY DEFAULT" | null; + isGenerated: boolean; + isNullable: boolean; + isUpdatable: boolean; + isUnique: boolean; + enums: string[]; + check: string | null; + comment: string | null; +}; + +export type TableResponse = { + id: number; + schema: string; + name: string; + rlsEnabled: boolean; + rlsForced: boolean; + replicaIdentity: "DEFAULT" | "INDEX" | "FULL" | "NOTHING"; + bytes: number; + size: string; + liveRowsEstimate: number; + deadRowsEstimate: number; + comment: string | null; + columns?: ColumnResponse[]; + primaryKeys: { + schema: string; + tableName: string; + name: string; + tableId: number; + }[]; + relationships: { + id: number; + constraintName: string; + sourceSchema: string; + sourceTableName: string; + sourceColumnName: string; + targetTableSchema: string; + targetTableName: string; + targetColumnName: string; + }[]; +}; + +export type TablesResponse = TableResponse[]; + +export async function fetchTables(): Promise<{ + tables: TablesResponse; + joinTables: TablesResponse; +}> { + const tablesResponse = await axios.get( + `${process.env.BACKENGINE_BASE_URL}/api/v1/projects/${process.env.BACKENGINE_PROJECT_ID}/pg-meta/tables`, + { + headers: { + Authorization: `Bearer ${process.env.BACKENGINE_API_KEY}`, + Accept: "application/json", + }, + } + ); + log("Fetched table metadata"); + + const publicTables = tablesResponse.data.filter( + (table) => table.schema === "public" + ); + + const tables = publicTables.filter(({ primaryKeys }) => + primaryKeys.some(({ name }) => name === "id") + ); + const joinTables = publicTables.filter( + ({ primaryKeys, relationships }) => + primaryKeys.length === 2 && relationships.length === 2 + ); + return { tables, joinTables }; +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 1cc7f7a..1873f81 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -36,12 +36,14 @@ const sanitiseFormat = (format: string): string => { export const parseNameFormats = ( name: string ): { + name: string; pascalCase: string; pascalCasePlural: string; camelCase: string; camelCasePlural: string; } => { return { + name, pascalCase: sanitiseFormat(singular(toPascalCase(name))), pascalCasePlural: sanitiseFormat(plural(toPascalCase(name))), camelCase: sanitiseFormat(singular(toCamelCase(name))), From b94e553b50346090887fbca377c4628d7bf08ba2 Mon Sep 17 00:00:00 2001 From: Charlie Cooper Date: Wed, 23 Aug 2023 11:13:41 +0100 Subject: [PATCH 2/2] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2168a43..21cbc6a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@backengine/codegen", - "version": "1.0.15", + "version": "1.0.16", "description": "Generate code for Backengine projects.", "bin": "build/index.js", "files": [