import { Item_Unresolvable } from '@efforting.tech/errors'; //TODO: Should this be integrated with rules.mjs for predicates? Possibly a normalization step during rule insertion. export class Abstract_Resolver { resolve(item, extra_info={}) { const result = this.resolve_handler(item, extra_info); if (!result?.handler) { throw new Item_Unresolvable({ resolver: this, item }); } // TO DOC: Spreading result into the resulting context means there are some reserved keys we need to be mindful of to avoid clobbering them return result.handler({ resolver: this, item, ...extra_info, ...result }); } } export class Chained_Resolver extends Abstract_Resolver { constructor(chain_links) { super(); Object.assign(this, { chain_links }); } resolve_handler(item, extra_info={}) { const { chain_links } = this; for (const link of chain_links) { const result = link.resolve_handler(item, extra_info); if (result?.handler) { return result; } } } } export class Predicate_Resolver extends Abstract_Resolver { constructor(rules=[]) { // NOTE: Rules should be iterable as [predicate, handler] pairs super(); Object.assign(this, { rules }); } resolve_handler(item, extra_info={}) { const { rules } = this; for (const [predicate, handler] of rules) { const predicate_result = predicate(item, extra_info); // NOTE: to return a falsy predicate_result as a positive hit you must wrap it in something if (predicate_result) { return { handler, predicate_result }; } } } } export class RegExp_Resolver extends Predicate_Resolver { constructor(rules=[]) { // NOTE: Rules should be iterable as [predicate, handler] pairs super(); Object.assign(this, { rules: rules.map(([pattern, handler]) => [(str, extra_info) => str.match(pattern), handler]) }); } } export class Mapping_Resolver extends Abstract_Resolver { constructor(rules=new Map(), key_function=null) { super(); Object.assign(this, { rules, key_function }); } resolve_handler(item, extra_info={}) { const { key_function, rules } = this; const key = key_function ? key_function(item) : item; return rules.get(key); } } export class Sequential_Resolver extends Abstract_Resolver { constructor(rules=[]) { // NOTE: Rules should be iterable as [Abstract_Sequential_Condition, handler] pairs super(); Object.assign(this, { rules }); } resolve_handler(item, extra_info={}) { const { rules } = this; for (const [sequential_condition, handler] of rules) { const sequential_condition_result = sequential_condition.match(item, extra_info); // NOTE: to return a falsy predicate_result as a positive hit you must wrap it in something if (sequential_condition_result) { return { handler, sequential_condition_result }; } } } }