qubu 0.3.4 → 0.4.0
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/codegen.d.mts +1 -1
- package/dist/codegen.mjs +1 -1
- package/dist/column-D-8OqGuV.mjs +328 -0
- package/dist/{table-llv9tsZ8.mjs → column-LV7oGQde.mjs} +9 -97
- package/dist/{complete-types-BdFqUfbb.d.mts → complete-types-BavBtv8J.d.mts} +1 -1
- package/dist/core.d.mts +1 -1
- package/dist/core.mjs +3 -3
- package/dist/ddl.d.mts +1 -1
- package/dist/ddl.mjs +3 -3
- package/dist/diff.d.mts +1 -1
- package/dist/drizzle-mysql.d.mts +2 -2
- package/dist/drizzle-mysql.mjs +2 -2
- package/dist/drizzle-postgres.d.mts +2 -2
- package/dist/drizzle-postgres.mjs +2 -2
- package/dist/drizzle-sqlite.d.mts +36 -3
- package/dist/drizzle-sqlite.mjs +30 -4
- package/dist/drizzle.d.mts +1 -1
- package/dist/{index-Dug5HnLB.d.mts → index-D3ZOwPT-.d.mts} +2 -2
- package/dist/{index-Ds7-mhJi.d.mts → index-DH0qV6aS.d.mts} +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +64 -34
- package/dist/introspection.d.mts +2 -2
- package/dist/introspection.mjs +1 -1
- package/dist/migration.d.mts +2 -2
- package/dist/{mysql-DqkqXB6A.mjs → mysql-CKEKGnj4.mjs} +3 -3
- package/dist/mysql.d.mts +1 -1
- package/dist/{on-conflict-BxnxubMb.mjs → on-conflict-4MOBl51J.mjs} +4 -3
- package/dist/{postgres-DEBBeh52.mjs → postgres-BK2APkKP.mjs} +3 -2
- package/dist/postgres.d.mts +1 -1
- package/dist/postgres.mjs +3 -3
- package/dist/{registry-BufIskVN.mjs → registry-CXV8u7Pt.mjs} +3 -108
- package/dist/{relational-DCZrrNia.mjs → relational-DuQ9IHSb.mjs} +4 -4
- package/dist/{runtime-BTr-MTlo.mjs → runtime-Cn_Xgzta.mjs} +1 -1
- package/dist/schema.d.mts +2 -2
- package/dist/schema.mjs +6 -4
- package/dist/{serialize-PF1cfH2P.mjs → serialize-CFtYYAdk.mjs} +2 -2
- package/dist/{snapshot-CWPgzxNx.mjs → snapshot-DJpfmxhQ.mjs} +1 -1
- package/dist/snapshot.d.mts +3 -3
- package/dist/snapshot.mjs +4 -4
- package/dist/{source-DUoJVXmL.mjs → source-DGO3DRgg.mjs} +3 -3
- package/dist/{sqlite-BU6DBxef.mjs → sqlite-CrsK0Fza.mjs} +3 -3
- package/dist/sqlite.d.mts +1 -1
- package/dist/sqlite.mjs +1 -1
- package/dist/table-D6rcs8SB.mjs +96 -0
- package/dist/{types-BX0mckiU.d.mts → types-CSNJTYaM.d.mts} +1 -1
- package/dist/{types-H4vyCw8_.d.mts → types-DZsueVoI.d.mts} +1 -1
- package/dist/{types-4Q076HKo.d.mts → types-Deo_q43Y.d.mts} +1442 -916
- package/dist/{types-Cec0xzo4.mjs → types-g-vVvj1B.mjs} +4 -3
- package/dist/{value-BvilP0oz.mjs → value-b6OFZXVS.mjs} +1 -1
- package/dist/vite/ambient.d.ts +13 -61
- package/docs/dialects-and-execution.md +84 -34
- package/docs/guides/drizzle.md +27 -0
- package/docs/reference/supported-surface.md +7 -7
- package/package.json +1 -1
- package/dist/column-CXMxx8Hq.mjs +0 -118
- package/dist/naming-QVCOnSj2.mjs +0 -20
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { s as resolveCastTarget } from "./json-CUZlv4HT.mjs";
|
|
2
2
|
import { t as standardDialect } from "./standard-BTVYKh_F.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { C as fragment, v as makeExpression, y as makeSchemaExpression } from "./column-D-8OqGuV.mjs";
|
|
4
|
+
import { a as columnResultValue } from "./column-LV7oGQde.mjs";
|
|
5
|
+
import { t as asValue } from "./value-b6OFZXVS.mjs";
|
|
5
6
|
//#region src/core/render.ts
|
|
6
7
|
function render(query, options = {}) {
|
|
7
8
|
const dialect = isDialect(options) ? options : options.dialect ?? standardDialect();
|
|
@@ -51,7 +52,7 @@ function cast(expression, target) {
|
|
|
51
52
|
context.append(" AS ");
|
|
52
53
|
context.append(typeof target === "string" ? target : resolveCastTarget(context.dialect, target.castTarget));
|
|
53
54
|
context.append(")");
|
|
54
|
-
});
|
|
55
|
+
}, typeof target === "string" ? void 0 : columnResultValue(target));
|
|
55
56
|
}
|
|
56
57
|
/** Create a cast whose JS output and SQL result domain are declared up front. */
|
|
57
58
|
function typedCast() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as fragment, y as makeSchemaExpression } from "./column-D-8OqGuV.mjs";
|
|
2
2
|
//#region src/core/primitives/parameter.ts
|
|
3
3
|
function parameter(_value) {
|
|
4
4
|
return fragment((context) => context.parameter(_value));
|
package/dist/vite/ambient.d.ts
CHANGED
|
@@ -133,41 +133,9 @@ declare global {
|
|
|
133
133
|
const withCte: typeof import('qubu').withCte
|
|
134
134
|
const uuid: typeof import('qubu').uuid
|
|
135
135
|
|
|
136
|
-
type
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
TInsert = TOutput,
|
|
140
|
-
TUpdate = TInsert,
|
|
141
|
-
THasDefault extends boolean = false,
|
|
142
|
-
TGenerated extends boolean = false,
|
|
143
|
-
TSqlType extends import('qubu').AnySqlType = import('qubu').SqlUnknown,
|
|
144
|
-
TStorage extends import('qubu').ColumnStorage | undefined = undefined,
|
|
145
|
-
TDefault extends import('qubu').ColumnDefault | undefined =
|
|
146
|
-
| import('qubu').ColumnDefault
|
|
147
|
-
| undefined,
|
|
148
|
-
TGeneratedColumn extends
|
|
149
|
-
| import('qubu').GeneratedColumnDescriptor
|
|
150
|
-
| undefined = import('qubu').GeneratedColumnDescriptor | undefined,
|
|
151
|
-
TIdentity extends import('qubu').IdentityDescriptor | undefined =
|
|
152
|
-
| import('qubu').IdentityDescriptor
|
|
153
|
-
| undefined,
|
|
154
|
-
TOnUpdate extends import('qubu').AnySchemaExpression | undefined =
|
|
155
|
-
| import('qubu').AnySchemaExpression
|
|
156
|
-
| undefined,
|
|
157
|
-
> = import('qubu').ColumnDefinition<
|
|
158
|
-
TOutput,
|
|
159
|
-
TNullable,
|
|
160
|
-
TInsert,
|
|
161
|
-
TUpdate,
|
|
162
|
-
THasDefault,
|
|
163
|
-
TGenerated,
|
|
164
|
-
TSqlType,
|
|
165
|
-
TStorage,
|
|
166
|
-
TDefault,
|
|
167
|
-
TGeneratedColumn,
|
|
168
|
-
TIdentity,
|
|
169
|
-
TOnUpdate
|
|
170
|
-
>
|
|
136
|
+
type ColumnDefinitionConfig = import('qubu').ColumnDefinitionConfig
|
|
137
|
+
type ColumnDefinition<TConfig extends ColumnDefinitionConfig = {}> =
|
|
138
|
+
import('qubu').ColumnDefinition<TConfig>
|
|
171
139
|
type ColumnSqlType<T> = import('qubu').ColumnSqlType<T>
|
|
172
140
|
type ColumnDefault = import('qubu').ColumnDefault
|
|
173
141
|
type ColumnDefaultInput = import('qubu').ColumnDefaultInput
|
|
@@ -317,32 +285,16 @@ declare global {
|
|
|
317
285
|
type StreamingTransactionalQueryAdapter =
|
|
318
286
|
import('qubu').StreamingTransactionalQueryAdapter
|
|
319
287
|
type RenderedQuery = import('qubu').RenderedQuery
|
|
320
|
-
type
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
> = import('qubu').
|
|
325
|
-
type
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
> = import('qubu').Query<TRow, TCardinality, TMetadata>
|
|
331
|
-
type SelectQuery<
|
|
332
|
-
TRow extends object = Record<string, unknown>,
|
|
333
|
-
TCardinality extends
|
|
334
|
-
import('qubu').QueryCardinality = import('qubu').QueryCardinality,
|
|
335
|
-
TMetadata = never,
|
|
336
|
-
> = import('qubu').SelectQuery<TRow, TCardinality, TMetadata>
|
|
337
|
-
type Source<
|
|
338
|
-
TIdentity = unknown,
|
|
339
|
-
TRow extends object = Record<string, unknown>,
|
|
340
|
-
TMetadata = never,
|
|
341
|
-
TSqlTypes extends {
|
|
342
|
-
readonly [K in keyof TRow]: import('qubu').AnySqlType
|
|
343
|
-
} = { readonly [K in keyof TRow]: import('qubu').SqlUnknown },
|
|
344
|
-
TConstraints extends import('qubu').SourceConstraintsRecord = {},
|
|
345
|
-
> = import('qubu').Source<TIdentity, TRow, TMetadata, TSqlTypes, TConstraints>
|
|
288
|
+
type MutationQueryConfig = import('qubu').MutationQueryConfig
|
|
289
|
+
type MutationQuery<TConfig extends MutationQueryConfig = {}> =
|
|
290
|
+
import('qubu').MutationQuery<TConfig>
|
|
291
|
+
type QueryConfig = import('qubu').QueryConfig
|
|
292
|
+
type Query<TConfig extends QueryConfig = {}> = import('qubu').Query<TConfig>
|
|
293
|
+
type SelectQuery<TConfig extends QueryConfig = {}> =
|
|
294
|
+
import('qubu').SelectQuery<TConfig>
|
|
295
|
+
type SourceConfig = import('qubu').SourceConfig
|
|
296
|
+
type Source<TConfig extends SourceConfig = {}> =
|
|
297
|
+
import('qubu').Source<TConfig>
|
|
346
298
|
type SourceConstraint = import('qubu').SourceConstraint
|
|
347
299
|
type SourceConstraintsRecord = import('qubu').SourceConstraintsRecord
|
|
348
300
|
type SourceIndexesRecord = import('qubu').SourceIndexesRecord
|
|
@@ -81,15 +81,16 @@ does not re-export concrete dialect constructors.
|
|
|
81
81
|
|
|
82
82
|
## The adapter owns the driver
|
|
83
83
|
|
|
84
|
-
Qubu does not open connections
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
Qubu does not open connections or bind values for a particular client. An
|
|
85
|
+
adapter receives an `ExecutionRequest` and returns driver-normalized object
|
|
86
|
+
rows. Qubu then uses the query's result shape and the adapter's decoder policy
|
|
87
|
+
to produce the typed `ExecutionResult`. A `TransactionalQueryAdapter` can also
|
|
88
|
+
pin one driver connection for a callback transaction:
|
|
88
89
|
|
|
89
90
|
```ts
|
|
90
91
|
import { qubu } from 'qubu'
|
|
91
92
|
import { postgresDialect } from 'qubu/postgres'
|
|
92
|
-
import type { ExecutionRequest,
|
|
93
|
+
import type { ExecutionRequest, QueryAdapter } from 'qubu'
|
|
93
94
|
|
|
94
95
|
declare const driver: {
|
|
95
96
|
query<TRow extends object>(
|
|
@@ -101,9 +102,9 @@ declare const driver: {
|
|
|
101
102
|
|
|
102
103
|
const adapter: QueryAdapter = {
|
|
103
104
|
dialect: postgresDialect(),
|
|
104
|
-
async execute
|
|
105
|
+
async execute(request: ExecutionRequest) {
|
|
105
106
|
const { statement, queryKind, signal } = request
|
|
106
|
-
const result = await driver.query<
|
|
107
|
+
const result = await driver.query<Record<string, unknown>>(
|
|
107
108
|
statement.text,
|
|
108
109
|
statement.parameters,
|
|
109
110
|
{ signal }
|
|
@@ -115,11 +116,61 @@ const adapter: QueryAdapter = {
|
|
|
115
116
|
result.rowCount !== null
|
|
116
117
|
? { affectedRows: result.rowCount }
|
|
117
118
|
: {}),
|
|
118
|
-
}
|
|
119
|
+
}
|
|
119
120
|
},
|
|
120
121
|
}
|
|
121
122
|
```
|
|
122
123
|
|
|
124
|
+
### Decode schema-aware result values
|
|
125
|
+
|
|
126
|
+
Portable boolean, date, timestamp, and JSON columns retain their logical
|
|
127
|
+
result domains through projection aliases, derived queries, CTEs, set
|
|
128
|
+
operations, and mutation `RETURNING`. Register only the conversions required
|
|
129
|
+
by the selected driver configuration:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import {
|
|
133
|
+
booleanResultDecoder,
|
|
134
|
+
dateResultDecoder,
|
|
135
|
+
jsonTextResultDecoder,
|
|
136
|
+
timestampResultDecoder,
|
|
137
|
+
} from 'qubu'
|
|
138
|
+
import type { AdapterExecutionResult, QueryAdapter, RenderedQuery } from 'qubu'
|
|
139
|
+
import { sqliteDialect } from 'qubu/sqlite'
|
|
140
|
+
|
|
141
|
+
declare const sqliteDriver: {
|
|
142
|
+
execute(statement: RenderedQuery): Promise<AdapterExecutionResult>
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const adapter: QueryAdapter = {
|
|
146
|
+
dialect: sqliteDialect(),
|
|
147
|
+
decoders: {
|
|
148
|
+
boolean: booleanResultDecoder,
|
|
149
|
+
date: dateResultDecoder,
|
|
150
|
+
timestamp: timestampResultDecoder,
|
|
151
|
+
json: jsonTextResultDecoder,
|
|
152
|
+
},
|
|
153
|
+
async execute(request) {
|
|
154
|
+
return sqliteDriver.execute(request.statement)
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Do not register `jsonTextResultDecoder` when the driver already returns parsed
|
|
160
|
+
JSON. A JSON string is otherwise ambiguous: it may be serialized JSON or an
|
|
161
|
+
already-decoded JSON string scalar. With no registered decoder, Qubu preserves
|
|
162
|
+
the driver's value.
|
|
163
|
+
|
|
164
|
+
Use a column decoder for a custom stored type, or `mapResult()` for one
|
|
165
|
+
expression. Both override adapter policy for that field:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { column, mapResult, value } from 'qubu'
|
|
169
|
+
|
|
170
|
+
const score = column<number>({ decode: value => Number(value) })
|
|
171
|
+
const decodedTotal = mapResult(value('42'), value => Number(value))
|
|
172
|
+
```
|
|
173
|
+
|
|
123
174
|
## Stream read results
|
|
124
175
|
|
|
125
176
|
Add `StreamingQueryAdapter` when a driver can return rows through an
|
|
@@ -130,11 +181,7 @@ Mutations stay on `execute()` and `executeRows()`, including mutations with
|
|
|
130
181
|
|
|
131
182
|
```ts
|
|
132
183
|
import { qubu } from 'qubu'
|
|
133
|
-
import type {
|
|
134
|
-
ExecutionRequest,
|
|
135
|
-
ExecutionResult,
|
|
136
|
-
StreamingQueryAdapter,
|
|
137
|
-
} from 'qubu'
|
|
184
|
+
import type { ExecutionRequest, StreamingQueryAdapter } from 'qubu'
|
|
138
185
|
import { postgresDialect } from 'qubu/postgres'
|
|
139
186
|
|
|
140
187
|
declare const driver: {
|
|
@@ -152,8 +199,8 @@ declare const driver: {
|
|
|
152
199
|
|
|
153
200
|
const adapter: StreamingQueryAdapter = {
|
|
154
201
|
dialect: postgresDialect(),
|
|
155
|
-
async execute
|
|
156
|
-
const result = await driver.query<
|
|
202
|
+
async execute(request: ExecutionRequest) {
|
|
203
|
+
const result = await driver.query<Record<string, unknown>>(
|
|
157
204
|
request.statement.text,
|
|
158
205
|
request.statement.parameters,
|
|
159
206
|
{ signal: request.signal }
|
|
@@ -161,10 +208,10 @@ const adapter: StreamingQueryAdapter = {
|
|
|
161
208
|
return {
|
|
162
209
|
rows: result.rows,
|
|
163
210
|
...(result.rowCount === null ? {} : { affectedRows: result.rowCount }),
|
|
164
|
-
}
|
|
211
|
+
}
|
|
165
212
|
},
|
|
166
|
-
stream
|
|
167
|
-
return driver.stream<
|
|
213
|
+
stream(request: ExecutionRequest) {
|
|
214
|
+
return driver.stream<Record<string, unknown>>(
|
|
168
215
|
request.statement.text,
|
|
169
216
|
request.statement.parameters,
|
|
170
217
|
{ signal: request.signal }
|
|
@@ -178,11 +225,11 @@ for await (const row of db.stream(readQuery)) {
|
|
|
178
225
|
}
|
|
179
226
|
```
|
|
180
227
|
|
|
181
|
-
Qubu renders the query before calling `stream()
|
|
182
|
-
|
|
183
|
-
same `ExecutionRequest` used by `execute()`. The adapter
|
|
184
|
-
|
|
185
|
-
|
|
228
|
+
Qubu renders the query with the selected dialect before calling `stream()`. It
|
|
229
|
+
passes ordered raw parameters, query kind, result shape, and the optional
|
|
230
|
+
`AbortSignal` in the same `ExecutionRequest` used by `execute()`. The adapter
|
|
231
|
+
binds values and returns the iterable. Qubu lazily decodes each row without
|
|
232
|
+
opening a cursor or connection, buffering rows, or imposing a fetch size.
|
|
186
233
|
|
|
187
234
|
The adapter owns the iterator's cleanup contract:
|
|
188
235
|
|
|
@@ -359,12 +406,12 @@ const result = await execute(query, adapter)
|
|
|
359
406
|
const rows = await executeRows(readQuery, adapter)
|
|
360
407
|
```
|
|
361
408
|
|
|
362
|
-
| Result field |
|
|
363
|
-
| -------------- |
|
|
364
|
-
| `rows` | `readonly
|
|
365
|
-
| `affectedRows` | `number \| bigint`
|
|
366
|
-
| `changedRows` | `number \| bigint`
|
|
367
|
-
| `insertId` | `string \| number \| bigint`
|
|
409
|
+
| Result field | Adapter type | Contract |
|
|
410
|
+
| -------------- | ------------------------------------ | ------------------------------------------------------------------------------------- |
|
|
411
|
+
| `rows` | `readonly Record<string, unknown>[]` | Key by rendered aliases; Qubu returns the decoded `readonly TRow[]` |
|
|
412
|
+
| `affectedRows` | `number \| bigint` | Rows inserted, updated, or deleted when the driver reports an affected count |
|
|
413
|
+
| `changedRows` | `number \| bigint` | Rows whose stored values changed when the driver distinguishes them from matched rows |
|
|
414
|
+
| `insertId` | `string \| number \| bigint` | One insert identifier when the driver reports it |
|
|
368
415
|
|
|
369
416
|
The last three fields are optional. For example, an adapter can map PostgreSQL
|
|
370
417
|
`rowCount`, MySQL `affectedRows`, `changedRows`, and `insertId`, or SQLite
|
|
@@ -373,9 +420,11 @@ report accurately. Qubu does not derive mutation metadata from returned rows.
|
|
|
373
420
|
|
|
374
421
|
The adapter's `dialect` becomes the default for standalone and bound execution.
|
|
375
422
|
A `dialect` in the execution options overrides that rendering policy. Qubu
|
|
376
|
-
passes `signal` and
|
|
423
|
+
passes `signal`, `queryKind`, and `resultShape` to the adapter without changing
|
|
377
424
|
them. The adapter decides whether and how its driver supports cancellation.
|
|
378
|
-
Driver errors pass through unchanged.
|
|
425
|
+
Driver errors pass through unchanged. Decoder failures become a
|
|
426
|
+
`ResultDecodingError` that identifies the row and field without exposing the
|
|
427
|
+
raw value.
|
|
379
428
|
|
|
380
429
|
```mermaid
|
|
381
430
|
sequenceDiagram
|
|
@@ -386,11 +435,12 @@ sequenceDiagram
|
|
|
386
435
|
|
|
387
436
|
App->>Qubu: db.execute(query, options)
|
|
388
437
|
Qubu->>Qubu: render with selected dialect
|
|
389
|
-
Qubu->>Adapter: statement + queryKind + signal
|
|
438
|
+
Qubu->>Adapter: statement + queryKind + resultShape + signal
|
|
390
439
|
Adapter->>Driver: bind and execute
|
|
391
440
|
Driver-->>Adapter: driver result or error
|
|
392
|
-
Adapter-->>Qubu:
|
|
393
|
-
Qubu
|
|
441
|
+
Adapter-->>Qubu: normalized object rows + optional mutation facts
|
|
442
|
+
Qubu->>Qubu: apply field and adapter decoders
|
|
443
|
+
Qubu-->>App: typed result envelope or rows
|
|
394
444
|
```
|
|
395
445
|
|
|
396
446
|
## Create a small custom dialect
|
package/docs/guides/drizzle.md
CHANGED
|
@@ -68,6 +68,33 @@ const rows = await db
|
|
|
68
68
|
Existing Qubu queries can keep importing `users`. Move call sites to
|
|
69
69
|
`drizzleTables.users` one at a time without duplicating the table declaration.
|
|
70
70
|
|
|
71
|
+
### Keep SQLite integer timestamps
|
|
72
|
+
|
|
73
|
+
Use `sqliteTimestamp()` when an existing SQLite schema stores dates as integer
|
|
74
|
+
Unix timestamps and Drizzle must continue reading and writing `Date` values:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { schema, table } from 'qubu'
|
|
78
|
+
import { sqliteTimestamp, toSqliteDrizzleSchema } from 'qubu/drizzle/sqlite'
|
|
79
|
+
|
|
80
|
+
const events = table('events', {
|
|
81
|
+
createdAt: sqliteTimestamp({
|
|
82
|
+
mode: 'timestamp',
|
|
83
|
+
defaultFn: () => new Date(),
|
|
84
|
+
}),
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const drizzleTables = toSqliteDrizzleSchema(schema({ events }))
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`timestamp` stores Unix seconds and `timestamp_ms` stores Unix milliseconds.
|
|
91
|
+
Both modes expose `Date` values through Drizzle. `defaultFn` runs when a
|
|
92
|
+
Drizzle insert omits the column; it is runtime behavior and is not emitted as a
|
|
93
|
+
database `DEFAULT` or serialized into a Qubu snapshot.
|
|
94
|
+
|
|
95
|
+
Use Qubu's root `timestamp()` helper for portable schemas. SQLite stores that
|
|
96
|
+
portable form as ISO text instead of an integer.
|
|
97
|
+
|
|
71
98
|
## Type behavior
|
|
72
99
|
|
|
73
100
|
The converted columns preserve selected values, nullability, required insert
|
|
@@ -70,13 +70,13 @@ Snapshot creation, diffing, migration planning, and DDL emission are pure.
|
|
|
70
70
|
interfaces the application provides. Qubu can emit DDL, but it never applies
|
|
71
71
|
that DDL to a database.
|
|
72
72
|
|
|
73
|
-
| Boundary | Qubu side
|
|
74
|
-
| ------------------------ |
|
|
75
|
-
| Query rendering | Builds a typed query and renders SQL text with ordered raw parameter values
|
|
76
|
-
| Query execution | Binds an adapter with `qubu()` when requested;
|
|
77
|
-
| Catalog introspection | Selects fixed parameterized catalog queries, normalizes rows, and maps catalog data to snapshots
|
|
78
|
-
| Schema source generation | Prints deterministic TypeScript from complete, non-lossy Snapshot v1 introspection without writing files
|
|
79
|
-
| Schema changes | Creates snapshots, compares them, builds deterministic migration plans, and emits DDL from approved plans with preflight diagnostics
|
|
73
|
+
| Boundary | Qubu side | Application side |
|
|
74
|
+
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
75
|
+
| Query rendering | Builds a typed query and renders SQL text with ordered raw parameter values | Keeps the runtime database schema aligned with query definitions and validates any dynamic syntax passed to an unsafe helper |
|
|
76
|
+
| Query execution | Binds an adapter with `qubu()` when requested; passes rendered statements and result shapes to execution adapters; applies registered logical field decoders to buffered or streamed object rows; scopes transaction callbacks; returns typed results, plans, rows, or streams | Owns the adapter, driver, connections, pools, cursors, stream cleanup, transactions, savepoints, retries, parameter encoding, proprietary row normalization, decoder policy, plan-row decoding, backpressure, cancellation, driver error translation, and database lifecycle |
|
|
77
|
+
| Catalog introspection | Selects fixed parameterized catalog queries, normalizes rows, and maps catalog data to snapshots | Supplies `CatalogConnection`, credentials, already-decoded catalog rows, logging, and connection lifecycle |
|
|
78
|
+
| Schema source generation | Prints deterministic TypeScript from complete, non-lossy Snapshot v1 introspection without writing files | Owns generated-file writes, replacement policy, hand-edit merging, and CLI integration |
|
|
79
|
+
| Schema changes | Creates snapshots, compares them, builds deterministic migration plans, and emits DDL from approved plans with preflight diagnostics | Reviews decisions, executes or rolls back statements, acquires locks, manages transactions and migration journals, and owns database lifecycle |
|
|
80
80
|
|
|
81
81
|
Start with [Dialects and execution](../dialects-and-execution.md) for the query
|
|
82
82
|
adapter contract. The schema path is documented in [Canonical schema
|
package/package.json
CHANGED
package/dist/column-CXMxx8Hq.mjs
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { a as assertDialectCapability } from "./json-CUZlv4HT.mjs";
|
|
2
|
-
//#region src/core/fragment.ts
|
|
3
|
-
function fragment(render) {
|
|
4
|
-
return Object.freeze({ render });
|
|
5
|
-
}
|
|
6
|
-
function sequence(parts, separator = " ") {
|
|
7
|
-
return fragment((context) => {
|
|
8
|
-
let first = true;
|
|
9
|
-
for (const part of parts) {
|
|
10
|
-
if (!first) context.append(separator);
|
|
11
|
-
context.render(part);
|
|
12
|
-
first = false;
|
|
13
|
-
}
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
function parenthesize(part) {
|
|
17
|
-
return fragment((context) => {
|
|
18
|
-
context.append("(");
|
|
19
|
-
context.render(part);
|
|
20
|
-
context.append(")");
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
function isFragment(value) {
|
|
24
|
-
return typeof value === "object" && value !== null && "render" in value && typeof value.render === "function";
|
|
25
|
-
}
|
|
26
|
-
//#endregion
|
|
27
|
-
//#region src/expressions/types.ts
|
|
28
|
-
/** Runtime/type-level proof that an expression is safe to use in schema SQL. */
|
|
29
|
-
const schemaExpressionBrand = Symbol("qubu.schema-expression");
|
|
30
|
-
/** Add a concrete dialect requirement without dropping expression metadata. */
|
|
31
|
-
function withDialectCapability(expression, capability) {
|
|
32
|
-
const wrapped = makeExpression(expression.expressionKind, (context) => {
|
|
33
|
-
assertDialectCapability(context.dialect, capability);
|
|
34
|
-
context.render(expression);
|
|
35
|
-
});
|
|
36
|
-
return isSchemaExpression(expression) ? markSchemaExpression(wrapped) : wrapped;
|
|
37
|
-
}
|
|
38
|
-
function makeExpression(expressionKind, render, expressionCategory) {
|
|
39
|
-
return Object.freeze({
|
|
40
|
-
expressionKind,
|
|
41
|
-
...expressionCategory ? { expressionCategory } : {},
|
|
42
|
-
...fragment(render)
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
/** Mark a query-only expression category without changing its SQL renderer. */
|
|
46
|
-
function markExpressionCategory(expression, category) {
|
|
47
|
-
return Object.freeze({
|
|
48
|
-
...expression,
|
|
49
|
-
expressionCategory: category
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Mark a built-in or explicitly audited renderer as schema-deterministic.
|
|
54
|
-
* Prefer {@link defineSchemaExpression} for application extensions because it
|
|
55
|
-
* supplies a restricted schema rendering context.
|
|
56
|
-
*/
|
|
57
|
-
function makeSchemaExpression(expressionKind, render) {
|
|
58
|
-
const expression = makeExpression(expressionKind, render);
|
|
59
|
-
return Object.freeze({
|
|
60
|
-
...expression,
|
|
61
|
-
[schemaExpressionBrand]: true
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
/** Add the schema-determinism brand to an explicitly audited expression. */
|
|
65
|
-
function markSchemaExpression(expression) {
|
|
66
|
-
if (isSchemaExpression(expression)) return expression;
|
|
67
|
-
return Object.freeze({
|
|
68
|
-
...expression,
|
|
69
|
-
[schemaExpressionBrand]: true
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
/** Test the runtime brand used by schema rendering and validation. */
|
|
73
|
-
function isSchemaExpression(value) {
|
|
74
|
-
return typeof value === "object" && value !== null && schemaExpressionBrand in value && value[schemaExpressionBrand] === true;
|
|
75
|
-
}
|
|
76
|
-
function isExpression(value) {
|
|
77
|
-
return typeof value === "object" && value !== null && "expressionKind" in value && "render" in value && typeof value.render === "function";
|
|
78
|
-
}
|
|
79
|
-
//#endregion
|
|
80
|
-
//#region src/core/primitives/identifier.ts
|
|
81
|
-
function identifier(name) {
|
|
82
|
-
return fragment((context) => context.append(context.dialect.quoteIdentifier(name)));
|
|
83
|
-
}
|
|
84
|
-
function qualifiedIdentifier(...parts) {
|
|
85
|
-
return fragment((context) => {
|
|
86
|
-
parts.forEach((part, index) => {
|
|
87
|
-
if (index > 0) context.append(".");
|
|
88
|
-
context.append(context.dialect.quoteIdentifier(part));
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
//#endregion
|
|
93
|
-
//#region src/expressions/column.ts
|
|
94
|
-
function createColumnReference(columnName, sourceReference, fieldName) {
|
|
95
|
-
const expression = makeSchemaExpression("column", (context) => {
|
|
96
|
-
if (context.renderColumnReference) {
|
|
97
|
-
context.renderColumnReference(columnName);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
context.render(sourceReference);
|
|
101
|
-
context.append(".");
|
|
102
|
-
context.render(identifier(columnName));
|
|
103
|
-
});
|
|
104
|
-
return Object.freeze({
|
|
105
|
-
...expression,
|
|
106
|
-
fieldName,
|
|
107
|
-
columnName
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
function isColumnReference(value) {
|
|
111
|
-
return typeof value === "object" && value !== null && "expressionKind" in value && value.expressionKind === "column";
|
|
112
|
-
}
|
|
113
|
-
/** Turn an expression into a fragment that renders it without changing it. */
|
|
114
|
-
function expressionFragment(expression) {
|
|
115
|
-
return fragment((context) => context.render(expression));
|
|
116
|
-
}
|
|
117
|
-
//#endregion
|
|
118
|
-
export { qualifiedIdentifier as a, makeExpression as c, markSchemaExpression as d, withDialectCapability as f, sequence as g, parenthesize as h, identifier as i, makeSchemaExpression as l, isFragment as m, expressionFragment as n, isExpression as o, fragment as p, isColumnReference as r, isSchemaExpression as s, createColumnReference as t, markExpressionCategory as u };
|
package/dist/naming-QVCOnSj2.mjs
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
//#region src/core/naming.ts
|
|
2
|
-
/** Convert an application-facing field name to a SQL-facing identifier. */
|
|
3
|
-
function snakeCaseIdentifier(name) {
|
|
4
|
-
return name.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase();
|
|
5
|
-
}
|
|
6
|
-
function resolveSqlNames(fields) {
|
|
7
|
-
const sqlNames = {};
|
|
8
|
-
const fieldsBySqlName = /* @__PURE__ */ new Map();
|
|
9
|
-
for (const field of fields) {
|
|
10
|
-
const sqlName = field.sqlName ?? snakeCaseIdentifier(field.fieldName);
|
|
11
|
-
if (sqlName.length === 0) throw new Error(`SQL name for field "${field.fieldName}" cannot be empty`);
|
|
12
|
-
const existingField = fieldsBySqlName.get(sqlName);
|
|
13
|
-
if (existingField) throw new Error(`Fields "${existingField}" and "${field.fieldName}" both resolve to SQL name "${sqlName}"`);
|
|
14
|
-
fieldsBySqlName.set(sqlName, field.fieldName);
|
|
15
|
-
sqlNames[field.fieldName] = sqlName;
|
|
16
|
-
}
|
|
17
|
-
return Object.freeze(sqlNames);
|
|
18
|
-
}
|
|
19
|
-
//#endregion
|
|
20
|
-
export { snakeCaseIdentifier as n, resolveSqlNames as t };
|