rado 1.2.1 → 1.3.0-preview.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 +163 -257
- package/dist/compat.d.ts +3 -0
- package/dist/core/Builder.d.ts +16 -9
- package/dist/core/Builder.js +52 -4
- package/dist/core/Column.d.ts +25 -21
- package/dist/core/Column.js +39 -23
- package/dist/core/Constraint.d.ts +2 -1
- package/dist/core/Constraint.js +2 -1
- package/dist/core/Database.d.ts +5 -4
- package/dist/core/Database.js +14 -13
- package/dist/core/Emitter.d.ts +2 -2
- package/dist/core/Index.d.ts +1 -1
- package/dist/core/Internal.d.ts +19 -2
- package/dist/core/Internal.js +29 -11
- package/dist/core/Param.js +2 -0
- package/dist/core/Queries.d.ts +1 -0
- package/dist/core/Queries.js +11 -1
- package/dist/core/Resolver.js +2 -2
- package/dist/core/Schema.d.ts +1 -3
- package/dist/core/Selection.d.ts +9 -4
- package/dist/core/Selection.js +94 -5
- package/dist/core/Sql.d.ts +4 -3
- package/dist/core/Sql.js +8 -2
- package/dist/core/Table.d.ts +18 -11
- package/dist/core/Table.js +45 -25
- package/dist/core/View.d.ts +34 -0
- package/dist/core/View.js +153 -0
- package/dist/core/Virtual.d.ts +7 -2
- package/dist/core/Virtual.js +14 -10
- package/dist/core/expr/Conditions.d.ts +7 -7
- package/dist/core/expr/Include.d.ts +4 -4
- package/dist/core/expr/Include.js +2 -2
- package/dist/core/expr/Input.d.ts +1 -1
- package/dist/core/expr/Input.js +5 -1
- package/dist/core/query/CTE.d.ts +3 -5
- package/dist/core/query/CTE.js +1 -13
- package/dist/core/query/Delete.d.ts +10 -10
- package/dist/core/query/Delete.js +2 -2
- package/dist/core/query/Insert.d.ts +6 -5
- package/dist/core/query/Insert.js +22 -9
- package/dist/core/query/Query.d.ts +26 -11
- package/dist/core/query/Select.d.ts +41 -8
- package/dist/core/query/Select.js +235 -25
- package/dist/core/query/Shared.d.ts +2 -1
- package/dist/core/query/Shared.js +8 -5
- package/dist/core/query/Update.d.ts +9 -9
- package/dist/core/query/Update.js +24 -12
- package/dist/driver/better-sqlite3.js +4 -0
- package/dist/driver/bun-sqlite.js +3 -0
- package/dist/driver/d1.js +3 -0
- package/dist/driver/libsql.js +4 -0
- package/dist/driver/mysql2.js +5 -0
- package/dist/driver/pg.d.ts +17 -1
- package/dist/driver/pg.js +10 -1
- package/dist/driver/pglite.js +21 -6
- package/dist/driver/sql.js.js +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mysql/columns.d.ts +30 -30
- package/dist/mysql/columns.js +25 -24
- package/dist/mysql/dialect.js +6 -2
- package/dist/mysql/diff.js +1 -1
- package/dist/mysql.d.ts +1 -0
- package/dist/mysql.js +2 -0
- package/dist/postgres/columns.d.ts +103 -33
- package/dist/postgres/columns.js +317 -35
- package/dist/postgres/diff.js +3 -3
- package/dist/postgres/enum.d.ts +17 -0
- package/dist/postgres/enum.js +64 -0
- package/dist/postgres/schema.d.ts +19 -0
- package/dist/postgres/schema.js +47 -0
- package/dist/postgres.d.ts +3 -1
- package/dist/postgres.js +9 -2
- package/dist/sqlite/columns.d.ts +29 -14
- package/dist/sqlite/columns.js +41 -9
- package/dist/sqlite/dialect.js +3 -1
- package/dist/sqlite/diff.js +1 -1
- package/dist/sqlite/functions.d.ts +1 -1
- package/dist/sqlite/functions.js +1 -1
- package/dist/sqlite.d.ts +1 -0
- package/dist/sqlite.js +3 -6
- package/dist/universal/columns.d.ts +8 -8
- package/dist/universal/columns.js +10 -9
- package/dist/universal/functions.d.ts +1 -1
- package/dist/universal/functions.js +1 -1
- package/package.json +57 -38
package/README.md
CHANGED
|
@@ -3,332 +3,238 @@
|
|
|
3
3
|
|
|
4
4
|
# rado
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
A fully typed, lightweight TypeScript query builder for SQLite, PostgreSQL and
|
|
7
|
+
MySQL.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import {eq} from 'rado'
|
|
11
|
+
|
|
12
|
+
const greatPosts = await db.select().from(Post).where(eq(Post.rating, 5))
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
If you've used [Drizzle ORM](https://orm.drizzle.team), the code above looks
|
|
16
|
+
familiar. That's intentional. Rado deliberately aligns its query building
|
|
17
|
+
syntax with Drizzle, while making a few different choices under the hood:
|
|
18
|
+
simpler types, immutable queries, a much smaller footprint and queries that can
|
|
19
|
+
run on any of the supported databases. Read about the project's history in
|
|
20
|
+
[Taking the Drizzle challenge](https://ben.mk/notes/taking-the-drizzle-challenge).
|
|
7
21
|
|
|
8
22
|
## Features
|
|
9
23
|
|
|
10
|
-
- Fully typed
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
|
|
24
|
+
- **Fully typed**: schema definitions drive query and result types, with
|
|
25
|
+
plain, readable TypeScript types
|
|
26
|
+
- **Immutable queries**: every method returns a new query, so you can branch
|
|
27
|
+
and reuse query fragments without surprises
|
|
28
|
+
- **Universal queries**: write a query once, run it on SQLite, PostgreSQL or
|
|
29
|
+
MySQL, chosen at runtime
|
|
30
|
+
- **First-class JSON columns**: select and filter on typed JSON fields with
|
|
31
|
+
plain property access
|
|
32
|
+
- **`include` instead of an ORM**: fetch related rows as nested arrays or
|
|
33
|
+
objects inside the query builder, no relations setup required
|
|
34
|
+
- **Auto-migrations**: diff your schema against the database and update it
|
|
35
|
+
anywhere, including the browser
|
|
36
|
+
- **Zero dependencies, small bundle**: `rado` + the PostgreSQL utilities
|
|
37
|
+
bundle to roughly 8.5 kB gzipped
|
|
38
|
+
- **No code generation step**: your schema is just TypeScript
|
|
16
39
|
|
|
17
40
|
## Installation
|
|
18
41
|
|
|
19
|
-
|
|
42
|
+
```sh
|
|
43
|
+
npm install rado
|
|
44
|
+
```
|
|
20
45
|
|
|
21
|
-
|
|
46
|
+
Rado is also published on JSR as [@rado/rado](https://jsr.io/@rado/rado) for
|
|
47
|
+
Deno users.
|
|
22
48
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
49
|
+
## Quick start
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import {eq} from 'rado'
|
|
53
|
+
import {integer, pgTable, text} from 'rado/postgres'
|
|
26
54
|
import {connect} from 'rado/driver/pg'
|
|
27
55
|
import {Pool} from 'pg'
|
|
28
56
|
|
|
29
|
-
// Define
|
|
30
|
-
const User =
|
|
31
|
-
id: integer().primaryKey(),
|
|
57
|
+
// Define a schema
|
|
58
|
+
const User = pgTable('user', {
|
|
59
|
+
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
|
32
60
|
name: text().notNull(),
|
|
33
61
|
email: text().unique()
|
|
34
62
|
})
|
|
35
63
|
|
|
36
|
-
// Connect
|
|
37
|
-
const db = connect(new Pool({
|
|
38
|
-
host: 'localhost',
|
|
39
|
-
database: 'my_database'
|
|
40
|
-
}))
|
|
64
|
+
// Connect a database
|
|
65
|
+
const db = connect(new Pool({connectionString: process.env.DATABASE_URL}))
|
|
41
66
|
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
67
|
+
// Create the table and start querying
|
|
68
|
+
await db.create(User)
|
|
69
|
+
|
|
70
|
+
await db.insert(User).values({name: 'Ada', email: 'ada@example.com'})
|
|
46
71
|
|
|
47
|
-
|
|
72
|
+
const users = await db.select().from(User).where(eq(User.name, 'Ada'))
|
|
73
|
+
```
|
|
48
74
|
|
|
49
|
-
|
|
75
|
+
## Documentation
|
|
50
76
|
|
|
51
|
-
|
|
52
|
-
| -------------------------- | ------------------------------ |
|
|
53
|
-
| pg | rado/driver/pg |
|
|
54
|
-
| @electric-sql/pglite | rado/driver/pglite |
|
|
55
|
-
| @neondatabase/serverless | rado/driver/pg |
|
|
56
|
-
| @vercel/postgres | rado/driver/pg |
|
|
77
|
+
The full documentation lives in [docs](docs/README.md). A map of the territory:
|
|
57
78
|
|
|
58
|
-
|
|
|
59
|
-
|
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
|
|
|
64
|
-
|
|
|
79
|
+
| Section | What's inside |
|
|
80
|
+
| ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
81
|
+
| [Getting started](docs/getting-started.md) | Install, connect, define a schema, run your first queries |
|
|
82
|
+
| [Drivers](docs/drivers.md) | Every supported driver and how to connect it |
|
|
83
|
+
| [Tables](docs/schema/tables.md) | Defining tables, column modifiers, defaults, references |
|
|
84
|
+
| [Column types](docs/schema/columns-sqlite.md) | Per dialect: [SQLite](docs/schema/columns-sqlite.md), [PostgreSQL](docs/schema/columns-postgres.md), [MySQL](docs/schema/columns-mysql.md), [universal](docs/schema/columns-universal.md) |
|
|
85
|
+
| [Indexes & constraints](docs/schema/indexes-and-constraints.md) | Primary keys, unique constraints, foreign keys, indexes |
|
|
86
|
+
| [Views](docs/schema/views.md) | Views and PostgreSQL materialized views |
|
|
87
|
+
| [Schemas & enums](docs/schema/postgres-schemas-and-enums.md) | `pgSchema` and `pgEnum` |
|
|
88
|
+
| [Custom column types](docs/schema/custom-types.md) | Mapping your own types to and from the database |
|
|
89
|
+
| [Select](docs/queries/select.md) | Selections, filtering, ordering, grouping, pagination, locking |
|
|
90
|
+
| [Joins](docs/queries/joins.md) | Inner/left/right/full/cross joins and lateral variants |
|
|
91
|
+
| [Insert](docs/queries/insert.md) | Values, bulk inserts, upserts, insert-from-select |
|
|
92
|
+
| [Update](docs/queries/update.md) / [Delete](docs/queries/delete.md) | Modifying and removing rows, `returning` |
|
|
93
|
+
| [Filter operators](docs/queries/operators.md) | `eq`, `and`, `or`, `inArray`, `like`, `between` and friends |
|
|
94
|
+
| [Aggregates](docs/queries/aggregates.md) | `count`, `sum`, `avg`, `min`, `max` and grouping |
|
|
95
|
+
| [Include](docs/queries/include.md) | Nested results without an ORM |
|
|
96
|
+
| [JSON](docs/queries/json.md) | Typed JSON columns, selecting and filtering nested fields |
|
|
97
|
+
| [Set operations](docs/queries/set-operations.md) | `union`, `intersect`, `except` |
|
|
98
|
+
| [Subqueries & CTEs](docs/queries/subqueries-and-ctes.md) | `.as()`, `$with`, recursive CTEs |
|
|
99
|
+
| [The sql tag](docs/queries/sql.md) | Raw SQL escape hatches, safely |
|
|
100
|
+
| [Transactions & batch](docs/runtime/transactions-and-batch.md) | Atomic operations, rollbacks, batching |
|
|
101
|
+
| [Prepared statements](docs/runtime/prepared-statements.md) | Placeholders, `prepare`, inspecting generated SQL |
|
|
102
|
+
| [Migrations](docs/runtime/migrations.md) | `db.create`, `db.migrate` and how diffing works |
|
|
103
|
+
| [Universal queries](docs/runtime/universal-queries.md) | One query, three databases |
|
|
104
|
+
| [Coming from Drizzle](docs/drizzle.md) | Side-by-side comparison and migration notes |
|
|
65
105
|
|
|
66
|
-
|
|
67
|
-
| -------------------------- | ------------------------------ |
|
|
68
|
-
| mysql2 | rado/driver/mysql2 |
|
|
106
|
+
## Supported drivers
|
|
69
107
|
|
|
70
|
-
Pass
|
|
108
|
+
Pass a database client instance to the matching `connect` function:
|
|
71
109
|
|
|
72
110
|
```ts
|
|
73
111
|
import Database from 'better-sqlite3'
|
|
74
112
|
import {connect} from 'rado/driver/better-sqlite3'
|
|
75
113
|
|
|
76
|
-
const db = connect(new Database('
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## Querying
|
|
80
|
-
|
|
81
|
-
### Select
|
|
82
|
-
|
|
83
|
-
Select operations allow you to retrieve data from your database.
|
|
84
|
-
|
|
85
|
-
```typescript
|
|
86
|
-
// Basic select
|
|
87
|
-
const allUsers = await db.select().from(User)
|
|
88
|
-
|
|
89
|
-
// Select specific columns
|
|
90
|
-
const userNames = await db.select({id: User.id, name: User.name}).from(User)
|
|
114
|
+
const db = connect(new Database('app.db'))
|
|
91
115
|
```
|
|
92
116
|
|
|
93
|
-
|
|
117
|
+
| Database | Package | Import | Sync/async |
|
|
118
|
+
| ---------- | ---------------------------------------------------------------------------------- | ---------------------------- | ---------- |
|
|
119
|
+
| PostgreSQL | [pg](https://www.npmjs.com/package/pg) | `rado/driver/pg` | async |
|
|
120
|
+
| PostgreSQL | [@electric-sql/pglite](https://www.npmjs.com/package/@electric-sql/pglite) | `rado/driver/pglite` | async |
|
|
121
|
+
| PostgreSQL | [@neondatabase/serverless](https://www.npmjs.com/package/@neondatabase/serverless) | `rado/driver/pg` | async |
|
|
122
|
+
| PostgreSQL | [@vercel/postgres](https://www.npmjs.com/package/@vercel/postgres) | `rado/driver/pg` | async |
|
|
123
|
+
| SQLite | [better-sqlite3](https://www.npmjs.com/package/better-sqlite3) | `rado/driver/better-sqlite3` | sync |
|
|
124
|
+
| SQLite | `bun:sqlite` | `rado/driver/bun-sqlite` | sync |
|
|
125
|
+
| SQLite | [sql.js](https://www.npmjs.com/package/sql.js) | `rado/driver/sql.js` | sync |
|
|
126
|
+
| SQLite | [@libsql/client](https://www.npmjs.com/package/@libsql/client) | `rado/driver/libsql` | async |
|
|
127
|
+
| SQLite | Cloudflare D1 | `rado/driver/d1` | async |
|
|
128
|
+
| MySQL | [mysql2](https://www.npmjs.com/package/mysql2) | `rado/driver/mysql2` | async |
|
|
94
129
|
|
|
95
|
-
|
|
130
|
+
Synchronous drivers give you a fully synchronous database. No `await`
|
|
131
|
+
required, although awaiting queries still works. See
|
|
132
|
+
[Drivers](docs/drivers.md) for details.
|
|
96
133
|
|
|
97
|
-
|
|
98
|
-
import {eq, and, or, gt, isNull} from 'rado'
|
|
134
|
+
## A taste of the API
|
|
99
135
|
|
|
100
|
-
|
|
101
|
-
const john = await db.select().from(User).where(eq(User.name, 'John'))
|
|
136
|
+
### Immutable queries
|
|
102
137
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
.where(
|
|
106
|
-
gt(User.age, 18),
|
|
107
|
-
or(
|
|
108
|
-
eq(User.name, 'Alice'),
|
|
109
|
-
isNull(User.email)
|
|
110
|
-
)
|
|
111
|
-
)
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
### Joins
|
|
115
|
-
|
|
116
|
-
Joins allow you to combine data from multiple tables.
|
|
138
|
+
Queries are values. Branch them, reuse them, store them in variables. Nothing
|
|
139
|
+
mutates:
|
|
117
140
|
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
userName: User.name,
|
|
121
|
-
postTitle: Post.title,
|
|
122
|
-
})
|
|
123
|
-
.from(User)
|
|
124
|
-
.leftJoin(Post, eq(User.id, Post.authorId))
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### Ordering and Grouping
|
|
128
|
-
|
|
129
|
-
You can order and group your query results:
|
|
130
|
-
|
|
131
|
-
```typescript
|
|
132
|
-
import {desc, asc, count} from 'rado'
|
|
141
|
+
```ts
|
|
142
|
+
import {count, gt} from 'rado'
|
|
133
143
|
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
.orderBy(asc(User.name))
|
|
144
|
+
const allUsers = db.select(count()).from(User)
|
|
145
|
+
const filtered = allUsers.where(gt(User.id, 1))
|
|
137
146
|
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
postCount: count(Post.id)
|
|
141
|
-
})
|
|
142
|
-
.from(User)
|
|
143
|
-
.leftJoin(Post, eq(User.id, Post.authorId))
|
|
144
|
-
.groupBy(User.name)
|
|
147
|
+
const [total] = await allUsers // unaffected by the where above
|
|
148
|
+
const [matching] = await filtered
|
|
145
149
|
```
|
|
146
150
|
|
|
147
|
-
###
|
|
151
|
+
### Nested results with `include`
|
|
148
152
|
|
|
149
|
-
|
|
153
|
+
No relations file, no separate query API. Declare the relationship inline:
|
|
150
154
|
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
const page = 2
|
|
155
|
+
```ts
|
|
156
|
+
import {eq, include} from 'rado'
|
|
154
157
|
|
|
155
|
-
const
|
|
158
|
+
const usersWithPosts = await db
|
|
159
|
+
.select({
|
|
160
|
+
...User,
|
|
161
|
+
posts: include(db.select().from(Post).where(eq(Post.authorId, User.id)))
|
|
162
|
+
})
|
|
156
163
|
.from(User)
|
|
157
|
-
.limit(pageSize)
|
|
158
|
-
.offset((page - 1) * pageSize)
|
|
159
164
|
```
|
|
160
165
|
|
|
161
|
-
|
|
166
|
+
Each row comes back with a typed `posts` array. Use `include.one` for a single
|
|
167
|
+
related row. More in [Include](docs/queries/include.md).
|
|
162
168
|
|
|
163
|
-
|
|
169
|
+
### Typed JSON columns
|
|
164
170
|
|
|
165
|
-
```
|
|
166
|
-
import {
|
|
171
|
+
```ts
|
|
172
|
+
import {eq} from 'rado'
|
|
173
|
+
import {jsonb, pgTable, serial, text} from 'rado/postgres'
|
|
167
174
|
|
|
168
|
-
const User = pgTable(
|
|
175
|
+
const User = pgTable('user', {
|
|
169
176
|
id: serial().primaryKey(),
|
|
170
177
|
name: text(),
|
|
171
|
-
|
|
178
|
+
settings: jsonb<{theme: 'light' | 'dark'; notifications: boolean}>()
|
|
172
179
|
})
|
|
173
180
|
|
|
174
|
-
const
|
|
181
|
+
const darkSide = await db
|
|
182
|
+
.select(User.name)
|
|
175
183
|
.from(User)
|
|
176
|
-
.where(eq(User.
|
|
184
|
+
.where(eq(User.settings.theme, 'dark'))
|
|
177
185
|
```
|
|
178
186
|
|
|
179
|
-
|
|
187
|
+
That `User.settings.theme` is a real, typed expression that compiles to the
|
|
188
|
+
correct JSON access syntax for your database. More in
|
|
189
|
+
[JSON](docs/queries/json.md).
|
|
180
190
|
|
|
181
|
-
|
|
182
|
-
const subquery = db.select({authorId: Post.authorId})
|
|
183
|
-
.from(Post)
|
|
184
|
-
.groupBy(Post.authorId)
|
|
185
|
-
.having(gt(count(Post.id), 5))
|
|
186
|
-
.as('authorIds')
|
|
191
|
+
### Universal queries
|
|
187
192
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
.where(inArray(User.id, subquery))
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
### Include
|
|
194
|
-
|
|
195
|
-
Aggregate rows using the `include` function:
|
|
196
|
-
|
|
197
|
-
```typescript
|
|
198
|
-
import {include} from 'rado'
|
|
199
|
-
|
|
200
|
-
const usersWithPosts = await db.select({
|
|
201
|
-
...User,
|
|
202
|
-
posts: include(
|
|
203
|
-
db.select().from(Post).where(eq(Post.authorId, User.id))
|
|
204
|
-
)
|
|
205
|
-
}).from(User)
|
|
206
|
-
|
|
207
|
-
// Use include.one for a single related record
|
|
208
|
-
const usersWithLatestPost = await db.select({
|
|
209
|
-
...User,
|
|
210
|
-
latestPost: include.one(
|
|
211
|
-
db.select()
|
|
212
|
-
.from(Post)
|
|
213
|
-
.where(eq(Post.authorId, User.id))
|
|
214
|
-
.orderBy(desc(Post.createdAt))
|
|
215
|
-
.limit(1)
|
|
216
|
-
)
|
|
217
|
-
}).from(User)
|
|
218
|
-
```
|
|
193
|
+
Define your schema with `rado/universal` column types and pick the database at
|
|
194
|
+
runtime:
|
|
219
195
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
```typescript
|
|
225
|
-
import {sql} from 'rado'
|
|
226
|
-
|
|
227
|
-
const minAge = 18
|
|
228
|
-
const adultUsers = await db.select()
|
|
229
|
-
.from(User)
|
|
230
|
-
.where(sql`${User.age} >= ${minAge}`)
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
## Modifying Data
|
|
234
|
-
|
|
235
|
-
### Insert
|
|
236
|
-
|
|
237
|
-
Insert operations allow you to add new records to your database:
|
|
238
|
-
|
|
239
|
-
```typescript
|
|
240
|
-
// Single insert
|
|
241
|
-
const newUser = await db.insert(User)
|
|
242
|
-
.values({name: 'Alice', email: 'alice@example.com'})
|
|
243
|
-
.returning()
|
|
244
|
-
|
|
245
|
-
// Bulk insert
|
|
246
|
-
const newUsers = await db.insert(User)
|
|
247
|
-
.values([
|
|
248
|
-
{name: 'Bob', email: 'bob@example.com'},
|
|
249
|
-
{name: 'Charlie', email: 'charlie@example.com'},
|
|
250
|
-
])
|
|
251
|
-
.returning()
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
### Update
|
|
255
|
-
|
|
256
|
-
Update operations modify existing records:
|
|
257
|
-
|
|
258
|
-
```typescript
|
|
259
|
-
const updatedCount = await db.update(User)
|
|
260
|
-
.set({name: 'Johnny'})
|
|
261
|
-
.where(eq(User.name, 'John'))
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
### Delete
|
|
196
|
+
```ts
|
|
197
|
+
import {table} from 'rado'
|
|
198
|
+
import {id, text} from 'rado/universal'
|
|
265
199
|
|
|
266
|
-
|
|
200
|
+
const User = table('user', {
|
|
201
|
+
id: id(), // auto-incrementing primary key on every dialect
|
|
202
|
+
name: text()
|
|
203
|
+
})
|
|
267
204
|
|
|
268
|
-
|
|
269
|
-
await db.
|
|
205
|
+
const db = useSqlite ? sqliteDb : postgresDb
|
|
206
|
+
const names = await db.select(User.name).from(User)
|
|
270
207
|
```
|
|
271
208
|
|
|
272
|
-
|
|
209
|
+
More in [Universal queries](docs/runtime/universal-queries.md).
|
|
273
210
|
|
|
274
|
-
|
|
211
|
+
### Auto-migrations
|
|
275
212
|
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
.values({ name: 'Alice', email: 'alice@example.com' })
|
|
280
|
-
.returning()
|
|
281
|
-
const post = await tx.insert(Post)
|
|
282
|
-
.values({ title: 'My First Post', authorId: user.id })
|
|
283
|
-
.returning()
|
|
284
|
-
return {user, post}
|
|
285
|
-
})
|
|
213
|
+
```ts
|
|
214
|
+
// Compares the defined schema to the actual database and applies the diff
|
|
215
|
+
await db.migrate(User, Post)
|
|
286
216
|
```
|
|
287
217
|
|
|
288
|
-
|
|
218
|
+
Works in Node.js, Bun, Deno and the browser. It is powerful and a little
|
|
219
|
+
direct, so read [Migrations](docs/runtime/migrations.md) before pointing it at
|
|
220
|
+
production.
|
|
289
221
|
|
|
290
|
-
|
|
291
|
-
engines, whether they run synchronously or asynchronously. This is useful for
|
|
292
|
-
writing database-agnostic code.
|
|
222
|
+
## Coming from Drizzle?
|
|
293
223
|
|
|
294
|
-
|
|
295
|
-
import {table} from 'rado'
|
|
296
|
-
import {id, text} from 'rado/universal'
|
|
224
|
+
Most queries port by changing imports:
|
|
297
225
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const db = process.env.SQLITE ? sqliteDb : postgresDb
|
|
304
|
-
|
|
305
|
-
const userNames = await db.select(User.name).from(User)
|
|
226
|
+
```ts
|
|
227
|
+
import {sql, eq, and, or} from 'rado' // was 'drizzle-orm'
|
|
228
|
+
import {pgTable, integer, text} from 'rado/postgres' // was 'drizzle-orm/pg-core'
|
|
229
|
+
import {sqliteTable} from 'rado/sqlite' // was 'drizzle-orm/sqlite-core'
|
|
230
|
+
import {mysqlTable} from 'rado/mysql' // was 'drizzle-orm/mysql-core'
|
|
306
231
|
```
|
|
307
232
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
233
|
+
Rado also exposes compatibility helpers such as `InferSelectModel`,
|
|
234
|
+
`InferInsertModel`, `getTableColumns` and `TransactionRollbackError`. See
|
|
235
|
+
[Coming from Drizzle](docs/drizzle.md) for a full side-by-side comparison of
|
|
236
|
+
what's the same, what's better and what's missing.
|
|
312
237
|
|
|
313
|
-
|
|
314
|
-
import {Column, column, table, sql} from 'rado'
|
|
238
|
+
## License
|
|
315
239
|
|
|
316
|
-
|
|
317
|
-
return column({
|
|
318
|
-
name,
|
|
319
|
-
type: sql`tinyint(1)`,
|
|
320
|
-
mapFromDriverValue(value: number): boolean {
|
|
321
|
-
return value === 1
|
|
322
|
-
},
|
|
323
|
-
mapToDriverValue(value: boolean): number {
|
|
324
|
-
return value ? 1 : 0
|
|
325
|
-
}
|
|
326
|
-
})
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Usage
|
|
330
|
-
const User = table('user', {
|
|
331
|
-
// ...
|
|
332
|
-
isActive: bool()
|
|
333
|
-
})
|
|
334
|
-
```
|
|
240
|
+
MIT
|
package/dist/compat.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ import type { Sql } from './core/Sql.js';
|
|
|
3
3
|
import type { Table, TableDefinition, TableFields } from './core/Table.js';
|
|
4
4
|
export type { InsertRow as InferInsertModel, SelectRow as InferSelectModel } from './core/Table.js';
|
|
5
5
|
export type SQL<T = unknown> = Sql<T>;
|
|
6
|
+
export declare namespace SQL {
|
|
7
|
+
type Aliased<T = unknown> = Sql<T>;
|
|
8
|
+
}
|
|
6
9
|
export type SQLWrapper<T = unknown> = HasSql<T>;
|
|
7
10
|
export declare function getTableColumns<Definition extends TableDefinition>(table: Table<Definition>): TableFields<Definition>;
|
|
8
11
|
export { Rollback as TransactionRollbackError } from './core/Database.js';
|
package/dist/core/Builder.d.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { type HasSql, internalData } from './Internal.js';
|
|
2
2
|
import type { IsPostgres, QueryMeta } from './MetaData.js';
|
|
3
3
|
import type { QueryData, SingleQuery } from './Queries.js';
|
|
4
|
-
import type {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import { DeleteFrom } from './query/Delete.js';
|
|
8
|
-
import { InsertInto } from './query/Insert.js';
|
|
4
|
+
import type { CTE } from './query/CTE.js';
|
|
5
|
+
import { Delete, DeleteFrom } from './query/Delete.js';
|
|
6
|
+
import { Insert, InsertInto } from './query/Insert.js';
|
|
9
7
|
import type { DeleteQuery, FromGuard, FromQuery, FromRow, InsertQuery, QueryBase, SelectionQuery, UpdateQuery } from './query/Query.js';
|
|
10
8
|
import type { UnionBase, WithSelection, WithoutSelection } from './query/Select.js';
|
|
11
|
-
import { UpdateTable } from './query/Update.js';
|
|
9
|
+
import { Update, UpdateTable } from './query/Update.js';
|
|
10
|
+
import { type SelectionInput, type SelectionRow } from './Selection.js';
|
|
11
|
+
import { Sql } from './Sql.js';
|
|
12
|
+
import type { Table, TableDefinition } from './Table.js';
|
|
12
13
|
declare class BuilderBase<Meta extends QueryMeta> {
|
|
13
14
|
readonly [internalData]: QueryData<Meta> & QueryBase;
|
|
14
15
|
constructor(data?: QueryData<Meta> & QueryBase);
|
|
@@ -21,15 +22,21 @@ declare class BuilderBase<Meta extends QueryMeta> {
|
|
|
21
22
|
select<Input extends SelectionInput>(select: Input): WithSelection<Input, Meta>;
|
|
22
23
|
selectDistinct(): WithoutSelection<Meta>;
|
|
23
24
|
selectDistinct<Input extends SelectionInput>(selection: Input): WithSelection<Input, Meta>;
|
|
24
|
-
selectDistinctOn(this:
|
|
25
|
-
selectDistinctOn<Input extends SelectionInput>(this:
|
|
25
|
+
selectDistinctOn(this: BuilderBase<IsPostgres>, columns: Array<HasSql>): WithoutSelection<Meta>;
|
|
26
|
+
selectDistinctOn<Input extends SelectionInput>(this: BuilderBase<IsPostgres>, columns: Array<HasSql>, selection: Input): WithSelection<Input, Meta>;
|
|
26
27
|
update<Definition extends TableDefinition>(table: Table<Definition>): UpdateTable<Definition, Meta>;
|
|
27
28
|
insert<Definition extends TableDefinition>(into: Table<Definition>): InsertInto<Definition, Meta>;
|
|
28
29
|
delete<Definition extends TableDefinition>(from: Table<Definition>): DeleteFrom<Definition, Meta>;
|
|
29
30
|
}
|
|
30
31
|
export declare class Builder<Meta extends QueryMeta> extends BuilderBase<Meta> {
|
|
32
|
+
$with<Input extends SelectionInput>(cteName: string, columns: Input): {
|
|
33
|
+
as(query: Sql): CTE<Input>;
|
|
34
|
+
};
|
|
31
35
|
$with(cteName: string): {
|
|
32
|
-
as<Input
|
|
36
|
+
as<Input>(query: UnionBase<Input, Meta>): CTE<Input>;
|
|
37
|
+
as<Input>(query: Delete<Input, Meta>): CTE<Input>;
|
|
38
|
+
as<Input>(query: Update<Input, Meta>): CTE<Input>;
|
|
39
|
+
as<Input>(query: Insert<Input, Meta>): CTE<Input>;
|
|
33
40
|
};
|
|
34
41
|
with(...definitions: Array<CTE>): BuilderBase<Meta>;
|
|
35
42
|
withRecursive(...definitions: Array<CTE>): BuilderBase<Meta>;
|
package/dist/core/Builder.js
CHANGED
|
@@ -1,10 +1,47 @@
|
|
|
1
1
|
// src/core/Builder.ts
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
getData,
|
|
4
|
+
getQuery,
|
|
5
|
+
getSelection,
|
|
6
|
+
getSql,
|
|
7
|
+
hasData,
|
|
8
|
+
hasQuery,
|
|
9
|
+
hasSql,
|
|
10
|
+
hasSelection,
|
|
11
|
+
internalData,
|
|
12
|
+
internalSelection,
|
|
13
|
+
internalQuery
|
|
14
|
+
} from "./Internal.js";
|
|
4
15
|
import { Delete, DeleteFrom } from "./query/Delete.js";
|
|
5
16
|
import { Insert, InsertInto } from "./query/Insert.js";
|
|
6
17
|
import { Select } from "./query/Select.js";
|
|
7
18
|
import { Update, UpdateTable } from "./query/Update.js";
|
|
19
|
+
import {
|
|
20
|
+
StarSelection
|
|
21
|
+
} from "./Selection.js";
|
|
22
|
+
import { Sql } from "./Sql.js";
|
|
23
|
+
import { virtualTarget } from "./Virtual.js";
|
|
24
|
+
function aliasSql(input, name) {
|
|
25
|
+
const expr = getSql(input);
|
|
26
|
+
if (expr.alias) return expr;
|
|
27
|
+
const aliased = new Sql((emitter) => expr.emit(emitter));
|
|
28
|
+
aliased.alias = name;
|
|
29
|
+
aliased.mapFromDriverValue = expr.mapFromDriverValue;
|
|
30
|
+
return aliased;
|
|
31
|
+
}
|
|
32
|
+
function aliasUnnamedFields(input, name) {
|
|
33
|
+
if (hasSql(input)) {
|
|
34
|
+
if (!name) return input;
|
|
35
|
+
return aliasSql(input, name);
|
|
36
|
+
}
|
|
37
|
+
if (!input || typeof input !== "object") return input;
|
|
38
|
+
return Object.fromEntries(
|
|
39
|
+
Object.entries(input).map(([key, value]) => [
|
|
40
|
+
key,
|
|
41
|
+
aliasUnnamedFields(value, key)
|
|
42
|
+
])
|
|
43
|
+
);
|
|
44
|
+
}
|
|
8
45
|
var BuilderBase = class {
|
|
9
46
|
[internalData];
|
|
10
47
|
constructor(data = {}) {
|
|
@@ -48,10 +85,21 @@ var BuilderBase = class {
|
|
|
48
85
|
}
|
|
49
86
|
};
|
|
50
87
|
var Builder = class extends BuilderBase {
|
|
51
|
-
$with(cteName) {
|
|
88
|
+
$with(cteName, columns) {
|
|
52
89
|
return {
|
|
53
90
|
as(query) {
|
|
54
|
-
|
|
91
|
+
let input = hasQuery(query) ? hasSelection(query) ? getSelection(query).input : columns : columns;
|
|
92
|
+
const data = hasQuery(query) && hasData(query) ? getData(query) : void 0;
|
|
93
|
+
if (input && data?.compound?.length > 1)
|
|
94
|
+
input = aliasUnnamedFields(input);
|
|
95
|
+
const target = virtualTarget(cteName, input ?? {});
|
|
96
|
+
return {
|
|
97
|
+
...target,
|
|
98
|
+
[internalSelection]: new StarSelection(target),
|
|
99
|
+
get [internalQuery]() {
|
|
100
|
+
return hasQuery(query) ? getQuery(query).nameSelf(cteName) : getSql(query);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
55
103
|
}
|
|
56
104
|
};
|
|
57
105
|
}
|