turbine-orm 0.50.0 → 0.51.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 +66 -66
- package/dist/adapters/cockroachdb.d.ts +5 -5
- package/dist/adapters/cockroachdb.js +10 -10
- package/dist/adapters/index.d.ts +5 -5
- package/dist/adapters/index.js +7 -7
- package/dist/adapters/yugabytedb.d.ts +7 -7
- package/dist/adapters/yugabytedb.js +10 -10
- package/dist/cjs/adapters/cockroachdb.d.ts +5 -5
- package/dist/cjs/adapters/cockroachdb.js +10 -10
- package/dist/cjs/adapters/index.d.ts +5 -5
- package/dist/cjs/adapters/index.js +7 -7
- package/dist/cjs/adapters/yugabytedb.d.ts +7 -7
- package/dist/cjs/adapters/yugabytedb.js +10 -10
- package/dist/cjs/cli/config.d.ts +13 -2
- package/dist/cjs/cli/config.js +3 -2
- package/dist/cjs/cli/destructive.d.ts +1 -1
- package/dist/cjs/cli/destructive.js +1 -1
- package/dist/cjs/cli/index.d.ts +10 -10
- package/dist/cjs/cli/index.js +49 -45
- package/dist/cjs/cli/loader.d.ts +7 -7
- package/dist/cjs/cli/loader.js +9 -9
- package/dist/cjs/cli/mcp.js +4 -4
- package/dist/cjs/cli/migrate.d.ts +5 -5
- package/dist/cjs/cli/migrate.js +11 -11
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/cli/ui.d.ts +2 -2
- package/dist/cjs/cli/ui.js +2 -2
- package/dist/cjs/client.d.ts +49 -38
- package/dist/cjs/client.js +57 -56
- package/dist/cjs/dialect.d.ts +62 -18
- package/dist/cjs/dialect.js +40 -2
- package/dist/cjs/errors.d.ts +5 -5
- package/dist/cjs/errors.js +11 -11
- package/dist/cjs/generate.d.ts +6 -6
- package/dist/cjs/generate.js +31 -29
- package/dist/cjs/index-advisor.d.ts +5 -5
- package/dist/cjs/index-advisor.js +0 -0
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +7 -7
- package/dist/cjs/introspect.d.ts +35 -9
- package/dist/cjs/introspect.js +83 -32
- package/dist/cjs/mssql.d.ts +11 -11
- package/dist/cjs/mssql.js +64 -29
- package/dist/cjs/mysql.d.ts +8 -8
- package/dist/cjs/mysql.js +61 -23
- package/dist/cjs/nested-write.d.ts +21 -2
- package/dist/cjs/nested-write.js +51 -14
- package/dist/cjs/optional-peer-import.cjs +7 -7
- package/dist/cjs/optional-peer-import.d.cts +7 -7
- package/dist/cjs/pipeline-submittable.d.ts +2 -2
- package/dist/cjs/pipeline-submittable.js +6 -6
- package/dist/cjs/pipeline.d.ts +1 -1
- package/dist/cjs/pipeline.js +4 -4
- package/dist/cjs/powdb-introspect.d.ts +1 -1
- package/dist/cjs/powdb-introspect.js +1 -1
- package/dist/cjs/powdb.d.ts +28 -28
- package/dist/cjs/powdb.js +66 -66
- package/dist/cjs/powql.d.ts +27 -27
- package/dist/cjs/powql.js +73 -52
- package/dist/cjs/query/aggregates.d.ts +1 -1
- package/dist/cjs/query/aggregates.js +5 -5
- package/dist/cjs/query/batched-loader.d.ts +11 -11
- package/dist/cjs/query/batched-loader.js +24 -24
- package/dist/cjs/query/builder.d.ts +39 -21
- package/dist/cjs/query/builder.js +99 -57
- package/dist/cjs/query/compound-unique.d.ts +1 -1
- package/dist/cjs/query/compound-unique.js +0 -0
- package/dist/cjs/query/deferred.d.ts +12 -6
- package/dist/cjs/query/deferred.js +1 -1
- package/dist/cjs/query/filters.d.ts +31 -11
- package/dist/cjs/query/filters.js +67 -14
- package/dist/cjs/query/index.d.ts +1 -1
- package/dist/cjs/query/index.js +1 -1
- package/dist/cjs/query/relations.d.ts +9 -9
- package/dist/cjs/query/relations.js +164 -57
- package/dist/cjs/query/types.d.ts +86 -35
- package/dist/cjs/query/types.js +1 -1
- package/dist/cjs/query/utils.d.ts +27 -10
- package/dist/cjs/query/utils.js +86 -14
- package/dist/cjs/query/where.d.ts +47 -28
- package/dist/cjs/query/where.js +130 -31
- package/dist/cjs/query/writes.d.ts +24 -5
- package/dist/cjs/query/writes.js +102 -13
- package/dist/cjs/realtime.d.ts +7 -7
- package/dist/cjs/realtime.js +9 -9
- package/dist/cjs/schema-builder.d.ts +18 -7
- package/dist/cjs/schema-builder.js +17 -10
- package/dist/cjs/schema-metadata.d.ts +3 -3
- package/dist/cjs/schema-metadata.js +9 -9
- package/dist/cjs/schema-sql.d.ts +9 -9
- package/dist/cjs/schema-sql.js +20 -20
- package/dist/cjs/schema.d.ts +19 -9
- package/dist/cjs/schema.js +6 -6
- package/dist/cjs/serverless.d.ts +15 -15
- package/dist/cjs/serverless.js +16 -16
- package/dist/cjs/sqlite.d.ts +8 -8
- package/dist/cjs/sqlite.js +53 -22
- package/dist/cjs/typed-sql.d.ts +4 -4
- package/dist/cjs/typed-sql.js +5 -5
- package/dist/cli/config.d.ts +13 -2
- package/dist/cli/config.js +3 -2
- package/dist/cli/destructive.d.ts +1 -1
- package/dist/cli/destructive.js +1 -1
- package/dist/cli/index.d.ts +10 -10
- package/dist/cli/index.js +49 -45
- package/dist/cli/loader.d.ts +7 -7
- package/dist/cli/loader.js +9 -9
- package/dist/cli/mcp.js +4 -4
- package/dist/cli/migrate.d.ts +5 -5
- package/dist/cli/migrate.js +11 -11
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/cli/ui.d.ts +2 -2
- package/dist/cli/ui.js +2 -2
- package/dist/client.d.ts +49 -38
- package/dist/client.js +57 -56
- package/dist/dialect.d.ts +62 -18
- package/dist/dialect.js +40 -2
- package/dist/errors.d.ts +5 -5
- package/dist/errors.js +11 -11
- package/dist/generate.d.ts +6 -6
- package/dist/generate.js +31 -29
- package/dist/index-advisor.d.ts +5 -5
- package/dist/index-advisor.js +0 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7 -7
- package/dist/introspect.d.ts +35 -9
- package/dist/introspect.js +82 -32
- package/dist/mssql.d.ts +11 -11
- package/dist/mssql.js +64 -29
- package/dist/mysql.d.ts +8 -8
- package/dist/mysql.js +61 -23
- package/dist/nested-write.d.ts +21 -2
- package/dist/nested-write.js +51 -14
- package/dist/optional-peer-import.cjs +7 -7
- package/dist/optional-peer-import.d.cts +7 -7
- package/dist/pipeline-submittable.d.ts +2 -2
- package/dist/pipeline-submittable.js +6 -6
- package/dist/pipeline.d.ts +1 -1
- package/dist/pipeline.js +4 -4
- package/dist/powdb-introspect.d.ts +1 -1
- package/dist/powdb-introspect.js +1 -1
- package/dist/powdb.d.ts +28 -28
- package/dist/powdb.js +66 -66
- package/dist/powql.d.ts +27 -27
- package/dist/powql.js +73 -52
- package/dist/query/aggregates.d.ts +1 -1
- package/dist/query/aggregates.js +5 -5
- package/dist/query/batched-loader.d.ts +11 -11
- package/dist/query/batched-loader.js +24 -24
- package/dist/query/builder.d.ts +39 -21
- package/dist/query/builder.js +100 -58
- package/dist/query/compound-unique.d.ts +1 -1
- package/dist/query/compound-unique.js +0 -0
- package/dist/query/deferred.d.ts +12 -6
- package/dist/query/deferred.js +1 -1
- package/dist/query/filters.d.ts +31 -11
- package/dist/query/filters.js +66 -13
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +1 -1
- package/dist/query/relations.d.ts +9 -9
- package/dist/query/relations.js +165 -58
- package/dist/query/types.d.ts +86 -35
- package/dist/query/types.js +1 -1
- package/dist/query/utils.d.ts +27 -10
- package/dist/query/utils.js +84 -14
- package/dist/query/where.d.ts +47 -28
- package/dist/query/where.js +129 -32
- package/dist/query/writes.d.ts +24 -5
- package/dist/query/writes.js +101 -13
- package/dist/realtime.d.ts +7 -7
- package/dist/realtime.js +9 -9
- package/dist/schema-builder.d.ts +18 -7
- package/dist/schema-builder.js +17 -10
- package/dist/schema-metadata.d.ts +3 -3
- package/dist/schema-metadata.js +9 -9
- package/dist/schema-sql.d.ts +9 -9
- package/dist/schema-sql.js +20 -20
- package/dist/schema.d.ts +19 -9
- package/dist/schema.js +6 -6
- package/dist/serverless.d.ts +15 -15
- package/dist/serverless.js +16 -16
- package/dist/sqlite.d.ts +8 -8
- package/dist/sqlite.js +53 -22
- package/dist/typed-sql.d.ts +4 -4
- package/dist/typed-sql.js +5 -5
- package/package.json +2 -2
package/dist/dialect.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* turbine-orm
|
|
2
|
+
* turbine-orm, SQL dialect contract
|
|
3
3
|
*
|
|
4
4
|
* Phase-1 seam for future database packages. The current package remains
|
|
5
5
|
* PostgreSQL-native by default, but query generation now depends on this
|
|
6
6
|
* contract for the SQL primitives that vary across MySQL and SQLite.
|
|
7
7
|
*/
|
|
8
8
|
import { ValidationError } from './errors.js';
|
|
9
|
+
import { coerceJsonWireValue, jsonWireCoercionOid } from './query/utils.js';
|
|
9
10
|
import { pgArrayType, pgTypeToTs } from './schema.js';
|
|
10
11
|
/** PostgreSQL implementation of the dialect contract. */
|
|
11
12
|
export const postgresDialect = {
|
|
@@ -80,6 +81,28 @@ export const postgresDialect = {
|
|
|
80
81
|
if (!input.columnArrayTypes || input.columnArrayTypes.length !== input.columns.length) {
|
|
81
82
|
throw new ValidationError('PostgreSQL bulk insert requires one array type per column');
|
|
82
83
|
}
|
|
84
|
+
// Row-major form: required when a target column is itself array-typed,
|
|
85
|
+
// because the UNNEST transpose below flattens nested arrays (see
|
|
86
|
+
// `requireRowValues`). One placeholder per cell instead of per column.
|
|
87
|
+
if (input.requireRowValues) {
|
|
88
|
+
const params = [];
|
|
89
|
+
const tuples = input.rowValues.map((row) => {
|
|
90
|
+
const cells = input.columns.map((_, i) => {
|
|
91
|
+
params.push(row[i]);
|
|
92
|
+
// No cast: PostgreSQL infers each parameter's type from the INSERT
|
|
93
|
+
// target column, which is what single-row `create` already relies on
|
|
94
|
+
// and is what makes arrays and enums both work here. The casts in
|
|
95
|
+
// `columnArrayTypes` are ARRAY-OF-column-type, correct for the
|
|
96
|
+
// UNNEST transpose below but wrong for an individual cell.
|
|
97
|
+
return this.paramPlaceholder(params.length);
|
|
98
|
+
});
|
|
99
|
+
return `(${cells.join(', ')})`;
|
|
100
|
+
});
|
|
101
|
+
let rowSql = `INSERT INTO ${input.table} (${input.columns.join(', ')}) VALUES ${tuples.join(', ')}`;
|
|
102
|
+
if (input.skipDuplicates)
|
|
103
|
+
rowSql += ' ON CONFLICT DO NOTHING';
|
|
104
|
+
return { sql: `${rowSql}${this.buildReturningClause(input.returning)}`, params };
|
|
105
|
+
}
|
|
83
106
|
const columnArrays = input.columns.map((_, columnIndex) => input.rowValues.map((row) => row[columnIndex]));
|
|
84
107
|
const unnestArgs = input.columns.map((_, i) => `${this.paramPlaceholder(i + 1)}::${input.columnArrayTypes[i]}`);
|
|
85
108
|
let sql = `INSERT INTO ${input.table} (${input.columns.join(', ')}) SELECT * FROM UNNEST(${unnestArgs.join(', ')})`;
|
|
@@ -124,6 +147,21 @@ export const postgresDialect = {
|
|
|
124
147
|
arrayType(baseType) {
|
|
125
148
|
return pgArrayType(baseType);
|
|
126
149
|
},
|
|
150
|
+
jsonWireRule(columnType) {
|
|
151
|
+
// `json_build_object` renders these as a JSON number / bare token that does
|
|
152
|
+
// not match what the driver returns for the same column (numeric past
|
|
153
|
+
// double precision and int8 past 2^53 lose digits outright). Carry the
|
|
154
|
+
// driver's own wire TEXT instead and hand it back to the driver's parser
|
|
155
|
+
// for that OID, so the join strategy returns exactly what a top-level read
|
|
156
|
+
// returns.
|
|
157
|
+
const oid = jsonWireCoercionOid(columnType);
|
|
158
|
+
if (oid === undefined)
|
|
159
|
+
return undefined;
|
|
160
|
+
return {
|
|
161
|
+
sql: (ref) => `${ref}::text`,
|
|
162
|
+
decode: (value) => coerceJsonWireValue(oid, value),
|
|
163
|
+
};
|
|
164
|
+
},
|
|
127
165
|
buildColumnType(input) {
|
|
128
166
|
if (input.type === 'VARCHAR' && input.maxLength != null) {
|
|
129
167
|
return `VARCHAR(${input.maxLength})`;
|
|
@@ -195,7 +233,7 @@ export const postgresDialect = {
|
|
|
195
233
|
return `ROLLBACK TO SAVEPOINT ${name}`;
|
|
196
234
|
},
|
|
197
235
|
buildSetSessionConfig(name, value) {
|
|
198
|
-
// set_config(name, value, is_local=true)
|
|
236
|
+
// set_config(name, value, is_local=true), the parameterizable,
|
|
199
237
|
// transaction-local equivalent of `SET LOCAL` (which rejects bind params).
|
|
200
238
|
return {
|
|
201
239
|
sql: `SELECT set_config(${this.paramPlaceholder(1)}, ${this.paramPlaceholder(2)}, true)`,
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* turbine-orm
|
|
2
|
+
* turbine-orm, Error types
|
|
3
3
|
*
|
|
4
4
|
* Typed errors with error codes for programmatic handling.
|
|
5
5
|
* All Turbine errors extend TurbineError which includes a `code` property.
|
|
@@ -39,7 +39,7 @@ export declare class TurbineError extends Error {
|
|
|
39
39
|
*
|
|
40
40
|
* Defaults to `'safe'` to avoid leaking PII into error logs (Sentry, Datadog,
|
|
41
41
|
* etc.). The full `where` object is always available as `err.where` for
|
|
42
|
-
* programmatic access
|
|
42
|
+
* programmatic access, only the human-readable message is redacted.
|
|
43
43
|
*
|
|
44
44
|
* Set via `setErrorMessageMode('verbose')` or by constructing TurbineClient
|
|
45
45
|
* with `{ errorMessages: 'verbose' }`.
|
|
@@ -182,7 +182,7 @@ export declare class NotNullViolationError extends TurbineError {
|
|
|
182
182
|
/**
|
|
183
183
|
* Thrown when Postgres detects a deadlock (pg code 40P01).
|
|
184
184
|
*
|
|
185
|
-
* This error is **retryable
|
|
185
|
+
* This error is **retryable**, when caught, callers can safely retry the
|
|
186
186
|
* transaction (typically with backoff). Catch it explicitly:
|
|
187
187
|
*
|
|
188
188
|
* ```ts
|
|
@@ -207,9 +207,9 @@ export declare class DeadlockError extends TurbineError {
|
|
|
207
207
|
}
|
|
208
208
|
/**
|
|
209
209
|
* Thrown when a Serializable transaction fails due to a serialization
|
|
210
|
-
* conflict (pg code 40001
|
|
210
|
+
* conflict (pg code 40001, `could not serialize access due to ...`).
|
|
211
211
|
*
|
|
212
|
-
* This error is **retryable
|
|
212
|
+
* This error is **retryable**, by Postgres documentation, the recommended
|
|
213
213
|
* response is to re-run the entire transaction. Catch it explicitly:
|
|
214
214
|
*
|
|
215
215
|
* ```ts
|
package/dist/errors.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* turbine-orm
|
|
2
|
+
* turbine-orm, Error types
|
|
3
3
|
*
|
|
4
4
|
* Typed errors with error codes for programmatic handling.
|
|
5
5
|
* All Turbine errors extend TurbineError which includes a `code` property.
|
|
@@ -140,7 +140,7 @@ export class NotFoundError extends TurbineError {
|
|
|
140
140
|
where;
|
|
141
141
|
operation;
|
|
142
142
|
constructor(input) {
|
|
143
|
-
// Back-compat: string argument (or undefined)
|
|
143
|
+
// Back-compat: string argument (or undefined), replicate legacy behavior.
|
|
144
144
|
if (typeof input === 'string' || input === undefined) {
|
|
145
145
|
super(TurbineErrorCode.NOT_FOUND, input ?? 'Record not found');
|
|
146
146
|
this.name = 'NotFoundError';
|
|
@@ -255,7 +255,7 @@ export class UniqueConstraintError extends TurbineError {
|
|
|
255
255
|
// PII-safe by default: the raw pg `detail` string contains the
|
|
256
256
|
// conflicting row VALUES (e.g. `Key (email)=(alice@x.com) already
|
|
257
257
|
// exists.`). Only append it in 'verbose' mode. In 'safe' mode the
|
|
258
|
-
// message carries keys/constraint/column names only
|
|
258
|
+
// message carries keys/constraint/column names only, the structured
|
|
259
259
|
// `.columns`/`.constraint`/`.column` fields and `.cause` still expose
|
|
260
260
|
// the full detail for programmatic use.
|
|
261
261
|
const detail = errorMessageMode === 'verbose' ? detailFromCause(cause) : undefined;
|
|
@@ -282,7 +282,7 @@ export class ForeignKeyError extends TurbineError {
|
|
|
282
282
|
// PII-safe by default: the raw pg `detail` string contains the
|
|
283
283
|
// conflicting row VALUES (e.g. `Key (email)=(alice@x.com) already
|
|
284
284
|
// exists.`). Only append it in 'verbose' mode. In 'safe' mode the
|
|
285
|
-
// message carries keys/constraint/column names only
|
|
285
|
+
// message carries keys/constraint/column names only, the structured
|
|
286
286
|
// `.columns`/`.constraint`/`.column` fields and `.cause` still expose
|
|
287
287
|
// the full detail for programmatic use.
|
|
288
288
|
const detail = errorMessageMode === 'verbose' ? detailFromCause(cause) : undefined;
|
|
@@ -308,7 +308,7 @@ export class NotNullViolationError extends TurbineError {
|
|
|
308
308
|
// PII-safe by default: the raw pg `detail` string contains the
|
|
309
309
|
// conflicting row VALUES (e.g. `Key (email)=(alice@x.com) already
|
|
310
310
|
// exists.`). Only append it in 'verbose' mode. In 'safe' mode the
|
|
311
|
-
// message carries keys/constraint/column names only
|
|
311
|
+
// message carries keys/constraint/column names only, the structured
|
|
312
312
|
// `.columns`/`.constraint`/`.column` fields and `.cause` still expose
|
|
313
313
|
// the full detail for programmatic use.
|
|
314
314
|
const detail = errorMessageMode === 'verbose' ? detailFromCause(cause) : undefined;
|
|
@@ -324,7 +324,7 @@ export class NotNullViolationError extends TurbineError {
|
|
|
324
324
|
/**
|
|
325
325
|
* Thrown when Postgres detects a deadlock (pg code 40P01).
|
|
326
326
|
*
|
|
327
|
-
* This error is **retryable
|
|
327
|
+
* This error is **retryable**, when caught, callers can safely retry the
|
|
328
328
|
* transaction (typically with backoff). Catch it explicitly:
|
|
329
329
|
*
|
|
330
330
|
* ```ts
|
|
@@ -355,9 +355,9 @@ export class DeadlockError extends TurbineError {
|
|
|
355
355
|
}
|
|
356
356
|
/**
|
|
357
357
|
* Thrown when a Serializable transaction fails due to a serialization
|
|
358
|
-
* conflict (pg code 40001
|
|
358
|
+
* conflict (pg code 40001, `could not serialize access due to ...`).
|
|
359
359
|
*
|
|
360
|
-
* This error is **retryable
|
|
360
|
+
* This error is **retryable**, by Postgres documentation, the recommended
|
|
361
361
|
* response is to re-run the entire transaction. Catch it explicitly:
|
|
362
362
|
*
|
|
363
363
|
* ```ts
|
|
@@ -399,7 +399,7 @@ export class CheckConstraintError extends TurbineError {
|
|
|
399
399
|
// PII-safe by default: the raw pg `detail` string contains the
|
|
400
400
|
// conflicting row VALUES (e.g. `Key (email)=(alice@x.com) already
|
|
401
401
|
// exists.`). Only append it in 'verbose' mode. In 'safe' mode the
|
|
402
|
-
// message carries keys/constraint/column names only
|
|
402
|
+
// message carries keys/constraint/column names only, the structured
|
|
403
403
|
// `.columns`/`.constraint`/`.column` fields and `.cause` still expose
|
|
404
404
|
// the full detail for programmatic use.
|
|
405
405
|
const detail = errorMessageMode === 'verbose' ? detailFromCause(cause) : undefined;
|
|
@@ -424,7 +424,7 @@ export class ExclusionConstraintError extends TurbineError {
|
|
|
424
424
|
// PII-safe by default: the raw pg `detail` string contains the
|
|
425
425
|
// conflicting row VALUES (e.g. `Key (email)=(alice@x.com) already
|
|
426
426
|
// exists.`). Only append it in 'verbose' mode. In 'safe' mode the
|
|
427
|
-
// message carries keys/constraint/column names only
|
|
427
|
+
// message carries keys/constraint/column names only, the structured
|
|
428
428
|
// `.columns`/`.constraint`/`.column` fields and `.cause` still expose
|
|
429
429
|
// the full detail for programmatic use.
|
|
430
430
|
const detail = errorMessageMode === 'verbose' ? detailFromCause(cause) : undefined;
|
|
@@ -482,7 +482,7 @@ export class OptimisticLockError extends TurbineError {
|
|
|
482
482
|
versionField;
|
|
483
483
|
expectedVersion;
|
|
484
484
|
constructor(opts) {
|
|
485
|
-
super(TurbineErrorCode.OPTIMISTIC_LOCK, `[turbine] Optimistic lock failed on "${opts.table}"
|
|
485
|
+
super(TurbineErrorCode.OPTIMISTIC_LOCK, `[turbine] Optimistic lock failed on "${opts.table}", ` +
|
|
486
486
|
`expected ${opts.versionField} = ${opts.expectedVersion} but row was modified by another transaction`);
|
|
487
487
|
this.name = 'OptimisticLockError';
|
|
488
488
|
this.table = opts.table;
|
package/dist/generate.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* turbine-orm
|
|
2
|
+
* turbine-orm, Code generator
|
|
3
3
|
*
|
|
4
4
|
* Takes an IntrospectedSchema and emits TypeScript files:
|
|
5
|
-
* - types.ts
|
|
6
|
-
* - metadata.ts
|
|
7
|
-
* - index.ts
|
|
5
|
+
* - types.ts , Entity interfaces, Create/Update input types
|
|
6
|
+
* - metadata.ts , Runtime schema metadata (column maps, relations, etc.)
|
|
7
|
+
* - index.ts , Configured TurbineClient with typed table accessors
|
|
8
8
|
*
|
|
9
9
|
* Output goes to the specified directory (default: ./generated/turbine/).
|
|
10
10
|
*/
|
|
@@ -19,13 +19,13 @@ export interface GenerateOptions {
|
|
|
19
19
|
/**
|
|
20
20
|
* Also emit `zod.ts` with per-table `XSchema` / `XCreateSchema` /
|
|
21
21
|
* `XUpdateSchema` Zod validators (H1). The file imports the user-side `zod`
|
|
22
|
-
* package
|
|
22
|
+
* package, it is never imported by Turbine's runtime, so Zod stays out of the
|
|
23
23
|
* library's dependency graph. Default: `false`.
|
|
24
24
|
*/
|
|
25
25
|
zod?: boolean;
|
|
26
26
|
/**
|
|
27
27
|
* Omit the `Generated at: <ISO timestamp>` line from every generated file
|
|
28
|
-
* header (T-8b
|
|
28
|
+
* header (T-8b, reproducible codegen). With this set, byte-identical
|
|
29
29
|
* schemas regenerate to byte-identical output, so regens produce empty
|
|
30
30
|
* diffs. Default: `false` (timestamp included, unchanged behavior).
|
|
31
31
|
*/
|
package/dist/generate.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* turbine-orm
|
|
2
|
+
* turbine-orm, Code generator
|
|
3
3
|
*
|
|
4
4
|
* Takes an IntrospectedSchema and emits TypeScript files:
|
|
5
|
-
* - types.ts
|
|
6
|
-
* - metadata.ts
|
|
7
|
-
* - index.ts
|
|
5
|
+
* - types.ts , Entity interfaces, Create/Update input types
|
|
6
|
+
* - metadata.ts , Runtime schema metadata (column maps, relations, etc.)
|
|
7
|
+
* - index.ts , Configured TurbineClient with typed table accessors
|
|
8
8
|
*
|
|
9
9
|
* Output goes to the specified directory (default: ./generated/turbine/).
|
|
10
10
|
*/
|
|
@@ -66,7 +66,7 @@ function escSQ(value) {
|
|
|
66
66
|
// ---------------------------------------------------------------------------
|
|
67
67
|
export function generate(options) {
|
|
68
68
|
const outDir = options.outDir ?? './generated/turbine';
|
|
69
|
-
// Path traversal protection
|
|
69
|
+
// Path traversal protection, ensure output stays within project root
|
|
70
70
|
const resolved = resolve(outDir);
|
|
71
71
|
const rel = relative(process.cwd(), resolved);
|
|
72
72
|
if (rel.startsWith('..') || resolve(rel) !== resolved) {
|
|
@@ -92,7 +92,7 @@ export function generate(options) {
|
|
|
92
92
|
const indexContent = generateIndex(schema, fileOptions);
|
|
93
93
|
writeFileSync(join(outDir, 'index.ts'), indexContent, 'utf-8');
|
|
94
94
|
files.push('index.ts');
|
|
95
|
-
// Generate zod.ts (optional
|
|
95
|
+
// Generate zod.ts (optional, --zod flag)
|
|
96
96
|
if (options.zod) {
|
|
97
97
|
const zodContent = generateZod(schema, fileOptions);
|
|
98
98
|
writeFileSync(join(outDir, 'zod.ts'), zodContent, 'utf-8');
|
|
@@ -251,7 +251,7 @@ function generatedFileHeader(options) {
|
|
|
251
251
|
// unchanged schema produces byte-identical files.
|
|
252
252
|
return [
|
|
253
253
|
'/**',
|
|
254
|
-
' * Auto-generated by turbine-orm
|
|
254
|
+
' * Auto-generated by turbine-orm, DO NOT EDIT',
|
|
255
255
|
' *',
|
|
256
256
|
...(options?.noTimestamp ? [] : [` * Generated at: ${new Date().toISOString()}`]),
|
|
257
257
|
' * @see https://turbineorm.dev',
|
|
@@ -265,7 +265,7 @@ function generatedFileHeader(options) {
|
|
|
265
265
|
* column: `interface XWithY extends X` becomes TS2430, the `XCreate & { y?: … }`
|
|
266
266
|
* intersection collapses (TS2322), and neither the column nor the relation is
|
|
267
267
|
* targetable. Introspection no longer produces such names (they are
|
|
268
|
-
* disambiguated at the source), but hand-written or legacy metadata may
|
|
268
|
+
* disambiguated at the source), but hand-written or legacy metadata may -
|
|
269
269
|
* skip those relations here with a warning instead of emitting broken types.
|
|
270
270
|
* The runtime metadata (metadata.ts) still carries every relation.
|
|
271
271
|
*/
|
|
@@ -275,7 +275,7 @@ function typeSafeRelations(table, warn = true) {
|
|
|
275
275
|
for (const [relName, rel] of Object.entries(table.relations)) {
|
|
276
276
|
if (columnFields.has(relName)) {
|
|
277
277
|
if (warn) {
|
|
278
|
-
console.warn(`[turbine] Relation "${relName}" on table "${table.name}" shadows a column field of the same name
|
|
278
|
+
console.warn(`[turbine] Relation "${relName}" on table "${table.name}" shadows a column field of the same name, ` +
|
|
279
279
|
`omitting it from the generated types. Rename the relation (or the column) to expose it.`);
|
|
280
280
|
}
|
|
281
281
|
continue;
|
|
@@ -349,7 +349,7 @@ export function generateTypes(schema, options) {
|
|
|
349
349
|
lines.push(`/** Input type for creating a row in \`${table.name}\` */`);
|
|
350
350
|
lines.push(`export type ${typeName}Create = {`);
|
|
351
351
|
for (const col of table.columns) {
|
|
352
|
-
// STORED generated columns are computed by the database
|
|
352
|
+
// STORED generated columns are computed by the database, never writable.
|
|
353
353
|
if (col.isGeneratedStored)
|
|
354
354
|
continue;
|
|
355
355
|
const isPk = table.primaryKey.includes(col.name);
|
|
@@ -381,7 +381,7 @@ export function generateTypes(schema, options) {
|
|
|
381
381
|
// Each relation is emitted as a `RelationDescriptor<Target, Cardinality,
|
|
382
382
|
// TargetRelations>` brand-field interface. This is what enables the
|
|
383
383
|
// recursive `WithResult` type to walk through nested `with` clauses at
|
|
384
|
-
// any depth
|
|
384
|
+
// any depth, `RelationRelations<R[K]>` reads the third type parameter
|
|
385
385
|
// and threads it into the next recursion step. If the target table has
|
|
386
386
|
// no relations of its own, the descriptor uses `{}` (the default).
|
|
387
387
|
const safeRelations = safeRelationsByTable.get(table.name) ?? [];
|
|
@@ -425,7 +425,7 @@ export function generateTypes(schema, options) {
|
|
|
425
425
|
const typeName = entityName(table.name);
|
|
426
426
|
const safeRelations = safeRelationsByTable.get(table.name) ?? [];
|
|
427
427
|
const hasRels = safeRelations.length > 0;
|
|
428
|
-
// WhereUnique
|
|
428
|
+
// WhereUnique, union of unique constraint shapes, deduplicating PK
|
|
429
429
|
const seen = new Set();
|
|
430
430
|
const uniqueSets = [];
|
|
431
431
|
// Always include the primary key first
|
|
@@ -502,7 +502,7 @@ export function generateTypes(schema, options) {
|
|
|
502
502
|
lines.push(`export type ${typeName}WhereUnique = ${branches.join(' | ')};`);
|
|
503
503
|
lines.push('');
|
|
504
504
|
}
|
|
505
|
-
// CreateInput / UpdateInput
|
|
505
|
+
// CreateInput / UpdateInput, extends base type with optional relation fields
|
|
506
506
|
if (hasRels) {
|
|
507
507
|
lines.push(`export type ${typeName}CreateInput = ${typeName}Create & {`);
|
|
508
508
|
for (const [relName, rel] of safeRelations) {
|
|
@@ -556,11 +556,11 @@ export function generateTypes(schema, options) {
|
|
|
556
556
|
return lines.join('\n');
|
|
557
557
|
}
|
|
558
558
|
// ---------------------------------------------------------------------------
|
|
559
|
-
// zod.ts generator (H1
|
|
559
|
+
// zod.ts generator (H1, `turbine generate --zod`)
|
|
560
560
|
// ---------------------------------------------------------------------------
|
|
561
561
|
/**
|
|
562
562
|
* Map a TypeScript primitive (as produced by {@link pgTypeToTs}) to its Zod
|
|
563
|
-
* expression. `Date` uses `z.coerce.date()
|
|
563
|
+
* expression. `Date` uses `z.coerce.date()`, the generated schemas double as
|
|
564
564
|
* request-body validators where dates arrive as ISO strings, and coercion keeps
|
|
565
565
|
* both a `Date` and a valid date-string acceptable (documented decision).
|
|
566
566
|
*/
|
|
@@ -579,7 +579,7 @@ function zodScalar(ts) {
|
|
|
579
579
|
case 'Buffer':
|
|
580
580
|
return 'z.instanceof(Uint8Array)';
|
|
581
581
|
case 'number[]':
|
|
582
|
-
// pgvector
|
|
582
|
+
// pgvector, `pgTypeToTs('vector')` yields `number[]`.
|
|
583
583
|
return 'z.array(z.number())';
|
|
584
584
|
default:
|
|
585
585
|
// json/jsonb and any unmapped user-defined type.
|
|
@@ -589,7 +589,7 @@ function zodScalar(ts) {
|
|
|
589
589
|
/**
|
|
590
590
|
* Base Zod expression for a column, resolving enums → `z.enum([...])`, arrays →
|
|
591
591
|
* `.array()`, and vectors → `z.array(z.number())`. Does NOT append
|
|
592
|
-
* `.nullable()` / `.optional()
|
|
592
|
+
* `.nullable()` / `.optional()`, callers layer those on per-schema.
|
|
593
593
|
*
|
|
594
594
|
* `forWrite` mirrors {@link writeColumnTsType}: the Create/Update schemas
|
|
595
595
|
* validate WRITE input, where a `time` / `timetz` column also accepts a JS
|
|
@@ -624,7 +624,7 @@ function zodBaseType(col, enums, forWrite = false) {
|
|
|
624
624
|
*/
|
|
625
625
|
export function generateZod(schema, options) {
|
|
626
626
|
const lines = [...generatedFileHeader(options)];
|
|
627
|
-
// `zod` is a USER dependency
|
|
627
|
+
// `zod` is a USER dependency, this generated file imports it, but the Turbine
|
|
628
628
|
// library runtime never does, so Zod stays out of the package's dep graph.
|
|
629
629
|
lines.push("import { z } from 'zod';");
|
|
630
630
|
lines.push('');
|
|
@@ -641,7 +641,7 @@ export function generateZod(schema, options) {
|
|
|
641
641
|
}
|
|
642
642
|
lines.push('});');
|
|
643
643
|
lines.push('');
|
|
644
|
-
// Create schema
|
|
644
|
+
// Create schema, STORED generated columns can never be written; PK,
|
|
645
645
|
// defaulted, and nullable columns are optional.
|
|
646
646
|
lines.push(`/** Zod schema for creating a \`${table.name}\` row */`);
|
|
647
647
|
lines.push(`export const ${typeName}CreateSchema = z.object({`);
|
|
@@ -658,7 +658,7 @@ export function generateZod(schema, options) {
|
|
|
658
658
|
}
|
|
659
659
|
lines.push('});');
|
|
660
660
|
lines.push('');
|
|
661
|
-
// Update schema
|
|
661
|
+
// Update schema, PK and STORED generated columns omitted; all else optional.
|
|
662
662
|
lines.push(`/** Zod schema for updating a \`${table.name}\` row */`);
|
|
663
663
|
lines.push(`export const ${typeName}UpdateSchema = z.object({`);
|
|
664
664
|
for (const col of table.columns) {
|
|
@@ -740,7 +740,7 @@ export function generateMetadata(schema, options) {
|
|
|
740
740
|
const refLiteral = Array.isArray(rel.referenceKey)
|
|
741
741
|
? `[${rel.referenceKey.map((c) => `'${escSQ(c)}'`).join(', ')}]`
|
|
742
742
|
: `'${escSQ(rel.referenceKey)}'`;
|
|
743
|
-
// manyToMany relations carry a `through` junction descriptor
|
|
743
|
+
// manyToMany relations carry a `through` junction descriptor, emit it so
|
|
744
744
|
// the runtime query builder can JOIN through the junction table.
|
|
745
745
|
let throughLiteral = '';
|
|
746
746
|
if (rel.through) {
|
|
@@ -774,7 +774,7 @@ export function generateMetadata(schema, options) {
|
|
|
774
774
|
}
|
|
775
775
|
lines.push(' ],');
|
|
776
776
|
}
|
|
777
|
-
// isView
|
|
777
|
+
// isView, read-only marker; the runtime write guard reads it.
|
|
778
778
|
if (table.isView)
|
|
779
779
|
lines.push(' isView: true,');
|
|
780
780
|
lines.push(' },');
|
|
@@ -827,7 +827,7 @@ export function generateIndex(schema, options) {
|
|
|
827
827
|
lines.push(`import type { ${typeImports.join(', ')} } from './types${ext}';`);
|
|
828
828
|
lines.push('');
|
|
829
829
|
// -------------------------------------------------------------------------
|
|
830
|
-
// TypedTransactionClient
|
|
830
|
+
// TypedTransactionClient, same typed table accessors as TurbineClient,
|
|
831
831
|
// but scoped to a single transaction connection. The runtime instance is
|
|
832
832
|
// an ordinary `TransactionClient` from turbine-orm; this declaration just
|
|
833
833
|
// teaches TypeScript about the auto-attached accessors so users get
|
|
@@ -835,7 +835,7 @@ export function generateIndex(schema, options) {
|
|
|
835
835
|
// -------------------------------------------------------------------------
|
|
836
836
|
lines.push('/**');
|
|
837
837
|
lines.push(' * Transaction-scoped client with the same typed table accessors as TurbineClient.');
|
|
838
|
-
lines.push(' * Created automatically by `db.$transaction(async (tx) => ...)
|
|
838
|
+
lines.push(' * Created automatically by `db.$transaction(async (tx) => ...)`, never instantiate');
|
|
839
839
|
lines.push(' * directly. All queries run on a dedicated connection within a BEGIN/COMMIT block.');
|
|
840
840
|
lines.push(' */');
|
|
841
841
|
lines.push('export class TypedTransactionClient extends BaseTransactionClient {');
|
|
@@ -900,7 +900,7 @@ export function generateIndex(schema, options) {
|
|
|
900
900
|
// so users get autocomplete on `tx.users`, `tx.posts`, etc.
|
|
901
901
|
//
|
|
902
902
|
// IMPORTANT: the merged member must be compatible with the base class's
|
|
903
|
-
// $transaction ON ITS OWN (TS2415)
|
|
903
|
+
// $transaction ON ITS OWN (TS2415), since v0.26 the base method also has a
|
|
904
904
|
// batch-array overload (`$transaction([...queries])`), so the merged
|
|
905
905
|
// interface must redeclare BOTH signatures. Emitting only the callback form
|
|
906
906
|
// makes every generated client fail `tsc` with "incorrectly extends".
|
|
@@ -1030,7 +1030,7 @@ function serializeColumn(col) {
|
|
|
1030
1030
|
`arrayType: '${escSQ(col.arrayType ?? col.pgArrayType)}'`,
|
|
1031
1031
|
`pgArrayType: '${escSQ(col.pgArrayType)}'`,
|
|
1032
1032
|
];
|
|
1033
|
-
// Cross-schema type marker
|
|
1033
|
+
// Cross-schema type marker, introspection records it only for types living
|
|
1034
1034
|
// outside the introspected schema; it must survive codegen or the runtime
|
|
1035
1035
|
// enum-cast guard in query/builder.ts loses the signal (N-5).
|
|
1036
1036
|
if (col.pgTypeSchema !== undefined)
|
|
@@ -1039,7 +1039,7 @@ function serializeColumn(col) {
|
|
|
1039
1039
|
// output stays byte-identical for the common client-default columns.
|
|
1040
1040
|
if (col.isGenerated)
|
|
1041
1041
|
parts.push(`isGenerated: true`);
|
|
1042
|
-
// STORED generated columns
|
|
1042
|
+
// STORED generated columns, the runtime write guard reads isGeneratedStored.
|
|
1043
1043
|
if (col.isGeneratedStored)
|
|
1044
1044
|
parts.push(`isGeneratedStored: true`);
|
|
1045
1045
|
if (col.generationExpression !== undefined) {
|
|
@@ -1050,6 +1050,8 @@ function serializeColumn(col) {
|
|
|
1050
1050
|
// object built from `defineSchema` (pii: true) carries it through codegen.
|
|
1051
1051
|
if (col.pii)
|
|
1052
1052
|
parts.push(`pii: true`);
|
|
1053
|
+
if (col.updatedAt)
|
|
1054
|
+
parts.push(`updatedAt: true`);
|
|
1053
1055
|
if (col.maxLength !== undefined)
|
|
1054
1056
|
parts.push(`maxLength: ${col.maxLength}`);
|
|
1055
1057
|
return `{ ${parts.join(', ')} }`;
|
|
@@ -1065,7 +1067,7 @@ function snakeToCamelStr(s) {
|
|
|
1065
1067
|
* `T | UpdateOperatorInput<number> | null?` so users can write atomic
|
|
1066
1068
|
* operators (`{ increment: 1 }`, `{ multiply: 2 }`, etc.) without casts.
|
|
1067
1069
|
*
|
|
1068
|
-
* The check is purely structural
|
|
1070
|
+
* The check is purely structural, if the column's TS type contains
|
|
1069
1071
|
* `'number'` (e.g. `number`, `number | null`), it's eligible. Other
|
|
1070
1072
|
* scalar types (`string`, `boolean`, `Date`, `unknown`, `Buffer`,
|
|
1071
1073
|
* `Date | null`, etc.) pass through unchanged.
|
|
@@ -1079,7 +1081,7 @@ function updateFieldType(tsType) {
|
|
|
1079
1081
|
}
|
|
1080
1082
|
/**
|
|
1081
1083
|
* Detect whether a TypeScript type expression contains the `number` primitive
|
|
1082
|
-
* as a top-level union member. Conservative on purpose
|
|
1084
|
+
* as a top-level union member. Conservative on purpose, only matches
|
|
1083
1085
|
* `number`, `number | null`, `null | number`, etc., not `number[]` or
|
|
1084
1086
|
* `Record<string, number>`.
|
|
1085
1087
|
*/
|
package/dist/index-advisor.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Index advisor
|
|
2
|
+
* Index advisor, finds relation probes that lack index support.
|
|
3
3
|
*
|
|
4
4
|
* Turbine loads `with` relations as correlated subqueries: for every parent row,
|
|
5
5
|
* the child table is probed by its FK column(s) (`child.fk = parent.pk`). Relation
|
|
6
6
|
* filters (`some`/`none`/`every`, `is`/`isNot`) probe the same columns. This
|
|
7
7
|
* strategy outperforms batched loading (`WHERE fk IN (ids)`) when the probed
|
|
8
|
-
* column is indexed
|
|
8
|
+
* column is indexed, but with NO index, each probe is a full table scan and the
|
|
9
9
|
* cost multiplies by the parent rowcount, while batched loading would pay the
|
|
10
10
|
* scan only once. A missing FK index that is invisible under a batched-loader
|
|
11
11
|
* ORM becomes pathological under a correlated one.
|
|
@@ -27,7 +27,7 @@ export interface RelationProbe {
|
|
|
27
27
|
export interface MissingRelationIndex {
|
|
28
28
|
/** Table that gets probed per parent row */
|
|
29
29
|
table: string;
|
|
30
|
-
/** Probed column(s)
|
|
30
|
+
/** Probed column(s), equality lookups, so a covering index must LEAD with one of them */
|
|
31
31
|
columns: string[];
|
|
32
32
|
/** Every relation that generates this probe */
|
|
33
33
|
probes: RelationProbe[];
|
|
@@ -46,7 +46,7 @@ export interface CreateIndexSqlOptions {
|
|
|
46
46
|
* it must also carry the `-- turbine:no-transaction` directive.
|
|
47
47
|
*/
|
|
48
48
|
concurrently?: boolean;
|
|
49
|
-
/** Emit `IF NOT EXISTS` (default true
|
|
49
|
+
/** Emit `IF NOT EXISTS` (default true, required for idempotent no-transaction migrations). */
|
|
50
50
|
ifNotExists?: boolean;
|
|
51
51
|
/**
|
|
52
52
|
* Emit a partial index `... WHERE <col> IS NOT NULL`. Only applied for a
|
|
@@ -134,7 +134,7 @@ export declare function schemaHasIndexInfo(schema: SchemaMetadata): boolean;
|
|
|
134
134
|
* Single-relation check for the dev-mode runtime warning: the (table, columns)
|
|
135
135
|
* this relation probes and whether that probe is unindexed. Returns null when
|
|
136
136
|
* indexed, unknown, or when the schema carries no index info at all (metadata
|
|
137
|
-
* built without introspection
|
|
137
|
+
* built without introspection, warning would be a blanket false positive).
|
|
138
138
|
*/
|
|
139
139
|
export declare function missingIndexForRelation(schema: SchemaMetadata, relDef: {
|
|
140
140
|
name: string;
|
package/dist/index-advisor.js
CHANGED
|
Binary file
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* turbine-orm
|
|
3
3
|
*
|
|
4
|
-
* Turbine TypeScript SDK
|
|
4
|
+
* Turbine TypeScript SDK, type-safe Postgres queries with nested relations
|
|
5
5
|
* and pipeline batching. Feels like Prisma, runs at raw-SQL speed.
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
@@ -50,21 +50,21 @@ export { HttpJsonSink, PgMetricsSink, } from './observe.js';
|
|
|
50
50
|
export { executePipeline, pipelineSupported } from './pipeline.js';
|
|
51
51
|
// Query builder
|
|
52
52
|
export { AUTO_ASSUMED_ROUND_TRIP_MS, AUTO_JOIN_PENALTY_MS_PER_ROW, AUTO_TO_ONE_JOIN_MAX_ROWS, AUTO_TO_ONE_JOIN_ROWS_MAX, AUTO_TO_ONE_JOIN_ROWS_MIN, QueryInterface, } from './query/index.js';
|
|
53
|
-
// Realtime
|
|
53
|
+
// Realtime, LISTEN/NOTIFY pub/sub
|
|
54
54
|
export { validateChannel } from './realtime.js';
|
|
55
55
|
// Schema utilities
|
|
56
56
|
export { camelToSnake, isDateType, normalizeKeyColumns, pgArrayType, pgTypeToTs, singularize, snakeToCamel, snakeToPascal, withDbFieldNames, } from './schema.js';
|
|
57
|
-
// Schema builder
|
|
57
|
+
// Schema builder, define schemas in TypeScript
|
|
58
58
|
export { applyManyToManyRelations, ColumnBuilder, column, defineSchema, isDocFieldIndexDef,
|
|
59
|
-
// Legacy compat (deprecated
|
|
59
|
+
// Legacy compat (deprecated, use object format with defineSchema)
|
|
60
60
|
table, } from './schema-builder.js';
|
|
61
|
-
// Schema metadata bridge
|
|
61
|
+
// Schema metadata bridge, defineSchema() → SchemaMetadata without a live DB
|
|
62
62
|
export { schemaDefToMetadata } from './schema-metadata.js';
|
|
63
|
-
// Schema SQL
|
|
63
|
+
// Schema SQL, generate DDL, diff, and push
|
|
64
64
|
export { DestructivePushRefusal, schemaDiff, schemaPush, schemaToSQL, schemaToSQLString, } from './schema-sql.js';
|
|
65
65
|
// Seed helper
|
|
66
66
|
export { defineSeed } from './seed.js';
|
|
67
67
|
// Serverless / edge factory
|
|
68
68
|
export { turbineHttp } from './serverless.js';
|
|
69
|
-
// Typed raw SQL
|
|
69
|
+
// Typed raw SQL, Turbine's TypedSQL escape hatch
|
|
70
70
|
export { buildTypedSql, TypedSqlQuery } from './typed-sql.js';
|