flint-orm 0.7.0 → 0.7.1
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/README.md +2 -0
- package/package.json +15 -3
- package/skills/batch-transactions/SKILL.md +120 -0
- package/skills/define-schema/SKILL.md +212 -0
- package/skills/define-schema/references/column-modifiers.md +176 -0
- package/skills/driver-selection/SKILL.md +180 -0
- package/skills/driver-selection/references/driver-comparison.md +141 -0
- package/skills/run-migrations/SKILL.md +163 -0
- package/skills/setup-client/SKILL.md +142 -0
- package/skills/update-schema/SKILL.md +186 -0
- package/skills/write-queries/SKILL.md +267 -0
- package/skills/write-queries/references/condition-operators.md +220 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: update-schema
|
|
3
|
+
description: >
|
|
4
|
+
Add columns and tables without data loss. Covers safe vs unsafe migrations,
|
|
5
|
+
rebuildTable operation, additive migration patterns, and which changes
|
|
6
|
+
trigger table rebuilds. Load when modifying schema after initial migration
|
|
7
|
+
or when planning schema evolution.
|
|
8
|
+
metadata:
|
|
9
|
+
type: core
|
|
10
|
+
library: flint-orm
|
|
11
|
+
library_version: 0.7.0
|
|
12
|
+
sources:
|
|
13
|
+
- 'kavenlabs/flint-orm:src/migration/diff.ts'
|
|
14
|
+
- 'kavenlabs/flint-orm:src/migration/sql.ts'
|
|
15
|
+
- 'kavenlabs/flint-orm:src/migration/migrate.ts'
|
|
16
|
+
- 'kavenlabs/flint-orm:README.md'
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# flint-orm — Schema Updates
|
|
20
|
+
|
|
21
|
+
## Setup
|
|
22
|
+
|
|
23
|
+
After defining your initial schema, you'll need to evolve it over time. flint-orm detects changes and generates appropriate migrations.
|
|
24
|
+
|
|
25
|
+
## Core Patterns
|
|
26
|
+
|
|
27
|
+
### Safe migration: Adding a column with default
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
// Add a new column with a default value — safe, no data loss
|
|
31
|
+
const users = snakeCase.table('users', {
|
|
32
|
+
id: integer().autoIncrement().primaryKey(),
|
|
33
|
+
name: text().notNull(),
|
|
34
|
+
status: text().notNull().default('active'), // NEW: safe to add
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Safe migration: Adding a new table
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
// Add a new table — always safe
|
|
42
|
+
const posts = snakeCase.table('posts', {
|
|
43
|
+
id: integer().autoIncrement().primaryKey(),
|
|
44
|
+
userId: integer().notNull().references(users.id),
|
|
45
|
+
title: text().notNull(),
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Unsafe migration: Changing column type
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
// Changing a column type triggers a table rebuild
|
|
53
|
+
// The old column was: age: text('age')
|
|
54
|
+
// The new column is: age: integer('age')
|
|
55
|
+
const users = snakeCase.table('users', {
|
|
56
|
+
id: integer().autoIncrement().primaryKey(),
|
|
57
|
+
name: text().notNull(),
|
|
58
|
+
age: integer('age').notNull().default(0), // TYPE CHANGE: triggers rebuild
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Unsafe migration: Adding NOT NULL without default
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
// Adding NOT NULL without a default triggers a table rebuild
|
|
66
|
+
const users = snakeCase.table('users', {
|
|
67
|
+
id: integer().autoIncrement().primaryKey(),
|
|
68
|
+
name: text().notNull(),
|
|
69
|
+
status: text().notNull(), // NO DEFAULT: triggers rebuild
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Migration Safety Reference
|
|
74
|
+
|
|
75
|
+
### Safe changes (no rebuild)
|
|
76
|
+
|
|
77
|
+
| Change | Example |
|
|
78
|
+
|--------|---------|
|
|
79
|
+
| Add column with default | `status: text().default('active')` |
|
|
80
|
+
| Add new table | `snakeCase.table('new_table', {...})` |
|
|
81
|
+
| Add index | `index('idx_name').on(t.name)` |
|
|
82
|
+
| Drop index | Remove from index callback |
|
|
83
|
+
| Change default value | `status: text().default('inactive')` |
|
|
84
|
+
|
|
85
|
+
### Unsafe changes (triggers rebuild)
|
|
86
|
+
|
|
87
|
+
| Change | Example |
|
|
88
|
+
|--------|---------|
|
|
89
|
+
| Change column type | `text()` → `integer()` |
|
|
90
|
+
| Add NOT NULL without default | `text().notNull()` (no `.default()`) |
|
|
91
|
+
| Remove NOT NULL | `text().notNull()` → `text()` |
|
|
92
|
+
| Add/remove UNIQUE | `text().unique()` → `text()` |
|
|
93
|
+
| Remove default | `text().default('x')` → `text()` |
|
|
94
|
+
| Add/change/remove foreign key | `.references(col)` changes |
|
|
95
|
+
| Change FK actions | `.onDelete('cascade')` changes |
|
|
96
|
+
| Change autoincrement | `.autoIncrement()` changes |
|
|
97
|
+
|
|
98
|
+
### What is rebuildTable?
|
|
99
|
+
|
|
100
|
+
When an unsafe change is detected, flint-orm generates a `rebuildTable` operation that:
|
|
101
|
+
|
|
102
|
+
1. Creates a temporary table with the new schema
|
|
103
|
+
2. Copies data from the old table
|
|
104
|
+
3. Drops the old table
|
|
105
|
+
4. Renames the temporary table to the original name
|
|
106
|
+
5. Recreates indexes
|
|
107
|
+
|
|
108
|
+
This happens within a transaction — if anything fails, the original table is preserved.
|
|
109
|
+
|
|
110
|
+
## Common Mistakes
|
|
111
|
+
|
|
112
|
+
### HIGH Adding NOT NULL column without a default
|
|
113
|
+
|
|
114
|
+
Wrong:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
// After initial migration, adding:
|
|
118
|
+
const users = snakeCase.table('users', {
|
|
119
|
+
id: integer().autoIncrement().primaryKey(),
|
|
120
|
+
name: text().notNull(),
|
|
121
|
+
status: text().notNull(), // No default — triggers rebuild
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Correct:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const users = snakeCase.table('users', {
|
|
129
|
+
id: integer().autoIncrement().primaryKey(),
|
|
130
|
+
name: text().notNull(),
|
|
131
|
+
status: text().notNull().default('active'), // Safe: has default
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
SQLite cannot add NOT NULL columns to existing tables without a default — this triggers a table rebuild.
|
|
136
|
+
|
|
137
|
+
Source: src/migration/diff.ts
|
|
138
|
+
|
|
139
|
+
### HIGH Changing a column type
|
|
140
|
+
|
|
141
|
+
Wrong:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
// Changing after initial migration:
|
|
145
|
+
const users = snakeCase.table('users', {
|
|
146
|
+
id: integer().autoIncrement().primaryKey(),
|
|
147
|
+
name: text().notNull(),
|
|
148
|
+
age: integer('age'), // Was: age: text('age')
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Correct:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
// Plan for the type from the start, or accept the rebuild
|
|
156
|
+
const users = snakeCase.table('users', {
|
|
157
|
+
id: integer().autoIncrement().primaryKey(),
|
|
158
|
+
name: text().notNull(),
|
|
159
|
+
age: integer('age').notNull().default(0), // Correct type from start
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
SQLite has no `ALTER COLUMN TYPE` — this always triggers a table rebuild.
|
|
164
|
+
|
|
165
|
+
Source: src/migration/diff.ts
|
|
166
|
+
|
|
167
|
+
### HIGH Dropping a column referenced by foreign keys
|
|
168
|
+
|
|
169
|
+
Wrong:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
// Dropping users.id when orders.userId references it
|
|
173
|
+
// Migration fails with: Cannot rebuild "users" — referenced by: orders
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Correct:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# Migrate dependent tables first, then rebuild the parent
|
|
180
|
+
flint migrate # Apply orders migration first
|
|
181
|
+
flint generate # Then rebuild users
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The migration runner checks for incoming foreign keys before rebuild — it will refuse if other tables reference the table being rebuilt.
|
|
185
|
+
|
|
186
|
+
Source: src/migration/migrate.ts
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: write-queries
|
|
3
|
+
description: >
|
|
4
|
+
Read, write, and modify data with type-safe SELECT, INSERT, UPDATE, DELETE,
|
|
5
|
+
joins, and aggregates. Covers selectFrom, insert, update, delete, leftJoin,
|
|
6
|
+
innerJoin, where, columns, single, orderBy, limit, offset, distinct,
|
|
7
|
+
returning, onConflictDoNothing, onConflictDoUpdate, count, sum, avg, min,
|
|
8
|
+
max, and raw SQL. Load when building queries or troubleshooting query issues.
|
|
9
|
+
metadata:
|
|
10
|
+
type: core
|
|
11
|
+
library: flint-orm
|
|
12
|
+
library_version: 0.7.0
|
|
13
|
+
sources:
|
|
14
|
+
- 'kavenlabs/flint-orm:src/query/builder.ts'
|
|
15
|
+
- 'kavenlabs/flint-orm:src/query/conditions.ts'
|
|
16
|
+
- 'kavenlabs/flint-orm:src/query/aggregates.ts'
|
|
17
|
+
- 'kavenlabs/flint-orm:src/flint.ts'
|
|
18
|
+
- 'kavenlabs/flint-orm:README.md'
|
|
19
|
+
- 'kavenlabs/flint-orm:API.md'
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# flint-orm — Queries
|
|
23
|
+
|
|
24
|
+
## Setup
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { flint } from 'flint-orm/bun-sqlite';
|
|
28
|
+
import { table, text, integer } from 'flint-orm/table';
|
|
29
|
+
import { eq, and } from 'flint-orm/expressions';
|
|
30
|
+
|
|
31
|
+
const users = table('users', {
|
|
32
|
+
id: text('id').primaryKey(),
|
|
33
|
+
name: text('name').notNull(),
|
|
34
|
+
age: integer('age'),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const db = flint({ url: './app.db' });
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Core Patterns
|
|
41
|
+
|
|
42
|
+
### SELECT with conditions
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
const adults = await db
|
|
46
|
+
.selectFrom(users)
|
|
47
|
+
.where(eq(users.age, 18))
|
|
48
|
+
.execute();
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### SELECT specific columns
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
const names = await db
|
|
55
|
+
.selectFrom(users)
|
|
56
|
+
.columns(['id', 'name'])
|
|
57
|
+
.execute();
|
|
58
|
+
// Returns: { id: string; name: string }[]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Get a single row
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
const user = await db
|
|
65
|
+
.selectFrom(users)
|
|
66
|
+
.where(eq(users.id, 'u1'))
|
|
67
|
+
.single()
|
|
68
|
+
.execute();
|
|
69
|
+
// Returns: User | null
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### INSERT with returning
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
// Return all columns
|
|
76
|
+
const inserted = await db
|
|
77
|
+
.insert(users)
|
|
78
|
+
.values({ id: 'u1', name: 'Alice', age: 30 })
|
|
79
|
+
.returning()
|
|
80
|
+
.execute();
|
|
81
|
+
|
|
82
|
+
// Return specific columns
|
|
83
|
+
const inserted = await db
|
|
84
|
+
.insert(users)
|
|
85
|
+
.values({ id: 'u1', name: 'Alice', age: 30 })
|
|
86
|
+
.returning(['id', 'name'])
|
|
87
|
+
.execute();
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### UPSERT
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
await db
|
|
94
|
+
.insert(users)
|
|
95
|
+
.values({ id: 'u1', name: 'Alice' })
|
|
96
|
+
.onConflictDoUpdate({
|
|
97
|
+
target: users.id,
|
|
98
|
+
set: { name: 'Alice Updated' },
|
|
99
|
+
})
|
|
100
|
+
.execute();
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### UPDATE with conditions
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
// Update without returning
|
|
107
|
+
await db
|
|
108
|
+
.update(users)
|
|
109
|
+
.set({ name: 'Bob' })
|
|
110
|
+
.where(eq(users.id, 'u1'))
|
|
111
|
+
.execute();
|
|
112
|
+
|
|
113
|
+
// Update with returning
|
|
114
|
+
const updated = await db
|
|
115
|
+
.update(users)
|
|
116
|
+
.set({ name: 'Bob' })
|
|
117
|
+
.where(eq(users.id, 'u1'))
|
|
118
|
+
.returning(['id', 'name'])
|
|
119
|
+
.execute();
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### DELETE with conditions
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
// Delete without returning
|
|
126
|
+
await db
|
|
127
|
+
.delete(users)
|
|
128
|
+
.where(eq(users.id, 'u1'))
|
|
129
|
+
.execute();
|
|
130
|
+
|
|
131
|
+
// Delete with returning
|
|
132
|
+
const deleted = await db
|
|
133
|
+
.delete(users)
|
|
134
|
+
.where(eq(users.id, 'u1'))
|
|
135
|
+
.returning()
|
|
136
|
+
.execute();
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### LEFT JOIN
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
const posts = table('posts', {
|
|
143
|
+
id: text('id').primaryKey(),
|
|
144
|
+
userId: text('userId').notNull().references(users.id),
|
|
145
|
+
title: text('title').notNull(),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const postsWithAuthors = await db
|
|
149
|
+
.leftJoin(users)
|
|
150
|
+
.on(posts)
|
|
151
|
+
.execute();
|
|
152
|
+
// Returns: { id: string; name: string; posts: Post[] }[]
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Aggregate functions
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
const total = await db.count(users);
|
|
159
|
+
const activeCount = await db.count(users, eq(users.active, true));
|
|
160
|
+
const totalViews = await db.sum(posts, posts.views);
|
|
161
|
+
const avgAge = await db.avg(users, users.age);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Building Queries Conditionally
|
|
165
|
+
|
|
166
|
+
The query builder is immutable — you can compose queries conditionally:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
let query = db.selectFrom(users);
|
|
170
|
+
|
|
171
|
+
if (filter.name) {
|
|
172
|
+
query = query.where(eq(users.name, filter.name));
|
|
173
|
+
}
|
|
174
|
+
if (filter.minAge) {
|
|
175
|
+
query = query.where(gte(users.age, filter.minAge));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const results = await query.execute();
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Call `.execute()` at the terminal state when ready to run the query.
|
|
182
|
+
|
|
183
|
+
## Common Mistakes
|
|
184
|
+
|
|
185
|
+
### CRITICAL Not calling .execute() at terminal state
|
|
186
|
+
|
|
187
|
+
Wrong:
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
const query = db.selectFrom(users).where(eq(users.id, 'u1')); // Builder, not executed
|
|
191
|
+
const data = query; // Missing .execute()
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Correct:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
// Building queries conditionally is fine:
|
|
198
|
+
let query = db.selectFrom(users);
|
|
199
|
+
if (filter) query = query.where(eq(users.active, true));
|
|
200
|
+
// But call .execute() at the terminal state:
|
|
201
|
+
const data = await query.execute();
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Queries are builders — you can compose them conditionally, but `.execute()` must be called when ready to run the query.
|
|
205
|
+
|
|
206
|
+
Source: maintainer interview
|
|
207
|
+
|
|
208
|
+
### CRITICAL Not awaiting .execute()
|
|
209
|
+
|
|
210
|
+
Wrong:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
const users = db.selectFrom(users).where(eq(users.id, 'u1')).execute(); // Promise, not data
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Correct:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
const users = await db.selectFrom(users).where(eq(users.id, 'u1')).execute();
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
`.execute()` returns a Promise — without `await`, you get the Promise object, not the data.
|
|
223
|
+
|
|
224
|
+
Source: maintainer interview
|
|
225
|
+
|
|
226
|
+
### HIGH Mixing Kysely/Drizzle/Supabase API patterns
|
|
227
|
+
|
|
228
|
+
Wrong:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
// Supabase-style (wrong)
|
|
232
|
+
const users = await db.from('users').select('id, name');
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Correct:
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
// Flint-style
|
|
239
|
+
const users = await db.selectFrom(users).columns(['id', 'name']).execute();
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
flint-orm has its own API — `selectFrom().columns([...]).single()` is specific to this library.
|
|
243
|
+
|
|
244
|
+
Source: maintainer interview
|
|
245
|
+
|
|
246
|
+
### HIGH Using WHERE conditions with columns from wrong table
|
|
247
|
+
|
|
248
|
+
Wrong:
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
// Using posts column in users query
|
|
252
|
+
await db.selectFrom(users).where(eq(posts.userId, 'u1')).execute(); // ERROR
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Correct:
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
await db.selectFrom(users).where(eq(users.id, 'u1')).execute();
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
The builder validates column ownership — columns in WHERE must belong to the queried table(s).
|
|
262
|
+
|
|
263
|
+
Source: src/query/builder.ts
|
|
264
|
+
|
|
265
|
+
## References
|
|
266
|
+
|
|
267
|
+
- [Full condition operators reference](references/condition-operators.md)
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Condition Operators Reference
|
|
2
|
+
|
|
3
|
+
Detailed reference for all condition operators in flint-orm.
|
|
4
|
+
|
|
5
|
+
All conditions are imported from `flint-orm/expressions`.
|
|
6
|
+
|
|
7
|
+
## Comparison Operators
|
|
8
|
+
|
|
9
|
+
### eq(column, value)
|
|
10
|
+
|
|
11
|
+
Equal comparison.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { eq } from 'flint-orm/expressions';
|
|
15
|
+
|
|
16
|
+
// Value comparison
|
|
17
|
+
eq(users.name, 'Alice') // name = 'Alice'
|
|
18
|
+
|
|
19
|
+
// Column-to-column comparison
|
|
20
|
+
eq(posts.userId, users.id) // posts.userId = users.id
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### neq(column, value)
|
|
24
|
+
|
|
25
|
+
Not equal comparison.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { neq } from 'flint-orm/expressions';
|
|
29
|
+
|
|
30
|
+
neq(users.id, 'excluded') // id != 'excluded'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### gt(column, value)
|
|
34
|
+
|
|
35
|
+
Greater than.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { gt } from 'flint-orm/expressions';
|
|
39
|
+
|
|
40
|
+
gt(users.age, 18) // age > 18
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### gte(column, value)
|
|
44
|
+
|
|
45
|
+
Greater than or equal.
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { gte } from 'flint-orm/expressions';
|
|
49
|
+
|
|
50
|
+
gte(users.age, 18) // age >= 18
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### lt(column, value)
|
|
54
|
+
|
|
55
|
+
Less than.
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { lt } from 'flint-orm/expressions';
|
|
59
|
+
|
|
60
|
+
lt(users.age, 65) // age < 65
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### lte(column, value)
|
|
64
|
+
|
|
65
|
+
Less than or equal.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { lte } from 'flint-orm/expressions';
|
|
69
|
+
|
|
70
|
+
lte(users.age, 65) // age <= 65
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Range Operator
|
|
74
|
+
|
|
75
|
+
### between(column, low, high)
|
|
76
|
+
|
|
77
|
+
Between two values (inclusive).
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { between } from 'flint-orm/expressions';
|
|
81
|
+
|
|
82
|
+
between(users.age, 18, 65) // age BETWEEN 18 AND 65
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Null Check Operators
|
|
86
|
+
|
|
87
|
+
### isNull(column)
|
|
88
|
+
|
|
89
|
+
Check for NULL.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { isNull } from 'flint-orm/expressions';
|
|
93
|
+
|
|
94
|
+
isNull(users.deletedAt) // deletedAt IS NULL
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### isNotNull(column)
|
|
98
|
+
|
|
99
|
+
Check for NOT NULL.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { isNotNull } from 'flint-orm/expressions';
|
|
103
|
+
|
|
104
|
+
isNotNull(users.email) // email IS NOT NULL
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Array Operators
|
|
108
|
+
|
|
109
|
+
### isIn(column, values)
|
|
110
|
+
|
|
111
|
+
Check if value is in array.
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { isIn } from 'flint-orm/expressions';
|
|
115
|
+
|
|
116
|
+
isIn(users.id, ['u1', 'u2', 'u3']) // id IN ('u1', 'u2', 'u3')
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### isNotIn(column, values)
|
|
120
|
+
|
|
121
|
+
Check if value is not in array.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { isNotIn } from 'flint-orm/expressions';
|
|
125
|
+
|
|
126
|
+
isNotIn(users.id, ['excluded']) // id NOT IN ('excluded')
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Pattern Matching Operators
|
|
130
|
+
|
|
131
|
+
### like(column, pattern)
|
|
132
|
+
|
|
133
|
+
SQL LIKE pattern matching (% = any characters, _ = single char, case-insensitive).
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import { like } from 'flint-orm/expressions';
|
|
137
|
+
|
|
138
|
+
like(users.name, 'A%') // name LIKE 'A%'
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### glob(column, pattern)
|
|
142
|
+
|
|
143
|
+
SQL GLOB pattern matching (* = any characters, ? = single char, case-sensitive).
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import { glob } from 'flint-orm/expressions';
|
|
147
|
+
|
|
148
|
+
glob(users.name, 'A*') // name GLOB 'A*'
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Logical Operators
|
|
152
|
+
|
|
153
|
+
### and(...conditions)
|
|
154
|
+
|
|
155
|
+
Logical AND.
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
import { and, eq, gt } from 'flint-orm/expressions';
|
|
159
|
+
|
|
160
|
+
and(eq(users.active, true), gt(users.age, 18))
|
|
161
|
+
// active = 1 AND age > 18
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### or(...conditions)
|
|
165
|
+
|
|
166
|
+
Logical OR (wraps in parentheses).
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import { or, eq } from 'flint-orm/expressions';
|
|
170
|
+
|
|
171
|
+
or(eq(users.role, 'admin'), eq(users.role, 'moderator'))
|
|
172
|
+
// (role = 'admin' OR role = 'moderator')
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Composing Conditions
|
|
176
|
+
|
|
177
|
+
Conditions can be nested and combined:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
import { and, or, eq, gt, lte, isIn } from 'flint-orm/expressions';
|
|
181
|
+
|
|
182
|
+
// Complex condition
|
|
183
|
+
and(
|
|
184
|
+
or(eq(users.role, 'admin'), eq(users.role, 'moderator')),
|
|
185
|
+
gt(users.age, 18),
|
|
186
|
+
isIn(users.status, ['active', 'pending'])
|
|
187
|
+
)
|
|
188
|
+
// ((role = 'admin' OR role = 'moderator') AND age > 18 AND status IN ('active', 'pending'))
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Column-to-Column Comparisons
|
|
192
|
+
|
|
193
|
+
`eq` supports comparing two columns:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import { eq } from 'flint-orm/expressions';
|
|
197
|
+
|
|
198
|
+
// Compare columns from different tables (useful for joins)
|
|
199
|
+
eq(posts.userId, users.id)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Operator Summary
|
|
203
|
+
|
|
204
|
+
| Operator | SQL | Notes |
|
|
205
|
+
|----------|-----|-------|
|
|
206
|
+
| `eq` | `=` | Supports column-to-column |
|
|
207
|
+
| `neq` | `!=` | |
|
|
208
|
+
| `gt` | `>` | |
|
|
209
|
+
| `gte` | `>=` | |
|
|
210
|
+
| `lt` | `<` | |
|
|
211
|
+
| `lte` | `<=` | |
|
|
212
|
+
| `between` | `BETWEEN` | Inclusive range |
|
|
213
|
+
| `isNull` | `IS NULL` | |
|
|
214
|
+
| `isNotNull` | `IS NOT NULL` | |
|
|
215
|
+
| `isIn` | `IN` | Array of values |
|
|
216
|
+
| `isNotIn` | `NOT IN` | Array of values |
|
|
217
|
+
| `like` | `LIKE` | % and _ wildcards, case-insensitive |
|
|
218
|
+
| `glob` | `GLOB` | * and ? wildcards, case-sensitive |
|
|
219
|
+
| `and` | `AND` | Composable |
|
|
220
|
+
| `or` | `OR` | Wraps in parentheses |
|