qubu 0.6.1 → 0.6.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/{complete-DP7pliuY.mjs → canonical-B5_ouh-c.mjs} +466 -241
- package/dist/codegen.d.mts +2 -2
- package/dist/codegen.mjs +180 -90
- package/dist/column-Cyc2CMnG.mjs +116 -0
- package/dist/column-DDRvD7SF.mjs +721 -0
- package/dist/{constraints-DM_tarXc.mjs → constraints-CAmi18Uk.mjs} +8 -3
- package/dist/core.d.mts +3 -3
- package/dist/core.mjs +4 -4
- package/dist/diff.d.mts +9 -9
- package/dist/diff.mjs +152 -102
- package/dist/{explain-CkIK13L_.mjs → explain-BIqEmZha.mjs} +1 -1
- package/dist/{expressions-BCjc08zw.mjs → expressions-_6JF_J77.mjs} +2 -1
- package/dist/index-CaxrMD1A.d.mts +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +368 -74
- package/dist/introspection/mysql.d.mts +1 -1
- package/dist/introspection/mysql.mjs +156 -23
- package/dist/introspection/postgres.d.mts +6 -3
- package/dist/introspection/postgres.mjs +388 -53
- package/dist/introspection/sqlite.d.mts +1 -1
- package/dist/introspection/sqlite.mjs +198 -12
- package/dist/introspection.d.mts +26 -12
- package/dist/introspection.mjs +2 -672
- package/dist/mysql.d.mts +3 -3
- package/dist/mysql.mjs +6 -5
- package/dist/{errors-Dxv73YJu.mjs → omit-VEr9Ydux.mjs} +5 -1
- package/dist/{on-conflict-CnaY5qso.mjs → on-conflict-B2rFyHGF.mjs} +6 -8
- package/dist/on-duplicate-key-update-Czsw1Yq-.mjs +95 -0
- package/dist/{postgres-Dey7QXPL.mjs → postgres-hFhd0I9n.mjs} +4 -5
- package/dist/postgres.d.mts +2 -2
- package/dist/postgres.mjs +2 -2
- package/dist/{registry-oWDiqD7i.mjs → registry-BXE_4M9P.mjs} +1 -1
- package/dist/{relational-DSAJ-l58.mjs → relational-CoPBETjI.mjs} +3 -2
- package/dist/schema.d.mts +2 -2
- package/dist/schema.mjs +8 -8
- package/dist/{serialize-CE-gw5_s.mjs → serialize-CyobNEx-.mjs} +174 -30
- package/dist/serialize-Du2UPZMt.d.mts +92 -0
- package/dist/snapshot/mysql.d.mts +5 -5
- package/dist/snapshot/mysql.mjs +8 -8
- package/dist/snapshot/postgres.d.mts +3 -3
- package/dist/snapshot/postgres.mjs +9 -9
- package/dist/snapshot/sqlite.d.mts +3 -3
- package/dist/snapshot/sqlite.mjs +7 -7
- package/dist/snapshot-mIb-Zzb5.mjs +1489 -0
- package/dist/snapshot.d.mts +4 -5
- package/dist/snapshot.mjs +3 -4
- package/dist/{source-BDuUXmAk.mjs → source-DYSUqzvb.mjs} +2 -2
- package/dist/sqlite.d.mts +2 -2
- package/dist/sqlite.mjs +6 -6
- package/dist/{table-C1QGNe4P.mjs → table-B8zEq0az.mjs} +4 -4
- package/dist/{types-BLNRatG_.mjs → types-CYHpSPwj.mjs} +10 -5
- package/dist/{types-BEn0N_al.d.mts → types-CiMvKi5V.d.mts} +14 -4
- package/dist/{types-DUe6eeI0.d.mts → types-Dqr4o2I1.d.mts} +590 -168
- package/dist/value-CpaUFtjw.mjs +45 -0
- package/dist/vite/ambient.d.ts +2 -0
- package/dist/vite.d.mts +1 -1
- package/dist/vite.mjs +2 -0
- package/docs/dialects-and-execution.md +61 -11
- package/docs/getting-started.md +1 -1
- package/docs/guides/extensions/typed-expressions.md +5 -3
- package/docs/guides/json.md +95 -2
- package/docs/guides/mutations.md +36 -0
- package/docs/index.md +1 -1
- package/docs/migrations/adapters.md +58 -21
- package/docs/migrations/recovery.md +7 -1
- package/docs/query-model/fragments.md +21 -1
- package/docs/reference/introspection-support.md +16 -18
- package/docs/reference/supported-surface.md +8 -7
- package/docs/schema/catalog-model.md +20 -7
- package/docs/schema/code-generation.md +1 -1
- package/docs/schema/diff.md +2 -2
- package/docs/schema/introspection.md +21 -14
- package/docs/schema/snapshots.md +18 -8
- package/docs/schema/storage-and-schema-sql.md +7 -2
- package/package.json +2 -1
- package/dist/canonical-DMvR9yBe.mjs +0 -972
- package/dist/column-BzN8KFJa.mjs +0 -364
- package/dist/column-CFvSbil0.mjs +0 -309
- package/dist/complete-types-CNMWBWap.d.mts +0 -371
- package/dist/index-CGui70hi.d.mts +0 -32
- package/dist/json-Db7XRD91.mjs +0 -169
- package/dist/omit-OxV58AwX.mjs +0 -5
- package/dist/serialize-OvXCLzjm.d.mts +0 -66
- package/dist/snapshot-DgsOhf_8.mjs +0 -354
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { A as fragment, E as makeSchemaExpression, U as resultValue, Y as standardJson, Z as createDialect } from "./column-DDRvD7SF.mjs";
|
|
2
|
+
//#region src/dialects/standard.ts
|
|
3
|
+
/** SQL:2008-style rendering defaults used by the core builder. */
|
|
4
|
+
function standardDialect() {
|
|
5
|
+
return createDialect({
|
|
6
|
+
name: "standard-sql",
|
|
7
|
+
placeholder: () => "?",
|
|
8
|
+
json: standardJson
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/core/primitives/parameter.ts
|
|
13
|
+
function parameter(_value, sqlType) {
|
|
14
|
+
return fragment((context) => context.parameter(_value, sqlType));
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/expressions/value.ts
|
|
18
|
+
/** Build a parameterized value expression with an optional runtime SQL domain for the adapter. */
|
|
19
|
+
function value(input, sqlType) {
|
|
20
|
+
const expression = makeSchemaExpression("value", (context) => context.render(parameter(input, sqlType)), resultValue(void 0, void 0, sqlType));
|
|
21
|
+
return Object.freeze({
|
|
22
|
+
...expression,
|
|
23
|
+
value: input
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Bind a value while declaring its compile-time and runtime SQL semantic domain.
|
|
28
|
+
*
|
|
29
|
+
* @remarks
|
|
30
|
+
* The domain is a binding hint; it does not select a JavaScript result decoder.
|
|
31
|
+
*/
|
|
32
|
+
function typedValue(input, sqlType) {
|
|
33
|
+
return value(input, sqlType);
|
|
34
|
+
}
|
|
35
|
+
function isExpressionValue(valueToCheck) {
|
|
36
|
+
return typeof valueToCheck === "object" && valueToCheck !== null && "expressionKind" in valueToCheck && "render" in valueToCheck && typeof valueToCheck.render === "function";
|
|
37
|
+
}
|
|
38
|
+
function isValueExpression(valueToCheck) {
|
|
39
|
+
return isExpressionValue(valueToCheck) && valueToCheck.expressionKind === "value" && "value" in valueToCheck;
|
|
40
|
+
}
|
|
41
|
+
function asValue(input, sqlType) {
|
|
42
|
+
return isExpressionValue(input) ? input : value(input, sqlType);
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { parameter as a, value as i, isValueExpression as n, standardDialect as o, typedValue as r, asValue as t };
|
package/dist/vite/ambient.d.ts
CHANGED
|
@@ -68,6 +68,8 @@ declare global {
|
|
|
68
68
|
const isNull: typeof import("qubu").isNull
|
|
69
69
|
const isTrue: typeof import("qubu").isTrue
|
|
70
70
|
const json: typeof import("qubu").json
|
|
71
|
+
const jsonArrayFrom: typeof import("qubu").jsonArrayFrom
|
|
72
|
+
const jsonObjectFrom: typeof import("qubu").jsonObjectFrom
|
|
71
73
|
const jsonBoolean: typeof import("qubu").jsonBoolean
|
|
72
74
|
const jsonExists: typeof import("qubu").jsonExists
|
|
73
75
|
const jsonNumber: typeof import("qubu").jsonNumber
|
package/dist/vite.d.mts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* with the ordinary authoring surface of `qubu`; fragment internals, dialect construction, and
|
|
5
5
|
* schema extensions belong to the layered entrypoints.
|
|
6
6
|
*/
|
|
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"];
|
|
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", "jsonArrayFrom", "jsonObjectFrom", "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"];
|
|
8
8
|
type QubuGlobal = (typeof qubuGlobals)[number];
|
|
9
9
|
//#endregion
|
|
10
10
|
//#region src/vite/index.d.ts
|
package/dist/vite.mjs
CHANGED
|
@@ -79,6 +79,11 @@ rows. Qubu then uses the query's result shape and the adapter's decoder policy
|
|
|
79
79
|
to produce the typed `ExecutionResult`. A `TransactionalQueryAdapter` can also
|
|
80
80
|
pin one driver connection for a callback transaction:
|
|
81
81
|
|
|
82
|
+
When present, `request.statement.parameterSqlTypes` is an optional sidecar
|
|
83
|
+
aligned with `statement.parameters`. Adapters can pass each domain to their
|
|
84
|
+
value encoder or driver binding layer when a client distinguishes values such
|
|
85
|
+
as `DATE`, `TIMESTAMP`, `UUID`, and `DECIMAL`.
|
|
86
|
+
|
|
82
87
|
```ts
|
|
83
88
|
import { qubu } from "qubu"
|
|
84
89
|
import { postgresDialect } from "qubu/postgres"
|
|
@@ -147,10 +152,12 @@ lifecycle.
|
|
|
147
152
|
|
|
148
153
|
### Decode schema-aware result values
|
|
149
154
|
|
|
150
|
-
Portable boolean, date, timestamp, and
|
|
151
|
-
result domains through projection aliases, derived queries, CTEs, set
|
|
152
|
-
operations, and mutation `RETURNING`.
|
|
153
|
-
|
|
155
|
+
Portable boolean, date, timestamp, JSON, and bigint columns retain their
|
|
156
|
+
logical result domains through projection aliases, derived queries, CTEs, set
|
|
157
|
+
operations, and mutation `RETURNING`. The result field exposes that domain as
|
|
158
|
+
`sqlType` before execution. Register only the conversions required by the
|
|
159
|
+
selected driver configuration. `sqlType` identifies the SQL domain; `type` and
|
|
160
|
+
the selected decoder determine whether Qubu converts the returned value:
|
|
154
161
|
|
|
155
162
|
```ts
|
|
156
163
|
import {
|
|
@@ -180,6 +187,11 @@ const adapter: QueryAdapter = {
|
|
|
180
187
|
}
|
|
181
188
|
```
|
|
182
189
|
|
|
190
|
+
Qubu can decode bigint values exactly when a driver returns a bigint, a safe
|
|
191
|
+
integer, or an integer string. For arbitrary-precision `DECIMAL` values, keep
|
|
192
|
+
the driver's exact representation (usually a string or decimal object) rather
|
|
193
|
+
than converting it to a JavaScript number.
|
|
194
|
+
|
|
183
195
|
Do not register `jsonTextResultDecoder` when the driver already returns parsed
|
|
184
196
|
JSON. A JSON string is otherwise ambiguous: it may be serialized JSON or an
|
|
185
197
|
already-decoded JSON string scalar. With no registered decoder, Qubu preserves
|
|
@@ -459,13 +471,51 @@ back after it rejects, and releases the connection in every case. Qubu only
|
|
|
459
471
|
creates the scoped client and passes the callback result through. It never
|
|
460
472
|
emits `BEGIN`, `COMMIT`, or `ROLLBACK` itself.
|
|
461
473
|
|
|
462
|
-
|
|
463
|
-
`
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
474
|
+
A scoped client's methods follow its adapter's capabilities: `execute()` and
|
|
475
|
+
`rows()` are always available; EXPLAIN and streaming require their respective
|
|
476
|
+
capabilities. `TransactionOptions.signal` is passed to the adapter. Isolation
|
|
477
|
+
levels and other driver-specific settings remain adapter-specific.
|
|
478
|
+
|
|
479
|
+
### Roll back part of a transaction
|
|
480
|
+
|
|
481
|
+
The pg, mysql2, and node:sqlite adapters expose `transaction()` on scoped
|
|
482
|
+
clients through the shared `NestedTransactionalQueryAdapter` capability.
|
|
483
|
+
Other adapters retain their existing transaction surface. For example, with a
|
|
484
|
+
bound pg client, catch a nested failure to keep earlier work:
|
|
485
|
+
|
|
486
|
+
```ts
|
|
487
|
+
await db.transaction(async (outer) => {
|
|
488
|
+
await outer.execute(firstMutation)
|
|
489
|
+
try {
|
|
490
|
+
await outer.transaction(async (inner) => {
|
|
491
|
+
await inner.execute(optionalMutation)
|
|
492
|
+
})
|
|
493
|
+
} catch (error) {
|
|
494
|
+
// The nested work was rolled back; the outer transaction can continue.
|
|
495
|
+
}
|
|
496
|
+
await outer.execute(secondMutation)
|
|
497
|
+
})
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
Each nested callback uses a uniquely named savepoint on the same connection.
|
|
501
|
+
Success releases it; failure rolls back to it and releases it. Letting the
|
|
502
|
+
failure escape also rolls back the outer transaction. Failed savepoint creation
|
|
503
|
+
or recovery makes the entire transaction unsafe to commit, even if the callback
|
|
504
|
+
catches the error. Primary and cleanup failures are retained in `AggregateError`.
|
|
505
|
+
|
|
506
|
+
Await every query and nested transaction before returning. These three adapters
|
|
507
|
+
reject finished scoped clients, overlapping sibling scopes, a child started
|
|
508
|
+
while its parent has pending queries, and parent queries while a child is active.
|
|
509
|
+
If a callback finishes with work still pending, the adapter waits for that work
|
|
510
|
+
and rolls back instead of committing. Use the active scoped client for all work
|
|
511
|
+
on a directly supplied connection; its root client rejects unrelated operations
|
|
512
|
+
during the transaction. A pg pool still accepts independent queries and
|
|
513
|
+
transactions on other acquired connections. Raw driver calls and separately
|
|
514
|
+
constructed adapters remain the application's responsibility.
|
|
515
|
+
|
|
516
|
+
EXPLAIN and result decoding remain available at every depth. Nested transaction
|
|
517
|
+
hooks identify their enclosing transaction with `parentId`; queries identify
|
|
518
|
+
their immediate scope. Cancellation does not interrupt savepoint recovery.
|
|
469
519
|
|
|
470
520
|
The standalone functions remain useful when the adapter varies by call or a
|
|
471
521
|
small module does not need a bound client:
|
package/docs/getting-started.md
CHANGED
|
@@ -91,6 +91,6 @@ type UserRow = typeof query.row
|
|
|
91
91
|
inputs.
|
|
92
92
|
- [Choose a database dialect](dialects-and-execution.md) when the
|
|
93
93
|
driver expects different identifier, placeholder, or pagination syntax.
|
|
94
|
-
- [
|
|
94
|
+
- [Query nested JSON](guides/json.md) or read scalars from a JSON column.
|
|
95
95
|
- [Use the Vite compiler hint](guides/vite-plugin.md) for directive-based
|
|
96
96
|
imports.
|
|
@@ -60,7 +60,7 @@ Declare result domains at other extension boundaries too:
|
|
|
60
60
|
import { typedCall, typedCast, typedValue, unsafeExpression } from "qubu/core"
|
|
61
61
|
import type { SqlText, SqlUuid } from "qubu"
|
|
62
62
|
|
|
63
|
-
const id = typedValue<SqlUuid, string>("108cb836-20d2-41b2-8c23-f0c94700aa7e")
|
|
63
|
+
const id = typedValue<SqlUuid, string>("108cb836-20d2-41b2-8c23-f0c94700aa7e", "uuid")
|
|
64
64
|
const normalized = typedCall<SqlText, string>()("custom_text", users.name)
|
|
65
65
|
const rawNameAsText = typedCast<string, SqlText>()(users.name, "TEXT")
|
|
66
66
|
const generated = unsafeExpression<string, SqlText>("custom_text()")
|
|
@@ -69,8 +69,10 @@ const generated = unsafeExpression<string, SqlText>("custom_text()")
|
|
|
69
69
|
`typedCall()` preserves source requirements from its arguments. `typedCast()`
|
|
70
70
|
is the fallback when no reusable definition describes the target. It preserves
|
|
71
71
|
operand nullability and source metadata while emitting its supplied type name
|
|
72
|
-
verbatim. `typedValue()` binds a parameter
|
|
73
|
-
|
|
72
|
+
verbatim. `typedValue()` binds a parameter and declares its runtime SQL domain
|
|
73
|
+
for the adapter; it does not select a JavaScript result decoder. Schema columns
|
|
74
|
+
carry result-decoder metadata separately. `unsafeExpression()` emits its string
|
|
75
|
+
unchanged and should remain a last resort.
|
|
74
76
|
|
|
75
77
|
The lower-level forms also expose the SQL domain in their generic lists:
|
|
76
78
|
`call<Output, Name, Arguments, NullableFrom, SqlType>()` and
|
package/docs/guides/json.md
CHANGED
|
@@ -1,6 +1,99 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Query nested JSON
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Build inferred nested results, or read scalar values from stored JSON documents.
|
|
4
|
+
|
|
5
|
+
Use jsonArrayFrom() to nest a query's rows and jsonObjectFrom() for a query
|
|
6
|
+
proven to return at most one row. Both preserve filtering, correlation,
|
|
7
|
+
ordering, and pagination:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import {
|
|
11
|
+
correlate,
|
|
12
|
+
desc,
|
|
13
|
+
eq,
|
|
14
|
+
fetchFirst,
|
|
15
|
+
from,
|
|
16
|
+
integer,
|
|
17
|
+
jsonArrayFrom,
|
|
18
|
+
jsonObjectFrom,
|
|
19
|
+
orderBy,
|
|
20
|
+
select,
|
|
21
|
+
table,
|
|
22
|
+
text,
|
|
23
|
+
where,
|
|
24
|
+
} from "qubu"
|
|
25
|
+
|
|
26
|
+
const users = table("users", { id: integer(), name: text() })
|
|
27
|
+
const posts = table("posts", {
|
|
28
|
+
id: integer(),
|
|
29
|
+
authorId: integer(),
|
|
30
|
+
title: text(),
|
|
31
|
+
})
|
|
32
|
+
const latestPosts = select(
|
|
33
|
+
{ id: posts.id, title: posts.title },
|
|
34
|
+
from(posts),
|
|
35
|
+
correlate(users),
|
|
36
|
+
where(eq(posts.authorId, users.id)),
|
|
37
|
+
orderBy(desc(posts.id)),
|
|
38
|
+
fetchFirst(3),
|
|
39
|
+
)
|
|
40
|
+
const latestPost = select(
|
|
41
|
+
{ title: posts.title },
|
|
42
|
+
from(posts),
|
|
43
|
+
correlate(users),
|
|
44
|
+
where(eq(posts.authorId, users.id)),
|
|
45
|
+
orderBy(desc(posts.id)),
|
|
46
|
+
fetchFirst(1),
|
|
47
|
+
)
|
|
48
|
+
const query = select(
|
|
49
|
+
{
|
|
50
|
+
name: users.name,
|
|
51
|
+
posts: jsonArrayFrom(latestPosts),
|
|
52
|
+
latestPost: jsonObjectFrom(latestPost),
|
|
53
|
+
},
|
|
54
|
+
from(users),
|
|
55
|
+
)
|
|
56
|
+
// Row: { name: string; posts: { id: number; title: string }[];
|
|
57
|
+
// latestPost: { title: string } | null }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Execute the query through a Qubu adapter to decode nested results. An empty
|
|
61
|
+
array query returns []; an empty object query returns null. A source-free
|
|
62
|
+
query proven to return exactly one row produces a non-null object type.
|
|
63
|
+
Object queries need Qubu's cardinality proof: an unconditional fetchFirst(1)
|
|
64
|
+
or fetchFirst(0) establishes the bound. A conditional limit does not.
|
|
65
|
+
|
|
66
|
+
The helpers compose inside further select() projections, so nesting can
|
|
67
|
+
continue without result-type assertions. correlate() and the outer query's
|
|
68
|
+
FROM/JOIN scope remain checked at every level.
|
|
69
|
+
|
|
70
|
+
### Preserve ordering and logical values
|
|
71
|
+
|
|
72
|
+
Nested arrays retain explicit ORDER BY and pagination. Tied sort keys retain
|
|
73
|
+
SQL's unspecified tie order; add a unique tie-breaker when order matters.
|
|
74
|
+
DISTINCT ordering must use the same expressions as the selection. Without an
|
|
75
|
+
ORDER BY, array order is unspecified.
|
|
76
|
+
|
|
77
|
+
Nested results support PostgreSQL, MySQL 8.0.21+, and SQLite 3.45+. Other
|
|
78
|
+
dialects fail during rendering. SQLite's minimum includes the JSON aggregate
|
|
79
|
+
ordering fix needed to retain object values.
|
|
80
|
+
|
|
81
|
+
Built-in column domains decode to their declared types, including bigint,
|
|
82
|
+
Uint8Array, Date, boolean, and nested JSON. Qubu transports precision-sensitive
|
|
83
|
+
values as text and rejects numbers that lose significant decimal digits or
|
|
84
|
+
exceed JavaScript's safe integer range. Use bigint columns for exact large
|
|
85
|
+
integers. Unknown or custom SQL domains need a supported explicit cast, for
|
|
86
|
+
example cast(value(7), integer()); declaring a TypeScript result alone does
|
|
87
|
+
not provide runtime decoding information.
|
|
88
|
+
|
|
89
|
+
Custom mapResult() and column decoders receive the JSON transport value as
|
|
90
|
+
unknown: bigint and decimal strings, hexadecimal binary strings, serialized
|
|
91
|
+
JSON strings, or ordinary JSON scalar values. They own conversion to their
|
|
92
|
+
advertised application type. Adapter-wide decoders do not run inside nested
|
|
93
|
+
objects. Keep arbitrary stored JSON within JavaScript's numeric precision;
|
|
94
|
+
unsupported numeric representations fail instead of silently rounding.
|
|
95
|
+
|
|
96
|
+
## Read stored JSON scalars
|
|
4
97
|
|
|
5
98
|
Use a structured jsonPath() when a query needs a scalar or an existence check
|
|
6
99
|
inside a JSON document:
|
package/docs/guides/mutations.md
CHANGED
|
@@ -176,6 +176,42 @@ before rendering when every field is omitted. Possible expression branches
|
|
|
176
176
|
remain source- and capability-aware even when their runtime alternative is
|
|
177
177
|
`omit`.
|
|
178
178
|
|
|
179
|
+
## Update duplicate keys in MySQL
|
|
180
|
+
|
|
181
|
+
Use `onDuplicateKeyUpdate()` from `qubu/mysql` to update a row when an insert
|
|
182
|
+
conflicts with any primary or unique key. MySQL chooses the conflicting key;
|
|
183
|
+
this clause has no conflict-target argument or `RETURNING` support.
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
import { insertInto, values } from "qubu"
|
|
187
|
+
import { incoming, onDuplicateKeyUpdate } from "qubu/mysql"
|
|
188
|
+
|
|
189
|
+
const proposed = incoming(users)
|
|
190
|
+
const query = insertInto(
|
|
191
|
+
users,
|
|
192
|
+
values({ name: "Ada", email: "ada@example.com" }),
|
|
193
|
+
onDuplicateKeyUpdate(users, { name: proposed.name }),
|
|
194
|
+
)
|
|
195
|
+
await db.execute(query)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Declare a unique key on `email` in the database for this example. A duplicate
|
|
199
|
+
email replaces the existing name with the proposed name. Assignments accept
|
|
200
|
+
writable target columns, target-table expressions, `incoming(table)` columns,
|
|
201
|
+
or `omit`. Raw values retain the target column's parameter encoder. At least
|
|
202
|
+
one assignment must remain after omissions.
|
|
203
|
+
|
|
204
|
+
Incoming references are available only inside the matching table's duplicate-key
|
|
205
|
+
assignments. Qubu renders MySQL row aliases for `values()` and `defaultValues()`.
|
|
206
|
+
For `insertSelect()`, it wraps the query in a derived projection and maps its
|
|
207
|
+
fields positionally to the target-column list. Incoming references then cover
|
|
208
|
+
only that list; referencing an omitted target column fails during rendering.
|
|
209
|
+
This syntax requires MySQL 8.0.19 or later.
|
|
210
|
+
|
|
211
|
+
The result retains mysql2's `affectedRows`, `changedRows`, and `insertId` metadata
|
|
212
|
+
when supplied by the driver. These facts do not identify which branch ran for
|
|
213
|
+
every row of a batch. Read rows with a separate query when needed.
|
|
214
|
+
|
|
179
215
|
## Delete with a predicate
|
|
180
216
|
|
|
181
217
|
```ts
|
package/docs/index.md
CHANGED
|
@@ -33,7 +33,7 @@ define a table, build a `SELECT`, and inspect its SQL and parameters.
|
|
|
33
33
|
derivation and a native transactional database adapter.
|
|
34
34
|
- [Extend Qubu](guides/extensions/overview.md) with a custom source, clause,
|
|
35
35
|
dialect policy, or typed expression.
|
|
36
|
-
- [
|
|
36
|
+
- [Query nested JSON](guides/json.md) or read scalars from structured paths.
|
|
37
37
|
- [Enable the Vite compiler hint](guides/vite-plugin.md) when query modules
|
|
38
38
|
should opt into named imports through a directive.
|
|
39
39
|
- [Inspect an existing database](schema/introspection.md) through the optional
|
|
@@ -2,38 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
> Select a migration adapter from capabilities proven by its driver and environment, not from dialect name alone.
|
|
4
4
|
|
|
5
|
-
Every executable migration adapter opens
|
|
5
|
+
Every executable migration adapter opens a migration session and
|
|
6
6
|
advertises the exact behavior the executor may use:
|
|
7
7
|
|
|
8
|
-
| Field | Contract
|
|
9
|
-
| -------------------------------------- |
|
|
10
|
-
| `dialect`, `serverVersion` | Physical target and optional version used for compatibility checks
|
|
11
|
-
| `session` |
|
|
12
|
-
| `transactionalDdl` | Whether DDL effects can roll back
|
|
13
|
-
| `optionalTransactions`, `transactions` | Whether optional phases join a transaction and which requirements are proven
|
|
14
|
-
| `lease`, `leaseKind` | Database-backed exclusion of another migration runner
|
|
15
|
-
| `locks` | Independently supported program DDL lock requirements
|
|
16
|
-
| `journal` | Database storage, head compare-and-swap, and atomic applied-record/head advancement
|
|
17
|
-
| `parameters` | Supported tagged parameter kinds
|
|
18
|
-
| `commitAmbiguity` | Ambiguous commit becomes `recovery-required`
|
|
19
|
-
| `forbiddenPhases` | Checkpointed support or explicit rejection
|
|
20
|
-
| `features` | Named constraints an artifact may require
|
|
8
|
+
| Field | Contract |
|
|
9
|
+
| -------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
10
|
+
| `dialect`, `serverVersion` | Physical target and optional version used for compatibility checks |
|
|
11
|
+
| `session` | `pinned` for the full lifecycle, or `atomic-batch` for one complete artifact per batch |
|
|
12
|
+
| `transactionalDdl` | Whether DDL effects can roll back |
|
|
13
|
+
| `optionalTransactions`, `transactions` | Whether optional phases join a transaction and which requirements are proven |
|
|
14
|
+
| `lease`, `leaseKind` | Database-backed exclusion of another migration runner |
|
|
15
|
+
| `locks` | Independently supported program DDL lock requirements |
|
|
16
|
+
| `journal` | Database storage, head compare-and-swap, and atomic applied-record/head advancement |
|
|
17
|
+
| `parameters` | Supported tagged parameter kinds |
|
|
18
|
+
| `commitAmbiguity` | Ambiguous commit becomes `recovery-required` |
|
|
19
|
+
| `forbiddenPhases` | Checkpointed support or explicit rejection |
|
|
20
|
+
| `features` | Named constraints an artifact may require |
|
|
21
21
|
|
|
22
22
|
The migrator lease and a program's DDL lock are different controls. The lease
|
|
23
23
|
excludes another Qubu runner; a DDL lock protects the database operation. The
|
|
24
24
|
executor never treats one as proof of the other.
|
|
25
25
|
|
|
26
|
+
## Trusted migration SQL
|
|
27
|
+
|
|
28
|
+
Migration SQL, including SQL conditions, is trusted across all adapters. Qubu
|
|
29
|
+
validates program structure and adapter capabilities, but does not parse SQL
|
|
30
|
+
to enforce safety. Callers must preserve executor-owned transactions, connection
|
|
31
|
+
settings, and migration journal state. For example, an explicit `COMMIT` can
|
|
32
|
+
leave schema changes applied without their journal record; atomicity and recovery
|
|
33
|
+
guarantees depend on respecting this contract.
|
|
34
|
+
|
|
26
35
|
## Current profiles
|
|
27
36
|
|
|
28
37
|
The following stable profiles have live conformance coverage in this checkout:
|
|
29
38
|
|
|
30
|
-
| Migration entrypoint | Dialect | Transactions | Locks | Forbidden phases | Notes
|
|
31
|
-
| ------------------------------------- | ---------- | ----------------------------- | --------------- | ---------------- |
|
|
32
|
-
| `@qubu/adapter-libsql/migration` | SQLite | required, optional | none, exclusive | unsupported |
|
|
33
|
-
| `@qubu/adapter-node-sqlite/migration` | SQLite | required, optional | none, exclusive | unsupported | Pinned application-owned `DatabaseSync`
|
|
34
|
-
| `@qubu/adapter-pg/migration` | PostgreSQL | required, optional, forbidden | none, exclusive | checkpointed | Caller supplies an already-pinned client
|
|
35
|
-
| `@qubu/adapter-postgresjs/migration` | PostgreSQL | required, optional, forbidden | none, exclusive | checkpointed | Reserves and releases one connection
|
|
36
|
-
| `@qubu/adapter-pglite/migration` | PostgreSQL | required, optional, forbidden | none, exclusive | checkpointed | Uses the database query queue as the pinned session
|
|
39
|
+
| Migration entrypoint | Dialect | Transactions | Locks | Forbidden phases | Notes |
|
|
40
|
+
| ------------------------------------- | ---------- | ----------------------------- | --------------- | ---------------- | ------------------------------------------------------ |
|
|
41
|
+
| `@qubu/adapter-libsql/migration` | SQLite | required, optional | none, exclusive | unsupported | Single-phase atomic batches through `client.migrate()` |
|
|
42
|
+
| `@qubu/adapter-node-sqlite/migration` | SQLite | required, optional | none, exclusive | unsupported | Pinned application-owned `DatabaseSync` |
|
|
43
|
+
| `@qubu/adapter-pg/migration` | PostgreSQL | required, optional, forbidden | none, exclusive | checkpointed | Caller supplies an already-pinned client |
|
|
44
|
+
| `@qubu/adapter-postgresjs/migration` | PostgreSQL | required, optional, forbidden | none, exclusive | checkpointed | Reserves and releases one connection |
|
|
45
|
+
| `@qubu/adapter-pglite/migration` | PostgreSQL | required, optional, forbidden | none, exclusive | checkpointed | Uses the database query queue as the pinned session |
|
|
37
46
|
|
|
38
47
|
All five support every current tagged parameter kind (`null`, `boolean`,
|
|
39
48
|
`string`, `number`, `bigint`, `bytes`, and `json`), a database journal and
|
|
@@ -68,3 +77,31 @@ const adapter = libsqlMigrationAdapter(client, {
|
|
|
68
77
|
|
|
69
78
|
`DATABASE_URL` remains application configuration; neither the adapter nor CLI
|
|
70
79
|
assigns deployment-provider meaning to it.
|
|
80
|
+
|
|
81
|
+
## libSQL batch execution
|
|
82
|
+
|
|
83
|
+
Each executable artifact must contain exactly one phase and an embedded before
|
|
84
|
+
snapshot. The adapter submits its statements, SQL assertions, applied-history
|
|
85
|
+
record, head update, and terminal attempt state in one `client.migrate()` call.
|
|
86
|
+
For example, creating a table and recording that migration either both commit
|
|
87
|
+
or both roll back. Multiple artifacts are separate batches; earlier successful
|
|
88
|
+
artifacts remain applied if a later one fails.
|
|
89
|
+
|
|
90
|
+
Preparation reads the schema in a read transaction. The submitted batch checks
|
|
91
|
+
that the catalog still matches that inspection, the lease is still owned, and
|
|
92
|
+
the head still equals the expected parent. Foreign-key validation runs before
|
|
93
|
+
commit because libSQL temporarily disables enforcement during `migrate()`.
|
|
94
|
+
|
|
95
|
+
Schema fingerprint and property preconditions are checked against the embedded
|
|
96
|
+
before snapshot, whose physical facts are verified during preparation and
|
|
97
|
+
guarded by the in-batch catalog assertion. Object-presence and scalar SQL checks
|
|
98
|
+
run inside the batch. Postconditions must be object-presence/absence checks
|
|
99
|
+
without fingerprints, or scalar SQL checks returning `1`. Unsupported conditions
|
|
100
|
+
and multiple phases are rejected. SQL content is passed to the driver without
|
|
101
|
+
safety validation; each program entry must follow the driver's statement contract.
|
|
102
|
+
|
|
103
|
+
The database-row lease has no expiry or heartbeat. A process crash can leave
|
|
104
|
+
it held; ownership must be resolved before another runner can proceed. A lost
|
|
105
|
+
batch response is an uncertain outcome requiring journal inspection and, when
|
|
106
|
+
the attempt remains unresolved, explicit recovery. It is never assumed to be
|
|
107
|
+
a successful rollback.
|
|
@@ -46,13 +46,19 @@ head, or a non-prefix repository fail before any statement executes.
|
|
|
46
46
|
## Execution and concurrency guarantees
|
|
47
47
|
|
|
48
48
|
For each invocation, the executor verifies the entire repository, opens one
|
|
49
|
-
|
|
49
|
+
migration session, checks capabilities, acquires the migrator lease, validates the
|
|
50
50
|
journal and repository prefix, checks the live before-snapshot digest, then
|
|
51
51
|
applies each pending artifact. Within an artifact it creates an attempt,
|
|
52
52
|
executes ordered phases with preconditions and postconditions, writes durable
|
|
53
53
|
checkpoints, appends immutable history, and compare-and-swaps the head.
|
|
54
54
|
Resources are released in reverse order: DDL lock, migrator lease, then session.
|
|
55
55
|
|
|
56
|
+
An `atomic-batch` profile instead applies one single-phase artifact in one
|
|
57
|
+
database transaction, including its checks and terminal journal writes. It
|
|
58
|
+
records a completed phase checkpoint rather than intermediate statement
|
|
59
|
+
checkpoints. See [libSQL batch execution](./adapters.md#libsql-batch-execution)
|
|
60
|
+
for its supported conditions and concurrency guards.
|
|
61
|
+
|
|
56
62
|
A second runner cannot rely on the lease alone. Atomic applied-record/head
|
|
57
63
|
advancement uses the expected parent as a compare-and-swap guard. A runner that
|
|
58
64
|
observes the already-matching head exits idempotently; a conflicting head is a
|
|
@@ -51,7 +51,8 @@ producer, consumer, and regression test all exist.
|
|
|
51
51
|
## Parameters are runtime data
|
|
52
52
|
|
|
53
53
|
Parameter values are not fragment metadata. A renderer calls
|
|
54
|
-
`context.parameter(value)`, and `render()` collects values in placeholder order
|
|
54
|
+
`context.parameter(value)`, and `render()` collects values in placeholder order.
|
|
55
|
+
Pass a second argument when the adapter needs the runtime SQL domain too:
|
|
55
56
|
|
|
56
57
|
```ts
|
|
57
58
|
import { and, eq, from, integer, like, render, select, table, text, where } from "qubu"
|
|
@@ -72,6 +73,25 @@ render(query)
|
|
|
72
73
|
// parameters: [7, '%Ada%']
|
|
73
74
|
```
|
|
74
75
|
|
|
76
|
+
The optional domain metadata stays in a sidecar aligned with `parameters`:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { typedValue } from "qubu/core"
|
|
80
|
+
import type { SqlUuid } from "qubu"
|
|
81
|
+
|
|
82
|
+
const id = typedValue<SqlUuid, string>("108cb836-20d2-41b2-8c23-f0c94700aa7e", "uuid")
|
|
83
|
+
render(id)
|
|
84
|
+
// parameterSqlTypes: ["uuid"]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The domain is a binding hint, not an instruction to convert the returned
|
|
88
|
+
JavaScript value. Result decoding comes from a field's `type` or an explicit
|
|
89
|
+
decoder.
|
|
90
|
+
|
|
91
|
+
Plain JavaScript values remain untyped at runtime. Use an explicit domain when
|
|
92
|
+
`Date` could mean either `DATE` or `TIMESTAMP`, or when a string is a UUID
|
|
93
|
+
rather than text.
|
|
94
|
+
|
|
75
95
|
The parameter array follows the placeholders in the rendered text. `select()`
|
|
76
96
|
normalizes independent clause values, but keep the final call in SQL order in
|
|
77
97
|
new code so source scope and repair hints are visible at a glance.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
The optional `qubu/introspection` entrypoint reads one selected database
|
|
6
6
|
namespace through a user-owned `CatalogConnection`. It returns normalized
|
|
7
|
-
catalog data and can map that data to canonical Snapshot v1
|
|
7
|
+
catalog data and can map that data to canonical Snapshot v1. The
|
|
8
8
|
application owns the driver and connection lifecycle. Snapshot diffing,
|
|
9
9
|
migration planning, and DDL emission are separate Qubu capabilities; see the
|
|
10
10
|
[ownership map](supported-surface.md#ownership-boundary).
|
|
@@ -46,7 +46,7 @@ SQL.
|
|
|
46
46
|
## PostgreSQL complete catalog surface
|
|
47
47
|
|
|
48
48
|
The PostgreSQL reader also fills the complete normalized catalog used by
|
|
49
|
-
Snapshot
|
|
49
|
+
Snapshot v1. It keeps PostgreSQL OIDs in current-run physical references and
|
|
50
50
|
keeps decompiler output such as `pg_get_viewdef`, `pg_get_triggerdef`, and
|
|
51
51
|
`pg_get_functiondef` as tagged SQL data. It does not evaluate that text.
|
|
52
52
|
|
|
@@ -55,9 +55,9 @@ The query and normalization layout follows the catalog-oriented parts of the
|
|
|
55
55
|
while Qubu keeps the result as typed data instead of generating TypeScript
|
|
56
56
|
declarations.
|
|
57
57
|
|
|
58
|
-
Use `
|
|
59
|
-
`
|
|
60
|
-
Snapshot v1.
|
|
58
|
+
Use `mapCatalogToSnapshot()` or its explicit
|
|
59
|
+
`mapCatalogToCompleteSnapshot()` alias for this object set. Both produce the
|
|
60
|
+
canonical Snapshot v1 shape.
|
|
61
61
|
|
|
62
62
|
## SQLite complete catalog surface
|
|
63
63
|
|
|
@@ -86,10 +86,9 @@ the selected namespace. SQLite does not provide the PostgreSQL object families
|
|
|
86
86
|
such as routines, materialized views, policies, or ownership, so the reader
|
|
87
87
|
does not fabricate them.
|
|
88
88
|
|
|
89
|
-
`
|
|
90
|
-
objects, opaque boundaries, and dialect
|
|
91
|
-
|
|
92
|
-
behavior.
|
|
89
|
+
`mapCatalogToSnapshot()` delegates to `mapCatalogToCompleteSnapshot()` and
|
|
90
|
+
retains typed views, triggers, deferred objects, opaque boundaries, and dialect
|
|
91
|
+
extensions in Snapshot v1.
|
|
93
92
|
|
|
94
93
|
The query and normalization seams follow the catalog-reading portions of the
|
|
95
94
|
[Drizzle SQLite introspector](https://github.com/drizzle-team/drizzle-orm/blob/main/drizzle-kit/src/introspect-sqlite.ts).
|
|
@@ -110,13 +109,13 @@ MySQL has typed complete records for views, routines and their parameters,
|
|
|
110
109
|
triggers, partitions, collations used by selected tables or columns, and
|
|
111
110
|
comments. View definitions come from `INFORMATION_SCHEMA.VIEWS`; each view's
|
|
112
111
|
columns are joined back to the matching `COLUMNS` rows by physical table name,
|
|
113
|
-
so the complete Snapshot
|
|
112
|
+
so the complete Snapshot v1 cross-reference points at the view's own column
|
|
114
113
|
IDs. Missing view definitions or unresolved trigger, partition, or other
|
|
115
114
|
object references become deferred records with diagnostics.
|
|
116
115
|
|
|
117
116
|
Scheduled events are retained as `CatalogOpaqueObject` records. Their event
|
|
118
117
|
metadata and definition remain opaque, and the reader emits an
|
|
119
|
-
`unmodeled-object` warning. Snapshot
|
|
118
|
+
`unmodeled-object` warning. Snapshot v1 keeps these records in
|
|
120
119
|
`opaqueObjects`; they are not treated as typed routines, triggers, or
|
|
121
120
|
migration operations.
|
|
122
121
|
|
|
@@ -132,13 +131,12 @@ The query and normalization layout follows the catalog-reading portions of the
|
|
|
132
131
|
while Qubu keeps the result as typed data instead of generating TypeScript
|
|
133
132
|
declarations.
|
|
134
133
|
|
|
135
|
-
Use `mapCatalogToCompleteSnapshot()` to retain these
|
|
136
|
-
the opaque or deferred boundaries in Snapshot
|
|
137
|
-
when a caller explicitly needs the existing table-only Snapshot v1.
|
|
134
|
+
Use `mapCatalogToSnapshot()` or `mapCatalogToCompleteSnapshot()` to retain these
|
|
135
|
+
typed MySQL families and the opaque or deferred boundaries in Snapshot v1.
|
|
138
136
|
|
|
139
137
|
## Snapshot v1 surface
|
|
140
138
|
|
|
141
|
-
The mapper
|
|
139
|
+
The mapper emits these facts in canonical Snapshot v1:
|
|
142
140
|
|
|
143
141
|
- one namespace and ordinary tables;
|
|
144
142
|
- exact dialect-native column storage;
|
|
@@ -162,14 +160,14 @@ Snapshot v1 objects:
|
|
|
162
160
|
- views and materialized views;
|
|
163
161
|
- sequences, enums, domains, routines, triggers, policies, extensions,
|
|
164
162
|
collations, comments, and partition metadata;
|
|
165
|
-
- PostgreSQL identity sequence options that have no typed
|
|
163
|
+
- PostgreSQL identity sequence options that have no typed field;
|
|
166
164
|
- SQLite virtual/shadow tables, attached namespaces, and unrecoverable
|
|
167
165
|
generated or expression definitions;
|
|
168
166
|
- MySQL/MariaDB differences, prefix indexes, invisible indexes, and advanced
|
|
169
167
|
functional, full-text, or spatial index semantics.
|
|
170
168
|
|
|
171
|
-
Use the
|
|
172
|
-
|
|
169
|
+
Use the Snapshot v1 mapper to retain supported PostgreSQL and MySQL families.
|
|
170
|
+
MySQL scheduled events stay opaque, and MySQL sequences,
|
|
173
171
|
materialized views, row-level security (RLS) policies, extension objects, and
|
|
174
172
|
ownership stay unsupported or deferred. When a row cannot be normalized safely, the reader
|
|
175
173
|
keeps a typed deferred or opaque record and emits a diagnostic instead of
|