turbine-orm 0.19.0 → 0.19.2

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.
@@ -109,6 +109,7 @@ function sqlToPreparedName(sql) {
109
109
  }
110
110
  /** Known operator keys — used to detect operator objects vs plain values */
111
111
  exports.OPERATOR_KEYS = new Set([
112
+ 'equals',
112
113
  'gt',
113
114
  'gte',
114
115
  'lt',
package/dist/cli/index.js CHANGED
@@ -20,14 +20,14 @@
20
20
  * npx turbine init --url postgres://...
21
21
  * npx turbine migrate create add_users_table
22
22
  */
23
- import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
23
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
24
24
  import { dirname, relative, resolve } from 'node:path';
25
25
  import { pathToFileURL } from 'node:url';
26
26
  import { generate } from '../generate.js';
27
27
  import { introspect } from '../introspect.js';
28
28
  import { schemaDiff, schemaPush } from '../schema-sql.js';
29
29
  import { configTemplate, findConfigFile, loadConfig, resolveConfig } from './config.js';
30
- import { needsTsLoader, registerTsLoader } from './loader.js';
30
+ import { canResolveTsx, getTsLoaderError, needsTsLoader, registerTsLoader } from './loader.js';
31
31
  import { createMigration, listMigrationFiles, migrateDown, migrateStatus, migrateUp } from './migrate.js';
32
32
  import { startObserve } from './observe.js';
33
33
  import { startStudio } from './studio.js';
@@ -132,6 +132,15 @@ function failMissingTsLoader(filePath, reason) {
132
132
  console.log(` ${dim('Your Node.js version does not support')} ${cyan('module.register()')}.`);
133
133
  console.log(` ${dim('Upgrade to Node.js')} ${cyan('20.6+')} ${dim('or use a')} ${cyan('.js')} ${dim('/')} ${cyan('.mjs')} ${dim('config file.')}`);
134
134
  }
135
+ else if (reason === 'failed') {
136
+ // tsx IS installed but registering its loader threw. Report the real
137
+ // cause — telling the user to install tsx here would be a misdiagnosis.
138
+ console.log(` ${dim('tsx is installed, but registering its TypeScript loader failed:')}`);
139
+ newline();
140
+ console.log(` ${getTsLoaderError() ?? '(unknown error)'}`);
141
+ newline();
142
+ console.log(` ${dim('Try upgrading tsx:')} ${cyan('npm install --save-dev tsx@latest')}${dim(', or rename your file to')} ${cyan('.mjs')}.`);
143
+ }
135
144
  else {
136
145
  console.log(` ${dim('Loading .ts config / schema files requires')} ${cyan('tsx')} ${dim('to be installed.')}`);
137
146
  newline();
@@ -175,7 +184,7 @@ async function loadSchemaFile(schemaFile) {
175
184
  // ERR_UNKNOWN_FILE_EXTENSION for `.ts`.
176
185
  if (needsTsLoader(absPath)) {
177
186
  const status = await registerTsLoader();
178
- if (status === 'missing' || status === 'unsupported') {
187
+ if (status === 'missing' || status === 'unsupported' || status === 'failed') {
179
188
  failMissingTsLoader(schemaFile, status);
180
189
  }
181
190
  }
@@ -311,7 +320,7 @@ export default defineSchema({
311
320
  // id: { type: 'serial', primaryKey: true },
312
321
  // email: { type: 'text', notNull: true, unique: true },
313
322
  // name: { type: 'text', notNull: true },
314
- // created_at: { type: 'timestamptz', default: 'NOW()' },
323
+ // created_at: { type: 'timestamp', default: 'NOW()' },
315
324
  // },
316
325
  });
317
326
  `, 'utf-8');
@@ -374,6 +383,9 @@ export default defineSchema({
374
383
  console.log(` ${dim('or create a')} ${cyan('.env')} ${dim('file with')} ${cyan('DATABASE_URL=postgres://...')}`);
375
384
  }
376
385
  console.log(` ${dim('2.')} Run ${cyan('npx turbine generate')} to introspect your DB`);
386
+ if (!canResolveTsx()) {
387
+ console.log(` ${dim('Note: the TypeScript config requires')} ${cyan('tsx')} ${dim('—')} ${cyan('npm install --save-dev tsx')}`);
388
+ }
377
389
  }
378
390
  else {
379
391
  console.log(` ${dim('1.')} Import the generated client:`);
@@ -1117,13 +1129,15 @@ function showMigrateHelp() {
1117
1129
  newline();
1118
1130
  console.log(` ${bold('Options:')}`);
1119
1131
  console.log(` ${cyan('--url, -u')} ${dim('<url>')} Postgres connection string`);
1132
+ console.log(` ${cyan('--auto')} Auto-generate UP/DOWN SQL from schema diff ${dim('(create only)')}`);
1120
1133
  console.log(` ${cyan('--step, -n')} ${dim('<N>')} Number of migrations to apply/rollback`);
1121
- console.log(` ${cyan('--dry-run')} Show SQL without executing`);
1122
- console.log(` ${cyan('--allow-drift')} Bypass checksum validation ${dim('(migrate up only — advanced)')}`);
1123
- console.log(` ${cyan('--verbose, -v')} Show detailed output`);
1134
+ console.log(` ${cyan('--dry-run')} Show SQL without executing`);
1135
+ console.log(` ${cyan('--allow-drift')} Bypass checksum validation ${dim('(migrate up only — advanced)')}`);
1136
+ console.log(` ${cyan('--verbose, -v')} Show detailed output`);
1124
1137
  newline();
1125
1138
  console.log(` ${bold('Examples:')}`);
1126
1139
  console.log(` ${dim('$')} npx turbine migrate create add_users_table`);
1140
+ console.log(` ${dim('$')} npx turbine migrate create add_email_index --auto`);
1127
1141
  console.log(` ${dim('$')} npx turbine migrate up`);
1128
1142
  console.log(` ${dim('$')} npx turbine migrate down --step 2`);
1129
1143
  console.log(` ${dim('$')} npx turbine migrate status`);
@@ -1168,16 +1182,17 @@ function showHelp() {
1168
1182
  newline();
1169
1183
  console.log(` ${bold('Commands:')}`);
1170
1184
  console.log(` ${cyan('init')} Initialize a Turbine project`);
1171
- console.log(` ${cyan('generate')} ${dim('| pull')} Introspect database ${symbols.arrow} generate types`);
1185
+ console.log(` ${cyan('generate')} ${dim('| pull')} Introspect database ${symbols.arrow} generate types`);
1172
1186
  console.log(` ${cyan('push')} Apply schema definitions to database`);
1173
- console.log(` ${cyan('migrate')} ${dim('<sub>')} SQL migration management`);
1174
- console.log(` ${dim('create <name>')} Create a new migration file`);
1187
+ console.log(` ${cyan('migrate')} ${dim('<sub>')} SQL migration management`);
1188
+ console.log(` ${dim('create <name>')} Create a new migration file`);
1175
1189
  console.log(` ${dim('up')} Apply pending migrations`);
1176
1190
  console.log(` ${dim('down')} Rollback last migration`);
1177
1191
  console.log(` ${dim('status')} Show applied/pending migrations`);
1178
1192
  console.log(` ${cyan('seed')} Run seed file`);
1179
- console.log(` ${cyan('status')} ${dim('| info')} Show schema summary`);
1193
+ console.log(` ${cyan('status')} ${dim('| info')} Show schema summary`);
1180
1194
  console.log(` ${cyan('studio')} Launch local read-only web UI`);
1195
+ console.log(` ${cyan('observe')} Launch metrics dashboard ${dim('(requires TURBINE_OBSERVE_URL)')}`);
1181
1196
  newline();
1182
1197
  console.log(` ${bold('Options:')}`);
1183
1198
  console.log(` ${cyan('--url, -u')} ${dim('<url>')} Postgres connection string`);
@@ -1189,8 +1204,13 @@ function showHelp() {
1189
1204
  console.log(` ${cyan('--verbose, -v')} Show detailed output`);
1190
1205
  console.log(` ${cyan('--force, -f')} Overwrite existing files`);
1191
1206
  newline();
1192
- console.log(` ${bold('Studio options:')}`);
1193
- console.log(` ${cyan('--port')} ${dim('<n>')} HTTP port ${dim('(default: 4983)')}`);
1207
+ console.log(` ${bold('Migrate options:')}`);
1208
+ console.log(` ${cyan('--auto')} Auto-generate UP/DOWN SQL from schema diff ${dim('(create)')}`);
1209
+ console.log(` ${cyan('--step, -n')} ${dim('<N>')} Number of migrations to apply/rollback`);
1210
+ console.log(` ${cyan('--allow-drift')} Bypass checksum validation on ${cyan('migrate up')} ${dim('(advanced)')}`);
1211
+ newline();
1212
+ console.log(` ${bold('Studio / observe options:')}`);
1213
+ console.log(` ${cyan('--port')} ${dim('<n>')} HTTP port ${dim('(default: 4983 studio, 4984 observe)')}`);
1194
1214
  console.log(` ${cyan('--host')} ${dim('<addr>')} Bind address ${dim('(default: 127.0.0.1)')}`);
1195
1215
  console.log(` ${cyan('--no-open')} Don't auto-open the browser`);
1196
1216
  newline();
@@ -1214,7 +1234,17 @@ function showVersion() {
1214
1234
  // Using process.argv[1] instead of import.meta.url so the same code compiles
1215
1235
  // cleanly for both the ESM and CJS builds.
1216
1236
  try {
1217
- let dir = dirname(process.argv[1] ?? '');
1237
+ // Resolve symlinks first: `npx turbine` runs via node_modules/.bin/turbine,
1238
+ // a symlink whose dirname would walk the CONSUMER's tree and never find
1239
+ // turbine-orm's package.json (printing no version number at all).
1240
+ let entry = process.argv[1] ?? '';
1241
+ try {
1242
+ entry = realpathSync(entry);
1243
+ }
1244
+ catch {
1245
+ // keep the raw path if realpath fails (e.g. deleted cwd)
1246
+ }
1247
+ let dir = dirname(entry);
1218
1248
  for (let i = 0; i < 6; i++) {
1219
1249
  const candidate = resolve(dir, 'package.json');
1220
1250
  if (existsSync(candidate)) {
@@ -1262,7 +1292,7 @@ async function main() {
1262
1292
  const configPath = findConfigFile();
1263
1293
  if (needsTsLoader(configPath)) {
1264
1294
  const status = await registerTsLoader();
1265
- if (status === 'missing' || status === 'unsupported') {
1295
+ if (status === 'missing' || status === 'unsupported' || status === 'failed') {
1266
1296
  failMissingTsLoader(configPath ?? 'turbine.config.ts', status);
1267
1297
  }
1268
1298
  }
@@ -8,9 +8,18 @@
8
8
  *
9
9
  * Strategy:
10
10
  * 1. If the file we're about to import ends in `.ts` / `.mts` / `.cts`,
11
- * probe whether `tsx/esm` is resolvable from the user's CWD.
12
- * 2. If yes, call `module.register('tsx/esm', ...)` ONCE per process.
13
- * 3. If no, surface an actionable error telling the user to install `tsx`.
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".
14
23
  *
15
24
  * `tsx` is intentionally NOT a runtime dependency — many projects already
16
25
  * have it, and adding a heavy dev tool to a 1-dependency ORM would be silly.
@@ -28,7 +37,12 @@ export declare function needsTsLoader(filePath: string | null | undefined): bool
28
37
  * Accepts an injected `resolver` so unit tests don't need a real filesystem.
29
38
  */
30
39
  export declare function canResolveTsx(resolver?: (id: string) => string): boolean;
31
- export type TsLoaderStatus = 'registered' | 'already' | 'unsupported' | 'missing';
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;
32
46
  /**
33
47
  * Register the tsx ESM loader so subsequent dynamic imports of `.ts` files
34
48
  * work. Safe to call multiple times — internal flag prevents double registration.
@@ -36,8 +50,11 @@ export type TsLoaderStatus = 'registered' | 'already' | 'unsupported' | 'missing
36
50
  * Returns:
37
51
  * - 'registered' loader was successfully registered this call
38
52
  * - 'already' a loader was previously registered (idempotent)
39
- * - 'unsupported' Node lacks `module.register()` (Node < 20.6)
53
+ * - 'unsupported' Node lacks `module.register()` (Node < 20.6) and tsx has
54
+ * no programmatic API to fall back to
40
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
41
58
  */
42
59
  export declare function registerTsLoader(): Promise<TsLoaderStatus>;
43
60
  /** Reset the loader state — used by unit tests only. */
@@ -8,9 +8,18 @@
8
8
  *
9
9
  * Strategy:
10
10
  * 1. If the file we're about to import ends in `.ts` / `.mts` / `.cts`,
11
- * probe whether `tsx/esm` is resolvable from the user's CWD.
12
- * 2. If yes, call `module.register('tsx/esm', ...)` ONCE per process.
13
- * 3. If no, surface an actionable error telling the user to install `tsx`.
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".
14
23
  *
15
24
  * `tsx` is intentionally NOT a runtime dependency — many projects already
16
25
  * have it, and adding a heavy dev tool to a 1-dependency ORM would be silly.
@@ -50,6 +59,14 @@ export function canResolveTsx(resolver) {
50
59
  }
51
60
  }
52
61
  let tsLoaderState = null;
62
+ let tsLoaderError = null;
63
+ /**
64
+ * The underlying error message from the last failed registration attempt,
65
+ * or null. Lets the CLI report the REAL cause instead of guessing.
66
+ */
67
+ export function getTsLoaderError() {
68
+ return tsLoaderError;
69
+ }
53
70
  /**
54
71
  * Register the tsx ESM loader so subsequent dynamic imports of `.ts` files
55
72
  * work. Safe to call multiple times — internal flag prevents double registration.
@@ -57,17 +74,51 @@ let tsLoaderState = null;
57
74
  * Returns:
58
75
  * - 'registered' loader was successfully registered this call
59
76
  * - 'already' a loader was previously registered (idempotent)
60
- * - 'unsupported' Node lacks `module.register()` (Node < 20.6)
77
+ * - 'unsupported' Node lacks `module.register()` (Node < 20.6) and tsx has
78
+ * no programmatic API to fall back to
61
79
  * - 'missing' `tsx` is not installed in the user's project
80
+ * - 'failed' tsx IS installed but registration threw — see
81
+ * {@link getTsLoaderError} for the underlying message
62
82
  */
63
83
  export async function registerTsLoader() {
64
84
  if (tsLoaderState === 'registered' || tsLoaderState === 'already') {
65
85
  return 'already';
66
86
  }
87
+ const userRequire = createRequire(`${process.cwd()}/`);
88
+ // Preferred: tsx's supported programmatic API (tsx >= 4.0).
89
+ let apiPath = null;
90
+ try {
91
+ apiPath = userRequire.resolve('tsx/esm/api');
92
+ }
93
+ catch {
94
+ apiPath = null;
95
+ }
96
+ if (apiPath) {
97
+ try {
98
+ const api = (await import(pathToFileURL(apiPath).href));
99
+ if (typeof api.register !== 'function') {
100
+ throw new Error(`tsx/esm/api resolved at ${apiPath} but exports no register() function`);
101
+ }
102
+ api.register();
103
+ tsLoaderState = 'registered';
104
+ tsLoaderError = null;
105
+ return 'registered';
106
+ }
107
+ catch (err) {
108
+ tsLoaderState = 'failed';
109
+ tsLoaderError = err instanceof Error ? err.message : String(err);
110
+ return 'failed';
111
+ }
112
+ }
113
+ // tsx/esm/api not resolvable — is tsx installed at all?
67
114
  if (!canResolveTsx()) {
68
115
  tsLoaderState = 'missing';
69
116
  return 'missing';
70
117
  }
118
+ // Legacy fallback for tsx < 4.0 (no tsx/esm/api): Node's module.register.
119
+ // On tsx >= 4.19 this path throws ("tsx must be loaded with --import
120
+ // instead of --loader") — but those versions all ship tsx/esm/api, so we
121
+ // only land here for genuinely old installs.
71
122
  try {
72
123
  const mod = await import('node:module');
73
124
  const register = mod.register;
@@ -77,15 +128,18 @@ export async function registerTsLoader() {
77
128
  }
78
129
  register('tsx/esm', pathToFileURL(`${process.cwd()}/`));
79
130
  tsLoaderState = 'registered';
131
+ tsLoaderError = null;
80
132
  return 'registered';
81
133
  }
82
- catch {
83
- tsLoaderState = 'missing';
84
- return 'missing';
134
+ catch (err) {
135
+ tsLoaderState = 'failed';
136
+ tsLoaderError = err instanceof Error ? err.message : String(err);
137
+ return 'failed';
85
138
  }
86
139
  }
87
140
  /** Reset the loader state — used by unit tests only. */
88
141
  export function _resetTsLoaderStateForTests() {
89
142
  tsLoaderState = null;
143
+ tsLoaderError = null;
90
144
  }
91
145
  //# sourceMappingURL=loader.js.map
@@ -113,8 +113,8 @@ export declare function deriveLockId(databaseName: string): number;
113
113
  */
114
114
  export declare function migrateUp(connectionString: string, migrationsDir: string, options?: {
115
115
  step?: number;
116
- allowDrift?: boolean /** @deprecated use allowDrift */;
117
- force?: boolean;
116
+ allowDrift?: boolean;
117
+ force?: boolean /** @deprecated use allowDrift */;
118
118
  adapter?: DatabaseAdapter;
119
119
  dialect?: Dialect;
120
120
  }): Promise<{