turbine-orm 0.50.0 → 0.51.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/README.md +66 -66
- package/dist/adapters/cockroachdb.d.ts +5 -5
- package/dist/adapters/cockroachdb.js +10 -10
- package/dist/adapters/index.d.ts +5 -5
- package/dist/adapters/index.js +7 -7
- package/dist/adapters/yugabytedb.d.ts +7 -7
- package/dist/adapters/yugabytedb.js +10 -10
- package/dist/cjs/adapters/cockroachdb.d.ts +5 -5
- package/dist/cjs/adapters/cockroachdb.js +10 -10
- package/dist/cjs/adapters/index.d.ts +5 -5
- package/dist/cjs/adapters/index.js +7 -7
- package/dist/cjs/adapters/yugabytedb.d.ts +7 -7
- package/dist/cjs/adapters/yugabytedb.js +10 -10
- package/dist/cjs/cli/config.d.ts +13 -2
- package/dist/cjs/cli/config.js +3 -2
- package/dist/cjs/cli/destructive.d.ts +1 -1
- package/dist/cjs/cli/destructive.js +1 -1
- package/dist/cjs/cli/index.d.ts +10 -10
- package/dist/cjs/cli/index.js +49 -45
- package/dist/cjs/cli/loader.d.ts +7 -7
- package/dist/cjs/cli/loader.js +9 -9
- package/dist/cjs/cli/mcp.js +4 -4
- package/dist/cjs/cli/migrate.d.ts +5 -5
- package/dist/cjs/cli/migrate.js +11 -11
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/cli/ui.d.ts +2 -2
- package/dist/cjs/cli/ui.js +2 -2
- package/dist/cjs/client.d.ts +49 -38
- package/dist/cjs/client.js +57 -56
- package/dist/cjs/dialect.d.ts +62 -18
- package/dist/cjs/dialect.js +40 -2
- package/dist/cjs/errors.d.ts +5 -5
- package/dist/cjs/errors.js +11 -11
- package/dist/cjs/generate.d.ts +6 -6
- package/dist/cjs/generate.js +31 -29
- package/dist/cjs/index-advisor.d.ts +5 -5
- package/dist/cjs/index-advisor.js +0 -0
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +7 -7
- package/dist/cjs/introspect.d.ts +35 -9
- package/dist/cjs/introspect.js +83 -32
- package/dist/cjs/mssql.d.ts +11 -11
- package/dist/cjs/mssql.js +64 -29
- package/dist/cjs/mysql.d.ts +8 -8
- package/dist/cjs/mysql.js +61 -23
- package/dist/cjs/nested-write.d.ts +21 -2
- package/dist/cjs/nested-write.js +51 -14
- package/dist/cjs/optional-peer-import.cjs +7 -7
- package/dist/cjs/optional-peer-import.d.cts +7 -7
- package/dist/cjs/pipeline-submittable.d.ts +2 -2
- package/dist/cjs/pipeline-submittable.js +6 -6
- package/dist/cjs/pipeline.d.ts +1 -1
- package/dist/cjs/pipeline.js +4 -4
- package/dist/cjs/powdb-introspect.d.ts +1 -1
- package/dist/cjs/powdb-introspect.js +1 -1
- package/dist/cjs/powdb.d.ts +28 -28
- package/dist/cjs/powdb.js +66 -66
- package/dist/cjs/powql.d.ts +27 -27
- package/dist/cjs/powql.js +73 -52
- package/dist/cjs/query/aggregates.d.ts +1 -1
- package/dist/cjs/query/aggregates.js +5 -5
- package/dist/cjs/query/batched-loader.d.ts +11 -11
- package/dist/cjs/query/batched-loader.js +24 -24
- package/dist/cjs/query/builder.d.ts +39 -21
- package/dist/cjs/query/builder.js +99 -57
- package/dist/cjs/query/compound-unique.d.ts +1 -1
- package/dist/cjs/query/compound-unique.js +0 -0
- package/dist/cjs/query/deferred.d.ts +12 -6
- package/dist/cjs/query/deferred.js +1 -1
- package/dist/cjs/query/filters.d.ts +31 -11
- package/dist/cjs/query/filters.js +67 -14
- package/dist/cjs/query/index.d.ts +1 -1
- package/dist/cjs/query/index.js +1 -1
- package/dist/cjs/query/relations.d.ts +9 -9
- package/dist/cjs/query/relations.js +164 -57
- package/dist/cjs/query/types.d.ts +86 -35
- package/dist/cjs/query/types.js +1 -1
- package/dist/cjs/query/utils.d.ts +27 -10
- package/dist/cjs/query/utils.js +86 -14
- package/dist/cjs/query/where.d.ts +47 -28
- package/dist/cjs/query/where.js +130 -31
- package/dist/cjs/query/writes.d.ts +24 -5
- package/dist/cjs/query/writes.js +102 -13
- package/dist/cjs/realtime.d.ts +7 -7
- package/dist/cjs/realtime.js +9 -9
- package/dist/cjs/schema-builder.d.ts +18 -7
- package/dist/cjs/schema-builder.js +17 -10
- package/dist/cjs/schema-metadata.d.ts +3 -3
- package/dist/cjs/schema-metadata.js +9 -9
- package/dist/cjs/schema-sql.d.ts +9 -9
- package/dist/cjs/schema-sql.js +20 -20
- package/dist/cjs/schema.d.ts +19 -9
- package/dist/cjs/schema.js +6 -6
- package/dist/cjs/serverless.d.ts +15 -15
- package/dist/cjs/serverless.js +16 -16
- package/dist/cjs/sqlite.d.ts +8 -8
- package/dist/cjs/sqlite.js +53 -22
- package/dist/cjs/typed-sql.d.ts +4 -4
- package/dist/cjs/typed-sql.js +5 -5
- package/dist/cli/config.d.ts +13 -2
- package/dist/cli/config.js +3 -2
- package/dist/cli/destructive.d.ts +1 -1
- package/dist/cli/destructive.js +1 -1
- package/dist/cli/index.d.ts +10 -10
- package/dist/cli/index.js +49 -45
- package/dist/cli/loader.d.ts +7 -7
- package/dist/cli/loader.js +9 -9
- package/dist/cli/mcp.js +4 -4
- package/dist/cli/migrate.d.ts +5 -5
- package/dist/cli/migrate.js +11 -11
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/cli/ui.d.ts +2 -2
- package/dist/cli/ui.js +2 -2
- package/dist/client.d.ts +49 -38
- package/dist/client.js +57 -56
- package/dist/dialect.d.ts +62 -18
- package/dist/dialect.js +40 -2
- package/dist/errors.d.ts +5 -5
- package/dist/errors.js +11 -11
- package/dist/generate.d.ts +6 -6
- package/dist/generate.js +31 -29
- package/dist/index-advisor.d.ts +5 -5
- package/dist/index-advisor.js +0 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7 -7
- package/dist/introspect.d.ts +35 -9
- package/dist/introspect.js +82 -32
- package/dist/mssql.d.ts +11 -11
- package/dist/mssql.js +64 -29
- package/dist/mysql.d.ts +8 -8
- package/dist/mysql.js +61 -23
- package/dist/nested-write.d.ts +21 -2
- package/dist/nested-write.js +51 -14
- package/dist/optional-peer-import.cjs +7 -7
- package/dist/optional-peer-import.d.cts +7 -7
- package/dist/pipeline-submittable.d.ts +2 -2
- package/dist/pipeline-submittable.js +6 -6
- package/dist/pipeline.d.ts +1 -1
- package/dist/pipeline.js +4 -4
- package/dist/powdb-introspect.d.ts +1 -1
- package/dist/powdb-introspect.js +1 -1
- package/dist/powdb.d.ts +28 -28
- package/dist/powdb.js +66 -66
- package/dist/powql.d.ts +27 -27
- package/dist/powql.js +73 -52
- package/dist/query/aggregates.d.ts +1 -1
- package/dist/query/aggregates.js +5 -5
- package/dist/query/batched-loader.d.ts +11 -11
- package/dist/query/batched-loader.js +24 -24
- package/dist/query/builder.d.ts +39 -21
- package/dist/query/builder.js +100 -58
- package/dist/query/compound-unique.d.ts +1 -1
- package/dist/query/compound-unique.js +0 -0
- package/dist/query/deferred.d.ts +12 -6
- package/dist/query/deferred.js +1 -1
- package/dist/query/filters.d.ts +31 -11
- package/dist/query/filters.js +66 -13
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +1 -1
- package/dist/query/relations.d.ts +9 -9
- package/dist/query/relations.js +165 -58
- package/dist/query/types.d.ts +86 -35
- package/dist/query/types.js +1 -1
- package/dist/query/utils.d.ts +27 -10
- package/dist/query/utils.js +84 -14
- package/dist/query/where.d.ts +47 -28
- package/dist/query/where.js +129 -32
- package/dist/query/writes.d.ts +24 -5
- package/dist/query/writes.js +101 -13
- package/dist/realtime.d.ts +7 -7
- package/dist/realtime.js +9 -9
- package/dist/schema-builder.d.ts +18 -7
- package/dist/schema-builder.js +17 -10
- package/dist/schema-metadata.d.ts +3 -3
- package/dist/schema-metadata.js +9 -9
- package/dist/schema-sql.d.ts +9 -9
- package/dist/schema-sql.js +20 -20
- package/dist/schema.d.ts +19 -9
- package/dist/schema.js +6 -6
- package/dist/serverless.d.ts +15 -15
- package/dist/serverless.js +16 -16
- package/dist/sqlite.d.ts +8 -8
- package/dist/sqlite.js +53 -22
- package/dist/typed-sql.d.ts +4 -4
- package/dist/typed-sql.js +5 -5
- package/package.json +2 -2
package/dist/cjs/introspect.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* turbine-orm
|
|
2
|
+
* turbine-orm, Schema introspection
|
|
3
3
|
*
|
|
4
4
|
* Connects to a live Postgres database, reads information_schema + pg_catalog,
|
|
5
5
|
* and produces a SchemaMetadata object describing every table, column, relation,
|
|
@@ -65,6 +65,15 @@ export interface IntrospectOptions {
|
|
|
65
65
|
include?: string[];
|
|
66
66
|
/** Tables to exclude (default: none). Applied after include. */
|
|
67
67
|
exclude?: string[];
|
|
68
|
+
/**
|
|
69
|
+
* Rename derived relations, as `{ table: { derivedName: desiredName } }`.
|
|
70
|
+
*
|
|
71
|
+
* Relation names are composed by introspection (the database does not name
|
|
72
|
+
* relationships), so a port from another ORM that named them differently has
|
|
73
|
+
* to touch every call site. Declaring the mapping here makes it mechanical.
|
|
74
|
+
* A typo is an error, not a silent no-op: see {@link applyRelationRenames}.
|
|
75
|
+
*/
|
|
76
|
+
relationNames?: Record<string, Record<string, string>>;
|
|
68
77
|
/**
|
|
69
78
|
* Also introspect **views** and **materialized views** as read-only
|
|
70
79
|
* {@link TableMetadata} entries (`isView: true`). Off by default. Write
|
|
@@ -102,6 +111,23 @@ export interface IntrospectOptions {
|
|
|
102
111
|
* SQL. PostgreSQL is driven by {@link introspectPostgresCatalog}.
|
|
103
112
|
*/
|
|
104
113
|
export declare function introspect(options: IntrospectOptions): Promise<SchemaMetadata>;
|
|
114
|
+
/**
|
|
115
|
+
* Rename introspected relations, per table, from the name turbine derived to
|
|
116
|
+
* the name the caller wants.
|
|
117
|
+
*
|
|
118
|
+
* Relation names are DERIVED, not declared: the database has no name for a
|
|
119
|
+
* foreign key's relationship, so introspection composes one, and two foreign
|
|
120
|
+
* keys pointing at the same table produce composed names (`msgsBySender`,
|
|
121
|
+
* `msgsByRecipient`) that nobody would predict. A codebase migrating from
|
|
122
|
+
* another ORM already has names for these, chosen by different rules, so every
|
|
123
|
+
* call site has to be hand-edited. This map turns that into a mechanical
|
|
124
|
+
* mapping done once in the config.
|
|
125
|
+
*
|
|
126
|
+
* Renames are validated rather than best-effort: an unknown table or an
|
|
127
|
+
* unknown source relation is an ERROR, because silently ignoring a typo here
|
|
128
|
+
* means the call sites it was supposed to fix break at runtime instead.
|
|
129
|
+
*/
|
|
130
|
+
export declare function applyRelationRenames(schema: SchemaMetadata, renames: Record<string, Record<string, string>>): SchemaMetadata;
|
|
105
131
|
/**
|
|
106
132
|
* PostgreSQL catalog introspector: reads information_schema + pg_catalog and
|
|
107
133
|
* produces {@link SchemaMetadata}. This is the implementation wrapped by
|
|
@@ -148,7 +174,7 @@ export interface ForeignKeyEntry {
|
|
|
148
174
|
}
|
|
149
175
|
/**
|
|
150
176
|
* Derive a belongsTo relation name from its FK column. Strips a trailing
|
|
151
|
-
* `_id` (snake_case) or `Id` (camelCase column names
|
|
177
|
+
* `_id` (snake_case) or `Id` (camelCase column names, common in Prisma-ported
|
|
152
178
|
* schemas where columns are quoted camelCase identifiers), then camelCases:
|
|
153
179
|
* `current_version_id` and `currentVersionId` both yield `currentVersion`.
|
|
154
180
|
* Stripping is what keeps the scalar FK field (`currentVersionId`) targetable
|
|
@@ -189,7 +215,7 @@ export declare function parsePlainUniqueIndexColumns(indexdef: string): string[]
|
|
|
189
215
|
export declare function detectUniqueForeignKeySets(pkByTable: Map<string, string[]>, uniqueByTable: Map<string, string[][]>, indexesByTable: Map<string, IndexMetadata[]>): Map<string, string[][]>;
|
|
190
216
|
/**
|
|
191
217
|
* Build the belongsTo/hasMany relation maps for every table from its foreign
|
|
192
|
-
* keys. Naming rules (LEGACY-FIRST
|
|
218
|
+
* keys. Naming rules (LEGACY-FIRST, a relation name that previously worked at
|
|
193
219
|
* runtime must never change out from under a regenerating app):
|
|
194
220
|
*
|
|
195
221
|
* 1. First compute the historical derivation exactly as it shipped before
|
|
@@ -197,7 +223,7 @@ export declare function detectUniqueForeignKeySets(pkByTable: Map<string, string
|
|
|
197
223
|
* suffix (`snakeToCamel(col.replace(/_id$/, ''))` when several FKs point
|
|
198
224
|
* at the same target, else the singularized target table), and hasMany is
|
|
199
225
|
* `snakeToCamel(`${source}_by_${strippedColumn}`)` (else the source
|
|
200
|
-
* table). If that legacy name is free, KEEP IT
|
|
226
|
+
* table). If that legacy name is free, KEEP IT, even when it looks odd
|
|
201
227
|
* (`blogPostsByAuthorId`, `postsBy_Author`): those names were collision-
|
|
202
228
|
* free and worked, so regenerating must not rename them.
|
|
203
229
|
* 2. If the legacy name collides ONLY with a scalar column whose tsType is
|
|
@@ -206,17 +232,17 @@ export declare function detectUniqueForeignKeySets(pkByTable: Map<string, string
|
|
|
206
232
|
* relation payload; generate.ts's typeSafeRelations omits the relation
|
|
207
233
|
* from the type layer).
|
|
208
234
|
* 3. On a genuine collision (concrete-typed column shadow, or a previously
|
|
209
|
-
* assigned relation), fall back to the modern derivation
|
|
235
|
+
* assigned relation), fall back to the modern derivation, the `_id`/`Id`
|
|
210
236
|
* case-insensitive strip of {@link relationNameFromColumn} plus the
|
|
211
|
-
* `By`-composed reverse name
|
|
237
|
+
* `By`-composed reverse name, which fixes the camelCase-FK shadowing
|
|
212
238
|
* shapes that were actually BROKEN before (relation name === scalar FK
|
|
213
239
|
* field → unusable types).
|
|
214
240
|
* 4. Last resort: deterministic `Rel`/`Rel2` suffix + warning.
|
|
215
241
|
*
|
|
216
|
-
* @param columnFieldsByTable camelCase column *fields* per table
|
|
242
|
+
* @param columnFieldsByTable camelCase column *fields* per table, used to
|
|
217
243
|
* guarantee relations never shadow concrete-typed scalar columns.
|
|
218
244
|
* @param unknownTypedFieldsByTable subset of the column fields whose tsType is
|
|
219
|
-
* `unknown` (json/jsonb)
|
|
245
|
+
* `unknown` (json/jsonb), legacy shadows of these are preserved (rule 2).
|
|
220
246
|
* @param uniqueSetsByTable when provided (F2), the child-table column sets that
|
|
221
247
|
* guarantee at-most-one row (PK + unique constraints + plain unique indexes,
|
|
222
248
|
* from {@link detectUniqueForeignKeySets}). A reverse relation whose FK column
|
|
@@ -262,7 +288,7 @@ export declare function addAutoManyToManyRelations(tableNames: Iterable<string>,
|
|
|
262
288
|
* MSSQL): filters the FK list to the introspected table set, seeds the
|
|
263
289
|
* taken-name / json-shadow maps from the engine's column metadata, and runs
|
|
264
290
|
* the SAME `buildRelationsFromForeignKeys` + `addAutoManyToManyRelations`
|
|
265
|
-
* pipeline as the Postgres introspector
|
|
291
|
+
* pipeline as the Postgres introspector, so every engine derives identical
|
|
266
292
|
* relation names for the same logical schema (the engines previously carried
|
|
267
293
|
* stale copies of a retired naming scheme).
|
|
268
294
|
*/
|
package/dist/cjs/introspect.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* turbine-orm
|
|
3
|
+
* turbine-orm, Schema introspection
|
|
4
4
|
*
|
|
5
5
|
* Connects to a live Postgres database, reads information_schema + pg_catalog,
|
|
6
6
|
* and produces a SchemaMetadata object describing every table, column, relation,
|
|
@@ -17,6 +17,7 @@ exports.pgConfActionToReferential = pgConfActionToReferential;
|
|
|
17
17
|
exports.applyTableFilters = applyTableFilters;
|
|
18
18
|
exports.defaultExcludedTablesPresent = defaultExcludedTablesPresent;
|
|
19
19
|
exports.introspect = introspect;
|
|
20
|
+
exports.applyRelationRenames = applyRelationRenames;
|
|
20
21
|
exports.introspectPostgresCatalog = introspectPostgresCatalog;
|
|
21
22
|
exports.parseIndexColumns = parseIndexColumns;
|
|
22
23
|
exports.indexHasWhere = indexHasWhere;
|
|
@@ -30,6 +31,7 @@ exports.addAutoManyToManyRelations = addAutoManyToManyRelations;
|
|
|
30
31
|
exports.deriveEngineRelations = deriveEngineRelations;
|
|
31
32
|
const pg_1 = __importDefault(require("pg"));
|
|
32
33
|
const dialect_js_1 = require("./dialect.js");
|
|
34
|
+
const errors_js_1 = require("./errors.js");
|
|
33
35
|
const schema_js_1 = require("./schema.js");
|
|
34
36
|
/**
|
|
35
37
|
* Map a `pg_constraint.confdeltype` / `confupdtype` character to a
|
|
@@ -146,21 +148,21 @@ const SQL_CHECKS = `
|
|
|
146
148
|
WHERE con.contype = 'c'
|
|
147
149
|
AND n.nspname = $1
|
|
148
150
|
`;
|
|
149
|
-
// Views (relkind 'v')
|
|
151
|
+
// Views (relkind 'v'), column metadata comes free from information_schema.columns.
|
|
150
152
|
const SQL_VIEWS = `
|
|
151
153
|
SELECT table_name
|
|
152
154
|
FROM information_schema.views
|
|
153
155
|
WHERE table_schema = $1
|
|
154
156
|
ORDER BY table_name
|
|
155
157
|
`;
|
|
156
|
-
// Materialized views (relkind 'm')
|
|
158
|
+
// Materialized views (relkind 'm'), NOT in information_schema; read from pg_catalog.
|
|
157
159
|
const SQL_MATVIEWS = `
|
|
158
160
|
SELECT matviewname AS table_name
|
|
159
161
|
FROM pg_matviews
|
|
160
162
|
WHERE schemaname = $1
|
|
161
163
|
ORDER BY matviewname
|
|
162
164
|
`;
|
|
163
|
-
// Materialized-view columns
|
|
165
|
+
// Materialized-view columns, information_schema.columns omits matviews, so pull
|
|
164
166
|
// them from pg_attribute. Aliased to mirror SQL_COLUMNS so the same row-mapping
|
|
165
167
|
// applies (array types surface as data_type 'ARRAY' + a '_'-prefixed udt_name).
|
|
166
168
|
const SQL_MATVIEW_COLUMNS = `
|
|
@@ -262,11 +264,60 @@ function defaultExcludedTablesPresent(names, options = {}) {
|
|
|
262
264
|
async function introspect(options) {
|
|
263
265
|
const dialect = options.dialect ?? dialect_js_1.postgresDialect;
|
|
264
266
|
const introspector = dialect.introspector;
|
|
265
|
-
|
|
266
|
-
|
|
267
|
+
const schema = introspector
|
|
268
|
+
? await introspector.introspect(options)
|
|
269
|
+
: // Dialects without an introspector fall back to the Postgres catalog reader.
|
|
270
|
+
await introspectPostgresCatalog(options);
|
|
271
|
+
// Applied here rather than inside each introspector so every engine gets it.
|
|
272
|
+
return options.relationNames ? applyRelationRenames(schema, options.relationNames) : schema;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Rename introspected relations, per table, from the name turbine derived to
|
|
276
|
+
* the name the caller wants.
|
|
277
|
+
*
|
|
278
|
+
* Relation names are DERIVED, not declared: the database has no name for a
|
|
279
|
+
* foreign key's relationship, so introspection composes one, and two foreign
|
|
280
|
+
* keys pointing at the same table produce composed names (`msgsBySender`,
|
|
281
|
+
* `msgsByRecipient`) that nobody would predict. A codebase migrating from
|
|
282
|
+
* another ORM already has names for these, chosen by different rules, so every
|
|
283
|
+
* call site has to be hand-edited. This map turns that into a mechanical
|
|
284
|
+
* mapping done once in the config.
|
|
285
|
+
*
|
|
286
|
+
* Renames are validated rather than best-effort: an unknown table or an
|
|
287
|
+
* unknown source relation is an ERROR, because silently ignoring a typo here
|
|
288
|
+
* means the call sites it was supposed to fix break at runtime instead.
|
|
289
|
+
*/
|
|
290
|
+
function applyRelationRenames(schema, renames) {
|
|
291
|
+
const tables = { ...schema.tables };
|
|
292
|
+
for (const [table, mapping] of Object.entries(renames)) {
|
|
293
|
+
const meta = tables[table];
|
|
294
|
+
if (!meta) {
|
|
295
|
+
throw new errors_js_1.ValidationError(`[turbine] relationNames: unknown table "${table}". Known tables: ${Object.keys(tables).join(', ')}.`);
|
|
296
|
+
}
|
|
297
|
+
const relations = { ...meta.relations };
|
|
298
|
+
const columnFields = new Set(Object.values(meta.columnMap));
|
|
299
|
+
for (const [from, to] of Object.entries(mapping)) {
|
|
300
|
+
if (!Object.hasOwn(relations, from)) {
|
|
301
|
+
throw new errors_js_1.ValidationError(`[turbine] relationNames: table "${table}" has no relation "${from}". ` +
|
|
302
|
+
`Derived relations: ${Object.keys(relations).join(', ') || '(none)'}.`);
|
|
303
|
+
}
|
|
304
|
+
if (from === to)
|
|
305
|
+
continue;
|
|
306
|
+
if (Object.hasOwn(relations, to)) {
|
|
307
|
+
throw new errors_js_1.ValidationError(`[turbine] relationNames: cannot rename "${table}.${from}" to "${to}", that relation already exists.`);
|
|
308
|
+
}
|
|
309
|
+
if (columnFields.has(to)) {
|
|
310
|
+
throw new errors_js_1.ValidationError(`[turbine] relationNames: cannot rename "${table}.${from}" to "${to}", a column on "${table}" ` +
|
|
311
|
+
`already uses that field name, and a relation must never shadow a column.`);
|
|
312
|
+
}
|
|
313
|
+
const def = relations[from];
|
|
314
|
+
delete relations[from];
|
|
315
|
+
// `RelationDef.name` is the relation's own identity, so it moves with it.
|
|
316
|
+
relations[to] = { ...def, name: to };
|
|
317
|
+
}
|
|
318
|
+
tables[table] = { ...meta, relations };
|
|
267
319
|
}
|
|
268
|
-
|
|
269
|
-
return introspectPostgresCatalog(options);
|
|
320
|
+
return { ...schema, tables };
|
|
270
321
|
}
|
|
271
322
|
/**
|
|
272
323
|
* PostgreSQL catalog introspector: reads information_schema + pg_catalog and
|
|
@@ -360,7 +411,7 @@ async function introspectPostgresCatalog(options) {
|
|
|
360
411
|
// (gen_random_uuid(), now()), which Turbine must still synthesize.
|
|
361
412
|
isGenerated: (typeof row.column_default === 'string' && row.column_default.includes('nextval(')) ||
|
|
362
413
|
row.is_identity === 'YES',
|
|
363
|
-
// GENERATED ALWAYS AS (expr) STORED
|
|
414
|
+
// GENERATED ALWAYS AS (expr) STORED, computed by the database, never
|
|
364
415
|
// writable. Distinct from isGenerated (serial/identity, which a client
|
|
365
416
|
// MAY override). is_generated is 'ALWAYS' for STORED columns, else 'NEVER'.
|
|
366
417
|
isGeneratedStored: row.is_generated === 'ALWAYS',
|
|
@@ -371,7 +422,7 @@ async function introspectPostgresCatalog(options) {
|
|
|
371
422
|
maxLength: row.character_maximum_length ?? undefined,
|
|
372
423
|
// Record the type's schema ONLY when it lives outside the introspected
|
|
373
424
|
// schema (and isn't a pg_catalog builtin). A same-named enum in another
|
|
374
|
-
// schema must NOT get this schema's `::"enum"` cast
|
|
425
|
+
// schema must NOT get this schema's `::"enum"` cast, search_path would
|
|
375
426
|
// resolve the cast to the wrong type (see enumTypeForColumn). Omitting
|
|
376
427
|
// it for the common case keeps generated metadata byte-identical.
|
|
377
428
|
...(typeof row.udt_schema === 'string' && row.udt_schema !== schema && row.udt_schema !== 'pg_catalog'
|
|
@@ -475,13 +526,13 @@ async function introspectPostgresCatalog(options) {
|
|
|
475
526
|
// per-FK-column when several FKs point at the same target, and every name
|
|
476
527
|
// is collision-checked against the table's scalar column fields so a
|
|
477
528
|
// relation can never shadow a column (which generated unsound types and
|
|
478
|
-
// made both surfaces unusable
|
|
529
|
+
// made both surfaces unusable, dogfood T-4).
|
|
479
530
|
const columnFieldsByTable = new Map();
|
|
480
531
|
const unknownTypedFieldsByTable = new Map();
|
|
481
532
|
for (const [tbl, cols] of columnsByTable) {
|
|
482
533
|
columnFieldsByTable.set(tbl, new Set(cols.map((c) => c.field)));
|
|
483
534
|
// Enum-typed columns also report tsType 'unknown' here, but generate.ts
|
|
484
|
-
// gives them a concrete union type
|
|
535
|
+
// gives them a concrete union type, a shadow of one was type-broken on
|
|
485
536
|
// main, so only genuine json/jsonb columns qualify as historical shadows.
|
|
486
537
|
unknownTypedFieldsByTable.set(tbl, new Set(cols.filter((c) => isUnknownTsType(c.tsType) && !Object.hasOwn(enums, c.pgType)).map((c) => c.field)));
|
|
487
538
|
}
|
|
@@ -503,14 +554,14 @@ async function introspectPostgresCatalog(options) {
|
|
|
503
554
|
// 1. J's primary key is exactly two columns.
|
|
504
555
|
// 2. J has exactly two FKs, each single-column.
|
|
505
556
|
// 3. Each FK's source column is one of J's two PK columns (the PK *is* the
|
|
506
|
-
// two FK columns
|
|
557
|
+
// two FK columns, no surrogate PK, no extra identity).
|
|
507
558
|
// 4. The two FKs target two DISTINCT tables (A and B).
|
|
508
559
|
// 5. J has no columns beyond those two FK/PK columns (no payload columns
|
|
509
560
|
// like `grade` or `created_at`).
|
|
510
561
|
//
|
|
511
562
|
// For such a J linking A and B we ADD a `manyToMany` relation on A → B and
|
|
512
563
|
// symmetrically on B → A, both routed `through` J. The existing belongsTo /
|
|
513
|
-
// hasMany relations derived from J's FKs are left untouched
|
|
564
|
+
// hasMany relations derived from J's FKs are left untouched, this block
|
|
514
565
|
// never removes or renames anything. Naming/collision handling lives in the
|
|
515
566
|
// shared addAutoManyToManyRelations helper.
|
|
516
567
|
//
|
|
@@ -645,7 +696,7 @@ function stripCheckWrapper(def) {
|
|
|
645
696
|
}
|
|
646
697
|
/**
|
|
647
698
|
* Derive a belongsTo relation name from its FK column. Strips a trailing
|
|
648
|
-
* `_id` (snake_case) or `Id` (camelCase column names
|
|
699
|
+
* `_id` (snake_case) or `Id` (camelCase column names, common in Prisma-ported
|
|
649
700
|
* schemas where columns are quoted camelCase identifiers), then camelCases:
|
|
650
701
|
* `current_version_id` and `currentVersionId` both yield `currentVersion`.
|
|
651
702
|
* Stripping is what keeps the scalar FK field (`currentVersionId`) targetable
|
|
@@ -764,7 +815,7 @@ function detectUniqueForeignKeySets(pkByTable, uniqueByTable, indexesByTable) {
|
|
|
764
815
|
/**
|
|
765
816
|
* Resolve a derived relation name against the names already taken on the
|
|
766
817
|
* table (scalar column fields + previously assigned relations). On collision,
|
|
767
|
-
* applies a deterministic `Rel` / `Rel2` / `Rel3`… suffix and warns
|
|
818
|
+
* applies a deterministic `Rel` / `Rel2` / `Rel3`… suffix and warns, a
|
|
768
819
|
* colliding name would otherwise shadow a column field and generate types
|
|
769
820
|
* that fail `tsc --strict` (TS2430/TS2322).
|
|
770
821
|
*/
|
|
@@ -774,12 +825,12 @@ function resolveRelationNameCollision(candidate, taken, table, source) {
|
|
|
774
825
|
let name = `${candidate}Rel`;
|
|
775
826
|
for (let i = 2; taken.has(name); i++)
|
|
776
827
|
name = `${candidate}Rel${i}`;
|
|
777
|
-
console.warn(`[turbine] Relation name "${candidate}" on table "${table}" (from ${source}) collides with an existing column or relation
|
|
828
|
+
console.warn(`[turbine] Relation name "${candidate}" on table "${table}" (from ${source}) collides with an existing column or relation, using "${name}" instead.`);
|
|
778
829
|
return name;
|
|
779
830
|
}
|
|
780
831
|
/**
|
|
781
832
|
* Build the belongsTo/hasMany relation maps for every table from its foreign
|
|
782
|
-
* keys. Naming rules (LEGACY-FIRST
|
|
833
|
+
* keys. Naming rules (LEGACY-FIRST, a relation name that previously worked at
|
|
783
834
|
* runtime must never change out from under a regenerating app):
|
|
784
835
|
*
|
|
785
836
|
* 1. First compute the historical derivation exactly as it shipped before
|
|
@@ -787,7 +838,7 @@ function resolveRelationNameCollision(candidate, taken, table, source) {
|
|
|
787
838
|
* suffix (`snakeToCamel(col.replace(/_id$/, ''))` when several FKs point
|
|
788
839
|
* at the same target, else the singularized target table), and hasMany is
|
|
789
840
|
* `snakeToCamel(`${source}_by_${strippedColumn}`)` (else the source
|
|
790
|
-
* table). If that legacy name is free, KEEP IT
|
|
841
|
+
* table). If that legacy name is free, KEEP IT, even when it looks odd
|
|
791
842
|
* (`blogPostsByAuthorId`, `postsBy_Author`): those names were collision-
|
|
792
843
|
* free and worked, so regenerating must not rename them.
|
|
793
844
|
* 2. If the legacy name collides ONLY with a scalar column whose tsType is
|
|
@@ -796,17 +847,17 @@ function resolveRelationNameCollision(candidate, taken, table, source) {
|
|
|
796
847
|
* relation payload; generate.ts's typeSafeRelations omits the relation
|
|
797
848
|
* from the type layer).
|
|
798
849
|
* 3. On a genuine collision (concrete-typed column shadow, or a previously
|
|
799
|
-
* assigned relation), fall back to the modern derivation
|
|
850
|
+
* assigned relation), fall back to the modern derivation, the `_id`/`Id`
|
|
800
851
|
* case-insensitive strip of {@link relationNameFromColumn} plus the
|
|
801
|
-
* `By`-composed reverse name
|
|
852
|
+
* `By`-composed reverse name, which fixes the camelCase-FK shadowing
|
|
802
853
|
* shapes that were actually BROKEN before (relation name === scalar FK
|
|
803
854
|
* field → unusable types).
|
|
804
855
|
* 4. Last resort: deterministic `Rel`/`Rel2` suffix + warning.
|
|
805
856
|
*
|
|
806
|
-
* @param columnFieldsByTable camelCase column *fields* per table
|
|
857
|
+
* @param columnFieldsByTable camelCase column *fields* per table, used to
|
|
807
858
|
* guarantee relations never shadow concrete-typed scalar columns.
|
|
808
859
|
* @param unknownTypedFieldsByTable subset of the column fields whose tsType is
|
|
809
|
-
* `unknown` (json/jsonb)
|
|
860
|
+
* `unknown` (json/jsonb), legacy shadows of these are preserved (rule 2).
|
|
810
861
|
* @param uniqueSetsByTable when provided (F2), the child-table column sets that
|
|
811
862
|
* guarantee at-most-one row (PK + unique constraints + plain unique indexes,
|
|
812
863
|
* from {@link detectUniqueForeignKeySets}). A reverse relation whose FK column
|
|
@@ -835,7 +886,7 @@ function buildRelationsFromForeignKeys(foreignKeys, columnFieldsByTable, fkActio
|
|
|
835
886
|
}
|
|
836
887
|
return taken;
|
|
837
888
|
};
|
|
838
|
-
// Relation names actually assigned so far (as opposed to column fields)
|
|
889
|
+
// Relation names actually assigned so far (as opposed to column fields) -
|
|
839
890
|
// needed to tell "collides only with a column" apart from "collides with an
|
|
840
891
|
// already-assigned relation" for the legacy-shadow-preserving rule.
|
|
841
892
|
const assignedByTable = new Map();
|
|
@@ -847,17 +898,17 @@ function buildRelationsFromForeignKeys(foreignKeys, columnFieldsByTable, fkActio
|
|
|
847
898
|
}
|
|
848
899
|
return assigned;
|
|
849
900
|
};
|
|
850
|
-
/** Legacy-first name resolution
|
|
901
|
+
/** Legacy-first name resolution, see the naming rules in the JSDoc above. */
|
|
851
902
|
const resolveName = (legacy, modern, table, source) => {
|
|
852
903
|
const taken = takenFor(table);
|
|
853
904
|
if (!taken.has(legacy))
|
|
854
905
|
return legacy;
|
|
855
906
|
// Historical json/jsonb shadow: previously worked at runtime AND compiled
|
|
856
|
-
// (tsType `unknown` absorbs the relation payload). Keep the name, warn
|
|
907
|
+
// (tsType `unknown` absorbs the relation payload). Keep the name, warn -
|
|
857
908
|
// typeSafeRelations() keeps the generated type layer sound.
|
|
858
909
|
if (!assignedFor(table).has(legacy) && unknownTypedFieldsByTable?.get(table)?.has(legacy)) {
|
|
859
910
|
console.warn(`[turbine] Relation "${legacy}" on table "${table}" (from ${source}) shadows the json/jsonb column ` +
|
|
860
|
-
`"${legacy}"
|
|
911
|
+
`"${legacy}", keeping the historical name for runtime compatibility; the relation is omitted from ` +
|
|
861
912
|
`the generated types. Rename the column to expose it.`);
|
|
862
913
|
return legacy;
|
|
863
914
|
}
|
|
@@ -873,7 +924,7 @@ function buildRelationsFromForeignKeys(foreignKeys, columnFieldsByTable, fkActio
|
|
|
873
924
|
// For multi-column (composite) FKs, use array form.
|
|
874
925
|
const foreignKey = singleColumn ? fk.sourceColumns[0] : fk.sourceColumns;
|
|
875
926
|
const referenceKey = fk.targetColumns.length === 1 ? fk.targetColumns[0] : fk.targetColumns;
|
|
876
|
-
// Composite FKs have no single column to derive from
|
|
927
|
+
// Composite FKs have no single column to derive from, fall back to the
|
|
877
928
|
// constraint name (with the usual fk_/-_fkey affixes stripped).
|
|
878
929
|
const constraintBase = fk.constraintName.replace(/^fk_/, '').replace(/_fkey$/, '');
|
|
879
930
|
// --- belongsTo on the source (child) table ---
|
|
@@ -977,7 +1028,7 @@ function buildRelationsFromForeignKeys(foreignKeys, columnFieldsByTable, fkActio
|
|
|
977
1028
|
*/
|
|
978
1029
|
function addAutoManyToManyRelations(tableNames, foreignKeys, pkByTable, columnNamesByTable, relationsByTable, columnFieldsByTable, unknownTypedFieldsByTable, uniqueIndexColsByTable) {
|
|
979
1030
|
for (const tableName of tableNames) {
|
|
980
|
-
// FKs whose source is this table
|
|
1031
|
+
// FKs whose source is this table, both must be single-column.
|
|
981
1032
|
const tableFks = foreignKeys.filter((fk) => fk.sourceTable === tableName);
|
|
982
1033
|
if (tableFks.length !== 2)
|
|
983
1034
|
continue;
|
|
@@ -1029,9 +1080,9 @@ function addAutoManyToManyRelations(tableNames, foreignKeys, pkByTable, columnNa
|
|
|
1029
1080
|
const columnFields = columnFieldsByTable?.get(sourceTbl);
|
|
1030
1081
|
if (columnFields?.has(relName)) {
|
|
1031
1082
|
if (unknownTypedFieldsByTable?.get(sourceTbl)?.has(relName)) {
|
|
1032
|
-
// Historical json/jsonb shadow
|
|
1083
|
+
// Historical json/jsonb shadow, worked at runtime, compiled fine.
|
|
1033
1084
|
console.warn(`[turbine] Relation "${relName}" on table "${sourceTbl}" (junction ${tableName}) shadows the ` +
|
|
1034
|
-
`json/jsonb column "${relName}"
|
|
1085
|
+
`json/jsonb column "${relName}", keeping the historical name for runtime compatibility; ` +
|
|
1035
1086
|
`the relation is omitted from the generated types.`);
|
|
1036
1087
|
}
|
|
1037
1088
|
else {
|
|
@@ -1065,7 +1116,7 @@ function addAutoManyToManyRelations(tableNames, foreignKeys, pkByTable, columnNa
|
|
|
1065
1116
|
* MSSQL): filters the FK list to the introspected table set, seeds the
|
|
1066
1117
|
* taken-name / json-shadow maps from the engine's column metadata, and runs
|
|
1067
1118
|
* the SAME `buildRelationsFromForeignKeys` + `addAutoManyToManyRelations`
|
|
1068
|
-
* pipeline as the Postgres introspector
|
|
1119
|
+
* pipeline as the Postgres introspector, so every engine derives identical
|
|
1069
1120
|
* relation names for the same logical schema (the engines previously carried
|
|
1070
1121
|
* stale copies of a retired naming scheme).
|
|
1071
1122
|
*/
|
package/dist/cjs/mssql.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* turbine-orm/mssql
|
|
2
|
+
* turbine-orm/mssql, Microsoft SQL Server engine (driver-injected, optional peer)
|
|
3
3
|
*
|
|
4
4
|
* Binds Turbine to SQL Server 2016+ via the `mssql` driver (which wraps
|
|
5
|
-
* `tedious`). `mssql` is **not** a root dependency
|
|
5
|
+
* `tedious`). `mssql` is **not** a root dependency, it is an **optional peer**:
|
|
6
6
|
* `npm i turbine-orm` pulls nothing extra, and only consumers who
|
|
7
7
|
* `import 'turbine-orm/mssql'` install `mssql` themselves. The factory loads it
|
|
8
8
|
* through a dynamic `import('mssql')` so importing this module never crashes when
|
|
@@ -33,14 +33,14 @@
|
|
|
33
33
|
* real JSON instead of being escaped as a string. `INCLUDE_NULL_VALUES`
|
|
34
34
|
* keeps NULL columns present (matching PostgreSQL `json_build_object`).
|
|
35
35
|
* 3. **No `LIMIT`.** Paging is `ORDER BY … OFFSET n ROWS FETCH NEXT m ROWS ONLY`,
|
|
36
|
-
* which requires an ORDER BY
|
|
36
|
+
* which requires an ORDER BY, a stable `ORDER BY (SELECT NULL)` is injected
|
|
37
37
|
* when the query has none (`Dialect.buildLimitOffset`).
|
|
38
38
|
*
|
|
39
39
|
* ## Named `@pN` placeholders (no positional `?`)
|
|
40
40
|
*
|
|
41
41
|
* `mssqlDialect.paramPlaceholder = (i) => '@p' + i`. The driver shim binds via
|
|
42
42
|
* `request.input('p' + i, value)`, so binding is by NAME and independent of where
|
|
43
|
-
* each placeholder lands in the SQL text
|
|
43
|
+
* each placeholder lands in the SQL text, exactly the guarantee PostgreSQL's
|
|
44
44
|
* numbered `$N` gives. (SQL Server is naturally named-param friendly, sidestepping
|
|
45
45
|
* the positional-`?` mis-bind bug the SQLite/MySQL phases hit.)
|
|
46
46
|
*
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
*
|
|
49
49
|
* - **Single query nested relations preserved** via `FOR JSON PATH` (SQL Server
|
|
50
50
|
* 2016+). Ordered/limited to-many uses `ORDER BY … OFFSET/FETCH` inside the FOR
|
|
51
|
-
* JSON subquery (no inner-subquery rewrite needed
|
|
51
|
+
* JSON subquery (no inner-subquery rewrite needed, FOR JSON aggregates AFTER
|
|
52
52
|
* the row selection).
|
|
53
53
|
* - **Result strategy `'output'`:** create/update/delete/upsert return their rows
|
|
54
54
|
* from the same statement. `createMany` returns the inserted rows via
|
|
55
55
|
* `OUTPUT INSERTED.*` on the multi-row VALUES insert (≤ 1000 rows / 2100 params
|
|
56
|
-
* per statement
|
|
56
|
+
* per statement, exceeding either throws a clear `ValidationError`; chunk
|
|
57
57
|
* yourself or use single `create`s).
|
|
58
58
|
* - **MERGE concurrency caveat:** `MERGE` is the upsert primitive; under high
|
|
59
59
|
* concurrency a `MERGE` can still race (it is NOT a substitute for a unique
|
|
@@ -62,20 +62,20 @@
|
|
|
62
62
|
* loser of a race.
|
|
63
63
|
* - **Unsupported (throw `UnsupportedFeatureError`):** pgvector distance ops,
|
|
64
64
|
* LISTEN/NOTIFY (`$listen`/`$notify`), RLS `sessionContext` (sp_set_session_context
|
|
65
|
-
* exists but is connection-scoped, not transaction-local, so it is not wired
|
|
65
|
+
* exists but is connection-scoped, not transaction-local, so it is not wired -
|
|
66
66
|
* throws rather than silently leaking context across pooled connections).
|
|
67
67
|
* - **Advisory-lock migration locking** is available in principle via
|
|
68
68
|
* `sp_getapplock`/`sp_releaseapplock` (`supportsAdvisoryLock = true`); the
|
|
69
69
|
* migrate CLI is still PostgreSQL-only, so this flag documents intent for a
|
|
70
70
|
* future adapter.
|
|
71
|
-
* - **Case-insensitive matching** uses `LOWER(col) LIKE LOWER(ref)
|
|
71
|
+
* - **Case-insensitive matching** uses `LOWER(col) LIKE LOWER(ref)`, deterministic
|
|
72
72
|
* regardless of the column's collation (note this can defeat an index unless a
|
|
73
73
|
* computed/persisted `LOWER()` index exists).
|
|
74
74
|
* - **bignum:** the shim applies the same safe-int policy Turbine uses for Postgres
|
|
75
75
|
* `int8` (number when it fits in 2^53, decimal string otherwise) WITHOUT mutating
|
|
76
76
|
* any global driver state. `DECIMAL`/`NUMERIC`/`MONEY` come back as strings;
|
|
77
77
|
* `BIT` binds/returns booleans.
|
|
78
|
-
* - **`DISTINCT ON`** is PostgreSQL-only and is not translated
|
|
78
|
+
* - **`DISTINCT ON`** is PostgreSQL-only and is not translated, avoid `distinct`
|
|
79
79
|
* on SQL Server.
|
|
80
80
|
*
|
|
81
81
|
* ## Example
|
|
@@ -146,7 +146,7 @@ type QueryArg = string | {
|
|
|
146
146
|
* physical connection.
|
|
147
147
|
*/
|
|
148
148
|
export declare class MssqlPool implements PgCompatPool {
|
|
149
|
-
/** The underlying `mssql` ConnectionPool
|
|
149
|
+
/** The underlying `mssql` ConnectionPool, exposed as an escape hatch (seed / DDL / advanced ops). */
|
|
150
150
|
readonly pool: MssqlConnectionPool;
|
|
151
151
|
private readonly sqlNS;
|
|
152
152
|
private closed;
|
|
@@ -215,7 +215,7 @@ export interface TurbineMssqlOptions extends Pick<TurbineConfig, 'logging' | 'de
|
|
|
215
215
|
* Pass one of:
|
|
216
216
|
* - a connection string (`'mssql://sa:pass@host:1433/db'`),
|
|
217
217
|
* - an `mssql` config object (`{ server, user, password, database, options }`),
|
|
218
|
-
* - an existing `MssqlPool` (injection
|
|
218
|
+
* - an existing `MssqlPool` (injection, you own its lifecycle, `disconnect()` is
|
|
219
219
|
* a no-op).
|
|
220
220
|
*
|
|
221
221
|
* When Turbine builds the pool (string/config), it probes
|