turbine-orm 0.9.2 → 0.10.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 +34 -16
- package/dist/adapters/cockroachdb.d.ts +40 -0
- package/dist/adapters/cockroachdb.js +172 -0
- package/dist/adapters/index.d.ts +107 -0
- package/dist/adapters/index.js +83 -0
- package/dist/adapters/yugabytedb.d.ts +52 -0
- package/dist/adapters/yugabytedb.js +156 -0
- package/dist/cjs/adapters/cockroachdb.js +174 -0
- package/dist/cjs/adapters/index.js +87 -0
- package/dist/cjs/adapters/yugabytedb.js +158 -0
- package/dist/cjs/cli/index.js +2 -1
- package/dist/cjs/cli/migrate.js +18 -12
- package/dist/cjs/cli/studio.js +5 -4
- package/dist/cjs/generate.js +8 -1
- package/dist/cjs/index.js +10 -3
- package/dist/cjs/introspect.js +46 -18
- package/dist/cjs/query/builder.js +22 -5
- package/dist/cjs/query/index.js +2 -1
- package/dist/cjs/query/utils.js +18 -0
- package/dist/cjs/schema.js +8 -0
- package/dist/cli/config.d.ts +11 -0
- package/dist/cli/index.js +2 -1
- package/dist/cli/migrate.d.ts +3 -0
- package/dist/cli/migrate.js +16 -10
- package/dist/cli/studio.d.ts +4 -0
- package/dist/cli/studio.js +5 -4
- package/dist/generate.js +8 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/introspect.js +46 -18
- package/dist/query/builder.js +23 -6
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +1 -1
- package/dist/query/utils.d.ts +8 -0
- package/dist/query/utils.js +17 -0
- package/dist/schema.d.ts +6 -4
- package/dist/schema.js +7 -0
- package/package.json +7 -2
package/dist/cjs/cli/studio.js
CHANGED
|
@@ -74,7 +74,8 @@ async function startStudio(options) {
|
|
|
74
74
|
});
|
|
75
75
|
const authToken = (0, node_crypto_1.randomBytes)(24).toString('hex');
|
|
76
76
|
const stateDir = (0, node_path_1.resolve)(options.stateDir ?? '.turbine');
|
|
77
|
-
const
|
|
77
|
+
const statementTimeoutSQL = options.adapter?.statementTimeout?.(30) ?? `SET LOCAL statement_timeout = '30s'`;
|
|
78
|
+
const ctx = { pool, metadata, options, authToken, stateDir, statementTimeoutSQL };
|
|
78
79
|
const server = (0, node_http_1.createServer)((req, res) => {
|
|
79
80
|
handleRequest(req, res, ctx).catch((err) => {
|
|
80
81
|
sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
@@ -293,7 +294,7 @@ async function apiTableRows(res, ctx, rawTableName, params) {
|
|
|
293
294
|
const client = await ctx.pool.connect();
|
|
294
295
|
try {
|
|
295
296
|
await client.query('BEGIN READ ONLY');
|
|
296
|
-
await client.query(
|
|
297
|
+
await client.query(ctx.statementTimeoutSQL);
|
|
297
298
|
const result = await client.query(sql, mainValues);
|
|
298
299
|
const countResult = await client.query(countSql, countValues);
|
|
299
300
|
await client.query('COMMIT');
|
|
@@ -359,7 +360,7 @@ async function apiQuery(req, res, ctx) {
|
|
|
359
360
|
const client = await ctx.pool.connect();
|
|
360
361
|
try {
|
|
361
362
|
await client.query('BEGIN READ ONLY');
|
|
362
|
-
await client.query(
|
|
363
|
+
await client.query(ctx.statementTimeoutSQL);
|
|
363
364
|
const started = Date.now();
|
|
364
365
|
const result = await client.query(rawSql);
|
|
365
366
|
const elapsedMs = Date.now() - started;
|
|
@@ -411,7 +412,7 @@ async function apiBuilder(req, res, ctx) {
|
|
|
411
412
|
const client = await ctx.pool.connect();
|
|
412
413
|
try {
|
|
413
414
|
await client.query('BEGIN READ ONLY');
|
|
414
|
-
await client.query(
|
|
415
|
+
await client.query(ctx.statementTimeoutSQL);
|
|
415
416
|
const started = Date.now();
|
|
416
417
|
const result = await client.query(deferred.sql, deferred.params);
|
|
417
418
|
const elapsedMs = Date.now() - started;
|
package/dist/cjs/generate.js
CHANGED
|
@@ -233,7 +233,14 @@ function generateMetadata(schema) {
|
|
|
233
233
|
// relations
|
|
234
234
|
lines.push(' relations: {');
|
|
235
235
|
for (const [relName, rel] of Object.entries(table.relations)) {
|
|
236
|
-
|
|
236
|
+
// Emit foreignKey/referenceKey as string for single-column, array for composite
|
|
237
|
+
const fkLiteral = Array.isArray(rel.foreignKey)
|
|
238
|
+
? `[${rel.foreignKey.map((c) => `'${escSQ(c)}'`).join(', ')}]`
|
|
239
|
+
: `'${escSQ(rel.foreignKey)}'`;
|
|
240
|
+
const refLiteral = Array.isArray(rel.referenceKey)
|
|
241
|
+
? `[${rel.referenceKey.map((c) => `'${escSQ(c)}'`).join(', ')}]`
|
|
242
|
+
: `'${escSQ(rel.referenceKey)}'`;
|
|
243
|
+
lines.push(` ${relName}: { type: '${escSQ(rel.type)}', name: '${escSQ(rel.name)}', from: '${escSQ(rel.from)}', to: '${escSQ(rel.to)}', foreignKey: ${fkLiteral}, referenceKey: ${refLiteral} },`);
|
|
237
244
|
}
|
|
238
245
|
lines.push(' },');
|
|
239
246
|
// indexes
|
package/dist/cjs/index.js
CHANGED
|
@@ -34,7 +34,13 @@
|
|
|
34
34
|
* ```
|
|
35
35
|
*/
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
exports.turbineHttp = exports.schemaToSQLString = exports.schemaToSQL = exports.schemaPush = exports.schemaDiff = exports.table = exports.defineSchema = exports.column = exports.ColumnBuilder = exports.snakeToPascal = exports.snakeToCamel = exports.singularize = exports.pgTypeToTs = exports.pgArrayType = exports.isDateType = exports.camelToSnake = exports.QueryInterface = exports.pipelineSupported = exports.executePipeline = exports.introspect = exports.generate = exports.wrapPgError = exports.ValidationError = exports.UniqueConstraintError = exports.TurbineErrorCode = exports.TurbineError = exports.TimeoutError = exports.setErrorMessageMode = exports.SerializationFailureError = exports.RelationError = exports.PipelineError = exports.NotNullViolationError = exports.NotFoundError = exports.MigrationError = exports.getErrorMessageMode = exports.ForeignKeyError = exports.DeadlockError = exports.ConnectionError = exports.CircularRelationError = exports.CheckConstraintError = exports.TurbineClient = exports.TransactionClient = void 0;
|
|
37
|
+
exports.turbineHttp = exports.schemaToSQLString = exports.schemaToSQL = exports.schemaPush = exports.schemaDiff = exports.table = exports.defineSchema = exports.column = exports.ColumnBuilder = exports.snakeToPascal = exports.snakeToCamel = exports.singularize = exports.pgTypeToTs = exports.pgArrayType = exports.normalizeKeyColumns = exports.isDateType = exports.camelToSnake = exports.QueryInterface = exports.pipelineSupported = exports.executePipeline = exports.introspect = exports.generate = exports.wrapPgError = exports.ValidationError = exports.UniqueConstraintError = exports.TurbineErrorCode = exports.TurbineError = exports.TimeoutError = exports.setErrorMessageMode = exports.SerializationFailureError = exports.RelationError = exports.PipelineError = exports.NotNullViolationError = exports.NotFoundError = exports.MigrationError = exports.getErrorMessageMode = exports.ForeignKeyError = exports.DeadlockError = exports.ConnectionError = exports.CircularRelationError = exports.CheckConstraintError = exports.TurbineClient = exports.TransactionClient = exports.yugabytedb = exports.timescale = exports.postgresql = exports.cockroachdb = exports.alloydb = void 0;
|
|
38
|
+
var index_js_1 = require("./adapters/index.js");
|
|
39
|
+
Object.defineProperty(exports, "alloydb", { enumerable: true, get: function () { return index_js_1.alloydb; } });
|
|
40
|
+
Object.defineProperty(exports, "cockroachdb", { enumerable: true, get: function () { return index_js_1.cockroachdb; } });
|
|
41
|
+
Object.defineProperty(exports, "postgresql", { enumerable: true, get: function () { return index_js_1.postgresql; } });
|
|
42
|
+
Object.defineProperty(exports, "timescale", { enumerable: true, get: function () { return index_js_1.timescale; } });
|
|
43
|
+
Object.defineProperty(exports, "yugabytedb", { enumerable: true, get: function () { return index_js_1.yugabytedb; } });
|
|
38
44
|
// Client
|
|
39
45
|
var client_js_1 = require("./client.js");
|
|
40
46
|
Object.defineProperty(exports, "TransactionClient", { enumerable: true, get: function () { return client_js_1.TransactionClient; } });
|
|
@@ -71,12 +77,13 @@ var pipeline_js_1 = require("./pipeline.js");
|
|
|
71
77
|
Object.defineProperty(exports, "executePipeline", { enumerable: true, get: function () { return pipeline_js_1.executePipeline; } });
|
|
72
78
|
Object.defineProperty(exports, "pipelineSupported", { enumerable: true, get: function () { return pipeline_js_1.pipelineSupported; } });
|
|
73
79
|
// Query builder
|
|
74
|
-
var
|
|
75
|
-
Object.defineProperty(exports, "QueryInterface", { enumerable: true, get: function () { return
|
|
80
|
+
var index_js_2 = require("./query/index.js");
|
|
81
|
+
Object.defineProperty(exports, "QueryInterface", { enumerable: true, get: function () { return index_js_2.QueryInterface; } });
|
|
76
82
|
// Schema utilities
|
|
77
83
|
var schema_js_1 = require("./schema.js");
|
|
78
84
|
Object.defineProperty(exports, "camelToSnake", { enumerable: true, get: function () { return schema_js_1.camelToSnake; } });
|
|
79
85
|
Object.defineProperty(exports, "isDateType", { enumerable: true, get: function () { return schema_js_1.isDateType; } });
|
|
86
|
+
Object.defineProperty(exports, "normalizeKeyColumns", { enumerable: true, get: function () { return schema_js_1.normalizeKeyColumns; } });
|
|
80
87
|
Object.defineProperty(exports, "pgArrayType", { enumerable: true, get: function () { return schema_js_1.pgArrayType; } });
|
|
81
88
|
Object.defineProperty(exports, "pgTypeToTs", { enumerable: true, get: function () { return schema_js_1.pgTypeToTs; } });
|
|
82
89
|
Object.defineProperty(exports, "singularize", { enumerable: true, get: function () { return schema_js_1.singularize; } });
|
package/dist/cjs/introspect.js
CHANGED
|
@@ -72,13 +72,16 @@ const SQL_FOREIGN_KEYS = `
|
|
|
72
72
|
const SQL_UNIQUE_CONSTRAINTS = `
|
|
73
73
|
SELECT
|
|
74
74
|
tc.table_name,
|
|
75
|
-
|
|
75
|
+
tc.constraint_name,
|
|
76
|
+
kcu.column_name,
|
|
77
|
+
kcu.ordinal_position
|
|
76
78
|
FROM information_schema.table_constraints tc
|
|
77
79
|
JOIN information_schema.key_column_usage kcu
|
|
78
80
|
ON tc.constraint_name = kcu.constraint_name
|
|
79
81
|
AND tc.table_schema = kcu.table_schema
|
|
80
82
|
WHERE tc.constraint_type = 'UNIQUE'
|
|
81
83
|
AND tc.table_schema = $1
|
|
84
|
+
ORDER BY tc.table_name, tc.constraint_name, kcu.ordinal_position
|
|
82
85
|
`;
|
|
83
86
|
const SQL_INDEXES = `
|
|
84
87
|
SELECT tablename, indexname, indexdef
|
|
@@ -159,14 +162,22 @@ async function introspect(options) {
|
|
|
159
162
|
pkByTable.get(row.table_name).push(row.column_name);
|
|
160
163
|
}
|
|
161
164
|
// ----- Group unique constraints by table -----
|
|
165
|
+
// Group rows by (table_name, constraint_name) to correctly handle multi-column unique constraints
|
|
162
166
|
const uniqueByTable = new Map();
|
|
167
|
+
const uniqueConstraintGroups = new Map();
|
|
163
168
|
for (const row of uniqueResult.rows) {
|
|
164
169
|
if (!tableSet.has(row.table_name))
|
|
165
170
|
continue;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
171
|
+
const key = `${row.table_name}::${row.constraint_name}`;
|
|
172
|
+
if (!uniqueConstraintGroups.has(key)) {
|
|
173
|
+
uniqueConstraintGroups.set(key, { table: row.table_name, columns: [] });
|
|
174
|
+
}
|
|
175
|
+
uniqueConstraintGroups.get(key).columns.push(row.column_name);
|
|
176
|
+
}
|
|
177
|
+
for (const { table, columns } of uniqueConstraintGroups.values()) {
|
|
178
|
+
if (!uniqueByTable.has(table))
|
|
179
|
+
uniqueByTable.set(table, []);
|
|
180
|
+
uniqueByTable.get(table).push(columns);
|
|
170
181
|
}
|
|
171
182
|
// ----- Group indexes by table -----
|
|
172
183
|
const indexesByTable = new Map();
|
|
@@ -193,17 +204,25 @@ async function introspect(options) {
|
|
|
193
204
|
enums[row.typname] = [];
|
|
194
205
|
enums[row.typname].push(row.enumlabel);
|
|
195
206
|
}
|
|
196
|
-
const
|
|
207
|
+
const fkGroups = new Map();
|
|
197
208
|
for (const row of fkResult.rows) {
|
|
198
209
|
if (!tableSet.has(row.source_table) || !tableSet.has(row.target_table))
|
|
199
210
|
continue;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
211
|
+
const key = row.constraint_name;
|
|
212
|
+
if (!fkGroups.has(key)) {
|
|
213
|
+
fkGroups.set(key, {
|
|
214
|
+
sourceTable: row.source_table,
|
|
215
|
+
sourceColumns: [],
|
|
216
|
+
targetTable: row.target_table,
|
|
217
|
+
targetColumns: [],
|
|
218
|
+
constraintName: key,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
const entry = fkGroups.get(key);
|
|
222
|
+
entry.sourceColumns.push(row.source_column);
|
|
223
|
+
entry.targetColumns.push(row.target_column);
|
|
206
224
|
}
|
|
225
|
+
const foreignKeys = Array.from(fkGroups.values());
|
|
207
226
|
// ----- Build relations from foreign keys -----
|
|
208
227
|
// Count FKs per (source, target) pair for disambiguation
|
|
209
228
|
const fkCounts = new Map();
|
|
@@ -215,10 +234,17 @@ async function introspect(options) {
|
|
|
215
234
|
for (const fk of foreignKeys) {
|
|
216
235
|
const pairKey = `${fk.sourceTable}→${fk.targetTable}`;
|
|
217
236
|
const needsDisambiguation = (fkCounts.get(pairKey) ?? 0) > 1;
|
|
237
|
+
// For single-column FKs, keep string form for backwards compatibility.
|
|
238
|
+
// For multi-column (composite) FKs, use array form.
|
|
239
|
+
const foreignKey = fk.sourceColumns.length === 1 ? fk.sourceColumns[0] : fk.sourceColumns;
|
|
240
|
+
const referenceKey = fk.targetColumns.length === 1 ? fk.targetColumns[0] : fk.targetColumns;
|
|
218
241
|
// --- belongsTo on the source (child) table ---
|
|
219
242
|
// e.g. posts.user_id → users.id creates posts.user (belongsTo)
|
|
243
|
+
// For composite FKs with disambiguation, use the constraint name
|
|
220
244
|
const belongsToName = needsDisambiguation
|
|
221
|
-
?
|
|
245
|
+
? fk.sourceColumns.length === 1
|
|
246
|
+
? (0, schema_js_1.snakeToCamel)(fk.sourceColumns[0].replace(/_id$/, ''))
|
|
247
|
+
: (0, schema_js_1.snakeToCamel)(fk.constraintName.replace(/^fk_/, '').replace(/_fkey$/, ''))
|
|
222
248
|
: (0, schema_js_1.singularize)((0, schema_js_1.snakeToCamel)(fk.targetTable));
|
|
223
249
|
if (!relationsByTable.has(fk.sourceTable))
|
|
224
250
|
relationsByTable.set(fk.sourceTable, {});
|
|
@@ -227,13 +253,15 @@ async function introspect(options) {
|
|
|
227
253
|
name: belongsToName,
|
|
228
254
|
from: fk.sourceTable,
|
|
229
255
|
to: fk.targetTable,
|
|
230
|
-
foreignKey
|
|
231
|
-
referenceKey
|
|
256
|
+
foreignKey,
|
|
257
|
+
referenceKey,
|
|
232
258
|
};
|
|
233
259
|
// --- hasMany on the target (parent) table ---
|
|
234
260
|
// e.g. posts.user_id → users.id creates users.posts (hasMany)
|
|
235
261
|
const hasManyName = needsDisambiguation
|
|
236
|
-
?
|
|
262
|
+
? fk.sourceColumns.length === 1
|
|
263
|
+
? (0, schema_js_1.snakeToCamel)(`${fk.sourceTable}_by_${fk.sourceColumns[0].replace(/_id$/, '')}`)
|
|
264
|
+
: (0, schema_js_1.snakeToCamel)(`${fk.sourceTable}_by_${fk.constraintName.replace(/^fk_/, '').replace(/_fkey$/, '')}`)
|
|
237
265
|
: (0, schema_js_1.snakeToCamel)(fk.sourceTable);
|
|
238
266
|
if (!relationsByTable.has(fk.targetTable))
|
|
239
267
|
relationsByTable.set(fk.targetTable, {});
|
|
@@ -242,8 +270,8 @@ async function introspect(options) {
|
|
|
242
270
|
name: hasManyName,
|
|
243
271
|
from: fk.targetTable,
|
|
244
272
|
to: fk.sourceTable,
|
|
245
|
-
foreignKey
|
|
246
|
-
referenceKey
|
|
273
|
+
foreignKey,
|
|
274
|
+
referenceKey,
|
|
247
275
|
};
|
|
248
276
|
}
|
|
249
277
|
// ----- Assemble TableMetadata for each table -----
|
|
@@ -99,6 +99,7 @@ function findArrayUniqueKey(value) {
|
|
|
99
99
|
}
|
|
100
100
|
return null;
|
|
101
101
|
}
|
|
102
|
+
// biome-ignore lint/complexity/noBannedTypes: {} means "no relations known" — intentional for untyped table access
|
|
102
103
|
class QueryInterface {
|
|
103
104
|
pool;
|
|
104
105
|
table;
|
|
@@ -261,6 +262,7 @@ class QueryInterface {
|
|
|
261
262
|
// -------------------------------------------------------------------------
|
|
262
263
|
// findUnique
|
|
263
264
|
// -------------------------------------------------------------------------
|
|
265
|
+
// biome-ignore lint/complexity/noBannedTypes: {} means "no with clause" — matches TypedWithClause default
|
|
264
266
|
async findUnique(args) {
|
|
265
267
|
return this.executeWithMiddleware('findUnique', args, async () => {
|
|
266
268
|
const deferred = this.buildFindUnique(args);
|
|
@@ -268,6 +270,7 @@ class QueryInterface {
|
|
|
268
270
|
return deferred.transform(result);
|
|
269
271
|
});
|
|
270
272
|
}
|
|
273
|
+
// biome-ignore lint/complexity/noBannedTypes: {} means "no with clause" — matches TypedWithClause default
|
|
271
274
|
buildFindUnique(args) {
|
|
272
275
|
const columnsList = this.resolveColumns(args.select, args.omit);
|
|
273
276
|
const whereObj = args.where;
|
|
@@ -363,6 +366,7 @@ class QueryInterface {
|
|
|
363
366
|
// -------------------------------------------------------------------------
|
|
364
367
|
// findMany
|
|
365
368
|
// -------------------------------------------------------------------------
|
|
369
|
+
// biome-ignore lint/complexity/noBannedTypes: {} means "no with clause" — matches TypedWithClause default
|
|
366
370
|
async findMany(args) {
|
|
367
371
|
this.maybeWarnUnlimited(args);
|
|
368
372
|
// Dev-only: warn on deeply nested with clauses
|
|
@@ -420,6 +424,7 @@ class QueryInterface {
|
|
|
420
424
|
}
|
|
421
425
|
return maxDepth;
|
|
422
426
|
}
|
|
427
|
+
// biome-ignore lint/complexity/noBannedTypes: {} means "no with clause" — matches TypedWithClause default
|
|
423
428
|
buildFindMany(args) {
|
|
424
429
|
const columnsList = this.resolveColumns(args?.select, args?.omit);
|
|
425
430
|
const colKey = columnsList ? columnsList.join(',') : '*';
|
|
@@ -563,6 +568,7 @@ class QueryInterface {
|
|
|
563
568
|
* }
|
|
564
569
|
* ```
|
|
565
570
|
*/
|
|
571
|
+
// biome-ignore lint/complexity/noBannedTypes: {} means "no with clause" — matches TypedWithClause default
|
|
566
572
|
async *findManyStream(args) {
|
|
567
573
|
const batchSize = Math.max(1, Math.floor(Number(args?.batchSize ?? 1000)));
|
|
568
574
|
const hasRelations = !!args?.with;
|
|
@@ -619,6 +625,7 @@ class QueryInterface {
|
|
|
619
625
|
// -------------------------------------------------------------------------
|
|
620
626
|
// findFirst — like findMany but returns a single row or null
|
|
621
627
|
// -------------------------------------------------------------------------
|
|
628
|
+
// biome-ignore lint/complexity/noBannedTypes: {} means "no with clause" — matches TypedWithClause default
|
|
622
629
|
async findFirst(args) {
|
|
623
630
|
return this.executeWithMiddleware('findFirst', (args ?? {}), async () => {
|
|
624
631
|
const deferred = this.buildFindFirst(args);
|
|
@@ -626,6 +633,7 @@ class QueryInterface {
|
|
|
626
633
|
return deferred.transform(result);
|
|
627
634
|
});
|
|
628
635
|
}
|
|
636
|
+
// biome-ignore lint/complexity/noBannedTypes: {} means "no with clause" — matches TypedWithClause default
|
|
629
637
|
buildFindFirst(args) {
|
|
630
638
|
// Reuse findMany's SQL builder but force LIMIT 1
|
|
631
639
|
const findManyArgs = { ...args, limit: 1 };
|
|
@@ -643,6 +651,7 @@ class QueryInterface {
|
|
|
643
651
|
// -------------------------------------------------------------------------
|
|
644
652
|
// findFirstOrThrow — like findFirst but throws if no record found
|
|
645
653
|
// -------------------------------------------------------------------------
|
|
654
|
+
// biome-ignore lint/complexity/noBannedTypes: {} means "no with clause" — matches TypedWithClause default
|
|
646
655
|
async findFirstOrThrow(args) {
|
|
647
656
|
return this.executeWithMiddleware('findFirstOrThrow', (args ?? {}), async () => {
|
|
648
657
|
const deferred = this.buildFindFirstOrThrow(args);
|
|
@@ -650,6 +659,7 @@ class QueryInterface {
|
|
|
650
659
|
return deferred.transform(result);
|
|
651
660
|
});
|
|
652
661
|
}
|
|
662
|
+
// biome-ignore lint/complexity/noBannedTypes: {} means "no with clause" — matches TypedWithClause default
|
|
653
663
|
buildFindFirstOrThrow(args) {
|
|
654
664
|
const inner = this.buildFindFirst(args);
|
|
655
665
|
return {
|
|
@@ -672,6 +682,7 @@ class QueryInterface {
|
|
|
672
682
|
// -------------------------------------------------------------------------
|
|
673
683
|
// findUniqueOrThrow — like findUnique but throws if no record found
|
|
674
684
|
// -------------------------------------------------------------------------
|
|
685
|
+
// biome-ignore lint/complexity/noBannedTypes: {} means "no with clause" — matches TypedWithClause default
|
|
675
686
|
async findUniqueOrThrow(args) {
|
|
676
687
|
return this.executeWithMiddleware('findUniqueOrThrow', args, async () => {
|
|
677
688
|
const deferred = this.buildFindUniqueOrThrow(args);
|
|
@@ -679,6 +690,7 @@ class QueryInterface {
|
|
|
679
690
|
return deferred.transform(result);
|
|
680
691
|
});
|
|
681
692
|
}
|
|
693
|
+
// biome-ignore lint/complexity/noBannedTypes: {} means "no with clause" — matches TypedWithClause default
|
|
682
694
|
buildFindUniqueOrThrow(args) {
|
|
683
695
|
const inner = this.buildFindUnique(args);
|
|
684
696
|
return {
|
|
@@ -2016,15 +2028,15 @@ class QueryInterface {
|
|
|
2016
2028
|
const qt = (0, utils_js_1.quoteIdent)(targetTable);
|
|
2017
2029
|
const qSelf = (0, utils_js_1.quoteIdent)(this.table);
|
|
2018
2030
|
const clauses = [];
|
|
2019
|
-
// Correlation: link child table to parent table
|
|
2031
|
+
// Correlation: link child table to parent table (supports composite FKs)
|
|
2020
2032
|
let correlation;
|
|
2021
2033
|
if (relDef.type === 'hasMany' || relDef.type === 'hasOne') {
|
|
2022
2034
|
// parent.pk = child.fk
|
|
2023
|
-
correlation =
|
|
2035
|
+
correlation = (0, utils_js_1.buildCorrelation)(qt, relDef.foreignKey, qSelf, relDef.referenceKey);
|
|
2024
2036
|
}
|
|
2025
2037
|
else {
|
|
2026
2038
|
// belongsTo: parent.fk = child.pk
|
|
2027
|
-
correlation =
|
|
2039
|
+
correlation = (0, utils_js_1.buildCorrelation)(qt, relDef.referenceKey, qSelf, relDef.foreignKey);
|
|
2028
2040
|
}
|
|
2029
2041
|
// "some": EXISTS (SELECT 1 FROM target WHERE correlation AND filter)
|
|
2030
2042
|
if (filterObj.some !== undefined) {
|
|
@@ -2067,6 +2079,10 @@ class QueryInterface {
|
|
|
2067
2079
|
if (value === undefined)
|
|
2068
2080
|
continue;
|
|
2069
2081
|
const col = meta.columnMap[field] ?? (0, schema_js_1.camelToSnake)(field);
|
|
2082
|
+
if (!meta.allColumns.includes(col)) {
|
|
2083
|
+
throw new errors_js_1.ValidationError(`[turbine] Unknown field "${field}" in relation filter for table "${targetTable}". ` +
|
|
2084
|
+
`Known fields: ${Object.keys(meta.columnMap).join(', ') || '(none)'}.`);
|
|
2085
|
+
}
|
|
2070
2086
|
const qCol = `${qt}.${(0, utils_js_1.quoteIdent)(col)}`;
|
|
2071
2087
|
if (value === null) {
|
|
2072
2088
|
conditions.push(`${qCol} IS NULL`);
|
|
@@ -2474,12 +2490,13 @@ class QueryInterface {
|
|
|
2474
2490
|
// Build WHERE — correlate to parent via parentRef (alias or table name).
|
|
2475
2491
|
// For hasMany: target has FK, so alias.fk = parentRef.pk
|
|
2476
2492
|
// For belongsTo: source has FK, so alias.pk = parentRef.fk (reversed)
|
|
2493
|
+
// Supports composite foreign keys (string[]) via buildCorrelation.
|
|
2477
2494
|
let whereClause;
|
|
2478
2495
|
if (relDef.type === 'belongsTo' || relDef.type === 'hasOne') {
|
|
2479
|
-
whereClause =
|
|
2496
|
+
whereClause = (0, utils_js_1.buildCorrelation)(alias, relDef.referenceKey, qParent, relDef.foreignKey);
|
|
2480
2497
|
}
|
|
2481
2498
|
else {
|
|
2482
|
-
whereClause =
|
|
2499
|
+
whereClause = (0, utils_js_1.buildCorrelation)(alias, relDef.foreignKey, qParent, relDef.referenceKey);
|
|
2483
2500
|
}
|
|
2484
2501
|
// Additional filters — properly parameterized
|
|
2485
2502
|
if (spec !== true && spec.where) {
|
package/dist/cjs/query/index.js
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* former monolithic `import { … } from './query.js'`.
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.QueryInterface = exports.sqlToPreparedName = exports.quoteIdent = exports.OPERATOR_KEYS = exports.LRUCache = exports.fnv1a64Hex = exports.escSingleQuote = exports.escapeLike = void 0;
|
|
10
|
+
exports.QueryInterface = exports.sqlToPreparedName = exports.quoteIdent = exports.OPERATOR_KEYS = exports.LRUCache = exports.fnv1a64Hex = exports.escSingleQuote = exports.escapeLike = exports.buildCorrelation = void 0;
|
|
11
11
|
var utils_js_1 = require("./utils.js");
|
|
12
|
+
Object.defineProperty(exports, "buildCorrelation", { enumerable: true, get: function () { return utils_js_1.buildCorrelation; } });
|
|
12
13
|
Object.defineProperty(exports, "escapeLike", { enumerable: true, get: function () { return utils_js_1.escapeLike; } });
|
|
13
14
|
Object.defineProperty(exports, "escSingleQuote", { enumerable: true, get: function () { return utils_js_1.escSingleQuote; } });
|
|
14
15
|
Object.defineProperty(exports, "fnv1a64Hex", { enumerable: true, get: function () { return utils_js_1.fnv1a64Hex; } });
|
package/dist/cjs/query/utils.js
CHANGED
|
@@ -11,6 +11,7 @@ exports.escSingleQuote = escSingleQuote;
|
|
|
11
11
|
exports.escapeLike = escapeLike;
|
|
12
12
|
exports.fnv1a64Hex = fnv1a64Hex;
|
|
13
13
|
exports.sqlToPreparedName = sqlToPreparedName;
|
|
14
|
+
exports.buildCorrelation = buildCorrelation;
|
|
14
15
|
// ---------------------------------------------------------------------------
|
|
15
16
|
// Identifier quoting — prevents SQL injection via table/column names
|
|
16
17
|
// ---------------------------------------------------------------------------
|
|
@@ -120,3 +121,20 @@ exports.OPERATOR_KEYS = new Set([
|
|
|
120
121
|
'endsWith',
|
|
121
122
|
'mode',
|
|
122
123
|
]);
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Composite key correlation helper
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
/**
|
|
128
|
+
* Build a correlation clause joining columns between two table references.
|
|
129
|
+
* Handles both single-column (string) and multi-column (string[]) foreign keys.
|
|
130
|
+
*
|
|
131
|
+
* For single-column: `"alias"."col" = "parent"."col"`
|
|
132
|
+
* For multi-column: `"alias"."col_a" = "parent"."ref_a" AND "alias"."col_b" = "parent"."ref_b"`
|
|
133
|
+
*/
|
|
134
|
+
function buildCorrelation(leftRef, leftColumns, rightRef, rightColumns) {
|
|
135
|
+
const leftCols = Array.isArray(leftColumns) ? leftColumns : [leftColumns];
|
|
136
|
+
const rightCols = Array.isArray(rightColumns) ? rightColumns : [rightColumns];
|
|
137
|
+
return leftCols
|
|
138
|
+
.map((col, i) => `${leftRef}.${quoteIdent(col)} = ${rightRef}.${quoteIdent(rightCols[i])}`)
|
|
139
|
+
.join(' AND ');
|
|
140
|
+
}
|
package/dist/cjs/schema.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* They're used by the query builder, code generator, and CLI.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.normalizeKeyColumns = normalizeKeyColumns;
|
|
9
10
|
exports.pgTypeToTs = pgTypeToTs;
|
|
10
11
|
exports.isDateType = isDateType;
|
|
11
12
|
exports.pgArrayType = pgArrayType;
|
|
@@ -14,6 +15,13 @@ exports.camelToSnake = camelToSnake;
|
|
|
14
15
|
exports.snakeToPascal = snakeToPascal;
|
|
15
16
|
exports.singularize = singularize;
|
|
16
17
|
// ---------------------------------------------------------------------------
|
|
18
|
+
// Helpers for composite key handling
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
/** Normalize foreignKey/referenceKey to always be an array for uniform processing */
|
|
21
|
+
function normalizeKeyColumns(key) {
|
|
22
|
+
return Array.isArray(key) ? key : [key];
|
|
23
|
+
}
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
17
25
|
// Type mapping: Postgres → TypeScript
|
|
18
26
|
// ---------------------------------------------------------------------------
|
|
19
27
|
const PG_TO_TS = {
|
package/dist/cli/config.d.ts
CHANGED
|
@@ -21,6 +21,17 @@ export interface TurbineCliConfig {
|
|
|
21
21
|
seedFile?: string;
|
|
22
22
|
/** Schema builder file path (for push command) */
|
|
23
23
|
schemaFile?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Database adapter for PostgreSQL-compatible databases that need
|
|
26
|
+
* dialect-specific behavior (e.g. CockroachDB, YugabyteDB).
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* import { cockroachdb } from 'turbine-orm/adapters';
|
|
31
|
+
* export default { url: process.env.DATABASE_URL, adapter: cockroachdb };
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
adapter?: import('../adapters/index.js').DatabaseAdapter;
|
|
24
35
|
}
|
|
25
36
|
/**
|
|
26
37
|
* Attempt to load a turbine config file from the current directory.
|
package/dist/cli/index.js
CHANGED
|
@@ -888,7 +888,8 @@ async function cmdStatus(_args, config) {
|
|
|
888
888
|
const isLast = i === rels.length - 1;
|
|
889
889
|
const prefix = isLast ? symbols.teeEnd : symbols.tee;
|
|
890
890
|
const relColor = rel.type === 'hasMany' ? blue : yellow;
|
|
891
|
-
|
|
891
|
+
const fkDisplay = Array.isArray(rel.foreignKey) ? rel.foreignKey.join(', ') : rel.foreignKey;
|
|
892
|
+
console.log(` ${dim(prefix)} ${relColor(relName)} ${dim(symbols.arrow)} ${rel.to} ${dim(`(${rel.type}, FK: ${fkDisplay})`)}`);
|
|
892
893
|
}
|
|
893
894
|
}
|
|
894
895
|
newline();
|
package/dist/cli/migrate.d.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* -- DOWN
|
|
12
12
|
* DROP TABLE users;
|
|
13
13
|
*/
|
|
14
|
+
import type { DatabaseAdapter } from '../adapters/index.js';
|
|
14
15
|
export interface MigrationFile {
|
|
15
16
|
/** Full filename (e.g. "20260325120000_create_users.sql") */
|
|
16
17
|
filename: string;
|
|
@@ -113,6 +114,7 @@ export declare function migrateUp(connectionString: string, migrationsDir: strin
|
|
|
113
114
|
step?: number;
|
|
114
115
|
allowDrift?: boolean /** @deprecated use allowDrift */;
|
|
115
116
|
force?: boolean;
|
|
117
|
+
adapter?: DatabaseAdapter;
|
|
116
118
|
}): Promise<{
|
|
117
119
|
applied: MigrationFile[];
|
|
118
120
|
errors: Array<{
|
|
@@ -130,6 +132,7 @@ export declare function migrateUp(connectionString: string, migrationsDir: strin
|
|
|
130
132
|
*/
|
|
131
133
|
export declare function migrateDown(connectionString: string, migrationsDir: string, options?: {
|
|
132
134
|
step?: number;
|
|
135
|
+
adapter?: DatabaseAdapter;
|
|
133
136
|
}): Promise<{
|
|
134
137
|
rolledBack: MigrationFile[];
|
|
135
138
|
errors: Array<{
|
package/dist/cli/migrate.js
CHANGED
|
@@ -15,6 +15,7 @@ import { createHash } from 'node:crypto';
|
|
|
15
15
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
16
16
|
import { join } from 'node:path';
|
|
17
17
|
import pg from 'pg';
|
|
18
|
+
import { postgresql } from '../adapters/index.js';
|
|
18
19
|
import { MigrationError } from '../errors.js';
|
|
19
20
|
import { quoteIdent } from '../query/index.js';
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
@@ -234,12 +235,14 @@ async function getCurrentDatabaseName(client) {
|
|
|
234
235
|
const result = await client.query(`SELECT current_database()`);
|
|
235
236
|
return result.rows[0]?.current_database ?? '';
|
|
236
237
|
}
|
|
237
|
-
async function acquireLock(client, lockId) {
|
|
238
|
-
const
|
|
239
|
-
|
|
238
|
+
async function acquireLock(client, lockId, adapter) {
|
|
239
|
+
const a = adapter ?? postgresql;
|
|
240
|
+
// pg.Client satisfies PgCompatPoolClient (query + release)
|
|
241
|
+
return a.acquireLock(client, lockId);
|
|
240
242
|
}
|
|
241
|
-
async function releaseLock(client, lockId) {
|
|
242
|
-
|
|
243
|
+
async function releaseLock(client, lockId, adapter) {
|
|
244
|
+
const a = adapter ?? postgresql;
|
|
245
|
+
await a.releaseLock(client, lockId);
|
|
243
246
|
}
|
|
244
247
|
/**
|
|
245
248
|
* Validate that applied migration files have not been modified or deleted since they were run.
|
|
@@ -306,8 +309,10 @@ export async function migrateUp(connectionString, migrationsDir, options) {
|
|
|
306
309
|
// sibling databases on the same Postgres cluster do not contend.
|
|
307
310
|
const dbName = await getCurrentDatabaseName(client);
|
|
308
311
|
const lockId = deriveLockId(dbName);
|
|
309
|
-
// Acquire
|
|
310
|
-
|
|
312
|
+
// Acquire lock to prevent concurrent migrations.
|
|
313
|
+
// The adapter determines the strategy (advisory lock vs table lock).
|
|
314
|
+
const adapter = options?.adapter;
|
|
315
|
+
const gotLock = await acquireLock(client, lockId, adapter);
|
|
311
316
|
if (!gotLock) {
|
|
312
317
|
throw new MigrationError('[turbine] Could not acquire migration lock — another migration is already running');
|
|
313
318
|
}
|
|
@@ -379,7 +384,7 @@ export async function migrateUp(connectionString, migrationsDir, options) {
|
|
|
379
384
|
return { applied: results, errors };
|
|
380
385
|
}
|
|
381
386
|
finally {
|
|
382
|
-
await releaseLock(client, lockId);
|
|
387
|
+
await releaseLock(client, lockId, adapter);
|
|
383
388
|
}
|
|
384
389
|
}
|
|
385
390
|
finally {
|
|
@@ -402,7 +407,8 @@ export async function migrateDown(connectionString, migrationsDir, options) {
|
|
|
402
407
|
// sibling databases on the same cluster do not contend.
|
|
403
408
|
const dbName = await getCurrentDatabaseName(client);
|
|
404
409
|
const lockId = deriveLockId(dbName);
|
|
405
|
-
const
|
|
410
|
+
const adapter = options?.adapter;
|
|
411
|
+
const gotLock = await acquireLock(client, lockId, adapter);
|
|
406
412
|
if (!gotLock) {
|
|
407
413
|
throw new MigrationError('[turbine] Could not acquire migration lock — another migration is already running');
|
|
408
414
|
}
|
|
@@ -449,7 +455,7 @@ export async function migrateDown(connectionString, migrationsDir, options) {
|
|
|
449
455
|
return { rolledBack: results, errors };
|
|
450
456
|
}
|
|
451
457
|
finally {
|
|
452
|
-
await releaseLock(client, lockId);
|
|
458
|
+
await releaseLock(client, lockId, adapter);
|
|
453
459
|
}
|
|
454
460
|
}
|
|
455
461
|
finally {
|
package/dist/cli/studio.d.ts
CHANGED
|
@@ -28,6 +28,8 @@ export interface StudioOptions {
|
|
|
28
28
|
exclude?: string[];
|
|
29
29
|
/** Directory where studio-queries.json is persisted. Defaults to `.turbine/` in cwd. */
|
|
30
30
|
stateDir?: string;
|
|
31
|
+
/** Database adapter for dialect-specific behavior (e.g. statement timeout syntax). */
|
|
32
|
+
adapter?: import('../adapters/index.js').DatabaseAdapter;
|
|
31
33
|
}
|
|
32
34
|
export interface StudioHandle {
|
|
33
35
|
/** Shut down the server + pool cleanly. */
|
|
@@ -43,6 +45,8 @@ export interface StudioContext {
|
|
|
43
45
|
options: StudioOptions;
|
|
44
46
|
authToken: string;
|
|
45
47
|
stateDir: string;
|
|
48
|
+
/** Resolved statement timeout SQL string (adapter-aware). */
|
|
49
|
+
statementTimeoutSQL: string;
|
|
46
50
|
}
|
|
47
51
|
/**
|
|
48
52
|
* Start the Studio server. Returns a handle with the session token, a pre-built
|
package/dist/cli/studio.js
CHANGED
|
@@ -59,7 +59,8 @@ export async function startStudio(options) {
|
|
|
59
59
|
});
|
|
60
60
|
const authToken = randomBytes(24).toString('hex');
|
|
61
61
|
const stateDir = pathResolve(options.stateDir ?? '.turbine');
|
|
62
|
-
const
|
|
62
|
+
const statementTimeoutSQL = options.adapter?.statementTimeout?.(30) ?? `SET LOCAL statement_timeout = '30s'`;
|
|
63
|
+
const ctx = { pool, metadata, options, authToken, stateDir, statementTimeoutSQL };
|
|
63
64
|
const server = createServer((req, res) => {
|
|
64
65
|
handleRequest(req, res, ctx).catch((err) => {
|
|
65
66
|
sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
@@ -278,7 +279,7 @@ export async function apiTableRows(res, ctx, rawTableName, params) {
|
|
|
278
279
|
const client = await ctx.pool.connect();
|
|
279
280
|
try {
|
|
280
281
|
await client.query('BEGIN READ ONLY');
|
|
281
|
-
await client.query(
|
|
282
|
+
await client.query(ctx.statementTimeoutSQL);
|
|
282
283
|
const result = await client.query(sql, mainValues);
|
|
283
284
|
const countResult = await client.query(countSql, countValues);
|
|
284
285
|
await client.query('COMMIT');
|
|
@@ -344,7 +345,7 @@ async function apiQuery(req, res, ctx) {
|
|
|
344
345
|
const client = await ctx.pool.connect();
|
|
345
346
|
try {
|
|
346
347
|
await client.query('BEGIN READ ONLY');
|
|
347
|
-
await client.query(
|
|
348
|
+
await client.query(ctx.statementTimeoutSQL);
|
|
348
349
|
const started = Date.now();
|
|
349
350
|
const result = await client.query(rawSql);
|
|
350
351
|
const elapsedMs = Date.now() - started;
|
|
@@ -396,7 +397,7 @@ export async function apiBuilder(req, res, ctx) {
|
|
|
396
397
|
const client = await ctx.pool.connect();
|
|
397
398
|
try {
|
|
398
399
|
await client.query('BEGIN READ ONLY');
|
|
399
|
-
await client.query(
|
|
400
|
+
await client.query(ctx.statementTimeoutSQL);
|
|
400
401
|
const started = Date.now();
|
|
401
402
|
const result = await client.query(deferred.sql, deferred.params);
|
|
402
403
|
const elapsedMs = Date.now() - started;
|
package/dist/generate.js
CHANGED
|
@@ -229,7 +229,14 @@ function generateMetadata(schema) {
|
|
|
229
229
|
// relations
|
|
230
230
|
lines.push(' relations: {');
|
|
231
231
|
for (const [relName, rel] of Object.entries(table.relations)) {
|
|
232
|
-
|
|
232
|
+
// Emit foreignKey/referenceKey as string for single-column, array for composite
|
|
233
|
+
const fkLiteral = Array.isArray(rel.foreignKey)
|
|
234
|
+
? `[${rel.foreignKey.map((c) => `'${escSQ(c)}'`).join(', ')}]`
|
|
235
|
+
: `'${escSQ(rel.foreignKey)}'`;
|
|
236
|
+
const refLiteral = Array.isArray(rel.referenceKey)
|
|
237
|
+
? `[${rel.referenceKey.map((c) => `'${escSQ(c)}'`).join(', ')}]`
|
|
238
|
+
: `'${escSQ(rel.referenceKey)}'`;
|
|
239
|
+
lines.push(` ${relName}: { type: '${escSQ(rel.type)}', name: '${escSQ(rel.name)}', from: '${escSQ(rel.from)}', to: '${escSQ(rel.to)}', foreignKey: ${fkLiteral}, referenceKey: ${refLiteral} },`);
|
|
233
240
|
}
|
|
234
241
|
lines.push(' },');
|
|
235
242
|
// indexes
|
package/dist/index.d.ts
CHANGED
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
* await db.disconnect();
|
|
33
33
|
* ```
|
|
34
34
|
*/
|
|
35
|
+
export type { DatabaseAdapter, IntrospectionOverrides } from './adapters/index.js';
|
|
36
|
+
export { alloydb, cockroachdb, postgresql, timescale, yugabytedb } from './adapters/index.js';
|
|
35
37
|
export { type Middleware, type MiddlewareNext, type MiddlewareParams, type PgCompatPool, type PgCompatPoolClient, type PgCompatQueryResult, TransactionClient, type TransactionOptions, TurbineClient, type TurbineConfig, } from './client.js';
|
|
36
38
|
export { CheckConstraintError, CircularRelationError, ConnectionError, DeadlockError, type ErrorMessageMode, ForeignKeyError, getErrorMessageMode, MigrationError, NotFoundError, NotNullViolationError, PipelineError, type PipelineResultSlot, RelationError, SerializationFailureError, setErrorMessageMode, TimeoutError, TurbineError, TurbineErrorCode, UniqueConstraintError, ValidationError, wrapPgError, } from './errors.js';
|
|
37
39
|
export { type GenerateOptions, generate } from './generate.js';
|
|
@@ -39,7 +41,7 @@ export { type IntrospectOptions, introspect } from './introspect.js';
|
|
|
39
41
|
export { executePipeline, type PipelineOptions, type PipelineResults, pipelineSupported } from './pipeline.js';
|
|
40
42
|
export { type AggregateArgs, type AggregateResult, type ArrayFilter, type CountArgs, type CreateArgs, type CreateManyArgs, type DeferredQuery, type DeleteArgs, type DeleteManyArgs, type FindManyArgs, type FindManyStreamArgs, type FindUniqueArgs, type GroupByArgs, type JsonFilter, type OrderDirection, QueryInterface, type RelationDescriptor, type RelationFilter, type TypedWithClause, type UpdateArgs, type UpdateInput, type UpdateManyArgs, type UpdateOperatorInput, type UpsertArgs, type WithClause, type WithOptions, type WithResult, } from './query/index.js';
|
|
41
43
|
export type { ColumnMetadata, IndexMetadata, RelationDef, SchemaMetadata, TableMetadata, } from './schema.js';
|
|
42
|
-
export { camelToSnake, isDateType, pgArrayType, pgTypeToTs, singularize, snakeToCamel, snakeToPascal, } from './schema.js';
|
|
44
|
+
export { camelToSnake, isDateType, normalizeKeyColumns, pgArrayType, pgTypeToTs, singularize, snakeToCamel, snakeToPascal, } from './schema.js';
|
|
43
45
|
export { ColumnBuilder, type ColumnConfig, type ColumnDef, type ColumnType, type ColumnTypeName, column, defineSchema, type SchemaDef, type TableDef, table, } from './schema-builder.js';
|
|
44
46
|
export { type AlterColumnDef, type AlterDef, type DiffResult, type PushResult, schemaDiff, schemaPush, schemaToSQL, schemaToSQLString, } from './schema-sql.js';
|
|
45
47
|
export { type TurbineHttpOptions, turbineHttp } from './serverless.js';
|