qubu 0.6.2 → 0.6.3

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.
Files changed (50) hide show
  1. package/dist/mysql.d.mts +1 -1
  2. package/dist/postgres.d.mts +1 -1
  3. package/docs/dialects-and-execution.md +54 -25
  4. package/docs/getting-started.md +9 -9
  5. package/docs/guides/better-auth.md +16 -5
  6. package/docs/guides/compose-queries.md +21 -9
  7. package/docs/guides/drizzle.md +8 -3
  8. package/docs/guides/extensions/dialects.md +1 -1
  9. package/docs/guides/extensions/overview.md +1 -1
  10. package/docs/guides/extensions/sources-and-clauses.md +7 -3
  11. package/docs/guides/extensions/typed-expressions.md +25 -13
  12. package/docs/guides/extensions/unsafe-syntax.md +10 -6
  13. package/docs/guides/json.md +52 -27
  14. package/docs/guides/mutations.md +15 -6
  15. package/docs/guides/select/conditions.md +18 -11
  16. package/docs/guides/select/grouping-and-windows.md +5 -2
  17. package/docs/guides/select/ordering-and-pagination.md +5 -3
  18. package/docs/guides/select/overview.md +6 -3
  19. package/docs/guides/sql-templates.md +11 -5
  20. package/docs/guides/valtio-sync.md +11 -5
  21. package/docs/guides/vite-plugin.md +2 -2
  22. package/docs/index.md +24 -17
  23. package/docs/migrations/adapters.md +47 -19
  24. package/docs/migrations/artifacts-and-policy.md +49 -20
  25. package/docs/migrations/index.md +15 -8
  26. package/docs/migrations/lotta-adoption.md +16 -5
  27. package/docs/migrations/operations.md +29 -15
  28. package/docs/migrations/recovery.md +34 -17
  29. package/docs/query-model/fragments.md +13 -5
  30. package/docs/query-model/result-shapes.md +2 -2
  31. package/docs/query-model/source-scope.md +5 -3
  32. package/docs/reference/introspection-support.md +26 -19
  33. package/docs/reference/mysql-snapshot.md +19 -4
  34. package/docs/reference/postgres-snapshot.md +17 -4
  35. package/docs/reference/sqlite-snapshot.md +19 -2
  36. package/docs/reference/supported-surface.md +221 -85
  37. package/docs/schema/catalog-model.md +24 -7
  38. package/docs/schema/code-generation.md +40 -21
  39. package/docs/schema/columns-and-writes.md +21 -11
  40. package/docs/schema/constraints-and-indexes.md +12 -5
  41. package/docs/schema/ddl-emission.md +16 -5
  42. package/docs/schema/diff.md +12 -4
  43. package/docs/schema/introspection.md +47 -21
  44. package/docs/schema/migration-plans.md +18 -10
  45. package/docs/schema/snapshots.md +57 -29
  46. package/docs/schema/storage-and-schema-sql.md +10 -4
  47. package/docs/schema/tables-and-names.md +1 -1
  48. package/docs/sql-semantic-types.md +11 -8
  49. package/docs/troubleshooting.md +14 -6
  50. package/package.json +1 -1
package/dist/mysql.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Cr as onDuplicateKeyUpdate, Pt as Dialect, Sr as incoming, br as IncomingIdentity, xr as OnDuplicateKeyUpdateClause, yr as IncomingColumns } from "./types-Dqr4o2I1.mjs";
2
2
  //#region src/dialects/mysql.d.ts
3
- declare function mysqlDialect(): Dialect<"row-locking" | "on-duplicate-key-update" | "json">;
3
+ declare function mysqlDialect(): Dialect<"json" | "row-locking" | "on-duplicate-key-update">;
4
4
  //#endregion
5
5
  export { type IncomingColumns, type IncomingIdentity, type OnDuplicateKeyUpdateClause, incoming, mysqlDialect, onDuplicateKeyUpdate };
@@ -4,7 +4,7 @@ import { Ad as MetadataOf, Dr as UpdateFromClause, Gu as ExpressionWithOutput, K
4
4
  * PostgreSQL's only core rendering difference is positional parameters. PostgreSQL-specific
5
5
  * expressions and clauses should remain separate modules.
6
6
  */
7
- declare function postgresDialect(): Dialect<"row-locking" | "json" | "on-conflict" | "ilike" | "update-from">;
7
+ declare function postgresDialect(): Dialect<"json" | "ilike" | "on-conflict" | "row-locking" | "update-from">;
8
8
  /** PostgreSQL's case-insensitive pattern-match operator. */
9
9
  declare function ilike<TLeft extends ExpressionWithOutput<string>, R extends Operand<string>>(left: TLeft & ComparisonValidation<TLeft, R, "ILIKE">, pattern: R): SchemaExpression<ResultMeta<boolean, ((MetadataOf<TLeft & SqlCapabilityValidation<(MetadataOf<TLeft> extends (infer T) ? T extends MetadataOf<TLeft> ? T extends {
10
10
  readonly kind: "result";
@@ -1,6 +1,6 @@
1
1
  # Dialects and execution
2
2
 
3
- > Keep portable query construction separate from placeholder, identifier, pagination, cast-target, and driver decisions at the rendering boundary.
3
+ > Choose a SQL dialect and connect Qubu queries to your database driver.
4
4
 
5
5
  ## Render once, choose a policy at the boundary
6
6
 
@@ -77,10 +77,10 @@ Qubu does not open connections or bind values for a particular client. An
77
77
  adapter receives an `ExecutionRequest` and returns driver-normalized object
78
78
  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
- pin one driver connection for a callback transaction:
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
82
+ `request.statement.parameterSqlTypes`, when present, lists SQL domains in
83
+ the same order as `statement.parameters`. Adapters can pass each domain to their
84
84
  value encoder or driver binding layer when a client distinguishes values such
85
85
  as `DATE`, `TIMESTAMP`, `UUID`, and `DECIMAL`.
86
86
 
@@ -143,12 +143,14 @@ per request, binds Qubu's ordered parameters, copies object rows, and finalizes
143
143
  the statement in a `finally` block. SQLite's change count and generated row ID
144
144
  are returned as mutation metadata when the request is a mutation.
145
145
 
146
- The package does not create or terminate workers. Initialize the official
147
- SQLite module inside a dedicated worker, construct the adapter around its
148
- `sqlite3.oo1.DB`, and close the adapter before terminating that worker. The
149
- browser build must serve the package's `sqlite3.wasm` asset beside the bundled
150
- worker module; the combo runner's verified browser scenario demonstrates this
151
- lifecycle.
146
+ Your application manages the worker:
147
+
148
+ 1. Initialize the official SQLite module inside a dedicated worker.
149
+ 2. Construct the adapter around its `sqlite3.oo1.DB`.
150
+ 3. Close the adapter before terminating the worker.
151
+
152
+ Serve the package’s `sqlite3.wasm` asset beside the bundled worker module. The
153
+ combo runner’s verified browser scenario demonstrates this setup.
152
154
 
153
155
  ### Decode schema-aware result values
154
156
 
@@ -434,12 +436,16 @@ queries identify their parent transaction operation. Hooks are synchronous,
434
436
  and their failures are sent to `onHookError` without changing the database
435
437
  operation's result.
436
438
 
439
+ ### What observations include
440
+
437
441
  Hook metadata accepts only strings, numbers, and booleans. Observations include
438
442
  rendered SQL and parameter count, but never parameter values, result rows,
439
443
  decoded values, or insert identifiers. Rendered SQL can still contain literals
440
444
  introduced by unsafe SQL helpers, so treat it according to the application's
441
445
  logging policy.
442
446
 
447
+ ### Stream observation timing
448
+
443
449
  Streaming adapters are still called eagerly. A consumed stream completes its
444
450
  observation when it is exhausted, closed early, or fails. A stream created but
445
451
  never consumed has no completion observation. Hooks are available only on
@@ -465,11 +471,16 @@ const result = await transactionalDb.transaction(async (transaction) => {
465
471
  })
466
472
  ```
467
473
 
468
- The adapter owns the driver lifecycle. It acquires and pins one connection,
469
- begins the transaction, invokes the callback, commits after it resolves, rolls
470
- back after it rejects, and releases the connection in every case. Qubu only
471
- creates the scoped client and passes the callback result through. It never
472
- emits `BEGIN`, `COMMIT`, or `ROLLBACK` itself.
474
+ The adapter manages the transaction:
475
+
476
+ 1. Acquire and pin one connection.
477
+ 2. Begin the transaction.
478
+ 3. Run the callback.
479
+ 4. Commit if the callback resolves, or roll back if it rejects.
480
+ 5. Release the connection in either case.
481
+
482
+ Qubu creates the scoped client and returns the callback result. The adapter
483
+ emits `BEGIN`, `COMMIT`, and `ROLLBACK`.
473
484
 
474
485
  A scoped client's methods follow its adapter's capabilities: `execute()` and
475
486
  `rows()` are always available; EXPLAIN and streaming require their respective
@@ -503,20 +514,32 @@ failure escape also rolls back the outer transaction. Failed savepoint creation
503
514
  or recovery makes the entire transaction unsafe to commit, even if the callback
504
515
  catches the error. Primary and cleanup failures are retained in `AggregateError`.
505
516
 
517
+ #### Finish work before leaving a scope
518
+
506
519
  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.
520
+ reject:
521
+
522
+ - Calls on a finished scoped client.
523
+ - Overlapping sibling scopes.
524
+ - A child scope started while its parent has pending queries.
525
+ - Parent queries while a child is active.
526
+
527
+ If a callback finishes with work pending, the adapter waits for that work and
528
+ rolls back.
529
+
530
+ Use the active scoped client for all work on a directly supplied connection.
531
+ Its root client rejects unrelated operations during the transaction. A pg pool
532
+ can still run independent queries on other connections.
533
+
534
+ Your application remains responsible for raw driver calls and separately
535
+ constructed adapters.
515
536
 
516
537
  EXPLAIN and result decoding remain available at every depth. Nested transaction
517
538
  hooks identify their enclosing transaction with `parentId`; queries identify
518
539
  their immediate scope. Cancellation does not interrupt savepoint recovery.
519
540
 
541
+ ## Execute without a bound client
542
+
520
543
  The standalone functions remain useful when the adapter varies by call or a
521
544
  small module does not need a bound client:
522
545
 
@@ -527,6 +550,8 @@ const result = await execute(query, adapter)
527
550
  const rows = await executeRows(readQuery, adapter)
528
551
  ```
529
552
 
553
+ ### Result fields
554
+
530
555
  | Result field | Adapter type | Contract |
531
556
  | -------------- | ------------------------------------ | ------------------------------------------------------------------------------------- |
532
557
  | `rows` | `readonly Record<string, unknown>[]` | Key by rendered aliases; Qubu returns the decoded `readonly TRow[]` |
@@ -539,11 +564,15 @@ The last three fields are optional. For example, an adapter can map PostgreSQL
539
564
  `changes` and `lastInsertRowid`. Omit a fact that the selected driver cannot
540
565
  report accurately. Qubu does not derive mutation metadata from returned rows.
541
566
 
567
+ ### Dialect overrides and errors
568
+
542
569
  The adapter's `dialect` becomes the default for standalone and bound execution.
543
570
  A `dialect` in the execution options overrides that rendering policy. Qubu
544
571
  passes `signal`, `queryKind`, and `resultShape` to the adapter without changing
545
- them. The adapter decides whether and how its driver supports cancellation.
546
- Driver errors pass through unchanged. Decoder failures become a
572
+ them.
573
+
574
+ The adapter decides whether and how its driver supports cancellation. Driver
575
+ errors pass through unchanged. Decoder failures become a
547
576
  `ResultDecodingError` that identifies the row and field without exposing the
548
577
  raw value.
549
578
 
@@ -1,6 +1,6 @@
1
1
  # Getting started
2
2
 
3
- > Define a typed table, build one parameterized query, and inspect the exact SQL before connecting a driver.
3
+ > Define a table and inspect your first query’s SQL and parameters.
4
4
 
5
5
  ## Install Qubu
6
6
 
@@ -13,10 +13,9 @@ pnpm add qubu
13
13
  Import query-building functions from the package root. Qubu does not need a
14
14
  database connection to construct or render a query.
15
15
 
16
- The examples use the same order as the rendered statement: projection, `FROM`,
17
- then `WHERE`, ordering, grouping, and pagination. `select()` still accepts
18
- independent clauses in any order, which lets reusable values be composed, but
19
- keeping the final call in SQL order makes the query easy to scan and repair.
16
+ The examples write clauses in SQL order so the query is easy to scan.
17
+ `select()` also accepts independent clauses in any order and puts them in SQL
18
+ order when rendering.
20
19
 
21
20
  ## Define a table
22
21
 
@@ -38,10 +37,9 @@ nullable email column is inferred as `string | null` when selected.
38
37
 
39
38
  ## Build and render a query
40
39
 
41
- Pass a named projection and the final clauses to `select()` in SQL order. Qubu
42
- also accepts independent clause values in another order when composition needs
43
- it, then renders the normalized statement in SQL order. The example uses the
44
- `users` table from the previous section.
40
+ Pass the fields you want to return as a named object, called the projection.
41
+ Then add the clauses. This example uses the `users` table from the previous
42
+ section.
45
43
 
46
44
  ```ts
47
45
  import { eq, from, render, select, where } from "qubu"
@@ -69,6 +67,8 @@ statement.parameters
69
67
  // [7]
70
68
  ```
71
69
 
70
+ ### Inspect the result type
71
+
72
72
  The selected row type is available on the query value:
73
73
 
74
74
  ```ts
@@ -1,6 +1,8 @@
1
1
  # Better Auth
2
2
 
3
- > Derive Qubu-owned auth tables and run Better Auth through a transactional Qubu client.
3
+ > Define auth tables with Qubu and connect Better Auth to a transactional Qubu client.
4
+
5
+ ## Install the integration
4
6
 
5
7
  Install the integration next to Qubu and Better Auth:
6
8
 
@@ -8,10 +10,15 @@ Install the integration next to Qubu and Better Auth:
8
10
  pnpm add qubu @qubu/better-auth better-auth
9
11
  ```
10
12
 
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.
13
+ ## Define the auth schema
14
+
15
+ Define the Better Auth options once. Qubu derives its schema from Better Auth’s
16
+ resolved public metadata, including:
17
+
18
+ - Core tables and renamed models or fields.
19
+ - Additional fields and plugin tables.
20
+ - References and unique constraints.
21
+ - Compound indexes.
15
22
 
16
23
  ```ts
17
24
  import { betterAuth } from "better-auth"
@@ -44,6 +51,8 @@ export const auth = betterAuth({
44
51
  migration-plan, and DDL workflows. The adapter's Better Auth `createSchema`
45
52
  hook emits a TypeScript module that reconstructs the same Qubu-owned metadata.
46
53
 
54
+ ## Database requirements
55
+
47
56
  The package never imports PostgreSQL, MySQL, or SQLite drivers. It executes
48
57
  through Qubu's query and transaction boundaries. PostgreSQL and SQLite use one
49
58
  limited mutation statement for atomic consume and guarded increment operations;
@@ -51,6 +60,8 @@ MySQL locks one selected row inside the Qubu-owned transaction. A client without
51
60
  transaction support, or a dialect other than PostgreSQL, MySQL, or SQLite, is
52
61
  rejected during adapter construction.
53
62
 
63
+ ## Enum limitation
64
+
54
65
  Better Auth enum metadata is currently rejected because Qubu cannot preserve
55
66
  the closed value set as a portable column without adding a database constraint.
56
67
  The error includes the model and field path instead of silently widening it to
@@ -1,6 +1,6 @@
1
1
  # Compose queries
2
2
 
3
- > Reuse a query's inferred row shape as a typed source for CTEs, derived tables, subqueries, and set operations.
3
+ > Reuse a query as a CTE, a derived table, or a subquery, and combine query results.
4
4
 
5
5
  ## Turn a query into a CTE
6
6
 
@@ -28,6 +28,8 @@ The rendered statement includes the `WITH` clause before `SELECT`. Selected
28
28
  camelCase keys use snake_case while they belong to the CTE relation; the outer
29
29
  result projection aliases them back to camelCase for the returned row.
30
30
 
31
+ ### Use a CTE in a mutation
32
+
31
33
  Attach the same clause to an insert, update, or delete when the mutation reads
32
34
  through the CTE. For example, an insert can consume a filtered CTE through
33
35
  `insertSelect()`:
@@ -66,11 +68,20 @@ const numbers = recursiveCte("numbers", select({ value: cast(value(1), integer()
66
68
  const query = select({ value: numbers.value }, withCte(numbers), from(numbers))
67
69
  ```
68
70
 
69
- The anchor names the fields, application types, nullability, and SQL domains
70
- that the returned source exposes. The member must project those same fields
71
- with compatible types. Give bound anchor values an explicit SQL type with
71
+ The anchor defines the fields the CTE returns:
72
+
73
+ - Field names.
74
+ - Application types.
75
+ - Nullability.
76
+ - SQL domains.
77
+
78
+ The recursive member must select the same fields with compatible types.
79
+
80
+ Give bound anchor values an explicit SQL type with
72
81
  `cast()` when the database cannot infer it from surrounding columns; PostgreSQL
73
- requires this for recursive CTE anchors. Qubu renders `WITH RECURSIVE`, an
82
+ requires this for recursive CTE anchors.
83
+
84
+ Qubu renders `WITH RECURSIVE`, an
74
85
  explicit relation column list, and `anchor UNION ALL member`; ordinary and
75
86
  recursive CTEs can share one `withCte()` clause.
76
87
 
@@ -117,7 +128,9 @@ const query = select(
117
128
  `scalar()` throws at runtime when the query selects more than one field. Its
118
129
  type is the selected field's value type, widened with `null` when the query may
119
130
  return no rows. An ordinary select and `fetchFirst(1)` are both nullable: the
120
- limit proves at most one row, not that a row exists. A source-free select such
131
+ limit proves at most one row, not that a row exists.
132
+
133
+ A source-free select such
121
134
  as `select({ value: value(42) })` is known to produce exactly one row.
122
135
 
123
136
  Qubu does not treat an arbitrary predicate as proof of exactness. Use
@@ -163,9 +176,8 @@ code.
163
176
  ## Constrain a reusable fragment by required fields
164
177
 
165
178
  Use `TableLike` when a fragment requires a physical table and `SourceLike`
166
- when aliases, CTEs, derived tables, or custom sources are also valid. Both are
167
- lower-bound constraints: the source may contain additional fields, and the
168
- generic function retains its exact source identity.
179
+ when aliases, CTEs, derived tables, or custom sources are also valid. Both allow the source to contain additional fields. The generic function
180
+ retains the source’s exact identity.
169
181
 
170
182
  For an application-level requirement, describe the required JavaScript row:
171
183
 
@@ -123,9 +123,14 @@ override.
123
123
  ## Runtime metadata
124
124
 
125
125
  Each dialect adapter maps Qubu storage descriptors to its own Drizzle builders.
126
- It also transfers concrete defaults, generated expressions, common primary and
127
- unique constraints, checks, foreign keys, and indexes. Native storage must
128
- belong to the selected dialect:
126
+ It also transfers:
127
+
128
+ - Concrete defaults and generated expressions.
129
+ - Common primary and unique constraints.
130
+ - Checks and foreign keys.
131
+ - Indexes.
132
+
133
+ Native storage must belong to the selected dialect:
129
134
 
130
135
  ```ts
131
136
  import { nativeColumn, schema, table } from "qubu"
@@ -1,6 +1,6 @@
1
1
  # Add a dialect policy
2
2
 
3
- > Change identifiers, placeholders, pagination, or cast targets at the rendering boundary without changing portable query construction.
3
+ > Customize how Qubu renders SQL for your driver.
4
4
 
5
5
  Use `createDialect()` when the query is portable but the driver changes
6
6
  identifiers, placeholders, or pagination:
@@ -1,6 +1,6 @@
1
1
  # Extend Qubu
2
2
 
3
- > Choose an extension boundary when the built-in API does not cover a driver-specific or uncommon SQL feature.
3
+ > Add SQL features that Qubu’s built-in helpers do not cover.
4
4
 
5
5
  Qubu extensions are values that render SQL and carry the metadata later
6
6
  composition needs. Choose the page that matches the thing you are adding:
@@ -1,6 +1,6 @@
1
1
  # Add sources and clauses
2
2
 
3
- > Publish a custom SQL clause or relation while preserving parameter order and source-scope checks.
3
+ > Add a SQL clause or table-like source that works with Qubu’s query checks.
4
4
 
5
5
  ## Add a custom clause
6
6
 
@@ -67,9 +67,13 @@ render(query)
67
67
  ```
68
68
 
69
69
  `identity` is the source-scope key; `reference` is the SQL qualifier used by
70
- the generated columns. A nullable column remains nullable intrinsically, and a
70
+ the generated columns.
71
+
72
+ A nullable column remains nullable, and a
71
73
  `leftJoin(rows, ...)` adds outer-join nullability to every selected row
72
- column. Render the complete relation in the producer and bind values with
74
+ column.
75
+
76
+ Render the complete relation in the producer and bind values with
73
77
  `context.parameter()`; the normal renderer preserves parameter order.
74
78
 
75
79
  ## Read next
@@ -1,6 +1,6 @@
1
1
  # Add typed expressions
2
2
 
3
- > Extend Qubu with expressions that retain source, nullability, result, and SQL-domain metadata.
3
+ > Build custom expressions that preserve result types and query checks.
4
4
 
5
5
  ## Build expressions from public helpers
6
6
 
@@ -47,13 +47,21 @@ const nameAsCitext = cast(users.name, citext)
47
47
  ```
48
48
 
49
49
  The first three `column` type arguments are output, insert, and update values;
50
- the fourth is the SQL domain. The `text` equality and ordering groups make the
51
- custom domain compatible with `SqlText`. Use a distinct group when cross-type
52
- comparison is not portable. `castType` also makes this definition a cast
53
- target; its SQL text is emitted verbatim, so keep it in trusted extension code.
50
+ the fourth is the SQL domain.
51
+
52
+ The `text` equality and ordering groups make the custom domain compatible with `SqlText`. Use a distinct group when cross-type
53
+ comparison is not portable.
54
+
55
+ ### Use the definition as a cast target
56
+
57
+ `castType` also makes this definition a cast target. Its SQL text is emitted
58
+ unchanged, so keep it in trusted extension code.
59
+
54
60
  Definitions with schema flags are not accepted as cast targets because cast
55
61
  nullability comes from the operand and write flags have no cast meaning.
56
62
 
63
+ ### Type individual expressions
64
+
57
65
  Declare result domains at other extension boundaries too:
58
66
 
59
67
  ```ts
@@ -66,13 +74,17 @@ const rawNameAsText = typedCast<string, SqlText>()(users.name, "TEXT")
66
74
  const generated = unsafeExpression<string, SqlText>("custom_text()")
67
75
  ```
68
76
 
69
- `typedCall()` preserves source requirements from its arguments. `typedCast()`
70
- is the fallback when no reusable definition describes the target. It preserves
71
- operand nullability and source metadata while emitting its supplied type name
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.
77
+ Choose the helper for the operation:
78
+
79
+ - `typedCall()` preserves source requirements from its arguments.
80
+ - `typedCast()` supplies a cast target when no reusable definition describes
81
+ it. It preserves operand nullability and source metadata, and emits the
82
+ supplied type name unchanged.
83
+ - `typedValue()` binds a parameter and declares its runtime SQL domain for
84
+ the adapter. It does not choose a JavaScript result decoder; schema columns
85
+ carry decoder metadata separately.
86
+ - `unsafeExpression()` emits its string unchanged. Use it only when the other
87
+ helpers cannot express the syntax.
76
88
 
77
89
  The lower-level forms also expose the SQL domain in their generic lists:
78
90
  `call<Output, Name, Arguments, NullableFrom, SqlType>()` and
@@ -80,7 +92,7 @@ The lower-level forms also expose the SQL domain in their generic lists:
80
92
  argument or nullability types in its own generic signature.
81
93
 
82
94
  Untyped `column()`, `value()`, `call()`, and custom expressions use
83
- `SqlUnknown`, which stays permissive for backward compatibility. Declaring a
95
+ `SqlUnknown`, which allows composition without SQL-domain checks. Declaring a
84
96
  known domain opts the extension into incompatible-operation errors. See
85
97
  [SQL semantic types](../../sql-semantic-types.md) for the capability model and
86
98
  its limits.
@@ -14,12 +14,16 @@ const query = select({
14
14
  })
15
15
  ```
16
16
 
17
- Keep raw identifiers and values out of the string. Prefer a typed custom
18
- fragment when the syntax will be reused. Use the [`sql` template
19
- tag](../sql-templates.md) when fixed trusted syntax needs bound runtime values
20
- or existing Qubu fragments. Keep dynamic SQL text on `unsafeExpression()` and
21
- runtime identifiers on `identifier()` or `qualifiedIdentifier()` from
22
- `qubu/core`.
17
+ ## Choose the right helper
18
+
19
+ Keep raw identifiers and values out of the string:
20
+
21
+ - Use a typed custom fragment for syntax you will reuse.
22
+ - Use the [`sql` template tag](../sql-templates.md) for fixed trusted syntax
23
+ with bound values or existing Qubu fragments.
24
+ - Use `unsafeExpression()` for trusted dynamic SQL text.
25
+ - Use `identifier()` or `qualifiedIdentifier()` from `qubu/core` for runtime
26
+ identifiers.
23
27
 
24
28
  Read [Dialects and execution](../../dialects-and-execution.md) for the boundary
25
29
  between rendering and driver behavior. Read [Add typed
@@ -2,7 +2,9 @@
2
2
 
3
3
  > Build inferred nested results, or read scalar values from stored JSON documents.
4
4
 
5
- Use jsonArrayFrom() to nest a query's rows and jsonObjectFrom() for a query
5
+ ## Nest query results
6
+
7
+ Use `jsonArrayFrom()` to nest a query's rows and `jsonObjectFrom()` for a query
6
8
  proven to return at most one row. Both preserve filtering, correlation,
7
9
  ordering, and pagination:
8
10
 
@@ -57,45 +59,63 @@ const query = select(
57
59
  // latestPost: { title: string } | null }
58
60
  ```
59
61
 
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.
62
+ ### Empty results and row limits
63
+
64
+ Execute the query through a Qubu adapter to decode nested results:
65
+
66
+ - An empty array query returns `[]`.
67
+ - An empty object query returns `null`.
68
+ - A source-free query proven to return exactly one row produces a non-null object.
65
69
 
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.
70
+ An object query must be known to return at most one row. An unconditional
71
+ `fetchFirst(1)` or `fetchFirst(0)` proves that limit; a conditional limit does not.
69
72
 
70
- ### Preserve ordering and logical values
73
+ The helpers compose inside further `select()` projections, so nesting can
74
+ continue without result-type assertions. `correlate()` and the outer query's
75
+ `FROM/JOIN` scope remain checked at every level.
71
76
 
72
- Nested arrays retain explicit ORDER BY and pagination. Tied sort keys retain
77
+ ### Preserve ordering
78
+
79
+ Nested arrays retain explicit `ORDER BY` and pagination. Tied sort keys retain
73
80
  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.
81
+ `DISTINCT` ordering must use the same expressions as the selection. Without an
82
+ `ORDER BY`, array order is unspecified.
83
+
84
+ ### Supported databases
76
85
 
77
86
  Nested results support PostgreSQL, MySQL 8.0.21+, and SQLite 3.45+. Other
78
87
  dialects fail during rendering. SQLite's minimum includes the JSON aggregate
79
88
  ordering fix needed to retain object values.
80
89
 
90
+ ### Decode nested values
91
+
81
92
  Built-in column domains decode to their declared types, including bigint,
82
- Uint8Array, Date, boolean, and nested JSON. Qubu transports precision-sensitive
93
+ `Uint8Array`, `Date`, boolean, and nested JSON. Qubu transports precision-sensitive
83
94
  values as text and rejects numbers that lose significant decimal digits or
84
95
  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
96
+ integers.
97
+
98
+ Unknown or custom SQL domains need a supported explicit cast, for
99
+ example `cast(value(7), integer())`; declaring a TypeScript result alone does
87
100
  not provide runtime decoding information.
88
101
 
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;
102
+ Custom `mapResult()` and column decoders receive the JSON transport value as
103
+ `unknown`. The value may be:
104
+
105
+ - A bigint or decimal string.
106
+ - A hexadecimal binary string.
107
+ - A serialized JSON string.
108
+ - An ordinary JSON scalar.
109
+
110
+ The custom decoder converts it to the declared application type. Adapter-wide
111
+ decoders do not run inside nested objects.
112
+
113
+ Keep arbitrary stored JSON within JavaScript’s numeric precision;
94
114
  unsupported numeric representations fail instead of silently rounding.
95
115
 
96
116
  ## Read stored JSON scalars
97
117
 
98
- Use a structured jsonPath() when a query needs a scalar or an existence check
118
+ Use a structured `jsonPath()` when a query needs a scalar or an existence check
99
119
  inside a JSON document:
100
120
 
101
121
  ```ts
@@ -135,7 +155,7 @@ interpolating caller-provided SQL.
135
155
  ## Understand missing values
136
156
 
137
157
  Scalar reads return SQL NULL when the path is missing, contains JSON null, or
138
- resolves to another JSON scalar type. jsonExists() returns true for a present
158
+ resolves to another JSON scalar type. `jsonExists()` returns true for a present
139
159
  JSON null, false for a missing path, and false when the document itself is SQL
140
160
  NULL.
141
161
 
@@ -143,16 +163,21 @@ These rules keep path existence separate from extraction nullability.
143
163
 
144
164
  ## Check dialect support
145
165
 
146
- The standard dialect emits SQL/JSON JSON_VALUE and JSON_EXISTS syntax.
166
+ The standard dialect emits SQL/JSON `JSON_VALUE` and `JSON_EXISTS` syntax.
147
167
  PostgreSQL, MySQL, and SQLite use their native JSON policies. The current
148
168
  policies require PostgreSQL 12 or newer, MySQL 8.0.21 or newer, and SQLite JSON
149
169
  functions. An application-created dialect must provide a JSON renderer.
150
170
 
151
171
  ## Know the current limits
152
172
 
153
- JSON paths cover deterministic key and index traversal. Wildcards, filters,
154
- recursive descent, JSON-returning extraction, document mutation, and row
155
- expansion remain dialect-specific extensions.
173
+ JSON paths follow explicit keys and indexes. These features require
174
+ dialect-specific extensions:
175
+
176
+ - Wildcards and filters.
177
+ - Recursive descent.
178
+ - Extraction that returns JSON.
179
+ - Document mutation.
180
+ - Row expansion.
156
181
 
157
182
  For the SQL domain and nullability rules behind JSON columns, read
158
183
  [SQL semantic types](../sql-semantic-types.md).