turbine-orm 0.34.0 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +18 -16
  2. package/dist/cjs/cli/index.js +109 -16
  3. package/dist/cjs/cli/migrate.js +78 -3
  4. package/dist/cjs/cli/studio-ui.generated.js +1 -1
  5. package/dist/cjs/cli/studio.js +333 -22
  6. package/dist/cjs/cli/ui.js +7 -1
  7. package/dist/cjs/client.js +26 -4
  8. package/dist/cjs/dialect.js +2 -1
  9. package/dist/cjs/errors.js +41 -1
  10. package/dist/cjs/generate.js +23 -2
  11. package/dist/cjs/index.js +4 -2
  12. package/dist/cjs/mssql.js +27 -5
  13. package/dist/cjs/mysql.js +4 -0
  14. package/dist/cjs/powdb.js +197 -25
  15. package/dist/cjs/powql.js +515 -51
  16. package/dist/cjs/query/aggregates.js +683 -0
  17. package/dist/cjs/query/batched-loader.js +2 -0
  18. package/dist/cjs/query/builder.js +361 -4508
  19. package/dist/cjs/query/filters.js +12 -0
  20. package/dist/cjs/query/relations.js +1698 -0
  21. package/dist/cjs/query/where-compile.js +180 -0
  22. package/dist/cjs/query/where.js +1491 -0
  23. package/dist/cjs/query/writes.js +680 -0
  24. package/dist/cjs/schema-builder.js +6 -0
  25. package/dist/cjs/schema-metadata.js +4 -0
  26. package/dist/cjs/schema-sql.js +265 -3
  27. package/dist/cjs/sqlite.js +4 -1
  28. package/dist/cli/index.d.ts +8 -2
  29. package/dist/cli/index.js +111 -18
  30. package/dist/cli/migrate.d.ts +24 -1
  31. package/dist/cli/migrate.js +77 -3
  32. package/dist/cli/studio-ui.generated.js +1 -1
  33. package/dist/cli/studio.d.ts +46 -13
  34. package/dist/cli/studio.js +331 -23
  35. package/dist/cli/ui.js +7 -1
  36. package/dist/client.d.ts +32 -5
  37. package/dist/client.js +26 -4
  38. package/dist/dialect.d.ts +28 -6
  39. package/dist/dialect.js +2 -1
  40. package/dist/errors.d.ts +36 -0
  41. package/dist/errors.js +39 -0
  42. package/dist/generate.js +23 -2
  43. package/dist/index.d.ts +3 -3
  44. package/dist/index.js +2 -2
  45. package/dist/mssql.js +27 -5
  46. package/dist/mysql.js +4 -0
  47. package/dist/powdb.d.ts +135 -9
  48. package/dist/powdb.js +197 -25
  49. package/dist/powql.d.ts +166 -4
  50. package/dist/powql.js +516 -52
  51. package/dist/query/aggregates.d.ts +74 -0
  52. package/dist/query/aggregates.js +641 -0
  53. package/dist/query/batched-loader.d.ts +6 -0
  54. package/dist/query/batched-loader.js +2 -0
  55. package/dist/query/builder.d.ts +98 -830
  56. package/dist/query/builder.js +366 -4513
  57. package/dist/query/deferred.d.ts +13 -2
  58. package/dist/query/filters.d.ts +7 -0
  59. package/dist/query/filters.js +11 -0
  60. package/dist/query/relations.d.ts +441 -0
  61. package/dist/query/relations.js +1627 -0
  62. package/dist/query/types.d.ts +25 -6
  63. package/dist/query/where-compile.d.ts +139 -0
  64. package/dist/query/where-compile.js +175 -0
  65. package/dist/query/where.d.ts +494 -0
  66. package/dist/query/where.js +1431 -0
  67. package/dist/query/writes.d.ts +131 -0
  68. package/dist/query/writes.js +626 -0
  69. package/dist/schema-builder.d.ts +18 -3
  70. package/dist/schema-builder.js +6 -0
  71. package/dist/schema-metadata.js +4 -0
  72. package/dist/schema-sql.d.ts +60 -3
  73. package/dist/schema-sql.js +261 -4
  74. package/dist/schema.d.ts +10 -0
  75. package/dist/sqlite.js +4 -1
  76. package/package.json +4 -4
package/dist/client.js CHANGED
@@ -104,15 +104,27 @@ export class TransactionClient {
104
104
  schema;
105
105
  middlewares;
106
106
  queryOptions;
107
+ sourcePool;
107
108
  tableCache = new Map();
108
109
  savepointCounter = 0;
109
110
  /** Active SQL dialect — owns savepoint keywords and raw-SQL placeholders. */
110
111
  dialect;
111
- constructor(client, schema, middlewares, queryOptions) {
112
+ constructor(client, schema, middlewares, queryOptions,
113
+ /**
114
+ * The parent pool this transaction runs on. Only its `readonly` and
115
+ * `capabilities` are read (both PowDB-only flags), so the transaction-scoped
116
+ * proxy pool built by {@link createTxPool} carries them through: without this
117
+ * a read-only client's `$transaction` writes bypass the E018 guard, and an
118
+ * older-engine client falls back to ALL_POWDB_CAPABILITIES inside the tx
119
+ * (emitting join PowQL a pre-0.13 engine rejects). Undefined / absent flags
120
+ * for a plain pg pool leave the proxy unchanged.
121
+ */
122
+ sourcePool) {
112
123
  this.client = client;
113
124
  this.schema = schema;
114
125
  this.middlewares = middlewares;
115
126
  this.queryOptions = queryOptions;
127
+ this.sourcePool = sourcePool;
116
128
  this.dialect = queryOptions?.dialect ?? postgresDialect;
117
129
  // Auto-create typed table accessors for all tables in the schema
118
130
  for (const tableName of Object.keys(schema.tables)) {
@@ -192,7 +204,7 @@ export class TransactionClient {
192
204
  const client = this.client;
193
205
  // Return a minimal pool-compatible object that routes queries
194
206
  // through the transaction client
195
- return {
207
+ const txPool = {
196
208
  query: async (textOrConfig, values) => {
197
209
  try {
198
210
  if (typeof textOrConfig === 'string') {
@@ -209,6 +221,14 @@ export class TransactionClient {
209
221
  },
210
222
  connect: () => Promise.resolve(client),
211
223
  };
224
+ // Carry the parent pool's PowDB-only flags through so a transaction-scoped
225
+ // PowqlInterface reads the same read-only guard and capabilities it would
226
+ // outside the transaction (a plain pg pool has neither, so nothing changes).
227
+ if (this.sourcePool?.readonly !== undefined)
228
+ txPool.readonly = this.sourcePool.readonly;
229
+ if (this.sourcePool?.capabilities !== undefined)
230
+ txPool.capabilities = this.sourcePool.capabilities;
231
+ return txPool;
212
232
  }
213
233
  }
214
234
  // ---------------------------------------------------------------------------
@@ -876,8 +896,10 @@ export class TurbineClient {
876
896
  await client.query(cfg.sql, cfg.params);
877
897
  }
878
898
  }
879
- // Create the transaction client with typed table accessors
880
- const tx = new TransactionClient(client, this.schema, this.middlewares, this.queryOptions);
899
+ // Create the transaction client with typed table accessors. Pass the
900
+ // parent pool so its read-only guard + PowDB capabilities flow into the
901
+ // transaction-scoped proxy pool (see TransactionClient.createTxPool).
902
+ const tx = new TransactionClient(client, this.schema, this.middlewares, this.queryOptions, this.pool);
881
903
  // Dynamically attach table accessors to tx
882
904
  for (const tableName of Object.keys(this.schema.tables)) {
883
905
  const camelName = tableName.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
package/dist/dialect.d.ts CHANGED
@@ -8,6 +8,15 @@
8
8
  import type { WithOptions } from './query/types.js';
9
9
  import { type RelationDef, type SchemaMetadata, type TableMetadata } from './schema.js';
10
10
  export type DialectName = 'postgresql' | 'mysql' | 'sqlite' | (string & {});
11
+ /**
12
+ * A write statement's returning/output selection. `'*'` returns every column
13
+ * (the historical default, byte-identical SQL). A `string[]` is an explicit,
14
+ * SQL-ready quoted column list used to exclude PII-tagged columns from a
15
+ * write's returned row at the SQL level, so PII never crosses the wire
16
+ * unrequested. Each dialect renders it into its own returning surface
17
+ * (`RETURNING …`, `OUTPUT INSERTED.…`).
18
+ */
19
+ export type ReturningSelection = '*' | readonly string[];
11
20
  export interface InsertStatementInput {
12
21
  /** SQL-ready quoted table name. */
13
22
  table: string;
@@ -16,7 +25,7 @@ export interface InsertStatementInput {
16
25
  /** SQL-ready parameter placeholders/expressions for VALUES. */
17
26
  valuePlaceholders: string[];
18
27
  /** Optional SQL-ready RETURNING selection. */
19
- returning?: string;
28
+ returning?: ReturningSelection;
20
29
  }
21
30
  export interface BulkInsertStatementInput {
22
31
  /** SQL-ready quoted table name. */
@@ -30,7 +39,7 @@ export interface BulkInsertStatementInput {
30
39
  /** Skip duplicate rows when supported by the dialect. */
31
40
  skipDuplicates?: boolean;
32
41
  /** Optional SQL-ready RETURNING selection. */
33
- returning?: string;
42
+ returning?: ReturningSelection;
34
43
  }
35
44
  export interface BuiltStatement {
36
45
  sql: string;
@@ -56,7 +65,7 @@ export interface UpsertStatementInput {
56
65
  */
57
66
  updateWhere?: string;
58
67
  /** Optional SQL-ready RETURNING selection. */
59
- returning?: string;
68
+ returning?: ReturningSelection;
60
69
  }
61
70
  export interface ColumnTypeInput {
62
71
  /** Schema-builder column type name (PostgreSQL-native in the root package). */
@@ -136,7 +145,7 @@ export interface UpdateStatementInput {
136
145
  /** SQL-ready WHERE fragment INCLUDING the leading ` WHERE ` (or '' for none). */
137
146
  whereSql: string;
138
147
  /** SQL-ready returning selection (default `*`). */
139
- returning?: string;
148
+ returning?: ReturningSelection;
140
149
  }
141
150
  /**
142
151
  * Inputs for {@link Dialect.buildDeleteStatement} — full DELETE assembly. SQL Server
@@ -148,7 +157,7 @@ export interface DeleteStatementInput {
148
157
  /** SQL-ready WHERE fragment INCLUDING the leading ` WHERE ` (or '' for none). */
149
158
  whereSql: string;
150
159
  /** SQL-ready returning selection (default `*`). */
151
- returning?: string;
160
+ returning?: ReturningSelection;
152
161
  }
153
162
  /**
154
163
  * Inputs for {@link Dialect.buildLimitOffset} — the trailing pagination clause of an
@@ -302,8 +311,21 @@ export interface Dialect {
302
311
  * lateral plan (else E017). PostgreSQL only in this release.
303
312
  */
304
313
  readonly supportsLateralJoin?: boolean;
314
+ /**
315
+ * How this dialect surfaces a query plan for a compiled SELECT. When present,
316
+ * `QueryInterface.explain()` prepends `prefix` (plus a single space) to the
317
+ * compiled findMany SQL and runs it as a read, returning the plan text lines.
318
+ * PostgreSQL / CockroachDB / YugabyteDB and MySQL use `EXPLAIN`, SQLite
319
+ * `EXPLAIN QUERY PLAN`. Absent means the engine
320
+ * cannot explain a compiled query in-band (SQL Server, whose SHOWPLAN needs a
321
+ * separate session toggle), so `QueryInterface.explain()` throws E017.
322
+ * Optional: dialects that predate this hook keep throwing E017.
323
+ */
324
+ readonly explainQuery?: {
325
+ prefix: string;
326
+ };
305
327
  /** Build a dialect-specific RETURNING clause. Return an empty string when unsupported. */
306
- buildReturningClause(selection?: string): string;
328
+ buildReturningClause(selection?: ReturningSelection): string;
307
329
  /** Build a single-row INSERT statement. Inputs are SQL-ready quoted fragments. */
308
330
  buildInsertStatement(input: InsertStatementInput): string;
309
331
  /** Build a multi-row bulk INSERT statement and its dialect-shaped params. */
package/dist/dialect.js CHANGED
@@ -23,6 +23,7 @@ export const postgresDialect = {
23
23
  supportsRLS: true,
24
24
  supportsAdvisoryLock: true,
25
25
  supportsLateralJoin: true,
26
+ explainQuery: { prefix: 'EXPLAIN' },
26
27
  paramPlaceholder(index) {
27
28
  return `$${index}`;
28
29
  },
@@ -68,7 +69,7 @@ export const postgresDialect = {
68
69
  return `COALESCE((${subquery}), ${fallback})`;
69
70
  },
70
71
  buildReturningClause(selection = '*') {
71
- return ` RETURNING ${selection}`;
72
+ return ` RETURNING ${selection === '*' ? '*' : selection.join(', ')}`;
72
73
  },
73
74
  buildInsertStatement(input) {
74
75
  return `INSERT INTO ${input.table} (${input.columns.join(', ')}) VALUES (${input.valuePlaceholders.join(', ')})${this.buildReturningClause(input.returning)}`;
package/dist/errors.d.ts CHANGED
@@ -23,6 +23,7 @@ export declare const TurbineErrorCode: {
23
23
  readonly OPTIMISTIC_LOCK: "TURBINE_E015";
24
24
  readonly EXCLUSION_VIOLATION: "TURBINE_E016";
25
25
  readonly UNSUPPORTED_FEATURE: "TURBINE_E017";
26
+ readonly READ_ONLY: "TURBINE_E018";
26
27
  };
27
28
  export type TurbineErrorCode = (typeof TurbineErrorCode)[keyof typeof TurbineErrorCode];
28
29
  /** Base error class for all Turbine errors */
@@ -303,6 +304,41 @@ export declare class UnsupportedFeatureError extends TurbineError {
303
304
  readonly dialect: string;
304
305
  constructor(feature: string, dialect: string, hint?: string);
305
306
  }
307
+ /**
308
+ * Thrown when a write or DDL statement is refused because the target is
309
+ * read-only. Two shapes reach here, both on PowDB:
310
+ * - an embedded database opened read-only for snapshot serving refuses a write
311
+ * with `readonly mode: statement requires a writer …`;
312
+ * - a networked read-only role refuses a write with `permission denied: role
313
+ * '<role>' cannot execute write statements` (translated by `wrapPowdbError`).
314
+ * It is also raised locally, before the wire, when a write is issued on a pool
315
+ * the caller marked read-only (fail-fast). The message carries the engine text
316
+ * plus a hint to route writes to a writable primary.
317
+ *
318
+ * NOT retryable: the same write against the same read-only target fails
319
+ * identically; route it to a writable primary instead.
320
+ */
321
+ export declare class ReadOnlyError extends TurbineError {
322
+ /**
323
+ * Why the write was refused. `'snapshot'`: the database itself is read-only
324
+ * (snapshot serving, an embedded `readonly: true` open, or the client-level
325
+ * fail-fast flag), so NOTHING can write here and writes must route to the
326
+ * primary. `'rbac'`: the database is writable but THIS connection's role may
327
+ * not write (per-connection permission), so re-authenticating may suffice.
328
+ */
329
+ readonly reason: 'snapshot' | 'rbac';
330
+ /**
331
+ * @param detail human-readable description of the refused write (the engine
332
+ * message, or a local fail-fast description). A "route writes to a writable
333
+ * primary" hint is always appended.
334
+ * @param options optional driver `cause` to preserve when wrapping a refusal,
335
+ * and the refusal `reason` (default `'snapshot'`).
336
+ */
337
+ constructor(detail: string, options?: {
338
+ cause?: unknown;
339
+ reason?: 'snapshot' | 'rbac';
340
+ });
341
+ }
306
342
  /**
307
343
  * Translate a pg driver error into a typed Turbine error.
308
344
  * If the error doesn't match a known constraint code, returns it unchanged.
package/dist/errors.js CHANGED
@@ -23,6 +23,7 @@ export const TurbineErrorCode = {
23
23
  OPTIMISTIC_LOCK: 'TURBINE_E015',
24
24
  EXCLUSION_VIOLATION: 'TURBINE_E016',
25
25
  UNSUPPORTED_FEATURE: 'TURBINE_E017',
26
+ READ_ONLY: 'TURBINE_E018',
26
27
  };
27
28
  /**
28
29
  * Prefix a human message with its stable error code so logs are greppable
@@ -479,6 +480,44 @@ export class UnsupportedFeatureError extends TurbineError {
479
480
  this.dialect = dialect;
480
481
  }
481
482
  }
483
+ /**
484
+ * Thrown when a write or DDL statement is refused because the target is
485
+ * read-only. Two shapes reach here, both on PowDB:
486
+ * - an embedded database opened read-only for snapshot serving refuses a write
487
+ * with `readonly mode: statement requires a writer …`;
488
+ * - a networked read-only role refuses a write with `permission denied: role
489
+ * '<role>' cannot execute write statements` (translated by `wrapPowdbError`).
490
+ * It is also raised locally, before the wire, when a write is issued on a pool
491
+ * the caller marked read-only (fail-fast). The message carries the engine text
492
+ * plus a hint to route writes to a writable primary.
493
+ *
494
+ * NOT retryable: the same write against the same read-only target fails
495
+ * identically; route it to a writable primary instead.
496
+ */
497
+ export class ReadOnlyError extends TurbineError {
498
+ /**
499
+ * Why the write was refused. `'snapshot'`: the database itself is read-only
500
+ * (snapshot serving, an embedded `readonly: true` open, or the client-level
501
+ * fail-fast flag), so NOTHING can write here and writes must route to the
502
+ * primary. `'rbac'`: the database is writable but THIS connection's role may
503
+ * not write (per-connection permission), so re-authenticating may suffice.
504
+ */
505
+ reason;
506
+ /**
507
+ * @param detail human-readable description of the refused write (the engine
508
+ * message, or a local fail-fast description). A "route writes to a writable
509
+ * primary" hint is always appended.
510
+ * @param options optional driver `cause` to preserve when wrapping a refusal,
511
+ * and the refusal `reason` (default `'snapshot'`).
512
+ */
513
+ constructor(detail, options) {
514
+ super(TurbineErrorCode.READ_ONLY, `[turbine] ${detail} Route writes to a writable primary.`, {
515
+ cause: options?.cause,
516
+ });
517
+ this.name = 'ReadOnlyError';
518
+ this.reason = options?.reason ?? 'snapshot';
519
+ }
520
+ }
482
521
  /**
483
522
  * Parse column names out of a pg `detail` string like:
484
523
  * "Key (email)=(foo@bar) already exists."
package/dist/generate.js CHANGED
@@ -161,8 +161,13 @@ export function generateTypes(schema, options) {
161
161
  for (const col of table.columns) {
162
162
  const pkNote = table.primaryKey.includes(col.name) ? ' (primary key)' : '';
163
163
  const nullNote = col.nullable ? ' (nullable)' : '';
164
- lines.push(` /** Column: ${col.name} ${col.pgType}${pkNote}${nullNote} */`);
165
- lines.push(` ${col.field}: ${columnTsType(col, schema.enums)};`);
164
+ // PII columns are excluded from default projections, so the field is
165
+ // absent unless the query names it in `select` or passes `includePii`.
166
+ // The emitted type marks it optional so it tells the truth about absence.
167
+ const piiNote = col.pii ? ' (PII: absent unless selected or includePii)' : '';
168
+ const optional = col.pii ? '?' : '';
169
+ lines.push(` /** Column: ${col.name}, ${col.pgType}${pkNote}${nullNote}${piiNote} */`);
170
+ lines.push(` ${col.field}${optional}: ${columnTsType(col, schema.enums)};`);
166
171
  }
167
172
  lines.push('}');
168
173
  lines.push('');
@@ -524,6 +529,17 @@ export function generateMetadata(schema, options) {
524
529
  lines.push(` { name: '${escSQ(idx.name)}', columns: [${idx.columns.map((c) => `'${escSQ(c)}'`).join(', ')}], unique: ${idx.unique}, definition: ${JSON.stringify(idx.definition)} },`);
525
530
  }
526
531
  lines.push(' ],');
532
+ // checks: introspected named CHECK constraints. Emitted only when present
533
+ // (byte-stable for check-less tables) and sorted by name so `--no-timestamp`
534
+ // output is deterministic regardless of catalog row order.
535
+ if (table.checks && table.checks.length > 0) {
536
+ const sortedChecks = [...table.checks].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
537
+ lines.push(' checks: [');
538
+ for (const chk of sortedChecks) {
539
+ lines.push(` { name: '${escSQ(chk.name)}', expression: ${JSON.stringify(chk.expression)} },`);
540
+ }
541
+ lines.push(' ],');
542
+ }
527
543
  // isView — read-only marker; the runtime write guard reads it.
528
544
  if (table.isView)
529
545
  lines.push(' isView: true,');
@@ -727,6 +743,11 @@ function serializeColumn(col) {
727
743
  if (col.generationExpression !== undefined) {
728
744
  parts.push(`generationExpression: '${escSQ(col.generationExpression)}'`);
729
745
  }
746
+ // PII marker: emitted only when set, so untagged schemas stay byte-identical.
747
+ // Introspection never sets this (code-first declaration), but a metadata
748
+ // object built from `defineSchema` (pii: true) carries it through codegen.
749
+ if (col.pii)
750
+ parts.push(`pii: true`);
730
751
  if (col.maxLength !== undefined)
731
752
  parts.push(`maxLength: ${col.maxLength}`);
732
753
  return `{ ${parts.join(', ')} }`;
package/dist/index.d.ts CHANGED
@@ -37,19 +37,19 @@ export { alloydb, cockroachdb, postgresql, timescale, yugabytedb } from './adapt
37
37
  export { type Middleware, type MiddlewareNext, type MiddlewareParams, type PgCompatPool, type PgCompatPoolClient, type PgCompatQueryResult, type RetryOptions, TransactionClient, type TransactionOptions, TurbineClient, type TurbineConfig, type TurbineDriver, withRetry, } from './client.js';
38
38
  export type { BuiltStatement, BulkInsertStatementInput, ColumnDefinitionInput, ColumnTypeInput, CreateIndexStatementInput, CreateTableStatementInput, Dialect, DialectIntrospector, DialectMigrator, DialectName, InsertStatementInput, IntrospectOptions as DialectIntrospectOptions, ResultStrategy, StreamableConnection, UpsertStatementInput, } from './dialect.js';
39
39
  export { postgresDialect } from './dialect.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, UnsupportedFeatureError, ValidationError, wrapPgError, } from './errors.js';
40
+ export { CheckConstraintError, CircularRelationError, ConnectionError, DeadlockError, type ErrorMessageMode, ExclusionConstraintError, ForeignKeyError, getErrorMessageMode, MigrationError, NotFoundError, NotNullViolationError, OptimisticLockError, PipelineError, type PipelineResultSlot, ReadOnlyError, RelationError, SerializationFailureError, setErrorMessageMode, TimeoutError, TurbineError, TurbineErrorCode, UniqueConstraintError, UnsupportedFeatureError, ValidationError, wrapPgError, } from './errors.js';
41
41
  export { type GenerateOptions, generate } from './generate.js';
42
42
  export { type IntrospectOptions, introspect } from './introspect.js';
43
43
  export { executeNestedCreate, executeNestedUpdate, hasRelationFields, type NestedWriteContext, } from './nested-write.js';
44
44
  export type { ObserveConfig, ObserveHandle } from './observe.js';
45
45
  export { executePipeline, type PipelineOptions, type PipelineResults, pipelineSupported } from './pipeline.js';
46
- export { type AggregateArgs, type AggregateResult, type ArrayFilter, type ColumnRef, type ConnectOrCreateOp, type CountArgs, type CreateArgs, type CreateDataInput, type CreateManyArgs, type DeferredQuery, type DeleteArgs, type DeleteManyArgs, type FieldResult, type FindManyArgs, type FindManyStreamArgs, type FindUniqueArgs, type GlobalFilters, type GroupByAggregateSpec, type GroupByArgs, type GroupByDistinctOn, type HavingClause, type JsonFilter, type JsonPathAggregateTarget, type JsonPathGroupKey, type JsonPathOrderBy, type MiddlewareFn, type NestedCreateOp, type NestedUpdateOp, type NestedUpdateOpItem, type NestedUpsertOpItem, type OmitResult, type OrderByClause, type OrderDirection, type QueryEvent, type QueryEventListener, QueryInterface, type QueryResult, type RelationDescriptor, type RelationFilter, type RelationPickBy, type RelationPickOrderBy, type SelectResult, type SkipGlobalFilters, type TextSearchFilter, type TypedWithClause, type UpdateArgs, type UpdateDataInput, type UpdateInput, type UpdateManyArgs, type UpdateOperatorInput, type UpsertArgs, type VectorDistanceFilter, type VectorFilter, type VectorMetric, type VectorOrderBy, type VectorOrderByDistance, type WhereClause, type WhereOperator, type WhereValue, type WithClause, type WithOptions, type WithResult, } from './query/index.js';
46
+ export { type AggregateArgs, type AggregateResult, type ArrayFilter, type ColumnRef, type ConnectOrCreateOp, type CountArgs, type CreateArgs, type CreateDataInput, type CreateManyArgs, type DeferredQuery, type DeleteArgs, type DeleteManyArgs, type FieldResult, type FindManyArgs, type FindManyStreamArgs, type FindUniqueArgs, type GlobalFilters, type GroupByAggregateSpec, type GroupByArgs, type GroupByDistinctOn, type HavingClause, type JsonFilter, type JsonPathAggregateTarget, type JsonPathGroupKey, type JsonPathOrderBy, type MiddlewareFn, type NestedCreateOp, type NestedUpdateOp, type NestedUpdateOpItem, type NestedUpsertOpItem, type OmitResult, type OrderByClause, type OrderDirection, type QueryEvent, type QueryEventListener, QueryInterface, type QueryResult, type RelationDescriptor, type RelationFilter, type RelationLoadStrategy, type RelationPickBy, type RelationPickOrderBy, type SelectResult, type SkipGlobalFilters, type TextSearchFilter, type TypedWithClause, type UpdateArgs, type UpdateDataInput, type UpdateInput, type UpdateManyArgs, type UpdateOperatorInput, type UpsertArgs, type VectorDistanceFilter, type VectorFilter, type VectorMetric, type VectorOrderBy, type VectorOrderByDistance, type WhereClause, type WhereOperator, type WhereValue, type WithClause, type WithOptions, type WithResult, } from './query/index.js';
47
47
  export { type ActiveSubscription, type NotificationHandler, type Subscription, validateChannel } from './realtime.js';
48
48
  export type { CheckMetadata, ColumnMetadata, IndexMetadata, ReferentialAction, RelationDef, SchemaMetadata, TableMetadata, } from './schema.js';
49
49
  export { camelToSnake, isDateType, normalizeKeyColumns, pgArrayType, pgTypeToTs, singularize, snakeToCamel, snakeToPascal, } from './schema.js';
50
50
  export { applyManyToManyRelations, type CheckDef, ColumnBuilder, type ColumnConfig, type ColumnDef, type ColumnIndexDef, type ColumnType, type ColumnTypeName, column, type DefineSchemaOptions, type DocFieldIndexDef, defineSchema, isDocFieldIndexDef, type ManyToManyDef, type ReferenceDef, type SchemaDef, type SchemaIndexDef, type TableDef, table, } from './schema-builder.js';
51
51
  export { schemaDefToMetadata } from './schema-metadata.js';
52
- export { type AlterColumnDef, type AlterDef, type DiffResult, type PushResult, type SchemaSqlOptions, schemaDiff, schemaPush, schemaToSQL, schemaToSQLString, } from './schema-sql.js';
52
+ export { type AlterColumnDef, type AlterDef, DestructivePushRefusal, type DiffResult, type PushResult, type SchemaSqlOptions, schemaDiff, schemaPush, schemaToSQL, schemaToSQLString, } from './schema-sql.js';
53
53
  export { type DefinedSeed, defineSeed, type SeedFunction } from './seed.js';
54
54
  export { type TurbineHttpOptions, turbineHttp } from './serverless.js';
55
55
  export { buildTypedSql, TypedSqlQuery } from './typed-sql.js';
package/dist/index.js CHANGED
@@ -37,7 +37,7 @@ export { alloydb, cockroachdb, postgresql, timescale, yugabytedb } from './adapt
37
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, ExclusionConstraintError, ForeignKeyError, getErrorMessageMode, MigrationError, NotFoundError, NotNullViolationError, OptimisticLockError, PipelineError, RelationError, SerializationFailureError, setErrorMessageMode, TimeoutError, TurbineError, TurbineErrorCode, UniqueConstraintError, UnsupportedFeatureError, ValidationError, wrapPgError, } from './errors.js';
40
+ export { CheckConstraintError, CircularRelationError, ConnectionError, DeadlockError, ExclusionConstraintError, ForeignKeyError, getErrorMessageMode, MigrationError, NotFoundError, NotNullViolationError, OptimisticLockError, PipelineError, ReadOnlyError, RelationError, SerializationFailureError, setErrorMessageMode, TimeoutError, TurbineError, TurbineErrorCode, UniqueConstraintError, UnsupportedFeatureError, ValidationError, wrapPgError, } from './errors.js';
41
41
  // Code generation
42
42
  export { generate } from './generate.js';
43
43
  // Introspection
@@ -59,7 +59,7 @@ table, } from './schema-builder.js';
59
59
  // Schema metadata bridge — defineSchema() → SchemaMetadata without a live DB
60
60
  export { schemaDefToMetadata } from './schema-metadata.js';
61
61
  // Schema SQL — generate DDL, diff, and push
62
- export { schemaDiff, schemaPush, schemaToSQL, schemaToSQLString, } from './schema-sql.js';
62
+ export { DestructivePushRefusal, schemaDiff, schemaPush, schemaToSQL, schemaToSQLString, } from './schema-sql.js';
63
63
  // Seed helper
64
64
  export { defineSeed } from './seed.js';
65
65
  // Serverless / edge factory
package/dist/mssql.js CHANGED
@@ -448,6 +448,23 @@ function mssqlColumnType(type, maxLength) {
448
448
  // ---------------------------------------------------------------------------
449
449
  // mssqlDialect — the full Dialect contract for SQL Server 2016+
450
450
  // ---------------------------------------------------------------------------
451
+ /**
452
+ * Render the SQL Server `OUTPUT` clause for a write's returning selection.
453
+ * `'*'` → ` OUTPUT INSERTED.*` (byte-identical to the historical default); a
454
+ * quoted column list → ` OUTPUT INSERTED.[c1], INSERTED.[c2]`; each column
455
+ * carries its own `INSERTED.`/`DELETED.` prefix (a bare comma list is invalid
456
+ * T-SQL). Used to exclude PII columns from a write's returned row. Empty
457
+ * selection → no clause.
458
+ */
459
+ function mssqlOutput(returning, alias) {
460
+ if (!returning)
461
+ return '';
462
+ if (returning === '*')
463
+ return ` OUTPUT ${alias}.*`;
464
+ if (returning.length === 0)
465
+ return '';
466
+ return ` OUTPUT ${returning.map((col) => `${alias}.${col}`).join(', ')}`;
467
+ }
451
468
  /**
452
469
  * SQL Server 2016+ implementation of the {@link Dialect} contract. Bracket
453
470
  * identifier quoting (`[…]`), named `@pN` placeholders, the `FOR JSON PATH`
@@ -472,6 +489,11 @@ export const mssqlDialect = {
472
489
  supportsLateralJoin: false,
473
490
  // sp_getapplock / sp_releaseapplock exist (used by a future migrate adapter).
474
491
  supportsAdvisoryLock: true,
492
+ // No in-band EXPLAIN: SQL Server's SHOWPLAN is a session toggle
493
+ // (SET SHOWPLAN_ALL ON), not a statement prefix, so a compiled query cannot
494
+ // be explained in one round-trip. Override the inherited Postgres `EXPLAIN`
495
+ // to absent → QueryInterface.explain() throws E017.
496
+ explainQuery: undefined,
475
497
  // FOR JSON over zero rows is NULL → coalesced in the relation override.
476
498
  aggSupportsInlineOrderBy: false,
477
499
  jsonPathSupport: 'limited',
@@ -506,7 +528,7 @@ export const mssqlDialect = {
506
528
  return '';
507
529
  },
508
530
  buildInsertStatement(input) {
509
- const out = input.returning ? ` OUTPUT INSERTED.${input.returning}` : '';
531
+ const out = mssqlOutput(input.returning, 'INSERTED');
510
532
  return `INSERT INTO ${input.table} (${input.columns.join(', ')})${out} VALUES (${input.valuePlaceholders.join(', ')})`;
511
533
  },
512
534
  buildBulkInsertStatement(input) {
@@ -526,7 +548,7 @@ export const mssqlDialect = {
526
548
  const placeholders = input.rowValues
527
549
  .map((row) => `(${row.map(() => this.paramPlaceholder(++n)).join(', ')})`)
528
550
  .join(', ');
529
- const out = input.returning ? ` OUTPUT INSERTED.${input.returning}` : '';
551
+ const out = mssqlOutput(input.returning, 'INSERTED');
530
552
  // skipDuplicates has no single-statement equivalent here; ignored (documented).
531
553
  return {
532
554
  sql: `INSERT INTO ${input.table} (${input.columns.join(', ')})${out} VALUES ${placeholders}`,
@@ -541,7 +563,7 @@ export const mssqlDialect = {
541
563
  const on = input.conflictColumns.map((c) => `T.${c} = S.${c}`).join(' AND ');
542
564
  const insertCols = input.insertColumns.join(', ');
543
565
  const sourceVals = input.insertColumns.map((c) => `S.${c}`).join(', ');
544
- const out = input.returning ? ` OUTPUT INSERTED.${input.returning}` : '';
566
+ const out = mssqlOutput(input.returning, 'INSERTED');
545
567
  return (`MERGE INTO ${input.table} AS T ` +
546
568
  `USING (VALUES (${input.valuePlaceholders.join(', ')})) AS S (${insertCols}) ` +
547
569
  `ON (${on}) ` +
@@ -552,11 +574,11 @@ export const mssqlDialect = {
552
574
  // UPDATE/DELETE inject OUTPUT mid-statement (between SET and WHERE / FROM and
553
575
  // WHERE) — a trailing clause would be invalid T-SQL.
554
576
  buildUpdateStatement(input) {
555
- const out = input.returning ? ` OUTPUT INSERTED.${input.returning}` : '';
577
+ const out = mssqlOutput(input.returning, 'INSERTED');
556
578
  return `UPDATE ${input.table} SET ${input.setClauses.join(', ')}${out}${input.whereSql}`;
557
579
  },
558
580
  buildDeleteStatement(input) {
559
- const out = input.returning ? ` OUTPUT DELETED.${input.returning}` : '';
581
+ const out = mssqlOutput(input.returning, 'DELETED');
560
582
  return `DELETE FROM ${input.table}${out}${input.whereSql}`;
561
583
  },
562
584
  // SQL Server has no LIMIT — emit OFFSET/FETCH, injecting a stable ORDER BY when
package/dist/mysql.js CHANGED
@@ -373,6 +373,10 @@ export const mysqlDialect = {
373
373
  supportsLateralJoin: false,
374
374
  // GET_LOCK / RELEASE_LOCK exist (used by a future migrate adapter).
375
375
  supportsAdvisoryLock: true,
376
+ // Plain `EXPLAIN` (one row of tabular plan columns) works on every supported
377
+ // MySQL 8.0.x; the readable `FORMAT=TREE` variant only exists from 8.0.16 and
378
+ // the engine floor here is 8.0.0. Plan text is a diagnostic, not a contract.
379
+ explainQuery: { prefix: 'EXPLAIN' },
376
380
  // JSON_ARRAYAGG has no inline ORDER BY argument → force the inner-subquery
377
381
  // rewrite for every ordered to-many relation.
378
382
  aggSupportsInlineOrderBy: false,