turbine-orm 0.41.0 → 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/cli/index.js +23 -3
- package/dist/cjs/cli/prisma-resolve.js +69 -16
- package/dist/cjs/generate.js +17 -3
- package/dist/cjs/introspect.js +91 -19
- package/dist/cjs/prisma-compat.js +56 -99
- package/dist/cjs/query/compound-unique.js +0 -0
- package/dist/cli/index.js +23 -3
- package/dist/cli/prisma-resolve.d.ts +14 -1
- package/dist/cli/prisma-resolve.js +70 -17
- package/dist/generate.js +17 -3
- package/dist/introspect.d.ts +32 -4
- package/dist/introspect.js +89 -19
- package/dist/prisma-compat.js +56 -99
- package/dist/query/compound-unique.d.ts +6 -4
- package/dist/query/compound-unique.js +0 -0
- package/dist/schema.d.ts +10 -0
- package/package.json +1 -1
package/dist/cjs/cli/index.js
CHANGED
|
@@ -1078,11 +1078,12 @@ async function cmdMigrateFromPrisma(args, config) {
|
|
|
1078
1078
|
(0, ui_js_1.label)('Enums', String(ast.enums.length));
|
|
1079
1079
|
// Resolve against the live database, unless --no-db (parse-only).
|
|
1080
1080
|
let schemaMeta = null;
|
|
1081
|
+
let url;
|
|
1081
1082
|
if (args.noDb) {
|
|
1082
1083
|
(0, ui_js_1.info)('Parse-only mode (--no-db): names will not be resolved.');
|
|
1083
1084
|
}
|
|
1084
1085
|
else {
|
|
1085
|
-
|
|
1086
|
+
url = requireUrl(config);
|
|
1086
1087
|
(0, ui_js_1.label)('Database', (0, ui_js_1.redactUrl)(url));
|
|
1087
1088
|
const spinner = new ui_js_1.Spinner('Introspecting database schema').start();
|
|
1088
1089
|
schemaMeta = await (0, introspect_js_1.introspect)({
|
|
@@ -1098,7 +1099,10 @@ async function cmdMigrateFromPrisma(args, config) {
|
|
|
1098
1099
|
spinner.succeed(`Introspected ${(0, ui_js_1.bold)(String(Object.keys(schemaMeta.tables).length))} tables`);
|
|
1099
1100
|
}
|
|
1100
1101
|
(0, ui_js_1.newline)();
|
|
1101
|
-
|
|
1102
|
+
// `--keep-column-names` makes the generated client key fields by raw DB column
|
|
1103
|
+
// names; resolve the name map against the same transformed schema so the
|
|
1104
|
+
// emitted PRISMA_MAP field values agree with the client (D).
|
|
1105
|
+
const result = (0, prisma_resolve_js_1.resolvePrismaSchema)(ast, schemaMeta, { keepColumnNames: config.keepColumnNames });
|
|
1102
1106
|
// Console summary.
|
|
1103
1107
|
(0, ui_js_1.header)('Resolution');
|
|
1104
1108
|
for (const line of (0, prisma_report_js_1.summaryLines)(result)) {
|
|
@@ -1118,10 +1122,26 @@ async function cmdMigrateFromPrisma(args, config) {
|
|
|
1118
1122
|
const reportPath = (0, node_path_1.join)(outDir, 'prisma-migration-report.md');
|
|
1119
1123
|
(0, node_fs_1.writeFileSync)(reportPath, (0, prisma_report_js_1.formatPrismaReport)(result, { schemaPath: prismaPath, noTimestamp: args.noTimestamp }), 'utf-8');
|
|
1120
1124
|
console.log(` ${(0, ui_js_1.dim)(ui_js_1.symbols.teeEnd)} ${(0, ui_js_1.cyan)(reportPath)} ${(0, ui_js_1.dim)('(report)')}`);
|
|
1121
|
-
if (!args.noDb) {
|
|
1125
|
+
if (!args.noDb && schemaMeta) {
|
|
1122
1126
|
const mapPath = (0, node_path_1.join)(outDir, 'prisma-map.ts');
|
|
1123
1127
|
(0, node_fs_1.writeFileSync)(mapPath, (0, generate_js_1.generatePrismaMap)(result.map, { noTimestamp: args.noTimestamp }), 'utf-8');
|
|
1124
1128
|
console.log(` ${(0, ui_js_1.dim)(ui_js_1.symbols.teeEnd)} ${(0, ui_js_1.cyan)(mapPath)} ${(0, ui_js_1.dim)('(typed name map)')}`);
|
|
1129
|
+
// Always emit the standard generated client alongside the report + name map.
|
|
1130
|
+
// It is built from the live introspected metadata, so unresolved Prisma
|
|
1131
|
+
// items never block it; a partially resolved run (--allow-partial) still
|
|
1132
|
+
// gets a working client (C). `--keep-column-names` flows through so the
|
|
1133
|
+
// client's field names match the name map (D).
|
|
1134
|
+
const gen = (0, generate_js_1.generate)({
|
|
1135
|
+
schema: schemaMeta,
|
|
1136
|
+
outDir: config.out,
|
|
1137
|
+
connectionString: url,
|
|
1138
|
+
noTimestamp: args.noTimestamp,
|
|
1139
|
+
importExtension: config.importExtension,
|
|
1140
|
+
keepColumnNames: config.keepColumnNames,
|
|
1141
|
+
});
|
|
1142
|
+
for (const file of gen.files) {
|
|
1143
|
+
console.log(` ${(0, ui_js_1.dim)(ui_js_1.symbols.teeEnd)} ${(0, ui_js_1.cyan)((0, node_path_1.join)(outDir, file))} ${(0, ui_js_1.dim)('(client)')}`);
|
|
1144
|
+
}
|
|
1125
1145
|
}
|
|
1126
1146
|
(0, ui_js_1.newline)();
|
|
1127
1147
|
// Exit non-zero when anything is UNRESOLVED, unless --allow-partial.
|
|
@@ -75,16 +75,18 @@ function fieldColumn(model, fieldName) {
|
|
|
75
75
|
function isRelationField(typeName, modelNames) {
|
|
76
76
|
return modelNames.has(typeName);
|
|
77
77
|
}
|
|
78
|
-
// ---------------------------------------------------------------------------
|
|
79
|
-
// Main entry
|
|
80
|
-
// ---------------------------------------------------------------------------
|
|
81
78
|
/**
|
|
82
79
|
* Resolve `ast` against introspected `schema` (or `null` for parse-only).
|
|
83
80
|
*/
|
|
84
|
-
function resolvePrismaSchema(ast, schema) {
|
|
85
|
-
|
|
81
|
+
function resolvePrismaSchema(ast, schema, options = {}) {
|
|
82
|
+
// Under keep-column-names the generated client keys fields by raw DB column
|
|
83
|
+
// names; resolve against the same transformed metadata so the name map's
|
|
84
|
+
// field values match the client (D).
|
|
85
|
+
const resolvedSchema = schema && options.keepColumnNames ? (0, schema_js_1.withDbFieldNames)(schema) : schema;
|
|
86
|
+
const noDb = resolvedSchema === null;
|
|
86
87
|
const modelNames = new Set(ast.models.map((m) => m.name));
|
|
87
|
-
const
|
|
88
|
+
const modelsByName = new Map(ast.models.map((m) => [m.name, m]));
|
|
89
|
+
const tableNames = resolvedSchema ? new Set(Object.keys(resolvedSchema.tables)) : new Set();
|
|
88
90
|
// Pass 1 - resolve each model to a table so relation targets are known.
|
|
89
91
|
const modelTable = new Map();
|
|
90
92
|
for (const model of ast.models) {
|
|
@@ -102,7 +104,7 @@ function resolvePrismaSchema(ast, schema) {
|
|
|
102
104
|
for (const model of ast.models) {
|
|
103
105
|
const rt = modelTable.get(model.name);
|
|
104
106
|
const table = rt.table;
|
|
105
|
-
const tableMeta = table &&
|
|
107
|
+
const tableMeta = table && resolvedSchema ? resolvedSchema.tables[table] : undefined;
|
|
106
108
|
const accessor = table ? (0, schema_js_1.snakeToCamel)(table) : null;
|
|
107
109
|
const status = noDb ? 'parsed' : table ? 'resolved' : 'unresolved';
|
|
108
110
|
const resolved = {
|
|
@@ -119,7 +121,7 @@ function resolvePrismaSchema(ast, schema) {
|
|
|
119
121
|
};
|
|
120
122
|
for (const field of model.fields) {
|
|
121
123
|
if (isRelationField(field.type, modelNames)) {
|
|
122
|
-
resolved.relations.push(resolveRelation(model, field.name, field.type, field.isList, modelTable,
|
|
124
|
+
resolved.relations.push(resolveRelation(model, field.name, field.type, field.isList, modelTable, modelsByName, resolvedSchema, tableMeta, noDb));
|
|
123
125
|
}
|
|
124
126
|
else {
|
|
125
127
|
resolved.fields.push(resolveScalarField(model, field.name, tableMeta, noDb));
|
|
@@ -156,7 +158,7 @@ function resolvePrismaSchema(ast, schema) {
|
|
|
156
158
|
}
|
|
157
159
|
// Enums.
|
|
158
160
|
for (const en of ast.enums) {
|
|
159
|
-
const r = resolveEnum(en.name, en.map,
|
|
161
|
+
const r = resolveEnum(en.name, en.map, resolvedSchema, noDb);
|
|
160
162
|
result.enums.push(r);
|
|
161
163
|
if (!noDb && r.status === 'resolved' && r.turbineName)
|
|
162
164
|
result.map.enums[en.name] = r.turbineName;
|
|
@@ -213,7 +215,28 @@ function resolveScalarField(model, fieldName, tableMeta, noDb) {
|
|
|
213
215
|
reason: `column "${column}" not found on table "${tableMeta.name}"`,
|
|
214
216
|
};
|
|
215
217
|
}
|
|
216
|
-
|
|
218
|
+
/**
|
|
219
|
+
* The `@relation("Name")` name on a field, from the `name:` argument or the
|
|
220
|
+
* first positional string argument. Absent when the field has no `@relation`
|
|
221
|
+
* attribute or the attribute carries no name.
|
|
222
|
+
*/
|
|
223
|
+
function relationNameOf(field) {
|
|
224
|
+
const relAttr = field?.attrs.find((a) => a.name === 'relation');
|
|
225
|
+
if (!relAttr)
|
|
226
|
+
return undefined;
|
|
227
|
+
const named = relAttr.args.find((a) => a.key === 'name' && a.kind === 'string');
|
|
228
|
+
if (named?.value)
|
|
229
|
+
return named.value;
|
|
230
|
+
const positional = relAttr.args.find((a) => a.key === undefined && a.kind === 'string');
|
|
231
|
+
return positional?.value;
|
|
232
|
+
}
|
|
233
|
+
/** The FK column list a field pins via `@relation(fields: [...])`, resolved to columns. */
|
|
234
|
+
function relationFkColumns(model, field) {
|
|
235
|
+
const relAttr = field?.attrs.find((a) => a.name === 'relation');
|
|
236
|
+
const fieldsArg = relAttr?.args.find((a) => a.key === 'fields' && a.kind === 'array');
|
|
237
|
+
return fieldsArg?.items?.map((pf) => fieldColumn(model, pf)) ?? null;
|
|
238
|
+
}
|
|
239
|
+
function resolveRelation(model, fieldName, targetModelName, isList, modelTable, modelsByName, schema, tableMeta, noDb) {
|
|
217
240
|
const cardinality = isList ? 'many' : 'one';
|
|
218
241
|
const base = {
|
|
219
242
|
prismaName: fieldName,
|
|
@@ -230,9 +253,24 @@ function resolveRelation(model, fieldName, targetModelName, isList, modelTable,
|
|
|
230
253
|
const targetTable = modelTable.get(targetModelName)?.table ?? null;
|
|
231
254
|
// Explicit @relation(fields: [...]) names the FK columns on THIS side.
|
|
232
255
|
const field = model.fields.find((f) => f.name === fieldName);
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
256
|
+
let fkColumns = relationFkColumns(model, field);
|
|
257
|
+
// Inverse side (no fields on this side) with a @relation("Name"): pair by that
|
|
258
|
+
// name FIRST. Find the opposing model's field carrying the same relation name
|
|
259
|
+
// AND the FK (fields: [...]), and resolve through ITS foreign key. This is how
|
|
260
|
+
// Prisma disambiguates two or more relations to the same target model. Only
|
|
261
|
+
// fall back to the ambiguity handling below when there is no relation name or
|
|
262
|
+
// the named pair cannot be found.
|
|
263
|
+
if (!fkColumns || fkColumns.length === 0) {
|
|
264
|
+
const relName = relationNameOf(field);
|
|
265
|
+
const targetModel = modelsByName.get(targetModelName);
|
|
266
|
+
if (relName && targetModel) {
|
|
267
|
+
const opposing = targetModel.fields.find((f) => f.type === model.name &&
|
|
268
|
+
relationNameOf(f) === relName &&
|
|
269
|
+
(relationFkColumns(targetModel, f)?.length ?? 0) > 0);
|
|
270
|
+
if (opposing)
|
|
271
|
+
fkColumns = relationFkColumns(targetModel, opposing);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
236
274
|
const candidates = Object.values(tableMeta.relations).filter((def) => {
|
|
237
275
|
if (targetTable && def.to !== targetTable)
|
|
238
276
|
return false;
|
|
@@ -293,10 +331,25 @@ function resolveCompoundUnique(model, key, tableMeta, noDb) {
|
|
|
293
331
|
return { ...base, turbineFields, status: 'resolved' };
|
|
294
332
|
return { ...base, reason: `no compound primary key on "${tableMeta.name}" matches (${columns.join(', ')})` };
|
|
295
333
|
}
|
|
296
|
-
//
|
|
297
|
-
|
|
334
|
+
// A composite unique can surface EITHER as a unique constraint (uniqueColumns)
|
|
335
|
+
// OR as a plain UNIQUE INDEX (Prisma creates unique indexes, not table
|
|
336
|
+
// constraints), so accept a matching unique index too. A partial unique index
|
|
337
|
+
// does not enforce uniqueness across the whole table, so it never satisfies a
|
|
338
|
+
// @@unique; skip it defensively (the marker may be added to IndexMetadata).
|
|
339
|
+
const uniqueIndexMatches = tableMeta.indexes.some((idx) => {
|
|
340
|
+
if (!idx.unique)
|
|
341
|
+
return false;
|
|
342
|
+
if (idx.partial)
|
|
343
|
+
return false;
|
|
344
|
+
return [...idx.columns].sort().join(',') === want;
|
|
345
|
+
});
|
|
346
|
+
if (matches(tableMeta.uniqueColumns) || uniqueIndexMatches) {
|
|
298
347
|
return { ...base, turbineFields, status: 'resolved' };
|
|
299
|
-
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
...base,
|
|
351
|
+
reason: `no unique constraint or unique index on "${tableMeta.name}" matches (${columns.join(', ')})`,
|
|
352
|
+
};
|
|
300
353
|
}
|
|
301
354
|
function resolveEnum(name, map, schema, noDb) {
|
|
302
355
|
if (noDb || !schema)
|
package/dist/cjs/generate.js
CHANGED
|
@@ -460,13 +460,23 @@ function generateTypes(schema, options) {
|
|
|
460
460
|
for (const uc of table.uniqueColumns)
|
|
461
461
|
addCompound(uc);
|
|
462
462
|
for (const idx of table.indexes) {
|
|
463
|
-
|
|
463
|
+
// A partial unique index does not guarantee table-wide row uniqueness,
|
|
464
|
+
// so it must not become a compound-unique selector (matches the runtime
|
|
465
|
+
// exclusion in query/compound-unique.ts).
|
|
466
|
+
if (idx.unique && !idx.docPath && !idx.partial)
|
|
464
467
|
addCompound(idx.columns);
|
|
465
468
|
}
|
|
466
469
|
const selectorEntries = compoundSets.map((cols) => {
|
|
467
470
|
const members = cols.map(memberType);
|
|
468
471
|
return {
|
|
469
|
-
|
|
472
|
+
// The selector NAME is the underscore-join of the member FIELD names.
|
|
473
|
+
// It is normally a valid identifier (`orgId_userId`), but a
|
|
474
|
+
// junction-style column that is not a valid identifier (e.g. a quoted
|
|
475
|
+
// uppercase `"A"` / `"B"`) would join into a broken object key. Emit
|
|
476
|
+
// any non-identifier name as ONE quoted string-literal key so the
|
|
477
|
+
// generated types.ts always parses; the runtime selector map in
|
|
478
|
+
// query/compound-unique.ts registers the same name spelling.
|
|
479
|
+
selectorName: quoteIfNeeded(members.map((m) => m.field).join('_')),
|
|
470
480
|
memberType: `{ ${members.map((m) => `${quoteIfNeeded(m.field)}: ${m.tsType}`).join('; ')} }`,
|
|
471
481
|
};
|
|
472
482
|
});
|
|
@@ -725,7 +735,11 @@ function generateMetadata(schema, options) {
|
|
|
725
735
|
// indexes
|
|
726
736
|
lines.push(' indexes: [');
|
|
727
737
|
for (const idx of table.indexes) {
|
|
728
|
-
|
|
738
|
+
// `partial` must round-trip: the runtime compound-unique derivation reads
|
|
739
|
+
// the GENERATED metadata, so dropping the flag here would re-arm a
|
|
740
|
+
// partial-unique selector that types.ts correctly excludes.
|
|
741
|
+
const partialSeg = idx.partial ? ', partial: true' : '';
|
|
742
|
+
lines.push(` { name: '${escSQ(idx.name)}', columns: [${idx.columns.map((c) => `'${escSQ(c)}'`).join(', ')}], unique: ${idx.unique}${partialSeg}, definition: ${JSON.stringify(idx.definition)} },`);
|
|
729
743
|
}
|
|
730
744
|
lines.push(' ],');
|
|
731
745
|
// checks: introspected named CHECK constraints. Emitted only when present
|
package/dist/cjs/introspect.js
CHANGED
|
@@ -18,6 +18,8 @@ exports.applyTableFilters = applyTableFilters;
|
|
|
18
18
|
exports.defaultExcludedTablesPresent = defaultExcludedTablesPresent;
|
|
19
19
|
exports.introspect = introspect;
|
|
20
20
|
exports.introspectPostgresCatalog = introspectPostgresCatalog;
|
|
21
|
+
exports.parseIndexColumns = parseIndexColumns;
|
|
22
|
+
exports.indexHasWhere = indexHasWhere;
|
|
21
23
|
exports.stripCheckWrapper = stripCheckWrapper;
|
|
22
24
|
exports.relationNameFromColumn = relationNameFromColumn;
|
|
23
25
|
exports.isUnknownTsType = isUnknownTsType;
|
|
@@ -414,15 +416,15 @@ async function introspectPostgresCatalog(options) {
|
|
|
414
416
|
continue;
|
|
415
417
|
if (!indexesByTable.has(row.tablename))
|
|
416
418
|
indexesByTable.set(row.tablename, []);
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
const
|
|
420
|
-
const columns = colMatch ? colMatch[1].split(',').map((c) => c.trim().replace(/ (ASC|DESC)/i, '')) : [];
|
|
419
|
+
const indexdef = row.indexdef;
|
|
420
|
+
const isUnique = indexdef.includes('UNIQUE');
|
|
421
|
+
const isPartial = indexHasWhere(indexdef);
|
|
421
422
|
indexesByTable.get(row.tablename).push({
|
|
422
423
|
name: row.indexname,
|
|
423
|
-
columns,
|
|
424
|
+
columns: parseIndexColumns(indexdef),
|
|
424
425
|
unique: isUnique,
|
|
425
|
-
definition:
|
|
426
|
+
definition: indexdef,
|
|
427
|
+
...(isPartial ? { partial: true } : {}),
|
|
426
428
|
});
|
|
427
429
|
}
|
|
428
430
|
// ----- Group check constraints by table -----
|
|
@@ -511,7 +513,17 @@ async function introspectPostgresCatalog(options) {
|
|
|
511
513
|
// hasMany relations derived from J's FKs are left untouched — this block
|
|
512
514
|
// never removes or renames anything. Naming/collision handling lives in the
|
|
513
515
|
// shared addAutoManyToManyRelations helper.
|
|
514
|
-
|
|
516
|
+
//
|
|
517
|
+
// Prisma's implicit m2m junctions have no primary key (just a two-column
|
|
518
|
+
// UNIQUE index over the FK columns), so pass the introspected two-column
|
|
519
|
+
// unique indexes as the fallback junction-key source.
|
|
520
|
+
const uniqueIndexColsByTable = new Map();
|
|
521
|
+
for (const [tbl, idxs] of indexesByTable) {
|
|
522
|
+
const twoColUniques = idxs.filter((idx) => idx.unique && idx.columns.length === 2).map((idx) => idx.columns);
|
|
523
|
+
if (twoColUniques.length > 0)
|
|
524
|
+
uniqueIndexColsByTable.set(tbl, twoColUniques);
|
|
525
|
+
}
|
|
526
|
+
addAutoManyToManyRelations(tableNames, foreignKeys, pkByTable, new Map(Array.from(columnsByTable, ([tbl, cols]) => [tbl, cols.map((c) => c.name)])), relationsByTable, columnFieldsByTable, unknownTypedFieldsByTable, uniqueIndexColsByTable);
|
|
515
527
|
// ----- Assemble TableMetadata for each table -----
|
|
516
528
|
const tables = {};
|
|
517
529
|
for (const tableName of tableNames) {
|
|
@@ -556,6 +568,50 @@ async function introspectPostgresCatalog(options) {
|
|
|
556
568
|
await pool.end();
|
|
557
569
|
}
|
|
558
570
|
}
|
|
571
|
+
/**
|
|
572
|
+
* Parse the indexed column names out of a `pg_indexes.indexdef` string.
|
|
573
|
+
*
|
|
574
|
+
* `indexdef` always reads `CREATE [UNIQUE] INDEX name ON tbl USING method
|
|
575
|
+
* (col, ...) [WHERE predicate]`. We anchor on the `USING` clause's parenthesised
|
|
576
|
+
* column list (the same precedent as `describeIndexDefMismatch` in
|
|
577
|
+
* schema-sql.ts) so a PARTIAL index's trailing `WHERE (...)` parentheses are
|
|
578
|
+
* never mistaken for the column list. The older greedy `/\((.+)\)/` swallowed
|
|
579
|
+
* `) WHERE (` and spliced a raw predicate fragment into the column names, which
|
|
580
|
+
* then leaked into generated compound-unique selector names.
|
|
581
|
+
*
|
|
582
|
+
* Each column is de-quoted (Postgres quotes non-lowercase identifiers such as a
|
|
583
|
+
* Prisma implicit m2m junction's `"A"` / `"B"`), so the names match the
|
|
584
|
+
* unquoted column names carried elsewhere in the metadata. Expression columns
|
|
585
|
+
* (anything containing a parenthesis) are dropped conservatively: a functional
|
|
586
|
+
* index does not name a plain column.
|
|
587
|
+
*/
|
|
588
|
+
function parseIndexColumns(indexdef) {
|
|
589
|
+
const m = indexdef.match(/USING\s+\w+\s*\(([^)]*)\)/i) ?? indexdef.match(/\(([^)]*)\)/);
|
|
590
|
+
if (!m)
|
|
591
|
+
return [];
|
|
592
|
+
return m[1]
|
|
593
|
+
.split(',')
|
|
594
|
+
.map((c) => unquoteIndexIdent(c
|
|
595
|
+
.trim()
|
|
596
|
+
.replace(/\s+(ASC|DESC)$/i, '')
|
|
597
|
+
.trim()))
|
|
598
|
+
.filter((c) => c.length > 0 && !c.includes('(') && !c.includes(')'));
|
|
599
|
+
}
|
|
600
|
+
/** Strip one pair of surrounding double quotes and unescape doubled `""`. */
|
|
601
|
+
function unquoteIndexIdent(col) {
|
|
602
|
+
if (col.length >= 2 && col.startsWith('"') && col.endsWith('"')) {
|
|
603
|
+
return col.slice(1, -1).replace(/""/g, '"');
|
|
604
|
+
}
|
|
605
|
+
return col;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Whether an `indexdef` carries a top-level `WHERE` predicate (a PARTIAL index).
|
|
609
|
+
* pg_indexes only ever emits `WHERE` as the partial predicate, so a keyword
|
|
610
|
+
* match is sufficient (matches the `describeIndexDefMismatch` precedent).
|
|
611
|
+
*/
|
|
612
|
+
function indexHasWhere(indexdef) {
|
|
613
|
+
return /\bWHERE\b/i.test(indexdef);
|
|
614
|
+
}
|
|
559
615
|
/**
|
|
560
616
|
* Recover the raw check expression from `pg_get_constraintdef` output, which
|
|
561
617
|
* wraps it as `CHECK ((expr))`. Strips the leading `CHECK ` keyword and one
|
|
@@ -900,11 +956,15 @@ function buildRelationsFromForeignKeys(foreignKeys, columnFieldsByTable, fkActio
|
|
|
900
956
|
* IDENTICAL relation names for the same logical schema.
|
|
901
957
|
*
|
|
902
958
|
* A table J is a PURE junction only when ALL of these hold:
|
|
903
|
-
* 1. J's
|
|
959
|
+
* 1. J's junction KEY is exactly two columns: either a two-column primary
|
|
960
|
+
* key, OR (Prisma implicit m2m junctions have NO primary key) a two-column
|
|
961
|
+
* UNIQUE index over exactly the two FK columns, supplied via the optional
|
|
962
|
+
* `uniqueIndexColsByTable`. When that map is absent the behavior is
|
|
963
|
+
* unchanged: only a two-column PK qualifies.
|
|
904
964
|
* 2. J has exactly two FKs, each single-column.
|
|
905
|
-
* 3. Each FK's source column is one of J's two
|
|
965
|
+
* 3. Each FK's source column is one of J's two key columns.
|
|
906
966
|
* 4. The two FKs target two DISTINCT tables (A and B).
|
|
907
|
-
* 5. J has no payload columns beyond the two FK/
|
|
967
|
+
* 5. J has no payload columns beyond the two FK/key columns.
|
|
908
968
|
*
|
|
909
969
|
* For such a J linking A and B this ADDS a `manyToMany` on A → B and B → A
|
|
910
970
|
* routed `through` J. It never removes or renames an existing relation:
|
|
@@ -915,11 +975,8 @@ function buildRelationsFromForeignKeys(foreignKeys, columnFieldsByTable, fkActio
|
|
|
915
975
|
* - a shadowed concrete-typed column → deterministic `Rel` suffix + warn
|
|
916
976
|
* instead of silently dropping the relation.
|
|
917
977
|
*/
|
|
918
|
-
function addAutoManyToManyRelations(tableNames, foreignKeys, pkByTable, columnNamesByTable, relationsByTable, columnFieldsByTable, unknownTypedFieldsByTable) {
|
|
978
|
+
function addAutoManyToManyRelations(tableNames, foreignKeys, pkByTable, columnNamesByTable, relationsByTable, columnFieldsByTable, unknownTypedFieldsByTable, uniqueIndexColsByTable) {
|
|
919
979
|
for (const tableName of tableNames) {
|
|
920
|
-
const pk = pkByTable.get(tableName) ?? [];
|
|
921
|
-
if (pk.length !== 2)
|
|
922
|
-
continue;
|
|
923
980
|
// FKs whose source is this table — both must be single-column.
|
|
924
981
|
const tableFks = foreignKeys.filter((fk) => fk.sourceTable === tableName);
|
|
925
982
|
if (tableFks.length !== 2)
|
|
@@ -927,20 +984,35 @@ function addAutoManyToManyRelations(tableNames, foreignKeys, pkByTable, columnNa
|
|
|
927
984
|
if (tableFks.some((fk) => fk.sourceColumns.length !== 1))
|
|
928
985
|
continue;
|
|
929
986
|
const fkCols = tableFks.map((fk) => fk.sourceColumns[0]);
|
|
930
|
-
const pkSet = new Set(pk);
|
|
931
|
-
// Both FK columns must be the PK columns (and vice-versa).
|
|
932
|
-
if (!fkCols.every((c) => pkSet.has(c)))
|
|
933
|
-
continue;
|
|
934
987
|
if (new Set(fkCols).size !== 2)
|
|
935
988
|
continue;
|
|
989
|
+
const fkSet = new Set(fkCols);
|
|
990
|
+
// The junction KEY is normally the two-column PK. Prisma's implicit m2m
|
|
991
|
+
// junctions have NO primary key, so accept instead a two-column UNIQUE
|
|
992
|
+
// index that covers exactly the two FK columns. Only a PK-less table is
|
|
993
|
+
// eligible for the unique-index fallback, so a real entity that happens to
|
|
994
|
+
// carry a two-column unique index is never mistaken for a junction.
|
|
995
|
+
const pk = pkByTable.get(tableName) ?? [];
|
|
996
|
+
let keyCols;
|
|
997
|
+
if (pk.length === 2 && pk.every((c) => fkSet.has(c))) {
|
|
998
|
+
keyCols = pk;
|
|
999
|
+
}
|
|
1000
|
+
else if (pk.length === 0) {
|
|
1001
|
+
const uniques = uniqueIndexColsByTable?.get(tableName) ?? [];
|
|
1002
|
+
keyCols = uniques.find((u) => u.length === 2 && u.every((c) => fkSet.has(c)));
|
|
1003
|
+
}
|
|
1004
|
+
if (!keyCols)
|
|
1005
|
+
continue;
|
|
936
1006
|
// Two DISTINCT target tables.
|
|
937
1007
|
const [fkA, fkB] = tableFks;
|
|
938
1008
|
if (fkA.targetTable === fkB.targetTable)
|
|
939
1009
|
continue;
|
|
940
|
-
// No payload columns: J's columns are exactly the two FK/
|
|
1010
|
+
// No payload columns: J's columns are exactly the two FK/key columns.
|
|
941
1011
|
const jCols = columnNamesByTable.get(tableName) ?? [];
|
|
942
1012
|
if (jCols.length !== 2)
|
|
943
1013
|
continue;
|
|
1014
|
+
if (!jCols.every((c) => fkSet.has(c)))
|
|
1015
|
+
continue;
|
|
944
1016
|
// For each direction, the m2m `referenceKey` is the *targeted* table's
|
|
945
1017
|
// referenced column(s); the junction's sourceKey is the FK column pointing
|
|
946
1018
|
// to that table; the targetKey is the FK column pointing to the OTHER table.
|
|
@@ -848,14 +848,28 @@ function prismaPropertyAlias(model) {
|
|
|
848
848
|
*/
|
|
849
849
|
function makeDelegate(ctx, mm, getQI) {
|
|
850
850
|
const pe = ctx.options.prismaErrorCodes;
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
851
|
+
// Build a lazy Prisma-style promise for one delegate call. Crucially, the
|
|
852
|
+
// Prisma-arg `translate` step runs INSIDE the deferred paths (the run closure
|
|
853
|
+
// and the batchable build closure), never eagerly at call time: a
|
|
854
|
+
// translation/validation error (unknown relation in `include`, unknown
|
|
855
|
+
// compound selector, negative take, ...) must surface as a REJECTED promise
|
|
856
|
+
// so a Prisma-shaped `.catch()` fires, not as a synchronous throw. The
|
|
857
|
+
// async run wrapper also converts any synchronous throw from the underlying
|
|
858
|
+
// `qi.*` build into a rejection; the array `$transaction([...])` batch path
|
|
859
|
+
// catches the same throw from `batch.build`.
|
|
860
|
+
const defer = (translate, run, batch) => {
|
|
861
|
+
const batchable = batch
|
|
862
|
+
? { build: () => batch.build(getQI(), translate()), reshape: batch.reshape }
|
|
863
|
+
: undefined;
|
|
864
|
+
return new CompatPromise(async () => {
|
|
865
|
+
try {
|
|
866
|
+
return await run(getQI(), translate());
|
|
867
|
+
}
|
|
868
|
+
catch (err) {
|
|
869
|
+
throw decorate(err, pe);
|
|
870
|
+
}
|
|
871
|
+
}, batchable);
|
|
872
|
+
};
|
|
859
873
|
const requireWhere = (args, op) => {
|
|
860
874
|
if (!args || args.where === undefined) {
|
|
861
875
|
throw new errors_js_1.ValidationError(`[turbine] prisma-compat: ${op} on "${modelName(ctx, mm)}" requires a \`where\`.`);
|
|
@@ -863,125 +877,68 @@ function makeDelegate(ctx, mm, getQI) {
|
|
|
863
877
|
return args;
|
|
864
878
|
};
|
|
865
879
|
return {
|
|
866
|
-
findMany: (args = {}) => {
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
findFirst: (args = {}) => {
|
|
873
|
-
const t = translateReadArgs(ctx, mm, args);
|
|
874
|
-
return lift(() => getQI()
|
|
875
|
-
.findFirst(t)
|
|
876
|
-
.then((r) => reshapeRowOrNull(ctx, mm, r)), { build: () => getQI().buildFindFirst(t), reshape: (raw) => reshapeRowOrNull(ctx, mm, raw) });
|
|
877
|
-
},
|
|
878
|
-
findUnique: (args) => {
|
|
879
|
-
const t = translateReadArgs(ctx, mm, requireWhere(args, 'findUnique'));
|
|
880
|
-
return lift(() => getQI()
|
|
881
|
-
.findUnique(t)
|
|
882
|
-
.then((r) => reshapeRowOrNull(ctx, mm, r)), { build: () => getQI().buildFindUnique(t), reshape: (raw) => reshapeRowOrNull(ctx, mm, raw) });
|
|
883
|
-
},
|
|
884
|
-
findFirstOrThrow: (args = {}) => {
|
|
885
|
-
const t = translateReadArgs(ctx, mm, args);
|
|
886
|
-
return lift(() => getQI()
|
|
887
|
-
.findFirstOrThrow(t)
|
|
888
|
-
.then((r) => reshapeRow(ctx, mm, r)), { build: () => getQI().buildFindFirstOrThrow(t), reshape: (raw) => reshapeRow(ctx, mm, raw) });
|
|
889
|
-
},
|
|
890
|
-
findUniqueOrThrow: (args) => {
|
|
891
|
-
const t = translateReadArgs(ctx, mm, requireWhere(args, 'findUniqueOrThrow'));
|
|
892
|
-
return lift(() => getQI()
|
|
893
|
-
.findUniqueOrThrow(t)
|
|
894
|
-
.then((r) => reshapeRow(ctx, mm, r)), { build: () => getQI().buildFindUniqueOrThrow(t), reshape: (raw) => reshapeRow(ctx, mm, raw) });
|
|
895
|
-
},
|
|
896
|
-
create: (args) => {
|
|
880
|
+
findMany: (args = {}) => defer(() => translateReadArgs(ctx, mm, args), (qi, t) => qi.findMany(t).then((r) => reshapeRows(ctx, mm, r)), { build: (qi, t) => qi.buildFindMany(t), reshape: (raw) => reshapeRows(ctx, mm, raw) }),
|
|
881
|
+
findFirst: (args = {}) => defer(() => translateReadArgs(ctx, mm, args), (qi, t) => qi.findFirst(t).then((r) => reshapeRowOrNull(ctx, mm, r)), { build: (qi, t) => qi.buildFindFirst(t), reshape: (raw) => reshapeRowOrNull(ctx, mm, raw) }),
|
|
882
|
+
findUnique: (args) => defer(() => translateReadArgs(ctx, mm, requireWhere(args, 'findUnique')), (qi, t) => qi.findUnique(t).then((r) => reshapeRowOrNull(ctx, mm, r)), { build: (qi, t) => qi.buildFindUnique(t), reshape: (raw) => reshapeRowOrNull(ctx, mm, raw) }),
|
|
883
|
+
findFirstOrThrow: (args = {}) => defer(() => translateReadArgs(ctx, mm, args), (qi, t) => qi.findFirstOrThrow(t).then((r) => reshapeRow(ctx, mm, r)), { build: (qi, t) => qi.buildFindFirstOrThrow(t), reshape: (raw) => reshapeRow(ctx, mm, raw) }),
|
|
884
|
+
findUniqueOrThrow: (args) => defer(() => translateReadArgs(ctx, mm, requireWhere(args, 'findUniqueOrThrow')), (qi, t) => qi.findUniqueOrThrow(t).then((r) => reshapeRow(ctx, mm, r)), { build: (qi, t) => qi.buildFindUniqueOrThrow(t), reshape: (raw) => reshapeRow(ctx, mm, raw) }),
|
|
885
|
+
create: (args) => defer(() => {
|
|
897
886
|
const t = { data: translateWriteData(ctx, mm, args.data) };
|
|
898
887
|
if (typeof args.timeout === 'number')
|
|
899
888
|
t.timeout = args.timeout;
|
|
900
|
-
return
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
},
|
|
904
|
-
createMany: (args) => {
|
|
889
|
+
return t;
|
|
890
|
+
}, (qi, t) => qi.create(t).then((r) => reshapeRow(ctx, mm, r)), { build: (qi, t) => qi.buildCreate(t), reshape: (raw) => reshapeRow(ctx, mm, raw) }),
|
|
891
|
+
createMany: (args) => defer(() => {
|
|
905
892
|
const data = args.data;
|
|
906
893
|
const rows = Array.isArray(data) ? data.map((d) => translateWriteData(ctx, mm, d)) : [];
|
|
907
894
|
const t = { data: rows };
|
|
908
895
|
if (args.skipDuplicates)
|
|
909
896
|
t.skipDuplicates = true;
|
|
910
|
-
return
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
},
|
|
914
|
-
update: (args) => {
|
|
897
|
+
return t;
|
|
898
|
+
}, (qi, t) => qi.createMany(t).then((r) => ({ count: r.length })), { build: (qi, t) => qi.buildCreateMany(t), reshape: (raw) => ({ count: raw.length }) }),
|
|
899
|
+
update: (args) => defer(() => {
|
|
915
900
|
const a = requireWhere(args, 'update');
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
.then((r) => reshapeRow(ctx, mm, r)), { build: () => getQI().buildUpdate(t), reshape: (raw) => reshapeRow(ctx, mm, raw) });
|
|
920
|
-
},
|
|
921
|
-
updateMany: (args) => {
|
|
901
|
+
return { where: translateWhere(ctx, mm, a.where), data: translateWriteData(ctx, mm, a.data) };
|
|
902
|
+
}, (qi, t) => qi.update(t).then((r) => reshapeRow(ctx, mm, r)), { build: (qi, t) => qi.buildUpdate(t), reshape: (raw) => reshapeRow(ctx, mm, raw) }),
|
|
903
|
+
updateMany: (args) => defer(() => {
|
|
922
904
|
const a = args;
|
|
923
905
|
const t = { where: translateWhere(ctx, mm, a.where ?? {}), data: translateWriteData(ctx, mm, a.data) };
|
|
924
906
|
if (a.where === undefined)
|
|
925
907
|
t.allowFullTableScan = true;
|
|
926
|
-
return
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
});
|
|
930
|
-
},
|
|
931
|
-
delete: (args) => {
|
|
908
|
+
return t;
|
|
909
|
+
}, (qi, t) => qi.updateMany(t), { build: (qi, t) => qi.buildUpdateMany(t), reshape: (raw) => raw }),
|
|
910
|
+
delete: (args) => defer(() => {
|
|
932
911
|
const a = requireWhere(args, 'delete');
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
.then((r) => reshapeRow(ctx, mm, r)), { build: () => getQI().buildDelete(t), reshape: (raw) => reshapeRow(ctx, mm, raw) });
|
|
937
|
-
},
|
|
938
|
-
deleteMany: (args = {}) => {
|
|
912
|
+
return { where: translateWhere(ctx, mm, a.where) };
|
|
913
|
+
}, (qi, t) => qi.delete(t).then((r) => reshapeRow(ctx, mm, r)), { build: (qi, t) => qi.buildDelete(t), reshape: (raw) => reshapeRow(ctx, mm, raw) }),
|
|
914
|
+
deleteMany: (args = {}) => defer(() => {
|
|
939
915
|
const a = args;
|
|
940
916
|
const t = { where: translateWhere(ctx, mm, a.where ?? {}) };
|
|
941
917
|
if (a.where === undefined)
|
|
942
918
|
t.allowFullTableScan = true;
|
|
943
|
-
return
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
});
|
|
947
|
-
},
|
|
948
|
-
upsert: (args) => {
|
|
919
|
+
return t;
|
|
920
|
+
}, (qi, t) => qi.deleteMany(t), { build: (qi, t) => qi.buildDeleteMany(t), reshape: (raw) => raw }),
|
|
921
|
+
upsert: (args) => defer(() => {
|
|
949
922
|
const a = requireWhere(args, 'upsert');
|
|
950
|
-
|
|
923
|
+
return {
|
|
951
924
|
where: translateWhere(ctx, mm, a.where),
|
|
952
925
|
create: translateWriteData(ctx, mm, a.create),
|
|
953
926
|
update: translateWriteData(ctx, mm, a.update),
|
|
954
927
|
};
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
.then((r) => reshapeRow(ctx, mm, r)), { build: () => getQI().buildUpsert(t), reshape: (raw) => reshapeRow(ctx, mm, raw) });
|
|
958
|
-
},
|
|
959
|
-
count: (args = {}) => {
|
|
928
|
+
}, (qi, t) => qi.upsert(t).then((r) => reshapeRow(ctx, mm, r)), { build: (qi, t) => qi.buildUpsert(t), reshape: (raw) => reshapeRow(ctx, mm, raw) }),
|
|
929
|
+
count: (args = {}) => defer(() => {
|
|
960
930
|
const t = {};
|
|
961
931
|
if (args.where !== undefined)
|
|
962
932
|
t.where = translateWhere(ctx, mm, args.where);
|
|
963
933
|
if (typeof args.timeout === 'number')
|
|
964
934
|
t.timeout = args.timeout;
|
|
965
|
-
return
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
return lift(() => getQI()
|
|
973
|
-
.aggregate(t)
|
|
974
|
-
.then((r) => reshapeAggregate(ctx, mm, r)), { build: () => getQI().buildAggregate(t), reshape: (raw) => reshapeAggregate(ctx, mm, raw) });
|
|
975
|
-
},
|
|
976
|
-
groupBy: (args) => {
|
|
977
|
-
const t = translateAggregateArgs(ctx, mm, args, true);
|
|
978
|
-
return lift(() => getQI()
|
|
979
|
-
.groupBy(t)
|
|
980
|
-
.then((rows) => rows.map((r) => reshapeGroupRow(ctx, mm, r))), {
|
|
981
|
-
build: () => getQI().buildGroupBy(t),
|
|
982
|
-
reshape: (raw) => raw.map((r) => reshapeGroupRow(ctx, mm, r)),
|
|
983
|
-
});
|
|
984
|
-
},
|
|
935
|
+
return t;
|
|
936
|
+
}, (qi, t) => qi.count(t), { build: (qi, t) => qi.buildCount(t), reshape: (raw) => raw }),
|
|
937
|
+
aggregate: (args) => defer(() => translateAggregateArgs(ctx, mm, args, false), (qi, t) => qi.aggregate(t).then((r) => reshapeAggregate(ctx, mm, r)), { build: (qi, t) => qi.buildAggregate(t), reshape: (raw) => reshapeAggregate(ctx, mm, raw) }),
|
|
938
|
+
groupBy: (args) => defer(() => translateAggregateArgs(ctx, mm, args, true), (qi, t) => qi.groupBy(t).then((rows) => rows.map((r) => reshapeGroupRow(ctx, mm, r))), {
|
|
939
|
+
build: (qi, t) => qi.buildGroupBy(t),
|
|
940
|
+
reshape: (raw) => raw.map((r) => reshapeGroupRow(ctx, mm, r)),
|
|
941
|
+
}),
|
|
985
942
|
};
|
|
986
943
|
}
|
|
987
944
|
function poolOf(db) {
|
|
Binary file
|