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 CHANGED
@@ -5,200 +5,330 @@
5
5
 
6
6
  Fully typed, lightweight TypeScript query builder.
7
7
 
8
- - Definition via TypeScript types
9
- - Composable queries
10
- - First class JSON columns
11
- - No code generation step
12
- - No dependencies
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
- ## Queries
21
+ ## Quick Start
17
22
 
18
- #### Where
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
- Conditions can be created by accessing the fields of the table:
29
+ // Define your schema
30
+ const User = table({
31
+ id: integer().primaryKey(),
32
+ name: text().notNull(),
33
+ email: text().unique()
34
+ })
21
35
 
22
- ```ts
23
- import {eq} from 'rado'
24
- eq(User.id, 1) // Compare with a value
25
- eq(User.id, Post.userId) // Compare to fields on other tables
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
- Rows can be selected by passing the condition in the where method:
47
+ ## Supported Databases
29
48
 
30
- ```ts
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
- #### Select
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
- Retrieve a single field
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
- ```ts
42
- db.select(User.name).from(User)
43
- ```
66
+ | `MySQL ` | `import ` |
67
+ | -------------------------- | ------------------------------ |
68
+ | mysql2 | rado/driver/mysql2 |
44
69
 
45
- Or an object of data
70
+ Pass an instance of the database to the `connect` function to get started:
46
71
 
47
72
  ```ts
48
- import {eq, include} from 'rado'
49
- db.select({
50
- // Get all user fields: id, username
51
- ...User,
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
- Join other tables and select fields from either table:
79
+ ## Querying
58
80
 
59
- ```ts
60
- db
61
- .select({
62
- username: User.username
63
- post: Post
64
- })
65
- .from(User)
66
- .innerJoin(Post, eq(Post.userId, User.id))
67
- .where(eq(User.id, 1))
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
- Order, group, limit queries:
93
+ ### Where conditions
71
94
 
72
- ```ts
73
- import {asc} from 'rado'
74
- db.select().from(User)
75
- .orderBy(asc(User.username))
76
- .groupBy(User.id, User.username)
77
- .limit(10, 20)
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
- #### Insert
114
+ ### Joins
81
115
 
82
- Insert a single row:
116
+ Joins allow you to combine data from multiple tables.
83
117
 
84
- ```ts
85
- db.insert(User).values({username: 'Mario'})
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
- Insert multiple rows:
127
+ ### Ordering and Grouping
89
128
 
90
- ```ts
91
- db.insert(User).values([{username: 'Mario'}, {username: 'Luigi'}])
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
- #### Update
147
+ ### Pagination
95
148
 
96
- Set values
149
+ Pagination helps you manage large datasets by retrieving results in smaller chunks:
97
150
 
98
- ```ts
99
- db.update(User)
100
- .set({username: 'Bowser'})
101
- .where(eq(User.id, 1))
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
- Use expressions to update complex values:
161
+ ### JSON columns
105
162
 
106
- ```ts
107
- import {concat} from 'rado/sqlite'
108
- db.update(User)
109
- .set({username: concat(User.username, ' [removed]')})
110
- .where(User.deleted)
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
- #### Delete
179
+ ### Subqueries
114
180
 
115
- Delete rows
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
- ```ts
118
- db.delete(User).where(eq(User.id, 1))
188
+ const prolificAuthors = await db.select()
189
+ .from(User)
190
+ .where(inArray(User.id, subquery))
119
191
  ```
120
192
 
121
- ## Schema definition
193
+ ### Include
122
194
 
123
- ```ts
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
- const User = table({
128
- id: sqlite.integer().primaryKey(),
129
- userName: sqlite.text('user') // Column names are optional
130
- })
197
+ ```typescript
198
+ import {include} from 'rado'
131
199
 
132
- const Post = table({
133
- id: sqlite.integer().primaryKey(),
134
- userId: sqlite.integer().references(() => User.id),
135
- content: sqlite.text()
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
- const Tag = table({
139
- id: sqlite.integer().primaryKey(),
140
- name: sqlite.text()
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
- const PostTags = table({
144
- postId: sqlite.integer().references(() => Post.id),
145
- tagId: sqlite.integer().references(() => Tag.id)
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
- ## Connections
233
+ ## Modifying Data
150
234
 
151
- Currently supported drivers:
235
+ ### Insert
152
236
 
153
- | `PostgreSQL ` | `import ` |
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
- | `SQLite ` | `import ` |
161
- | -------------------------- | ------------------------------ |
162
- | `better-sqlite3` | `'rado/driver/better-sqlite3'` |
163
- | `bun:sqlite` | `'rado/driver/bun-sqlite'` |
164
- | `sql.js` | `'rado/driver/sql.js'` |
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
- | `MySQL ` | `import ` |
168
- | -------------------------- | ------------------------------ |
169
- | `mysql2` | `'rado/driver/mysql2'` |
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
- Pass an instance of the database to the `connect` function to get started:
254
+ ### Update
172
255
 
173
- ```ts
174
- import Database from 'better-sqlite3'
175
- import {connect} from 'rado/driver/better-sqlite3'
256
+ Update operations modify existing records:
176
257
 
177
- const db = connect(new Database('foobar.db'))
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
- #### Run queries
268
+ ```typescript
269
+ await db.delete(User).where(eq(User.name, 'John'))
270
+ ```
182
271
 
183
- Call the connection with a query to retrieve its results:
272
+ ### Transactions
184
273
 
185
- ```ts
186
- // For sync drivers this will be an array:
187
- const posts = db.select().from(Post).all()
188
- // For async drivers this will be a Promise:
189
- const posts = await db.select().from(Post)
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
- #### Transactions
288
+ ## Universal Queries
193
289
 
194
- Run transactions within the `transaction` method which will isolate a connection
195
- and can optionally return a result:
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
- ```ts
198
- const firstUserId = db.transaction(tx => {
199
- const createTable = db.create(User)
200
- tx(createTable)
201
- const insertUser = db.insert(User).values({username: 'Mario'}).returning(User.id)
202
- return tx(insertUser)
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
+ ```
@@ -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
- constructor(createEmitter: new () => Emitter);
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
  }
@@ -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
  };
@@ -8,6 +8,7 @@ export interface BatchQuery {
8
8
  }
9
9
  export interface DriverSpecs {
10
10
  parsesJson: boolean;
11
+ supportsTransactions: boolean;
11
12
  }
12
13
  export interface PrepareOptions {
13
14
  isSelection: boolean;
@@ -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 }: {
@@ -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 { cte, into, values, select, onConflict, returning } = getData(insert);
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.runtime] ?? runtimes.default;
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 OnConflictUpdate<Definition extends TableDefinition> extends OnConflict {
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 && sql.join(
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([
@@ -32,6 +32,7 @@ var BetterSqlite3Driver = class _BetterSqlite3Driver {
32
32
  this.depth = depth;
33
33
  }
34
34
  parsesJson = false;
35
+ supportsTransactions = true;
35
36
  exec(query) {
36
37
  this.client.exec(query);
37
38
  }
@@ -29,6 +29,7 @@ var BunSqliteDriver = class _BunSqliteDriver {
29
29
  this.depth = depth;
30
30
  }
31
31
  parsesJson = false;
32
+ supportsTransactions = true;
32
33
  exec(query) {
33
34
  this.client.exec(query);
34
35
  }
@@ -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 results = [];
46
- for (const { sql, params, isSelection } of queries)
47
- results.push(await this.prepare(sql, { isSelection }).values(params));
48
- return results;
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
+ };
@@ -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;
@@ -38,6 +38,7 @@ var Mysql2Driver = class _Mysql2Driver {
38
38
  this.depth = depth;
39
39
  }
40
40
  parsesJson = true;
41
+ supportsTransactions = true;
41
42
  async exec(query) {
42
43
  await this.client.query(query);
43
44
  }
@@ -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
@@ -46,6 +46,7 @@ var PgDriver = class _PgDriver {
46
46
  this.depth = depth;
47
47
  }
48
48
  parsesJson = true;
49
+ supportsTransactions = true;
49
50
  async exec(query) {
50
51
  await this.client.query(query);
51
52
  }
@@ -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>;
@@ -35,6 +35,7 @@ var PGliteDriver = class _PGliteDriver {
35
35
  this.depth = depth;
36
36
  }
37
37
  parsesJson = true;
38
+ supportsTransactions = true;
38
39
  async exec(query) {
39
40
  await this.client.exec(query);
40
41
  }
@@ -42,6 +42,7 @@ var SqlJsDriver = class _SqlJsDriver {
42
42
  this.depth = depth;
43
43
  }
44
44
  parsesJson = false;
45
+ supportsTransactions = true;
45
46
  exec(query) {
46
47
  this.client.exec(query);
47
48
  }
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/mysql2.js";
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 connect8 } from "./driver/pglite.js";
12
- import { connect as connect9 } from "./driver/sql.js.js";
12
+ import { connect as connect9 } from "./driver/pglite.js";
13
+ import { connect as connect10 } from "./driver/sql.js.js";
13
14
  export {
14
- connect8 as "@electric-sql/pglite",
15
- connect5 as "@neondatabase/serverless",
16
- connect6 as "@vercel/postgres",
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
- connect4 as mysql2,
21
- connect7 as pg,
22
- connect9 as "sql.js"
22
+ connect5 as mysql2,
23
+ connect8 as pg,
24
+ connect10 as "sql.js"
23
25
  };
@@ -9,6 +9,7 @@ var SINGLE_QUOTE = "'";
9
9
  var ESCAPE_SINGLE_QUOTE = "''";
10
10
  var MATCH_SINGLE_QUOTE = /'/g;
11
11
  var mysqlDialect = new Dialect(
12
+ "mysql",
12
13
  class extends Emitter {
13
14
  runtime = "mysql";
14
15
  paramIndex = 0;
@@ -9,6 +9,7 @@ var SINGLE_QUOTE = "'";
9
9
  var ESCAPE_SINGLE_QUOTE = "''";
10
10
  var MATCH_SINGLE_QUOTE = /'/g;
11
11
  var postgresDialect = new Dialect(
12
+ "postgres",
12
13
  class extends Emitter {
13
14
  runtime = "postgres";
14
15
  paramIndex = 0;
@@ -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.10",
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",