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
|
@@ -20,45 +20,38 @@ import {
|
|
|
20
20
|
text,
|
|
21
21
|
unique,
|
|
22
22
|
value,
|
|
23
|
-
} from
|
|
23
|
+
} from "qubu"
|
|
24
24
|
|
|
25
|
-
const accounts = table(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
accountsSlug: index([accounts.slug], { unique: true }),
|
|
34
|
-
},
|
|
35
|
-
})
|
|
36
|
-
)
|
|
25
|
+
const accounts = table("accounts", { id: integer(), slug: text() }, (accounts) => ({
|
|
26
|
+
constraints: {
|
|
27
|
+
accountsPrimary: primaryKey(accounts.id),
|
|
28
|
+
},
|
|
29
|
+
indexes: {
|
|
30
|
+
accountsSlug: index([accounts.slug], { unique: true }),
|
|
31
|
+
},
|
|
32
|
+
}))
|
|
37
33
|
|
|
38
34
|
const memberships = table(
|
|
39
|
-
|
|
35
|
+
"memberships",
|
|
40
36
|
{
|
|
41
37
|
id: integer(),
|
|
42
38
|
accountId: integer(),
|
|
43
39
|
slug: text(),
|
|
44
40
|
displayName: text(),
|
|
45
41
|
},
|
|
46
|
-
memberships => ({
|
|
42
|
+
(memberships) => ({
|
|
47
43
|
constraints: {
|
|
48
44
|
membershipsPrimary: primaryKey(memberships.id),
|
|
49
45
|
membershipsUnique: unique(memberships.accountId, memberships.slug),
|
|
50
|
-
membershipsAccount: foreignKey(
|
|
51
|
-
|
|
52
|
-
references(accounts, accounts.id)
|
|
53
|
-
),
|
|
54
|
-
membershipsCheck: check(eq(memberships.slug, value('public'))),
|
|
46
|
+
membershipsAccount: foreignKey([memberships.accountId], references(accounts, accounts.id)),
|
|
47
|
+
membershipsCheck: check(eq(memberships.slug, value("public"))),
|
|
55
48
|
},
|
|
56
49
|
indexes: {
|
|
57
50
|
membershipsAccountSlug: index([memberships.accountId, memberships.slug], {
|
|
58
51
|
unique: true,
|
|
59
52
|
}),
|
|
60
53
|
},
|
|
61
|
-
})
|
|
54
|
+
}),
|
|
62
55
|
)
|
|
63
56
|
```
|
|
64
57
|
|
|
@@ -81,21 +74,17 @@ Use `uniqueConstraint()` when the database enforces uniqueness but the rule shou
|
|
|
81
74
|
not prove a functional dependency:
|
|
82
75
|
|
|
83
76
|
```ts
|
|
84
|
-
import { table, text, uniqueConstraint } from
|
|
85
|
-
|
|
86
|
-
const accounts = table(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
},
|
|
96
|
-
indexes: {},
|
|
97
|
-
})
|
|
98
|
-
)
|
|
77
|
+
import { table, text, uniqueConstraint } from "qubu"
|
|
78
|
+
|
|
79
|
+
const accounts = table("accounts", { email: text({ nullable: true }) }, (accounts) => ({
|
|
80
|
+
constraints: {
|
|
81
|
+
emailUnique: uniqueConstraint(accounts.email, {
|
|
82
|
+
nulls: "distinct",
|
|
83
|
+
physicalName: "accounts_email_key",
|
|
84
|
+
}),
|
|
85
|
+
},
|
|
86
|
+
indexes: {},
|
|
87
|
+
}))
|
|
99
88
|
```
|
|
100
89
|
|
|
101
90
|
`nulls: 'distinct'` describes the common rule where multiple NULLs do not
|
|
@@ -115,20 +104,15 @@ eligible unique index. Options such as onUpdate, onDelete, match, deferrable,
|
|
|
115
104
|
and initially remain metadata:
|
|
116
105
|
|
|
117
106
|
```ts
|
|
118
|
-
const memberships = table(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
),
|
|
128
|
-
},
|
|
129
|
-
indexes: {},
|
|
130
|
-
})
|
|
131
|
-
)
|
|
107
|
+
const memberships = table("memberships", { accountId: integer() }, (memberships) => ({
|
|
108
|
+
constraints: {
|
|
109
|
+
accountForeign: foreignKey([memberships.accountId], references(accounts, accounts.id), {
|
|
110
|
+
onDelete: "cascade",
|
|
111
|
+
onUpdate: "cascade",
|
|
112
|
+
}),
|
|
113
|
+
},
|
|
114
|
+
indexes: {},
|
|
115
|
+
}))
|
|
132
116
|
```
|
|
133
117
|
|
|
134
118
|
Use the preliminary callback table for direct self-references. Wrap the target
|
|
@@ -145,12 +129,12 @@ Grouping every column in a declared candidate key lets Qubu select other
|
|
|
145
129
|
columns from that source:
|
|
146
130
|
|
|
147
131
|
```ts
|
|
148
|
-
import { count, from, groupBy, select } from
|
|
132
|
+
import { count, from, groupBy, select } from "qubu"
|
|
149
133
|
|
|
150
134
|
const summary = select(
|
|
151
135
|
{ displayName: memberships.displayName, total: count() },
|
|
152
136
|
from(memberships),
|
|
153
|
-
groupBy(memberships.accountId, memberships.slug)
|
|
137
|
+
groupBy(memberships.accountId, memberships.slug),
|
|
154
138
|
)
|
|
155
139
|
```
|
|
156
140
|
|
|
@@ -8,13 +8,13 @@ migration journal. Preflight runs before rendering, so a blocked or incompatible
|
|
|
8
8
|
plan returns diagnostics and no SQL.
|
|
9
9
|
|
|
10
10
|
```ts
|
|
11
|
-
import { emitMigrationPlan } from
|
|
12
|
-
import { postgresSchemaDialect } from
|
|
11
|
+
import { emitMigrationPlan } from "qubu/ddl"
|
|
12
|
+
import { postgresSchemaDialect } from "qubu/snapshot"
|
|
13
13
|
|
|
14
14
|
const result = emitMigrationPlan(plan, postgresSchemaDialect)
|
|
15
15
|
if (!result.ok) {
|
|
16
16
|
// Review result.diagnostics. result.sql is an empty string.
|
|
17
|
-
throw new Error(result.diagnostics.map(item => item.message).join(
|
|
17
|
+
throw new Error(result.diagnostics.map((item) => item.message).join("\n"))
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
for (const statement of result.statements) {
|
|
@@ -44,9 +44,9 @@ produces `transaction-conflict`; no transaction is opened by the emitter.
|
|
|
44
44
|
|
|
45
45
|
```ts
|
|
46
46
|
const result = emitMigrationPlan(plan, postgresSchemaDialect, {
|
|
47
|
-
transaction:
|
|
48
|
-
lock:
|
|
49
|
-
serverVersion:
|
|
47
|
+
transaction: "managed",
|
|
48
|
+
lock: "exclusive",
|
|
49
|
+
serverVersion: "16",
|
|
50
50
|
})
|
|
51
51
|
```
|
|
52
52
|
|
package/docs/schema/diff.md
CHANGED
|
@@ -8,7 +8,7 @@ It keeps the object kind, namespace, logical ID, physical name, dialect, path,
|
|
|
8
8
|
and catalog evidence on every result object.
|
|
9
9
|
|
|
10
10
|
```ts
|
|
11
|
-
import { diffSnapshots } from
|
|
11
|
+
import { diffSnapshots } from "qubu/diff"
|
|
12
12
|
|
|
13
13
|
const result = diffSnapshots(previousSnapshot, currentSnapshot)
|
|
14
14
|
|
|
@@ -32,10 +32,10 @@ use an ID, a physical name, or an exact path when a nested scope repeats an ID.
|
|
|
32
32
|
const result = diffSnapshots(previousSnapshot, currentSnapshot, {
|
|
33
33
|
renameHints: [
|
|
34
34
|
{
|
|
35
|
-
kind:
|
|
36
|
-
namespace:
|
|
37
|
-
from:
|
|
38
|
-
to:
|
|
35
|
+
kind: "table",
|
|
36
|
+
namespace: "public",
|
|
37
|
+
from: "legacy_accounts",
|
|
38
|
+
to: "accounts",
|
|
39
39
|
},
|
|
40
40
|
],
|
|
41
41
|
})
|
|
@@ -37,10 +37,10 @@ queries, authenticate, or start a transaction. Adapt the driver you already
|
|
|
37
37
|
use to `CatalogConnection`:
|
|
38
38
|
|
|
39
39
|
```ts
|
|
40
|
-
import type { CatalogConnection } from
|
|
40
|
+
import type { CatalogConnection } from "qubu/introspection"
|
|
41
41
|
|
|
42
42
|
const connection: CatalogConnection = {
|
|
43
|
-
dialect:
|
|
43
|
+
dialect: "sqlite",
|
|
44
44
|
query(statement, options) {
|
|
45
45
|
// Adapt this call to the driver used by the application.
|
|
46
46
|
return db.query(statement, options)
|
|
@@ -59,16 +59,16 @@ catalog can later support inspection, source generation, or another snapshot
|
|
|
59
59
|
format:
|
|
60
60
|
|
|
61
61
|
```ts
|
|
62
|
-
import { mapCatalogToSnapshot, readSqliteCatalog } from
|
|
62
|
+
import { mapCatalogToSnapshot, readSqliteCatalog } from "qubu/introspection"
|
|
63
63
|
|
|
64
|
-
const catalog = await readSqliteCatalog(connection, { namespace:
|
|
64
|
+
const catalog = await readSqliteCatalog(connection, { namespace: "main" })
|
|
65
65
|
const result = mapCatalogToSnapshot(catalog, {
|
|
66
|
-
namespace:
|
|
67
|
-
mode:
|
|
66
|
+
namespace: "main",
|
|
67
|
+
mode: "strict",
|
|
68
68
|
})
|
|
69
69
|
|
|
70
70
|
if (!result.ok) {
|
|
71
|
-
throw new Error(result.diagnostics.map(issue => issue.message).join(
|
|
71
|
+
throw new Error(result.diagnostics.map((issue) => issue.message).join("\n"))
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
result.snapshot.tables // canonical Snapshot v1 data
|
|
@@ -8,7 +8,7 @@ logical and physical identity evidence, dependency edges, preconditions, safety,
|
|
|
8
8
|
lock and transaction requirements, and reversibility markers.
|
|
9
9
|
|
|
10
10
|
```ts
|
|
11
|
-
import { createMigrationPlan } from
|
|
11
|
+
import { createMigrationPlan } from "qubu/migration"
|
|
12
12
|
|
|
13
13
|
const result = createMigrationPlan(diff)
|
|
14
14
|
if (!result.ok) {
|
|
@@ -31,11 +31,11 @@ kind, namespace, and path, and each decision carries a review reason.
|
|
|
31
31
|
```ts
|
|
32
32
|
const reviewed = createMigrationPlan(diff, {
|
|
33
33
|
decisions: result.plan.operations
|
|
34
|
-
.filter(operation => operation.status ===
|
|
35
|
-
.map(operation => ({
|
|
34
|
+
.filter((operation) => operation.status === "decision-required")
|
|
35
|
+
.map((operation) => ({
|
|
36
36
|
operationId: operation.id,
|
|
37
|
-
action:
|
|
38
|
-
reason:
|
|
37
|
+
action: "allow",
|
|
38
|
+
reason: "Reviewed against the deployment change request",
|
|
39
39
|
})),
|
|
40
40
|
})
|
|
41
41
|
```
|
|
@@ -48,10 +48,10 @@ dialect, safety declaration, reason, reversibility, and dependency position:
|
|
|
48
48
|
createMigrationPlan(diff, {
|
|
49
49
|
customSql: [
|
|
50
50
|
{
|
|
51
|
-
sql:
|
|
52
|
-
dialect: { name:
|
|
53
|
-
safety:
|
|
54
|
-
reason:
|
|
51
|
+
sql: "ALTER TABLE accounts VALIDATE CONSTRAINT accounts_check",
|
|
52
|
+
dialect: { name: "postgresql", version: 1 },
|
|
53
|
+
safety: "review-required",
|
|
54
|
+
reason: "The dialect emitter does not model this catalog fact yet",
|
|
55
55
|
reversible: false,
|
|
56
56
|
position: 3,
|
|
57
57
|
},
|
package/docs/schema/snapshots.md
CHANGED
|
@@ -8,7 +8,7 @@ hashed, checked into source control, and handed to a dialect adapter. Importing
|
|
|
8
8
|
the snapshot entrypoint is optional; ordinary query imports do not load it.
|
|
9
9
|
|
|
10
10
|
```ts
|
|
11
|
-
import { createSchemaSnapshot, encodeSchemaSnapshot } from
|
|
11
|
+
import { createSchemaSnapshot, encodeSchemaSnapshot } from "qubu/snapshot"
|
|
12
12
|
|
|
13
13
|
const snapshot = createSchemaSnapshot(appSchema)
|
|
14
14
|
const json = encodeSchemaSnapshot(snapshot)
|
|
@@ -29,12 +29,12 @@ expression retains its dialect tag and is rejected when it does not match the
|
|
|
29
29
|
selected snapshot dialect.
|
|
30
30
|
|
|
31
31
|
```ts
|
|
32
|
-
import { decodeSchemaSnapshot } from
|
|
32
|
+
import { decodeSchemaSnapshot } from "qubu/snapshot"
|
|
33
33
|
|
|
34
34
|
const decoded = decodeSchemaSnapshot(json)
|
|
35
35
|
if (!decoded.ok) {
|
|
36
36
|
for (const issue of decoded.diagnostics) {
|
|
37
|
-
console.error(issue.path.join(
|
|
37
|
+
console.error(issue.path.join("."), issue.code, issue.message)
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
```
|
|
@@ -33,10 +33,10 @@ Use a dialect-native descriptor when a column needs an exact vendor
|
|
|
33
33
|
declaration:
|
|
34
34
|
|
|
35
35
|
```ts
|
|
36
|
-
import { nativeColumn, nativeStorage, table } from
|
|
36
|
+
import { nativeColumn, nativeStorage, table } from "qubu"
|
|
37
37
|
|
|
38
|
-
const accounts = table(
|
|
39
|
-
handle: nativeColumn(nativeStorage(
|
|
38
|
+
const accounts = table("accounts", {
|
|
39
|
+
handle: nativeColumn(nativeStorage("postgresql", 'citext COLLATE "C"')),
|
|
40
40
|
})
|
|
41
41
|
```
|
|
42
42
|
|
|
@@ -52,12 +52,12 @@ can be rendered for checks, generated columns, indexes, and other declaration
|
|
|
52
52
|
metadata:
|
|
53
53
|
|
|
54
54
|
```ts
|
|
55
|
-
import { eq, table, text } from
|
|
56
|
-
import { renderSchemaSql } from
|
|
55
|
+
import { eq, table, text } from "qubu"
|
|
56
|
+
import { renderSchemaSql } from "qubu/schema"
|
|
57
57
|
|
|
58
|
-
const accounts = table(
|
|
58
|
+
const accounts = table("accounts", { status: text() })
|
|
59
59
|
|
|
60
|
-
renderSchemaSql(eq(accounts.status,
|
|
60
|
+
renderSchemaSql(eq(accounts.status, "active"), { mode: "check" })
|
|
61
61
|
// ("status" = 'active')
|
|
62
62
|
```
|
|
63
63
|
|
|
@@ -74,13 +74,13 @@ subqueries are rejected in every schema mode.
|
|
|
74
74
|
An extension must opt into the schema contract with `defineSchemaExpression()`:
|
|
75
75
|
|
|
76
76
|
```ts
|
|
77
|
-
import { defineSchemaExpression, renderSchemaSql } from
|
|
77
|
+
import { defineSchemaExpression, renderSchemaSql } from "qubu/schema"
|
|
78
78
|
|
|
79
|
-
const currentDate = defineSchemaExpression(
|
|
80
|
-
context.append(
|
|
79
|
+
const currentDate = defineSchemaExpression("function", (context) => {
|
|
80
|
+
context.append("CURRENT_DATE")
|
|
81
81
|
})
|
|
82
82
|
|
|
83
|
-
renderSchemaSql(currentDate, { mode:
|
|
83
|
+
renderSchemaSql(currentDate, { mode: "default" })
|
|
84
84
|
```
|
|
85
85
|
|
|
86
86
|
## Use raw schema SQL only when necessary
|
|
@@ -12,17 +12,17 @@ record keys become logical table IDs and stay stable when a physical SQL name
|
|
|
12
12
|
changes:
|
|
13
13
|
|
|
14
14
|
```ts
|
|
15
|
-
import { integer, schema, table, text } from
|
|
15
|
+
import { integer, schema, table, text } from "qubu"
|
|
16
16
|
|
|
17
|
-
const accounts = table(
|
|
17
|
+
const accounts = table("account_records", {
|
|
18
18
|
id: integer(),
|
|
19
19
|
email: text(),
|
|
20
20
|
})
|
|
21
|
-
const memberships = table(
|
|
21
|
+
const memberships = table("membership_records", {
|
|
22
22
|
accountId: integer(),
|
|
23
23
|
})
|
|
24
24
|
|
|
25
|
-
const appSchema = schema({ accounts, memberships }, { namespace:
|
|
25
|
+
const appSchema = schema({ accounts, memberships }, { namespace: "public" })
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
appSchema.registry.accounts.id is "accounts", while the physical table name is
|
|
@@ -47,17 +47,14 @@ Write schema keys in camelCase. Qubu converts them to snake_case in SQL, then
|
|
|
47
47
|
uses the camelCase keys in the returned row:
|
|
48
48
|
|
|
49
49
|
```ts
|
|
50
|
-
import { from, select, table, timestamp, uuid } from
|
|
50
|
+
import { from, select, table, timestamp, uuid } from "qubu"
|
|
51
51
|
|
|
52
|
-
const events = table(
|
|
52
|
+
const events = table("events", {
|
|
53
53
|
userId: uuid(),
|
|
54
54
|
createdAt: timestamp(),
|
|
55
55
|
})
|
|
56
56
|
|
|
57
|
-
const query = select(
|
|
58
|
-
{ userId: events.userId, createdAt: events.createdAt },
|
|
59
|
-
from(events)
|
|
60
|
-
)
|
|
57
|
+
const query = select({ userId: events.userId, createdAt: events.createdAt }, from(events))
|
|
61
58
|
```
|
|
62
59
|
|
|
63
60
|
The query selects "events"."user_id" and "events"."created_at", then aliases
|
|
@@ -76,8 +73,8 @@ Prefer `userId` and `apiKey` when you control the TypeScript name. Use `sqlName`
|
|
|
76
73
|
the database name does not follow the convention:
|
|
77
74
|
|
|
78
75
|
```ts
|
|
79
|
-
const events = table(
|
|
80
|
-
createdAt: timestamp({ sqlName:
|
|
76
|
+
const events = table("events", {
|
|
77
|
+
createdAt: timestamp({ sqlName: "creation_timestamp" }),
|
|
81
78
|
})
|
|
82
79
|
```
|
|
83
80
|
|
|
@@ -89,9 +86,9 @@ their projected names stay snake_case. Only the outer result projection uses
|
|
|
89
86
|
camelCase aliases. Pass the database relation name explicitly:
|
|
90
87
|
|
|
91
88
|
```ts
|
|
92
|
-
import { table, uuid } from
|
|
89
|
+
import { table, uuid } from "qubu"
|
|
93
90
|
|
|
94
|
-
const accounts = table(
|
|
91
|
+
const accounts = table("user_accounts", {
|
|
95
92
|
id: uuid(),
|
|
96
93
|
})
|
|
97
94
|
```
|
|
@@ -43,10 +43,10 @@ Specific result domains are still retained. `lower(textColumn)` produces
|
|
|
43
43
|
`SqlText`, while `avg(integerColumn)` produces `SqlDecimal`:
|
|
44
44
|
|
|
45
45
|
```ts
|
|
46
|
-
import { avg, integer, lower, table, text } from
|
|
47
|
-
import type { SqlTypeOf } from
|
|
46
|
+
import { avg, integer, lower, table, text } from "qubu"
|
|
47
|
+
import type { SqlTypeOf } from "qubu"
|
|
48
48
|
|
|
49
|
-
const metrics = table(
|
|
49
|
+
const metrics = table("metrics", {
|
|
50
50
|
label: text(),
|
|
51
51
|
sampleCount: integer(),
|
|
52
52
|
})
|
|
@@ -72,15 +72,15 @@ semantics. Contextual JavaScript literals remain ergonomic for compatible
|
|
|
72
72
|
operators:
|
|
73
73
|
|
|
74
74
|
```ts
|
|
75
|
-
import { asc, eq, inList, like, lower, orderBy, table, uuid } from
|
|
75
|
+
import { asc, eq, inList, like, lower, orderBy, table, uuid } from "qubu"
|
|
76
76
|
|
|
77
|
-
const records = table(
|
|
77
|
+
const records = table("records", { id: uuid() })
|
|
78
78
|
|
|
79
|
-
eq(records.id,
|
|
80
|
-
inList(records.id, [
|
|
79
|
+
eq(records.id, "108cb836-20d2-41b2-8c23-f0c94700aa7e") // valid
|
|
80
|
+
inList(records.id, ["first-id", "second-id"]) // valid
|
|
81
81
|
|
|
82
82
|
lower(records.id) // TypeScript error: SqlUuid is not SqlTextLike
|
|
83
|
-
like(records.id,
|
|
83
|
+
like(records.id, "%uuid%") // TypeScript error: SqlUuid is not SqlTextLike
|
|
84
84
|
orderBy(asc(records.id)) // TypeScript error: SqlUuid is not portably orderable
|
|
85
85
|
```
|
|
86
86
|
|
|
@@ -90,10 +90,10 @@ operation. This contextual typing does not relabel an expression: comparing a
|
|
|
90
90
|
the database operation intentionally changes domains:
|
|
91
91
|
|
|
92
92
|
```ts
|
|
93
|
-
import { cast, like, text } from
|
|
93
|
+
import { cast, like, text } from "qubu"
|
|
94
94
|
|
|
95
95
|
const idAsText = cast(records.id, text())
|
|
96
|
-
like(idAsText,
|
|
96
|
+
like(idAsText, "108c%")
|
|
97
97
|
```
|
|
98
98
|
|
|
99
99
|
Built-in definitions carry logical cast targets, so the active dialect can
|
package/docs/troubleshooting.md
CHANGED
|
@@ -12,9 +12,9 @@ Check that the query includes the original source or use the columns exposed by
|
|
|
12
12
|
the alias, CTE, or derived table you actually placed in the query:
|
|
13
13
|
|
|
14
14
|
```ts
|
|
15
|
-
import { alias, from, select } from
|
|
15
|
+
import { alias, from, select } from "qubu"
|
|
16
16
|
|
|
17
|
-
const author = alias(users,
|
|
17
|
+
const author = alias(users, "author")
|
|
18
18
|
|
|
19
19
|
select({ name: author.name }, from(author))
|
|
20
20
|
```
|
|
@@ -27,9 +27,9 @@ identity after aliasing.
|
|
|
27
27
|
This is the default mutation safety check. Add a source-aware predicate:
|
|
28
28
|
|
|
29
29
|
```ts
|
|
30
|
-
import { deleteFrom, eq, update, where } from
|
|
30
|
+
import { deleteFrom, eq, update, where } from "qubu"
|
|
31
31
|
|
|
32
|
-
update(users, { name:
|
|
32
|
+
update(users, { name: "Ada" }, where(eq(users.id, 7)))
|
|
33
33
|
deleteFrom(users, where(eq(users.id, 8)))
|
|
34
34
|
```
|
|
35
35
|
|
|
@@ -43,7 +43,7 @@ A scalar subquery must return one selected field. Reduce the projection before
|
|
|
43
43
|
calling `scalar()`:
|
|
44
44
|
|
|
45
45
|
```ts
|
|
46
|
-
import { from, scalar, select } from
|
|
46
|
+
import { from, scalar, select } from "qubu"
|
|
47
47
|
|
|
48
48
|
const idQuery = select({ id: users.id }, from(users))
|
|
49
49
|
const idExpression = scalar(idQuery)
|
|
@@ -58,8 +58,8 @@ Render with the dialect that the adapter expects and inspect both fields of the
|
|
|
58
58
|
result:
|
|
59
59
|
|
|
60
60
|
```ts
|
|
61
|
-
import { render } from
|
|
62
|
-
import { postgresDialect } from
|
|
61
|
+
import { render } from "qubu"
|
|
62
|
+
import { postgresDialect } from "qubu/postgres"
|
|
63
63
|
|
|
64
64
|
const statement = render(query, postgresDialect())
|
|
65
65
|
console.log(statement.text)
|
|
@@ -78,7 +78,7 @@ path, and hint before changing the query. The hint names the repair boundary;
|
|
|
78
78
|
the path points to the clause or field that needs attention.
|
|
79
79
|
|
|
80
80
|
```ts
|
|
81
|
-
import { QueryValidationError, fetchFirst } from
|
|
81
|
+
import { QueryValidationError, fetchFirst } from "qubu"
|
|
82
82
|
|
|
83
83
|
try {
|
|
84
84
|
fetchFirst(-1)
|
|
@@ -140,10 +140,10 @@ placement. Rendering traverses that normalized statement, so inspect the final
|
|
|
140
140
|
`text` and `parameters` together rather than assuming source argument order:
|
|
141
141
|
|
|
142
142
|
```ts
|
|
143
|
-
import { eq, fetchFirst, from, render, select, where } from
|
|
143
|
+
import { eq, fetchFirst, from, render, select, where } from "qubu"
|
|
144
144
|
|
|
145
145
|
const statement = render(
|
|
146
|
-
select({ id: users.id }, from(users), where(eq(users.id, 7)), fetchFirst(10))
|
|
146
|
+
select({ id: users.id }, from(users), where(eq(users.id, 7)), fetchFirst(10)),
|
|
147
147
|
)
|
|
148
148
|
```
|
|
149
149
|
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qubu",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/aleclarson/qubu"
|
|
7
7
|
},
|
|
8
|
-
"type": "module",
|
|
9
8
|
"files": [
|
|
10
9
|
"dist",
|
|
11
10
|
"docs",
|
|
12
11
|
"skills"
|
|
13
12
|
],
|
|
13
|
+
"type": "module",
|
|
14
14
|
"exports": {
|
|
15
15
|
".": {
|
|
16
16
|
"types": "./dist/index.d.mts",
|
|
@@ -32,10 +32,6 @@
|
|
|
32
32
|
"types": "./dist/diff.d.mts",
|
|
33
33
|
"import": "./dist/diff.mjs"
|
|
34
34
|
},
|
|
35
|
-
"./drizzle": {
|
|
36
|
-
"types": "./dist/drizzle.d.mts",
|
|
37
|
-
"import": "./dist/drizzle.mjs"
|
|
38
|
-
},
|
|
39
35
|
"./introspection": {
|
|
40
36
|
"types": "./dist/introspection.d.mts",
|
|
41
37
|
"import": "./dist/introspection.mjs"
|
|
@@ -69,34 +65,29 @@
|
|
|
69
65
|
"import": "./dist/vite.mjs"
|
|
70
66
|
},
|
|
71
67
|
"./package.json": "./package.json",
|
|
72
|
-
"./drizzle/mysql": {
|
|
73
|
-
"types": "./dist/drizzle-mysql.d.mts",
|
|
74
|
-
"import": "./dist/drizzle-mysql.mjs"
|
|
75
|
-
},
|
|
76
|
-
"./drizzle/postgres": {
|
|
77
|
-
"types": "./dist/drizzle-postgres.d.mts",
|
|
78
|
-
"import": "./dist/drizzle-postgres.mjs"
|
|
79
|
-
},
|
|
80
|
-
"./drizzle/sqlite": {
|
|
81
|
-
"types": "./dist/drizzle-sqlite.d.mts",
|
|
82
|
-
"import": "./dist/drizzle-sqlite.mjs"
|
|
83
|
-
},
|
|
84
68
|
"./globals": {
|
|
85
69
|
"types": "./dist/vite/ambient.d.ts"
|
|
86
70
|
}
|
|
87
71
|
},
|
|
88
|
-
"
|
|
72
|
+
"dependencies": {
|
|
73
|
+
"@standard-schema/spec": "^1.0.0"
|
|
74
|
+
},
|
|
89
75
|
"devDependencies": {
|
|
90
76
|
"@alloc/prettier-config": "^1.0.0",
|
|
77
|
+
"@electric-sql/pglite": "^0.5.8",
|
|
78
|
+
"@libsql/client": "^0.17.4",
|
|
91
79
|
"@pg-nano/pg-tmp": "^2.0.3",
|
|
80
|
+
"@stylistic/eslint-plugin": "^6.0.0-beta.6",
|
|
92
81
|
"@types/bun": "^1.3.0",
|
|
93
82
|
"@types/node": "^24.6.2",
|
|
94
83
|
"@types/pg": "^8.23.1",
|
|
95
|
-
"drizzle-orm": "1.0.0-rc.4",
|
|
96
84
|
"lildocs": "^0.1.27",
|
|
97
85
|
"mysql2": "^3.23.4",
|
|
86
|
+
"oxfmt": "^0.58.0",
|
|
87
|
+
"oxlint": "1.73.0",
|
|
98
88
|
"pg": "^8.23.0",
|
|
99
89
|
"picospawn": "^0.3.10",
|
|
90
|
+
"postgres": "^3.4.9",
|
|
100
91
|
"prettier": "^3.6.2",
|
|
101
92
|
"qubu": "link:",
|
|
102
93
|
"radashi": "^12.6.2",
|
|
@@ -106,30 +97,27 @@
|
|
|
106
97
|
"typescript": "^5.9.3",
|
|
107
98
|
"vitest": "^3.2.4"
|
|
108
99
|
},
|
|
109
|
-
"
|
|
110
|
-
"@standard-schema/spec": "^1.0.0"
|
|
111
|
-
},
|
|
112
|
-
"peerDependencies": {
|
|
113
|
-
"drizzle-orm": "^1.0.0-rc.4"
|
|
114
|
-
},
|
|
115
|
-
"peerDependenciesMeta": {
|
|
116
|
-
"drizzle-orm": {
|
|
117
|
-
"optional": true
|
|
118
|
-
}
|
|
119
|
-
},
|
|
100
|
+
"prettier": "@alloc/prettier-config",
|
|
120
101
|
"scripts": {
|
|
121
102
|
"build": "tsdown",
|
|
103
|
+
"build:workspace-packages": "pnpm --recursive --filter './adapters/*' build",
|
|
122
104
|
"check:package": "node ./scripts/test-package.mjs --check-built",
|
|
105
|
+
"check:workspace-versions": "node ./scripts/set-release-version.mjs --check",
|
|
123
106
|
"playground": "bun run ./playground/playground.ts",
|
|
124
107
|
"test": "vitest",
|
|
125
|
-
"test:e2e": "vitest --run test/e2e
|
|
108
|
+
"test:e2e": "vitest --run test/e2e",
|
|
126
109
|
"test:type-performance": "node ./scripts/test-type-performance.mjs",
|
|
127
110
|
"diff-check": "vitest --run test/diff.test.ts test/migration.test.ts",
|
|
128
111
|
"test:bun": "node ./scripts/test-package.mjs --runtime=bun",
|
|
129
112
|
"test:package": "node ./scripts/test-package.mjs",
|
|
113
|
+
"test:workspace-children": "pnpm --recursive --filter './adapters/*' build && node ./scripts/test-workspace-packages.mjs",
|
|
114
|
+
"test:workspace-packages": "pnpm run test:package && pnpm run test:workspace-children",
|
|
130
115
|
"test:package:node": "node ./scripts/test-package.mjs --runtime=node",
|
|
131
116
|
"typecheck": "vitest --typecheck --run",
|
|
117
|
+
"typecheck:workspace-packages": "pnpm --recursive --filter './adapters/*' typecheck",
|
|
132
118
|
"typecheck:playground": "tsc --project ./playground/tsconfig.json",
|
|
133
|
-
"
|
|
119
|
+
"format": "oxlint --fix && oxfmt",
|
|
120
|
+
"release:version": "node ./scripts/set-release-version.mjs",
|
|
121
|
+
"release:publish": "node ./scripts/publish-workspace.mjs"
|
|
134
122
|
}
|
|
135
123
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
interface:
|
|
2
|
-
display_name:
|
|
3
|
-
short_description:
|
|
4
|
-
default_prompt:
|
|
2
|
+
display_name: "Qubu"
|
|
3
|
+
short_description: "Route Qubu work to package docs"
|
|
4
|
+
default_prompt: "Use $qubu to find the smallest canonical Qubu guide for this task."
|
|
5
5
|
policy:
|
|
6
6
|
allow_implicit_invocation: true
|