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/query/utils.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import pg from '#pg';
|
|
7
7
|
import { ValidationError } from '../errors.js';
|
|
8
|
+
import { consoleLogger, logTo } from '../logger.js';
|
|
8
9
|
import { camelToSnake, localDateTimeKind, snakeToCamel, timeOfDayKind } from '../schema.js';
|
|
9
10
|
import { shouldWarnOnce, WARN_NS } from './warn-registry.js';
|
|
10
11
|
// ---------------------------------------------------------------------------
|
|
@@ -259,9 +260,15 @@ export function canonicalWriteEntries(meta, entries) {
|
|
|
259
260
|
* where walker enumerates) never sees it and no emitted SQL can change.
|
|
260
261
|
*/
|
|
261
262
|
export const INTERNAL_COMBINATOR = Symbol.for('turbine.internalCombinator');
|
|
263
|
+
/** Shared by every brand: a frozen descriptor saves an allocation per branded where. */
|
|
264
|
+
const INTERNAL_COMBINATOR_DESCRIPTOR = Object.freeze({
|
|
265
|
+
value: true,
|
|
266
|
+
enumerable: false,
|
|
267
|
+
configurable: true,
|
|
268
|
+
});
|
|
262
269
|
/** Tag `where` as carrying a Turbine-synthesized combinator, and return it. */
|
|
263
270
|
export function markInternalCombinator(where) {
|
|
264
|
-
Object.defineProperty(where, INTERNAL_COMBINATOR,
|
|
271
|
+
Object.defineProperty(where, INTERNAL_COMBINATOR, INTERNAL_COMBINATOR_DESCRIPTOR);
|
|
265
272
|
return where;
|
|
266
273
|
}
|
|
267
274
|
/** Was this WHERE object's combinator synthesized by Turbine? */
|
|
@@ -275,12 +282,17 @@ export function isInternalCombinator(where) {
|
|
|
275
282
|
export function escSingleQuote(s) {
|
|
276
283
|
return s.replace(/'/g, "''");
|
|
277
284
|
}
|
|
285
|
+
const LIKE_META = /[\\%_]/;
|
|
286
|
+
const LIKE_META_G = /[\\%_]/g;
|
|
278
287
|
/**
|
|
279
288
|
* Escape LIKE pattern metacharacters: %, _, and \.
|
|
280
289
|
* Must be used with `ESCAPE '\'` in the LIKE clause.
|
|
281
290
|
*/
|
|
282
291
|
export function escapeLike(value) {
|
|
283
|
-
|
|
292
|
+
// One pass, and none at all for the common operand with no metacharacter.
|
|
293
|
+
// Identical output to escaping `\` then `%` then `_` in turn: the backslashes
|
|
294
|
+
// the first step adds are never `%` or `_`, so the steps never interact.
|
|
295
|
+
return LIKE_META.test(value) ? value.replace(LIKE_META_G, '\\$&') : value;
|
|
284
296
|
}
|
|
285
297
|
// ---------------------------------------------------------------------------
|
|
286
298
|
// LRU cache, bounded SQL template cache to prevent memory leaks
|
|
@@ -366,6 +378,307 @@ export function fnv1a64Hex(s) {
|
|
|
366
378
|
export function sqlToPreparedName(sql) {
|
|
367
379
|
return `t_${fnv1a64Hex(sql)}`;
|
|
368
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* How many distinct statement names one process hands out. Every connection
|
|
383
|
+
* keeps each named statement it has prepared, at 9 to 45 KB of server memory
|
|
384
|
+
* apiece, until the connection closes, so the number of names is what bounds
|
|
385
|
+
* that memory. A statement past the budget is sent unnamed: parsed on every
|
|
386
|
+
* execution and never kept, which costs a few microseconds on a cheap query.
|
|
387
|
+
*/
|
|
388
|
+
export const PREPARED_STATEMENT_BUDGET = 200;
|
|
389
|
+
/** `Symbol.for` so the ESM and CJS copies of this module share one budget. */
|
|
390
|
+
const PREPARED_NAMES = Symbol.for('turbine.preparedStatementNames');
|
|
391
|
+
function preparedNames() {
|
|
392
|
+
const holder = globalThis;
|
|
393
|
+
let names = holder[PREPARED_NAMES];
|
|
394
|
+
if (!names) {
|
|
395
|
+
names = new Set();
|
|
396
|
+
holder[PREPARED_NAMES] = names;
|
|
397
|
+
}
|
|
398
|
+
return names;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* The prepared-statement name for `sql`, or `''` (send it unnamed) once the
|
|
402
|
+
* process has named {@link PREPARED_STATEMENT_BUDGET} other statements.
|
|
403
|
+
*
|
|
404
|
+
* The first shapes a process runs keep their names for its lifetime, which in
|
|
405
|
+
* practice are its hot shapes. A server-side `DEALLOCATE` cannot replace this:
|
|
406
|
+
* a pooled query does not know which connection it will run on, and a
|
|
407
|
+
* connection keeps every name it ever prepared.
|
|
408
|
+
*/
|
|
409
|
+
export function boundedPreparedName(sql) {
|
|
410
|
+
return claimPreparedName(sqlToPreparedName(sql));
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Claim an already derived statement name from the same budget as
|
|
414
|
+
* {@link boundedPreparedName}: `name` itself, or `''` (send it unnamed) once
|
|
415
|
+
* the budget is spent. `sqlComment`'s `c_` names (query/sql-comment.ts) are
|
|
416
|
+
* claimed here, so a commented statement costs one slot like any other.
|
|
417
|
+
*/
|
|
418
|
+
export function claimPreparedName(name) {
|
|
419
|
+
const names = preparedNames();
|
|
420
|
+
if (names.has(name))
|
|
421
|
+
return name;
|
|
422
|
+
if (names.size >= PREPARED_STATEMENT_BUDGET)
|
|
423
|
+
return '';
|
|
424
|
+
names.add(name);
|
|
425
|
+
return name;
|
|
426
|
+
}
|
|
427
|
+
/** @internal Tests only: forget every name handed out. */
|
|
428
|
+
export function resetPreparedNameBudget() {
|
|
429
|
+
preparedNames().clear();
|
|
430
|
+
}
|
|
431
|
+
function argsRewriteError(model, action, path) {
|
|
432
|
+
return new TypeError(`[turbine] middleware cannot change \`params.${path}\` on ${model}.${action}: the query is compiled from the ` +
|
|
433
|
+
'arguments the caller passed, so a change made in middleware would not reliably reach it. Put the ' +
|
|
434
|
+
'condition in the query itself, or use `globalFilters` for a filter that applies to every query.');
|
|
435
|
+
}
|
|
436
|
+
/** Plain objects and arrays are copied; a Date, Buffer, class instance or other value is shared as it is. */
|
|
437
|
+
function isPlainContainer(value) {
|
|
438
|
+
if (value === null || typeof value !== 'object')
|
|
439
|
+
return false;
|
|
440
|
+
if (Array.isArray(value))
|
|
441
|
+
return true;
|
|
442
|
+
const proto = Object.getPrototypeOf(value);
|
|
443
|
+
return proto === Object.prototype || proto === null;
|
|
444
|
+
}
|
|
445
|
+
/** Nesting past this is shared, not copied, so a cyclic object cannot recurse without end. */
|
|
446
|
+
const MIDDLEWARE_ARGS_MAX_DEPTH = 100;
|
|
447
|
+
/**
|
|
448
|
+
* A copy of `value` that middleware may read, spread, serialize or
|
|
449
|
+
* `structuredClone`: plain objects and arrays are copied at every depth, and
|
|
450
|
+
* everything else (Date, Buffer, bigint, symbol, class instance) is shared.
|
|
451
|
+
* Symbol-keyed properties are copied by descriptor, so the markers the
|
|
452
|
+
* pipeline reads (the internal-combinator brand, the inclusive-cursor flag,
|
|
453
|
+
* column and expression brands) are on the copy exactly as on the original,
|
|
454
|
+
* non-enumerable ones included.
|
|
455
|
+
*/
|
|
456
|
+
function copyArgs(value, depth) {
|
|
457
|
+
if (depth > MIDDLEWARE_ARGS_MAX_DEPTH || !isPlainContainer(value))
|
|
458
|
+
return value;
|
|
459
|
+
let out;
|
|
460
|
+
if (Array.isArray(value)) {
|
|
461
|
+
const items = new Array(value.length);
|
|
462
|
+
// A long list of ids is the common big argument; keep its scalars off the call path.
|
|
463
|
+
for (let i = 0; i < value.length; i++) {
|
|
464
|
+
const item = value[i];
|
|
465
|
+
items[i] = item !== null && typeof item === 'object' ? copyArgs(item, depth + 1) : item;
|
|
466
|
+
}
|
|
467
|
+
out = items;
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
out = Object.getPrototypeOf(value) === null ? Object.create(null) : {};
|
|
471
|
+
for (const key of Object.keys(value)) {
|
|
472
|
+
const item = value[key];
|
|
473
|
+
const copied = item !== null && typeof item === 'object' ? copyArgs(item, depth + 1) : item;
|
|
474
|
+
// `__proto__` from JSON.parse is an own key; assigning it would set the prototype.
|
|
475
|
+
if (key === '__proto__')
|
|
476
|
+
Object.defineProperty(out, key, { value: copied, enumerable: true, writable: true, configurable: true });
|
|
477
|
+
else
|
|
478
|
+
out[key] = copied;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
for (const sym of Object.getOwnPropertySymbols(value)) {
|
|
482
|
+
Object.defineProperty(out, sym, Object.getOwnPropertyDescriptor(value, sym));
|
|
483
|
+
}
|
|
484
|
+
return out;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Where `b` first differs from `a`, as a path suffix (`.where.id`, or `''` for
|
|
488
|
+
* the values themselves), or undefined when they are structurally equal. The
|
|
489
|
+
* suffix is built on the way back out, so an equal pair costs no string work.
|
|
490
|
+
*/
|
|
491
|
+
function argsDifference(a, b, depth) {
|
|
492
|
+
if (Object.is(a, b))
|
|
493
|
+
return undefined;
|
|
494
|
+
if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object')
|
|
495
|
+
return '';
|
|
496
|
+
if (a instanceof Date || b instanceof Date) {
|
|
497
|
+
return a instanceof Date && b instanceof Date && Object.is(a.getTime(), b.getTime()) ? undefined : '';
|
|
498
|
+
}
|
|
499
|
+
if (ArrayBuffer.isView(a) || ArrayBuffer.isView(b)) {
|
|
500
|
+
if (!(ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) || a.byteLength !== b.byteLength)
|
|
501
|
+
return '';
|
|
502
|
+
const x = new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
|
|
503
|
+
const y = new Uint8Array(b.buffer, b.byteOffset, b.byteLength);
|
|
504
|
+
for (let i = 0; i < x.length; i++)
|
|
505
|
+
if (x[i] !== y[i])
|
|
506
|
+
return '';
|
|
507
|
+
return undefined;
|
|
508
|
+
}
|
|
509
|
+
if (a instanceof RegExp || b instanceof RegExp)
|
|
510
|
+
return String(a) === String(b) ? undefined : '';
|
|
511
|
+
if (a instanceof Map || a instanceof Set || b instanceof Map || b instanceof Set)
|
|
512
|
+
return '';
|
|
513
|
+
if (Array.isArray(a) !== Array.isArray(b) || depth > MIDDLEWARE_ARGS_MAX_DEPTH)
|
|
514
|
+
return '';
|
|
515
|
+
if (Array.isArray(a)) {
|
|
516
|
+
const other = b;
|
|
517
|
+
const shared = Math.min(a.length, other.length);
|
|
518
|
+
for (let i = 0; i < shared; i++) {
|
|
519
|
+
if (Object.is(a[i], other[i]))
|
|
520
|
+
continue;
|
|
521
|
+
const inner = argsDifference(a[i], other[i], depth + 1);
|
|
522
|
+
if (inner !== undefined)
|
|
523
|
+
return `.${i}${inner}`;
|
|
524
|
+
}
|
|
525
|
+
return a.length === other.length ? undefined : `.${shared}`;
|
|
526
|
+
}
|
|
527
|
+
const left = a;
|
|
528
|
+
const right = b;
|
|
529
|
+
for (const key of Object.keys(left)) {
|
|
530
|
+
if (!Object.hasOwn(right, key))
|
|
531
|
+
return `.${key}`;
|
|
532
|
+
const inner = argsDifference(left[key], right[key], depth + 1);
|
|
533
|
+
if (inner !== undefined)
|
|
534
|
+
return `.${key}${inner}`;
|
|
535
|
+
}
|
|
536
|
+
for (const key of Object.keys(right))
|
|
537
|
+
if (!Object.hasOwn(left, key))
|
|
538
|
+
return `.${key}`;
|
|
539
|
+
return undefined;
|
|
540
|
+
}
|
|
541
|
+
const MIDDLEWARE_ARGS = new WeakMap();
|
|
542
|
+
/** The key Node's `util.inspect` looks for, from the global registry so no `node:` import is needed. */
|
|
543
|
+
const NODE_INSPECT = Symbol.for('nodejs.util.inspect.custom');
|
|
544
|
+
/** Makes `console.log(params)` show the values rather than an accessor. Not own, so a spread of `params` never carries it. */
|
|
545
|
+
const MIDDLEWARE_PARAMS_PROTOTYPE = Object.defineProperty({}, NODE_INSPECT, {
|
|
546
|
+
value(_depth, options, inspect) {
|
|
547
|
+
return inspect({ model: this.model, action: this.action, args: this.args }, options);
|
|
548
|
+
},
|
|
549
|
+
});
|
|
550
|
+
/**
|
|
551
|
+
* The `params` a middleware receives. `params.args` is a plain copy of the
|
|
552
|
+
* caller's args, made when it is first read, so middleware can read it, spread
|
|
553
|
+
* it, serialize it and `structuredClone` it, and one that never reads it pays
|
|
554
|
+
* nothing.
|
|
555
|
+
*
|
|
556
|
+
* It is a copy because the query is compiled from the caller's args, not from
|
|
557
|
+
* what middleware sees. A change to the copy would never reach the query, so
|
|
558
|
+
* {@link assertMiddlewareArgsKept} refuses it, naming the path, when the
|
|
559
|
+
* middleware calls `next`. That check compares contents, which is why the copy
|
|
560
|
+
* is not frozen: a frozen object ignores a write silently in a script that is
|
|
561
|
+
* not in strict mode, and a filter added there would be dropped without a word.
|
|
562
|
+
* Assigning to `params.args` itself throws at once.
|
|
563
|
+
*/
|
|
564
|
+
export function readOnlyMiddlewareParams(model, action, args) {
|
|
565
|
+
const state = { model, action, original: args, copy: undefined };
|
|
566
|
+
const params = {
|
|
567
|
+
__proto__: MIDDLEWARE_PARAMS_PROTOTYPE,
|
|
568
|
+
model,
|
|
569
|
+
action,
|
|
570
|
+
get args() {
|
|
571
|
+
if (state.copy === undefined)
|
|
572
|
+
state.copy = copyArgs(args, 0);
|
|
573
|
+
return state.copy;
|
|
574
|
+
},
|
|
575
|
+
set args(_replacement) {
|
|
576
|
+
throw argsRewriteError(model, action, 'args');
|
|
577
|
+
},
|
|
578
|
+
};
|
|
579
|
+
MIDDLEWARE_ARGS.set(params, state);
|
|
580
|
+
return params;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Refuse a `next(p)` whose `p.args` is not the caller's args: one that
|
|
584
|
+
* middleware changed in place, or replaced with an object that differs from
|
|
585
|
+
* them. A copy that is structurally equal passes (`structuredClone(params.args)`,
|
|
586
|
+
* `{ ...params.args }`), because nothing it would change is lost. Throws a
|
|
587
|
+
* TypeError naming the first path that differs.
|
|
588
|
+
*/
|
|
589
|
+
export function assertMiddlewareArgsKept(handedOut, passed) {
|
|
590
|
+
const state = MIDDLEWARE_ARGS.get(handedOut);
|
|
591
|
+
if (passed === handedOut) {
|
|
592
|
+
if (handedOut.model !== state.model)
|
|
593
|
+
throw argsRewriteError(state.model, state.action, 'model');
|
|
594
|
+
if (handedOut.action !== state.action)
|
|
595
|
+
throw argsRewriteError(state.model, state.action, 'action');
|
|
596
|
+
// Nothing has read the args, so nothing could have changed them.
|
|
597
|
+
if (state.copy === undefined)
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
const given = passed?.args;
|
|
601
|
+
if (given === state.original)
|
|
602
|
+
return;
|
|
603
|
+
const changed = argsDifference(state.original, given, 0);
|
|
604
|
+
if (changed !== undefined)
|
|
605
|
+
throw argsRewriteError(state.model, state.action, `args${changed}`);
|
|
606
|
+
}
|
|
607
|
+
/** Whether `pool` is a node-postgres `Pool`, whose checkouts are real backend connections. */
|
|
608
|
+
export function isNodePostgresPool(pool) {
|
|
609
|
+
const Pool = pg.Pool;
|
|
610
|
+
return typeof Pool === 'function' && pool instanceof Pool;
|
|
611
|
+
}
|
|
612
|
+
/** Whether `client` is a node-postgres connection a cancel request can reach. */
|
|
613
|
+
export function canCancelBackend(client) {
|
|
614
|
+
const c = client;
|
|
615
|
+
return (typeof pg.Connection === 'function' &&
|
|
616
|
+
!!c &&
|
|
617
|
+
typeof c.processID === 'number' &&
|
|
618
|
+
typeof c.secretKey === 'number' &&
|
|
619
|
+
(typeof c.port === 'number' || typeof c.port === 'string'));
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Ask the server to cancel whatever `client`'s backend is running, with the
|
|
623
|
+
* protocol's CancelRequest: a separate short connection carrying the backend's
|
|
624
|
+
* process id and secret key. No login, no session state, and a pooler forwards
|
|
625
|
+
* it to the right server backend, which `pg_cancel_backend(pid)` through a
|
|
626
|
+
* pooler would not.
|
|
627
|
+
*
|
|
628
|
+
* Resolves `true` once the server has closed the cancel connection, i.e. has
|
|
629
|
+
* signalled the backend; `false` when the request could not be delivered
|
|
630
|
+
* within `waitMs`. A cancel that arrives after the statement finished is
|
|
631
|
+
* ignored by the server.
|
|
632
|
+
*/
|
|
633
|
+
export function sendCancelRequest(client, waitMs) {
|
|
634
|
+
if (!canCancelBackend(client))
|
|
635
|
+
return Promise.resolve(false);
|
|
636
|
+
const target = client;
|
|
637
|
+
const Connection = pg.Connection;
|
|
638
|
+
return new Promise((resolve) => {
|
|
639
|
+
let con;
|
|
640
|
+
let sent = false;
|
|
641
|
+
let done = false;
|
|
642
|
+
const finish = (delivered) => {
|
|
643
|
+
if (done)
|
|
644
|
+
return;
|
|
645
|
+
done = true;
|
|
646
|
+
clearTimeout(timer);
|
|
647
|
+
try {
|
|
648
|
+
con?.stream?.destroy?.();
|
|
649
|
+
}
|
|
650
|
+
catch {
|
|
651
|
+
// already closed
|
|
652
|
+
}
|
|
653
|
+
resolve(delivered);
|
|
654
|
+
};
|
|
655
|
+
const timer = setTimeout(() => finish(false), waitMs);
|
|
656
|
+
try {
|
|
657
|
+
con = new Connection({ ssl: target.ssl, sslNegotiation: target.sslNegotiation });
|
|
658
|
+
const send = () => {
|
|
659
|
+
sent = true;
|
|
660
|
+
con?.cancel(target.processID, target.secretKey);
|
|
661
|
+
};
|
|
662
|
+
con.on('error', () => finish(false));
|
|
663
|
+
con.on('end', () => finish(sent));
|
|
664
|
+
con.on('connect', () => {
|
|
665
|
+
if (!target.ssl)
|
|
666
|
+
send();
|
|
667
|
+
else if (target.sslNegotiation !== 'direct')
|
|
668
|
+
con?.requestSsl();
|
|
669
|
+
});
|
|
670
|
+
con.on('sslconnect', send);
|
|
671
|
+
const host = typeof target.host === 'string' ? target.host : undefined;
|
|
672
|
+
if (host?.startsWith('/'))
|
|
673
|
+
con.connect(`${host}/.s.PGSQL.${String(target.port)}`);
|
|
674
|
+
else
|
|
675
|
+
con.connect(target.port, host);
|
|
676
|
+
}
|
|
677
|
+
catch {
|
|
678
|
+
finish(false);
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
}
|
|
369
682
|
/** Known operator keys, used to detect operator objects vs plain values */
|
|
370
683
|
export const OPERATOR_KEYS = new Set([
|
|
371
684
|
'equals',
|
|
@@ -527,6 +840,12 @@ const TZ_SUFFIX_RE = /(?:Z|[+-]\d{2}(?::?\d{2})?)$/;
|
|
|
527
840
|
* as-is.
|
|
528
841
|
*/
|
|
529
842
|
export function parseDbDate(value) {
|
|
843
|
+
const date = parseDbDateCommon(value);
|
|
844
|
+
// The shapes JS cannot parse (a BC year, an offset with seconds, a year past
|
|
845
|
+
// 9999) come out invalid; only those pay for the general parse.
|
|
846
|
+
return Number.isNaN(date.getTime()) ? (parseDbDateGeneral(value) ?? date) : date;
|
|
847
|
+
}
|
|
848
|
+
function parseDbDateCommon(value) {
|
|
530
849
|
// Date-only values (`2026-07-07`, from `date` columns in json_agg output)
|
|
531
850
|
// have no time to zone-pin, and their `-07` tail must not be read as an
|
|
532
851
|
// offset. JS parses bare ISO dates as UTC midnight already.
|
|
@@ -541,6 +860,79 @@ export function parseDbDate(value) {
|
|
|
541
860
|
// normalize `YYYY-MM-DD HH:MM:SS` (driver form) to ISO before pinning UTC
|
|
542
861
|
return new Date(`${value.replace(' ', 'T')}Z`);
|
|
543
862
|
}
|
|
863
|
+
const DB_DATE_GENERAL_RE = /^(\d{4,})-(\d\d)-(\d\d)(?:[T ](\d\d):(\d\d):(\d\d)(\.\d+)?)?(?:(Z)|([+-])(\d\d)(?::?(\d\d))?(?::?(\d\d))?)?( BC)?$/;
|
|
864
|
+
/**
|
|
865
|
+
* The Postgres renderings JS `Date` cannot parse: a ` BC` suffix, a
|
|
866
|
+
* local-mean-time offset with seconds (`-04:56:02`, any zone before about
|
|
867
|
+
* 1900), and years of five or more digits. Offset-less text is read as UTC,
|
|
868
|
+
* as in {@link parseDbDate}. `undefined` when the text is none of these.
|
|
869
|
+
*/
|
|
870
|
+
function parseDbDateGeneral(value) {
|
|
871
|
+
const m = DB_DATE_GENERAL_RE.exec(value);
|
|
872
|
+
if (!m)
|
|
873
|
+
return undefined;
|
|
874
|
+
let year = Number(m[1]);
|
|
875
|
+
if (m[13])
|
|
876
|
+
year = 1 - year;
|
|
877
|
+
const month = Number(m[2]) - 1;
|
|
878
|
+
const ms = m[7] ? Math.trunc(1000 * Number(`0${m[7]}`)) : 0;
|
|
879
|
+
const date = new Date(Date.UTC(2000, month, Number(m[3]), Number(m[4] ?? 0), Number(m[5] ?? 0), Number(m[6] ?? 0), ms));
|
|
880
|
+
// setUTCFullYear, not Date.UTC: Date.UTC reads years 0-99 as 1900-1999.
|
|
881
|
+
date.setUTCFullYear(year, month, Number(m[3]));
|
|
882
|
+
if (m[9]) {
|
|
883
|
+
const offset = Number(m[10]) * 3600 + Number(m[11] ?? 0) * 60 + Number(m[12] ?? 0);
|
|
884
|
+
return new Date(date.getTime() - (m[9] === '-' ? -offset : offset) * 1000);
|
|
885
|
+
}
|
|
886
|
+
return date;
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* A float column's value as the driver reads it. JSON has no NaN or infinity,
|
|
890
|
+
* so `json_build_object` renders them as the strings "NaN", "Infinity" and
|
|
891
|
+
* "-Infinity"; every other value passes through.
|
|
892
|
+
*/
|
|
893
|
+
export function readJsonFloat(value) {
|
|
894
|
+
if (typeof value !== 'string')
|
|
895
|
+
return value;
|
|
896
|
+
if (value === 'NaN')
|
|
897
|
+
return Number.NaN;
|
|
898
|
+
if (value === 'Infinity')
|
|
899
|
+
return Number.POSITIVE_INFINITY;
|
|
900
|
+
if (value === '-Infinity')
|
|
901
|
+
return Number.NEGATIVE_INFINITY;
|
|
902
|
+
return value;
|
|
903
|
+
}
|
|
904
|
+
/** {@link readJsonFloat} over a float array (at any depth); the input when nothing changes. */
|
|
905
|
+
export function readJsonFloatArray(value) {
|
|
906
|
+
if (!Array.isArray(value))
|
|
907
|
+
return value;
|
|
908
|
+
let out;
|
|
909
|
+
for (let i = 0; i < value.length; i++) {
|
|
910
|
+
const el = value[i];
|
|
911
|
+
const read = Array.isArray(el) ? readJsonFloatArray(el) : readJsonFloat(el);
|
|
912
|
+
if (read !== el) {
|
|
913
|
+
out ??= value.slice();
|
|
914
|
+
out[i] = read;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
return out ?? value;
|
|
918
|
+
}
|
|
919
|
+
let textArrayParser;
|
|
920
|
+
/**
|
|
921
|
+
* An array column the driver has no parser for (an enum array) arrives as
|
|
922
|
+
* Postgres array text, `{ok,sad}`; read it into an array of strings, as the
|
|
923
|
+
* same column reads through a relation. The input when it cannot be parsed.
|
|
924
|
+
*/
|
|
925
|
+
export function readTextArray(value) {
|
|
926
|
+
if (typeof value !== 'string' || value.charCodeAt(0) !== 123)
|
|
927
|
+
return value;
|
|
928
|
+
try {
|
|
929
|
+
textArrayParser ??= createPgArrayParser((text) => text);
|
|
930
|
+
return textArrayParser(value) ?? value;
|
|
931
|
+
}
|
|
932
|
+
catch {
|
|
933
|
+
return value;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
544
936
|
/**
|
|
545
937
|
* Is `value` one of the two representations a Postgres temporal `infinity` /
|
|
546
938
|
* `-infinity` reaches the ORM row parser in?
|
|
@@ -1079,7 +1471,7 @@ export function isDefaultTextParser(oid, parser) {
|
|
|
1079
1471
|
* construction, and only when somebody else's non-default parser is actually
|
|
1080
1472
|
* being replaced.
|
|
1081
1473
|
*/
|
|
1082
|
-
export function warnParserOverwrite(oid, typeName) {
|
|
1474
|
+
export function warnParserOverwrite(oid, typeName, logger = consoleLogger) {
|
|
1083
1475
|
const getParser = pg.types.getTypeParser;
|
|
1084
1476
|
const current = getParser(oid, 'text');
|
|
1085
1477
|
// Turbine's own earlier registration is not a third party's expectation.
|
|
@@ -1098,13 +1490,13 @@ export function warnParserOverwrite(oid, typeName) {
|
|
|
1098
1490
|
const remedy = TEMPORAL_PARSER_OIDS.has(oid)
|
|
1099
1491
|
? ' `utcTimestamps: false` leaves the six temporal OIDs (1114, 1082, 1115, 1182, 1184, 1185) alone entirely.'
|
|
1100
1492
|
: ' There is no opt-out for this OID: Turbine registers it so bigint values come back as numbers.';
|
|
1101
|
-
|
|
1493
|
+
logTo(logger, 'warn', `pg type parser for OID ${oid} (${typeName}) was already customized by something else in this ` +
|
|
1102
1494
|
'process, and Turbine is replacing it. `pg.types.setTypeParser` is process-global and takes effect ' +
|
|
1103
1495
|
'immediately for EVERY pg.Pool in the process, including pools that already exist and are already ' +
|
|
1104
1496
|
'querying, so whatever set that parser will now read this column differently. If yours should win, ' +
|
|
1105
1497
|
`register it AFTER constructing the client.${remedy} This warning fires under \`NODE_ENV=production\` ` +
|
|
1106
1498
|
'too: which parser wins depends on module evaluation order, so a process can be clean in dev and wrong ' +
|
|
1107
|
-
'in production from import order alone.');
|
|
1499
|
+
'in production from import order alone.', { code: 'parser-overwrite', oid, type: typeName });
|
|
1108
1500
|
}
|
|
1109
1501
|
/**
|
|
1110
1502
|
* Register Turbine's temporal text parsers on the pg module. SIX OIDs, doing
|
|
@@ -1140,16 +1532,16 @@ export function warnParserOverwrite(oid, typeName) {
|
|
|
1140
1532
|
* per row at decode time, not captured per pool). If any of the four OIDs is
|
|
1141
1533
|
* already on a NON-default parser, {@link warnParserOverwrite} says so once.
|
|
1142
1534
|
*/
|
|
1143
|
-
export function registerUtcTemporalParsers() {
|
|
1535
|
+
export function registerUtcTemporalParsers(logger) {
|
|
1144
1536
|
// pg-types declares get/setTypeParser over its own OID enum, which lists the
|
|
1145
1537
|
// scalar types only. The array OIDs are just as real, so both calls are
|
|
1146
1538
|
// retyped over a plain number rather than the incomplete enum.
|
|
1147
1539
|
const getParser = pg.types.getTypeParser;
|
|
1148
1540
|
const setParser = pg.types.setTypeParser;
|
|
1149
|
-
warnParserOverwrite(1114, 'timestamp');
|
|
1150
|
-
warnParserOverwrite(1082, 'date');
|
|
1151
|
-
warnParserOverwrite(1115, 'timestamp[]');
|
|
1152
|
-
warnParserOverwrite(1182, 'date[]');
|
|
1541
|
+
warnParserOverwrite(1114, 'timestamp', logger);
|
|
1542
|
+
warnParserOverwrite(1082, 'date', logger);
|
|
1543
|
+
warnParserOverwrite(1115, 'timestamp[]', logger);
|
|
1544
|
+
warnParserOverwrite(1182, 'date[]', logger);
|
|
1153
1545
|
const parseDate = createUtcDateParser(getParser(1082, 'text'));
|
|
1154
1546
|
const parseTimestamp = createUtcTimestampParser(getParser(1114, 'text'));
|
|
1155
1547
|
setParser(1114, markTurbineParser(parseTimestamp));
|
|
@@ -1318,8 +1710,11 @@ function nameCloseness(input, candidate) {
|
|
|
1318
1710
|
const b = candidate.toLowerCase();
|
|
1319
1711
|
if (a === b)
|
|
1320
1712
|
return 1000;
|
|
1321
|
-
|
|
1713
|
+
// Containment only between names of 3+ characters: `id` sits inside almost
|
|
1714
|
+
// any name, so `tenentId` must reach `tenantId` by edit distance instead.
|
|
1715
|
+
if (Math.min(a.length, b.length) >= 3 && (a.includes(b) || b.includes(a))) {
|
|
1322
1716
|
return 500 + Math.min(a.length, b.length);
|
|
1717
|
+
}
|
|
1323
1718
|
// Levenshtein, bounded: only near-misses are worth suggesting.
|
|
1324
1719
|
const rows = a.length + 1;
|
|
1325
1720
|
const cols = b.length + 1;
|
|
@@ -1459,7 +1854,7 @@ export function relationInProjectionMessage(table, field, clause) {
|
|
|
1459
1854
|
' out of the result.';
|
|
1460
1855
|
}
|
|
1461
1856
|
/**
|
|
1462
|
-
*
|
|
1857
|
+
* Advice (`diagnostics.advice`) for a sort/grouping term Turbine dropped as redundant
|
|
1463
1858
|
* (`filters.ts` `dedupeOrderEntries` / `dedupeColumnList`).
|
|
1464
1859
|
*
|
|
1465
1860
|
* Deliberately a WARNING and not an error, and deliberately not silent either.
|
|
@@ -1468,15 +1863,16 @@ export function relationInProjectionMessage(table, field, clause) {
|
|
|
1468
1863
|
* plus an unconditional primary-key tiebreak, which collide exactly when the
|
|
1469
1864
|
* caller sorts by the primary key). But a caller who wrote the duplicate by
|
|
1470
1865
|
* hand, or who believes a second key is doing something, should hear about it
|
|
1471
|
-
* once.
|
|
1866
|
+
* once. Advice for the usual reason: it describes the QUERY the application
|
|
1472
1867
|
* sends, which does not vary with the environment, so a production process
|
|
1473
|
-
* learns nothing from repeating it.
|
|
1868
|
+
* learns nothing from repeating it. The caller passes its advice logger, or
|
|
1869
|
+
* `undefined` when advice is off.
|
|
1474
1870
|
*
|
|
1475
1871
|
* Keyed per dropped key, so a sort assembled per request from the same UI says
|
|
1476
1872
|
* it once rather than once per request.
|
|
1477
1873
|
*/
|
|
1478
|
-
export function warnRedundantSortTerm(table, clause, dropped) {
|
|
1479
|
-
if (
|
|
1874
|
+
export function warnRedundantSortTerm(advice, table, clause, dropped) {
|
|
1875
|
+
if (!advice)
|
|
1480
1876
|
return;
|
|
1481
1877
|
for (const d of dropped) {
|
|
1482
1878
|
if (!shouldWarnOnce(WARN_NS.redundantSortTerm, `${table}|${clause}|${d.key}`))
|
|
@@ -1484,9 +1880,9 @@ export function warnRedundantSortTerm(table, clause, dropped) {
|
|
|
1484
1880
|
const how = d.first === d.key
|
|
1485
1881
|
? `"${d.key}" is named twice`
|
|
1486
1882
|
: `"${d.key}" and the earlier "${d.first}" both target ${d.resolved}`;
|
|
1487
|
-
|
|
1883
|
+
logTo(advice, 'warn', `${clause} on table "${table}": ${how}, so the later one was dropped. It could not have ` +
|
|
1488
1884
|
'changed the result (the earlier term already orders those rows), and dropping it keeps the query on ' +
|
|
1489
|
-
'a shared prepared statement. Remove it to silence this.');
|
|
1885
|
+
'a shared prepared statement. Remove it to silence this.', { code: 'redundant-sort-term', table, clause, key: d.key });
|
|
1490
1886
|
}
|
|
1491
1887
|
}
|
|
1492
1888
|
/**
|
|
@@ -127,6 +127,17 @@ export declare const WARN_NS: {
|
|
|
127
127
|
* Keyed on the requested mode.
|
|
128
128
|
*/
|
|
129
129
|
readonly planCacheModeIgnored: "planCacheModeIgnored";
|
|
130
|
+
/**
|
|
131
|
+
* `statementTimeoutMs` or `maxLifetimeSeconds` was set on a client given an
|
|
132
|
+
* EXTERNAL pool, whose connections Turbine never opens (client.ts
|
|
133
|
+
* `warnExternalPoolOptions`). Keyed on the option name.
|
|
134
|
+
*/
|
|
135
|
+
readonly connectionOptionIgnored: "connectionOptionIgnored";
|
|
136
|
+
/**
|
|
137
|
+
* `preparedStatements: true` on a connection string that names a connection
|
|
138
|
+
* pooler (client.ts `warnPreparedOnPooler`). Keyed on `host:port`.
|
|
139
|
+
*/
|
|
140
|
+
readonly preparedStatementsOnPooler: "preparedStatementsOnPooler";
|
|
130
141
|
/**
|
|
131
142
|
* A relation's JSON payload could not be parsed in `parseNestedRow`
|
|
132
143
|
* (relations.ts). Keyed on `table.relation`, so a page of 10,000 rows whose
|
|
@@ -134,4 +145,11 @@ export declare const WARN_NS: {
|
|
|
134
145
|
* warn sits in the per-relation loop, which runs once per row per relation.
|
|
135
146
|
*/
|
|
136
147
|
readonly relationParseFailure: "relationParseFailure";
|
|
148
|
+
/**
|
|
149
|
+
* `findMany` with no row limit (builder.ts `maybeWarnUnlimited`, powql.ts).
|
|
150
|
+
* Keyed on the table and process-wide: every `$transaction` builds its own
|
|
151
|
+
* query interfaces, so a dedupe held on the interface said it again in
|
|
152
|
+
* every transaction.
|
|
153
|
+
*/
|
|
154
|
+
readonly unlimitedFindMany: "unlimitedFindMany";
|
|
137
155
|
};
|
|
@@ -162,6 +162,17 @@ export const WARN_NS = {
|
|
|
162
162
|
* Keyed on the requested mode.
|
|
163
163
|
*/
|
|
164
164
|
planCacheModeIgnored: 'planCacheModeIgnored',
|
|
165
|
+
/**
|
|
166
|
+
* `statementTimeoutMs` or `maxLifetimeSeconds` was set on a client given an
|
|
167
|
+
* EXTERNAL pool, whose connections Turbine never opens (client.ts
|
|
168
|
+
* `warnExternalPoolOptions`). Keyed on the option name.
|
|
169
|
+
*/
|
|
170
|
+
connectionOptionIgnored: 'connectionOptionIgnored',
|
|
171
|
+
/**
|
|
172
|
+
* `preparedStatements: true` on a connection string that names a connection
|
|
173
|
+
* pooler (client.ts `warnPreparedOnPooler`). Keyed on `host:port`.
|
|
174
|
+
*/
|
|
175
|
+
preparedStatementsOnPooler: 'preparedStatementsOnPooler',
|
|
165
176
|
/**
|
|
166
177
|
* A relation's JSON payload could not be parsed in `parseNestedRow`
|
|
167
178
|
* (relations.ts). Keyed on `table.relation`, so a page of 10,000 rows whose
|
|
@@ -169,4 +180,11 @@ export const WARN_NS = {
|
|
|
169
180
|
* warn sits in the per-relation loop, which runs once per row per relation.
|
|
170
181
|
*/
|
|
171
182
|
relationParseFailure: 'relationParseFailure',
|
|
183
|
+
/**
|
|
184
|
+
* `findMany` with no row limit (builder.ts `maybeWarnUnlimited`, powql.ts).
|
|
185
|
+
* Keyed on the table and process-wide: every `$transaction` builds its own
|
|
186
|
+
* query interfaces, so a dedupe held on the interface said it again in
|
|
187
|
+
* every transaction.
|
|
188
|
+
*/
|
|
189
|
+
unlimitedFindMany: 'unlimitedFindMany',
|
|
172
190
|
};
|