flint-orm 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +208 -211
- package/README.md +341 -0
- package/dist/index.js +288 -255
- package/package.json +46 -11
- package/src/cli.ts +103 -48
package/API.md
CHANGED
|
@@ -1,60 +1,85 @@
|
|
|
1
1
|
# Flint ORM — API Reference
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A type-safe, driver-agnostic SQLite ORM for JavaScript. One schema, any driver.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
bun add flint-orm
|
|
9
|
+
# or
|
|
10
|
+
npm install flint-orm
|
|
9
11
|
```
|
|
10
12
|
|
|
11
13
|
## Quick Start
|
|
12
14
|
|
|
13
15
|
```ts
|
|
14
|
-
import { flint
|
|
16
|
+
import { flint } from 'flint-orm/bun-sqlite';
|
|
17
|
+
import { table, text, integer, date } from 'flint-orm/table';
|
|
18
|
+
import { eq } from 'flint-orm/expressions';
|
|
15
19
|
|
|
16
20
|
// Define schema
|
|
17
21
|
const users = table('users', {
|
|
18
|
-
id: text(
|
|
19
|
-
name: text(
|
|
20
|
-
email: text(
|
|
21
|
-
age: integer(
|
|
22
|
+
id: text().primaryKey(),
|
|
23
|
+
name: text().notNull(),
|
|
24
|
+
email: text().unique(),
|
|
25
|
+
age: integer(),
|
|
26
|
+
createdAt: date().defaultNow(),
|
|
22
27
|
});
|
|
23
28
|
|
|
24
29
|
// Connect
|
|
25
|
-
const db = flint({ url: 'app.db' });
|
|
30
|
+
const db = flint({ url: './app.db' });
|
|
26
31
|
|
|
27
32
|
// Query
|
|
28
|
-
const user = db.select().from(users).where(eq(users.id, 'u1')).single().execute();
|
|
29
|
-
// { id: "u1", name: "Alice", email: "alice@example.com", age: 30 }
|
|
33
|
+
const user = await db.select().from(users).where(eq(users.id, 'u1')).single().execute();
|
|
34
|
+
// { id: "u1", name: "Alice", email: "alice@example.com", age: 30, createdAt: Date }
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Config
|
|
40
|
+
|
|
41
|
+
Create `flint.config.ts` in your project root:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { defineConfig } from 'flint-orm/config';
|
|
45
|
+
|
|
46
|
+
export default defineConfig({
|
|
47
|
+
driver: 'bun-sqlite', // 'bun-sqlite' | 'better-sqlite3' | 'libsql' | 'libsql-web' | 'turso' | 'turso-sync'
|
|
48
|
+
database: {
|
|
49
|
+
url: './app.db',
|
|
50
|
+
authToken: '...', // for libsql/libsql-web/turso-sync only
|
|
51
|
+
},
|
|
52
|
+
schema: './src/schema',
|
|
53
|
+
migrations: './flint',
|
|
54
|
+
});
|
|
30
55
|
```
|
|
31
56
|
|
|
32
57
|
---
|
|
33
58
|
|
|
34
59
|
## Schema
|
|
35
60
|
|
|
36
|
-
### `table(name, columns)`
|
|
61
|
+
### `table(name, columns, indexFn?)`
|
|
37
62
|
|
|
38
63
|
Define a table. Columns live as direct properties. SQL metadata is under `._`.
|
|
39
64
|
|
|
40
65
|
```ts
|
|
41
|
-
import { table, text, integer, boolean } from 'flint-orm';
|
|
66
|
+
import { table, text, integer, boolean, index } from 'flint-orm/table';
|
|
42
67
|
|
|
43
68
|
const users = table('users', {
|
|
44
|
-
id: text(
|
|
45
|
-
name: text(
|
|
46
|
-
email: text(
|
|
47
|
-
active: boolean(
|
|
48
|
-
age: integer(
|
|
69
|
+
id: text().primaryKey(),
|
|
70
|
+
name: text().notNull(),
|
|
71
|
+
email: text().unique(),
|
|
72
|
+
active: boolean().default(true),
|
|
73
|
+
age: integer(),
|
|
49
74
|
});
|
|
50
75
|
```
|
|
51
76
|
|
|
52
|
-
### `snakeCase.table(name, columns)`
|
|
77
|
+
### `snakeCase.table(name, columns, indexFn?)`
|
|
53
78
|
|
|
54
79
|
Auto-converts camelCase keys to snake_case SQL names.
|
|
55
80
|
|
|
56
81
|
```ts
|
|
57
|
-
import { snakeCase, text } from 'flint-orm';
|
|
82
|
+
import { snakeCase, text } from 'flint-orm/table';
|
|
58
83
|
|
|
59
84
|
const users = snakeCase.table('users', {
|
|
60
85
|
id: text().primaryKey(), // SQL: id
|
|
@@ -65,21 +90,21 @@ const users = snakeCase.table('users', {
|
|
|
65
90
|
|
|
66
91
|
### Column Types
|
|
67
92
|
|
|
68
|
-
| Function | TS Type | SQLite Storage | Notes
|
|
69
|
-
| ----------- | --------- | ------------------ |
|
|
70
|
-
| `text()` | `string` | TEXT |
|
|
71
|
-
| `integer()` | `number` | INTEGER | Supports `.autoIncrement()`
|
|
72
|
-
| `boolean()` | `boolean` | INTEGER (0/1) | Encodes/decodes automatically
|
|
73
|
-
| `json<T>()` | `T` | TEXT (JSON) | Generic, encodes/decodes automatically
|
|
74
|
-
| `real()` | `number` | REAL |
|
|
75
|
-
| `date()` | `Date` | INTEGER (epoch ms) | Supports `.defaultNow()`, `.
|
|
93
|
+
| Function | TS Type | SQLite Storage | Notes |
|
|
94
|
+
| ----------- | --------- | ------------------ | ------------------------------------------------ |
|
|
95
|
+
| `text()` | `string` | TEXT | |
|
|
96
|
+
| `integer()` | `number` | INTEGER | Supports `.autoIncrement()` |
|
|
97
|
+
| `boolean()` | `boolean` | INTEGER (0/1) | Encodes/decodes automatically |
|
|
98
|
+
| `json<T>()` | `T` | TEXT (JSON) | Generic, encodes/decodes automatically |
|
|
99
|
+
| `real()` | `number` | REAL | |
|
|
100
|
+
| `date()` | `Date` | INTEGER (epoch ms) | Supports `.defaultNow()`, `.onUpdateTimestamp()` |
|
|
76
101
|
|
|
77
102
|
### Column Modifiers
|
|
78
103
|
|
|
79
104
|
Every column supports chaining:
|
|
80
105
|
|
|
81
106
|
```ts
|
|
82
|
-
text(
|
|
107
|
+
text()
|
|
83
108
|
.primaryKey() // PRIMARY KEY
|
|
84
109
|
.notNull() // NOT NULL
|
|
85
110
|
.unique() // UNIQUE
|
|
@@ -91,15 +116,15 @@ text('name')
|
|
|
91
116
|
**Integer-only:**
|
|
92
117
|
|
|
93
118
|
```ts
|
|
94
|
-
integer(
|
|
119
|
+
integer().autoIncrement(); // AUTOINCREMENT
|
|
95
120
|
```
|
|
96
121
|
|
|
97
122
|
**Date-only:**
|
|
98
123
|
|
|
99
124
|
```ts
|
|
100
|
-
date(
|
|
125
|
+
date()
|
|
101
126
|
.defaultNow() // DEFAULT (current epoch ms)
|
|
102
|
-
.
|
|
127
|
+
.onUpdateTimestamp(); // Always set to now on UPDATE
|
|
103
128
|
```
|
|
104
129
|
|
|
105
130
|
### `InferRow<T>`
|
|
@@ -107,8 +132,10 @@ date('created_at')
|
|
|
107
132
|
Derives the row type from a table definition.
|
|
108
133
|
|
|
109
134
|
```ts
|
|
135
|
+
import type { InferRow } from 'flint-orm/table';
|
|
136
|
+
|
|
110
137
|
type UserRow = InferRow<typeof users>;
|
|
111
|
-
// { id: string; name: string; email: string | null; active: boolean; age: number | null }
|
|
138
|
+
// { id: string; name: string; email: string | null; active: boolean; age: number | null; createdAt: Date }
|
|
112
139
|
```
|
|
113
140
|
|
|
114
141
|
### `InsertRow<T>`
|
|
@@ -116,20 +143,44 @@ type UserRow = InferRow<typeof users>;
|
|
|
116
143
|
Row type for INSERT. Columns with defaults or autoIncrement are optional.
|
|
117
144
|
|
|
118
145
|
```ts
|
|
146
|
+
import type { InsertRow } from 'flint-orm/table';
|
|
147
|
+
|
|
119
148
|
type UserInsert = InsertRow<typeof users>;
|
|
120
|
-
// { id: string; name: string; email?: string; active?: boolean; age?: number }
|
|
149
|
+
// { id: string; name: string; email?: string; active?: boolean; age?: number; createdAt?: Date }
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Indexes
|
|
155
|
+
|
|
156
|
+
### `index(name).on(columns).unique()`
|
|
157
|
+
|
|
158
|
+
Define indexes via the table callback. Chainable API.
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
const users = table(
|
|
162
|
+
'users',
|
|
163
|
+
{
|
|
164
|
+
id: text().primaryKey(),
|
|
165
|
+
email: text(),
|
|
166
|
+
name: text(),
|
|
167
|
+
},
|
|
168
|
+
(t) => [index('idx_users_email').on(t.email).unique(), index('idx_users_name').on(t.name)],
|
|
169
|
+
);
|
|
121
170
|
```
|
|
122
171
|
|
|
123
172
|
---
|
|
124
173
|
|
|
125
174
|
## Query Builder
|
|
126
175
|
|
|
176
|
+
All `execute()` methods return `Promise<T>` — always `await` regardless of driver.
|
|
177
|
+
|
|
127
178
|
### `db.select().from(table)`
|
|
128
179
|
|
|
129
180
|
Start a SELECT query. Two-phase: `.from()` is required before anything else.
|
|
130
181
|
|
|
131
182
|
```ts
|
|
132
|
-
db.select().from(users).execute();
|
|
183
|
+
await db.select().from(users).execute();
|
|
133
184
|
// SELECT * FROM users
|
|
134
185
|
```
|
|
135
186
|
|
|
@@ -138,7 +189,7 @@ db.select().from(users).execute();
|
|
|
138
189
|
Narrow which columns appear in the result.
|
|
139
190
|
|
|
140
191
|
```ts
|
|
141
|
-
db.select().from(users).columns(['id', 'name']).execute();
|
|
192
|
+
await db.select().from(users).columns(['id', 'name']).execute();
|
|
142
193
|
// SELECT id, name FROM users
|
|
143
194
|
// Returns: { id: string; name: string }[]
|
|
144
195
|
```
|
|
@@ -148,7 +199,7 @@ db.select().from(users).columns(['id', 'name']).execute();
|
|
|
148
199
|
Filter rows.
|
|
149
200
|
|
|
150
201
|
```ts
|
|
151
|
-
db.select().from(users).where(eq(users.active, true)).execute();
|
|
202
|
+
await db.select().from(users).where(eq(users.active, true)).execute();
|
|
152
203
|
// SELECT * FROM users WHERE active = 1
|
|
153
204
|
```
|
|
154
205
|
|
|
@@ -157,7 +208,7 @@ db.select().from(users).where(eq(users.active, true)).execute();
|
|
|
157
208
|
Return one row or null instead of an array. Adds `LIMIT 1`.
|
|
158
209
|
|
|
159
210
|
```ts
|
|
160
|
-
db.select().from(users).where(eq(users.id, 'u1')).single().execute();
|
|
211
|
+
await db.select().from(users).where(eq(users.id, 'u1')).single().execute();
|
|
161
212
|
// SELECT * FROM users WHERE id = ? LIMIT 1
|
|
162
213
|
// Returns: UserRow | null
|
|
163
214
|
```
|
|
@@ -167,7 +218,7 @@ db.select().from(users).where(eq(users.id, 'u1')).single().execute();
|
|
|
167
218
|
Sort results. Default direction is `"asc"`.
|
|
168
219
|
|
|
169
220
|
```ts
|
|
170
|
-
db.select().from(users).orderBy('name', 'desc').execute();
|
|
221
|
+
await db.select().from(users).orderBy('name', 'desc').execute();
|
|
171
222
|
// SELECT * FROM users ORDER BY name DESC
|
|
172
223
|
```
|
|
173
224
|
|
|
@@ -176,7 +227,7 @@ db.select().from(users).orderBy('name', 'desc').execute();
|
|
|
176
227
|
Limit the number of results.
|
|
177
228
|
|
|
178
229
|
```ts
|
|
179
|
-
db.select().from(users).limit(10).execute();
|
|
230
|
+
await db.select().from(users).limit(10).execute();
|
|
180
231
|
// SELECT * FROM users LIMIT 10
|
|
181
232
|
```
|
|
182
233
|
|
|
@@ -185,7 +236,7 @@ db.select().from(users).limit(10).execute();
|
|
|
185
236
|
Skip N rows.
|
|
186
237
|
|
|
187
238
|
```ts
|
|
188
|
-
db.select().from(users).limit(10).offset(20).execute();
|
|
239
|
+
await db.select().from(users).limit(10).offset(20).execute();
|
|
189
240
|
// SELECT * FROM users LIMIT 10 OFFSET 20
|
|
190
241
|
```
|
|
191
242
|
|
|
@@ -194,17 +245,10 @@ db.select().from(users).limit(10).offset(20).execute();
|
|
|
194
245
|
Return unique rows.
|
|
195
246
|
|
|
196
247
|
```ts
|
|
197
|
-
db.select().from(users).columns(['name']).distinct().execute();
|
|
248
|
+
await db.select().from(users).columns(['name']).distinct().execute();
|
|
198
249
|
// SELECT DISTINCT name FROM users
|
|
199
250
|
```
|
|
200
251
|
|
|
201
|
-
### Full Example
|
|
202
|
-
|
|
203
|
-
```ts
|
|
204
|
-
const results = db.select().from(users).columns(['id', 'name']).where(eq(users.active, true)).orderBy('name', 'asc').limit(10).offset(0).execute();
|
|
205
|
-
// Returns: { id: string; name: string }[]
|
|
206
|
-
```
|
|
207
|
-
|
|
208
252
|
---
|
|
209
253
|
|
|
210
254
|
### `db.insert(table).values(row)`
|
|
@@ -213,23 +257,16 @@ Insert one or more rows. Two-phase: `.values()` is required before `.execute()`.
|
|
|
213
257
|
|
|
214
258
|
```ts
|
|
215
259
|
// Single row
|
|
216
|
-
db.insert(users).values({ id: 'u1', name: 'Alice', email: 'alice@example.com' }).execute();
|
|
217
|
-
// INSERT INTO users (id, name, email) VALUES (?, ?, ?)
|
|
260
|
+
await db.insert(users).values({ id: 'u1', name: 'Alice', email: 'alice@example.com' }).execute();
|
|
218
261
|
|
|
219
|
-
// Multiple rows
|
|
220
|
-
db
|
|
262
|
+
// Multiple rows
|
|
263
|
+
await db
|
|
264
|
+
.insert(users)
|
|
221
265
|
.values([
|
|
222
266
|
{ id: 'u1', name: 'Alice' },
|
|
223
267
|
{ id: 'u2', name: 'Bob' },
|
|
224
268
|
])
|
|
225
269
|
.execute();
|
|
226
|
-
// INSERT INTO users (id, name) VALUES (?, ?), (?, ?)
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
Columns with defaults can be omitted:
|
|
230
|
-
|
|
231
|
-
```ts
|
|
232
|
-
db.insert(users).values({ id: 'u1', name: 'Alice' }).execute();
|
|
233
270
|
```
|
|
234
271
|
|
|
235
272
|
### `.returning()`
|
|
@@ -237,12 +274,10 @@ db.insert(users).values({ id: 'u1', name: 'Alice' }).execute();
|
|
|
237
274
|
Return the inserted row(s) instead of void. Pass an array to narrow which columns are returned.
|
|
238
275
|
|
|
239
276
|
```ts
|
|
240
|
-
|
|
241
|
-
const user = db.insert(users).values({ id: 'u1', name: 'Alice' }).returning().execute();
|
|
277
|
+
const user = await db.insert(users).values({ id: 'u1', name: 'Alice' }).returning().execute();
|
|
242
278
|
// Returns: { id: string; name: string; ... }[]
|
|
243
279
|
|
|
244
|
-
|
|
245
|
-
const user = db.insert(users).values({ id: 'u1', name: 'Alice' }).returning(['id', 'name']).execute();
|
|
280
|
+
const user = await db.insert(users).values({ id: 'u1', name: 'Alice' }).returning(['id', 'name']).execute();
|
|
246
281
|
// Returns: { id: string; name: string }[]
|
|
247
282
|
```
|
|
248
283
|
|
|
@@ -251,8 +286,7 @@ const user = db.insert(users).values({ id: 'u1', name: 'Alice' }).returning(['id
|
|
|
251
286
|
Skip the insert if a row with the same primary key already exists.
|
|
252
287
|
|
|
253
288
|
```ts
|
|
254
|
-
db.insert(users).values({ id: 'u1', name: 'Alice' }).onConflictDoNothing().execute();
|
|
255
|
-
// INSERT OR IGNORE INTO users ...
|
|
289
|
+
await db.insert(users).values({ id: 'u1', name: 'Alice' }).onConflictDoNothing().execute();
|
|
256
290
|
```
|
|
257
291
|
|
|
258
292
|
### `.onConflictDoUpdate()`
|
|
@@ -260,14 +294,14 @@ db.insert(users).values({ id: 'u1', name: 'Alice' }).onConflictDoNothing().execu
|
|
|
260
294
|
Update specific columns when a row with the same primary key already exists (upsert).
|
|
261
295
|
|
|
262
296
|
```ts
|
|
263
|
-
db
|
|
297
|
+
await db
|
|
298
|
+
.insert(users)
|
|
264
299
|
.values({ id: 'u1', name: 'Alice' })
|
|
265
300
|
.onConflictDoUpdate({
|
|
266
301
|
target: users.id,
|
|
267
302
|
set: { name: 'Alice Updated' },
|
|
268
303
|
})
|
|
269
304
|
.execute();
|
|
270
|
-
// INSERT INTO users ... ON CONFLICT (id) DO UPDATE SET name = excluded.name
|
|
271
305
|
```
|
|
272
306
|
|
|
273
307
|
---
|
|
@@ -277,29 +311,22 @@ db.insert(users)
|
|
|
277
311
|
Update rows. Two-phase: `.set()` is required before `.execute()`.
|
|
278
312
|
|
|
279
313
|
```ts
|
|
280
|
-
db.update(users).set({ name: 'Bob' }).where(eq(users.id, 'u1')).execute();
|
|
281
|
-
// UPDATE users SET name = ? WHERE id = ?
|
|
314
|
+
await db.update(users).set({ name: 'Bob' }).where(eq(users.id, 'u1')).execute();
|
|
282
315
|
```
|
|
283
316
|
|
|
284
317
|
Multiple `.set()` calls merge:
|
|
285
318
|
|
|
286
319
|
```ts
|
|
287
|
-
db.update(users).set({ name: 'Bob' }).set({ email: 'bob@example.com' }).where(eq(users.id, 'u1')).execute();
|
|
288
|
-
// UPDATE users SET name = ?, email = ? WHERE id = ?
|
|
320
|
+
await db.update(users).set({ name: 'Bob' }).set({ email: 'bob@example.com' }).where(eq(users.id, 'u1')).execute();
|
|
289
321
|
```
|
|
290
322
|
|
|
291
323
|
### `.returning()`
|
|
292
324
|
|
|
293
|
-
Return the updated row(s) instead of void.
|
|
325
|
+
Return the updated row(s) instead of void.
|
|
294
326
|
|
|
295
327
|
```ts
|
|
296
|
-
|
|
297
|
-
const updated = db.update(users).set({ name: 'Bob' }).where(eq(users.id, 'u1')).returning().execute();
|
|
328
|
+
const updated = await db.update(users).set({ name: 'Bob' }).where(eq(users.id, 'u1')).returning().execute();
|
|
298
329
|
// Returns: { id: string; name: string; ... }[]
|
|
299
|
-
|
|
300
|
-
// Return specific columns
|
|
301
|
-
const updated = db.update(users).set({ name: 'Bob' }).where(eq(users.id, 'u1')).returning(['id', 'name']).execute();
|
|
302
|
-
// Returns: { id: string; name: string }[]
|
|
303
330
|
```
|
|
304
331
|
|
|
305
332
|
---
|
|
@@ -309,22 +336,16 @@ const updated = db.update(users).set({ name: 'Bob' }).where(eq(users.id, 'u1')).
|
|
|
309
336
|
Delete rows.
|
|
310
337
|
|
|
311
338
|
```ts
|
|
312
|
-
db.delete(users).where(eq(users.id, 'u1')).execute();
|
|
313
|
-
// DELETE FROM users WHERE id = ?
|
|
339
|
+
await db.delete(users).where(eq(users.id, 'u1')).execute();
|
|
314
340
|
```
|
|
315
341
|
|
|
316
342
|
### `.returning()`
|
|
317
343
|
|
|
318
|
-
Return the deleted row(s) instead of void.
|
|
344
|
+
Return the deleted row(s) instead of void.
|
|
319
345
|
|
|
320
346
|
```ts
|
|
321
|
-
|
|
322
|
-
const deleted = db.delete(users).where(eq(users.id, 'u1')).returning().execute();
|
|
347
|
+
const deleted = await db.delete(users).where(eq(users.id, 'u1')).returning().execute();
|
|
323
348
|
// Returns: { id: string; name: string; ... }[]
|
|
324
|
-
|
|
325
|
-
// Return specific columns
|
|
326
|
-
const deleted = db.delete(users).where(eq(users.id, 'u1')).returning(['id', 'name']).execute();
|
|
327
|
-
// Returns: { id: string; name: string }[]
|
|
328
349
|
```
|
|
329
350
|
|
|
330
351
|
---
|
|
@@ -333,20 +354,20 @@ const deleted = db.delete(users).where(eq(users.id, 'u1')).returning(['id', 'nam
|
|
|
333
354
|
|
|
334
355
|
### `db.leftJoin(parent).on(child, condition?)`
|
|
335
356
|
|
|
336
|
-
LEFT JOIN. Returns all parent rows, with matching child data
|
|
357
|
+
LEFT JOIN. Returns all parent rows, with matching child data nested under the child table name.
|
|
337
358
|
|
|
338
359
|
```ts
|
|
339
360
|
const orders = table('orders', {
|
|
340
|
-
id: text(
|
|
341
|
-
userId: text(
|
|
342
|
-
total: integer(
|
|
361
|
+
id: text().primaryKey(),
|
|
362
|
+
userId: text().notNull().references(users.id),
|
|
363
|
+
total: integer().notNull(),
|
|
343
364
|
});
|
|
344
365
|
|
|
345
366
|
// With explicit condition
|
|
346
|
-
db.leftJoin(
|
|
367
|
+
await db.leftJoin(users).on(orders, eq(orders.userId, users.id)).execute();
|
|
347
368
|
|
|
348
369
|
// Auto-join from foreign key (if .references() is defined)
|
|
349
|
-
db.leftJoin(
|
|
370
|
+
await db.leftJoin(users).on(orders).execute();
|
|
350
371
|
```
|
|
351
372
|
|
|
352
373
|
### `db.innerJoin(parent).on(child, condition?)`
|
|
@@ -354,7 +375,7 @@ db.leftJoin(orders).on(users).execute();
|
|
|
354
375
|
INNER JOIN. Returns only rows where both tables match.
|
|
355
376
|
|
|
356
377
|
```ts
|
|
357
|
-
db.innerJoin(
|
|
378
|
+
await db.innerJoin(users).on(orders, eq(orders.userId, users.id)).execute();
|
|
358
379
|
```
|
|
359
380
|
|
|
360
381
|
### Join Result Shape
|
|
@@ -362,7 +383,6 @@ db.innerJoin(orders).on(users, eq(orders.userId, users.id)).execute();
|
|
|
362
383
|
Joins return **nested** results, not flat-merged:
|
|
363
384
|
|
|
364
385
|
```ts
|
|
365
|
-
// One-to-many: one user with multiple orders
|
|
366
386
|
[
|
|
367
387
|
{
|
|
368
388
|
id: 'u1',
|
|
@@ -380,26 +400,36 @@ Joins return **nested** results, not flat-merged:
|
|
|
380
400
|
Narrow parent columns with `.columns()`:
|
|
381
401
|
|
|
382
402
|
```ts
|
|
383
|
-
db.leftJoin(
|
|
403
|
+
await db.leftJoin(users).on(orders).columns(['id', 'name']).execute();
|
|
384
404
|
// Returns: { id: string; name: string; orders: OrderRow[] }[]
|
|
385
405
|
```
|
|
386
406
|
|
|
387
407
|
### `.single()` on Joins
|
|
388
408
|
|
|
389
409
|
```ts
|
|
390
|
-
db.leftJoin(
|
|
410
|
+
await db.leftJoin(users).on(orders).single().execute();
|
|
391
411
|
// Returns: { id: string; name: string; orders: OrderRow[] } | null
|
|
392
412
|
```
|
|
393
413
|
|
|
414
|
+
### Multi-Join
|
|
415
|
+
|
|
416
|
+
Chain multiple joins:
|
|
417
|
+
|
|
418
|
+
```ts
|
|
419
|
+
await db.leftJoin(users).on(orders).leftJoin(orders).on(orderItems).execute();
|
|
420
|
+
```
|
|
421
|
+
|
|
394
422
|
---
|
|
395
423
|
|
|
396
424
|
## Conditions
|
|
397
425
|
|
|
398
|
-
All conditions are imported from `flint-orm`.
|
|
426
|
+
All conditions are imported from `flint-orm/expressions`.
|
|
399
427
|
|
|
400
428
|
### Comparison
|
|
401
429
|
|
|
402
430
|
```ts
|
|
431
|
+
import { eq, neq, gt, gte, lt, lte } from 'flint-orm/expressions';
|
|
432
|
+
|
|
403
433
|
eq(column, value); // column = value
|
|
404
434
|
eq(left, right); // left = right (column-to-column)
|
|
405
435
|
neq(column, value); // column != value
|
|
@@ -412,12 +442,16 @@ lte(column, value); // column <= value
|
|
|
412
442
|
### Range
|
|
413
443
|
|
|
414
444
|
```ts
|
|
445
|
+
import { between } from 'flint-orm/expressions';
|
|
446
|
+
|
|
415
447
|
between(column, low, high); // column BETWEEN low AND high
|
|
416
448
|
```
|
|
417
449
|
|
|
418
450
|
### Null Checks
|
|
419
451
|
|
|
420
452
|
```ts
|
|
453
|
+
import { isNull, isNotNull } from 'flint-orm/expressions';
|
|
454
|
+
|
|
421
455
|
isNull(column); // column IS NULL
|
|
422
456
|
isNotNull(column); // column IS NOT NULL
|
|
423
457
|
```
|
|
@@ -425,6 +459,8 @@ isNotNull(column); // column IS NOT NULL
|
|
|
425
459
|
### Array
|
|
426
460
|
|
|
427
461
|
```ts
|
|
462
|
+
import { isIn, isNotIn } from 'flint-orm/expressions';
|
|
463
|
+
|
|
428
464
|
isIn(column, values); // column IN (?, ?, ...)
|
|
429
465
|
isNotIn(column, values); // column NOT IN (?, ?, ...)
|
|
430
466
|
```
|
|
@@ -432,6 +468,8 @@ isNotIn(column, values); // column NOT IN (?, ?, ...)
|
|
|
432
468
|
### Pattern Matching
|
|
433
469
|
|
|
434
470
|
```ts
|
|
471
|
+
import { like, glob } from 'flint-orm/expressions';
|
|
472
|
+
|
|
435
473
|
like(column, pattern); // column LIKE ? (% and _ wildcards, case-insensitive)
|
|
436
474
|
glob(column, pattern); // column GLOB ? (* and ? wildcards, case-sensitive)
|
|
437
475
|
```
|
|
@@ -439,181 +477,140 @@ glob(column, pattern); // column GLOB ? (* and ? wildcards, case-sensitive)
|
|
|
439
477
|
### Logical
|
|
440
478
|
|
|
441
479
|
```ts
|
|
480
|
+
import { and, or } from 'flint-orm/expressions';
|
|
481
|
+
|
|
442
482
|
and(...conditions); // cond1 AND cond2 AND ...
|
|
443
483
|
or(...conditions); // (cond1 OR cond2 OR ...)
|
|
444
484
|
```
|
|
445
485
|
|
|
446
|
-
### Examples
|
|
447
|
-
|
|
448
|
-
```ts
|
|
449
|
-
import { eq, and, or, gt, isIn, like, between } from "flint-orm";
|
|
450
|
-
|
|
451
|
-
// Simple equality
|
|
452
|
-
.where(eq(users.name, "Alice"))
|
|
453
|
-
|
|
454
|
-
// Column-to-column
|
|
455
|
-
.where(eq(orders.userId, users.id))
|
|
456
|
-
|
|
457
|
-
// Multiple conditions
|
|
458
|
-
.where(and(eq(users.active, true), gt(users.age, 18)))
|
|
459
|
-
|
|
460
|
-
// OR
|
|
461
|
-
.where(or(eq(users.name, "Alice"), eq(users.name, "Bob")))
|
|
462
|
-
|
|
463
|
-
// IN
|
|
464
|
-
.where(isIn(users.status, ["active", "pending"]))
|
|
465
|
-
|
|
466
|
-
// LIKE
|
|
467
|
-
.where(like(users.email, "%@example.com"))
|
|
468
|
-
|
|
469
|
-
// BETWEEN
|
|
470
|
-
.where(between(users.age, 18, 65))
|
|
471
|
-
```
|
|
472
|
-
|
|
473
486
|
---
|
|
474
487
|
|
|
475
488
|
## Aggregates
|
|
476
489
|
|
|
477
|
-
Aggregate functions are methods on the `db` object. They
|
|
478
|
-
|
|
479
|
-
### `db.count(table, condition?)`
|
|
480
|
-
|
|
481
|
-
Count all rows.
|
|
490
|
+
Aggregate functions are methods on the `db` object. They return `Promise<T>`.
|
|
482
491
|
|
|
483
492
|
```ts
|
|
484
|
-
db.count(users);
|
|
485
|
-
db.count(users, eq(users.active, true));
|
|
493
|
+
const total = await db.count(users);
|
|
494
|
+
const active = await db.count(users, eq(users.active, true));
|
|
495
|
+
const totalViews = await db.sum(posts, posts.views);
|
|
496
|
+
const avgAge = await db.avg(users, users.age);
|
|
497
|
+
const minAge = await db.min(users, users.age);
|
|
498
|
+
const maxAge = await db.max(users, users.age);
|
|
486
499
|
```
|
|
487
500
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
Count non-null values in a column.
|
|
491
|
-
|
|
492
|
-
```ts
|
|
493
|
-
db.countColumn(users, users.email); // 145 (5 users have no email)
|
|
494
|
-
```
|
|
501
|
+
---
|
|
495
502
|
|
|
496
|
-
|
|
503
|
+
## Batch
|
|
497
504
|
|
|
498
|
-
|
|
505
|
+
Run multiple queries atomically in a single transaction.
|
|
499
506
|
|
|
500
507
|
```ts
|
|
501
|
-
db.
|
|
502
|
-
db.
|
|
508
|
+
await db.batch([
|
|
509
|
+
db.insert(orders).values({ id: 'o1', userId: 'u1', total: 100 }),
|
|
510
|
+
db.update(users).set({ totalOrders: 1 }).where(eq(users.id, 'u1')),
|
|
511
|
+
]);
|
|
503
512
|
```
|
|
504
513
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
Average of values. Returns `null` if no rows match.
|
|
514
|
+
---
|
|
508
515
|
|
|
509
|
-
|
|
510
|
-
db.avg(orders, orders.total); // 300
|
|
511
|
-
db.avg(orders, orders.total, eq(orders.userId, 'u1')); // 500
|
|
512
|
-
```
|
|
516
|
+
## Raw SQL
|
|
513
517
|
|
|
514
|
-
### `db
|
|
518
|
+
### `db.$run(sql, ...params)`
|
|
515
519
|
|
|
516
|
-
|
|
520
|
+
Execute raw SQL directly against the database.
|
|
517
521
|
|
|
518
522
|
```ts
|
|
519
|
-
db
|
|
520
|
-
db
|
|
523
|
+
await db.$run('CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)');
|
|
524
|
+
await db.$run('INSERT INTO test VALUES (?, ?)', 1, 'Alice');
|
|
521
525
|
```
|
|
522
526
|
|
|
523
|
-
###
|
|
527
|
+
### Tagged Template SQL
|
|
524
528
|
|
|
525
|
-
|
|
529
|
+
Build parameterized SQL expressions with automatic placeholder handling.
|
|
526
530
|
|
|
527
531
|
```ts
|
|
528
|
-
|
|
529
|
-
db.max(orders, orders.total, eq(orders.userId, 'u1')); // 800
|
|
530
|
-
```
|
|
531
|
-
|
|
532
|
-
### Multiple Aggregates
|
|
532
|
+
import { sql } from 'flint-orm';
|
|
533
533
|
|
|
534
|
-
|
|
534
|
+
const expr = sql`name = ${'Alice'} AND age > ${18}`;
|
|
535
|
+
// { sql: "name = ? AND age > ?", params: ["Alice", 18] }
|
|
535
536
|
|
|
536
|
-
|
|
537
|
-
const [total, revenue, avgOrder] = await Promise.all([db.count(orders), db.sum(orders, orders.total), db.avg(orders, orders.total)]);
|
|
537
|
+
const result = await db.select().from(users).where(expr).execute();
|
|
538
538
|
```
|
|
539
539
|
|
|
540
540
|
---
|
|
541
541
|
|
|
542
|
-
##
|
|
543
|
-
|
|
544
|
-
Run multiple queries atomically in a single transaction.
|
|
545
|
-
|
|
546
|
-
```ts
|
|
547
|
-
import { flint, table, text, eq } from 'flint-orm';
|
|
542
|
+
## Migration System
|
|
548
543
|
|
|
549
|
-
|
|
544
|
+
### CLI
|
|
550
545
|
|
|
551
|
-
|
|
546
|
+
```bash
|
|
547
|
+
flint generate --name init_schema # Generate migration
|
|
548
|
+
flint generate --preview # Preview SQL without writing
|
|
549
|
+
flint migrate # Apply pending migrations
|
|
550
|
+
flint migrate --status # Show applied vs pending
|
|
551
|
+
flint migrate --dry-run # Preview without executing
|
|
552
552
|
```
|
|
553
553
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
---
|
|
557
|
-
|
|
558
|
-
## Raw SQL
|
|
559
|
-
|
|
560
|
-
Access the underlying `bun:sqlite` client directly for raw queries.
|
|
554
|
+
### Programmatic API
|
|
561
555
|
|
|
562
556
|
```ts
|
|
563
|
-
|
|
564
|
-
const users = db.$client.prepare('SELECT * FROM users WHERE id = ?').all('u1');
|
|
565
|
-
|
|
566
|
-
// With type annotation
|
|
567
|
-
const rows = db.$client.prepare('SELECT count(*) as cnt FROM users').all() as { cnt: number }[];
|
|
568
|
-
```
|
|
557
|
+
import { generate, migrate, getMigrationStatus, serializeSchema, diffSchemas, generateSQL } from 'flint-orm/migration';
|
|
569
558
|
|
|
570
|
-
|
|
559
|
+
// Serialize table definitions to JSON
|
|
560
|
+
const state = serializeSchema([users, orders]);
|
|
571
561
|
|
|
572
|
-
|
|
562
|
+
// Diff two schema states
|
|
563
|
+
const operations = diffSchemas(previousState, currentState);
|
|
573
564
|
|
|
574
|
-
|
|
565
|
+
// Generate SQL from operations
|
|
566
|
+
const sql = generateSQL(operations);
|
|
575
567
|
|
|
576
|
-
|
|
577
|
-
|
|
568
|
+
// Generate a migration folder
|
|
569
|
+
const result = await generate([users, orders], './flint', { name: 'init_schema', interactive: true });
|
|
578
570
|
|
|
579
|
-
|
|
580
|
-
|
|
571
|
+
// Apply pending migrations
|
|
572
|
+
const result = await migrate(executor, { migrationsDir: './flint' });
|
|
581
573
|
|
|
582
|
-
//
|
|
583
|
-
const
|
|
574
|
+
// Check migration status
|
|
575
|
+
const status = await getMigrationStatus(executor, './flint');
|
|
584
576
|
```
|
|
585
577
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
## Escape Hatch
|
|
589
|
-
|
|
590
|
-
Access the underlying `bun:sqlite` client directly.
|
|
578
|
+
### Migration Operations
|
|
591
579
|
|
|
592
580
|
```ts
|
|
593
|
-
|
|
581
|
+
import { addTable, dropTable, renameTable, addColumn, dropColumn, renameColumn, createIndex, dropIndex } from 'flint-orm/migration';
|
|
594
582
|
```
|
|
595
583
|
|
|
584
|
+
| Operation | SQL Generated |
|
|
585
|
+
| -------------- | -------------------------------------- |
|
|
586
|
+
| `addTable` | `CREATE TABLE ...` |
|
|
587
|
+
| `dropTable` | `DROP TABLE ...` |
|
|
588
|
+
| `renameTable` | `ALTER TABLE ... RENAME TO ...` |
|
|
589
|
+
| `addColumn` | `ALTER TABLE ... ADD COLUMN ...` |
|
|
590
|
+
| `dropColumn` | `ALTER TABLE ... DROP COLUMN ...` |
|
|
591
|
+
| `renameColumn` | `ALTER TABLE ... RENAME COLUMN ... TO` |
|
|
592
|
+
| `createIndex` | `CREATE [UNIQUE] INDEX ...` |
|
|
593
|
+
| `dropIndex` | `DROP INDEX ...` |
|
|
594
|
+
|
|
596
595
|
---
|
|
597
596
|
|
|
598
597
|
## Types
|
|
599
598
|
|
|
600
|
-
| Type
|
|
601
|
-
|
|
|
602
|
-
| `TableDef<T>`
|
|
603
|
-
| `ColumnDef<T, S>`
|
|
604
|
-
| `InferRow<T>`
|
|
605
|
-
| `InsertRow<T>`
|
|
606
|
-
| `
|
|
607
|
-
| `
|
|
608
|
-
| `
|
|
609
|
-
| `
|
|
599
|
+
| Type | Description |
|
|
600
|
+
| ----------------- | ----------------------------------------------------------------------------------------- |
|
|
601
|
+
| `TableDef<T>` | Table definition with hidden `._` metadata |
|
|
602
|
+
| `ColumnDef<T, S>` | Column definition with phantom types |
|
|
603
|
+
| `InferRow<T>` | Derives row type from table definition |
|
|
604
|
+
| `InsertRow<T>` | Derives insert type (defaults are optional) |
|
|
605
|
+
| `Executor` | Database executor interface (all, get, run, transaction) |
|
|
606
|
+
| `SQLExpression` | `{ sql: string; params: unknown[] }` |
|
|
607
|
+
| `Executable` | Anything with a `.toSQL()` method (for `batch()`) |
|
|
608
|
+
| `Driver` | `'bun-sqlite' \| 'better-sqlite3' \| 'libsql' \| 'libsql-web' \| 'turso' \| 'turso-sync'` |
|
|
610
609
|
|
|
611
610
|
---
|
|
612
611
|
|
|
613
612
|
## Error Classes
|
|
614
613
|
|
|
615
|
-
Prefixed with `Flint` to avoid collisions in consumer codebases.
|
|
616
|
-
|
|
617
614
|
| Class | When |
|
|
618
615
|
| ---------------------- | ----------------------------------------------------------------- |
|
|
619
616
|
| `FlintValidationError` | Invalid query construction (e.g., no primary key for `.single()`) |
|