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/dist/vite.d.mts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
//#region src/vite/globals.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* Public runtime names that the `"use qubu"` transform can auto-import.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* the layered entrypoints.
|
|
3
|
+
* Public runtime names that the `"use qubu"` transform can auto-import. Keep this catalog aligned
|
|
4
|
+
* with the ordinary authoring surface of `qubu`; fragment internals, dialect construction, and
|
|
5
|
+
* schema extensions belong to the layered entrypoints.
|
|
7
6
|
*/
|
|
8
7
|
declare const qubuGlobals: readonly ["add", "alias", "all", "allowAll", "and", "asc", "asValue", "avg", "bigint", "between", "binary", "boolean", "call", "caseWhen", "cast", "check", "coalesce", "column", "concat", "count", "countDistinct", "correlate", "crossJoin", "cte", "date", "denseRank", "defaultValues", "deleteFrom", "desc", "distinct", "divide", "eq", "except", "execute", "executeRows", "externalDefault", "externalGeneratedColumn", "exists", "fetchFirst", "fetchNext", "foreignKey", "from", "fullJoin", "generatedColumn", "gt", "gte", "groupBy", "having", "identityColumn", "inList", "inQuery", "index", "innerJoin", "insertInto", "insertSelect", "integer", "intersect", "isDistinctFrom", "isNotDistinctFrom", "isNotNull", "isNull", "isTrue", "json", "jsonBoolean", "jsonExists", "jsonNumber", "jsonPath", "jsonText", "lateral", "leftJoin", "like", "lower", "lt", "lte", "max", "min", "modulo", "multiply", "naturalJoin", "nativeColumn", "nativeStorage", "ne", "not", "notExists", "notIn", "notLike", "nullsFirst", "nullsLast", "numeric", "nullable", "offset", "omit", "or", "order", "orderBy", "over", "portableStorage", "primaryKey", "qubu", "references", "recursiveCte", "render", "returning", "rightJoin", "rank", "rowNumber", "scalar", "schema", "schemaCall", "select", "sql", "stream", "subtract", "sum", "table", "text", "timestamp", "union", "unionAll", "unique", "uniqueConstraint", "update", "upper", "value", "values", "where", "withCte", "uuid"];
|
|
9
8
|
type QubuGlobal = (typeof qubuGlobals)[number];
|
|
@@ -25,8 +24,8 @@ interface QubuViteTransformResult {
|
|
|
25
24
|
}
|
|
26
25
|
/** A Vite-compatible plugin without a runtime dependency on Vite itself. */
|
|
27
26
|
interface QubuVitePlugin {
|
|
28
|
-
readonly name:
|
|
29
|
-
readonly enforce:
|
|
27
|
+
readonly name: "qubu:compiler-hint";
|
|
28
|
+
readonly enforce: "pre";
|
|
30
29
|
transform(code: string, id: string): QubuViteTransformResult | null;
|
|
31
30
|
}
|
|
32
31
|
declare function qubu(options?: QubuVitePluginOptions): QubuVitePlugin;
|
package/dist/vite.mjs
CHANGED
|
@@ -70,10 +70,9 @@ function isWhitespace$1(character) {
|
|
|
70
70
|
//#endregion
|
|
71
71
|
//#region src/vite/globals.ts
|
|
72
72
|
/**
|
|
73
|
-
* Public runtime names that the `"use qubu"` transform can auto-import.
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* the layered entrypoints.
|
|
73
|
+
* Public runtime names that the `"use qubu"` transform can auto-import. Keep this catalog aligned
|
|
74
|
+
* with the ordinary authoring surface of `qubu`; fragment internals, dialect construction, and
|
|
75
|
+
* schema extensions belong to the layered entrypoints.
|
|
77
76
|
*/
|
|
78
77
|
const qubuGlobals = [
|
|
79
78
|
"add",
|
|
@@ -20,8 +20,8 @@ Construct the query without choosing a driver, then render it with the policy
|
|
|
20
20
|
the adapter expects:
|
|
21
21
|
|
|
22
22
|
```ts
|
|
23
|
-
import { render } from
|
|
24
|
-
import { postgresDialect } from
|
|
23
|
+
import { render } from "qubu"
|
|
24
|
+
import { postgresDialect } from "qubu/postgres"
|
|
25
25
|
|
|
26
26
|
const standard = render(query)
|
|
27
27
|
const postgres = render(query, postgresDialect())
|
|
@@ -40,15 +40,11 @@ capability requirement to the rendering boundary. PostgreSQL's `ilike()` is
|
|
|
40
40
|
the first such feature:
|
|
41
41
|
|
|
42
42
|
```ts
|
|
43
|
-
import { from, like, render, select, where } from
|
|
44
|
-
import { ilike, postgresDialect } from
|
|
45
|
-
import { sqliteDialect } from
|
|
43
|
+
import { from, like, render, select, where } from "qubu"
|
|
44
|
+
import { ilike, postgresDialect } from "qubu/postgres"
|
|
45
|
+
import { sqliteDialect } from "qubu/sqlite"
|
|
46
46
|
|
|
47
|
-
const postgresQuery = select(
|
|
48
|
-
{ name: users.name },
|
|
49
|
-
from(users),
|
|
50
|
-
where(ilike(users.name, '%ada%'))
|
|
51
|
-
)
|
|
47
|
+
const postgresQuery = select({ name: users.name }, from(users), where(ilike(users.name, "%ada%")))
|
|
52
48
|
|
|
53
49
|
render(postgresQuery, postgresDialect()) // supported
|
|
54
50
|
render(postgresQuery, sqliteDialect()) // TypeScript error
|
|
@@ -59,14 +55,10 @@ received from an untyped integration. Use the portable operator when the
|
|
|
59
55
|
query must render across dialects:
|
|
60
56
|
|
|
61
57
|
```ts
|
|
62
|
-
import { from, like, render, select, where } from
|
|
63
|
-
import { sqliteDialect } from
|
|
58
|
+
import { from, like, render, select, where } from "qubu"
|
|
59
|
+
import { sqliteDialect } from "qubu/sqlite"
|
|
64
60
|
|
|
65
|
-
const portableQuery = select(
|
|
66
|
-
{ name: users.name },
|
|
67
|
-
from(users),
|
|
68
|
-
where(like(users.name, '%ada%'))
|
|
69
|
-
)
|
|
61
|
+
const portableQuery = select({ name: users.name }, from(users), where(like(users.name, "%ada%")))
|
|
70
62
|
|
|
71
63
|
render(portableQuery)
|
|
72
64
|
render(portableQuery, sqliteDialect())
|
|
@@ -81,45 +73,94 @@ does not re-export concrete dialect constructors.
|
|
|
81
73
|
|
|
82
74
|
## The adapter owns the driver
|
|
83
75
|
|
|
84
|
-
Qubu does not open connections
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
76
|
+
Qubu does not open connections or bind values for a particular client. An
|
|
77
|
+
adapter receives an `ExecutionRequest` and returns driver-normalized object
|
|
78
|
+
rows. Qubu then uses the query's result shape and the adapter's decoder policy
|
|
79
|
+
to produce the typed `ExecutionResult`. A `TransactionalQueryAdapter` can also
|
|
80
|
+
pin one driver connection for a callback transaction:
|
|
88
81
|
|
|
89
82
|
```ts
|
|
90
|
-
import { qubu } from
|
|
91
|
-
import { postgresDialect } from
|
|
92
|
-
import type { ExecutionRequest,
|
|
83
|
+
import { qubu } from "qubu"
|
|
84
|
+
import { postgresDialect } from "qubu/postgres"
|
|
85
|
+
import type { ExecutionRequest, QueryAdapter } from "qubu"
|
|
93
86
|
|
|
94
87
|
declare const driver: {
|
|
95
88
|
query<TRow extends object>(
|
|
96
89
|
text: string,
|
|
97
90
|
parameters: readonly unknown[],
|
|
98
|
-
options: { signal?: AbortSignal }
|
|
91
|
+
options: { signal?: AbortSignal },
|
|
99
92
|
): Promise<{ rows: readonly TRow[]; rowCount: number | null }>
|
|
100
93
|
}
|
|
101
94
|
|
|
102
95
|
const adapter: QueryAdapter = {
|
|
103
96
|
dialect: postgresDialect(),
|
|
104
|
-
async execute
|
|
97
|
+
async execute(request: ExecutionRequest) {
|
|
105
98
|
const { statement, queryKind, signal } = request
|
|
106
|
-
const result = await driver.query<
|
|
99
|
+
const result = await driver.query<Record<string, unknown>>(
|
|
107
100
|
statement.text,
|
|
108
101
|
statement.parameters,
|
|
109
|
-
{ signal }
|
|
102
|
+
{ signal },
|
|
110
103
|
)
|
|
111
104
|
return {
|
|
112
105
|
rows: result.rows,
|
|
113
|
-
...(queryKind !==
|
|
114
|
-
queryKind !== 'set' &&
|
|
115
|
-
result.rowCount !== null
|
|
106
|
+
...(queryKind !== "select" && queryKind !== "set" && result.rowCount !== null
|
|
116
107
|
? { affectedRows: result.rowCount }
|
|
117
108
|
: {}),
|
|
118
|
-
}
|
|
109
|
+
}
|
|
119
110
|
},
|
|
120
111
|
}
|
|
121
112
|
```
|
|
122
113
|
|
|
114
|
+
### Decode schema-aware result values
|
|
115
|
+
|
|
116
|
+
Portable boolean, date, timestamp, and JSON columns retain their logical
|
|
117
|
+
result domains through projection aliases, derived queries, CTEs, set
|
|
118
|
+
operations, and mutation `RETURNING`. Register only the conversions required
|
|
119
|
+
by the selected driver configuration:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import {
|
|
123
|
+
booleanResultDecoder,
|
|
124
|
+
dateResultDecoder,
|
|
125
|
+
jsonTextResultDecoder,
|
|
126
|
+
timestampResultDecoder,
|
|
127
|
+
} from "qubu"
|
|
128
|
+
import type { AdapterExecutionResult, QueryAdapter, RenderedQuery } from "qubu"
|
|
129
|
+
import { sqliteDialect } from "qubu/sqlite"
|
|
130
|
+
|
|
131
|
+
declare const sqliteDriver: {
|
|
132
|
+
execute(statement: RenderedQuery): Promise<AdapterExecutionResult>
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const adapter: QueryAdapter = {
|
|
136
|
+
dialect: sqliteDialect(),
|
|
137
|
+
decoders: {
|
|
138
|
+
boolean: booleanResultDecoder,
|
|
139
|
+
date: dateResultDecoder,
|
|
140
|
+
timestamp: timestampResultDecoder,
|
|
141
|
+
json: jsonTextResultDecoder,
|
|
142
|
+
},
|
|
143
|
+
async execute(request) {
|
|
144
|
+
return sqliteDriver.execute(request.statement)
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Do not register `jsonTextResultDecoder` when the driver already returns parsed
|
|
150
|
+
JSON. A JSON string is otherwise ambiguous: it may be serialized JSON or an
|
|
151
|
+
already-decoded JSON string scalar. With no registered decoder, Qubu preserves
|
|
152
|
+
the driver's value.
|
|
153
|
+
|
|
154
|
+
Use a column decoder for a custom stored type, or `mapResult()` for one
|
|
155
|
+
expression. Both override adapter policy for that field:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
import { column, mapResult, value } from "qubu"
|
|
159
|
+
|
|
160
|
+
const score = column<number>({ decode: (value) => Number(value) })
|
|
161
|
+
const decodedTotal = mapResult(value("42"), (value) => Number(value))
|
|
162
|
+
```
|
|
163
|
+
|
|
123
164
|
## Stream read results
|
|
124
165
|
|
|
125
166
|
Add `StreamingQueryAdapter` when a driver can return rows through an
|
|
@@ -129,45 +170,41 @@ Mutations stay on `execute()` and `executeRows()`, including mutations with
|
|
|
129
170
|
`RETURNING`.
|
|
130
171
|
|
|
131
172
|
```ts
|
|
132
|
-
import { qubu } from
|
|
133
|
-
import type {
|
|
134
|
-
|
|
135
|
-
ExecutionResult,
|
|
136
|
-
StreamingQueryAdapter,
|
|
137
|
-
} from 'qubu'
|
|
138
|
-
import { postgresDialect } from 'qubu/postgres'
|
|
173
|
+
import { qubu } from "qubu"
|
|
174
|
+
import type { ExecutionRequest, StreamingQueryAdapter } from "qubu"
|
|
175
|
+
import { postgresDialect } from "qubu/postgres"
|
|
139
176
|
|
|
140
177
|
declare const driver: {
|
|
141
178
|
query<TRow extends object>(
|
|
142
179
|
text: string,
|
|
143
180
|
parameters: readonly unknown[],
|
|
144
|
-
options: { signal?: AbortSignal }
|
|
181
|
+
options: { signal?: AbortSignal },
|
|
145
182
|
): Promise<{ rows: readonly TRow[]; rowCount: number | null }>
|
|
146
183
|
stream<TRow extends object>(
|
|
147
184
|
text: string,
|
|
148
185
|
parameters: readonly unknown[],
|
|
149
|
-
options: { signal?: AbortSignal }
|
|
186
|
+
options: { signal?: AbortSignal },
|
|
150
187
|
): AsyncIterable<TRow>
|
|
151
188
|
}
|
|
152
189
|
|
|
153
190
|
const adapter: StreamingQueryAdapter = {
|
|
154
191
|
dialect: postgresDialect(),
|
|
155
|
-
async execute
|
|
156
|
-
const result = await driver.query<
|
|
192
|
+
async execute(request: ExecutionRequest) {
|
|
193
|
+
const result = await driver.query<Record<string, unknown>>(
|
|
157
194
|
request.statement.text,
|
|
158
195
|
request.statement.parameters,
|
|
159
|
-
{ signal: request.signal }
|
|
196
|
+
{ signal: request.signal },
|
|
160
197
|
)
|
|
161
198
|
return {
|
|
162
199
|
rows: result.rows,
|
|
163
200
|
...(result.rowCount === null ? {} : { affectedRows: result.rowCount }),
|
|
164
|
-
}
|
|
201
|
+
}
|
|
165
202
|
},
|
|
166
|
-
stream
|
|
167
|
-
return driver.stream<
|
|
203
|
+
stream(request: ExecutionRequest) {
|
|
204
|
+
return driver.stream<Record<string, unknown>>(
|
|
168
205
|
request.statement.text,
|
|
169
206
|
request.statement.parameters,
|
|
170
|
-
{ signal: request.signal }
|
|
207
|
+
{ signal: request.signal },
|
|
171
208
|
)
|
|
172
209
|
},
|
|
173
210
|
}
|
|
@@ -178,11 +215,11 @@ for await (const row of db.stream(readQuery)) {
|
|
|
178
215
|
}
|
|
179
216
|
```
|
|
180
217
|
|
|
181
|
-
Qubu renders the query before calling `stream()
|
|
182
|
-
|
|
183
|
-
same `ExecutionRequest` used by `execute()`. The adapter
|
|
184
|
-
|
|
185
|
-
|
|
218
|
+
Qubu renders the query with the selected dialect before calling `stream()`. It
|
|
219
|
+
passes ordered raw parameters, query kind, result shape, and the optional
|
|
220
|
+
`AbortSignal` in the same `ExecutionRequest` used by `execute()`. The adapter
|
|
221
|
+
binds values and returns the iterable. Qubu lazily decodes each row without
|
|
222
|
+
opening a cursor or connection, buffering rows, or imposing a fetch size.
|
|
186
223
|
|
|
187
224
|
The adapter owns the iterator's cleanup contract:
|
|
188
225
|
|
|
@@ -212,10 +249,10 @@ transaction callback receives a streaming client. The adapter must keep its
|
|
|
212
249
|
cursor and connection valid until the callback's streams finish or close:
|
|
213
250
|
|
|
214
251
|
```ts
|
|
215
|
-
declare const transactionalAdapter: import(
|
|
252
|
+
declare const transactionalAdapter: import("qubu").StreamingTransactionalQueryAdapter
|
|
216
253
|
const transactionalDb = qubu(transactionalAdapter)
|
|
217
254
|
|
|
218
|
-
await transactionalDb.transaction(async transaction => {
|
|
255
|
+
await transactionalDb.transaction(async (transaction) => {
|
|
219
256
|
for await (const row of transaction.stream(readQuery)) {
|
|
220
257
|
consume(row)
|
|
221
258
|
}
|
|
@@ -234,17 +271,17 @@ standalone `explain()` function and the bound `db.explain()` method render a
|
|
|
234
271
|
plan request without calling `execute()`:
|
|
235
272
|
|
|
236
273
|
```ts
|
|
237
|
-
import { explain, qubu } from
|
|
238
|
-
import type { ExplainableQueryAdapter } from
|
|
239
|
-
import { postgresDialect } from
|
|
274
|
+
import { explain, qubu } from "qubu"
|
|
275
|
+
import type { ExplainableQueryAdapter } from "qubu"
|
|
276
|
+
import { postgresDialect } from "qubu/postgres"
|
|
240
277
|
|
|
241
|
-
type PostgresPlanRow = {
|
|
278
|
+
type PostgresPlanRow = { "QUERY PLAN": string }
|
|
242
279
|
|
|
243
280
|
declare const driver: {
|
|
244
281
|
query<TRow extends object>(
|
|
245
282
|
text: string,
|
|
246
283
|
parameters: readonly unknown[],
|
|
247
|
-
options: { signal?: AbortSignal }
|
|
284
|
+
options: { signal?: AbortSignal },
|
|
248
285
|
): Promise<{ rows: readonly TRow[] }>
|
|
249
286
|
}
|
|
250
287
|
|
|
@@ -257,7 +294,7 @@ const adapter: ExplainableQueryAdapter<PostgresPlanRow> = {
|
|
|
257
294
|
const result = await driver.query<PostgresPlanRow>(
|
|
258
295
|
request.statement.text,
|
|
259
296
|
request.statement.parameters,
|
|
260
|
-
{ signal: request.signal }
|
|
297
|
+
{ signal: request.signal },
|
|
261
298
|
)
|
|
262
299
|
return { rows: result.rows }
|
|
263
300
|
},
|
|
@@ -322,13 +359,13 @@ Use a transactional adapter when several queries must share one commit or
|
|
|
322
359
|
rollback boundary:
|
|
323
360
|
|
|
324
361
|
```ts
|
|
325
|
-
import { qubu } from
|
|
326
|
-
import type { TransactionalQueryAdapter } from
|
|
362
|
+
import { qubu } from "qubu"
|
|
363
|
+
import type { TransactionalQueryAdapter } from "qubu"
|
|
327
364
|
|
|
328
365
|
declare const transactionalAdapter: TransactionalQueryAdapter
|
|
329
366
|
const transactionalDb = qubu(transactionalAdapter)
|
|
330
367
|
|
|
331
|
-
const result = await transactionalDb.transaction(async transaction => {
|
|
368
|
+
const result = await transactionalDb.transaction(async (transaction) => {
|
|
332
369
|
await transaction.execute(firstMutation)
|
|
333
370
|
await transaction.execute(secondMutation)
|
|
334
371
|
return transaction.rows(readQuery)
|
|
@@ -353,18 +390,18 @@ The standalone functions remain useful when the adapter varies by call or a
|
|
|
353
390
|
small module does not need a bound client:
|
|
354
391
|
|
|
355
392
|
```ts
|
|
356
|
-
import { execute, executeRows } from
|
|
393
|
+
import { execute, executeRows } from "qubu"
|
|
357
394
|
|
|
358
395
|
const result = await execute(query, adapter)
|
|
359
396
|
const rows = await executeRows(readQuery, adapter)
|
|
360
397
|
```
|
|
361
398
|
|
|
362
|
-
| Result field |
|
|
363
|
-
| -------------- |
|
|
364
|
-
| `rows` | `readonly
|
|
365
|
-
| `affectedRows` | `number \| bigint`
|
|
366
|
-
| `changedRows` | `number \| bigint`
|
|
367
|
-
| `insertId` | `string \| number \| bigint`
|
|
399
|
+
| Result field | Adapter type | Contract |
|
|
400
|
+
| -------------- | ------------------------------------ | ------------------------------------------------------------------------------------- |
|
|
401
|
+
| `rows` | `readonly Record<string, unknown>[]` | Key by rendered aliases; Qubu returns the decoded `readonly TRow[]` |
|
|
402
|
+
| `affectedRows` | `number \| bigint` | Rows inserted, updated, or deleted when the driver reports an affected count |
|
|
403
|
+
| `changedRows` | `number \| bigint` | Rows whose stored values changed when the driver distinguishes them from matched rows |
|
|
404
|
+
| `insertId` | `string \| number \| bigint` | One insert identifier when the driver reports it |
|
|
368
405
|
|
|
369
406
|
The last three fields are optional. For example, an adapter can map PostgreSQL
|
|
370
407
|
`rowCount`, MySQL `affectedRows`, `changedRows`, and `insertId`, or SQLite
|
|
@@ -373,9 +410,11 @@ report accurately. Qubu does not derive mutation metadata from returned rows.
|
|
|
373
410
|
|
|
374
411
|
The adapter's `dialect` becomes the default for standalone and bound execution.
|
|
375
412
|
A `dialect` in the execution options overrides that rendering policy. Qubu
|
|
376
|
-
passes `signal` and
|
|
413
|
+
passes `signal`, `queryKind`, and `resultShape` to the adapter without changing
|
|
377
414
|
them. The adapter decides whether and how its driver supports cancellation.
|
|
378
|
-
Driver errors pass through unchanged.
|
|
415
|
+
Driver errors pass through unchanged. Decoder failures become a
|
|
416
|
+
`ResultDecodingError` that identifies the row and field without exposing the
|
|
417
|
+
raw value.
|
|
379
418
|
|
|
380
419
|
```mermaid
|
|
381
420
|
sequenceDiagram
|
|
@@ -386,11 +425,12 @@ sequenceDiagram
|
|
|
386
425
|
|
|
387
426
|
App->>Qubu: db.execute(query, options)
|
|
388
427
|
Qubu->>Qubu: render with selected dialect
|
|
389
|
-
Qubu->>Adapter: statement + queryKind + signal
|
|
428
|
+
Qubu->>Adapter: statement + queryKind + resultShape + signal
|
|
390
429
|
Adapter->>Driver: bind and execute
|
|
391
430
|
Driver-->>Adapter: driver result or error
|
|
392
|
-
Adapter-->>Qubu:
|
|
393
|
-
Qubu
|
|
431
|
+
Adapter-->>Qubu: normalized object rows + optional mutation facts
|
|
432
|
+
Qubu->>Qubu: apply field and adapter decoders
|
|
433
|
+
Qubu-->>App: typed result envelope or rows
|
|
394
434
|
```
|
|
395
435
|
|
|
396
436
|
## Create a small custom dialect
|
|
@@ -399,13 +439,13 @@ Use `createDialect()` when a driver needs a different policy but the query
|
|
|
399
439
|
syntax stays portable:
|
|
400
440
|
|
|
401
441
|
```ts
|
|
402
|
-
import { render } from
|
|
403
|
-
import { createDialect } from
|
|
442
|
+
import { render } from "qubu"
|
|
443
|
+
import { createDialect } from "qubu/core"
|
|
404
444
|
|
|
405
445
|
const namedParameters = createDialect({
|
|
406
|
-
name:
|
|
407
|
-
placeholder: position => `:p${position}`,
|
|
408
|
-
castTypes: { text:
|
|
446
|
+
name: "named-parameters",
|
|
447
|
+
placeholder: (position) => `:p${position}`,
|
|
448
|
+
castTypes: { text: "STRING" },
|
|
409
449
|
})
|
|
410
450
|
|
|
411
451
|
const statement = render(query, namedParameters)
|
package/docs/getting-started.md
CHANGED
|
@@ -24,9 +24,9 @@ Use `table()` once for each query-facing table. Column helpers describe the
|
|
|
24
24
|
application values that can be selected and, for mutations, written.
|
|
25
25
|
|
|
26
26
|
```ts
|
|
27
|
-
import { integer, table, text } from
|
|
27
|
+
import { integer, table, text } from "qubu"
|
|
28
28
|
|
|
29
|
-
const users = table(
|
|
29
|
+
const users = table("users", {
|
|
30
30
|
id: integer(),
|
|
31
31
|
name: text(),
|
|
32
32
|
email: text({ nullable: true }),
|
|
@@ -44,7 +44,7 @@ it, then renders the normalized statement in SQL order. The example uses the
|
|
|
44
44
|
`users` table from the previous section.
|
|
45
45
|
|
|
46
46
|
```ts
|
|
47
|
-
import { eq, from, render, select, where } from
|
|
47
|
+
import { eq, from, render, select, where } from "qubu"
|
|
48
48
|
|
|
49
49
|
const query = select(
|
|
50
50
|
{
|
|
@@ -52,7 +52,7 @@ const query = select(
|
|
|
52
52
|
displayName: users.name,
|
|
53
53
|
},
|
|
54
54
|
from(users),
|
|
55
|
-
where(eq(users.id, 7))
|
|
55
|
+
where(eq(users.id, 7)),
|
|
56
56
|
)
|
|
57
57
|
|
|
58
58
|
const statement = render(query)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Better Auth
|
|
2
|
+
|
|
3
|
+
> Derive Qubu-owned auth tables and run Better Auth through a transactional Qubu client.
|
|
4
|
+
|
|
5
|
+
Install the integration next to Qubu and Better Auth:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add qubu @qubu/better-auth better-auth
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Define the Better Auth options once. The schema derivation reads Better Auth's
|
|
12
|
+
resolved public metadata, so core tables, renamed models and fields, additional
|
|
13
|
+
fields, plugin tables, references, unique constraints, and compound indexes all
|
|
14
|
+
participate.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { betterAuth } from "better-auth"
|
|
18
|
+
import type { BetterAuthOptions } from "better-auth/types"
|
|
19
|
+
import { twoFactor } from "better-auth/plugins"
|
|
20
|
+
import { betterAuthSchema, qubuAdapter } from "@qubu/better-auth"
|
|
21
|
+
import { qubu } from "qubu"
|
|
22
|
+
import { pgAdapter } from "@qubu/adapter-pg"
|
|
23
|
+
|
|
24
|
+
const options = {
|
|
25
|
+
user: {
|
|
26
|
+
modelName: "auth_users",
|
|
27
|
+
additionalFields: {
|
|
28
|
+
locale: { type: "string", required: false },
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
plugins: [twoFactor()],
|
|
32
|
+
} satisfies BetterAuthOptions
|
|
33
|
+
|
|
34
|
+
const authSchema = betterAuthSchema(options, "postgresql")
|
|
35
|
+
const db = qubu(pgAdapter(pool))
|
|
36
|
+
|
|
37
|
+
export const auth = betterAuth({
|
|
38
|
+
...options,
|
|
39
|
+
database: qubuAdapter(db, { schema: authSchema }),
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`authSchema` is an ordinary Qubu `Schema`. Hand it to Qubu snapshot, diff,
|
|
44
|
+
migration-plan, and DDL workflows. The adapter's Better Auth `createSchema`
|
|
45
|
+
hook emits a TypeScript module that reconstructs the same Qubu-owned metadata.
|
|
46
|
+
|
|
47
|
+
The package never imports PostgreSQL, MySQL, or SQLite drivers. It executes
|
|
48
|
+
through Qubu's query and transaction boundaries. PostgreSQL and SQLite use one
|
|
49
|
+
limited mutation statement for atomic consume and guarded increment operations;
|
|
50
|
+
MySQL locks one selected row inside the Qubu-owned transaction. A client without
|
|
51
|
+
transaction support, or a dialect other than PostgreSQL, MySQL, or SQLite, is
|
|
52
|
+
rejected during adapter construction.
|
|
53
|
+
|
|
54
|
+
Better Auth enum metadata is currently rejected because Qubu cannot preserve
|
|
55
|
+
the closed value set as a portable column without adding a database constraint.
|
|
56
|
+
The error includes the model and field path instead of silently widening it to
|
|
57
|
+
text.
|
|
@@ -8,37 +8,19 @@
|
|
|
8
8
|
Attach it with `withCte()` and use the CTE source in `from()`:
|
|
9
9
|
|
|
10
10
|
```ts
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
from,
|
|
15
|
-
integer,
|
|
16
|
-
select,
|
|
17
|
-
table,
|
|
18
|
-
text,
|
|
19
|
-
where,
|
|
20
|
-
withCte,
|
|
21
|
-
} from 'qubu'
|
|
22
|
-
|
|
23
|
-
const users = table('users', {
|
|
11
|
+
import { cte, eq, from, integer, select, table, text, where, withCte } from "qubu"
|
|
12
|
+
|
|
13
|
+
const users = table("users", {
|
|
24
14
|
id: integer(),
|
|
25
15
|
name: text(),
|
|
26
16
|
})
|
|
27
17
|
|
|
28
18
|
const activeUsers = cte(
|
|
29
|
-
|
|
30
|
-
select(
|
|
31
|
-
{ id: users.id, name: users.name },
|
|
32
|
-
from(users),
|
|
33
|
-
where(eq(users.id, 7))
|
|
34
|
-
)
|
|
19
|
+
"active_users",
|
|
20
|
+
select({ id: users.id, name: users.name }, from(users), where(eq(users.id, 7))),
|
|
35
21
|
)
|
|
36
22
|
|
|
37
|
-
const report = select(
|
|
38
|
-
{ displayName: activeUsers.name },
|
|
39
|
-
withCte(activeUsers),
|
|
40
|
-
from(activeUsers)
|
|
41
|
-
)
|
|
23
|
+
const report = select({ displayName: activeUsers.name }, withCte(activeUsers), from(activeUsers))
|
|
42
24
|
```
|
|
43
25
|
|
|
44
26
|
`activeUsers.name` is a typed column derived from the first query's row shape.
|
|
@@ -53,24 +35,10 @@ member. The callback receives a typed self-reference; introduce it through
|
|
|
53
35
|
`from()` or a join before selecting its fields:
|
|
54
36
|
|
|
55
37
|
```ts
|
|
56
|
-
import {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
from,
|
|
60
|
-
integer,
|
|
61
|
-
lt,
|
|
62
|
-
recursiveCte,
|
|
63
|
-
select,
|
|
64
|
-
value,
|
|
65
|
-
where,
|
|
66
|
-
withCte,
|
|
67
|
-
} from 'qubu'
|
|
68
|
-
|
|
69
|
-
const numbers = recursiveCte(
|
|
70
|
-
'numbers',
|
|
71
|
-
select({ value: cast(value(1), integer()) }),
|
|
72
|
-
self =>
|
|
73
|
-
select({ value: add(self.value, 1) }, from(self), where(lt(self.value, 3)))
|
|
38
|
+
import { add, cast, from, integer, lt, recursiveCte, select, value, where, withCte } from "qubu"
|
|
39
|
+
|
|
40
|
+
const numbers = recursiveCte("numbers", select({ value: cast(value(1), integer()) }), (self) =>
|
|
41
|
+
select({ value: add(self.value, 1) }, from(self), where(lt(self.value, 3))),
|
|
74
42
|
)
|
|
75
43
|
|
|
76
44
|
const query = select({ value: numbers.value }, withCte(numbers), from(numbers))
|
|
@@ -89,11 +57,11 @@ recursive CTEs can share one `withCte()` clause.
|
|
|
89
57
|
Alias a query when it should be used as an inline source:
|
|
90
58
|
|
|
91
59
|
```ts
|
|
92
|
-
import { alias, from, lower, select } from
|
|
93
|
-
import type { SqlTypeOf } from
|
|
60
|
+
import { alias, from, lower, select } from "qubu"
|
|
61
|
+
import type { SqlTypeOf } from "qubu"
|
|
94
62
|
|
|
95
63
|
const names = select({ name: lower(users.name) }, from(users))
|
|
96
|
-
const namesSource = alias(names,
|
|
64
|
+
const namesSource = alias(names, "names")
|
|
97
65
|
|
|
98
66
|
const query = select({ name: namesSource.name }, from(namesSource))
|
|
99
67
|
type NameSqlDomain = SqlTypeOf<typeof namesSource.name>
|
|
@@ -112,7 +80,7 @@ so downstream text operations remain checked without redeclaring the field.
|
|
|
112
80
|
`scalar()` turns a query with exactly one selected field into an expression:
|
|
113
81
|
|
|
114
82
|
```ts
|
|
115
|
-
import { from, scalar, select, value } from
|
|
83
|
+
import { from, scalar, select, value } from "qubu"
|
|
116
84
|
|
|
117
85
|
const firstId = select({ id: users.id }, from(users))
|
|
118
86
|
const query = select(
|
|
@@ -120,7 +88,7 @@ const query = select(
|
|
|
120
88
|
name: users.name,
|
|
121
89
|
firstId: scalar(firstId),
|
|
122
90
|
},
|
|
123
|
-
from(users)
|
|
91
|
+
from(users),
|
|
124
92
|
)
|
|
125
93
|
```
|
|
126
94
|
|
|
@@ -139,7 +107,7 @@ Set operations preserve the left query's row shape. Both queries must select
|
|
|
139
107
|
compatible rows:
|
|
140
108
|
|
|
141
109
|
```ts
|
|
142
|
-
import { eq, from, select, unionAll, where } from
|
|
110
|
+
import { eq, from, select, unionAll, where } from "qubu"
|
|
143
111
|
|
|
144
112
|
const first = select({ id: users.id }, from(users))
|
|
145
113
|
const second = select({ id: users.id }, from(users), where(eq(users.id, 7)))
|
|
@@ -156,17 +124,12 @@ are collected in traversal order.
|
|
|
156
124
|
Build reusable pieces as ordinary values and pass them into the final query:
|
|
157
125
|
|
|
158
126
|
```ts
|
|
159
|
-
import { desc, eq, from, orderBy, select, where } from
|
|
127
|
+
import { desc, eq, from, orderBy, select, where } from "qubu"
|
|
160
128
|
|
|
161
129
|
const byId = where(eq(users.id, 7))
|
|
162
130
|
const newest = orderBy(desc(users.id))
|
|
163
131
|
|
|
164
|
-
const query = select(
|
|
165
|
-
{ id: users.id, name: users.name },
|
|
166
|
-
from(users),
|
|
167
|
-
byId,
|
|
168
|
-
newest
|
|
169
|
-
)
|
|
132
|
+
const query = select({ id: users.id, name: users.name }, from(users), byId, newest)
|
|
170
133
|
```
|
|
171
134
|
|
|
172
135
|
This makes it possible to share a predicate or projection without mutating a
|
|
@@ -185,13 +148,10 @@ generic function retains its exact source identity.
|
|
|
185
148
|
For an application-level requirement, describe the required JavaScript row:
|
|
186
149
|
|
|
187
150
|
```ts
|
|
188
|
-
import { eq, where } from
|
|
189
|
-
import type { TableLike } from
|
|
151
|
+
import { eq, where } from "qubu"
|
|
152
|
+
import type { TableLike } from "qubu"
|
|
190
153
|
|
|
191
|
-
function byStringId<TTable extends TableLike<{ id: string }>>(
|
|
192
|
-
table: TTable,
|
|
193
|
-
id: string
|
|
194
|
-
) {
|
|
154
|
+
function byStringId<TTable extends TableLike<{ id: string }>>(table: TTable, id: string) {
|
|
195
155
|
return where(eq(table.columns.id, id))
|
|
196
156
|
}
|
|
197
157
|
```
|
|
@@ -203,18 +163,15 @@ extra fields and rejects `string | null`, but it does not distinguish
|
|
|
203
163
|
Use `FieldLike` when the fragment depends on SQL semantics:
|
|
204
164
|
|
|
205
165
|
```ts
|
|
206
|
-
import { eq, where } from
|
|
207
|
-
import type { FieldLike, SourceLike, SqlTextLike } from
|
|
166
|
+
import { eq, where } from "qubu"
|
|
167
|
+
import type { FieldLike, SourceLike, SqlTextLike } from "qubu"
|
|
208
168
|
|
|
209
169
|
type NonNullTextId = FieldLike<{
|
|
210
170
|
sqlType: SqlTextLike
|
|
211
171
|
nullable: false
|
|
212
172
|
}>
|
|
213
173
|
|
|
214
|
-
function byTextId<TSource extends SourceLike<{ id: NonNullTextId }>>(
|
|
215
|
-
source: TSource,
|
|
216
|
-
id: string
|
|
217
|
-
) {
|
|
174
|
+
function byTextId<TSource extends SourceLike<{ id: NonNullTextId }>>(source: TSource, id: string) {
|
|
218
175
|
return where(eq(source.columns.id, id))
|
|
219
176
|
}
|
|
220
177
|
```
|