snapshot of https://github.com/mountain-loop/yaak.git (main, 2026-09-23; blobs > 131072B omitted)
Some checks failed
Lint and Test / Lint/Test (push) Has been cancelled
Generate Sponsors README / deploy (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-23 17:39:41 +05:00
commit 906fb231cc
1544 changed files with 164131 additions and 0 deletions

View File

@@ -0,0 +1,76 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { debounce } from "./debounce";
describe("debounce", () => {
beforeEach(() => vi.useFakeTimers());
afterEach(() => vi.useRealTimers());
it("calls once with the latest args after the delay", () => {
const fn = vi.fn();
const d = debounce(fn, 100);
d("a");
d("b");
d("c");
expect(fn).not.toHaveBeenCalled();
vi.advanceTimersByTime(100);
expect(fn).toHaveBeenCalledExactlyOnceWith("c");
});
it("flush invokes a pending call immediately", () => {
const fn = vi.fn();
const d = debounce(fn, 100);
d("a");
d("b");
d.flush();
expect(fn).toHaveBeenCalledExactlyOnceWith("b");
// The scheduled call was cancelled, so waiting out the delay adds nothing
vi.advanceTimersByTime(100);
expect(fn).toHaveBeenCalledOnce();
});
it("flush is a no-op with nothing pending", () => {
const fn = vi.fn();
const d = debounce(fn, 100);
d.flush();
expect(fn).not.toHaveBeenCalled();
d("a");
vi.advanceTimersByTime(100);
d.flush();
expect(fn).toHaveBeenCalledExactlyOnceWith("a");
});
it("cancel drops the pending call and its args", () => {
const fn = vi.fn();
const d = debounce(fn, 100);
d("a");
d.cancel();
vi.advanceTimersByTime(100);
expect(fn).not.toHaveBeenCalled();
// A cancelled call must not leak its args into the next one
d.flush();
expect(fn).not.toHaveBeenCalled();
});
it("starts a fresh delay after firing", () => {
const fn = vi.fn();
const d = debounce(fn, 100);
d("a");
vi.advanceTimersByTime(100);
d("b");
vi.advanceTimersByTime(99);
expect(fn).toHaveBeenCalledOnce();
vi.advanceTimersByTime(1);
expect(fn).toHaveBeenCalledTimes(2);
expect(fn).toHaveBeenLastCalledWith("b");
});
});

View File

@@ -0,0 +1,32 @@
// oxlint-disable-next-line no-explicit-any
export function debounce(fn: (...args: any[]) => void, delay = 500) {
let timer: ReturnType<typeof setTimeout> | null = null;
// oxlint-disable-next-line no-explicit-any
let lastArgs: any[] | null = null;
// oxlint-disable-next-line no-explicit-any
const result = (...args: any[]) => {
lastArgs = args;
if (timer != null) clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
const argsToUse = lastArgs ?? [];
lastArgs = null;
fn(...argsToUse);
}, delay);
};
result.cancel = () => {
if (timer != null) clearTimeout(timer);
timer = null;
lastArgs = null;
};
// Invoke a pending call immediately instead of waiting out the delay
result.flush = () => {
if (timer == null) return;
clearTimeout(timer);
timer = null;
const argsToUse = lastArgs ?? [];
lastArgs = null;
fn(...argsToUse);
};
return result;
}

View File

@@ -0,0 +1,36 @@
export function eagerDebounceAsync(fn: () => Promise<void>, delay: number) {
let timer: ReturnType<typeof setTimeout> | null = null;
let inFlight: Promise<void> | null = null;
let runAfterInFlight = false;
const run = async () => {
if (inFlight != null) {
runAfterInFlight = true;
return;
}
runAfterInFlight = false;
inFlight = fn()
.catch(console.error)
.finally(() => {
inFlight = null;
if (runAfterInFlight && timer == null) {
void run();
}
});
await inFlight;
};
return () => {
if (timer == null) {
void run();
} else {
clearTimeout(timer);
}
timer = setTimeout(() => {
timer = null;
void run();
}, delay);
};
}

View File

@@ -0,0 +1,20 @@
export function formatSize(bytes: number): string {
let num: number;
let unit: string;
if (bytes > 1000 * 1000 * 1000) {
num = bytes / 1000 / 1000 / 1000;
unit = "GB";
} else if (bytes > 1000 * 1000) {
num = bytes / 1000 / 1000;
unit = "MB";
} else if (bytes > 1000) {
num = bytes / 1000;
unit = "KB";
} else {
num = bytes;
unit = "B";
}
return `${Math.round(num * 10) / 10} ${unit}`;
}

View File

@@ -0,0 +1,4 @@
export * from "./debounce";
export * from "./eagerDebounceAsync";
export * from "./formatSize";
export * from "./templateFunction";

View File

@@ -0,0 +1,39 @@
/** Literal steps into a JSON response, distinct from JSONPath operators. */
export type JsonPathSegment =
| { readonly kind: "key"; readonly key: string }
| { readonly kind: "index"; readonly index: number };
const PATH_SEGMENT = /^(?:\.([A-Za-z_$][A-Za-z0-9_$]*)|\[(0|[1-9]\d*)\]|\[("(?:[^"\\]|\\.)*")\])/;
/**
* Parse a single location with JSON-quoted keys (`$.a[0]["b.c"]`). Other
* JSONPath expressions belong to the filter engine, not to the breadcrumb trail.
*/
export function jsonPathToSegments(path: string): JsonPathSegment[] | null {
let rest = path.trim();
if (!rest.startsWith("$")) return null;
rest = rest.slice(1);
const segments: JsonPathSegment[] = [];
while (rest.length > 0) {
const match = PATH_SEGMENT.exec(rest);
if (match == null) return null;
const [whole, bareKey, index, quotedKey] = match;
if (bareKey != null) {
if (bareKey === "$") return null; // The library's root operator, not a literal key.
segments.push({ kind: "key", key: bareKey });
} else if (index != null) {
const value = Number(index);
if (!Number.isSafeInteger(value)) return null;
segments.push({ kind: "index", index: value });
} else if (quotedKey != null) {
try {
segments.push({ kind: "key", key: JSON.parse(quotedKey) as string });
} catch {
return null;
}
}
rest = rest.slice(whole.length);
}
return segments;
}

View File

@@ -0,0 +1,6 @@
{
"name": "@yaakapp-internal/lib",
"version": "1.0.0",
"private": true,
"main": "index.ts"
}

View File

@@ -0,0 +1,49 @@
import type {
CallTemplateFunctionArgs,
JsonPrimitive,
TemplateFunctionArg,
} from "@yaakapp-internal/plugins";
export function validateTemplateFunctionArgs(
fnName: string,
args: TemplateFunctionArg[],
values: CallTemplateFunctionArgs["values"],
): string | null {
for (const arg of args) {
if ("inputs" in arg && arg.inputs) {
// Recurse down
const err = validateTemplateFunctionArgs(fnName, arg.inputs, values);
if (err) return err;
}
if (!("name" in arg)) continue;
if (arg.optional) continue;
if (arg.defaultValue != null) continue;
if (arg.hidden) continue;
if (values[arg.name] != null) continue;
return `Missing required argument "${arg.label || arg.name}" for template function ${fnName}()`;
}
return null;
}
/** Recursively apply form input defaults to a set of values */
export function applyFormInputDefaults(
inputs: TemplateFunctionArg[],
values: { [p: string]: JsonPrimitive | undefined },
) {
let newValues: { [p: string]: JsonPrimitive | undefined } = { ...values };
for (const input of inputs) {
if ("defaultValue" in input && values[input.name] === undefined) {
newValues[input.name] = input.defaultValue;
}
if (input.type === "checkbox" && values[input.name] === undefined) {
newValues[input.name] = false;
}
// Recurse down to all child inputs
if ("inputs" in input) {
newValues = applyFormInputDefaults(input.inputs ?? [], newValues);
}
}
return newValues;
}