diff --git a/packages/pgkit/src/config.ts b/packages/pgkit/src/config.ts index 8e985ce0..66c816ab 100644 --- a/packages/pgkit/src/config.ts +++ b/packages/pgkit/src/config.ts @@ -1,3 +1,4 @@ +import {type Options as TypegenOptions} from '@pgkit/typegen' import * as fs from 'fs' import * as path from 'path' @@ -5,9 +6,7 @@ export type Config = { client: { connectionString: string } - typegen?: { - connectionString?: string - } + typegen?: Partial migrator?: { connectionString?: string /** @default '${cwd}/migrations' */ diff --git a/packages/pgkit/src/router.ts b/packages/pgkit/src/router.ts index 3f67cf2e..3d5de04f 100644 --- a/packages/pgkit/src/router.ts +++ b/packages/pgkit/src/router.ts @@ -1,7 +1,7 @@ import {Client, createClient} from '@pgkit/client' import {Migrator, createMigratorRouter} from '@pgkit/migrator' import {confirm} from '@pgkit/migrator/dist/cli' -import {router as typegenRouter} from '@pgkit/typegen' +import {generate} from '@pgkit/typegen' import express from 'express' import * as trpcCli from 'trpc-cli' import {z} from 'trpc-cli' @@ -18,7 +18,7 @@ const procedureWithClient = t.procedure.use(async ({ctx, next}) => { migrationTableName: config.migrator?.migrationTableName, })) return next({ - ctx: {...ctx, client, migrator}, + ctx: {...ctx, client, migrator, config}, }) }) @@ -33,7 +33,41 @@ export const router = t.router({ return next({ctx: {...ctx, confirm}}) }), ), - ...typegenRouter._def.procedures, + generate: procedureWithClient + .use(async ({rawInput, ctx, next}) => { + const inputObject = typeof rawInput === 'object' ? Object.keys(rawInput || {}) : [] + return next({ + ctx: {...ctx, inputKeys: new Set(Object.keys(inputObject))}, + }) + }) + .input( + z.object({ + watch: z + .boolean() + .optional() + .describe( + 'Run the type checker in watch mode. Files will be run through the code generator when changed or added.', + ), + lazy: z.boolean().optional().describe('Skip initial processing of input files. Implies --watch.'), + }), + ) + .mutation(async ({ctx, input: {watch, ...input}}) => { + watch = watch ?? input.lazy + if (input.lazy && !watch) throw new Error('Cannot specify --watch=false and --lazy') + + const run = await generate({ + connectionString: ctx.client, + ...input, + ...(ctx.config.typegen && + Object.fromEntries( + Object.entries(ctx.config.typegen).filter(([key]) => !ctx.inputKeys.has(key)), // don't override options explicitly passed, do override defaults + )), + }) + + if (watch) { + await run.watch() + } + }), admin: procedureWithClient .input( z.object({ diff --git a/packages/typegen/src/defaults.ts b/packages/typegen/src/defaults.ts index 73dc65e4..f5c25df5 100644 --- a/packages/typegen/src/defaults.ts +++ b/packages/typegen/src/defaults.ts @@ -48,7 +48,9 @@ export const resolveOptions = (partial: Partial): Options => { defaultType = defaultTypeScriptType, extractQueries = defaultExtractQueries, writeTypes = defaultWriteTypes(), - poolConfig = getWithWarning(logger, `Using default client config.`, {}), + poolConfig = typeof connectionString === 'string' + ? getWithWarning(logger, `Using default client config`, {}) + : connectionString.options, typeParsers = defaultTypeParsers(poolConfig.applyTypeParsers), migrate = undefined, checkClean = defaultCheckClean, @@ -68,7 +70,9 @@ export const resolveOptions = (partial: Partial): Options => { logger.warn(`Unexpected configuration keys: ${Object.keys(rest).join(', ')}`) } - assert.ok(!connectionString.includes(' \'"'), `Connection string should not contain spaces or quotes`) + if (typeof connectionString === 'string') { + assert.ok(!connectionString.includes(' \'"'), `Connection string should not contain spaces or quotes`) + } return { connectionString, diff --git a/packages/typegen/src/generate.ts b/packages/typegen/src/generate.ts index 69a45983..3219b660 100644 --- a/packages/typegen/src/generate.ts +++ b/packages/typegen/src/generate.ts @@ -10,7 +10,7 @@ import * as path from 'path' import * as defaults from './defaults' import {migrateLegacyCode} from './migrate' import {getEnumTypes, getRegtypeToPgTypnameMapping, psqlClient} from './pg' -import {getColumnInfo, getTypeability, removeSimpleComments} from './query' +import {getColumnInfo, getTypeability} from './query' import {getParameterTypes} from './query/parameters' import {ExtractedQuery, Options, QueryField, QueryParameter} from './types' import {changedFiles, checkClean, containsIgnoreComment, globList} from './util' @@ -21,10 +21,13 @@ export const generate = async (inputOptions: Partial) => { const options = defaults.resolveOptions(inputOptions) const logger = options.logger - const pool = createClient(options.connectionString, options.poolConfig) + const pool = + typeof options.connectionString === 'string' + ? createClient(options.connectionString, options.poolConfig) + : options.connectionString const _gdesc = (inputSql: string, searchPath?: string) => { - let connectionString = options.connectionString + let connectionString = pool.connectionString() if (searchPath) { const url = new URL(connectionString) const optionsString = url.searchParams.get('options') @@ -127,7 +130,7 @@ export const generate = async (inputOptions: Partial) => { const logMsgInclude = `pattern${options.include.length > 1 ? 's' : ''} ${options.include.join(', ')}` const logMsgExclude = options.exclude.length > 0 ? ` excluding ${options.exclude.join(', ')}` : '' const logMsgSince = options.since ? ` since ${options.since}` : '' - logger.info(`Matching files in ${getLogPath(cwd)} with ${logMsgInclude}${logMsgExclude}${logMsgSince}`) + logger.info(`Matching files in ${cwd} with ${logMsgInclude}${logMsgExclude}${logMsgSince}`) const getLogQueryReference = (query: {file: string; line: number}) => `${getLogPath(query.file)}:${query.line}` diff --git a/packages/typegen/src/router.ts b/packages/typegen/src/router.ts index 57c0fd4d..e5906a63 100644 --- a/packages/typegen/src/router.ts +++ b/packages/typegen/src/router.ts @@ -6,7 +6,8 @@ import {generate, Options} from './generate' const trpc = trpcServer.initTRPC.meta().create() -const CliOptions = z +// todo: deprecate in favour of inputs more like the ones in pgkit +export const CliOptions = z .object({ config: z .string() diff --git a/packages/typegen/src/types.ts b/packages/typegen/src/types.ts index 8dd2f05e..04b510d6 100644 --- a/packages/typegen/src/types.ts +++ b/packages/typegen/src/types.ts @@ -1,4 +1,4 @@ -import {ClientOptions} from '@pgkit/client' +import {Client, ClientOptions} from '@pgkit/client' /** * Options that can be specified in `typegen.config.js`. Each is optional (has a default value). @@ -11,7 +11,7 @@ export interface Options { * * Note: It's not recommended to run this tool against a production database, even though it doesn't perform any dynamic queries. */ - connectionString: string + connectionString: string | Client /** * How to execute `psql` from the machine running this tool. * diff --git a/pgkit.config.ts b/pgkit.config.ts index 5d382e7f..df6dff56 100644 --- a/pgkit.config.ts +++ b/pgkit.config.ts @@ -2,4 +2,8 @@ export default { client: { connectionString: 'postgresql://postgres:postgres@localhost:5432/postgres', }, + typegen: { + psqlCommand: 'docker-compose exec -T postgres psql', + checkClean: [], + } } satisfies import('./packages/pgkit/src/config').Config