Skip to content

Commit bc637a4

Browse files
committed
Enhance README and CLI: Update documentation structure, improve config loading, and add path existence check
1 parent c997e70 commit bc637a4

File tree

6 files changed

+59
-39
lines changed

6 files changed

+59
-39
lines changed

README.md

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,25 @@ A super lightweight TypeScript types generator that respects your laziness and l
2323

2424
Zero runtime dependencies, just types. This is just a super thin wrapper around [sqlc](https://sqlc.dev/) and a file generator - all the real magic is in sqlc. It just makes it more convenient to use in TypeScript projects.
2525

26-
## Demo 🚀
26+
## TLDR
27+
28+
- `pg_dump --schema-only postgres://user:password@localhost:5432/database > schema.sql` to dump your schema
29+
- Run `npx sqlc-typescript watch` (`src/**/*.ts` is default glob and `schema.sql` is default schema file)
30+
- Write SQL queries in your TypeScript files using the `/*sql*/` comment and `sqlc` function e.g.
31+
32+
```typescript
33+
const result = await sqlc(/*sql*/ `
34+
SELECT customer_id, first_name, last_name
35+
FROM customer
36+
WHERE customer_id = @customer_id
37+
`).exec(client, {
38+
customer_id: 1,
39+
});
40+
```
41+
42+
- Import the generated `sqlc` function and get perfect types 🔥
43+
44+
## 🚀 Demo
2745

2846
<img alt="image" src="https://github.com/user-attachments/assets/0556e61c-72ab-465e-86b7-3013e1b82c6f" />
2947

@@ -33,19 +51,19 @@ Zero runtime dependencies, just types. This is just a super thin wrapper around
3351
https://github.com/user-attachments/assets/dba59632-6c4c-48fe-80f0-da1514e2da1a
3452
</details>
3553

36-
## Why? 🤔
54+
## 🤔 Why?
3755

3856
If you're like me - you just want to write SQL, ship features and not deal with heavy abstractions or spend hours reading documentation (even if it's really good). That's exactly why this exists.
3957

40-
### The Problem
58+
### 🤯 The Problem
4159

4260
- ORMs are complex and make you learn their quirks
4361
- SQL-like query builders still make you learn their syntax and requires rewriting existing queries to their format
4462
- Writing SQL in separate files is annoying
4563
- Maintaining function names for every query is tedious
4664
- Other tools require database connections for type inference (which isn't always accurate)
4765

48-
### The Solution 🎯
66+
### 🎯 The Solution
4967

5068
Write SQL directly in your TypeScript files, get perfect types, and ship faster. That's it.
5169

@@ -67,7 +85,7 @@ const result = await sqlc(/*sql*/ `
6785
// result: { customer_id: number, first_name: string | null, last_name: string }[]
6886
```
6987

70-
## Installation 🛠️
88+
## 🛠️ Installation
7189

7290
```bash
7391
# Using npm
@@ -80,7 +98,7 @@ yarn add sqlc-typescript
8098
pnpm add sqlc-typescript
8199
```
82100

83-
## Configuration 📝
101+
## 📝 Configuration
84102

85103
Create a `sqlc.json` in your project root:
86104

@@ -132,21 +150,22 @@ npx sqlc-typescript generate -c sqlc.json
132150
npx sqlc-typescript watch -c sqlc.json
133151
```
134152

135-
## How It Works Under The Hood 🔧
153+
## 🔧 How It Works Under The Hood
136154

137155
1. **File Scanning**: The tool scans your TypeScript files for SQL queries marked with `/*sql*/`
138156
2. **Type Generation**: Uses [sqlc](https://github.com/sqlc-dev/sqlc) under the hood to analyze your SQL and generate types
139157
3. **Zero Runtime Overhead**: All the magic happens at build time - no runtime dependencies!
140158

141-
### Why Tagged Templates Can't Be Used 🏷️
159+
### 🏷️ Why Tagged Templates Can't Be Used
142160

143161
Unfortunately, we can't use tagged template literals like `` sql`SELECT * FROM users` `` for proper syntax highlighting. TypeScript template literals [can't be generic](https://github.com/microsoft/TypeScript/issues/33304), so we can use the `/*sql*/` comment approach instead. Your IDE or SQL plugin will still provide syntax highlighting!
144162

145-
### Comparison with Other Tools 🔍
163+
### 🔍 Comparison with Other Tools
146164

147165
- [pgTyped](https://github.com/adelsz/pgtyped): Requires separate SQL files and function imports. It uses PostgreSQL wire protocol for type inference which requires a database connection and can't handle nullability well.
148166
- [Prisma TypedSQL](https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/typedsql): SQL files are separate and require function imports and it's Prisma 🫠.
149167
- [SafeQL](https://github.com/ts-safeql/safeql): Great tool but requires ESLint and database connection for type inference.
168+
- [Drizzle](https://orm.drizzle.team/): SQL-like a great query builder but it's not just SQL. I don't want to learn another syntax even if it's very close to SQL. I can't copy-past my queries from psql back and forth.
150169

151170
The key difference: We use sqlc's SQL parser instead of PostgreSQL wire protocol for type inference, which means:
152171

src/cli.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { watch } from 'chokidar';
22
import { Command } from 'commander';
33
import fs from 'node:fs/promises';
44
import path from 'node:path';
5-
import { generate } from './generator.ts';
5+
import { generate, path_exists } from './generator.ts';
66
import type { Config } from './types.ts';
77

88
const program = new Command();
@@ -14,11 +14,7 @@ program
1414
.requiredOption('-c, --config <path>', 'Config file', 'sqlc.json')
1515
.action(async (options) => {
1616
const config_path = path.resolve(options.config);
17-
const root = path.dirname(config_path);
18-
const config = await fs
19-
.readFile(config_path, 'utf8')
20-
.then(JSON.parse)
21-
.then((config) => validate_config(config, root));
17+
const { config } = await load_config(config_path);
2218

2319
await generate(config);
2420
});
@@ -28,12 +24,7 @@ program
2824
.requiredOption('-c, --config <path>', 'Config file', 'sqlc.json')
2925
.action(async (options) => {
3026
const config_path = path.resolve(options.config);
31-
const root = path.dirname(config_path);
32-
const config = await fs
33-
.readFile(config_path, 'utf8')
34-
.then(JSON.parse)
35-
.then((config) => validate_config(config, root));
36-
27+
const { config, root } = await load_config(config_path);
3728
const watch_root = path.resolve(root, glob_root(config.include));
3829

3930
const watcher = watch(watch_root, {
@@ -59,6 +50,17 @@ program
5950
console.log(`🟣 Watching directory "${watch_root}"`);
6051
});
6152

53+
const load_config = async (config_path: string) => {
54+
const root = path.dirname(config_path);
55+
const exists = await path_exists(config_path);
56+
const raw_config = exists ? await fs.readFile(config_path, 'utf8').then(JSON.parse) : {};
57+
58+
return {
59+
config: validate_config(raw_config, root),
60+
root,
61+
};
62+
};
63+
6264
const validate_config = (config: unknown, root: string): Config => {
6365
if (typeof config !== 'object' || config == null) {
6466
throw new Error('Invalid config: expected an object');

src/generator.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -130,17 +130,7 @@ export const exec_sqlc = async ({ tmp_dir, root }: Pick<Config, 'tmp_dir' | 'roo
130130
export const prepare_tmp_dir = async ({ root, schema, tmp_dir }: Pick<Config, 'root' | 'schema' | 'tmp_dir'>) => {
131131
const full_tmp_dir = path.join(root, tmp_dir);
132132

133-
const exists = await fs
134-
.access(full_tmp_dir)
135-
.then(() => true)
136-
.catch((error) => {
137-
if (error.code === 'ENOENT') {
138-
return false;
139-
}
140-
141-
throw error;
142-
});
143-
133+
const exists = await path_exists(full_tmp_dir);
144134
if (exists) {
145135
await fs.rm(full_tmp_dir, { recursive: true });
146136
}
@@ -207,3 +197,16 @@ function render_query(sql: string) {
207197

208198
return { name, content };
209199
}
200+
201+
export const path_exists = (full_path: string) => {
202+
return fs
203+
.access(full_path)
204+
.then(() => true)
205+
.catch((error) => {
206+
if (error.code === 'ENOENT') {
207+
return false;
208+
}
209+
210+
throw error;
211+
});
212+
};

tests/sqlc.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
2-
"include": "queries/**/*.ts",
2+
"include": "src/**/*.ts",
3+
"output": "src/sqlc.ts",
34
"schema": "schema.sql",
4-
"output": "sqlc.ts",
55
"columns": {
66
"customer.customer_id": "UUID"
77
},

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import { sqlc } from '../sqlc';
1+
import { sqlc } from './sqlc';
22

33
const client = {
44
query: (query: string, params: unknown[]) => Promise.resolve({ rows: [] }),
55
};
66

77
sqlc(/*sql*/ `
88
SELECT
9-
customer_id,
109
first_name,
1110
last_name,
1211
email,

tests/sqlc.ts renamed to tests/src/sqlc.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ type Queries = typeof queries;
5454
const queries = {
5555
[`
5656
SELECT
57-
customer_id,
5857
first_name,
5958
last_name,
6059
email,
@@ -69,7 +68,6 @@ const queries = {
6968
customer_id = @customer_id
7069
`]: new Query<
7170
{
72-
customer_id: UUID;
7371
first_name: string;
7472
last_name: string;
7573
email: string | null;
@@ -82,7 +80,6 @@ const queries = {
8280
{ customer_id: UUID }
8381
>(
8482
`SELECT
85-
customer_id,
8683
first_name,
8784
last_name,
8885
email,

0 commit comments

Comments
 (0)