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
@@ -75,14 +75,23 @@ function generateCreateEnumType(enumName, labels, dialect) {
75
75
  /**
76
76
  * Resolve the DDL type token for a column: an enum type name, a `vector(n)`
77
77
  * literal, or the dialect's scalar type — with a trailing `[]` for arrays.
78
+ *
79
+ * `vectorDimensions` is the one number interpolated into the type token, so it
80
+ * is validated here as a positive integer within pgvector's limit rather than
81
+ * being trusted to be numeric.
78
82
  */
79
- function resolveDdlType(config, dialect) {
83
+ function resolveDdlType(config, dialect, columnName) {
80
84
  let base;
81
85
  if (config.enumName) {
82
86
  base = dialect.quoteIdentifier(config.enumName);
83
87
  }
84
88
  else if (config.vectorDimensions != null) {
85
- base = `vector(${config.vectorDimensions})`;
89
+ const dims = config.vectorDimensions;
90
+ // pgvector caps a `vector` column at 16000 dimensions.
91
+ if (typeof dims !== 'number' || !Number.isInteger(dims) || dims < 1 || dims > 16000) {
92
+ throw new errors_js_1.ValidationError(`[turbine] Column "${columnName}": vector dimensions must be an integer between 1 and 16000, got ${String(dims)}.`);
93
+ }
94
+ base = `vector(${dims})`;
86
95
  }
87
96
  else {
88
97
  base = dialect.buildColumnType({ type: config.type, maxLength: config.maxLength });
@@ -241,6 +250,9 @@ function generateCreateTable(table, resolveRef, dialect = dialect_js_1.postgresD
241
250
  columnDefs.push(dialect.buildPrimaryKeyConstraint(cols));
242
251
  }
243
252
  // Table-level CHECK constraints (named → CONSTRAINT "name" CHECK (...)).
253
+ // The constraint NAME is quoted; the EXPRESSION is raw SQL emitted verbatim
254
+ // by design (see the CheckSpec.expression contract) and must never be built
255
+ // from untrusted input.
244
256
  for (const chk of table.checks ?? []) {
245
257
  columnDefs.push(chk.name
246
258
  ? `CONSTRAINT ${dialect.quoteIdentifier(chk.name)} CHECK (${chk.expression})`
@@ -287,7 +299,7 @@ function generateColumnDef(fieldName, config, resolveRef, dialect = dialect_js_1
287
299
  // Resolve the DDL type (enum name / vector(n) / scalar, plus [] for arrays).
288
300
  // Passed as a fully-formed `type` token with no maxLength so the dialect
289
301
  // doesn't re-apply VARCHAR(n) on top of it.
290
- const ddlType = resolveDdlType(config, dialect);
302
+ const ddlType = resolveDdlType(config, dialect, snakeName);
291
303
  let def = dialect.buildColumnDefinition({
292
304
  name: dialect.quoteIdentifier(snakeName),
293
305
  type: ddlType,
@@ -306,7 +318,9 @@ function generateColumnDef(fieldName, config, resolveRef, dialect = dialect_js_1
306
318
  if (config.onUpdate)
307
319
  def += ` ON UPDATE ${referentialActionToSql(config.onUpdate)}`;
308
320
  }
309
- // Column-level CHECK constraint (raw SQL expression, user-authored).
321
+ // Column-level CHECK constraint. The expression is RAW SQL authored in the
322
+ // schema and is emitted verbatim by design (a DDL builder cannot parse or
323
+ // parameterize a boolean expression); never build it from untrusted input.
310
324
  if (config.check) {
311
325
  def += ` CHECK (${config.check})`;
312
326
  }
@@ -547,6 +561,8 @@ function diffCheckConstraints(table, schemaChecks, dbChecks, dialect = dialect_j
547
561
  const dbByName = new Map(dbChecks.map((c) => [c.name, c]));
548
562
  const schemaByName = new Map(schemaChecks.map((c) => [c.name, c]));
549
563
  const norm = (e) => e.replace(/\s+/g, ' ').trim();
564
+ // `c.expression` is raw SQL emitted verbatim by design (see CheckSpec):
565
+ // schema-authored only, never untrusted input.
550
566
  const addStmt = (c) => `ALTER TABLE ${q(table)} ADD CONSTRAINT ${q(c.name)} CHECK (${c.expression});`;
551
567
  const dropStmt = (name) => `ALTER TABLE ${q(table)} DROP CONSTRAINT ${q(name)};`;
552
568
  for (const sc of schemaChecks) {
@@ -759,7 +775,7 @@ async function schemaDiff(schema, connectionString) {
759
775
  if (expectedUdt && !isSerialType(config.type) && dbCol.udtName !== expectedUdt) {
760
776
  // resolveDdlType handles enum names, vector(n), arrays, and VARCHAR(n) —
761
777
  // config.type alone would emit the internal ENUM/VECTOR sentinels here.
762
- const sqlType = resolveDdlType(config, dialect);
778
+ const sqlType = resolveDdlType(config, dialect, snakeName);
763
779
  const oldSqlType = udtToSqlType(dbCol.udtName, dbCol.maxLength);
764
780
  const sql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} ALTER COLUMN ${dialect.quoteIdentifier(snakeName)} TYPE ${sqlType} USING ${dialect.quoteIdentifier(snakeName)}::${sqlType};`;
765
781
  const reverseSql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} ALTER COLUMN ${dialect.quoteIdentifier(snakeName)} TYPE ${oldSqlType} USING ${dialect.quoteIdentifier(snakeName)}::${oldSqlType};`;
@@ -897,6 +913,8 @@ async function schemaDiff(schema, connectionString) {
897
913
  const normExpr = (e) => e.replace(/\s+/g, ' ').trim();
898
914
  for (const sc of namedSchemaChecks) {
899
915
  const existing = dbCheckByName.get(sc.name);
916
+ // The constraint name is quoted; `sc.expression` is raw SQL emitted
917
+ // verbatim by design (see CheckSpec) and must be schema-authored.
900
918
  const addSql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} ADD CONSTRAINT ${dialect.quoteIdentifier(sc.name)} CHECK (${sc.expression});`;
901
919
  const dropSql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} DROP CONSTRAINT ${dialect.quoteIdentifier(sc.name)};`;
902
920
  if (!existing) {
@@ -0,0 +1,356 @@
1
+ /**
2
+ * turbine-orm — Schema metadata types
3
+ *
4
+ * These types represent the introspected database schema at runtime.
5
+ * They're used by the query builder, code generator, and CLI.
6
+ */
7
+ export interface SchemaMetadata {
8
+ /** All discovered tables, keyed by table name */
9
+ tables: Record<string, TableMetadata>;
10
+ /** Database-level enums (typname → labels) */
11
+ enums: Record<string, string[]>;
12
+ }
13
+ export interface TableMetadata {
14
+ /** Table name in the database (snake_case) */
15
+ name: string;
16
+ /** All columns in ordinal order */
17
+ columns: ColumnMetadata[];
18
+ /** camelCase field name → snake_case column name */
19
+ columnMap: Record<string, string>;
20
+ /** snake_case column name → camelCase field name */
21
+ reverseColumnMap: Record<string, string>;
22
+ /** snake_case columns that are timestamp/date types (need Date parsing) */
23
+ dateColumns: Set<string>;
24
+ /** snake_case column → dialect-native database type. */
25
+ dialectTypes?: Record<string, string>;
26
+ /** snake_case column → Postgres type for UNNEST casts. Back-compat alias for dialectTypes. */
27
+ pgTypes: Record<string, string>;
28
+ /** All snake_case column names in ordinal order */
29
+ allColumns: string[];
30
+ /** Primary key column(s) in snake_case */
31
+ primaryKey: string[];
32
+ /** Unique constraint columns */
33
+ uniqueColumns: string[][];
34
+ /** Relations defined on this table */
35
+ relations: Record<string, RelationDef>;
36
+ /** Indexes on this table */
37
+ indexes: IndexMetadata[];
38
+ /**
39
+ * Named `CHECK` constraints on this table (introspected from
40
+ * `pg_constraint` where `contype = 'c'`, excluding NOT NULL artifacts).
41
+ * Optional / defaults to `[]` for back-compat.
42
+ */
43
+ checks?: readonly CheckMetadata[];
44
+ /**
45
+ * True when this metadata entry describes a database **view** or
46
+ * **materialized view** rather than a base table (introspected with the
47
+ * `includeViews` option). Views are read-only: every write builder
48
+ * (`create`/`update`/`upsert`/`delete` + their `*Many` forms) throws a
49
+ * `ValidationError` (E003). A view without a primary key is additionally
50
+ * excluded from the `findUnique`-family generated accessor types.
51
+ * Optional / defaults to `false` for back-compat.
52
+ */
53
+ isView?: boolean;
54
+ }
55
+ export interface CheckMetadata {
56
+ /** Constraint name (system-generated or user-supplied). */
57
+ name: string;
58
+ /** The check expression source (`pg_get_constraintdef` inner text). */
59
+ expression: string;
60
+ }
61
+ export interface ColumnMetadata {
62
+ /** snake_case column name */
63
+ name: string;
64
+ /** camelCase field name for TypeScript */
65
+ field: string;
66
+ /** Dialect-native database type (e.g. PostgreSQL 'int8', MySQL 'bigint', SQLite 'INTEGER'). */
67
+ dialectType?: string;
68
+ /** Postgres base type (e.g. 'int8', 'text', 'timestamptz'). Back-compat alias for dialectType. */
69
+ pgType: string;
70
+ /**
71
+ * Schema the column's Postgres type lives in, recorded by introspection
72
+ * ONLY when it differs from the introspected schema (and isn't a
73
+ * `pg_catalog` builtin) — e.g. an enum or domain owned by another schema.
74
+ * Consumers use it as a cross-schema guard: a same-named enum in another
75
+ * schema must not receive this schema's `::"enum"` cast (search_path would
76
+ * resolve it to the wrong type). Absent for same-schema types, builtins,
77
+ * `defineSchema()` output, and legacy generated metadata.
78
+ */
79
+ pgTypeSchema?: string;
80
+ /** TypeScript type string (e.g. 'number', 'string', 'Date') */
81
+ tsType: string;
82
+ /** Whether the column allows NULL */
83
+ nullable: boolean;
84
+ /** Whether the column has a DEFAULT, is serial, or is generated */
85
+ hasDefault: boolean;
86
+ /**
87
+ * Whether the **database server** generates this column's value on insert —
88
+ * a `serial`/`BIGSERIAL` sequence, an `IDENTITY` column, or PowDB's `auto`
89
+ * modifier. This is a strict subset of {@link hasDefault} (a server-generated
90
+ * column always reports `hasDefault: true`), but unlike a client-side default
91
+ * expression (`gen_random_uuid()`, `now()`) the value is assigned by the
92
+ * engine, so Turbine must NOT synthesize one client-side and the PowDB DDL
93
+ * emits the `auto` modifier. Optional / defaults to `false` for back-compat.
94
+ */
95
+ isGenerated?: boolean;
96
+ /**
97
+ * True when this is a Postgres **`GENERATED ALWAYS AS (expr) STORED`** column
98
+ * (`information_schema.columns.is_generated = 'ALWAYS'`). Distinct from
99
+ * {@link isGenerated} — which flags a server-*assigned* identity/serial value
100
+ * that a client MAY still override — a STORED generated column's value is
101
+ * *computed from other columns* and can NEVER be supplied on insert/update
102
+ * (Postgres rejects it). Codegen therefore omits it from `*Create`/`*Update`
103
+ * input types, and the write builders reject any `data` containing it with a
104
+ * {@link ValidationError} (E003). Optional / defaults to `false`.
105
+ */
106
+ isGeneratedStored?: boolean;
107
+ /**
108
+ * The generation expression for a {@link isGeneratedStored} column
109
+ * (`information_schema.columns.generation_expression`), e.g. `price * qty`.
110
+ * Present only when `isGeneratedStored` is true and the catalog exposed it.
111
+ */
112
+ generationExpression?: string;
113
+ /**
114
+ * True when this column holds personally identifiable information (PII).
115
+ * Tagged in `defineSchema` (`pii: true`) and carried through generated
116
+ * metadata. A PII column is EXCLUDED from default projections: it comes back
117
+ * only when explicitly named in `select` or when the query passes
118
+ * `includePii: true` (full opt-in). Studio redacts PII cells by default.
119
+ * Optional / defaults to `false`; untagged schemas behave exactly as before.
120
+ * Introspection never auto-tags PII (it is a code-first declaration).
121
+ */
122
+ pii?: boolean;
123
+ /** Whether this is an array column */
124
+ isArray: boolean;
125
+ /** Dialect-specific array/bulk-insert type token when needed. */
126
+ arrayType?: string;
127
+ /** Postgres array type for UNNEST (e.g. 'bigint[]'). Back-compat alias for arrayType. */
128
+ pgArrayType: string;
129
+ /** Max character length (for varchar) */
130
+ maxLength?: number;
131
+ }
132
+ /**
133
+ * PostgreSQL referential action for a foreign key's `ON DELETE` / `ON UPDATE`
134
+ * clause. `'no action'` is the implicit default (matches Postgres) and is
135
+ * omitted from emitted DDL.
136
+ */
137
+ export type ReferentialAction = 'cascade' | 'restrict' | 'set null' | 'set default' | 'no action';
138
+ export interface RelationDef {
139
+ type: 'hasMany' | 'hasOne' | 'belongsTo' | 'manyToMany';
140
+ /**
141
+ * FK `ON DELETE` action (introspected from `pg_constraint.confdeltype`).
142
+ * Present on `belongsTo`/`hasMany` relations derived from a real FK; omitted
143
+ * when unknown (e.g. `defineSchema`-only metadata) or `'no action'`.
144
+ */
145
+ onDelete?: ReferentialAction;
146
+ /** FK `ON UPDATE` action (introspected from `pg_constraint.confupdtype`). */
147
+ onUpdate?: ReferentialAction;
148
+ /** Relation name (camelCase, used as the field name) */
149
+ name: string;
150
+ /** Source table */
151
+ from: string;
152
+ /** Target table */
153
+ to: string;
154
+ /** FK column(s) on the "many" / "child" side (snake_case). Array for composite FKs. */
155
+ foreignKey: string | string[];
156
+ /** Referenced column(s) on the "one" / "parent" side (snake_case). Array for composite FKs. */
157
+ referenceKey: string | string[];
158
+ /**
159
+ * For `manyToMany` relations only: the junction (join) table that links the
160
+ * source and target tables. The subquery JOINs the target through this table.
161
+ *
162
+ * - `table` — junction table name (snake_case).
163
+ * - `sourceKey` — junction column(s) referencing the SOURCE table's
164
+ * {@link referenceKey} (typically the source PK).
165
+ * - `targetKey` — junction column(s) referencing the TARGET table's PK.
166
+ *
167
+ * Array forms support composite keys (paired positionally with the
168
+ * referenced columns). Omitted for non-m2m relations.
169
+ */
170
+ through?: {
171
+ table: string;
172
+ sourceKey: string | string[];
173
+ targetKey: string | string[];
174
+ };
175
+ }
176
+ /** Normalize foreignKey/referenceKey to always be an array for uniform processing */
177
+ export declare function normalizeKeyColumns(key: string | string[]): string[];
178
+ export interface IndexMetadata {
179
+ name: string;
180
+ columns: string[];
181
+ unique: boolean;
182
+ definition: string;
183
+ /**
184
+ * True when the index carries a top-level `WHERE` predicate (a Postgres
185
+ * PARTIAL index). A partial UNIQUE index only guarantees uniqueness over the
186
+ * rows matching its predicate, NOT over the whole table, so it must be
187
+ * EXCLUDED from compound-unique selector derivation (both the runtime
188
+ * `where` expansion and the generated `*WhereUnique` selector branches):
189
+ * addressing a row by it could match zero or many rows. Introspection sets
190
+ * it from the `indexdef`; absent / `false` means a full (table-wide) index.
191
+ */
192
+ partial?: boolean;
193
+ /**
194
+ * Set only for a PowDB doc-field expression index: the JSON path (string keys
195
+ * and integer array indexes) into the single json document column named by
196
+ * `columns[0]`. When present, `columns` is `[<json column>]` and the index
197
+ * targets `columns[0]-><segments>` rather than the raw column.
198
+ *
199
+ * Consumed by the PowDB DDL generator (`powqlSchemaDDL` emits
200
+ * `alter T add index (.col->"seg")`). The missing-FK index advisor ignores
201
+ * doc-field indexes entirely (a JSON expression index never covers an
202
+ * equality probe on the raw column). Doc-field indexes are invisible to
203
+ * `describe`-based introspection, so they do NOT round-trip through
204
+ * introspection.
205
+ */
206
+ docPath?: (string | number)[];
207
+ /**
208
+ * Set for indexes DECLARED in a code-first `defineSchema` (`TableDef.indexes`)
209
+ * rather than read from a live database by introspection. The SQL DDL
210
+ * generators (`schema-sql.ts` / `schemaDiff`) do NOT emit these yet, so a
211
+ * declared index does not reflect a real database index on the SQL engines.
212
+ * The missing-FK index advisor therefore treats declared indexes as
213
+ * "index-info unknown" (same as an index-less schema): counting them would
214
+ * both arm blanket FK false positives and suppress warnings for indexes that
215
+ * were never created. Introspected metadata never sets this.
216
+ */
217
+ declared?: boolean;
218
+ }
219
+ /** Map a Postgres type to its TypeScript equivalent */
220
+ export declare function pgTypeToTs(pgType: string, nullable: boolean): string;
221
+ /** Check if a Postgres type is a date/timestamp that needs Date parsing */
222
+ export declare function isDateType(pgType: string): boolean;
223
+ /**
224
+ * Classify a column type as a TIME-OF-DAY type (`time` / `timetz`), or `null`
225
+ * for anything else.
226
+ *
227
+ * These are deliberately NOT part of {@link isDateType}/`dateColumns`: a
228
+ * time-of-day value has no date part, so it is never coerced to a JS `Date` on
229
+ * read (Postgres hands back `09:00:00` and that is what the row carries). They
230
+ * still need their own classification on the WRITE path, because a JS `Date`
231
+ * bound straight through serializes as a full ISO timestamp
232
+ * (`1970-01-01T04:00:00.000-05:00`), which Postgres rejects for a `time`
233
+ * column with `22007 invalid input syntax for type time`.
234
+ *
235
+ * Recognizes the `udt_name` spellings introspection records (`time`, `timetz`),
236
+ * the SQL-standard long spellings, and a trailing precision suffix
237
+ * (`time(6)`), so MySQL and SQL Server `TIME` columns classify from their
238
+ * dialect type as well.
239
+ */
240
+ export declare function timeOfDayKind(dbType: string | undefined): 'time' | 'timetz' | null;
241
+ /**
242
+ * Classify a column type as a ZONE-LESS date/timestamp type (`date`,
243
+ * `timestamp` = `timestamp without time zone`), or `null` for anything else.
244
+ *
245
+ * `timestamptz` is deliberately excluded: it carries a real instant, and the
246
+ * driver's local-offset serialization is CORRECT for it (Postgres converts the
247
+ * offset away). The two types here store the literal wall-clock fields they
248
+ * are given, so binding a JS `Date` through the driver stores the process's
249
+ * LOCAL calendar fields — in `America/Los_Angeles`, `2026-07-25T00:00:00Z`
250
+ * lands as `2026-07-24 17:00:00`. The read path already interprets an
251
+ * offset-less value as UTC (see `parseDbDate`), so the write path has to bind
252
+ * the UTC components for the round trip to be stable.
253
+ *
254
+ * Recognizes the `udt_name` spellings introspection records, the SQL-standard
255
+ * long spellings, and a trailing precision suffix (`timestamp(3)`).
256
+ * Deliberately does NOT recognize `datetime`: MySQL's `TIMESTAMP`/`DATETIME`
257
+ * are converted by the session time zone, so a UTC-component literal would be
258
+ * misread there. The callers additionally gate on the PostgreSQL dialect.
259
+ */
260
+ export declare function localDateTimeKind(dbType: string | undefined): 'date' | 'timestamp' | null;
261
+ /** Get the Postgres array cast type for UNNEST batch inserts */
262
+ export declare function pgArrayType(pgType: string): string;
263
+ /** snake_case → camelCase */
264
+ export declare function snakeToCamel(s: string): string;
265
+ /** camelCase → snake_case */
266
+ export declare function camelToSnake(s: string): string;
267
+ /** snake_case → PascalCase (for type names) */
268
+ export declare function snakeToPascal(s: string): string;
269
+ /** Naive singularize: "posts" → "post", "categories" → "category" */
270
+ export declare function singularize(s: string): string;
271
+ /**
272
+ * Return a copy of `schema` in which every column's TypeScript **field** name is
273
+ * the raw database column name (snake_case) instead of the camelCased default,
274
+ * i.e. `user_id` stays `user_id` rather than becoming `userId`.
275
+ *
276
+ * This is a PURE, generate-time transform with ZERO runtime changes: it only
277
+ * rewrites `column.field` and rebuilds each table's `columnMap` /
278
+ * `reverseColumnMap` as IDENTITY maps (`user_id → user_id`). Because every
279
+ * runtime surface resolves column names through those maps (`toColumn`,
280
+ * `parseRow`, relation `json_build_object` keys, batched-loader stitching,
281
+ * positional decoding, aggregate/groupBy naming), the generated client returns
282
+ * rows keyed by DB column names and accepts DB column names in
283
+ * `where`/`orderBy`/`select` with no code path aware of the difference.
284
+ *
285
+ * Deliberately left untouched (the flag is literally about column names):
286
+ * - `name`, `allColumns`, `dateColumns`, `dialectTypes`, `pgTypes`,
287
+ * `primaryKey`, `uniqueColumns`, `indexes`, `checks`, `isView` (all already
288
+ * keyed by snake_case column names);
289
+ * - `relations` (relation PROPERTY names are synthetic introspection names
290
+ * with no DB column equivalent, and `foreignKey`/`referenceKey`/`through`
291
+ * already hold DB column names);
292
+ * - entity type names, table accessors, and `enums`.
293
+ *
294
+ * Every non-PII field of each {@link ColumnMetadata} (`pii`, `pgType`,
295
+ * `dialectType`, `nullable`, …) is preserved. Exported from the package root so
296
+ * runtime-introspection and serverless users can apply the same identity mapping
297
+ * to a schema they build at runtime, e.g. `turbineHttp(pool,
298
+ * withDbFieldNames(schema))`.
299
+ */
300
+ export declare function withDbFieldNames(schema: SchemaMetadata): SchemaMetadata;
301
+ /**
302
+ * A typed name map from a Prisma schema onto a Turbine client, produced by
303
+ * `turbine migrate-from-prisma` (`generatePrismaMap` writes it as a
304
+ * `prisma-map.ts` module next to the generated client). It is library-side so
305
+ * the phase-2 `turbine-orm/prisma-compat` runtime adapter can consume the same
306
+ * shape to translate Prisma model/field/relation/compound-unique names onto the
307
+ * Turbine surface without re-parsing anything.
308
+ *
309
+ * Every name it carries was RESOLVED against live introspected metadata: a model
310
+ * or field that could not be matched is omitted from the map and listed in the
311
+ * migration report instead, so the map only ever contains verified mappings.
312
+ */
313
+ export interface PrismaCompatMap {
314
+ /** Prisma model name → its resolved mapping. */
315
+ models: Record<string, PrismaModelMap>;
316
+ /** Prisma enum name → resolved database enum-type name. */
317
+ enums: Record<string, string>;
318
+ }
319
+ /** One Prisma model's resolved mapping onto a Turbine table + client accessor. */
320
+ export interface PrismaModelMap {
321
+ /** Resolved snake_case database table name. */
322
+ table: string;
323
+ /** camelCase `TurbineClient` accessor (`db.<accessor>`). */
324
+ accessor: string;
325
+ /** Prisma field name → Turbine field name (camelCase). Relation fields excluded. */
326
+ fields: Record<string, string>;
327
+ /** Prisma relation-field name → resolved Turbine relation + cardinality. */
328
+ relations: Record<string, PrismaRelationMap>;
329
+ /**
330
+ * Prisma compound-unique/compound-id selector name → the Turbine field names
331
+ * (in declared order). The selector name is Prisma's: the explicit
332
+ * `@@unique(name:)` / `@@id(name:)` argument, else the field names joined with
333
+ * `_`. Consumed by the phase-2 client to translate
334
+ * `where: { <selector>: { ... } }`, including custom `@@unique(name:)` names
335
+ * the core `findUnique`-family derivation cannot know.
336
+ */
337
+ compoundUniques: Record<string, string[]>;
338
+ /**
339
+ * Prisma CLIENT-side default per Prisma field name. Prisma's
340
+ * `@default(uuid())` / `@default(cuid())` / `@updatedAt` (and `@default(now())`
341
+ * when the migration left no database default) are filled by the Prisma
342
+ * client, not the database, so the columns commonly have NO db default and a
343
+ * Prisma call site omits them. The compat adapter fills these on create
344
+ * (`uuid` / `cuid` / `now`) and touches `updatedAt` fields on
345
+ * update/updateMany/upsert, exactly like Prisma. Emitted by
346
+ * `migrate-from-prisma` only for columns the database does not default.
347
+ */
348
+ clientDefaults?: Record<string, 'uuid' | 'cuid' | 'now' | 'updatedAt'>;
349
+ }
350
+ /** A resolved Prisma relation field → Turbine relation. */
351
+ export interface PrismaRelationMap {
352
+ /** Turbine relation name (the `with` clause key). */
353
+ name: string;
354
+ /** `'one'` (to-one) or `'many'` (to-many, including m2m). */
355
+ cardinality: 'one' | 'many';
356
+ }
@@ -9,6 +9,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.normalizeKeyColumns = normalizeKeyColumns;
10
10
  exports.pgTypeToTs = pgTypeToTs;
11
11
  exports.isDateType = isDateType;
12
+ exports.timeOfDayKind = timeOfDayKind;
13
+ exports.localDateTimeKind = localDateTimeKind;
12
14
  exports.pgArrayType = pgArrayType;
13
15
  exports.snakeToCamel = snakeToCamel;
14
16
  exports.camelToSnake = camelToSnake;
@@ -89,26 +91,81 @@ const PG_TO_TS = {
89
91
  vector: 'number[]',
90
92
  };
91
93
  const DATE_TYPES = new Set(['timestamptz', 'timestamp', 'date']);
94
+ /**
95
+ * Postgres type → the array cast `createMany`'s `UNNEST(ARRAY[...]::<type>)`
96
+ * applies to that column's value list.
97
+ *
98
+ * Every entry must be present for a type that has NO assignment cast from
99
+ * `text`: the `text[]` fallback below then produces text-typed UNNEST output
100
+ * and Postgres refuses the insert with `42804 column "x" is of type <t> but
101
+ * expression is of type text`. `varchar` / `char` / `bpchar` deliberately keep
102
+ * the `text[]` cast — `text` assignment-casts to all three, and pinning them
103
+ * would change already-emitted SQL for no behavioral gain.
104
+ */
92
105
  const PG_TO_ARRAY = {
106
+ // Numeric
93
107
  int2: 'smallint[]',
94
108
  int4: 'integer[]',
95
109
  int8: 'bigint[]',
96
110
  float4: 'real[]',
97
111
  float8: 'double precision[]',
98
112
  numeric: 'numeric[]',
113
+ money: 'money[]',
114
+ oid: 'oid[]',
99
115
  bool: 'boolean[]',
116
+ // Character
100
117
  text: 'text[]',
101
118
  varchar: 'text[]',
102
119
  char: 'text[]',
103
120
  bpchar: 'text[]',
121
+ name: 'name[]',
122
+ citext: 'citext[]',
123
+ xml: 'xml[]',
104
124
  uuid: 'uuid[]',
125
+ // Date / time. `time` and `timetz` were missing, so a `createMany` on a
126
+ // time-of-day column cast its values `text[]` and failed 42804 even though
127
+ // the per-value narrowing produced a valid `'09:00:00'` literal.
105
128
  timestamptz: 'timestamptz[]',
106
129
  timestamp: 'timestamp[]',
107
130
  date: 'date[]',
131
+ time: 'time[]',
132
+ timetz: 'timetz[]',
133
+ interval: 'interval[]',
134
+ // JSON / binary
108
135
  json: 'json[]',
109
136
  jsonb: 'jsonb[]',
110
137
  bytea: 'bytea[]',
138
+ // Network
111
139
  inet: 'inet[]',
140
+ cidr: 'cidr[]',
141
+ macaddr: 'macaddr[]',
142
+ macaddr8: 'macaddr8[]',
143
+ // Geometric
144
+ point: 'point[]',
145
+ line: 'line[]',
146
+ lseg: 'lseg[]',
147
+ box: 'box[]',
148
+ path: 'path[]',
149
+ polygon: 'polygon[]',
150
+ circle: 'circle[]',
151
+ // Text search
152
+ tsvector: 'tsvector[]',
153
+ tsquery: 'tsquery[]',
154
+ // Ranges / multiranges
155
+ int4range: 'int4range[]',
156
+ int8range: 'int8range[]',
157
+ numrange: 'numrange[]',
158
+ tsrange: 'tsrange[]',
159
+ tstzrange: 'tstzrange[]',
160
+ daterange: 'daterange[]',
161
+ int4multirange: 'int4multirange[]',
162
+ int8multirange: 'int8multirange[]',
163
+ nummultirange: 'nummultirange[]',
164
+ tsmultirange: 'tsmultirange[]',
165
+ tstzmultirange: 'tstzmultirange[]',
166
+ datemultirange: 'datemultirange[]',
167
+ // pgvector
168
+ vector: 'vector[]',
112
169
  };
113
170
  /** Map a Postgres type to its TypeScript equivalent */
114
171
  function pgTypeToTs(pgType, nullable) {
@@ -125,6 +182,74 @@ function pgTypeToTs(pgType, nullable) {
125
182
  function isDateType(pgType) {
126
183
  return DATE_TYPES.has(pgType);
127
184
  }
185
+ /**
186
+ * Classify a column type as a TIME-OF-DAY type (`time` / `timetz`), or `null`
187
+ * for anything else.
188
+ *
189
+ * These are deliberately NOT part of {@link isDateType}/`dateColumns`: a
190
+ * time-of-day value has no date part, so it is never coerced to a JS `Date` on
191
+ * read (Postgres hands back `09:00:00` and that is what the row carries). They
192
+ * still need their own classification on the WRITE path, because a JS `Date`
193
+ * bound straight through serializes as a full ISO timestamp
194
+ * (`1970-01-01T04:00:00.000-05:00`), which Postgres rejects for a `time`
195
+ * column with `22007 invalid input syntax for type time`.
196
+ *
197
+ * Recognizes the `udt_name` spellings introspection records (`time`, `timetz`),
198
+ * the SQL-standard long spellings, and a trailing precision suffix
199
+ * (`time(6)`), so MySQL and SQL Server `TIME` columns classify from their
200
+ * dialect type as well.
201
+ */
202
+ function timeOfDayKind(dbType) {
203
+ if (!dbType)
204
+ return null;
205
+ const t = dbType
206
+ .trim()
207
+ .toLowerCase()
208
+ // Precision can sit at the end (`time(6)`) or mid-spelling
209
+ // (`time(6) with time zone`), so strip it wherever it appears.
210
+ .replace(/\s*\(\s*\d+\s*\)\s*/, ' ')
211
+ .trim();
212
+ if (t === 'time' || t === 'time without time zone')
213
+ return 'time';
214
+ if (t === 'timetz' || t === 'time with time zone')
215
+ return 'timetz';
216
+ return null;
217
+ }
218
+ /**
219
+ * Classify a column type as a ZONE-LESS date/timestamp type (`date`,
220
+ * `timestamp` = `timestamp without time zone`), or `null` for anything else.
221
+ *
222
+ * `timestamptz` is deliberately excluded: it carries a real instant, and the
223
+ * driver's local-offset serialization is CORRECT for it (Postgres converts the
224
+ * offset away). The two types here store the literal wall-clock fields they
225
+ * are given, so binding a JS `Date` through the driver stores the process's
226
+ * LOCAL calendar fields — in `America/Los_Angeles`, `2026-07-25T00:00:00Z`
227
+ * lands as `2026-07-24 17:00:00`. The read path already interprets an
228
+ * offset-less value as UTC (see `parseDbDate`), so the write path has to bind
229
+ * the UTC components for the round trip to be stable.
230
+ *
231
+ * Recognizes the `udt_name` spellings introspection records, the SQL-standard
232
+ * long spellings, and a trailing precision suffix (`timestamp(3)`).
233
+ * Deliberately does NOT recognize `datetime`: MySQL's `TIMESTAMP`/`DATETIME`
234
+ * are converted by the session time zone, so a UTC-component literal would be
235
+ * misread there. The callers additionally gate on the PostgreSQL dialect.
236
+ */
237
+ function localDateTimeKind(dbType) {
238
+ if (!dbType)
239
+ return null;
240
+ const t = dbType
241
+ .trim()
242
+ .toLowerCase()
243
+ // Precision can sit at the end (`timestamp(3)`) or mid-spelling
244
+ // (`timestamp(3) without time zone`), so strip it wherever it appears.
245
+ .replace(/\s*\(\s*\d+\s*\)\s*/, ' ')
246
+ .trim();
247
+ if (t === 'date')
248
+ return 'date';
249
+ if (t === 'timestamp' || t === 'timestamp without time zone')
250
+ return 'timestamp';
251
+ return null;
252
+ }
128
253
  /** Get the Postgres array cast type for UNNEST batch inserts */
129
254
  function pgArrayType(pgType) {
130
255
  return PG_TO_ARRAY[pgType] ?? 'text[]';
@@ -0,0 +1,15 @@
1
+ import { TurbineClient } from './client.js';
2
+ export type SeedFunction = (db: TurbineClient) => Promise<void> | void;
3
+ export type DefinedSeed = () => Promise<void>;
4
+ /**
5
+ * Extract the filesystem path from a single V8 stack-trace line, regardless of
6
+ * whether the frame is a `file://` URL (ESM), a bare absolute path (CJS / tsx),
7
+ * or a wrapped `(… )` location. The trailing `:line:col` (and any surrounding
8
+ * parens) are peeled from the END so a Windows drive colon or a URL scheme colon
9
+ * inside the path never confuses the match. Non-file frames (`node:internal/…`,
10
+ * `<anonymous>`) return null.
11
+ *
12
+ * Exported for unit testing the frame parser in isolation.
13
+ */
14
+ export declare function parseStackFramePath(line: string): string | null;
15
+ export declare function defineSeed(fn: SeedFunction): DefinedSeed;