turbine-orm 0.40.1 → 0.41.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 +22 -4
- package/dist/cjs/cli/config.js +3 -0
- package/dist/cjs/cli/index.js +179 -0
- package/dist/cjs/cli/prisma-report.js +216 -0
- package/dist/cjs/cli/prisma-resolve.js +335 -0
- package/dist/cjs/cli/prisma-schema.js +484 -0
- package/dist/cjs/client.js +1 -0
- package/dist/cjs/generate.js +279 -22
- package/dist/cjs/index.js +3 -2
- package/dist/cjs/introspect.js +203 -26
- package/dist/cjs/mssql.js +9 -10
- package/dist/cjs/mysql.js +3 -9
- package/dist/cjs/powdb-introspect.js +5 -10
- package/dist/cjs/powql.js +13 -0
- package/dist/cjs/prisma-compat.js +1147 -0
- package/dist/cjs/query/aggregates.js +67 -7
- package/dist/cjs/query/builder.js +388 -17
- package/dist/cjs/query/compound-unique.js +0 -0
- package/dist/cjs/query/relations.js +7 -5
- package/dist/cjs/query/warn-registry.js +98 -0
- package/dist/cjs/query/writes.js +13 -5
- package/dist/cjs/schema.js +47 -0
- package/dist/cjs/sqlite.js +4 -9
- package/dist/cli/config.d.ts +26 -0
- package/dist/cli/config.js +3 -0
- package/dist/cli/index.d.ts +11 -0
- package/dist/cli/index.js +180 -1
- package/dist/cli/prisma-report.d.ts +19 -0
- package/dist/cli/prisma-report.js +211 -0
- package/dist/cli/prisma-resolve.d.ts +87 -0
- package/dist/cli/prisma-resolve.js +330 -0
- package/dist/cli/prisma-schema.d.ts +116 -0
- package/dist/cli/prisma-schema.js +479 -0
- package/dist/cli/ui.d.ts +1 -1
- package/dist/client.d.ts +18 -2
- package/dist/client.js +1 -0
- package/dist/generate.d.ts +80 -1
- package/dist/generate.js +277 -25
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/introspect.d.ts +92 -2
- package/dist/introspect.js +198 -26
- package/dist/mssql.js +10 -11
- package/dist/mysql.js +4 -10
- package/dist/powdb-introspect.js +5 -10
- package/dist/powql.js +13 -0
- package/dist/prisma-compat.d.ts +281 -0
- package/dist/prisma-compat.js +1143 -0
- package/dist/query/aggregates.js +67 -7
- package/dist/query/builder.d.ts +77 -4
- package/dist/query/builder.js +390 -19
- package/dist/query/compound-unique.d.ts +49 -0
- package/dist/query/compound-unique.js +0 -0
- package/dist/query/deferred.d.ts +18 -0
- package/dist/query/relations.js +7 -5
- package/dist/query/types.d.ts +70 -9
- package/dist/query/warn-registry.d.ts +57 -0
- package/dist/query/warn-registry.js +92 -0
- package/dist/query/writes.js +13 -5
- package/dist/schema.d.ts +75 -0
- package/dist/schema.js +46 -0
- package/dist/sqlite.js +5 -10
- package/package.json +6 -1
|
@@ -89,10 +89,9 @@ const schema_js_1 = require("../schema.js");
|
|
|
89
89
|
const batched_loader_js_1 = require("./batched-loader.js");
|
|
90
90
|
const filters_js_1 = require("./filters.js");
|
|
91
91
|
const utils_js_1 = require("./utils.js");
|
|
92
|
+
const warn_registry_js_1 = require("./warn-registry.js");
|
|
92
93
|
const whereMod = __importStar(require("./where.js"));
|
|
93
94
|
const writesMod = __importStar(require("./writes.js"));
|
|
94
|
-
/** Relations already warned about missing FK indexes (once per process, dev only). */
|
|
95
|
-
const unindexedRelationWarned = new Set();
|
|
96
95
|
/**
|
|
97
96
|
* Resolve select/omit options into a list of snake_case column names.
|
|
98
97
|
* Returns null if neither is provided (meaning all columns).
|
|
@@ -1397,10 +1396,13 @@ function buildRelationSubquery(qi, relDef, spec, params, parentRef, aliasCounter
|
|
|
1397
1396
|
// instead of letting the slowness look like an ORM problem.
|
|
1398
1397
|
if (process.env.NODE_ENV !== 'production') {
|
|
1399
1398
|
const warnKey = `${relDef.from}.${relDef.name}`;
|
|
1400
|
-
|
|
1399
|
+
// Compute the (potentially costly) probe only for a key not yet warned, and
|
|
1400
|
+
// claim the key through the process-wide registry ONLY when we actually warn,
|
|
1401
|
+
// so a dual-package / HMR-reevaluated second module copy cannot re-warn the
|
|
1402
|
+
// same relation, and indexed relations never consume the dedupe cap.
|
|
1403
|
+
if (!(0, warn_registry_js_1.hasWarnedOnce)(warn_registry_js_1.WARN_NS.unindexedRelation, warnKey)) {
|
|
1401
1404
|
const miss = (0, index_advisor_js_1.missingIndexForRelation)(qi.schema, relDef);
|
|
1402
|
-
if (miss) {
|
|
1403
|
-
unindexedRelationWarned.add(warnKey);
|
|
1405
|
+
if (miss && (0, warn_registry_js_1.shouldWarnOnce)(warn_registry_js_1.WARN_NS.unindexedRelation, warnKey)) {
|
|
1404
1406
|
console.warn(`[turbine] Relation "${relDef.name}" on "${relDef.from}" probes ` +
|
|
1405
1407
|
`"${miss.table}"(${miss.columns.join(', ')}) which has no covering index — ` +
|
|
1406
1408
|
`each parent row scans the full table. Fix: ${miss.createSql}; ` +
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* turbine-orm, process-wide once-per-key dev-warning dedupe registry.
|
|
4
|
+
*
|
|
5
|
+
* Several dev-only diagnostics (the missing-FK-index warning in relations.ts,
|
|
6
|
+
* the `relationLoadStrategy: 'auto'` engagement note, the deep-`with` warning)
|
|
7
|
+
* must fire AT MOST ONCE per distinct key for the life of the process. A
|
|
8
|
+
* module-level `Set` almost does this, but it is defeated by the two field
|
|
9
|
+
* realities this package actually ships into:
|
|
10
|
+
*
|
|
11
|
+
* 1. **Dual-package loading.** Turbine ships ESM (`dist/`) AND CJS
|
|
12
|
+
* (`dist/cjs/`). A mixed `require`/`import` graph (a compat layer, a tool
|
|
13
|
+
* that loads both) instantiates the module twice, giving two independent
|
|
14
|
+
* `Set`s that each warn once, a double warning.
|
|
15
|
+
* 2. **Bundler / HMR re-evaluation.** Under Next.js dev the module is
|
|
16
|
+
* re-evaluated per recompile, resetting a module-level `Set` and making the
|
|
17
|
+
* warning appear to fire every time.
|
|
18
|
+
*
|
|
19
|
+
* Hanging the registry off `globalThis` under a `Symbol.for(...)` key gives every
|
|
20
|
+
* module copy in the realm ONE shared registry (cross-copy identity without
|
|
21
|
+
* polluting enumerable globals), and `globalThis` survives webpack recompiles
|
|
22
|
+
* because the realm persists, which is exactly what fixes the every-recompile
|
|
23
|
+
* firing in dev servers. Per-process firing (worker threads, separate processes)
|
|
24
|
+
* is acceptable and stays.
|
|
25
|
+
*
|
|
26
|
+
* Bounded: each namespace stops recording AND stops warning once it reaches
|
|
27
|
+
* {@link WARN_ONCE_CAP} distinct keys. A schema with 500+ distinct unindexed
|
|
28
|
+
* relations has long since gotten the message, and the cap prevents unbounded
|
|
29
|
+
* growth if metadata objects are churned dynamically. (Clearing on overflow
|
|
30
|
+
* would be wrong, it would re-warn.)
|
|
31
|
+
*/
|
|
32
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
+
exports.WARN_NS = exports.WARN_ONCE_CAP = void 0;
|
|
34
|
+
exports.shouldWarnOnce = shouldWarnOnce;
|
|
35
|
+
exports.hasWarnedOnce = hasWarnedOnce;
|
|
36
|
+
exports.resetWarnOnce = resetWarnOnce;
|
|
37
|
+
const REGISTRY_KEY = Symbol.for('turbine.warnOnce.registry');
|
|
38
|
+
/** Per-namespace cap on distinct recorded keys (see module doc). */
|
|
39
|
+
exports.WARN_ONCE_CAP = 500;
|
|
40
|
+
function registry() {
|
|
41
|
+
const g = globalThis;
|
|
42
|
+
let reg = g[REGISTRY_KEY];
|
|
43
|
+
if (!reg) {
|
|
44
|
+
reg = Object.create(null);
|
|
45
|
+
g[REGISTRY_KEY] = reg;
|
|
46
|
+
}
|
|
47
|
+
return reg;
|
|
48
|
+
}
|
|
49
|
+
function namespaceSet(ns) {
|
|
50
|
+
const reg = registry();
|
|
51
|
+
let set = reg[ns];
|
|
52
|
+
if (!set) {
|
|
53
|
+
set = new Set();
|
|
54
|
+
reg[ns] = set;
|
|
55
|
+
}
|
|
56
|
+
return set;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Record `(ns, key)` and report whether THIS call is the first to see it
|
|
60
|
+
* process-wide. Returns `true` exactly once per distinct key (the caller should
|
|
61
|
+
* emit its warning then), `false` on every subsequent call for that key, and
|
|
62
|
+
* `false` once the namespace has recorded {@link WARN_ONCE_CAP} distinct keys
|
|
63
|
+
* (bounded growth; the warning simply stops rather than re-firing).
|
|
64
|
+
*/
|
|
65
|
+
function shouldWarnOnce(ns, key) {
|
|
66
|
+
const set = namespaceSet(ns);
|
|
67
|
+
if (set.has(key))
|
|
68
|
+
return false;
|
|
69
|
+
if (set.size >= exports.WARN_ONCE_CAP)
|
|
70
|
+
return false;
|
|
71
|
+
set.add(key);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
/** True when `(ns, key)` has already been recorded (no mutation). */
|
|
75
|
+
function hasWarnedOnce(ns, key) {
|
|
76
|
+
return namespaceSet(ns).has(key);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* @internal Test-only: clear one namespace, or the whole registry when `ns` is
|
|
80
|
+
* omitted. Lets a single test process verify that a warning fires once and then
|
|
81
|
+
* re-verify after a reset without spawning a new process.
|
|
82
|
+
*/
|
|
83
|
+
function resetWarnOnce(ns) {
|
|
84
|
+
if (ns === undefined) {
|
|
85
|
+
globalThis[REGISTRY_KEY] = undefined;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
registry()[ns] = undefined;
|
|
89
|
+
}
|
|
90
|
+
/** Namespace constants so callers never typo a bare string. */
|
|
91
|
+
exports.WARN_NS = {
|
|
92
|
+
/** Missing-FK-index runtime warning (relations.ts `buildRelationSubquery`). */
|
|
93
|
+
unindexedRelation: 'unindexedRelation',
|
|
94
|
+
/** `relationLoadStrategy: 'auto'` batched-fallback engagement note. */
|
|
95
|
+
autoStrategy: 'autoStrategy',
|
|
96
|
+
/** Deep-`with` (depth > 5) advisory (builder.ts `findMany`). */
|
|
97
|
+
deepWith: 'deepWith',
|
|
98
|
+
};
|
package/dist/cjs/query/writes.js
CHANGED
|
@@ -66,6 +66,7 @@ exports.fingerprintSet = fingerprintSet;
|
|
|
66
66
|
exports.collectSetParams = collectSetParams;
|
|
67
67
|
const errors_js_1 = require("../errors.js");
|
|
68
68
|
const schema_js_1 = require("../schema.js");
|
|
69
|
+
const compound_unique_js_1 = require("./compound-unique.js");
|
|
69
70
|
const filters_js_1 = require("./filters.js");
|
|
70
71
|
const whereMod = __importStar(require("./where.js"));
|
|
71
72
|
/**
|
|
@@ -187,7 +188,10 @@ function buildUpdate(qi, args) {
|
|
|
187
188
|
qi.currentSkip = args.skipGlobalFilters;
|
|
188
189
|
const dataObj = args.data;
|
|
189
190
|
assertNoGeneratedColumns(qi, dataObj, 'update');
|
|
190
|
-
|
|
191
|
+
// Prisma compound-unique selector (e.g. `{ orgId_userId: { orgId, userId } }`)
|
|
192
|
+
// → the column conjunction, before the empty-`where` guard so the expanded
|
|
193
|
+
// members count as a real predicate.
|
|
194
|
+
const userWhere = (0, compound_unique_js_1.expandCompoundUniqueWhere)(qi.tableMeta, args.where);
|
|
191
195
|
const lock = args.optimisticLock;
|
|
192
196
|
// The empty-`where` guard checks the USER predicate only — a global filter
|
|
193
197
|
// must never turn an unguarded mass update into an allowed one.
|
|
@@ -293,9 +297,11 @@ function buildUpdate(qi, args) {
|
|
|
293
297
|
function buildDelete(qi, args) {
|
|
294
298
|
assertWritable(qi, 'delete');
|
|
295
299
|
qi.currentSkip = args.skipGlobalFilters;
|
|
300
|
+
// Prisma compound-unique selector → the column conjunction (before the guard).
|
|
301
|
+
const userWhere = (0, compound_unique_js_1.expandCompoundUniqueWhere)(qi.tableMeta, args.where);
|
|
296
302
|
// Guard the USER predicate (a global filter must not satisfy the guard).
|
|
297
|
-
whereMod.assertMutationHasPredicate(qi, 'delete', whereMod.userPredicateIsEmpty(qi,
|
|
298
|
-
const whereObj = (whereMod.mergeGlobalFilter(qi,
|
|
303
|
+
whereMod.assertMutationHasPredicate(qi, 'delete', whereMod.userPredicateIsEmpty(qi, userWhere) ? '' : ' WHERE x', args.allowFullTableScan);
|
|
304
|
+
const whereObj = (whereMod.mergeGlobalFilter(qi, userWhere) ?? {});
|
|
299
305
|
const whereFp = whereMod.fingerprintWhere(qi, whereObj);
|
|
300
306
|
const ck = `d:${whereFp}${whereMod.globalFilterCacheSegment(qi)}`;
|
|
301
307
|
const params = [];
|
|
@@ -346,6 +352,8 @@ function buildUpsert(qi, args) {
|
|
|
346
352
|
assertNoGeneratedColumns(qi, args.create, 'upsert');
|
|
347
353
|
assertNoGeneratedColumns(qi, args.update, 'upsert');
|
|
348
354
|
qi.currentSkip = args.skipGlobalFilters;
|
|
355
|
+
// Prisma compound-unique selector on the conflict target → its member columns.
|
|
356
|
+
const upsertWhere = (0, compound_unique_js_1.expandCompoundUniqueWhere)(qi.tableMeta, args.where);
|
|
349
357
|
// Build the INSERT part from create data
|
|
350
358
|
const createEntries = Object.entries(args.create).filter(([, v]) => v !== undefined);
|
|
351
359
|
const columns = createEntries.map(([k]) => qi.toSqlColumn(k));
|
|
@@ -353,7 +361,7 @@ function buildUpsert(qi, args) {
|
|
|
353
361
|
// Enum columns get an explicit `::"EnumName"` cast (see enumTypeForColumn).
|
|
354
362
|
const placeholders = createEntries.map(([k], i) => `${qi.p(i + 1)}${whereMod.enumCastSuffix(qi, qi.toColumn(k))}`);
|
|
355
363
|
// The conflict target comes from `where` keys — must be unique/PK columns
|
|
356
|
-
const conflictKeys = Object.keys(
|
|
364
|
+
const conflictKeys = Object.keys(upsertWhere).filter((k) => upsertWhere[k] !== undefined);
|
|
357
365
|
const conflictColumns = conflictKeys.map((k) => qi.toSqlColumn(k));
|
|
358
366
|
// Build the UPDATE SET part
|
|
359
367
|
const updateEntries = Object.entries(args.update).filter(([, v]) => v !== undefined);
|
|
@@ -404,7 +412,7 @@ function buildUpsert(qi, args) {
|
|
|
404
412
|
reselect: qi.dialect.resultStrategy === 'reselect'
|
|
405
413
|
? async (exec) => {
|
|
406
414
|
await exec(sql, params);
|
|
407
|
-
const sel = buildReselectByWhere(qi, (whereMod.mergeGlobalFilter(qi,
|
|
415
|
+
const sel = buildReselectByWhere(qi, (whereMod.mergeGlobalFilter(qi, upsertWhere) ?? {}));
|
|
408
416
|
return exec(sel.sql, sel.params);
|
|
409
417
|
}
|
|
410
418
|
: undefined,
|
package/dist/cjs/schema.js
CHANGED
|
@@ -14,6 +14,7 @@ exports.snakeToCamel = snakeToCamel;
|
|
|
14
14
|
exports.camelToSnake = camelToSnake;
|
|
15
15
|
exports.snakeToPascal = snakeToPascal;
|
|
16
16
|
exports.singularize = singularize;
|
|
17
|
+
exports.withDbFieldNames = withDbFieldNames;
|
|
17
18
|
// ---------------------------------------------------------------------------
|
|
18
19
|
// Helpers for composite key handling
|
|
19
20
|
// ---------------------------------------------------------------------------
|
|
@@ -156,3 +157,49 @@ function singularize(s) {
|
|
|
156
157
|
return s.slice(0, -1);
|
|
157
158
|
return s;
|
|
158
159
|
}
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
// keepColumnNames transform (F4)
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
/**
|
|
164
|
+
* Return a copy of `schema` in which every column's TypeScript **field** name is
|
|
165
|
+
* the raw database column name (snake_case) instead of the camelCased default,
|
|
166
|
+
* i.e. `user_id` stays `user_id` rather than becoming `userId`.
|
|
167
|
+
*
|
|
168
|
+
* This is a PURE, generate-time transform with ZERO runtime changes: it only
|
|
169
|
+
* rewrites `column.field` and rebuilds each table's `columnMap` /
|
|
170
|
+
* `reverseColumnMap` as IDENTITY maps (`user_id → user_id`). Because every
|
|
171
|
+
* runtime surface resolves column names through those maps (`toColumn`,
|
|
172
|
+
* `parseRow`, relation `json_build_object` keys, batched-loader stitching,
|
|
173
|
+
* positional decoding, aggregate/groupBy naming), the generated client returns
|
|
174
|
+
* rows keyed by DB column names and accepts DB column names in
|
|
175
|
+
* `where`/`orderBy`/`select` with no code path aware of the difference.
|
|
176
|
+
*
|
|
177
|
+
* Deliberately left untouched (the flag is literally about column names):
|
|
178
|
+
* - `name`, `allColumns`, `dateColumns`, `dialectTypes`, `pgTypes`,
|
|
179
|
+
* `primaryKey`, `uniqueColumns`, `indexes`, `checks`, `isView` (all already
|
|
180
|
+
* keyed by snake_case column names);
|
|
181
|
+
* - `relations` (relation PROPERTY names are synthetic introspection names
|
|
182
|
+
* with no DB column equivalent, and `foreignKey`/`referenceKey`/`through`
|
|
183
|
+
* already hold DB column names);
|
|
184
|
+
* - entity type names, table accessors, and `enums`.
|
|
185
|
+
*
|
|
186
|
+
* Every non-PII field of each {@link ColumnMetadata} (`pii`, `pgType`,
|
|
187
|
+
* `dialectType`, `nullable`, …) is preserved. Exported from the package root so
|
|
188
|
+
* runtime-introspection and serverless users can apply the same identity mapping
|
|
189
|
+
* to a schema they build at runtime, e.g. `turbineHttp(pool,
|
|
190
|
+
* withDbFieldNames(schema))`.
|
|
191
|
+
*/
|
|
192
|
+
function withDbFieldNames(schema) {
|
|
193
|
+
const tables = {};
|
|
194
|
+
for (const [tableKey, table] of Object.entries(schema.tables)) {
|
|
195
|
+
const columns = table.columns.map((col) => ({ ...col, field: col.name }));
|
|
196
|
+
const columnMap = {};
|
|
197
|
+
const reverseColumnMap = {};
|
|
198
|
+
for (const col of columns) {
|
|
199
|
+
columnMap[col.name] = col.name;
|
|
200
|
+
reverseColumnMap[col.name] = col.name;
|
|
201
|
+
}
|
|
202
|
+
tables[tableKey] = { ...table, columns, columnMap, reverseColumnMap };
|
|
203
|
+
}
|
|
204
|
+
return { ...schema, tables };
|
|
205
|
+
}
|
package/dist/cjs/sqlite.js
CHANGED
|
@@ -558,15 +558,10 @@ function pragma(db, sql) {
|
|
|
558
558
|
*/
|
|
559
559
|
function introspectSqliteDatabase(db, options = {}) {
|
|
560
560
|
// ----- Tables (skip SQLite internal + the migration tracking table) -----
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
}
|
|
566
|
-
if (options.exclude?.length) {
|
|
567
|
-
const exc = new Set(options.exclude);
|
|
568
|
-
tableNames = tableNames.filter((t) => !exc.has(t));
|
|
569
|
-
}
|
|
561
|
+
const candidateTables = pragma(db, "SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name").map((r) => r.name);
|
|
562
|
+
// include / exclude + default bookkeeping-table exclusions (F12), shared with
|
|
563
|
+
// every other introspector via applyTableFilters.
|
|
564
|
+
const tableNames = (0, introspect_js_1.applyTableFilters)(candidateTables, options);
|
|
570
565
|
const tableSet = new Set(tableNames);
|
|
571
566
|
const columnsByTable = new Map();
|
|
572
567
|
const pkByTable = new Map();
|
package/dist/cli/config.d.ts
CHANGED
|
@@ -15,6 +15,23 @@ export interface TurbineCliConfig {
|
|
|
15
15
|
include?: string[];
|
|
16
16
|
/** Tables to exclude */
|
|
17
17
|
exclude?: string[];
|
|
18
|
+
/**
|
|
19
|
+
* Extension for the generated `index.ts` sibling imports (F3):
|
|
20
|
+
* `'js'` (`./types.js`), `'none'` (`./types`), or `'auto'` (default:
|
|
21
|
+
* tsconfig-detected, falling back to `'js'`).
|
|
22
|
+
*/
|
|
23
|
+
importExtension?: 'js' | 'none' | 'auto';
|
|
24
|
+
/**
|
|
25
|
+
* Keep raw database column names as generated field names (snake_case)
|
|
26
|
+
* instead of camelCase (F4). Opt-in; default `false`.
|
|
27
|
+
*/
|
|
28
|
+
keepColumnNames?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Opt OUT of the unique-FK → one-to-one (`hasOne`) introspection flip (F2),
|
|
31
|
+
* emitting the pre-0.41 `hasMany` shape for unique-covered child relations.
|
|
32
|
+
* Default `false` (detection on).
|
|
33
|
+
*/
|
|
34
|
+
legacyToManyUniques?: boolean;
|
|
18
35
|
/** Directory for migration files (default: ./turbine/migrations) */
|
|
19
36
|
migrationsDir?: string;
|
|
20
37
|
/** Path to seed file. Defaults are resolved from seed.ts, seed.js, then seed.sql. */
|
|
@@ -120,6 +137,12 @@ export interface ResolvedConfig {
|
|
|
120
137
|
migrationsDir: string;
|
|
121
138
|
seedFile?: string;
|
|
122
139
|
schemaFile: string;
|
|
140
|
+
/** Resolved generator import-extension mode (F3). */
|
|
141
|
+
importExtension: 'js' | 'none' | 'auto';
|
|
142
|
+
/** Resolved keep-column-names generator flag (F4). */
|
|
143
|
+
keepColumnNames: boolean;
|
|
144
|
+
/** Resolved opt-out of the unique-FK → hasOne introspection flip (F2). */
|
|
145
|
+
legacyToManyUniques: boolean;
|
|
123
146
|
}
|
|
124
147
|
export interface CliOverrides {
|
|
125
148
|
url?: string;
|
|
@@ -127,6 +150,9 @@ export interface CliOverrides {
|
|
|
127
150
|
schema?: string;
|
|
128
151
|
include?: string[];
|
|
129
152
|
exclude?: string[];
|
|
153
|
+
importExtension?: 'js' | 'none' | 'auto';
|
|
154
|
+
keepColumnNames?: boolean;
|
|
155
|
+
legacyToManyUniques?: boolean;
|
|
130
156
|
}
|
|
131
157
|
/**
|
|
132
158
|
* Merge config file values with CLI overrides and env vars.
|
package/dist/cli/config.js
CHANGED
|
@@ -141,6 +141,9 @@ export function resolveConfig(fileConfig, overrides) {
|
|
|
141
141
|
migrationsDir: fileConfig.migrationsDir ?? './turbine/migrations',
|
|
142
142
|
seedFile: fileConfig.seed ?? fileConfig.seedFile,
|
|
143
143
|
schemaFile: fileConfig.schemaFile ?? './turbine/schema.ts',
|
|
144
|
+
importExtension: overrides.importExtension ?? fileConfig.importExtension ?? 'auto',
|
|
145
|
+
keepColumnNames: overrides.keepColumnNames ?? fileConfig.keepColumnNames ?? false,
|
|
146
|
+
legacyToManyUniques: overrides.legacyToManyUniques ?? fileConfig.legacyToManyUniques ?? false,
|
|
144
147
|
};
|
|
145
148
|
}
|
|
146
149
|
/**
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Commands:
|
|
6
6
|
* turbine init — Initialize a Turbine project
|
|
7
7
|
* turbine generate | pull — Introspect database and generate TypeScript types
|
|
8
|
+
* turbine migrate-from-prisma - Parse a schema.prisma and emit a Prisma->Turbine name map + report
|
|
8
9
|
* turbine push - Apply schema-builder definitions to database (destructive ops gated)
|
|
9
10
|
* turbine migrate create <name> - Create a new SQL migration file (--auto | --from-diff | --recipe <name>)
|
|
10
11
|
* turbine migrate up — Apply pending migrations
|
|
@@ -60,6 +61,12 @@ export interface CliArgs {
|
|
|
60
61
|
includeViews?: boolean;
|
|
61
62
|
/** Omit the `Generated at:` header line for reproducible (diff-stable) output. */
|
|
62
63
|
noTimestamp?: boolean;
|
|
64
|
+
/** `generate --import-ext <js|none|auto>`: sibling-import extension mode (F3). */
|
|
65
|
+
importExtension?: 'js' | 'none' | 'auto';
|
|
66
|
+
/** `generate --keep-column-names`: keep raw DB column names as field names (F4). */
|
|
67
|
+
keepColumnNames?: boolean;
|
|
68
|
+
/** `generate --legacy-to-many-uniques`: opt out of the unique-FK → hasOne flip (F2). */
|
|
69
|
+
legacyToManyUniques?: boolean;
|
|
63
70
|
port?: number;
|
|
64
71
|
host?: string;
|
|
65
72
|
noOpen?: boolean;
|
|
@@ -71,6 +78,10 @@ export interface CliArgs {
|
|
|
71
78
|
showPii?: boolean;
|
|
72
79
|
/** Launch Studio with a seeded in-memory sample database (`studio --demo`). */
|
|
73
80
|
demo?: boolean;
|
|
81
|
+
/** `migrate-from-prisma --allow-partial`: exit 0 even when some items are UNRESOLVED. */
|
|
82
|
+
allowPartial?: boolean;
|
|
83
|
+
/** `migrate-from-prisma --no-db`: parse-only, skip database resolution. */
|
|
84
|
+
noDb?: boolean;
|
|
74
85
|
}
|
|
75
86
|
export declare function parseArgs(argv?: string[]): CliArgs;
|
|
76
87
|
/** Where a resolved `DATABASE_URL` came from, after the `.env` load. */
|
package/dist/cli/index.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Commands:
|
|
6
6
|
* turbine init — Initialize a Turbine project
|
|
7
7
|
* turbine generate | pull — Introspect database and generate TypeScript types
|
|
8
|
+
* turbine migrate-from-prisma - Parse a schema.prisma and emit a Prisma->Turbine name map + report
|
|
8
9
|
* turbine push - Apply schema-builder definitions to database (destructive ops gated)
|
|
9
10
|
* turbine migrate create <name> - Create a new SQL migration file (--auto | --from-diff | --recipe <name>)
|
|
10
11
|
* turbine migrate up — Apply pending migrations
|
|
@@ -27,7 +28,7 @@ import { appendFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, realp
|
|
|
27
28
|
import { tmpdir } from 'node:os';
|
|
28
29
|
import { basename, dirname, extname, join, relative, resolve } from 'node:path';
|
|
29
30
|
import { pathToFileURL } from 'node:url';
|
|
30
|
-
import { generate } from '../generate.js';
|
|
31
|
+
import { generate, generatePrismaMap } from '../generate.js';
|
|
31
32
|
import { findMissingRelationIndexes } from '../index-advisor.js';
|
|
32
33
|
import { introspect } from '../introspect.js';
|
|
33
34
|
import { DestructivePushRefusal, schemaDiff, schemaPush } from '../schema-sql.js';
|
|
@@ -37,6 +38,9 @@ import { canResolveTsx, getTsLoaderError, needsTsLoader, registerTsLoader } from
|
|
|
37
38
|
import { runMcpServer } from './mcp.js';
|
|
38
39
|
import { buildDiffMigrationBody, collectUpDestructive, createMigration, formatChecksumMismatchError, inspectMigrationDeploy, listMigrationFiles, MIGRATION_RECIPES, migrateDeploy, migrateDown, migrateStatus, migrateUp, } from './migrate.js';
|
|
39
40
|
import { startObserve } from './observe.js';
|
|
41
|
+
import { formatPrismaReport, summaryLines } from './prisma-report.js';
|
|
42
|
+
import { DEFAULT_EXCLUDED_TABLES, resolvePrismaSchema } from './prisma-resolve.js';
|
|
43
|
+
import { PrismaParseError, parsePrismaSchema } from './prisma-schema.js';
|
|
40
44
|
import { startStudio } from './studio.js';
|
|
41
45
|
import { banner, blue, bold, box, cyan, dim, divider, elapsed, error, table as formatTable, gray, green, header, info, label, magenta, newline, red, redactUrl, Spinner, success, symbols, warn, yellow, } from './ui.js';
|
|
42
46
|
export function parseArgs(argv = process.argv.slice(2)) {
|
|
@@ -126,6 +130,21 @@ export function parseArgs(argv = process.argv.slice(2)) {
|
|
|
126
130
|
case '--no-timestamp':
|
|
127
131
|
result.noTimestamp = true;
|
|
128
132
|
break;
|
|
133
|
+
case '--import-ext':
|
|
134
|
+
case '--import-extension':
|
|
135
|
+
if (next !== 'js' && next !== 'none' && next !== 'auto') {
|
|
136
|
+
console.error(`--import-ext requires one of: js, none, auto (got ${next ?? '(nothing)'})`);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
result.importExtension = next;
|
|
140
|
+
i++;
|
|
141
|
+
break;
|
|
142
|
+
case '--keep-column-names':
|
|
143
|
+
result.keepColumnNames = true;
|
|
144
|
+
break;
|
|
145
|
+
case '--legacy-to-many-uniques':
|
|
146
|
+
result.legacyToManyUniques = true;
|
|
147
|
+
break;
|
|
129
148
|
case '--allow-destructive':
|
|
130
149
|
result.allowDestructive = true;
|
|
131
150
|
break;
|
|
@@ -172,6 +191,12 @@ export function parseArgs(argv = process.argv.slice(2)) {
|
|
|
172
191
|
case '--demo':
|
|
173
192
|
result.demo = true;
|
|
174
193
|
break;
|
|
194
|
+
case '--allow-partial':
|
|
195
|
+
result.allowPartial = true;
|
|
196
|
+
break;
|
|
197
|
+
case '--no-db':
|
|
198
|
+
result.noDb = true;
|
|
199
|
+
break;
|
|
175
200
|
default:
|
|
176
201
|
if (!arg.startsWith('-')) {
|
|
177
202
|
result.positional.push(arg);
|
|
@@ -887,17 +912,25 @@ async function cmdGenerate(args, config) {
|
|
|
887
912
|
newline();
|
|
888
913
|
// Introspect
|
|
889
914
|
const spinner = new Spinner('Introspecting database schema').start();
|
|
915
|
+
const skippedInternalTables = [];
|
|
890
916
|
const schema = await introspect({
|
|
891
917
|
connectionString: url,
|
|
892
918
|
schema: config.schema,
|
|
893
919
|
include: config.include.length ? config.include : undefined,
|
|
894
920
|
exclude: config.exclude.length ? config.exclude : undefined,
|
|
895
921
|
includeViews: args.includeViews,
|
|
922
|
+
legacyToManyUniques: config.legacyToManyUniques,
|
|
923
|
+
onDefaultTableExclusion: (tables) => skippedInternalTables.push(...tables),
|
|
896
924
|
});
|
|
897
925
|
const tableNames = Object.keys(schema.tables);
|
|
898
926
|
const totalColumns = Object.values(schema.tables).reduce((sum, t) => sum + t.columns.length, 0);
|
|
899
927
|
const totalRelations = Object.values(schema.tables).reduce((sum, t) => sum + Object.keys(t.relations).length, 0);
|
|
900
928
|
spinner.succeed(`Found ${bold(String(tableNames.length))} tables, ${bold(String(totalColumns))} columns, ${bold(String(totalRelations))} relations`);
|
|
929
|
+
// F12: make the default bookkeeping-table exclusions discoverable rather than
|
|
930
|
+
// silent. `include` re-adds any of them byte-for-byte.
|
|
931
|
+
for (const t of skippedInternalTables) {
|
|
932
|
+
console.log(` ${dim(`${symbols.teeEnd} skipped internal table ${t} (add it to include to keep it)`)}`);
|
|
933
|
+
}
|
|
901
934
|
// Guard: zero tables means the generated client would be empty. That is almost
|
|
902
935
|
// always a misconfiguration (wrong `schema`, an include/exclude that filtered
|
|
903
936
|
// everything, or a database with no tables yet) rather than intent. Fail loudly
|
|
@@ -937,6 +970,8 @@ async function cmdGenerate(args, config) {
|
|
|
937
970
|
connectionString: url,
|
|
938
971
|
zod: args.zod,
|
|
939
972
|
noTimestamp: args.noTimestamp,
|
|
973
|
+
importExtension: config.importExtension,
|
|
974
|
+
keepColumnNames: config.keepColumnNames,
|
|
940
975
|
});
|
|
941
976
|
genSpinner.succeed(`Generated ${bold(String(result.files.length))} files in ${elapsed(startTime)}`);
|
|
942
977
|
// List files
|
|
@@ -955,6 +990,112 @@ async function cmdGenerate(args, config) {
|
|
|
955
990
|
newline();
|
|
956
991
|
}
|
|
957
992
|
// ---------------------------------------------------------------------------
|
|
993
|
+
// migrate-from-prisma
|
|
994
|
+
// ---------------------------------------------------------------------------
|
|
995
|
+
/**
|
|
996
|
+
* `turbine migrate-from-prisma --schema prisma/schema.prisma` parses a Prisma
|
|
997
|
+
* schema, resolve its models/fields/relations/compound-uniques against the live
|
|
998
|
+
* database (unless `--no-db`), and emit (a) a Markdown resolution report and
|
|
999
|
+
* (b) a typed `prisma-map.ts` name map next to the generated client.
|
|
1000
|
+
*
|
|
1001
|
+
* NOTE: within THIS command `--schema` names the Prisma schema FILE (not the
|
|
1002
|
+
* Postgres namespace, which the rest of the CLI's `--schema` means). The
|
|
1003
|
+
* Postgres namespace is `public` here; multi-schema (`@@schema`) is unsupported
|
|
1004
|
+
* in v1 and listed as a parser note in the report.
|
|
1005
|
+
*/
|
|
1006
|
+
async function cmdMigrateFromPrisma(args, config) {
|
|
1007
|
+
banner();
|
|
1008
|
+
// `--schema` is the Prisma schema file path in this command.
|
|
1009
|
+
const prismaPath = resolve(args.schema ?? 'prisma/schema.prisma');
|
|
1010
|
+
if (!existsSync(prismaPath)) {
|
|
1011
|
+
error(`Prisma schema file not found: ${cyan(prismaPath)}`);
|
|
1012
|
+
newline();
|
|
1013
|
+
console.log(` ${dim('Point at it with')} ${cyan('--schema <path/to/schema.prisma>')} ${dim('(default: prisma/schema.prisma).')}`);
|
|
1014
|
+
newline();
|
|
1015
|
+
process.exit(1);
|
|
1016
|
+
}
|
|
1017
|
+
// Parse (fatal only on a construct we must understand).
|
|
1018
|
+
const source = readFileSync(prismaPath, 'utf-8');
|
|
1019
|
+
let ast;
|
|
1020
|
+
try {
|
|
1021
|
+
ast = parsePrismaSchema(source);
|
|
1022
|
+
}
|
|
1023
|
+
catch (err) {
|
|
1024
|
+
if (err instanceof PrismaParseError) {
|
|
1025
|
+
newline();
|
|
1026
|
+
error(`Could not parse ${cyan(prismaPath)}`);
|
|
1027
|
+
console.log(` ${red(err.message)}`);
|
|
1028
|
+
newline();
|
|
1029
|
+
process.exit(1);
|
|
1030
|
+
}
|
|
1031
|
+
throw err;
|
|
1032
|
+
}
|
|
1033
|
+
label('Prisma schema', prismaPath);
|
|
1034
|
+
label('Models', String(ast.models.length));
|
|
1035
|
+
label('Enums', String(ast.enums.length));
|
|
1036
|
+
// Resolve against the live database, unless --no-db (parse-only).
|
|
1037
|
+
let schemaMeta = null;
|
|
1038
|
+
if (args.noDb) {
|
|
1039
|
+
info('Parse-only mode (--no-db): names will not be resolved.');
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
const url = requireUrl(config);
|
|
1043
|
+
label('Database', redactUrl(url));
|
|
1044
|
+
const spinner = new Spinner('Introspecting database schema').start();
|
|
1045
|
+
schemaMeta = await introspect({
|
|
1046
|
+
connectionString: url,
|
|
1047
|
+
// The Postgres NAMESPACE is fixed to `public` here (`--schema` names the
|
|
1048
|
+
// Prisma file, not the namespace).
|
|
1049
|
+
schema: 'public',
|
|
1050
|
+
// Prisma `view` models resolve against introspected views.
|
|
1051
|
+
includeViews: true,
|
|
1052
|
+
// Inherit the shared bookkeeping-table exclusions (Turbine + Prisma).
|
|
1053
|
+
exclude: [...new Set([...config.exclude, ...DEFAULT_EXCLUDED_TABLES])],
|
|
1054
|
+
});
|
|
1055
|
+
spinner.succeed(`Introspected ${bold(String(Object.keys(schemaMeta.tables).length))} tables`);
|
|
1056
|
+
}
|
|
1057
|
+
newline();
|
|
1058
|
+
const result = resolvePrismaSchema(ast, schemaMeta);
|
|
1059
|
+
// Console summary.
|
|
1060
|
+
header('Resolution');
|
|
1061
|
+
for (const line of summaryLines(result)) {
|
|
1062
|
+
const marker = line.includes('[UNRESOLVED]') ? red(symbols.cross) : green(symbols.check);
|
|
1063
|
+
console.log(` ${marker} ${line}`);
|
|
1064
|
+
}
|
|
1065
|
+
newline();
|
|
1066
|
+
// Write outputs into the generate outDir.
|
|
1067
|
+
const outDir = resolve(config.out);
|
|
1068
|
+
const rel = relative(process.cwd(), outDir);
|
|
1069
|
+
if (rel.startsWith('..') || resolve(rel) !== outDir) {
|
|
1070
|
+
error(`Output directory must be within the project root. Got: ${config.out}`);
|
|
1071
|
+
newline();
|
|
1072
|
+
process.exit(1);
|
|
1073
|
+
}
|
|
1074
|
+
mkdirSync(outDir, { recursive: true });
|
|
1075
|
+
const reportPath = join(outDir, 'prisma-migration-report.md');
|
|
1076
|
+
writeFileSync(reportPath, formatPrismaReport(result, { schemaPath: prismaPath, noTimestamp: args.noTimestamp }), 'utf-8');
|
|
1077
|
+
console.log(` ${dim(symbols.teeEnd)} ${cyan(reportPath)} ${dim('(report)')}`);
|
|
1078
|
+
if (!args.noDb) {
|
|
1079
|
+
const mapPath = join(outDir, 'prisma-map.ts');
|
|
1080
|
+
writeFileSync(mapPath, generatePrismaMap(result.map, { noTimestamp: args.noTimestamp }), 'utf-8');
|
|
1081
|
+
console.log(` ${dim(symbols.teeEnd)} ${cyan(mapPath)} ${dim('(typed name map)')}`);
|
|
1082
|
+
}
|
|
1083
|
+
newline();
|
|
1084
|
+
// Exit non-zero when anything is UNRESOLVED, unless --allow-partial.
|
|
1085
|
+
if (result.hasUnresolved && !args.allowPartial) {
|
|
1086
|
+
warn('Some items could not be resolved (see the report). Re-run with --allow-partial to accept a partial map.');
|
|
1087
|
+
newline();
|
|
1088
|
+
process.exit(1);
|
|
1089
|
+
}
|
|
1090
|
+
if (args.noDb) {
|
|
1091
|
+
info(`Parse-only report written. Re-run without ${cyan('--no-db')} against your database to resolve names.`);
|
|
1092
|
+
}
|
|
1093
|
+
else {
|
|
1094
|
+
success('Prisma name map generated.');
|
|
1095
|
+
}
|
|
1096
|
+
newline();
|
|
1097
|
+
}
|
|
1098
|
+
// ---------------------------------------------------------------------------
|
|
958
1099
|
// Command: push
|
|
959
1100
|
// ---------------------------------------------------------------------------
|
|
960
1101
|
async function cmdPush(args, config) {
|
|
@@ -2121,6 +2262,7 @@ function showSubcommandHelp(command) {
|
|
|
2121
2262
|
init: showInitHelp,
|
|
2122
2263
|
generate: showGenerateHelp,
|
|
2123
2264
|
pull: showGenerateHelp,
|
|
2265
|
+
'migrate-from-prisma': showMigrateFromPrismaHelp,
|
|
2124
2266
|
push: showPushHelp,
|
|
2125
2267
|
migrate: showMigrateHelp,
|
|
2126
2268
|
migration: showMigrateHelp,
|
|
@@ -2182,9 +2324,39 @@ function showGenerateHelp() {
|
|
|
2182
2324
|
console.log(` ${cyan('--zod')} Also emit ${cyan('zod.ts')} validation schemas ${dim('(needs the zod dep)')}`);
|
|
2183
2325
|
console.log(` ${cyan('--include-views')} Include views + materialized views as read-only entities`);
|
|
2184
2326
|
console.log(` ${cyan('--no-timestamp')} Omit the ${dim('Generated at:')} header line ${dim('(reproducible, diff-stable output)')}`);
|
|
2327
|
+
console.log(` ${cyan('--import-ext')} ${dim('<mode>')} Sibling-import extension: ${cyan('js')} / ${cyan('none')} / ${cyan('auto')} ${dim('(default: auto)')}`);
|
|
2328
|
+
console.log(` ${cyan('--keep-column-names')} Keep raw DB column names as field names ${dim('(snake_case, not camelCase)')}`);
|
|
2329
|
+
console.log(` ${cyan('--legacy-to-many-uniques')} Emit ${cyan('hasMany')} for unique-FK children ${dim('(pre-0.41 shape; default flips to hasOne)')}`);
|
|
2185
2330
|
console.log(` ${cyan('--allow-empty')} Generate even when introspection matches 0 tables`);
|
|
2186
2331
|
newline();
|
|
2187
2332
|
}
|
|
2333
|
+
function showMigrateFromPrismaHelp() {
|
|
2334
|
+
banner();
|
|
2335
|
+
console.log(` ${bold('turbine migrate-from-prisma')} - Map a Prisma schema onto a Turbine client`);
|
|
2336
|
+
newline();
|
|
2337
|
+
console.log(` ${bold('Usage:')}`);
|
|
2338
|
+
console.log(` npx turbine migrate-from-prisma ${dim('--schema prisma/schema.prisma [options]')}`);
|
|
2339
|
+
newline();
|
|
2340
|
+
console.log(` Parses your ${cyan('schema.prisma')}, resolves models/fields/relations/compound`);
|
|
2341
|
+
console.log(` uniques against the live database, and writes into the output directory:`);
|
|
2342
|
+
console.log(` ${dim('•')} ${cyan('prisma-migration-report.md')} - per-model resolution + unresolved items`);
|
|
2343
|
+
console.log(` ${dim('•')} ${cyan('prisma-map.ts')} - typed PRISMA_MAP name map`);
|
|
2344
|
+
newline();
|
|
2345
|
+
console.log(` ${dim('Note:')} here ${cyan('--schema')} names the Prisma FILE (not the Postgres namespace).`);
|
|
2346
|
+
newline();
|
|
2347
|
+
console.log(` ${bold('Options:')}`);
|
|
2348
|
+
console.log(` ${cyan('--schema')} ${dim('<file>')} Path to schema.prisma ${dim('(default: prisma/schema.prisma)')}`);
|
|
2349
|
+
console.log(` ${cyan('--url, -u')} ${dim('<url>')} Postgres connection string ${dim('(unless --no-db)')}`);
|
|
2350
|
+
console.log(` ${cyan('--out, -o')} ${dim('<dir>')} Output directory ${dim('(default: ./generated/turbine)')}`);
|
|
2351
|
+
console.log(` ${cyan('--no-db')} Parse-only: write the report without resolving names`);
|
|
2352
|
+
console.log(` ${cyan('--allow-partial')} Exit 0 even when some items are UNRESOLVED`);
|
|
2353
|
+
console.log(` ${cyan('--no-timestamp')} Omit the ${dim('Generated:')} lines ${dim('(reproducible output)')}`);
|
|
2354
|
+
newline();
|
|
2355
|
+
console.log(` ${bold('Examples:')}`);
|
|
2356
|
+
console.log(` ${dim('$')} DATABASE_URL=postgres://... npx turbine migrate-from-prisma --schema prisma/schema.prisma`);
|
|
2357
|
+
console.log(` ${dim('$')} npx turbine migrate-from-prisma --schema prisma/schema.prisma --no-db`);
|
|
2358
|
+
newline();
|
|
2359
|
+
}
|
|
2188
2360
|
function showPushHelp() {
|
|
2189
2361
|
banner();
|
|
2190
2362
|
console.log(` ${bold('turbine push')} — Apply schema-builder definitions to database`);
|
|
@@ -2297,6 +2469,7 @@ function showHelp() {
|
|
|
2297
2469
|
console.log(` ${bold('Commands:')}`);
|
|
2298
2470
|
console.log(` ${cyan('init')} Initialize a Turbine project`);
|
|
2299
2471
|
console.log(` ${cyan('generate')} ${dim('| pull')} Introspect database ${symbols.arrow} generate types`);
|
|
2472
|
+
console.log(` ${cyan('migrate-from-prisma')} Map a schema.prisma onto Turbine ${dim('(report + typed name map)')}`);
|
|
2300
2473
|
console.log(` ${cyan('push')} Apply schema definitions to database`);
|
|
2301
2474
|
console.log(` ${cyan('migrate')} ${dim('<sub>')} SQL migration management`);
|
|
2302
2475
|
console.log(` ${dim('create <name>')} Create a new migration file`);
|
|
@@ -2457,6 +2630,9 @@ async function main() {
|
|
|
2457
2630
|
schema: args.schema,
|
|
2458
2631
|
include: args.include,
|
|
2459
2632
|
exclude: args.exclude,
|
|
2633
|
+
importExtension: args.importExtension,
|
|
2634
|
+
keepColumnNames: args.keepColumnNames,
|
|
2635
|
+
legacyToManyUniques: args.legacyToManyUniques,
|
|
2460
2636
|
};
|
|
2461
2637
|
const config = resolveConfig(fileConfig, overrides);
|
|
2462
2638
|
// Warn (don't change precedence) when an .env-sourced DATABASE_URL is silently
|
|
@@ -2483,6 +2659,9 @@ async function main() {
|
|
|
2483
2659
|
case 'pull':
|
|
2484
2660
|
await cmdGenerate(args, config);
|
|
2485
2661
|
break;
|
|
2662
|
+
case 'migrate-from-prisma':
|
|
2663
|
+
await cmdMigrateFromPrisma(args, config);
|
|
2664
|
+
break;
|
|
2486
2665
|
case 'push':
|
|
2487
2666
|
await cmdPush(args, config);
|
|
2488
2667
|
break;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render a {@link ResolutionResult} into the `prisma-migration-report.md`
|
|
3
|
+
* artifact and a short console summary. Pure leaf - string in, string out.
|
|
4
|
+
*/
|
|
5
|
+
import type { ResolutionResult } from './prisma-resolve.js';
|
|
6
|
+
export interface ReportOptions {
|
|
7
|
+
/** Path the Prisma schema was read from (for the report header). */
|
|
8
|
+
schemaPath?: string;
|
|
9
|
+
/** Omit the volatile `Generated: <ISO>` line for reproducible output. */
|
|
10
|
+
noTimestamp?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Build the full Markdown migration report.
|
|
14
|
+
*/
|
|
15
|
+
export declare function formatPrismaReport(result: ResolutionResult, options?: ReportOptions): string;
|
|
16
|
+
/** Flat list of unresolved item descriptions across the whole result. */
|
|
17
|
+
export declare function collectUnresolved(result: ResolutionResult): string[];
|
|
18
|
+
/** A one-line-per-model console summary for the CLI. */
|
|
19
|
+
export declare function summaryLines(result: ResolutionResult): string[];
|