feat: scaffold yonexus MVP core with storage, auth, query, and scope memory

This commit is contained in:
2026-03-07 05:21:25 +00:00
parent e7f4aeae1a
commit 1436d63a8c
18 changed files with 600 additions and 29 deletions

43
src/tools/query.ts Normal file
View File

@@ -0,0 +1,43 @@
import type { Identity, QueryFilter, QueryInput, QueryOptions, YonexusSchema } from "../models/types";
const DEFAULT_LIMIT = 20;
const MAX_LIMIT = 100;
function isQueryable(field: string, schema: YonexusSchema): boolean {
return Boolean(schema[field]?.queryable);
}
function matchFilter(identity: Identity, filter: QueryFilter): boolean {
const raw = identity.meta[filter.field] ?? "";
switch (filter.op) {
case "eq":
return raw === filter.value;
case "contains":
return raw.toLowerCase().includes(filter.value.toLowerCase());
case "regex": {
const re = new RegExp(filter.value);
return re.test(raw);
}
default:
return false;
}
}
function normalizeOptions(options?: QueryOptions): Required<QueryOptions> {
const limit = Math.min(Math.max(1, options?.limit ?? DEFAULT_LIMIT), MAX_LIMIT);
const offset = Math.max(0, options?.offset ?? 0);
return { limit, offset };
}
export function queryIdentities(identities: Identity[], input: QueryInput, schema: YonexusSchema): Identity[] {
for (const filter of input.filters) {
if (!isQueryable(filter.field, schema)) {
throw new Error(`field_not_queryable: ${filter.field}`);
}
}
const filtered = identities.filter((identity) => input.filters.every((f) => matchFilter(identity, f)));
const { limit, offset } = normalizeOptions(input.options);
return filtered.slice(offset, offset + limit);
}