rado 0.1.63 → 0.2.0

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
@@ -1 +1,270 @@
1
1
  # rado
2
+
3
+ Fully typed, lightweight TypeScript query builder.
4
+ Currently focused on SQLite.
5
+
6
+ ```
7
+ npm install rado
8
+ ```
9
+
10
+ - Definition via TypeScript types
11
+ - Composable queries
12
+ - Aggregate rows in selects
13
+ - First class JSON columns
14
+ - No code generation step
15
+ - No dependencies
16
+
17
+ ## Queries
18
+
19
+ #### Where
20
+
21
+ Conditions can be created by accessing the fields of the table
22
+ instances and using the operators of `Expr`:
23
+
24
+ ```ts
25
+ User.id // This is an Expr<number> which has an `is` method to compare
26
+ User.id.is(1) // Compare with a value, results in an Expr<boolean>
27
+ User.id.is(Post.userId) // Compare to fields on other tables
28
+ ```
29
+
30
+ Rows can be selected by passing the condition in the where method:
31
+
32
+ ```ts
33
+ // Call a table instance to start a query selecting from that table
34
+ User().where(User.id.is(1))
35
+ // Conditions can be as complex as you want
36
+ User().where(User.id.isGreater(1).and(User.id.isLess(5)))
37
+ // `And` conditions can also be comma separated in `where` to improve readability
38
+ User().where(User.id.isGreater(1), User.id.isLess(5))
39
+ ```
40
+
41
+ There's a shortcut for comparing columns of a table directly in its call.
42
+ The first comparison above can be simplified to:
43
+
44
+ ```ts
45
+ User({id: 1}) // Is the same as User().where(User.id.is(1))
46
+ ```
47
+
48
+ This comes in handy when joining tables too:
49
+
50
+ ```ts
51
+ User({id: 1}).innerJoin(Post({userId: User.id}))
52
+ // Is equal to:
53
+ User().innerJoin(Post, Post.userId.is(User.id)).where(User.id.is(1))
54
+ ```
55
+
56
+ #### Select
57
+
58
+ Retrieve a single field
59
+
60
+ ```ts
61
+ User().select(User.name)
62
+ ```
63
+
64
+ Or an object of data
65
+
66
+ ```ts
67
+ User().select({
68
+ ...User, // Get all user fields: id, username
69
+ posts: Post({userId: User.id}) // Aggregate posts of this user
70
+ })
71
+ ```
72
+
73
+ You can call the `posts` helper method defined in the example schema to achieve
74
+ the same:
75
+
76
+ ```ts
77
+ User().select({
78
+ ...User,
79
+ posts: User.posts()
80
+ })
81
+ ```
82
+
83
+ Retrieve all posts with their author and tags and count the tag usage in other
84
+ posts:
85
+
86
+ ```ts
87
+ import {count} from 'rado/sqlite'
88
+ // Alias tables using `as`
89
+ const pt = PostTags().as('pt')
90
+ Post().select({
91
+ ...Post,
92
+ author: Post.author().select(User.username),
93
+ tags: Post.tags().select({
94
+ count: pt({tagId: Tag.id}).select(count()).sure(),
95
+ name: Tag.name
96
+ })
97
+ })
98
+ ```
99
+
100
+ Join other tables and select fields from either table:
101
+
102
+ ```ts
103
+ User({id: 1}).innerJoin(Post({userId: User.id})).select({
104
+ username: User.username
105
+ post: Post
106
+ })
107
+ ```
108
+
109
+ Create expressions to select complex values:
110
+
111
+ ```ts
112
+ Person().select({
113
+ name: Person.firstName.concat(' ').concat(Person.lastName),
114
+ isMario: Person.firstName.is('Mario')
115
+ })
116
+ ```
117
+
118
+ Order, group, limit queries:
119
+
120
+ ```ts
121
+ User()
122
+ .orderBy(User.username.asc())
123
+ .groupBy(User.id, User.username)
124
+ .skip(20)
125
+ .take(10)
126
+ ```
127
+
128
+ #### Insert
129
+
130
+ Insert a single row and return the result:
131
+
132
+ ```ts
133
+ User().insertOne({username: 'Mario'})
134
+ ```
135
+
136
+ Insert multiple rows:
137
+
138
+ ```ts
139
+ User().insertAll([{username: 'Mario'}, {username: 'Luigi'}])
140
+ ```
141
+
142
+ #### Update
143
+
144
+ Set values
145
+
146
+ ```ts
147
+ User({id: 1}).set({username: 'Bowser'})
148
+ ```
149
+
150
+ Use expressions to update complex values:
151
+
152
+ ```ts
153
+ User()
154
+ .set({username: User.username.concat(' [removed]')})
155
+ .where(User.deleted)
156
+ ```
157
+
158
+ #### Delete
159
+
160
+ Delete rows
161
+
162
+ ```ts
163
+ User({id: 1}).delete()
164
+ ```
165
+
166
+ ## Schema definition
167
+
168
+ ```ts
169
+ import {table, column} from 'rado'
170
+
171
+ const User = table({
172
+ // Pass a definition under a key with the actual name of the table in database
173
+ user: class {
174
+ id = column.integer().primaryKey()
175
+ username = column.string()
176
+
177
+ // Define helper methods directly on the model
178
+ posts() {
179
+ return Post({userId: this.id})
180
+ }
181
+ }
182
+ })
183
+
184
+ const Post = table({
185
+ post: class {
186
+ id = column.integer().primaryKey()
187
+ userId = column.integer().references(() => User.id)
188
+ content = column.string()
189
+
190
+ author() {
191
+ return User({id: this.userId}).sure()
192
+ }
193
+
194
+ tags() {
195
+ return Tag({id: PostTags.tagId}).innerJoin(PostTags({postId: this.id}))
196
+ }
197
+ }
198
+ })
199
+
200
+ const Tag = table({
201
+ tag: class {
202
+ id = column.integer().primaryKey()
203
+ name = column.string()
204
+ }
205
+ })
206
+
207
+ const PostTags = table({
208
+ post_tag: class {
209
+ postId = column.integer().references(() => Post.id)
210
+ tagId = column.integer().references(() => Tag.id)
211
+ }
212
+ })
213
+ ```
214
+
215
+ ## Connections
216
+
217
+ Currently supported SQLite drivers:
218
+ `better-sqlite3`, `sql.js`, `sqlite3`, `bun:sqlite`
219
+
220
+ Pass an instance of the database to the `connect` function to get started:
221
+
222
+ ```ts
223
+ import Database from 'better-sqlite3'
224
+ import {connect} from 'rado/driver/better-sqlite3'
225
+
226
+ const db = connect(new Database('foobar.db'))
227
+ ```
228
+
229
+ Currently all drivers are synchronous except for the `sqlite3` driver which is
230
+ async.
231
+
232
+ #### Run queries
233
+
234
+ Call the connection with a query to retrieve its results:
235
+
236
+ ```ts
237
+ const posts = db(Post())
238
+ // For async drivers this will be a Promise:
239
+ const posts = await db(Post())
240
+ ```
241
+
242
+ #### Iterate results
243
+
244
+ Iterate over query results:
245
+
246
+ ```ts
247
+ const allPosts = Post()
248
+ for (const row of db.iterate(allPosts)) {
249
+ console.log(row)
250
+ }
251
+ // For async drivers:
252
+ for await (const row of db.iterate(allPosts)) {
253
+ console.log(row)
254
+ }
255
+ ```
256
+
257
+ #### Transactions
258
+
259
+ Run transactions within the `transaction` method which will isolate a connection
260
+ for you and can optionally return a result:
261
+
262
+ ```ts
263
+ const firstUserId = db.transaction(tx => {
264
+ const createTable = User().create()
265
+ tx(createTable)
266
+ const insertUser = User().insertOne({username: 'Mario'})
267
+ tx(insertUser)
268
+ return insertUser.id
269
+ })
270
+ ```
@@ -15,44 +15,44 @@ interface PartialColumnData {
15
15
  primaryKey?: boolean;
16
16
  unique?: boolean;
17
17
  references?: () => ExprData;
18
+ enumerable?: boolean;
18
19
  }
19
20
  export interface ColumnData extends PartialColumnData {
20
21
  type: ColumnType;
21
22
  name: string;
22
23
  }
23
- export declare class Column<T> {
24
+ export declare class Column<T> extends Expr<T> {
24
25
  data: PartialColumnData;
25
26
  constructor(data: PartialColumnData);
26
27
  name(name: string): Column<T>;
27
28
  nullable(): Column<T | null>;
28
- autoIncrement(): Column<Column.IsOptional<T>>;
29
- primaryKey<K extends string>(create?: () => EV<T>): Column<Column.IsPrimary<T, K>>;
29
+ autoIncrement(): OptionalColumn<T>;
30
+ primaryKey<K = string>(create?: () => EV<T>): PrimaryColumn<T, K>;
30
31
  references<X extends T>(column: Expr<X> | (() => Expr<X>)): Column<X>;
31
32
  unique(): Column<T>;
32
- defaultValue(create: () => EV<T>): Column<Column.IsOptional<T>>;
33
- defaultValue(value: EV<T>): Column<Column.IsOptional<T>>;
33
+ defaultValue(create: () => EV<T>): OptionalColumn<T>;
34
+ defaultValue(value: EV<T>): OptionalColumn<T>;
34
35
  }
35
- export type PrimaryKey<T, K> = string extends K ? T : T & {
36
- [Column.isPrimary]: K;
37
- };
38
36
  export declare namespace Column {
39
37
  const isOptional: unique symbol;
40
- type IsOptional<T> = {
41
- [isOptional]: true;
42
- __t: T;
43
- };
44
38
  const isPrimary: unique symbol;
45
- type IsPrimary<T, K> = {
46
- [isPrimary]: K;
47
- __t: T;
48
- };
49
39
  }
40
+ export declare class OptionalColumn<T> extends Column<T> {
41
+ [Column.isOptional]: true;
42
+ }
43
+ export declare class PrimaryColumn<T, K> extends Column<T> {
44
+ [Column.isPrimary]: K;
45
+ }
46
+ export type PrimaryKey<T, K> = string extends K ? T : T & {
47
+ [Column.isPrimary]: K;
48
+ };
50
49
  export declare const column: {
51
50
  string<T extends string = string>(): Column<T>;
52
51
  integer<T_1 extends number = number>(): Column<T_1>;
53
52
  number<T_2 extends number = number>(): Column<T_2>;
54
53
  boolean<T_3 extends boolean = boolean>(): Column<T_3>;
55
- object<T_4 extends object = object>(): Column<T_4>;
56
- array<T_5 = any>(): Column<T_5[]>;
54
+ json<T_4 = any>(): Column<T_4>;
55
+ object<T_5 extends object = object>(): Column<T_5>;
56
+ array<T_6 = any>(): Column<T_6[]>;
57
57
  };
58
58
  export {};
@@ -1,5 +1,5 @@
1
1
  // src/define/Column.ts
2
- import { ExprData } from "./Expr.js";
2
+ import { Expr, ExprData } from "./Expr.js";
3
3
  var ColumnType = /* @__PURE__ */ ((ColumnType2) => {
4
4
  ColumnType2["String"] = "String";
5
5
  ColumnType2["Integer"] = "Integer";
@@ -8,8 +8,9 @@ var ColumnType = /* @__PURE__ */ ((ColumnType2) => {
8
8
  ColumnType2["Json"] = "Json";
9
9
  return ColumnType2;
10
10
  })(ColumnType || {});
11
- var Column = class {
11
+ var Column = class extends Expr {
12
12
  constructor(data) {
13
+ super(void 0);
13
14
  this.data = data;
14
15
  }
15
16
  name(name) {
@@ -19,10 +20,10 @@ var Column = class {
19
20
  return new Column({ ...this.data, nullable: true });
20
21
  }
21
22
  autoIncrement() {
22
- return new Column({ ...this.data, autoIncrement: true });
23
+ return new OptionalColumn({ ...this.data, autoIncrement: true });
23
24
  }
24
25
  primaryKey(create) {
25
- return new Column({
26
+ return new PrimaryColumn({
26
27
  ...this.data,
27
28
  primaryKey: true,
28
29
  defaultValue: create ? () => ExprData.create(create()) : this.data.defaultValue
@@ -40,7 +41,7 @@ var Column = class {
40
41
  return new Column({ ...this.data, unique: true });
41
42
  }
42
43
  defaultValue(value) {
43
- return new Column({
44
+ return new OptionalColumn({
44
45
  ...this.data,
45
46
  defaultValue: typeof value === "function" ? () => ExprData.create(value()) : ExprData.create(value)
46
47
  });
@@ -48,6 +49,12 @@ var Column = class {
48
49
  };
49
50
  ((Column2) => {
50
51
  })(Column || (Column = {}));
52
+ var OptionalColumn = class extends Column {
53
+ [Column.isOptional];
54
+ };
55
+ var PrimaryColumn = class extends Column {
56
+ [Column.isPrimary];
57
+ };
51
58
  var column = {
52
59
  string() {
53
60
  return new Column({ type: "String" /* String */ });
@@ -61,6 +68,9 @@ var column = {
61
68
  boolean() {
62
69
  return new Column({ type: "Boolean" /* Boolean */ });
63
70
  },
71
+ json() {
72
+ return new Column({ type: "Json" /* Json */ });
73
+ },
64
74
  object() {
65
75
  return new Column({ type: "Json" /* Json */ });
66
76
  },
@@ -71,5 +81,7 @@ var column = {
71
81
  export {
72
82
  Column,
73
83
  ColumnType,
84
+ OptionalColumn,
85
+ PrimaryColumn,
74
86
  column
75
87
  };
@@ -3,18 +3,17 @@ import { CompileOptions } from '../lib/Formatter';
3
3
  import { EV, Expr } from './Expr';
4
4
  import { OrderBy } from './OrderBy';
5
5
  import { Query } from './Query';
6
- import { Schema } from './Schema';
7
6
  import { Selection } from './Selection';
8
- import { Table } from './Table';
9
- import { Update as UpdateSet } from './Update';
7
+ import { Table, TableData } from './Table';
10
8
  export declare class Cursor<T> {
11
9
  [Selection.__cursorType](): T;
12
10
  constructor(query: Query<T>);
13
11
  static all(strings: TemplateStringsArray, ...params: Array<any>): Cursor<any>;
14
12
  query(): Query<T>;
13
+ next<T>(cursor: Cursor<T>): Cursor<T>;
15
14
  on(driver: Driver.Sync): T;
16
15
  on(driver: Driver.Async): Promise<T>;
17
- compile(driver: Driver, options?: CompileOptions): import("..").Statement;
16
+ toSql(driver: Driver, options?: CompileOptions): string;
18
17
  toJSON(): Query<T>;
19
18
  }
20
19
  export declare namespace Cursor {
@@ -26,62 +25,74 @@ export declare namespace Cursor {
26
25
  take(limit: number | undefined): Delete;
27
26
  skip(offset: number | undefined): Delete;
28
27
  }
29
- class Update<T> extends Cursor<{
28
+ class Update<Definition> extends Cursor<{
30
29
  rowsAffected: number;
31
30
  }> {
32
31
  query(): Query.Update;
33
- set(set: UpdateSet<T>): Update<T>;
34
- where(...where: Array<EV<boolean>>): Update<T>;
35
- take(limit: number | undefined): Update<T>;
36
- skip(offset: number | undefined): Update<T>;
32
+ set(set: Table.Update<Definition>): Update<Definition>;
33
+ where(...where: Array<EV<boolean>>): Update<Definition>;
34
+ take(limit: number | undefined): Update<Definition>;
35
+ skip(offset: number | undefined): Update<Definition>;
37
36
  }
38
37
  class InsertValuesReturning<T> extends Cursor<T> {
39
38
  }
40
- class InsertValues extends Cursor<{
39
+ class Inserted extends Cursor<{
41
40
  rowsAffected: number;
42
41
  }> {
43
42
  query(): Query.Insert;
44
43
  returning<X extends Selection>(selection: X): InsertValuesReturning<Selection.Infer<X>>;
45
44
  }
46
- class Insert<T> {
47
- protected into: Schema;
48
- constructor(into: Schema);
49
- values(...data: Array<Table.Insert<T>>): InsertValues;
45
+ class Insert<Definition> {
46
+ protected into: TableData;
47
+ constructor(into: TableData);
48
+ selection(query: Cursor.SelectMultiple<Table.Select<Definition>>): Inserted;
49
+ values(...data: Array<Table.Insert<Definition>>): Inserted;
50
50
  }
51
51
  class CreateTable extends Cursor<void> {
52
- protected table: Schema;
53
- constructor(table: Schema);
52
+ protected table: TableData;
53
+ constructor(table: TableData);
54
54
  }
55
55
  class Batch<T = void> extends Cursor<T> {
56
56
  protected queries: Array<Query>;
57
+ query(): Query.Batch;
57
58
  constructor(queries: Array<Query>);
59
+ next<T>(cursor: Cursor<T>): Cursor<T>;
58
60
  }
59
- class SelectMultiple<T> extends Cursor<Array<T>> {
61
+ class SelectMultiple<Row> extends Cursor<Array<Row>> {
60
62
  query(): Query.Select;
61
- leftJoin<C>(that: Table<C>, ...on: Array<EV<boolean>>): SelectMultiple<T>;
62
- innerJoin<C>(that: Table<C>, ...on: Array<EV<boolean>>): SelectMultiple<T>;
63
+ leftJoin<C>(that: Table<C> | TableSelect<C>, ...on: Array<EV<boolean>>): SelectMultiple<Row>;
64
+ innerJoin<C>(that: Table<C> | TableSelect<C>, ...on: Array<EV<boolean>>): SelectMultiple<Row>;
63
65
  select<X extends Selection>(selection: X): SelectMultiple<Selection.Infer<X>>;
64
66
  count(): SelectSingle<number>;
65
- orderBy(...orderBy: Array<Expr<any> | OrderBy>): SelectMultiple<T>;
66
- groupBy(...groupBy: Array<Expr<any>>): SelectMultiple<T>;
67
- first(): SelectSingle<T | undefined>;
68
- sure(): SelectSingle<T>;
69
- where(...where: Array<EV<boolean>>): SelectMultiple<T>;
70
- take(limit: number | undefined): SelectMultiple<T>;
71
- skip(offset: number | undefined): SelectMultiple<T>;
72
- toExpr(): Expr<T>;
67
+ orderBy(...orderBy: Array<Expr<any> | OrderBy>): SelectMultiple<Row>;
68
+ groupBy(...groupBy: Array<Expr<any>>): SelectMultiple<Row>;
69
+ first(): SelectSingle<Row | undefined>;
70
+ sure(): SelectSingle<Row>;
71
+ where(...where: Array<EV<boolean>>): SelectMultiple<Row>;
72
+ take(limit: number | undefined): SelectMultiple<Row>;
73
+ skip(offset: number | undefined): SelectMultiple<Row>;
73
74
  }
74
- class SelectSingle<T> extends Cursor<T> {
75
+ class TableSelect<Definition> extends SelectMultiple<Table.Select<Definition>> {
76
+ protected table: TableData;
77
+ constructor(table: TableData, conditions?: Array<EV<boolean>>);
78
+ as(alias: string): Table<Definition>;
79
+ create(): CreateTable;
80
+ insertSelect(query: SelectMultiple<Table.Insert<Definition>>): Inserted;
81
+ insertOne(record: Table.Insert<Definition>): Cursor<Table.Select<Definition>>;
82
+ insertAll(data: Array<Table.Insert<Definition>>): Inserted;
83
+ set(data: Table.Update<Definition>): Update<Definition>;
84
+ delete(): Delete;
85
+ }
86
+ class SelectSingle<Row> extends Cursor<Row> {
75
87
  query(): Query.Select;
76
- leftJoin<C>(that: Table<C>, ...on: Array<EV<boolean>>): SelectSingle<T>;
77
- innerJoin<C>(that: Table<C>, ...on: Array<EV<boolean>>): SelectSingle<T>;
88
+ leftJoin<C>(that: Table<C> | TableSelect<C>, ...on: Array<EV<boolean>>): SelectSingle<Row>;
89
+ innerJoin<C>(that: Table<C> | TableSelect<C>, ...on: Array<EV<boolean>>): SelectSingle<Row>;
78
90
  select<X extends Selection>(selection: X): SelectSingle<Selection.Infer<X>>;
79
- orderBy(...orderBy: Array<OrderBy>): SelectSingle<T>;
80
- groupBy(...groupBy: Array<Expr<any>>): SelectSingle<T>;
81
- where(...where: Array<EV<boolean>>): SelectSingle<T>;
82
- take(limit: number | undefined): SelectSingle<T>;
83
- skip(offset: number | undefined): SelectSingle<T>;
84
- all(): SelectMultiple<T>;
85
- toExpr(): Expr<T>;
91
+ orderBy(...orderBy: Array<OrderBy>): SelectSingle<Row>;
92
+ groupBy(...groupBy: Array<Expr<any>>): SelectSingle<Row>;
93
+ where(...where: Array<EV<boolean>>): SelectSingle<Row>;
94
+ take(limit: number | undefined): SelectSingle<Row>;
95
+ skip(offset: number | undefined): SelectSingle<Row>;
96
+ all(): SelectMultiple<Row>;
86
97
  }
87
98
  }