turbine-orm 0.8.0 → 0.9.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/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('_sum_' + col)}`);
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('_avg_' + col)}`);
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('_min_' + col)}`);
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('_max_' + col)}`);
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('_count_' + col)}`);
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('_sum_' + col)}`);
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('_avg_' + col)}`);
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('_min_' + col)}`);
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('_max_' + col)}`);
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
- return camelToSnake(field);
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(targetTable, subWhere) {
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 [field, value] of Object.entries(subWhere)) {
1709
+ for (const [_field, value] of Object.entries(subWhere)) {
1696
1710
  if (value === undefined)
1697
1711
  continue;
1698
1712
  if (value === null)
@@ -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.8.0",
3
+ "version": "0.9.1",
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"