From 96ec9c808beee4be9ef3bbd36aebe4be58761e71 Mon Sep 17 00:00:00 2001 From: typicode Date: Wed, 18 Feb 2026 00:54:32 +0100 Subject: [PATCH] feat: add in operator for where filters --- src/matches-where.test.ts | 6 ++++++ src/matches-where.ts | 4 ++++ src/parse-where.test.ts | 12 ++++++++++++ src/parse-where.ts | 9 +++++++++ src/where-operators.ts | 2 +- 5 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/matches-where.test.ts b/src/matches-where.test.ts index 66f00dc..c3fb9e7 100644 --- a/src/matches-where.test.ts +++ b/src/matches-where.test.ts @@ -26,6 +26,12 @@ await test('matchesWhere', async (t) => { [{ or: [{ a: { lt: 0 } }, { b: { gt: 20 } }] }, false], [{ nested: { a: { eq: 10 } } }, true], [{ nested: { b: { lt: 20 } } }, false], + [{ a: { in: [10, 11] } }, true], + [{ a: { in: [1, 2] } }, false], + [{ c: { in: ['x', 'y'] } }, true], + [{ c: { in: ['y', 'z'] } }, false], + [{ a: { in: [10, 11], gt: 9 } }, true], + [{ a: { in: [10, 11], gt: 10 } }, false], [{ a: { foo: 10 } }, true], [{ a: { foo: 10, eq: 10 } }, true], [{ missing: { foo: 1 } }, true], diff --git a/src/matches-where.ts b/src/matches-where.ts index 07a7e7d..8a1da62 100644 --- a/src/matches-where.ts +++ b/src/matches-where.ts @@ -53,6 +53,10 @@ export function matchesWhere(obj: JsonObject, where: JsonObject): boolean { if (knownOps.includes('gte') && !((field as any) >= (op.gte as any))) return false if (knownOps.includes('eq') && !((field as any) === (op.eq as any))) return false if (knownOps.includes('ne') && !((field as any) !== (op.ne as any))) return false + if (knownOps.includes('in')) { + const inValues = Array.isArray(op.in) ? op.in : [op.in] + if (!inValues.some((v) => (field as any) === (v as any))) return false + } continue } diff --git a/src/parse-where.test.ts b/src/parse-where.test.ts index 2bf0f89..a2d46d8 100644 --- a/src/parse-where.test.ts +++ b/src/parse-where.test.ts @@ -83,6 +83,18 @@ await test('parseWhere', async (t) => { views: { gt: 100, lt: 300 }, }, ], + [ + 'id:in=1,3', + { + id: { in: [1, 3] }, + }, + ], + [ + 'title_in=hello,world', + { + title: { in: ['hello', 'world'] }, + }, + ], ] for (const [query, expected] of cases) { diff --git a/src/parse-where.ts b/src/parse-where.ts index ffde648..06b3bc6 100644 --- a/src/parse-where.ts +++ b/src/parse-where.ts @@ -30,6 +30,15 @@ function splitKey(key: string): { path: string; op: WhereOperator | null } { function setPathOp(root: JsonObject, path: string, op: WhereOperator, value: string): void { const fullPath = `${path}.${op}` + if (op === 'in') { + setProperty( + root, + fullPath, + value.split(',').map((part) => coerceValue(part.trim())), + ) + return + } + setProperty(root, fullPath, coerceValue(value)) } diff --git a/src/where-operators.ts b/src/where-operators.ts index ec31a04..f522931 100644 --- a/src/where-operators.ts +++ b/src/where-operators.ts @@ -1,4 +1,4 @@ -export const WHERE_OPERATORS = ['lt', 'lte', 'gt', 'gte', 'eq', 'ne'] as const +export const WHERE_OPERATORS = ['lt', 'lte', 'gt', 'gte', 'eq', 'ne', 'in'] as const export type WhereOperator = (typeof WHERE_OPERATORS)[number]