11# sqlf
22
3- MVP for a SQL-first TypeScript code generator targeting ` effect ` + ` @effect/sql ` .
3+ ` sqlf ` is an MVP for a SQL-first TypeScript code generator targeting ` effect ` + ` @effect/sql ` .
4+
5+ It is inspired by ` sqlc ` , but uses:
6+
7+ - TypeScript / JS config via ` defineConfig() `
8+ - database introspection for result / parameter typing
9+ - generated ` Schema ` + ` SqlSchema.* ` helpers for Effect-native usage
410
511## Workspace
612
713- ` packages/core ` — config loading, SQL parsing, Postgres analysis, code generation
8- - ` packages/cli ` — ` sqlf generate ` command
9- - ` examples/basic ` — example config and SQL queries
14+ - ` packages/cli ` — ` sqlf generate `
15+ - ` examples/basic ` — self-contained Postgres + generated CRUD example + Effect HTTP API
16+
17+ ## MVP scope
18+
19+ Current MVP supports:
20+
21+ - JS / TS config via ` defineConfig() `
22+ - Postgres-only
23+ - sqlc-style query annotations: ` :one ` , ` :maybeOne ` , ` :many ` , ` :exec `
24+ - named params via ` @param `
25+ - Postgres-backed result type inference
26+ - generated Effect SQL wrappers via ` SqlSchema.single/findOne/findAll/void `
1027
1128## Commands
1229
@@ -16,46 +33,176 @@ vp lint
1633vp run -r check
1734vp run -r test
1835vp run -r build
36+ ```
37+
38+ ## Quick start
39+
40+ Generate the example output:
41+
42+ ``` bash
43+ vp run -r build
1944vp run @sqlf/example-basic#generate
2045```
2146
22- ## Generated output
47+ Start the example API:
48+
49+ ``` bash
50+ vp run @sqlf/example-basic#start
51+ ```
52+
53+ Reset the example database if you want to re-run ` schema.sql ` from scratch:
2354
24- The generated module now emits:
55+ ``` bash
56+ cd examples/basic
57+ pnpm run db:stop
58+ pnpm run db:start
59+ ```
2560
26- - ` *ParamsSchema ` and ` *ResultSchema `
27- - ` *Params ` and ` *Result ` type aliases via ` Schema.Schema.Type<...> `
28- - raw ` *Sql ` constants
29- - query docs with source file + SQL
30- - ` SqlSchema.single/findOne/findAll/void ` wrappers
61+ ## Example: generated CRUD module
3162
32- ## Integration testing
63+ The basic example lives in ` examples/basic ` and is fully local:
3364
34- ` packages/core/tests/postgres.integration.test.ts ` runs against:
65+ - ` examples/basic/compose.yaml ` starts Postgres on ` 127.0.0.1:54329 `
66+ - ` examples/basic/schema.sql ` creates and seeds the ` users ` table
67+ - ` examples/basic/sql/queries.sql ` defines CRUD queries
68+ - ` examples/basic/generated/index.ts ` is committed generated output
69+ - ` examples/basic/src/httpApi.ts ` shows how to use generated queries inside an Effect ` HttpApi `
3570
36- - ` SQLF_TEST_DATABASE_URL ` , if provided, or
37- - an ephemeral Docker ` postgres:16-alpine ` container
71+ The example SQL includes:
72+
73+ ``` sql
74+ -- name: CreateUser :one
75+ INSERT INTO users (id, email, created_at)
76+ VALUES (@id::uuid, @email::text , now())
77+ RETURNING id, email, created_at;
78+
79+ -- name: GetUser :one
80+ SELECT id, email, created_at
81+ FROM users
82+ WHERE id = @id::uuid;
83+
84+ -- name: ListUsers :many
85+ SELECT id, email, created_at
86+ FROM users
87+ ORDER BY created_at DESC ;
88+
89+ -- name: UpdateUser :one
90+ UPDATE users
91+ SET email = @email::text
92+ WHERE id = @id::uuid
93+ RETURNING id, email, created_at;
94+
95+ -- name: DeleteUser :exec
96+ DELETE FROM users
97+ WHERE id = @id::uuid;
98+ ```
99+
100+ ## Example: direct generated usage
38101
39- ## Example
102+ You can call the generated functions directly by providing a SQL client layer:
40103
41- ` examples/basic ` now includes a Docker Compose Postgres setup.
104+ ``` ts
105+ import { PgClient } from " @effect/sql-pg" ;
106+ import { Effect , Redacted } from " effect" ;
107+ import { createUser , listUsers } from " ./generated/index.ts" ;
108+
109+ const program = Effect .gen (function * () {
110+ const created = yield * createUser ({
111+ id: " 33333333-3333-3333-3333-333333333333" ,
112+ email: " grace@example.com" ,
113+ });
114+
115+ const users = yield * listUsers ({});
116+
117+ return { created , users };
118+ });
119+
120+ const runnable = program .pipe (
121+ Effect .provide (
122+ PgClient .layer ({
123+ url: Redacted .make (" postgres://postgres:postgres@127.0.0.1:54329/postgres" ),
124+ }),
125+ ),
126+ );
127+ ```
128+
129+ ## Example: generated output shape
130+
131+ Generated modules export:
132+
133+ - ` *ParamsSchema `
134+ - ` *ResultSchema `
135+ - ` *Params ` / ` *Result ` type aliases
136+ - raw ` *Sql ` strings
137+ - ` SqlSchema.* ` wrappers
138+
139+ Example output from ` examples/basic/generated/index.ts ` :
140+
141+ ``` ts
142+ export const CreateUserResultSchema = Schema .Struct ({
143+ id: Schema .UUID ,
144+ email: Schema .String ,
145+ created_at: Schema .DateFromSelf ,
146+ });
147+
148+ export const createUser = SqlSchema .single ({
149+ Request: CreateUserParamsSchema ,
150+ Result: CreateUserResultSchema ,
151+ execute : (request ) =>
152+ Effect .flatMap (SqlClient .SqlClient , (sql ) =>
153+ sql .unsafe (createUserSql , [request .id , request .email ]),
154+ ),
155+ });
156+ ```
157+
158+ ## Example: Effect HTTP API
159+
160+ ` examples/basic/src/httpApi.ts ` wires the generated module into an Effect ` HttpApi ` .
161+
162+ It exposes:
163+
164+ - ` POST /users `
165+ - ` GET /users `
166+ - ` GET /users/:id `
167+ - ` PUT /users/:id `
168+ - ` DELETE /users/:id `
169+
170+ Start it:
42171
43172``` bash
44- vp run -r build
45- vp run @sqlf/example-basic#generate
173+ vp run @sqlf/example-basic#start
46174```
47175
48- That will :
176+ Then try it :
49177
50- - start Postgres via ` examples/basic/compose.yaml `
51- - initialize the ` users ` table from ` examples/basic/schema.sql `
52- - generate ` examples/basic/generated/index.ts `
178+ ``` bash
179+ curl http://127.0.0.1:3000/users
53180
54- ## MVP scope
181+ curl http://127.0.0.1:3000/users/11111111-1111-1111-1111-111111111111
55182
56- - JS/TS config via ` defineConfig() `
57- - Postgres-only
58- - Query annotations inspired by sqlc (` :one ` , ` :maybeOne ` , ` :many ` , ` :exec ` )
59- - Named params via ` @param `
60- - Result type inference through lightweight Postgres analysis
61- - Effect SQL code generation
183+ curl -X POST http://127.0.0.1:3000/users \
184+ -H ' content-type: application/json' \
185+ -d ' {"email":"grace@example.com"}'
186+
187+ curl -X PUT http://127.0.0.1:3000/users/11111111-1111-1111-1111-111111111111 \
188+ -H ' content-type: application/json' \
189+ -d ' {"email":"ada+updated@example.com"}'
190+
191+ curl -X DELETE http://127.0.0.1:3000/users/11111111-1111-1111-1111-111111111111
192+ ```
193+
194+ ## Generated output notes
195+
196+ The generator currently emits:
197+
198+ - query doc comments with source file + SQL
199+ - Effect schemas for params and results
200+ - raw SQL strings
201+ - executable wrappers built on ` SqlSchema `
202+
203+ ## Integration testing
204+
205+ ` packages/core/tests/postgres.integration.test.ts ` runs against either:
206+
207+ - ` SQLF_TEST_DATABASE_URL ` , or
208+ - an ephemeral Docker ` postgres:16-alpine ` container
0 commit comments