turbine-orm 0.35.0 → 0.36.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 +18 -16
- package/dist/cjs/cli/index.js +109 -16
- package/dist/cjs/cli/migrate.js +78 -3
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/cli/studio.js +333 -22
- package/dist/cjs/cli/ui.js +7 -1
- package/dist/cjs/dialect.js +1 -1
- package/dist/cjs/generate.js +23 -2
- package/dist/cjs/index.js +2 -1
- package/dist/cjs/mssql.js +22 -5
- package/dist/cjs/powdb.js +41 -1
- package/dist/cjs/powql.js +80 -25
- package/dist/cjs/query/aggregates.js +683 -0
- package/dist/cjs/query/batched-loader.js +2 -0
- package/dist/cjs/query/builder.js +297 -4504
- package/dist/cjs/query/filters.js +12 -0
- package/dist/cjs/query/relations.js +1698 -0
- package/dist/cjs/query/where-compile.js +180 -0
- package/dist/cjs/query/where.js +1491 -0
- package/dist/cjs/query/writes.js +680 -0
- package/dist/cjs/schema-builder.js +6 -0
- package/dist/cjs/schema-metadata.js +4 -0
- package/dist/cjs/schema-sql.js +265 -3
- package/dist/cjs/sqlite.js +1 -1
- package/dist/cli/index.d.ts +8 -2
- package/dist/cli/index.js +111 -18
- package/dist/cli/migrate.d.ts +24 -1
- package/dist/cli/migrate.js +77 -3
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/cli/studio.d.ts +46 -13
- package/dist/cli/studio.js +331 -23
- package/dist/cli/ui.js +7 -1
- package/dist/dialect.d.ts +15 -6
- package/dist/dialect.js +1 -1
- package/dist/generate.js +23 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mssql.js +22 -5
- package/dist/powdb.d.ts +20 -0
- package/dist/powdb.js +40 -0
- package/dist/powql.d.ts +33 -1
- package/dist/powql.js +80 -25
- package/dist/query/aggregates.d.ts +74 -0
- package/dist/query/aggregates.js +641 -0
- package/dist/query/batched-loader.d.ts +6 -0
- package/dist/query/batched-loader.js +2 -0
- package/dist/query/builder.d.ts +62 -829
- package/dist/query/builder.js +302 -4509
- package/dist/query/deferred.d.ts +7 -0
- package/dist/query/filters.d.ts +7 -0
- package/dist/query/filters.js +11 -0
- package/dist/query/relations.d.ts +441 -0
- package/dist/query/relations.js +1627 -0
- package/dist/query/types.d.ts +15 -0
- package/dist/query/where-compile.d.ts +139 -0
- package/dist/query/where-compile.js +175 -0
- package/dist/query/where.d.ts +494 -0
- package/dist/query/where.js +1431 -0
- package/dist/query/writes.d.ts +131 -0
- package/dist/query/writes.js +626 -0
- package/dist/schema-builder.d.ts +18 -3
- package/dist/schema-builder.js +6 -0
- package/dist/schema-metadata.js +4 -0
- package/dist/schema-sql.d.ts +60 -3
- package/dist/schema-sql.js +261 -4
- package/dist/schema.d.ts +10 -0
- package/dist/sqlite.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* turbine-orm: Shared WHERE-clause walk
|
|
4
|
+
*
|
|
5
|
+
* The SQL template cache requires three code paths over a table-scoped WHERE
|
|
6
|
+
* object to stay in perfect lockstep:
|
|
7
|
+
* - `fingerprintWhere` : the value-invariant cache KEY,
|
|
8
|
+
* - `buildWhereClause` : the SQL text + `$N` params on a cache MISS,
|
|
9
|
+
* - `collectWhereParams` : the params ONLY on a cache HIT (no SQL rebuild).
|
|
10
|
+
*
|
|
11
|
+
* If any two of them enumerate the WHERE keys in a different order, or classify
|
|
12
|
+
* a key's value into a different filter shape, the cached SQL's `$N`
|
|
13
|
+
* placeholders bind the wrong values: a silent cross-value (and, with tenant
|
|
14
|
+
* columns, cross-tenant) leak. That drift shipped twice historically (permuted
|
|
15
|
+
* where-key order; an orderBy fingerprint collision).
|
|
16
|
+
*
|
|
17
|
+
* This module removes the drift BY CONSTRUCTION:
|
|
18
|
+
* - {@link walkWhere} is the ONE enumeration. It sorts keys canonically,
|
|
19
|
+
* skips `undefined`, dispatches the `OR`/`AND`/`NOT` combinators and
|
|
20
|
+
* relation filters, and yields a flat, ordered {@link WhereEvent} stream.
|
|
21
|
+
* All three consumers iterate this same stream, so their key order and
|
|
22
|
+
* combinator structure can never diverge again.
|
|
23
|
+
* - {@link classifyScalarForSql} is the ONE scalar-shape decision the SQL
|
|
24
|
+
* paths use. `buildWhereClause` and `collectWhereParams` BOTH call it with
|
|
25
|
+
* the same `(rawColumn, value)`, so they always take the same branch and
|
|
26
|
+
* therefore push params in the same order.
|
|
27
|
+
* - {@link fingerprintScalarToken} is the fingerprint's own (deliberately
|
|
28
|
+
* column-blind) scalar token. It over-distinguishes relative to the SQL
|
|
29
|
+
* classifier (which is always safe), so a fingerprint match still implies
|
|
30
|
+
* an identical SQL shape.
|
|
31
|
+
*
|
|
32
|
+
* The dev-mode / sampled-production cross-check in `builder.ts` stays as the
|
|
33
|
+
* tripwire: with this shared walk it should never fire, but it remains the
|
|
34
|
+
* last-line guard against a future leaf builder / collect mirror falling out of
|
|
35
|
+
* step.
|
|
36
|
+
*/
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.walkWhere = walkWhere;
|
|
39
|
+
exports.classifyScalarForSql = classifyScalarForSql;
|
|
40
|
+
exports.fingerprintScalarToken = fingerprintScalarToken;
|
|
41
|
+
const filters_js_1 = require("./filters.js");
|
|
42
|
+
/** True when a normalized relation filter carries at least one cardinality key. */
|
|
43
|
+
function isRelationFilterObj(filterObj) {
|
|
44
|
+
return ('some' in filterObj || 'every' in filterObj || 'none' in filterObj || 'is' in filterObj || 'isNot' in filterObj);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* THE canonical WHERE enumeration. Yields events in sorted-key order (skipping
|
|
48
|
+
* `undefined`), dispatching combinators and relation filters, so every consumer
|
|
49
|
+
* (fingerprint, SQL build, param collect) walks identically.
|
|
50
|
+
*
|
|
51
|
+
* Combinator SKIP rules match the historical code exactly: an `OR`/`AND` whose
|
|
52
|
+
* value is a non-array or an empty array is skipped entirely (it contributes
|
|
53
|
+
* neither SQL, params, nor a fingerprint token); `NOT` is always emitted.
|
|
54
|
+
* A key that names a relation but whose value is not a `{ some/every/none/is/
|
|
55
|
+
* isNot }` filter falls through to the scalar path, exactly as before.
|
|
56
|
+
*/
|
|
57
|
+
function walkWhere(host, where) {
|
|
58
|
+
const events = [];
|
|
59
|
+
for (const key of (0, filters_js_1.sortedKeys)(where)) {
|
|
60
|
+
const value = where[key];
|
|
61
|
+
if (value === undefined)
|
|
62
|
+
continue;
|
|
63
|
+
if (key === 'OR') {
|
|
64
|
+
const arr = value;
|
|
65
|
+
if (!Array.isArray(arr) || arr.length === 0)
|
|
66
|
+
continue;
|
|
67
|
+
events.push({ kind: 'or', conditions: arr });
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (key === 'AND') {
|
|
71
|
+
const arr = value;
|
|
72
|
+
if (!Array.isArray(arr) || arr.length === 0)
|
|
73
|
+
continue;
|
|
74
|
+
events.push({ kind: 'and', conditions: arr });
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (key === 'NOT') {
|
|
78
|
+
events.push({ kind: 'not', condition: value });
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const relDef = host.tableMeta.relations[key];
|
|
82
|
+
if (relDef && typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
83
|
+
const filterObj = host.normalizeRelationFilter(relDef, value);
|
|
84
|
+
if (isRelationFilterObj(filterObj)) {
|
|
85
|
+
events.push({ kind: 'relation', key, relDef, filterObj });
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
events.push({ kind: 'scalar', key, value });
|
|
90
|
+
}
|
|
91
|
+
return events;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Column-aware SQL classification of a scalar WHERE value. Reproduces the SQL
|
|
95
|
+
* builder's linear fall-through: a JSON/array-shaped value on a non-JSON/array
|
|
96
|
+
* column falls THROUGH to the next shape (and ultimately equality) unless it
|
|
97
|
+
* carries a shape-unique key, in which case the build path reports a typed
|
|
98
|
+
* error (`*Throw`). Both the build and collect paths call this, so they can
|
|
99
|
+
* never classify the same value differently.
|
|
100
|
+
*/
|
|
101
|
+
function classifyScalarForSql(host, rawColumn, value) {
|
|
102
|
+
if (value === null)
|
|
103
|
+
return { kind: 'null' };
|
|
104
|
+
if (typeof value === 'object' && !Array.isArray(value) && (0, filters_js_1.isVectorFilter)(value)) {
|
|
105
|
+
return { kind: 'vector' };
|
|
106
|
+
}
|
|
107
|
+
if (typeof value === 'object' && !Array.isArray(value) && (0, filters_js_1.isJsonFilter)(value)) {
|
|
108
|
+
const colType = host.getColumnPgType(rawColumn);
|
|
109
|
+
if (host.isJsonColumnType(colType))
|
|
110
|
+
return { kind: 'json' };
|
|
111
|
+
const jsonKey = (0, filters_js_1.findJsonUniqueKey)(value);
|
|
112
|
+
if (jsonKey)
|
|
113
|
+
return { kind: 'jsonThrow', jsonKey };
|
|
114
|
+
// else: fall through, `equals`/`contains` on a non-JSON column keep their
|
|
115
|
+
// WhereOperator meaning (matches builder.ts: no `continue`).
|
|
116
|
+
}
|
|
117
|
+
if (typeof value === 'object' && !Array.isArray(value) && (0, filters_js_1.isArrayFilter)(value)) {
|
|
118
|
+
const colType = host.getColumnPgType(rawColumn);
|
|
119
|
+
if (colType.startsWith('_'))
|
|
120
|
+
return { kind: 'array', colType };
|
|
121
|
+
const arrayKey = (0, filters_js_1.findArrayUniqueKey)(value);
|
|
122
|
+
if (arrayKey)
|
|
123
|
+
return { kind: 'arrayThrow', arrayKey };
|
|
124
|
+
// else: fall through.
|
|
125
|
+
}
|
|
126
|
+
if (typeof value === 'object' && !Array.isArray(value) && (0, filters_js_1.isTextSearchFilter)(value)) {
|
|
127
|
+
return { kind: 'textsearch' };
|
|
128
|
+
}
|
|
129
|
+
if ((0, filters_js_1.isWhereOperator)(value))
|
|
130
|
+
return { kind: 'operator' };
|
|
131
|
+
return { kind: 'equality' };
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The fingerprint's scalar token: deliberately COLUMN-BLIND and in the
|
|
135
|
+
* historical fingerprint precedence (operator before vector/json/array), so a
|
|
136
|
+
* value that both looks like an operator and a JSON filter (`equals`/`contains`
|
|
137
|
+
* overlap) tokenizes as an operator exactly as it did before. Column-blindness
|
|
138
|
+
* only ever over-distinguishes versus {@link classifyScalarForSql}, which is
|
|
139
|
+
* safe: it can cause an extra cache MISS, never a wrong-value HIT.
|
|
140
|
+
*/
|
|
141
|
+
function fingerprintScalarToken(value) {
|
|
142
|
+
// null → distinct from any value token.
|
|
143
|
+
if (value === null)
|
|
144
|
+
return 'null';
|
|
145
|
+
// Operator objects, checked first (column-blind precedence).
|
|
146
|
+
if ((0, filters_js_1.isWhereOperator)(value))
|
|
147
|
+
return (0, filters_js_1.fingerprintOperatorShape)(value);
|
|
148
|
+
// Vector distance filter: metric (operator) and present comparators change
|
|
149
|
+
// the SQL shape, so both go in the token.
|
|
150
|
+
if (typeof value === 'object' && !Array.isArray(value) && (0, filters_js_1.isVectorFilter)(value)) {
|
|
151
|
+
const dist = value.distance;
|
|
152
|
+
const cmps = Object.keys(filters_js_1.VECTOR_DISTANCE_COMPARATORS)
|
|
153
|
+
.filter((c) => dist[c] !== undefined)
|
|
154
|
+
.sort()
|
|
155
|
+
.join('|');
|
|
156
|
+
return `vec(${dist.metric},${cmps})`;
|
|
157
|
+
}
|
|
158
|
+
// JSON filter: range ops carry a numeric/string annotation (different cast).
|
|
159
|
+
if (typeof value === 'object' && !Array.isArray(value) && (0, filters_js_1.isJsonFilter)(value)) {
|
|
160
|
+
return (0, filters_js_1.fingerprintJsonFilterShape)(value);
|
|
161
|
+
}
|
|
162
|
+
// Array filter.
|
|
163
|
+
if (typeof value === 'object' && !Array.isArray(value) && (0, filters_js_1.isArrayFilter)(value)) {
|
|
164
|
+
return `arr(${(0, filters_js_1.fingerprintArrayFilterShape)(value)})`;
|
|
165
|
+
}
|
|
166
|
+
// Text search filter.
|
|
167
|
+
if (typeof value === 'object' && !Array.isArray(value) && (0, filters_js_1.isTextSearchFilter)(value)) {
|
|
168
|
+
const cfg = value.config ?? 'english';
|
|
169
|
+
return `fts(${cfg})`;
|
|
170
|
+
}
|
|
171
|
+
// Plain object literal that matched no filter shape: a token distinct from
|
|
172
|
+
// real equality so a cache entry warmed by genuine equality can't serve it.
|
|
173
|
+
if ((0, filters_js_1.isUnmatchedPlainObject)(value)) {
|
|
174
|
+
return `obj(${Object.keys(value)
|
|
175
|
+
.sort()
|
|
176
|
+
.join(',')})`;
|
|
177
|
+
}
|
|
178
|
+
// Plain equality.
|
|
179
|
+
return 'eq';
|
|
180
|
+
}
|