// qkf/stepRegistry.js function escapeRegex(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function normalizeText(t) { return String(t || "").trim().replace(/\s+/g, " "); } function patternToRegex(pattern) { const parts = String(pattern).split("{string}"); const escaped = parts.map((p) => escapeRegex(normalizeText(p))); const joined = escaped.join("\\s+(.+?)\\s+"); const final = "^" + joined.replace(/\s+/g, "\\s+") + "$"; return new RegExp(final, "i"); } function createStepRegistry(initialCtx = {}) { const defs = []; function register(pattern, fn, meta = {}) { defs.push({ pattern: String(pattern), regex: patternToRegex(pattern), fn, meta, }); } function WHEN(pattern, fn) { register(pattern, fn, { keyword: "WHEN" }); } function AND(pattern, fn) { register(pattern, fn, { keyword: "AND" }); } function THEN(pattern, fn) { register(pattern, fn, { keyword: "THEN" }); } function GIVEN(pattern, fn){ register(pattern, fn, { keyword: "GIVEN" }); } function BUT(pattern, fn) { register(pattern, fn, { keyword: "BUT" }); } async function run(stepText, extraCtx = {}) { const text = normalizeText(stepText); for (const d of defs) { const m = d.regex.exec(text); if (!m) continue; const captures = m.slice(1).map((x) => normalizeText(x)); const ctx = { ...initialCtx, ...extraCtx }; // ✅ Begin step tracking so QKF calls are awaited even if step defs don't `await` if (ctx.qkf && typeof ctx.qkf.__beginStep === "function") ctx.qkf.__beginStep(); try { // Step defs get (...captures, ctx) const res = d.fn.apply(null, [...captures, ctx]); // Await the step function itself (if it's async) await res; // ✅ Then await all QKF actions triggered during the step if (ctx.qkf && typeof ctx.qkf.__endStep === "function") await ctx.qkf.__endStep(); return; } catch (err) { // try to flush pending actions so failures surface cleanly try { if (ctx.qkf && typeof ctx.qkf.__endStep === "function") await ctx.qkf.__endStep(); } catch (_) { // ignore secondary errors } throw err; } } const available = defs.map((d) => `- ${d.pattern}`).join("\n"); throw new Error( `No step definition matched:\n "${text}"\n\nAvailable definitions:\n${available}` ); } return { defs, register, run, WHEN, AND, THEN, GIVEN, BUT, }; } module.exports = { createStepRegistry };