turbine-orm 0.8.0 → 0.9.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 +31 -5
- package/dist/cjs/cli/index.js +102 -10
- package/dist/cjs/cli/migrate.js +50 -13
- package/dist/cjs/cli/studio-ui.generated.js +6 -0
- package/dist/cjs/cli/studio.js +641 -0
- package/dist/cjs/query.js +26 -12
- package/dist/cjs/schema-builder.js +23 -3
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +103 -11
- package/dist/cli/migrate.d.ts +16 -0
- package/dist/cli/migrate.js +49 -13
- package/dist/cli/studio-ui.generated.d.ts +2 -0
- package/dist/cli/studio-ui.generated.js +4 -0
- package/dist/cli/studio.d.ts +75 -0
- package/dist/cli/studio.js +627 -0
- package/dist/query.js +26 -12
- package/dist/schema-builder.js +23 -3
- package/package.json +8 -4
package/dist/query.js
CHANGED
|
@@ -1139,7 +1139,7 @@ export class QueryInterface {
|
|
|
1139
1139
|
for (const [field, enabled] of Object.entries(args._sum)) {
|
|
1140
1140
|
if (enabled) {
|
|
1141
1141
|
const col = this.toColumn(field);
|
|
1142
|
-
selectExprs.push(`SUM(${quoteIdent(col)}) AS ${quoteIdent(
|
|
1142
|
+
selectExprs.push(`SUM(${quoteIdent(col)}) AS ${quoteIdent(`_sum_${col}`)}`);
|
|
1143
1143
|
}
|
|
1144
1144
|
}
|
|
1145
1145
|
}
|
|
@@ -1148,7 +1148,7 @@ export class QueryInterface {
|
|
|
1148
1148
|
for (const [field, enabled] of Object.entries(args._avg)) {
|
|
1149
1149
|
if (enabled) {
|
|
1150
1150
|
const col = this.toColumn(field);
|
|
1151
|
-
selectExprs.push(`AVG(${quoteIdent(col)})::float AS ${quoteIdent(
|
|
1151
|
+
selectExprs.push(`AVG(${quoteIdent(col)})::float AS ${quoteIdent(`_avg_${col}`)}`);
|
|
1152
1152
|
}
|
|
1153
1153
|
}
|
|
1154
1154
|
}
|
|
@@ -1157,7 +1157,7 @@ export class QueryInterface {
|
|
|
1157
1157
|
for (const [field, enabled] of Object.entries(args._min)) {
|
|
1158
1158
|
if (enabled) {
|
|
1159
1159
|
const col = this.toColumn(field);
|
|
1160
|
-
selectExprs.push(`MIN(${quoteIdent(col)}) AS ${quoteIdent(
|
|
1160
|
+
selectExprs.push(`MIN(${quoteIdent(col)}) AS ${quoteIdent(`_min_${col}`)}`);
|
|
1161
1161
|
}
|
|
1162
1162
|
}
|
|
1163
1163
|
}
|
|
@@ -1166,7 +1166,7 @@ export class QueryInterface {
|
|
|
1166
1166
|
for (const [field, enabled] of Object.entries(args._max)) {
|
|
1167
1167
|
if (enabled) {
|
|
1168
1168
|
const col = this.toColumn(field);
|
|
1169
|
-
selectExprs.push(`MAX(${quoteIdent(col)}) AS ${quoteIdent(
|
|
1169
|
+
selectExprs.push(`MAX(${quoteIdent(col)}) AS ${quoteIdent(`_max_${col}`)}`);
|
|
1170
1170
|
}
|
|
1171
1171
|
}
|
|
1172
1172
|
}
|
|
@@ -1278,7 +1278,7 @@ export class QueryInterface {
|
|
|
1278
1278
|
for (const [field, enabled] of Object.entries(args._count)) {
|
|
1279
1279
|
if (enabled) {
|
|
1280
1280
|
const col = this.toColumn(field);
|
|
1281
|
-
selectExprs.push(`COUNT(${quoteIdent(col)})::int AS ${quoteIdent(
|
|
1281
|
+
selectExprs.push(`COUNT(${quoteIdent(col)})::int AS ${quoteIdent(`_count_${col}`)}`);
|
|
1282
1282
|
}
|
|
1283
1283
|
}
|
|
1284
1284
|
}
|
|
@@ -1287,7 +1287,7 @@ export class QueryInterface {
|
|
|
1287
1287
|
for (const [field, enabled] of Object.entries(args._sum)) {
|
|
1288
1288
|
if (enabled) {
|
|
1289
1289
|
const col = this.toColumn(field);
|
|
1290
|
-
selectExprs.push(`SUM(${quoteIdent(col)}) AS ${quoteIdent(
|
|
1290
|
+
selectExprs.push(`SUM(${quoteIdent(col)}) AS ${quoteIdent(`_sum_${col}`)}`);
|
|
1291
1291
|
}
|
|
1292
1292
|
}
|
|
1293
1293
|
}
|
|
@@ -1296,7 +1296,7 @@ export class QueryInterface {
|
|
|
1296
1296
|
for (const [field, enabled] of Object.entries(args._avg)) {
|
|
1297
1297
|
if (enabled) {
|
|
1298
1298
|
const col = this.toColumn(field);
|
|
1299
|
-
selectExprs.push(`AVG(${quoteIdent(col)})::float AS ${quoteIdent(
|
|
1299
|
+
selectExprs.push(`AVG(${quoteIdent(col)})::float AS ${quoteIdent(`_avg_${col}`)}`);
|
|
1300
1300
|
}
|
|
1301
1301
|
}
|
|
1302
1302
|
}
|
|
@@ -1305,7 +1305,7 @@ export class QueryInterface {
|
|
|
1305
1305
|
for (const [field, enabled] of Object.entries(args._min)) {
|
|
1306
1306
|
if (enabled) {
|
|
1307
1307
|
const col = this.toColumn(field);
|
|
1308
|
-
selectExprs.push(`MIN(${quoteIdent(col)}) AS ${quoteIdent(
|
|
1308
|
+
selectExprs.push(`MIN(${quoteIdent(col)}) AS ${quoteIdent(`_min_${col}`)}`);
|
|
1309
1309
|
}
|
|
1310
1310
|
}
|
|
1311
1311
|
}
|
|
@@ -1314,7 +1314,7 @@ export class QueryInterface {
|
|
|
1314
1314
|
for (const [field, enabled] of Object.entries(args._max)) {
|
|
1315
1315
|
if (enabled) {
|
|
1316
1316
|
const col = this.toColumn(field);
|
|
1317
|
-
selectExprs.push(`MAX(${quoteIdent(col)}) AS ${quoteIdent(
|
|
1317
|
+
selectExprs.push(`MAX(${quoteIdent(col)}) AS ${quoteIdent(`_max_${col}`)}`);
|
|
1318
1318
|
}
|
|
1319
1319
|
}
|
|
1320
1320
|
}
|
|
@@ -1420,7 +1420,21 @@ export class QueryInterface {
|
|
|
1420
1420
|
const mapped = this.tableMeta.columnMap[field];
|
|
1421
1421
|
if (mapped)
|
|
1422
1422
|
return mapped;
|
|
1423
|
-
|
|
1423
|
+
// Fall back to camelToSnake ONLY if that snake_cased name also exists as a
|
|
1424
|
+
// real column on the table. This preserves the convenience of writing
|
|
1425
|
+
// `userId` when the schema exposes `user_id` under an unusual field name,
|
|
1426
|
+
// but rejects arbitrary strings — closing the defense-in-depth gap for
|
|
1427
|
+
// SQL injection and catching typos like `where: { emial: 'x' }` with a
|
|
1428
|
+
// clear error instead of a cryptic Postgres "column does not exist".
|
|
1429
|
+
const snake = camelToSnake(field);
|
|
1430
|
+
if (this.tableMeta.reverseColumnMap?.[snake]) {
|
|
1431
|
+
return snake;
|
|
1432
|
+
}
|
|
1433
|
+
if (this.tableMeta.allColumns?.includes(snake)) {
|
|
1434
|
+
return snake;
|
|
1435
|
+
}
|
|
1436
|
+
throw new ValidationError(`[turbine] Unknown field "${field}" on table "${this.table}". ` +
|
|
1437
|
+
`Known fields: ${Object.keys(this.tableMeta.columnMap).join(', ') || '(none)'}.`);
|
|
1424
1438
|
}
|
|
1425
1439
|
/** Convert camelCase field name to a double-quoted SQL identifier */
|
|
1426
1440
|
toSqlColumn(field) {
|
|
@@ -1580,7 +1594,7 @@ export class QueryInterface {
|
|
|
1580
1594
|
/**
|
|
1581
1595
|
* Fingerprint a relation filter sub-where for some/every/none.
|
|
1582
1596
|
*/
|
|
1583
|
-
fingerprintRelFilter(
|
|
1597
|
+
fingerprintRelFilter(_targetTable, subWhere) {
|
|
1584
1598
|
const keys = Object.keys(subWhere)
|
|
1585
1599
|
.filter((k) => subWhere[k] !== undefined)
|
|
1586
1600
|
.sort();
|
|
@@ -1692,7 +1706,7 @@ export class QueryInterface {
|
|
|
1692
1706
|
const meta = this.schema.tables[targetTable];
|
|
1693
1707
|
if (!meta)
|
|
1694
1708
|
return;
|
|
1695
|
-
for (const [
|
|
1709
|
+
for (const [_field, value] of Object.entries(subWhere)) {
|
|
1696
1710
|
if (value === undefined)
|
|
1697
1711
|
continue;
|
|
1698
1712
|
if (value === null)
|
package/dist/schema-builder.js
CHANGED
|
@@ -255,15 +255,35 @@ export class ColumnBuilder {
|
|
|
255
255
|
return { ...this._config };
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
|
+
/** Type guard: is `prop` a known nullary ColumnBuilder type method? */
|
|
259
|
+
function isNullaryColumnType(prop) {
|
|
260
|
+
return (prop === 'serial' ||
|
|
261
|
+
prop === 'bigint' ||
|
|
262
|
+
prop === 'integer' ||
|
|
263
|
+
prop === 'smallint' ||
|
|
264
|
+
prop === 'text' ||
|
|
265
|
+
prop === 'boolean' ||
|
|
266
|
+
prop === 'timestamp' ||
|
|
267
|
+
prop === 'date' ||
|
|
268
|
+
prop === 'json' ||
|
|
269
|
+
prop === 'uuid' ||
|
|
270
|
+
prop === 'real' ||
|
|
271
|
+
prop === 'doublePrecision' ||
|
|
272
|
+
prop === 'numeric' ||
|
|
273
|
+
prop === 'bytea');
|
|
274
|
+
}
|
|
258
275
|
/** @deprecated Use defineSchema() with plain objects instead */
|
|
259
276
|
export const column = new Proxy({}, {
|
|
260
277
|
get(_target, prop) {
|
|
261
278
|
if (prop === 'varchar')
|
|
262
279
|
return (length) => new ColumnBuilder().varchar(length);
|
|
280
|
+
if (isNullaryColumnType(prop)) {
|
|
281
|
+
return () => {
|
|
282
|
+
const builder = new ColumnBuilder();
|
|
283
|
+
return builder[prop]();
|
|
284
|
+
};
|
|
285
|
+
}
|
|
263
286
|
return () => {
|
|
264
|
-
const builder = new ColumnBuilder();
|
|
265
|
-
if (typeof builder[prop] === 'function')
|
|
266
|
-
return builder[prop].call(builder);
|
|
267
287
|
throw new Error(`Unknown column type: ${prop}`);
|
|
268
288
|
};
|
|
269
289
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "turbine-orm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Postgres-native TypeScript ORM — runs on Neon, Vercel Postgres, Cloudflare, Supabase. Streaming cursors, typed errors, single-query nested relations. 1 dependency, ~110KB",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
],
|
|
37
37
|
"sideEffects": false,
|
|
38
38
|
"scripts": {
|
|
39
|
+
"prebuild": "node scripts/build-studio-ui.mjs",
|
|
39
40
|
"build": "tsc && tsc --project tsconfig.cjs.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json",
|
|
40
41
|
"dev": "tsc --watch",
|
|
41
42
|
"typecheck": "tsc --noEmit --project tsconfig.test.json",
|
|
@@ -43,13 +44,16 @@
|
|
|
43
44
|
"status": "tsx src/cli/index.ts status",
|
|
44
45
|
"examples": "tsx examples/examples.ts",
|
|
45
46
|
"test": "tsx --test src/test/*.test.ts",
|
|
46
|
-
"test:unit": "tsx --test src/test/schema-builder.test.ts src/test/errors.test.ts src/test/stress.test.ts src/test/migrate.test.ts src/test/update-operators.test.ts src/test/empty-where-guard.test.ts src/test/cli.test.ts src/test/serverless.test.ts src/test/pipeline.test.ts src/test/pipeline-submittable.test.ts src/test/with-inference.test.ts src/test/operator-validation.test.ts src/test/unlimited-warning.test.ts src/test/generate-relations.test.ts src/test/stream-and-parse.test.ts src/test/sql-cache.test.ts",
|
|
47
|
-
"test:coverage": "c8 tsx --test src/test/schema-builder.test.ts src/test/errors.test.ts src/test/stress.test.ts src/test/migrate.test.ts src/test/update-operators.test.ts src/test/empty-where-guard.test.ts src/test/cli.test.ts src/test/serverless.test.ts src/test/pipeline.test.ts src/test/pipeline-submittable.test.ts src/test/with-inference.test.ts src/test/operator-validation.test.ts src/test/unlimited-warning.test.ts src/test/generate-relations.test.ts src/test/stream-and-parse.test.ts src/test/sql-cache.test.ts",
|
|
47
|
+
"test:unit": "tsx --test src/test/schema-builder.test.ts src/test/errors.test.ts src/test/stress.test.ts src/test/migrate.test.ts src/test/update-operators.test.ts src/test/empty-where-guard.test.ts src/test/cli.test.ts src/test/serverless.test.ts src/test/pipeline.test.ts src/test/pipeline-submittable.test.ts src/test/with-inference.test.ts src/test/operator-validation.test.ts src/test/unlimited-warning.test.ts src/test/generate-relations.test.ts src/test/stream-and-parse.test.ts src/test/sql-cache.test.ts src/test/studio.test.ts src/test/sql-injection.test.ts src/test/cli-flags.test.ts",
|
|
48
|
+
"test:coverage": "c8 tsx --test src/test/schema-builder.test.ts src/test/errors.test.ts src/test/stress.test.ts src/test/migrate.test.ts src/test/update-operators.test.ts src/test/empty-where-guard.test.ts src/test/cli.test.ts src/test/serverless.test.ts src/test/pipeline.test.ts src/test/pipeline-submittable.test.ts src/test/with-inference.test.ts src/test/operator-validation.test.ts src/test/unlimited-warning.test.ts src/test/generate-relations.test.ts src/test/stream-and-parse.test.ts src/test/sql-cache.test.ts src/test/studio.test.ts src/test/sql-injection.test.ts src/test/cli-flags.test.ts",
|
|
48
49
|
"lint": "biome check src/",
|
|
49
50
|
"lint:fix": "biome check --write src/",
|
|
50
51
|
"format": "biome format --write src/",
|
|
51
52
|
"prepublishOnly": "npm run build && npm run typecheck && npm run lint && npm run test:unit",
|
|
52
|
-
"prepare": "husky"
|
|
53
|
+
"prepare": "husky",
|
|
54
|
+
"site:dev": "cd site && npm run dev",
|
|
55
|
+
"site:build": "cd site && npm run build",
|
|
56
|
+
"site:deploy": "cd site && vercel --prod"
|
|
53
57
|
},
|
|
54
58
|
"engines": {
|
|
55
59
|
"node": ">=18.0.0"
|