turbine-orm 0.19.0 → 0.19.1
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 +8 -8
- package/dist/adapters/index.d.ts +3 -2
- package/dist/cjs/cli/index.js +26 -4
- package/dist/cjs/cli/loader.js +62 -7
- package/dist/cjs/cli/studio.js +25 -5
- package/dist/cjs/client.js +8 -0
- package/dist/cjs/query/builder.js +238 -66
- package/dist/cli/index.js +28 -6
- package/dist/cli/loader.d.ts +22 -5
- package/dist/cli/loader.js +61 -7
- package/dist/cli/studio.d.ts +9 -4
- package/dist/cli/studio.js +25 -5
- package/dist/client.js +8 -0
- package/dist/index.d.ts +1 -1
- package/dist/query/builder.d.ts +35 -0
- package/dist/query/builder.js +238 -66
- package/dist/query/index.d.ts +1 -1
- package/package.json +3 -3
- package/dist/cjs/query.js +0 -2711
- package/dist/query.d.ts +0 -878
- package/dist/query.js +0 -2705
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ npm install turbine-orm
|
|
|
13
13
|
Prisma ships a 1.6 MB WASM query engine. Drizzle ships zero runtime but no Studio, no typed errors, no migration checksums. Turbine ships **one dependency (`pg`) and no engine binary**, and bundles six things no other TS ORM has together:
|
|
14
14
|
|
|
15
15
|
1. **One runtime dependency (`pg`).** No engine binary, no WASM adapter, no adapter packages to keep in lockstep. The main entry bundles to ~30 KB gzipped (~109 KB minified); the edge entry to ~21 KB gzipped. Prisma's WASM query engine alone is 1.6 MB.
|
|
16
|
-
2. **Built-in read-only Studio.** `npx turbine studio` spins up a loopback-bound web UI with 192-bit auth tokens, `BEGIN READ ONLY` transactions, and
|
|
16
|
+
2. **Built-in read-only Studio.** `npx turbine studio` spins up a loopback-bound web UI with 192-bit auth tokens, `BEGIN READ ONLY` transactions, and — since v0.19 — no raw-SQL surface at all: queries are composed in the ORM's own validated builder. The only TS ORM Studio that physically cannot mutate your database. DBA-approvable.
|
|
17
17
|
3. **PII-safe error messages.** Turbine errors show WHERE keys, not values. A `UniqueConstraintError` says which column violated the constraint — never the actual user data. Safe to log, safe to surface to monitoring, no scrubbing needed.
|
|
18
18
|
4. **SQL-first migrations with drift detection.** Write real SQL. SHA-256 checksums catch modified migration files. `pg_try_advisory_lock()` prevents concurrent runs. Each migration in its own transaction. No shadow database, no magic DSL.
|
|
19
19
|
5. **Edge-native — one import swap.** `turbineHttp(pool, schema)` — same API on Neon, Vercel Postgres, Cloudflare Hyperdrive, Supabase. No WASM bundle, no adapter package, no separate serverless build.
|
|
@@ -614,7 +614,7 @@ npx turbine migrate status
|
|
|
614
614
|
|
|
615
615
|
## Studio
|
|
616
616
|
|
|
617
|
-
The only Postgres ORM with a Studio your DBA will approve. `turbine studio` launches a local, read-only web UI for exploring your database — no mutations, no writes, no
|
|
617
|
+
The only Postgres ORM with a Studio your DBA will approve. `turbine studio` launches a local, read-only web UI for exploring your database — no mutations, no writes, and since v0.19 **no raw-SQL surface at all**: every query is composed visually in the ORM and compiled by the same validated query builder your application uses.
|
|
618
618
|
|
|
619
619
|
```bash
|
|
620
620
|
DATABASE_URL=postgres://user:pass@localhost:5432/mydb npx turbine studio
|
|
@@ -624,19 +624,19 @@ npx turbine studio --port 5173 --host 127.0.0.1 --no-open
|
|
|
624
624
|
|
|
625
625
|
**Features**
|
|
626
626
|
|
|
627
|
-
- **Data / Schema
|
|
628
|
-
- **
|
|
627
|
+
- **Query / Data / Schema tabs.** Compose queries visually, browse rows, and inspect tables and relations.
|
|
628
|
+
- **ORM-native query composer.** The Query tab builds a real `findMany` — drill into relations (`with`) to any depth, pick fields (`select`/`omit`), add filters (`where`), `orderBy`, and `limit` at every level — with a live TypeScript preview of the exact call to copy into your codebase.
|
|
629
|
+
- **Saved queries.** Named builder queries persisted to `.turbine/studio-queries.json` — share them across runs without committing them.
|
|
629
630
|
- **Cmd+K command palette.** Jump to any table, tab, or saved query in one keystroke.
|
|
630
631
|
- **Full-text search across rows.** The Data tab supports substring search across every text column of the current table.
|
|
631
|
-
- **Visual query composer.** The Builder tab lets you click together `where` / `orderBy` / `with` / `limit` clauses and renders the matching `db.table.findMany(...)` TypeScript in real time — copy it into your codebase.
|
|
632
632
|
|
|
633
633
|
**Security posture (read-only by design)**
|
|
634
634
|
|
|
635
|
+
- **No SQL input surface.** There is nothing to inject into — builder requests are validated identifier-by-identifier against the introspected schema, and every value is bound as a `$N` parameter.
|
|
635
636
|
- **Loopback by default** (`127.0.0.1`) with a loud warning if you bind to a non-loopback address.
|
|
636
637
|
- **Per-process auth token** — 24 random bytes of hex, stored in a `SameSite=Strict` `HttpOnly` cookie.
|
|
637
|
-
- **Every query runs inside `BEGIN READ ONLY
|
|
638
|
-
- **
|
|
639
|
-
- **Security headers on every response** — `X-Content-Type-Options`, `X-Frame-Options: DENY`, `Referrer-Policy: no-referrer`.
|
|
638
|
+
- **Every query runs inside `BEGIN READ ONLY`** with a 30s transaction-local statement timeout (parameterized `set_config`). Writes are physically impossible at the transaction level.
|
|
639
|
+
- **Security headers on every response** — CSP, `X-Content-Type-Options`, `X-Frame-Options: DENY`, `Referrer-Policy: no-referrer` — plus per-session rate limiting and cross-origin refusal.
|
|
640
640
|
|
|
641
641
|
## Serverless / Edge
|
|
642
642
|
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -55,8 +55,9 @@ export interface DatabaseAdapter {
|
|
|
55
55
|
introspectionOverrides?: Partial<IntrospectionOverrides>;
|
|
56
56
|
/**
|
|
57
57
|
* Generate the SQL to set a statement timeout within a transaction.
|
|
58
|
-
* PostgreSQL uses `
|
|
59
|
-
* CockroachDB uses `
|
|
58
|
+
* PostgreSQL uses `SELECT set_config('statement_timeout', $1, true)`.
|
|
59
|
+
* CockroachDB uses `SELECT set_config('transaction_timeout', $1, true)` (v23.1+).
|
|
60
|
+
* (`SET LOCAL ... = $1` is a syntax error — SET takes no bind params.)
|
|
60
61
|
*
|
|
61
62
|
* @param seconds — timeout in seconds
|
|
62
63
|
* @returns an object with the parameterized SQL and its bound values
|
package/dist/cjs/cli/index.js
CHANGED
|
@@ -167,6 +167,15 @@ function failMissingTsLoader(filePath, reason) {
|
|
|
167
167
|
console.log(` ${(0, ui_js_1.dim)('Your Node.js version does not support')} ${(0, ui_js_1.cyan)('module.register()')}.`);
|
|
168
168
|
console.log(` ${(0, ui_js_1.dim)('Upgrade to Node.js')} ${(0, ui_js_1.cyan)('20.6+')} ${(0, ui_js_1.dim)('or use a')} ${(0, ui_js_1.cyan)('.js')} ${(0, ui_js_1.dim)('/')} ${(0, ui_js_1.cyan)('.mjs')} ${(0, ui_js_1.dim)('config file.')}`);
|
|
169
169
|
}
|
|
170
|
+
else if (reason === 'failed') {
|
|
171
|
+
// tsx IS installed but registering its loader threw. Report the real
|
|
172
|
+
// cause — telling the user to install tsx here would be a misdiagnosis.
|
|
173
|
+
console.log(` ${(0, ui_js_1.dim)('tsx is installed, but registering its TypeScript loader failed:')}`);
|
|
174
|
+
(0, ui_js_1.newline)();
|
|
175
|
+
console.log(` ${(0, loader_js_1.getTsLoaderError)() ?? '(unknown error)'}`);
|
|
176
|
+
(0, ui_js_1.newline)();
|
|
177
|
+
console.log(` ${(0, ui_js_1.dim)('Try upgrading tsx:')} ${(0, ui_js_1.cyan)('npm install --save-dev tsx@latest')}${(0, ui_js_1.dim)(', or rename your file to')} ${(0, ui_js_1.cyan)('.mjs')}.`);
|
|
178
|
+
}
|
|
170
179
|
else {
|
|
171
180
|
console.log(` ${(0, ui_js_1.dim)('Loading .ts config / schema files requires')} ${(0, ui_js_1.cyan)('tsx')} ${(0, ui_js_1.dim)('to be installed.')}`);
|
|
172
181
|
(0, ui_js_1.newline)();
|
|
@@ -210,7 +219,7 @@ async function loadSchemaFile(schemaFile) {
|
|
|
210
219
|
// ERR_UNKNOWN_FILE_EXTENSION for `.ts`.
|
|
211
220
|
if ((0, loader_js_1.needsTsLoader)(absPath)) {
|
|
212
221
|
const status = await (0, loader_js_1.registerTsLoader)();
|
|
213
|
-
if (status === 'missing' || status === 'unsupported') {
|
|
222
|
+
if (status === 'missing' || status === 'unsupported' || status === 'failed') {
|
|
214
223
|
failMissingTsLoader(schemaFile, status);
|
|
215
224
|
}
|
|
216
225
|
}
|
|
@@ -346,7 +355,7 @@ export default defineSchema({
|
|
|
346
355
|
// id: { type: 'serial', primaryKey: true },
|
|
347
356
|
// email: { type: 'text', notNull: true, unique: true },
|
|
348
357
|
// name: { type: 'text', notNull: true },
|
|
349
|
-
// created_at: { type: '
|
|
358
|
+
// created_at: { type: 'timestamp', default: 'NOW()' },
|
|
350
359
|
// },
|
|
351
360
|
});
|
|
352
361
|
`, 'utf-8');
|
|
@@ -409,6 +418,9 @@ export default defineSchema({
|
|
|
409
418
|
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://...')}`);
|
|
410
419
|
}
|
|
411
420
|
console.log(` ${(0, ui_js_1.dim)('2.')} Run ${(0, ui_js_1.cyan)('npx turbine generate')} to introspect your DB`);
|
|
421
|
+
if (!(0, loader_js_1.canResolveTsx)()) {
|
|
422
|
+
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')}`);
|
|
423
|
+
}
|
|
412
424
|
}
|
|
413
425
|
else {
|
|
414
426
|
console.log(` ${(0, ui_js_1.dim)('1.')} Import the generated client:`);
|
|
@@ -1249,7 +1261,17 @@ function showVersion() {
|
|
|
1249
1261
|
// Using process.argv[1] instead of import.meta.url so the same code compiles
|
|
1250
1262
|
// cleanly for both the ESM and CJS builds.
|
|
1251
1263
|
try {
|
|
1252
|
-
|
|
1264
|
+
// Resolve symlinks first: `npx turbine` runs via node_modules/.bin/turbine,
|
|
1265
|
+
// a symlink whose dirname would walk the CONSUMER's tree and never find
|
|
1266
|
+
// turbine-orm's package.json (printing no version number at all).
|
|
1267
|
+
let entry = process.argv[1] ?? '';
|
|
1268
|
+
try {
|
|
1269
|
+
entry = (0, node_fs_1.realpathSync)(entry);
|
|
1270
|
+
}
|
|
1271
|
+
catch {
|
|
1272
|
+
// keep the raw path if realpath fails (e.g. deleted cwd)
|
|
1273
|
+
}
|
|
1274
|
+
let dir = (0, node_path_1.dirname)(entry);
|
|
1253
1275
|
for (let i = 0; i < 6; i++) {
|
|
1254
1276
|
const candidate = (0, node_path_1.resolve)(dir, 'package.json');
|
|
1255
1277
|
if ((0, node_fs_1.existsSync)(candidate)) {
|
|
@@ -1297,7 +1319,7 @@ async function main() {
|
|
|
1297
1319
|
const configPath = (0, config_js_1.findConfigFile)();
|
|
1298
1320
|
if ((0, loader_js_1.needsTsLoader)(configPath)) {
|
|
1299
1321
|
const status = await (0, loader_js_1.registerTsLoader)();
|
|
1300
|
-
if (status === 'missing' || status === 'unsupported') {
|
|
1322
|
+
if (status === 'missing' || status === 'unsupported' || status === 'failed') {
|
|
1301
1323
|
failMissingTsLoader(configPath ?? 'turbine.config.ts', status);
|
|
1302
1324
|
}
|
|
1303
1325
|
}
|
package/dist/cjs/cli/loader.js
CHANGED
|
@@ -9,9 +9,18 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Strategy:
|
|
11
11
|
* 1. If the file we're about to import ends in `.ts` / `.mts` / `.cts`,
|
|
12
|
-
* probe whether `tsx
|
|
13
|
-
* 2.
|
|
14
|
-
*
|
|
12
|
+
* probe whether `tsx` is resolvable from the user's CWD.
|
|
13
|
+
* 2. Prefer tsx's supported programmatic API, `tsx/esm/api`'s `register()`.
|
|
14
|
+
* Calling Node's `module.register('tsx/esm', ...)` directly throws
|
|
15
|
+
* "tsx must be loaded with --import instead of --loader" on every Node
|
|
16
|
+
* version that has `module.register()` (>= 20.6) — tsx's hook file
|
|
17
|
+
* guards against being loaded that way. The `tsx/esm/api` entry point
|
|
18
|
+
* is the documented path and works everywhere `module.register()` does.
|
|
19
|
+
* 3. Fall back to `module.register('tsx/esm', ...)` only for very old tsx
|
|
20
|
+
* versions (< 4.0) that predate `tsx/esm/api`.
|
|
21
|
+
* 4. If tsx isn't installed, or registration genuinely fails, surface an
|
|
22
|
+
* actionable error — including the REAL underlying error message, never
|
|
23
|
+
* a misdiagnosed "tsx is not installed".
|
|
15
24
|
*
|
|
16
25
|
* `tsx` is intentionally NOT a runtime dependency — many projects already
|
|
17
26
|
* have it, and adding a heavy dev tool to a 1-dependency ORM would be silly.
|
|
@@ -52,6 +61,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
52
61
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
62
|
exports.needsTsLoader = needsTsLoader;
|
|
54
63
|
exports.canResolveTsx = canResolveTsx;
|
|
64
|
+
exports.getTsLoaderError = getTsLoaderError;
|
|
55
65
|
exports.registerTsLoader = registerTsLoader;
|
|
56
66
|
exports._resetTsLoaderStateForTests = _resetTsLoaderStateForTests;
|
|
57
67
|
const node_module_1 = require("node:module");
|
|
@@ -89,6 +99,14 @@ function canResolveTsx(resolver) {
|
|
|
89
99
|
}
|
|
90
100
|
}
|
|
91
101
|
let tsLoaderState = null;
|
|
102
|
+
let tsLoaderError = null;
|
|
103
|
+
/**
|
|
104
|
+
* The underlying error message from the last failed registration attempt,
|
|
105
|
+
* or null. Lets the CLI report the REAL cause instead of guessing.
|
|
106
|
+
*/
|
|
107
|
+
function getTsLoaderError() {
|
|
108
|
+
return tsLoaderError;
|
|
109
|
+
}
|
|
92
110
|
/**
|
|
93
111
|
* Register the tsx ESM loader so subsequent dynamic imports of `.ts` files
|
|
94
112
|
* work. Safe to call multiple times — internal flag prevents double registration.
|
|
@@ -96,17 +114,51 @@ let tsLoaderState = null;
|
|
|
96
114
|
* Returns:
|
|
97
115
|
* - 'registered' loader was successfully registered this call
|
|
98
116
|
* - 'already' a loader was previously registered (idempotent)
|
|
99
|
-
* - 'unsupported' Node lacks `module.register()` (Node < 20.6)
|
|
117
|
+
* - 'unsupported' Node lacks `module.register()` (Node < 20.6) and tsx has
|
|
118
|
+
* no programmatic API to fall back to
|
|
100
119
|
* - 'missing' `tsx` is not installed in the user's project
|
|
120
|
+
* - 'failed' tsx IS installed but registration threw — see
|
|
121
|
+
* {@link getTsLoaderError} for the underlying message
|
|
101
122
|
*/
|
|
102
123
|
async function registerTsLoader() {
|
|
103
124
|
if (tsLoaderState === 'registered' || tsLoaderState === 'already') {
|
|
104
125
|
return 'already';
|
|
105
126
|
}
|
|
127
|
+
const userRequire = (0, node_module_1.createRequire)(`${process.cwd()}/`);
|
|
128
|
+
// Preferred: tsx's supported programmatic API (tsx >= 4.0).
|
|
129
|
+
let apiPath = null;
|
|
130
|
+
try {
|
|
131
|
+
apiPath = userRequire.resolve('tsx/esm/api');
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
apiPath = null;
|
|
135
|
+
}
|
|
136
|
+
if (apiPath) {
|
|
137
|
+
try {
|
|
138
|
+
const api = (await Promise.resolve(`${(0, node_url_1.pathToFileURL)(apiPath).href}`).then(s => __importStar(require(s))));
|
|
139
|
+
if (typeof api.register !== 'function') {
|
|
140
|
+
throw new Error(`tsx/esm/api resolved at ${apiPath} but exports no register() function`);
|
|
141
|
+
}
|
|
142
|
+
api.register();
|
|
143
|
+
tsLoaderState = 'registered';
|
|
144
|
+
tsLoaderError = null;
|
|
145
|
+
return 'registered';
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
tsLoaderState = 'failed';
|
|
149
|
+
tsLoaderError = err instanceof Error ? err.message : String(err);
|
|
150
|
+
return 'failed';
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// tsx/esm/api not resolvable — is tsx installed at all?
|
|
106
154
|
if (!canResolveTsx()) {
|
|
107
155
|
tsLoaderState = 'missing';
|
|
108
156
|
return 'missing';
|
|
109
157
|
}
|
|
158
|
+
// Legacy fallback for tsx < 4.0 (no tsx/esm/api): Node's module.register.
|
|
159
|
+
// On tsx >= 4.19 this path throws ("tsx must be loaded with --import
|
|
160
|
+
// instead of --loader") — but those versions all ship tsx/esm/api, so we
|
|
161
|
+
// only land here for genuinely old installs.
|
|
110
162
|
try {
|
|
111
163
|
const mod = await Promise.resolve().then(() => __importStar(require('node:module')));
|
|
112
164
|
const register = mod.register;
|
|
@@ -116,14 +168,17 @@ async function registerTsLoader() {
|
|
|
116
168
|
}
|
|
117
169
|
register('tsx/esm', (0, node_url_1.pathToFileURL)(`${process.cwd()}/`));
|
|
118
170
|
tsLoaderState = 'registered';
|
|
171
|
+
tsLoaderError = null;
|
|
119
172
|
return 'registered';
|
|
120
173
|
}
|
|
121
|
-
catch {
|
|
122
|
-
tsLoaderState = '
|
|
123
|
-
|
|
174
|
+
catch (err) {
|
|
175
|
+
tsLoaderState = 'failed';
|
|
176
|
+
tsLoaderError = err instanceof Error ? err.message : String(err);
|
|
177
|
+
return 'failed';
|
|
124
178
|
}
|
|
125
179
|
}
|
|
126
180
|
/** Reset the loader state — used by unit tests only. */
|
|
127
181
|
function _resetTsLoaderStateForTests() {
|
|
128
182
|
tsLoaderState = null;
|
|
183
|
+
tsLoaderError = null;
|
|
129
184
|
}
|
package/dist/cjs/cli/studio.js
CHANGED
|
@@ -3,15 +3,20 @@
|
|
|
3
3
|
* turbine-orm CLI — Studio
|
|
4
4
|
*
|
|
5
5
|
* A local, read-only web UI for browsing databases, exploring relations,
|
|
6
|
-
* and
|
|
7
|
-
*
|
|
6
|
+
* and composing queries visually. ORM-native since v0.19: there is no
|
|
7
|
+
* raw-SQL input surface — the Query tab builds `findMany` args that are
|
|
8
|
+
* validated against introspected metadata and compiled by QueryInterface
|
|
9
|
+
* (`/api/builder`). Pure Node (built-in `http` module), no runtime
|
|
10
|
+
* dependencies beyond `pg`, bound to 127.0.0.1 only.
|
|
8
11
|
*
|
|
9
12
|
* Security model:
|
|
10
13
|
* • Bind 127.0.0.1 only (never 0.0.0.0 — no LAN exposure)
|
|
11
14
|
* • Random auth token generated per process, required in Cookie header
|
|
12
|
-
* •
|
|
15
|
+
* • No SQL input surface at all — every identifier in a builder request is
|
|
16
|
+
* validated against the introspected schema; all values are $N params
|
|
13
17
|
* • Every query runs in a READ ONLY transaction (belt-and-suspenders)
|
|
14
|
-
* • 30s statement timeout
|
|
18
|
+
* • 30s statement timeout via parameterized set_config()
|
|
19
|
+
* • Per-session rate limiting, CSP + security headers, cross-origin refusal
|
|
15
20
|
*
|
|
16
21
|
* Not implemented (deliberately): row editing, DDL, destructive operations.
|
|
17
22
|
* Studio is for inspection. Use the CLI, migrate, or raw SQL for writes.
|
|
@@ -420,6 +425,11 @@ async function apiBuilder(req, res, ctx) {
|
|
|
420
425
|
try {
|
|
421
426
|
await client.query('BEGIN READ ONLY');
|
|
422
427
|
await client.query(ctx.statementTimeout.sql, ctx.statementTimeout.params);
|
|
428
|
+
// QueryInterface emits unqualified table identifiers, which resolve via
|
|
429
|
+
// the connection's search_path. Pin it to the configured --schema so the
|
|
430
|
+
// Query tab reads the same schema as the Data tab (set_config is
|
|
431
|
+
// transaction-local and fully parameterized).
|
|
432
|
+
await client.query(`SELECT set_config('search_path', $1, true)`, [ctx.options.schema]);
|
|
423
433
|
const started = Date.now();
|
|
424
434
|
const result = await client.query(deferred.sql, deferred.params);
|
|
425
435
|
const elapsedMs = Date.now() - started;
|
|
@@ -448,6 +458,8 @@ async function apiBuilder(req, res, ctx) {
|
|
|
448
458
|
function savedQueriesPath(ctx) {
|
|
449
459
|
return (0, node_path_1.resolve)(ctx.stateDir, 'studio-queries.json');
|
|
450
460
|
}
|
|
461
|
+
/** One-shot flag so the legacy saved-query notice isn't logged on every request. */
|
|
462
|
+
let legacyDropNoticeShown = false;
|
|
451
463
|
function loadSavedQueries(ctx) {
|
|
452
464
|
const file = savedQueriesPath(ctx);
|
|
453
465
|
if (!(0, node_fs_1.existsSync)(file))
|
|
@@ -457,8 +469,16 @@ function loadSavedQueries(ctx) {
|
|
|
457
469
|
const parsed = JSON.parse(raw);
|
|
458
470
|
if (!parsed.queries || !Array.isArray(parsed.queries))
|
|
459
471
|
return { version: 1, queries: [] };
|
|
460
|
-
// Drop any legacy raw-SQL entries — Studio is builder-only now.
|
|
472
|
+
// Drop any legacy raw-SQL entries — Studio is builder-only now. Tell the
|
|
473
|
+
// user instead of silently discarding their saved work (the file on disk
|
|
474
|
+
// is only rewritten when a new query is saved, so this is recoverable).
|
|
461
475
|
const queries = parsed.queries.filter((q) => q && q.kind === 'builder');
|
|
476
|
+
const dropped = parsed.queries.length - queries.length;
|
|
477
|
+
if (dropped > 0 && !legacyDropNoticeShown) {
|
|
478
|
+
legacyDropNoticeShown = true;
|
|
479
|
+
console.warn(`[turbine studio] Ignoring ${dropped} legacy raw-SQL saved quer${dropped === 1 ? 'y' : 'ies'} in ${file} — ` +
|
|
480
|
+
'Studio is builder-only since v0.19. The entries remain in the file until a new query is saved.');
|
|
481
|
+
}
|
|
462
482
|
return { version: 1, queries };
|
|
463
483
|
}
|
|
464
484
|
catch {
|
package/dist/cjs/client.js
CHANGED
|
@@ -210,6 +210,14 @@ class TurbineClient {
|
|
|
210
210
|
/** Active LISTEN subscriptions — torn down on disconnect() so it never hangs */
|
|
211
211
|
activeSubscriptions = new Set();
|
|
212
212
|
constructor(config = {}, schema) {
|
|
213
|
+
// Constructing without schema metadata previously crashed deep in the
|
|
214
|
+
// constructor with an opaque "Cannot read properties of undefined
|
|
215
|
+
// (reading 'tables')". Fail fast with an actionable message instead.
|
|
216
|
+
if (!schema || typeof schema !== 'object' || !schema.tables) {
|
|
217
|
+
throw new errors_js_1.ValidationError('[turbine] TurbineClient requires schema metadata as its second argument. ' +
|
|
218
|
+
'Run `npx turbine generate` and use the generated client (`turbine()` from your output dir), ' +
|
|
219
|
+
'or pass the generated `schemaMetadata` object: new TurbineClient(config, schemaMetadata).');
|
|
220
|
+
}
|
|
213
221
|
/**
|
|
214
222
|
* Parse int8 (bigint, OID 20) as JavaScript number instead of string.
|
|
215
223
|
* Safe for values up to Number.MAX_SAFE_INTEGER (9,007,199,254,740,991).
|