npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@on-the-ground/quackquack

v0.0.12

Published

Message-based duck typing system focused on behavioral roles instead of structural types.

Readme

QuackQuack


❌ Not That Duck (Seriously)

You might have seen another package called duck.
It’s great — but completely different from what we’re doing here.

Here's a quick comparison:

| Feature | duck | quackquack (@on-the-ground/quack) | | --------------- | -------------------------------------------- | ------------------------------------------------ | | 📦 Purpose | Assertion matcher for tests | Runtime signature validator | | 🧠 Focus | Structural object matching | Behavioral contract matching | | 🔍 Scope | Deep introspection of object contents | Zero introspection – ignores internals | | 🧪 Typical Use | duck.assertThat(obj, matcher) | expectQuack('(string, number) => boolean')(fn) | | ⚠️ Fails If | Object is missing properties | Function signature doesn’t match | | 📘 Mental Model | “Does this object look like this?” | “Can this function play the Role I expect?” |


🎯 Philosophy: Only the Role Matters

quackquack doesn’t care how your function is implemented.
We don’t parse the body.
We don’t read its variables.
We don’t care if it mutates state, opens a socket, or whispers to ducks.

We only care about one thing:

Does it look like it can play the Role?
(As defined by its signature.)

That’s it.

You can define Roles declaratively using our DSL:

const duckFn = expectQuack("(string, number?) => boolean")(fn);

Or check full objects at once:

const duckObj = expectDuck({
  add: "(number, number) => number",
  log: "(string) => void",
})(obj);

Under the hood, this creates a runtime contract
a guarantee that your function (or object) quacks like the Role it's meant to play.


✨ Why Use quackquack?

  • ✅ Works at runtime — no TypeScript required
  • ✅ Zero dependence on internal object structure
  • ✅ Perfect for plugin systems, dynamic registries, adapter validation
  • ✅ Supports strict mode: fail if shape doesn't match exactly
  • ✅ Bridges the gap between duck typing and role declaration

🧬 Minimalism By Design

“We’re not here to judge how you built it —
we just want to know if it can do the job.”

That’s the quackquack way.

No reflection. No object traversal.
Just plain, declarative, behavioral contracts.


🚀 Quick Start

This library helps you declare Roles via a lightweight DSL, and enforce them at runtime or with decorators.

✅ 1. Mark a function as quackable

const greet = quackable("(name: string) => void")((name) => {
  console.log(`Hello, ${name}`);
});

✅ 2. Mark a method with the same decorator

class Greeter {
  @quackable("(name: string) => void")
  greet(name: string) {
    console.log(`Hi, ${name}`);
  }
}

✅ 3. Enforce quack match with expectQuack

const greet = expectQuack("(name: string) => void")(greetFromArgs);

If the function wasn't decorated, it'll be wrapped with validation.
If already decorated but mismatched, it throws.


✅ 4. Enforce a whole object with expectDuck

const role = expectDuck({
  greet: "(name: string) => void",
  add: "(x: number, y: number) => number",
})(obj);

Each method will be verified to match the expected Role signature.


✍️ Quack DSL: Writing Quack Signatures

The Quack DSL supports two styles for writing function signatures:

(name: string, age?: number) => void  // JavaScript-style
(string, number?) => void             // Simplified

Both are valid and compiled to the same internal representation.

Note: The DSL is inspired by JavaScript, but it doesn't aim to replicate JS syntax exactly. Its purpose is to express roles, not real function declarations.


🪶 Ultra-Light: No Overhead, No Interference

Once a function or method is quackable (i.e., decorated with @quackable),
quackquack will not wrap, proxy, or alter it in any way.

When you expectQuack a quackable function,

It simply compares two precompiled QuackAsts:

  • ✅ The expected signature string
  • ✅ The given function’s signature

That’s it.
No call-time parsing. No reflection.
Zero overhead. Near-instant validation.

This makes it ideal for:

  • Performance-sensitive plugin systems
  • Dynamically loaded modules
  • High-frequency contract checks

☁️ And Yes, We Can Expect Non-Quackables Too

Not every function needs to be quackable.
You can still use expectQuack or expectDuck
on plain JavaScript functions or objects.

In that case, quackquack will fall back to runtime introspection,
using tools like zod
to validate the shape of parameters, arity, and behavior.

So even if you forgot (or chose not) to decorate it —
you’re still covered.

Decorated? Instant match by signature → Zero overhead
Undecorated? Smart fallback with runtime validation
You? Worry-free API contract checking 🫶


🎭 Role-First Object Orientation

This project is named after Alan Kay — not because it’s directly affiliated with him, but because we deeply respect his philosophy of message passing between objects. Our intention is to carry that spirit forward.

Ironically, however, to truly honor that spirit, we believe we must break free from the habit of treating everything as an object.

Objects only gain real value when they aren’t created arbitrarily. An object deserves to exist only when it can justify its presence — specifically, when it can fulfill a Role.

In this regard, we depart from Alan Kay's assertion that “everything is an object.” We say instead: “Everything begins with a Role.” A Role arises first, born from a concrete need. Then, and only then, does an object emerge — as a candidate that may fulfill that Role.

Messages are not sent to objects, but to Roles. An object is simply a responder — an implementer of the behavior required by the Role.


🧱 Classes Do Not Represent Roles

We often treat classes as mere “bundles of data and methods.” But starting with data and then slapping on some methods quickly leads to unprincipled, trash-bin objects.

Objects are meant to represent high cohesion. But what defines “right” cohesion has always been ambiguous. Now, we propose a clear answer: the right cohesion comes from the right Role.

A Role is essentially a collection of related functions. For example, the role of a Transaction might include play and rollback. These functions are grouped not because they share data, but because they share responsibility for the same behavior.

In this light, cohesion should be driven not by data, but by behavior and responsibility.


⏳ Avoid Bring-Up First Design

If you design objects before you clearly understand the roles they need to fulfill, you fall into the trap of Bring-Up First Design — an accidental architecture shaped by momentum rather than intention.

The most valuable objects and roles only become clear when you wait for genuine needs to emerge. The longer you delay their introduction, the sharper and more robust they become.


🔍 Class vs Role

| Concept | Role | Class | | ------------ | ------------------------------------ | ------------------------------------- | | Declared by | The side that needs the behavior | The side that produces the object | | Purpose | Specify behavioral expectations | Describe internal structure | | Focus | “Can you handle this message?” | “What are you made of?” | | Prerequisite | Must satisfy external demand | Reflects internal design |

While classes describe objects from the inside out, Roles describe them from the outside in — from the perspective of what the world needs.


🧠 Real OOP Is About Behavior, Not Reflection

Real object-oriented programming doesn't ask:

“What are you made of?”

That's reflection — and it has no place in message-driven design.

Instead, real OOP asks:

“I need someone who can play this role. Can you handle it?”

Everything should be designed around behavior and responsibility, not around static structure.