turbine-orm 0.27.0 → 0.28.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 (52) hide show
  1. package/README.md +17 -13
  2. package/dist/cjs/cli/config.js +20 -3
  3. package/dist/cjs/cli/destructive.js +47 -31
  4. package/dist/cjs/cli/index.js +273 -71
  5. package/dist/cjs/cli/mcp.js +788 -0
  6. package/dist/cjs/cli/migrate.js +95 -20
  7. package/dist/cjs/cli/studio.js +3 -2
  8. package/dist/cjs/client.js +267 -34
  9. package/dist/cjs/dialect.js +2 -0
  10. package/dist/cjs/generate.js +171 -7
  11. package/dist/cjs/index.js +4 -1
  12. package/dist/cjs/introspect.js +177 -4
  13. package/dist/cjs/query/batched-loader.js +148 -0
  14. package/dist/cjs/query/builder.js +714 -133
  15. package/dist/cjs/schema-builder.js +59 -4
  16. package/dist/cjs/schema-sql.js +315 -6
  17. package/dist/cjs/seed.js +66 -0
  18. package/dist/cli/config.d.ts +9 -2
  19. package/dist/cli/config.js +19 -3
  20. package/dist/cli/destructive.js +47 -31
  21. package/dist/cli/index.d.ts +52 -1
  22. package/dist/cli/index.js +272 -74
  23. package/dist/cli/mcp.d.ts +17 -0
  24. package/dist/cli/mcp.js +781 -0
  25. package/dist/cli/migrate.d.ts +37 -0
  26. package/dist/cli/migrate.js +92 -20
  27. package/dist/cli/studio.d.ts +3 -2
  28. package/dist/cli/studio.js +3 -2
  29. package/dist/client.d.ts +136 -1
  30. package/dist/client.js +267 -34
  31. package/dist/dialect.d.ts +17 -0
  32. package/dist/dialect.js +2 -0
  33. package/dist/generate.d.ts +17 -0
  34. package/dist/generate.js +171 -10
  35. package/dist/index.d.ts +4 -3
  36. package/dist/index.js +2 -0
  37. package/dist/introspect.d.ts +20 -1
  38. package/dist/introspect.js +175 -4
  39. package/dist/query/batched-loader.d.ts +29 -2
  40. package/dist/query/batched-loader.js +148 -1
  41. package/dist/query/builder.d.ts +156 -8
  42. package/dist/query/builder.js +715 -134
  43. package/dist/query/index.d.ts +1 -1
  44. package/dist/query/types.d.ts +113 -8
  45. package/dist/schema-builder.d.ts +73 -8
  46. package/dist/schema-builder.js +59 -4
  47. package/dist/schema-sql.d.ts +67 -0
  48. package/dist/schema-sql.js +310 -6
  49. package/dist/schema.d.ts +53 -0
  50. package/dist/seed.d.ts +4 -0
  51. package/dist/seed.js +63 -0
  52. package/package.json +2 -3
@@ -24,6 +24,7 @@ export function looksLikeSchemaFilePath(schema) {
24
24
  // Config file names, in priority order
25
25
  // ---------------------------------------------------------------------------
26
26
  const CONFIG_FILES = ['turbine.config.ts', 'turbine.config.mts', 'turbine.config.js', 'turbine.config.mjs'];
27
+ const DEFAULT_SEED_CANDIDATES = ['seed.ts', 'seed.js', 'seed.sql'];
27
28
  // ---------------------------------------------------------------------------
28
29
  // Load config
29
30
  // ---------------------------------------------------------------------------
@@ -81,10 +82,25 @@ export function resolveConfig(fileConfig, overrides) {
81
82
  include: overrides.include ?? fileConfig.include ?? [],
82
83
  exclude: overrides.exclude ?? fileConfig.exclude ?? [],
83
84
  migrationsDir: fileConfig.migrationsDir ?? './turbine/migrations',
84
- seedFile: fileConfig.seedFile ?? './turbine/seed.ts',
85
+ seedFile: fileConfig.seed ?? fileConfig.seedFile,
85
86
  schemaFile: fileConfig.schemaFile ?? './turbine/schema.ts',
86
87
  };
87
88
  }
89
+ /**
90
+ * Resolve the seed file path. An explicit config value wins even if the file
91
+ * does not exist yet; otherwise the root-level defaults are tried in order.
92
+ */
93
+ export function resolveSeedFile(config, cwd = process.cwd()) {
94
+ const explicit = config.seed ?? config.seedFile;
95
+ if (explicit)
96
+ return resolve(cwd, explicit);
97
+ for (const candidate of DEFAULT_SEED_CANDIDATES) {
98
+ const filePath = resolve(cwd, candidate);
99
+ if (existsSync(filePath))
100
+ return filePath;
101
+ }
102
+ return null;
103
+ }
88
104
  // ---------------------------------------------------------------------------
89
105
  // Config file template (for `turbine init`)
90
106
  // ---------------------------------------------------------------------------
@@ -113,8 +129,8 @@ ${urlLine}
113
129
  /** Directory for SQL migration files */
114
130
  migrationsDir: './turbine/migrations',
115
131
 
116
- /** Path to seed file */
117
- seedFile: './turbine/seed.ts',
132
+ /** Path to seed file (defaults: ./seed.ts, ./seed.js, ./seed.sql) */
133
+ seed: './seed.ts',
118
134
 
119
135
  /** Path to schema builder file (for turbine push) */
120
136
  schemaFile: './turbine/schema.ts',
@@ -71,6 +71,45 @@ function stripCommentsAndStrings(sql) {
71
71
  /** Unquote a "quoted" identifier for display. */
72
72
  const ident = (raw) => (raw ?? '?').replace(/^"|"$/g, '');
73
73
  const IDENT = String.raw `("[^"]+"|[a-zA-Z_][\w$]*)(\.("[^"]+"|[a-zA-Z_][\w$]*))?`;
74
+ /** Ordered rules — first match per statement wins. */
75
+ const RULES = [
76
+ {
77
+ kind: 'drop-table',
78
+ regex: new RegExp(String.raw `^DROP\s+TABLE\s+(IF\s+EXISTS\s+)?${IDENT}`, 'i'),
79
+ target: (m) => (m[4] ? `${ident(m[2])}.${ident(m[4])}` : ident(m[2])),
80
+ },
81
+ {
82
+ kind: 'drop-schema',
83
+ regex: new RegExp(String.raw `^DROP\s+SCHEMA\s+(IF\s+EXISTS\s+)?${IDENT}`, 'i'),
84
+ target: (m) => ident(m[2]),
85
+ },
86
+ {
87
+ kind: 'truncate',
88
+ regex: new RegExp(String.raw `^TRUNCATE\s+(TABLE\s+)?(ONLY\s+)?${IDENT}`, 'i'),
89
+ target: (m) => (m[5] ? `${ident(m[3])}.${ident(m[5])}` : ident(m[3])),
90
+ },
91
+ {
92
+ kind: 'drop-column',
93
+ regex: new RegExp(String.raw `^ALTER\s+TABLE\s+(IF\s+EXISTS\s+)?(ONLY\s+)?${IDENT}[\s\S]*?\bDROP\s+COLUMN\s+(IF\s+EXISTS\s+)?${IDENT}`, 'i'),
94
+ target: (m) => `${ident(m[3])}.${ident(m[7])}`,
95
+ },
96
+ {
97
+ kind: 'alter-column-type',
98
+ regex: new RegExp(String.raw `^ALTER\s+TABLE\s+(IF\s+EXISTS\s+)?(ONLY\s+)?${IDENT}[\s\S]*?\bALTER\s+(COLUMN\s+)?${IDENT}\s+(SET\s+DATA\s+)?TYPE\b`, 'i'),
99
+ target: (m) => `${ident(m[3])}.${ident(m[7])}`,
100
+ },
101
+ {
102
+ kind: 'delete',
103
+ regex: new RegExp(String.raw `^DELETE\s+FROM\s+(ONLY\s+)?${IDENT}`, 'i'),
104
+ target: (m) => ident(m[2]),
105
+ },
106
+ {
107
+ kind: 'update-without-where',
108
+ regex: new RegExp(String.raw `^UPDATE\s+(ONLY\s+)?${IDENT}\b`, 'i'),
109
+ target: (m) => ident(m[2]),
110
+ also: (stmt) => !/\bWHERE\b/i.test(stmt),
111
+ },
112
+ ];
74
113
  /**
75
114
  * Scan SQL (one file's worth; may contain many `;`-separated statements) and
76
115
  * return every statement that can destroy data.
@@ -82,37 +121,14 @@ export function scanDestructiveSql(sql) {
82
121
  const stmt = rawStmt.trim();
83
122
  if (!stmt)
84
123
  continue;
85
- const display = stmt.replace(/\s+/g, ' ');
86
- let m;
87
- if ((m = stmt.match(new RegExp(String.raw `^DROP\s+TABLE\s+(IF\s+EXISTS\s+)?${IDENT}`, 'i')))) {
88
- found.push({
89
- statement: display,
90
- kind: 'drop-table',
91
- target: ident(m[4] ? `${ident(m[2])}.${ident(m[4])}` : m[2]),
92
- });
93
- }
94
- else if ((m = stmt.match(new RegExp(String.raw `^DROP\s+SCHEMA\s+(IF\s+EXISTS\s+)?${IDENT}`, 'i')))) {
95
- found.push({ statement: display, kind: 'drop-schema', target: ident(m[2]) });
96
- }
97
- else if ((m = stmt.match(new RegExp(String.raw `^TRUNCATE\s+(TABLE\s+)?(ONLY\s+)?${IDENT}`, 'i')))) {
98
- found.push({
99
- statement: display,
100
- kind: 'truncate',
101
- target: ident(m[5] ? `${ident(m[3])}.${ident(m[5])}` : m[3]),
102
- });
103
- }
104
- else if ((m = stmt.match(new RegExp(String.raw `^ALTER\s+TABLE\s+(IF\s+EXISTS\s+)?(ONLY\s+)?${IDENT}[\s\S]*?\bDROP\s+COLUMN\s+(IF\s+EXISTS\s+)?${IDENT}`, 'i')))) {
105
- found.push({ statement: display, kind: 'drop-column', target: `${ident(m[3])}.${ident(m[7])}` });
106
- }
107
- else if ((m = stmt.match(new RegExp(String.raw `^ALTER\s+TABLE\s+(IF\s+EXISTS\s+)?(ONLY\s+)?${IDENT}[\s\S]*?\bALTER\s+(COLUMN\s+)?${IDENT}\s+(SET\s+DATA\s+)?TYPE\b`, 'i')))) {
108
- found.push({ statement: display, kind: 'alter-column-type', target: `${ident(m[3])}.${ident(m[7])}` });
109
- }
110
- else if ((m = stmt.match(new RegExp(String.raw `^DELETE\s+FROM\s+(ONLY\s+)?${IDENT}`, 'i')))) {
111
- found.push({ statement: display, kind: 'delete', target: ident(m[2]) });
112
- }
113
- else if ((m = stmt.match(new RegExp(String.raw `^UPDATE\s+(ONLY\s+)?${IDENT}\b`, 'i'))) &&
114
- !/\bWHERE\b/i.test(stmt)) {
115
- found.push({ statement: display, kind: 'update-without-where', target: ident(m[2]) });
124
+ for (const rule of RULES) {
125
+ const m = stmt.match(rule.regex);
126
+ if (!m)
127
+ continue;
128
+ if (rule.also && !rule.also(stmt))
129
+ continue;
130
+ found.push({ statement: stmt.replace(/\s+/g, ' '), kind: rule.kind, target: rule.target(m) });
131
+ break;
116
132
  }
117
133
  }
118
134
  return found;
@@ -8,12 +8,14 @@
8
8
  * turbine push — Apply schema-builder definitions to database
9
9
  * turbine migrate create <name> — Create a new SQL migration file
10
10
  * turbine migrate up — Apply pending migrations
11
+ * turbine migrate deploy — Apply pending migrations without prompts
11
12
  * turbine migrate down — Rollback last migration
12
13
  * turbine migrate status — Show migration status
13
14
  * turbine seed — Run seed file
14
15
  * turbine status — Show schema summary
15
16
  * turbine doctor — Check relations for missing FK indexes (--fix emits migration)
16
17
  * turbine studio — Launch local read-only web UI
18
+ * turbine mcp — Start read-only MCP server over JSON-RPC stdio
17
19
  * turbine observe — Launch metrics dashboard (requires TURBINE_OBSERVE_URL)
18
20
  *
19
21
  * Usage:
@@ -21,4 +23,53 @@
21
23
  * npx turbine init --url postgres://...
22
24
  * npx turbine migrate create add_users_table
23
25
  */
24
- export {};
26
+ export interface CliArgs {
27
+ command: string;
28
+ subcommand?: string;
29
+ positional: string[];
30
+ url?: string;
31
+ out?: string;
32
+ schema?: string;
33
+ include?: string[];
34
+ exclude?: string[];
35
+ step?: number;
36
+ dryRun?: boolean;
37
+ force?: boolean;
38
+ verbose?: boolean;
39
+ help?: boolean;
40
+ auto?: boolean;
41
+ allowDrift?: boolean;
42
+ allowEmpty?: boolean;
43
+ allowDestructive?: boolean;
44
+ fix?: boolean;
45
+ zod?: boolean;
46
+ includeViews?: boolean;
47
+ port?: number;
48
+ host?: string;
49
+ noOpen?: boolean;
50
+ /** Opt-in to bind Studio/Observe on a non-loopback host. */
51
+ allowRemote?: boolean;
52
+ }
53
+ export declare function parseArgs(argv?: string[]): CliArgs;
54
+ export declare function buildMigrateDeployOptions(_args: CliArgs): {
55
+ allowDrift: false;
56
+ allowDestructive: true;
57
+ step: undefined;
58
+ };
59
+ export type SeedExecutionPlan = {
60
+ kind: 'tsx';
61
+ command: 'npx';
62
+ args: string[];
63
+ } | {
64
+ kind: 'js';
65
+ file: string;
66
+ } | {
67
+ kind: 'sql';
68
+ file: string;
69
+ };
70
+ export declare function getSeedExecutionPlan(seedFile: string): SeedExecutionPlan;
71
+ /**
72
+ * True when `host` is a loopback address Studio/Observe may bind without
73
+ * `--allow-remote`. Accepts IPv4, IPv6, and the common bracket form.
74
+ */
75
+ export declare function isLoopbackHost(host: string): boolean;