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

ts-klass

v0.5.1

Published

Type-strong klasses

Downloads

2

Readme

ts-klass

We know what you want.

We know your colleagues don't like your pesky classes.

We know how everyone likes this shiny "functional programming" thing.

We know they get mad just seeing one new in the code base.

We know how many existing prior art out there don't even have typings (last publish: 9 years ago, what do you expect?), and we know that you and your colleagues, as fashionable people of the new age, like to keep your code strongly typed.

ts-klass does this one specific thing: providing a DSL that's both functional (what does that even mean? Well, without the class or new keywords, obviously) and strongly-typed.

How to use

You first create a klass with the klass function:

import klass from "ts-klass";

const Animal = klass({
  makeSound() {
    console.log(this.sound);
  },
});

You can then craft instances from it, as you expect:

const dog = Animal({ sound: "woof" });
dog.makeSound();

Even better, if you like how well new Animal() reads, we offer you this API called nеw.

import { nеw } from "ts-klass";

const dog = nеw(Animal)({ sound: "woof" });
dog.makeSound();

Take great care. That's not a normal e but a Cyrillic е, because (by decreasing order of importance):

  1. We have agreed how new is reminiscent of the disgusting "OO" paradigm.
  2. No-one will ever discover this if they search for new.
  3. new is a keyword in JS and cannot be used as function names.

Of course, you may need to turn off your editor's highlighting for suspicious characters. If you find nеw hard to type, maybe it's time to install a Cyrillic input method.

Notably, you can't new a klass, because we don't like new and you may get hunted down by your colleagues.

const dog = new Animal({ sound: "woof" }); // Throws error

Using nеw offers more security than calling the klass constructor directly, because it will first do a branded check to make sure Animal is a proper klass instead of any random function.

Explicit constructors

By default, the constructor returned from klass, when being called, will merge its first argument with the constructed instance. You can also provide a custom constructor.

const Animal = klass({
  constructor(sound) {
    this.sound = sound;
  },
  makeSound() {
    return this.sound;
  },
});
const cat = Animal("meow");
cat.makeSound();

Static members

You can have static members by... simply adding static before the klass declaration.

const Animal = klass({
  "static greet"() {
    console.log("Hello");
  },
});
Animal.greet();

Static methods will have this pointing to the klass body instead of the klass instance, as you would expect.

const Animal = klass({
  "static greet"() {
    console.log(this.name);
  },
  "static name": 1,
});
Animal.greet();

Extending klasses

You can use klass.extends() to create a derived klass.

const Entity = klass({
  x: 1,
  y: 2,
});
const Animal = klass.extends(Entity)({
  location() {
    return [this.x, this.y];
  },
});
const dog = Animal();
console.log(dog.location());

Named klasses can have a super klass as well.

const Animal = klass.extends(Entity)({
  location() {
    return [this.x, this.y];
  },
});

The argument of extends must be a klass constructor.

super.constructor

The semantics of super are roughly the same as in ES classes.

const Entity = klass({
  greet() {
    console.log("Hello");
  },
});

const Animal = klass.extends(Entity)({
  greet() {
    super.greet();
  },
});

Animal().greet(); // Logs "Hello"

In constructors, you also need to call super.constructor() to request the base klass to modify this. Note that we have to use super.constructor() instead of super(), because the latter is not valid in an object literal.

const Entity = klass({
  constructor() {
    this.a = 1;
  },
});

const Animal = klass.extends(Entity)({
  constructor() {
    super.constructor();
    this.b = this.a + 1;
  },
});

console.log(Animal()); // Logs { a: 1, b: 2 }

As you would expect, you cannot access this before calling super.constructor.

const Animal = klass.extends(Entity)({
  constructor() {
    this.b = this.a + 1; // Throws error
    super.constructor();
  },
});

Klass name

Unfortunately, because klass is ultimately a normal ECMAScript function, there's no great way for us to automatically bind a klass' name based on what it's assigned to. If a klass' name is important to you, you can explicitly bind a name.

const Animal = klass("Animal")({
  makeSound() {
    console.log(this.sound);
  },
});

const dog = Animal();
// Logs "A dog is an Animal."
console.log(`A dog is an ${dog.constructor.name}.`);

This can only be done once. After a klass has already been bound to a name, you can't overwrite its name by calling the constructor again. You can't assign it either—following ECMAScript semantics.

const animalKlassCtor = klass("Animal");

const Animal = animalKlassCtor("Dog")({
  // Won't work; throws error ^^^^^^^
  makeSound() {
    console.log(this.sound);
  },
});

Accessors

You can use accessors in the klass body, and they behave as you would expect.

const Animal = klass({
  a: 1,
  get b() {
    return this.a;
  },
  "static c": 1,
  get "static d"() {
    return this.c;
  },
});

console.log(Animal().b);
console.log(Animal.d);

Branded check

A klass is not an ECMAScript class (because everyone hates it). When you use klass.extends(SomeKlass) or nеw(SomeKlass), SomeKlass must be a klass constructed from the klass() function. You can check if something is a klass (and therefore can be extended or nеw'ed) with isKlass(SomeKlass).

import { isKlass } from "ts-klass";

const RealKlass = klass({});
isKlass(RealKlass); // true
const NotKlass = class {};
isKlass(NotKlass); // false

You can also use instanceof to do branded checks.

RealKlass instanceof klass; // true

Terminology

A klass is what you regard in normal ECMAScript as "class". For example, klass({ foo: 1 }) creates a klass just as class { foo = 1 } creates a class. Because klasses are directly called instead of new'ed (they can be optionally nеw'ed, though), "klass constructor" and "klass" are the same thing.

The klass() function itself is called the klass creator. Its equivalent in ECMAScript is the class keyword—you have to simultaneously provide a body, a klass name, and other metadata like extends in order to properly declare a klass.

When you write klass("name"), the return value is a new klass creator. It's called a name-bound klass creator because klasses instantiated from this creator will have names.

FAQ

Why does using this module result in a runtime error?

Although this sounds like an idea from the age of dinosaurs, this module actually uses the latest JS features. For example, Object.hasOwn is only available in Node v16.10+. If you are using it in browser, you almost always want to polyfill certain APIs.

Also, this module is literally a module: it uses ECMAScript modules (ESM) instead of CommonJS (CJS) ones. You need to import it with import klass from "ts-klass" instead of const klass = require("klass").

Can I use this in production?

If I haven't made it clear enough—please don't. A klass has much worse performance than a native class while offering all the semantics and paradigms that classes do offer. If your team wants to enforce functional programming style, please do realize that composition is a fundamentally different approach than inheritance, which klasses are built upon.

Still, this module has been fully tested and follows ECMAScript semantics (where applicable) to the best of our knowledge, so it should not be dangerous to use, per se.

TODOs

This project is still in its early infancy.

  1. Private methods/fields
  2. Interfaces
  3. Abstract klasses