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/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
@@ -8,7 +8,8 @@
8
8
  * This is the foundation of `npx turbine generate`.
9
9
  */
10
10
  import pg from 'pg';
11
- import { isDateType, pgArrayType, pgTypeToTs, singularize, snakeToCamel, } from './schema.js';
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
- pgType: row.udt_name,
138
- tsType: pgTypeToTs(isArray ? row.udt_name : baseType, isNullable),
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
- pgArrayType: pgArrayType(baseType),
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