turbine-orm 0.16.0 → 0.19.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.
Files changed (43) hide show
  1. package/README.md +180 -12
  2. package/dist/adapters/cockroachdb.js +4 -2
  3. package/dist/adapters/index.js +4 -1
  4. package/dist/adapters/yugabytedb.js +4 -2
  5. package/dist/cjs/adapters/cockroachdb.js +4 -2
  6. package/dist/cjs/adapters/index.js +4 -1
  7. package/dist/cjs/adapters/yugabytedb.js +4 -2
  8. package/dist/cjs/cli/studio-ui.generated.js +1 -1
  9. package/dist/cjs/cli/studio.js +35 -73
  10. package/dist/cjs/client.js +164 -0
  11. package/dist/cjs/errors.js +35 -5
  12. package/dist/cjs/generate.js +14 -3
  13. package/dist/cjs/index.js +10 -2
  14. package/dist/cjs/introspect.js +81 -0
  15. package/dist/cjs/nested-write.js +70 -6
  16. package/dist/cjs/query/builder.js +581 -17
  17. package/dist/cjs/realtime.js +147 -0
  18. package/dist/cjs/schema-builder.js +86 -0
  19. package/dist/cjs/schema.js +10 -0
  20. package/dist/cjs/typed-sql.js +149 -0
  21. package/dist/cli/studio-ui.generated.js +1 -1
  22. package/dist/cli/studio.js +35 -73
  23. package/dist/client.d.ts +120 -0
  24. package/dist/client.js +165 -1
  25. package/dist/errors.js +35 -5
  26. package/dist/generate.js +14 -3
  27. package/dist/index.d.ts +4 -2
  28. package/dist/index.js +5 -1
  29. package/dist/introspect.js +81 -0
  30. package/dist/nested-write.js +70 -6
  31. package/dist/query/builder.d.ts +104 -1
  32. package/dist/query/builder.js +582 -18
  33. package/dist/query/index.d.ts +1 -1
  34. package/dist/query/types.d.ts +126 -2
  35. package/dist/realtime.d.ts +71 -0
  36. package/dist/realtime.js +144 -0
  37. package/dist/schema-builder.d.ts +68 -1
  38. package/dist/schema-builder.js +85 -0
  39. package/dist/schema.d.ts +18 -1
  40. package/dist/schema.js +10 -0
  41. package/dist/typed-sql.d.ts +101 -0
  42. package/dist/typed-sql.js +145 -0
  43. package/package.json +17 -15
@@ -0,0 +1,101 @@
1
+ /**
2
+ * turbine-orm — Typed raw SQL (Turbine's answer to Prisma's TypedSQL)
3
+ *
4
+ * `client.raw()` returns untyped rows. This module adds a *typed* escape hatch:
5
+ * a generic tagged template where the caller supplies the row shape, and the
6
+ * builder yields a typed result that can be awaited as an array of rows, or
7
+ * narrowed to a single row (`.one()`) or a single scalar value (`.scalar()`).
8
+ *
9
+ * Design goals & guarantees:
10
+ *
11
+ * 1. **Compile-time only types.** `T` is supplied by the caller and never
12
+ * validated at runtime — exactly like Prisma's TypedSQL and the existing
13
+ * `raw<T>()`. Postgres still returns whatever the SQL selects; the generic
14
+ * is a convenience for autocomplete and downstream type-checking.
15
+ *
16
+ * 2. **Mandatory parameterization.** Only the *static* string segments of the
17
+ * template literal ever reach the SQL text. Every interpolated `${value}`
18
+ * becomes a `$N` placeholder and is passed in the params array — it is
19
+ * impossible to string-concatenate a value into the query through this API.
20
+ * This is the whole point of the tagged-template shape: the literal segments
21
+ * are frozen by the compiler (`TemplateStringsArray`), and the only way to
22
+ * get a runtime value into the query is via `${...}`, which we bind.
23
+ *
24
+ * 3. **Rows are returned as-is (no snake→camel mapping).** This matches the
25
+ * existing `client.raw()` behavior: a typed raw query is a literal escape
26
+ * hatch, so the result columns are whatever your `SELECT` names them. Alias
27
+ * columns in SQL (`SELECT created_at AS "createdAt"`) if you want camelCase.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * // Awaited directly -> rows
32
+ * const rows = await db.sql<{ id: number; name: string }>`
33
+ * SELECT id, name FROM users WHERE org_id = ${orgId}
34
+ * `;
35
+ * // ^? { id: number; name: string }[]
36
+ *
37
+ * // .one() -> single row or null
38
+ * const user = await db.sql<{ id: number; name: string }>`
39
+ * SELECT id, name FROM users WHERE id = ${userId}
40
+ * `.one();
41
+ * // ^? { id: number; name: string } | null
42
+ *
43
+ * // .scalar() -> first column of first row, or null
44
+ * const total = await db.sql<{ count: number }>`
45
+ * SELECT COUNT(*)::int AS count FROM users WHERE org_id = ${orgId}
46
+ * `.scalar();
47
+ * // ^? number | null
48
+ * ```
49
+ */
50
+ import type { PgCompatPool } from './client.js';
51
+ /**
52
+ * Build a `(sql, params)` pair from a tagged-template invocation.
53
+ *
54
+ * Each interpolated value is replaced by a positional `$N` placeholder and
55
+ * pushed to the params array in order. The static string segments are the only
56
+ * thing concatenated into the SQL text. This is the single point that
57
+ * guarantees parameterization for the entire typed-SQL surface.
58
+ *
59
+ * Exported for unit testing the parameterization invariant without a database.
60
+ */
61
+ export declare function buildTypedSql(strings: TemplateStringsArray, values: readonly unknown[]): {
62
+ sql: string;
63
+ params: unknown[];
64
+ };
65
+ /**
66
+ * A pending typed raw SQL query. Implements the thenable contract, so it can be
67
+ * `await`ed directly to get `T[]`, or refined via `.one()` / `.scalar()` first.
68
+ *
69
+ * The query is executed lazily and exactly once per terminal call (`then`,
70
+ * `one`, `scalar`). Each terminal method runs the query independently — this is
71
+ * an escape hatch, not a cached query object, so don't call two terminals on
72
+ * the same builder expecting a single round-trip; build a fresh template each
73
+ * time (the common pattern is `await db.sql\`...\`` inline).
74
+ */
75
+ export declare class TypedSqlQuery<T extends Record<string, unknown>> implements PromiseLike<T[]> {
76
+ private readonly pool;
77
+ private readonly sql;
78
+ private readonly params;
79
+ private readonly logging;
80
+ constructor(pool: PgCompatPool, sql: string, params: unknown[], logging: boolean);
81
+ /** Execute and return all rows. Internal; powers `then`, `one`, and `scalar`. */
82
+ private run;
83
+ /**
84
+ * PromiseLike implementation: `await db.sql<T>\`...\`` resolves to `T[]`.
85
+ */
86
+ then<TResult1 = T[], TResult2 = never>(onfulfilled?: ((value: T[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
87
+ /**
88
+ * Execute and return the first row, or `null` if the query returns no rows.
89
+ * Use for queries you expect to match at most one row.
90
+ */
91
+ one(): Promise<T | null>;
92
+ /**
93
+ * Execute and return the first column of the first row, or `null` if there
94
+ * are no rows. Useful for `SELECT COUNT(*)`, `SELECT EXISTS(...)`, etc.
95
+ *
96
+ * The generic `V` defaults to the value type of `T`'s first property, but you
97
+ * can override it: `db.sql<{ count: number }>\`...\`.scalar<number>()`.
98
+ */
99
+ scalar<V = T[keyof T]>(): Promise<V | null>;
100
+ }
101
+ //# sourceMappingURL=typed-sql.d.ts.map
@@ -0,0 +1,145 @@
1
+ /**
2
+ * turbine-orm — Typed raw SQL (Turbine's answer to Prisma's TypedSQL)
3
+ *
4
+ * `client.raw()` returns untyped rows. This module adds a *typed* escape hatch:
5
+ * a generic tagged template where the caller supplies the row shape, and the
6
+ * builder yields a typed result that can be awaited as an array of rows, or
7
+ * narrowed to a single row (`.one()`) or a single scalar value (`.scalar()`).
8
+ *
9
+ * Design goals & guarantees:
10
+ *
11
+ * 1. **Compile-time only types.** `T` is supplied by the caller and never
12
+ * validated at runtime — exactly like Prisma's TypedSQL and the existing
13
+ * `raw<T>()`. Postgres still returns whatever the SQL selects; the generic
14
+ * is a convenience for autocomplete and downstream type-checking.
15
+ *
16
+ * 2. **Mandatory parameterization.** Only the *static* string segments of the
17
+ * template literal ever reach the SQL text. Every interpolated `${value}`
18
+ * becomes a `$N` placeholder and is passed in the params array — it is
19
+ * impossible to string-concatenate a value into the query through this API.
20
+ * This is the whole point of the tagged-template shape: the literal segments
21
+ * are frozen by the compiler (`TemplateStringsArray`), and the only way to
22
+ * get a runtime value into the query is via `${...}`, which we bind.
23
+ *
24
+ * 3. **Rows are returned as-is (no snake→camel mapping).** This matches the
25
+ * existing `client.raw()` behavior: a typed raw query is a literal escape
26
+ * hatch, so the result columns are whatever your `SELECT` names them. Alias
27
+ * columns in SQL (`SELECT created_at AS "createdAt"`) if you want camelCase.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * // Awaited directly -> rows
32
+ * const rows = await db.sql<{ id: number; name: string }>`
33
+ * SELECT id, name FROM users WHERE org_id = ${orgId}
34
+ * `;
35
+ * // ^? { id: number; name: string }[]
36
+ *
37
+ * // .one() -> single row or null
38
+ * const user = await db.sql<{ id: number; name: string }>`
39
+ * SELECT id, name FROM users WHERE id = ${userId}
40
+ * `.one();
41
+ * // ^? { id: number; name: string } | null
42
+ *
43
+ * // .scalar() -> first column of first row, or null
44
+ * const total = await db.sql<{ count: number }>`
45
+ * SELECT COUNT(*)::int AS count FROM users WHERE org_id = ${orgId}
46
+ * `.scalar();
47
+ * // ^? number | null
48
+ * ```
49
+ */
50
+ import { ValidationError, wrapPgError } from './errors.js';
51
+ /**
52
+ * Build a `(sql, params)` pair from a tagged-template invocation.
53
+ *
54
+ * Each interpolated value is replaced by a positional `$N` placeholder and
55
+ * pushed to the params array in order. The static string segments are the only
56
+ * thing concatenated into the SQL text. This is the single point that
57
+ * guarantees parameterization for the entire typed-SQL surface.
58
+ *
59
+ * Exported for unit testing the parameterization invariant without a database.
60
+ */
61
+ export function buildTypedSql(strings, values) {
62
+ // The tagged-template API guarantees `strings.length === values.length + 1`.
63
+ // Guard the directly-callable (exported) surface so a mismatched call can't
64
+ // silently desync `$N` placeholders from params (pg would reject it, but fail
65
+ // loudly here instead).
66
+ if (strings.length !== values.length + 1) {
67
+ throw new ValidationError(`[turbine] sql template segment/value count mismatch: ${strings.length} segments, ${values.length} values.`);
68
+ }
69
+ let sql = '';
70
+ for (let i = 0; i < strings.length; i++) {
71
+ sql += strings[i];
72
+ if (i < values.length) {
73
+ sql += `$${i + 1}`;
74
+ }
75
+ }
76
+ return { sql, params: values.slice() };
77
+ }
78
+ /**
79
+ * A pending typed raw SQL query. Implements the thenable contract, so it can be
80
+ * `await`ed directly to get `T[]`, or refined via `.one()` / `.scalar()` first.
81
+ *
82
+ * The query is executed lazily and exactly once per terminal call (`then`,
83
+ * `one`, `scalar`). Each terminal method runs the query independently — this is
84
+ * an escape hatch, not a cached query object, so don't call two terminals on
85
+ * the same builder expecting a single round-trip; build a fresh template each
86
+ * time (the common pattern is `await db.sql\`...\`` inline).
87
+ */
88
+ export class TypedSqlQuery {
89
+ pool;
90
+ sql;
91
+ params;
92
+ logging;
93
+ constructor(pool, sql, params, logging) {
94
+ this.pool = pool;
95
+ this.sql = sql;
96
+ this.params = params;
97
+ this.logging = logging;
98
+ }
99
+ /** Execute and return all rows. Internal; powers `then`, `one`, and `scalar`. */
100
+ async run() {
101
+ if (this.logging) {
102
+ console.log(`[turbine] Typed SQL: ${this.sql.trim().substring(0, 120)}...`);
103
+ }
104
+ try {
105
+ const result = await this.pool.query(this.sql, this.params);
106
+ return result.rows;
107
+ }
108
+ catch (err) {
109
+ throw wrapPgError(err);
110
+ }
111
+ }
112
+ /**
113
+ * PromiseLike implementation: `await db.sql<T>\`...\`` resolves to `T[]`.
114
+ */
115
+ // biome-ignore lint/suspicious/noThenProperty: intentional thenable — this IS the PromiseLike contract that makes `await db.sql\`...\`` resolve to rows
116
+ then(onfulfilled, onrejected) {
117
+ return this.run().then(onfulfilled, onrejected);
118
+ }
119
+ /**
120
+ * Execute and return the first row, or `null` if the query returns no rows.
121
+ * Use for queries you expect to match at most one row.
122
+ */
123
+ async one() {
124
+ const rows = await this.run();
125
+ return rows.length > 0 ? rows[0] : null;
126
+ }
127
+ /**
128
+ * Execute and return the first column of the first row, or `null` if there
129
+ * are no rows. Useful for `SELECT COUNT(*)`, `SELECT EXISTS(...)`, etc.
130
+ *
131
+ * The generic `V` defaults to the value type of `T`'s first property, but you
132
+ * can override it: `db.sql<{ count: number }>\`...\`.scalar<number>()`.
133
+ */
134
+ async scalar() {
135
+ const rows = await this.run();
136
+ if (rows.length === 0)
137
+ return null;
138
+ const first = rows[0];
139
+ const keys = Object.keys(first);
140
+ if (keys.length === 0)
141
+ return null;
142
+ return first[keys[0]];
143
+ }
144
+ }
145
+ //# sourceMappingURL=typed-sql.js.map
package/package.json CHANGED
@@ -1,28 +1,28 @@
1
1
  {
2
2
  "name": "turbine-orm",
3
- "version": "0.16.0",
4
- "description": "Postgres-native TypeScript ORM — runs on Neon, Vercel Postgres, Cloudflare, Supabase. Streaming cursors, typed errors, single-query nested relations. 1 dependency, ~110KB",
3
+ "version": "0.19.0",
4
+ "description": "Postgres-native TypeScript ORM — runs on Neon, Vercel Postgres, Cloudflare, Supabase. Streaming cursors, typed errors, single-query nested relations. One dependency, no WASM engine",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": {
8
+ "types": "./dist/index.d.ts",
8
9
  "import": "./dist/index.js",
9
- "require": "./dist/cjs/index.js",
10
- "types": "./dist/index.d.ts"
10
+ "require": "./dist/cjs/index.js"
11
11
  },
12
12
  "./serverless": {
13
+ "types": "./dist/serverless.d.ts",
13
14
  "import": "./dist/serverless.js",
14
- "require": "./dist/cjs/serverless.js",
15
- "types": "./dist/serverless.d.ts"
15
+ "require": "./dist/cjs/serverless.js"
16
16
  },
17
17
  "./cli": {
18
+ "types": "./dist/cli/config.d.ts",
18
19
  "import": "./dist/cli/config.js",
19
- "require": "./dist/cjs/cli/config.js",
20
- "types": "./dist/cli/config.d.ts"
20
+ "require": "./dist/cjs/cli/config.js"
21
21
  },
22
22
  "./adapters": {
23
+ "types": "./dist/adapters/index.d.ts",
23
24
  "import": "./dist/adapters/index.js",
24
- "require": "./dist/cjs/adapters/index.js",
25
- "types": "./dist/adapters/index.d.ts"
25
+ "require": "./dist/cjs/adapters/index.js"
26
26
  }
27
27
  },
28
28
  "main": "./dist/cjs/index.js",
@@ -48,9 +48,10 @@
48
48
  "generate": "tsx src/cli/index.ts generate",
49
49
  "status": "tsx src/cli/index.ts status",
50
50
  "examples": "tsx examples/examples.ts",
51
- "test": "tsx --test src/test/*.test.ts",
51
+ "dogfood": "tsx examples/dogfood.ts",
52
+ "test": "tsx --test --test-concurrency=1 src/test/*.test.ts",
52
53
  "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/dialect.test.ts src/test/studio.test.ts src/test/sql-injection.test.ts src/test/cli-flags.test.ts src/test/cockroachdb-adapter.test.ts src/test/yugabytedb-adapter.test.ts src/test/pg-compat.test.ts src/test/relation-filter-validation.test.ts src/test/client-coverage.test.ts src/test/schema-diff.test.ts src/test/composite-fk.test.ts src/test/retry.test.ts src/test/text-search.test.ts src/test/optimistic-lock.test.ts src/test/sql-safety-property.test.ts src/test/nested-write.test.ts src/test/nested-write-update-upsert.test.ts src/test/cursor-pagination.test.ts src/test/client-branches.test.ts src/test/is-isNot-filter.test.ts src/test/event-emitter.test.ts src/test/observe.test.ts",
53
- "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/dialect.test.ts src/test/studio.test.ts src/test/sql-injection.test.ts src/test/cli-flags.test.ts",
54
+ "test:coverage": "c8 tsx --test --test-concurrency=1 src/test/*.test.ts",
54
55
  "lint": "biome check src/",
55
56
  "lint:fix": "biome check --write src/",
56
57
  "format": "biome format --write src/",
@@ -74,17 +75,18 @@
74
75
  "node": ">=18.0.0"
75
76
  },
76
77
  "dependencies": {
78
+ "@types/pg": "^8.11.11",
77
79
  "pg": "^8.13.1"
78
80
  },
79
81
  "devDependencies": {
80
82
  "@biomejs/biome": "^2.4.10",
81
- "@size-limit/file": "^12.0.1",
83
+ "@size-limit/esbuild": "^12.1.0",
84
+ "@size-limit/file": "^12.1.0",
82
85
  "@types/node": "^22.10.0",
83
- "@types/pg": "^8.11.11",
84
86
  "c8": "^11.0.0",
85
87
  "husky": "^9.1.7",
86
88
  "lint-staged": "^16.4.0",
87
- "size-limit": "^12.0.1",
89
+ "size-limit": "^12.1.0",
88
90
  "tsx": "^4.19.0",
89
91
  "typescript": "^5.7.0"
90
92
  },