turbine-orm 0.71.0 → 0.72.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 +2 -2
- package/dist/cjs/client.d.ts +0 -18
- package/dist/cjs/client.js +2 -29
- package/dist/cjs/connection-url.d.ts +30 -0
- package/dist/cjs/connection-url.js +15 -17
- package/dist/cjs/powql.d.ts +38 -1
- package/dist/cjs/powql.js +106 -18
- package/dist/cjs/query/aggregates.d.ts +0 -13
- package/dist/cjs/query/aggregates.js +81 -33
- package/dist/cjs/query/batched-loader.d.ts +13 -1
- package/dist/cjs/query/batched-loader.js +46 -11
- package/dist/cjs/query/builder.d.ts +13 -0
- package/dist/cjs/query/builder.js +104 -14
- package/dist/cjs/query/compound-unique.js +29 -5
- package/dist/cjs/query/relation-names.d.ts +52 -0
- package/dist/cjs/query/relation-names.js +120 -0
- package/dist/cjs/query/relations.d.ts +11 -6
- package/dist/cjs/query/relations.js +45 -27
- package/dist/cjs/query/utils.d.ts +107 -3
- package/dist/cjs/query/utils.js +408 -7
- package/dist/cjs/query/where-compile.js +9 -4
- package/dist/cjs/query/where.js +9 -5
- package/dist/client.d.ts +0 -18
- package/dist/client.js +2 -29
- package/dist/connection-url.d.ts +30 -0
- package/dist/connection-url.js +15 -18
- package/dist/powql.d.ts +38 -1
- package/dist/powql.js +107 -19
- package/dist/query/aggregates.d.ts +0 -13
- package/dist/query/aggregates.js +82 -34
- package/dist/query/batched-loader.d.ts +13 -1
- package/dist/query/batched-loader.js +47 -12
- package/dist/query/builder.d.ts +13 -0
- package/dist/query/builder.js +105 -15
- package/dist/query/compound-unique.js +30 -6
- package/dist/query/relation-names.d.ts +52 -0
- package/dist/query/relation-names.js +117 -0
- package/dist/query/relations.d.ts +11 -6
- package/dist/query/relations.js +47 -29
- package/dist/query/utils.d.ts +107 -3
- package/dist/query/utils.js +404 -8
- package/dist/query/where-compile.js +10 -5
- package/dist/query/where.js +10 -6
- package/package.json +5 -3
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Caller-supplied RELATION names, normalized to their declared spelling once,
|
|
3
|
+
* before anything reads them.
|
|
4
|
+
*
|
|
5
|
+
* ## The rule
|
|
6
|
+
*
|
|
7
|
+
* A relation has one declared name (`ripeningChecks`), while the DDL anyone
|
|
8
|
+
* reads has only the table name (`ripening_checks`). Writing back what the
|
|
9
|
+
* schema shows therefore failed: E005 in `with`, E003 in a relation filter,
|
|
10
|
+
* E005 in `orderBy`, on names the error text was already computing correctly
|
|
11
|
+
* ("Did you mean ...?"). This accepts the snake_case spelling wherever the
|
|
12
|
+
* declared name is accepted, by the same resolve-then-validate rule
|
|
13
|
+
* {@link resolveRelation} states, which is the relation-level twin of
|
|
14
|
+
* `resolveColumnName`. It is not a guess: `snakeToCamel(key)` is accepted ONLY
|
|
15
|
+
* when it names a real declared relation, so a typo is still a typo, and an
|
|
16
|
+
* exact declared name always wins first.
|
|
17
|
+
*
|
|
18
|
+
* ## Why this is ONE pass up front and not a fix at each lookup
|
|
19
|
+
*
|
|
20
|
+
* The `with` tree is walked by SIX independent functions, each of which decides
|
|
21
|
+
* for itself which keys name relations: `withFingerprint`, `collectWithParams`,
|
|
22
|
+
* `buildRelationShapes`, `planFlattenWith`, `buildSelectWithRelations` and the
|
|
23
|
+
* batched loader, plus the positional row parser. Teaching each of them that a
|
|
24
|
+
* key has two spellings would make seven places that must agree about it, and
|
|
25
|
+
* disagreement is not a clean failure: the fingerprint is the SQL-cache key, so
|
|
26
|
+
* a walker that resolved differently from the builder would serve one query's
|
|
27
|
+
* template to another, silently. That is the drift class this repo has paid for
|
|
28
|
+
* repeatedly (the where-clause walkers, the two projection resolvers).
|
|
29
|
+
*
|
|
30
|
+
* Normalizing before any walker runs makes all seven correct with no knowledge
|
|
31
|
+
* of the second spelling, and keeps ONE authority for the rule.
|
|
32
|
+
*
|
|
33
|
+
* ## Shape
|
|
34
|
+
*
|
|
35
|
+
* Returns the SAME object when nothing needed rewriting, which is every query
|
|
36
|
+
* that already spells its relations the declared way, so the common path
|
|
37
|
+
* allocates nothing and is reference-identical to its input.
|
|
38
|
+
*
|
|
39
|
+
* An UNRESOLVABLE key is left exactly as written, deliberately. Reporting it is
|
|
40
|
+
* the builder's job, and it already names the offending key and lists the
|
|
41
|
+
* available relations; rejecting it here would move that error away from its
|
|
42
|
+
* context and change which error type callers see.
|
|
43
|
+
*/
|
|
44
|
+
import type { SchemaMetadata } from '../schema.js';
|
|
45
|
+
import type { WithClause } from './types.js';
|
|
46
|
+
/**
|
|
47
|
+
* `withClause` with every relation key replaced by its declared spelling,
|
|
48
|
+
* recursively, including the relation names inside a `_count`.
|
|
49
|
+
*
|
|
50
|
+
* Returns the input by reference when no key changed.
|
|
51
|
+
*/
|
|
52
|
+
export declare function normalizeWithClause(schema: SchemaMetadata, table: string, withClause: WithClause | undefined, depth?: number): WithClause | undefined;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Caller-supplied RELATION names, normalized to their declared spelling once,
|
|
4
|
+
* before anything reads them.
|
|
5
|
+
*
|
|
6
|
+
* ## The rule
|
|
7
|
+
*
|
|
8
|
+
* A relation has one declared name (`ripeningChecks`), while the DDL anyone
|
|
9
|
+
* reads has only the table name (`ripening_checks`). Writing back what the
|
|
10
|
+
* schema shows therefore failed: E005 in `with`, E003 in a relation filter,
|
|
11
|
+
* E005 in `orderBy`, on names the error text was already computing correctly
|
|
12
|
+
* ("Did you mean ...?"). This accepts the snake_case spelling wherever the
|
|
13
|
+
* declared name is accepted, by the same resolve-then-validate rule
|
|
14
|
+
* {@link resolveRelation} states, which is the relation-level twin of
|
|
15
|
+
* `resolveColumnName`. It is not a guess: `snakeToCamel(key)` is accepted ONLY
|
|
16
|
+
* when it names a real declared relation, so a typo is still a typo, and an
|
|
17
|
+
* exact declared name always wins first.
|
|
18
|
+
*
|
|
19
|
+
* ## Why this is ONE pass up front and not a fix at each lookup
|
|
20
|
+
*
|
|
21
|
+
* The `with` tree is walked by SIX independent functions, each of which decides
|
|
22
|
+
* for itself which keys name relations: `withFingerprint`, `collectWithParams`,
|
|
23
|
+
* `buildRelationShapes`, `planFlattenWith`, `buildSelectWithRelations` and the
|
|
24
|
+
* batched loader, plus the positional row parser. Teaching each of them that a
|
|
25
|
+
* key has two spellings would make seven places that must agree about it, and
|
|
26
|
+
* disagreement is not a clean failure: the fingerprint is the SQL-cache key, so
|
|
27
|
+
* a walker that resolved differently from the builder would serve one query's
|
|
28
|
+
* template to another, silently. That is the drift class this repo has paid for
|
|
29
|
+
* repeatedly (the where-clause walkers, the two projection resolvers).
|
|
30
|
+
*
|
|
31
|
+
* Normalizing before any walker runs makes all seven correct with no knowledge
|
|
32
|
+
* of the second spelling, and keeps ONE authority for the rule.
|
|
33
|
+
*
|
|
34
|
+
* ## Shape
|
|
35
|
+
*
|
|
36
|
+
* Returns the SAME object when nothing needed rewriting, which is every query
|
|
37
|
+
* that already spells its relations the declared way, so the common path
|
|
38
|
+
* allocates nothing and is reference-identical to its input.
|
|
39
|
+
*
|
|
40
|
+
* An UNRESOLVABLE key is left exactly as written, deliberately. Reporting it is
|
|
41
|
+
* the builder's job, and it already names the offending key and lists the
|
|
42
|
+
* available relations; rejecting it here would move that error away from its
|
|
43
|
+
* context and change which error type callers see.
|
|
44
|
+
*/
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.normalizeWithClause = normalizeWithClause;
|
|
47
|
+
const utils_js_1 = require("./utils.js");
|
|
48
|
+
/** Depth cap mirroring the builder's own, so a cyclic `with` cannot spin here. */
|
|
49
|
+
const MAX_DEPTH = 12;
|
|
50
|
+
/**
|
|
51
|
+
* `withClause` with every relation key replaced by its declared spelling,
|
|
52
|
+
* recursively, including the relation names inside a `_count`.
|
|
53
|
+
*
|
|
54
|
+
* Returns the input by reference when no key changed.
|
|
55
|
+
*/
|
|
56
|
+
function normalizeWithClause(schema, table, withClause, depth = 0) {
|
|
57
|
+
if (!withClause || typeof withClause !== 'object' || depth > MAX_DEPTH)
|
|
58
|
+
return withClause;
|
|
59
|
+
const meta = schema.tables[table];
|
|
60
|
+
if (!meta)
|
|
61
|
+
return withClause;
|
|
62
|
+
let changed = false;
|
|
63
|
+
const out = {};
|
|
64
|
+
for (const [key, spec] of Object.entries(withClause)) {
|
|
65
|
+
if (key === '_count') {
|
|
66
|
+
const nextCount = normalizeCount(meta, spec);
|
|
67
|
+
if (nextCount !== spec)
|
|
68
|
+
changed = true;
|
|
69
|
+
out[key] = nextCount;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const resolved = (0, utils_js_1.resolveRelation)(meta.relations, key);
|
|
73
|
+
// Unknown key: keep it verbatim and let the builder raise E005 by name.
|
|
74
|
+
const name = resolved?.name ?? key;
|
|
75
|
+
if (name !== key)
|
|
76
|
+
changed = true;
|
|
77
|
+
const nextSpec = resolved ? normalizeSpec(schema, resolved.def.to, spec, depth) : spec;
|
|
78
|
+
if (nextSpec !== spec)
|
|
79
|
+
changed = true;
|
|
80
|
+
out[name] = nextSpec;
|
|
81
|
+
}
|
|
82
|
+
return changed ? out : withClause;
|
|
83
|
+
}
|
|
84
|
+
/** A relation's `with` options, normalizing its nested `with` against the TARGET table. */
|
|
85
|
+
function normalizeSpec(schema, target, spec, depth) {
|
|
86
|
+
if (spec === true || spec === false || spec === null || typeof spec !== 'object')
|
|
87
|
+
return spec;
|
|
88
|
+
const opts = spec;
|
|
89
|
+
if (!opts.with)
|
|
90
|
+
return spec;
|
|
91
|
+
const nested = normalizeWithClause(schema, target, opts.with, depth + 1);
|
|
92
|
+
return nested === opts.with ? spec : { ...opts, with: nested };
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* `_count` names relations too. `true` means every relation and has nothing to
|
|
96
|
+
* rename; the record form has one key per counted relation.
|
|
97
|
+
*/
|
|
98
|
+
function normalizeCount(meta, count) {
|
|
99
|
+
if (!count || typeof count !== 'object')
|
|
100
|
+
return count;
|
|
101
|
+
let changed = false;
|
|
102
|
+
const out = {};
|
|
103
|
+
for (const [key, value] of Object.entries(count)) {
|
|
104
|
+
const name = (0, utils_js_1.resolveRelation)(meta.relations, key)?.name ?? key;
|
|
105
|
+
if (name !== key)
|
|
106
|
+
changed = true;
|
|
107
|
+
out[name] = value;
|
|
108
|
+
}
|
|
109
|
+
return changed ? out : count;
|
|
110
|
+
}
|
|
111
|
+
/*
|
|
112
|
+
* NOTE for the next person: there are deliberately no `declaredRelationName` /
|
|
113
|
+
* `namesRelation` wrappers here. The argument positions where a relation key
|
|
114
|
+
* sits INTERLEAVED with column keys (`where`'s some/every/none, `orderBy`'s
|
|
115
|
+
* relation targets, the simple-where fast path) cannot be normalized up front
|
|
116
|
+
* the way `with` can, so they call `resolveRelation` / `resolveRelationDef`
|
|
117
|
+
* from query/utils.ts directly at their branch point. Each of those branches is
|
|
118
|
+
* already a documented single authority; wrapping them here would add a second
|
|
119
|
+
* name for the same rule without removing a caller.
|
|
120
|
+
*/
|
|
@@ -117,12 +117,17 @@ export declare function isRelationOrderByValue(_qi: BuilderCtx, value: unknown):
|
|
|
117
117
|
*/
|
|
118
118
|
export declare function nullsSuffix(qi: BuilderCtx, nulls: 'first' | 'last' | undefined): string;
|
|
119
119
|
/**
|
|
120
|
-
* Resolve an orderBy key to its snake_case column via
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
120
|
+
* Resolve an orderBy key to its snake_case column via {@link resolveColumnName},
|
|
121
|
+
* throwing the SAME unknown-field E003 the top-level where path uses. Shared by
|
|
122
|
+
* top-level JSON-path ordering and every nested relation orderBy path so nested
|
|
123
|
+
* orderBy accepts exactly what top-level accepts (the 0.30.x bug: nested orderBy
|
|
124
|
+
* skipped the columnMap and rejected camelCase-named DB columns like
|
|
125
|
+
* "sortOrder").
|
|
126
|
+
*
|
|
127
|
+
* The rule is REACHED here, never restated: this used to inline
|
|
128
|
+
* `columnMap ?? camelToSnake` + an `allColumns` check, which agreed with
|
|
129
|
+
* `resolveColumnName` while the top-level scalar path a few lines up disagreed
|
|
130
|
+
* with both.
|
|
126
131
|
*/
|
|
127
132
|
export declare function resolveOrderByColumn(_qi: BuilderCtx, table: string, meta: TableMetadata, key: string): string;
|
|
128
133
|
/**
|
|
@@ -117,7 +117,10 @@ function projectionColumn(table, meta, field, clause) {
|
|
|
117
117
|
// A relation named in a projection is a habit, not a typo, so it gets its own
|
|
118
118
|
// message pointing at `with`. Checked BEFORE the generic throw because the
|
|
119
119
|
// generic one degrades into "Did you mean <exactly what you typed>?".
|
|
120
|
-
|
|
120
|
+
// Resolved, so `select: { ripening_checks: true }` gets the "that is a
|
|
121
|
+
// relation, use `with`" message rather than degrading to an unknown-field
|
|
122
|
+
// suggestion that names the relation back at the caller.
|
|
123
|
+
if ((0, utils_js_1.resolveRelationDef)(meta.relations, field))
|
|
121
124
|
throw new errors_js_1.ValidationError((0, utils_js_1.relationInProjectionMessage)(table, field, clause));
|
|
122
125
|
throw new errors_js_1.ValidationError((0, utils_js_1.unknownFieldMessage)(table, field, meta));
|
|
123
126
|
}
|
|
@@ -541,10 +544,8 @@ function buildOrderBy(qi, orderBy, params, lateralSink) {
|
|
|
541
544
|
.map(([key, value]) => {
|
|
542
545
|
// Vector KNN ordering: { distance: { to, metric, direction? } }
|
|
543
546
|
if ((0, filters_js_1.isVectorOrderBy)(value)) {
|
|
544
|
-
if (meta
|
|
545
|
-
|
|
546
|
-
`Known fields: ${Object.keys(meta.columnMap).join(', ') || '(none)'}.`);
|
|
547
|
-
}
|
|
547
|
+
if (meta)
|
|
548
|
+
resolveOrderByColumn(qi, qi.table, meta, key);
|
|
548
549
|
if (!params) {
|
|
549
550
|
throw new errors_js_1.ValidationError(`[turbine] Vector distance ordering on "${key}" is only supported in a top-level findMany orderBy.`);
|
|
550
551
|
}
|
|
@@ -567,10 +568,13 @@ function buildOrderBy(qi, orderBy, params, lateralSink) {
|
|
|
567
568
|
return buildRelationOrderBy(qi, key, value, `ord${relOrdCounter++}`, params, undefined, lateralSink);
|
|
568
569
|
}
|
|
569
570
|
// Scalar column ordering, a plain direction or an OrderBySpec (nulls).
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
571
|
+
// Through `resolveOrderByColumn`, which is the same resolve-or-E003 the
|
|
572
|
+
// nested orderBy paths below already used and emits the same message
|
|
573
|
+
// this used to inline. It used to test `key in meta.columnMap`, which
|
|
574
|
+
// knows only the FIELD spelling, and so rejected the snake_case COLUMN
|
|
575
|
+
// name that `where` / `select` / `distinct` accept on the same table.
|
|
576
|
+
if (meta)
|
|
577
|
+
resolveOrderByColumn(qi, qi.table, meta, key);
|
|
574
578
|
// Refuse a direction that is neither asc nor desc. normalizeOrderBy is
|
|
575
579
|
// `=== 'desc' ? DESC : ASC`, so without this every typo sorted ASCENDING
|
|
576
580
|
// and returned a correct-looking page in the reverse order.
|
|
@@ -608,16 +612,21 @@ function nullsSuffix(qi, nulls) {
|
|
|
608
612
|
return nulls === 'first' ? ' NULLS FIRST' : ' NULLS LAST';
|
|
609
613
|
}
|
|
610
614
|
/**
|
|
611
|
-
* Resolve an orderBy key to its snake_case column via
|
|
612
|
-
*
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
615
|
+
* Resolve an orderBy key to its snake_case column via {@link resolveColumnName},
|
|
616
|
+
* throwing the SAME unknown-field E003 the top-level where path uses. Shared by
|
|
617
|
+
* top-level JSON-path ordering and every nested relation orderBy path so nested
|
|
618
|
+
* orderBy accepts exactly what top-level accepts (the 0.30.x bug: nested orderBy
|
|
619
|
+
* skipped the columnMap and rejected camelCase-named DB columns like
|
|
620
|
+
* "sortOrder").
|
|
621
|
+
*
|
|
622
|
+
* The rule is REACHED here, never restated: this used to inline
|
|
623
|
+
* `columnMap ?? camelToSnake` + an `allColumns` check, which agreed with
|
|
624
|
+
* `resolveColumnName` while the top-level scalar path a few lines up disagreed
|
|
625
|
+
* with both.
|
|
617
626
|
*/
|
|
618
627
|
function resolveOrderByColumn(_qi, table, meta, key) {
|
|
619
|
-
const col = (0, utils_js_1.
|
|
620
|
-
if (
|
|
628
|
+
const col = (0, utils_js_1.resolveColumnName)(meta, key);
|
|
629
|
+
if (col === undefined) {
|
|
621
630
|
throw new errors_js_1.ValidationError(`[turbine] Unknown field "${key}" in orderBy on table "${table}". ` +
|
|
622
631
|
`Known fields: ${Object.keys(meta.columnMap).join(', ') || '(none)'}.`);
|
|
623
632
|
}
|
|
@@ -746,14 +755,18 @@ function buildChainedToOneOrderBy(qi, head, nextRelName, nextValue, params) {
|
|
|
746
755
|
`(got: ${entries.map(([k]) => k).join(', ') || '(empty)'}).`);
|
|
747
756
|
}
|
|
748
757
|
const [key, entryValue] = entries[0];
|
|
749
|
-
|
|
750
|
-
|
|
758
|
+
// Resolved, so a chained orderBy descends through the snake_case spelling
|
|
759
|
+
// of a relation exactly as `with` does; `relName` carries the DECLARED
|
|
760
|
+
// name onward so the path reported in errors is the canonical one.
|
|
761
|
+
const chained = (0, utils_js_1.resolveRelation)(currentMeta.relations, key);
|
|
762
|
+
if (chained && isRelationOrderByValue(qi, entryValue)) {
|
|
763
|
+
relName = chained.name;
|
|
751
764
|
value = entryValue;
|
|
752
765
|
continue;
|
|
753
766
|
}
|
|
754
767
|
// Terminal: a column on the last table in the chain.
|
|
755
|
-
const snakeCol = (0, utils_js_1.
|
|
756
|
-
if (
|
|
768
|
+
const snakeCol = (0, utils_js_1.resolveColumnName)(currentMeta, key);
|
|
769
|
+
if (snakeCol === undefined) {
|
|
757
770
|
throw new errors_js_1.ValidationError(`[turbine] Unknown column "${key}" in orderBy on relation "${path.join('.')}" (table "${currentMeta.name}").`);
|
|
758
771
|
}
|
|
759
772
|
(0, types_js_1.assertOrderDirection)(entryValue, `orderBy on relation path "${path.join('.')}"`);
|
|
@@ -788,7 +801,12 @@ function buildRelationOrderBy(qi, relName, value, alias, params, ctx, lateralSin
|
|
|
788
801
|
const ownerMeta = ctx?.meta ?? qi.tableMeta;
|
|
789
802
|
const ownerTable = ctx?.table ?? qi.table;
|
|
790
803
|
const parentRef = ctx?.parentRef ?? qi.table;
|
|
791
|
-
|
|
804
|
+
// Resolved, then `relName` is rebound to the declared spelling so the alias
|
|
805
|
+
// and every message below use one name.
|
|
806
|
+
const resolvedOwner = (0, utils_js_1.resolveRelation)(ownerMeta.relations, relName);
|
|
807
|
+
if (resolvedOwner)
|
|
808
|
+
relName = resolvedOwner.name;
|
|
809
|
+
const relDef = resolvedOwner?.def;
|
|
792
810
|
if (!relDef) {
|
|
793
811
|
// A table with no relations at all would otherwise render a dangling
|
|
794
812
|
// "Available: " and read as a broken message; and the most likely cause of
|
|
@@ -796,7 +814,7 @@ function buildRelationOrderBy(qi, relName, value, alias, params, ctx, lateralSin
|
|
|
796
814
|
// scalar column, which deserves to be named rather than reported as a
|
|
797
815
|
// missing relation.
|
|
798
816
|
const known = Object.keys(ownerMeta.relations);
|
|
799
|
-
const isColumn =
|
|
817
|
+
const isColumn = (0, utils_js_1.resolveColumnName)(ownerMeta, relName) !== undefined;
|
|
800
818
|
throw new errors_js_1.RelationError(isColumn
|
|
801
819
|
? `[turbine] orderBy on "${ownerTable}.${relName}" got a relation-shaped value, but "${relName}" is a ` +
|
|
802
820
|
`column. Order a column with 'asc' / 'desc' (or { sort, nulls }); the object form is for relations.`
|
|
@@ -847,10 +865,10 @@ function buildRelationOrderBy(qi, relName, value, alias, params, ctx, lateralSin
|
|
|
847
865
|
if (nestedRel && isRelationOrderByValue(qi, dirValue)) {
|
|
848
866
|
return buildChainedToOneOrderBy(qi, { relName, relDef, alias, correlation }, col, dirValue, params);
|
|
849
867
|
}
|
|
850
|
-
//
|
|
851
|
-
//
|
|
852
|
-
const snakeCol = (0, utils_js_1.
|
|
853
|
-
if (
|
|
868
|
+
// The ONE key-resolution rule ({@link resolveColumnName}), so a target
|
|
869
|
+
// column resolves here exactly as it does in the top-level orderBy.
|
|
870
|
+
const snakeCol = (0, utils_js_1.resolveColumnName)(targetMeta, col);
|
|
871
|
+
if (snakeCol === undefined) {
|
|
854
872
|
const relationHint = (0, utils_js_1.ownLookup)(targetMeta.relations, col)
|
|
855
873
|
? ` "${col}" is a relation on "${relDef.to}": order by one of ITS columns, e.g. ` +
|
|
856
874
|
`{ ${relName}: { ${col}: { <column>: 'asc' } } }.`
|
|
@@ -59,6 +59,47 @@ export interface ColumnNameSource {
|
|
|
59
59
|
* name (see {@link ownLookup}).
|
|
60
60
|
*/
|
|
61
61
|
export declare function resolveColumnName(meta: ColumnNameSource, key: string): string | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Resolve a user-supplied key to a relation's CANONICAL name and definition, or
|
|
64
|
+
* `undefined` when the key names no relation on the table.
|
|
65
|
+
*
|
|
66
|
+
* The relation-name half of the rule {@link resolveColumnName} states for
|
|
67
|
+
* columns, and deliberately the same shape: the declared name first, else
|
|
68
|
+
* `snakeToCamel(key)` accepted ONLY when that names a real relation.
|
|
69
|
+
* `snakeToCamel` is idempotent on an already-camel string, so a canonical key
|
|
70
|
+
* takes the first branch and this is a no-op for every existing caller.
|
|
71
|
+
*
|
|
72
|
+
* WHY IT EXISTS. A relation has one declared name, `ripeningChecks`, while the
|
|
73
|
+
* DDL anyone reads has only the TABLE name, `ripening_checks`. Writing back
|
|
74
|
+
* what the schema shows therefore failed with E005 in `with`, E003 in a
|
|
75
|
+
* relation filter, and E005 in `orderBy`, on names the error text was already
|
|
76
|
+
* computing correctly ("Did you mean ...?"). A system that can name the
|
|
77
|
+
* intended relation can accept it.
|
|
78
|
+
*
|
|
79
|
+
* NOT A GUESS, for the same reason the column rule is not: the transformed name
|
|
80
|
+
* is accepted only when it is a real declared relation, so an unknown key still
|
|
81
|
+
* fails and a typo is still a typo. Exact match wins first, so a schema that
|
|
82
|
+
* literally declares `ripening_checks` keeps it, even alongside a
|
|
83
|
+
* `ripeningChecks`.
|
|
84
|
+
*
|
|
85
|
+
* The RESULT KEY is the canonical name, not the caller's spelling, matching the
|
|
86
|
+
* column side (`select: { ledger_handle: true }` already returns
|
|
87
|
+
* `{ ledgerHandle }`). Resolving here rather than normalizing the args up front
|
|
88
|
+
* also means both spellings share one SQL-cache entry instead of minting two
|
|
89
|
+
* templates for one query.
|
|
90
|
+
*
|
|
91
|
+
* Prototype-safe via {@link ownLookup}, so `__proto__` cannot name a relation.
|
|
92
|
+
*/
|
|
93
|
+
export declare function resolveRelation<R>(relations: Record<string, R>, key: string): {
|
|
94
|
+
name: string;
|
|
95
|
+
def: R;
|
|
96
|
+
} | undefined;
|
|
97
|
+
/**
|
|
98
|
+
* {@link resolveRelation} when only the definition is wanted: a drop-in for the
|
|
99
|
+
* `ownLookup(meta.relations, key)` it replaces, with the same signature and the
|
|
100
|
+
* same `undefined` on a miss.
|
|
101
|
+
*/
|
|
102
|
+
export declare function resolveRelationDef<R>(relations: Record<string, R>, key: string): R | undefined;
|
|
62
103
|
/**
|
|
63
104
|
* THE canonical order for a caller-supplied set of columns: the table's own
|
|
64
105
|
* `allColumns` order, which is the order `omit` and the default projection
|
|
@@ -356,6 +397,19 @@ export declare function isTemporalInfinity(value: unknown): boolean;
|
|
|
356
397
|
* astronomical year (`0044 BC` → -43) the way the driver's own parser does.
|
|
357
398
|
*/
|
|
358
399
|
export declare function createUtcDateParser(fallback: (text: string) => unknown): (text: string) => unknown;
|
|
400
|
+
/**
|
|
401
|
+
* The `date` (OID 1082) parser WITHOUT the fast scan in front of it: the regex
|
|
402
|
+
* implementation described by {@link createUtcDateParser}, and the reference
|
|
403
|
+
* side of the differential test.
|
|
404
|
+
*
|
|
405
|
+
* Exported so "what the fast path must agree with" is the running code rather
|
|
406
|
+
* than a transcription of it in a test file. Two hand-synced copies of a
|
|
407
|
+
* parser is the drift class this repo has been bitten by before; there is one
|
|
408
|
+
* copy, and the fast path delegates to it.
|
|
409
|
+
*
|
|
410
|
+
* @internal
|
|
411
|
+
*/
|
|
412
|
+
export declare function createUtcDateParserGeneral(fallback: (text: string) => unknown): (text: string) => unknown;
|
|
359
413
|
/**
|
|
360
414
|
* Build the driver parser for Postgres `timestamp` (OID 1114) that reads an
|
|
361
415
|
* offset-less date-time as UTC. Also lifted to the `_timestamp` array OID
|
|
@@ -376,6 +430,34 @@ export declare function createUtcDateParser(fallback: (text: string) => unknown)
|
|
|
376
430
|
* `Date`-string parsing did too.
|
|
377
431
|
*/
|
|
378
432
|
export declare function createUtcTimestampParser(fallback: (text: string) => unknown): (text: string) => unknown;
|
|
433
|
+
/**
|
|
434
|
+
* The `timestamp` (OID 1114) parser WITHOUT the fast scan in front of it: the
|
|
435
|
+
* regex implementation described by {@link createUtcTimestampParser}, and the
|
|
436
|
+
* reference side of the differential test. Same reasoning as
|
|
437
|
+
* {@link createUtcDateParserGeneral}.
|
|
438
|
+
*
|
|
439
|
+
* @internal
|
|
440
|
+
*/
|
|
441
|
+
export declare function createUtcTimestampParserGeneral(fallback: (text: string) => unknown): (text: string) => unknown;
|
|
442
|
+
/**
|
|
443
|
+
* Build the driver parser for Postgres `timestamptz` (OID 1184): the ISO wire
|
|
444
|
+
* shape decoded by {@link scanIsoTemporal}, everything else handed straight to
|
|
445
|
+
* `fallback`.
|
|
446
|
+
*
|
|
447
|
+
* UNLIKE the `date` and `timestamp` parsers beside it, this one changes NO
|
|
448
|
+
* READING. A `timestamptz` arrives with an explicit offset, so its instant is
|
|
449
|
+
* unambiguous and both this and `postgres-date` produce the same `Date`; the
|
|
450
|
+
* only difference is how long it takes. That is also why it is not governed by
|
|
451
|
+
* a semantic decision the way `utcTimestamps` governs the zone-less types: there
|
|
452
|
+
* is no second interpretation to choose between.
|
|
453
|
+
*
|
|
454
|
+
* `fallback` must be captured with `pg.types.getTypeParser(1184, 'text')`
|
|
455
|
+
* BEFORE registration, for the same reason as the parsers above: reading it
|
|
456
|
+
* afterwards hands back this function and recurses forever. It is what keeps
|
|
457
|
+
* `infinity` / `-infinity`, ` BC`, wide and low years, and every non-ISO
|
|
458
|
+
* `DateStyle` on `postgres-date`, which already handles them.
|
|
459
|
+
*/
|
|
460
|
+
export declare function createFastTimestamptzParser(fallback: (text: string) => unknown): (text: string) => unknown;
|
|
379
461
|
/**
|
|
380
462
|
* The offset-less-timestamp-as-UTC reading, with no fallback: `text` must be a
|
|
381
463
|
* plain `YYYY-MM-DD HH:MM:SS[.ffffff]`. Used where the input shape is already
|
|
@@ -395,8 +477,17 @@ export declare function parseUtcTimestampText(text: string): Date;
|
|
|
395
477
|
* `pg.types.arrayParser` is a public member of the `pg` module (it is what the
|
|
396
478
|
* driver's own `_text` / `_date` parsers are built from), so this adds no
|
|
397
479
|
* dependency. NULL elements stay `null` and are never handed to `element`.
|
|
480
|
+
*
|
|
481
|
+
* The empty-string guard mirrors pg's own `parseDateArray`, which opens
|
|
482
|
+
* `if (!value) return null`. Turbine's copy did not, and answered `[]` where
|
|
483
|
+
* the driver answers `null` for the same input. No column produces it (a SQL
|
|
484
|
+
* NULL never reaches a parser, and an empty array is `{}`), so this is parity
|
|
485
|
+
* for its own sake rather than a bug report; it matters because these parsers
|
|
486
|
+
* are registered process-globally over pg's, and a shape where Turbine's
|
|
487
|
+
* answer differs from the driver's is a difference somebody eventually finds
|
|
488
|
+
* the hard way.
|
|
398
489
|
*/
|
|
399
|
-
export declare function createPgArrayParser(element: (text: string) => unknown): (text: string) => unknown[];
|
|
490
|
+
export declare function createPgArrayParser(element: (text: string) => unknown): (text: string) => unknown[] | null;
|
|
400
491
|
/** Tag `parser` as Turbine's own and return it (see {@link TURBINE_PARSER}). */
|
|
401
492
|
export declare function markTurbineParser<F extends (text: string) => unknown>(parser: F): F;
|
|
402
493
|
/**
|
|
@@ -458,8 +549,21 @@ export declare function isDefaultTextParser(oid: number, parser: (text: string)
|
|
|
458
549
|
*/
|
|
459
550
|
export declare function warnParserOverwrite(oid: number, typeName: string): void;
|
|
460
551
|
/**
|
|
461
|
-
* Register
|
|
462
|
-
*
|
|
552
|
+
* Register Turbine's temporal text parsers on the pg module. SIX OIDs, doing
|
|
553
|
+
* two different jobs:
|
|
554
|
+
*
|
|
555
|
+
* 1114 / 1082 / 1115 / 1182 the UTC READING of the zone-less types,
|
|
556
|
+
* `timestamp`, `date` and their array forms.
|
|
557
|
+
* This changes what a column means and is what
|
|
558
|
+
* `utcTimestamps` is named for.
|
|
559
|
+
* 1184 / 1185 the fast decode path for `timestamptz` and
|
|
560
|
+
* `timestamptz[]`. This changes NOTHING about
|
|
561
|
+
* what a column means: an offset-carrying value
|
|
562
|
+
* has one instant and this reads the same one.
|
|
563
|
+
* It is here for speed, `timestamptz` being ~88%
|
|
564
|
+
* of the client-side decode cost of a wide row
|
|
565
|
+
* drain, and it DECLINES rather than overwrites
|
|
566
|
+
* (see the comment at the call site).
|
|
463
567
|
*
|
|
464
568
|
* ONE place, because `pg.types.setTypeParser` is process-global and the pairing
|
|
465
569
|
* matters: registering a scalar without its array form, or a `date` without the
|