turbine-orm 0.81.0 → 0.90.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 +26 -25
- package/dist/checkout.d.ts +27 -2
- package/dist/checkout.js +75 -4
- package/dist/cjs/checkout.d.ts +27 -2
- package/dist/cjs/checkout.js +76 -3
- package/dist/cjs/cli/code-first-tags.d.ts +62 -0
- package/dist/cjs/cli/code-first-tags.js +124 -0
- package/dist/cjs/cli/compile-query.js +9 -0
- package/dist/cjs/cli/config.d.ts +35 -6
- package/dist/cjs/cli/config.js +17 -1
- package/dist/cjs/cli/destructive.d.ts +7 -4
- package/dist/cjs/cli/destructive.js +224 -20
- package/dist/cjs/cli/error-catalog.js +36 -0
- package/dist/cjs/cli/error-text.d.ts +15 -0
- package/dist/cjs/cli/error-text.js +40 -0
- package/dist/cjs/cli/index.d.ts +62 -4
- package/dist/cjs/cli/index.js +568 -108
- package/dist/cjs/cli/local-server-guard.d.ts +53 -0
- package/dist/cjs/cli/local-server-guard.js +119 -0
- package/dist/cjs/cli/local-server-output.d.ts +41 -0
- package/dist/cjs/cli/local-server-output.js +103 -0
- package/dist/cjs/cli/mcp.d.ts +8 -0
- package/dist/cjs/cli/mcp.js +121 -32
- package/dist/cjs/cli/migrate.d.ts +113 -5
- package/dist/cjs/cli/migrate.js +270 -21
- package/dist/cjs/cli/observe.d.ts +6 -1
- package/dist/cjs/cli/observe.js +85 -55
- package/dist/cjs/cli/pii-predicate-guard.d.ts +43 -23
- package/dist/cjs/cli/pii-predicate-guard.js +98 -37
- package/dist/cjs/cli/prisma-resolve.js +17 -13
- package/dist/cjs/cli/prisma-schema.js +12 -4
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/cli/studio.d.ts +14 -2
- package/dist/cjs/cli/studio.js +244 -69
- package/dist/cjs/cli/ui.d.ts +1 -1
- package/dist/cjs/client.d.ts +558 -85
- package/dist/cjs/client.js +1270 -395
- package/dist/cjs/connection-guard.d.ts +13 -0
- package/dist/cjs/connection-guard.js +28 -0
- package/dist/cjs/connection-url.d.ts +51 -16
- package/dist/cjs/connection-url.js +67 -34
- package/dist/cjs/diagnostics.d.ts +81 -0
- package/dist/cjs/diagnostics.js +147 -0
- package/dist/cjs/dialect.d.ts +449 -20
- package/dist/cjs/dialect.js +117 -76
- package/dist/cjs/engine-config.d.ts +7 -6
- package/dist/cjs/errors.d.ts +126 -6
- package/dist/cjs/errors.js +256 -13
- package/dist/cjs/generate.d.ts +57 -7
- package/dist/cjs/generate.js +635 -279
- package/dist/cjs/global-filters-config.d.ts +59 -0
- package/dist/cjs/global-filters-config.js +367 -0
- package/dist/cjs/index.d.ts +9 -4
- package/dist/cjs/index.js +10 -2
- package/dist/cjs/introspect.d.ts +31 -6
- package/dist/cjs/introspect.js +108 -50
- package/dist/cjs/log-codes.d.ts +12 -0
- package/dist/cjs/log-codes.js +77 -0
- package/dist/cjs/logger.d.ts +87 -0
- package/dist/cjs/logger.js +98 -0
- package/dist/cjs/mssql.d.ts +8 -5
- package/dist/cjs/mssql.js +352 -65
- package/dist/cjs/mysql.d.ts +3 -5
- package/dist/cjs/mysql.js +420 -70
- package/dist/cjs/nested-write.d.ts +3 -0
- package/dist/cjs/nested-write.js +179 -114
- package/dist/cjs/pipeline-submittable.d.ts +12 -0
- package/dist/cjs/pipeline-submittable.js +118 -44
- package/dist/cjs/pipeline.js +21 -26
- package/dist/cjs/powdb-introspect.d.ts +5 -2
- package/dist/cjs/powdb-introspect.js +20 -15
- package/dist/cjs/powdb-shared.d.ts +36 -5
- package/dist/cjs/powdb-shared.js +67 -8
- package/dist/cjs/powdb.d.ts +4 -0
- package/dist/cjs/powdb.js +183 -51
- package/dist/cjs/powql.d.ts +394 -51
- package/dist/cjs/powql.js +1829 -284
- package/dist/cjs/prisma-compat.d.ts +23 -15
- package/dist/cjs/prisma-compat.js +507 -372
- package/dist/cjs/query/aggregates.d.ts +17 -1
- package/dist/cjs/query/aggregates.js +261 -29
- package/dist/cjs/query/batched-loader.d.ts +25 -6
- package/dist/cjs/query/batched-loader.js +103 -20
- package/dist/cjs/query/builder.d.ts +325 -184
- package/dist/cjs/query/builder.js +1452 -490
- package/dist/cjs/query/column-ref.d.ts +61 -0
- package/dist/cjs/query/column-ref.js +73 -0
- package/dist/cjs/query/compound-unique.d.ts +21 -0
- package/dist/cjs/query/compound-unique.js +98 -18
- package/dist/cjs/query/deferred.d.ts +53 -16
- package/dist/cjs/query/expr.d.ts +217 -0
- package/dist/cjs/query/expr.js +859 -0
- package/dist/cjs/query/filters.d.ts +155 -53
- package/dist/cjs/query/filters.js +613 -76
- package/dist/cjs/query/index.d.ts +2 -0
- package/dist/cjs/query/index.js +7 -1
- package/dist/cjs/query/json-value.d.ts +43 -0
- package/dist/cjs/query/json-value.js +59 -0
- package/dist/cjs/query/normalize-args.d.ts +87 -0
- package/dist/cjs/query/normalize-args.js +527 -0
- package/dist/cjs/query/option-surface.d.ts +16 -4
- package/dist/cjs/query/option-surface.js +57 -14
- package/dist/cjs/query/relation-names.d.ts +14 -0
- package/dist/cjs/query/relation-names.js +77 -18
- package/dist/cjs/query/relations.d.ts +30 -3
- package/dist/cjs/query/relations.js +219 -63
- package/dist/cjs/query/row-lock.d.ts +49 -0
- package/dist/cjs/query/row-lock.js +138 -0
- package/dist/cjs/query/sql-comment.d.ts +161 -0
- package/dist/cjs/query/sql-comment.js +381 -0
- package/dist/cjs/query/stream-idle.d.ts +132 -0
- package/dist/cjs/query/stream-idle.js +212 -0
- package/dist/cjs/query/table-client.d.ts +101 -0
- package/dist/cjs/query/table-client.js +74 -0
- package/dist/cjs/query/types.d.ts +677 -298
- package/dist/cjs/query/utils.d.ts +105 -6
- package/dist/cjs/query/utils.js +426 -19
- package/dist/cjs/query/warn-registry.d.ts +18 -0
- package/dist/cjs/query/warn-registry.js +18 -0
- package/dist/cjs/query/where-compile.d.ts +138 -14
- package/dist/cjs/query/where-compile.js +513 -29
- package/dist/cjs/query/where.d.ts +159 -59
- package/dist/cjs/query/where.js +716 -246
- package/dist/cjs/query/writes.d.ts +112 -50
- package/dist/cjs/query/writes.js +1498 -200
- package/dist/cjs/query-events.d.ts +4 -2
- package/dist/cjs/query-events.js +3 -1
- package/dist/cjs/realtime.d.ts +7 -3
- package/dist/cjs/realtime.js +13 -2
- package/dist/cjs/schema-builder.d.ts +55 -4
- package/dist/cjs/schema-builder.js +382 -18
- package/dist/cjs/schema-metadata.d.ts +21 -1
- package/dist/cjs/schema-metadata.js +29 -7
- package/dist/cjs/schema-sql.d.ts +75 -5
- package/dist/cjs/schema-sql.js +497 -45
- package/dist/cjs/schema.d.ts +26 -5
- package/dist/cjs/schema.js +110 -13
- package/dist/cjs/sql-placeholders.d.ts +56 -0
- package/dist/cjs/sql-placeholders.js +204 -0
- package/dist/cjs/sqlite.d.ts +75 -20
- package/dist/cjs/sqlite.js +593 -95
- package/dist/cjs/supplied-pool-types.d.ts +48 -0
- package/dist/cjs/supplied-pool-types.js +256 -0
- package/dist/cjs/transaction-control.d.ts +51 -0
- package/dist/cjs/transaction-control.js +316 -0
- package/dist/cjs/transaction-state.d.ts +342 -0
- package/dist/cjs/transaction-state.js +707 -0
- package/dist/cjs/typed-sql.d.ts +37 -3
- package/dist/cjs/typed-sql.js +102 -8
- package/dist/cli/code-first-tags.d.ts +62 -0
- package/dist/cli/code-first-tags.js +120 -0
- package/dist/cli/compile-query.js +9 -0
- package/dist/cli/config.d.ts +35 -6
- package/dist/cli/config.js +16 -1
- package/dist/cli/destructive.d.ts +7 -4
- package/dist/cli/destructive.js +224 -20
- package/dist/cli/error-catalog.js +36 -0
- package/dist/cli/error-text.d.ts +15 -0
- package/dist/cli/error-text.js +37 -0
- package/dist/cli/index.d.ts +62 -4
- package/dist/cli/index.js +569 -112
- package/dist/cli/local-server-guard.d.ts +53 -0
- package/dist/cli/local-server-guard.js +113 -0
- package/dist/cli/local-server-output.d.ts +41 -0
- package/dist/cli/local-server-output.js +98 -0
- package/dist/cli/mcp.d.ts +8 -0
- package/dist/cli/mcp.js +121 -34
- package/dist/cli/migrate.d.ts +113 -5
- package/dist/cli/migrate.js +261 -21
- package/dist/cli/observe.d.ts +6 -1
- package/dist/cli/observe.js +84 -54
- package/dist/cli/pii-predicate-guard.d.ts +43 -23
- package/dist/cli/pii-predicate-guard.js +100 -35
- package/dist/cli/prisma-resolve.js +15 -11
- package/dist/cli/prisma-schema.js +12 -4
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/cli/studio.d.ts +14 -2
- package/dist/cli/studio.js +246 -71
- package/dist/cli/ui.d.ts +1 -1
- package/dist/client.d.ts +558 -85
- package/dist/client.js +1275 -400
- package/dist/connection-guard.d.ts +13 -0
- package/dist/connection-guard.js +27 -0
- package/dist/connection-url.d.ts +51 -16
- package/dist/connection-url.js +63 -34
- package/dist/diagnostics.d.ts +81 -0
- package/dist/diagnostics.js +144 -0
- package/dist/dialect.d.ts +449 -20
- package/dist/dialect.js +115 -43
- package/dist/engine-config.d.ts +7 -6
- package/dist/errors.d.ts +126 -6
- package/dist/errors.js +252 -12
- package/dist/generate.d.ts +57 -7
- package/dist/generate.js +635 -281
- package/dist/global-filters-config.d.ts +59 -0
- package/dist/global-filters-config.js +361 -0
- package/dist/index.d.ts +9 -4
- package/dist/index.js +4 -2
- package/dist/introspect.d.ts +31 -6
- package/dist/introspect.js +106 -50
- package/dist/log-codes.d.ts +12 -0
- package/dist/log-codes.js +74 -0
- package/dist/logger.d.ts +87 -0
- package/dist/logger.js +93 -0
- package/dist/mssql.d.ts +8 -5
- package/dist/mssql.js +354 -67
- package/dist/mysql.d.ts +3 -5
- package/dist/mysql.js +423 -73
- package/dist/nested-write.d.ts +3 -0
- package/dist/nested-write.js +177 -112
- package/dist/pipeline-submittable.d.ts +12 -0
- package/dist/pipeline-submittable.js +117 -44
- package/dist/pipeline.js +21 -26
- package/dist/powdb-introspect.d.ts +5 -2
- package/dist/powdb-introspect.js +21 -16
- package/dist/powdb-shared.d.ts +36 -5
- package/dist/powdb-shared.js +65 -8
- package/dist/powdb.d.ts +4 -0
- package/dist/powdb.js +183 -51
- package/dist/powql.d.ts +394 -51
- package/dist/powql.js +1836 -291
- package/dist/prisma-compat.d.ts +23 -15
- package/dist/prisma-compat.js +508 -373
- package/dist/query/aggregates.d.ts +17 -1
- package/dist/query/aggregates.js +263 -32
- package/dist/query/batched-loader.d.ts +25 -6
- package/dist/query/batched-loader.js +101 -21
- package/dist/query/builder.d.ts +325 -184
- package/dist/query/builder.js +1458 -495
- package/dist/query/column-ref.d.ts +61 -0
- package/dist/query/column-ref.js +66 -0
- package/dist/query/compound-unique.d.ts +21 -0
- package/dist/query/compound-unique.js +97 -19
- package/dist/query/deferred.d.ts +53 -16
- package/dist/query/expr.d.ts +217 -0
- package/dist/query/expr.js +848 -0
- package/dist/query/filters.d.ts +155 -53
- package/dist/query/filters.js +597 -72
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.js +4 -0
- package/dist/query/json-value.d.ts +43 -0
- package/dist/query/json-value.js +54 -0
- package/dist/query/normalize-args.d.ts +87 -0
- package/dist/query/normalize-args.js +517 -0
- package/dist/query/option-surface.d.ts +16 -4
- package/dist/query/option-surface.js +56 -13
- package/dist/query/relation-names.d.ts +14 -0
- package/dist/query/relation-names.js +77 -19
- package/dist/query/relations.d.ts +30 -3
- package/dist/query/relations.js +220 -67
- package/dist/query/row-lock.d.ts +49 -0
- package/dist/query/row-lock.js +131 -0
- package/dist/query/sql-comment.d.ts +161 -0
- package/dist/query/sql-comment.js +374 -0
- package/dist/query/stream-idle.d.ts +132 -0
- package/dist/query/stream-idle.js +203 -0
- package/dist/query/table-client.d.ts +101 -0
- package/dist/query/table-client.js +70 -0
- package/dist/query/types.d.ts +677 -298
- package/dist/query/utils.d.ts +105 -6
- package/dist/query/utils.js +414 -18
- package/dist/query/warn-registry.d.ts +18 -0
- package/dist/query/warn-registry.js +18 -0
- package/dist/query/where-compile.d.ts +138 -14
- package/dist/query/where-compile.js +508 -31
- package/dist/query/where.d.ts +159 -59
- package/dist/query/where.js +711 -248
- package/dist/query/writes.d.ts +112 -50
- package/dist/query/writes.js +1496 -202
- package/dist/query-events.d.ts +4 -2
- package/dist/query-events.js +3 -1
- package/dist/realtime.d.ts +7 -3
- package/dist/realtime.js +13 -2
- package/dist/schema-builder.d.ts +55 -4
- package/dist/schema-builder.js +381 -18
- package/dist/schema-metadata.d.ts +21 -1
- package/dist/schema-metadata.js +28 -7
- package/dist/schema-sql.d.ts +75 -5
- package/dist/schema-sql.js +494 -44
- package/dist/schema.d.ts +26 -5
- package/dist/schema.js +109 -13
- package/dist/sql-placeholders.d.ts +56 -0
- package/dist/sql-placeholders.js +200 -0
- package/dist/sqlite.d.ts +75 -20
- package/dist/sqlite.js +597 -99
- package/dist/supplied-pool-types.d.ts +48 -0
- package/dist/supplied-pool-types.js +247 -0
- package/dist/transaction-control.d.ts +51 -0
- package/dist/transaction-control.js +311 -0
- package/dist/transaction-state.d.ts +342 -0
- package/dist/transaction-state.js +696 -0
- package/dist/typed-sql.d.ts +37 -3
- package/dist/typed-sql.js +100 -8
- package/package.json +7 -4
- package/skills/turbine-orm/SKILL.md +56 -6
package/dist/cli/destructive.js
CHANGED
|
@@ -19,9 +19,12 @@
|
|
|
19
19
|
* the optional-`COLUMN` shorthand (`ALTER TABLE t DROP email`), data-modifying
|
|
20
20
|
* CTEs (`WITH d AS (DELETE ...) SELECT ...`), `MERGE ... THEN DELETE`, dynamic
|
|
21
21
|
* SQL inside a `DO`/function body, an `UPDATE` whose only WHERE sits inside a
|
|
22
|
-
* subquery (which restricts nothing)
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* subquery (which restricts nothing) or is a constant-true predicate (`WHERE
|
|
23
|
+
* true`, `WHERE 1 = 1`), a `DROP ... CASCADE` of a non-table object (which takes
|
|
24
|
+
* dependent COLUMNS with it), a detached partition, an `ON CONFLICT DO UPDATE`
|
|
25
|
+
* with no `WHERE` (it rewrites every conflicting row), a `PREPARE`d destructive
|
|
26
|
+
* statement, `lo_unlink` (it deletes large objects), and an `EXPLAIN ANALYZE` of
|
|
27
|
+
* any of the above (which really executes it).
|
|
25
28
|
*
|
|
26
29
|
* The lexing, "where does a statement end" and "which characters are code",
|
|
27
30
|
* is NOT done here: it lives in `sql-statements.ts` and is shared with the
|
|
@@ -41,7 +44,9 @@ export const DESTRUCTIVE_KIND_LABEL = {
|
|
|
41
44
|
'detach-partition': 'detaches a partition, every row in it leaves the table',
|
|
42
45
|
truncate: 'deletes every row',
|
|
43
46
|
delete: 'deletes rows',
|
|
44
|
-
'update-without-where': 'rewrites every row (no WHERE clause)',
|
|
47
|
+
'update-without-where': 'rewrites every row (no WHERE clause, or one that is always true)',
|
|
48
|
+
'overwrite-on-conflict': 'overwrites every conflicting row (ON CONFLICT DO UPDATE with no restricting WHERE)',
|
|
49
|
+
'large-object-unlink': 'permanently deletes large objects (lo_unlink)',
|
|
45
50
|
'alter-column-type': 'rewrites a column type (cast may truncate or fail)',
|
|
46
51
|
'merge-delete': 'deletes matched rows (MERGE ... THEN DELETE)',
|
|
47
52
|
rename: 'renames a table or column, every query and view referencing the old name breaks',
|
|
@@ -52,6 +57,12 @@ export const DESTRUCTIVE_KIND_LABEL = {
|
|
|
52
57
|
/** Unquote a "quoted" identifier for display. */
|
|
53
58
|
const ident = (raw) => (raw ?? '?').replace(/^"|"$/g, '');
|
|
54
59
|
const IDENT = String.raw `("[^"]+"|[a-zA-Z_][\w$]*)(\.("[^"]+"|[a-zA-Z_][\w$]*))?`;
|
|
60
|
+
/**
|
|
61
|
+
* Ends an `IDENT` match. Not `\b`: after a closing `"` the next character is
|
|
62
|
+
* not a word character either, so `\b` fails and `UPDATE "users" SET ...` was
|
|
63
|
+
* never matched.
|
|
64
|
+
*/
|
|
65
|
+
const NOT_IDENT_CHAR = String.raw `(?![\w$])`;
|
|
55
66
|
/**
|
|
56
67
|
* Render one `IDENT` capture triple (name, `.qualifier`, qualifier) as text,
|
|
57
68
|
* where `base` is the 1-based index of the triple's first group.
|
|
@@ -102,10 +113,11 @@ const RULES = [
|
|
|
102
113
|
// CASCADE these are safe: Postgres refuses the drop while a dependency
|
|
103
114
|
// exists. With it, the dependents go too, and a dependent of a TYPE or a
|
|
104
115
|
// DOMAIN is typically a COLUMN, so `DROP TYPE order_status CASCADE`
|
|
105
|
-
// silently removes `orders.status` and every value in it
|
|
106
|
-
//
|
|
116
|
+
// silently removes `orders.status` and every value in it, and a dependent of
|
|
117
|
+
// a VIEW can be a materialized view, whose stored rows go with it. The
|
|
118
|
+
// presence of CASCADE is therefore the whole rule, not an extra detail.
|
|
107
119
|
kind: 'drop-cascade',
|
|
108
|
-
regex: new RegExp(String.raw `^DROP\s+(TYPE|DOMAIN|EXTENSION|SEQUENCE|FUNCTION|PROCEDURE|ROUTINE|AGGREGATE)\s+(IF\s+EXISTS\s+)?${IDENT}${OUTSIDE_QUOTES}\bCASCADE\b`, 'i'),
|
|
120
|
+
regex: new RegExp(String.raw `^DROP\s+(TYPE|DOMAIN|EXTENSION|SEQUENCE|FUNCTION|PROCEDURE|ROUTINE|AGGREGATE|VIEW)\s+(IF\s+EXISTS\s+)?${IDENT}${OUTSIDE_QUOTES}\bCASCADE\b`, 'i'),
|
|
109
121
|
target: (m) => `${m[1].toUpperCase()} ${qualified(m, 3)}`,
|
|
110
122
|
},
|
|
111
123
|
{
|
|
@@ -141,16 +153,43 @@ const RULES = [
|
|
|
141
153
|
{
|
|
142
154
|
// MERGE's DELETE action removes rows from the target table.
|
|
143
155
|
kind: 'merge-delete',
|
|
144
|
-
regex: new RegExp(String.raw `^MERGE\s+INTO\s+(ONLY\s+)?${IDENT}
|
|
156
|
+
regex: new RegExp(String.raw `^MERGE\s+INTO\s+(ONLY\s+)?${IDENT}${NOT_IDENT_CHAR}${OUTSIDE_QUOTES}\bTHEN\s+DELETE\b`, 'i'),
|
|
145
157
|
target: (m) => (m[4] ? `${ident(m[2])}.${ident(m[4])}` : ident(m[2])),
|
|
146
158
|
},
|
|
147
159
|
{
|
|
148
160
|
// A WHERE inside a scalar subquery (`SET x = (SELECT ... WHERE ...)`) does
|
|
149
|
-
// NOT restrict the rows updated, so the guard tests only the TOP level
|
|
161
|
+
// NOT restrict the rows updated, so the guard tests only the TOP level, and
|
|
162
|
+
// a top-level WHERE that is always true restricts nothing either.
|
|
150
163
|
kind: 'update-without-where',
|
|
151
|
-
regex: new RegExp(String.raw `^UPDATE\s+(ONLY\s+)?${IDENT}
|
|
164
|
+
regex: new RegExp(String.raw `^UPDATE\s+(ONLY\s+)?${IDENT}${NOT_IDENT_CHAR}`, 'i'),
|
|
152
165
|
target: (m) => (m[4] ? `${ident(m[2])}.${ident(m[4])}` : ident(m[2])),
|
|
153
|
-
also: (stmt) => !
|
|
166
|
+
also: (stmt) => !hasRestrictingWhere(stmt),
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
// `INSERT ... ON CONFLICT ... DO UPDATE SET ...` rewrites every row the
|
|
170
|
+
// insert conflicts with. The WHERE that narrows it sits AFTER `DO UPDATE`;
|
|
171
|
+
// the one before it is the conflict target's index predicate and restricts
|
|
172
|
+
// nothing. Add `WHERE t.col IS DISTINCT FROM EXCLUDED.col` to say which rows
|
|
173
|
+
// may change.
|
|
174
|
+
kind: 'overwrite-on-conflict',
|
|
175
|
+
regex: new RegExp(String.raw `^INSERT\s+INTO\s+${IDENT}${NOT_IDENT_CHAR}`, 'i'),
|
|
176
|
+
target: (m) => (m[3] ? `${ident(m[1])}.${ident(m[3])}` : ident(m[1])),
|
|
177
|
+
also: (stmt) => {
|
|
178
|
+
const code = stmt.replace(/"(?:[^"]|"")*"/g, '""');
|
|
179
|
+
if (!/\bON\s+CONFLICT\b/i.test(code))
|
|
180
|
+
return false;
|
|
181
|
+
const at = code.search(/\bDO\s+UPDATE\b/i);
|
|
182
|
+
return at !== -1 && !hasRestrictingWhere(code.slice(at));
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
// `SELECT lo_unlink(oid) FROM pg_largeobject_metadata` deletes the large
|
|
187
|
+
// objects whatever they hold, and there is no WHERE to look for. Statements
|
|
188
|
+
// that only NAME the function (GRANT, COMMENT, ALTER, DROP) call nothing.
|
|
189
|
+
kind: 'large-object-unlink',
|
|
190
|
+
regex: /\blo_unlink\s*\(/i,
|
|
191
|
+
target: () => 'large objects',
|
|
192
|
+
also: (stmt) => !/^(?:GRANT|REVOKE|COMMENT|ALTER|DROP)\b/i.test(stmt),
|
|
154
193
|
},
|
|
155
194
|
{
|
|
156
195
|
// `CREATE RULE r AS ON INSERT TO t DO INSTEAD DELETE FROM u`. The rule
|
|
@@ -169,7 +208,7 @@ const RULES = [
|
|
|
169
208
|
// that appears in ordinary migrations. DELETE, TRUNCATE and DROP stay
|
|
170
209
|
// unconditional: a DELETE inside a rule destroys on every matching
|
|
171
210
|
// statement whether or not it is narrowed, and the other two take no WHERE.
|
|
172
|
-
also: (stmt) => !/\bDO\s+(?:ALSO\s+|INSTEAD\s+)?\(?\s*UPDATE\b/i.test(stmt) || !
|
|
211
|
+
also: (stmt) => !/\bDO\s+(?:ALSO\s+|INSTEAD\s+)?\(?\s*UPDATE\b/i.test(stmt) || !hasRestrictingWhere(stmt),
|
|
173
212
|
},
|
|
174
213
|
// Renames come LAST: they destroy no data, so any statement that is BOTH a
|
|
175
214
|
// rename and a data-loss operation should report the data loss instead.
|
|
@@ -191,21 +230,165 @@ const RULES = [
|
|
|
191
230
|
target: (m) => `${qualified(m, 3)}.${qualified(m, 7)}`,
|
|
192
231
|
},
|
|
193
232
|
];
|
|
194
|
-
/**
|
|
195
|
-
|
|
196
|
-
|
|
233
|
+
/**
|
|
234
|
+
* The predicate of the statement's top-level `WHERE` (outside every
|
|
235
|
+
* parenthesized group), up to a top-level `RETURNING` or the end, or `null` when
|
|
236
|
+
* there is none.
|
|
237
|
+
*/
|
|
238
|
+
function topLevelWherePredicate(stmt) {
|
|
239
|
+
// A quoted identifier is consumed whole, so a column named "where" is not one.
|
|
240
|
+
const re = /"(?:[^"]|"")*"|[()]|\bWHERE\b|\bRETURNING\b/gi;
|
|
197
241
|
let depth = 0;
|
|
242
|
+
let from = -1;
|
|
198
243
|
let m = re.exec(stmt);
|
|
199
244
|
while (m !== null) {
|
|
200
|
-
if (m[0]
|
|
245
|
+
if (m[0].startsWith('"')) {
|
|
246
|
+
// not a keyword
|
|
247
|
+
}
|
|
248
|
+
else if (m[0] === '(')
|
|
201
249
|
depth++;
|
|
202
250
|
else if (m[0] === ')')
|
|
203
251
|
depth = Math.max(0, depth - 1);
|
|
204
|
-
else if (depth === 0)
|
|
205
|
-
|
|
252
|
+
else if (depth === 0) {
|
|
253
|
+
if (from === -1 && /^WHERE$/i.test(m[0]))
|
|
254
|
+
from = m.index + m[0].length;
|
|
255
|
+
else if (from !== -1 && /^RETURNING$/i.test(m[0]))
|
|
256
|
+
return stmt.slice(from, m.index).trim();
|
|
257
|
+
}
|
|
206
258
|
m = re.exec(stmt);
|
|
207
259
|
}
|
|
208
|
-
return
|
|
260
|
+
return from === -1 ? null : stmt.slice(from).trim();
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* True when the statement has a top-level `WHERE` that can leave some rows out.
|
|
264
|
+
* A predicate proven always true (`true`, `1 = 1`, `x OR true`) restricts nothing
|
|
265
|
+
* and counts as no WHERE. A predicate this cannot read is assumed to restrict,
|
|
266
|
+
* and that statement is then NOT flagged, so only a proof of constant-true may
|
|
267
|
+
* turn a WHERE into none.
|
|
268
|
+
*/
|
|
269
|
+
function hasRestrictingWhere(stmt) {
|
|
270
|
+
const predicate = topLevelWherePredicate(stmt);
|
|
271
|
+
return predicate !== null && constantValue(predicate) !== true;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Split on a top-level (paren depth 0, outside `CASE ... END`) `AND` or `OR`.
|
|
275
|
+
* The `AND` of a `BETWEEN a AND b` belongs to the BETWEEN and splits nothing.
|
|
276
|
+
*/
|
|
277
|
+
function splitTopLevel(text, keyword) {
|
|
278
|
+
const re = /"(?:[^"]|"")*"|[()]|\b(?:AND|OR|BETWEEN|CASE|END)\b/gi;
|
|
279
|
+
const parts = [];
|
|
280
|
+
let depth = 0;
|
|
281
|
+
let caseDepth = 0;
|
|
282
|
+
let betweenOpen = 0;
|
|
283
|
+
let last = 0;
|
|
284
|
+
let m = re.exec(text);
|
|
285
|
+
while (m !== null) {
|
|
286
|
+
const token = m[0].toUpperCase();
|
|
287
|
+
if (token.startsWith('"')) {
|
|
288
|
+
// a quoted identifier, never a keyword
|
|
289
|
+
}
|
|
290
|
+
else if (token === '(')
|
|
291
|
+
depth++;
|
|
292
|
+
else if (token === ')')
|
|
293
|
+
depth = Math.max(0, depth - 1);
|
|
294
|
+
else if (depth === 0) {
|
|
295
|
+
if (token === 'CASE')
|
|
296
|
+
caseDepth++;
|
|
297
|
+
else if (token === 'END')
|
|
298
|
+
caseDepth = Math.max(0, caseDepth - 1);
|
|
299
|
+
else if (caseDepth === 0) {
|
|
300
|
+
if (token === 'BETWEEN')
|
|
301
|
+
betweenOpen++;
|
|
302
|
+
else if (token === 'AND' && betweenOpen > 0)
|
|
303
|
+
betweenOpen--;
|
|
304
|
+
else if (token === keyword) {
|
|
305
|
+
parts.push(text.slice(last, m.index));
|
|
306
|
+
last = m.index + m[0].length;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
m = re.exec(text);
|
|
311
|
+
}
|
|
312
|
+
parts.push(text.slice(last));
|
|
313
|
+
return parts.map((part) => part.trim());
|
|
314
|
+
}
|
|
315
|
+
/** Strip parentheses that wrap the WHOLE text: `((a = 1))` becomes `a = 1`. */
|
|
316
|
+
function unwrapParens(text) {
|
|
317
|
+
let out = text.trim();
|
|
318
|
+
while (out.startsWith('(') && closingParenIndex(out, 0) === out.length - 1)
|
|
319
|
+
out = out.slice(1, -1).trim();
|
|
320
|
+
return out;
|
|
321
|
+
}
|
|
322
|
+
const NUMBER_LITERAL = String.raw `[+-]?\d+(?:\.\d+)?`;
|
|
323
|
+
const COMPARISON = new RegExp(String.raw `^(${NUMBER_LITERAL}|'')\s*(=|<>|!=|<=|>=|<|>)\s*(${NUMBER_LITERAL}|'')$`);
|
|
324
|
+
const SELF_COMPARISON = new RegExp(String.raw `^(${IDENT})\s*(=|<>|!=|<=|>=|<|>)\s*(${IDENT})$`);
|
|
325
|
+
/**
|
|
326
|
+
* The truth value of a predicate when it does not depend on any row: `true`,
|
|
327
|
+
* `false`, or `undefined` when it does (or when this cannot tell). Strings reach
|
|
328
|
+
* here already emptied to `''`, so two string literals compare equal; the only
|
|
329
|
+
* mistake that costs is calling a false predicate true, a prompt on a statement
|
|
330
|
+
* that updates nothing.
|
|
331
|
+
*/
|
|
332
|
+
function constantValue(predicate) {
|
|
333
|
+
const text = unwrapParens(predicate);
|
|
334
|
+
if (text === '')
|
|
335
|
+
return undefined;
|
|
336
|
+
const ors = splitTopLevel(text, 'OR');
|
|
337
|
+
if (ors.length > 1) {
|
|
338
|
+
const values = ors.map(constantValue);
|
|
339
|
+
if (values.includes(true))
|
|
340
|
+
return true;
|
|
341
|
+
return values.every((v) => v === false) ? false : undefined;
|
|
342
|
+
}
|
|
343
|
+
const ands = splitTopLevel(text, 'AND');
|
|
344
|
+
if (ands.length > 1) {
|
|
345
|
+
const values = ands.map(constantValue);
|
|
346
|
+
if (values.includes(false))
|
|
347
|
+
return false;
|
|
348
|
+
return values.every((v) => v === true) ? true : undefined;
|
|
349
|
+
}
|
|
350
|
+
const negated = /^NOT\s+([\s\S]+)$/i.exec(text);
|
|
351
|
+
if (negated) {
|
|
352
|
+
const inner = constantValue(negated[1]);
|
|
353
|
+
return inner === undefined ? undefined : !inner;
|
|
354
|
+
}
|
|
355
|
+
if (/^TRUE$/i.test(text))
|
|
356
|
+
return true;
|
|
357
|
+
if (/^(?:FALSE|NULL)$/i.test(text))
|
|
358
|
+
return false;
|
|
359
|
+
const literals = COMPARISON.exec(text);
|
|
360
|
+
if (literals)
|
|
361
|
+
return compareConstants(literals[1], literals[2], literals[3]);
|
|
362
|
+
// `id = id` is true for every row whose id is not null; calling it true errs
|
|
363
|
+
// toward the prompt, which is the safe side.
|
|
364
|
+
const self = SELF_COMPARISON.exec(text);
|
|
365
|
+
if (self && self[1].toLowerCase() === self[6].toLowerCase())
|
|
366
|
+
return compareConstants('0', self[5], '0');
|
|
367
|
+
return undefined;
|
|
368
|
+
}
|
|
369
|
+
/** Compare two literals, where two `''` (emptied strings) are taken as equal. */
|
|
370
|
+
function compareConstants(left, op, right) {
|
|
371
|
+
const a = left === "''" ? 0 : Number(left);
|
|
372
|
+
const b = right === "''" ? 0 : Number(right);
|
|
373
|
+
if (Number.isNaN(a) || Number.isNaN(b))
|
|
374
|
+
return undefined;
|
|
375
|
+
switch (op) {
|
|
376
|
+
case '=':
|
|
377
|
+
return a === b;
|
|
378
|
+
case '<>':
|
|
379
|
+
case '!=':
|
|
380
|
+
return a !== b;
|
|
381
|
+
case '<':
|
|
382
|
+
return a < b;
|
|
383
|
+
case '<=':
|
|
384
|
+
return a <= b;
|
|
385
|
+
case '>':
|
|
386
|
+
return a > b;
|
|
387
|
+
case '>=':
|
|
388
|
+
return a >= b;
|
|
389
|
+
default:
|
|
390
|
+
return undefined;
|
|
391
|
+
}
|
|
209
392
|
}
|
|
210
393
|
/**
|
|
211
394
|
* A leading CTE list is a prefix, not a statement: `WITH c AS (SELECT 1) DELETE
|
|
@@ -265,6 +448,25 @@ function copySubstatements(stmt) {
|
|
|
265
448
|
// above rather than being judged as one blob here.
|
|
266
449
|
return /^(?:DELETE|UPDATE|INSERT|MERGE|WITH)\b/i.test(inner) ? [inner] : [];
|
|
267
450
|
}
|
|
451
|
+
/**
|
|
452
|
+
* The statement a `PREPARE name [(types)] AS <statement>` stores. Nothing runs at
|
|
453
|
+
* PREPARE time, but `EXECUTE name` runs it later on the same connection, so the
|
|
454
|
+
* stored statement is judged as if it had been written out. A prepared `DELETE`
|
|
455
|
+
* or unrestricted `UPDATE` followed by `EXECUTE` was reported as clean.
|
|
456
|
+
*/
|
|
457
|
+
function prepareSubstatements(stmt) {
|
|
458
|
+
const head = /^PREPARE\s+(?:"[^"]+"|[A-Za-z_][\w$]*)\s*/i.exec(stmt);
|
|
459
|
+
if (!head)
|
|
460
|
+
return [];
|
|
461
|
+
let rest = stmt.slice(head[0].length);
|
|
462
|
+
if (rest.startsWith('('))
|
|
463
|
+
rest = rest.slice(closingParenIndex(rest, 0) + 1).trimStart();
|
|
464
|
+
const as = /^AS\s+/i.exec(rest);
|
|
465
|
+
if (!as)
|
|
466
|
+
return [];
|
|
467
|
+
const inner = rest.slice(as[0].length);
|
|
468
|
+
return [stripLeadingCtes(inner), ...cteSubstatements(inner)];
|
|
469
|
+
}
|
|
268
470
|
/**
|
|
269
471
|
* Data-modifying CTE bodies: `WITH d AS (DELETE FROM t ...) SELECT ...` runs a
|
|
270
472
|
* real DELETE even though the statement reads as a SELECT. Each candidate is cut
|
|
@@ -334,7 +536,8 @@ function proceduralCandidates(body) {
|
|
|
334
536
|
// Literals have to survive: they are where dynamic SQL keeps its payload,
|
|
335
537
|
// and `stripped` would have emptied exactly them.
|
|
336
538
|
const text = statement.code;
|
|
337
|
-
|
|
539
|
+
// `DO UPDATE` is the tail of an `ON CONFLICT` clause, judged with its INSERT.
|
|
540
|
+
const re = /\b(?:(?:DROP|TRUNCATE|DELETE|ALTER|MERGE|INSERT)\s|(?<!\bDO\s+)UPDATE\s|lo_unlink\s*\()/gi;
|
|
338
541
|
let m = re.exec(text);
|
|
339
542
|
while (m !== null) {
|
|
340
543
|
out.push({ text: text.slice(m.index), before: text.slice(0, m.index), statementCode: text });
|
|
@@ -366,6 +569,7 @@ export function scanDestructiveSql(sql) {
|
|
|
366
569
|
stripLeadingCtes(body),
|
|
367
570
|
...cteSubstatements(body),
|
|
368
571
|
...copySubstatements(body).flatMap((inner) => [inner, ...cteSubstatements(inner)]),
|
|
572
|
+
...prepareSubstatements(body),
|
|
369
573
|
].map((text) => ({
|
|
370
574
|
text,
|
|
371
575
|
display,
|
|
@@ -102,11 +102,13 @@ const CATALOG = {
|
|
|
102
102
|
'TLS: a self-signed or expired certificate, or a hostname the certificate does not cover.',
|
|
103
103
|
'The connection string is malformed, or points at a pooler port the server is not on.',
|
|
104
104
|
'The server closed an idle pooled connection (proxy idle timeout, restart, failover).',
|
|
105
|
+
'A transaction-pooling endpoint (PgBouncer in transaction mode, a managed `-pooler` host, port 6432 or 6543) with named prepared statements: sqlstate 42P05 or 26000.',
|
|
105
106
|
],
|
|
106
107
|
howToFix: [
|
|
107
108
|
'Read `err.sqlstate` and the hint in the message: each driver code carries its own next step.',
|
|
108
109
|
'Check the host, port and database name; for TLS, supply the CA via `ssl: { ca }` rather than disabling verification.',
|
|
109
110
|
'For a serverless driver, pass the external pool on `TurbineConfig.pool` instead of a connection string.',
|
|
111
|
+
'For 42P05 / 26000 behind a pooler, connect to the direct endpoint, set `preparedStatements: false`, or run PgBouncer 1.21+ with `max_prepared_statements` above 0.',
|
|
110
112
|
],
|
|
111
113
|
properties: ['sqlstate'],
|
|
112
114
|
},
|
|
@@ -344,6 +346,40 @@ const CATALOG = {
|
|
|
344
346
|
],
|
|
345
347
|
properties: ['reason'],
|
|
346
348
|
},
|
|
349
|
+
TURBINE_E019: {
|
|
350
|
+
className: 'TransactionRolledBackError',
|
|
351
|
+
retryable: false,
|
|
352
|
+
origin: 'turbine',
|
|
353
|
+
whenThrown: 'A transaction callback returned or committed after work inside it had already failed, so the server rolled the transaction back.',
|
|
354
|
+
likelyCauses: [
|
|
355
|
+
'The callback caught an error from a statement (a unique violation, a timeout) and carried on, then returned. PostgreSQL refuses every later statement in an aborted transaction and answers COMMIT with ROLLBACK.',
|
|
356
|
+
'A query inside `$transaction` hit its per-query `timeout`; the transaction cannot continue after the cancel.',
|
|
357
|
+
'Raw `transaction()`: a statement failed and the code returned without a SAVEPOINT around it.',
|
|
358
|
+
],
|
|
359
|
+
howToFix: [
|
|
360
|
+
'Let the first error propagate so the whole transaction fails with its own class, or wrap the step that may fail in a nested `tx.$transaction` (a SAVEPOINT) and catch around that.',
|
|
361
|
+
'On raw `transaction()`, issue `SAVEPOINT` before the statement that may fail and `ROLLBACK TO SAVEPOINT` when it does.',
|
|
362
|
+
'Deadlock, serialization and lock-not-available failures keep their own classes and stay retryable; E019 is not, because retrying the same callback repeats the same swallowed failure.',
|
|
363
|
+
],
|
|
364
|
+
properties: [],
|
|
365
|
+
},
|
|
366
|
+
TURBINE_E020: {
|
|
367
|
+
className: 'InvariantError',
|
|
368
|
+
retryable: false,
|
|
369
|
+
origin: 'turbine',
|
|
370
|
+
whenThrown: 'Turbine caught a bug in itself before sending a statement it knew was wrong: two internal paths that must agree did not, or a branch no argument can reach was reached.',
|
|
371
|
+
likelyCauses: [
|
|
372
|
+
'`invariant: "sql-cache-lockstep"`: the SQL-template cache built a statement whose parameters, or whose cached text, disagree with what a fresh build of the same query produces.',
|
|
373
|
+
'`invariant: "sql-cache-param-count"`: a cached statement was about to run with a different number of parameters than its placeholders.',
|
|
374
|
+
'Any other `invariant` id names one internal rule; the message says which two things disagreed.',
|
|
375
|
+
],
|
|
376
|
+
howToFix: [
|
|
377
|
+
'Report it at https://github.com/zvndev/turbine-orm/issues with the message and `err.invariant`. The message carries the query shape and no row or parameter value.',
|
|
378
|
+
'Nothing in the call is wrong, so rewriting the arguments is a workaround at best. There is no option that turns these checks off: each one fires only where Turbine would otherwise have sent the wrong SQL or the wrong parameters.',
|
|
379
|
+
'Do not retry: the same call fails the same way.',
|
|
380
|
+
],
|
|
381
|
+
properties: ['invariant'],
|
|
382
|
+
},
|
|
347
383
|
};
|
|
348
384
|
/** Every code in the catalog, in code order. */
|
|
349
385
|
export const CATALOGUED_ERROR_CODES = Object.keys(CATALOG).sort();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The text the CLI prints for a caught error. A zero-import leaf, so every
|
|
3
|
+
* command can use it.
|
|
4
|
+
*
|
|
5
|
+
* Reading `.message` alone is not enough. When `localhost` resolves to both
|
|
6
|
+
* `::1` and `127.0.0.1` and both refuse, Node's `net` rejects with an
|
|
7
|
+
* `AggregateError` whose message is EMPTY: the reasons are on its `errors`.
|
|
8
|
+
* Every command then printed its prefix and nothing else ("Failed to start
|
|
9
|
+
* Studio: "), and the top-level handler, which classifies by message text,
|
|
10
|
+
* could not tell that it was a refused connection.
|
|
11
|
+
*
|
|
12
|
+
* Order: the error's own message; else the messages of the errors it
|
|
13
|
+
* aggregates (each once); else its `cause`; else its `code`; else its name.
|
|
14
|
+
*/
|
|
15
|
+
export declare function errorText(err: unknown, depth?: number): string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The text the CLI prints for a caught error. A zero-import leaf, so every
|
|
3
|
+
* command can use it.
|
|
4
|
+
*
|
|
5
|
+
* Reading `.message` alone is not enough. When `localhost` resolves to both
|
|
6
|
+
* `::1` and `127.0.0.1` and both refuse, Node's `net` rejects with an
|
|
7
|
+
* `AggregateError` whose message is EMPTY: the reasons are on its `errors`.
|
|
8
|
+
* Every command then printed its prefix and nothing else ("Failed to start
|
|
9
|
+
* Studio: "), and the top-level handler, which classifies by message text,
|
|
10
|
+
* could not tell that it was a refused connection.
|
|
11
|
+
*
|
|
12
|
+
* Order: the error's own message; else the messages of the errors it
|
|
13
|
+
* aggregates (each once); else its `cause`; else its `code`; else its name.
|
|
14
|
+
*/
|
|
15
|
+
export function errorText(err, depth = 0) {
|
|
16
|
+
if (!(err instanceof Error))
|
|
17
|
+
return String(err);
|
|
18
|
+
if (err.message)
|
|
19
|
+
return err.message;
|
|
20
|
+
if (depth < 4) {
|
|
21
|
+
const inner = err.errors;
|
|
22
|
+
if (Array.isArray(inner)) {
|
|
23
|
+
const parts = [...new Set(inner.map((e) => errorText(e, depth + 1)).filter((t) => t !== ''))];
|
|
24
|
+
if (parts.length > 0)
|
|
25
|
+
return parts.join('; ');
|
|
26
|
+
}
|
|
27
|
+
if (err.cause !== undefined) {
|
|
28
|
+
const cause = errorText(err.cause, depth + 1);
|
|
29
|
+
if (cause !== '')
|
|
30
|
+
return cause;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const code = err.code;
|
|
34
|
+
if (typeof code === 'string' && code !== '')
|
|
35
|
+
return code;
|
|
36
|
+
return err.name || 'Error';
|
|
37
|
+
}
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
* npx turbine init --url postgres://...
|
|
26
26
|
* npx turbine migrate create add_users_table
|
|
27
27
|
*/
|
|
28
|
+
import { type SchemaMetadata } from '../schema.js';
|
|
29
|
+
import { type AppliedColumnTags } from './code-first-tags.js';
|
|
28
30
|
import type { CliOverrides, ConfigLoadError, ResolvedConfig, TurbineCliConfig } from './config.js';
|
|
29
31
|
import { type PrismaSchemaAst, type ResolvedPrismaDatasourceUrl } from './prisma-schema.js';
|
|
30
32
|
export interface CliArgs {
|
|
@@ -59,6 +61,15 @@ export interface CliArgs {
|
|
|
59
61
|
allowDrift?: boolean;
|
|
60
62
|
allowEmpty?: boolean;
|
|
61
63
|
allowDestructive?: boolean;
|
|
64
|
+
/** `migrate create --auto|--from-diff --allow-pending`: diff even while migrations are pending. */
|
|
65
|
+
allowPending?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* `push --dry-run --exit-code` / `migrate status --exit-code`: exit 2 when the
|
|
68
|
+
* database differs from the schema file / the migration history is not clean.
|
|
69
|
+
*/
|
|
70
|
+
exitCode?: boolean;
|
|
71
|
+
/** `migrate deploy --wait[=seconds]`: seconds to wait for a concurrent deploy's lock. */
|
|
72
|
+
wait?: number;
|
|
62
73
|
/** `migrate create --recipe <name>` scaffold selector. */
|
|
63
74
|
recipe?: string;
|
|
64
75
|
fix?: boolean;
|
|
@@ -76,7 +87,7 @@ export interface CliArgs {
|
|
|
76
87
|
metricsUrl?: string;
|
|
77
88
|
/** `doctor --no-plan-divergence`: skip the cached-plan divergence section (and its pg_stats read). */
|
|
78
89
|
noPlanDivergence?: boolean;
|
|
79
|
-
/** `doctor --allow-pooler`: run even when the connection string looks like a transaction pooler. */
|
|
90
|
+
/** `doctor` / `migrate up|deploy|down --allow-pooler`: run even when the connection string looks like a transaction pooler. */
|
|
80
91
|
allowPooler?: boolean;
|
|
81
92
|
/** `init --yes`/`-y`: accept every step's default non-interactively. */
|
|
82
93
|
yes?: boolean;
|
|
@@ -98,8 +109,8 @@ export interface CliArgs {
|
|
|
98
109
|
includeViews?: boolean;
|
|
99
110
|
/** Omit the `Generated at:` header line for reproducible (diff-stable) output. */
|
|
100
111
|
noTimestamp?: boolean;
|
|
101
|
-
/** `generate --import-ext <js|none|auto>`: sibling-import extension mode (F3). */
|
|
102
|
-
importExtension?: 'js' | 'none' | 'auto';
|
|
112
|
+
/** `generate --import-ext <js|ts|none|auto>`: sibling-import extension mode (F3). */
|
|
113
|
+
importExtension?: 'js' | 'ts' | 'none' | 'auto';
|
|
103
114
|
/** `generate --keep-column-names`: keep raw DB column names as field names (F4). */
|
|
104
115
|
keepColumnNames?: boolean;
|
|
105
116
|
/** `generate --legacy-to-many-uniques`: opt out of the unique-FK → hasOne flip (F2). */
|
|
@@ -133,6 +144,8 @@ export interface CliArgs {
|
|
|
133
144
|
/** `skill --dir <path>`: the skills root to install into (default `.claude/skills`). */
|
|
134
145
|
dir?: string;
|
|
135
146
|
}
|
|
147
|
+
/** How long a bare `migrate deploy --wait` waits for a concurrent deploy to finish. */
|
|
148
|
+
export declare const DEFAULT_LOCK_WAIT_SECONDS = 300;
|
|
136
149
|
/**
|
|
137
150
|
* The canonical name of `command`, or undefined when nothing dispatches it.
|
|
138
151
|
*
|
|
@@ -490,6 +503,43 @@ export declare function initEnvNotice(input: {
|
|
|
490
503
|
export declare function refuseSchemaFilePath(config: Pick<ResolvedConfig, 'schema' | 'schemaFile'>, options?: {
|
|
491
504
|
escapeHatch?: string;
|
|
492
505
|
}): void;
|
|
506
|
+
/**
|
|
507
|
+
* Overlay the column tags the defineSchema() file declares (`pii`, `updatedAt`,
|
|
508
|
+
* `maxLength`) onto an introspected schema, before it is generated.
|
|
509
|
+
*
|
|
510
|
+
* Introspection reads the database, and the database has no column for these
|
|
511
|
+
* flags, so a client generated from introspection alone returns PII by default,
|
|
512
|
+
* never stamps `updatedAt`, and leaves Studio and the MCP server with nothing to
|
|
513
|
+
* redact. The schema file is the only place the tags exist.
|
|
514
|
+
*
|
|
515
|
+
* Loaded when the file was named (`--schema-file` / config `schemaFile`) or sits
|
|
516
|
+
* at the default path. A named file that is missing, or any file that cannot be
|
|
517
|
+
* loaded, fails the command: carrying on would generate the unredacted client
|
|
518
|
+
* this exists to prevent. Only a missing DEFAULT file is skipped, which is a
|
|
519
|
+
* database-first project with no schema file at all.
|
|
520
|
+
*
|
|
521
|
+
* Returns what was applied, or `null` when there was no schema file to read.
|
|
522
|
+
*
|
|
523
|
+
* @internal exported for tests.
|
|
524
|
+
*/
|
|
525
|
+
export declare function overlayCodeFirstTags(schema: SchemaMetadata, config: Pick<ResolvedConfig, 'schemaFile' | 'schemaFileConfigured' | 'include' | 'exclude'>): Promise<AppliedColumnTags | null>;
|
|
526
|
+
/**
|
|
527
|
+
* The generator's `columnTypes`: the schema file's `tsType` declarations, then
|
|
528
|
+
* the config file's `columnTypes`, which wins on the same key. A config key
|
|
529
|
+
* whose table the run's include / exclude filters leave out is dropped, since
|
|
530
|
+
* that table has no generated type to override; every other key goes to the
|
|
531
|
+
* generator, which rejects one naming no table or column.
|
|
532
|
+
*
|
|
533
|
+
* An `include` list leaves out every table it does not name, so a misspelt
|
|
534
|
+
* table looks exactly like a real table that was left out. `databaseTables`
|
|
535
|
+
* (every table the schema holds, before filtering) tells them apart: only a
|
|
536
|
+
* table the database has is dropped, and a name it does not have goes on to the
|
|
537
|
+
* generator, which names the closest match. Without it every key outside the
|
|
538
|
+
* filters is dropped.
|
|
539
|
+
*
|
|
540
|
+
* @internal exported for tests.
|
|
541
|
+
*/
|
|
542
|
+
export declare function generatorColumnTypes(applied: Pick<AppliedColumnTags, 'columnTypes'> | null, config: Pick<ResolvedConfig, 'columnTypes' | 'include' | 'exclude'>, schema: Pick<SchemaMetadata, 'tables'>, databaseTables?: ReadonlySet<string>): Record<string, string> | undefined;
|
|
493
543
|
/**
|
|
494
544
|
* `turbine migrate-from-prisma --schema prisma/schema.prisma` parses a Prisma
|
|
495
545
|
* schema, resolve its models/fields/relations/compound-uniques against the live
|
|
@@ -557,6 +607,11 @@ export type SeedExecutionPlan = {
|
|
|
557
607
|
file: string;
|
|
558
608
|
};
|
|
559
609
|
export declare function getSeedExecutionPlan(seedFile: string): SeedExecutionPlan;
|
|
610
|
+
/**
|
|
611
|
+
* Why `migrate up`, `deploy` and `down` refuse a pooled endpoint. The reason is
|
|
612
|
+
* the migration lock, not the doctor's session-state argument.
|
|
613
|
+
*/
|
|
614
|
+
export declare const MIGRATE_POOLER_EXPLANATION: readonly string[];
|
|
560
615
|
/**
|
|
561
616
|
* Refuse a `doctor` run whose connection string points at a transaction pooler.
|
|
562
617
|
*
|
|
@@ -578,7 +633,10 @@ export declare function getSeedExecutionPlan(seedFile: string): SeedExecutionPla
|
|
|
578
633
|
* Exported for the unit test only (like {@link isLoopbackHost}): a gate whose
|
|
579
634
|
* failure path is `process.exit` is untestable through the command itself.
|
|
580
635
|
*/
|
|
581
|
-
export declare function refusePoolerConnection(url: string, args: CliArgs
|
|
636
|
+
export declare function refusePoolerConnection(url: string, args: CliArgs, refusal?: {
|
|
637
|
+
command: string;
|
|
638
|
+
explanation?: readonly string[];
|
|
639
|
+
}): void;
|
|
582
640
|
/**
|
|
583
641
|
* True when `host` is a loopback address Studio/Observe may bind without
|
|
584
642
|
* `--allow-remote`. Accepts IPv4, IPv6, and the common bracket form.
|