Show HN: Pg-typesafe – PostgreSQL 및 TypeScript용 강력한 타입 지정 쿼리
Source: Hacker News
pg-typesafe
pg-typesafe는 PostgreSQL 쿼리를 위한 TypeScript 타입을 생성합니다.
런타임 의존성이 없으며 추가적인 코드 양도 전혀 늘어나지 않습니다.
const { rows } = client.query(
"select id, name, last_modified from tbl where id = $1",
[42],
);
쿼리는 일반 pg 쿼리와 동일하게 보이지만, 완전히 타입이 지정됩니다:
- 파라미터는 필수이며
number여야 합니다. rows의 타입은{ id: number; name: string; last_modified: Date }[]입니다.
시작하기
pg-typesafe 설치:
npm i -D pg-typesafe
처음 실행:
npm exec pg-typesafe -- --connectionString postgres://postgres:example@localhost
이 명령은 src/defs.gen.ts 파일을 생성하며, 여기에는 pg‑typesafe 타입이 들어 있습니다.
Pool을 pg‑typesafe 타입으로 캐스팅:
import type { TypesafePool } from "./defs.gen.ts";
export const pool = new Pool() as TypesafePool;
이제 pg‑typesafe가 쿼리를 찾아 타입을 지정할 수 있습니다. 쿼리를 추가하거나 변경할 때마다 타입을 다시 생성하세요:
npm exec pg-typesafe -- --connectionString postgres://postgres:example@localhost
제한 사항
pg-typesafe는 SQL 문자열이 상수인 경우에만 쿼리 타입을 지정할 수 있습니다. 동적 쿼리는 분석할 수 없으므로 타입이 지정되지 않습니다. 상수 쿼리를 사용하면 SQL 인젝션을 방지하고 성능을 향상시킬 수 있습니다.
명령줄 옵션
--connectionString (기본값: undefined)
PostgreSQL 데이터베이스에 대한 연결 문자열입니다. pg-typesafe는 표준 node-pg 환경 변수(PGHOST, PGDATABASE 등)도 지원합니다.
--definitionsFile (기본값: src/defs.gen.ts)
생성된 타입 정의 파일의 경로입니다.
--tsConfigFile (기본값: tsconfig.json)
프로젝트의 tsconfig.json 파일 경로입니다. pg-typesafe는 이 파일을 사용해 분석할 소스 파일들을 찾습니다.
--configFile (기본값: pg-typesafe.config.ts)
pg‑typesafe 설정 파일의 경로입니다.
구성 파일
A basic pg-typesafe.config.ts:
import { defineConfig } from "pg-typesafe";
export default defineConfig({
connectionString: "postgres://postgres:example@localhost",
});
전체 구성 매개변수 목록은 패키지의 JSDoc을 통해 문서화되어 있습니다.
레시피
BIGINT를 JavaScript bigint로 변환하기
기본적으로 pg는 BIGINT 값을 문자열로 반환합니다. 이는 JavaScript 숫자의 안전 정수 범위를 초과할 수 있기 때문입니다. 최신 Node.js 버전에서는 네이티브 bigint 타입을 지원합니다.
pg 측에서:
import { types } from "pg";
types.setTypeParser(20, (val) => BigInt(val));
pg‑typesafe 측에서: 파라미터와 결과 필드에 bigint를 사용하도록 변환기를 설정합니다.
import {
defineConfig,
defaultTransformParameter,
defaultTransformField,
} from "pg-typesafe";
export default defineConfig({
// 파라미터 변환
transformParameter(param) {
if (param.type_oid === 20) {
return { type: "bigint" };
}
return defaultTransformParameter(param);
},
// 필드(결과 컬럼) 변환
transformField(field) {
if (field.type_oid === 20) {
return { type: "bigint" };
}
return defaultTransformField(field);
},
});
JSONB 컬럼을 적절한 TypeScript 타입으로 매핑하기
JSONB 컬럼이 알려진 스키마를 따르는 경우, 이를 특정 TypeScript 인터페이스에 매핑할 수 있습니다.
import { defineConfig, defaultTransformField } from "pg-typesafe";
export default defineConfig({
transformField(field) {
if (field.type_oid === 3802 && field.column) {
const c = field.column;
const typeName = `${c.table_name}_${c.column_name}`;
return {
type: typeName,
imports: [{ name: typeName, path: "./jsonb_columns.ts" }],
};
}
return defaultTransformField(field);
},
});
jsonb_columns.ts 파일을 생성하고 해당 인터페이스들을 정의합니다. 예시:
export interface hello_data {
foo: string;
bar: number;
}
타입 전파
생성된 타입이 올바르게 적용되도록, 코드 전체에서 pg‑typesafe‑enhanced 타입을 사용하십시오. 이는 일반적으로 자동으로 작동하며, pool.connect를 통해 클라이언트를 획득할 때도 마찬가지입니다.
연결을 전달할 때는 제공된 타입으로 주석을 달 수 있습니다:
import type {
TypesafePoolClient,
TypesafeQuerier,
TypesafeQueryFn,
} from "./defs.gen.ts";
async function fetchFoos(client: TypesafeQuerier) {
const { rows } = await client.query("select id, name from foo");
return rows;
}