feat: optimize query path and add smoke test/demo assets

This commit is contained in:
2026-03-07 07:00:51 +00:00
parent 0ede080e85
commit 76d6a31d25
9 changed files with 709 additions and 10 deletions

View File

@@ -4,6 +4,25 @@ import type { Identity, QueryFilter, QueryInput, QueryOptions, YonexusSchema } f
const DEFAULT_LIMIT = 20;
const MAX_LIMIT = 100;
const regexCache = new Map<string, RegExp>();
const containsCache = new Map<string, string>();
function getRegex(pattern: string): RegExp {
const cached = regexCache.get(pattern);
if (cached) return cached;
const created = new RegExp(pattern);
regexCache.set(pattern, created);
return created;
}
function normalizeNeedle(value: string): string {
const cached = containsCache.get(value);
if (cached) return cached;
const normalized = value.toLowerCase();
containsCache.set(value, normalized);
return normalized;
}
function isQueryable(field: string, schema: YonexusSchema): boolean {
return Boolean(schema[field]?.queryable);
}
@@ -15,9 +34,9 @@ function matchFilter(identity: Identity, filter: QueryFilter): boolean {
case "eq":
return raw === filter.value;
case "contains":
return raw.toLowerCase().includes(filter.value.toLowerCase());
return raw.toLowerCase().includes(normalizeNeedle(filter.value));
case "regex": {
const re = new RegExp(filter.value);
const re = getRegex(filter.value);
return re.test(raw);
}
default:
@@ -31,6 +50,15 @@ function normalizeOptions(options?: QueryOptions): Required<QueryOptions> {
return { limit, offset };
}
function sortFilters(filters: QueryFilter[]): QueryFilter[] {
const weight = (f: QueryFilter): number => {
if (f.op === 'eq') return 1;
if (f.op === 'contains') return 2;
return 3;
};
return [...filters].sort((a, b) => weight(a) - weight(b));
}
export function queryIdentities(identities: Identity[], input: QueryInput, schema: YonexusSchema): Identity[] {
for (const filter of input.filters) {
if (!isQueryable(filter.field, schema)) {
@@ -40,7 +68,8 @@ export function queryIdentities(identities: Identity[], input: QueryInput, schem
}
}
const filtered = identities.filter((identity) => input.filters.every((f) => matchFilter(identity, f)));
const orderedFilters = sortFilters(input.filters);
const filtered = identities.filter((identity) => orderedFilters.every((f) => matchFilter(identity, f)));
const { limit, offset } = normalizeOptions(input.options);
return filtered.slice(offset, offset + limit);
}