mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-04 08:04:37 +08:00
Higher-order wrappers over `(...args: any) => any` that forward `arguments` with `fn.apply(this, arguments)` and type their options with `Parameters<F>` / `ReturnType<F>`, and their rest-tuple rewrite, did not compile. Generic calls already monomorphize per call site; these pieces now resolve to that site's concrete signature: - Rest parameters typed as fixed tuples (`...args: [A, B]`, `...args: A` bound to a tuple, `...args: Parameters<F>`) flatten to positional parameters, so `(...args: [A, B]) => R` and `(a: A, b: B) => R` share one function type. Trailing optional positions become `T | undefined` slots. - `Parameters<F>`, `ReturnType<F>`, `Awaited<ReturnType<F>>` and the `ReturnType<F> extends PromiseLike<infer U> ? U : …` spelling resolve through F's binding. - A parameterless function expression reading `arguments` lowers against the fixed signature its value is asserted to (or contextually typed as); `arguments` is that signature's tuple. - `f.apply(thisArg, args)` and `f(...args)` over a forwarded tuple call directly, passing `thisArg` as the receiver. A tuple that is only forwarded is never allocated. - `fn.name` on a function parameter folds to the call site's static name; the instantiation key carries it. Also: optional callback fields narrowed at the call (`o.cb ? o.cb(x) : …`) compile, effect-free surplus arguments to fixed-arity function values are dropped, and `arguments.length` no longer counts a `this` parameter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
82 lines
2.7 KiB
TypeScript
82 lines
2.7 KiB
TypeScript
// Variadic generic wrappers around async functions: the wrapper forwards
|
|
// `arguments`, awaits the wrapped promise and reports its settled value
|
|
// through an Awaited<ReturnType<F>> callback; rejections propagate.
|
|
type AnyFn = (...args: any) => any;
|
|
|
|
interface Opts<F extends AnyFn> {
|
|
name?: string;
|
|
tags?: (...args: Parameters<F>) => Record<string, string>;
|
|
onSettled?: (value: Awaited<ReturnType<F>>) => void;
|
|
}
|
|
|
|
function traceAsync<F extends (...args: any) => Promise<any>>(fn: F, opts: Opts<F> = {}): F {
|
|
return async function (this: unknown) {
|
|
const args = arguments as unknown as Parameters<F>;
|
|
const tags = opts.tags ? opts.tags(...args) : {};
|
|
console.log("start", opts.name ?? fn.name, JSON.stringify(tags));
|
|
try {
|
|
const value: Awaited<ReturnType<F>> = await fn.apply(this, arguments as any);
|
|
opts.onSettled?.(value);
|
|
console.log("ok", opts.name ?? fn.name);
|
|
return value;
|
|
} catch (e) {
|
|
console.log("failed", opts.name ?? fn.name, (e as Error).message);
|
|
throw e;
|
|
}
|
|
} as F;
|
|
}
|
|
|
|
// The settled result spelled as a conditional over ReturnType<F>.
|
|
type Settled<F extends AnyFn> = ReturnType<F> extends PromiseLike<infer U> ? U : ReturnType<F>;
|
|
|
|
function summarized<F extends (...args: any) => Promise<any>>(
|
|
fn: F,
|
|
summarize: (value: Settled<F>) => string,
|
|
): F {
|
|
return async function (this: unknown) {
|
|
const value: Settled<F> = await fn.apply(this, arguments as any);
|
|
console.log("summary", summarize(value));
|
|
return value;
|
|
} as F;
|
|
}
|
|
|
|
async function fetchUser(id: number, prefix: string): Promise<string> {
|
|
await Promise.resolve();
|
|
if (id < 0) throw new Error(`bad id ${id}`);
|
|
return `${prefix}${id}`;
|
|
}
|
|
|
|
const getUser = traceAsync(fetchUser, {
|
|
tags: (id) => ({ id: String(id) }),
|
|
onSettled: (v) => console.log("settled", v.toUpperCase()),
|
|
});
|
|
|
|
const sleepless = traceAsync(async (): Promise<number> => 7, { name: "seven" });
|
|
|
|
interface Report {
|
|
ok: boolean;
|
|
items: string[];
|
|
}
|
|
const report = summarized(
|
|
async (count: number): Promise<Report> => ({ ok: count > 0, items: ["a", "b"].slice(0, count) }),
|
|
(r) => `${r.ok}:${r.items.join("+")}`,
|
|
);
|
|
const counted = summarized(async (n: number): Promise<number> => n * 2, (n) => `n=${n + 1}`);
|
|
|
|
async function main(): Promise<void> {
|
|
console.log(await getUser(1, "u-"));
|
|
console.log(await sleepless());
|
|
console.log(JSON.stringify(await report(1)));
|
|
console.log(await counted(4));
|
|
try {
|
|
await getUser(-2, "u-");
|
|
} catch (e) {
|
|
console.log("caught", (e as Error).message);
|
|
}
|
|
const pending = getUser(3, "a-");
|
|
const second = await getUser(4, "b-");
|
|
console.log(second, await pending);
|
|
}
|
|
|
|
main().then(() => console.log("main done"));
|