flint-orm 0.7.0 → 0.7.2

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 CHANGED
@@ -19,6 +19,8 @@ bun add flint-orm
19
19
  npm install flint-orm
20
20
  ```
21
21
 
22
+ **Using an AI agent?** Run `npx @tanstack/intent@latest install` to get agent-friendly skills for this library.
23
+
22
24
  ## Quick Start
23
25
 
24
26
  ```ts
package/package.json CHANGED
@@ -1,13 +1,25 @@
1
1
  {
2
2
  "name": "flint-orm",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
+ "keywords": [
5
+ "database",
6
+ "libsql",
7
+ "orm",
8
+ "sqlite",
9
+ "tanstack-intent",
10
+ "turso",
11
+ "typescript"
12
+ ],
13
+ "repository": "github:kavenlabs/flint-orm",
4
14
  "bin": {
5
15
  "flint": "./dist/src/cli.js"
6
16
  },
7
17
  "files": [
8
18
  "dist",
9
19
  "API.md",
10
- "README.md"
20
+ "README.md",
21
+ "skills",
22
+ "!skills/_artifacts"
11
23
  ],
12
24
  "type": "module",
13
25
  "main": "./dist/src/index.js",
@@ -69,6 +81,7 @@
69
81
  "devDependencies": {
70
82
  "@clack/prompts": "1.7.0",
71
83
  "@libsql/client": "0.17.4",
84
+ "@tanstack/intent": "0.3.5",
72
85
  "@tursodatabase/database": "0.6.1",
73
86
  "@tursodatabase/sync": "0.6.1",
74
87
  "@types/better-sqlite3": "7.6.13",
@@ -0,0 +1,120 @@
1
+ ---
2
+ name: batch-transactions
3
+ description: >
4
+ Run multiple queries atomically in a single transaction. Covers db.batch(),
5
+ Executable interface, and transaction safety. Load when combining multiple
6
+ writes that must succeed or fail together, or when debugging transaction
7
+ issues.
8
+ metadata:
9
+ type: core
10
+ library: flint-orm
11
+ library_version: 0.7.0
12
+ sources:
13
+ - 'kavenlabs/flint-orm:src/flint.ts'
14
+ - 'kavenlabs/flint-orm:src/query/builder.ts'
15
+ - 'kavenlabs/flint-orm:README.md'
16
+ - 'kavenlabs/flint-orm:API.md'
17
+ ---
18
+
19
+ # flint-orm — Batch Transactions
20
+
21
+ ## Setup
22
+
23
+ ```ts
24
+ import { flint } from 'flint-orm/bun-sqlite';
25
+ import { table, text, integer } from 'flint-orm/table';
26
+ import { eq } from 'flint-orm/expressions';
27
+
28
+ const users = table('users', {
29
+ id: text('id').primaryKey(),
30
+ name: text('name').notNull(),
31
+ totalOrders: integer('totalOrders').default(0),
32
+ });
33
+
34
+ const posts = table('posts', {
35
+ id: text('id').primaryKey(),
36
+ userId: text('userId').notNull().references(users.id),
37
+ title: text('title').notNull(),
38
+ });
39
+
40
+ const db = flint({ url: './app.db' });
41
+ ```
42
+
43
+ ## Core Patterns
44
+
45
+ ### Run multiple inserts atomically
46
+
47
+ ```ts
48
+ await db.batch([
49
+ db.insert(users).values({ id: 'u1', name: 'Alice' }),
50
+ db.insert(posts).values({ id: 'p1', userId: 'u1', title: 'Hello' }),
51
+ ]);
52
+ ```
53
+
54
+ ### Combine reads and writes
55
+
56
+ ```ts
57
+ await db.batch([
58
+ db.update(users).set({ totalOrders: 1 }).where(eq(users.id, 'u1')),
59
+ db.insert(posts).values({ id: 'p1', userId: 'u1', title: 'Hello' }),
60
+ ]);
61
+ ```
62
+
63
+ ### All-or-nothing execution
64
+
65
+ If any query in the batch fails, all queries are rolled back:
66
+
67
+ ```ts
68
+ // If the second insert fails, the first insert is also rolled back
69
+ await db.batch([
70
+ db.insert(users).values({ id: 'u1', name: 'Alice' }),
71
+ db.insert(posts).values({ id: 'p1', userId: 'u1', title: 'Hello' }), // If this fails...
72
+ ]);
73
+ // ...the user insert is also rolled back
74
+ ```
75
+
76
+ ## Common Mistakes
77
+
78
+ ### HIGH Not awaiting the batch call
79
+
80
+ Wrong:
81
+
82
+ ```ts
83
+ db.batch([
84
+ db.insert(users).values({ id: 'u1', name: 'Alice' }),
85
+ db.insert(posts).values({ id: 'p1', userId: 'u1', title: 'Hello' }),
86
+ ]); // Not awaited!
87
+ ```
88
+
89
+ Correct:
90
+
91
+ ```ts
92
+ await db.batch([
93
+ db.insert(users).values({ id: 'u1', name: 'Alice' }),
94
+ db.insert(posts).values({ id: 'p1', userId: 'u1', title: 'Hello' }),
95
+ ]);
96
+ ```
97
+
98
+ `batch()` returns a Promise — without `await`, the transaction may not complete.
99
+
100
+ Source: src/flint.ts
101
+
102
+ ### MEDIUM Passing non-Executable objects to batch()
103
+
104
+ Wrong:
105
+
106
+ ```ts
107
+ db.batch([{ sql: 'INSERT ...', params: [] }]); // Not an Executable
108
+ ```
109
+
110
+ Correct:
111
+
112
+ ```ts
113
+ db.batch([
114
+ db.insert(users).values({ id: 'u1', name: 'Alice' }),
115
+ ]);
116
+ ```
117
+
118
+ `batch()` only accepts objects with a `.toSQL()` method (query builders), not raw SQL objects.
119
+
120
+ Source: src/flint.ts
@@ -0,0 +1,212 @@
1
+ ---
2
+ name: define-schema
3
+ description: >
4
+ Create tables with columns, indexes, constraints, and foreign keys. Covers
5
+ table(), snakeCase.table(), column constructors (text, integer, boolean,
6
+ json, real, date), column modifiers (primaryKey, notNull, unique, default,
7
+ references, onDelete, onUpdate, autoIncrement, defaultNow, onUpdateTimestamp),
8
+ index builder, and InferRow/InsertRow types. Load when defining or modifying
9
+ database schemas.
10
+ metadata:
11
+ type: core
12
+ library: flint-orm
13
+ library_version: 0.7.0
14
+ sources:
15
+ - 'kavenlabs/flint-orm:src/schema/columns.ts'
16
+ - 'kavenlabs/flint-orm:src/schema/table.ts'
17
+ - 'kavenlabs/flint-orm:README.md'
18
+ - 'kavenlabs/flint-orm:API.md'
19
+ ---
20
+
21
+ # flint-orm — Schema Definition
22
+
23
+ ## Setup
24
+
25
+ ```ts
26
+ import * as d from 'flint-orm/table';
27
+
28
+ const users = d.snakeCase.table('users', {
29
+ id: d.integer().autoIncrement().primaryKey(),
30
+ name: d.text().notNull(),
31
+ email: d.text().unique(),
32
+ age: d.integer(),
33
+ active: d.boolean().default(true),
34
+ createdAt: d.date().defaultNow(),
35
+ });
36
+ ```
37
+
38
+ ## Core Patterns
39
+
40
+ ### Define a table with snake_case columns
41
+
42
+ Use `snakeCase.table()` to auto-convert camelCase keys to snake_case SQL names:
43
+
44
+ ```ts
45
+ import * as d from 'flint-orm/table';
46
+
47
+ const users = d.snakeCase.table('users', {
48
+ id: d.integer().autoIncrement().primaryKey(),
49
+ firstName: d.text().notNull(), // SQL: first_name
50
+ lastName: d.text().notNull(), // SQL: last_name
51
+ createdAt: d.date().defaultNow(), // SQL: created_at
52
+ });
53
+ ```
54
+
55
+ ### Add indexes
56
+
57
+ Define indexes via the table callback:
58
+
59
+ ```ts
60
+ import * as d from 'flint-orm/table';
61
+
62
+ const users = d.snakeCase.table(
63
+ 'users',
64
+ {
65
+ id: d.integer().autoIncrement().primaryKey(),
66
+ email: d.text().notNull(),
67
+ name: d.text().notNull(),
68
+ },
69
+ (t) => [
70
+ d.index('idx_users_email').on(t.email).unique(),
71
+ d.index('idx_users_name').on(t.name),
72
+ ],
73
+ );
74
+ ```
75
+
76
+ ### Define foreign keys
77
+
78
+ Use `.references()` to link columns across tables:
79
+
80
+ ```ts
81
+ import * as d from 'flint-orm/table';
82
+
83
+ const users = d.snakeCase.table('users', {
84
+ id: d.integer().autoIncrement().primaryKey(),
85
+ name: d.text().notNull(),
86
+ });
87
+
88
+ const posts = d.snakeCase.table('posts', {
89
+ id: d.integer().autoIncrement().primaryKey(),
90
+ userId: d.integer().notNull().references(users.id),
91
+ title: d.text().notNull(),
92
+ });
93
+ ```
94
+
95
+ ### Derive TypeScript types
96
+
97
+ ```ts
98
+ import type { InferRow, InsertRow } from 'flint-orm/table';
99
+
100
+ type User = InferRow<typeof users>;
101
+ // { id: number; name: string; email: string | null; active: boolean; createdAt: Date }
102
+
103
+ type NewUser = InsertRow<typeof users>;
104
+ // { id?: number; name: string; email?: string; active?: boolean; createdAt?: Date }
105
+ ```
106
+
107
+ ## Column Types
108
+
109
+ | Function | TS Type | SQLite Storage | Notes |
110
+ |----------|---------|----------------|-------|
111
+ | `text()` | `string` | TEXT | |
112
+ | `integer()` | `number` | INTEGER | Supports `.autoIncrement()` |
113
+ | `boolean()` | `boolean` | INTEGER (0/1) | Encodes/decodes automatically |
114
+ | `json<T>()` | `T` | TEXT (JSON) | Generic, encodes/decodes automatically |
115
+ | `real()` | `number` | REAL | |
116
+ | `date()` | `Date` | INTEGER (epoch ms) | Supports `.defaultNow()`, `.onUpdateTimestamp()` |
117
+
118
+ ## Column Modifiers
119
+
120
+ | Modifier | Applies to | Description |
121
+ |----------|------------|-------------|
122
+ | `.primaryKey()` | All | Mark as primary key |
123
+ | `.notNull()` | All | Disallow NULL values |
124
+ | `.unique()` | All | Add unique constraint |
125
+ | `.default(value)` | All | Static default value |
126
+ | `.defaultFn(fn)` | All | Dynamic default (called on insert) |
127
+ | `.references(col)` | All | Foreign key reference |
128
+ | `.onDelete(action)` | All | ON DELETE action (requires `.references()`) |
129
+ | `.onUpdate(action)` | All | ON UPDATE action (requires `.references()`) |
130
+ | `.autoIncrement()` | integer only | Auto-increment |
131
+ | `.defaultNow()` | date only | Use `Date.now()` as default |
132
+ | `.onUpdateTimestamp()` | date only | Always set to `Date.now()` on update |
133
+
134
+ ## Common Mistakes
135
+
136
+ ### HIGH Using table() with camelCase keys
137
+
138
+ Wrong:
139
+
140
+ ```ts
141
+ import { table, text } from 'flint-orm/table';
142
+
143
+ const users = table('users', {
144
+ id: text('id').primaryKey(),
145
+ firstName: text('firstName'), // SQL: firstName, not first_name
146
+ });
147
+ ```
148
+
149
+ Correct:
150
+
151
+ ```ts
152
+ import * as d from 'flint-orm/table';
153
+
154
+ const users = d.snakeCase.table('users', {
155
+ id: d.integer().autoIncrement().primaryKey(),
156
+ firstName: d.text().notNull(), // SQL: first_name
157
+ });
158
+ ```
159
+
160
+ `table()` uses the object key as the SQL column name — camelCase keys produce camelCase SQL columns. Use `snakeCase.table()` for automatic conversion.
161
+
162
+ Source: maintainer interview
163
+
164
+ ### HIGH Using .__internal in application code
165
+
166
+ Wrong:
167
+
168
+ ```ts
169
+ const typeName = users.name.__internal._type; // DON'T DO THIS
170
+ ```
171
+
172
+ Correct:
173
+
174
+ ```ts
175
+ import type { InferRow } from 'flint-orm/table';
176
+ type User = InferRow<typeof users>;
177
+ ```
178
+
179
+ `.__internal` is for ORM internals — its shape can change between versions without notice.
180
+
181
+ Source: maintainer interview
182
+
183
+ ### MEDIUM Calling .references() before table() stamps it
184
+
185
+ Wrong:
186
+
187
+ ```ts
188
+ import { text } from 'flint-orm/table';
189
+ const col = text('userId').references(users.id); // ERROR: users not yet defined
190
+ ```
191
+
192
+ Correct:
193
+
194
+ ```ts
195
+ import * as d from 'flint-orm/table';
196
+
197
+ const users = d.snakeCase.table('users', {
198
+ id: d.integer().autoIncrement().primaryKey(),
199
+ });
200
+
201
+ const posts = d.snakeCase.table('posts', {
202
+ userId: d.integer().notNull().references(users.id),
203
+ });
204
+ ```
205
+
206
+ TypeScript prevents this — `.references()` requires a column that has been attached to a table via `table()`.
207
+
208
+ Source: src/schema/columns.ts
209
+
210
+ ## References
211
+
212
+ - [Full column modifiers reference](references/column-modifiers.md)
@@ -0,0 +1,176 @@
1
+ # Column Modifiers Reference
2
+
3
+ Detailed reference for all column modifiers in flint-orm.
4
+
5
+ ## .primaryKey()
6
+
7
+ Mark a column as the primary key.
8
+
9
+ ```ts
10
+ const id = d.integer().primaryKey();
11
+ // SQL: id INTEGER PRIMARY KEY
12
+ ```
13
+
14
+ **Notes:**
15
+ - Only one column per table can be primary key
16
+ - Primary keys are implicitly NOT NULL
17
+
18
+ ## .notNull()
19
+
20
+ Disallow NULL values.
21
+
22
+ ```ts
23
+ const name = d.text().notNull();
24
+ // SQL: name TEXT NOT NULL
25
+ ```
26
+
27
+ **Notes:**
28
+ - Adding NOT NULL to an existing column requires a default value (triggers table rebuild otherwise)
29
+ - Removing NOT NULL is an unsafe migration (triggers table rebuild)
30
+
31
+ ## .unique()
32
+
33
+ Add a unique constraint.
34
+
35
+ ```ts
36
+ const email = d.text().unique();
37
+ // SQL: email TEXT UNIQUE
38
+ ```
39
+
40
+ **Notes:**
41
+ - Adding or removing UNIQUE triggers a table rebuild
42
+
43
+ ## .default(value)
44
+
45
+ Set a static default value.
46
+
47
+ ```ts
48
+ const active = d.boolean().default(true);
49
+ // SQL: active INTEGER DEFAULT 1
50
+
51
+ const status = d.text().default('active');
52
+ // SQL: status TEXT DEFAULT 'active'
53
+ ```
54
+
55
+ **Notes:**
56
+ - The value must match the column's TypeScript type
57
+ - Removing a default is an unsafe migration (SQLite has no DROP DEFAULT syntax)
58
+
59
+ ## .defaultFn(fn)
60
+
61
+ Set a dynamic default function called on insert when the value is omitted.
62
+
63
+ ```ts
64
+ const id = d.text().defaultFn(() => crypto.randomUUID());
65
+ ```
66
+
67
+ **Notes:**
68
+ - The function is called at insert time, not at schema definition time
69
+ - Useful for UUIDs, timestamps, or computed values
70
+
71
+ ## .references(target)
72
+
73
+ Define a foreign key reference to another table's column.
74
+
75
+ ```ts
76
+ const users = d.snakeCase.table('users', {
77
+ id: d.integer().autoIncrement().primaryKey(),
78
+ });
79
+
80
+ const posts = d.snakeCase.table('posts', {
81
+ userId: d.integer().notNull().references(users.id),
82
+ });
83
+ // SQL: userId INTEGER NOT NULL REFERENCES users(id)
84
+ ```
85
+
86
+ **Notes:**
87
+ - The target column must be from a table defined with `table()`
88
+ - TypeScript prevents calling `.references()` before the target table exists
89
+ - Changing the FK target triggers a table rebuild
90
+
91
+ ## .onDelete(action)
92
+
93
+ Set the ON DELETE action for a foreign key. Requires `.references()` to be called first.
94
+
95
+ ```ts
96
+ const posts = d.snakeCase.table('posts', {
97
+ userId: d.integer().notNull().references(users.id).onDelete('cascade'),
98
+ });
99
+ // SQL: userId INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE
100
+ ```
101
+
102
+ **Valid actions:** `'cascade'`, `'set null'`, `'set default'`, `'restrict'`, `'no action'`
103
+
104
+ **Notes:**
105
+ - Changing ON DELETE action triggers a table rebuild
106
+
107
+ ## .onUpdate(action)
108
+
109
+ Set the ON UPDATE action for a foreign key. Requires `.references()` to be called first.
110
+
111
+ ```ts
112
+ const posts = d.snakeCase.table('posts', {
113
+ userId: d.integer().notNull().references(users.id).onUpdate('cascade'),
114
+ });
115
+ // SQL: userId INTEGER NOT NULL REFERENCES users(id) ON UPDATE CASCADE
116
+ ```
117
+
118
+ **Valid actions:** `'cascade'`, `'set null'`, `'set default'`, `'restrict'`, `'no action'`
119
+
120
+ **Notes:**
121
+ - Changing ON UPDATE action triggers a table rebuild
122
+
123
+ ## .autoIncrement() (integer only)
124
+
125
+ Mark an integer column as auto-increment (SQLite ROWID alias).
126
+
127
+ ```ts
128
+ const id = d.integer().primaryKey().autoIncrement();
129
+ // SQL: id INTEGER PRIMARY KEY AUTOINCREMENT
130
+ ```
131
+
132
+ **Notes:**
133
+ - Only works with integer columns
134
+ - Changing autoincrement triggers a table rebuild
135
+
136
+ ## .defaultNow() (date only)
137
+
138
+ Use `Date.now()` as the default when the value is omitted during insert.
139
+
140
+ ```ts
141
+ const createdAt = d.date().defaultNow();
142
+ // SQL: created_at INTEGER DEFAULT (unixepoch * 1000)
143
+ ```
144
+
145
+ **Notes:**
146
+ - Only works with date columns
147
+ - Makes the column non-nullable in query results (guaranteed to have a value)
148
+
149
+ ## .onUpdateTimestamp() (date only)
150
+
151
+ Always set to `Date.now()` on update, regardless of the provided value.
152
+
153
+ ```ts
154
+ const updatedAt = d.date().onUpdateTimestamp();
155
+ ```
156
+
157
+ **Notes:**
158
+ - Only works with date columns
159
+ - The value is set automatically on every UPDATE operation
160
+ - Not reflected in SQL schema — handled by the ORM at runtime
161
+
162
+ ## Migration Safety
163
+
164
+ | Change | Safe? | Notes |
165
+ |--------|-------|-------|
166
+ | Adding NOT NULL with default | ✅ | Safe |
167
+ | Adding NOT NULL without default | ❌ | Triggers rebuild |
168
+ | Removing NOT NULL | ❌ | Triggers rebuild |
169
+ | Adding UNIQUE | ❌ | Triggers rebuild |
170
+ | Removing UNIQUE | ❌ | Triggers rebuild |
171
+ | Adding/changing DEFAULT | ✅ | Safe |
172
+ | Removing DEFAULT | ❌ | SQLite has no DROP DEFAULT |
173
+ | Adding/changing FK | ❌ | Triggers rebuild |
174
+ | Removing FK | ❌ | Triggers rebuild |
175
+ | Changing FK actions | ❌ | Triggers rebuild |
176
+ | Changing autoincrement | ❌ | Triggers rebuild |