qubu 0.5.1 → 0.6.1
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/README.md +112 -0
- package/dist/codegen.d.mts +1 -1
- package/dist/codegen.mjs +1 -1
- package/dist/column-BzN8KFJa.mjs +364 -0
- package/dist/column-CFvSbil0.mjs +309 -0
- package/dist/{complete-types-CY0KbzNw.d.mts → complete-types-CNMWBWap.d.mts} +1 -1
- package/dist/constraints-DM_tarXc.mjs +208 -0
- package/dist/core.d.mts +1 -1
- package/dist/core.mjs +2 -3
- package/dist/diagnostics-I9vVtXkc.mjs +40 -0
- package/dist/diff.d.mts +2 -2
- package/dist/expressions-BCjc08zw.mjs +129 -0
- package/dist/index-CGui70hi.d.mts +32 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +58 -13
- package/dist/introspection/mysql.d.mts +26 -0
- package/dist/introspection/mysql.mjs +1145 -0
- package/dist/introspection/postgres.d.mts +40 -0
- package/dist/introspection/postgres.mjs +1554 -0
- package/dist/introspection/sqlite.d.mts +15 -0
- package/dist/introspection/sqlite.mjs +986 -0
- package/dist/introspection.d.mts +3 -78
- package/dist/introspection.mjs +5 -3683
- package/dist/mysql.d.mts +1 -1
- package/dist/mysql.mjs +2 -2
- package/dist/{on-conflict-jPsl9l0K.mjs → on-conflict-CnaY5qso.mjs} +76 -3
- package/dist/postgres-Dey7QXPL.mjs +69 -0
- package/dist/postgres.d.mts +3 -3
- package/dist/postgres.mjs +3 -52
- package/dist/registry-oWDiqD7i.mjs +127 -0
- package/dist/{relational-x3BDVX9e.mjs → relational-DSAJ-l58.mjs} +1 -2
- package/dist/schema.d.mts +1 -1
- package/dist/schema.mjs +7 -4
- package/dist/{sqlite-CsIUtZ2Q.mjs → serialize-CE-gw5_s.mjs} +4 -318
- package/dist/serialize-OvXCLzjm.d.mts +66 -0
- package/dist/snapshot/mysql.d.mts +17 -0
- package/dist/snapshot/mysql.mjs +356 -0
- package/dist/snapshot/postgres.d.mts +17 -0
- package/dist/snapshot/postgres.mjs +237 -0
- package/dist/snapshot/sqlite.d.mts +25 -0
- package/dist/snapshot/sqlite.mjs +321 -0
- package/dist/{snapshot-BSraiLtH.mjs → snapshot-DgsOhf_8.mjs} +4 -42
- package/dist/snapshot.d.mts +5 -4
- package/dist/snapshot.mjs +2 -583
- package/dist/{source-C4Vmu5bb.mjs → source-BDuUXmAk.mjs} +2 -1
- package/dist/sqlite.d.mts +1 -1
- package/dist/sqlite.mjs +6 -6
- package/dist/{table-DLQ7YWth.mjs → table-C1QGNe4P.mjs} +3 -2
- package/dist/{types-CTCqtFlS.d.mts → types-BEn0N_al.d.mts} +1 -1
- package/dist/{types-CTENnDh9.mjs → types-BLNRatG_.mjs} +2 -3
- package/dist/{types-C0VkiwpR.d.mts → types-DUe6eeI0.d.mts} +57 -28
- package/docs/guides/compose-queries.md +22 -0
- package/docs/guides/drizzle.md +11 -11
- package/docs/guides/mutations.md +89 -0
- package/docs/guides/valtio-sync.md +113 -0
- package/docs/migrations/index.md +57 -19
- package/docs/migrations/operations.md +20 -6
- package/docs/reference/mysql-snapshot.md +5 -5
- package/docs/reference/postgres-snapshot.md +7 -7
- package/docs/reference/sqlite-snapshot.md +5 -5
- package/docs/reference/supported-surface.md +20 -6
- package/docs/schema/code-generation.md +5 -4
- package/docs/schema/ddl-emission.md +1 -1
- package/docs/schema/introspection.md +3 -2
- package/docs/schema/snapshots.md +12 -0
- package/docs/sql-semantic-types.md +8 -0
- package/package.json +25 -1
- package/dist/column-DmazTL67.mjs +0 -633
- package/dist/index-C-480HmV.d.mts +0 -146
- package/dist/registry-BGqa05et.mjs +0 -461
- package/dist/standard-DfcZEVOj.mjs +0 -12
- package/dist/value-BEEj_Ayd.mjs +0 -29
package/README.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Qubu
|
|
2
|
+
|
|
3
|
+
> Build parameterized SQL from simple type declarations and composable values.
|
|
4
|
+
|
|
5
|
+
Qubu is a functional-first, type-aware SQL builder for TypeScript. Define
|
|
6
|
+
tables once, combine expressions and clauses as ordinary values, and inspect
|
|
7
|
+
the exact SQL and parameters before execution.
|
|
8
|
+
|
|
9
|
+
Qubu stays close to SQL rather than replacing it with an object model. The type
|
|
10
|
+
system tracks the facts needed to compose a query safely, while the rendered
|
|
11
|
+
statement remains recognizable to anyone who knows SQL.
|
|
12
|
+
|
|
13
|
+
> [!IMPORTANT]
|
|
14
|
+
> Qubu is pre-alpha. Its public APIs and package structure may change between
|
|
15
|
+
> releases.
|
|
16
|
+
|
|
17
|
+
## A small query
|
|
18
|
+
|
|
19
|
+
Declare a table, build a reusable condition, and pass it into a query:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { eq, from, integer, render, select, table, text, where } from "qubu"
|
|
23
|
+
|
|
24
|
+
const users = table("users", {
|
|
25
|
+
id: integer(),
|
|
26
|
+
name: text(),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const byId = where(eq(users.id, 7))
|
|
30
|
+
|
|
31
|
+
const query = select(
|
|
32
|
+
{
|
|
33
|
+
id: users.id,
|
|
34
|
+
name: users.name,
|
|
35
|
+
},
|
|
36
|
+
from(users),
|
|
37
|
+
byId,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
type UserRow = typeof query.row
|
|
41
|
+
// { id: number; name: string }
|
|
42
|
+
|
|
43
|
+
render(query)
|
|
44
|
+
// {
|
|
45
|
+
// text: 'SELECT "users"."id" AS "id", "users"."name" AS "name" FROM "users" WHERE ("users"."id" = ?)',
|
|
46
|
+
// parameters: [7],
|
|
47
|
+
// }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The table declaration supplies the application types, the condition remains a
|
|
51
|
+
value that can be reused, and `7` becomes a bound parameter instead of SQL
|
|
52
|
+
text. The projection determines the inferred result row.
|
|
53
|
+
|
|
54
|
+
Clauses are independent values. `select()` accepts them in any argument order
|
|
55
|
+
and renders them in canonical SQL order. Writing the final call in SQL order is
|
|
56
|
+
still the preferred style because it is easier to scan.
|
|
57
|
+
|
|
58
|
+
## Designed to be understood
|
|
59
|
+
|
|
60
|
+
Qubu favors small declarations and functions that return composable values. A
|
|
61
|
+
query can be assembled or changed one piece at a time, and TypeScript reports
|
|
62
|
+
when those pieces do not fit. Rendering provides the SQL and parameters needed
|
|
63
|
+
to check the result directly.
|
|
64
|
+
|
|
65
|
+
These properties also help coding agents. A table declaration gives an agent a
|
|
66
|
+
compact description of the data, composable values keep changes local, and the
|
|
67
|
+
type checker and rendered SQL provide concrete feedback. That reduces guessing
|
|
68
|
+
and makes the resulting work easier for a person to review.
|
|
69
|
+
|
|
70
|
+
Qubu ships [version-matched documentation](docs/index.md) and a
|
|
71
|
+
[Qubu skill](skills/qubu/SKILL.md) that routes agent tasks to the relevant
|
|
72
|
+
guide for the installed package.
|
|
73
|
+
|
|
74
|
+
## Scope and boundaries
|
|
75
|
+
|
|
76
|
+
Qubu is for developers who know SQL and want composition and type checking
|
|
77
|
+
without adopting an ORM model. It does not provide relationship loading,
|
|
78
|
+
identity maps, lazy loading, or change tracking.
|
|
79
|
+
|
|
80
|
+
Qubu constructs and renders queries, and it can pass them through an
|
|
81
|
+
application-supplied adapter. The application continues to own its database
|
|
82
|
+
driver, connections, and database lifecycle. PostgreSQL, SQLite, and MySQL
|
|
83
|
+
differences remain visible through explicit dialect entrypoints.
|
|
84
|
+
|
|
85
|
+
Optional entrypoints and workspace packages cover schema introspection, source
|
|
86
|
+
generation, snapshots, diffs, migration planning, DDL emission, and migration
|
|
87
|
+
operations. These stages remain separate so applications can inspect and
|
|
88
|
+
approve a change before executing it. See [Supported features](docs/reference/supported-surface.md)
|
|
89
|
+
for the complete package and ownership map.
|
|
90
|
+
|
|
91
|
+
## Start here
|
|
92
|
+
|
|
93
|
+
Install Qubu in a TypeScript project:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pnpm add qubu
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Continue with the documentation for the task at hand:
|
|
100
|
+
|
|
101
|
+
- [Getting started](docs/getting-started.md) defines a table and renders the
|
|
102
|
+
first query.
|
|
103
|
+
- [Build a `SELECT`](docs/guides/select/overview.md) covers projections,
|
|
104
|
+
predicates, joins, grouping, and pagination.
|
|
105
|
+
- [Write mutations](docs/guides/mutations.md) covers typed `INSERT`, `UPDATE`,
|
|
106
|
+
and `DELETE` statements.
|
|
107
|
+
- [Dialects and execution](docs/dialects-and-execution.md) explains rendering
|
|
108
|
+
policies and the application-owned adapter boundary.
|
|
109
|
+
- [Schema and migration documentation](docs/schema/tables-and-names.md) starts
|
|
110
|
+
with table metadata and links through snapshots, diffs, plans, and DDL.
|
|
111
|
+
- [Troubleshooting](docs/troubleshooting.md) starts from common query failures
|
|
112
|
+
and their repair paths.
|
package/dist/codegen.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { St as IntrospectionDiagnostic, dt as IntrospectionResult, i as CatalogClassificationConfidence, p as CatalogDialect, z as CatalogPortableStorageType } from "./types-
|
|
1
|
+
import { St as IntrospectionDiagnostic, dt as IntrospectionResult, i as CatalogClassificationConfidence, p as CatalogDialect, z as CatalogPortableStorageType } from "./types-BEn0N_al.mjs";
|
|
2
2
|
//#region src/codegen/types.d.ts
|
|
3
3
|
/** TypeScript value types that the controlled source printer can emit. */
|
|
4
4
|
type CodegenApplicationType = "unknown" | "string" | "number" | "boolean" | "bigint" | "Date" | "Uint8Array";
|
package/dist/codegen.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { l as decodeSchemaSnapshot } from "./canonical-DMvR9yBe.mjs";
|
|
2
|
-
import { t as mapCatalogToSnapshot } from "./snapshot-
|
|
2
|
+
import { t as mapCatalogToSnapshot } from "./snapshot-DgsOhf_8.mjs";
|
|
3
3
|
//#region src/codegen/source.ts
|
|
4
4
|
const header = `/* Generated by Qubu. Do not edit this machine-owned file. */
|
|
5
5
|
import * as _qubu from 'qubu'
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { a as assertDialectCapability, i as standardJson, o as createDialect } from "./json-Db7XRD91.mjs";
|
|
2
|
+
//#region src/dialects/standard.ts
|
|
3
|
+
/** SQL:2008-style rendering defaults used by the core builder. */
|
|
4
|
+
function standardDialect() {
|
|
5
|
+
return createDialect({
|
|
6
|
+
name: "standard-sql",
|
|
7
|
+
placeholder: () => "?",
|
|
8
|
+
json: standardJson
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/result.ts
|
|
13
|
+
const resultValueMetadata = Symbol("qubu.result-value-metadata");
|
|
14
|
+
function resultValue(type, decoder) {
|
|
15
|
+
return type === void 0 && decoder === void 0 ? void 0 : Object.freeze({
|
|
16
|
+
...type === void 0 ? {} : { type },
|
|
17
|
+
...decoder === void 0 ? {} : { decoder }
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function resultValueOf(value) {
|
|
21
|
+
if (typeof value !== "object" || value === null) return;
|
|
22
|
+
return value[resultValueMetadata];
|
|
23
|
+
}
|
|
24
|
+
function attachResultValue(value, metadata) {
|
|
25
|
+
if (metadata === void 0) return value;
|
|
26
|
+
return Object.freeze({
|
|
27
|
+
...value,
|
|
28
|
+
[resultValueMetadata]: metadata
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function createResultShape(fields) {
|
|
32
|
+
return Object.freeze({ fields: Object.freeze(fields.map((field) => Object.freeze({ ...field }))) });
|
|
33
|
+
}
|
|
34
|
+
function resultShapeValue(shape, name) {
|
|
35
|
+
const field = shape.fields.find((candidate) => candidate.name === name);
|
|
36
|
+
return field === void 0 ? void 0 : resultValue(field.type, field.decoder);
|
|
37
|
+
}
|
|
38
|
+
/** Decode SQLite/MySQL-style boolean values while accepting native booleans. */
|
|
39
|
+
const booleanResultDecoder = (value) => {
|
|
40
|
+
if (typeof value === "boolean") return value;
|
|
41
|
+
if (value === 0 || value === 0n) return false;
|
|
42
|
+
if (value === 1 || value === 1n) return true;
|
|
43
|
+
throw new TypeError("Expected a boolean or numeric 0/1 result");
|
|
44
|
+
};
|
|
45
|
+
/** Decode a SQL DATE string as a JavaScript Date at UTC midnight. */
|
|
46
|
+
const dateResultDecoder = (value) => {
|
|
47
|
+
if (value instanceof Date && !Number.isNaN(value.getTime())) return value;
|
|
48
|
+
if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/u.test(value)) throw new TypeError("Expected a valid Date or YYYY-MM-DD result");
|
|
49
|
+
const decoded = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
|
|
50
|
+
if (Number.isNaN(decoded.getTime()) || decoded.toISOString().slice(0, 10) !== value) throw new TypeError("Expected a valid calendar date result");
|
|
51
|
+
return decoded;
|
|
52
|
+
};
|
|
53
|
+
/** Decode a SQL/ISO timestamp string while accepting driver-created Dates. */
|
|
54
|
+
const timestampResultDecoder = (value) => {
|
|
55
|
+
if (value instanceof Date && !Number.isNaN(value.getTime())) return value;
|
|
56
|
+
if (typeof value !== "string") throw new TypeError("Expected a valid Date or timestamp string result");
|
|
57
|
+
const normalized = value.includes("T") ? value : value.replace(" ", "T");
|
|
58
|
+
const decoded = new Date(/(?:Z|[+-]\d{2}:?\d{2})$/u.test(normalized) ? normalized : `${normalized}Z`);
|
|
59
|
+
if (Number.isNaN(decoded.getTime())) throw new TypeError("Expected a valid timestamp result");
|
|
60
|
+
return decoded;
|
|
61
|
+
};
|
|
62
|
+
/** Decode a serialized JSON result. Use only when the adapter returns JSON text. */
|
|
63
|
+
const jsonTextResultDecoder = (value) => {
|
|
64
|
+
if (typeof value !== "string") return value;
|
|
65
|
+
return JSON.parse(value);
|
|
66
|
+
};
|
|
67
|
+
/** Error raised when a projected driver value cannot be decoded. */
|
|
68
|
+
var ResultDecodingError = class extends TypeError {
|
|
69
|
+
name = "ResultDecodingError";
|
|
70
|
+
field;
|
|
71
|
+
rowIndex;
|
|
72
|
+
resultType;
|
|
73
|
+
constructor(field, rowIndex, resultType) {
|
|
74
|
+
super(`Could not decode result field "${field}" in row ${rowIndex}${resultType === void 0 ? "" : ` as ${resultType}`}`);
|
|
75
|
+
this.field = field;
|
|
76
|
+
this.rowIndex = rowIndex;
|
|
77
|
+
this.resultType = resultType;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
function decodeResultRow(row, shape, decoders, dialect, rowIndex) {
|
|
81
|
+
let decoded;
|
|
82
|
+
for (const field of shape.fields) {
|
|
83
|
+
const decoder = field.decoder ?? (field.type ? decoders?.[field.type] : void 0);
|
|
84
|
+
if (decoder === void 0 || row[field.name] === null) continue;
|
|
85
|
+
try {
|
|
86
|
+
decoded ??= { ...row };
|
|
87
|
+
decoded[field.name] = decoder(row[field.name], {
|
|
88
|
+
dialect,
|
|
89
|
+
field: field.name,
|
|
90
|
+
rowIndex
|
|
91
|
+
});
|
|
92
|
+
} catch {
|
|
93
|
+
throw new ResultDecodingError(field.name, rowIndex, field.type);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return decoded ?? row;
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/core/fragment.ts
|
|
100
|
+
function fragment(render) {
|
|
101
|
+
return Object.freeze({ render });
|
|
102
|
+
}
|
|
103
|
+
function sequence(parts, separator = " ") {
|
|
104
|
+
return fragment((context) => {
|
|
105
|
+
let first = true;
|
|
106
|
+
for (const part of parts) {
|
|
107
|
+
if (!first) context.append(separator);
|
|
108
|
+
context.render(part);
|
|
109
|
+
first = false;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function parenthesize(part) {
|
|
114
|
+
return fragment((context) => {
|
|
115
|
+
context.append("(");
|
|
116
|
+
context.render(part);
|
|
117
|
+
context.append(")");
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function isFragment(value) {
|
|
121
|
+
return typeof value === "object" && value !== null && "render" in value && typeof value.render === "function";
|
|
122
|
+
}
|
|
123
|
+
//#endregion
|
|
124
|
+
//#region src/expressions/types.ts
|
|
125
|
+
/** Runtime/type-level proof that an expression is safe to use in schema SQL. */
|
|
126
|
+
const schemaExpressionBrand = Symbol("qubu.schema-expression");
|
|
127
|
+
/** Add a concrete dialect requirement without dropping expression metadata. */
|
|
128
|
+
function withDialectCapability(expression, capability) {
|
|
129
|
+
const wrapped = makeExpression(expression.expressionKind, (context) => {
|
|
130
|
+
assertDialectCapability(context.dialect, capability);
|
|
131
|
+
context.render(expression);
|
|
132
|
+
}, expression.expressionCategory, resultValueOf(expression));
|
|
133
|
+
return isSchemaExpression(expression) ? markSchemaExpression(wrapped) : wrapped;
|
|
134
|
+
}
|
|
135
|
+
function makeExpression(expressionKind, render, expressionCategory, result) {
|
|
136
|
+
return attachResultValue(Object.freeze({
|
|
137
|
+
expressionKind,
|
|
138
|
+
...expressionCategory ? { expressionCategory } : {},
|
|
139
|
+
...fragment(render)
|
|
140
|
+
}), result);
|
|
141
|
+
}
|
|
142
|
+
/** Mark a query-only expression category without changing its SQL renderer. */
|
|
143
|
+
function markExpressionCategory(expression, category) {
|
|
144
|
+
return Object.freeze({
|
|
145
|
+
...expression,
|
|
146
|
+
expressionCategory: category
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Mark a built-in or explicitly audited renderer as schema-deterministic. Prefer
|
|
151
|
+
* {@link defineSchemaExpression} for application extensions because it supplies a restricted schema
|
|
152
|
+
* rendering context.
|
|
153
|
+
*/
|
|
154
|
+
function makeSchemaExpression(expressionKind, render, result) {
|
|
155
|
+
const expression = makeExpression(expressionKind, render, void 0, result);
|
|
156
|
+
return Object.freeze({
|
|
157
|
+
...expression,
|
|
158
|
+
[schemaExpressionBrand]: true
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/** Add the schema-determinism brand to an explicitly audited expression. */
|
|
162
|
+
function markSchemaExpression(expression) {
|
|
163
|
+
if (isSchemaExpression(expression)) return expression;
|
|
164
|
+
return Object.freeze({
|
|
165
|
+
...expression,
|
|
166
|
+
[schemaExpressionBrand]: true
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
/** Test the runtime brand used by schema rendering and validation. */
|
|
170
|
+
function isSchemaExpression(value) {
|
|
171
|
+
return typeof value === "object" && value !== null && schemaExpressionBrand in value && value[schemaExpressionBrand] === true;
|
|
172
|
+
}
|
|
173
|
+
function isExpression(value) {
|
|
174
|
+
return typeof value === "object" && value !== null && "expressionKind" in value && "render" in value && typeof value.render === "function";
|
|
175
|
+
}
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region src/core/naming.ts
|
|
178
|
+
/** Convert an application-facing field name to a SQL-facing identifier. */
|
|
179
|
+
function snakeCaseIdentifier(name) {
|
|
180
|
+
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();
|
|
181
|
+
}
|
|
182
|
+
function resolveSqlNames(fields) {
|
|
183
|
+
const sqlNames = {};
|
|
184
|
+
const fieldsBySqlName = /* @__PURE__ */ new Map();
|
|
185
|
+
for (const field of fields) {
|
|
186
|
+
const sqlName = field.sqlName ?? snakeCaseIdentifier(field.fieldName);
|
|
187
|
+
if (sqlName.length === 0) throw new Error(`SQL name for field "${field.fieldName}" cannot be empty`);
|
|
188
|
+
const existingField = fieldsBySqlName.get(sqlName);
|
|
189
|
+
if (existingField) throw new Error(`Fields "${existingField}" and "${field.fieldName}" both resolve to SQL name "${sqlName}"`);
|
|
190
|
+
fieldsBySqlName.set(sqlName, field.fieldName);
|
|
191
|
+
sqlNames[field.fieldName] = sqlName;
|
|
192
|
+
}
|
|
193
|
+
return Object.freeze(sqlNames);
|
|
194
|
+
}
|
|
195
|
+
//#endregion
|
|
196
|
+
//#region src/schema/metadata.ts
|
|
197
|
+
/** Error raised when relational metadata cannot be represented safely. */
|
|
198
|
+
var SchemaMetadataValidationError = class extends TypeError {
|
|
199
|
+
name = "SchemaMetadataValidationError";
|
|
200
|
+
diagnostics;
|
|
201
|
+
issues;
|
|
202
|
+
constructor(diagnostics) {
|
|
203
|
+
const frozenDiagnostics = Object.freeze(diagnostics.map((diagnostic) => Object.freeze({
|
|
204
|
+
...diagnostic,
|
|
205
|
+
path: Object.freeze([...diagnostic.path]),
|
|
206
|
+
relatedPaths: diagnostic.relatedPaths ? Object.freeze(diagnostic.relatedPaths.map((path) => Object.freeze([...path]))) : void 0
|
|
207
|
+
})));
|
|
208
|
+
super(frozenDiagnostics.map((diagnostic) => diagnostic.message).join("\n"));
|
|
209
|
+
this.diagnostics = frozenDiagnostics;
|
|
210
|
+
this.issues = frozenDiagnostics;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
/** Generate the version-one physical name for a relational object ID. */
|
|
214
|
+
function generatedSchemaObjectName(id) {
|
|
215
|
+
return snakeCaseIdentifier(id);
|
|
216
|
+
}
|
|
217
|
+
/** Deep-freeze plain metadata values without introducing executable nodes. */
|
|
218
|
+
function freezeSchemaMetadata(value) {
|
|
219
|
+
if (Array.isArray(value)) return Object.freeze(value.map((item) => freezeSchemaMetadata(item)));
|
|
220
|
+
if (typeof value !== "object" || value === null) return value;
|
|
221
|
+
const frozen = Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, freezeSchemaMetadata(nested)]));
|
|
222
|
+
return Object.freeze(frozen);
|
|
223
|
+
}
|
|
224
|
+
/** Check the portable identifier subset used for generated metadata names. */
|
|
225
|
+
function isValidSchemaObjectName(name) {
|
|
226
|
+
return name.length > 0 && name === name.trim() && !/[.\\/\u0000-\u001f\u007f"']/u.test(name);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Attach a record key and resolved physical name without changing the enumerable shape of legacy
|
|
230
|
+
* constraint/index values. The properties are still ordinary read-only runtime metadata and are
|
|
231
|
+
* available to snapshot traversals.
|
|
232
|
+
*/
|
|
233
|
+
function materializeSchemaObjectIdentity(value, id, physicalName) {
|
|
234
|
+
const materialized = { ...value };
|
|
235
|
+
Object.defineProperties(materialized, {
|
|
236
|
+
id: {
|
|
237
|
+
configurable: false,
|
|
238
|
+
enumerable: false,
|
|
239
|
+
value: id,
|
|
240
|
+
writable: false
|
|
241
|
+
},
|
|
242
|
+
physicalName: {
|
|
243
|
+
configurable: false,
|
|
244
|
+
enumerable: false,
|
|
245
|
+
value: physicalName ?? generatedSchemaObjectName(id),
|
|
246
|
+
writable: false
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
return Object.freeze(materialized);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Resolve and validate names for one metadata record. The caller decides whether constraint and
|
|
253
|
+
* index names share a database namespace.
|
|
254
|
+
*/
|
|
255
|
+
function materializeSchemaObjectRecord(record, kind) {
|
|
256
|
+
const diagnostics = [];
|
|
257
|
+
const names = /* @__PURE__ */ new Map();
|
|
258
|
+
const result = {};
|
|
259
|
+
for (const [id, value] of Object.entries(record)) {
|
|
260
|
+
const physicalName = value.physicalName ?? generatedSchemaObjectName(id);
|
|
261
|
+
const path = [kind === "constraint" ? "constraints" : "indexes", id];
|
|
262
|
+
if (!isValidSchemaObjectName(physicalName)) diagnostics.push({
|
|
263
|
+
code: "invalid-physical-name",
|
|
264
|
+
message: `The ${kind} "${id}" has invalid physical name "${physicalName}"`,
|
|
265
|
+
path: [...path, "physicalName"]
|
|
266
|
+
});
|
|
267
|
+
const previousId = names.get(physicalName);
|
|
268
|
+
if (previousId !== void 0) diagnostics.push({
|
|
269
|
+
code: "duplicate-physical-name",
|
|
270
|
+
message: `The ${kind}s "${previousId}" and "${id}" both use physical name "${physicalName}"`,
|
|
271
|
+
path: [...path, "physicalName"],
|
|
272
|
+
relatedPaths: [[
|
|
273
|
+
kind === "constraint" ? "constraints" : "indexes",
|
|
274
|
+
previousId,
|
|
275
|
+
"physicalName"
|
|
276
|
+
]]
|
|
277
|
+
});
|
|
278
|
+
else names.set(physicalName, id);
|
|
279
|
+
result[id] = materializeSchemaObjectIdentity(value, id, physicalName);
|
|
280
|
+
}
|
|
281
|
+
if (diagnostics.length > 0) throw new SchemaMetadataValidationError(diagnostics);
|
|
282
|
+
return Object.freeze(result);
|
|
283
|
+
}
|
|
284
|
+
/** Return a diagnostic when a dialect extension is used by another adapter. */
|
|
285
|
+
function dialectMismatchDiagnostic(extension, dialect, path) {
|
|
286
|
+
if (extension.dialect === dialect) return;
|
|
287
|
+
return {
|
|
288
|
+
code: "dialect-mismatch",
|
|
289
|
+
message: `Dialect extension belongs to "${extension.dialect}" but the active schema dialect is "${dialect}"`,
|
|
290
|
+
path,
|
|
291
|
+
dialect
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
/** Throw a structured error for one or more dialect capability findings. */
|
|
295
|
+
function assertSchemaDialectSupport(diagnostics) {
|
|
296
|
+
if (diagnostics.length > 0) throw new SchemaMetadataValidationError(diagnostics);
|
|
297
|
+
}
|
|
298
|
+
//#endregion
|
|
299
|
+
//#region src/core/primitives/parameter.ts
|
|
300
|
+
function parameter(_value) {
|
|
301
|
+
return fragment((context) => context.parameter(_value));
|
|
302
|
+
}
|
|
303
|
+
//#endregion
|
|
304
|
+
//#region src/expressions/value.ts
|
|
305
|
+
function value(input) {
|
|
306
|
+
const expression = makeSchemaExpression("value", (context) => context.render(parameter(input)));
|
|
307
|
+
return Object.freeze({
|
|
308
|
+
...expression,
|
|
309
|
+
value: input
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
/** Bind a value while declaring its SQL semantic domain. */
|
|
313
|
+
function typedValue(input) {
|
|
314
|
+
return value(input);
|
|
315
|
+
}
|
|
316
|
+
function isExpressionValue(valueToCheck) {
|
|
317
|
+
return typeof valueToCheck === "object" && valueToCheck !== null && "expressionKind" in valueToCheck && "render" in valueToCheck && typeof valueToCheck.render === "function";
|
|
318
|
+
}
|
|
319
|
+
function isValueExpression(valueToCheck) {
|
|
320
|
+
return isExpressionValue(valueToCheck) && valueToCheck.expressionKind === "value" && "value" in valueToCheck;
|
|
321
|
+
}
|
|
322
|
+
function asValue(input) {
|
|
323
|
+
return isExpressionValue(input) ? input : value(input);
|
|
324
|
+
}
|
|
325
|
+
//#endregion
|
|
326
|
+
//#region src/core/primitives/identifier.ts
|
|
327
|
+
function identifier(name) {
|
|
328
|
+
return fragment((context) => context.append(context.dialect.quoteIdentifier(name)));
|
|
329
|
+
}
|
|
330
|
+
function qualifiedIdentifier(...parts) {
|
|
331
|
+
return fragment((context) => {
|
|
332
|
+
parts.forEach((part, index) => {
|
|
333
|
+
if (index > 0) context.append(".");
|
|
334
|
+
context.append(context.dialect.quoteIdentifier(part));
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
//#endregion
|
|
339
|
+
//#region src/expressions/column.ts
|
|
340
|
+
function createColumnReference(columnName, sourceReference, fieldName, result) {
|
|
341
|
+
const expression = makeSchemaExpression("column", (context) => {
|
|
342
|
+
if (context.renderColumnReference) {
|
|
343
|
+
context.renderColumnReference(columnName);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
context.render(sourceReference);
|
|
347
|
+
context.append(".");
|
|
348
|
+
context.render(identifier(columnName));
|
|
349
|
+
}, result);
|
|
350
|
+
return Object.freeze({
|
|
351
|
+
...expression,
|
|
352
|
+
fieldName,
|
|
353
|
+
columnName
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
function isColumnReference(value) {
|
|
357
|
+
return typeof value === "object" && value !== null && "expressionKind" in value && value.expressionKind === "column";
|
|
358
|
+
}
|
|
359
|
+
/** Turn an expression into a fragment that renders it without changing it. */
|
|
360
|
+
function expressionFragment(expression) {
|
|
361
|
+
return fragment((context) => context.render(expression));
|
|
362
|
+
}
|
|
363
|
+
//#endregion
|
|
364
|
+
export { parenthesize as A, resultValueOf as B, makeExpression as C, withDialectCapability as D, markSchemaExpression as E, dateResultDecoder as F, standardDialect as H, decodeResultRow as I, jsonTextResultDecoder as L, ResultDecodingError as M, booleanResultDecoder as N, fragment as O, createResultShape as P, resultShapeValue as R, isSchemaExpression as S, markExpressionCategory as T, timestampResultDecoder as V, materializeSchemaObjectIdentity as _, qualifiedIdentifier as a, snakeCaseIdentifier as b, typedValue as c, SchemaMetadataValidationError as d, assertSchemaDialectSupport as f, isValidSchemaObjectName as g, generatedSchemaObjectName as h, identifier as i, sequence as j, isFragment as k, value as l, freezeSchemaMetadata as m, expressionFragment as n, asValue as o, dialectMismatchDiagnostic as p, isColumnReference as r, isValueExpression as s, createColumnReference as t, parameter as u, materializeSchemaObjectRecord as v, makeSchemaExpression as w, isExpression as x, resolveSqlNames as y, resultValue as z };
|