rado 0.3.3 → 0.3.4

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.
Files changed (87) hide show
  1. package/README.md +277 -277
  2. package/dist/define/CTE.d.ts +2 -0
  3. package/dist/define/CTE.js +51 -0
  4. package/dist/define/Cursor.d.ts +108 -0
  5. package/dist/define/Cursor.js +338 -0
  6. package/dist/define/Table.d.ts +5 -1
  7. package/dist/define/Table.js +5 -0
  8. package/dist/define/Update.d.ts +4 -0
  9. package/dist/define/Update.js +0 -0
  10. package/dist/define/query/Batch.d.ts +7 -0
  11. package/dist/define/query/Batch.js +17 -0
  12. package/dist/define/query/CreateTable.d.ts +6 -0
  13. package/dist/define/query/CreateTable.js +11 -0
  14. package/dist/define/query/Delete.d.ts +11 -0
  15. package/dist/define/query/Delete.js +18 -0
  16. package/dist/define/query/Insert.d.ts +22 -0
  17. package/dist/define/query/Insert.js +47 -0
  18. package/dist/define/query/Select.d.ts +39 -0
  19. package/dist/define/query/Select.js +170 -0
  20. package/dist/define/query/TableSelect.d.ts +28 -0
  21. package/dist/define/query/TableSelect.js +79 -0
  22. package/dist/define/query/Union.d.ts +17 -0
  23. package/dist/define/query/Union.js +116 -0
  24. package/dist/define/query/Update.d.ts +12 -0
  25. package/dist/define/query/Update.js +18 -0
  26. package/dist/deno/driver/better-sqlite3.js +57 -0
  27. package/dist/deno/driver/bun-sqlite.js +69 -0
  28. package/dist/deno/driver/sql.js.js +72 -0
  29. package/dist/deno/driver/sqlite3.js +123 -0
  30. package/dist/deno/index.js +14 -0
  31. package/dist/deno/lib/Driver.js +330 -0
  32. package/dist/deno/lib/Formatter.js +715 -0
  33. package/dist/deno/lib/Sanitizer.js +0 -0
  34. package/dist/deno/lib/SqlError.js +11 -0
  35. package/dist/deno/lib/Statement.js +108 -0
  36. package/dist/deno/sqlite/SqliteFormatter.js +70 -0
  37. package/dist/deno/sqlite/SqliteFunctions.js +150 -0
  38. package/dist/deno/sqlite/SqliteSchema.js +47 -0
  39. package/dist/deno/sqlite.js +3 -0
  40. package/dist/deno/util/Callable.js +15 -0
  41. package/dist/lib/Formatter.js +17 -3
  42. package/dist/sqlite/index.d.ts +2 -0
  43. package/dist/sqlite/index.js +3 -0
  44. package/dist/src/driver/better-sqlite3.js +49 -0
  45. package/dist/src/driver/sql.js.js +66 -0
  46. package/dist/src/driver/sqlite3.js +87 -0
  47. package/dist/src/index.js +24 -0
  48. package/dist/src/lib/Column.js +63 -0
  49. package/dist/src/lib/Cursor.js +231 -0
  50. package/dist/src/lib/Driver.js +316 -0
  51. package/dist/src/lib/Expr.js +284 -0
  52. package/dist/src/lib/Fields.js +0 -0
  53. package/dist/src/lib/Formatter.js +625 -0
  54. package/dist/src/lib/Functions.js +11 -0
  55. package/dist/src/lib/Id.js +0 -0
  56. package/dist/src/lib/Index.js +20 -0
  57. package/dist/src/lib/Ops.js +45 -0
  58. package/dist/src/lib/OrderBy.js +9 -0
  59. package/dist/src/lib/Param.js +27 -0
  60. package/dist/src/lib/Query.js +74 -0
  61. package/dist/src/lib/Sanitizer.js +0 -0
  62. package/dist/src/lib/Schema.js +67 -0
  63. package/dist/src/lib/Selection.js +7 -0
  64. package/dist/src/lib/Statement.js +103 -0
  65. package/dist/src/lib/Table.js +130 -0
  66. package/dist/src/lib/Target.js +37 -0
  67. package/dist/src/lib/Update.js +0 -0
  68. package/dist/src/sqlite/SqliteFormatter.js +67 -0
  69. package/dist/src/sqlite/SqliteFunctions.js +146 -0
  70. package/dist/src/sqlite/SqliteSchema.js +47 -0
  71. package/dist/src/sqlite.js +3 -0
  72. package/dist/test/DbSuite.js +28 -0
  73. package/dist/test/TSTestTypes.js +6 -0
  74. package/dist/test/TestBasic.js +172 -0
  75. package/dist/test/TestExtend.js +68 -0
  76. package/dist/test/TestFts5.js +34 -0
  77. package/dist/test/TestFunctions.js +28 -0
  78. package/dist/test/TestJoins.js +34 -0
  79. package/dist/test/TestJson.js +33 -0
  80. package/dist/test/TestPrepare.js +27 -0
  81. package/dist/test/TestRawSql.js +31 -0
  82. package/dist/test/TestSchema.js +69 -0
  83. package/dist/test/TestSubQueries.js +79 -0
  84. package/dist/test/TestTransaction.js +54 -0
  85. package/dist/test/TestUpdate.js +95 -0
  86. package/dist/test/index.js +14 -0
  87. package/package.json +74 -74
package/README.md CHANGED
@@ -1,277 +1,277 @@
1
- [![npm](https://img.shields.io/npm/v/rado.svg)](https://npmjs.org/package/rado)
2
-
3
- # rado
4
-
5
- Fully typed, lightweight TypeScript query builder.
6
- Currently focused on SQLite.
7
-
8
- - Definition via TypeScript types
9
- - Composable queries
10
- - Aggregate rows in selects
11
- - First class JSON columns
12
- - No code generation step
13
- - No dependencies
14
-
15
- <pre>npm install <a href="https://www.npmjs.com/package/rado">rado</a></pre>
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 below
74
- to achieve 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()).first(),
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
- import {iif} from 'rado/sqlite'
113
- Person().select({
114
- name: Person.firstName.concat(' ').concat(Person.lastName),
115
- isMario: Person.firstName.is('Mario'),
116
- isActor: Person.id.isIn(Actor().select(Actor.personId)),
117
- email: iif(Person.email.isNotNull(), Person.email, 'its.me@mario'),
118
- twitterHandle: Person.socialMedia
119
- .filter(media => media.type.is('twitter'))
120
- .map(media => media.handle)
121
- .maybeFirst()
122
- })
123
- ```
124
-
125
- Order, group, limit queries:
126
-
127
- ```ts
128
- User()
129
- .orderBy(User.username.asc())
130
- .groupBy(User.id, User.username)
131
- .skip(20)
132
- .take(10)
133
- ```
134
-
135
- #### Insert
136
-
137
- Insert a single row and return the result:
138
-
139
- ```ts
140
- User().insertOne({username: 'Mario'})
141
- ```
142
-
143
- Insert multiple rows:
144
-
145
- ```ts
146
- User().insertAll([{username: 'Mario'}, {username: 'Luigi'}])
147
- ```
148
-
149
- #### Update
150
-
151
- Set values
152
-
153
- ```ts
154
- User({id: 1}).set({username: 'Bowser'})
155
- ```
156
-
157
- Use expressions to update complex values:
158
-
159
- ```ts
160
- User()
161
- .set({username: User.username.concat(' [removed]')})
162
- .where(User.deleted)
163
- ```
164
-
165
- #### Delete
166
-
167
- Delete rows
168
-
169
- ```ts
170
- User({id: 1}).delete()
171
- ```
172
-
173
- ## Schema definition
174
-
175
- ```ts
176
- import {table, column} from 'rado'
177
-
178
- const User = table({
179
- // Pass a definition under a key with the actual name of the table in database
180
- user: class {
181
- id = column.integer.primaryKey()
182
- username = column.string
183
-
184
- // Define helper methods directly on the model
185
- posts() {
186
- return Post({userId: this.id})
187
- }
188
- }
189
- })
190
-
191
- const Post = table({
192
- post: class {
193
- id = column.integer.primaryKey()
194
- userId = column.integer.references(() => User.id)
195
- content = column.string
196
-
197
- author() {
198
- return User({id: this.userId}).first()
199
- }
200
-
201
- tags() {
202
- return Tag({id: PostTags.tagId}).innerJoin(PostTags({postId: this.id}))
203
- }
204
- }
205
- })
206
-
207
- const Tag = table({
208
- tag: class {
209
- id = column.integer.primaryKey()
210
- name = column.string
211
- }
212
- })
213
-
214
- const PostTags = table({
215
- post_tag: class {
216
- postId = column.integer.references(() => Post.id)
217
- tagId = column.integer.references(() => Tag.id)
218
- }
219
- })
220
- ```
221
-
222
- ## Connections
223
-
224
- Currently supported SQLite drivers:
225
- `better-sqlite3`, `sql.js`, `sqlite3`, `bun:sqlite`
226
-
227
- Pass an instance of the database to the `connect` function to get started:
228
-
229
- ```ts
230
- import Database from 'better-sqlite3'
231
- import {connect} from 'rado/driver/better-sqlite3'
232
-
233
- const db = connect(new Database('foobar.db'))
234
- ```
235
-
236
- Currently all drivers are synchronous except for the `sqlite3` driver which is
237
- async.
238
-
239
- #### Run queries
240
-
241
- Call the connection with a query to retrieve its results:
242
-
243
- ```ts
244
- const posts = db(Post())
245
- // For async drivers this will be a Promise:
246
- const posts = await db(Post())
247
- ```
248
-
249
- #### Iterate results
250
-
251
- Iterate over query results:
252
-
253
- ```ts
254
- const allPosts = Post()
255
- for (const row of db.iterate(allPosts)) {
256
- console.log(row)
257
- }
258
- // For async drivers:
259
- for await (const row of db.iterate(allPosts)) {
260
- console.log(row)
261
- }
262
- ```
263
-
264
- #### Transactions
265
-
266
- Run transactions within the `transaction` method which will isolate a connection
267
- and can optionally return a result:
268
-
269
- ```ts
270
- const firstUserId = db.transaction(tx => {
271
- const createTable = User().create()
272
- tx(createTable)
273
- const insertUser = User().insertOne({username: 'Mario'})
274
- tx(insertUser)
275
- return insertUser.id
276
- })
277
- ```
1
+ [![npm](https://img.shields.io/npm/v/rado.svg)](https://npmjs.org/package/rado)
2
+
3
+ # rado
4
+
5
+ Fully typed, lightweight TypeScript query builder.
6
+ Currently focused on SQLite.
7
+
8
+ - Definition via TypeScript types
9
+ - Composable queries
10
+ - Aggregate rows in selects
11
+ - First class JSON columns
12
+ - No code generation step
13
+ - No dependencies
14
+
15
+ <pre>npm install <a href="https://www.npmjs.com/package/rado">rado</a></pre>
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 below
74
+ to achieve 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()).first(),
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
+ import {iif} from 'rado/sqlite'
113
+ Person().select({
114
+ name: Person.firstName.concat(' ').concat(Person.lastName),
115
+ isMario: Person.firstName.is('Mario'),
116
+ isActor: Person.id.isIn(Actor().select(Actor.personId)),
117
+ email: iif(Person.email.isNotNull(), Person.email, 'its.me@mario'),
118
+ twitterHandle: Person.socialMedia
119
+ .filter(media => media.type.is('twitter'))
120
+ .map(media => media.handle)
121
+ .maybeFirst()
122
+ })
123
+ ```
124
+
125
+ Order, group, limit queries:
126
+
127
+ ```ts
128
+ User()
129
+ .orderBy(User.username.asc())
130
+ .groupBy(User.id, User.username)
131
+ .skip(20)
132
+ .take(10)
133
+ ```
134
+
135
+ #### Insert
136
+
137
+ Insert a single row and return the result:
138
+
139
+ ```ts
140
+ User().insertOne({username: 'Mario'})
141
+ ```
142
+
143
+ Insert multiple rows:
144
+
145
+ ```ts
146
+ User().insertAll([{username: 'Mario'}, {username: 'Luigi'}])
147
+ ```
148
+
149
+ #### Update
150
+
151
+ Set values
152
+
153
+ ```ts
154
+ User({id: 1}).set({username: 'Bowser'})
155
+ ```
156
+
157
+ Use expressions to update complex values:
158
+
159
+ ```ts
160
+ User()
161
+ .set({username: User.username.concat(' [removed]')})
162
+ .where(User.deleted)
163
+ ```
164
+
165
+ #### Delete
166
+
167
+ Delete rows
168
+
169
+ ```ts
170
+ User({id: 1}).delete()
171
+ ```
172
+
173
+ ## Schema definition
174
+
175
+ ```ts
176
+ import {table, column} from 'rado'
177
+
178
+ const User = table({
179
+ // Pass a definition under a key with the actual name of the table in database
180
+ user: class {
181
+ id = column.integer.primaryKey()
182
+ username = column.string
183
+
184
+ // Define helper methods directly on the model
185
+ posts() {
186
+ return Post({userId: this.id})
187
+ }
188
+ }
189
+ })
190
+
191
+ const Post = table({
192
+ post: class {
193
+ id = column.integer.primaryKey()
194
+ userId = column.integer.references(() => User.id)
195
+ content = column.string
196
+
197
+ author() {
198
+ return User({id: this.userId}).first()
199
+ }
200
+
201
+ tags() {
202
+ return Tag({id: PostTags.tagId}).innerJoin(PostTags({postId: this.id}))
203
+ }
204
+ }
205
+ })
206
+
207
+ const Tag = table({
208
+ tag: class {
209
+ id = column.integer.primaryKey()
210
+ name = column.string
211
+ }
212
+ })
213
+
214
+ const PostTags = table({
215
+ post_tag: class {
216
+ postId = column.integer.references(() => Post.id)
217
+ tagId = column.integer.references(() => Tag.id)
218
+ }
219
+ })
220
+ ```
221
+
222
+ ## Connections
223
+
224
+ Currently supported SQLite drivers:
225
+ `better-sqlite3`, `sql.js`, `sqlite3`, `bun:sqlite`
226
+
227
+ Pass an instance of the database to the `connect` function to get started:
228
+
229
+ ```ts
230
+ import Database from 'better-sqlite3'
231
+ import {connect} from 'rado/driver/better-sqlite3'
232
+
233
+ const db = connect(new Database('foobar.db'))
234
+ ```
235
+
236
+ Currently all drivers are synchronous except for the `sqlite3` driver which is
237
+ async.
238
+
239
+ #### Run queries
240
+
241
+ Call the connection with a query to retrieve its results:
242
+
243
+ ```ts
244
+ const posts = db(Post())
245
+ // For async drivers this will be a Promise:
246
+ const posts = await db(Post())
247
+ ```
248
+
249
+ #### Iterate results
250
+
251
+ Iterate over query results:
252
+
253
+ ```ts
254
+ const allPosts = Post()
255
+ for (const row of db.iterate(allPosts)) {
256
+ console.log(row)
257
+ }
258
+ // For async drivers:
259
+ for await (const row of db.iterate(allPosts)) {
260
+ console.log(row)
261
+ }
262
+ ```
263
+
264
+ #### Transactions
265
+
266
+ Run transactions within the `transaction` method which will isolate a connection
267
+ and can optionally return a result:
268
+
269
+ ```ts
270
+ const firstUserId = db.transaction(tx => {
271
+ const createTable = User().create()
272
+ tx(createTable)
273
+ const insertUser = User().insertOne({username: 'Mario'})
274
+ tx(insertUser)
275
+ return insertUser.id
276
+ })
277
+ ```
@@ -0,0 +1,2 @@
1
+ import { Query, QueryData } from './Query';
2
+ export declare function makeRecursiveUnion<T extends Query<any>>(initial: QueryData.Select, createUnion: (target: any) => T, operator: QueryData.UnionOperation): QueryData.Select;
@@ -0,0 +1,51 @@
1
+ // src/define/CTE.ts
2
+ import { Expr, ExprData, ExprType } from "./Expr.js";
3
+ import { Query, QueryData } from "./Query.js";
4
+ import { virtualTable } from "./Table.js";
5
+ import { Target, TargetType } from "./Target.js";
6
+ var { keys, create, fromEntries } = Object;
7
+ function columnsOf(expr) {
8
+ switch (expr.type) {
9
+ case ExprType.Record:
10
+ return keys(expr.fields);
11
+ case ExprType.Row:
12
+ switch (expr.target.type) {
13
+ case TargetType.Table:
14
+ return keys(expr.target.table.columns);
15
+ default:
16
+ throw new Error("Could not retrieve CTE columns");
17
+ }
18
+ default:
19
+ throw new Error("Could not retrieve CTE columns");
20
+ }
21
+ }
22
+ function makeRecursiveUnion(initial, createUnion, operator) {
23
+ let name;
24
+ const alias = new Proxy(create(null), {
25
+ get(_, key) {
26
+ name = key;
27
+ return virtualTable(name);
28
+ }
29
+ });
30
+ const right = createUnion(alias)[Query.Data];
31
+ if (!name)
32
+ throw new TypeError("No CTE name provided");
33
+ const cte = alias[name];
34
+ const cols = columnsOf(initial.selection);
35
+ const selection = new ExprData.Record(
36
+ fromEntries(cols.map((col) => [col, cte[col][Expr.Data]]))
37
+ );
38
+ const union = new QueryData.Union({
39
+ a: initial,
40
+ operator,
41
+ b: right
42
+ });
43
+ const from = new Target.CTE(name, union);
44
+ return new QueryData.Select({
45
+ selection,
46
+ from
47
+ });
48
+ }
49
+ export {
50
+ makeRecursiveUnion
51
+ };