staticql
Version:
Type-safe query engine for static content including Markdown, YAML, JSON, and more.
111 lines (110 loc) • 3.76 kB
JavaScript
import { resolveField } from "./field.js";
/**
* Builds a map from foreign key values to parent objects.
*
* Useful for indexing related records by a specified field.
*
* @param data - Array of objects to index.
* @param foreignKeyPath - Dot-notated path to the key field.
* @returns A map of key to array of matching objects.
*/
export function buildForeignKeyMap(data, foreignKeyPath) {
const map = new Map();
for (const obj of data) {
const values = resolveField(obj, foreignKeyPath);
for (const v of values) {
if (!map.has(v)) {
map.set(v, []);
}
map.get(v).push(obj);
}
}
return map;
}
/**
* Finds map entries whose keys partially match a keyword.
*
* @param map - A map to search.
* @param keyword - Keyword to match.
* @param options - Optional case-insensitive matching.
* @returns Matching values.
*/
export function findEntriesByPartialKey(map, keyword, options) {
const matchFn = options?.caseInsensitive
? (k) => k.toLowerCase().includes(keyword.toLowerCase())
: (k) => k.includes(keyword);
return Array.from(map.entries())
.filter(([key]) => key && matchFn(key))
.map(([, value]) => value);
}
/**
* Resolves a direct relation (e.g., hasOne, hasMany) for a given row.
*
* @param row - The source object.
* @param rel - Relation metadata (including localKey and foreignKey).
* @param foreignData - The target dataset to match against.
* @param foreignMapOpt - (optional) Pre-built foreign key map for performance.
* @returns Related object(s) or null.
*/
export function resolveDirectRelation(row, rel, foreignData, foreignMapOpt) {
const foreignMap = foreignMapOpt ?? buildForeignKeyMap(foreignData, rel.foreignKey);
const relType = rel.type;
const localKeys = resolveField(row, rel.localKey).flat();
let matches = [];
if (localKeys.length === 1) {
for (const k of localKeys) {
const arr = foreignMap.get(k);
if (arr)
matches.push(...arr);
}
}
else {
matches = localKeys
.map((k) => findEntriesByPartialKey(foreignMap, k))
.flat()
.flat();
}
if (relType === "hasOne") {
return matches.length > 0 ? matches[0] : null;
}
else {
return matches;
}
}
/**
* Resolves a through-relation (e.g., hasOneThrough, hasManyThrough).
*
* @param row - The source object.
* @param rel - Relation metadata including through and target keys.
* @param throughData - The intermediate dataset.
* @param targetData - The final related dataset.
* @param targetMapOpt - (optional) Pre-built target key map for performance.
* @returns Related object(s) or null.
*/
export function resolveThroughRelation(row, rel, throughData, targetData, targetMapOpt, throughMapOpt) {
const sourceKeys = resolveField(row, rel.sourceLocalKey).flat();
const throughMap = throughMapOpt ?? buildForeignKeyMap(throughData, rel.throughForeignKey);
const targetMap = targetMapOpt ?? buildForeignKeyMap(targetData, rel.targetForeignKey);
const throughRecords = sourceKeys
.map((k) => throughMap.get(k))
.filter((v) => !!v)
.filter(Boolean)
.flat();
const targets = throughRecords
.map((t) => {
const throughLocalKeys = resolveField(t, rel.throughLocalKey)
.filter((v) => !!v)
.flat();
return throughLocalKeys
.map((k) => targetMap.get(k))
.filter((v) => !!v)
.flat();
})
.flat();
if (rel.type === "hasOneThrough") {
return targets.length > 0 ? targets[0] : null;
}
else {
return targets;
}
}