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.
Files changed (157) hide show
  1. package/README.md +122 -39
  2. package/dist/cjs/adapters/cockroachdb.d.ts +39 -0
  3. package/dist/cjs/adapters/index.d.ts +110 -0
  4. package/dist/cjs/adapters/yugabytedb.d.ts +51 -0
  5. package/dist/cjs/cli/config.d.ts +181 -0
  6. package/dist/cjs/cli/config.js +32 -6
  7. package/dist/cjs/cli/destructive.d.ts +38 -0
  8. package/dist/cjs/cli/index.d.ts +359 -0
  9. package/dist/cjs/cli/index.js +228 -56
  10. package/dist/cjs/cli/loader.d.ts +61 -0
  11. package/dist/cjs/cli/mcp.d.ts +42 -0
  12. package/dist/cjs/cli/migrate.d.ts +356 -0
  13. package/dist/cjs/cli/migrate.js +131 -40
  14. package/dist/cjs/cli/observe-ui.d.ts +1 -0
  15. package/dist/cjs/cli/observe-ui.js +14 -5
  16. package/dist/cjs/cli/observe.d.ts +25 -0
  17. package/dist/cjs/cli/observe.js +49 -12
  18. package/dist/cjs/cli/pii-tags.d.ts +53 -0
  19. package/dist/cjs/cli/prisma-report.d.ts +33 -0
  20. package/dist/cjs/cli/prisma-report.js +73 -0
  21. package/dist/cjs/cli/prisma-resolve.d.ts +106 -0
  22. package/dist/cjs/cli/prisma-resolve.js +1 -0
  23. package/dist/cjs/cli/prisma-schema.d.ts +176 -0
  24. package/dist/cjs/cli/prisma-schema.js +82 -4
  25. package/dist/cjs/cli/rate-limit.d.ts +32 -0
  26. package/dist/cjs/cli/rate-limit.js +45 -0
  27. package/dist/cjs/cli/studio-demo.d.ts +43 -0
  28. package/dist/cjs/cli/studio-ui.generated.d.ts +1 -0
  29. package/dist/cjs/cli/studio.d.ts +207 -0
  30. package/dist/cjs/cli/studio.js +136 -71
  31. package/dist/cjs/cli/ui.d.ts +73 -0
  32. package/dist/cjs/cli/ui.js +51 -9
  33. package/dist/cjs/client.d.ts +837 -0
  34. package/dist/cjs/client.js +3 -0
  35. package/dist/cjs/dialect.d.ts +516 -0
  36. package/dist/cjs/dialect.js +37 -12
  37. package/dist/cjs/errors.d.ts +370 -0
  38. package/dist/cjs/generate.d.ts +137 -0
  39. package/dist/cjs/generate.js +39 -6
  40. package/dist/cjs/index-advisor.d.ts +153 -0
  41. package/dist/cjs/index-stats.d.ts +384 -0
  42. package/dist/cjs/index.d.ts +55 -0
  43. package/dist/cjs/index.js +7 -2
  44. package/dist/cjs/introspect.d.ts +269 -0
  45. package/dist/cjs/mssql.d.ts +232 -0
  46. package/dist/cjs/mssql.js +6 -0
  47. package/dist/cjs/mysql.d.ts +173 -0
  48. package/dist/cjs/mysql.js +16 -0
  49. package/dist/cjs/nested-write.d.ts +96 -0
  50. package/dist/cjs/nested-write.js +414 -24
  51. package/dist/cjs/observe.d.ts +115 -0
  52. package/dist/cjs/optional-peer-import.d.cts +72 -0
  53. package/dist/cjs/pipeline-submittable.d.ts +93 -0
  54. package/dist/cjs/pipeline.d.ts +71 -0
  55. package/dist/cjs/powdb-introspect.d.ts +84 -0
  56. package/dist/cjs/powdb.d.ts +931 -0
  57. package/dist/cjs/powdb.js +106 -21
  58. package/dist/cjs/powql.d.ts +592 -0
  59. package/dist/cjs/powql.js +42 -6
  60. package/dist/cjs/prisma-compat.d.ts +283 -0
  61. package/dist/cjs/prisma-compat.js +167 -9
  62. package/dist/cjs/query/aggregates.d.ts +92 -0
  63. package/dist/cjs/query/aggregates.js +7 -3
  64. package/dist/cjs/query/batched-loader.d.ts +193 -0
  65. package/dist/cjs/query/builder.d.ts +849 -0
  66. package/dist/cjs/query/builder.js +571 -65
  67. package/dist/cjs/query/compound-unique.d.ts +51 -0
  68. package/dist/cjs/query/deferred.d.ts +223 -0
  69. package/dist/cjs/query/filters.d.ts +201 -0
  70. package/dist/cjs/query/index.d.ts +14 -0
  71. package/dist/cjs/query/index.js +6 -1
  72. package/dist/cjs/query/relations.d.ts +609 -0
  73. package/dist/cjs/query/relations.js +693 -46
  74. package/dist/cjs/query/types.d.ts +1300 -0
  75. package/dist/cjs/query/utils.d.ts +209 -0
  76. package/dist/cjs/query/utils.js +208 -1
  77. package/dist/cjs/query/warn-registry.d.ts +68 -0
  78. package/dist/cjs/query/warn-registry.js +9 -0
  79. package/dist/cjs/query/where-compile.d.ts +139 -0
  80. package/dist/cjs/query/where.d.ts +548 -0
  81. package/dist/cjs/query/where.js +58 -22
  82. package/dist/cjs/query/writes.d.ts +172 -0
  83. package/dist/cjs/query/writes.js +105 -12
  84. package/dist/cjs/realtime.d.ts +70 -0
  85. package/dist/cjs/schema-builder.d.ts +354 -0
  86. package/dist/cjs/schema-metadata.d.ts +83 -0
  87. package/dist/cjs/schema-sql.d.ts +217 -0
  88. package/dist/cjs/schema-sql.js +23 -5
  89. package/dist/cjs/schema.d.ts +356 -0
  90. package/dist/cjs/schema.js +125 -0
  91. package/dist/cjs/seed.d.ts +15 -0
  92. package/dist/cjs/serverless.d.ts +142 -0
  93. package/dist/cjs/sqlite.d.ts +143 -0
  94. package/dist/cjs/sqlite.js +4 -0
  95. package/dist/cjs/typed-sql.d.ts +102 -0
  96. package/dist/cli/config.d.ts +18 -4
  97. package/dist/cli/config.js +31 -6
  98. package/dist/cli/index.d.ts +123 -0
  99. package/dist/cli/index.js +223 -58
  100. package/dist/cli/migrate.d.ts +59 -10
  101. package/dist/cli/migrate.js +128 -41
  102. package/dist/cli/observe-ui.d.ts +1 -1
  103. package/dist/cli/observe-ui.js +14 -5
  104. package/dist/cli/observe.d.ts +7 -1
  105. package/dist/cli/observe.js +48 -12
  106. package/dist/cli/prisma-report.d.ts +14 -0
  107. package/dist/cli/prisma-report.js +72 -0
  108. package/dist/cli/prisma-resolve.d.ts +6 -0
  109. package/dist/cli/prisma-resolve.js +1 -0
  110. package/dist/cli/prisma-schema.d.ts +62 -2
  111. package/dist/cli/prisma-schema.js +81 -4
  112. package/dist/cli/rate-limit.d.ts +32 -0
  113. package/dist/cli/rate-limit.js +40 -0
  114. package/dist/cli/studio.d.ts +5 -5
  115. package/dist/cli/studio.js +135 -70
  116. package/dist/cli/ui.d.ts +1 -1
  117. package/dist/cli/ui.js +51 -9
  118. package/dist/client.d.ts +40 -0
  119. package/dist/client.js +3 -0
  120. package/dist/dialect.d.ts +17 -1
  121. package/dist/dialect.js +37 -12
  122. package/dist/generate.js +40 -7
  123. package/dist/index.d.ts +1 -1
  124. package/dist/index.js +1 -1
  125. package/dist/mssql.js +6 -0
  126. package/dist/mysql.js +16 -0
  127. package/dist/nested-write.d.ts +2 -0
  128. package/dist/nested-write.js +415 -25
  129. package/dist/powdb.d.ts +4 -2
  130. package/dist/powdb.js +106 -21
  131. package/dist/powql.d.ts +5 -0
  132. package/dist/powql.js +42 -6
  133. package/dist/prisma-compat.d.ts +2 -0
  134. package/dist/prisma-compat.js +166 -8
  135. package/dist/query/aggregates.js +7 -3
  136. package/dist/query/builder.d.ts +292 -21
  137. package/dist/query/builder.js +570 -64
  138. package/dist/query/deferred.d.ts +39 -0
  139. package/dist/query/index.d.ts +1 -1
  140. package/dist/query/index.js +1 -1
  141. package/dist/query/relations.d.ts +173 -5
  142. package/dist/query/relations.js +688 -47
  143. package/dist/query/types.d.ts +123 -39
  144. package/dist/query/utils.d.ts +116 -0
  145. package/dist/query/utils.js +198 -0
  146. package/dist/query/warn-registry.d.ts +9 -0
  147. package/dist/query/warn-registry.js +9 -0
  148. package/dist/query/where.d.ts +38 -1
  149. package/dist/query/where.js +58 -23
  150. package/dist/query/writes.d.ts +42 -1
  151. package/dist/query/writes.js +104 -13
  152. package/dist/schema-sql.d.ts +14 -0
  153. package/dist/schema-sql.js +23 -5
  154. package/dist/schema.d.ts +38 -0
  155. package/dist/schema.js +123 -0
  156. package/dist/sqlite.js +4 -0
  157. package/package.json +77 -28
@@ -60,10 +60,17 @@ var __importStar = (this && this.__importStar) || (function () {
60
60
  })();
61
61
  Object.defineProperty(exports, "__esModule", { value: true });
62
62
  exports.parseArgs = parseArgs;
63
+ exports.usesProjectConfig = usesProjectConfig;
64
+ exports.bootstrapCliConfig = bootstrapCliConfig;
63
65
  exports.loadDotEnvForCli = loadDotEnvForCli;
64
66
  exports.dotEnvUrlConflictWarning = dotEnvUrlConflictWarning;
67
+ exports.detectPackageManager = detectPackageManager;
68
+ exports.tsxInstallCommand = tsxInstallCommand;
69
+ exports.tsxRequiredNotice = tsxRequiredNotice;
65
70
  exports.detectConsumerModuleType = detectConsumerModuleType;
66
71
  exports.planInitSteps = planInitSteps;
72
+ exports.initEnvNotice = initEnvNotice;
73
+ exports.resolveMigrateFromPrismaUrl = resolveMigrateFromPrismaUrl;
67
74
  exports.buildMigrateDeployOptions = buildMigrateDeployOptions;
68
75
  exports.getSeedExecutionPlan = getSeedExecutionPlan;
69
76
  exports.isLoopbackHost = isLoopbackHost;
@@ -310,9 +317,49 @@ function failMissingTsLoader(filePath, reason) {
310
317
  process.exit(1);
311
318
  }
312
319
  // ---------------------------------------------------------------------------
313
- // Helpers
320
+ // Config bootstrap
314
321
  // ---------------------------------------------------------------------------
315
- function requireUrl(config) {
322
+ /**
323
+ * Does this invocation need a `turbine.config.*` file?
324
+ *
325
+ * Everything does, with one deliberate exception: `turbine studio --demo` boots
326
+ * a seeded in-memory database, needs no `DATABASE_URL` and no config file, and
327
+ * is the very next command the quickstart suggests after `turbine init`.
328
+ * Resolving the config anyway means a freshly scaffolded directory (a
329
+ * `turbine.config.ts` with `tsx` not installed yet) dies with "Cannot load
330
+ * TypeScript file" before demo mode ever starts.
331
+ *
332
+ * @internal exported for tests.
333
+ */
334
+ function usesProjectConfig(args) {
335
+ return !(args.command === 'studio' && args.demo === true);
336
+ }
337
+ /**
338
+ * Resolve the effective CLI config: register the tsx loader when the config file
339
+ * is TypeScript, import it, then merge it with env vars and CLI flags. Exits with
340
+ * the actionable "Cannot load TypeScript file" error when a `.ts` config cannot
341
+ * be loaded. Config-free invocations short-circuit without touching the disk.
342
+ *
343
+ * @internal exported for tests.
344
+ */
345
+ async function bootstrapCliConfig(args, overrides) {
346
+ if (!usesProjectConfig(args)) {
347
+ return { config: (0, config_js_1.resolveConfig)({}, overrides), fileConfig: {}, skipped: true };
348
+ }
349
+ // If the user has a TypeScript config file, register the tsx ESM loader
350
+ // before we attempt to import it. Otherwise Node throws
351
+ // ERR_UNKNOWN_FILE_EXTENSION for `.ts`.
352
+ const configPath = (0, config_js_1.findConfigFile)();
353
+ if ((0, loader_js_1.needsTsLoader)(configPath)) {
354
+ const status = await (0, loader_js_1.registerTsLoader)();
355
+ if (status === 'missing' || status === 'unsupported' || status === 'failed') {
356
+ failMissingTsLoader(configPath ?? 'turbine.config.ts', status);
357
+ }
358
+ }
359
+ const { config: fileConfig, loadError } = await (0, config_js_1.loadConfigResult)();
360
+ return { config: (0, config_js_1.resolveConfig)(fileConfig, overrides), fileConfig, loadError, skipped: false };
361
+ }
362
+ function requireUrl(config, options = {}) {
316
363
  if (!config.url) {
317
364
  (0, ui_js_1.error)('No database URL provided.');
318
365
  (0, ui_js_1.newline)();
@@ -322,6 +369,12 @@ function requireUrl(config) {
322
369
  const envFileNote = typeof process.loadEnvFile === 'function' ? '(auto-loaded)' : '(needs Node 20.12+ to auto-load)';
323
370
  console.log(` ${(0, ui_js_1.dim)('2.')} Set ${(0, ui_js_1.cyan)('DATABASE_URL')} in your environment or a ${(0, ui_js_1.cyan)('.env')} file ${(0, ui_js_1.dim)(envFileNote)}`);
324
371
  console.log(` ${(0, ui_js_1.dim)('3.')} Pass ${(0, ui_js_1.cyan)('--url')} flag`);
372
+ const vars = options.datasourceVars ?? [];
373
+ if (vars.length > 0) {
374
+ const list = vars.map((v) => (0, ui_js_1.cyan)(v)).join(', ');
375
+ const plural = vars.length > 1 ? 'these variables' : 'this variable';
376
+ console.log(` ${(0, ui_js_1.dim)('4.')} Set ${list} ${(0, ui_js_1.dim)(`(${plural}, declared by your schema.prisma datasource, ${vars.length > 1 ? 'are' : 'is'} unset)`)}`);
377
+ }
325
378
  (0, ui_js_1.newline)();
326
379
  process.exit(1);
327
380
  }
@@ -473,6 +526,57 @@ function dotEnvUrlConflictWarning(input) {
473
526
  `(${(0, ui_js_1.redactUrl)(fileUrl)}). Using the .env value. Remove DATABASE_URL from .env, or unset the config url, ` +
474
527
  `to silence this.`);
475
528
  }
529
+ /**
530
+ * Detect the consumer's package manager from its lockfile, defaulting to npm.
531
+ * Used only to print an exact, copy-pasteable install command.
532
+ *
533
+ * @internal exported for tests.
534
+ */
535
+ function detectPackageManager(cwd = process.cwd()) {
536
+ if ((0, node_fs_1.existsSync)((0, node_path_1.join)(cwd, 'pnpm-lock.yaml')))
537
+ return 'pnpm';
538
+ if ((0, node_fs_1.existsSync)((0, node_path_1.join)(cwd, 'yarn.lock')))
539
+ return 'yarn';
540
+ if ((0, node_fs_1.existsSync)((0, node_path_1.join)(cwd, 'bun.lockb')) || (0, node_fs_1.existsSync)((0, node_path_1.join)(cwd, 'bun.lock')))
541
+ return 'bun';
542
+ return 'npm';
543
+ }
544
+ /**
545
+ * The exact "add tsx as a dev dependency" command for a package manager.
546
+ *
547
+ * @internal exported for tests.
548
+ */
549
+ function tsxInstallCommand(pm) {
550
+ switch (pm) {
551
+ case 'pnpm':
552
+ return 'pnpm add -D tsx';
553
+ case 'yarn':
554
+ return 'yarn add -D tsx';
555
+ case 'bun':
556
+ return 'bun add -d tsx';
557
+ default:
558
+ return 'npm install --save-dev tsx';
559
+ }
560
+ }
561
+ /**
562
+ * The heads-up `turbine init` prints when it has just scaffolded TypeScript
563
+ * files and `tsx` is not resolvable: without it the CLI cannot load them, and
564
+ * the very next command the user runs dies on "Cannot load TypeScript file".
565
+ * Pure (returns the lines, prints nothing) so it can be asserted in tests.
566
+ *
567
+ * @internal exported for tests.
568
+ */
569
+ function tsxRequiredNotice(tsFiles, installCommand) {
570
+ return [
571
+ `Turbine needs ${(0, ui_js_1.cyan)('tsx')} to load the TypeScript files just created:`,
572
+ ...tsFiles.map((f) => ` ${(0, ui_js_1.dim)(ui_js_1.symbols.dot)} ${(0, ui_js_1.cyan)(f)}`),
573
+ '',
574
+ ` ${(0, ui_js_1.dim)('Install it as a dev dependency:')}`,
575
+ ` ${(0, ui_js_1.cyan)(installCommand)}`,
576
+ '',
577
+ ` ${(0, ui_js_1.dim)('Without it, the next Turbine command fails with')} ${(0, ui_js_1.dim)('"Cannot load TypeScript file".')}`,
578
+ ];
579
+ }
476
580
  /**
477
581
  * Read the consumer's `package.json` `"type"` field. Returns `'module'` for an
478
582
  * ESM project, `'commonjs'` for an explicit or absent (defaulted) CommonJS
@@ -798,6 +902,48 @@ function initPromptQuestion(step, config, seedFilePath) {
798
902
  return `Run ${initStepLabel(step.id)}?`;
799
903
  }
800
904
  }
905
+ /**
906
+ * Decide which connection notice `turbine init` prints. Pure so the whole
907
+ * decision matrix is testable.
908
+ *
909
+ * "No DATABASE_URL found in environment" is reserved for the case where NO
910
+ * source supplied one: printing it while happily using `--url` (or a config
911
+ * `url`) reads like a failure the user then goes looking for.
912
+ *
913
+ * @internal exported for tests.
914
+ */
915
+ function initEnvNotice(input) {
916
+ if (input.envUrl) {
917
+ return { kind: 'success', message: `Detected ${(0, ui_js_1.cyan)('DATABASE_URL')} in the environment` };
918
+ }
919
+ if (input.hasEnvFile && !input.canAutoLoadEnv) {
920
+ return {
921
+ kind: 'info',
922
+ message: `Found ${(0, ui_js_1.cyan)('.env')} ${(0, ui_js_1.dim)('(this Node version cannot auto-load it. Upgrade to Node 20.12+ or export')} ${(0, ui_js_1.cyan)('DATABASE_URL')}${(0, ui_js_1.dim)(')')}`,
923
+ };
924
+ }
925
+ if (input.hasEnvFile) {
926
+ // .env exists but did not provide DATABASE_URL; if it had, the auto-load
927
+ // in main() would have populated envUrl above.
928
+ return {
929
+ kind: 'info',
930
+ message: `Found ${(0, ui_js_1.cyan)('.env')} ${(0, ui_js_1.dim)('(no')} ${(0, ui_js_1.cyan)('DATABASE_URL')} ${(0, ui_js_1.dim)('set in it yet)')}`,
931
+ };
932
+ }
933
+ if (input.hasEnvLocal) {
934
+ return {
935
+ kind: 'info',
936
+ message: `Found ${(0, ui_js_1.cyan)('.env.local')} ${(0, ui_js_1.dim)('(note: Turbine only auto-loads')} ${(0, ui_js_1.cyan)('.env')}${(0, ui_js_1.dim)(')')}`,
937
+ };
938
+ }
939
+ if (input.flagUrl) {
940
+ return { kind: 'success', message: `Using the connection string passed with ${(0, ui_js_1.cyan)('--url')}` };
941
+ }
942
+ if (input.configUrl) {
943
+ return { kind: 'success', message: `Using the ${(0, ui_js_1.cyan)('url')} from your config file` };
944
+ }
945
+ return { kind: 'info', message: `No ${(0, ui_js_1.cyan)('DATABASE_URL')} found in environment` };
946
+ }
801
947
  async function cmdInit(args, config) {
802
948
  (0, ui_js_1.banner)();
803
949
  (0, ui_js_1.header)('Initializing Turbine project');
@@ -810,23 +956,18 @@ async function cmdInit(args, config) {
810
956
  // On Node < 20.12 (no process.loadEnvFile) main() could not auto-load .env, so
811
957
  // we cannot claim it "has no DATABASE_URL"; we simply could not read it.
812
958
  const canAutoLoadEnv = typeof process.loadEnvFile === 'function';
813
- if (envUrl) {
814
- (0, ui_js_1.success)(`Detected ${(0, ui_js_1.cyan)('DATABASE_URL')} in the environment`);
815
- }
816
- else if (hasEnvFile && !canAutoLoadEnv) {
817
- (0, ui_js_1.info)(`Found ${(0, ui_js_1.cyan)('.env')} ${(0, ui_js_1.dim)('(this Node version cannot auto-load it. Upgrade to Node 20.12+ or export')} ${(0, ui_js_1.cyan)('DATABASE_URL')}${(0, ui_js_1.dim)(')')}`);
818
- }
819
- else if (hasEnvFile) {
820
- // .env exists but did not provide DATABASE_URL; if it had, the auto-load
821
- // in main() would have populated envUrl above.
822
- (0, ui_js_1.info)(`Found ${(0, ui_js_1.cyan)('.env')} ${(0, ui_js_1.dim)('(no')} ${(0, ui_js_1.cyan)('DATABASE_URL')} ${(0, ui_js_1.dim)('set in it yet)')}`);
823
- }
824
- else if (hasEnvLocal) {
825
- (0, ui_js_1.info)(`Found ${(0, ui_js_1.cyan)('.env.local')} ${(0, ui_js_1.dim)('(note: Turbine only auto-loads')} ${(0, ui_js_1.cyan)('.env')}${(0, ui_js_1.dim)(')')}`);
826
- }
827
- else {
828
- (0, ui_js_1.info)(`No ${(0, ui_js_1.cyan)('DATABASE_URL')} found in environment`);
829
- }
959
+ const envNotice = initEnvNotice({
960
+ envUrl,
961
+ hasEnvFile,
962
+ hasEnvLocal,
963
+ canAutoLoadEnv,
964
+ flagUrl: args.url,
965
+ configUrl: config.url,
966
+ });
967
+ if (envNotice.kind === 'success')
968
+ (0, ui_js_1.success)(envNotice.message);
969
+ else
970
+ (0, ui_js_1.info)(envNotice.message);
830
971
  (0, ui_js_1.newline)();
831
972
  // Heads-up (not an edit) about the consumer's module system. A CommonJS
832
973
  // project (`npm init -y` default, or no "type" field) works fine now that the
@@ -843,7 +984,9 @@ async function cmdInit(args, config) {
843
984
  const hasUrl = Boolean(url);
844
985
  const interactive = Boolean(process.stdin.isTTY);
845
986
  const yes = args.yes === true;
846
- const seedFilePath = config.seedFile ?? './seed.ts';
987
+ // Where init scaffolds the seed file. A root-level ./seed.ts from an older
988
+ // init still counts, so a re-run never scaffolds a second seed file.
989
+ const seedFilePath = config.seedFile ?? ((0, node_fs_1.existsSync)('./seed.ts') ? './seed.ts' : config_js_1.DEFAULT_INIT_SEED_FILE);
847
990
  const configPath = (0, config_js_1.findConfigFile)();
848
991
  const state = {
849
992
  configExists: Boolean(configPath),
@@ -877,6 +1020,8 @@ async function cmdInit(args, config) {
877
1020
  // Supporting dirs + .gitignore, regardless of prompts (unchanged behavior).
878
1021
  ensureInitScaffoldDirs(config);
879
1022
  (0, ui_js_1.newline)();
1023
+ // TypeScript files this run actually created: they drive the tsx heads-up below.
1024
+ const tsFilesWritten = [];
880
1025
  // Execute the plan in order. `run` proceeds; `prompt` asks; `skip` reports.
881
1026
  for (const step of plan) {
882
1027
  if (step.action === 'skip') {
@@ -892,12 +1037,17 @@ async function cmdInit(args, config) {
892
1037
  case 'config':
893
1038
  (0, node_fs_1.writeFileSync)('turbine.config.ts', (0, config_js_1.configTemplate)(args.url ?? undefined), 'utf-8');
894
1039
  (0, ui_js_1.success)(state.configExists ? `Overwrote ${(0, ui_js_1.cyan)('turbine.config.ts')}` : `Created ${(0, ui_js_1.cyan)('turbine.config.ts')}`);
1040
+ tsFilesWritten.push('turbine.config.ts');
895
1041
  break;
896
1042
  case 'schema':
897
1043
  writeInitSchemaTemplate(config);
1044
+ if ((0, loader_js_1.needsTsLoader)(config.schemaFile))
1045
+ tsFilesWritten.push(config.schemaFile);
898
1046
  break;
899
1047
  case 'seed-file':
900
1048
  writeInitSeedTemplate(seedFilePath);
1049
+ if ((0, loader_js_1.needsTsLoader)(seedFilePath))
1050
+ tsFilesWritten.push(seedFilePath);
901
1051
  break;
902
1052
  case 'push':
903
1053
  await runInitPush(config, url);
@@ -910,6 +1060,17 @@ async function cmdInit(args, config) {
910
1060
  break;
911
1061
  }
912
1062
  }
1063
+ // Scaffolding .ts files and staying silent about tsx is how a fresh project
1064
+ // hits "Cannot load TypeScript file" on its very next command. Warn now, name
1065
+ // the exact install command, and never install anything ourselves.
1066
+ const tsxMissing = tsFilesWritten.length > 0 && !(0, loader_js_1.canResolveTsx)();
1067
+ if (tsxMissing) {
1068
+ (0, ui_js_1.newline)();
1069
+ const [headline, ...rest] = tsxRequiredNotice(tsFilesWritten, tsxInstallCommand(detectPackageManager()));
1070
+ (0, ui_js_1.warn)(headline ?? '');
1071
+ for (const line of rest)
1072
+ console.log(line);
1073
+ }
913
1074
  if (degraded) {
914
1075
  (0, ui_js_1.newline)();
915
1076
  (0, ui_js_1.info)('Non-interactive shell: interactive prompts were skipped.');
@@ -927,8 +1088,9 @@ async function cmdInit(args, config) {
927
1088
  console.log(` ${(0, ui_js_1.dim)('or create a')} ${(0, ui_js_1.cyan)('.env')} ${(0, ui_js_1.dim)('file with')} ${(0, ui_js_1.cyan)('DATABASE_URL=postgres://...')}`);
928
1089
  }
929
1090
  console.log(` ${(0, ui_js_1.dim)('2.')} Run ${(0, ui_js_1.cyan)('npx turbine generate')} to introspect your DB`);
930
- if (!(0, loader_js_1.canResolveTsx)()) {
931
- console.log(` ${(0, ui_js_1.dim)('Note: the TypeScript config requires')} ${(0, ui_js_1.cyan)('tsx')} ${(0, ui_js_1.dim)('—')} ${(0, ui_js_1.cyan)('npm install --save-dev tsx')}`);
1091
+ // Only when the fuller heads-up above did not already run (nothing scaffolded).
1092
+ if (!tsxMissing && !(0, loader_js_1.canResolveTsx)()) {
1093
+ console.log(` ${(0, ui_js_1.dim)('Note: the TypeScript config requires')} ${(0, ui_js_1.cyan)('tsx')}: ${(0, ui_js_1.cyan)(tsxInstallCommand(detectPackageManager()))}`);
932
1094
  }
933
1095
  }
934
1096
  else {
@@ -1053,20 +1215,30 @@ async function cmdGenerate(args, config) {
1053
1215
  console.log(` ${(0, ui_js_1.dim)('const user = await db.users.findUnique({ where: { id: 1 } });')}`);
1054
1216
  (0, ui_js_1.newline)();
1055
1217
  }
1056
- // ---------------------------------------------------------------------------
1057
- // migrate-from-prisma
1058
- // ---------------------------------------------------------------------------
1059
1218
  /**
1060
- * `turbine migrate-from-prisma --schema prisma/schema.prisma` parses a Prisma
1061
- * schema, resolve its models/fields/relations/compound-uniques against the live
1062
- * database (unless `--no-db`), and emit (a) a Markdown resolution report and
1063
- * (b) a typed `prisma-map.ts` name map next to the generated client.
1219
+ * Pick the connection string for `migrate-from-prisma`.
1064
1220
  *
1065
- * NOTE: within THIS command `--schema` names the Prisma schema FILE (not the
1066
- * Postgres namespace, which the rest of the CLI's `--schema` means). The
1067
- * Postgres namespace is `public` here; multi-schema (`@@schema`) is unsupported
1068
- * in v1 and listed as a parser note in the report.
1221
+ * `configUrl` is what {@link resolveConfig} already produced (`--url`, then
1222
+ * `DATABASE_URL`, then `turbine.config.ts`) and always wins: an explicit flag
1223
+ * must never be overridden by a value declared in someone else's schema file.
1224
+ * Only when that is empty do we fall back to the `datasource` block, which
1225
+ * removes the flag a project with a non-standard variable name would otherwise
1226
+ * pass on every run.
1069
1227
  */
1228
+ function resolveMigrateFromPrismaUrl(configUrl, ast, env) {
1229
+ const lookup = (0, prisma_schema_js_1.resolvePrismaDatasourceUrl)(ast, env);
1230
+ if (configUrl)
1231
+ return { url: configUrl, source: 'config', missingVariables: lookup.missingVariables };
1232
+ if (lookup.resolved) {
1233
+ return {
1234
+ url: lookup.resolved.url,
1235
+ source: 'datasource',
1236
+ datasource: lookup.resolved,
1237
+ missingVariables: lookup.missingVariables,
1238
+ };
1239
+ }
1240
+ return { source: 'none', missingVariables: lookup.missingVariables };
1241
+ }
1070
1242
  async function cmdMigrateFromPrisma(args, config) {
1071
1243
  (0, ui_js_1.banner)();
1072
1244
  // `--schema` is the Prisma schema file path in this command.
@@ -1104,7 +1276,15 @@ async function cmdMigrateFromPrisma(args, config) {
1104
1276
  (0, ui_js_1.info)('Parse-only mode (--no-db): names will not be resolved.');
1105
1277
  }
1106
1278
  else {
1107
- url = requireUrl(config);
1279
+ // The schema.prisma datasource is the LAST resort for the connection string,
1280
+ // below `--url`, `DATABASE_URL`, and `turbine.config.ts`.
1281
+ const resolvedUrl = resolveMigrateFromPrismaUrl(config.url, ast, process.env);
1282
+ if (resolvedUrl.source === 'datasource' && resolvedUrl.datasource) {
1283
+ const { variable, datasource: dsName, key } = resolvedUrl.datasource;
1284
+ const origin = variable ? `${(0, ui_js_1.cyan)(variable)} via datasource "${dsName}"` : `datasource "${dsName}" ${key}`;
1285
+ (0, ui_js_1.info)(`Using the connection string declared by your Prisma schema (${origin}).`);
1286
+ }
1287
+ url = resolvedUrl.url ?? requireUrl(config, { datasourceVars: resolvedUrl.missingVariables });
1108
1288
  (0, ui_js_1.label)('Database', (0, ui_js_1.redactUrl)(url));
1109
1289
  const spinner = new ui_js_1.Spinner('Introspecting database schema').start();
1110
1290
  schemaMeta = await (0, introspect_js_1.introspect)({
@@ -2795,6 +2975,8 @@ function showMigrateFromPrismaHelp() {
2795
2975
  console.log(` ${(0, ui_js_1.dim)('•')} ${(0, ui_js_1.cyan)('prisma-map.ts')} - typed PRISMA_MAP name map`);
2796
2976
  (0, ui_js_1.newline)();
2797
2977
  console.log(` ${(0, ui_js_1.dim)('Note:')} here ${(0, ui_js_1.cyan)('--schema')} names the Prisma FILE (not the Postgres namespace).`);
2978
+ console.log(` ${(0, ui_js_1.dim)('Note:')} with no ${(0, ui_js_1.cyan)('--url')} / ${(0, ui_js_1.cyan)('DATABASE_URL')} / config ${(0, ui_js_1.cyan)('url')}, the connection string`);
2979
+ console.log(` declared by your ${(0, ui_js_1.cyan)('datasource')} block is used (including its ${(0, ui_js_1.cyan)('env("...")')}).`);
2798
2980
  (0, ui_js_1.newline)();
2799
2981
  console.log(` ${(0, ui_js_1.bold)('Options:')}`);
2800
2982
  console.log(` ${(0, ui_js_1.cyan)('--schema')} ${(0, ui_js_1.dim)('<file>')} Path to schema.prisma ${(0, ui_js_1.dim)('(default: prisma/schema.prisma)')}`);
@@ -3055,27 +3237,6 @@ async function main() {
3055
3237
  (0, ui_js_1.warn)(`Found ${(0, ui_js_1.cyan)('.env')} but this Node version cannot auto-load it. ` +
3056
3238
  `Upgrade to Node 20.12+ or export ${(0, ui_js_1.cyan)('DATABASE_URL')} yourself.`);
3057
3239
  }
3058
- // If the user has a TypeScript config file, register the tsx ESM loader
3059
- // before we attempt to import it. Otherwise Node throws
3060
- // ERR_UNKNOWN_FILE_EXTENSION for `.ts`.
3061
- const configPath = (0, config_js_1.findConfigFile)();
3062
- if ((0, loader_js_1.needsTsLoader)(configPath)) {
3063
- const status = await (0, loader_js_1.registerTsLoader)();
3064
- if (status === 'missing' || status === 'unsupported' || status === 'failed') {
3065
- failMissingTsLoader(configPath ?? 'turbine.config.ts', status);
3066
- }
3067
- }
3068
- // Load config file. A config that exists but fails to import is surfaced
3069
- // loudly (with a name + the underlying error) instead of being swallowed and
3070
- // later misreported as a missing database URL.
3071
- const { config: fileConfig, loadError } = await (0, config_js_1.loadConfigResult)();
3072
- if (loadError && args.command !== 'init') {
3073
- const underlying = loadError.error instanceof Error ? loadError.error.message : String(loadError.error);
3074
- (0, ui_js_1.warn)(`Could not load ${(0, ui_js_1.cyan)(loadError.filename)}: ${underlying}`);
3075
- if (loadError.error instanceof Error)
3076
- printCjsHintIfApplicable(loadError.error);
3077
- (0, ui_js_1.newline)();
3078
- }
3079
3240
  const overrides = {
3080
3241
  url: args.url,
3081
3242
  out: args.out,
@@ -3086,7 +3247,18 @@ async function main() {
3086
3247
  keepColumnNames: args.keepColumnNames,
3087
3248
  legacyToManyUniques: args.legacyToManyUniques,
3088
3249
  };
3089
- const config = (0, config_js_1.resolveConfig)(fileConfig, overrides);
3250
+ // Resolve the config file (skipped entirely for config-free invocations such
3251
+ // as `studio --demo`). A config that exists but fails to import is surfaced
3252
+ // loudly (with a name + the underlying error) instead of being swallowed and
3253
+ // later misreported as a missing database URL.
3254
+ const { config, fileConfig, loadError } = await bootstrapCliConfig(args, overrides);
3255
+ if (loadError && args.command !== 'init') {
3256
+ const underlying = loadError.error instanceof Error ? loadError.error.message : String(loadError.error);
3257
+ (0, ui_js_1.warn)(`Could not load ${(0, ui_js_1.cyan)(loadError.filename)}: ${underlying}`);
3258
+ if (loadError.error instanceof Error)
3259
+ printCjsHintIfApplicable(loadError.error);
3260
+ (0, ui_js_1.newline)();
3261
+ }
3090
3262
  // Warn (don't change precedence) when an .env-sourced DATABASE_URL is silently
3091
3263
  // overriding a differing, non-empty url in the config file (a wrong-database
3092
3264
  // hazard for push/migrate/seed). Shell-exported DATABASE_URL stays silent.
@@ -0,0 +1,61 @@
1
+ /**
2
+ * turbine-orm CLI — TypeScript loader registration
3
+ *
4
+ * The CLI loads user-supplied config and schema files via dynamic `import()`.
5
+ * Plain Node has no built-in `.ts` loader, so importing `turbine.config.ts`
6
+ * blows up with `ERR_UNKNOWN_FILE_EXTENSION` unless we register a TypeScript
7
+ * loader first.
8
+ *
9
+ * Strategy:
10
+ * 1. If the file we're about to import ends in `.ts` / `.mts` / `.cts`,
11
+ * probe whether `tsx` is resolvable from the user's CWD.
12
+ * 2. Prefer tsx's supported programmatic API, `tsx/esm/api`'s `register()`.
13
+ * Calling Node's `module.register('tsx/esm', ...)` directly throws
14
+ * "tsx must be loaded with --import instead of --loader" on every Node
15
+ * version that has `module.register()` (>= 20.6) — tsx's hook file
16
+ * guards against being loaded that way. The `tsx/esm/api` entry point
17
+ * is the documented path and works everywhere `module.register()` does.
18
+ * 3. Fall back to `module.register('tsx/esm', ...)` only for very old tsx
19
+ * versions (< 4.0) that predate `tsx/esm/api`.
20
+ * 4. If tsx isn't installed, or registration genuinely fails, surface an
21
+ * actionable error — including the REAL underlying error message, never
22
+ * a misdiagnosed "tsx is not installed".
23
+ *
24
+ * `tsx` is intentionally NOT a runtime dependency — many projects already
25
+ * have it, and adding a heavy dev tool to a 1-dependency ORM would be silly.
26
+ */
27
+ /**
28
+ * Detect whether a config / schema file path needs the tsx ESM loader.
29
+ * Returns true for `.ts`, `.mts`, and `.cts` files; false for `.js`, `.mjs`,
30
+ * `.cjs`, `.json`, missing paths, or anything else.
31
+ */
32
+ export declare function needsTsLoader(filePath: string | null | undefined): boolean;
33
+ /**
34
+ * Probe whether `tsx/esm` is resolvable from the user's current working
35
+ * directory. Returns true if `tsx` is installed in the user's project.
36
+ *
37
+ * Accepts an injected `resolver` so unit tests don't need a real filesystem.
38
+ */
39
+ export declare function canResolveTsx(resolver?: (id: string) => string): boolean;
40
+ export type TsLoaderStatus = 'registered' | 'already' | 'unsupported' | 'missing' | 'failed';
41
+ /**
42
+ * The underlying error message from the last failed registration attempt,
43
+ * or null. Lets the CLI report the REAL cause instead of guessing.
44
+ */
45
+ export declare function getTsLoaderError(): string | null;
46
+ /**
47
+ * Register the tsx ESM loader so subsequent dynamic imports of `.ts` files
48
+ * work. Safe to call multiple times — internal flag prevents double registration.
49
+ *
50
+ * Returns:
51
+ * - 'registered' loader was successfully registered this call
52
+ * - 'already' a loader was previously registered (idempotent)
53
+ * - 'unsupported' Node lacks `module.register()` (Node < 20.6) and tsx has
54
+ * no programmatic API to fall back to
55
+ * - 'missing' `tsx` is not installed in the user's project
56
+ * - 'failed' tsx IS installed but registration threw — see
57
+ * {@link getTsLoaderError} for the underlying message
58
+ */
59
+ export declare function registerTsLoader(): Promise<TsLoaderStatus>;
60
+ /** Reset the loader state — used by unit tests only. */
61
+ export declare function _resetTsLoaderStateForTests(): void;
@@ -0,0 +1,42 @@
1
+ import type { Readable, Writable } from 'node:stream';
2
+ import { type ColumnMetadata, type RelationDef } from '../schema.js';
3
+ export interface McpServerOptions {
4
+ url: string;
5
+ schema: string;
6
+ migrationsDir: string;
7
+ include?: string[];
8
+ exclude?: string[];
9
+ /**
10
+ * Directory holding generated Turbine metadata (`turbine generate`'s `out`).
11
+ * PII tags are code-first declarations that introspection never sets, so
12
+ * without this the server has nothing to redact against. Read as text;
13
+ * nothing from it is executed. See `pii-tags.ts`.
14
+ */
15
+ metadataDir?: string;
16
+ }
17
+ export interface McpTransport {
18
+ input?: Readable;
19
+ output?: Writable;
20
+ }
21
+ export interface McpServerHandle {
22
+ dispose(): Promise<void>;
23
+ }
24
+ export declare function startMcpServer(options: McpServerOptions, transport?: McpTransport): McpServerHandle;
25
+ interface ForeignKeyRow {
26
+ source_table: string;
27
+ source_column: string;
28
+ target_table: string;
29
+ target_column: string;
30
+ constraint_name: string;
31
+ }
32
+ /**
33
+ * Group raw FK rows into constraint-level entries and delegate relation
34
+ * naming to the SHARED introspection builder (`buildRelationsFromForeignKeys`
35
+ * + `addAutoManyToManyRelations` in ../introspect.ts). MCP previously carried
36
+ * a stale copy of a retired naming scheme, so `turbine mcp` and `turbine
37
+ * generate` derived DIFFERENT relation names from the same database (N-3).
38
+ * Exported for the parity unit test.
39
+ */
40
+ export declare function buildRelations(tableNames: string[], columnsByTable: Map<string, ColumnMetadata[]>, pkByTable: Map<string, string[]>, rows: ForeignKeyRow[], enums?: Record<string, string[]>): Map<string, Record<string, RelationDef>>;
41
+ export declare function runMcpServer(options: McpServerOptions): Promise<void>;
42
+ export {};