turbine-orm 0.49.0 → 0.50.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 (157) hide show
  1. package/README.md +122 -39
  2. package/dist/cjs/adapters/cockroachdb.d.ts +39 -0
  3. package/dist/cjs/adapters/index.d.ts +110 -0
  4. package/dist/cjs/adapters/yugabytedb.d.ts +51 -0
  5. package/dist/cjs/cli/config.d.ts +181 -0
  6. package/dist/cjs/cli/config.js +32 -6
  7. package/dist/cjs/cli/destructive.d.ts +38 -0
  8. package/dist/cjs/cli/index.d.ts +359 -0
  9. package/dist/cjs/cli/index.js +228 -56
  10. package/dist/cjs/cli/loader.d.ts +61 -0
  11. package/dist/cjs/cli/mcp.d.ts +42 -0
  12. package/dist/cjs/cli/migrate.d.ts +356 -0
  13. package/dist/cjs/cli/migrate.js +131 -40
  14. package/dist/cjs/cli/observe-ui.d.ts +1 -0
  15. package/dist/cjs/cli/observe-ui.js +14 -5
  16. package/dist/cjs/cli/observe.d.ts +25 -0
  17. package/dist/cjs/cli/observe.js +49 -12
  18. package/dist/cjs/cli/pii-tags.d.ts +53 -0
  19. package/dist/cjs/cli/prisma-report.d.ts +33 -0
  20. package/dist/cjs/cli/prisma-report.js +73 -0
  21. package/dist/cjs/cli/prisma-resolve.d.ts +106 -0
  22. package/dist/cjs/cli/prisma-resolve.js +1 -0
  23. package/dist/cjs/cli/prisma-schema.d.ts +176 -0
  24. package/dist/cjs/cli/prisma-schema.js +82 -4
  25. package/dist/cjs/cli/rate-limit.d.ts +32 -0
  26. package/dist/cjs/cli/rate-limit.js +45 -0
  27. package/dist/cjs/cli/studio-demo.d.ts +43 -0
  28. package/dist/cjs/cli/studio-ui.generated.d.ts +1 -0
  29. package/dist/cjs/cli/studio.d.ts +207 -0
  30. package/dist/cjs/cli/studio.js +136 -71
  31. package/dist/cjs/cli/ui.d.ts +73 -0
  32. package/dist/cjs/cli/ui.js +51 -9
  33. package/dist/cjs/client.d.ts +837 -0
  34. package/dist/cjs/client.js +3 -0
  35. package/dist/cjs/dialect.d.ts +516 -0
  36. package/dist/cjs/dialect.js +37 -12
  37. package/dist/cjs/errors.d.ts +370 -0
  38. package/dist/cjs/generate.d.ts +137 -0
  39. package/dist/cjs/generate.js +39 -6
  40. package/dist/cjs/index-advisor.d.ts +153 -0
  41. package/dist/cjs/index-stats.d.ts +384 -0
  42. package/dist/cjs/index.d.ts +55 -0
  43. package/dist/cjs/index.js +7 -2
  44. package/dist/cjs/introspect.d.ts +269 -0
  45. package/dist/cjs/mssql.d.ts +232 -0
  46. package/dist/cjs/mssql.js +6 -0
  47. package/dist/cjs/mysql.d.ts +173 -0
  48. package/dist/cjs/mysql.js +16 -0
  49. package/dist/cjs/nested-write.d.ts +96 -0
  50. package/dist/cjs/nested-write.js +414 -24
  51. package/dist/cjs/observe.d.ts +115 -0
  52. package/dist/cjs/optional-peer-import.d.cts +72 -0
  53. package/dist/cjs/pipeline-submittable.d.ts +93 -0
  54. package/dist/cjs/pipeline.d.ts +71 -0
  55. package/dist/cjs/powdb-introspect.d.ts +84 -0
  56. package/dist/cjs/powdb.d.ts +931 -0
  57. package/dist/cjs/powdb.js +106 -21
  58. package/dist/cjs/powql.d.ts +592 -0
  59. package/dist/cjs/powql.js +42 -6
  60. package/dist/cjs/prisma-compat.d.ts +283 -0
  61. package/dist/cjs/prisma-compat.js +167 -9
  62. package/dist/cjs/query/aggregates.d.ts +92 -0
  63. package/dist/cjs/query/aggregates.js +7 -3
  64. package/dist/cjs/query/batched-loader.d.ts +193 -0
  65. package/dist/cjs/query/builder.d.ts +849 -0
  66. package/dist/cjs/query/builder.js +571 -65
  67. package/dist/cjs/query/compound-unique.d.ts +51 -0
  68. package/dist/cjs/query/deferred.d.ts +223 -0
  69. package/dist/cjs/query/filters.d.ts +201 -0
  70. package/dist/cjs/query/index.d.ts +14 -0
  71. package/dist/cjs/query/index.js +6 -1
  72. package/dist/cjs/query/relations.d.ts +609 -0
  73. package/dist/cjs/query/relations.js +693 -46
  74. package/dist/cjs/query/types.d.ts +1300 -0
  75. package/dist/cjs/query/utils.d.ts +209 -0
  76. package/dist/cjs/query/utils.js +208 -1
  77. package/dist/cjs/query/warn-registry.d.ts +68 -0
  78. package/dist/cjs/query/warn-registry.js +9 -0
  79. package/dist/cjs/query/where-compile.d.ts +139 -0
  80. package/dist/cjs/query/where.d.ts +548 -0
  81. package/dist/cjs/query/where.js +58 -22
  82. package/dist/cjs/query/writes.d.ts +172 -0
  83. package/dist/cjs/query/writes.js +105 -12
  84. package/dist/cjs/realtime.d.ts +70 -0
  85. package/dist/cjs/schema-builder.d.ts +354 -0
  86. package/dist/cjs/schema-metadata.d.ts +83 -0
  87. package/dist/cjs/schema-sql.d.ts +217 -0
  88. package/dist/cjs/schema-sql.js +23 -5
  89. package/dist/cjs/schema.d.ts +356 -0
  90. package/dist/cjs/schema.js +125 -0
  91. package/dist/cjs/seed.d.ts +15 -0
  92. package/dist/cjs/serverless.d.ts +142 -0
  93. package/dist/cjs/sqlite.d.ts +143 -0
  94. package/dist/cjs/sqlite.js +4 -0
  95. package/dist/cjs/typed-sql.d.ts +102 -0
  96. package/dist/cli/config.d.ts +18 -4
  97. package/dist/cli/config.js +31 -6
  98. package/dist/cli/index.d.ts +123 -0
  99. package/dist/cli/index.js +223 -58
  100. package/dist/cli/migrate.d.ts +59 -10
  101. package/dist/cli/migrate.js +128 -41
  102. package/dist/cli/observe-ui.d.ts +1 -1
  103. package/dist/cli/observe-ui.js +14 -5
  104. package/dist/cli/observe.d.ts +7 -1
  105. package/dist/cli/observe.js +48 -12
  106. package/dist/cli/prisma-report.d.ts +14 -0
  107. package/dist/cli/prisma-report.js +72 -0
  108. package/dist/cli/prisma-resolve.d.ts +6 -0
  109. package/dist/cli/prisma-resolve.js +1 -0
  110. package/dist/cli/prisma-schema.d.ts +62 -2
  111. package/dist/cli/prisma-schema.js +81 -4
  112. package/dist/cli/rate-limit.d.ts +32 -0
  113. package/dist/cli/rate-limit.js +40 -0
  114. package/dist/cli/studio.d.ts +5 -5
  115. package/dist/cli/studio.js +135 -70
  116. package/dist/cli/ui.d.ts +1 -1
  117. package/dist/cli/ui.js +51 -9
  118. package/dist/client.d.ts +40 -0
  119. package/dist/client.js +3 -0
  120. package/dist/dialect.d.ts +17 -1
  121. package/dist/dialect.js +37 -12
  122. package/dist/generate.js +40 -7
  123. package/dist/index.d.ts +1 -1
  124. package/dist/index.js +1 -1
  125. package/dist/mssql.js +6 -0
  126. package/dist/mysql.js +16 -0
  127. package/dist/nested-write.d.ts +2 -0
  128. package/dist/nested-write.js +415 -25
  129. package/dist/powdb.d.ts +4 -2
  130. package/dist/powdb.js +106 -21
  131. package/dist/powql.d.ts +5 -0
  132. package/dist/powql.js +42 -6
  133. package/dist/prisma-compat.d.ts +2 -0
  134. package/dist/prisma-compat.js +166 -8
  135. package/dist/query/aggregates.js +7 -3
  136. package/dist/query/builder.d.ts +292 -21
  137. package/dist/query/builder.js +570 -64
  138. package/dist/query/deferred.d.ts +39 -0
  139. package/dist/query/index.d.ts +1 -1
  140. package/dist/query/index.js +1 -1
  141. package/dist/query/relations.d.ts +173 -5
  142. package/dist/query/relations.js +688 -47
  143. package/dist/query/types.d.ts +123 -39
  144. package/dist/query/utils.d.ts +116 -0
  145. package/dist/query/utils.js +198 -0
  146. package/dist/query/warn-registry.d.ts +9 -0
  147. package/dist/query/warn-registry.js +9 -0
  148. package/dist/query/where.d.ts +38 -1
  149. package/dist/query/where.js +58 -23
  150. package/dist/query/writes.d.ts +42 -1
  151. package/dist/query/writes.js +104 -13
  152. package/dist/schema-sql.d.ts +14 -0
  153. package/dist/schema-sql.js +23 -5
  154. package/dist/schema.d.ts +38 -0
  155. package/dist/schema.js +123 -0
  156. package/dist/sqlite.js +4 -0
  157. package/package.json +77 -28
@@ -0,0 +1,269 @@
1
+ /**
2
+ * turbine-orm — Schema introspection
3
+ *
4
+ * Connects to a live Postgres database, reads information_schema + pg_catalog,
5
+ * and produces a SchemaMetadata object describing every table, column, relation,
6
+ * and index in the target schema.
7
+ *
8
+ * This is the foundation of `npx turbine generate`.
9
+ */
10
+ import { type Dialect } from './dialect.js';
11
+ import { type ColumnMetadata, type IndexMetadata, type ReferentialAction, type RelationDef, type SchemaMetadata } from './schema.js';
12
+ /**
13
+ * Map a `pg_constraint.confdeltype` / `confupdtype` character to a
14
+ * {@link ReferentialAction}. Postgres encodes: `a` = NO ACTION, `r` = RESTRICT,
15
+ * `c` = CASCADE, `n` = SET NULL, `d` = SET DEFAULT.
16
+ */
17
+ export declare function pgConfActionToReferential(ch: string): ReferentialAction;
18
+ /**
19
+ * Migration-bookkeeping tables that introspection drops by default: Turbine's
20
+ * own `_turbine_migrations` / `_turbine_metrics` and Prisma's
21
+ * `_prisma_migrations`. These are almost never meant to be surfaced as typed
22
+ * accessors, and a fresh migrate-from-Prisma introspection would otherwise emit
23
+ * a `PrismaMigrations` entity plus stray FK-derived relations on neighbours.
24
+ *
25
+ * A table named here is dropped UNLESS it is explicitly listed in
26
+ * `options.include` (`include` is the escape hatch, no separate flag), and
27
+ * naming a default-excluded table restores its old generated output byte for
28
+ * byte. The list is deliberately tight (exactly these three); leading-
29
+ * underscore tables are legitimate user tables and are never blanket-excluded.
30
+ */
31
+ export declare const DEFAULT_EXCLUDED_TABLES: readonly ["_turbine_migrations", "_prisma_migrations", "_turbine_metrics"];
32
+ /** The include / exclude filters shared by every introspector's table selection. */
33
+ export interface TableFilterOptions {
34
+ /** Tables to include (empty/undefined = all). Applied first. */
35
+ include?: string[];
36
+ /** Tables the user asked to exclude. Applied after include. */
37
+ exclude?: string[];
38
+ }
39
+ /**
40
+ * The single authority for turning a raw list of candidate table names into the
41
+ * introspected set, shared by the Postgres catalog reader and every engine
42
+ * introspector (SQLite / MySQL / MSSQL / PowDB) so all surfaces agree.
43
+ *
44
+ * Order of operations:
45
+ * 1. `include` filter: when non-empty, keep only the named tables.
46
+ * 2. user `exclude`: drop anything the caller listed.
47
+ * 3. {@link DEFAULT_EXCLUDED_TABLES}: drop migration bookkeeping tables,
48
+ * EXCEPT any that the caller explicitly named in `include` (the escape
49
+ * hatch that restores the pre-0.41 output for those tables).
50
+ */
51
+ export declare function applyTableFilters(names: string[], options?: TableFilterOptions): string[];
52
+ /**
53
+ * The subset of {@link DEFAULT_EXCLUDED_TABLES} that were present in `names` but
54
+ * dropped by {@link applyTableFilters} (i.e. not re-added via `include`). Pure
55
+ * helper so the CLI can report "skipped internal table X" without re-deriving
56
+ * the filtering rule.
57
+ */
58
+ export declare function defaultExcludedTablesPresent(names: string[], options?: TableFilterOptions): string[];
59
+ export interface IntrospectOptions {
60
+ /** Postgres connection string */
61
+ connectionString: string;
62
+ /** Schema to introspect (default: 'public') */
63
+ schema?: string;
64
+ /** Tables to include (default: all). Glob-like patterns not supported yet. */
65
+ include?: string[];
66
+ /** Tables to exclude (default: none). Applied after include. */
67
+ exclude?: string[];
68
+ /**
69
+ * Also introspect **views** and **materialized views** as read-only
70
+ * {@link TableMetadata} entries (`isView: true`). Off by default. Write
71
+ * builders reject views (E003); a view without a primary key is excluded from
72
+ * the generated `findUnique`-family accessor types.
73
+ */
74
+ includeViews?: boolean;
75
+ /**
76
+ * Opt OUT of the unique-FK → `hasOne` flip (F2). By default (`false`)
77
+ * introspection emits a to-one (`hasOne`) relation on the parent side when a
78
+ * child's foreign-key column set is EXACTLY covered by a UNIQUE constraint or
79
+ * a non-partial, non-expression UNIQUE index, matching Prisma one-to-one
80
+ * introspection. Set to `true` to keep the pre-0.41 behavior where every such
81
+ * relation was emitted as `hasMany` (a to-many array). See
82
+ * {@link detectUniqueForeignKeySets}.
83
+ */
84
+ legacyToManyUniques?: boolean;
85
+ /**
86
+ * Called with any {@link DEFAULT_EXCLUDED_TABLES} that were present in the
87
+ * database but dropped from this run (F12), so the CLI can print a
88
+ * "skipped internal table X (add it to include to keep it)" note. Not invoked
89
+ * when the set is empty. Postgres path only for now.
90
+ */
91
+ onDefaultTableExclusion?: (tables: string[]) => void;
92
+ /**
93
+ * Dialect whose {@link Dialect.introspector} drives the catalog reads.
94
+ * Defaults to {@link postgresDialect}. Engines plug their own introspector
95
+ * here so `introspect()` works across databases.
96
+ */
97
+ dialect?: Dialect;
98
+ }
99
+ /**
100
+ * Introspect a database into {@link SchemaMetadata}, routing through the active
101
+ * dialect's {@link Dialect.introspector} so each engine can override the catalog
102
+ * SQL. PostgreSQL is driven by {@link introspectPostgresCatalog}.
103
+ */
104
+ export declare function introspect(options: IntrospectOptions): Promise<SchemaMetadata>;
105
+ /**
106
+ * PostgreSQL catalog introspector: reads information_schema + pg_catalog and
107
+ * produces {@link SchemaMetadata}. This is the implementation wrapped by
108
+ * `postgresDialect.introspector`; call {@link introspect} for dialect routing.
109
+ */
110
+ export declare function introspectPostgresCatalog(options: IntrospectOptions): Promise<SchemaMetadata>;
111
+ /**
112
+ * Parse the indexed column names out of a `pg_indexes.indexdef` string.
113
+ *
114
+ * `indexdef` always reads `CREATE [UNIQUE] INDEX name ON tbl USING method
115
+ * (col, ...) [WHERE predicate]`. We anchor on the `USING` clause's parenthesised
116
+ * column list (the same precedent as `describeIndexDefMismatch` in
117
+ * schema-sql.ts) so a PARTIAL index's trailing `WHERE (...)` parentheses are
118
+ * never mistaken for the column list. The older greedy `/\((.+)\)/` swallowed
119
+ * `) WHERE (` and spliced a raw predicate fragment into the column names, which
120
+ * then leaked into generated compound-unique selector names.
121
+ *
122
+ * Each column is de-quoted (Postgres quotes non-lowercase identifiers such as a
123
+ * Prisma implicit m2m junction's `"A"` / `"B"`), so the names match the
124
+ * unquoted column names carried elsewhere in the metadata. Expression columns
125
+ * (anything containing a parenthesis) are dropped conservatively: a functional
126
+ * index does not name a plain column.
127
+ */
128
+ export declare function parseIndexColumns(indexdef: string): string[];
129
+ /**
130
+ * Whether an `indexdef` carries a top-level `WHERE` predicate (a PARTIAL index).
131
+ * pg_indexes only ever emits `WHERE` as the partial predicate, so a keyword
132
+ * match is sufficient (matches the `describeIndexDefMismatch` precedent).
133
+ */
134
+ export declare function indexHasWhere(indexdef: string): boolean;
135
+ /**
136
+ * Recover the raw check expression from `pg_get_constraintdef` output, which
137
+ * wraps it as `CHECK ((expr))`. Strips the leading `CHECK ` keyword and one
138
+ * balanced outer paren pair; leaves anything unexpected untouched.
139
+ */
140
+ export declare function stripCheckWrapper(def: string): string;
141
+ /** A foreign-key constraint grouped by constraint name (composite FKs carry column arrays). */
142
+ export interface ForeignKeyEntry {
143
+ sourceTable: string;
144
+ sourceColumns: string[];
145
+ targetTable: string;
146
+ targetColumns: string[];
147
+ constraintName: string;
148
+ }
149
+ /**
150
+ * Derive a belongsTo relation name from its FK column. Strips a trailing
151
+ * `_id` (snake_case) or `Id` (camelCase column names — common in Prisma-ported
152
+ * schemas where columns are quoted camelCase identifiers), then camelCases:
153
+ * `current_version_id` and `currentVersionId` both yield `currentVersion`.
154
+ * Stripping is what keeps the scalar FK field (`currentVersionId`) targetable
155
+ * alongside the relation. A column literally named `id` (nothing left after
156
+ * stripping) keeps its own name.
157
+ */
158
+ export declare function relationNameFromColumn(column: string): string;
159
+ /**
160
+ * True for the tsType forms a json/jsonb column maps to (`unknown`, nullable
161
+ * `unknown | null`). A relation shadowing such a column is a HISTORICAL shadow
162
+ * that worked at runtime and compiled (`unknown` absorbs the relation
163
+ * payload), so the legacy-first naming keeps it instead of renaming.
164
+ */
165
+ export declare function isUnknownTsType(tsType: string): boolean;
166
+ /**
167
+ * Parse the column list of a PLAIN unique index from its `pg_indexes.indexdef`,
168
+ * returning `null` for anything that does NOT guarantee at-most-one child row:
169
+ *
170
+ * - a PARTIAL index (has a `WHERE` clause): only unique within the predicate;
171
+ * - an EXPRESSION index (`lower(email)`, `(a || b)`): the uniqueness is on the
172
+ * expression, not the raw FK column set.
173
+ *
174
+ * Anchors on the `USING <method> (` clause the same way
175
+ * {@link describeIndexDefMismatch} does, so a partial index's `WHERE (...)`
176
+ * parentheses are never mistaken for the column list. Every column token must be
177
+ * a bare or double-quoted identifier; anything else (a function call, an
178
+ * operator expression) fails the check and yields `null`.
179
+ */
180
+ export declare function parsePlainUniqueIndexColumns(indexdef: string): string[] | null;
181
+ /**
182
+ * Assemble, per table, every column set that EXACTLY guarantees at-most-one row:
183
+ * the primary key, every UNIQUE constraint, and every PLAIN (non-partial,
184
+ * non-expression) UNIQUE index. Consumed by
185
+ * {@link buildRelationsFromForeignKeys} to flip a child relation whose FK column
186
+ * set matches one of these sets from `hasMany` to `hasOne` (F2, Prisma
187
+ * one-to-one parity).
188
+ */
189
+ export declare function detectUniqueForeignKeySets(pkByTable: Map<string, string[]>, uniqueByTable: Map<string, string[][]>, indexesByTable: Map<string, IndexMetadata[]>): Map<string, string[][]>;
190
+ /**
191
+ * Build the belongsTo/hasMany relation maps for every table from its foreign
192
+ * keys. Naming rules (LEGACY-FIRST — a relation name that previously worked at
193
+ * runtime must never change out from under a regenerating app):
194
+ *
195
+ * 1. First compute the historical derivation exactly as it shipped before
196
+ * the collision guard existed: belongsTo strips a case-SENSITIVE `_id`
197
+ * suffix (`snakeToCamel(col.replace(/_id$/, ''))` when several FKs point
198
+ * at the same target, else the singularized target table), and hasMany is
199
+ * `snakeToCamel(`${source}_by_${strippedColumn}`)` (else the source
200
+ * table). If that legacy name is free, KEEP IT — even when it looks odd
201
+ * (`blogPostsByAuthorId`, `postsBy_Author`): those names were collision-
202
+ * free and worked, so regenerating must not rename them.
203
+ * 2. If the legacy name collides ONLY with a scalar column whose tsType is
204
+ * `unknown` (json/jsonb), keep it anyway with a warning: the shadow is
205
+ * historical, ran fine at runtime, and compiled (`unknown` absorbs the
206
+ * relation payload; generate.ts's typeSafeRelations omits the relation
207
+ * from the type layer).
208
+ * 3. On a genuine collision (concrete-typed column shadow, or a previously
209
+ * assigned relation), fall back to the modern derivation — the `_id`/`Id`
210
+ * case-insensitive strip of {@link relationNameFromColumn} plus the
211
+ * `By`-composed reverse name — which fixes the camelCase-FK shadowing
212
+ * shapes that were actually BROKEN before (relation name === scalar FK
213
+ * field → unusable types).
214
+ * 4. Last resort: deterministic `Rel`/`Rel2` suffix + warning.
215
+ *
216
+ * @param columnFieldsByTable camelCase column *fields* per table — used to
217
+ * guarantee relations never shadow concrete-typed scalar columns.
218
+ * @param unknownTypedFieldsByTable subset of the column fields whose tsType is
219
+ * `unknown` (json/jsonb) — legacy shadows of these are preserved (rule 2).
220
+ * @param uniqueSetsByTable when provided (F2), the child-table column sets that
221
+ * guarantee at-most-one row (PK + unique constraints + plain unique indexes,
222
+ * from {@link detectUniqueForeignKeySets}). A reverse relation whose FK column
223
+ * set EXACTLY matches one of the child's unique sets is emitted as `hasOne`
224
+ * (to-one) instead of `hasMany`, and named with the SINGULAR of the child
225
+ * table (falling back to the legacy plural name on collision). Omit it (the
226
+ * engine introspectors and `defineSchema` path do) to keep every reverse
227
+ * relation `hasMany`.
228
+ */
229
+ export declare function buildRelationsFromForeignKeys(foreignKeys: ForeignKeyEntry[], columnFieldsByTable: Map<string, Set<string>>, fkActions?: Map<string, {
230
+ onDelete: ReferentialAction;
231
+ onUpdate: ReferentialAction;
232
+ }>, unknownTypedFieldsByTable?: Map<string, Set<string>>, uniqueSetsByTable?: Map<string, string[][]>): Map<string, Record<string, RelationDef>>;
233
+ /**
234
+ * Conservative auto-`manyToMany` detection over pure junction tables, shared
235
+ * by the Postgres introspector, the engine introspectors (SQLite / MySQL /
236
+ * MSSQL), the MCP server, and `schemaDefToMetadata()` so all surfaces derive
237
+ * IDENTICAL relation names for the same logical schema.
238
+ *
239
+ * A table J is a PURE junction only when ALL of these hold:
240
+ * 1. J's junction KEY is exactly two columns: either a two-column primary
241
+ * key, OR (Prisma implicit m2m junctions have NO primary key) a two-column
242
+ * UNIQUE index over exactly the two FK columns, supplied via the optional
243
+ * `uniqueIndexColsByTable`. When that map is absent the behavior is
244
+ * unchanged: only a two-column PK qualifies.
245
+ * 2. J has exactly two FKs, each single-column.
246
+ * 3. Each FK's source column is one of J's two key columns.
247
+ * 4. The two FKs target two DISTINCT tables (A and B).
248
+ * 5. J has no payload columns beyond the two FK/key columns.
249
+ *
250
+ * For such a J linking A and B this ADDS a `manyToMany` on A → B and B → A
251
+ * routed `through` J. It never removes or renames an existing relation:
252
+ * - an already-assigned relation with the same name → SKIP (additive-only,
253
+ * unchanged historical behavior);
254
+ * - a shadowed json/jsonb (`unknown`-typed) column → keep the historical
255
+ * name + warn (it worked at runtime and compiled);
256
+ * - a shadowed concrete-typed column → deterministic `Rel` suffix + warn
257
+ * instead of silently dropping the relation.
258
+ */
259
+ export declare function addAutoManyToManyRelations(tableNames: Iterable<string>, foreignKeys: ForeignKeyEntry[], pkByTable: Map<string, string[]>, columnNamesByTable: Map<string, string[]>, relationsByTable: Map<string, Record<string, RelationDef>>, columnFieldsByTable?: Map<string, Set<string>>, unknownTypedFieldsByTable?: Map<string, Set<string>>, uniqueIndexColsByTable?: Map<string, string[][]>): void;
260
+ /**
261
+ * One-stop relation derivation for the engine introspectors (SQLite / MySQL /
262
+ * MSSQL): filters the FK list to the introspected table set, seeds the
263
+ * taken-name / json-shadow maps from the engine's column metadata, and runs
264
+ * the SAME `buildRelationsFromForeignKeys` + `addAutoManyToManyRelations`
265
+ * pipeline as the Postgres introspector — so every engine derives identical
266
+ * relation names for the same logical schema (the engines previously carried
267
+ * stale copies of a retired naming scheme).
268
+ */
269
+ export declare function deriveEngineRelations(tableNames: string[], foreignKeys: ForeignKeyEntry[], pkByTable: Map<string, string[]>, columnsByTable: Map<string, Pick<ColumnMetadata, 'name' | 'field' | 'tsType'>[]>): Map<string, Record<string, RelationDef>>;
@@ -0,0 +1,232 @@
1
+ /**
2
+ * turbine-orm/mssql — Microsoft SQL Server engine (driver-injected, optional peer)
3
+ *
4
+ * Binds Turbine to SQL Server 2016+ via the `mssql` driver (which wraps
5
+ * `tedious`). `mssql` is **not** a root dependency — it is an **optional peer**:
6
+ * `npm i turbine-orm` pulls nothing extra, and only consumers who
7
+ * `import 'turbine-orm/mssql'` install `mssql` themselves. The factory loads it
8
+ * through a dynamic `import('mssql')` so importing this module never crashes when
9
+ * `mssql` is absent for a consumer who does not use it. Turbine's root runtime
10
+ * dependency stays exactly `pg`.
11
+ *
12
+ * ## The three hard SQL Server realities this engine solves
13
+ *
14
+ * 1. **No `RETURNING`.** `INSERT`/`UPDATE`/`DELETE` cannot trail a `RETURNING`
15
+ * clause; SQL Server returns affected rows via `OUTPUT INSERTED.*` /
16
+ * `OUTPUT DELETED.*` injected MID-statement (between the column list and
17
+ * `VALUES`, or between `SET …` and `WHERE …`). `mssqlDialect.resultStrategy =
18
+ * 'output'`: the statement returns its own rows in ONE round-trip (executed
19
+ * exactly like the PostgreSQL `'returning'` path). Upsert becomes a
20
+ * `MERGE … WHEN MATCHED … WHEN NOT MATCHED … OUTPUT INSERTED.* ;` (a MERGE
21
+ * must end with `;`). This is the first shipped engine to exercise the
22
+ * Phase-0 `'output'` result strategy.
23
+ * 2. **No `json_agg`.** SQL Server has no JSON aggregate function; the idiomatic
24
+ * single-query nested-relation path is `(SELECT child cols … FOR JSON PATH)`,
25
+ * whose object shape is expressed by the child SELECT's column ALIASES rather
26
+ * than an explicit `JSON_OBJECT(...)`. That does NOT map onto
27
+ * `buildJsonObject`/`buildJsonArrayAgg`, so `mssqlDialect` defines the
28
+ * additive `Dialect.buildRelationSubquery` override (the sanctioned Phase-3
29
+ * seam extension) and owns the whole correlated subquery. To-many wraps
30
+ * `ISNULL((… FOR JSON PATH), '[]')` (FOR JSON over zero rows is NULL, not
31
+ * `[]`); to-one adds `, WITHOUT_ARRAY_WRAPPER` and lets NULL be the no-row
32
+ * value. Nested relations are embedded with `JSON_QUERY(...)` so they stay
33
+ * real JSON instead of being escaped as a string. `INCLUDE_NULL_VALUES`
34
+ * keeps NULL columns present (matching PostgreSQL `json_build_object`).
35
+ * 3. **No `LIMIT`.** Paging is `ORDER BY … OFFSET n ROWS FETCH NEXT m ROWS ONLY`,
36
+ * which requires an ORDER BY — a stable `ORDER BY (SELECT NULL)` is injected
37
+ * when the query has none (`Dialect.buildLimitOffset`).
38
+ *
39
+ * ## Named `@pN` placeholders (no positional `?`)
40
+ *
41
+ * `mssqlDialect.paramPlaceholder = (i) => '@p' + i`. The driver shim binds via
42
+ * `request.input('p' + i, value)`, so binding is by NAME and independent of where
43
+ * each placeholder lands in the SQL text — exactly the guarantee PostgreSQL's
44
+ * numbered `$N` gives. (SQL Server is naturally named-param friendly, sidestepping
45
+ * the positional-`?` mis-bind bug the SQLite/MySQL phases hit.)
46
+ *
47
+ * ## Capabilities & limits (vs PostgreSQL)
48
+ *
49
+ * - **Single query nested relations preserved** via `FOR JSON PATH` (SQL Server
50
+ * 2016+). Ordered/limited to-many uses `ORDER BY … OFFSET/FETCH` inside the FOR
51
+ * JSON subquery (no inner-subquery rewrite needed — FOR JSON aggregates AFTER
52
+ * the row selection).
53
+ * - **Result strategy `'output'`:** create/update/delete/upsert return their rows
54
+ * from the same statement. `createMany` returns the inserted rows via
55
+ * `OUTPUT INSERTED.*` on the multi-row VALUES insert (≤ 1000 rows / 2100 params
56
+ * per statement — exceeding either throws a clear `ValidationError`; chunk
57
+ * yourself or use single `create`s).
58
+ * - **MERGE concurrency caveat:** `MERGE` is the upsert primitive; under high
59
+ * concurrency a `MERGE` can still race (it is NOT a substitute for a unique
60
+ * constraint). Keep the conflict target backed by a real `UNIQUE`/`PK` index,
61
+ * and rely on the typed `UniqueConstraintError` (2627/2601 → E008) for the
62
+ * loser of a race.
63
+ * - **Unsupported (throw `UnsupportedFeatureError`):** pgvector distance ops,
64
+ * LISTEN/NOTIFY (`$listen`/`$notify`), RLS `sessionContext` (sp_set_session_context
65
+ * exists but is connection-scoped, not transaction-local, so it is not wired —
66
+ * throws rather than silently leaking context across pooled connections).
67
+ * - **Advisory-lock migration locking** is available in principle via
68
+ * `sp_getapplock`/`sp_releaseapplock` (`supportsAdvisoryLock = true`); the
69
+ * migrate CLI is still PostgreSQL-only, so this flag documents intent for a
70
+ * future adapter.
71
+ * - **Case-insensitive matching** uses `LOWER(col) LIKE LOWER(ref)` — deterministic
72
+ * regardless of the column's collation (note this can defeat an index unless a
73
+ * computed/persisted `LOWER()` index exists).
74
+ * - **bignum:** the shim applies the same safe-int policy Turbine uses for Postgres
75
+ * `int8` (number when it fits in 2^53, decimal string otherwise) WITHOUT mutating
76
+ * any global driver state. `DECIMAL`/`NUMERIC`/`MONEY` come back as strings;
77
+ * `BIT` binds/returns booleans.
78
+ * - **`DISTINCT ON`** is PostgreSQL-only and is not translated — avoid `distinct`
79
+ * on SQL Server.
80
+ *
81
+ * ## Example
82
+ *
83
+ * ```ts
84
+ * import { turbineMssql } from 'turbine-orm/mssql';
85
+ * import { SCHEMA } from './generated/turbine/metadata.js';
86
+ *
87
+ * const db = await turbineMssql('mssql://sa:Passw0rd!@localhost:1433/app', SCHEMA);
88
+ * const users = await db.users.findMany({ with: { posts: true }, limit: 10 });
89
+ * await db.disconnect();
90
+ * ```
91
+ */
92
+ import { type PgCompatPool, type PgCompatPoolClient, TurbineClient, type TurbineConfig } from './client.js';
93
+ import { type Dialect, type IntrospectOptions } from './dialect.js';
94
+ import { type SchemaMetadata } from './schema.js';
95
+ interface MssqlRequest {
96
+ input(name: string, value: unknown): MssqlRequest;
97
+ query<R = Record<string, unknown>>(text: string): Promise<MssqlQueryResult<R>>;
98
+ batch<R = Record<string, unknown>>(text: string): Promise<MssqlQueryResult<R>>;
99
+ }
100
+ /** Per-column metadata `mssql` attaches to a recordset (`recordset.columns`). */
101
+ interface MssqlColumnMeta {
102
+ [name: string]: {
103
+ type?: {
104
+ name?: string;
105
+ declaration?: string;
106
+ };
107
+ } | undefined;
108
+ }
109
+ interface MssqlQueryResult<R = Record<string, unknown>> {
110
+ recordset?: R[] & {
111
+ columns?: MssqlColumnMeta;
112
+ };
113
+ recordsets?: R[][];
114
+ rowsAffected?: number[];
115
+ }
116
+ interface MssqlTransaction {
117
+ begin(isolationLevel?: number): Promise<unknown>;
118
+ commit(): Promise<unknown>;
119
+ rollback(): Promise<unknown>;
120
+ }
121
+ interface MssqlConnectionPool {
122
+ connect(): Promise<MssqlConnectionPool>;
123
+ request(): MssqlRequest;
124
+ close(): Promise<unknown>;
125
+ readonly connected?: boolean;
126
+ }
127
+ /** The subset of the `mssql` module namespace the shim constructs. */
128
+ interface MssqlModule {
129
+ connect(config: any): Promise<MssqlConnectionPool>;
130
+ ConnectionPool: new (config: any) => MssqlConnectionPool;
131
+ Request: new (parent: MssqlConnectionPool | MssqlTransaction) => MssqlRequest;
132
+ Transaction: new (pool: MssqlConnectionPool) => MssqlTransaction;
133
+ ISOLATION_LEVEL: Record<string, number>;
134
+ }
135
+ /** pg-style query argument: a SQL string or a `{ text, values }` config object. */
136
+ type QueryArg = string | {
137
+ name?: string;
138
+ text: string;
139
+ values?: unknown[];
140
+ };
141
+ /**
142
+ * A {@link PgCompatPool} backed by an `mssql` ConnectionPool. Non-transaction
143
+ * queries run on a fresh pooled `Request`; `connect()` returns a
144
+ * {@link MssqlTxClient} that drives a single transaction through the mssql
145
+ * `Transaction` API so `BEGIN`/`COMMIT`/`ROLLBACK`/savepoints all run on the same
146
+ * physical connection.
147
+ */
148
+ export declare class MssqlPool implements PgCompatPool {
149
+ /** The underlying `mssql` ConnectionPool — exposed as an escape hatch (seed / DDL / advanced ops). */
150
+ readonly pool: MssqlConnectionPool;
151
+ private readonly sqlNS;
152
+ private closed;
153
+ constructor(pool: MssqlConnectionPool, sqlNS: MssqlModule);
154
+ query(text: QueryArg, values?: unknown[]): Promise<any>;
155
+ connect(): Promise<PgCompatPoolClient>;
156
+ end(): Promise<void>;
157
+ }
158
+ /**
159
+ * Map a SQL Server column type to a TypeScript type. `dialectType` is the
160
+ * `INFORMATION_SCHEMA.COLUMNS.DATA_TYPE` (lowercase, e.g. `bigint`, `nvarchar`,
161
+ * `datetime2`, `bit`, `uniqueidentifier`).
162
+ */
163
+ export declare function mssqlTypeToTs(dialectType: string, nullable: boolean): string;
164
+ /**
165
+ * SQL Server 2016+ implementation of the {@link Dialect} contract. Bracket
166
+ * identifier quoting (`[…]`), named `@pN` placeholders, the `FOR JSON PATH`
167
+ * nested-relation override (no `json_agg`), no `RETURNING`
168
+ * (`resultStrategy = 'output'` via `OUTPUT INSERTED.*` / `MERGE`), `OFFSET/FETCH`
169
+ * paging, and the Postgres-only capabilities disabled (vector / LISTEN-NOTIFY /
170
+ * RLS).
171
+ */
172
+ export declare const mssqlDialect: Dialect;
173
+ /** Async executor that returns plain row objects for a parameterized (`@pN`) query. */
174
+ export type MssqlRowExecutor = (sql: string, params: unknown[]) => Promise<Record<string, unknown>[]>;
175
+ /**
176
+ * Introspect a SQL Server database into the same {@link SchemaMetadata} shape the
177
+ * Postgres catalog introspector produces, using a caller-supplied query executor
178
+ * (so tests can dogfood an already-open `mssql` pool/connection). Reads
179
+ * `INFORMATION_SCHEMA.*` plus `sys.identity_columns` / `sys.foreign_keys` /
180
+ * `sys.indexes`.
181
+ *
182
+ * @param exec Runs a parameterized (`@p1`, `@p2`, …) query and returns rows.
183
+ * @param schema The SQL Server schema to introspect (default `dbo`).
184
+ */
185
+ export declare function introspectMssqlWith(exec: MssqlRowExecutor, schema?: string, options?: {
186
+ include?: string[];
187
+ exclude?: string[];
188
+ }): Promise<SchemaMetadata>;
189
+ /**
190
+ * Open a short-lived `mssql` connection from `options.connectionString`, introspect
191
+ * the database (schema = `options.schema` or `dbo`), and close it. Wraps
192
+ * {@link introspectMssqlWith} for the {@link DialectIntrospector} seam used by
193
+ * `introspect()` / `npx turbine generate`.
194
+ */
195
+ export declare function introspectMssql(options: IntrospectOptions): Promise<SchemaMetadata>;
196
+ interface MssqlConnectionConfig {
197
+ server?: string;
198
+ port?: number;
199
+ user?: string;
200
+ password?: string;
201
+ database?: string;
202
+ options?: {
203
+ encrypt?: boolean;
204
+ trustServerCertificate?: boolean;
205
+ };
206
+ }
207
+ /** Options for {@link turbineMssql}. Mirrors the relevant {@link TurbineConfig} fields. */
208
+ export interface TurbineMssqlOptions extends Pick<TurbineConfig, 'logging' | 'defaultLimit' | 'warnOnUnlimited'> {
209
+ /** SQL Server schema for introspection / DDL (default `dbo`). */
210
+ schema?: string;
211
+ }
212
+ /**
213
+ * Create a {@link TurbineClient} bound to SQL Server 2016+ via `mssql`.
214
+ *
215
+ * Pass one of:
216
+ * - a connection string (`'mssql://sa:pass@host:1433/db'`),
217
+ * - an `mssql` config object (`{ server, user, password, database, options }`),
218
+ * - an existing `MssqlPool` (injection — you own its lifecycle, `disconnect()` is
219
+ * a no-op).
220
+ *
221
+ * When Turbine builds the pool (string/config), it probes
222
+ * `SERVERPROPERTY('ProductMajorVersion')` to reject SQL Server < 2016, and
223
+ * `disconnect()` closes the pool it created.
224
+ *
225
+ * @example
226
+ * ```ts
227
+ * import { turbineMssql } from 'turbine-orm/mssql';
228
+ * const db = await turbineMssql('mssql://sa:Passw0rd!@localhost:1433/app', SCHEMA);
229
+ * ```
230
+ */
231
+ export declare function turbineMssql(target: string | MssqlConnectionConfig | MssqlPool, schema: SchemaMetadata, options?: TurbineMssqlOptions): Promise<TurbineClient>;
232
+ export {};
package/dist/cjs/mssql.js CHANGED
@@ -491,6 +491,12 @@ exports.mssqlDialect = {
491
491
  // round-trip (Phase-0 'output' strategy, executed like 'returning').
492
492
  resultStrategy: 'output',
493
493
  supportsReturning: false,
494
+ // MERGE can carry `WHEN MATCHED AND <pred>`, but the predicate the builder
495
+ // hands over is written in UNQUALIFIED column references, which are ambiguous
496
+ // between the MERGE target (T) and source (S) aliases. `buildUpsertStatement`
497
+ // below therefore emits no predicate; reporting false keeps the builder from
498
+ // compiling one (and from binding its now-orphaned parameters).
499
+ supportsUpsertUpdateWhere: false,
494
500
  supportsILike: false,
495
501
  supportsVector: false,
496
502
  // SQL Server full-text is `CONTAINS`/`FREETEXT` over a full-text catalog: a