qubu 0.4.0 → 0.4.2
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/dist/{canonical-BbnqavJm.mjs → canonical-BXUguqfo.mjs} +23 -23
- package/dist/codegen.d.mts +25 -33
- package/dist/codegen.mjs +38 -45
- package/dist/column-hqKr7-1I.mjs +620 -0
- package/dist/{complete-D5Djh-zo.mjs → complete-WYyVozgK.mjs} +50 -50
- package/dist/{complete-types-BavBtv8J.d.mts → complete-types-IjEn5VPN.d.mts} +56 -56
- package/dist/core.d.mts +2 -2
- package/dist/core.mjs +4 -4
- package/dist/ddl.d.mts +12 -12
- package/dist/ddl.mjs +13 -14
- package/dist/{dialect-b2-Z6uBF.mjs → dialect-wUKrnPMB.mjs} +2 -2
- package/dist/diff.d.mts +1 -1
- package/dist/diff.mjs +6 -7
- package/dist/{index-DH0qV6aS.d.mts → index-1DpA3mUh.d.mts} +20 -20
- package/dist/{index-D3ZOwPT-.d.mts → index-CPvfEheG.d.mts} +13 -15
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +94 -95
- package/dist/introspection.d.mts +8 -8
- package/dist/introspection.mjs +29 -30
- package/dist/{json-CUZlv4HT.mjs → json-Db7XRD91.mjs} +2 -2
- package/dist/migration.d.mts +20 -21
- package/dist/migration.mjs +5 -6
- package/dist/{mysql-CKEKGnj4.mjs → mysql-B_cYzzX2.mjs} +237 -7
- package/dist/mysql.d.mts +2 -2
- package/dist/mysql.mjs +3 -3
- package/dist/{on-conflict-4MOBl51J.mjs → on-conflict-hfPW0KmQ.mjs} +3 -4
- package/dist/postgres.d.mts +5 -5
- package/dist/postgres.mjs +6 -6
- package/dist/{registry-CXV8u7Pt.mjs → registry-BRMLYwDp.mjs} +26 -29
- package/dist/{relational-DuQ9IHSb.mjs → relational-BZ3WDPzC.mjs} +2 -2
- package/dist/schema.d.mts +1 -1
- package/dist/schema.mjs +5 -6
- package/dist/{snapshot-DJpfmxhQ.mjs → snapshot-C-W65HEd.mjs} +5 -5
- package/dist/snapshot.d.mts +3 -3
- package/dist/snapshot.mjs +5 -7
- package/dist/{source-DGO3DRgg.mjs → source-BcS2AsIg.mjs} +6 -8
- package/dist/{serialize-CFtYYAdk.mjs → sqlite-Cg0nwYEH.mjs} +326 -12
- package/dist/sqlite.d.mts +1 -1
- package/dist/sqlite.mjs +2 -2
- package/dist/{standard-BTVYKh_F.mjs → standard-DfcZEVOj.mjs} +1 -1
- package/dist/{table-D6rcs8SB.mjs → table-Bp5irMSj.mjs} +2 -3
- package/dist/{types-Deo_q43Y.d.mts → types-BK1COGZe.d.mts} +1113 -1128
- package/dist/{types-g-vVvj1B.mjs → types-JM3FcAnX.mjs} +7 -8
- package/dist/{types-CSNJTYaM.d.mts → types-JSZHpUEj.d.mts} +83 -84
- package/dist/{value-b6OFZXVS.mjs → value-Bi71Agyf.mjs} +1 -1
- package/dist/vite/ambient.d.ts +309 -340
- package/dist/vite.d.mts +5 -6
- package/dist/vite.mjs +3 -4
- package/docs/dialects-and-execution.md +45 -55
- package/docs/getting-started.md +4 -4
- package/docs/guides/better-auth.md +57 -0
- package/docs/guides/compose-queries.md +24 -67
- package/docs/guides/drizzle.md +23 -23
- package/docs/guides/extensions/dialects.md +4 -4
- package/docs/guides/extensions/sources-and-clauses.md +15 -15
- package/docs/guides/extensions/typed-expressions.md +13 -17
- package/docs/guides/extensions/unsafe-syntax.md +3 -3
- package/docs/guides/json.md +7 -7
- package/docs/guides/mutations.md +14 -21
- package/docs/guides/select/conditions.md +6 -8
- package/docs/guides/select/grouping-and-windows.md +4 -15
- package/docs/guides/select/ordering-and-pagination.md +6 -19
- package/docs/guides/select/overview.md +12 -26
- package/docs/guides/sql-templates.md +20 -24
- package/docs/guides/vite-plugin.md +9 -13
- package/docs/index.md +5 -7
- package/docs/query-model/fragments.md +4 -15
- package/docs/query-model/result-shapes.md +11 -14
- package/docs/query-model/source-scope.md +31 -49
- package/docs/reference/mysql-snapshot.md +2 -5
- package/docs/reference/postgres-snapshot.md +3 -3
- package/docs/reference/sqlite-snapshot.md +2 -5
- package/docs/reference/supported-surface.md +26 -26
- package/docs/schema/catalog-model.md +2 -5
- package/docs/schema/code-generation.md +18 -18
- package/docs/schema/columns-and-writes.md +17 -25
- package/docs/schema/constraints-and-indexes.md +36 -52
- package/docs/schema/ddl-emission.md +6 -6
- package/docs/schema/diff.md +5 -5
- package/docs/schema/introspection.md +7 -7
- package/docs/schema/migration-plans.md +9 -9
- package/docs/schema/snapshots.md +3 -3
- package/docs/schema/storage-and-schema-sql.md +11 -11
- package/docs/schema/tables-and-names.md +11 -14
- package/docs/sql-semantic-types.md +10 -10
- package/docs/troubleshooting.md +10 -10
- package/package.json +21 -33
- package/skills/qubu/agents/openai.yaml +3 -3
- package/dist/column-D-8OqGuV.mjs +0 -328
- package/dist/column-LV7oGQde.mjs +0 -296
- package/dist/drizzle-mysql.d.mts +0 -24
- package/dist/drizzle-mysql.mjs +0 -72
- package/dist/drizzle-postgres.d.mts +0 -24
- package/dist/drizzle-postgres.mjs +0 -73
- package/dist/drizzle-sqlite.d.mts +0 -57
- package/dist/drizzle-sqlite.mjs +0 -108
- package/dist/drizzle.d.mts +0 -13
- package/dist/drizzle.mjs +0 -2
- package/dist/errors-BGCoLe_r.mjs +0 -14
- package/dist/postgres-BK2APkKP.mjs +0 -236
- package/dist/runtime-Cn_Xgzta.mjs +0 -193
- package/dist/sqlite-CrsK0Fza.mjs +0 -320
- package/dist/types-DZsueVoI.d.mts +0 -45
|
@@ -8,18 +8,18 @@ Qubu extensions are ordinary fragments. A custom clause supplies a placement,
|
|
|
8
8
|
render order, and renderer:
|
|
9
9
|
|
|
10
10
|
```ts
|
|
11
|
-
import { from, render, select, table, text } from
|
|
12
|
-
import { customClause } from
|
|
11
|
+
import { from, render, select, table, text } from "qubu"
|
|
12
|
+
import { customClause } from "qubu/core"
|
|
13
13
|
|
|
14
|
-
const users = table(
|
|
14
|
+
const users = table("users", { name: text() })
|
|
15
15
|
|
|
16
16
|
const fetchWithTies = customClause({
|
|
17
|
-
name:
|
|
17
|
+
name: "fetch-with-ties",
|
|
18
18
|
order: 100,
|
|
19
19
|
render(context) {
|
|
20
|
-
context.append(
|
|
20
|
+
context.append("FETCH FIRST ")
|
|
21
21
|
context.parameter(10)
|
|
22
|
-
context.append(
|
|
22
|
+
context.append(" ROWS WITH TIES")
|
|
23
23
|
},
|
|
24
24
|
})
|
|
25
25
|
|
|
@@ -41,23 +41,23 @@ shape is known to the application but cannot be declared with `table()`. Its
|
|
|
41
41
|
table, while `from()` or a join supplies the source to the query scope:
|
|
42
42
|
|
|
43
43
|
```ts
|
|
44
|
-
import { from, integer, render, select, text } from
|
|
45
|
-
import { identifier } from
|
|
46
|
-
import { customSource } from
|
|
44
|
+
import { from, integer, render, select, text } from "qubu"
|
|
45
|
+
import { identifier } from "qubu/core"
|
|
46
|
+
import { customSource } from "qubu/schema"
|
|
47
47
|
|
|
48
48
|
const rows = customSource({
|
|
49
|
-
identity: { sourceKind:
|
|
50
|
-
sourceKind:
|
|
51
|
-
reference: identifier(
|
|
49
|
+
identity: { sourceKind: "table-function", name: "json_each", alias: "row" },
|
|
50
|
+
sourceKind: "table-function",
|
|
51
|
+
reference: identifier("row"),
|
|
52
52
|
columns: {
|
|
53
53
|
key: integer(),
|
|
54
54
|
value: text({ nullable: true }),
|
|
55
55
|
},
|
|
56
56
|
render(context) {
|
|
57
|
-
context.append(
|
|
57
|
+
context.append("json_each(")
|
|
58
58
|
context.parameter('{"a":1}')
|
|
59
|
-
context.append(
|
|
60
|
-
context.render(identifier(
|
|
59
|
+
context.append(") AS ")
|
|
60
|
+
context.render(identifier("row"))
|
|
61
61
|
},
|
|
62
62
|
})
|
|
63
63
|
|
|
@@ -29,22 +29,18 @@ actually supports. Compatibility groups allow a dialect-specific domain to
|
|
|
29
29
|
interoperate with a built-in family:
|
|
30
30
|
|
|
31
31
|
```ts
|
|
32
|
-
import { cast, column } from
|
|
33
|
-
import type {
|
|
34
|
-
SqlEqualityComparable,
|
|
35
|
-
SqlOrderable,
|
|
36
|
-
SqlSemanticType,
|
|
37
|
-
SqlTextLike,
|
|
38
|
-
} from 'qubu'
|
|
32
|
+
import { cast, column } from "qubu"
|
|
33
|
+
import type { SqlEqualityComparable, SqlOrderable, SqlSemanticType, SqlTextLike } from "qubu"
|
|
39
34
|
|
|
40
35
|
interface SqlCitext
|
|
41
|
-
extends
|
|
36
|
+
extends
|
|
37
|
+
SqlSemanticType<"postgres.citext">,
|
|
42
38
|
SqlTextLike,
|
|
43
|
-
SqlOrderable<
|
|
44
|
-
SqlEqualityComparable<
|
|
39
|
+
SqlOrderable<"text">,
|
|
40
|
+
SqlEqualityComparable<"text"> {}
|
|
45
41
|
|
|
46
42
|
const citext = column<string, string, string, SqlCitext>({
|
|
47
|
-
castType:
|
|
43
|
+
castType: "CITEXT",
|
|
48
44
|
})
|
|
49
45
|
|
|
50
46
|
const nameAsCitext = cast(users.name, citext)
|
|
@@ -61,13 +57,13 @@ nullability comes from the operand and write flags have no cast meaning.
|
|
|
61
57
|
Declare result domains at other extension boundaries too:
|
|
62
58
|
|
|
63
59
|
```ts
|
|
64
|
-
import { typedCall, typedCast, typedValue, unsafeExpression } from
|
|
65
|
-
import type { SqlText, SqlUuid } from
|
|
60
|
+
import { typedCall, typedCast, typedValue, unsafeExpression } from "qubu/core"
|
|
61
|
+
import type { SqlText, SqlUuid } from "qubu"
|
|
66
62
|
|
|
67
|
-
const id = typedValue<SqlUuid, string>(
|
|
68
|
-
const normalized = typedCall<SqlText, string>()(
|
|
69
|
-
const rawNameAsText = typedCast<string, SqlText>()(users.name,
|
|
70
|
-
const generated = unsafeExpression<string, SqlText>(
|
|
63
|
+
const id = typedValue<SqlUuid, string>("108cb836-20d2-41b2-8c23-f0c94700aa7e")
|
|
64
|
+
const normalized = typedCall<SqlText, string>()("custom_text", users.name)
|
|
65
|
+
const rawNameAsText = typedCast<string, SqlText>()(users.name, "TEXT")
|
|
66
|
+
const generated = unsafeExpression<string, SqlText>("custom_text()")
|
|
71
67
|
```
|
|
72
68
|
|
|
73
69
|
`typedCall()` preserves source requirements from its arguments. `typedCast()`
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
modeled yet. They do not quote identifiers or bind values for you:
|
|
7
7
|
|
|
8
8
|
```ts
|
|
9
|
-
import { select } from
|
|
10
|
-
import { unsafeExpression } from
|
|
9
|
+
import { select } from "qubu"
|
|
10
|
+
import { unsafeExpression } from "qubu/core"
|
|
11
11
|
|
|
12
12
|
const query = select({
|
|
13
|
-
today: unsafeExpression(
|
|
13
|
+
today: unsafeExpression("CURRENT_DATE"),
|
|
14
14
|
})
|
|
15
15
|
```
|
|
16
16
|
|
package/docs/guides/json.md
CHANGED
|
@@ -16,9 +16,9 @@ import {
|
|
|
16
16
|
jsonText,
|
|
17
17
|
select,
|
|
18
18
|
table,
|
|
19
|
-
} from
|
|
19
|
+
} from "qubu"
|
|
20
20
|
|
|
21
|
-
const events = table(
|
|
21
|
+
const events = table("events", {
|
|
22
22
|
payload: json<{
|
|
23
23
|
user?: { name?: string; active?: boolean; score?: number }
|
|
24
24
|
}>(),
|
|
@@ -26,12 +26,12 @@ const events = table('events', {
|
|
|
26
26
|
|
|
27
27
|
const query = select(
|
|
28
28
|
{
|
|
29
|
-
name: jsonText(events.payload, jsonPath(
|
|
30
|
-
active: jsonBoolean(events.payload, jsonPath(
|
|
31
|
-
score: jsonNumber(events.payload, jsonPath(
|
|
32
|
-
hasUser: jsonExists(events.payload, jsonPath(
|
|
29
|
+
name: jsonText(events.payload, jsonPath("user", "name")),
|
|
30
|
+
active: jsonBoolean(events.payload, jsonPath("user", "active")),
|
|
31
|
+
score: jsonNumber(events.payload, jsonPath("user", "score")),
|
|
32
|
+
hasUser: jsonExists(events.payload, jsonPath("user")),
|
|
33
33
|
},
|
|
34
|
-
from(events)
|
|
34
|
+
from(events),
|
|
35
35
|
)
|
|
36
36
|
```
|
|
37
37
|
|
package/docs/guides/mutations.md
CHANGED
|
@@ -9,9 +9,9 @@ columns accept `null` as a value, which is distinct from omitting a defaulted
|
|
|
9
9
|
field:
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
|
-
import { integer, table, text } from
|
|
12
|
+
import { integer, table, text } from "qubu"
|
|
13
13
|
|
|
14
|
-
const users = table(
|
|
14
|
+
const users = table("users", {
|
|
15
15
|
id: integer({ generated: true }),
|
|
16
16
|
name: text(),
|
|
17
17
|
email: text({ nullable: true, hasDefault: true }),
|
|
@@ -27,15 +27,12 @@ Pass one or more rows to `values()`. Qubu checks that every row uses the same
|
|
|
27
27
|
columns and that required, non-generated fields are present:
|
|
28
28
|
|
|
29
29
|
```ts
|
|
30
|
-
import { insertInto, render, returning, values } from
|
|
30
|
+
import { insertInto, render, returning, values } from "qubu"
|
|
31
31
|
|
|
32
32
|
const query = insertInto(
|
|
33
33
|
users,
|
|
34
|
-
values(
|
|
35
|
-
|
|
36
|
-
{ name: 'Grace', email: 'grace@example.com' }
|
|
37
|
-
),
|
|
38
|
-
returning({ id: users.id, name: users.name })
|
|
34
|
+
values({ name: "Ada", email: null }, { name: "Grace", email: "grace@example.com" }),
|
|
35
|
+
returning({ id: users.id, name: users.name }),
|
|
39
36
|
)
|
|
40
37
|
|
|
41
38
|
render(query)
|
|
@@ -54,13 +51,13 @@ default. Use `insertSelect(query, columns)` for an `INSERT ... SELECT` source.
|
|
|
54
51
|
the target table:
|
|
55
52
|
|
|
56
53
|
```ts
|
|
57
|
-
import { eq, returning, update, upper, where } from
|
|
54
|
+
import { eq, returning, update, upper, where } from "qubu"
|
|
58
55
|
|
|
59
56
|
const query = update(
|
|
60
57
|
users,
|
|
61
58
|
{ name: upper(users.name) },
|
|
62
59
|
where(eq(users.id, 7)),
|
|
63
|
-
returning({ id: users.id, name: users.name })
|
|
60
|
+
returning({ id: users.id, name: users.name }),
|
|
64
61
|
)
|
|
65
62
|
```
|
|
66
63
|
|
|
@@ -71,15 +68,15 @@ Use `omit` for a runtime-conditional assignment. Qubu removes omitted fields
|
|
|
71
68
|
before validating and rendering the effective assignment set:
|
|
72
69
|
|
|
73
70
|
```ts
|
|
74
|
-
import { eq, omit, update, where } from
|
|
71
|
+
import { eq, omit, update, where } from "qubu"
|
|
75
72
|
|
|
76
73
|
const query = update(
|
|
77
74
|
users,
|
|
78
75
|
{
|
|
79
|
-
name: rename ?
|
|
76
|
+
name: rename ? "Archived" : omit,
|
|
80
77
|
email: clearEmail ? null : omit,
|
|
81
78
|
},
|
|
82
|
-
where(eq(users.id, 7))
|
|
79
|
+
where(eq(users.id, 7)),
|
|
83
80
|
)
|
|
84
81
|
```
|
|
85
82
|
|
|
@@ -93,13 +90,9 @@ remain source- and capability-aware even when their runtime alternative is
|
|
|
93
90
|
## Delete with a predicate
|
|
94
91
|
|
|
95
92
|
```ts
|
|
96
|
-
import { deleteFrom, eq, returning, where } from
|
|
93
|
+
import { deleteFrom, eq, returning, where } from "qubu"
|
|
97
94
|
|
|
98
|
-
const query = deleteFrom(
|
|
99
|
-
users,
|
|
100
|
-
where(eq(users.id, 8)),
|
|
101
|
-
returning({ id: users.id })
|
|
102
|
-
)
|
|
95
|
+
const query = deleteFrom(users, where(eq(users.id, 8)), returning({ id: users.id }))
|
|
103
96
|
```
|
|
104
97
|
|
|
105
98
|
## Keep unrestricted writes explicit
|
|
@@ -108,9 +101,9 @@ Both `UPDATE` and `DELETE` require a `WHERE` clause by default. If an operation
|
|
|
108
101
|
really must affect every row, opt in at the call site:
|
|
109
102
|
|
|
110
103
|
```ts
|
|
111
|
-
import { allowAll, update } from
|
|
104
|
+
import { allowAll, update } from "qubu"
|
|
112
105
|
|
|
113
|
-
const query = update(users, { name:
|
|
106
|
+
const query = update(users, { name: "Archived" }, allowAll())
|
|
114
107
|
```
|
|
115
108
|
|
|
116
109
|
`allowAll()` is a safety boundary, not a replacement for authorization or
|
|
@@ -11,14 +11,14 @@ Use `omit` as the other branch of a JavaScript conditional when a `WHERE`,
|
|
|
11
11
|
`HAVING`, `ORDER BY`, or `DISTINCT` clause is optional:
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
|
-
import { eq, from, omit, select, where } from
|
|
14
|
+
import { eq, from, omit, select, where } from "qubu"
|
|
15
15
|
|
|
16
16
|
declare const userId: number | undefined
|
|
17
17
|
|
|
18
18
|
const query = select(
|
|
19
19
|
{ id: users.id, name: users.name },
|
|
20
20
|
from(users),
|
|
21
|
-
userId === undefined ? omit : where(eq(users.id, userId))
|
|
21
|
+
userId === undefined ? omit : where(eq(users.id, userId)),
|
|
22
22
|
)
|
|
23
23
|
```
|
|
24
24
|
|
|
@@ -29,14 +29,12 @@ Use the same token inside `and()`, `or()`, and `orderBy()` when individual
|
|
|
29
29
|
predicates or ordering terms are conditional:
|
|
30
30
|
|
|
31
31
|
```ts
|
|
32
|
-
import { and, desc, eq, omit, orderBy, where } from
|
|
32
|
+
import { and, desc, eq, omit, orderBy, where } from "qubu"
|
|
33
33
|
|
|
34
34
|
declare const includeName: boolean
|
|
35
35
|
declare const newestFirst: boolean
|
|
36
36
|
|
|
37
|
-
const filter = where(
|
|
38
|
-
and(eq(users.id, 7), includeName ? eq(users.name, 'Ada') : omit)
|
|
39
|
-
)
|
|
37
|
+
const filter = where(and(eq(users.id, 7), includeName ? eq(users.name, "Ada") : omit))
|
|
40
38
|
const ordering = orderBy(newestFirst ? desc(users.name) : omit)
|
|
41
39
|
```
|
|
42
40
|
|
|
@@ -56,7 +54,7 @@ const query = select(
|
|
|
56
54
|
id: users.id,
|
|
57
55
|
email: includeEmail ? users.email : omit,
|
|
58
56
|
},
|
|
59
|
-
from(users)
|
|
57
|
+
from(users),
|
|
60
58
|
)
|
|
61
59
|
|
|
62
60
|
// typeof query.row:
|
|
@@ -82,7 +80,7 @@ separate queries when those structural parts differ at runtime.
|
|
|
82
80
|
Equality with `null` is translated to the SQL null predicate:
|
|
83
81
|
|
|
84
82
|
```ts
|
|
85
|
-
import { eq, isDistinctFrom, ne } from
|
|
83
|
+
import { eq, isDistinctFrom, ne } from "qubu"
|
|
86
84
|
|
|
87
85
|
eq(users.name, null) // ... IS NULL
|
|
88
86
|
ne(users.name, null) // ... IS NOT NULL
|
|
@@ -11,18 +11,7 @@ Object projection keys provide stable names for aggregates. Group every
|
|
|
11
11
|
non-aggregate column dependency:
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
|
-
import {
|
|
15
|
-
count,
|
|
16
|
-
desc,
|
|
17
|
-
eq,
|
|
18
|
-
from,
|
|
19
|
-
groupBy,
|
|
20
|
-
gt,
|
|
21
|
-
having,
|
|
22
|
-
leftJoin,
|
|
23
|
-
orderBy,
|
|
24
|
-
select,
|
|
25
|
-
} from 'qubu'
|
|
14
|
+
import { count, desc, eq, from, groupBy, gt, having, leftJoin, orderBy, select } from "qubu"
|
|
26
15
|
|
|
27
16
|
const counts = select(
|
|
28
17
|
{
|
|
@@ -33,7 +22,7 @@ const counts = select(
|
|
|
33
22
|
leftJoin(posts, eq(users.id, posts.authorId)),
|
|
34
23
|
groupBy(users.name),
|
|
35
24
|
having(gt(count(posts.id), 0)),
|
|
36
|
-
orderBy(desc(count(posts.id)))
|
|
25
|
+
orderBy(desc(count(posts.id))),
|
|
37
26
|
)
|
|
38
27
|
```
|
|
39
28
|
|
|
@@ -52,7 +41,7 @@ The initial window scope supports `PARTITION BY` and `ORDER BY`; the same
|
|
|
52
41
|
`asc()` and `desc()` terms used by a query-level `orderBy()` can be reused:
|
|
53
42
|
|
|
54
43
|
```ts
|
|
55
|
-
import { desc, from, over, rowNumber, select } from
|
|
44
|
+
import { desc, from, over, rowNumber, select } from "qubu"
|
|
56
45
|
|
|
57
46
|
const rankedUsers = select(
|
|
58
47
|
{
|
|
@@ -62,7 +51,7 @@ const rankedUsers = select(
|
|
|
62
51
|
orderBy: [desc(users.id)],
|
|
63
52
|
}),
|
|
64
53
|
},
|
|
65
|
-
from(users)
|
|
54
|
+
from(users),
|
|
66
55
|
)
|
|
67
56
|
```
|
|
68
57
|
|
|
@@ -9,13 +9,9 @@ The examples use the `users` table from [Build a `SELECT`](overview.md).
|
|
|
9
9
|
Wrap ordering terms in `orderBy()`:
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
|
-
import { desc, from, orderBy, select } from
|
|
12
|
+
import { desc, from, orderBy, select } from "qubu"
|
|
13
13
|
|
|
14
|
-
const ordered = select(
|
|
15
|
-
{ id: users.id, name: users.name },
|
|
16
|
-
from(users),
|
|
17
|
-
orderBy(desc(users.name))
|
|
18
|
-
)
|
|
14
|
+
const ordered = select({ id: users.id, name: users.name }, from(users), orderBy(desc(users.name)))
|
|
19
15
|
```
|
|
20
16
|
|
|
21
17
|
Use `asc()` or `desc()` for each term. The selected dialect controls the
|
|
@@ -26,16 +22,7 @@ identifier quoting and any dialect-specific ordering syntax.
|
|
|
26
22
|
Choose `fetchFirst()` or `offset()` for pagination:
|
|
27
23
|
|
|
28
24
|
```ts
|
|
29
|
-
import {
|
|
30
|
-
desc,
|
|
31
|
-
eq,
|
|
32
|
-
fetchFirst,
|
|
33
|
-
from,
|
|
34
|
-
offset,
|
|
35
|
-
orderBy,
|
|
36
|
-
select,
|
|
37
|
-
where,
|
|
38
|
-
} from 'qubu'
|
|
25
|
+
import { desc, eq, fetchFirst, from, offset, orderBy, select, where } from "qubu"
|
|
39
26
|
|
|
40
27
|
const page = select(
|
|
41
28
|
{ id: users.id, name: users.name },
|
|
@@ -43,7 +30,7 @@ const page = select(
|
|
|
43
30
|
where(eq(users.id, 7)),
|
|
44
31
|
orderBy(desc(users.name)),
|
|
45
32
|
offset(20),
|
|
46
|
-
fetchFirst(20)
|
|
33
|
+
fetchFirst(20),
|
|
47
34
|
)
|
|
48
35
|
```
|
|
49
36
|
|
|
@@ -54,14 +41,14 @@ pagination. The active dialect decides whether pagination uses standard
|
|
|
54
41
|
Pair a pagination clause with `omit` when the row bound is optional at runtime:
|
|
55
42
|
|
|
56
43
|
```ts
|
|
57
|
-
import { fetchFirst, omit } from
|
|
44
|
+
import { fetchFirst, omit } from "qubu"
|
|
58
45
|
|
|
59
46
|
declare const pageSize: number | undefined
|
|
60
47
|
|
|
61
48
|
const page = select(
|
|
62
49
|
{ id: users.id, name: users.name },
|
|
63
50
|
from(users),
|
|
64
|
-
pageSize === undefined ? omit : fetchFirst(pageSize)
|
|
51
|
+
pageSize === undefined ? omit : fetchFirst(pageSize),
|
|
65
52
|
)
|
|
66
53
|
```
|
|
67
54
|
|
|
@@ -9,19 +9,15 @@ independent clauses. A source-aware column must be provided by `from()` or a
|
|
|
9
9
|
join:
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
|
-
import { eq, from, integer, render, select, table, text, where } from
|
|
12
|
+
import { eq, from, integer, render, select, table, text, where } from "qubu"
|
|
13
13
|
|
|
14
|
-
const users = table(
|
|
14
|
+
const users = table("users", {
|
|
15
15
|
id: integer(),
|
|
16
16
|
name: text(),
|
|
17
17
|
email: text({ nullable: true }),
|
|
18
18
|
})
|
|
19
19
|
|
|
20
|
-
const query = select(
|
|
21
|
-
{ id: users.id, name: users.name },
|
|
22
|
-
from(users),
|
|
23
|
-
where(eq(users.id, 7))
|
|
24
|
-
)
|
|
20
|
+
const query = select({ id: users.id, name: users.name }, from(users), where(eq(users.id, 7)))
|
|
25
21
|
|
|
26
22
|
render(query).text
|
|
27
23
|
// SELECT "users"."id" AS "id", "users"."name" AS "name" FROM "users" WHERE ("users"."id" = ?)
|
|
@@ -34,12 +30,9 @@ named projection object, so it can still be spread alongside computed
|
|
|
34
30
|
expressions:
|
|
35
31
|
|
|
36
32
|
```ts
|
|
37
|
-
import { all, from, select, upper } from
|
|
33
|
+
import { all, from, select, upper } from "qubu"
|
|
38
34
|
|
|
39
|
-
const query = select(
|
|
40
|
-
{ ...all(users), normalizedName: upper(users.name) },
|
|
41
|
-
from(users)
|
|
42
|
-
)
|
|
35
|
+
const query = select({ ...all(users), normalizedName: upper(users.name) }, from(users))
|
|
43
36
|
```
|
|
44
37
|
|
|
45
38
|
`all(source)` expands to explicit named columns rather than emitting
|
|
@@ -66,9 +59,9 @@ import {
|
|
|
66
59
|
text,
|
|
67
60
|
integer,
|
|
68
61
|
where,
|
|
69
|
-
} from
|
|
62
|
+
} from "qubu"
|
|
70
63
|
|
|
71
|
-
const posts = table(
|
|
64
|
+
const posts = table("posts", {
|
|
72
65
|
id: integer(),
|
|
73
66
|
authorId: integer(),
|
|
74
67
|
title: text(),
|
|
@@ -78,7 +71,7 @@ const query = select(
|
|
|
78
71
|
{ userId: users.id, title: posts.title },
|
|
79
72
|
from(users),
|
|
80
73
|
innerJoin(posts, eq(users.id, posts.authorId)),
|
|
81
|
-
where(isNotNull(users.email))
|
|
74
|
+
where(isNotNull(users.email)),
|
|
82
75
|
)
|
|
83
76
|
```
|
|
84
77
|
|
|
@@ -98,7 +91,7 @@ const summary = select(
|
|
|
98
91
|
postCount: count(posts.id),
|
|
99
92
|
},
|
|
100
93
|
from(users),
|
|
101
|
-
leftJoin(posts, eq(users.id, posts.authorId))
|
|
94
|
+
leftJoin(posts, eq(users.id, posts.authorId)),
|
|
102
95
|
)
|
|
103
96
|
|
|
104
97
|
// typeof summary.row:
|
|
@@ -108,18 +101,11 @@ const summary = select(
|
|
|
108
101
|
Compose boolean expressions explicitly:
|
|
109
102
|
|
|
110
103
|
```ts
|
|
111
|
-
import { and, gt, inList, or } from
|
|
104
|
+
import { and, gt, inList, or } from "qubu"
|
|
112
105
|
|
|
113
|
-
const filter = and(
|
|
114
|
-
gt(users.id, 0),
|
|
115
|
-
or(inList(users.id, [7, 8]), eq(users.name, 'Ada'))
|
|
116
|
-
)
|
|
106
|
+
const filter = and(gt(users.id, 0), or(inList(users.id, [7, 8]), eq(users.name, "Ada")))
|
|
117
107
|
|
|
118
|
-
const filtered = select(
|
|
119
|
-
{ id: users.id, name: users.name },
|
|
120
|
-
from(users),
|
|
121
|
-
where(filter)
|
|
122
|
-
)
|
|
108
|
+
const filtered = select({ id: users.id, name: users.name }, from(users), where(filter))
|
|
123
109
|
```
|
|
124
110
|
|
|
125
111
|
Values such as `7`, `'Ada'`, and list members become parameters. They are not
|
|
@@ -9,11 +9,11 @@ substitution as a parameter. This includes strings, numbers, objects, arrays,
|
|
|
9
9
|
and `null`:
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
|
-
import { integer, render, sql, table, text } from
|
|
12
|
+
import { integer, render, sql, table, text } from "qubu"
|
|
13
13
|
|
|
14
|
-
const users = table(
|
|
15
|
-
const posts = table(
|
|
16
|
-
const search =
|
|
14
|
+
const users = table("users", { name: text() })
|
|
15
|
+
const posts = table("posts", { id: integer() })
|
|
16
|
+
const search = "Ada%"
|
|
17
17
|
const predicate = sql`${users.name} LIKE ${search}`
|
|
18
18
|
|
|
19
19
|
render(predicate)
|
|
@@ -33,16 +33,12 @@ statement. Parameters keep one placeholder sequence across nested templates
|
|
|
33
33
|
and queries:
|
|
34
34
|
|
|
35
35
|
```ts
|
|
36
|
-
import { eq, from, render, select, sql, where } from
|
|
37
|
-
import { postgresDialect } from
|
|
36
|
+
import { eq, from, render, select, sql, where } from "qubu"
|
|
37
|
+
import { postgresDialect } from "qubu/postgres"
|
|
38
38
|
|
|
39
|
-
const selectedNames = select(
|
|
40
|
-
{ displayName: users.name },
|
|
41
|
-
from(users),
|
|
42
|
-
where(eq(users.name, 'Ada'))
|
|
43
|
-
)
|
|
39
|
+
const selectedNames = select({ displayName: users.name }, from(users), where(eq(users.name, "Ada")))
|
|
44
40
|
|
|
45
|
-
const exists = sql`EXISTS (${selectedNames}) AND ${users.name} <> ${
|
|
41
|
+
const exists = sql`EXISTS (${selectedNames}) AND ${users.name} <> ${"root"}`
|
|
46
42
|
|
|
47
43
|
render(exists, postgresDialect())
|
|
48
44
|
// {
|
|
@@ -62,8 +58,8 @@ An unannotated template has application output `unknown` and SQL domain
|
|
|
62
58
|
named projection:
|
|
63
59
|
|
|
64
60
|
```ts
|
|
65
|
-
import { from, select, sql } from
|
|
66
|
-
import type { SqlText } from
|
|
61
|
+
import { from, select, sql } from "qubu"
|
|
62
|
+
import type { SqlText } from "qubu"
|
|
67
63
|
|
|
68
64
|
const normalizedName = sql.type<string, SqlText>()`LOWER(${users.name})`
|
|
69
65
|
|
|
@@ -98,11 +94,11 @@ Do not use a dotted string as an identifier. Pass each part to
|
|
|
98
94
|
syntax that cannot use a fixed template segment:
|
|
99
95
|
|
|
100
96
|
```ts
|
|
101
|
-
import { sql } from
|
|
102
|
-
import { identifier, unsafeExpression } from
|
|
97
|
+
import { sql } from "qubu"
|
|
98
|
+
import { identifier, unsafeExpression } from "qubu/core"
|
|
103
99
|
|
|
104
|
-
const sortColumn =
|
|
105
|
-
const direction =
|
|
100
|
+
const sortColumn = "display_name"
|
|
101
|
+
const direction = "DESC" as const
|
|
106
102
|
|
|
107
103
|
const ordering = sql`ORDER BY ${identifier(sortColumn)} ${unsafeExpression(direction)}`
|
|
108
104
|
```
|
|
@@ -120,8 +116,8 @@ those facts from unchecked template text.
|
|
|
120
116
|
Use a built-in expression as the substitution when its semantics matter:
|
|
121
117
|
|
|
122
118
|
```ts
|
|
123
|
-
import { count, sql } from
|
|
124
|
-
import type { SqlInteger } from
|
|
119
|
+
import { count, sql } from "qubu"
|
|
120
|
+
import type { SqlInteger } from "qubu"
|
|
125
121
|
|
|
126
122
|
const postCount = sql.type<number, SqlInteger>()`${count(posts.id)}`
|
|
127
123
|
```
|
|
@@ -140,13 +136,13 @@ Declare a capability when the template text itself uses dialect-specific
|
|
|
140
136
|
syntax:
|
|
141
137
|
|
|
142
138
|
```ts
|
|
143
|
-
import { sql } from
|
|
144
|
-
import { withDialectCapability } from
|
|
145
|
-
import type { SqlBoolean } from
|
|
139
|
+
import { sql } from "qubu"
|
|
140
|
+
import { withDialectCapability } from "qubu/core"
|
|
141
|
+
import type { SqlBoolean } from "qubu"
|
|
146
142
|
|
|
147
143
|
const postgresMatch = withDialectCapability(
|
|
148
144
|
sql.type<boolean, SqlBoolean>()`${users.name} ILIKE ${search}`,
|
|
149
|
-
|
|
145
|
+
"ilike",
|
|
150
146
|
)
|
|
151
147
|
```
|
|
152
148
|
|
|
@@ -11,8 +11,8 @@ Add the plugin to Vite and add the matching ambient declarations to TypeScript:
|
|
|
11
11
|
|
|
12
12
|
```ts
|
|
13
13
|
// vite.config.ts
|
|
14
|
-
import { defineConfig } from
|
|
15
|
-
import { qubu } from
|
|
14
|
+
import { defineConfig } from "vite"
|
|
15
|
+
import { qubu } from "qubu/vite"
|
|
16
16
|
|
|
17
17
|
export default defineConfig({
|
|
18
18
|
plugins: [qubu()],
|
|
@@ -36,24 +36,20 @@ ambient value and type declarations for the TypeScript compiler.
|
|
|
36
36
|
Put the directive in the module's initial directive prologue:
|
|
37
37
|
|
|
38
38
|
```ts
|
|
39
|
-
|
|
39
|
+
"use qubu"
|
|
40
40
|
|
|
41
|
-
const users = table(
|
|
41
|
+
const users = table("users", {
|
|
42
42
|
id: integer(),
|
|
43
43
|
name: text(),
|
|
44
44
|
})
|
|
45
45
|
|
|
46
|
-
const query = select(
|
|
47
|
-
{ id: users.id, name: users.name },
|
|
48
|
-
from(users),
|
|
49
|
-
where(eq(users.id, 42))
|
|
50
|
-
)
|
|
46
|
+
const query = select({ id: users.id, name: users.name }, from(users), where(eq(users.id, 42)))
|
|
51
47
|
```
|
|
52
48
|
|
|
53
49
|
Conceptually, the transform adds the imports that this module references:
|
|
54
50
|
|
|
55
51
|
```ts
|
|
56
|
-
import { eq, from, integer, select, table, text, where } from
|
|
52
|
+
import { eq, from, integer, select, table, text, where } from "qubu"
|
|
57
53
|
```
|
|
58
54
|
|
|
59
55
|
Existing imports remain valid. The transform does not rewrite member properties,
|
|
@@ -63,10 +59,10 @@ strings, comments, or names outside the public Qubu global catalog.
|
|
|
63
59
|
|
|
64
60
|
```ts
|
|
65
61
|
qubu({
|
|
66
|
-
module:
|
|
67
|
-
include: id => id.includes(
|
|
62
|
+
module: "qubu",
|
|
63
|
+
include: (id) => id.includes("/src/"),
|
|
68
64
|
exclude: /\.stories\./,
|
|
69
|
-
globals: [
|
|
65
|
+
globals: ["select", "from", "where", "eq", "table"],
|
|
70
66
|
})
|
|
71
67
|
```
|
|
72
68
|
|
package/docs/index.md
CHANGED
|
@@ -29,6 +29,8 @@ define a table, build a `SELECT`, and inspect its SQL and parameters.
|
|
|
29
29
|
`DELETE` statements.
|
|
30
30
|
- [Use Qubu tables with Drizzle](guides/drizzle.md) while moving query call
|
|
31
31
|
sites without duplicating schema declarations.
|
|
32
|
+
- [Use Qubu with Better Auth](guides/better-auth.md) with plugin-aware schema
|
|
33
|
+
derivation and a native transactional database adapter.
|
|
32
34
|
- [Extend Qubu](guides/extensions/overview.md) with a custom source, clause,
|
|
33
35
|
dialect policy, or typed expression.
|
|
34
36
|
- [Read JSON scalars](guides/json.md) from structured JSON paths.
|
|
@@ -82,18 +84,14 @@ needs to preserve a fact across composition:
|
|
|
82
84
|
## A small example
|
|
83
85
|
|
|
84
86
|
```ts
|
|
85
|
-
import { eq, from, integer, render, select, table, text, where } from
|
|
87
|
+
import { eq, from, integer, render, select, table, text, where } from "qubu"
|
|
86
88
|
|
|
87
|
-
const users = table(
|
|
89
|
+
const users = table("users", {
|
|
88
90
|
id: integer(),
|
|
89
91
|
name: text(),
|
|
90
92
|
})
|
|
91
93
|
|
|
92
|
-
const query = select(
|
|
93
|
-
{ id: users.id, name: users.name },
|
|
94
|
-
from(users),
|
|
95
|
-
where(eq(users.id, 7))
|
|
96
|
-
)
|
|
94
|
+
const query = select({ id: users.id, name: users.name }, from(users), where(eq(users.id, 7)))
|
|
97
95
|
|
|
98
96
|
render(query)
|
|
99
97
|
// {
|