qubu 0.3.5 → 0.4.1
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-B2PO6wQD.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-7qc6OcIC.d.mts → index-1DpA3mUh.d.mts} +20 -20
- package/dist/{index-CqWnouTK.d.mts → index-CPvfEheG.d.mts} +13 -15
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +148 -120
- 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-DqkqXB6A.mjs → mysql-B_cYzzX2.mjs} +237 -7
- package/dist/mysql.d.mts +1 -1
- package/dist/mysql.mjs +3 -3
- package/dist/{on-conflict-BxnxubMb.mjs → on-conflict-hfPW0KmQ.mjs} +4 -4
- package/dist/postgres.d.mts +5 -5
- package/dist/postgres.mjs +6 -6
- package/dist/{registry-BufIskVN.mjs → registry-BRMLYwDp.mjs} +27 -135
- package/dist/{relational-DCZrrNia.mjs → relational-BZ3WDPzC.mjs} +4 -4
- package/dist/schema.d.mts +2 -2
- package/dist/schema.mjs +6 -6
- package/dist/{snapshot-CWPgzxNx.mjs → snapshot-C-W65HEd.mjs} +5 -5
- package/dist/snapshot.d.mts +3 -3
- package/dist/snapshot.mjs +5 -7
- package/dist/{source-DUoJVXmL.mjs → source-BcS2AsIg.mjs} +7 -9
- package/dist/{serialize-PF1cfH2P.mjs → sqlite-Cg0nwYEH.mjs} +326 -12
- package/dist/sqlite.d.mts +2 -2
- package/dist/sqlite.mjs +2 -2
- package/dist/{standard-BTVYKh_F.mjs → standard-DfcZEVOj.mjs} +1 -1
- package/dist/{table-CCUJ60rB.mjs → table-Bp5irMSj.mjs} +5 -7
- package/dist/{types-D8M1yZF4.d.mts → types-BK1COGZe.d.mts} +2661 -2150
- package/dist/{types-Cec0xzo4.mjs → types-JM3FcAnX.mjs} +8 -8
- package/dist/{types-LBt5rclR.d.mts → types-JSZHpUEj.d.mts} +83 -84
- package/dist/{value-BvilP0oz.mjs → value-Bi71Agyf.mjs} +1 -1
- package/dist/vite/ambient.d.ts +311 -390
- package/dist/vite.d.mts +5 -6
- package/dist/vite.mjs +3 -4
- package/docs/dialects-and-execution.md +121 -81
- 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 +33 -33
- 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-CXMxx8Hq.mjs +0 -118
- package/dist/column-CYMbKbOy.mjs +0 -290
- 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 -50
- 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/naming-QVCOnSj2.mjs +0 -20
- package/dist/postgres-DEBBeh52.mjs +0 -235
- package/dist/runtime-Cn_Xgzta.mjs +0 -193
- package/dist/sqlite-BU6DBxef.mjs +0 -320
- package/dist/types-Ctlxz1ip.d.mts +0 -45
package/docs/guides/drizzle.md
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
## Install the optional integration
|
|
7
7
|
|
|
8
|
-
Install
|
|
9
|
-
|
|
8
|
+
Install the integration next to Qubu and Drizzle. The `@qubu/drizzle` package
|
|
9
|
+
owns schema conversion; the root `qubu` package does not depend on Drizzle.
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
pnpm add qubu drizzle-orm@rc
|
|
12
|
+
pnpm add qubu @qubu/drizzle drizzle-orm@rc
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
The converter supports PostgreSQL, MySQL, and SQLite with Drizzle 1.0.0-rc.4
|
|
@@ -21,29 +21,29 @@ Declare tables and the root registry with Qubu, then import the converter for
|
|
|
21
21
|
your database:
|
|
22
22
|
|
|
23
23
|
```ts
|
|
24
|
-
import { integer, schema, table, text } from
|
|
25
|
-
import { toPostgresDrizzleSchema } from
|
|
24
|
+
import { integer, schema, table, text } from "qubu"
|
|
25
|
+
import { toPostgresDrizzleSchema } from "@qubu/drizzle/postgres"
|
|
26
26
|
|
|
27
|
-
const users = table(
|
|
27
|
+
const users = table("user_records", {
|
|
28
28
|
id: integer({ generated: true }),
|
|
29
29
|
name: text(),
|
|
30
30
|
nickname: text({ nullable: true }),
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
-
const appSchema = schema({ users }, { namespace:
|
|
33
|
+
const appSchema = schema({ users }, { namespace: "app" })
|
|
34
34
|
const drizzleTables = toPostgresDrizzleSchema(appSchema)
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
The import path selects the dialect. Each module imports only its matching
|
|
38
38
|
Drizzle core package:
|
|
39
39
|
|
|
40
|
-
| Database | Import
|
|
41
|
-
| ---------- |
|
|
42
|
-
| PostgreSQL |
|
|
43
|
-
| MySQL |
|
|
44
|
-
| SQLite |
|
|
40
|
+
| Database | Import | Converter |
|
|
41
|
+
| ---------- | ------------------------ | --------------------------- |
|
|
42
|
+
| PostgreSQL | `@qubu/drizzle/postgres` | `toPostgresDrizzleSchema()` |
|
|
43
|
+
| MySQL | `@qubu/drizzle/mysql` | `toMysqlDrizzleSchema()` |
|
|
44
|
+
| SQLite | `@qubu/drizzle/sqlite` | `toSqliteDrizzleSchema()` |
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
`@qubu/drizzle` exports the shared conversion error and dialect types. It does
|
|
47
47
|
not import a dialect core or provide a universal runtime converter.
|
|
48
48
|
|
|
49
49
|
`drizzleTables.users` is a real Drizzle `PgTable`. The logical `users` key,
|
|
@@ -54,8 +54,8 @@ Use the converted tables in ordinary Drizzle queries. Table objects do not need
|
|
|
54
54
|
to be passed to `drizzle()`:
|
|
55
55
|
|
|
56
56
|
```ts
|
|
57
|
-
import { eq } from
|
|
58
|
-
import { drizzle } from
|
|
57
|
+
import { eq } from "drizzle-orm"
|
|
58
|
+
import { drizzle } from "drizzle-orm/node-postgres"
|
|
59
59
|
|
|
60
60
|
const db = drizzle(pool)
|
|
61
61
|
|
|
@@ -74,12 +74,12 @@ Use `sqliteTimestamp()` when an existing SQLite schema stores dates as integer
|
|
|
74
74
|
Unix timestamps and Drizzle must continue reading and writing `Date` values:
|
|
75
75
|
|
|
76
76
|
```ts
|
|
77
|
-
import { schema, table } from
|
|
78
|
-
import { sqliteTimestamp, toSqliteDrizzleSchema } from
|
|
77
|
+
import { schema, table } from "qubu"
|
|
78
|
+
import { sqliteTimestamp, toSqliteDrizzleSchema } from "@qubu/drizzle/sqlite"
|
|
79
79
|
|
|
80
|
-
const events = table(
|
|
80
|
+
const events = table("events", {
|
|
81
81
|
createdAt: sqliteTimestamp({
|
|
82
|
-
mode:
|
|
82
|
+
mode: "timestamp",
|
|
83
83
|
defaultFn: () => new Date(),
|
|
84
84
|
}),
|
|
85
85
|
})
|
|
@@ -127,11 +127,11 @@ unique constraints, checks, foreign keys, and indexes. Native storage must
|
|
|
127
127
|
belong to the selected dialect:
|
|
128
128
|
|
|
129
129
|
```ts
|
|
130
|
-
import { nativeColumn, schema, table } from
|
|
131
|
-
import { toPostgresDrizzleSchema } from
|
|
130
|
+
import { nativeColumn, schema, table } from "qubu"
|
|
131
|
+
import { toPostgresDrizzleSchema } from "@qubu/drizzle/postgres"
|
|
132
132
|
|
|
133
|
-
const records = table(
|
|
134
|
-
handle: nativeColumn(
|
|
133
|
+
const records = table("records", {
|
|
134
|
+
handle: nativeColumn("postgresql", "CITEXT"),
|
|
135
135
|
})
|
|
136
136
|
|
|
137
137
|
const tables = toPostgresDrizzleSchema(schema({ records }))
|
|
@@ -8,12 +8,12 @@ identifiers, placeholders, or pagination:
|
|
|
8
8
|
The examples assume a query has already been built.
|
|
9
9
|
|
|
10
10
|
```ts
|
|
11
|
-
import { render } from
|
|
12
|
-
import { createDialect } from
|
|
11
|
+
import { render } from "qubu"
|
|
12
|
+
import { createDialect } from "qubu/core"
|
|
13
13
|
|
|
14
14
|
const colonDialect = createDialect({
|
|
15
|
-
name:
|
|
16
|
-
placeholder: position =>
|
|
15
|
+
name: "colon",
|
|
16
|
+
placeholder: (position) => ":p" + position,
|
|
17
17
|
})
|
|
18
18
|
|
|
19
19
|
render(query, colonDialect)
|
|
@@ -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
|