Skip to content

Commit 4f5dc4d

Browse files
committed
Refactor configuration options in README, update SQL type handling, and clean up ignore files
1 parent 334a0cf commit 4f5dc4d

8 files changed

Lines changed: 109 additions & 41 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ web_modules/
8686
# Next.js build output
8787
.next
8888
out
89-
.sqlc
89+
**/.sqlc
9090

9191
# Nuxt.js build / generate output
9292
.nuxt

.npmignore

Lines changed: 0 additions & 9 deletions
This file was deleted.

.vscode/settings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{
22
"cSpell.words": [
3+
"dbsource",
4+
"dbtype",
35
"execrows",
46
"sqlc",
57
"tstype"

README.md

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,19 +98,43 @@ yarn add sqlc-typescript
9898
pnpm add sqlc-typescript
9999
```
100100

101-
## 📝 Configuration
101+
## 📝 Configuration Options
102102

103-
Create a `sqlc.json` in your project root:
103+
The following configuration options can be set in your `sqlc.json` file:
104+
105+
| Option | Type | Default | Description |
106+
| ----------- | --------------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
107+
| `schema` | `string` | `"schema.sql"` | Path to your SQL schema file, typically generated using `pg_dump --schema-only`. This file should contain your database schema definitions including tables, views, and types. |
108+
| `include` | `string` | `"src/**/*.ts"` | Glob pattern for TypeScript files to scan for SQL queries. The generator will look for queries marked with `/*sql*/` in these files. |
109+
| `output` | `string` | `"src/sqlc.ts"` | Location where the generated TypeScript types file will be written. This file will contain all the type definitions and the `sqlc` function. |
110+
| `tmp_dir` | `string` | `".sqlc"` | Directory used for temporary files during type generation. This directory will contain intermediate files used by sqlc. |
111+
| `clear_tmp` | `boolean` | `true` | Whether to remove the temporary directory after type generation is complete. Set to `false` if you need to inspect the intermediate files for debugging. |
112+
| `types` | `{ [key: string]: string }` | `{}` | Map of PostgreSQL types to TypeScript types. Use this to override the default type mappings for specific database types. |
113+
| `columns` | `{ [key: string]: string }` | `{}` | Map of specific column types to TypeScript types. This takes precedence over both default type mappings and `types` overrides. The key should be in the format `"table.column"` or `"schema.table.column"`. |
114+
| `imports` | `string[]` | `[]` | Array of import statements to include in the generated file. Use this when you need to import custom types used in your `types` or `columns` mappings. |
115+
116+
### Example Configuration
104117

105118
```json
106119
{
107-
"include": "src/**/*.ts",
108-
"schema": "schema.sql",
109-
"output": "src/sqlc.ts",
120+
"schema": "db/schema.sql",
121+
"include": "src/**/*.{ts,tsx}",
122+
"output": "src/generated/sqlc.ts",
123+
"tmp_dir": ".sqlc-temp",
124+
"clear_tmp": true,
125+
"types": {
126+
"timestamptz": "DateTime",
127+
"json": "JSONValue"
128+
},
110129
"columns": {
111-
"customer.customer_id": "UUID"
130+
"users.id": "UUID",
131+
"orders.status": "OrderStatus"
112132
},
113-
"imports": ["import { UUID } from '../types'"]
133+
"imports": [
134+
"import type { UUID } from '../types'",
135+
"import type { OrderStatus } from '../db-types'",
136+
"import type { JSONValue } from '../json-types'"
137+
]
114138
}
115139
```
116140

src/render.ts

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const generate_types = ({
2020
line += `{ `;
2121
line += query.columns
2222
.map((column) => {
23-
return `"${column.name}": ${column_to_tstype({ column, config, schema_types })}`;
23+
return `"${column.name}": ${column_to_tstype({ column, config, schema_types, branded: true })}`;
2424
})
2525
.join(';');
2626
line += ` },`;
@@ -32,7 +32,7 @@ export const generate_types = ({
3232
line += `{`;
3333
line += query.params
3434
.map((param) => {
35-
return `"${param.column.name}": ${column_to_tstype({ column: param.column, config, schema_types })}`;
35+
return `"${param.column.name}": ${column_to_tstype({ column: param.column, config, schema_types, branded: false })}`;
3636
})
3737
.join(';');
3838
line += `}`;
@@ -99,12 +99,14 @@ const column_to_tstype = ({
9999
column,
100100
schema_types,
101101
config,
102+
branded,
102103
}: {
103104
column: Column;
104105
schema_types: Set<string>;
105106
config: Pick<Config, 'types' | 'columns'>;
107+
branded: boolean;
106108
}) => {
107-
let type = get_column_type({ config, column, schema_types });
109+
let type = get_column_type({ config, column, schema_types, branded });
108110

109111
if (column.is_array) {
110112
type = `Array<${type}>`;
@@ -121,10 +123,12 @@ const get_column_type = ({
121123
column,
122124
schema_types,
123125
config,
126+
branded,
124127
}: {
125128
column: Column;
126129
schema_types: Set<string>;
127130
config: Pick<Config, 'types' | 'columns'>;
131+
branded: boolean;
128132
}) => {
129133
const source = [
130134
...[column.table?.schema, ...(column.table?.name.split('.') ?? [])].filter(
@@ -133,20 +137,29 @@ const get_column_type = ({
133137
column.original_name,
134138
].join('.');
135139

136-
if (config.columns[source]) {
137-
return config.columns[source];
138-
}
139-
140140
const parts = [column.type.schema, column.type.name.split('.')]
141141
.flat()
142142
.filter((x) => x != null && x !== '' && x !== 'public' && x !== 'pg_catalog');
143143

144-
const type = parts.join('.');
145-
if (schema_types.has(type)) {
146-
return [column.type.schema, column.type.name].join('.');
144+
const db_type = parts.join('.');
145+
146+
const final_type = (() => {
147+
if (config.columns[source]) {
148+
return config.columns[source];
149+
}
150+
151+
if (schema_types.has(db_type)) {
152+
return [column.type.schema, column.type.name].join('.');
153+
}
154+
155+
return DEFAULT_TYPES[db_type] || config.types[db_type] || DEFAULT_TYPES[db_type] || 'unknown';
156+
})();
157+
158+
if (branded) {
159+
return `SqlType<${final_type}, '${db_type}', '${source}'>`;
147160
}
148161

149-
return config.types[type] || DEFAULT_TYPES[type] || 'unknown';
162+
return final_type;
150163
};
151164

152165
export const render_template = ({
@@ -191,6 +204,13 @@ type ExecFn<TRow, TParam> = [TParam] extends [never]
191204
params: TParam & Record<string, unknown>,
192205
) => Promise<Array<ApplyOverride<TSpec, TRow>>>;
193206
207+
class BrandedSqlType<TDbType, TDbSource> {
208+
protected __dbtype: TDbType;
209+
protected __dbsource: TDbSource;
210+
}
211+
212+
type SqlType<T, TDbType, TDbSource> = T & BrandedSqlType<TDbType, TDbSource>;
213+
194214
class Query<TRow, TParam> {
195215
public query;
196216
public params;

tests/sqlc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"include": "src/**/*.ts",
33
"output": "src/sqlc.ts",
44
"schema": "schema.sql",
5+
"clear_tmp": true,
56
"columns": {
67
"customer.customer_id": "UUID"
78
},

tests/src/001.ts renamed to tests/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const client = {
44
query: (query: string, params: unknown[]) => Promise.resolve({ rows: [] }),
55
};
66

7-
sqlc(/*sql*/ `
7+
const ss = await sqlc(/*sql*/ `
88
SELECT
99
first_name,
1010
last_name,

tests/src/sqlc.ts

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ type ExecFn<TRow, TParam> = [TParam] extends [never]
3030
params: TParam & Record<string, unknown>,
3131
) => Promise<Array<ApplyOverride<TSpec, TRow>>>;
3232

33+
class BrandedSqlType<TDbType, TDbSource> {
34+
protected __dbtype: TDbType;
35+
protected __dbsource: TDbSource;
36+
}
37+
38+
type SqlType<T, TDbType, TDbSource> = T & BrandedSqlType<TDbType, TDbSource>;
39+
3340
class Query<TRow, TParam> {
3441
public query;
3542
public params;
@@ -68,14 +75,14 @@ const queries = {
6875
customer_id = @customer_id
6976
`]: new Query<
7077
{
71-
first_name: string;
72-
last_name: string;
73-
email: string | null;
74-
address_id: number;
75-
store_id: number;
76-
activebool: boolean;
77-
create_date: Date;
78-
last_update: Date | null;
78+
first_name: SqlType<string, 'text', 'customer.first_name'>;
79+
last_name: SqlType<string, 'text', 'customer.last_name'>;
80+
email: SqlType<string, 'text', 'customer.email'> | null;
81+
address_id: SqlType<number, 'int4', 'customer.address_id'>;
82+
store_id: SqlType<number, 'int4', 'customer.store_id'>;
83+
activebool: SqlType<boolean, 'bool', 'customer.activebool'>;
84+
create_date: SqlType<Date, 'date', 'customer.create_date'>;
85+
last_update: SqlType<Date, 'timestamptz', 'customer.last_update'> | null;
7986
},
8087
{ customer_id: UUID }
8188
>(
@@ -106,7 +113,13 @@ const queries = {
106113
WHERE
107114
title LIKE '%' || @film_title || '%';
108115
`]: new Query<
109-
{ film_id: number; title: string; description: string | null; release_year: unknown | null; rental_rate: number },
116+
{
117+
film_id: SqlType<number, 'int4', 'film.film_id'>;
118+
title: SqlType<string, 'text', 'film.title'>;
119+
description: SqlType<string, 'text', 'film.description'> | null;
120+
release_year: SqlType<unknown, 'year', 'film.release_year'> | null;
121+
rental_rate: SqlType<number, 'numeric', 'film.rental_rate'>;
122+
},
110123
{ film_title: string | null }
111124
>(
112125
`SELECT
@@ -137,7 +150,15 @@ const queries = {
137150
r.customer_id = @customer_id
138151
ORDER BY
139152
r.rental_date DESC;
140-
`]: new Query<{ rental_id: number; rental_date: Date; film_title: string; return_date: Date | null }, { customer_id: number }>(
153+
`]: new Query<
154+
{
155+
rental_id: SqlType<number, 'int4', 'rental.rental_id'>;
156+
rental_date: SqlType<Date, 'timestamptz', 'rental.rental_date'>;
157+
film_title: SqlType<string, 'text', 'film.title'>;
158+
return_date: SqlType<Date, 'timestamptz', 'rental.return_date'> | null;
159+
},
160+
{ customer_id: number }
161+
>(
141162
`SELECT
142163
r.rental_id,
143164
r.rental_date,
@@ -165,7 +186,10 @@ const queries = {
165186
customer_id
166187
HAVING
167188
customer_id = @customer_id
168-
`]: new Query<{ customer_id: number; rental_count: number }, { customer_id: number }>(
189+
`]: new Query<
190+
{ customer_id: SqlType<number, 'int4', 'rental.customer_id'>; rental_count: SqlType<number, 'bigint', ''> },
191+
{ customer_id: number }
192+
>(
169193
`SELECT
170194
customer_id,
171195
COUNT(*) AS rental_count
@@ -205,7 +229,13 @@ const queries = {
205229
ORDER BY
206230
total_revenue DESC
207231
LIMIT 5
208-
`]: new Query<{ category_name: string; total_revenue: number }, never>(
232+
`]: new Query<
233+
{
234+
category_name: SqlType<string, 'text', 'categoryrevenue.category_name'>;
235+
total_revenue: SqlType<number, 'bigint', 'categoryrevenue.total_revenue'>;
236+
},
237+
never
238+
>(
209239
`WITH CategoryRevenue AS (
210240
SELECT
211241
c.name AS category_name,

0 commit comments

Comments
 (0)