import _endent from 'endent'; import * as fs from 'fs'; import type { JSONPgSmartTags } from 'graphile-utils'; import JSON5 from 'json5'; import * as path from 'path'; import { type BaseSmartTags, type PgColumn, type PgFkColumn, type PgTable, type TableSmartTags, } from '../abstractions/index.js'; // endent is a TypeScript-compiled CJS package that emits // `module.exports = { __esModule: true, default: value }`. // Node.js native ESM does not honour the __esModule sentinel — the default // import receives the whole module.exports wrapper. `endent.pretty()` is also // accessible via `.default.pretty`. Pattern 9 in ESM_MIGRATION_GUIDE.md. // The `?? _endent` fallback keeps the CJS build safe. const endent = ((_endent as any).default ?? _endent) as typeof _endent; /** * Builds smart tags for FK constraints. * @param columns - Array of FK columns. */ export function buildFkConstraintSmartTags(columns: PgFkColumn[]): { [name: string]: BaseSmartTags; } { return columns.reduce((acc, col) => { if (col.fkName) { acc[col.fkName] = { tags: { foreignFieldName: `${col.table.displayName ?? col.table.name}`, }, }; } return acc; }, {} as { [name: string]: BaseSmartTags }); } /** * Builds class (table) level smart tags. * @param tables - Array of tables. */ export function buildClassSmartTags(tables: PgTable[]): { [name: string]: TableSmartTags; } { const tags: { [name: string]: TableSmartTags } = {}; tables.forEach((t) => { tags[t.name] = t.buildSmartTags(); }); return tags; } /** * Builds smart tags for virtual FK columns. * @param virtualFks - Array of virtual FK columns. */ export function buildVirtualFkSmartTags(virtualFks: PgFkColumn[]): string[] { return virtualFks.map((vfk) => { return `(${vfk.name}) references ${vfk.targetPk.table.name}|@omit many`; }); } /** * Builds attribute (column) level smart tags. * @param columns - Array of PgColumns. */ export function buildAttributeSmartTags(columns: PgColumn[]): { [name: string]: BaseSmartTags; } { return columns.reduce((acc, col) => { acc[col.name] = col.buildSmartTags(); return acc; }, {} as { [name: string]: BaseSmartTags }); } // TODO: Replace with camelCase from graphile-build. export function snakeCaseToCamelCase(input: string): string { return input .split('_') .reduce( (res, word, i) => i === 0 ? word.toLowerCase() : `${res}${word.charAt(0).toUpperCase()}${word .substr(1) .toLowerCase()}`, '', ); } /** * Generates a smart tags plugin file. * @param tags - Smart tags configuration. * @param outPath - Output path where the plugin will be written. */ export function generateSmartTagsPlugin( tags: JSONPgSmartTags, outPath: string, ): void { const dirPath = path.dirname(outPath); if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } console.log(`Writing smart tags plugn to to ${outPath}.`); const pluginSrc = endent` import graphileUtils from 'graphile-utils'; const { makeJSONPgSmartTagsPlugin } = graphileUtils; /** * Smart tags to adjust and enhance the generated GraphQL API. */ export const SmartTagsPlugin = makeJSONPgSmartTagsPlugin(${endent.pretty( // Use JSON5 to generate keys without quotes to make the generated code already compatible with prettier. JSON5.stringify(tags, null, 2), )}); `; fs.writeFileSync(outPath, pluginSrc); }