turbine-orm 0.49.0 → 0.50.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 +122 -39
- package/dist/cjs/adapters/cockroachdb.d.ts +39 -0
- package/dist/cjs/adapters/index.d.ts +110 -0
- package/dist/cjs/adapters/yugabytedb.d.ts +51 -0
- package/dist/cjs/cli/config.d.ts +181 -0
- package/dist/cjs/cli/config.js +32 -6
- package/dist/cjs/cli/destructive.d.ts +38 -0
- package/dist/cjs/cli/index.d.ts +359 -0
- package/dist/cjs/cli/index.js +228 -56
- package/dist/cjs/cli/loader.d.ts +61 -0
- package/dist/cjs/cli/mcp.d.ts +42 -0
- package/dist/cjs/cli/migrate.d.ts +356 -0
- package/dist/cjs/cli/migrate.js +131 -40
- package/dist/cjs/cli/observe-ui.d.ts +1 -0
- package/dist/cjs/cli/observe-ui.js +14 -5
- package/dist/cjs/cli/observe.d.ts +25 -0
- package/dist/cjs/cli/observe.js +49 -12
- package/dist/cjs/cli/pii-tags.d.ts +53 -0
- package/dist/cjs/cli/prisma-report.d.ts +33 -0
- package/dist/cjs/cli/prisma-report.js +73 -0
- package/dist/cjs/cli/prisma-resolve.d.ts +106 -0
- package/dist/cjs/cli/prisma-resolve.js +1 -0
- package/dist/cjs/cli/prisma-schema.d.ts +176 -0
- package/dist/cjs/cli/prisma-schema.js +82 -4
- package/dist/cjs/cli/rate-limit.d.ts +32 -0
- package/dist/cjs/cli/rate-limit.js +45 -0
- package/dist/cjs/cli/studio-demo.d.ts +43 -0
- package/dist/cjs/cli/studio-ui.generated.d.ts +1 -0
- package/dist/cjs/cli/studio.d.ts +207 -0
- package/dist/cjs/cli/studio.js +136 -71
- package/dist/cjs/cli/ui.d.ts +73 -0
- package/dist/cjs/cli/ui.js +51 -9
- package/dist/cjs/client.d.ts +837 -0
- package/dist/cjs/client.js +3 -0
- package/dist/cjs/dialect.d.ts +516 -0
- package/dist/cjs/dialect.js +37 -12
- package/dist/cjs/errors.d.ts +370 -0
- package/dist/cjs/generate.d.ts +137 -0
- package/dist/cjs/generate.js +39 -6
- package/dist/cjs/index-advisor.d.ts +153 -0
- package/dist/cjs/index-stats.d.ts +384 -0
- package/dist/cjs/index.d.ts +55 -0
- package/dist/cjs/index.js +7 -2
- package/dist/cjs/introspect.d.ts +269 -0
- package/dist/cjs/mssql.d.ts +232 -0
- package/dist/cjs/mssql.js +6 -0
- package/dist/cjs/mysql.d.ts +173 -0
- package/dist/cjs/mysql.js +16 -0
- package/dist/cjs/nested-write.d.ts +96 -0
- package/dist/cjs/nested-write.js +414 -24
- package/dist/cjs/observe.d.ts +115 -0
- package/dist/cjs/optional-peer-import.d.cts +72 -0
- package/dist/cjs/pipeline-submittable.d.ts +93 -0
- package/dist/cjs/pipeline.d.ts +71 -0
- package/dist/cjs/powdb-introspect.d.ts +84 -0
- package/dist/cjs/powdb.d.ts +931 -0
- package/dist/cjs/powdb.js +106 -21
- package/dist/cjs/powql.d.ts +592 -0
- package/dist/cjs/powql.js +42 -6
- package/dist/cjs/prisma-compat.d.ts +283 -0
- package/dist/cjs/prisma-compat.js +167 -9
- package/dist/cjs/query/aggregates.d.ts +92 -0
- package/dist/cjs/query/aggregates.js +7 -3
- package/dist/cjs/query/batched-loader.d.ts +193 -0
- package/dist/cjs/query/builder.d.ts +849 -0
- package/dist/cjs/query/builder.js +571 -65
- package/dist/cjs/query/compound-unique.d.ts +51 -0
- package/dist/cjs/query/deferred.d.ts +223 -0
- package/dist/cjs/query/filters.d.ts +201 -0
- package/dist/cjs/query/index.d.ts +14 -0
- package/dist/cjs/query/index.js +6 -1
- package/dist/cjs/query/relations.d.ts +609 -0
- package/dist/cjs/query/relations.js +693 -46
- package/dist/cjs/query/types.d.ts +1300 -0
- package/dist/cjs/query/utils.d.ts +209 -0
- package/dist/cjs/query/utils.js +208 -1
- package/dist/cjs/query/warn-registry.d.ts +68 -0
- package/dist/cjs/query/warn-registry.js +9 -0
- package/dist/cjs/query/where-compile.d.ts +139 -0
- package/dist/cjs/query/where.d.ts +548 -0
- package/dist/cjs/query/where.js +58 -22
- package/dist/cjs/query/writes.d.ts +172 -0
- package/dist/cjs/query/writes.js +105 -12
- package/dist/cjs/realtime.d.ts +70 -0
- package/dist/cjs/schema-builder.d.ts +354 -0
- package/dist/cjs/schema-metadata.d.ts +83 -0
- package/dist/cjs/schema-sql.d.ts +217 -0
- package/dist/cjs/schema-sql.js +23 -5
- package/dist/cjs/schema.d.ts +356 -0
- package/dist/cjs/schema.js +125 -0
- package/dist/cjs/seed.d.ts +15 -0
- package/dist/cjs/serverless.d.ts +142 -0
- package/dist/cjs/sqlite.d.ts +143 -0
- package/dist/cjs/sqlite.js +4 -0
- package/dist/cjs/typed-sql.d.ts +102 -0
- package/dist/cli/config.d.ts +18 -4
- package/dist/cli/config.js +31 -6
- package/dist/cli/index.d.ts +123 -0
- package/dist/cli/index.js +223 -58
- package/dist/cli/migrate.d.ts +59 -10
- package/dist/cli/migrate.js +128 -41
- package/dist/cli/observe-ui.d.ts +1 -1
- package/dist/cli/observe-ui.js +14 -5
- package/dist/cli/observe.d.ts +7 -1
- package/dist/cli/observe.js +48 -12
- package/dist/cli/prisma-report.d.ts +14 -0
- package/dist/cli/prisma-report.js +72 -0
- package/dist/cli/prisma-resolve.d.ts +6 -0
- package/dist/cli/prisma-resolve.js +1 -0
- package/dist/cli/prisma-schema.d.ts +62 -2
- package/dist/cli/prisma-schema.js +81 -4
- package/dist/cli/rate-limit.d.ts +32 -0
- package/dist/cli/rate-limit.js +40 -0
- package/dist/cli/studio.d.ts +5 -5
- package/dist/cli/studio.js +135 -70
- package/dist/cli/ui.d.ts +1 -1
- package/dist/cli/ui.js +51 -9
- package/dist/client.d.ts +40 -0
- package/dist/client.js +3 -0
- package/dist/dialect.d.ts +17 -1
- package/dist/dialect.js +37 -12
- package/dist/generate.js +40 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mssql.js +6 -0
- package/dist/mysql.js +16 -0
- package/dist/nested-write.d.ts +2 -0
- package/dist/nested-write.js +415 -25
- package/dist/powdb.d.ts +4 -2
- package/dist/powdb.js +106 -21
- package/dist/powql.d.ts +5 -0
- package/dist/powql.js +42 -6
- package/dist/prisma-compat.d.ts +2 -0
- package/dist/prisma-compat.js +166 -8
- package/dist/query/aggregates.js +7 -3
- package/dist/query/builder.d.ts +292 -21
- package/dist/query/builder.js +570 -64
- package/dist/query/deferred.d.ts +39 -0
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +1 -1
- package/dist/query/relations.d.ts +173 -5
- package/dist/query/relations.js +688 -47
- package/dist/query/types.d.ts +123 -39
- package/dist/query/utils.d.ts +116 -0
- package/dist/query/utils.js +198 -0
- package/dist/query/warn-registry.d.ts +9 -0
- package/dist/query/warn-registry.js +9 -0
- package/dist/query/where.d.ts +38 -1
- package/dist/query/where.js +58 -23
- package/dist/query/writes.d.ts +42 -1
- package/dist/query/writes.js +104 -13
- package/dist/schema-sql.d.ts +14 -0
- package/dist/schema-sql.js +23 -5
- package/dist/schema.d.ts +38 -0
- package/dist/schema.js +123 -0
- package/dist/sqlite.js +4 -0
- package/package.json +77 -28
package/dist/cli/index.js
CHANGED
|
@@ -33,7 +33,7 @@ import { buildCreateIndexSql, buildDropIndexSql, collectDoctorProbeIndexNames, c
|
|
|
33
33
|
import { auditDoctorIndexes, collectStatsSnapshot, collectTableHeat, findInvalidIndexes, findRedundantIndexes, findUnusedIndexes, formatBytes, isSnapshotUsable, STATS_THRESHOLDS, scoreMissingIndex, } from '../index-stats.js';
|
|
34
34
|
import { introspect } from '../introspect.js';
|
|
35
35
|
import { DestructivePushRefusal, schemaDiff, schemaPush } from '../schema-sql.js';
|
|
36
|
-
import { configTemplate, findConfigFile, loadConfigResult, looksLikeSchemaFilePath, resolveConfig, resolveSeedFile, unwrapModuleDefault, } from './config.js';
|
|
36
|
+
import { configTemplate, DEFAULT_INIT_SEED_FILE, findConfigFile, loadConfigResult, looksLikeSchemaFilePath, resolveConfig, resolveSeedFile, unwrapModuleDefault, } from './config.js';
|
|
37
37
|
import { DESTRUCTIVE_KIND_LABEL } from './destructive.js';
|
|
38
38
|
import { canResolveTsx, getTsLoaderError, needsTsLoader, registerTsLoader } from './loader.js';
|
|
39
39
|
import { runMcpServer } from './mcp.js';
|
|
@@ -41,7 +41,7 @@ import { buildDiffMigrationBody, collectUpDestructive, createMigration, formatCh
|
|
|
41
41
|
import { startObserve } from './observe.js';
|
|
42
42
|
import { formatPrismaReport, summaryLines } from './prisma-report.js';
|
|
43
43
|
import { DEFAULT_EXCLUDED_TABLES, resolvePrismaSchema } from './prisma-resolve.js';
|
|
44
|
-
import { PrismaParseError, parsePrismaSchema } from './prisma-schema.js';
|
|
44
|
+
import { PrismaParseError, parsePrismaSchema, resolvePrismaDatasourceUrl, } from './prisma-schema.js';
|
|
45
45
|
import { startStudio } from './studio.js';
|
|
46
46
|
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';
|
|
47
47
|
export function parseArgs(argv = process.argv.slice(2)) {
|
|
@@ -267,9 +267,49 @@ function failMissingTsLoader(filePath, reason) {
|
|
|
267
267
|
process.exit(1);
|
|
268
268
|
}
|
|
269
269
|
// ---------------------------------------------------------------------------
|
|
270
|
-
//
|
|
270
|
+
// Config bootstrap
|
|
271
271
|
// ---------------------------------------------------------------------------
|
|
272
|
-
|
|
272
|
+
/**
|
|
273
|
+
* Does this invocation need a `turbine.config.*` file?
|
|
274
|
+
*
|
|
275
|
+
* Everything does, with one deliberate exception: `turbine studio --demo` boots
|
|
276
|
+
* a seeded in-memory database, needs no `DATABASE_URL` and no config file, and
|
|
277
|
+
* is the very next command the quickstart suggests after `turbine init`.
|
|
278
|
+
* Resolving the config anyway means a freshly scaffolded directory (a
|
|
279
|
+
* `turbine.config.ts` with `tsx` not installed yet) dies with "Cannot load
|
|
280
|
+
* TypeScript file" before demo mode ever starts.
|
|
281
|
+
*
|
|
282
|
+
* @internal exported for tests.
|
|
283
|
+
*/
|
|
284
|
+
export function usesProjectConfig(args) {
|
|
285
|
+
return !(args.command === 'studio' && args.demo === true);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Resolve the effective CLI config: register the tsx loader when the config file
|
|
289
|
+
* is TypeScript, import it, then merge it with env vars and CLI flags. Exits with
|
|
290
|
+
* the actionable "Cannot load TypeScript file" error when a `.ts` config cannot
|
|
291
|
+
* be loaded. Config-free invocations short-circuit without touching the disk.
|
|
292
|
+
*
|
|
293
|
+
* @internal exported for tests.
|
|
294
|
+
*/
|
|
295
|
+
export async function bootstrapCliConfig(args, overrides) {
|
|
296
|
+
if (!usesProjectConfig(args)) {
|
|
297
|
+
return { config: resolveConfig({}, overrides), fileConfig: {}, skipped: true };
|
|
298
|
+
}
|
|
299
|
+
// If the user has a TypeScript config file, register the tsx ESM loader
|
|
300
|
+
// before we attempt to import it. Otherwise Node throws
|
|
301
|
+
// ERR_UNKNOWN_FILE_EXTENSION for `.ts`.
|
|
302
|
+
const configPath = findConfigFile();
|
|
303
|
+
if (needsTsLoader(configPath)) {
|
|
304
|
+
const status = await registerTsLoader();
|
|
305
|
+
if (status === 'missing' || status === 'unsupported' || status === 'failed') {
|
|
306
|
+
failMissingTsLoader(configPath ?? 'turbine.config.ts', status);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const { config: fileConfig, loadError } = await loadConfigResult();
|
|
310
|
+
return { config: resolveConfig(fileConfig, overrides), fileConfig, loadError, skipped: false };
|
|
311
|
+
}
|
|
312
|
+
function requireUrl(config, options = {}) {
|
|
273
313
|
if (!config.url) {
|
|
274
314
|
error('No database URL provided.');
|
|
275
315
|
newline();
|
|
@@ -279,6 +319,12 @@ function requireUrl(config) {
|
|
|
279
319
|
const envFileNote = typeof process.loadEnvFile === 'function' ? '(auto-loaded)' : '(needs Node 20.12+ to auto-load)';
|
|
280
320
|
console.log(` ${dim('2.')} Set ${cyan('DATABASE_URL')} in your environment or a ${cyan('.env')} file ${dim(envFileNote)}`);
|
|
281
321
|
console.log(` ${dim('3.')} Pass ${cyan('--url')} flag`);
|
|
322
|
+
const vars = options.datasourceVars ?? [];
|
|
323
|
+
if (vars.length > 0) {
|
|
324
|
+
const list = vars.map((v) => cyan(v)).join(', ');
|
|
325
|
+
const plural = vars.length > 1 ? 'these variables' : 'this variable';
|
|
326
|
+
console.log(` ${dim('4.')} Set ${list} ${dim(`(${plural}, declared by your schema.prisma datasource, ${vars.length > 1 ? 'are' : 'is'} unset)`)}`);
|
|
327
|
+
}
|
|
282
328
|
newline();
|
|
283
329
|
process.exit(1);
|
|
284
330
|
}
|
|
@@ -430,6 +476,57 @@ export function dotEnvUrlConflictWarning(input) {
|
|
|
430
476
|
`(${redactUrl(fileUrl)}). Using the .env value. Remove DATABASE_URL from .env, or unset the config url, ` +
|
|
431
477
|
`to silence this.`);
|
|
432
478
|
}
|
|
479
|
+
/**
|
|
480
|
+
* Detect the consumer's package manager from its lockfile, defaulting to npm.
|
|
481
|
+
* Used only to print an exact, copy-pasteable install command.
|
|
482
|
+
*
|
|
483
|
+
* @internal exported for tests.
|
|
484
|
+
*/
|
|
485
|
+
export function detectPackageManager(cwd = process.cwd()) {
|
|
486
|
+
if (existsSync(join(cwd, 'pnpm-lock.yaml')))
|
|
487
|
+
return 'pnpm';
|
|
488
|
+
if (existsSync(join(cwd, 'yarn.lock')))
|
|
489
|
+
return 'yarn';
|
|
490
|
+
if (existsSync(join(cwd, 'bun.lockb')) || existsSync(join(cwd, 'bun.lock')))
|
|
491
|
+
return 'bun';
|
|
492
|
+
return 'npm';
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* The exact "add tsx as a dev dependency" command for a package manager.
|
|
496
|
+
*
|
|
497
|
+
* @internal exported for tests.
|
|
498
|
+
*/
|
|
499
|
+
export function tsxInstallCommand(pm) {
|
|
500
|
+
switch (pm) {
|
|
501
|
+
case 'pnpm':
|
|
502
|
+
return 'pnpm add -D tsx';
|
|
503
|
+
case 'yarn':
|
|
504
|
+
return 'yarn add -D tsx';
|
|
505
|
+
case 'bun':
|
|
506
|
+
return 'bun add -d tsx';
|
|
507
|
+
default:
|
|
508
|
+
return 'npm install --save-dev tsx';
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* The heads-up `turbine init` prints when it has just scaffolded TypeScript
|
|
513
|
+
* files and `tsx` is not resolvable: without it the CLI cannot load them, and
|
|
514
|
+
* the very next command the user runs dies on "Cannot load TypeScript file".
|
|
515
|
+
* Pure (returns the lines, prints nothing) so it can be asserted in tests.
|
|
516
|
+
*
|
|
517
|
+
* @internal exported for tests.
|
|
518
|
+
*/
|
|
519
|
+
export function tsxRequiredNotice(tsFiles, installCommand) {
|
|
520
|
+
return [
|
|
521
|
+
`Turbine needs ${cyan('tsx')} to load the TypeScript files just created:`,
|
|
522
|
+
...tsFiles.map((f) => ` ${dim(symbols.dot)} ${cyan(f)}`),
|
|
523
|
+
'',
|
|
524
|
+
` ${dim('Install it as a dev dependency:')}`,
|
|
525
|
+
` ${cyan(installCommand)}`,
|
|
526
|
+
'',
|
|
527
|
+
` ${dim('Without it, the next Turbine command fails with')} ${dim('"Cannot load TypeScript file".')}`,
|
|
528
|
+
];
|
|
529
|
+
}
|
|
433
530
|
/**
|
|
434
531
|
* Read the consumer's `package.json` `"type"` field. Returns `'module'` for an
|
|
435
532
|
* ESM project, `'commonjs'` for an explicit or absent (defaulted) CommonJS
|
|
@@ -755,6 +852,48 @@ function initPromptQuestion(step, config, seedFilePath) {
|
|
|
755
852
|
return `Run ${initStepLabel(step.id)}?`;
|
|
756
853
|
}
|
|
757
854
|
}
|
|
855
|
+
/**
|
|
856
|
+
* Decide which connection notice `turbine init` prints. Pure so the whole
|
|
857
|
+
* decision matrix is testable.
|
|
858
|
+
*
|
|
859
|
+
* "No DATABASE_URL found in environment" is reserved for the case where NO
|
|
860
|
+
* source supplied one: printing it while happily using `--url` (or a config
|
|
861
|
+
* `url`) reads like a failure the user then goes looking for.
|
|
862
|
+
*
|
|
863
|
+
* @internal exported for tests.
|
|
864
|
+
*/
|
|
865
|
+
export function initEnvNotice(input) {
|
|
866
|
+
if (input.envUrl) {
|
|
867
|
+
return { kind: 'success', message: `Detected ${cyan('DATABASE_URL')} in the environment` };
|
|
868
|
+
}
|
|
869
|
+
if (input.hasEnvFile && !input.canAutoLoadEnv) {
|
|
870
|
+
return {
|
|
871
|
+
kind: 'info',
|
|
872
|
+
message: `Found ${cyan('.env')} ${dim('(this Node version cannot auto-load it. Upgrade to Node 20.12+ or export')} ${cyan('DATABASE_URL')}${dim(')')}`,
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
if (input.hasEnvFile) {
|
|
876
|
+
// .env exists but did not provide DATABASE_URL; if it had, the auto-load
|
|
877
|
+
// in main() would have populated envUrl above.
|
|
878
|
+
return {
|
|
879
|
+
kind: 'info',
|
|
880
|
+
message: `Found ${cyan('.env')} ${dim('(no')} ${cyan('DATABASE_URL')} ${dim('set in it yet)')}`,
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
if (input.hasEnvLocal) {
|
|
884
|
+
return {
|
|
885
|
+
kind: 'info',
|
|
886
|
+
message: `Found ${cyan('.env.local')} ${dim('(note: Turbine only auto-loads')} ${cyan('.env')}${dim(')')}`,
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
if (input.flagUrl) {
|
|
890
|
+
return { kind: 'success', message: `Using the connection string passed with ${cyan('--url')}` };
|
|
891
|
+
}
|
|
892
|
+
if (input.configUrl) {
|
|
893
|
+
return { kind: 'success', message: `Using the ${cyan('url')} from your config file` };
|
|
894
|
+
}
|
|
895
|
+
return { kind: 'info', message: `No ${cyan('DATABASE_URL')} found in environment` };
|
|
896
|
+
}
|
|
758
897
|
async function cmdInit(args, config) {
|
|
759
898
|
banner();
|
|
760
899
|
header('Initializing Turbine project');
|
|
@@ -767,23 +906,18 @@ async function cmdInit(args, config) {
|
|
|
767
906
|
// On Node < 20.12 (no process.loadEnvFile) main() could not auto-load .env, so
|
|
768
907
|
// we cannot claim it "has no DATABASE_URL"; we simply could not read it.
|
|
769
908
|
const canAutoLoadEnv = typeof process.loadEnvFile === 'function';
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
info(`Found ${cyan('.env.local')} ${dim('(note: Turbine only auto-loads')} ${cyan('.env')}${dim(')')}`);
|
|
783
|
-
}
|
|
784
|
-
else {
|
|
785
|
-
info(`No ${cyan('DATABASE_URL')} found in environment`);
|
|
786
|
-
}
|
|
909
|
+
const envNotice = initEnvNotice({
|
|
910
|
+
envUrl,
|
|
911
|
+
hasEnvFile,
|
|
912
|
+
hasEnvLocal,
|
|
913
|
+
canAutoLoadEnv,
|
|
914
|
+
flagUrl: args.url,
|
|
915
|
+
configUrl: config.url,
|
|
916
|
+
});
|
|
917
|
+
if (envNotice.kind === 'success')
|
|
918
|
+
success(envNotice.message);
|
|
919
|
+
else
|
|
920
|
+
info(envNotice.message);
|
|
787
921
|
newline();
|
|
788
922
|
// Heads-up (not an edit) about the consumer's module system. A CommonJS
|
|
789
923
|
// project (`npm init -y` default, or no "type" field) works fine now that the
|
|
@@ -800,7 +934,9 @@ async function cmdInit(args, config) {
|
|
|
800
934
|
const hasUrl = Boolean(url);
|
|
801
935
|
const interactive = Boolean(process.stdin.isTTY);
|
|
802
936
|
const yes = args.yes === true;
|
|
803
|
-
|
|
937
|
+
// Where init scaffolds the seed file. A root-level ./seed.ts from an older
|
|
938
|
+
// init still counts, so a re-run never scaffolds a second seed file.
|
|
939
|
+
const seedFilePath = config.seedFile ?? (existsSync('./seed.ts') ? './seed.ts' : DEFAULT_INIT_SEED_FILE);
|
|
804
940
|
const configPath = findConfigFile();
|
|
805
941
|
const state = {
|
|
806
942
|
configExists: Boolean(configPath),
|
|
@@ -834,6 +970,8 @@ async function cmdInit(args, config) {
|
|
|
834
970
|
// Supporting dirs + .gitignore, regardless of prompts (unchanged behavior).
|
|
835
971
|
ensureInitScaffoldDirs(config);
|
|
836
972
|
newline();
|
|
973
|
+
// TypeScript files this run actually created: they drive the tsx heads-up below.
|
|
974
|
+
const tsFilesWritten = [];
|
|
837
975
|
// Execute the plan in order. `run` proceeds; `prompt` asks; `skip` reports.
|
|
838
976
|
for (const step of plan) {
|
|
839
977
|
if (step.action === 'skip') {
|
|
@@ -849,12 +987,17 @@ async function cmdInit(args, config) {
|
|
|
849
987
|
case 'config':
|
|
850
988
|
writeFileSync('turbine.config.ts', configTemplate(args.url ?? undefined), 'utf-8');
|
|
851
989
|
success(state.configExists ? `Overwrote ${cyan('turbine.config.ts')}` : `Created ${cyan('turbine.config.ts')}`);
|
|
990
|
+
tsFilesWritten.push('turbine.config.ts');
|
|
852
991
|
break;
|
|
853
992
|
case 'schema':
|
|
854
993
|
writeInitSchemaTemplate(config);
|
|
994
|
+
if (needsTsLoader(config.schemaFile))
|
|
995
|
+
tsFilesWritten.push(config.schemaFile);
|
|
855
996
|
break;
|
|
856
997
|
case 'seed-file':
|
|
857
998
|
writeInitSeedTemplate(seedFilePath);
|
|
999
|
+
if (needsTsLoader(seedFilePath))
|
|
1000
|
+
tsFilesWritten.push(seedFilePath);
|
|
858
1001
|
break;
|
|
859
1002
|
case 'push':
|
|
860
1003
|
await runInitPush(config, url);
|
|
@@ -867,6 +1010,17 @@ async function cmdInit(args, config) {
|
|
|
867
1010
|
break;
|
|
868
1011
|
}
|
|
869
1012
|
}
|
|
1013
|
+
// Scaffolding .ts files and staying silent about tsx is how a fresh project
|
|
1014
|
+
// hits "Cannot load TypeScript file" on its very next command. Warn now, name
|
|
1015
|
+
// the exact install command, and never install anything ourselves.
|
|
1016
|
+
const tsxMissing = tsFilesWritten.length > 0 && !canResolveTsx();
|
|
1017
|
+
if (tsxMissing) {
|
|
1018
|
+
newline();
|
|
1019
|
+
const [headline, ...rest] = tsxRequiredNotice(tsFilesWritten, tsxInstallCommand(detectPackageManager()));
|
|
1020
|
+
warn(headline ?? '');
|
|
1021
|
+
for (const line of rest)
|
|
1022
|
+
console.log(line);
|
|
1023
|
+
}
|
|
870
1024
|
if (degraded) {
|
|
871
1025
|
newline();
|
|
872
1026
|
info('Non-interactive shell: interactive prompts were skipped.');
|
|
@@ -884,8 +1038,9 @@ async function cmdInit(args, config) {
|
|
|
884
1038
|
console.log(` ${dim('or create a')} ${cyan('.env')} ${dim('file with')} ${cyan('DATABASE_URL=postgres://...')}`);
|
|
885
1039
|
}
|
|
886
1040
|
console.log(` ${dim('2.')} Run ${cyan('npx turbine generate')} to introspect your DB`);
|
|
887
|
-
|
|
888
|
-
|
|
1041
|
+
// Only when the fuller heads-up above did not already run (nothing scaffolded).
|
|
1042
|
+
if (!tsxMissing && !canResolveTsx()) {
|
|
1043
|
+
console.log(` ${dim('Note: the TypeScript config requires')} ${cyan('tsx')}: ${cyan(tsxInstallCommand(detectPackageManager()))}`);
|
|
889
1044
|
}
|
|
890
1045
|
}
|
|
891
1046
|
else {
|
|
@@ -1010,20 +1165,30 @@ async function cmdGenerate(args, config) {
|
|
|
1010
1165
|
console.log(` ${dim('const user = await db.users.findUnique({ where: { id: 1 } });')}`);
|
|
1011
1166
|
newline();
|
|
1012
1167
|
}
|
|
1013
|
-
// ---------------------------------------------------------------------------
|
|
1014
|
-
// migrate-from-prisma
|
|
1015
|
-
// ---------------------------------------------------------------------------
|
|
1016
1168
|
/**
|
|
1017
|
-
* `
|
|
1018
|
-
* schema, resolve its models/fields/relations/compound-uniques against the live
|
|
1019
|
-
* database (unless `--no-db`), and emit (a) a Markdown resolution report and
|
|
1020
|
-
* (b) a typed `prisma-map.ts` name map next to the generated client.
|
|
1169
|
+
* Pick the connection string for `migrate-from-prisma`.
|
|
1021
1170
|
*
|
|
1022
|
-
*
|
|
1023
|
-
*
|
|
1024
|
-
*
|
|
1025
|
-
*
|
|
1171
|
+
* `configUrl` is what {@link resolveConfig} already produced (`--url`, then
|
|
1172
|
+
* `DATABASE_URL`, then `turbine.config.ts`) and always wins: an explicit flag
|
|
1173
|
+
* must never be overridden by a value declared in someone else's schema file.
|
|
1174
|
+
* Only when that is empty do we fall back to the `datasource` block, which
|
|
1175
|
+
* removes the flag a project with a non-standard variable name would otherwise
|
|
1176
|
+
* pass on every run.
|
|
1026
1177
|
*/
|
|
1178
|
+
export function resolveMigrateFromPrismaUrl(configUrl, ast, env) {
|
|
1179
|
+
const lookup = resolvePrismaDatasourceUrl(ast, env);
|
|
1180
|
+
if (configUrl)
|
|
1181
|
+
return { url: configUrl, source: 'config', missingVariables: lookup.missingVariables };
|
|
1182
|
+
if (lookup.resolved) {
|
|
1183
|
+
return {
|
|
1184
|
+
url: lookup.resolved.url,
|
|
1185
|
+
source: 'datasource',
|
|
1186
|
+
datasource: lookup.resolved,
|
|
1187
|
+
missingVariables: lookup.missingVariables,
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
return { source: 'none', missingVariables: lookup.missingVariables };
|
|
1191
|
+
}
|
|
1027
1192
|
async function cmdMigrateFromPrisma(args, config) {
|
|
1028
1193
|
banner();
|
|
1029
1194
|
// `--schema` is the Prisma schema file path in this command.
|
|
@@ -1061,7 +1226,15 @@ async function cmdMigrateFromPrisma(args, config) {
|
|
|
1061
1226
|
info('Parse-only mode (--no-db): names will not be resolved.');
|
|
1062
1227
|
}
|
|
1063
1228
|
else {
|
|
1064
|
-
|
|
1229
|
+
// The schema.prisma datasource is the LAST resort for the connection string,
|
|
1230
|
+
// below `--url`, `DATABASE_URL`, and `turbine.config.ts`.
|
|
1231
|
+
const resolvedUrl = resolveMigrateFromPrismaUrl(config.url, ast, process.env);
|
|
1232
|
+
if (resolvedUrl.source === 'datasource' && resolvedUrl.datasource) {
|
|
1233
|
+
const { variable, datasource: dsName, key } = resolvedUrl.datasource;
|
|
1234
|
+
const origin = variable ? `${cyan(variable)} via datasource "${dsName}"` : `datasource "${dsName}" ${key}`;
|
|
1235
|
+
info(`Using the connection string declared by your Prisma schema (${origin}).`);
|
|
1236
|
+
}
|
|
1237
|
+
url = resolvedUrl.url ?? requireUrl(config, { datasourceVars: resolvedUrl.missingVariables });
|
|
1065
1238
|
label('Database', redactUrl(url));
|
|
1066
1239
|
const spinner = new Spinner('Introspecting database schema').start();
|
|
1067
1240
|
schemaMeta = await introspect({
|
|
@@ -2752,6 +2925,8 @@ function showMigrateFromPrismaHelp() {
|
|
|
2752
2925
|
console.log(` ${dim('•')} ${cyan('prisma-map.ts')} - typed PRISMA_MAP name map`);
|
|
2753
2926
|
newline();
|
|
2754
2927
|
console.log(` ${dim('Note:')} here ${cyan('--schema')} names the Prisma FILE (not the Postgres namespace).`);
|
|
2928
|
+
console.log(` ${dim('Note:')} with no ${cyan('--url')} / ${cyan('DATABASE_URL')} / config ${cyan('url')}, the connection string`);
|
|
2929
|
+
console.log(` declared by your ${cyan('datasource')} block is used (including its ${cyan('env("...")')}).`);
|
|
2755
2930
|
newline();
|
|
2756
2931
|
console.log(` ${bold('Options:')}`);
|
|
2757
2932
|
console.log(` ${cyan('--schema')} ${dim('<file>')} Path to schema.prisma ${dim('(default: prisma/schema.prisma)')}`);
|
|
@@ -3012,27 +3187,6 @@ async function main() {
|
|
|
3012
3187
|
warn(`Found ${cyan('.env')} but this Node version cannot auto-load it. ` +
|
|
3013
3188
|
`Upgrade to Node 20.12+ or export ${cyan('DATABASE_URL')} yourself.`);
|
|
3014
3189
|
}
|
|
3015
|
-
// If the user has a TypeScript config file, register the tsx ESM loader
|
|
3016
|
-
// before we attempt to import it. Otherwise Node throws
|
|
3017
|
-
// ERR_UNKNOWN_FILE_EXTENSION for `.ts`.
|
|
3018
|
-
const configPath = findConfigFile();
|
|
3019
|
-
if (needsTsLoader(configPath)) {
|
|
3020
|
-
const status = await registerTsLoader();
|
|
3021
|
-
if (status === 'missing' || status === 'unsupported' || status === 'failed') {
|
|
3022
|
-
failMissingTsLoader(configPath ?? 'turbine.config.ts', status);
|
|
3023
|
-
}
|
|
3024
|
-
}
|
|
3025
|
-
// Load config file. A config that exists but fails to import is surfaced
|
|
3026
|
-
// loudly (with a name + the underlying error) instead of being swallowed and
|
|
3027
|
-
// later misreported as a missing database URL.
|
|
3028
|
-
const { config: fileConfig, loadError } = await loadConfigResult();
|
|
3029
|
-
if (loadError && args.command !== 'init') {
|
|
3030
|
-
const underlying = loadError.error instanceof Error ? loadError.error.message : String(loadError.error);
|
|
3031
|
-
warn(`Could not load ${cyan(loadError.filename)}: ${underlying}`);
|
|
3032
|
-
if (loadError.error instanceof Error)
|
|
3033
|
-
printCjsHintIfApplicable(loadError.error);
|
|
3034
|
-
newline();
|
|
3035
|
-
}
|
|
3036
3190
|
const overrides = {
|
|
3037
3191
|
url: args.url,
|
|
3038
3192
|
out: args.out,
|
|
@@ -3043,7 +3197,18 @@ async function main() {
|
|
|
3043
3197
|
keepColumnNames: args.keepColumnNames,
|
|
3044
3198
|
legacyToManyUniques: args.legacyToManyUniques,
|
|
3045
3199
|
};
|
|
3046
|
-
|
|
3200
|
+
// Resolve the config file (skipped entirely for config-free invocations such
|
|
3201
|
+
// as `studio --demo`). A config that exists but fails to import is surfaced
|
|
3202
|
+
// loudly (with a name + the underlying error) instead of being swallowed and
|
|
3203
|
+
// later misreported as a missing database URL.
|
|
3204
|
+
const { config, fileConfig, loadError } = await bootstrapCliConfig(args, overrides);
|
|
3205
|
+
if (loadError && args.command !== 'init') {
|
|
3206
|
+
const underlying = loadError.error instanceof Error ? loadError.error.message : String(loadError.error);
|
|
3207
|
+
warn(`Could not load ${cyan(loadError.filename)}: ${underlying}`);
|
|
3208
|
+
if (loadError.error instanceof Error)
|
|
3209
|
+
printCjsHintIfApplicable(loadError.error);
|
|
3210
|
+
newline();
|
|
3211
|
+
}
|
|
3047
3212
|
// Warn (don't change precedence) when an .env-sourced DATABASE_URL is silently
|
|
3048
3213
|
// overriding a differing, non-empty url in the config file (a wrong-database
|
|
3049
3214
|
// hazard for push/migrate/seed). Shell-exported DATABASE_URL stays silent.
|
package/dist/cli/migrate.d.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* -- DOWN
|
|
12
12
|
* DROP TABLE users;
|
|
13
13
|
*/
|
|
14
|
+
import pg from 'pg';
|
|
14
15
|
import type { DatabaseAdapter } from '../adapters/index.js';
|
|
15
16
|
import { type Dialect } from '../dialect.js';
|
|
16
17
|
import { type DestructiveStatement } from './destructive.js';
|
|
@@ -133,6 +134,29 @@ export declare function splitSqlStatements(sql: string): string[];
|
|
|
133
134
|
* Parse a migration file into UP and DOWN sections.
|
|
134
135
|
*/
|
|
135
136
|
export declare function parseMigrationSQL(filePath: string): ParsedMigration;
|
|
137
|
+
/**
|
|
138
|
+
* Can a stored pre-v0.6 checksum be safely upgraded to SHA-256?
|
|
139
|
+
*
|
|
140
|
+
* Only when the LEGACY algorithm, run over the file's CURRENT content,
|
|
141
|
+
* reproduces the stored value: that is what proves the file has not changed
|
|
142
|
+
* since it was applied. Upgrading without this proof blesses whatever the file
|
|
143
|
+
* says today and permanently disables drift detection for that migration.
|
|
144
|
+
*
|
|
145
|
+
* @internal exported for tests.
|
|
146
|
+
*/
|
|
147
|
+
export declare function canUpgradeLegacyChecksum(stored: string, content: string): boolean;
|
|
148
|
+
/**
|
|
149
|
+
* Is an applied migration's stored checksum still valid for the file on disk?
|
|
150
|
+
*
|
|
151
|
+
* A SHA-256 match is the normal case. A pre-v0.6 row that {@link
|
|
152
|
+
* canUpgradeLegacyChecksum} would upgrade counts as valid too: `migrate up`
|
|
153
|
+
* accepts and upgrades it, so reporting it as invalid in `migrate status` would
|
|
154
|
+
* have the two commands disagree about the same unchanged file. Genuine drift
|
|
155
|
+
* (a legacy hash the current content no longer reproduces) still reports false.
|
|
156
|
+
*
|
|
157
|
+
* @internal exported for tests.
|
|
158
|
+
*/
|
|
159
|
+
export declare function isChecksumValid(stored: string, content: string): boolean;
|
|
136
160
|
/**
|
|
137
161
|
* A named migration scaffold. `build()` returns the commented-SQL UP/DOWN body
|
|
138
162
|
* for the recipe; `createMigration({ recipe })` wraps it in the file header.
|
|
@@ -216,6 +240,28 @@ export declare function createMigration(migrationsDir: string, name: string, aut
|
|
|
216
240
|
* without contending on a single hardcoded lock ID.
|
|
217
241
|
*/
|
|
218
242
|
export declare function deriveLockId(databaseName: string): number;
|
|
243
|
+
/**
|
|
244
|
+
* The minimal query surface a transactional migration body needs.
|
|
245
|
+
* `pg.Client` satisfies it; tests supply a fake.
|
|
246
|
+
*/
|
|
247
|
+
export interface MigrationTxClient {
|
|
248
|
+
query(sql: string, params?: unknown[]): Promise<unknown>;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Run one migration body (UP or DOWN) plus its tracking-table write inside a
|
|
252
|
+
* single transaction. Returns `null` on success, or the error message on
|
|
253
|
+
* failure, leaving the caller to record it and stop.
|
|
254
|
+
*
|
|
255
|
+
* The ROLLBACK is best-effort: if the connection died, ROLLBACK throws too, and
|
|
256
|
+
* letting that escape would replace the real migration failure with a
|
|
257
|
+
* connection error. Same guard as `client.ts`, `query/builder.ts`, `dialect.ts`.
|
|
258
|
+
*
|
|
259
|
+
* @internal exported for tests; not part of the CLI's public surface.
|
|
260
|
+
*/
|
|
261
|
+
export declare function runMigrationInTransaction(client: MigrationTxClient, body: string, tracking: {
|
|
262
|
+
sql: string;
|
|
263
|
+
params: unknown[];
|
|
264
|
+
}): Promise<string | null>;
|
|
219
265
|
export interface ChecksumMismatch {
|
|
220
266
|
name: string;
|
|
221
267
|
expected: string;
|
|
@@ -227,6 +273,13 @@ export interface MigrationDeployPlan {
|
|
|
227
273
|
pending: MigrationFile[];
|
|
228
274
|
mismatches: ChecksumMismatch[];
|
|
229
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* Validate that applied migration files have not been modified or deleted since they were run.
|
|
278
|
+
* Returns an array of mismatched migrations (empty if all are clean).
|
|
279
|
+
*
|
|
280
|
+
* @internal exported for tests; not part of the CLI's public surface.
|
|
281
|
+
*/
|
|
282
|
+
export declare function validateChecksums(client: pg.Client, migrationsDir: string, dialect?: Dialect): Promise<ChecksumMismatch[]>;
|
|
230
283
|
export declare function formatChecksumMismatchError(mismatches: ChecksumMismatch[]): string;
|
|
231
284
|
/**
|
|
232
285
|
* Build a deploy plan from local migration files and applied migration rows.
|
|
@@ -237,9 +290,7 @@ export declare function planMigrationDeploy(migrationsDir: string, applied: Appl
|
|
|
237
290
|
/**
|
|
238
291
|
* Inspect deploy status without applying migrations.
|
|
239
292
|
*/
|
|
240
|
-
export declare function inspectMigrationDeploy(connectionString: string, migrationsDir: string
|
|
241
|
-
dialect?: Dialect;
|
|
242
|
-
}): Promise<MigrationDeployPlan>;
|
|
293
|
+
export declare function inspectMigrationDeploy(connectionString: string, migrationsDir: string): Promise<MigrationDeployPlan>;
|
|
243
294
|
/**
|
|
244
295
|
* Apply all pending migrations (UP).
|
|
245
296
|
*
|
|
@@ -261,7 +312,6 @@ export declare function migrateUp(connectionString: string, migrationsDir: strin
|
|
|
261
312
|
/** Run migrations even when they contain data-destroying statements. Default false. */
|
|
262
313
|
allowDestructive?: boolean;
|
|
263
314
|
adapter?: DatabaseAdapter;
|
|
264
|
-
dialect?: Dialect;
|
|
265
315
|
/**
|
|
266
316
|
* Called right before a `-- turbine:no-transaction` migration runs, so the
|
|
267
317
|
* CLI can print its loud pre-run notice (a concurrent index build can wait a
|
|
@@ -275,7 +325,6 @@ export declare function migrateUp(connectionString: string, migrationsDir: strin
|
|
|
275
325
|
*/
|
|
276
326
|
export declare function migrateDeploy(connectionString: string, migrationsDir: string, options?: {
|
|
277
327
|
adapter?: DatabaseAdapter;
|
|
278
|
-
dialect?: Dialect;
|
|
279
328
|
allowDrift?: boolean;
|
|
280
329
|
}): Promise<MigrationRunResult>;
|
|
281
330
|
/**
|
|
@@ -290,7 +339,6 @@ export declare function migrateDown(connectionString: string, migrationsDir: str
|
|
|
290
339
|
step?: number;
|
|
291
340
|
allowDestructive?: boolean;
|
|
292
341
|
adapter?: DatabaseAdapter;
|
|
293
|
-
dialect?: Dialect;
|
|
294
342
|
}): Promise<{
|
|
295
343
|
rolledBack: MigrationFile[];
|
|
296
344
|
errors: Array<{
|
|
@@ -300,8 +348,9 @@ export declare function migrateDown(connectionString: string, migrationsDir: str
|
|
|
300
348
|
}>;
|
|
301
349
|
/**
|
|
302
350
|
* Get the status of all migrations (applied vs pending).
|
|
303
|
-
*
|
|
351
|
+
*
|
|
352
|
+
* Applied rows carry `checksumValid`, decided by {@link isChecksumValid} so an
|
|
353
|
+
* unchanged pre-v0.6 row reports the same way `migrate up` treats it (valid,
|
|
354
|
+
* pending an in-place hash upgrade) rather than looking like drift.
|
|
304
355
|
*/
|
|
305
|
-
export declare function migrateStatus(connectionString: string, migrationsDir: string
|
|
306
|
-
dialect?: Dialect;
|
|
307
|
-
}): Promise<MigrationStatus[]>;
|
|
356
|
+
export declare function migrateStatus(connectionString: string, migrationsDir: string): Promise<MigrationStatus[]>;
|