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.
- package/README.md +122 -39
- package/dist/cjs/adapters/cockroachdb.d.ts +39 -0
- package/dist/cjs/adapters/index.d.ts +110 -0
- package/dist/cjs/adapters/yugabytedb.d.ts +51 -0
- package/dist/cjs/cli/config.d.ts +181 -0
- package/dist/cjs/cli/config.js +32 -6
- package/dist/cjs/cli/destructive.d.ts +38 -0
- package/dist/cjs/cli/index.d.ts +359 -0
- package/dist/cjs/cli/index.js +228 -56
- package/dist/cjs/cli/loader.d.ts +61 -0
- package/dist/cjs/cli/mcp.d.ts +42 -0
- package/dist/cjs/cli/migrate.d.ts +356 -0
- package/dist/cjs/cli/migrate.js +131 -40
- package/dist/cjs/cli/observe-ui.d.ts +1 -0
- package/dist/cjs/cli/observe-ui.js +14 -5
- package/dist/cjs/cli/observe.d.ts +25 -0
- package/dist/cjs/cli/observe.js +49 -12
- package/dist/cjs/cli/pii-tags.d.ts +53 -0
- package/dist/cjs/cli/prisma-report.d.ts +33 -0
- package/dist/cjs/cli/prisma-report.js +73 -0
- package/dist/cjs/cli/prisma-resolve.d.ts +106 -0
- package/dist/cjs/cli/prisma-resolve.js +1 -0
- package/dist/cjs/cli/prisma-schema.d.ts +176 -0
- package/dist/cjs/cli/prisma-schema.js +82 -4
- package/dist/cjs/cli/rate-limit.d.ts +32 -0
- package/dist/cjs/cli/rate-limit.js +45 -0
- package/dist/cjs/cli/studio-demo.d.ts +43 -0
- package/dist/cjs/cli/studio-ui.generated.d.ts +1 -0
- package/dist/cjs/cli/studio.d.ts +207 -0
- package/dist/cjs/cli/studio.js +136 -71
- package/dist/cjs/cli/ui.d.ts +73 -0
- package/dist/cjs/cli/ui.js +51 -9
- package/dist/cjs/client.d.ts +837 -0
- package/dist/cjs/client.js +3 -0
- package/dist/cjs/dialect.d.ts +516 -0
- package/dist/cjs/dialect.js +37 -12
- package/dist/cjs/errors.d.ts +370 -0
- package/dist/cjs/generate.d.ts +137 -0
- package/dist/cjs/generate.js +39 -6
- package/dist/cjs/index-advisor.d.ts +153 -0
- package/dist/cjs/index-stats.d.ts +384 -0
- package/dist/cjs/index.d.ts +55 -0
- package/dist/cjs/index.js +7 -2
- package/dist/cjs/introspect.d.ts +269 -0
- package/dist/cjs/mssql.d.ts +232 -0
- package/dist/cjs/mssql.js +6 -0
- package/dist/cjs/mysql.d.ts +173 -0
- package/dist/cjs/mysql.js +16 -0
- package/dist/cjs/nested-write.d.ts +96 -0
- package/dist/cjs/nested-write.js +414 -24
- package/dist/cjs/observe.d.ts +115 -0
- package/dist/cjs/optional-peer-import.d.cts +72 -0
- package/dist/cjs/pipeline-submittable.d.ts +93 -0
- package/dist/cjs/pipeline.d.ts +71 -0
- package/dist/cjs/powdb-introspect.d.ts +84 -0
- package/dist/cjs/powdb.d.ts +931 -0
- package/dist/cjs/powdb.js +106 -21
- package/dist/cjs/powql.d.ts +592 -0
- package/dist/cjs/powql.js +42 -6
- package/dist/cjs/prisma-compat.d.ts +283 -0
- package/dist/cjs/prisma-compat.js +167 -9
- package/dist/cjs/query/aggregates.d.ts +92 -0
- package/dist/cjs/query/aggregates.js +7 -3
- package/dist/cjs/query/batched-loader.d.ts +193 -0
- package/dist/cjs/query/builder.d.ts +849 -0
- package/dist/cjs/query/builder.js +571 -65
- package/dist/cjs/query/compound-unique.d.ts +51 -0
- package/dist/cjs/query/deferred.d.ts +223 -0
- package/dist/cjs/query/filters.d.ts +201 -0
- package/dist/cjs/query/index.d.ts +14 -0
- package/dist/cjs/query/index.js +6 -1
- package/dist/cjs/query/relations.d.ts +609 -0
- package/dist/cjs/query/relations.js +693 -46
- package/dist/cjs/query/types.d.ts +1300 -0
- package/dist/cjs/query/utils.d.ts +209 -0
- package/dist/cjs/query/utils.js +208 -1
- package/dist/cjs/query/warn-registry.d.ts +68 -0
- package/dist/cjs/query/warn-registry.js +9 -0
- package/dist/cjs/query/where-compile.d.ts +139 -0
- package/dist/cjs/query/where.d.ts +548 -0
- package/dist/cjs/query/where.js +58 -22
- package/dist/cjs/query/writes.d.ts +172 -0
- package/dist/cjs/query/writes.js +105 -12
- package/dist/cjs/realtime.d.ts +70 -0
- package/dist/cjs/schema-builder.d.ts +354 -0
- package/dist/cjs/schema-metadata.d.ts +83 -0
- package/dist/cjs/schema-sql.d.ts +217 -0
- package/dist/cjs/schema-sql.js +23 -5
- package/dist/cjs/schema.d.ts +356 -0
- package/dist/cjs/schema.js +125 -0
- package/dist/cjs/seed.d.ts +15 -0
- package/dist/cjs/serverless.d.ts +142 -0
- package/dist/cjs/sqlite.d.ts +143 -0
- package/dist/cjs/sqlite.js +4 -0
- package/dist/cjs/typed-sql.d.ts +102 -0
- package/dist/cli/config.d.ts +18 -4
- package/dist/cli/config.js +31 -6
- package/dist/cli/index.d.ts +123 -0
- package/dist/cli/index.js +223 -58
- package/dist/cli/migrate.d.ts +59 -10
- package/dist/cli/migrate.js +128 -41
- package/dist/cli/observe-ui.d.ts +1 -1
- package/dist/cli/observe-ui.js +14 -5
- package/dist/cli/observe.d.ts +7 -1
- package/dist/cli/observe.js +48 -12
- package/dist/cli/prisma-report.d.ts +14 -0
- package/dist/cli/prisma-report.js +72 -0
- package/dist/cli/prisma-resolve.d.ts +6 -0
- package/dist/cli/prisma-resolve.js +1 -0
- package/dist/cli/prisma-schema.d.ts +62 -2
- package/dist/cli/prisma-schema.js +81 -4
- package/dist/cli/rate-limit.d.ts +32 -0
- package/dist/cli/rate-limit.js +40 -0
- package/dist/cli/studio.d.ts +5 -5
- package/dist/cli/studio.js +135 -70
- package/dist/cli/ui.d.ts +1 -1
- package/dist/cli/ui.js +51 -9
- package/dist/client.d.ts +40 -0
- package/dist/client.js +3 -0
- package/dist/dialect.d.ts +17 -1
- package/dist/dialect.js +37 -12
- package/dist/generate.js +40 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mssql.js +6 -0
- package/dist/mysql.js +16 -0
- package/dist/nested-write.d.ts +2 -0
- package/dist/nested-write.js +415 -25
- package/dist/powdb.d.ts +4 -2
- package/dist/powdb.js +106 -21
- package/dist/powql.d.ts +5 -0
- package/dist/powql.js +42 -6
- package/dist/prisma-compat.d.ts +2 -0
- package/dist/prisma-compat.js +166 -8
- package/dist/query/aggregates.js +7 -3
- package/dist/query/builder.d.ts +292 -21
- package/dist/query/builder.js +570 -64
- package/dist/query/deferred.d.ts +39 -0
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +1 -1
- package/dist/query/relations.d.ts +173 -5
- package/dist/query/relations.js +688 -47
- package/dist/query/types.d.ts +123 -39
- package/dist/query/utils.d.ts +116 -0
- package/dist/query/utils.js +198 -0
- package/dist/query/warn-registry.d.ts +9 -0
- package/dist/query/warn-registry.js +9 -0
- package/dist/query/where.d.ts +38 -1
- package/dist/query/where.js +58 -23
- package/dist/query/writes.d.ts +42 -1
- package/dist/query/writes.js +104 -13
- package/dist/schema-sql.d.ts +14 -0
- package/dist/schema-sql.js +23 -5
- package/dist/schema.d.ts +38 -0
- package/dist/schema.js +123 -0
- package/dist/sqlite.js +4 -0
- package/package.json +77 -28
package/dist/cjs/schema-sql.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
+
}
|
package/dist/cjs/schema.js
CHANGED
|
@@ -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;
|