qubu 0.0.0 → 0.3.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 (109) hide show
  1. package/dist/canonical-BbnqavJm.mjs +972 -0
  2. package/dist/codegen.d.mts +107 -0
  3. package/dist/codegen.mjs +1199 -0
  4. package/dist/column-CXMxx8Hq.mjs +118 -0
  5. package/dist/complete-D5Djh-zo.mjs +1788 -0
  6. package/dist/complete-types-BdFqUfbb.d.mts +371 -0
  7. package/dist/core.d.mts +33 -0
  8. package/dist/core.mjs +36 -0
  9. package/dist/ddl.d.mts +118 -0
  10. package/dist/ddl.mjs +1120 -0
  11. package/dist/dialect-b2-Z6uBF.mjs +13 -0
  12. package/dist/diff.d.mts +2 -0
  13. package/dist/diff.mjs +883 -0
  14. package/dist/drizzle-mysql.d.mts +24 -0
  15. package/dist/drizzle-mysql.mjs +72 -0
  16. package/dist/drizzle-postgres.d.mts +24 -0
  17. package/dist/drizzle-postgres.mjs +73 -0
  18. package/dist/drizzle-sqlite.d.mts +24 -0
  19. package/dist/drizzle-sqlite.mjs +82 -0
  20. package/dist/drizzle.d.mts +13 -0
  21. package/dist/drizzle.mjs +2 -0
  22. package/dist/errors-BGCoLe_r.mjs +14 -0
  23. package/dist/errors-Dxv73YJu.mjs +26 -0
  24. package/dist/explain-CkIK13L_.mjs +98 -0
  25. package/dist/index-Ds7-mhJi.d.mts +146 -0
  26. package/dist/index-Dug5HnLB.d.mts +204 -0
  27. package/dist/index.d.mts +2 -0
  28. package/dist/index.mjs +1276 -0
  29. package/dist/introspection.d.mts +117 -0
  30. package/dist/introspection.mjs +4341 -0
  31. package/dist/json-CUZlv4HT.mjs +169 -0
  32. package/dist/migration.d.mts +215 -0
  33. package/dist/migration.mjs +1161 -0
  34. package/dist/mysql-DqkqXB6A.mjs +355 -0
  35. package/dist/mysql.d.mts +5 -0
  36. package/dist/mysql.mjs +50 -0
  37. package/dist/naming-QVCOnSj2.mjs +20 -0
  38. package/dist/omit-OxV58AwX.mjs +5 -0
  39. package/dist/on-conflict-BxnxubMb.mjs +143 -0
  40. package/dist/postgres-DEBBeh52.mjs +235 -0
  41. package/dist/postgres.d.mts +123 -0
  42. package/dist/postgres.mjs +52 -0
  43. package/dist/registry-BufIskVN.mjs +569 -0
  44. package/dist/relational-DCZrrNia.mjs +77 -0
  45. package/dist/runtime-BTr-MTlo.mjs +193 -0
  46. package/dist/schema.d.mts +2 -0
  47. package/dist/schema.mjs +5 -0
  48. package/dist/serialize-PF1cfH2P.mjs +556 -0
  49. package/dist/snapshot-CWPgzxNx.mjs +392 -0
  50. package/dist/snapshot.d.mts +4 -0
  51. package/dist/snapshot.mjs +8 -0
  52. package/dist/source-DUoJVXmL.mjs +68 -0
  53. package/dist/sqlite-BU6DBxef.mjs +320 -0
  54. package/dist/sqlite.d.mts +5 -0
  55. package/dist/sqlite.mjs +48 -0
  56. package/dist/standard-BTVYKh_F.mjs +12 -0
  57. package/dist/table-llv9tsZ8.mjs +384 -0
  58. package/dist/types-4Q076HKo.d.mts +3636 -0
  59. package/dist/types-BX0mckiU.d.mts +644 -0
  60. package/dist/types-CO1KaRNc.d.mts +46 -0
  61. package/dist/types-Cec0xzo4.mjs +116 -0
  62. package/dist/value-BvilP0oz.mjs +29 -0
  63. package/dist/vite/ambient.d.ts +449 -0
  64. package/dist/vite.d.mts +35 -0
  65. package/dist/vite.mjs +498 -0
  66. package/docs/config.json +21 -0
  67. package/docs/dialects-and-execution.md +427 -0
  68. package/docs/getting-started.md +96 -0
  69. package/docs/guides/compose-queries.md +225 -0
  70. package/docs/guides/drizzle.md +129 -0
  71. package/docs/guides/extensions/dialects.md +40 -0
  72. package/docs/guides/extensions/overview.md +36 -0
  73. package/docs/guides/extensions/sources-and-clauses.md +82 -0
  74. package/docs/guides/extensions/typed-expressions.md +94 -0
  75. package/docs/guides/extensions/unsafe-syntax.md +27 -0
  76. package/docs/guides/json.md +65 -0
  77. package/docs/guides/mutations.md +138 -0
  78. package/docs/guides/select/conditions.md +110 -0
  79. package/docs/guides/select/grouping-and-windows.md +82 -0
  80. package/docs/guides/select/ordering-and-pagination.md +78 -0
  81. package/docs/guides/select/overview.md +139 -0
  82. package/docs/guides/sql-templates.md +157 -0
  83. package/docs/guides/vite-plugin.md +92 -0
  84. package/docs/index.md +111 -0
  85. package/docs/query-model/fragments.md +109 -0
  86. package/docs/query-model/result-shapes.md +125 -0
  87. package/docs/query-model/source-scope.md +182 -0
  88. package/docs/reference/introspection-support.md +196 -0
  89. package/docs/reference/mysql-snapshot.md +55 -0
  90. package/docs/reference/postgres-snapshot.md +61 -0
  91. package/docs/reference/sqlite-snapshot.md +53 -0
  92. package/docs/reference/supported-surface.md +104 -0
  93. package/docs/schema/catalog-model.md +58 -0
  94. package/docs/schema/code-generation.md +139 -0
  95. package/docs/schema/columns-and-writes.md +130 -0
  96. package/docs/schema/constraints-and-indexes.md +180 -0
  97. package/docs/schema/ddl-emission.md +75 -0
  98. package/docs/schema/diff.md +75 -0
  99. package/docs/schema/introspection.md +214 -0
  100. package/docs/schema/migration-plans.md +76 -0
  101. package/docs/schema/snapshots.md +93 -0
  102. package/docs/schema/storage-and-schema-sql.md +104 -0
  103. package/docs/schema/tables-and-names.md +106 -0
  104. package/docs/sql-semantic-types.md +141 -0
  105. package/docs/troubleshooting.md +151 -0
  106. package/package.json +132 -10
  107. package/skills/qubu/SKILL.md +42 -0
  108. package/skills/qubu/agents/openai.yaml +6 -0
  109. package/readme.md +0 -1
@@ -0,0 +1,427 @@
1
+ # Dialects and execution
2
+
3
+ > Keep portable query construction separate from placeholder, identifier, pagination, cast-target, and driver decisions at the rendering boundary.
4
+
5
+ ## Render once, choose a policy at the boundary
6
+
7
+ `render()` returns a `RenderedQuery` with SQL text and raw parameter values. The
8
+ default renderer uses Qubu's standard SQL policy. Select a concrete dialect
9
+ subpath when a driver needs another placeholder, identifier, pagination, or
10
+ capability policy:
11
+
12
+ | Dialect | Import | Identifiers | Placeholders | Pagination policy |
13
+ | ------------ | ------------------- | ------------- | --------------- | ------------------------------------- |
14
+ | Standard SQL | `render(query)` | double quotes | `?` | `OFFSET ... ROWS FETCH ... ROWS ONLY` |
15
+ | PostgreSQL | `postgresDialect()` | double quotes | `$1`, `$2`, ... | `LIMIT ... OFFSET ...`; `ILIKE` |
16
+ | SQLite | `sqliteDialect()` | double quotes | `?` | `LIMIT ... OFFSET ...` |
17
+ | MySQL | `mysqlDialect()` | backticks | `?` | `LIMIT ... OFFSET ...` |
18
+
19
+ Construct the query without choosing a driver, then render it with the policy
20
+ the adapter expects:
21
+
22
+ ```ts
23
+ import { render } from 'qubu'
24
+ import { postgresDialect } from 'qubu/postgres'
25
+
26
+ const standard = render(query)
27
+ const postgres = render(query, postgresDialect())
28
+
29
+ standard.text
30
+ // ... WHERE ("users"."id" = ?)
31
+
32
+ postgres.text
33
+ // ... WHERE ("users"."id" = $1)
34
+ ```
35
+
36
+ ## Capability requirements
37
+
38
+ Portable syntax stays portable, while dialect-specific syntax carries a
39
+ capability requirement to the rendering boundary. PostgreSQL's `ilike()` is
40
+ the first such feature:
41
+
42
+ ```ts
43
+ import { from, like, render, select, where } from 'qubu'
44
+ import { ilike, postgresDialect } from 'qubu/postgres'
45
+ import { sqliteDialect } from 'qubu/sqlite'
46
+
47
+ const postgresQuery = select(
48
+ { name: users.name },
49
+ from(users),
50
+ where(ilike(users.name, '%ada%'))
51
+ )
52
+
53
+ render(postgresQuery, postgresDialect()) // supported
54
+ render(postgresQuery, sqliteDialect()) // TypeScript error
55
+ ```
56
+
57
+ The same check runs at runtime when a dialect or query has been widened or
58
+ received from an untyped integration. Use the portable operator when the
59
+ query must render across dialects:
60
+
61
+ ```ts
62
+ import { from, like, render, select, where } from 'qubu'
63
+ import { sqliteDialect } from 'qubu/sqlite'
64
+
65
+ const portableQuery = select(
66
+ { name: users.name },
67
+ from(users),
68
+ where(like(users.name, '%ada%'))
69
+ )
70
+
71
+ render(portableQuery)
72
+ render(portableQuery, sqliteDialect())
73
+ ```
74
+
75
+ Custom dialects that implement a supported capability advertise it through
76
+ `createDialect({ capabilities: ['ilike'] })`. A dialect without that
77
+ advertisement is rejected for capability-bearing fragments.
78
+
79
+ Import `ilike` and `postgresDialect` from `qubu/postgres`. The root entrypoint
80
+ does not re-export concrete dialect constructors.
81
+
82
+ ## The adapter owns the driver
83
+
84
+ Qubu does not open connections, bind values for a particular client, or decode
85
+ rows. An adapter receives an `ExecutionRequest` and returns an
86
+ `ExecutionResult`. A `TransactionalQueryAdapter` can also pin one driver
87
+ connection for a callback transaction:
88
+
89
+ ```ts
90
+ import { qubu } from 'qubu'
91
+ import { postgresDialect } from 'qubu/postgres'
92
+ import type { ExecutionRequest, ExecutionResult, QueryAdapter } from 'qubu'
93
+
94
+ declare const driver: {
95
+ query<TRow extends object>(
96
+ text: string,
97
+ parameters: readonly unknown[],
98
+ options: { signal?: AbortSignal }
99
+ ): Promise<{ rows: readonly TRow[]; rowCount: number | null }>
100
+ }
101
+
102
+ const adapter: QueryAdapter = {
103
+ dialect: postgresDialect(),
104
+ async execute<TRow extends object>(request: ExecutionRequest) {
105
+ const { statement, queryKind, signal } = request
106
+ const result = await driver.query<TRow>(
107
+ statement.text,
108
+ statement.parameters,
109
+ { signal }
110
+ )
111
+ return {
112
+ rows: result.rows,
113
+ ...(queryKind !== 'select' &&
114
+ queryKind !== 'set' &&
115
+ result.rowCount !== null
116
+ ? { affectedRows: result.rowCount }
117
+ : {}),
118
+ } satisfies ExecutionResult<TRow>
119
+ },
120
+ }
121
+ ```
122
+
123
+ ## Stream read results
124
+
125
+ Add `StreamingQueryAdapter` when a driver can return rows through an
126
+ adapter-owned `AsyncIterable`. The standalone `stream()` function and the
127
+ bound `db.stream()` method accept only `SELECT` and set-operation queries.
128
+ Mutations stay on `execute()` and `executeRows()`, including mutations with
129
+ `RETURNING`.
130
+
131
+ ```ts
132
+ import { qubu } from 'qubu'
133
+ import type {
134
+ ExecutionRequest,
135
+ ExecutionResult,
136
+ StreamingQueryAdapter,
137
+ } from 'qubu'
138
+ import { postgresDialect } from 'qubu/postgres'
139
+
140
+ declare const driver: {
141
+ query<TRow extends object>(
142
+ text: string,
143
+ parameters: readonly unknown[],
144
+ options: { signal?: AbortSignal }
145
+ ): Promise<{ rows: readonly TRow[]; rowCount: number | null }>
146
+ stream<TRow extends object>(
147
+ text: string,
148
+ parameters: readonly unknown[],
149
+ options: { signal?: AbortSignal }
150
+ ): AsyncIterable<TRow>
151
+ }
152
+
153
+ const adapter: StreamingQueryAdapter = {
154
+ dialect: postgresDialect(),
155
+ async execute<TRow extends object>(request: ExecutionRequest) {
156
+ const result = await driver.query<TRow>(
157
+ request.statement.text,
158
+ request.statement.parameters,
159
+ { signal: request.signal }
160
+ )
161
+ return {
162
+ rows: result.rows,
163
+ ...(result.rowCount === null ? {} : { affectedRows: result.rowCount }),
164
+ } satisfies ExecutionResult<TRow>
165
+ },
166
+ stream<TRow extends object>(request: ExecutionRequest) {
167
+ return driver.stream<TRow>(
168
+ request.statement.text,
169
+ request.statement.parameters,
170
+ { signal: request.signal }
171
+ )
172
+ },
173
+ }
174
+
175
+ const db = qubu(adapter)
176
+ for await (const row of db.stream(readQuery)) {
177
+ consume(row)
178
+ }
179
+ ```
180
+
181
+ Qubu renders the query before calling `stream()` and passes the selected
182
+ dialect, ordered raw parameters, query kind, and optional `AbortSignal` in the
183
+ same `ExecutionRequest` used by `execute()`. The adapter binds values, decodes
184
+ rows, and returns the iterable. Qubu does not open a cursor or connection,
185
+ iterate the result, buffer rows, or impose a fetch size.
186
+
187
+ The adapter owns the iterator's cleanup contract:
188
+
189
+ | Event | Adapter responsibility |
190
+ | ------------------------------- | ------------------------------------------------------------------------------------------------------------- |
191
+ | Normal completion | Close the cursor and release stream-only resources before the iterable completes. |
192
+ | Early iterator close | Implement `return()` so a consumer can stop without leaking resources. |
193
+ | Iteration failure | Close the cursor and release resources before the failure reaches the consumer. |
194
+ | Aborted signal | Stop the driver operation and clean up any open stream resources. |
195
+ | Transaction callback completion | Consume or close every stream before the callback resolves, then commit or release the transaction resources. |
196
+
197
+ `for await` closes an iterator when a loop exits early. Code that manually
198
+ holds an iterator should close it in a `finally` block:
199
+
200
+ ```ts
201
+ const iterator = db.stream(readQuery)[Symbol.asyncIterator]()
202
+ try {
203
+ const first = await iterator.next()
204
+ if (!first.done) consume(first.value)
205
+ } finally {
206
+ await iterator.return?.()
207
+ }
208
+ ```
209
+
210
+ Inside a transaction, use a `StreamingTransactionalQueryAdapter` so the
211
+ transaction callback receives a streaming client. The adapter must keep its
212
+ cursor and connection valid until the callback's streams finish or close:
213
+
214
+ ```ts
215
+ declare const transactionalAdapter: import('qubu').StreamingTransactionalQueryAdapter
216
+ const transactionalDb = qubu(transactionalAdapter)
217
+
218
+ await transactionalDb.transaction(async transaction => {
219
+ for await (const row of transaction.stream(readQuery)) {
220
+ consume(row)
221
+ }
222
+ })
223
+ ```
224
+
225
+ The adapter decides how `next()` drives driver reads, whether it prefetches,
226
+ and how much data it buffers. Qubu only forwards the async-iterator protocol
227
+ and the abort signal. Driver errors and cancellation errors pass through
228
+ unchanged.
229
+
230
+ ## Inspect query plans
231
+
232
+ Add `ExplainableQueryAdapter` when the driver can decode its EXPLAIN rows. The
233
+ standalone `explain()` function and the bound `db.explain()` method render a
234
+ plan request without calling `execute()`:
235
+
236
+ ```ts
237
+ import { explain, qubu } from 'qubu'
238
+ import type { ExplainableQueryAdapter } from 'qubu'
239
+ import { postgresDialect } from 'qubu/postgres'
240
+
241
+ type PostgresPlanRow = { 'QUERY PLAN': string }
242
+
243
+ declare const driver: {
244
+ query<TRow extends object>(
245
+ text: string,
246
+ parameters: readonly unknown[],
247
+ options: { signal?: AbortSignal }
248
+ ): Promise<{ rows: readonly TRow[] }>
249
+ }
250
+
251
+ const adapter: ExplainableQueryAdapter<PostgresPlanRow> = {
252
+ dialect: postgresDialect(),
253
+ async execute() {
254
+ return { rows: [] }
255
+ },
256
+ async explain(request) {
257
+ const result = await driver.query<PostgresPlanRow>(
258
+ request.statement.text,
259
+ request.statement.parameters,
260
+ { signal: request.signal }
261
+ )
262
+ return { rows: result.rows }
263
+ },
264
+ }
265
+
266
+ const plan = await explain(readQuery, adapter, {
267
+ analyze: true,
268
+ verbose: true,
269
+ })
270
+ const db = qubu(adapter)
271
+ const samePlan = await db.explain(readQuery)
272
+ ```
273
+
274
+ Qubu keeps `ExplainResult.rows` in the adapter's vendor-specific shape. It
275
+ does not normalize PostgreSQL, SQLite, or MySQL plans into one tree. The
276
+ adapter owns parameter binding, plan-row decoding, connections, transactions,
277
+ and cancellation. `ExplainRequest` carries the rendered statement, ordered
278
+ raw parameters, query kind, and optional abort signal just like an ordinary
279
+ execution request.
280
+
281
+ The first-party policies accept these options:
282
+
283
+ | Dialect | Plan options | Restrictions |
284
+ | ---------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
285
+ | PostgreSQL | `analyze`, `verbose`, `buffers`, and `format: 'text' \| 'xml' \| 'json' \| 'yaml'` | `buffers` requires `analyze`; analysis is read-only |
286
+ | SQLite | `format: 'query-plan' \| 'bytecode'` or `queryPlan` | `analyze`, `verbose`, and `buffers` are unsupported |
287
+ | MySQL | `analyze` or `format: 'traditional' \| 'json' \| 'tree'` | `analyze` cannot be combined with `format`; analysis is read-only |
288
+
289
+ All supported queries can be explained, including `INSERT`, `UPDATE`, and
290
+ `DELETE`. Mutation EXPLAIN is always plan-only. The type and runtime
291
+ boundaries reject `analyze` for mutations so an inspection call cannot apply a
292
+ write. Unsupported options and invalid combinations raise a structured
293
+ `QueryValidationError` before the adapter is called.
294
+
295
+ ## Bind the adapter once
296
+
297
+ Use `qubu()` when several calls share one adapter. The returned client keeps
298
+ the adapter available as `db.adapter` and accepts the same execution options as
299
+ the standalone functions:
300
+
301
+ ```ts
302
+ const db = qubu(adapter)
303
+
304
+ const controller = new AbortController()
305
+ const result = await db.execute(query, {
306
+ signal: controller.signal,
307
+ })
308
+
309
+ result.rows
310
+ result.affectedRows
311
+
312
+ const rows = await db.rows(readQuery)
313
+ ```
314
+
315
+ `db.execute()` returns the structured result. `db.rows()` returns only its row
316
+ array. Both methods infer each row from the query projection. They do not make
317
+ query values executable or transfer connection ownership to Qubu.
318
+
319
+ ## Run a transaction
320
+
321
+ Use a transactional adapter when several queries must share one commit or
322
+ rollback boundary:
323
+
324
+ ```ts
325
+ import { qubu } from 'qubu'
326
+ import type { TransactionalQueryAdapter } from 'qubu'
327
+
328
+ declare const transactionalAdapter: TransactionalQueryAdapter
329
+ const transactionalDb = qubu(transactionalAdapter)
330
+
331
+ const result = await transactionalDb.transaction(async transaction => {
332
+ await transaction.execute(firstMutation)
333
+ await transaction.execute(secondMutation)
334
+ return transaction.rows(readQuery)
335
+ })
336
+ ```
337
+
338
+ The adapter owns the driver lifecycle. It acquires and pins one connection,
339
+ begins the transaction, invokes the callback, commits after it resolves, rolls
340
+ back after it rejects, and releases the connection in every case. Qubu only
341
+ creates the scoped client and passes the callback result through. It never
342
+ emits `BEGIN`, `COMMIT`, or `ROLLBACK` itself.
343
+
344
+ The transaction client exposes `execute()` and `rows()` but no public
345
+ `transaction()` method, so nested transactions are not part of this contract.
346
+ When the adapter also implements `StreamingQueryAdapter`, the scoped client
347
+ also exposes `stream()` and its streams follow the cleanup rule above. Use
348
+ adapter-specific savepoints when a driver needs nested partial rollback.
349
+ `TransactionOptions.signal` is passed to the adapter. Isolation levels and
350
+ other driver-specific settings remain adapter-specific.
351
+
352
+ The standalone functions remain useful when the adapter varies by call or a
353
+ small module does not need a bound client:
354
+
355
+ ```ts
356
+ import { execute, executeRows } from 'qubu'
357
+
358
+ const result = await execute(query, adapter)
359
+ const rows = await executeRows(readQuery, adapter)
360
+ ```
361
+
362
+ | Result field | Type | Adapter contract |
363
+ | -------------- | ---------------------------- | ------------------------------------------------------------------------------------- |
364
+ | `rows` | `readonly TRow[]` | Always present; use an empty array for a mutation without returned rows |
365
+ | `affectedRows` | `number \| bigint` | Rows inserted, updated, or deleted when the driver reports an affected count |
366
+ | `changedRows` | `number \| bigint` | Rows whose stored values changed when the driver distinguishes them from matched rows |
367
+ | `insertId` | `string \| number \| bigint` | One insert identifier when the driver reports it |
368
+
369
+ The last three fields are optional. For example, an adapter can map PostgreSQL
370
+ `rowCount`, MySQL `affectedRows`, `changedRows`, and `insertId`, or SQLite
371
+ `changes` and `lastInsertRowid`. Omit a fact that the selected driver cannot
372
+ report accurately. Qubu does not derive mutation metadata from returned rows.
373
+
374
+ The adapter's `dialect` becomes the default for standalone and bound execution.
375
+ A `dialect` in the execution options overrides that rendering policy. Qubu
376
+ passes `signal` and the query's `queryKind` to the adapter without changing
377
+ them. The adapter decides whether and how its driver supports cancellation.
378
+ Driver errors pass through unchanged.
379
+
380
+ ```mermaid
381
+ sequenceDiagram
382
+ participant App as Application
383
+ participant Qubu as Qubu renderer
384
+ participant Adapter as QueryAdapter
385
+ participant Driver as Database driver
386
+
387
+ App->>Qubu: db.execute(query, options)
388
+ Qubu->>Qubu: render with selected dialect
389
+ Qubu->>Adapter: statement + queryKind + signal
390
+ Adapter->>Driver: bind and execute
391
+ Driver-->>Adapter: driver result or error
392
+ Adapter-->>Qubu: typed rows + optional mutation facts
393
+ Qubu-->>App: result envelope or rows
394
+ ```
395
+
396
+ ## Create a small custom dialect
397
+
398
+ Use `createDialect()` when a driver needs a different policy but the query
399
+ syntax stays portable:
400
+
401
+ ```ts
402
+ import { render } from 'qubu'
403
+ import { createDialect } from 'qubu/core'
404
+
405
+ const namedParameters = createDialect({
406
+ name: 'named-parameters',
407
+ placeholder: position => `:p${position}`,
408
+ castTypes: { text: 'STRING' },
409
+ })
410
+
411
+ const statement = render(query, namedParameters)
412
+ // ... WHERE ("users"."id" = :p1)
413
+ ```
414
+
415
+ `castTypes` overrides how logical targets from definitions such as `text()`
416
+ render in `CAST` expressions. Omitted entries use the standard spelling. A
417
+ custom definition's explicit `castType` is emitted verbatim instead of passing
418
+ through this map.
419
+
420
+ For syntax that is not a small policy decision, add a [custom fragment or
421
+ clause](guides/extensions/overview.md) instead of making the standard dialect
422
+ pretend that vendor behavior is portable.
423
+
424
+ > [!WARNING]
425
+ > `RenderedQuery.parameters` contains raw application values. The adapter must
426
+ > bind or encode them using the driver API; do not concatenate them into
427
+ > `RenderedQuery.text`.
@@ -0,0 +1,96 @@
1
+ # Getting started
2
+
3
+ > Define a typed table, build one parameterized query, and inspect the exact SQL before connecting a driver.
4
+
5
+ ## Install Qubu
6
+
7
+ Add the package to a TypeScript project:
8
+
9
+ ```bash
10
+ pnpm add qubu
11
+ ```
12
+
13
+ Import query-building functions from the package root. Qubu does not need a
14
+ database connection to construct or render a query.
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.
20
+
21
+ ## Define a table
22
+
23
+ Use `table()` once for each query-facing table. Column helpers describe the
24
+ application values that can be selected and, for mutations, written.
25
+
26
+ ```ts
27
+ import { integer, table, text } from 'qubu'
28
+
29
+ const users = table('users', {
30
+ id: integer(),
31
+ name: text(),
32
+ email: text({ nullable: true }),
33
+ })
34
+ ```
35
+
36
+ `users.id`, `users.name`, and `users.email` are typed column expressions. The
37
+ nullable email column is inferred as `string | null` when selected.
38
+
39
+ ## Build and render a query
40
+
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.
45
+
46
+ ```ts
47
+ import { eq, from, render, select, where } from 'qubu'
48
+
49
+ const query = select(
50
+ {
51
+ id: users.id,
52
+ displayName: users.name,
53
+ },
54
+ from(users),
55
+ where(eq(users.id, 7))
56
+ )
57
+
58
+ const statement = render(query)
59
+ ```
60
+
61
+ The default dialect quotes identifiers with double quotes and uses `?` for
62
+ parameters:
63
+
64
+ ```ts
65
+ statement.text
66
+ // SELECT "users"."id" AS "id", "users"."name" AS "displayName" FROM "users" WHERE ("users"."id" = ?)
67
+
68
+ statement.parameters
69
+ // [7]
70
+ ```
71
+
72
+ The selected row type is available on the query value:
73
+
74
+ ```ts
75
+ type UserRow = typeof query.row
76
+ // { id: number; displayName: string }
77
+ ```
78
+
79
+ > [!NOTE]
80
+ > Rendering produces a statement; it does not execute it. Keep the
81
+ > `RenderedQuery` value for logging or testing. Bind an
82
+ > [application-owned adapter](dialects-and-execution.md) with `qubu()`, or use
83
+ > `execute()` and `executeRows()` directly, to run the query.
84
+
85
+ ## Next steps
86
+
87
+ - [Build a `SELECT`](guides/select/overview.md) with joins, predicates, aggregates, and
88
+ pagination.
89
+ - [Compose queries](guides/compose-queries.md) from CTEs and derived sources.
90
+ - [Write mutations](guides/mutations.md) with typed insert/update/delete
91
+ inputs.
92
+ - [Choose a database dialect](dialects-and-execution.md) when the
93
+ driver expects different identifier, placeholder, or pagination syntax.
94
+ - [Read JSON scalars](guides/json.md) from a JSON column.
95
+ - [Use the Vite compiler hint](guides/vite-plugin.md) for directive-based
96
+ imports.