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.
- package/README.md +17 -13
- package/dist/cjs/cli/config.js +20 -3
- package/dist/cjs/cli/destructive.js +47 -31
- package/dist/cjs/cli/index.js +273 -71
- package/dist/cjs/cli/mcp.js +788 -0
- package/dist/cjs/cli/migrate.js +95 -20
- package/dist/cjs/cli/studio.js +3 -2
- package/dist/cjs/client.js +267 -34
- package/dist/cjs/dialect.js +2 -0
- package/dist/cjs/generate.js +171 -7
- package/dist/cjs/index.js +4 -1
- package/dist/cjs/introspect.js +177 -4
- package/dist/cjs/query/batched-loader.js +148 -0
- package/dist/cjs/query/builder.js +714 -133
- package/dist/cjs/schema-builder.js +59 -4
- package/dist/cjs/schema-sql.js +315 -6
- package/dist/cjs/seed.js +66 -0
- package/dist/cli/config.d.ts +9 -2
- package/dist/cli/config.js +19 -3
- package/dist/cli/destructive.js +47 -31
- package/dist/cli/index.d.ts +52 -1
- package/dist/cli/index.js +272 -74
- package/dist/cli/mcp.d.ts +17 -0
- package/dist/cli/mcp.js +781 -0
- package/dist/cli/migrate.d.ts +37 -0
- package/dist/cli/migrate.js +92 -20
- package/dist/cli/studio.d.ts +3 -2
- package/dist/cli/studio.js +3 -2
- package/dist/client.d.ts +136 -1
- package/dist/client.js +267 -34
- package/dist/dialect.d.ts +17 -0
- package/dist/dialect.js +2 -0
- package/dist/generate.d.ts +17 -0
- package/dist/generate.js +171 -10
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -0
- package/dist/introspect.d.ts +20 -1
- package/dist/introspect.js +175 -4
- package/dist/query/batched-loader.d.ts +29 -2
- package/dist/query/batched-loader.js +148 -1
- package/dist/query/builder.d.ts +156 -8
- package/dist/query/builder.js +715 -134
- package/dist/query/index.d.ts +1 -1
- package/dist/query/types.d.ts +113 -8
- package/dist/schema-builder.d.ts +73 -8
- package/dist/schema-builder.js +59 -4
- package/dist/schema-sql.d.ts +67 -0
- package/dist/schema-sql.js +310 -6
- package/dist/schema.d.ts +53 -0
- package/dist/seed.d.ts +4 -0
- package/dist/seed.js +63 -0
- package/package.json +2 -3
package/dist/cli/config.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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',
|
package/dist/cli/destructive.js
CHANGED
|
@@ -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
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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;
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -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;
|