turbine-orm 0.13.3 → 0.15.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/dist/adapters/cockroachdb.js +1 -1
- package/dist/adapters/index.d.ts +7 -4
- package/dist/adapters/index.js +1 -1
- package/dist/adapters/yugabytedb.js +1 -1
- package/dist/cjs/adapters/cockroachdb.js +1 -1
- package/dist/cjs/adapters/index.js +1 -1
- package/dist/cjs/adapters/yugabytedb.js +1 -1
- package/dist/cjs/cli/studio.js +45 -7
- package/dist/cjs/client.js +48 -1
- package/dist/cjs/dialect.js +6 -4
- package/dist/cjs/errors.js +44 -1
- package/dist/cjs/generate.js +95 -1
- package/dist/cjs/index.js +10 -1
- package/dist/cjs/introspect.js +14 -4
- package/dist/cjs/nested-write.js +467 -0
- package/dist/cjs/query/builder.js +212 -16
- package/dist/cli/studio.d.ts +10 -2
- package/dist/cli/studio.js +45 -7
- package/dist/client.d.ts +23 -0
- package/dist/client.js +47 -1
- package/dist/dialect.d.ts +3 -3
- package/dist/dialect.js +6 -4
- package/dist/errors.d.ts +23 -0
- package/dist/errors.js +41 -0
- package/dist/generate.js +95 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +4 -2
- package/dist/introspect.js +15 -5
- package/dist/nested-write.d.ts +95 -0
- package/dist/nested-write.js +461 -0
- package/dist/query/builder.d.ts +28 -12
- package/dist/query/builder.js +180 -17
- package/dist/query/index.d.ts +1 -1
- package/dist/query/types.d.ts +76 -8
- package/dist/schema.d.ts +9 -3
- package/package.json +2 -2
package/dist/errors.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export declare const TurbineErrorCode: {
|
|
|
20
20
|
readonly DEADLOCK_DETECTED: "TURBINE_E012";
|
|
21
21
|
readonly SERIALIZATION_FAILURE: "TURBINE_E013";
|
|
22
22
|
readonly PIPELINE: "TURBINE_E014";
|
|
23
|
+
readonly OPTIMISTIC_LOCK: "TURBINE_E015";
|
|
24
|
+
readonly EXCLUSION_VIOLATION: "TURBINE_E016";
|
|
23
25
|
};
|
|
24
26
|
export type TurbineErrorCode = (typeof TurbineErrorCode)[keyof typeof TurbineErrorCode];
|
|
25
27
|
/** Base error class for all Turbine errors */
|
|
@@ -208,6 +210,16 @@ export declare class CheckConstraintError extends TurbineError {
|
|
|
208
210
|
cause?: unknown;
|
|
209
211
|
});
|
|
210
212
|
}
|
|
213
|
+
export declare class ExclusionConstraintError extends TurbineError {
|
|
214
|
+
readonly constraint?: string;
|
|
215
|
+
readonly table?: string;
|
|
216
|
+
constructor(opts?: {
|
|
217
|
+
constraint?: string;
|
|
218
|
+
table?: string;
|
|
219
|
+
message?: string;
|
|
220
|
+
cause?: unknown;
|
|
221
|
+
});
|
|
222
|
+
}
|
|
211
223
|
/** Result slot for a single query in a non-transactional pipeline */
|
|
212
224
|
export type PipelineResultSlot = {
|
|
213
225
|
status: 'ok';
|
|
@@ -251,6 +263,16 @@ export declare class PipelineError extends TurbineError {
|
|
|
251
263
|
cause?: unknown;
|
|
252
264
|
});
|
|
253
265
|
}
|
|
266
|
+
export declare class OptimisticLockError extends TurbineError {
|
|
267
|
+
readonly table: string;
|
|
268
|
+
readonly versionField: string;
|
|
269
|
+
readonly expectedVersion: unknown;
|
|
270
|
+
constructor(opts: {
|
|
271
|
+
table: string;
|
|
272
|
+
versionField: string;
|
|
273
|
+
expectedVersion: unknown;
|
|
274
|
+
});
|
|
275
|
+
}
|
|
254
276
|
/**
|
|
255
277
|
* Translate a pg driver error into a typed Turbine error.
|
|
256
278
|
* If the error doesn't match a known constraint code, returns it unchanged.
|
|
@@ -260,6 +282,7 @@ export declare class PipelineError extends TurbineError {
|
|
|
260
282
|
* 23503 (foreign_key_violation) -> ForeignKeyError
|
|
261
283
|
* 23502 (not_null_violation) -> NotNullViolationError
|
|
262
284
|
* 23514 (check_violation) -> CheckConstraintError
|
|
285
|
+
* 23P01 (exclusion_violation) -> ExclusionConstraintError
|
|
263
286
|
* 40P01 (deadlock_detected) -> DeadlockError (retryable)
|
|
264
287
|
* 40001 (serialization_failure) -> SerializationFailureError (retryable)
|
|
265
288
|
*
|
package/dist/errors.js
CHANGED
|
@@ -20,6 +20,8 @@ export const TurbineErrorCode = {
|
|
|
20
20
|
DEADLOCK_DETECTED: 'TURBINE_E012',
|
|
21
21
|
SERIALIZATION_FAILURE: 'TURBINE_E013',
|
|
22
22
|
PIPELINE: 'TURBINE_E014',
|
|
23
|
+
OPTIMISTIC_LOCK: 'TURBINE_E015',
|
|
24
|
+
EXCLUSION_VIOLATION: 'TURBINE_E016',
|
|
23
25
|
};
|
|
24
26
|
/** Base error class for all Turbine errors */
|
|
25
27
|
export class TurbineError extends Error {
|
|
@@ -331,6 +333,25 @@ export class CheckConstraintError extends TurbineError {
|
|
|
331
333
|
this.table = table;
|
|
332
334
|
}
|
|
333
335
|
}
|
|
336
|
+
export class ExclusionConstraintError extends TurbineError {
|
|
337
|
+
constraint;
|
|
338
|
+
table;
|
|
339
|
+
constructor(opts = {}) {
|
|
340
|
+
const { constraint, table, cause } = opts;
|
|
341
|
+
let message = opts.message;
|
|
342
|
+
if (!message) {
|
|
343
|
+
const constraintPart = constraint ? ` on ${constraint}` : '';
|
|
344
|
+
message = `[turbine] Exclusion constraint violation${constraintPart}`;
|
|
345
|
+
const detail = detailFromCause(cause);
|
|
346
|
+
if (detail)
|
|
347
|
+
message += `: ${detail}`;
|
|
348
|
+
}
|
|
349
|
+
super(TurbineErrorCode.EXCLUSION_VIOLATION, message, { cause });
|
|
350
|
+
this.name = 'ExclusionConstraintError';
|
|
351
|
+
this.constraint = constraint;
|
|
352
|
+
this.table = table;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
334
355
|
/**
|
|
335
356
|
* Thrown when a non-transactional pipeline has partial failures.
|
|
336
357
|
*
|
|
@@ -371,6 +392,19 @@ export class PipelineError extends TurbineError {
|
|
|
371
392
|
this.failedTag = failedTag;
|
|
372
393
|
}
|
|
373
394
|
}
|
|
395
|
+
export class OptimisticLockError extends TurbineError {
|
|
396
|
+
table;
|
|
397
|
+
versionField;
|
|
398
|
+
expectedVersion;
|
|
399
|
+
constructor(opts) {
|
|
400
|
+
super(TurbineErrorCode.OPTIMISTIC_LOCK, `[turbine] Optimistic lock failed on "${opts.table}" — ` +
|
|
401
|
+
`expected ${opts.versionField} = ${opts.expectedVersion} but row was modified by another transaction`);
|
|
402
|
+
this.name = 'OptimisticLockError';
|
|
403
|
+
this.table = opts.table;
|
|
404
|
+
this.versionField = opts.versionField;
|
|
405
|
+
this.expectedVersion = opts.expectedVersion;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
374
408
|
/**
|
|
375
409
|
* Parse column names out of a pg `detail` string like:
|
|
376
410
|
* "Key (email)=(foo@bar) already exists."
|
|
@@ -391,6 +425,7 @@ function parseColumnsFromDetail(detail) {
|
|
|
391
425
|
* 23503 (foreign_key_violation) -> ForeignKeyError
|
|
392
426
|
* 23502 (not_null_violation) -> NotNullViolationError
|
|
393
427
|
* 23514 (check_violation) -> CheckConstraintError
|
|
428
|
+
* 23P01 (exclusion_violation) -> ExclusionConstraintError
|
|
394
429
|
* 40P01 (deadlock_detected) -> DeadlockError (retryable)
|
|
395
430
|
* 40001 (serialization_failure) -> SerializationFailureError (retryable)
|
|
396
431
|
*
|
|
@@ -430,6 +465,12 @@ export function wrapPgError(err) {
|
|
|
430
465
|
table: e.table,
|
|
431
466
|
cause: err,
|
|
432
467
|
});
|
|
468
|
+
case '23P01':
|
|
469
|
+
return new ExclusionConstraintError({
|
|
470
|
+
constraint: e.constraint,
|
|
471
|
+
table: e.table,
|
|
472
|
+
cause: err,
|
|
473
|
+
});
|
|
433
474
|
case '40P01':
|
|
434
475
|
return new DeadlockError({
|
|
435
476
|
constraint: e.constraint,
|
package/dist/generate.js
CHANGED
|
@@ -177,6 +177,92 @@ export function generateTypes(schema) {
|
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// Nested write types (WhereUnique, NestedCreateInput, NestedUpdateInput,
|
|
182
|
+
// ConnectOrCreate, CreateInput, UpdateInput)
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
for (const table of Object.values(schema.tables)) {
|
|
185
|
+
const typeName = entityName(table.name);
|
|
186
|
+
const hasRels = Object.keys(table.relations).length > 0;
|
|
187
|
+
// WhereUnique — union of unique constraint shapes, deduplicating PK
|
|
188
|
+
const seen = new Set();
|
|
189
|
+
const uniqueSets = [];
|
|
190
|
+
// Always include the primary key first
|
|
191
|
+
const pkKey = table.primaryKey.join(',');
|
|
192
|
+
seen.add(pkKey);
|
|
193
|
+
uniqueSets.push(table.primaryKey);
|
|
194
|
+
// Add unique indexes that aren't duplicates of the PK
|
|
195
|
+
for (const uc of table.uniqueColumns) {
|
|
196
|
+
const ucKey = uc.join(',');
|
|
197
|
+
if (!seen.has(ucKey)) {
|
|
198
|
+
seen.add(ucKey);
|
|
199
|
+
uniqueSets.push(uc);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (uniqueSets.length > 0) {
|
|
203
|
+
const branches = uniqueSets.map((cols) => {
|
|
204
|
+
const fields = cols.map((colName) => {
|
|
205
|
+
const col = table.columns.find((c) => c.name === colName);
|
|
206
|
+
const field = col?.field ?? colName;
|
|
207
|
+
const tsType = col?.tsType ?? 'unknown';
|
|
208
|
+
return `${field}: ${tsType}`;
|
|
209
|
+
});
|
|
210
|
+
return `{ ${fields.join('; ')} }`;
|
|
211
|
+
});
|
|
212
|
+
lines.push(`export type ${typeName}WhereUnique = ${branches.join(' | ')};`);
|
|
213
|
+
lines.push('');
|
|
214
|
+
}
|
|
215
|
+
// CreateInput / UpdateInput — extends base type with optional relation fields
|
|
216
|
+
if (hasRels) {
|
|
217
|
+
lines.push(`export type ${typeName}CreateInput = ${typeName}Create & {`);
|
|
218
|
+
for (const [relName, rel] of Object.entries(table.relations)) {
|
|
219
|
+
const targetType = entityName(rel.to);
|
|
220
|
+
lines.push(` ${relName}?: ${targetType}NestedCreateInput;`);
|
|
221
|
+
}
|
|
222
|
+
lines.push('};');
|
|
223
|
+
lines.push('');
|
|
224
|
+
lines.push(`export type ${typeName}UpdateInput = ${typeName}Update & {`);
|
|
225
|
+
for (const [relName, rel] of Object.entries(table.relations)) {
|
|
226
|
+
const targetType = entityName(rel.to);
|
|
227
|
+
if (rel.type === 'hasMany') {
|
|
228
|
+
lines.push(` ${relName}?: ${targetType}NestedUpdateInput;`);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
lines.push(` ${relName}?: ${targetType}NestedCreateInput;`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
lines.push('};');
|
|
235
|
+
lines.push('');
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// Emit NestedCreateInput, NestedUpdateInput, ConnectOrCreate for every table
|
|
239
|
+
for (const table of Object.values(schema.tables)) {
|
|
240
|
+
const typeName = entityName(table.name);
|
|
241
|
+
const hasRels = Object.keys(table.relations).length > 0;
|
|
242
|
+
// NestedCreateInput uses *CreateInput (which includes relation fields) when
|
|
243
|
+
// the table has relations, otherwise falls back to the plain *Create type.
|
|
244
|
+
const createRefType = hasRels ? `${typeName}CreateInput` : `${typeName}Create`;
|
|
245
|
+
lines.push(`export interface ${typeName}NestedCreateInput {`);
|
|
246
|
+
lines.push(` create?: ${createRefType} | ${createRefType}[];`);
|
|
247
|
+
lines.push(` connect?: ${typeName}WhereUnique | ${typeName}WhereUnique[];`);
|
|
248
|
+
lines.push(` connectOrCreate?: ${typeName}ConnectOrCreate | ${typeName}ConnectOrCreate[];`);
|
|
249
|
+
lines.push('}');
|
|
250
|
+
lines.push('');
|
|
251
|
+
lines.push(`export interface ${typeName}NestedUpdateInput {`);
|
|
252
|
+
lines.push(` create?: ${createRefType} | ${createRefType}[];`);
|
|
253
|
+
lines.push(` connect?: ${typeName}WhereUnique | ${typeName}WhereUnique[];`);
|
|
254
|
+
lines.push(` connectOrCreate?: ${typeName}ConnectOrCreate | ${typeName}ConnectOrCreate[];`);
|
|
255
|
+
lines.push(` disconnect?: ${typeName}WhereUnique | ${typeName}WhereUnique[];`);
|
|
256
|
+
lines.push(` set?: ${typeName}WhereUnique[];`);
|
|
257
|
+
lines.push(` delete?: ${typeName}WhereUnique | ${typeName}WhereUnique[];`);
|
|
258
|
+
lines.push('}');
|
|
259
|
+
lines.push('');
|
|
260
|
+
lines.push(`export interface ${typeName}ConnectOrCreate {`);
|
|
261
|
+
lines.push(` where: ${typeName}WhereUnique;`);
|
|
262
|
+
lines.push(` create: ${createRefType};`);
|
|
263
|
+
lines.push('}');
|
|
264
|
+
lines.push('');
|
|
265
|
+
}
|
|
180
266
|
return lines.join('\n');
|
|
181
267
|
}
|
|
182
268
|
// ---------------------------------------------------------------------------
|
|
@@ -214,7 +300,13 @@ function generateMetadata(schema) {
|
|
|
214
300
|
// dateColumns
|
|
215
301
|
const dateCols = [...table.dateColumns];
|
|
216
302
|
lines.push(` dateColumns: new Set([${dateCols.map((c) => `'${escSQ(c)}'`).join(', ')}]),`);
|
|
217
|
-
// pgTypes
|
|
303
|
+
// dialectTypes + pgTypes (pgTypes is kept for backwards compatibility)
|
|
304
|
+
const dialectTypes = table.dialectTypes ?? table.pgTypes;
|
|
305
|
+
lines.push(' dialectTypes: {');
|
|
306
|
+
for (const [col, dialectType] of Object.entries(dialectTypes)) {
|
|
307
|
+
lines.push(` ${quoteIfNeeded(col)}: '${escSQ(dialectType)}',`);
|
|
308
|
+
}
|
|
309
|
+
lines.push(' },');
|
|
218
310
|
lines.push(' pgTypes: {');
|
|
219
311
|
for (const [col, pgType] of Object.entries(table.pgTypes)) {
|
|
220
312
|
lines.push(` ${quoteIfNeeded(col)}: '${escSQ(pgType)}',`);
|
|
@@ -388,11 +480,13 @@ function serializeColumn(col) {
|
|
|
388
480
|
const parts = [
|
|
389
481
|
`name: '${escSQ(col.name)}'`,
|
|
390
482
|
`field: '${escSQ(col.field)}'`,
|
|
483
|
+
`dialectType: '${escSQ(col.dialectType ?? col.pgType)}'`,
|
|
391
484
|
`pgType: '${escSQ(col.pgType)}'`,
|
|
392
485
|
`tsType: '${escSQ(col.tsType)}'`,
|
|
393
486
|
`nullable: ${col.nullable}`,
|
|
394
487
|
`hasDefault: ${col.hasDefault}`,
|
|
395
488
|
`isArray: ${col.isArray}`,
|
|
489
|
+
`arrayType: '${escSQ(col.arrayType ?? col.pgArrayType)}'`,
|
|
396
490
|
`pgArrayType: '${escSQ(col.pgArrayType)}'`,
|
|
397
491
|
];
|
|
398
492
|
if (col.maxLength !== undefined)
|
package/dist/index.d.ts
CHANGED
|
@@ -34,14 +34,15 @@
|
|
|
34
34
|
*/
|
|
35
35
|
export type { DatabaseAdapter, IntrospectionOverrides } from './adapters/index.js';
|
|
36
36
|
export { alloydb, cockroachdb, postgresql, timescale, yugabytedb } from './adapters/index.js';
|
|
37
|
-
export { type Middleware, type MiddlewareNext, type MiddlewareParams, type PgCompatPool, type PgCompatPoolClient, type PgCompatQueryResult, TransactionClient, type TransactionOptions, TurbineClient, type TurbineConfig, } from './client.js';
|
|
37
|
+
export { type Middleware, type MiddlewareNext, type MiddlewareParams, type PgCompatPool, type PgCompatPoolClient, type PgCompatQueryResult, type RetryOptions, TransactionClient, type TransactionOptions, TurbineClient, type TurbineConfig, withRetry, } from './client.js';
|
|
38
38
|
export type { BuiltStatement, BulkInsertStatementInput, ColumnDefinitionInput, ColumnTypeInput, CreateIndexStatementInput, CreateTableStatementInput, Dialect, DialectIntrospector, DialectMigrator, DialectName, InsertStatementInput, IntrospectOptions as DialectIntrospectOptions, UpsertStatementInput, } from './dialect.js';
|
|
39
39
|
export { postgresDialect } from './dialect.js';
|
|
40
|
-
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';
|
|
40
|
+
export { CheckConstraintError, CircularRelationError, ConnectionError, DeadlockError, type ErrorMessageMode, ExclusionConstraintError, ForeignKeyError, getErrorMessageMode, MigrationError, NotFoundError, NotNullViolationError, OptimisticLockError, PipelineError, type PipelineResultSlot, RelationError, SerializationFailureError, setErrorMessageMode, TimeoutError, TurbineError, TurbineErrorCode, UniqueConstraintError, ValidationError, wrapPgError, } from './errors.js';
|
|
41
41
|
export { type GenerateOptions, generate } from './generate.js';
|
|
42
42
|
export { type IntrospectOptions, introspect } from './introspect.js';
|
|
43
|
+
export { executeNestedCreate, executeNestedUpdate, hasRelationFields, type NestedWriteContext, } from './nested-write.js';
|
|
43
44
|
export { executePipeline, type PipelineOptions, type PipelineResults, pipelineSupported } from './pipeline.js';
|
|
44
|
-
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';
|
|
45
|
+
export { type AggregateArgs, type AggregateResult, type ArrayFilter, type ConnectOrCreateOp, type CountArgs, type CreateArgs, type CreateManyArgs, type DeferredQuery, type DeleteArgs, type DeleteManyArgs, type FieldResult, type FindManyArgs, type FindManyStreamArgs, type FindUniqueArgs, type GroupByArgs, type JsonFilter, type NestedCreateOp, type NestedUpdateOp, type OmitResult, type OrderDirection, QueryInterface, type QueryResult, type RelationDescriptor, type RelationFilter, type SelectResult, type TextSearchFilter, type TypedWithClause, type UpdateArgs, type UpdateInput, type UpdateManyArgs, type UpdateOperatorInput, type UpsertArgs, type WithClause, type WithOptions, type WithResult, } from './query/index.js';
|
|
45
46
|
export type { ColumnMetadata, IndexMetadata, RelationDef, SchemaMetadata, TableMetadata, } from './schema.js';
|
|
46
47
|
export { camelToSnake, isDateType, normalizeKeyColumns, pgArrayType, pgTypeToTs, singularize, snakeToCamel, snakeToPascal, } from './schema.js';
|
|
47
48
|
export { ColumnBuilder, type ColumnConfig, type ColumnDef, type ColumnType, type ColumnTypeName, column, defineSchema, type SchemaDef, type TableDef, table, } from './schema-builder.js';
|
package/dist/index.js
CHANGED
|
@@ -34,14 +34,16 @@
|
|
|
34
34
|
*/
|
|
35
35
|
export { alloydb, cockroachdb, postgresql, timescale, yugabytedb } from './adapters/index.js';
|
|
36
36
|
// Client
|
|
37
|
-
export { TransactionClient, TurbineClient, } from './client.js';
|
|
37
|
+
export { TransactionClient, TurbineClient, withRetry, } from './client.js';
|
|
38
38
|
export { postgresDialect } from './dialect.js';
|
|
39
39
|
// Error types
|
|
40
|
-
export { CheckConstraintError, CircularRelationError, ConnectionError, DeadlockError, ForeignKeyError, getErrorMessageMode, MigrationError, NotFoundError, NotNullViolationError, PipelineError, RelationError, SerializationFailureError, setErrorMessageMode, TimeoutError, TurbineError, TurbineErrorCode, UniqueConstraintError, ValidationError, wrapPgError, } from './errors.js';
|
|
40
|
+
export { CheckConstraintError, CircularRelationError, ConnectionError, DeadlockError, ExclusionConstraintError, ForeignKeyError, getErrorMessageMode, MigrationError, NotFoundError, NotNullViolationError, OptimisticLockError, PipelineError, RelationError, SerializationFailureError, setErrorMessageMode, TimeoutError, TurbineError, TurbineErrorCode, UniqueConstraintError, ValidationError, wrapPgError, } from './errors.js';
|
|
41
41
|
// Code generation
|
|
42
42
|
export { generate } from './generate.js';
|
|
43
43
|
// Introspection
|
|
44
44
|
export { introspect } from './introspect.js';
|
|
45
|
+
// Nested writes
|
|
46
|
+
export { executeNestedCreate, executeNestedUpdate, hasRelationFields, } from './nested-write.js';
|
|
45
47
|
// Pipeline
|
|
46
48
|
export { executePipeline, pipelineSupported } from './pipeline.js';
|
|
47
49
|
// Query builder
|
package/dist/introspect.js
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* This is the foundation of `npx turbine generate`.
|
|
9
9
|
*/
|
|
10
10
|
import pg from 'pg';
|
|
11
|
-
import {
|
|
11
|
+
import { postgresDialect } from './dialect.js';
|
|
12
|
+
import { isDateType, pgTypeToTs, singularize, snakeToCamel, } from './schema.js';
|
|
12
13
|
// ---------------------------------------------------------------------------
|
|
13
14
|
// SQL queries (all parameterized, no interpolation)
|
|
14
15
|
// ---------------------------------------------------------------------------
|
|
@@ -95,6 +96,7 @@ const SQL_ENUMS = `
|
|
|
95
96
|
// ---------------------------------------------------------------------------
|
|
96
97
|
export async function introspect(options) {
|
|
97
98
|
const schema = options.schema ?? 'public';
|
|
99
|
+
const dialect = postgresDialect;
|
|
98
100
|
const pool = new pg.Pool({
|
|
99
101
|
connectionString: options.connectionString,
|
|
100
102
|
max: 1,
|
|
@@ -131,15 +133,20 @@ export async function introspect(options) {
|
|
|
131
133
|
const isNullable = row.is_nullable === 'YES';
|
|
132
134
|
const isArray = row.data_type === 'ARRAY';
|
|
133
135
|
const baseType = isArray ? row.udt_name.slice(1) : row.udt_name;
|
|
136
|
+
const dialectType = row.udt_name;
|
|
137
|
+
const arrayType = dialect.arrayType?.(baseType) ?? 'text[]';
|
|
134
138
|
const col = {
|
|
135
139
|
name: row.column_name,
|
|
136
140
|
field: snakeToCamel(row.column_name),
|
|
137
|
-
|
|
138
|
-
|
|
141
|
+
dialectType,
|
|
142
|
+
pgType: dialectType,
|
|
143
|
+
tsType: dialect.typeToTypeScript?.(isArray ? dialectType : baseType, isNullable) ??
|
|
144
|
+
pgTypeToTs(isArray ? dialectType : baseType, isNullable),
|
|
139
145
|
nullable: isNullable,
|
|
140
146
|
hasDefault: row.column_default !== null,
|
|
141
147
|
isArray,
|
|
142
|
-
|
|
148
|
+
arrayType,
|
|
149
|
+
pgArrayType: arrayType,
|
|
143
150
|
maxLength: row.character_maximum_length ?? undefined,
|
|
144
151
|
};
|
|
145
152
|
if (!columnsByTable.has(tableName))
|
|
@@ -275,14 +282,16 @@ export async function introspect(options) {
|
|
|
275
282
|
const columnMap = {};
|
|
276
283
|
const reverseColumnMap = {};
|
|
277
284
|
const dateColumns = new Set();
|
|
285
|
+
const dialectTypes = {};
|
|
278
286
|
const pgTypes = {};
|
|
279
287
|
const allColumns = [];
|
|
280
288
|
for (const col of columns) {
|
|
281
289
|
columnMap[col.field] = col.name;
|
|
282
290
|
reverseColumnMap[col.name] = col.field;
|
|
283
291
|
allColumns.push(col.name);
|
|
292
|
+
dialectTypes[col.name] = col.dialectType ?? col.pgType;
|
|
284
293
|
pgTypes[col.name] = col.pgType;
|
|
285
|
-
const baseType = col.isArray ? col.pgType.slice(1) : col.pgType;
|
|
294
|
+
const baseType = col.isArray ? (col.dialectType ?? col.pgType).slice(1) : (col.dialectType ?? col.pgType);
|
|
286
295
|
if (isDateType(baseType)) {
|
|
287
296
|
dateColumns.add(col.name);
|
|
288
297
|
}
|
|
@@ -293,6 +302,7 @@ export async function introspect(options) {
|
|
|
293
302
|
columnMap,
|
|
294
303
|
reverseColumnMap,
|
|
295
304
|
dateColumns,
|
|
305
|
+
dialectTypes,
|
|
296
306
|
pgTypes,
|
|
297
307
|
allColumns,
|
|
298
308
|
primaryKey: pkByTable.get(tableName) ?? [],
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* turbine-orm — Nested write engine
|
|
3
|
+
*
|
|
4
|
+
* Tree-walking create/update that resolves relation fields in `data` into
|
|
5
|
+
* batched SQL operations within a transaction. Supports create, connect,
|
|
6
|
+
* connectOrCreate, disconnect, set, and delete on related records at
|
|
7
|
+
* arbitrary depth (capped at 10).
|
|
8
|
+
*
|
|
9
|
+
* This module is imported by `query/builder.ts` when the `data` argument
|
|
10
|
+
* of `create()` or `update()` contains relation fields. It never imports
|
|
11
|
+
* `client.ts` directly — the transaction handle is passed in via
|
|
12
|
+
* `NestedWriteContext`.
|
|
13
|
+
*/
|
|
14
|
+
import type { RelationDef, SchemaMetadata, TableMetadata } from './schema.js';
|
|
15
|
+
export interface ExtractedFields {
|
|
16
|
+
scalars: Record<string, unknown>;
|
|
17
|
+
relations: Record<string, Record<string, unknown>>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Transaction context for nested write operations.
|
|
21
|
+
* Matches the subset of TransactionClient that we actually use.
|
|
22
|
+
*/
|
|
23
|
+
export interface NestedWriteContext {
|
|
24
|
+
schema: SchemaMetadata;
|
|
25
|
+
tx: {
|
|
26
|
+
table<T extends object>(name: string): {
|
|
27
|
+
create(args: {
|
|
28
|
+
data: Partial<T>;
|
|
29
|
+
}): Promise<T>;
|
|
30
|
+
createMany(args: {
|
|
31
|
+
data: Partial<T>[];
|
|
32
|
+
}): Promise<T[]>;
|
|
33
|
+
update(args: {
|
|
34
|
+
where: Record<string, unknown>;
|
|
35
|
+
data: Record<string, unknown>;
|
|
36
|
+
}): Promise<T>;
|
|
37
|
+
updateMany(args: {
|
|
38
|
+
where: Record<string, unknown>;
|
|
39
|
+
data: Record<string, unknown>;
|
|
40
|
+
allowFullTableScan?: boolean;
|
|
41
|
+
}): Promise<{
|
|
42
|
+
count: number;
|
|
43
|
+
}>;
|
|
44
|
+
delete(args: {
|
|
45
|
+
where: Record<string, unknown>;
|
|
46
|
+
}): Promise<T>;
|
|
47
|
+
deleteMany(args: {
|
|
48
|
+
where: Record<string, unknown>;
|
|
49
|
+
}): Promise<{
|
|
50
|
+
count: number;
|
|
51
|
+
}>;
|
|
52
|
+
findMany(args: {
|
|
53
|
+
where: Record<string, unknown>;
|
|
54
|
+
}): Promise<T[]>;
|
|
55
|
+
findUnique(args: {
|
|
56
|
+
where: Record<string, unknown>;
|
|
57
|
+
with?: Record<string, unknown>;
|
|
58
|
+
}): Promise<T | null>;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Separates scalar data fields from relation operation fields.
|
|
64
|
+
*
|
|
65
|
+
* A key is treated as a relation field only when:
|
|
66
|
+
* 1. It matches a relation name in `tableMeta.relations`
|
|
67
|
+
* 2. Its value is a non-null, non-array, non-Date plain object
|
|
68
|
+
*
|
|
69
|
+
* Everything else goes into `scalars`.
|
|
70
|
+
*/
|
|
71
|
+
export declare function extractRelationFields(data: Record<string, unknown>, tableMeta: TableMetadata): ExtractedFields;
|
|
72
|
+
/**
|
|
73
|
+
* Quick check: does `data` contain any relation fields that would trigger
|
|
74
|
+
* the nested write path? Used by QueryInterface to decide whether to
|
|
75
|
+
* delegate to the nested write engine or take the fast scalar-only path.
|
|
76
|
+
*/
|
|
77
|
+
export declare function hasRelationFields(data: Record<string, unknown>, tableMeta: TableMetadata): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Inject the parent row's PK value(s) as FK field(s) into child data.
|
|
80
|
+
* Handles composite keys. Returns a new object (does not mutate input).
|
|
81
|
+
*/
|
|
82
|
+
export declare function injectForeignKey(childData: Record<string, unknown>, relation: RelationDef, parentRow: Record<string, unknown>, schema: SchemaMetadata): Record<string, unknown>;
|
|
83
|
+
/**
|
|
84
|
+
* Tree-walking create: inserts the parent row, then processes each relation
|
|
85
|
+
* operation (create, connect, connectOrCreate), and finally reads back the
|
|
86
|
+
* full tree using `findUnique` with an auto-built `with` clause.
|
|
87
|
+
*/
|
|
88
|
+
export declare function executeNestedCreate(ctx: NestedWriteContext, tableName: string, data: Record<string, unknown>, depth?: number, path?: string[]): Promise<Record<string, unknown>>;
|
|
89
|
+
/**
|
|
90
|
+
* Tree-walking update: updates the parent row with scalar data, then
|
|
91
|
+
* processes each relation operation (create, connect, connectOrCreate,
|
|
92
|
+
* disconnect, set, delete), and reads back the full tree.
|
|
93
|
+
*/
|
|
94
|
+
export declare function executeNestedUpdate(ctx: NestedWriteContext, tableName: string, where: Record<string, unknown>, data: Record<string, unknown>, depth?: number, path?: string[]): Promise<Record<string, unknown>>;
|
|
95
|
+
//# sourceMappingURL=nested-write.d.ts.map
|