UNPKG

doddle

Version:

Tiny yet feature-packed (async) iteration toolkit.

190 lines (180 loc) 7.37 kB
import { getClassName, getValueDesc, isArrayLike, isAsyncIterable, isBool, isDoddle, isFunction, isInt, isIterable, isNextable, isPair, isPosInt, isReadableStream, isThenable, orderedStages } from "../utils.js"; export class DoddleError extends Error { constructor(message) { super((!Array.isArray(message) ? [message] : message).flat(5).join(" ")); } } /* Sample error messages: > Doddle: Argument 'predicate' of oprator 'Seq.filter' must be a function, > but got 'true'. > Doddle: Optional argument 'projection' of operator 'Seq.window' must be a function, > but got 1. > Doddle: Function argument 'predicate' to operator 'Seq.filter' must return > true or false, but got 1. > Doddle: {conversion|operator} 'X' must be called with {A-B|A} arguments, but got Z. > Doddle: Input of conversion 'aseq' must be an async iterable, iterable, or a function, > but got "hello world" > Doddle: Each element of array argument 'others' to operator 'Seq.zip' must be an > (async) iterable or function, but got "hello world" at index 1. > OVERALL > ${subject} must ${verb} ${expectation}, but got ${value} ${suffix} subject.must # Subject - ${descriptor} '${name}' ${context} # Descriptors - Argument - Function argument # context - of operator '${operator}' - */ const getButGot = (value) => { return ["but got", getValueDesc(value)]; }; export const getSubject = (thing, context, verb) => { return [thing, "of", context, "must", verb]; }; const expectation = (expectation, check) => { return (start) => (x) => { if (!check(x)) { const msg = [start, expectation, getButGot(x)]; throw new DoddleError(msg); } return x; }; }; const expectInt = expectation(`a integer`, isInt); const expectString = expectation(`a string`, x => typeof x === "string"); const expectIntOrInfinity = expectation(`an integer or Infinity`, x => isInt(x) || x === Infinity); const expectPosInt = expectation(`a positive integer`, isPosInt); const expectBool = expectation(`true or false`, isBool); const expectError = expectation(`an error`, x => x instanceof Error); const expectFunc = expectation(`a function`, isFunction); const expectPair = expectation(`an array of length 2`, isPair); const expectStage = expectation("'before', 'after', 'both', or undefined", stage => orderedStages.indexOf(stage) > -1); const anOrStructure = (a, b) => ["an", a, "or", b]; const expectSyncInputValue = expectation(anOrStructure(["iterable", "iterator", "doddle"], "a function"), x => isIterable(x) || isFunction(x) || isDoddle(x) || isNextable(x) || isArrayLike(x)); const expectAsyncInputValue = expectation(anOrStructure(["(async) iterable", "iterator", "doddle"].join(", "), "a function"), x => isIterable(x) || isAsyncIterable(x) || isFunction(x) || isDoddle(x) || isNextable(x) || isReadableStream(x) || isArrayLike(x)); const iterableOrIterator = anOrStructure("iterable", "iterator"); const expectSyncIterableOrIterator = expectation(anOrStructure("iterable", "iterator"), x => isIterable(x) || isNextable(x) || isDoddle(x) || isArrayLike(x)); const expectAsyncIterableOrIterator = expectation(anOrStructure(["(async)", "iterable"], "iterator"), x => isIterable(x) || isAsyncIterable(x) || isNextable(x) || isDoddle(x) || isReadableStream(x) || isArrayLike(x)); const checkFunctionReturn = (thing, context, expectReturn, allowAsync) => { return (f) => { expectFunc(getSubject(thing, context, "be"))(f); return (...args) => { const result = f(...args); const resultChecker = expectReturn(getSubject(["function", thing], context, "return")); if (isThenable(result) && allowAsync) { return result.then(x => { if (isDoddle(x)) { return x.map(resultChecker); } return resultChecker(x); }); } if (isDoddle(result)) { return result.map(resultChecker); } return resultChecker(result); }; }; }; export const forOperator = (operator) => { const context = ["operator", `'${operator}'`]; function getArgThing(name) { return ["argument", `'${name}'`]; } const allowAsync = ["a", "A"].includes(operator[0]); function getArgSubject(name) { return getSubject(getArgThing(name), context, "be"); } function checkValue(name, exp) { return [name, exp(getArgSubject(name))]; } function checkFuncReturn(name, exp) { return [name, checkFunctionReturn(getArgThing(name), context, exp, allowAsync)]; } const simpleEntries = [ checkValue("size", expectPosInt), checkValue("start", expectInt), checkValue("end", expectIntOrInfinity), checkValue("index", expectInt), checkValue("count", expectIntOrInfinity), checkValue("projection", expectFunc), checkValue("action", expectFunc), checkValue("handler", expectFunc), checkValue("separator", expectString), checkValue("descending", expectBool), checkValue("reducer", expectFunc), checkValue("stage", expectStage), checkValue("skipCount", expectPosInt), checkValue("keyProjection", expectFunc), checkFuncReturn("kvpProjection", expectPair), checkFuncReturn("predicate", expectBool), checkFuncReturn("thrower", expectError), checkValue("ms", expectInt) ]; return Object.fromEntries(simpleEntries); }; const wInput = "input"; const wSeq = "'seq'"; const wAseq = "'aseq'"; export const checkSeqInputValue = (input) => { const context = ["conversion", wSeq]; expectSyncInputValue(getSubject(wInput, context, "be"))(input); if (isFunction(input)) { return checkFunctionReturn(wInput, context, expectSyncIterableOrIterator, false)(input); } return input; }; export const checkASeqInputValue = (input) => { const context = ["conversion", wAseq]; expectAsyncInputValue(getSubject(wInput, context, "be"))(input); if (isFunction(input)) { return checkFunctionReturn(wInput, context, expectAsyncIterableOrIterator, true)(input); } return input; }; export const gotAsyncIteratorInSyncContext = () => { throw new DoddleError([ getSubject(wInput, [wAseq], "be"), iterableOrIterator, ["but got", "an async", "iterator"] ]); }; const __checkers = "__checkers"; const LOADED = Symbol(__checkers); export function loadCheckers(target) { if (LOADED in target) { return target; } for (const key of Object.getOwnPropertyNames(target).filter(x => !x.startsWith("_"))) { const v = target[key]; if (isFunction(v) && !v[__checkers]) { Object.defineProperty(v, __checkers, { value: forOperator(`${getClassName(target)}.${key}`) }); } } return Object.defineProperty(target, LOADED, {}); } export function chk(input) { return input[__checkers]; } export const invalidRecursionError = (parentType) => { return `Child iterable called its own ${parentType} during iteration, which is illegal.`; }; export const reduceOnEmptyError = "Cannot reduce empty sequence with no initial value"; //# sourceMappingURL=error.js.map