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 🙏

© 2024 – Pkg Stats / Ryan Hefner

cahir

v0.0.6

Published

flexible interface for method chaining using Proxy and tagged template literals

Downloads

27

Readme

website DOI npm badge

Cahir is a function proxy that allows you interchangibly use tagged templates and method calls. You can use it as an imperative framework with declerative bits. You define the routines and you define the shortcuts.

The famous TODO wars:

Cahir is an imperative library, but still if you are wondering, this is how it works:

cahir-todo

The characters you use for method calls are completely customizible:

__init__: Cahir.tagify({
        strTransform: str => str
            .trim()
            .replace(/^\/>\s*/,"")
            .replace(/^\|>/, "pipe")
            ...
            .replace(/^👊/gi, "runtime")
            .replace(/^👈/gi, "appendTo")

Since Ch is a Proxy, you get to define how it reacts to keys that it does not have:

ch.div //logs a div DOM object
ch.li //logs a lig object

Can even do:

ch[`li{
    "attr":[["data-x", ${15}], ["data-y", ${0}]],
    "prop":[["a",3],["b",2],["innerHTML", ${
        `"<span>Hello World!</span>"`
    }]]
}`] //logs a li with data-x, data-y attributes with span as  child

What are the advantages?:

  • Create reusable tagged templates via ch.pickle
  • Allow arbitrary method names to be intercepted
  • Use tagged templates to their full potential by allowing marking/spreading values like namespaced variables
  • Interchange between tagged templates and method calls:
`method1 ${arg1}`.method2(arg2)`method3 arg3 ...${rest}`
  • Create shortcuts to your methods and use them as operators like ->, |>, +-> in your templates. You can use any character as you want

Browser/Installation

include the base script and your custom method collections (or the ones included in the collections folder of the repo):

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cahir.0.0.6.evergreen.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/collections/DOM/ch.js"></script>

Node/Bun

npm i cahir
pnpm add cahir

Example

//prepare your function
const ch = new Cahir({
    __init__: Cahir.tagify({
        strTransform: str => str
            .trim()
            .replace(/^\+\s*/,"add")
            .replace(/^x\s*/, "multiply")
            .replace(/^\|>/, "pipe")
    })(function (...args) {
        if (args.length <= 1) {
            this.currentNumber = args[0];
            return this;
        }
        return this[args[0]](...args.slice(1))
    }),
    add: function(a) {
        return this(this.currentNumber + a)
    },
    multiply: function(a) {
        return this(this.currentNumber * a)
    },
    pipe: function(command, ...args) {
        this[command].apply(this, [...args]);
        return this;
    }
})

//usage
ch(1)`
    |> add two:${2}
    x ${({values}) => values.two}
    + ${-10}
`

Logging ch.curentNumber will result in -4. You can save the template and run it later with other parameters:

const pickle = ch.pickle`
    |> add two:${2}
    x ${({values}) => values.two}
    + ${-10}
`

ch(5)(pickle); //ch2.currentNumber === 4
ch(7)(pickle); //ch2.currentNumber === 8

Usage

  1. Start with a base function, this will be your primary method. Below function sets an arbitrary property selected on itself if given a single argument, otherwise do a method call:
function base (...args) {
    if (args.length <= 1) {
        this.selected = args[0];
        return this;
    }
    return this[args[0]](...args.slice(1))
}
  1. Use the static tagify property on Cahir to convert the function for dual use (regular method calls + tagged templates)
const 
    tagger = Cahir.tagify(),
    taggified = tagger(base);
  1. Provide the resulting function inside a configuration object with __init__ key to Cahir
const ch = new Cahir({
    __init__: taggified,
    method1: function(){...}
    method2: function(){...}
    ...
})
  1. Now you can interchange between tagged templates and normal method calls:
ch(some_var).method1()`
    method1 ${arg1}
    method2 string-arg1 ${arg2}
`method2(arg1, arg2)(some_other_var)`
...
`
  1. You can use spread sytax within tagged templates:
ch`
    method ...${some_array}}
    ...
`
  1. You can mark values within literals to be used later:
ch`
    method my_field:${some_variable}
    method2 string-arg1 ${({values}) => /*do something with values['my_field']*/}
`

Values in literals can be anything, with a special case for functions. If you pass a function object, it will be given the arguments:

{
    thisArg, // Proxy: the proxy `ch` object
    self, //Any: current value in literal
    index, //Number: current index in literal in order of appereance
    values, //Array: all the values in the literal
    strings, //Array: all the strings in the liteal
    stringsTransformed //Array: all the strings after transformations applied
}
  1. If you want to pass a function to a method, use a function that returns a function:
ch(...)`
    method1 ${({thisArg, values, ...}) => () => /*this function is passed to method1*/}
`

If you want to label a function, use an object:

ch`
some_method some_label: ${await import("/path/to/some_script.js")}
`
ch`
method1 comparer:${{ en: new Intl.Collator("en").compare }}
method2 ${({values}) => values.comparer.en(a, b)}
...
`
  1. Cahir.tagify static method accepts optional arguments:
  • delim (string): used for splitting strings to seprate methods and string arguments
  • strTransform (function): transform the literal strings before evaluation during parsing
  • valTransform (function): transform the literal values before evaluation during parsing

Default values are reasonable, using a custom strTransform allows you to define method shortcuts:

const ch = new Cahir({
    __init__: Cahir.tagify({
        strTransform: str => str
            .trim()
            .replace(/^->/gi,"method1")
            .replace(/^=>/,"method2")
            .replace(/^\|>/, "method_pipe")
    })(function (...args) {
        //your function
    }),
    method1: function(...args) {
        ...
    },
    method2: function(...args) {
        ...
    },
    ...

then:

ch`
    -> ${arg1}
    => second:${arg2} |> method1 ...${[arg3, arg4]}
    => ${({values}) => ++values.second}
    ...
`
  1. Cahir accepts __intercepApply__ and __interceptGet__ properties on the configuration object. These 2 functions are called with:
  • the ch proxy as this
  • next to be called with no arguments, if you do NOT want to intercept
  • prop which is the requested property
  • receiver, same as the get handler for Proxies
const ch = new Cahir({
    __init__: taggified,
    method: function(){...}
    __interceptGet__: function (next, prop, receiver) {
        switch (true) {
            case (!(prop in this)):
                return this.method.bind(this, prop)
        }
        next();
    }
})

In above, any method that is not defined on ch will call method with prop. So ch.some_prop_string(arg1) would be the same as:

ch.method("some_prop_string", arg1)

Webcomponents

Inspect app code on card game example where each game card is a webcomponent:

ch`
    <game-card ${{ data: {values, d} }}/>
    +< ${values.cont}`.selected

Above creates a web component <game-card/>, appends it to values.cont and returns the component (via .selected). The string <game-card... is converted to a web component using strTransform at ch:

...
strTransform: str => str
    .trim()
    .replace(/^<((?:[a-z]+-+[a-z]*)+)/,"wc $1")
...

Above calls wc (webcomponent) method on ch, which in turn calls game-card method that is adopted earlier:

 adopt ...${[
    "game-card", 
    (await import(
        "./component-game-card.js"
    )).render
]}

component-game-card passes the user defined data to other downstream functions such as render-card

When creating webcomponents, you can provide several properties

        ch`<game-card ${{
            attrs = [["data-x", "y"], ...],
            styles = [["width", "auto"], ...],
            props = [["x", x], ["y", y], ...],
            data = "Any arbitrary value",
            innerHTML = Function|String,
            select = truthy|falsey
        }}/>`

All these are passed to method game-card or whatever name you assigned during adopting.

wc implementation of ch uses Symbols to detect and call connectedCallback only ONCE. To detect multiple additions/removals from DOM, you can use custom functions or MutationObserver. Or you can define your custom wc.

Credits

Card data for the website was taken from gwent.one.

Contributions

Ch implementations

You can help documenting different Ch implementations under collections using JSDoc syntax

PRs

Reasonable PRs are always welcome:

For Cahir itself:

  • state the bug/feature clearly
  • state what the proposition does
  • submit few test cases

For method collections:

  • state what the collection is aimed at (DOM manipulation, data visualization, calculations etc.)
  • explain method briefly in JSDoc syntax
  • include examples if possible