@kozou/api 0.0.1 → 1.0.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/LICENSE +202 -0
- package/README.md +200 -1
- package/dist/auth.d.ts +75 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +128 -0
- package/dist/auth.js.map +1 -0
- package/dist/embed.d.ts +46 -0
- package/dist/embed.d.ts.map +1 -0
- package/dist/embed.js +181 -0
- package/dist/embed.js.map +1 -0
- package/dist/errors.d.ts +19 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +33 -0
- package/dist/errors.js.map +1 -0
- package/dist/handler.d.ts +39 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +267 -0
- package/dist/handler.js.map +1 -0
- package/dist/ident.d.ts +7 -0
- package/dist/ident.d.ts.map +1 -0
- package/dist/ident.js +12 -0
- package/dist/ident.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/openapi.d.ts +9 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +300 -0
- package/dist/openapi.js.map +1 -0
- package/dist/query-builder.d.ts +83 -0
- package/dist/query-builder.d.ts.map +1 -0
- package/dist/query-builder.js +592 -0
- package/dist/query-builder.js.map +1 -0
- package/dist/schema-lookup.d.ts +30 -0
- package/dist/schema-lookup.d.ts.map +1 -0
- package/dist/schema-lookup.js +61 -0
- package/dist/schema-lookup.js.map +1 -0
- package/dist/startApiServer.d.ts +53 -0
- package/dist/startApiServer.d.ts.map +1 -0
- package/dist/startApiServer.js +210 -0
- package/dist/startApiServer.js.map +1 -0
- package/package.json +44 -4
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
// Pure SQL builders for the read path. No I/O — given a resolved Resource
|
|
2
|
+
// and structured request params, produce parameterized SQL text + values.
|
|
3
|
+
//
|
|
4
|
+
// Safety contract:
|
|
5
|
+
// - Identifiers (table/column names) only ever come from the resolved
|
|
6
|
+
// Resource's own schema/name/columns, never raw request strings. Any
|
|
7
|
+
// filter/sort key that is not a declared column is rejected with a
|
|
8
|
+
// 400 before it reaches the SQL text.
|
|
9
|
+
// - Every user-supplied value is a bound parameter ($1, $2, ...). No
|
|
10
|
+
// value is interpolated into the SQL string.
|
|
11
|
+
import { badRequest } from './errors.js';
|
|
12
|
+
import { quoteIdent, qualified } from './ident.js';
|
|
13
|
+
import { buildEmbedSelectFragment } from './embed.js';
|
|
14
|
+
export { quoteIdent };
|
|
15
|
+
export const DEFAULT_PAGE_SIZE = 50;
|
|
16
|
+
export const MAX_PAGE_SIZE = 200;
|
|
17
|
+
const SCALAR_OP_SQL = {
|
|
18
|
+
eq: '=',
|
|
19
|
+
neq: '<>',
|
|
20
|
+
gt: '>',
|
|
21
|
+
gte: '>=',
|
|
22
|
+
lt: '<',
|
|
23
|
+
lte: '<=',
|
|
24
|
+
like: 'LIKE',
|
|
25
|
+
ilike: 'ILIKE',
|
|
26
|
+
};
|
|
27
|
+
const IS_KEYWORD_SQL = {
|
|
28
|
+
null: 'NULL',
|
|
29
|
+
notnull: 'NOT NULL',
|
|
30
|
+
true: 'TRUE',
|
|
31
|
+
false: 'FALSE',
|
|
32
|
+
};
|
|
33
|
+
/** Wildcard mapping for LIKE/ILIKE: a literal `*` in the pattern maps to SQL
|
|
34
|
+
* `%`. Plain linear string replacement — no regular expression (avoids ReDoS,
|
|
35
|
+
* per the CodeQL `js/polynomial-redos` precedent). */
|
|
36
|
+
function toLikePattern(value) {
|
|
37
|
+
return value.split('*').join('%');
|
|
38
|
+
}
|
|
39
|
+
/** Reduce a `format_type`-style `dataType` to its base scalar type name, or
|
|
40
|
+
* `null` when it is an array (`text[]`) — arrays are not scalar `LIKE` /
|
|
41
|
+
* boolean targets. Lower-cases, drops a trailing length/precision modifier
|
|
42
|
+
* (`character varying(255)` -> `character varying`), and trims. No regex
|
|
43
|
+
* (linear scan), per the CodeQL `js/polynomial-redos` precedent. */
|
|
44
|
+
function baseScalarType(dataType) {
|
|
45
|
+
const lower = dataType.trim().toLowerCase();
|
|
46
|
+
if (lower.includes('['))
|
|
47
|
+
return null; // any array spelling, e.g. text[]
|
|
48
|
+
const paren = lower.indexOf('(');
|
|
49
|
+
return (paren === -1 ? lower : lower.slice(0, paren)).trim();
|
|
50
|
+
}
|
|
51
|
+
/** Base scalar types that accept `LIKE` / `ILIKE`. Judged by the underlying
|
|
52
|
+
* PostgreSQL type, not the widget — a `varchar` surfaced as an `enum-select`
|
|
53
|
+
* still accepts `ilike` (Kozou v1.0 issue #76). Exact-match on the normalized
|
|
54
|
+
* base type so array spellings (`text[]`) are excluded. */
|
|
55
|
+
const TEXT_LIKE_BASE_TYPES = new Set([
|
|
56
|
+
'text',
|
|
57
|
+
'character varying',
|
|
58
|
+
'varchar',
|
|
59
|
+
'character',
|
|
60
|
+
'char',
|
|
61
|
+
'bpchar',
|
|
62
|
+
'citext',
|
|
63
|
+
'name',
|
|
64
|
+
]);
|
|
65
|
+
function isTextLikeType(dataType) {
|
|
66
|
+
const base = baseScalarType(dataType);
|
|
67
|
+
return base !== null && TEXT_LIKE_BASE_TYPES.has(base);
|
|
68
|
+
}
|
|
69
|
+
/** A boolean column — the only type for which `is.true` / `is.false` is valid
|
|
70
|
+
* (a `boolean[]` array is excluded). */
|
|
71
|
+
function isBooleanType(dataType) {
|
|
72
|
+
const base = baseScalarType(dataType);
|
|
73
|
+
return base === 'boolean' || base === 'bool';
|
|
74
|
+
}
|
|
75
|
+
/** Inclusive [min, max] range for each integer width, used to reject values
|
|
76
|
+
* that parse as integers but overflow the column type at execution. */
|
|
77
|
+
const INTEGER_BOUNDS = {
|
|
78
|
+
smallint: [-32768n, 32767n],
|
|
79
|
+
int2: [-32768n, 32767n],
|
|
80
|
+
integer: [-2147483648n, 2147483647n],
|
|
81
|
+
int: [-2147483648n, 2147483647n],
|
|
82
|
+
int4: [-2147483648n, 2147483647n],
|
|
83
|
+
bigint: [-9223372036854775808n, 9223372036854775807n],
|
|
84
|
+
int8: [-9223372036854775808n, 9223372036854775807n],
|
|
85
|
+
};
|
|
86
|
+
const DECIMAL_BASE_TYPES = new Set([
|
|
87
|
+
'numeric',
|
|
88
|
+
'decimal',
|
|
89
|
+
'real',
|
|
90
|
+
'double precision',
|
|
91
|
+
'float',
|
|
92
|
+
'float4',
|
|
93
|
+
'float8',
|
|
94
|
+
]);
|
|
95
|
+
const BOOLEAN_LITERALS = new Set([
|
|
96
|
+
'true',
|
|
97
|
+
'false',
|
|
98
|
+
't',
|
|
99
|
+
'f',
|
|
100
|
+
'yes',
|
|
101
|
+
'no',
|
|
102
|
+
'y',
|
|
103
|
+
'n',
|
|
104
|
+
'on',
|
|
105
|
+
'off',
|
|
106
|
+
'1',
|
|
107
|
+
'0',
|
|
108
|
+
]);
|
|
109
|
+
/** A plain base-10 integer literal (optional sign, then digits). A manual
|
|
110
|
+
* scan — no regex — so it rejects the `0x`/`0b`/`0o` and exponent forms that
|
|
111
|
+
* JavaScript's `Number` tolerates but a PostgreSQL integer column does not. */
|
|
112
|
+
function isPlainInteger(s) {
|
|
113
|
+
let i = 0;
|
|
114
|
+
if (s[0] === '+' || s[0] === '-')
|
|
115
|
+
i = 1;
|
|
116
|
+
if (i === s.length)
|
|
117
|
+
return false; // sign only / empty
|
|
118
|
+
for (; i < s.length; i++) {
|
|
119
|
+
if (s[i] < '0' || s[i] > '9')
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
/** A lexical decimal literal: optional sign, an integer and/or fractional
|
|
125
|
+
* part (at least one digit overall), and an optional exponent. A manual scan
|
|
126
|
+
* (no regex) that validates *syntax only* — it does not coerce through JS
|
|
127
|
+
* `Number`, so arbitrary-precision PostgreSQL `numeric` values (hundreds of
|
|
128
|
+
* digits, magnitudes beyond JS range) are accepted, while `abc`, `0x10`, and
|
|
129
|
+
* `NaN`/`Infinity` are rejected. Range / precision are checked separately
|
|
130
|
+
* (see `decimalFitsRange`). */
|
|
131
|
+
function isLexicalDecimal(s) {
|
|
132
|
+
let i = 0;
|
|
133
|
+
const n = s.length;
|
|
134
|
+
if (i < n && (s[i] === '+' || s[i] === '-'))
|
|
135
|
+
i++;
|
|
136
|
+
let digits = 0;
|
|
137
|
+
while (i < n && s[i] >= '0' && s[i] <= '9') {
|
|
138
|
+
i++;
|
|
139
|
+
digits++;
|
|
140
|
+
}
|
|
141
|
+
if (i < n && s[i] === '.') {
|
|
142
|
+
i++;
|
|
143
|
+
while (i < n && s[i] >= '0' && s[i] <= '9') {
|
|
144
|
+
i++;
|
|
145
|
+
digits++;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (digits === 0)
|
|
149
|
+
return false; // need at least one digit somewhere
|
|
150
|
+
if (i < n && (s[i] === 'e' || s[i] === 'E')) {
|
|
151
|
+
i++;
|
|
152
|
+
if (i < n && (s[i] === '+' || s[i] === '-'))
|
|
153
|
+
i++;
|
|
154
|
+
let expDigits = 0;
|
|
155
|
+
while (i < n && s[i] >= '0' && s[i] <= '9') {
|
|
156
|
+
i++;
|
|
157
|
+
expDigits++;
|
|
158
|
+
}
|
|
159
|
+
if (expDigits === 0)
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
return i === n; // the whole string was consumed
|
|
163
|
+
}
|
|
164
|
+
/** Parse a `numeric(p,s)` / `numeric(p)` type modifier out of a `format_type`
|
|
165
|
+
* string, or `null` when the type carries none (arbitrary-precision numeric).
|
|
166
|
+
* Linear scan / split — no regex (CodeQL `js/polynomial-redos` precedent). */
|
|
167
|
+
function numericTypmod(dataType) {
|
|
168
|
+
const open = dataType.indexOf('(');
|
|
169
|
+
if (open === -1)
|
|
170
|
+
return null;
|
|
171
|
+
const close = dataType.indexOf(')', open + 1);
|
|
172
|
+
if (close === -1)
|
|
173
|
+
return null;
|
|
174
|
+
const parts = dataType.slice(open + 1, close).split(',');
|
|
175
|
+
const precision = Number.parseInt(parts[0].trim(), 10);
|
|
176
|
+
if (!Number.isInteger(precision))
|
|
177
|
+
return null;
|
|
178
|
+
const scale = parts.length > 1 ? Number.parseInt(parts[1].trim(), 10) : 0;
|
|
179
|
+
if (!Number.isInteger(scale))
|
|
180
|
+
return null;
|
|
181
|
+
return { precision, scale };
|
|
182
|
+
}
|
|
183
|
+
/** Number of decimal digits of a non-negative BigInt (`0n` -> 1). */
|
|
184
|
+
function bigIntDigits(n) {
|
|
185
|
+
return n === 0n ? 1 : n.toString().length;
|
|
186
|
+
}
|
|
187
|
+
/** Whether a lexically-valid decimal fits `numeric(precision, scale)`.
|
|
188
|
+
* PostgreSQL rounds the value to `scale` fractional digits, then requires the
|
|
189
|
+
* result to fit in `precision` total significant digits — i.e. the value
|
|
190
|
+
* scaled to an integer in units of `10^-scale` must have at most `precision`
|
|
191
|
+
* digits. Done with BigInt so rounding carry (`9999999999.995` ->
|
|
192
|
+
* `10000000000.00` on `numeric(12,2)`) is exact and arbitrary-precision values
|
|
193
|
+
* are never lost. A huge exponent is handled without materialising `10^exp`. */
|
|
194
|
+
function numericFitsTypmod(value, precision, scale) {
|
|
195
|
+
let s = value.trim();
|
|
196
|
+
if (s[0] === '+' || s[0] === '-')
|
|
197
|
+
s = s.slice(1);
|
|
198
|
+
let eIdx = s.indexOf('e');
|
|
199
|
+
if (eIdx === -1)
|
|
200
|
+
eIdx = s.indexOf('E');
|
|
201
|
+
let exp = 0;
|
|
202
|
+
if (eIdx !== -1) {
|
|
203
|
+
exp = Number.parseInt(s.slice(eIdx + 1), 10);
|
|
204
|
+
s = s.slice(0, eIdx);
|
|
205
|
+
}
|
|
206
|
+
const dot = s.indexOf('.');
|
|
207
|
+
const intPart = dot === -1 ? s : s.slice(0, dot);
|
|
208
|
+
const fracPart = dot === -1 ? '' : s.slice(dot + 1);
|
|
209
|
+
const d = intPart + fracPart === '' ? 0n : BigInt(intPart + fracPart);
|
|
210
|
+
if (d === 0n)
|
|
211
|
+
return true; // zero fits any numeric(p, s)
|
|
212
|
+
// value = d * 10^(exp - fracPart.length); the rounded scaled integer is
|
|
213
|
+
// R = round(value * 10^scale) = round(d * 10^j), and we need it to have at
|
|
214
|
+
// most `precision` digits.
|
|
215
|
+
const j = exp - fracPart.length + scale;
|
|
216
|
+
if (j >= 0) {
|
|
217
|
+
// d * 10^j has exactly bigIntDigits(d) + j digits — no exponentiation.
|
|
218
|
+
return bigIntDigits(d) + j <= precision;
|
|
219
|
+
}
|
|
220
|
+
const drop = -j;
|
|
221
|
+
// Dropping more digits than d has rounds the magnitude to 0 or 1 — always fits.
|
|
222
|
+
if (drop > bigIntDigits(d))
|
|
223
|
+
return true;
|
|
224
|
+
const pow = 10n ** BigInt(drop); // drop <= digit count, so this is bounded
|
|
225
|
+
const q = d / pow;
|
|
226
|
+
const rem = d % pow;
|
|
227
|
+
const r = rem * 2n >= pow ? q + 1n : q; // round half away from zero (PostgreSQL)
|
|
228
|
+
return bigIntDigits(r) <= precision;
|
|
229
|
+
}
|
|
230
|
+
/** Whether a lexically-valid decimal is genuinely zero — all mantissa digits
|
|
231
|
+
* are zero (`0`, `0.0`, `0e5`) — rather than a nonzero value that merely
|
|
232
|
+
* rounds to zero. Scans the mantissa only, not the exponent. */
|
|
233
|
+
function isLexicalZero(value) {
|
|
234
|
+
let end = value.indexOf('e');
|
|
235
|
+
if (end === -1)
|
|
236
|
+
end = value.indexOf('E');
|
|
237
|
+
if (end === -1)
|
|
238
|
+
end = value.length;
|
|
239
|
+
for (let i = 0; i < end; i++) {
|
|
240
|
+
if (value[i] >= '1' && value[i] <= '9')
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
/** Whether a lexically-valid decimal is representable by a PostgreSQL float
|
|
246
|
+
* type. PostgreSQL rejects both overflow (magnitude above the type maximum)
|
|
247
|
+
* and underflow (a nonzero magnitude that rounds to zero in the type). `real`
|
|
248
|
+
* is IEEE single precision, so round through `Math.fround` — it maps an
|
|
249
|
+
* overflow to `Infinity` and an underflow to `0`; `double precision` is IEEE
|
|
250
|
+
* double, which JS `Number` models exactly. */
|
|
251
|
+
function floatFits(value, single) {
|
|
252
|
+
const n = single ? Math.fround(Number(value)) : Number(value);
|
|
253
|
+
if (!Number.isFinite(n))
|
|
254
|
+
return false; // overflow -> Infinity
|
|
255
|
+
return n !== 0 || isLexicalZero(value); // nonzero input rounding to 0 = underflow
|
|
256
|
+
}
|
|
257
|
+
/** Whether a lexically-valid decimal fits the decimal/float column's range.
|
|
258
|
+
* `numeric(p,s)`: see `numericFitsTypmod` (rounded to `scale`, must fit in `p`
|
|
259
|
+
* total digits); without a typmod, precision is unlimited. `real` /
|
|
260
|
+
* `double precision`: must be within the type's representable range. */
|
|
261
|
+
function decimalFitsRange(base, dataType, value) {
|
|
262
|
+
if (base === 'real' || base === 'float4')
|
|
263
|
+
return floatFits(value, true);
|
|
264
|
+
if (base === 'double precision' || base === 'float8' || base === 'float') {
|
|
265
|
+
return floatFits(value, false);
|
|
266
|
+
}
|
|
267
|
+
const typmod = numericTypmod(dataType);
|
|
268
|
+
if (typmod === null)
|
|
269
|
+
return true; // arbitrary precision — no precision bound
|
|
270
|
+
return numericFitsTypmod(value, typmod.precision, typmod.scale);
|
|
271
|
+
}
|
|
272
|
+
/** Whether a string filter value parses as the column's type, for the types
|
|
273
|
+
* where a bad value would otherwise surface only at execution as a 500:
|
|
274
|
+
* integer family (exact, with width range), decimal/float family (lexical
|
|
275
|
+
* syntax plus range/precision, Kozou v1.0 issue #81), and boolean. Other
|
|
276
|
+
* types (uuid / date / json / ...) are not pre-checked and fall through to
|
|
277
|
+
* PostgreSQL.
|
|
278
|
+
*
|
|
279
|
+
* Decimal syntax is validated without coercing through JS `Number` (so an
|
|
280
|
+
* arbitrary-precision `numeric` is not false-rejected); range / precision is
|
|
281
|
+
* then checked from the type modifier (`numeric(p,s)`) or the float range. */
|
|
282
|
+
function valueFitsType(base, dataType, value) {
|
|
283
|
+
const trimmed = value.trim();
|
|
284
|
+
if (trimmed === '')
|
|
285
|
+
return false;
|
|
286
|
+
const bounds = INTEGER_BOUNDS[base];
|
|
287
|
+
if (bounds !== undefined) {
|
|
288
|
+
if (!isPlainInteger(trimmed))
|
|
289
|
+
return false;
|
|
290
|
+
const n = BigInt(trimmed);
|
|
291
|
+
return n >= bounds[0] && n <= bounds[1];
|
|
292
|
+
}
|
|
293
|
+
if (DECIMAL_BASE_TYPES.has(base)) {
|
|
294
|
+
if (!isLexicalDecimal(trimmed))
|
|
295
|
+
return false;
|
|
296
|
+
return decimalFitsRange(base, dataType, trimmed);
|
|
297
|
+
}
|
|
298
|
+
if (base === 'boolean' || base === 'bool') {
|
|
299
|
+
return BOOLEAN_LITERALS.has(trimmed.toLowerCase());
|
|
300
|
+
}
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Reject a filter value that cannot parse as the column's type *before* the
|
|
305
|
+
* query runs (Kozou v1.0 issue #76). Limited to numeric-family and boolean
|
|
306
|
+
* columns — the common cases that otherwise raise a PostgreSQL data error
|
|
307
|
+
* (a 500) at execution. The check is tied to the bound filter value, so a
|
|
308
|
+
* 400 here is unambiguously client-caused (no server/view error is masked).
|
|
309
|
+
*/
|
|
310
|
+
function assertFilterValueParsable(filter, column, resource) {
|
|
311
|
+
if (filter.op === 'is')
|
|
312
|
+
return; // fixed keyword clause, no bound value
|
|
313
|
+
const base = baseScalarType(column.dataType);
|
|
314
|
+
if (base === null)
|
|
315
|
+
return; // array etc. — not value-checked here
|
|
316
|
+
const values = filter.op === 'in' ? filter.values : [filter.value];
|
|
317
|
+
for (const value of values) {
|
|
318
|
+
if (!valueFitsType(base, column.dataType, value)) {
|
|
319
|
+
throw badRequest(`Filter value "${value}" is not valid for column "${filter.column}" (${column.dataType}) ` +
|
|
320
|
+
`on resource "${resource.name}".`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Reject statically-knowable operator/column-type mismatches with a 400 before
|
|
326
|
+
* the query runs (Kozou v1.0 issue #76): `like`/`ilike` need a text-like
|
|
327
|
+
* column, and `is.true`/`is.false` need a boolean column. Value-format
|
|
328
|
+
* mismatches that only surface at execution (e.g. `eq.abc` on a numeric
|
|
329
|
+
* column) are mapped to 400 by the handler's error classifier instead.
|
|
330
|
+
*/
|
|
331
|
+
function assertFilterTypeCompatible(filter, column, resource) {
|
|
332
|
+
if ((filter.op === 'like' || filter.op === 'ilike') && !isTextLikeType(column.dataType)) {
|
|
333
|
+
throw badRequest(`Operator "${filter.op}" requires a text-like column; "${filter.column}" on resource ` +
|
|
334
|
+
`"${resource.name}" is ${column.dataType}.`);
|
|
335
|
+
}
|
|
336
|
+
if (filter.op === 'is' &&
|
|
337
|
+
(filter.keyword === 'true' || filter.keyword === 'false') &&
|
|
338
|
+
!isBooleanType(column.dataType)) {
|
|
339
|
+
throw badRequest(`Filter "is.${filter.keyword}" requires a boolean column; "${filter.column}" on resource ` +
|
|
340
|
+
`"${resource.name}" is ${column.dataType}.`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function selectColumns(resource) {
|
|
344
|
+
if (resource.columns.length === 0)
|
|
345
|
+
return '*';
|
|
346
|
+
return resource.columns.map((c) => quoteIdent(c.name)).join(', ');
|
|
347
|
+
}
|
|
348
|
+
/** Columns that free-text search targets: text-like widgets whose underlying
|
|
349
|
+
* type is also text-like. uuid / enum / numeric columns are excluded (an
|
|
350
|
+
* `ILIKE` against them either errors or is meaningless), as are text-widget
|
|
351
|
+
* columns whose real type is an array / domain / other non-text scalar — the
|
|
352
|
+
* base-type guard keeps `?search=` from emitting an ILIKE that PostgreSQL
|
|
353
|
+
* rejects (Kozou v1.0 issue #76). */
|
|
354
|
+
function searchableColumns(resource) {
|
|
355
|
+
return resource.columns
|
|
356
|
+
.filter((c) => (c.widget === 'text' || c.widget === 'textarea') && isTextLikeType(c.dataType))
|
|
357
|
+
.map((c) => c.name);
|
|
358
|
+
}
|
|
359
|
+
function clampPageSize(pageSize) {
|
|
360
|
+
if (pageSize === undefined || !Number.isFinite(pageSize) || pageSize < 1) {
|
|
361
|
+
return DEFAULT_PAGE_SIZE;
|
|
362
|
+
}
|
|
363
|
+
return Math.min(Math.floor(pageSize), MAX_PAGE_SIZE);
|
|
364
|
+
}
|
|
365
|
+
function clampPage(page) {
|
|
366
|
+
if (page === undefined || !Number.isFinite(page) || page < 1)
|
|
367
|
+
return 1;
|
|
368
|
+
return Math.floor(page);
|
|
369
|
+
}
|
|
370
|
+
export function buildListQuery(resource, params) {
|
|
371
|
+
const columnsByName = new Map(resource.columns.map((c) => [c.name, c]));
|
|
372
|
+
const whereParts = [];
|
|
373
|
+
const whereValues = [];
|
|
374
|
+
const nextParam = () => `$${whereValues.length + 1}`;
|
|
375
|
+
// Horizontal filters. The column allowlist is the resource's own columns;
|
|
376
|
+
// every supplied value is a bound parameter ($n). `is` emits a fixed
|
|
377
|
+
// keyword clause (no value bound).
|
|
378
|
+
for (const filter of params.filters ?? []) {
|
|
379
|
+
const columnDef = columnsByName.get(filter.column);
|
|
380
|
+
if (columnDef === undefined) {
|
|
381
|
+
throw badRequest(`Unknown filter column "${filter.column}" on resource "${resource.name}".`);
|
|
382
|
+
}
|
|
383
|
+
assertFilterTypeCompatible(filter, columnDef, resource);
|
|
384
|
+
assertFilterValueParsable(filter, columnDef, resource);
|
|
385
|
+
const column = quoteIdent(filter.column);
|
|
386
|
+
if (filter.op === 'in') {
|
|
387
|
+
if (filter.values.length === 0) {
|
|
388
|
+
throw badRequest(`Filter "${filter.column}=in.()" needs at least one value.`);
|
|
389
|
+
}
|
|
390
|
+
const placeholders = [];
|
|
391
|
+
for (const value of filter.values) {
|
|
392
|
+
placeholders.push(nextParam());
|
|
393
|
+
whereValues.push(value);
|
|
394
|
+
}
|
|
395
|
+
whereParts.push(`${column} IN (${placeholders.join(', ')})`);
|
|
396
|
+
}
|
|
397
|
+
else if (filter.op === 'is') {
|
|
398
|
+
whereParts.push(`${column} IS ${IS_KEYWORD_SQL[filter.keyword]}`);
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
whereParts.push(`${column} ${SCALAR_OP_SQL[filter.op]} ${nextParam()}`);
|
|
402
|
+
whereValues.push(filter.op === 'like' || filter.op === 'ilike'
|
|
403
|
+
? toLikePattern(filter.value)
|
|
404
|
+
: filter.value);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
// Free-text search across text-like columns.
|
|
408
|
+
const search = params.search?.trim();
|
|
409
|
+
if (search !== undefined && search.length > 0) {
|
|
410
|
+
const cols = searchableColumns(resource);
|
|
411
|
+
if (cols.length > 0) {
|
|
412
|
+
const placeholder = nextParam();
|
|
413
|
+
whereValues.push(`%${search}%`);
|
|
414
|
+
const ors = cols.map((c) => `${quoteIdent(c)} ILIKE ${placeholder}`).join(' OR ');
|
|
415
|
+
whereParts.push(`(${ors})`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
const whereClause = whereParts.length > 0 ? ` WHERE ${whereParts.join(' AND ')}` : '';
|
|
419
|
+
// ORDER BY: explicit sort, else default to the primary key for stable
|
|
420
|
+
// pagination. Views (no PK) fall back to no ordering.
|
|
421
|
+
const orderParts = [];
|
|
422
|
+
if (params.sort && params.sort.length > 0) {
|
|
423
|
+
for (const s of params.sort) {
|
|
424
|
+
if (!columnsByName.has(s.field)) {
|
|
425
|
+
throw badRequest(`Unknown sort column "${s.field}" on resource "${resource.name}".`);
|
|
426
|
+
}
|
|
427
|
+
orderParts.push(`${quoteIdent(s.field)} ${s.order === 'desc' ? 'DESC' : 'ASC'}`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
for (const pk of resource.primaryKey) {
|
|
432
|
+
orderParts.push(`${quoteIdent(pk)} ASC`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
const orderClause = orderParts.length > 0 ? ` ORDER BY ${orderParts.join(', ')}` : '';
|
|
436
|
+
const page = clampPage(params.page);
|
|
437
|
+
const pageSize = clampPageSize(params.pageSize);
|
|
438
|
+
const offset = (page - 1) * pageSize;
|
|
439
|
+
const cols = selectColumns(resource);
|
|
440
|
+
const table = qualified(resource);
|
|
441
|
+
// Embeds append correlated subqueries to the SELECT list only — they add no
|
|
442
|
+
// bound parameters, so the $n numbering above is unaffected.
|
|
443
|
+
const embedSql = params.embed && params.embed.length > 0
|
|
444
|
+
? buildEmbedSelectFragment(params.embed, table, { n: 0 })
|
|
445
|
+
: '';
|
|
446
|
+
const dataValues = [...whereValues, pageSize, offset];
|
|
447
|
+
const limitParam = `$${whereValues.length + 1}`;
|
|
448
|
+
const offsetParam = `$${whereValues.length + 2}`;
|
|
449
|
+
const dataText = `SELECT ${cols}${embedSql} FROM ${table}${whereClause}${orderClause} LIMIT ${limitParam} OFFSET ${offsetParam}`;
|
|
450
|
+
const countText = `SELECT count(*) AS total FROM ${table}${whereClause}`;
|
|
451
|
+
return {
|
|
452
|
+
dataText,
|
|
453
|
+
dataValues,
|
|
454
|
+
countText,
|
|
455
|
+
countValues: [...whereValues],
|
|
456
|
+
page,
|
|
457
|
+
pageSize,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
/** Primary key columns for an item-by-id operation. A PK-less resource (a
|
|
461
|
+
* view, or a table without a primary key) cannot be addressed by id. */
|
|
462
|
+
function primaryKey(resource) {
|
|
463
|
+
if (resource.primaryKey.length === 0) {
|
|
464
|
+
throw badRequest(`Resource "${resource.name}" has no primary key; fetch/update/delete by id is unavailable.`);
|
|
465
|
+
}
|
|
466
|
+
return resource.primaryKey;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Resolve the bound key values from an item id segment.
|
|
470
|
+
*
|
|
471
|
+
* A single-column PK takes the id verbatim, so the value may itself contain a
|
|
472
|
+
* comma. A composite PK splits the segment on commas into one component per
|
|
473
|
+
* key column, in `primaryKey` declaration order; the component count must
|
|
474
|
+
* match the key arity (else 400).
|
|
475
|
+
*
|
|
476
|
+
* Limitation: with a composite key, a key *value* cannot contain a comma (the
|
|
477
|
+
* segment is split after URL-decoding). Single-column keys are unaffected.
|
|
478
|
+
*/
|
|
479
|
+
function resolveKeyValues(resource, id, keyColumns) {
|
|
480
|
+
if (keyColumns.length === 1)
|
|
481
|
+
return [id];
|
|
482
|
+
const parts = id.split(',');
|
|
483
|
+
if (parts.length !== keyColumns.length) {
|
|
484
|
+
throw badRequest(`Resource "${resource.name}" has a composite primary key (${keyColumns.join(', ')}); ` +
|
|
485
|
+
`expected ${keyColumns.length} comma-separated key components, got ${parts.length}.`);
|
|
486
|
+
}
|
|
487
|
+
return parts;
|
|
488
|
+
}
|
|
489
|
+
/** `pk0 = $start AND pk1 = $start+1 AND ...` for the given key columns. */
|
|
490
|
+
function keyWhereClause(keyColumns, startParam) {
|
|
491
|
+
return keyColumns.map((c, i) => `${quoteIdent(c)} = $${startParam + i}`).join(' AND ');
|
|
492
|
+
}
|
|
493
|
+
export function buildGetQuery(resource, id, embed) {
|
|
494
|
+
const keyColumns = primaryKey(resource);
|
|
495
|
+
const keyValues = resolveKeyValues(resource, id, keyColumns);
|
|
496
|
+
const table = qualified(resource);
|
|
497
|
+
const embedSql = embed && embed.length > 0 ? buildEmbedSelectFragment(embed, table, { n: 0 }) : '';
|
|
498
|
+
const where = keyWhereClause(keyColumns, 1);
|
|
499
|
+
const text = `SELECT ${selectColumns(resource)}${embedSql} FROM ${table} WHERE ${where} LIMIT 1`;
|
|
500
|
+
return { text, values: keyValues };
|
|
501
|
+
}
|
|
502
|
+
export const DEFAULT_RELATION_LIMIT = 20;
|
|
503
|
+
export const MAX_RELATION_LIMIT = 100;
|
|
504
|
+
function singlePrimaryKey(resource) {
|
|
505
|
+
if (resource.primaryKey.length !== 1) {
|
|
506
|
+
throw badRequest(`Resource "${resource.name}" does not have a single-column primary key.`);
|
|
507
|
+
}
|
|
508
|
+
return resource.primaryKey[0];
|
|
509
|
+
}
|
|
510
|
+
function assertKnownColumns(resource, keys) {
|
|
511
|
+
const columnNames = new Set(resource.columns.map((c) => c.name));
|
|
512
|
+
for (const key of keys) {
|
|
513
|
+
if (!columnNames.has(key)) {
|
|
514
|
+
throw badRequest(`Unknown column "${key}" on resource "${resource.name}".`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
export function buildInsertQuery(resource, data) {
|
|
519
|
+
const keys = Object.keys(data);
|
|
520
|
+
assertKnownColumns(resource, keys);
|
|
521
|
+
const returning = selectColumns(resource);
|
|
522
|
+
const table = qualified(resource);
|
|
523
|
+
// No columns supplied: insert a row of all column defaults.
|
|
524
|
+
if (keys.length === 0) {
|
|
525
|
+
return { text: `INSERT INTO ${table} DEFAULT VALUES RETURNING ${returning}`, values: [] };
|
|
526
|
+
}
|
|
527
|
+
const cols = keys.map(quoteIdent).join(', ');
|
|
528
|
+
const placeholders = keys.map((_, i) => `$${i + 1}`).join(', ');
|
|
529
|
+
const values = keys.map((k) => data[k]);
|
|
530
|
+
return {
|
|
531
|
+
text: `INSERT INTO ${table} (${cols}) VALUES (${placeholders}) RETURNING ${returning}`,
|
|
532
|
+
values,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
export function buildUpdateQuery(resource, id, data) {
|
|
536
|
+
const keyColumns = primaryKey(resource);
|
|
537
|
+
const keys = Object.keys(data);
|
|
538
|
+
if (keys.length === 0) {
|
|
539
|
+
throw badRequest(`No fields to update on resource "${resource.name}".`);
|
|
540
|
+
}
|
|
541
|
+
assertKnownColumns(resource, keys);
|
|
542
|
+
const keyValues = resolveKeyValues(resource, id, keyColumns);
|
|
543
|
+
const sets = keys.map((k, i) => `${quoteIdent(k)} = $${i + 1}`).join(', ');
|
|
544
|
+
const values = [...keys.map((k) => data[k]), ...keyValues];
|
|
545
|
+
const where = keyWhereClause(keyColumns, keys.length + 1);
|
|
546
|
+
const text = `UPDATE ${qualified(resource)} SET ${sets} WHERE ${where} RETURNING ${selectColumns(resource)}`;
|
|
547
|
+
return { text, values };
|
|
548
|
+
}
|
|
549
|
+
export function buildDeleteQuery(resource, id) {
|
|
550
|
+
const keyColumns = primaryKey(resource);
|
|
551
|
+
const keyValues = resolveKeyValues(resource, id, keyColumns);
|
|
552
|
+
const where = keyWhereClause(keyColumns, 1);
|
|
553
|
+
const text = `DELETE FROM ${qualified(resource)} WHERE ${where} RETURNING ${selectColumns(resource)}`;
|
|
554
|
+
return { text, values: keyValues };
|
|
555
|
+
}
|
|
556
|
+
function clampRelationLimit(limit) {
|
|
557
|
+
if (limit === undefined || !Number.isFinite(limit) || limit < 1)
|
|
558
|
+
return DEFAULT_RELATION_LIMIT;
|
|
559
|
+
return Math.min(Math.floor(limit), MAX_RELATION_LIMIT);
|
|
560
|
+
}
|
|
561
|
+
/** Lightweight `{ id, label }` lookup used by relation-select widgets. */
|
|
562
|
+
export function buildRelationOptionsQuery(resource, params) {
|
|
563
|
+
const pk = singlePrimaryKey(resource);
|
|
564
|
+
assertKnownColumns(resource, [params.labelField, ...params.searchFields]);
|
|
565
|
+
// `searchFields` is request-controlled (`?as=options&fields=`); each is
|
|
566
|
+
// ILIKE'd below, so reject a non-text-like field with a 400 rather than
|
|
567
|
+
// letting PostgreSQL raise an operator error (a 500) at execution
|
|
568
|
+
// (Kozou v1.0 issue #76).
|
|
569
|
+
const columnsByName = new Map(resource.columns.map((c) => [c.name, c]));
|
|
570
|
+
for (const field of params.searchFields) {
|
|
571
|
+
const column = columnsByName.get(field);
|
|
572
|
+
if (column !== undefined && !isTextLikeType(column.dataType)) {
|
|
573
|
+
throw badRequest(`Relation search field "${field}" must be a text-like column; it is ${column.dataType}.`);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
const values = [];
|
|
577
|
+
let where = '';
|
|
578
|
+
const q = params.query?.trim();
|
|
579
|
+
if (q !== undefined && q.length > 0 && params.searchFields.length > 0) {
|
|
580
|
+
values.push(`%${q}%`);
|
|
581
|
+
const ors = params.searchFields.map((f) => `${quoteIdent(f)} ILIKE $1`).join(' OR ');
|
|
582
|
+
where = ` WHERE (${ors})`;
|
|
583
|
+
}
|
|
584
|
+
values.push(clampRelationLimit(params.limit));
|
|
585
|
+
const limitParam = `$${values.length}`;
|
|
586
|
+
const cols = params.labelField === pk
|
|
587
|
+
? quoteIdent(pk)
|
|
588
|
+
: `${quoteIdent(pk)}, ${quoteIdent(params.labelField)}`;
|
|
589
|
+
const text = `SELECT ${cols} FROM ${qualified(resource)}${where} LIMIT ${limitParam}`;
|
|
590
|
+
return { text, values, primaryKey: pk, labelField: params.labelField };
|
|
591
|
+
}
|
|
592
|
+
//# sourceMappingURL=query-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-builder.js","sourceRoot":"","sources":["../src/query-builder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { SchemaContext, ColumnContext, RelationContext } from '@kozou/core';
|
|
2
|
+
export type ResourceKind = 'table' | 'view';
|
|
3
|
+
/** A normalized, query-ready view of a table or a view. */
|
|
4
|
+
export type Resource = {
|
|
5
|
+
kind: ResourceKind;
|
|
6
|
+
schema: string;
|
|
7
|
+
name: string;
|
|
8
|
+
qualifiedName: string;
|
|
9
|
+
columns: ColumnContext[];
|
|
10
|
+
/** Primary-key columns. Empty for views and PK-less tables. */
|
|
11
|
+
primaryKey: string[];
|
|
12
|
+
/** Outgoing forward (to-one / one-to-one) relations. Empty for views. */
|
|
13
|
+
relations: RelationContext[];
|
|
14
|
+
};
|
|
15
|
+
/** A child resource holding a foreign key that points back at some parent —
|
|
16
|
+
* the basis for reverse (one-to-many) embedding. */
|
|
17
|
+
export type ReverseRelation = {
|
|
18
|
+
child: Resource;
|
|
19
|
+
relation: RelationContext;
|
|
20
|
+
};
|
|
21
|
+
export type ResourceLookup = {
|
|
22
|
+
/** Resolve by bare name (when unambiguous) or by `schema.name`. */
|
|
23
|
+
resolve(name: string): Resource | undefined;
|
|
24
|
+
/** Qualified names of every addressable resource, sorted. */
|
|
25
|
+
list(): string[];
|
|
26
|
+
/** Children whose foreign key references the given qualified resource. */
|
|
27
|
+
reverse(qualifiedName: string): ReverseRelation[];
|
|
28
|
+
};
|
|
29
|
+
export declare function buildResourceLookup(schema: SchemaContext): ResourceLookup;
|
|
30
|
+
//# sourceMappingURL=schema-lookup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-lookup.d.ts","sourceRoot":"","sources":["../src/schema-lookup.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEjF,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,CAAC;AAE5C,2DAA2D;AAC3D,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,+DAA+D;IAC/D,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,yEAAyE;IACzE,SAAS,EAAE,eAAe,EAAE,CAAC;CAC9B,CAAC;AAEF;qDACqD;AACrD,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,QAAQ,CAAC;IAChB,QAAQ,EAAE,eAAe,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,mEAAmE;IACnE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC5C,6DAA6D;IAC7D,IAAI,IAAI,MAAM,EAAE,CAAC;IACjB,0EAA0E;IAC1E,OAAO,CAAC,aAAa,EAAE,MAAM,GAAG,eAAe,EAAE,CAAC;CACnD,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,cAAc,CA0DzE"}
|