@sigitex/outlaw 1.0.1 → 2.0.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 +193 -6
- package/package.json +6 -6
- package/src/api/DatabaseTable.ts +57 -31
- package/src/api/DatabaseView.ts +36 -13
- package/src/api/UnsupportedTransactionError.ts +7 -0
- package/src/api/api.types.ts +73 -100
- package/src/api/createDatabase.ts +31 -6
- package/src/api/index.ts +1 -0
- package/src/bun/bun.ts +120 -7
- package/src/cowboyMigration/CowboyConnection.ts +45 -13
- package/src/framework/Format.ts +4 -0
- package/src/framework/definitions.d.ts +4 -1
- package/src/queryBuilder/DeleteBuilder.ts +6 -4
- package/src/queryBuilder/Mappings.ts +1 -1
- package/src/queryBuilder/Projection.ts +83 -0
- package/src/queryBuilder/QueryScope.ts +272 -0
- package/src/queryBuilder/QuerySource.ts +231 -0
- package/src/queryBuilder/QuerySourceBuilder.ts +76 -0
- package/src/queryBuilder/SelectBuilder.ts +14 -20
- package/src/queryBuilder/SelectQueryBuilder.ts +61 -72
- package/src/queryBuilder/UpdateBuilder.ts +6 -4
- package/src/queryBuilder/index.ts +2 -0
- package/src/queryBuilder/queryBuilders.types.ts +21 -16
- package/src/queryGenerator/Clause.ts +36 -35
- package/src/queryGenerator/generateSelect.ts +38 -73
- package/src/schemaBuilder/ColumnRef.ts +36 -0
- package/src/schemaBuilder/columnBuilders.ts +7 -2
- package/src/schemaBuilder/createTable.ts +33 -26
- package/src/schemaBuilder/createView.ts +24 -12
- package/src/schemaBuilder/index.ts +1 -0
- package/src/schemaBuilder/metadata.ts +12 -6
- package/src/schemaBuilder/schemaBuilder.types.ts +57 -30
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ bun add @sigitex/outlaw
|
|
|
8
8
|
|
|
9
9
|
Attempts to mirror Sqlite syntax *very* closely. Currently works with Bun and Cloudflare Functions.
|
|
10
10
|
|
|
11
|
-
Define your schema in code, and Outlaw's [cowboy migrations](#cowboy-migrations) automatically diff and converge your database on startup
|
|
11
|
+
Define your schema in code, and Outlaw's [cowboy migrations](#cowboy-migrations) automatically diff and converge your database on startup.
|
|
12
12
|
|
|
13
13
|
> **Note:** This package exports TypeScript sources directly. A TypeScript-compatible runtime or bundler (Bun, etc.) is required.
|
|
14
14
|
|
|
@@ -155,6 +155,9 @@ Outlaw abstracts over any SQLite connection via the `Connection` interface:
|
|
|
155
155
|
type Connection = {
|
|
156
156
|
query<Row>(sql: string): Promise<Row[]>
|
|
157
157
|
script(statements: string[]): Promise<void>
|
|
158
|
+
transaction?<Result>(
|
|
159
|
+
work: (connection: TransactionalConnection) => Promise<Result>,
|
|
160
|
+
): Promise<Result>
|
|
158
161
|
}
|
|
159
162
|
```
|
|
160
163
|
|
|
@@ -188,6 +191,25 @@ import { createDatabase } from "@sigitex/outlaw"
|
|
|
188
191
|
const db = createDatabase(connection, schema)
|
|
189
192
|
```
|
|
190
193
|
|
|
194
|
+
Every database API exposes `transaction`. Supported connections provide a fresh
|
|
195
|
+
database API bound to the transaction-scoped connection:
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
await db.transaction(async (tx) => {
|
|
199
|
+
await tx.users.insert({ name: "Wyatt" }).execute()
|
|
200
|
+
|
|
201
|
+
await tx.transaction(async (nested) => {
|
|
202
|
+
await nested.products.insert({ name: "Hat" }).execute()
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Use only the callback's scoped database API until that callback ends. Bun
|
|
208
|
+
serializes root operations, uses `BEGIN IMMEDIATE`, and implements nested
|
|
209
|
+
callbacks with savepoints. Cloudflare D1 does not support interactive callback
|
|
210
|
+
transactions; `transaction` throws `UnsupportedTransactionError` before the
|
|
211
|
+
callback runs, while `script()` remains available through D1 batch.
|
|
212
|
+
|
|
191
213
|
### Select
|
|
192
214
|
|
|
193
215
|
```ts
|
|
@@ -216,17 +238,182 @@ db.users.select("*")
|
|
|
216
238
|
|
|
217
239
|
### Joins
|
|
218
240
|
|
|
241
|
+
Compose sources **before** selecting results. Examples in this section use a
|
|
242
|
+
`Connection` named `connection` and these schema definitions:
|
|
243
|
+
|
|
219
244
|
```ts
|
|
220
|
-
|
|
221
|
-
|
|
245
|
+
const users = createTable("users", {
|
|
246
|
+
id: integer.primaryKey,
|
|
247
|
+
name: text.notNull,
|
|
248
|
+
managerId: integer,
|
|
249
|
+
})
|
|
250
|
+
const posts = createTable("posts", {
|
|
251
|
+
id: integer.primaryKey,
|
|
252
|
+
userId: integer.notNull,
|
|
253
|
+
tenantId: integer.notNull,
|
|
254
|
+
title: text.notNull,
|
|
255
|
+
published: integer.map.boolean.notNull,
|
|
256
|
+
createdAt: integer.map.timestamp.notNull,
|
|
257
|
+
})
|
|
258
|
+
const profiles = createTable("profiles", {
|
|
259
|
+
userId: integer.notNull,
|
|
260
|
+
tenantId: integer.notNull,
|
|
261
|
+
bio: text,
|
|
262
|
+
})
|
|
263
|
+
const db = createDatabase(connection, createSchema({ users, posts, profiles }))
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Strings work throughout joined queries. Bare names retain SQLite name resolution;
|
|
267
|
+
qualified strings identify a specific source. Only selected columns appear in the
|
|
268
|
+
result:
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
await db.users
|
|
272
|
+
.join(posts)
|
|
273
|
+
.on("users.id", "=", "posts.userId")
|
|
274
|
+
.select("name", "title")
|
|
275
|
+
.where("title", "Hello")
|
|
276
|
+
.orderBy([["name", "asc"]])
|
|
277
|
+
.limit(20)
|
|
278
|
+
.offset(40)
|
|
222
279
|
.fetch()
|
|
280
|
+
```
|
|
223
281
|
|
|
224
|
-
|
|
225
|
-
|
|
282
|
+
Refs and strings can be mixed. Projection aliases are optional; use them when
|
|
283
|
+
distinct output names are useful. `users.all` selects `users.*`, not every joined
|
|
284
|
+
column:
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
await db.users
|
|
288
|
+
.leftJoin(posts)
|
|
289
|
+
.on(users.id, "=", posts.userId)
|
|
290
|
+
.select(users.id.as("userId"), posts.id.as("postId"), posts.title)
|
|
291
|
+
.fetch()
|
|
292
|
+
|
|
293
|
+
await db.users
|
|
294
|
+
.join(posts)
|
|
295
|
+
.on("users.id", "=", posts.userId)
|
|
296
|
+
.select(users.all, posts.title)
|
|
297
|
+
.where(posts.published, true)
|
|
298
|
+
.orderBy([[posts.createdAt, "desc"]])
|
|
299
|
+
.fetch()
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
#### Join forms and constraints
|
|
303
|
+
|
|
304
|
+
`join`, `leftJoin`, `rightJoin`, `fullJoin`, and `crossJoin` each accept tables,
|
|
305
|
+
views, or SELECT subqueries. Constraints are optional for every form. Repeated
|
|
306
|
+
`on()` calls combine with `AND` on the latest join. `using()` accepts one or more
|
|
307
|
+
common column names and cannot be combined with `on()` on that same join.
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
await db.users.rightJoin(posts).on(users.id, "=", posts.userId)
|
|
311
|
+
.select("name", "title").fetch()
|
|
312
|
+
await db.users.fullJoin(posts).on(users.id, "=", posts.userId)
|
|
313
|
+
.select("name", "title").fetch()
|
|
314
|
+
await db.users.crossJoin(posts).select("name", "title").fetch()
|
|
315
|
+
await db.users.join(posts).select("name", "title").fetch()
|
|
316
|
+
|
|
317
|
+
await db.users.crossJoin(posts)
|
|
318
|
+
.on(users.id, "=", posts.userId)
|
|
319
|
+
.on(users.name, "!=", posts.title)
|
|
320
|
+
.select("name", "title").fetch()
|
|
321
|
+
|
|
322
|
+
await db.posts.join(profiles).using("userId", "tenantId").select("*").fetch()
|
|
323
|
+
await db.posts.crossJoin(profiles).using("userId").select("userId").fetch()
|
|
324
|
+
await db.posts.fullJoin(profiles).using("userId").select("userId").fetch()
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Both operands of `on(left, operator, right)` are **column identifiers**, including
|
|
328
|
+
strings. By contrast, the right-hand value of `where(column, value)` or
|
|
329
|
+
`where(column, operator, value)` is always a **literal value**, even if it happens
|
|
330
|
+
to equal a column name. Literal-value ON expressions are not supported.
|
|
331
|
+
|
|
332
|
+
`select("*")` emits an actual `*`, preserving SQLite's common-column suppression
|
|
333
|
+
with `USING`. Qualified refs and wildcards retain each source's own columns.
|
|
334
|
+
Unqualified common keys from right/full joins include surviving right-side values.
|
|
335
|
+
For explicitly projected common columns coalesced by `FULL JOIN ... USING`, Outlaw
|
|
336
|
+
emits a same-name `AS` solely to prevent SQLite from retaining identifier quotes in
|
|
337
|
+
the output key. No source names or distinct output names are invented.
|
|
338
|
+
|
|
339
|
+
`RIGHT JOIN` and `FULL JOIN` require **SQLite 3.39.0 or newer**. Outlaw does not probe
|
|
340
|
+
capabilities, emulate unsupported joins, validate rows, or preflight SQL structure.
|
|
341
|
+
SQLite errors, including ambiguous bare identifiers, propagate from the connection.
|
|
342
|
+
`NATURAL` joins and comma-separated sources are deferred. Join verification uses
|
|
343
|
+
local Bun SQLite; no live D1 verification is claimed.
|
|
344
|
+
|
|
345
|
+
#### Self-joins, subqueries, and views
|
|
346
|
+
|
|
347
|
+
Source aliases are explicit and non-mutating:
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
const managers = users.as("managers")
|
|
351
|
+
await db.users.leftJoin(managers)
|
|
352
|
+
.on(users.managerId, "=", managers.id)
|
|
353
|
+
.select(users.name.as("employeeName"), managers.name.as("managerName"))
|
|
354
|
+
.orderBy([["employeeName", "asc"]])
|
|
355
|
+
.fetch()
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Subqueries expose only their projected outputs. Aliases remain optional; both
|
|
359
|
+
forms execute inline as part of one SELECT. Aliasing or embedding snapshots the
|
|
360
|
+
query, so later changes to the original builder do not alter an embedded query:
|
|
361
|
+
|
|
362
|
+
```ts
|
|
363
|
+
const publishedPosts = db.posts
|
|
364
|
+
.select("id", "userId", "title", "createdAt")
|
|
365
|
+
.where("published", true)
|
|
366
|
+
.as("publishedPosts")
|
|
367
|
+
|
|
368
|
+
await db.users.join(publishedPosts)
|
|
369
|
+
.on(users.id, "=", publishedPosts.userId)
|
|
370
|
+
.select("name", publishedPosts.title, publishedPosts.createdAt)
|
|
371
|
+
.fetch()
|
|
372
|
+
|
|
373
|
+
await db.users.join(db.posts.select("userId", "title"))
|
|
374
|
+
.on("users.id", "=", "userId")
|
|
375
|
+
.select("name", "title")
|
|
226
376
|
.fetch()
|
|
377
|
+
|
|
378
|
+
const userPosts = createView("user_posts",
|
|
379
|
+
users.leftJoin(posts).on(users.id, "=", posts.userId)
|
|
380
|
+
.select(users.name.as("userName"), posts.title),
|
|
381
|
+
)
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Schema-level queries need no connection. Add `userPosts` to `createSchema` to query
|
|
385
|
+
the view through the database API; it exposes only `userName` and nullable `title`.
|
|
386
|
+
|
|
387
|
+
#### Result types and duplicate names
|
|
388
|
+
|
|
389
|
+
Results stay flat. Left joins add `null` to incoming columns; right joins add
|
|
390
|
+
`null` to all accumulated left sources; full joins add it to both sides. Selected
|
|
391
|
+
properties remain required, never optional. For example, selecting `users.name`
|
|
392
|
+
and `posts.title` after a left join yields `{ name: string; title: string | null }`.
|
|
393
|
+
|
|
394
|
+
Mappings follow the selected source and output alias, including through subqueries
|
|
395
|
+
and views. Outer-join SQL nulls stay null; mapped values decode once at execution.
|
|
396
|
+
Filters use the referenced source's storage mapping.
|
|
397
|
+
|
|
398
|
+
Duplicate output names are allowed without aliases, nesting, or automatic
|
|
399
|
+
renaming. The connection's object-row behavior determines which duplicate value
|
|
400
|
+
survives; ordinary collisions have conservative union types. Incompatible mapping
|
|
401
|
+
collisions stay undecoded and are typed as `unknown`. Explicit distinct aliases
|
|
402
|
+
preserve precise field types when both values are needed.
|
|
403
|
+
|
|
404
|
+
#### Migrating the old join chain
|
|
405
|
+
|
|
406
|
+
`db.users.select(...).join(posts).on(...)` is removed, including schema-level
|
|
407
|
+
join-after-select calls. Move `select(...)` after source composition:
|
|
408
|
+
|
|
409
|
+
```ts
|
|
410
|
+
await db.users.join(posts).on(users.id, "=", posts.userId)
|
|
411
|
+
.select(users.name, posts.title).fetch()
|
|
227
412
|
```
|
|
228
413
|
|
|
229
|
-
|
|
414
|
+
Joined columns are no longer implicitly appended to the projection. Select the
|
|
415
|
+
outputs needed explicitly, or use `*`/qualified wildcards. Existing single-table
|
|
416
|
+
SELECT and unrelated INSERT/UPDATE/DELETE chains retain their interfaces.
|
|
230
417
|
|
|
231
418
|
### Insert
|
|
232
419
|
|
package/package.json
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
"description": "A trigger-happy sqlite framework.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Sigitex",
|
|
8
|
-
"url": "
|
|
8
|
+
"url": "https://sigitex.com"
|
|
9
9
|
},
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "https://github.com/sigitex/outlaw.git"
|
|
12
|
+
"url": "git+https://github.com/sigitex/outlaw.git"
|
|
13
13
|
},
|
|
14
14
|
"exports": {
|
|
15
15
|
".": {
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
"@semantic-release/exec": "^7.0.3",
|
|
29
29
|
"@types/bun": "^1.3.13",
|
|
30
30
|
"@types/fast-json-stable-stringify": "^2.1.2",
|
|
31
|
-
"@typescript/native-preview": "beta",
|
|
32
31
|
"husky": "^9.1.7",
|
|
33
32
|
"multi-semantic-release": "^3.1.0",
|
|
34
33
|
"oxfmt": "^0.47.0",
|
|
35
34
|
"oxlint": "^1.62.0",
|
|
36
|
-
"semantic-release": "^25.0.3"
|
|
35
|
+
"semantic-release": "^25.0.3",
|
|
36
|
+
"typescript": "^7.0.2"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@sigitex/print": "*",
|
|
@@ -43,11 +43,11 @@
|
|
|
43
43
|
"scripts": {
|
|
44
44
|
"prepare": "husky",
|
|
45
45
|
"lint": "oxlint",
|
|
46
|
-
"check": "
|
|
46
|
+
"check": "tsc --build",
|
|
47
47
|
"test": "bun test --pass-with-no-tests --tsconfig-override tsconfig.test.json"
|
|
48
48
|
},
|
|
49
49
|
"files": [
|
|
50
50
|
"src"
|
|
51
51
|
],
|
|
52
|
-
"version": "
|
|
52
|
+
"version": "2.0.0"
|
|
53
53
|
}
|
package/src/api/DatabaseTable.ts
CHANGED
|
@@ -1,44 +1,70 @@
|
|
|
1
1
|
// oxlint-disable typescript/no-explicit-any
|
|
2
|
-
import type { Delete, Connection, Insert,
|
|
2
|
+
import type { Delete, Connection, Insert, Update } from "./api.types"
|
|
3
3
|
import type { TableData } from "../schemaBuilder"
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export class DatabaseTable implements TableApi<any> {
|
|
12
|
-
private connection: Connection
|
|
4
|
+
import { DeleteBuilder, InsertBuilder, UpdateBuilder } from "../queryBuilder"
|
|
5
|
+
import { QuerySourceBuilder } from "../queryBuilder/QuerySourceBuilder"
|
|
6
|
+
import type { QuerySource } from "../queryBuilder/QuerySource"
|
|
7
|
+
import type { Projection } from "../queryBuilder/Projection"
|
|
8
|
+
|
|
9
|
+
export class DatabaseTable {
|
|
10
|
+
private readonly connection: Connection
|
|
13
11
|
private table: TableData
|
|
12
|
+
private readonly source: QuerySource.Data
|
|
14
13
|
|
|
15
14
|
constructor(connection: Connection, table: TableData) {
|
|
16
15
|
this.connection = connection
|
|
17
16
|
this.table = table
|
|
17
|
+
this.source = { kind: "table", name: table.name, tableData: table }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
select(...columns: Projection.Input[]) {
|
|
21
|
+
return new QuerySourceBuilder(this.source, this.connection).select(
|
|
22
|
+
...columns,
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
join(target: QuerySource.Input) {
|
|
27
|
+
return new QuerySourceBuilder(this.source, this.connection).join(target)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
leftJoin(target: QuerySource.Input) {
|
|
31
|
+
return new QuerySourceBuilder(this.source, this.connection).leftJoin(target)
|
|
18
32
|
}
|
|
19
33
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
rightJoin(target: QuerySource.Input) {
|
|
35
|
+
return new QuerySourceBuilder(this.source, this.connection).rightJoin(
|
|
36
|
+
target,
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fullJoin(target: QuerySource.Input) {
|
|
41
|
+
return new QuerySourceBuilder(this.source, this.connection).fullJoin(target)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
crossJoin(target: QuerySource.Input) {
|
|
45
|
+
return new QuerySourceBuilder(this.source, this.connection).crossJoin(
|
|
46
|
+
target,
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
insert(...rows: [Partial<any>, ...Partial<any>[]]): Insert<any, any, number>
|
|
51
|
+
insert(...rows: Record<string, unknown>[]): Insert<any, any, number> {
|
|
52
|
+
const first = rows[0]
|
|
53
|
+
if (!first) {
|
|
54
|
+
throw new Error("insert() requires at least one row")
|
|
55
|
+
}
|
|
56
|
+
const columns = Object.keys(first)
|
|
57
|
+
const expected = new Set(columns)
|
|
58
|
+
for (const row of rows.slice(1)) {
|
|
59
|
+
const rowColumns = Object.keys(row)
|
|
60
|
+
if (
|
|
61
|
+
rowColumns.length !== columns.length ||
|
|
62
|
+
rowColumns.some((column) => !expected.has(column))
|
|
63
|
+
) {
|
|
64
|
+
throw new Error("insert() rows must use the same columns")
|
|
65
|
+
}
|
|
39
66
|
}
|
|
40
|
-
|
|
41
|
-
return new InsertBuilder(this.connection, this.table, Object.keys(row), [row])
|
|
67
|
+
return new InsertBuilder(this.connection, this.table, columns, rows)
|
|
42
68
|
}
|
|
43
69
|
|
|
44
70
|
update(row: Partial<any>): Update<any, number> {
|
package/src/api/DatabaseView.ts
CHANGED
|
@@ -1,22 +1,45 @@
|
|
|
1
|
-
|
|
2
|
-
import type { Connection, Select, ViewApi } from "./api.types"
|
|
1
|
+
import type { Connection } from "./api.types"
|
|
3
2
|
import type { TableData } from "../schemaBuilder"
|
|
4
|
-
import {
|
|
3
|
+
import { QuerySourceBuilder } from "../queryBuilder/QuerySourceBuilder"
|
|
4
|
+
import type { QuerySource } from "../queryBuilder/QuerySource"
|
|
5
|
+
import type { Projection } from "../queryBuilder/Projection"
|
|
5
6
|
|
|
6
|
-
export class DatabaseView
|
|
7
|
-
private connection: Connection
|
|
8
|
-
private
|
|
7
|
+
export class DatabaseView {
|
|
8
|
+
private readonly connection: Connection
|
|
9
|
+
private readonly source: QuerySource.Data
|
|
9
10
|
|
|
10
11
|
constructor(connection: Connection, tableData: TableData) {
|
|
11
12
|
this.connection = connection
|
|
12
|
-
this.
|
|
13
|
+
this.source = { kind: "table", name: tableData.name, tableData }
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
select(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
select(...columns: Projection.Input[]) {
|
|
17
|
+
return new QuerySourceBuilder(this.source, this.connection).select(
|
|
18
|
+
...columns,
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
join(target: QuerySource.Input) {
|
|
23
|
+
return new QuerySourceBuilder(this.source, this.connection).join(target)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
leftJoin(target: QuerySource.Input) {
|
|
27
|
+
return new QuerySourceBuilder(this.source, this.connection).leftJoin(target)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
rightJoin(target: QuerySource.Input) {
|
|
31
|
+
return new QuerySourceBuilder(this.source, this.connection).rightJoin(
|
|
32
|
+
target,
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
fullJoin(target: QuerySource.Input) {
|
|
37
|
+
return new QuerySourceBuilder(this.source, this.connection).fullJoin(target)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
crossJoin(target: QuerySource.Input) {
|
|
41
|
+
return new QuerySourceBuilder(this.source, this.connection).crossJoin(
|
|
42
|
+
target,
|
|
43
|
+
)
|
|
21
44
|
}
|
|
22
45
|
}
|