@querykitjs/core 1.2.0 → 2.1.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.
@@ -0,0 +1,507 @@
1
+ 'use strict';
2
+
3
+ var chunk72KNINHB_cjs = require('./chunk-72KNINHB.cjs');
4
+
5
+ // src/internal/cursor.ts
6
+ function encodeCursor(value) {
7
+ let normalized = value instanceof Date ? value.toISOString() : value, json = JSON.stringify({ v: normalized });
8
+ return Buffer.from(json, "utf8").toString("base64url");
9
+ }
10
+ function decodeCursor(cursor) {
11
+ if (cursor)
12
+ try {
13
+ let json = Buffer.from(cursor, "base64url").toString("utf8");
14
+ return JSON.parse(json).v;
15
+ } catch {
16
+ return;
17
+ }
18
+ }
19
+ var isScalar = (value) => typeof value == "string" || typeof value == "boolean" || typeof value == "number" && Number.isFinite(value), scalarOf = (value) => {
20
+ let normalized = value instanceof Date ? value.toISOString() : typeof value == "object" && value !== null && typeof value.toJSON == "function" ? value.toJSON() : value;
21
+ return isScalar(normalized) ? normalized : void 0;
22
+ };
23
+ async function sign(body, secret) {
24
+ let encoder = new TextEncoder(), key = await crypto.subtle.importKey("raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]), signature = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
25
+ return Buffer.from(signature).toString("base64url");
26
+ }
27
+ function sameSignature(a, b) {
28
+ if (a.length !== b.length) return false;
29
+ let diff = 0;
30
+ for (let index = 0; index < a.length; index++) diff |= a.charCodeAt(index) ^ b.charCodeAt(index);
31
+ return diff === 0;
32
+ }
33
+ async function encodeCursorToken(payload, secret) {
34
+ let body = JSON.stringify({ v: 2, k: scalarOf(payload.k) ?? null, id: scalarOf(payload.id) ?? null }), signed = secret ? JSON.stringify({ ...JSON.parse(body), s: await sign(body, secret) }) : body;
35
+ return Buffer.from(signed, "utf8").toString("base64url");
36
+ }
37
+ async function decodeCursorToken(token, options) {
38
+ if (!token) return;
39
+ let refuse = () => {
40
+ throw new chunk72KNINHB_cjs.QueryKitError({ source: options.source, site: "cursorKey", key: options.key ?? "cursor", reason: "invalid-value" });
41
+ }, payload;
42
+ try {
43
+ payload = JSON.parse(Buffer.from(token, "base64url").toString("utf8"));
44
+ } catch {
45
+ return refuse();
46
+ }
47
+ if (typeof payload != "object" || payload === null || payload.v !== 2 || !isScalar(payload.k) || !isScalar(payload.id)) return refuse();
48
+ if (options.secret) {
49
+ let body = JSON.stringify({ v: 2, k: payload.k, id: payload.id });
50
+ if (typeof payload.s != "string" || !sameSignature(payload.s, await sign(body, options.secret))) return refuse();
51
+ }
52
+ return { k: payload.k, id: payload.id };
53
+ }
54
+
55
+ // src/internal/diagnostics.ts
56
+ function reportSkip(diag, info) {
57
+ if (!diag) return;
58
+ let full = { ...info, source: diag.source };
59
+ if (diag.strict) throw new chunk72KNINHB_cjs.QueryKitError(full);
60
+ diag.onSkipped?.(full);
61
+ }
62
+
63
+ // src/internal/context.ts
64
+ function loadAsyncLocalStorage() {
65
+ let asyncHooks = typeof process < "u" && typeof process.getBuiltinModule == "function" ? process.getBuiltinModule("node:async_hooks") : void 0;
66
+ if (!asyncHooks?.AsyncLocalStorage)
67
+ throw new Error("querykit: the repository context needs node:async_hooks (Node >= 22.12 or Bun) \u2014 a querykit adapter cannot run in this runtime");
68
+ return asyncHooks.AsyncLocalStorage;
69
+ }
70
+ function createContextStore() {
71
+ let AsyncLocalStorage = loadAsyncLocalStorage(), als = new AsyncLocalStorage();
72
+ return {
73
+ get: () => als.getStore(),
74
+ run: (patch, fn) => als.run({ ...als.getStore(), ...patch }, fn)
75
+ };
76
+ }
77
+
78
+ // src/internal/sort.ts
79
+ function normalizeSort(sort, diag) {
80
+ if (!sort) return [];
81
+ let items = [];
82
+ for (let item of sort)
83
+ item && typeof item.key == "string" && item.key ? items.push({ key: item.key, direction: item.direction ?? "asc" }) : reportSkip(diag, { site: "sort", key: "", reason: "invalid-value" });
84
+ return items;
85
+ }
86
+
87
+ // src/internal/pagination.ts
88
+ function clampPageSize(requested, fallback, max) {
89
+ return Math.min(max, Math.max(1, Math.trunc(requested ?? fallback)));
90
+ }
91
+ function clampPage(requested) {
92
+ return Math.max(1, Math.trunc(requested ?? 1));
93
+ }
94
+ function clampOffset(requested) {
95
+ return Math.max(0, Math.trunc(requested ?? 0));
96
+ }
97
+ function offsetMeta(page, perPage, totalItems) {
98
+ let totalPages = Math.ceil(totalItems / perPage);
99
+ return {
100
+ total_items: totalItems,
101
+ total_pages: totalPages,
102
+ current_page: page,
103
+ per_page: perPage,
104
+ has_next: page < totalPages,
105
+ has_prev: page > 1
106
+ };
107
+ }
108
+ function offsetPageWithoutTotal(rows, page, perPage) {
109
+ let hasNext = rows.length > perPage;
110
+ return {
111
+ data: hasNext ? rows.slice(0, perPage) : [...rows],
112
+ meta: { total_items: null, total_pages: null, current_page: page, per_page: perPage, has_next: hasNext, has_prev: page > 1 }
113
+ };
114
+ }
115
+ function infinitePage(rows, limit, offset) {
116
+ let hasMore = rows.length > limit, data = hasMore ? rows.slice(0, limit) : [...rows];
117
+ return {
118
+ data,
119
+ meta: { limit, offset, count: data.length, has_more: hasMore, next_offset: hasMore ? offset + limit : null }
120
+ };
121
+ }
122
+ async function cursorPage(rows, options) {
123
+ let { limit, direction, hasCursor, cursorField, idField, secret } = options, hasExtra = rows.length > limit, data = hasExtra ? rows.slice(0, limit) : [...rows];
124
+ direction === "backward" && (data = data.reverse());
125
+ let first = data[0], last = data[data.length - 1], hasNext = direction === "forward" ? hasExtra : hasCursor, hasPrev = direction === "forward" ? hasCursor : hasExtra;
126
+ return {
127
+ data,
128
+ meta: {
129
+ limit,
130
+ has_next: hasNext,
131
+ has_prev: hasPrev,
132
+ next_cursor: hasNext && last ? await encodeCursorToken({ k: last[cursorField], id: last[idField] }, secret) : null,
133
+ prev_cursor: hasPrev && first ? await encodeCursorToken({ k: first[cursorField], id: first[idField] }, secret) : null
134
+ }
135
+ };
136
+ }
137
+
138
+ // src/internal/mutation.ts
139
+ var isPlainObject = (value) => typeof value == "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
140
+ function isAllRows(filter) {
141
+ return isPlainObject(filter) && filter.all === true && Object.keys(filter).length === 1;
142
+ }
143
+ function isEmptyFilter(filter) {
144
+ return filter == null || Array.isArray(filter) && filter.length === 0 || isPlainObject(filter) && Object.keys(filter).length === 0;
145
+ }
146
+ function refuseEmptyFilter(source, method) {
147
+ throw new chunk72KNINHB_cjs.QueryKitError({ source, site: "mutation", key: method, reason: "empty-filter" });
148
+ }
149
+
150
+ // src/internal/scope.ts
151
+ var isPlainObject2 = (value) => typeof value == "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype, isInScope = (value) => isPlainObject2(value) && Object.keys(value).length === 1 && Array.isArray(value.in);
152
+ function scopeConditions(scope) {
153
+ return Object.entries(scope ?? {}).map(
154
+ ([key, value]) => isInScope(value) ? { key, operation: "in", value: [...value.in] } : { key, operation: "=", value }
155
+ );
156
+ }
157
+ function scopeDefaults(scope) {
158
+ return Object.fromEntries(Object.entries(scope ?? {}).filter(([, value]) => !isInScope(value)));
159
+ }
160
+
161
+ // src/internal/with.ts
162
+ var isPlainObject3 = (value) => typeof value == "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype, ruleOf = (relations, name) => {
163
+ let rule = relations && Object.prototype.hasOwnProperty.call(relations, name) ? relations[name] : void 0;
164
+ return isPlainObject3(rule) && (rule.columns === "*" || Array.isArray(rule.columns)) ? rule : void 0;
165
+ }, selectedKeys = (selection) => {
166
+ if (typeof selection == "string") return selection.split(/\s+/).filter(Boolean);
167
+ if (isPlainObject3(selection)) return Object.keys(selection).filter((key) => !!selection[key]);
168
+ };
169
+ function checkWith(withValue, relations, dialect, source, path = "") {
170
+ if (withValue == null) return;
171
+ let refuse = (key) => {
172
+ throw new chunk72KNINHB_cjs.QueryKitError({ source, site: "with", key: path + key, reason: "forbidden-key" });
173
+ };
174
+ if (!isPlainObject3(withValue)) return refuse("with");
175
+ let checked = {};
176
+ for (let [name, value] of Object.entries(withValue)) {
177
+ if (value === false || value === void 0) continue;
178
+ let rule = ruleOf(relations, name) ?? refuse(name), listed = rule.columns === "*" ? void 0 : new Set(rule.columns), selectListed = () => Object.fromEntries([...listed].map((column) => [column, true]));
179
+ if (value === true) {
180
+ checked[name] = listed ? { [dialect.columns]: selectListed() } : true;
181
+ continue;
182
+ }
183
+ isPlainObject3(value) || refuse(name);
184
+ let options = value, out = {};
185
+ for (let [key, option] of Object.entries(options)) {
186
+ let at = `${name}.${key}`;
187
+ if (key === dialect.columns) {
188
+ let keys = selectedKeys(option) ?? refuse(at);
189
+ for (let column of keys) listed && (column.startsWith("-") || !listed.has(column)) && refuse(`${name}.${column}`);
190
+ out[key] = option;
191
+ } else if (key === dialect.nested)
192
+ out[key] = checkWith(option, rule.nested, dialect, source, `${name}.`);
193
+ else if (dialect.filters.includes(key))
194
+ checkFilterFields(option, rule, dialect, (field) => refuse(`${name}.${field}`)), out[key] = option;
195
+ else if (dialect.paging.includes(key))
196
+ out[key] = option;
197
+ else if (key === dialect.orderBy) {
198
+ for (let item of Array.isArray(option) ? option : [option])
199
+ for (let column of selectedKeys(item) ?? refuse(at)) listed && !listed.has(column) && refuse(`${name}.${column}`);
200
+ out[key] = option;
201
+ } else if (key === dialect.options) {
202
+ for (let sub of Object.keys(isPlainObject3(option) ? option : refuse(at))) dialect.paging.includes(sub) || refuse(`${at}.${sub}`);
203
+ out[key] = option;
204
+ } else
205
+ refuse(at);
206
+ }
207
+ listed && out[dialect.columns] === void 0 && (out[dialect.columns] = selectListed()), checked[name] = out;
208
+ }
209
+ return checked;
210
+ }
211
+ function checkFilterFields(filter, rule, dialect, refuse) {
212
+ isPlainObject3(filter) || refuse("filter");
213
+ for (let [key, value] of Object.entries(filter))
214
+ if (dialect.logical.includes(key))
215
+ for (let part of Array.isArray(value) ? value : [value]) checkFilterFields(part, rule, dialect, refuse);
216
+ else (key.startsWith("$") || !rule.filterable || rule.filterable[key] === void 0) && refuse(key);
217
+ }
218
+
219
+ // src/internal/limits.ts
220
+ var isRecord = (value) => typeof value == "object" && value !== null && !Array.isArray(value), formatLimit = (limit, max) => `${limit} (${max})`;
221
+ function findFilterLimitViolation(filter, limits) {
222
+ let hit = (limit, path) => ({ limit, max: limits[limit], path }), pending = [], push = (children, depth, path) => {
223
+ for (let index = children.length - 1; index >= 0; index--) pending.push([children[index], depth, [...path, index]]);
224
+ };
225
+ Array.isArray(filter) ? push(filter, 1, []) : filter !== void 0 && pending.push([filter, 1, []]);
226
+ let conditions = 0;
227
+ for (let next = pending.pop(); next; next = pending.pop()) {
228
+ let [node, depth, path] = next;
229
+ if (depth > limits.maxDepth) return hit("maxDepth", path);
230
+ if (!isRecord(node)) continue;
231
+ if ("key" in node) {
232
+ if (++conditions > limits.maxConditions) return hit("maxConditions", path);
233
+ let { value } = node, valuePath = [...path, "value"];
234
+ if (Array.isArray(value) && value.length > limits.maxInItems) return hit("maxInItems", valuePath);
235
+ let tooLong = (Array.isArray(value) ? value : [value]).findIndex((item) => typeof item == "string" && item.length > limits.maxStringLength);
236
+ if (tooLong >= 0) return hit("maxStringLength", Array.isArray(value) ? [...valuePath, tooLong] : valuePath);
237
+ continue;
238
+ }
239
+ let group = "and" in node ? "and" : "or" in node ? "or" : void 0;
240
+ if (group) {
241
+ let children = node[group];
242
+ Array.isArray(children) && push(children, depth + 1, [...path, group]);
243
+ } else "not" in node && pending.push([node.not, depth + 1, [...path, "not"]]);
244
+ }
245
+ }
246
+ var LIKE_OPERATORS = /* @__PURE__ */ new Set(["like", "ilike", "notLike"]), isLikeOperator = (operation) => LIKE_OPERATORS.has(operation);
247
+ function likeWildcards(pattern) {
248
+ let count = 0, previous = "";
249
+ for (let index = 0; index < pattern.length; index++) {
250
+ let char = pattern[index];
251
+ if (char === "\\") {
252
+ index++, previous = "";
253
+ continue;
254
+ }
255
+ (char === "_" || char === "%" && previous !== "%") && count++, previous = char;
256
+ }
257
+ return count;
258
+ }
259
+ var offsetOf = (page, perPage) => ((page ?? 1) - 1) * (perPage ?? 20);
260
+
261
+ // src/internal/policy.ts
262
+ var DEFAULT_OPERATORS = ["=", "in"], OPERATOR_TOKEN = {
263
+ eq: "=",
264
+ ne: "!=",
265
+ gt: ">",
266
+ gte: ">=",
267
+ lt: "<",
268
+ lte: "<=",
269
+ contains: "%_%",
270
+ startsWith: "%_",
271
+ endsWith: "_%"
272
+ }, tokenOf = (operation) => OPERATOR_TOKEN[operation] ?? operation, isRecord2 = (value) => typeof value == "object" && value !== null && !Array.isArray(value);
273
+ function filterRules(options, canonical) {
274
+ let rules = /* @__PURE__ */ new Map();
275
+ if (options.filterable) {
276
+ for (let [key, operators] of Object.entries(options.filterable))
277
+ operators && rules.set(canonical(key), operators === "*" ? "*" : new Set(operators.map(tokenOf)));
278
+ return rules;
279
+ }
280
+ let fields = options.allowedColumns ?? (options.forcedColumns ? Object.keys(options.forcedColumns).filter((key) => options.forcedColumns[key]) : [options.primaryKey]), equality = new Set(DEFAULT_OPERATORS);
281
+ for (let field of fields) rules.set(canonical(field), equality);
282
+ return rules;
283
+ }
284
+ function resolvePolicy(options) {
285
+ let limits = chunk72KNINHB_cjs.resolveLimits(options.limits), canonical = (key) => options.fieldOf(key) ?? key, rules = filterRules(options, canonical), sortable = new Set((options.sortable ?? [options.primaryKey, ...options.createdAt ? [options.createdAt] : []]).map(canonical)), refuse = (site, key, reason, operation) => {
286
+ throw new chunk72KNINHB_cjs.QueryKitError({ source: options.source, site, key, reason, ...operation ? { operation } : {} });
287
+ }, checkCondition2 = (node) => {
288
+ let key = String(node.key), field = options.fieldOf(key);
289
+ if (field === void 0) return;
290
+ let operation = typeof node.operation == "string" ? node.operation : "=", rule = rules.get(field);
291
+ rule || refuse("filter", key, "forbidden-key", operation), rule !== "*" && !rule.has(tokenOf(operation)) && refuse("filter", key, "forbidden-operator", operation), isLikeOperator(operation) && typeof node.value == "string" && likeWildcards(node.value) > limits.maxLikeWildcards && refuse("filter", "maxLikeWildcards", "limit-exceeded", operation);
292
+ }, checkSortKey = (site, key) => {
293
+ let field = options.fieldOf(key);
294
+ field !== void 0 && !sortable.has(field) && refuse(site, key, "forbidden-key");
295
+ };
296
+ return {
297
+ limits,
298
+ checkFilter(filter) {
299
+ let violation = findFilterLimitViolation(filter, limits);
300
+ violation && refuse("filter", violation.limit, "limit-exceeded");
301
+ let pending = Array.isArray(filter) ? [...filter] : [filter];
302
+ for (; pending.length > 0; ) {
303
+ let node = pending.pop();
304
+ isRecord2(node) && ("key" in node ? checkCondition2(node) : Array.isArray(node.and) ? pending.push(...node.and) : Array.isArray(node.or) ? pending.push(...node.or) : "not" in node ? pending.push(node.not) : !chunk72KNINHB_cjs.isRaw(node) && !options.isNative?.(node) && refuse("filter", Object.keys(node)[0] ?? "", "forbidden-key"));
305
+ }
306
+ },
307
+ checkSort(sort) {
308
+ if (sort) {
309
+ sort.length > limits.maxSortFields && refuse("sort", "maxSortFields", "limit-exceeded");
310
+ for (let item of sort) item && typeof item.key == "string" && item.key && checkSortKey("sort", item.key);
311
+ }
312
+ },
313
+ checkCursorKey(key) {
314
+ checkSortKey("cursorKey", key);
315
+ },
316
+ checkPage(page) {
317
+ ("offset" in page ? page.offset : offsetOf(page.page, page.perPage)) > limits.maxOffset && refuse("page", "maxOffset", "limit-exceeded");
318
+ },
319
+ checkAggregateKey(key) {
320
+ let field = options.fieldOf(key);
321
+ field !== void 0 && !rules.has(field) && refuse("aggregate", key, "forbidden-key");
322
+ }
323
+ };
324
+ }
325
+
326
+ // src/internal/wire-checks.ts
327
+ var ALL_OPERATORS = /* @__PURE__ */ new Set(chunk72KNINHB_cjs.FILTER_OPERATORS), isRecord3 = (value) => typeof value == "object" && value !== null && !Array.isArray(value), isScalar2 = (value) => value === null || typeof value == "string" || typeof value == "boolean" || typeof value == "number" && Number.isFinite(value), isFilterValue = (value) => Array.isArray(value) ? value.every(isScalar2) : isScalar2(value), label = (value) => {
328
+ try {
329
+ return String(value);
330
+ } catch {
331
+ return Object.prototype.toString.call(value);
332
+ }
333
+ }, wireIssue = (key, path, args = {}) => ({ path, key, args });
334
+ function checkCondition(node, path, walk) {
335
+ if (!isRecord3(node)) {
336
+ walk.issues.push(wireIssue("querykit.filter.invalid_node", path));
337
+ return;
338
+ }
339
+ let { key, operation, value } = node;
340
+ if (typeof key != "string") {
341
+ walk.issues.push(wireIssue("querykit.filter.missing_key", path));
342
+ return;
343
+ }
344
+ walk.allowedKeys && !walk.allowedKeys.has(key) && walk.issues.push(wireIssue("querykit.filter.unknown_key", path, { key }));
345
+ let operator;
346
+ operation !== void 0 && (typeof operation == "string" && walk.operators.has(operation) ? operator = operation : walk.issues.push(wireIssue("querykit.filter.invalid_operator", [...path, "operation"], { operation: label(operation) }))), value !== void 0 && !isFilterValue(value) && walk.issues.push(wireIssue("querykit.filter.invalid_value", [...path, "value"])), walk.strictValue && operator !== void 0 && checkValueShape(operator, value, [...path, "value"], walk);
347
+ }
348
+ function checkValueShape(operator, value, path, walk) {
349
+ let key = (operator === "in" || operator === "notIn") && !Array.isArray(value) ? "querykit.filter.value_requires_array" : (operator === "between" || operator === "notBetween") && (!Array.isArray(value) || value.length !== 2) ? "querykit.filter.value_requires_tuple" : (operator === "isNull" || operator === "isNotNull") && value !== void 0 ? "querykit.filter.value_forbidden" : void 0;
350
+ key && walk.issues.push(wireIssue(key, path, { operation: operator }));
351
+ }
352
+ function walkGroup(group, depth, path, walk) {
353
+ if (!Array.isArray(group)) {
354
+ walk.issues.push(wireIssue("querykit.filter.invalid_group", path));
355
+ return;
356
+ }
357
+ group.forEach((child, index) => walkNode(child, depth + 1, [...path, index], walk));
358
+ }
359
+ function walkNode(node, depth, path, walk) {
360
+ if (depth > walk.maxDepth) {
361
+ walk.issues.push(wireIssue("querykit.filter.max_depth", path, { max: walk.maxDepth }));
362
+ return;
363
+ }
364
+ if (!isRecord3(node)) {
365
+ walk.issues.push(wireIssue("querykit.filter.invalid_node", path));
366
+ return;
367
+ }
368
+ "key" in node ? checkCondition(node, path, walk) : "and" in node ? walkGroup(node.and, depth, [...path, "and"], walk) : "or" in node ? walkGroup(node.or, depth, [...path, "or"], walk) : "not" in node ? walkNode(node.not, depth + 1, [...path, "not"], walk) : walk.issues.push(wireIssue("querykit.filter.missing_key", path));
369
+ }
370
+ function checkFilter(value, options = {}) {
371
+ if (value === void 0) return [];
372
+ let limits = chunk72KNINHB_cjs.resolveLimits(options), violation = findFilterLimitViolation(value, limits);
373
+ if (violation) {
374
+ let key = violation.limit === "maxDepth" ? "querykit.filter.max_depth" : "querykit.limit.exceeded";
375
+ return [wireIssue(key, violation.path, { limit: violation.limit, max: violation.max })];
376
+ }
377
+ let walk = {
378
+ issues: [],
379
+ allowedKeys: options.allowedKeys ? new Set(options.allowedKeys) : void 0,
380
+ operators: options.allowedOperators ? new Set(options.allowedOperators) : ALL_OPERATORS,
381
+ maxDepth: limits.maxDepth,
382
+ strictValue: options.strictValue ?? false
383
+ };
384
+ return Array.isArray(value) ? value.forEach((item, index) => checkCondition(item, [index], walk)) : walkNode(value, 1, [], walk), walk.issues;
385
+ }
386
+ function checkSort(value, options = {}) {
387
+ if (value === void 0) return [];
388
+ if (!Array.isArray(value)) return [wireIssue("querykit.sort.not_array", [])];
389
+ let { maxSortFields } = chunk72KNINHB_cjs.resolveLimits(options);
390
+ if (value.length > maxSortFields) return [wireIssue("querykit.limit.exceeded", [], { limit: "maxSortFields", max: maxSortFields })];
391
+ let allowedKeys = options.allowedKeys ? new Set(options.allowedKeys) : void 0, issues = [];
392
+ return value.forEach((item, index) => {
393
+ if (!isRecord3(item)) {
394
+ issues.push(wireIssue("querykit.sort.invalid_item", [index]));
395
+ return;
396
+ }
397
+ let { key, direction } = item;
398
+ if (typeof key != "string") {
399
+ issues.push(wireIssue("querykit.sort.missing_key", [index]));
400
+ return;
401
+ }
402
+ allowedKeys && !allowedKeys.has(key) && issues.push(wireIssue("querykit.sort.unknown_key", [index], { key })), direction !== void 0 && direction !== "asc" && direction !== "desc" && issues.push(wireIssue("querykit.sort.invalid_direction", [index, "direction"]));
403
+ }), issues;
404
+ }
405
+ var prefixed = (path, issues) => issues.map((found) => ({ ...found, path: [...path, ...found.path] })), accepts = (expected, value) => expected === "object" ? isRecord3(value) : expected === "string or null" ? value === null || typeof value == "string" : typeof value === expected, typed = (expected) => (value, path, issues) => {
406
+ accepts(expected, value) || issues.push(wireIssue("querykit.params.invalid_type", path, { expected: `${expected === "object" ? "an" : "a"} ${expected}` }));
407
+ }, oneOf = (values) => (value, path, issues) => {
408
+ (typeof value != "string" || !values.includes(value)) && issues.push(wireIssue("querykit.params.invalid_enum", path, { values: values.join(", ") }));
409
+ }, integer = (min, max, overLimit) => (value, path, issues) => {
410
+ typeof value != "number" || !Number.isFinite(value) ? issues.push(wireIssue("querykit.params.invalid_type", path, { expected: "a number" })) : Number.isInteger(value) ? value < min ? issues.push(wireIssue("querykit.params.out_of_range", path, { min })) : value > max && overLimit ? issues.push(wireIssue("querykit.limit.exceeded", path, { limit: overLimit, max })) : value > max && issues.push(wireIssue("querykit.params.out_of_range", path, { max })) : issues.push(wireIssue("querykit.params.not_integer", path));
411
+ }, checkColumns = (value, path, issues) => {
412
+ if (typed("object")(value, path, issues), isRecord3(value)) for (let [name, flag] of Object.entries(value)) typed("boolean")(flag, [...path, name], issues);
413
+ }, serverOwnedCheck = (field) => field === "columns" ? checkColumns : typed(field === "with" ? "object" : "boolean");
414
+ function fieldChecks(kind, options, limits) {
415
+ let checks = /* @__PURE__ */ new Map();
416
+ checks.set("filter", (value, path, issues) => issues.push(...prefixed(path, checkFilter(value, limits)))), kind !== "cursor" && checks.set("sort", (value, path, issues) => issues.push(...prefixed(path, checkSort(value, limits))));
417
+ for (let field of options.allow ?? []) checks.set(field, serverOwnedCheck(field));
418
+ let maxLimit = options.maxLimit ?? 200;
419
+ return kind === "offset" ? (checks.set("page", integer(1, Number.POSITIVE_INFINITY)), checks.set("perPage", integer(1, options.maxPerPage ?? 200)), checks.set("withTotal", typed("boolean"))) : kind === "infinite" ? (checks.set("limit", integer(1, maxLimit)), checks.set("offset", integer(0, limits.maxOffset, "maxOffset"))) : (checks.set("limit", integer(1, maxLimit)), checks.set("cursor", typed("string or null")), checks.set("cursorKey", typed("string")), checks.set("order", oneOf(["asc", "desc"])), checks.set("direction", oneOf(["forward", "backward"]))), checks;
420
+ }
421
+ function checkParams(kind, value, options = {}) {
422
+ if (!isRecord3(value)) return { issues: [wireIssue("querykit.params.invalid_type", [], { expected: "an object" })] };
423
+ let limits = chunk72KNINHB_cjs.resolveLimits(options.limits), checks = fieldChecks(kind, options, limits), issues = [], accepted = {};
424
+ for (let [name, field] of Object.entries(value)) {
425
+ let check = checks.get(name);
426
+ if (!check || field === void 0) continue;
427
+ let before = issues.length;
428
+ check(field, [name], issues), issues.length === before && (accepted[name] = field);
429
+ }
430
+ if (kind === "offset" && issues.length === 0) {
431
+ let page = typeof accepted.page == "number" ? accepted.page : void 0, perPage = typeof accepted.perPage == "number" ? accepted.perPage : void 0;
432
+ offsetOf(page, perPage) > limits.maxOffset && issues.push(wireIssue("querykit.limit.exceeded", ["page"], { limit: "maxOffset", max: limits.maxOffset }));
433
+ }
434
+ return issues.length ? { issues } : { value: accepted, issues };
435
+ }
436
+
437
+ // src/internal/messages.ts
438
+ var QUERYKIT_MESSAGES_EN = {
439
+ "querykit.filter.invalid_node": "{path} must be a filter condition or an and/or/not group",
440
+ "querykit.filter.missing_key": "{path} is missing the required `key` string",
441
+ "querykit.filter.unknown_key": "{path}: `{key}` is not an allowed filter key",
442
+ "querykit.filter.invalid_operator": "{path}: `{operation}` is not a supported filter operator",
443
+ "querykit.filter.invalid_value": "{path} must be a string, number, boolean, null, or an array of those",
444
+ "querykit.filter.invalid_group": "{path} must be an array of filter nodes",
445
+ "querykit.filter.max_depth": "{path} exceeds maxDepth ({max})",
446
+ "querykit.filter.value_requires_array": "{path}: `{operation}` requires an array value",
447
+ "querykit.filter.value_requires_tuple": "{path}: `{operation}` requires exactly two values",
448
+ "querykit.filter.value_forbidden": "{path}: `{operation}` must not carry a value",
449
+ "querykit.sort.not_array": "{path} must be an array of { key, direction } items",
450
+ "querykit.sort.invalid_item": "{path} must be an object with a `key` string",
451
+ "querykit.sort.missing_key": "{path} is missing the required `key` string",
452
+ "querykit.sort.unknown_key": "{path}: `{key}` is not an allowed sort key",
453
+ "querykit.sort.invalid_direction": "{path}: direction must be `asc` or `desc`",
454
+ "querykit.limit.exceeded": "{path} exceeds {limit} ({max})"
455
+ }, QUERYKIT_PARAMS_MESSAGES_EN = {
456
+ "querykit.params.invalid_type": "{path} must be {expected}",
457
+ "querykit.params.not_integer": "{path} must be an integer",
458
+ // args carry only the crossed bound (`min` or `max`), so the text names neither
459
+ "querykit.params.out_of_range": "{path} is out of the allowed range",
460
+ "querykit.params.invalid_enum": "{path} must be one of: {values}"
461
+ }, PLACEHOLDER = /\{(\w+)\}/g, interpolate = (template, args) => template.replace(PLACEHOLDER, (match, name) => {
462
+ let value = args[name];
463
+ return value === void 0 ? match : String(value);
464
+ });
465
+ function formatMessage(key, args = {}, catalog = QUERYKIT_MESSAGES_EN) {
466
+ return interpolate(catalog[key], args);
467
+ }
468
+
469
+ Object.defineProperty(exports, "isRaw", {
470
+ enumerable: true,
471
+ get: function () { return chunk72KNINHB_cjs.isRaw; }
472
+ });
473
+ exports.QUERYKIT_MESSAGES_EN = QUERYKIT_MESSAGES_EN;
474
+ exports.QUERYKIT_PARAMS_MESSAGES_EN = QUERYKIT_PARAMS_MESSAGES_EN;
475
+ exports.checkFilter = checkFilter;
476
+ exports.checkParams = checkParams;
477
+ exports.checkSort = checkSort;
478
+ exports.checkWith = checkWith;
479
+ exports.clampOffset = clampOffset;
480
+ exports.clampPage = clampPage;
481
+ exports.clampPageSize = clampPageSize;
482
+ exports.createContextStore = createContextStore;
483
+ exports.cursorPage = cursorPage;
484
+ exports.decodeCursor = decodeCursor;
485
+ exports.decodeCursorToken = decodeCursorToken;
486
+ exports.encodeCursor = encodeCursor;
487
+ exports.encodeCursorToken = encodeCursorToken;
488
+ exports.findFilterLimitViolation = findFilterLimitViolation;
489
+ exports.formatLimit = formatLimit;
490
+ exports.formatMessage = formatMessage;
491
+ exports.infinitePage = infinitePage;
492
+ exports.interpolate = interpolate;
493
+ exports.isAllRows = isAllRows;
494
+ exports.isEmptyFilter = isEmptyFilter;
495
+ exports.isLikeOperator = isLikeOperator;
496
+ exports.likeWildcards = likeWildcards;
497
+ exports.normalizeSort = normalizeSort;
498
+ exports.offsetMeta = offsetMeta;
499
+ exports.offsetOf = offsetOf;
500
+ exports.offsetPageWithoutTotal = offsetPageWithoutTotal;
501
+ exports.refuseEmptyFilter = refuseEmptyFilter;
502
+ exports.reportSkip = reportSkip;
503
+ exports.resolvePolicy = resolvePolicy;
504
+ exports.scopeConditions = scopeConditions;
505
+ exports.scopeDefaults = scopeDefaults;
506
+ //# sourceMappingURL=internal.cjs.map
507
+ //# sourceMappingURL=internal.cjs.map