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.
- package/dist/canonical-BbnqavJm.mjs +972 -0
- package/dist/codegen.d.mts +107 -0
- package/dist/codegen.mjs +1199 -0
- package/dist/column-CXMxx8Hq.mjs +118 -0
- package/dist/complete-D5Djh-zo.mjs +1788 -0
- package/dist/complete-types-BdFqUfbb.d.mts +371 -0
- package/dist/core.d.mts +33 -0
- package/dist/core.mjs +36 -0
- package/dist/ddl.d.mts +118 -0
- package/dist/ddl.mjs +1120 -0
- package/dist/dialect-b2-Z6uBF.mjs +13 -0
- package/dist/diff.d.mts +2 -0
- package/dist/diff.mjs +883 -0
- package/dist/drizzle-mysql.d.mts +24 -0
- package/dist/drizzle-mysql.mjs +72 -0
- package/dist/drizzle-postgres.d.mts +24 -0
- package/dist/drizzle-postgres.mjs +73 -0
- package/dist/drizzle-sqlite.d.mts +24 -0
- package/dist/drizzle-sqlite.mjs +82 -0
- package/dist/drizzle.d.mts +13 -0
- package/dist/drizzle.mjs +2 -0
- package/dist/errors-BGCoLe_r.mjs +14 -0
- package/dist/errors-Dxv73YJu.mjs +26 -0
- package/dist/explain-CkIK13L_.mjs +98 -0
- package/dist/index-Ds7-mhJi.d.mts +146 -0
- package/dist/index-Dug5HnLB.d.mts +204 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +1276 -0
- package/dist/introspection.d.mts +117 -0
- package/dist/introspection.mjs +4341 -0
- package/dist/json-CUZlv4HT.mjs +169 -0
- package/dist/migration.d.mts +215 -0
- package/dist/migration.mjs +1161 -0
- package/dist/mysql-DqkqXB6A.mjs +355 -0
- package/dist/mysql.d.mts +5 -0
- package/dist/mysql.mjs +50 -0
- package/dist/naming-QVCOnSj2.mjs +20 -0
- package/dist/omit-OxV58AwX.mjs +5 -0
- package/dist/on-conflict-BxnxubMb.mjs +143 -0
- package/dist/postgres-DEBBeh52.mjs +235 -0
- package/dist/postgres.d.mts +123 -0
- package/dist/postgres.mjs +52 -0
- package/dist/registry-BufIskVN.mjs +569 -0
- package/dist/relational-DCZrrNia.mjs +77 -0
- package/dist/runtime-BTr-MTlo.mjs +193 -0
- package/dist/schema.d.mts +2 -0
- package/dist/schema.mjs +5 -0
- package/dist/serialize-PF1cfH2P.mjs +556 -0
- package/dist/snapshot-CWPgzxNx.mjs +392 -0
- package/dist/snapshot.d.mts +4 -0
- package/dist/snapshot.mjs +8 -0
- package/dist/source-DUoJVXmL.mjs +68 -0
- package/dist/sqlite-BU6DBxef.mjs +320 -0
- package/dist/sqlite.d.mts +5 -0
- package/dist/sqlite.mjs +48 -0
- package/dist/standard-BTVYKh_F.mjs +12 -0
- package/dist/table-llv9tsZ8.mjs +384 -0
- package/dist/types-4Q076HKo.d.mts +3636 -0
- package/dist/types-BX0mckiU.d.mts +644 -0
- package/dist/types-CO1KaRNc.d.mts +46 -0
- package/dist/types-Cec0xzo4.mjs +116 -0
- package/dist/value-BvilP0oz.mjs +29 -0
- package/dist/vite/ambient.d.ts +449 -0
- package/dist/vite.d.mts +35 -0
- package/dist/vite.mjs +498 -0
- package/docs/config.json +21 -0
- package/docs/dialects-and-execution.md +427 -0
- package/docs/getting-started.md +96 -0
- package/docs/guides/compose-queries.md +225 -0
- package/docs/guides/drizzle.md +129 -0
- package/docs/guides/extensions/dialects.md +40 -0
- package/docs/guides/extensions/overview.md +36 -0
- package/docs/guides/extensions/sources-and-clauses.md +82 -0
- package/docs/guides/extensions/typed-expressions.md +94 -0
- package/docs/guides/extensions/unsafe-syntax.md +27 -0
- package/docs/guides/json.md +65 -0
- package/docs/guides/mutations.md +138 -0
- package/docs/guides/select/conditions.md +110 -0
- package/docs/guides/select/grouping-and-windows.md +82 -0
- package/docs/guides/select/ordering-and-pagination.md +78 -0
- package/docs/guides/select/overview.md +139 -0
- package/docs/guides/sql-templates.md +157 -0
- package/docs/guides/vite-plugin.md +92 -0
- package/docs/index.md +111 -0
- package/docs/query-model/fragments.md +109 -0
- package/docs/query-model/result-shapes.md +125 -0
- package/docs/query-model/source-scope.md +182 -0
- package/docs/reference/introspection-support.md +196 -0
- package/docs/reference/mysql-snapshot.md +55 -0
- package/docs/reference/postgres-snapshot.md +61 -0
- package/docs/reference/sqlite-snapshot.md +53 -0
- package/docs/reference/supported-surface.md +104 -0
- package/docs/schema/catalog-model.md +58 -0
- package/docs/schema/code-generation.md +139 -0
- package/docs/schema/columns-and-writes.md +130 -0
- package/docs/schema/constraints-and-indexes.md +180 -0
- package/docs/schema/ddl-emission.md +75 -0
- package/docs/schema/diff.md +75 -0
- package/docs/schema/introspection.md +214 -0
- package/docs/schema/migration-plans.md +76 -0
- package/docs/schema/snapshots.md +93 -0
- package/docs/schema/storage-and-schema-sql.md +104 -0
- package/docs/schema/tables-and-names.md +106 -0
- package/docs/sql-semantic-types.md +141 -0
- package/docs/troubleshooting.md +151 -0
- package/package.json +132 -10
- package/skills/qubu/SKILL.md +42 -0
- package/skills/qubu/agents/openai.yaml +6 -0
- package/readme.md +0 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Supported features
|
|
2
|
+
|
|
3
|
+
> Choose a public package entrypoint, check what Qubu handles, and keep database work on the application side of each adapter.
|
|
4
|
+
|
|
5
|
+
## Package entrypoints
|
|
6
|
+
|
|
7
|
+
| Import | Kind | Use it for |
|
|
8
|
+
| ----------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
9
|
+
| `qubu` | Runtime | Ordinary query and schema definitions, reads, writes, SQL templates, rendering, EXPLAIN, and execution contracts |
|
|
10
|
+
| `qubu/core` | Runtime | Fragment and rendering primitives, dialect construction, SQL types, and extension constructors |
|
|
11
|
+
| `qubu/codegen` | Runtime | Deterministic machine-owned TypeScript schemas from complete, non-lossy introspection |
|
|
12
|
+
| `qubu/ddl` | Runtime | DDL preflight and deterministic PostgreSQL, SQLite, or MySQL emission from a migration plan |
|
|
13
|
+
| `qubu/diff` | Runtime | Canonical Snapshot v1 or v2 comparison, rename hints, suggestions, and safety diagnostics |
|
|
14
|
+
| `qubu/drizzle` | Runtime | Shared Drizzle conversion errors and dialect types |
|
|
15
|
+
| `qubu/introspection` | Runtime | Catalog readers, normalized catalogs, and mapping to Snapshot v1 or v2 |
|
|
16
|
+
| `qubu/migration` | Runtime | Pure migration planning with dependencies, decisions, preconditions, and explicit custom SQL |
|
|
17
|
+
| `qubu/mysql` | Runtime | The MySQL query dialect policy |
|
|
18
|
+
| `qubu/postgres` | Runtime | PostgreSQL query dialect helpers such as `postgresDialect()` and `ilike()` |
|
|
19
|
+
| `qubu/schema` | Runtime | Advanced schema metadata, storage and constraint models, source models, and schema-expression extensions |
|
|
20
|
+
| `qubu/snapshot` | Runtime | Canonical Snapshot v1 and v2 traversal, encoding, decoding, diagnostics, and digests |
|
|
21
|
+
| `qubu/sqlite` | Runtime | The SQLite query dialect policy |
|
|
22
|
+
| `qubu/vite` | Runtime | The optional `qubu()` Vite compiler hint |
|
|
23
|
+
| `qubu/package.json` | JSON | The published package manifest |
|
|
24
|
+
| `qubu/drizzle/mysql` | Runtime | Runtime conversion from Qubu schemas to MySQL Drizzle tables |
|
|
25
|
+
| `qubu/drizzle/postgres` | Runtime | Runtime conversion from Qubu schemas to PostgreSQL Drizzle tables |
|
|
26
|
+
| `qubu/drizzle/sqlite` | Runtime | Runtime conversion from Qubu schemas to SQLite Drizzle tables |
|
|
27
|
+
| `qubu/globals` | TypeScript types | Opt-in ambient declarations for directive-bearing modules |
|
|
28
|
+
|
|
29
|
+
The package validator confirms 17 runtime entrypoints, 18 type entrypoints,
|
|
30
|
+
`qubu/package.json` as JSON, and `qubu/globals` as type-only. Concrete dialect
|
|
31
|
+
constructors live on their database subpaths. The root renderer uses Qubu's
|
|
32
|
+
standard SQL policy by default.
|
|
33
|
+
|
|
34
|
+
Snapshot dialect behavior is documented in the [PostgreSQL](postgres-snapshot.md),
|
|
35
|
+
[SQLite](sqlite-snapshot.md), and [MySQL](mysql-snapshot.md) support matrices.
|
|
36
|
+
|
|
37
|
+
## Canonical query vocabulary
|
|
38
|
+
|
|
39
|
+
Use the root names in new query code: `eq`, `ne`, `lt`, `lte`, `gt`, `gte`,
|
|
40
|
+
`avg`, `min`, `max`, `fetchFirst`, `alias`, `render`, `qubu`, `execute`,
|
|
41
|
+
`deleteFrom`, and `allowAll`. The package does not document competing aliases
|
|
42
|
+
for these operations. Keep advanced fragment, dialect-construction, and
|
|
43
|
+
schema-extension imports on `qubu/core` or `qubu/schema` as shown in the
|
|
44
|
+
entrypoint table.
|
|
45
|
+
|
|
46
|
+
## Capability map
|
|
47
|
+
|
|
48
|
+
| Area | Supported building blocks |
|
|
49
|
+
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
50
|
+
| Schema values | `table`, immutable `schema` registries with namespaces, portable and dialect-native column storage descriptors, canonical default and generated-column metadata, identity descriptors, named primary, candidate-key, nullable unique, foreign-key, and check constraints, physical object names, included-column indexes, typed dialect extensions, and typed column helpers |
|
|
51
|
+
| Read queries | Named projections, spreadable source columns, aliases, joins, typed custom and LATERAL `FROM` sources, correlated subqueries, `WHERE`, grouping with declared-key proofs, `HAVING`, ordering, window expressions, distinctness, pagination, row locking, ordinary and recursive CTEs, subqueries, and set operations |
|
|
52
|
+
| Expressions | Comparison, boolean, arithmetic, null, range, membership, aggregate, window, string, JSON scalar reads, definition-backed and raw casts, cases, parameterized SQL templates, custom expressions, and branded deterministic schema expressions |
|
|
53
|
+
| SQL type metadata | Portable domains and capabilities, physical column storage descriptors, `SqlTypeOf`, projected SQL type maps, `SourceLike` and `TableLike` field constraints, contextual literals, typed extension values, calls, and casts, plus a permissive `SqlUnknown` fallback |
|
|
54
|
+
| Write queries | `INSERT` values, defaults, and selects; `UPDATE`; `DELETE`; typed assignments; `RETURNING`; and explicit unrestricted-write opt-in |
|
|
55
|
+
| Rendering | Standard, PostgreSQL, SQLite, MySQL, and user-created policies for identifiers, placeholders, pagination, row locking, JSON, logical cast targets, schema literals, and EXPLAIN options |
|
|
56
|
+
| Execution boundary | `QueryAdapter`, opt-in `ExplainableQueryAdapter`, `StreamingQueryAdapter`, and `TransactionalQueryAdapter` capabilities, bound clients from `qubu()`, structured results from `execute()` or `db.execute()`, row-only results from `executeRows()` or `db.rows()`, typed read streams from `stream()` or `db.stream()`, and adapter-decoded plan rows from `explain()` or `db.explain()` |
|
|
57
|
+
| Snapshots | Pure Snapshot v1 and v2 creation, canonical encoding and strict decoding, immutable data, diagnostics, and content digests |
|
|
58
|
+
| Introspection | PostgreSQL, SQLite, and MySQL catalog readers for one selected namespace, normalized catalog data, structured diagnostics, and strict or explicit lossy snapshot mapping |
|
|
59
|
+
| Snapshot diffing | Pure Snapshot v1 and v2 comparison, explicit rename evidence, non-authoritative suggestions, and safety diagnostics |
|
|
60
|
+
| Migration planning | Pure, dialect-neutral plans with stable ordering, dependency edges, preconditions, explicit review decisions, and tagged custom SQL |
|
|
61
|
+
| DDL emission | Preflight plus deterministic PostgreSQL, SQLite, and MySQL statements from an approved `MigrationPlan` and matching `SchemaDialect` |
|
|
62
|
+
| Build tooling | The optional Vite directive transform and its matching TypeScript ambient declarations |
|
|
63
|
+
| Drizzle conversion | Optional, dialect-specific runtime conversion from Qubu schema registries to Drizzle tables |
|
|
64
|
+
| Source generation | Pure Snapshot v1 table source printing, deterministic camelCase IDs, exact physical metadata, controlled type mappings, and structured failure diagnostics |
|
|
65
|
+
|
|
66
|
+
## Ownership boundary
|
|
67
|
+
|
|
68
|
+
Snapshot creation, diffing, migration planning, and DDL emission are pure.
|
|
69
|
+
`execute()`, clients, and catalog readers can reach a driver only through
|
|
70
|
+
interfaces the application provides. Qubu can emit DDL, but it never applies
|
|
71
|
+
that DDL to a database.
|
|
72
|
+
|
|
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; renders and passes statements to `QueryAdapter`, `ExplainableQueryAdapter`, or a read-only `StreamingQueryAdapter`; scopes `db.transaction()` callbacks through `TransactionalQueryAdapter`; returns `ExecutionResult`, `ExplainResult`, rows, or an adapter-owned `AsyncIterable` | Owns the adapter, driver, connections, pools, cursors, stream cleanup, transaction begin/commit/rollback, savepoints, retries, parameter encoding, application-row and plan-row decoding, backpressure, cancellation behavior, 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
|
+
|
|
81
|
+
Start with [Dialects and execution](../dialects-and-execution.md) for the query
|
|
82
|
+
adapter contract. The schema path is documented in [Canonical schema
|
|
83
|
+
snapshots](../schema/snapshots.md), [Snapshot diffing](../schema/diff.md),
|
|
84
|
+
[Migration plans](../schema/migration-plans.md), and [DDL
|
|
85
|
+
emission](../schema/ddl-emission.md).
|
|
86
|
+
|
|
87
|
+
## SQL safety boundaries
|
|
88
|
+
|
|
89
|
+
Qubu binds values through the render context and quotes identifiers through the
|
|
90
|
+
active dialect. `UPDATE` and `DELETE` require a `WHERE` clause unless the caller
|
|
91
|
+
passes `allowAll()`.
|
|
92
|
+
|
|
93
|
+
The `sql` tag treats static template text as trusted SQL syntax. Ordinary
|
|
94
|
+
substitutions become parameters, and fragment substitutions compose through the
|
|
95
|
+
active renderer. Dynamic identifiers and syntax remain explicit through
|
|
96
|
+
`identifier()` and unsafe helpers. Those helpers are not sanitizers. Validate
|
|
97
|
+
dynamic syntax against an application-owned allowlist before it reaches an
|
|
98
|
+
unsafe helper.
|
|
99
|
+
|
|
100
|
+
SQL semantic types provide compile-time portable capability and compatibility
|
|
101
|
+
checks. They do not prove the runtime schema state or model every dialect's
|
|
102
|
+
implicit coercions. Custom and untyped extensions default to permissive
|
|
103
|
+
`SqlUnknown`; declare a domain when an extension should participate in stricter
|
|
104
|
+
checks.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Complete catalog model
|
|
2
|
+
|
|
3
|
+
Qubu keeps database discovery in a normalized catalog before producing a
|
|
4
|
+
snapshot. The catalog is a read-only record of observed facts; it does not
|
|
5
|
+
contain a connection, execute catalog SQL, or assign database catalog keys as
|
|
6
|
+
persisted logical IDs.
|
|
7
|
+
|
|
8
|
+
The optional `qubu/introspection` entry point exposes the complete object
|
|
9
|
+
families and an immutable materializer:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import {
|
|
13
|
+
createCompleteIntrospectionCatalog,
|
|
14
|
+
mapCatalogToCompleteSnapshot,
|
|
15
|
+
} from 'qubu/introspection'
|
|
16
|
+
|
|
17
|
+
const completeCatalog = createCompleteIntrospectionCatalog(catalog)
|
|
18
|
+
const result = mapCatalogToCompleteSnapshot(completeCatalog)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Tables, columns, views, materialized views, sequences, enums, domains,
|
|
22
|
+
collations, triggers, routines, partitions, row-level policies, extension
|
|
23
|
+
objects, comments, and ownership metadata have typed records. A reader may
|
|
24
|
+
also retain a deferred or opaque object when it observes a family that Qubu
|
|
25
|
+
cannot yet normalize. Such an object remains visible and can carry opaque
|
|
26
|
+
catalog data, SQL text, provenance, and a dialect extension; it is never
|
|
27
|
+
silently dropped.
|
|
28
|
+
|
|
29
|
+
Physical names and references describe the current database. Stable logical
|
|
30
|
+
IDs are evidence selected by the adapter's identity policy. PostgreSQL OIDs,
|
|
31
|
+
SQLite implementation names, and similar catalog keys stay in current-run
|
|
32
|
+
references and are not used as logical IDs.
|
|
33
|
+
|
|
34
|
+
## Snapshot v2
|
|
35
|
+
|
|
36
|
+
`qubu/snapshot` provides the strict complete format as a separate API:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import {
|
|
40
|
+
decodeCompleteSchemaSnapshot,
|
|
41
|
+
encodeCompleteSchemaSnapshot,
|
|
42
|
+
} from 'qubu/snapshot'
|
|
43
|
+
|
|
44
|
+
const encoded = encodeCompleteSchemaSnapshot(snapshotV2)
|
|
45
|
+
const decoded = decodeCompleteSchemaSnapshot(encoded)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Snapshot v2 uses the same `qubu-schema` envelope with `version: 2`. Its
|
|
49
|
+
namespace, capability facts, object-family arrays, cross-object references,
|
|
50
|
+
provenance, typed dialect extensions, and deferred/opaque boundaries are
|
|
51
|
+
strictly validated. Arrays are ordered by logical ID (with ordinal sequences
|
|
52
|
+
and index terms ordered by their semantic position), and the digest is computed
|
|
53
|
+
from the deterministic encoding.
|
|
54
|
+
|
|
55
|
+
Snapshot v1 remains a separate strict format. `decodeSchemaSnapshot` still
|
|
56
|
+
accepts only v1 and continues to reject unknown fields and future versions;
|
|
57
|
+
v2 callers must select `decodeCompleteSchemaSnapshot` explicitly. Neither
|
|
58
|
+
snapshot format evaluates database-provided SQL.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Generate a schema from introspection
|
|
2
|
+
|
|
3
|
+
> Turn one complete, non-lossy Snapshot v1 introspection result into a deterministic, machine-owned TypeScript schema module.
|
|
4
|
+
|
|
5
|
+
Source generation is an optional capability exported from `qubu/codegen`. It
|
|
6
|
+
is a pure handoff after introspection: it opens no connection, runs no catalog
|
|
7
|
+
query, and writes no file. The caller owns those boundaries.
|
|
8
|
+
|
|
9
|
+
## Generate a module
|
|
10
|
+
|
|
11
|
+
Read and map one namespace in strict mode, then pass that exact result to the
|
|
12
|
+
generator:
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { writeFile } from 'node:fs/promises'
|
|
16
|
+
import { generateSchemaSource } from 'qubu/codegen'
|
|
17
|
+
import { mapCatalogToSnapshot, readSqliteCatalog } from 'qubu/introspection'
|
|
18
|
+
|
|
19
|
+
const catalog = await readSqliteCatalog(connection, { namespace: 'main' })
|
|
20
|
+
const introspection = mapCatalogToSnapshot(catalog, { namespace: 'main' })
|
|
21
|
+
const generated = generateSchemaSource(introspection)
|
|
22
|
+
|
|
23
|
+
if (!generated.ok) {
|
|
24
|
+
throw new Error(generated.diagnostics.map(issue => issue.message).join('\n'))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
await writeFile('src/schema.generated.ts', generated.source, 'utf8')
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`writeFile()` belongs to the application; `generateSchemaSource()` only
|
|
31
|
+
returns data. A successful result contains deterministic `source` and every
|
|
32
|
+
retained diagnostic. A failed result contains diagnostics and no partial
|
|
33
|
+
source.
|
|
34
|
+
|
|
35
|
+
The module exports one declaration for every ordinary Snapshot v1 table and
|
|
36
|
+
one schema registry. It reconstructs physical names, exact native storage,
|
|
37
|
+
column write behavior, defaults, generated and identity metadata, constraints,
|
|
38
|
+
indexes, opaque predicates and expressions, and dialect extensions. Checks use
|
|
39
|
+
`catalogCheck()`. Foreign keys use lazy `catalogForeignKey()` targets so
|
|
40
|
+
forward declarations and cycles remain valid.
|
|
41
|
+
|
|
42
|
+
## Adopt the generated identity baseline
|
|
43
|
+
|
|
44
|
+
The first introspection snapshot commonly uses physical names as logical IDs.
|
|
45
|
+
Generated declarations use deterministic camelCase registry, table, column,
|
|
46
|
+
constraint, and index IDs while retaining every physical database name. Once
|
|
47
|
+
the generated module is accepted, its serialized snapshot becomes the identity
|
|
48
|
+
baseline for the next catalog read:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { mapCatalogToSnapshot } from 'qubu/introspection'
|
|
52
|
+
import { createSqliteSchemaSnapshot } from 'qubu/snapshot'
|
|
53
|
+
import { mainSchema } from './schema.generated.ts'
|
|
54
|
+
|
|
55
|
+
const previousSnapshot = createSqliteSchemaSnapshot(mainSchema)
|
|
56
|
+
const next = mapCatalogToSnapshot(nextCatalog, {
|
|
57
|
+
namespace: 'main',
|
|
58
|
+
previousSnapshot,
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
This handoff is deliberate. Do not keep using the pre-generation snapshot as
|
|
63
|
+
the long-term identity source, or later diffs will compare physical IDs with
|
|
64
|
+
the new generated IDs.
|
|
65
|
+
|
|
66
|
+
## Control names and column types
|
|
67
|
+
|
|
68
|
+
Application output, insert, and update types default independently to
|
|
69
|
+
`unknown`. Qubu may attach a SQL semantic domain only when catalog evidence is
|
|
70
|
+
exact. Native storage always keeps the catalog declaration, even when the
|
|
71
|
+
semantic domain remains `SqlUnknown`.
|
|
72
|
+
|
|
73
|
+
Use the controlled callbacks to adopt trusted names or application mappings:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const generated = generateSchemaSource(introspection, {
|
|
77
|
+
naming(context) {
|
|
78
|
+
if (context.kind === 'table' && context.physicalName === 'user_records') {
|
|
79
|
+
return 'users'
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
mapColumn(context) {
|
|
83
|
+
if (context.columnPhysicalName === 'account_id') {
|
|
84
|
+
return {
|
|
85
|
+
output: 'string',
|
|
86
|
+
insert: 'string',
|
|
87
|
+
update: 'string',
|
|
88
|
+
sqlDomain: 'uuid',
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
})
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Callbacks select names and fixed type tokens; they never take over printing and
|
|
96
|
+
cannot return imports, expressions, comments, or arbitrary source. A returned
|
|
97
|
+
name must still be a safe camelCase ID. Collisions and unsafe names fail with
|
|
98
|
+
diagnostics and no source.
|
|
99
|
+
|
|
100
|
+
## Diagnostics and source safety
|
|
101
|
+
|
|
102
|
+
Generation rejects failed or lossy introspection, an altered snapshot that no
|
|
103
|
+
longer matches its catalog, omitted Snapshot v1 facts, unresolved references,
|
|
104
|
+
unsafe names, invalid mapping tokens, and data that cannot be represented
|
|
105
|
+
without source injection. Existing introspection diagnostics stay attached to
|
|
106
|
+
the result.
|
|
107
|
+
|
|
108
|
+
> [!IMPORTANT]
|
|
109
|
+
> A database can allow a foreign key to reference a nullable `UNIQUE`
|
|
110
|
+
> constraint. Snapshot v1 retains that constraint as nullable uniqueness, not
|
|
111
|
+
> as a Qubu candidate key. Source generation returns an
|
|
112
|
+
> `unrepresentable-fact` diagnostic instead of weakening the generated
|
|
113
|
+
> `references()` proof. Use a non-null primary key, strict unique key, or
|
|
114
|
+
> candidate index as the foreign-key target before adopting generated source.
|
|
115
|
+
|
|
116
|
+
Catalog names, native declarations, SQL, and extension metadata are untrusted
|
|
117
|
+
input. The printer serializes them only as controlled literals beneath a
|
|
118
|
+
static header. It does not interpolate catalog text as code or comments, parse
|
|
119
|
+
opaque SQL, or merge a previous generated file with hand edits. Treat the file
|
|
120
|
+
as replaceable output and keep application customizations in separate modules.
|
|
121
|
+
|
|
122
|
+
The public types and TSDoc on `generateSchemaSource()`,
|
|
123
|
+
`SchemaCodegenOptions`, and `CodegenDiagnostic` define the exact callback and
|
|
124
|
+
result contracts.
|
|
125
|
+
|
|
126
|
+
## Snapshot v1 boundary
|
|
127
|
+
|
|
128
|
+
Generation covers ordinary Snapshot v1 tables in one namespace. Complete
|
|
129
|
+
catalog families outside that model—views, materialized views, sequences,
|
|
130
|
+
enums, domains, routines, triggers, partitions, policies, collations,
|
|
131
|
+
extensions, comments, ownership, and retained opaque or deferred objects—are
|
|
132
|
+
not emitted. Non-empty excluded families produce diagnostics so the generated
|
|
133
|
+
module does not look complete by omission.
|
|
134
|
+
|
|
135
|
+
The entrypoint does not provide a CLI, filesystem ownership, live driver
|
|
136
|
+
integration, multiple namespaces, runtime schema materialization, migrations,
|
|
137
|
+
DDL, Snapshot v2 object generation, or hand-edit merging. Use
|
|
138
|
+
[Database introspection](introspection.md) for the catalog boundary and
|
|
139
|
+
[Canonical schema snapshots](snapshots.md) for the identity artifact.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Column behavior and write types
|
|
2
|
+
|
|
3
|
+
> Separate selected values from insert and update inputs, then record the database rules that make fields optional or generated.
|
|
4
|
+
|
|
5
|
+
## Give each operation its own type
|
|
6
|
+
|
|
7
|
+
Every column has a selected output type. It can also describe what inserts and
|
|
8
|
+
updates accept:
|
|
9
|
+
|
|
10
|
+
| Option | Selected output | Insert input | Update input |
|
|
11
|
+
| ---------------- | --------------- | ----------------- | ----------------- |
|
|
12
|
+
| nullable: true | T or null | accepts T or null | accepts T or null |
|
|
13
|
+
| hasDefault: true | unchanged | key is optional | unchanged |
|
|
14
|
+
| generated: true | unchanged | key is omitted | key is omitted |
|
|
15
|
+
|
|
16
|
+
Use `column<Output, Insert, Update>()` when the driver returns a different type
|
|
17
|
+
from the type the application writes:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { column, integer, table, text } from 'qubu'
|
|
21
|
+
|
|
22
|
+
const accounts = table('accounts', {
|
|
23
|
+
id: integer({ generated: true }),
|
|
24
|
+
email: text(),
|
|
25
|
+
nickname: text({ nullable: true, hasDefault: true }),
|
|
26
|
+
externalScore: column<number, string, number>({ nullable: true }),
|
|
27
|
+
})
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The selected `externalScore` is number | null. Inserts accept string | null, and
|
|
31
|
+
updates accept number | null.
|
|
32
|
+
|
|
33
|
+
## Describe defaults and generated columns
|
|
34
|
+
|
|
35
|
+
The legacy hasDefault and generated flags describe the write contract. Use
|
|
36
|
+
complete metadata when schema tooling also needs the database fact:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import {
|
|
40
|
+
boolean,
|
|
41
|
+
generatedColumn,
|
|
42
|
+
identityColumn,
|
|
43
|
+
integer,
|
|
44
|
+
table,
|
|
45
|
+
text,
|
|
46
|
+
value,
|
|
47
|
+
} from 'qubu'
|
|
48
|
+
import { defineSchemaExpression } from 'qubu/schema'
|
|
49
|
+
|
|
50
|
+
const currentTimestamp = defineSchemaExpression('function', context => {
|
|
51
|
+
context.append('CURRENT_TIMESTAMP')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const accounts = table('accounts', {
|
|
55
|
+
id: integer({ identity: identityColumn('always') }),
|
|
56
|
+
status: text({ default: 'pending' }),
|
|
57
|
+
active: boolean({ default: true }),
|
|
58
|
+
score: integer({
|
|
59
|
+
generatedColumn: generatedColumn(value(1), 'stored'),
|
|
60
|
+
}),
|
|
61
|
+
createdAt: text({
|
|
62
|
+
default: currentTimestamp,
|
|
63
|
+
}),
|
|
64
|
+
})
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Primitive values in `default` are canonical literals. Strings are never
|
|
68
|
+
interpreted as SQL, and booleans remain semantic values so each dialect can
|
|
69
|
+
choose its own spelling. Pass a branded deterministic schema expression
|
|
70
|
+
directly when the default is SQL, and use `unsafeSchemaSql()` only for trusted
|
|
71
|
+
syntax Qubu does not model. Generated expressions record stored or virtual
|
|
72
|
+
mode. An identity descriptor stays separate because identity behavior is not
|
|
73
|
+
an ordinary generated expression.
|
|
74
|
+
|
|
75
|
+
Complete defaults cannot be combined with generated or identity metadata.
|
|
76
|
+
Contradictory flags fail with a structured `ColumnBehaviorError`. Use
|
|
77
|
+
`externalDefault()` or `externalGeneratedColumn()` when another schema authority
|
|
78
|
+
owns the missing detail.
|
|
79
|
+
|
|
80
|
+
Dialect-owned identity details stay on the identity descriptor. SQLite's
|
|
81
|
+
autoIncrement requires an exact INTEGER rowid alias that is the sole column of
|
|
82
|
+
a primary key. MySQL's AUTO_INCREMENT is a column-level identity extension, and
|
|
83
|
+
MySQL's ON UPDATE clause accepts a branded deterministic expression. The
|
|
84
|
+
database-specific restrictions are listed in the
|
|
85
|
+
[snapshot overview](snapshots.md) and its dialect matrices.
|
|
86
|
+
|
|
87
|
+
## Narrow an application type
|
|
88
|
+
|
|
89
|
+
Use `$type<T>()` to narrow a helper's TypeScript type without changing its
|
|
90
|
+
runtime column definition:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { table, text } from 'qubu'
|
|
94
|
+
|
|
95
|
+
const users = table('users', {
|
|
96
|
+
status: text().$type<'active' | 'disabled'>(),
|
|
97
|
+
})
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The narrowed type applies to selected values and insert and update inputs. It
|
|
101
|
+
does not validate values at runtime or add a database constraint.
|
|
102
|
+
|
|
103
|
+
## Derive write input types
|
|
104
|
+
|
|
105
|
+
`TableInsertInput` and `TableUpdateInput` expose the same rules to application
|
|
106
|
+
code. The following example uses the accounts table from the earlier example:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import type { TableInsertInput, TableUpdateInput } from 'qubu'
|
|
110
|
+
|
|
111
|
+
type AccountInsert = TableInsertInput<typeof accounts.definitions>
|
|
112
|
+
type AccountUpdate = TableUpdateInput<typeof accounts.definitions>
|
|
113
|
+
|
|
114
|
+
const insert: AccountInsert = {
|
|
115
|
+
email: 'ada@example.com',
|
|
116
|
+
externalScore: '10',
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const update: AccountUpdate = {
|
|
120
|
+
nickname: null,
|
|
121
|
+
externalScore: 10,
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`id` is not accepted because it is generated. `nickname` is optional on insert
|
|
126
|
+
because the database supplies a default, but it remains a valid nullable update
|
|
127
|
+
field.
|
|
128
|
+
|
|
129
|
+
Continue with [Write mutations](../guides/mutations.md) for typed INSERT,
|
|
130
|
+
UPDATE, and DELETE statements.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Constraints, keys, and indexes
|
|
2
|
+
|
|
3
|
+
> Record database constraints and indexes when Qubu needs them to check foreign keys or grouped query projections.
|
|
4
|
+
|
|
5
|
+
## Declare constraints and indexes
|
|
6
|
+
|
|
7
|
+
Pass a metadata callback to `table()` when the application schema knows which
|
|
8
|
+
rules the database enforces:
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import {
|
|
12
|
+
check,
|
|
13
|
+
eq,
|
|
14
|
+
foreignKey,
|
|
15
|
+
index,
|
|
16
|
+
integer,
|
|
17
|
+
primaryKey,
|
|
18
|
+
references,
|
|
19
|
+
table,
|
|
20
|
+
text,
|
|
21
|
+
unique,
|
|
22
|
+
value,
|
|
23
|
+
} from 'qubu'
|
|
24
|
+
|
|
25
|
+
const accounts = table(
|
|
26
|
+
'accounts',
|
|
27
|
+
{ id: integer(), slug: text() },
|
|
28
|
+
accounts => ({
|
|
29
|
+
constraints: {
|
|
30
|
+
accountsPrimary: primaryKey(accounts.id),
|
|
31
|
+
},
|
|
32
|
+
indexes: {
|
|
33
|
+
accountsSlug: index([accounts.slug], { unique: true }),
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
const memberships = table(
|
|
39
|
+
'memberships',
|
|
40
|
+
{
|
|
41
|
+
id: integer(),
|
|
42
|
+
accountId: integer(),
|
|
43
|
+
slug: text(),
|
|
44
|
+
displayName: text(),
|
|
45
|
+
},
|
|
46
|
+
memberships => ({
|
|
47
|
+
constraints: {
|
|
48
|
+
membershipsPrimary: primaryKey(memberships.id),
|
|
49
|
+
membershipsUnique: unique(memberships.accountId, memberships.slug),
|
|
50
|
+
membershipsAccount: foreignKey(
|
|
51
|
+
[memberships.accountId],
|
|
52
|
+
references(accounts, accounts.id)
|
|
53
|
+
),
|
|
54
|
+
membershipsCheck: check(eq(memberships.slug, value('public'))),
|
|
55
|
+
},
|
|
56
|
+
indexes: {
|
|
57
|
+
membershipsAccountSlug: index([memberships.accountId, memberships.slug], {
|
|
58
|
+
unique: true,
|
|
59
|
+
}),
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The callback receives the preliminary table, including its typed columns. Give
|
|
66
|
+
each item a stable application name in the constraints or indexes record. The
|
|
67
|
+
record key becomes `constraint.id` or `index.id`. Qubu resolves `physicalName` from
|
|
68
|
+
the explicit option or the version-one snake_case policy.
|
|
69
|
+
|
|
70
|
+
Index terms may use `asc()` or `desc()`. Set `unique: true` for a unique index,
|
|
71
|
+
`where` for a partial index, and `include` for columns stored in the index payload but not
|
|
72
|
+
used as key terms.
|
|
73
|
+
|
|
74
|
+
## Distinguish candidate keys from unique constraints
|
|
75
|
+
|
|
76
|
+
`unique()` describes a non-null candidate key. Qubu can use it to prove that a
|
|
77
|
+
grouped key determines other columns. Every key column must belong to the
|
|
78
|
+
callback table and be non-nullable.
|
|
79
|
+
|
|
80
|
+
Use `uniqueConstraint()` when the database enforces uniqueness but the rule should
|
|
81
|
+
not prove a functional dependency:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { table, text, uniqueConstraint } from 'qubu'
|
|
85
|
+
|
|
86
|
+
const accounts = table(
|
|
87
|
+
'accounts',
|
|
88
|
+
{ email: text({ nullable: true }) },
|
|
89
|
+
accounts => ({
|
|
90
|
+
constraints: {
|
|
91
|
+
emailUnique: uniqueConstraint(accounts.email, {
|
|
92
|
+
nulls: 'distinct',
|
|
93
|
+
physicalName: 'accounts_email_key',
|
|
94
|
+
}),
|
|
95
|
+
},
|
|
96
|
+
indexes: {},
|
|
97
|
+
})
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`nulls: 'distinct'` describes the common rule where multiple NULLs do not
|
|
102
|
+
conflict. `'not-distinct'` describes a rule where NULL participates in the
|
|
103
|
+
comparison. Neither form proves that a grouped column determines the rest of a
|
|
104
|
+
row.
|
|
105
|
+
|
|
106
|
+
## Add foreign keys
|
|
107
|
+
|
|
108
|
+
`foreignKey(localColumns, target, options)` accepts single or composite tuples.
|
|
109
|
+
Build the target with `references(table, ...columns)`. The tuples must have the
|
|
110
|
+
same length and matching known `SqlSemanticType` identities. `SqlUnknown` cannot
|
|
111
|
+
prove a foreign-key match.
|
|
112
|
+
|
|
113
|
+
The target tuple must exactly match a primary key, unique() constraint, or
|
|
114
|
+
eligible unique index. Options such as onUpdate, onDelete, match, deferrable,
|
|
115
|
+
and initially remain metadata:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const memberships = table(
|
|
119
|
+
'memberships',
|
|
120
|
+
{ accountId: integer() },
|
|
121
|
+
memberships => ({
|
|
122
|
+
constraints: {
|
|
123
|
+
accountForeign: foreignKey(
|
|
124
|
+
[memberships.accountId],
|
|
125
|
+
references(accounts, accounts.id),
|
|
126
|
+
{ onDelete: 'cascade', onUpdate: 'cascade' }
|
|
127
|
+
),
|
|
128
|
+
},
|
|
129
|
+
indexes: {},
|
|
130
|
+
})
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Use the preliminary callback table for direct self-references. Wrap the target
|
|
135
|
+
in a function when two modules import each other's tables.
|
|
136
|
+
|
|
137
|
+
Checks, index expressions, and partial-index predicates may read only columns
|
|
138
|
+
from their callback table. They cannot contain aggregates, window functions, or
|
|
139
|
+
subqueries. Check expressions and partial predicates must have the boolean SQL
|
|
140
|
+
domain.
|
|
141
|
+
|
|
142
|
+
## Use key metadata for grouped queries
|
|
143
|
+
|
|
144
|
+
Grouping every column in a declared candidate key lets Qubu select other
|
|
145
|
+
columns from that source:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { count, from, groupBy, select } from 'qubu'
|
|
149
|
+
|
|
150
|
+
const summary = select(
|
|
151
|
+
{ displayName: memberships.displayName, total: count() },
|
|
152
|
+
from(memberships),
|
|
153
|
+
groupBy(memberships.accountId, memberships.slug)
|
|
154
|
+
)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
A primary key, `unique()` constraint, or eligible unique index supplies the
|
|
158
|
+
proof. A partial index, expression index, nullable key, or included column does
|
|
159
|
+
not silently become candidate-key evidence.
|
|
160
|
+
|
|
161
|
+
The proof follows a table alias and remains source-local through a join,
|
|
162
|
+
including a source made nullable by `leftJoin()`. It does not cross a derived
|
|
163
|
+
query, CTE, LATERAL query, or custom-source boundary. Those relations need an
|
|
164
|
+
explicit proof on their own source model.
|
|
165
|
+
|
|
166
|
+
## Store dialect-specific metadata
|
|
167
|
+
|
|
168
|
+
Index methods, operator classes, concurrency, storage parameters, and
|
|
169
|
+
MySQL-specific algorithm or locking settings belong in the typed dialect
|
|
170
|
+
extension on an index. Constraint extensions follow the same pattern. A
|
|
171
|
+
dialect validator reports an error when the selected engine does not support an
|
|
172
|
+
extension.
|
|
173
|
+
|
|
174
|
+
## Read next
|
|
175
|
+
|
|
176
|
+
- [Column behavior and write types](columns-and-writes.md) covers metadata
|
|
177
|
+
attached to individual columns.
|
|
178
|
+
- [Storage and schema SQL](storage-and-schema-sql.md) covers physical storage
|
|
179
|
+
and deterministic expressions.
|
|
180
|
+
- [SQL semantic types](../sql-semantic-types.md) explains type compatibility.
|