From 2847e64b77e492d9d3276d1cf74c2378e55633a4 Mon Sep 17 00:00:00 2001 From: dswbx Date: Sun, 26 Oct 2025 21:22:42 +0100 Subject: [PATCH] feat: enhance query handling by ignoring undefined values - Updated query conversion logic to skip undefined values, improving robustness. - Added tests to validate that undefined values are correctly ignored in query specifications. --- app/__test__/core/object/query.spec.ts | 3 +++ app/src/core/object/query/query.ts | 9 ++++++- app/src/data/server/query.spec.ts | 33 ++++++++++++++++++++++++++ app/src/data/server/query.ts | 16 +------------ 4 files changed, 45 insertions(+), 16 deletions(-) diff --git a/app/__test__/core/object/query.spec.ts b/app/__test__/core/object/query.spec.ts index fac73e2..80383c6 100644 --- a/app/__test__/core/object/query.spec.ts +++ b/app/__test__/core/object/query.spec.ts @@ -110,6 +110,9 @@ describe("query", () => { // @ts-expect-error only strings are allowed for $like expect(() => validator.convert({ foo: { $like: 1 } })).toThrow(); + // undefined values are ignored + expect(validator.convert({ foo: undefined })).toEqual({}); + expect(validator.convert({ foo: "bar" })).toEqual({ foo: { $eq: "bar" } }); expect(validator.convert({ foo: { $eq: "bar" } })).toEqual({ foo: { $eq: "bar" } }); expect(validator.convert({ foo: { $like: "bar" } })).toEqual({ foo: { $like: "bar" } }); diff --git a/app/src/core/object/query/query.ts b/app/src/core/object/query/query.ts index 58a50cc..110cce6 100644 --- a/app/src/core/object/query/query.ts +++ b/app/src/core/object/query/query.ts @@ -55,7 +55,7 @@ function getExpression( } type LiteralExpressionCondition = { - [key: string]: Primitive | ExpressionCondition; + [key: string]: undefined | Primitive | ExpressionCondition; }; const OperandOr = "$or" as const; @@ -96,6 +96,11 @@ function _convert( } for (const [key, value] of Object.entries($query)) { + // skip undefined values + if (value === undefined) { + continue; + } + // if $or, convert each value if (key === "$or") { invariant(isPlainObject(value), "$or must be an object"); @@ -171,6 +176,8 @@ function _build( // check $and for (const [key, value] of Object.entries($and)) { + if (value === undefined) continue; + for (const [$op, $v] of Object.entries(value)) { const objValue = options.value_is_kv ? key : getPath(options.object, key); result.$and.push(__validate($op, $v, objValue, [key])); diff --git a/app/src/data/server/query.spec.ts b/app/src/data/server/query.spec.ts index 89585a3..eb2eb2b 100644 --- a/app/src/data/server/query.spec.ts +++ b/app/src/data/server/query.spec.ts @@ -1,6 +1,8 @@ import { test, describe, expect } from "bun:test"; import * as q from "./query"; import { parse as $parse, type ParseOptions } from "bknd/utils"; +import type { PrimaryFieldType } from "modules"; +import type { Generated } from "kysely"; const parse = (v: unknown, o: ParseOptions = {}) => $parse(q.repoQuery, v, { @@ -186,4 +188,35 @@ describe("server/query", () => { decode({ with: { images: {}, comments: {} } }, output); } }); + + test("types", () => { + const id = 1 as PrimaryFieldType; + const id2 = "1" as unknown as Generated; + + const c: q.RepoQueryIn = { + where: { + // @ts-expect-error only primitives are allowed for $eq + something: [], + // this gets ignored + another: undefined, + // @ts-expect-error null is not a valid value + null_is_okay: null, + some_id: id, + another_id: id2, + }, + }; + + const d: q.RepoQuery = { + where: { + // @ts-expect-error only primitives are allowed for $eq + something: [], + // this gets ignored + another: undefined, + // @ts-expect-error null is not a valid value + null_is_okay: null, + some_id: id, + another_id: id2, + }, + }; + }); }); diff --git a/app/src/data/server/query.ts b/app/src/data/server/query.ts index cb4defe..9a01e2a 100644 --- a/app/src/data/server/query.ts +++ b/app/src/data/server/query.ts @@ -84,8 +84,6 @@ const where = s.anyOf([s.string(), s.object({})], { return WhereBuilder.convert(q); }, }); -//type WhereSchemaIn = s.Static; -//type WhereSchema = s.StaticCoerced; // ------ // with @@ -128,7 +126,7 @@ const withSchema = (self: s.Schema): s.Schema<{}, Type, Type> => } } - return value as unknown as any; + return value as any; }, }) as any; @@ -167,15 +165,3 @@ export type RepoQueryIn = { export type RepoQuery = s.StaticCoerced & { sort: SortSchema; }; - -//export type RepoQuery = s.StaticCoerced; -// @todo: CURRENT WORKAROUND -/* export type RepoQuery = { - limit?: number; - offset?: number; - sort?: { by: string; dir: "asc" | "desc" }; - select?: string[]; - with?: Record; - join?: string[]; - where?: WhereQuery; -}; */