@on-the-ground/quackquack
Version:
Message-based duck typing system focused on behavioral roles instead of structural types.
135 lines (131 loc) • 4.99 kB
TypeScript
type QuackDSL = string;
/**
* Wraps a function with runtime validation based on a given Quack signature.
*
* This allows you to enforce that the function matches a specific structure
* (both syntactically and semantically) based on the Quack DSL. If the function
* was already annotated via `quackable()`, the signature is checked for compatibility.
*
* - If the function is not annotated, the signature is enforced via `zod` wrapper.
* - If the function is annotated and matches the expected signature, it's either:
* - used as-is (`strict = false`)
* - or rewrapped with validation (`strict = true`)
* - If the function is annotated but does not match, an error is thrown.
*
* @param expected - A Quack DSL string describing the expected function signature.
* @param strict - If `true`, wrap the function with runtime validation even if it already matches.
* @returns A higher-order function that validates or wraps the target function.
*
* @example
* ```ts
* const greet = expectQuack("(name: string) => void")((name) => console.log(name));
* greet("Alice"); // valid
* greet(123); // throws Zod validation error
* ```
*/
declare const expectQuack: (expected: QuackDSL, strict?: boolean) => <Q extends (...args: any[]) => any>(fn: Q) => Q;
/**
* Validates or wraps an entire object to ensure that each method matches a declared Quack signature.
*
* This is effectively a "duck typing contract enforcement" tool. Each method in the object
* is validated against its corresponding expected Quack DSL. You can choose whether to rewrap
* matched functions with runtime validation using the `strict` flag.
*
* @param expected - A mapping of method names to expected Quack DSL signatures.
* @param strict - If `true`, wrap even matching methods with Zod validation.
* @returns A higher-order function that returns a cloned object with validated/wrapped methods.
*
* @example
* ```ts
* const duck = expectDuck({
* greet: "(name: string) => void",
* add: "(x: number, y: number) => number"
* })({
* greet(name) { console.log(name); },
* add(x, y) { return x + y; }
* });
* ```
*/
declare const expectDuck: <D extends { [K in string]: QuackDSL; }>(expected: D, strict?: boolean) => <O extends { [K in keyof D]: (...args: any[]) => any; }>(obj: O) => { [K in keyof D]: O[K]; };
type ParsedType = "number" | "string" | "boolean" | "null" | "undefined" | "dontcare" | "void" | ArrayType | TupleType | PromiseType | FunctionType;
type ArrayType = {
type: "array";
element: ParsedType;
};
type TupleType = {
type: "tuple";
elements: ParsedType[];
};
type PromiseType = {
type: "promise";
inner: ParsedType;
};
type ParameterType = {
optional: boolean;
type: ParsedType;
};
type FunctionType = {
type: "function";
async: boolean;
parameters: ParameterType[];
return: ParsedType;
};
type QuackAst = FunctionType;
/**
* Parses a string representation of a function signature into a QuackAst (intermediate representation).
*
* @param fnSig - A string in the format of a function signature, e.g. "(x: number, y: string) => boolean"
* @returns A QuackAst object representing the parsed function signature.
*/
declare function quackAstOf(fnSig: string): QuackAst;
/**
* Retrieves the QuackAst metadata from a previously marked function.
*
* @param fn - A function which may have been decorated or marked with `quackable`.
* @returns The QuackAst if present, otherwise undefined.
*/
declare function quackAstFrom(fn: Function): QuackAst | null;
/**
* Checks if a function has been marked as quackable (i.e., has QuackAst metadata).
*
* @param fn - A function to check.
* @returns True if the function has a QuackAst attached, false otherwise.
*/
declare function isQuackable(fn: Function): boolean;
/**
* Marks a function or class method with a quackable signature.
*
* This allows later validators (like `expectQuack`) to verify that
* the function is intended to match a specific signature contract at runtime.
*
* ### Usage (Function)
* ```ts
* const add = quackable("(x: number, y: number) => number")(
* (x, y) => x + y
* );
* ```
*
* ### Usage (Class Method - Legacy Decorator)
* ```ts
* class MyClass {
* @quackable("(name: string) => void")
* greet(name: string) { console.log(name); }
* }
* ```
*
* ### Usage (Class Method - ECMA 2022 Decorator)
* ```ts
* class MyClass {
* greet = quackable("(name: string) => void")(function(name: string) {
* console.log(name);
* });
* }
* ```
*
* @param sig - The expected function signature in string form.
* @returns A decorator or a function wrapper that marks the target with QuackAst metadata.
*/
declare function quackable<I extends any[], O>(sig: string): (fn: (...args: I) => O) => (...args: I) => O;
declare function quackable(sig: string): MethodDecorator;
export { expectDuck, expectQuack, isQuackable, quackAstFrom, quackAstOf, quackable };
export type { QuackAst, QuackDSL };