|
1 | | -# 💬 Inquire |
| 1 | +# Inquire |
2 | 2 |
|
3 | 3 | [](https://www.npmjs.com/package/@stackpress/inquire) |
4 | 4 | [](https://github.com/stackpress/inquire/actions) |
5 | 5 | [](https://coveralls.io/github/stackpress/inquire?branch=main) |
6 | 6 | [](https://github.com/stackpress/inquire/commits/main/) |
7 | 7 | [](https://github.com/stackpress/inquire/blob/main/LICENSE) |
8 | 8 |
|
9 | | -Super lightweight generic typed SQL query builder, SQL dialects and composite engine. Schema builder, but no ORM. Bring your own database library. |
| 9 | +Inquire is a lightweight TypeScript SQL builder. It helps you define schemas, build SQL queries, and execute them through a connection wrapper. It does not model records, relationships, or unit-of-work behavior for you. |
10 | 10 |
|
11 | | -## What is Inquire? |
| 11 | +## What Inquire is for |
12 | 12 |
|
13 | | -Inquire is a powerful yet lightweight SQL query builder that provides a unified interface for working with multiple database engines. Unlike traditional ORMs, Inquire focuses on query building and execution while letting you bring your own database connection library. This approach gives you the flexibility to use your preferred database driver while benefiting from a consistent, type-safe query building experience. |
| 13 | +Use Inquire when you want: |
14 | 14 |
|
15 | | -## Key Features |
| 15 | +- typed query results in TypeScript |
| 16 | +- one small API for MySQL, PostgreSQL, SQLite, and PGlite |
| 17 | +- schema helpers without adopting a full ORM |
| 18 | +- the option to mix builders and raw SQL |
16 | 19 |
|
17 | | -- **🪶 Lightweight**: No ORM overhead - just pure query building |
18 | | -- **🔧 Generic Typed**: Full TypeScript support with generic types for enhanced type safety |
19 | | -- **🗄️ Multi-Database**: Same expressive query builder pattern for all SQL engines |
20 | | -- **🔄 Unified Interface**: Consistent API across different database engines |
21 | | -- **📝 Schema Builder**: Create and modify database schemas programmatically |
22 | | -- **🔗 Template Strings**: Support for type-safe template string query building |
23 | | -- **⚡ Transaction Support**: Common transaction pattern across all engines |
24 | | -- **🎯 Dialect Agnostic**: Query builders work with any supported SQL dialect |
| 20 | +Use something else if you want: |
25 | 21 |
|
26 | | -## Supported Databases |
| 22 | +- model instances and change tracking |
| 23 | +- relationship loading |
| 24 | +- repository patterns generated by the library |
| 25 | +- a full migration workflow with history and orchestration |
27 | 26 |
|
28 | | -Inquire supports a wide range of database engines through dedicated connection packages: |
| 27 | +## Supported databases |
29 | 28 |
|
30 | | -- **MySQL** - via `@stackpress/inquire-mysql2` (Node MySQL2) |
31 | | -- **PostgreSQL** - via `@stackpress/inquire-pg` (Node PostGres pg) |
32 | | -- **SQLite** - via `@stackpress/inquire-sqlite3` (Better SQLite3) |
33 | | -- **PGLite** - via `@stackpress/inquire-pglite` (PGLite) |
34 | | -- **CockroachDB** - Compatible with PostgreSQL adapter |
35 | | -- **NeonDB** - Compatible with PostgreSQL adapter |
36 | | -- **Vercel Postgres** - Compatible with PostgreSQL adapter |
37 | | -- **Supabase** - Compatible with PostgreSQL adapter |
| 29 | +Inquire supports these connection packages: |
38 | 30 |
|
39 | | -## Installation |
| 31 | +- `@stackpress/inquire-mysql2` |
| 32 | +- `@stackpress/inquire-pg` |
| 33 | +- `@stackpress/inquire-pglite` |
| 34 | +- `@stackpress/inquire-sqlite3` |
40 | 35 |
|
41 | | -Install the core library: |
| 36 | +PostgreSQL-compatible services such as CockroachDB, Neon, Supabase, and Vercel Postgres can use the PostgreSQL adapter when the underlying driver is compatible. |
| 37 | + |
| 38 | +## Requirements |
| 39 | + |
| 40 | +- Node.js 22 or newer |
| 41 | +- `yarn` |
| 42 | + |
| 43 | +## Install |
| 44 | + |
| 45 | +Install the core library and one connection package. |
42 | 46 |
|
43 | 47 | ```bash |
44 | | -npm install @stackpress/inquire |
| 48 | +# Core package |
| 49 | +yarn add @stackpress/inquire |
| 50 | + |
| 51 | +# MySQL |
| 52 | +yarn add @stackpress/inquire-mysql2 mysql2 |
| 53 | + |
| 54 | +# PostgreSQL |
| 55 | +yarn add @stackpress/inquire-pg pg |
| 56 | + |
| 57 | +# SQLite |
| 58 | +yarn add @stackpress/inquire-sqlite3 better-sqlite3 |
| 59 | + |
| 60 | +# PGlite |
| 61 | +yarn add @stackpress/inquire-pglite @electric-sql/pglite |
45 | 62 | ``` |
46 | 63 |
|
47 | | -Then install the appropriate database adapter: |
| 64 | +## Quick start |
48 | 65 |
|
49 | | -```bash |
50 | | -# For MySQL |
51 | | -npm install @stackpress/inquire-mysql2 mysql2 |
| 66 | +This is the shortest local path with SQLite. |
| 67 | + |
| 68 | +```ts |
| 69 | +import sqlite from 'better-sqlite3'; |
| 70 | +import connect from '@stackpress/inquire-sqlite3'; |
| 71 | + |
| 72 | +const resource = sqlite(':memory:'); |
| 73 | +const engine = connect(resource); |
52 | 74 |
|
53 | | -# For PostgreSQL |
54 | | -npm install @stackpress/inquire-pg pg |
| 75 | +await engine.create('users') |
| 76 | + .addField('id', { type: 'integer', autoIncrement: true }) |
| 77 | + .addField('email', { type: 'string', length: 255, nullable: false }) |
| 78 | + .addField('name', { type: 'string', length: 255, nullable: false }) |
| 79 | + .addPrimaryKey('id') |
| 80 | + .addUniqueKey('users_email_unique', 'email'); |
| 81 | + |
| 82 | +await engine.insert('users').values({ |
| 83 | + email: 'ada@example.com', |
| 84 | + name: 'Ada' |
| 85 | +}); |
55 | 86 |
|
56 | | -# For SQLite |
57 | | -npm install @stackpress/inquire-sqlite3 better-sqlite3 |
| 87 | +type UserRow = { |
| 88 | + id: number; |
| 89 | + email: string; |
| 90 | + name: string; |
| 91 | +}; |
| 92 | + |
| 93 | +const users = await engine.select<UserRow>([ |
| 94 | + 'id', |
| 95 | + 'email', |
| 96 | + 'name' |
| 97 | + ]) |
| 98 | + .from('users') |
| 99 | + .where('email = ?', ['ada@example.com']); |
58 | 100 |
|
59 | | -# For PGLite |
60 | | -npm install @stackpress/inquire-pglite @electric-sql/pglite |
| 101 | +console.log(users); |
61 | 102 | ``` |
62 | 103 |
|
63 | | -## Quick Start |
| 104 | +## Connection examples |
64 | 105 |
|
65 | | -### MySQL Connection |
| 106 | +### MySQL |
66 | 107 |
|
67 | | -```typescript |
| 108 | +```ts |
68 | 109 | import mysql from 'mysql2/promise'; |
69 | 110 | import connect from '@stackpress/inquire-mysql2'; |
70 | 111 |
|
71 | | -// Create the raw database connection |
72 | 112 | const resource = await mysql.createConnection({ |
73 | 113 | host: 'localhost', |
74 | 114 | user: 'root', |
75 | | - database: 'inquire', |
| 115 | + database: 'app' |
76 | 116 | }); |
77 | 117 |
|
78 | | -// Map the resource to the Inquire engine |
79 | 118 | const engine = connect(resource); |
80 | 119 | ``` |
81 | 120 |
|
82 | | -### PostgreSQL Connection |
| 121 | +### PostgreSQL |
83 | 122 |
|
84 | | -```typescript |
85 | | -import { Client, Pool } from 'pg'; |
| 123 | +```ts |
| 124 | +import { Client } from 'pg'; |
86 | 125 | import connect from '@stackpress/inquire-pg'; |
87 | 126 |
|
88 | | -// Using a Pool |
89 | | -const pool = new Pool({ |
90 | | - database: 'inquire', |
91 | | - user: 'postgres' |
92 | | -}); |
93 | | -const connection = await pool.connect(); |
94 | | - |
95 | | -// Or using a Client |
96 | 127 | const client = new Client({ |
97 | | - database: 'inquire', |
| 128 | + database: 'app', |
98 | 129 | user: 'postgres' |
99 | 130 | }); |
| 131 | + |
100 | 132 | await client.connect(); |
101 | 133 |
|
102 | | -// Map the resource to the Inquire engine |
103 | | -const engine = connect(connection); // or connect(client) |
| 134 | +const engine = connect(client); |
104 | 135 | ``` |
105 | 136 |
|
106 | | -### SQLite Connection |
| 137 | +### PGlite |
107 | 138 |
|
108 | | -```typescript |
109 | | -import sqlite from 'better-sqlite3'; |
110 | | -import connect from '@stackpress/inquire-sqlite3'; |
| 139 | +```ts |
| 140 | +import { PGlite } from '@electric-sql/pglite'; |
| 141 | +import connect from '@stackpress/inquire-pglite'; |
111 | 142 |
|
112 | | -// Create the raw database connection |
113 | | -const resource = sqlite(':memory:'); |
114 | | - |
115 | | -// Map the resource to the Inquire engine |
| 143 | +const resource = new PGlite('./build/database'); |
116 | 144 | const engine = connect(resource); |
117 | 145 | ``` |
118 | 146 |
|
119 | | -## Basic Usage |
| 147 | +## Core API |
120 | 148 |
|
121 | | -Once you have an engine instance, you can start building and executing queries: |
| 149 | +The `Engine` creates six main builders: |
122 | 150 |
|
123 | | -```typescript |
124 | | -// Create a table |
125 | | -await engine.create('users') |
126 | | - .addField('id', { type: 'INTEGER', autoIncrement: true }) |
127 | | - .addField('name', { type: 'VARCHAR', length: 255 }) |
128 | | - .addField('email', { type: 'VARCHAR', length: 255 }) |
129 | | - .addPrimaryKey('id'); |
130 | | - |
131 | | -// Insert data |
132 | | -await engine |
133 | | - .insert('users') |
134 | | - .values({ name: 'John Doe', email: 'john@example.com' }); |
135 | | - |
136 | | -// Select data |
137 | | -const users = await engine |
138 | | - .select('*') |
139 | | - .from('users') |
140 | | - .where('name = ?', ['John Doe']); |
141 | | - |
142 | | -console.log(users); |
143 | | - |
144 | | -// Update data |
145 | | -await engine |
146 | | - .update('users') |
147 | | - .set({ email: 'john.doe@example.com' }) |
148 | | - .where('id = ?', [1]); |
149 | | - |
150 | | -// Delete data |
151 | | -await engine |
152 | | - .delete('users') |
153 | | - .where('id = ?', [1]); |
154 | | -``` |
| 151 | +- `create(table)` |
| 152 | +- `alter(table)` |
| 153 | +- `select(columns?)` |
| 154 | +- `insert(table)` |
| 155 | +- `update(table)` |
| 156 | +- `delete(table)` |
155 | 157 |
|
156 | | -## Query Builders |
| 158 | +It also exposes: |
157 | 159 |
|
158 | | -Inquire provides comprehensive query builders for all common SQL operations: |
| 160 | +- `query(query, values?)` |
| 161 | +- `sql\`...\`` |
| 162 | +- `transaction(callback)` |
| 163 | +- `diff(from, to)` |
| 164 | +- `rename(from, to)` |
| 165 | +- `drop(table)` |
| 166 | +- `truncate(table, cascade?)` |
159 | 167 |
|
160 | | -- **[Create](./docs/builders/Create.md)** - Create tables and schemas |
161 | | -- **[Alter](./docs/builders/Alter.md)** - Modify existing tables |
162 | | -- **[Select](./docs/builders/Select.md)** - Query data with joins, conditions, and aggregations |
163 | | -- **[Insert](./docs/builders/Insert.md)** - Insert single or multiple records |
164 | | -- **[Update](./docs/builders/Update.md)** - Update existing records |
165 | | -- **[Delete](./docs/builders/Delete.md)** - Delete records with conditions |
| 168 | +## Raw SQL |
166 | 169 |
|
167 | | -## Template String Queries |
| 170 | +Use `query()` or `sql` when the builder surface is not enough. |
168 | 171 |
|
169 | | -For complex queries, you can use type-safe template strings: |
| 172 | +```ts |
| 173 | +const rows = await engine.query<{ id: number; email: string }>( |
| 174 | + 'SELECT id, email FROM users WHERE id = ?', |
| 175 | + [1] |
| 176 | +); |
170 | 177 |
|
171 | | -```typescript |
172 | | -type User = { |
173 | | - id: number; |
174 | | - name: string; |
175 | | - email: string; |
176 | | -}; |
177 | | - |
178 | | -const userId = 123; |
179 | | -const results = await engine.sql<User>` |
180 | | - SELECT u.*, p.title |
181 | | - FROM users u |
182 | | - LEFT JOIN posts p ON u.id = p.user_id |
183 | | - WHERE u.id = ${userId} |
| 178 | +const ids = await engine.sql<{ id: number }>` |
| 179 | + SELECT id |
| 180 | + FROM users |
| 181 | + WHERE email LIKE ${'%@example.com'} |
184 | 182 | `; |
185 | | -// results is typed as User[] |
186 | 183 | ``` |
187 | 184 |
|
188 | 185 | ## Transactions |
189 | 186 |
|
190 | | -Execute multiple queries in a transaction: |
| 187 | +Use `engine.transaction()` when several writes must succeed or fail together. |
191 | 188 |
|
192 | | -```typescript |
193 | | -const result = await engine.transaction(async (trx) => { |
194 | | - await trx.insert('users').values({ name: 'Alice' }); |
195 | | - await trx.insert('posts').values({ title: 'Hello World', user_id: 1 }); |
196 | | - return 'success'; |
| 189 | +```ts |
| 190 | +await engine.transaction(async (tx) => { |
| 191 | + await tx.query({ |
| 192 | + query: 'INSERT INTO users (email, name) VALUES (?, ?)', |
| 193 | + values: ['grace@example.com', 'Grace'] |
| 194 | + }); |
| 195 | + |
| 196 | + await tx.query({ |
| 197 | + query: 'INSERT INTO profiles (user_id) VALUES (?)', |
| 198 | + values: [1] |
| 199 | + }); |
197 | 200 | }); |
198 | 201 | ``` |
199 | 202 |
|
200 | | -## Type Safety |
| 203 | +The transaction callback receives the connection wrapper, not a nested `Engine`. |
| 204 | + |
| 205 | +## Type safety |
201 | 206 |
|
202 | | -Inquire is designed with TypeScript in mind, providing full type safety: |
| 207 | +Use TypeScript generics to type query results. |
203 | 208 |
|
204 | | -```typescript |
| 209 | +```ts |
205 | 210 | type User = { |
206 | 211 | id: number; |
207 | | - name: string; |
208 | 212 | email: string; |
| 213 | + name: string; |
209 | 214 | }; |
210 | 215 |
|
211 | | -// Type-safe queries |
212 | | -const users = await engine.select<User>('*').from('users'); |
213 | | -// users is now typed as User[] |
214 | | - |
215 | | -const user = await engine.select<User>('*') |
216 | | - .from('users') |
217 | | - .where('id = ?', [1]) |
218 | | - .limit(1); |
219 | | -// user is typed as User[] |
| 216 | +const users = await engine.select<User>([ |
| 217 | + 'id', |
| 218 | + 'email', |
| 219 | + 'name' |
| 220 | + ]) |
| 221 | + .from('users'); |
220 | 222 | ``` |
221 | 223 |
|
222 | | -## API Documentation |
| 224 | +## Documentation |
223 | 225 |
|
224 | | -For detailed API documentation, see: |
| 226 | +The full docs now live under `specs`: |
225 | 227 |
|
226 | | -- **[Engine](./docs/Engine.md)** - Core engine class and methods |
227 | | -- **[Connection Classes](./docs/Connections.md)** - Database-specific connection implementations |
228 | | -- **[Query Builders](./docs/builders/README.md)** - Detailed documentation for all query builders |
229 | | -- **[SQL Dialects](./docs/dialects/README.md)** - Detailed documentation for all SQL dialects |
230 | | -- **[Examples](./docs/Examples.md)** - Comprehensive usage examples |
| 228 | +- [Start here](./specs/README.md) |
| 229 | +- [Quick start](./specs/tutorials/quick-start.md) |
| 230 | +- [Mental model](./specs/explanation/mental-model.md) |
| 231 | +- [Guides](./specs/guides) |
| 232 | +- [API reference](./specs/api/README.md) |
231 | 233 |
|
232 | 234 | ## Examples |
233 | 235 |
|
234 | | -Check out the [examples directory](./examples) for complete working examples with different database engines: |
| 236 | +For package-level usage examples, see: |
235 | 237 |
|
236 | | -- [MySQL Example](./examples/with-mysql2) |
237 | | -- [PostgreSQL Example](./examples/with-pg) |
238 | | -- [SQLite Example](./examples/with-sqlite3) |
239 | | -- [PGLite Example](./examples/with-pglite) |
| 238 | +- [MySQL2 package](./packages/inquire-mysql2/README.md) |
| 239 | +- [PostgreSQL package](./packages/inquire-pg/README.md) |
| 240 | +- [SQLite3 package](./packages/inquire-sqlite3/README.md) |
| 241 | +- [PGlite package](./packages/inquire-pglite/README.md) |
0 commit comments