rado 1.0.10 → 1.0.12
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 +261 -131
- package/dist/core/Dialect.d.ts +3 -1
- package/dist/core/Dialect.js +5 -3
- package/dist/core/Driver.d.ts +1 -0
- package/dist/core/Emitter.d.ts +5 -4
- package/dist/core/Emitter.js +15 -2
- package/dist/core/query/Insert.d.ts +8 -4
- package/dist/core/query/Insert.js +15 -6
- package/dist/driver/better-sqlite3.js +1 -0
- package/dist/driver/bun-sqlite.js +1 -0
- package/dist/driver/d1.d.ts +1 -0
- package/dist/driver/d1.js +7 -9
- package/dist/driver/libsql.d.ts +28 -0
- package/dist/driver/libsql.js +76 -0
- package/dist/driver/mysql2.d.ts +1 -0
- package/dist/driver/mysql2.js +1 -0
- package/dist/driver/pg.d.ts +1 -0
- package/dist/driver/pg.js +1 -0
- package/dist/driver/pglite.d.ts +1 -0
- package/dist/driver/pglite.js +1 -0
- package/dist/driver/sql.js.js +1 -0
- package/dist/driver.d.ts +1 -0
- package/dist/driver.js +13 -11
- package/dist/mysql/dialect.js +1 -0
- package/dist/postgres/dialect.js +1 -0
- package/dist/sqlite/dialect.js +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -5,200 +5,330 @@
|
|
|
5
5
|
|
|
6
6
|
Fully typed, lightweight TypeScript query builder.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- Fully typed queries using TypeScript
|
|
11
|
+
- Composable and reusable query structures
|
|
12
|
+
- First-class support for JSON columns
|
|
13
|
+
- No code generation step required
|
|
14
|
+
- Zero dependencies
|
|
15
|
+
- Universal query support for multiple database engines
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
13
18
|
|
|
14
19
|
<pre>npm install <a href="https://www.npmjs.com/package/rado">rado</a></pre>
|
|
15
20
|
|
|
16
|
-
##
|
|
21
|
+
## Quick Start
|
|
17
22
|
|
|
18
|
-
|
|
23
|
+
```typescript
|
|
24
|
+
import {table} from 'rado'
|
|
25
|
+
import {text, integer} from 'rado/postgres'
|
|
26
|
+
import {connect} from 'rado/driver/pg'
|
|
27
|
+
import {Pool} from 'pg'
|
|
19
28
|
|
|
20
|
-
|
|
29
|
+
// Define your schema
|
|
30
|
+
const User = table({
|
|
31
|
+
id: integer().primaryKey(),
|
|
32
|
+
name: text().notNull(),
|
|
33
|
+
email: text().unique()
|
|
34
|
+
})
|
|
21
35
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
36
|
+
// Connect to the database
|
|
37
|
+
const db = connect(new Pool({
|
|
38
|
+
host: 'localhost',
|
|
39
|
+
database: 'my_database'
|
|
40
|
+
}))
|
|
41
|
+
|
|
42
|
+
// Perform a query
|
|
43
|
+
const users = await db.select().from(User)
|
|
44
|
+
console.log(users)
|
|
26
45
|
```
|
|
27
46
|
|
|
28
|
-
|
|
47
|
+
## Supported Databases
|
|
29
48
|
|
|
30
|
-
|
|
31
|
-
import {eq, or, gt, lt} from 'rado'
|
|
32
|
-
db.select().from(User).where(eq(User.id, 1))
|
|
33
|
-
// Conditions can be as complex as you want
|
|
34
|
-
db.select().from(User).where(or(gt(User.id, 1), lt(User.id, 5)))
|
|
35
|
-
```
|
|
49
|
+
Currently supported drivers:
|
|
36
50
|
|
|
37
|
-
|
|
51
|
+
| `PostgreSQL ` | `import ` |
|
|
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 |
|
|
38
57
|
|
|
39
|
-
|
|
58
|
+
| `SQLite ` | `import ` |
|
|
59
|
+
| -------------------------- | ------------------------------ |
|
|
60
|
+
| better-sqlite3 | rado/driver/better-sqlite3 |
|
|
61
|
+
| bun:sqlite | rado/driver/bun-sqlite |
|
|
62
|
+
| sql.js | rado/driver/sql.js |
|
|
63
|
+
| @libsql/client | rado/driver/libsql |
|
|
64
|
+
| Cloudflare D1 | rado/driver/d1 |
|
|
40
65
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
66
|
+
| `MySQL ` | `import ` |
|
|
67
|
+
| -------------------------- | ------------------------------ |
|
|
68
|
+
| mysql2 | rado/driver/mysql2 |
|
|
44
69
|
|
|
45
|
-
|
|
70
|
+
Pass an instance of the database to the `connect` function to get started:
|
|
46
71
|
|
|
47
72
|
```ts
|
|
48
|
-
import
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// Aggregate posts of this user
|
|
53
|
-
posts: include(db.select().from(Post).where(eq(Post.userId, User.id)))
|
|
54
|
-
}).from(User)
|
|
73
|
+
import Database from 'better-sqlite3'
|
|
74
|
+
import {connect} from 'rado/driver/better-sqlite3'
|
|
75
|
+
|
|
76
|
+
const db = connect(new Database('foobar.db'))
|
|
55
77
|
```
|
|
56
78
|
|
|
57
|
-
|
|
79
|
+
## Querying
|
|
58
80
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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)
|
|
68
91
|
```
|
|
69
92
|
|
|
70
|
-
|
|
93
|
+
### Where conditions
|
|
71
94
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
95
|
+
Where conditions help you filter your query results.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import {eq, and, or, gt, isNull} from 'rado'
|
|
99
|
+
|
|
100
|
+
// Simple equality
|
|
101
|
+
const john = await db.select().from(User).where(eq(User.name, 'John'))
|
|
102
|
+
|
|
103
|
+
// Complex conditions
|
|
104
|
+
const users = await db.select().from(User)
|
|
105
|
+
.where(
|
|
106
|
+
gt(User.age, 18),
|
|
107
|
+
or(
|
|
108
|
+
eq(User.name, 'Alice'),
|
|
109
|
+
isNull(User.email)
|
|
110
|
+
)
|
|
111
|
+
)
|
|
78
112
|
```
|
|
79
113
|
|
|
80
|
-
|
|
114
|
+
### Joins
|
|
81
115
|
|
|
82
|
-
|
|
116
|
+
Joins allow you to combine data from multiple tables.
|
|
83
117
|
|
|
84
|
-
```
|
|
85
|
-
db.
|
|
118
|
+
```typescript
|
|
119
|
+
const usersWithPosts = await db.select({
|
|
120
|
+
userName: User.name,
|
|
121
|
+
postTitle: Post.title,
|
|
122
|
+
})
|
|
123
|
+
.from(User)
|
|
124
|
+
.leftJoin(Post, eq(User.id, Post.authorId))
|
|
86
125
|
```
|
|
87
126
|
|
|
88
|
-
|
|
127
|
+
### Ordering and Grouping
|
|
89
128
|
|
|
90
|
-
|
|
91
|
-
|
|
129
|
+
You can order and group your query results:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import {desc, asc, count} from 'rado'
|
|
133
|
+
|
|
134
|
+
const orderedUsers = await db.select()
|
|
135
|
+
.from(User)
|
|
136
|
+
.orderBy(asc(User.name))
|
|
137
|
+
|
|
138
|
+
const userPostCounts = await db.select({
|
|
139
|
+
userName: User.name,
|
|
140
|
+
postCount: count(Post.id)
|
|
141
|
+
})
|
|
142
|
+
.from(User)
|
|
143
|
+
.leftJoin(Post, eq(User.id, Post.authorId))
|
|
144
|
+
.groupBy(User.name)
|
|
92
145
|
```
|
|
93
146
|
|
|
94
|
-
|
|
147
|
+
### Pagination
|
|
95
148
|
|
|
96
|
-
|
|
149
|
+
Pagination helps you manage large datasets by retrieving results in smaller chunks:
|
|
97
150
|
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
151
|
+
```typescript
|
|
152
|
+
const pageSize = 10
|
|
153
|
+
const page = 2
|
|
154
|
+
|
|
155
|
+
const paginatedUsers = await db.select()
|
|
156
|
+
.from(User)
|
|
157
|
+
.limit(pageSize)
|
|
158
|
+
.offset((page - 1) * pageSize)
|
|
102
159
|
```
|
|
103
160
|
|
|
104
|
-
|
|
161
|
+
### JSON columns
|
|
105
162
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
163
|
+
Rado provides first-class support for JSON columns:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import {pgTable, serial, text, jsonb} from 'rado/postgres'
|
|
167
|
+
|
|
168
|
+
const User = pgTable({
|
|
169
|
+
id: serial().primaryKey(),
|
|
170
|
+
name: text(),
|
|
171
|
+
metadata: jsonb<{subscribed: boolean}>()
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
const subscribedUsers = await db.select()
|
|
175
|
+
.from(User)
|
|
176
|
+
.where(eq(User.metadata.subscribed, true))
|
|
111
177
|
```
|
|
112
178
|
|
|
113
|
-
|
|
179
|
+
### Subqueries
|
|
114
180
|
|
|
115
|
-
|
|
181
|
+
```typescript
|
|
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')
|
|
116
187
|
|
|
117
|
-
|
|
118
|
-
|
|
188
|
+
const prolificAuthors = await db.select()
|
|
189
|
+
.from(User)
|
|
190
|
+
.where(inArray(User.id, subquery))
|
|
119
191
|
```
|
|
120
192
|
|
|
121
|
-
|
|
193
|
+
### Include
|
|
122
194
|
|
|
123
|
-
|
|
124
|
-
import * as sqlite from 'rado/sqlite'
|
|
125
|
-
import {sqliteTable as table} from 'rado/sqlite'
|
|
195
|
+
Aggregate rows using the `include` function:
|
|
126
196
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
userName: sqlite.text('user') // Column names are optional
|
|
130
|
-
})
|
|
197
|
+
```typescript
|
|
198
|
+
import {include} from 'rado'
|
|
131
199
|
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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)
|
|
137
206
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
+
```
|
|
142
219
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
220
|
+
### SQL Operator
|
|
221
|
+
|
|
222
|
+
The `sql` operator allows you to write raw SQL and interpolate values safely:
|
|
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}`)
|
|
147
231
|
```
|
|
148
232
|
|
|
149
|
-
##
|
|
233
|
+
## Modifying Data
|
|
150
234
|
|
|
151
|
-
|
|
235
|
+
### Insert
|
|
152
236
|
|
|
153
|
-
|
|
154
|
-
| -------------------------- | ------------------------------ |
|
|
155
|
-
| `pg` | `'rado/driver/pg'` |
|
|
156
|
-
| `@electric-sql/pglite` | `'rado/driver/pglite'` |
|
|
157
|
-
| `@neondatabase/serverless` | `'rado/driver/pg'` |
|
|
158
|
-
| `@vercel/postgres` | `'rado/driver/pg'` |
|
|
237
|
+
Insert operations allow you to add new records to your database:
|
|
159
238
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
| `Cloudflare D1` | `'rado/driver/d1'` |
|
|
239
|
+
```typescript
|
|
240
|
+
// Single insert
|
|
241
|
+
const newUser = await db.insert(User)
|
|
242
|
+
.values({name: 'Alice', email: 'alice@example.com'})
|
|
243
|
+
.returning()
|
|
166
244
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
+
```
|
|
170
253
|
|
|
171
|
-
|
|
254
|
+
### Update
|
|
172
255
|
|
|
173
|
-
|
|
174
|
-
import Database from 'better-sqlite3'
|
|
175
|
-
import {connect} from 'rado/driver/better-sqlite3'
|
|
256
|
+
Update operations modify existing records:
|
|
176
257
|
|
|
177
|
-
|
|
258
|
+
```typescript
|
|
259
|
+
const updatedCount = await db.update(User)
|
|
260
|
+
.set({name: 'Johnny'})
|
|
261
|
+
.where(eq(User.name, 'John'))
|
|
178
262
|
```
|
|
179
263
|
|
|
264
|
+
### Delete
|
|
265
|
+
|
|
266
|
+
Delete operations remove records from your database:
|
|
180
267
|
|
|
181
|
-
|
|
268
|
+
```typescript
|
|
269
|
+
await db.delete(User).where(eq(User.name, 'John'))
|
|
270
|
+
```
|
|
182
271
|
|
|
183
|
-
|
|
272
|
+
### Transactions
|
|
184
273
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const
|
|
274
|
+
Transactions allow you to group multiple operations into a single atomic unit.
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
const result = await db.transaction(async (tx) => {
|
|
278
|
+
const user = await tx.insert(User)
|
|
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
|
+
})
|
|
190
286
|
```
|
|
191
287
|
|
|
192
|
-
|
|
288
|
+
## Universal Queries
|
|
193
289
|
|
|
194
|
-
|
|
195
|
-
|
|
290
|
+
Rado provides a universal query builder that works across different database
|
|
291
|
+
engines, whether they run synchronously or asynchronously. This is useful for
|
|
292
|
+
writing database-agnostic code.
|
|
196
293
|
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
294
|
+
```typescript
|
|
295
|
+
import {table} from 'rado'
|
|
296
|
+
import {id, text} from 'rado/universal'
|
|
297
|
+
|
|
298
|
+
const User = table('user', {
|
|
299
|
+
id: id(),
|
|
300
|
+
name: text()
|
|
203
301
|
})
|
|
302
|
+
|
|
303
|
+
const db = process.env.SQLITE ? sqliteDb : postgresDb
|
|
304
|
+
|
|
305
|
+
const userNames = await db.select(User.name).from(User)
|
|
204
306
|
```
|
|
307
|
+
|
|
308
|
+
## Custom Column Types
|
|
309
|
+
|
|
310
|
+
You can define custom column types, such as a boolean column that is stored as a
|
|
311
|
+
tinyint in the database:
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
import {Column, column, table, sql} from 'rado'
|
|
315
|
+
|
|
316
|
+
export function bool(name?: string): Column<boolean | null> {
|
|
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
|
+
```
|
package/dist/core/Dialect.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { Emitter } from './Emitter.js';
|
|
2
2
|
import { type HasQuery, type HasSql } from './Internal.js';
|
|
3
|
+
import type { Runtime } from './MetaData.js';
|
|
3
4
|
export declare class Dialect {
|
|
4
5
|
#private;
|
|
5
|
-
|
|
6
|
+
runtime: Runtime;
|
|
7
|
+
constructor(runtime: Runtime, createEmitter: new (runtime: Runtime) => Emitter);
|
|
6
8
|
emit: (input: HasSql | HasQuery) => Emitter;
|
|
7
9
|
inline: (input: HasSql | HasQuery) => string;
|
|
8
10
|
}
|
package/dist/core/Dialect.js
CHANGED
|
@@ -5,19 +5,21 @@ import {
|
|
|
5
5
|
hasQuery
|
|
6
6
|
} from "./Internal.js";
|
|
7
7
|
var Dialect = class {
|
|
8
|
+
runtime;
|
|
8
9
|
#createEmitter;
|
|
9
|
-
constructor(createEmitter) {
|
|
10
|
+
constructor(runtime, createEmitter) {
|
|
11
|
+
this.runtime = runtime;
|
|
10
12
|
this.#createEmitter = createEmitter;
|
|
11
13
|
}
|
|
12
14
|
emit = (input) => {
|
|
13
15
|
const sql = hasQuery(input) ? getQuery(input) : getSql(input);
|
|
14
|
-
const emitter = new this.#createEmitter();
|
|
16
|
+
const emitter = new this.#createEmitter(this.runtime);
|
|
15
17
|
sql.emitTo(emitter);
|
|
16
18
|
return emitter;
|
|
17
19
|
};
|
|
18
20
|
inline = (input) => {
|
|
19
21
|
const sql = hasQuery(input) ? getQuery(input) : getSql(input);
|
|
20
|
-
const emitter = new this.#createEmitter();
|
|
22
|
+
const emitter = new this.#createEmitter(this.runtime);
|
|
21
23
|
sql.inlineValues().emitTo(emitter);
|
|
22
24
|
return emitter.sql;
|
|
23
25
|
};
|
package/dist/core/Driver.d.ts
CHANGED
package/dist/core/Emitter.d.ts
CHANGED
|
@@ -13,17 +13,18 @@ import type { SelectData } from './query/Select.js';
|
|
|
13
13
|
import type { UnionData } from './query/Union.js';
|
|
14
14
|
import type { Update } from './query/Update.js';
|
|
15
15
|
export declare abstract class Emitter {
|
|
16
|
+
#private;
|
|
16
17
|
sql: string;
|
|
17
18
|
protected params: Array<Param>;
|
|
18
|
-
get hasParams(): boolean;
|
|
19
|
-
bind(inputs?: Record<string, unknown>): Array<unknown>;
|
|
20
|
-
processValue(value: unknown): unknown;
|
|
21
|
-
abstract runtime: Runtime;
|
|
22
19
|
abstract emitIdentifier(value: string): void;
|
|
23
20
|
abstract emitValue(value: unknown): void;
|
|
24
21
|
abstract emitInline(value: unknown): void;
|
|
25
22
|
abstract emitJsonPath(path: JsonPath): void;
|
|
26
23
|
abstract emitPlaceholder(value: string): void;
|
|
24
|
+
constructor(runtime: Runtime);
|
|
25
|
+
get hasParams(): boolean;
|
|
26
|
+
bind(inputs?: Record<string, unknown>): Array<unknown>;
|
|
27
|
+
processValue(value: unknown): unknown;
|
|
27
28
|
emitIdentifierOrSelf(value: string): void;
|
|
28
29
|
selfName?: string;
|
|
29
30
|
emitSelf({ name, inner }: {
|
package/dist/core/Emitter.js
CHANGED
|
@@ -10,8 +10,12 @@ import { Sql, sql } from "./Sql.js";
|
|
|
10
10
|
import { callFunction } from "./expr/Functions.js";
|
|
11
11
|
import { jsonAggregateArray, jsonArray } from "./expr/Json.js";
|
|
12
12
|
var Emitter = class {
|
|
13
|
+
#runtime;
|
|
13
14
|
sql = "";
|
|
14
15
|
params = [];
|
|
16
|
+
constructor(runtime) {
|
|
17
|
+
this.#runtime = runtime;
|
|
18
|
+
}
|
|
15
19
|
get hasParams() {
|
|
16
20
|
return this.params.length > 0;
|
|
17
21
|
}
|
|
@@ -85,7 +89,15 @@ var Emitter = class {
|
|
|
85
89
|
}).emitTo(this);
|
|
86
90
|
}
|
|
87
91
|
emitInsert(insert) {
|
|
88
|
-
const {
|
|
92
|
+
const {
|
|
93
|
+
cte,
|
|
94
|
+
into,
|
|
95
|
+
values,
|
|
96
|
+
select,
|
|
97
|
+
onConflict,
|
|
98
|
+
onDuplicateKeyUpdate,
|
|
99
|
+
returning
|
|
100
|
+
} = getData(insert);
|
|
89
101
|
if (cte) this.emitWith(cte);
|
|
90
102
|
const table = getTable(into);
|
|
91
103
|
const tableName = sql.identifier(table.name);
|
|
@@ -93,6 +105,7 @@ var Emitter = class {
|
|
|
93
105
|
insertInto: sql`${tableName}(${table.listColumns()})`,
|
|
94
106
|
...values ? { values } : { "": select },
|
|
95
107
|
onConflict,
|
|
108
|
+
onDuplicateKeyUpdate,
|
|
96
109
|
returning
|
|
97
110
|
}).inlineFields(false).emitTo(this);
|
|
98
111
|
}
|
|
@@ -160,7 +173,7 @@ var Emitter = class {
|
|
|
160
173
|
sql`(select ${data.first ? subject : jsonAggregateArray(subject)} from (${inner}) as _)`.emitTo(this);
|
|
161
174
|
}
|
|
162
175
|
emitUniversal(runtimes) {
|
|
163
|
-
const sql2 = runtimes[this
|
|
176
|
+
const sql2 = runtimes[this.#runtime] ?? runtimes.default;
|
|
164
177
|
if (!sql2) throw new Error("Unsupported runtime");
|
|
165
178
|
sql2.emitTo(this);
|
|
166
179
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type HasSql, type HasTable, internalData, internalQuery, internalSelection } from '../Internal.js';
|
|
2
|
-
import type { IsPostgres, IsSqlite, QueryMeta } from '../MetaData.js';
|
|
2
|
+
import type { IsMysql, IsPostgres, IsSqlite, QueryMeta } from '../MetaData.js';
|
|
3
3
|
import { Query, type QueryData } from '../Query.js';
|
|
4
4
|
import { type Selection, type SelectionInput, type SelectionRow } from '../Selection.js';
|
|
5
5
|
import { type Sql } from '../Sql.js';
|
|
@@ -12,6 +12,7 @@ interface InsertIntoData<Meta extends QueryMeta> extends QueryData<Meta> {
|
|
|
12
12
|
export interface InsertData<Meta extends QueryMeta> extends InsertIntoData<Meta> {
|
|
13
13
|
returning?: Selection;
|
|
14
14
|
onConflict?: HasSql;
|
|
15
|
+
onDuplicateKeyUpdate?: HasSql;
|
|
15
16
|
}
|
|
16
17
|
export declare class Insert<Result, Meta extends QueryMeta = QueryMeta> extends Query<Result, Meta> {
|
|
17
18
|
readonly [internalData]: InsertData<Meta>;
|
|
@@ -27,14 +28,17 @@ export interface OnConflict {
|
|
|
27
28
|
target: HasSql | Array<HasSql>;
|
|
28
29
|
targetWhere?: HasSql<boolean>;
|
|
29
30
|
}
|
|
30
|
-
export interface
|
|
31
|
+
export interface OnConflictSet<Definition extends TableDefinition> {
|
|
31
32
|
set: TableUpdate<Definition>;
|
|
33
|
+
}
|
|
34
|
+
export interface OnConflictUpdate<Definition extends TableDefinition> extends OnConflict, OnConflictSet<Definition> {
|
|
32
35
|
setWhere?: HasSql<boolean>;
|
|
33
36
|
}
|
|
34
37
|
declare class InsertCanConflict<Definition extends TableDefinition, Meta extends QueryMeta> extends InsertCanReturn<Definition, Meta> {
|
|
35
38
|
#private;
|
|
36
|
-
onConflictDoNothing(this: InsertCanConflict<Definition, IsPostgres>, onConflict?: OnConflict): InsertCanReturn<Definition, Meta>;
|
|
37
|
-
onConflictDoUpdate(this: InsertCanConflict<Definition, IsPostgres>, onConflict: OnConflictUpdate<Definition>): InsertCanReturn<Definition, Meta>;
|
|
39
|
+
onConflictDoNothing(this: InsertCanConflict<Definition, IsPostgres | IsSqlite>, onConflict?: OnConflict): InsertCanReturn<Definition, Meta>;
|
|
40
|
+
onConflictDoUpdate(this: InsertCanConflict<Definition, IsPostgres | IsSqlite>, onConflict: OnConflictUpdate<Definition>): InsertCanReturn<Definition, Meta>;
|
|
41
|
+
onDuplicateKeyUpdate(this: InsertCanConflict<Definition, IsMysql>, update: OnConflictSet<Definition>): InsertCanReturn<Definition, Meta>;
|
|
38
42
|
}
|
|
39
43
|
export declare class InsertInto<Definition extends TableDefinition, Meta extends QueryMeta> {
|
|
40
44
|
[internalData]: InsertData<Meta>;
|
|
@@ -40,18 +40,27 @@ var InsertCanConflict = class extends InsertCanReturn {
|
|
|
40
40
|
onConflictDoUpdate(onConflict) {
|
|
41
41
|
return this.#onConflict(onConflict);
|
|
42
42
|
}
|
|
43
|
+
onDuplicateKeyUpdate(update) {
|
|
44
|
+
return new InsertCanReturn({
|
|
45
|
+
...getData(this),
|
|
46
|
+
onDuplicateKeyUpdate: this.#updateFields(update.set)
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
#updateFields(update) {
|
|
50
|
+
return sql.join(
|
|
51
|
+
Object.entries(update).map(
|
|
52
|
+
([key, value]) => sql`${sql.identifier(key)} = ${input(value)}`
|
|
53
|
+
),
|
|
54
|
+
sql`, `
|
|
55
|
+
);
|
|
56
|
+
}
|
|
43
57
|
#onConflict({
|
|
44
58
|
target,
|
|
45
59
|
targetWhere,
|
|
46
60
|
set,
|
|
47
61
|
setWhere
|
|
48
62
|
}) {
|
|
49
|
-
const update = set &&
|
|
50
|
-
Object.entries(set).map(
|
|
51
|
-
([key, value]) => sql`${sql.identifier(key)} = ${input(value)}`
|
|
52
|
-
),
|
|
53
|
-
sql`, `
|
|
54
|
-
);
|
|
63
|
+
const update = set && this.#updateFields(set);
|
|
55
64
|
return new InsertCanReturn({
|
|
56
65
|
...getData(this),
|
|
57
66
|
onConflict: sql.join([
|
package/dist/driver/d1.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ declare class PreparedStatement implements AsyncStatement {
|
|
|
14
14
|
export declare class D1Driver implements AsyncDriver {
|
|
15
15
|
private client;
|
|
16
16
|
parsesJson: boolean;
|
|
17
|
+
supportsTransactions: boolean;
|
|
17
18
|
constructor(client: Client);
|
|
18
19
|
exec(query: string): Promise<void>;
|
|
19
20
|
prepare(sql: string, options: PrepareOptions): PreparedStatement;
|
package/dist/driver/d1.js
CHANGED
|
@@ -17,11 +17,7 @@ var PreparedStatement = class {
|
|
|
17
17
|
return this.stmt.bind(...params).first();
|
|
18
18
|
}
|
|
19
19
|
async values(params) {
|
|
20
|
-
if (this.isSelection)
|
|
21
|
-
const results = await this.stmt.bind(...params).raw();
|
|
22
|
-
console.log(results);
|
|
23
|
-
return results;
|
|
24
|
-
}
|
|
20
|
+
if (this.isSelection) return this.stmt.bind(...params).raw();
|
|
25
21
|
await this.stmt.bind(...params).run();
|
|
26
22
|
return [];
|
|
27
23
|
}
|
|
@@ -33,6 +29,7 @@ var D1Driver = class {
|
|
|
33
29
|
this.client = client;
|
|
34
30
|
}
|
|
35
31
|
parsesJson = false;
|
|
32
|
+
supportsTransactions = false;
|
|
36
33
|
async exec(query) {
|
|
37
34
|
await this.client.exec(query);
|
|
38
35
|
}
|
|
@@ -42,10 +39,11 @@ var D1Driver = class {
|
|
|
42
39
|
async close() {
|
|
43
40
|
}
|
|
44
41
|
async batch(queries) {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
const stmts = queries.map(
|
|
43
|
+
({ sql, params }) => this.client.prepare(sql).bind(...params)
|
|
44
|
+
);
|
|
45
|
+
const rows = await this.client.batch(stmts);
|
|
46
|
+
return rows.map((row) => row.results);
|
|
49
47
|
}
|
|
50
48
|
async transaction() {
|
|
51
49
|
throw new Error("Transactions are not supported in D1");
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Client, InValue, Transaction } from '@libsql/client';
|
|
2
|
+
import { AsyncDatabase, type TransactionOptions } from '../core/Database.js';
|
|
3
|
+
import type { AsyncDriver, AsyncStatement, BatchQuery, PrepareOptions } from '../core/Driver.js';
|
|
4
|
+
type Queryable = Client | Transaction;
|
|
5
|
+
declare class PreparedStatement implements AsyncStatement {
|
|
6
|
+
private client;
|
|
7
|
+
private sql;
|
|
8
|
+
constructor(client: Queryable, sql: string);
|
|
9
|
+
all(params: Array<InValue>): Promise<Array<object>>;
|
|
10
|
+
run(params: Array<InValue>): Promise<void>;
|
|
11
|
+
get(params: Array<InValue>): Promise<object | null>;
|
|
12
|
+
values(params: Array<InValue>): Promise<Array<Array<unknown>>>;
|
|
13
|
+
free(): void;
|
|
14
|
+
}
|
|
15
|
+
export declare class LibSQLClient implements AsyncDriver {
|
|
16
|
+
private client;
|
|
17
|
+
private depth;
|
|
18
|
+
parsesJson: boolean;
|
|
19
|
+
supportsTransactions: boolean;
|
|
20
|
+
constructor(client: Queryable, depth?: number);
|
|
21
|
+
exec(query: string): Promise<void>;
|
|
22
|
+
prepare(sql: string, options: PrepareOptions): PreparedStatement;
|
|
23
|
+
close(): Promise<void>;
|
|
24
|
+
batch(queries: Array<BatchQuery>): Promise<Array<Array<unknown>>>;
|
|
25
|
+
transaction<T>(run: (inner: AsyncDriver) => Promise<T>, options: TransactionOptions['sqlite']): Promise<T>;
|
|
26
|
+
}
|
|
27
|
+
export declare function connect(client: Client): AsyncDatabase<'sqlite'>;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// src/driver/libsql.ts
|
|
2
|
+
import { AsyncDatabase } from "../core/Database.js";
|
|
3
|
+
import { sqliteDialect } from "../sqlite.js";
|
|
4
|
+
import { sqliteDiff } from "../sqlite/diff.js";
|
|
5
|
+
var PreparedStatement = class {
|
|
6
|
+
constructor(client, sql) {
|
|
7
|
+
this.client = client;
|
|
8
|
+
this.sql = sql;
|
|
9
|
+
}
|
|
10
|
+
async all(params) {
|
|
11
|
+
const result = await this.client.execute({ sql: this.sql, args: params });
|
|
12
|
+
return result.rows;
|
|
13
|
+
}
|
|
14
|
+
async run(params) {
|
|
15
|
+
const result = await this.client.execute({ sql: this.sql, args: params });
|
|
16
|
+
}
|
|
17
|
+
async get(params) {
|
|
18
|
+
return (await this.all(params))[0] ?? null;
|
|
19
|
+
}
|
|
20
|
+
async values(params) {
|
|
21
|
+
const result = await this.client.execute({ sql: this.sql, args: params });
|
|
22
|
+
return result.rows;
|
|
23
|
+
}
|
|
24
|
+
free() {
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var LibSQLClient = class _LibSQLClient {
|
|
28
|
+
constructor(client, depth = 0) {
|
|
29
|
+
this.client = client;
|
|
30
|
+
this.depth = depth;
|
|
31
|
+
}
|
|
32
|
+
parsesJson = false;
|
|
33
|
+
supportsTransactions = true;
|
|
34
|
+
async exec(query) {
|
|
35
|
+
await this.client.execute(query);
|
|
36
|
+
}
|
|
37
|
+
prepare(sql, options) {
|
|
38
|
+
return new PreparedStatement(this.client, sql);
|
|
39
|
+
}
|
|
40
|
+
async close() {
|
|
41
|
+
if ("close" in this.client) return this.client.close();
|
|
42
|
+
}
|
|
43
|
+
async batch(queries) {
|
|
44
|
+
const stmts = queries.map(({ sql, params }) => {
|
|
45
|
+
return { sql, args: params };
|
|
46
|
+
});
|
|
47
|
+
const rows = await this.client.batch(stmts);
|
|
48
|
+
return rows.map((row) => row.rows);
|
|
49
|
+
}
|
|
50
|
+
async transaction(run, options) {
|
|
51
|
+
const client = "transaction" in this.client ? await this.client.transaction() : this.client;
|
|
52
|
+
const driver = new _LibSQLClient(client, this.depth + 1);
|
|
53
|
+
if (this.depth > 0) await client.execute(`savepoint d${this.depth}`);
|
|
54
|
+
try {
|
|
55
|
+
const result = await run(driver);
|
|
56
|
+
if (this.depth > 0)
|
|
57
|
+
await client.execute(`release savepoint d${this.depth}`);
|
|
58
|
+
else await client.commit();
|
|
59
|
+
return result;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (this.depth > 0)
|
|
62
|
+
await client.execute(`rollback to savepoint d${this.depth}`);
|
|
63
|
+
else await client.rollback();
|
|
64
|
+
throw error;
|
|
65
|
+
} finally {
|
|
66
|
+
if (this.depth === 0) client.close();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
function connect(client) {
|
|
71
|
+
return new AsyncDatabase(new LibSQLClient(client), sqliteDialect, sqliteDiff);
|
|
72
|
+
}
|
|
73
|
+
export {
|
|
74
|
+
LibSQLClient,
|
|
75
|
+
connect
|
|
76
|
+
};
|
package/dist/driver/mysql2.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export declare class Mysql2Driver implements AsyncDriver {
|
|
|
19
19
|
private client;
|
|
20
20
|
private depth;
|
|
21
21
|
parsesJson: boolean;
|
|
22
|
+
supportsTransactions: boolean;
|
|
22
23
|
constructor(client: Queryable, depth?: number);
|
|
23
24
|
exec(query: string): Promise<void>;
|
|
24
25
|
prepare(sql: string, options?: PrepareOptions): PreparedStatement;
|
package/dist/driver/mysql2.js
CHANGED
package/dist/driver/pg.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export declare class PgDriver implements AsyncDriver {
|
|
|
17
17
|
private client;
|
|
18
18
|
private depth;
|
|
19
19
|
parsesJson: boolean;
|
|
20
|
+
supportsTransactions: boolean;
|
|
20
21
|
constructor(client: Queryable, depth?: number);
|
|
21
22
|
exec(query: string): Promise<void>;
|
|
22
23
|
prepare(sql: string, options?: PrepareOptions): PreparedStatement;
|
package/dist/driver/pg.js
CHANGED
package/dist/driver/pglite.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export declare class PGliteDriver implements AsyncDriver {
|
|
|
16
16
|
private client;
|
|
17
17
|
private depth;
|
|
18
18
|
parsesJson: boolean;
|
|
19
|
+
supportsTransactions: boolean;
|
|
19
20
|
constructor(client: Queryable, depth?: number);
|
|
20
21
|
exec(query: string): Promise<void>;
|
|
21
22
|
close(): Promise<void>;
|
package/dist/driver/pglite.js
CHANGED
package/dist/driver/sql.js.js
CHANGED
package/dist/driver.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { connect as 'better-sqlite3' } from './driver/better-sqlite3.js';
|
|
2
2
|
export { connect as 'bun:sqlite' } from './driver/bun-sqlite.js';
|
|
3
3
|
export { connect as 'd1' } from './driver/d1.js';
|
|
4
|
+
export { connect as '@libsql/client' } from './driver/libsql.js';
|
|
4
5
|
export { connect as 'mysql2' } from './driver/mysql2.js';
|
|
5
6
|
export { connect as '@neondatabase/serverless', connect as '@vercel/postgres', connect as 'pg' } from './driver/pg.js';
|
|
6
7
|
export { connect as '@electric-sql/pglite' } from './driver/pglite.js';
|
package/dist/driver.js
CHANGED
|
@@ -2,22 +2,24 @@
|
|
|
2
2
|
import { connect } from "./driver/better-sqlite3.js";
|
|
3
3
|
import { connect as connect2 } from "./driver/bun-sqlite.js";
|
|
4
4
|
import { connect as connect3 } from "./driver/d1.js";
|
|
5
|
-
import { connect as connect4 } from "./driver/
|
|
5
|
+
import { connect as connect4 } from "./driver/libsql.js";
|
|
6
|
+
import { connect as connect5 } from "./driver/mysql2.js";
|
|
6
7
|
import {
|
|
7
|
-
connect as connect5,
|
|
8
8
|
connect as connect6,
|
|
9
|
-
connect as connect7
|
|
9
|
+
connect as connect7,
|
|
10
|
+
connect as connect8
|
|
10
11
|
} from "./driver/pg.js";
|
|
11
|
-
import { connect as
|
|
12
|
-
import { connect as
|
|
12
|
+
import { connect as connect9 } from "./driver/pglite.js";
|
|
13
|
+
import { connect as connect10 } from "./driver/sql.js.js";
|
|
13
14
|
export {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
connect6 as "@
|
|
15
|
+
connect9 as "@electric-sql/pglite",
|
|
16
|
+
connect4 as "@libsql/client",
|
|
17
|
+
connect6 as "@neondatabase/serverless",
|
|
18
|
+
connect7 as "@vercel/postgres",
|
|
17
19
|
connect as "better-sqlite3",
|
|
18
20
|
connect2 as "bun:sqlite",
|
|
19
21
|
connect3 as d1,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
connect5 as mysql2,
|
|
23
|
+
connect8 as pg,
|
|
24
|
+
connect10 as "sql.js"
|
|
23
25
|
};
|
package/dist/mysql/dialect.js
CHANGED
package/dist/postgres/dialect.js
CHANGED
package/dist/sqlite/dialect.js
CHANGED
|
@@ -9,8 +9,8 @@ var SINGLE_QUOTE = "'";
|
|
|
9
9
|
var ESCAPE_SINGLE_QUOTE = "''";
|
|
10
10
|
var MATCH_SINGLE_QUOTE = /'/g;
|
|
11
11
|
var sqliteDialect = new Dialect(
|
|
12
|
+
"sqlite",
|
|
12
13
|
class extends Emitter {
|
|
13
|
-
runtime = "sqlite";
|
|
14
14
|
processValue(value) {
|
|
15
15
|
return typeof value === "boolean" ? value ? 1 : 0 : value;
|
|
16
16
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rado",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"@biomejs/biome": "^1.8.3",
|
|
31
31
|
"@cloudflare/workers-types": "^4.20230628.0",
|
|
32
32
|
"@electric-sql/pglite": "^0.1.5",
|
|
33
|
+
"@libsql/client": "^0.10.0",
|
|
33
34
|
"@miniflare/d1": "^2.14.2",
|
|
34
35
|
"@miniflare/shared": "^2.14.2",
|
|
35
36
|
"@sqlite.org/sqlite-wasm": "^3.42.0-build4",
|