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

@simonbackx/simple-encoding

v2.19.0

Published

This small library has no dependencies and adds interfaces and classes that you can use to encode and decode your own classes to a JSON compatible structure. This enables you to build powerful API's and storage that can automigrate to newer versions on th

Downloads

941

Readme

Simple encoding

This small library has no dependencies and adds interfaces and classes that you can use to encode and decode your own classes to a JSON compatible structure. This enables you to build powerful API's and storage that can automigrate to newer versions on the fly.

Encoding and decoding can get customized, and that's the whole point of this library. You'll need to write some minor boilerplate code, but that will allow you to customize and work fast. You can also use the decorators and write almost no boilerplate code.

Installation

Yarn

yarn add @simonbackx/simple-encoding

NPM

npm install @simonbackx/simple-encoding

Features

  • Great errors out of the box
  • Customize as you wish:
    • By not using automatic generation of the encoded format, you can easily let the field names and types differ between the encoded version and your actual instances.
    • You can add your own decoders/validators for custom fields (e.g. phone number)
  • Minor and readable boilerplate code

Todo

  • [x] Add context to Data: Allows for versioning (e.g. you can store a verion in the context and let the encoding/decoding differ between each version). In fact you could add this yourself by implementing Data.
  • [ ] Add some more basic types
  • [ ] Documentation on patchable types (allows you to build powerful patch behaviour for API's)

Usage example

Decorators

The shortest version you can have, with experimental TypeScript decorators.

import { Encodeable, StringDecoder, NumberDecoder, field } from "@simonbackx/simple-encoding";

class MyClass extends AutoEncoder {
    @field({ decoder: NumberDecoder })
    id: number;

    @field({ decoder: StringDecoder })
    name: string;

    @field({ decoder: MyClass, optional: true })
    other?: MyClass;

    writeName() {
        console.log(this.name);
    }
}

const test = MyClass.create({ id: 123, name: "Hello world" });
const test2 = MyClass.create({ id: 123, name: "Hello world", other: test });

// Simulate a network or file storage by converting to JSON
// You can also convert it to a different storage type
const json = JSON.stringify(test2.encode({ version: 1 }));

// ... Store or send the JSON somewhere

// Decode from JSON
const plainObject = JSON.parse(json);
const data = new ObjectData(plainObject, { version: 1 });
const instance = MyClass.decode(data);
// Decode throws if fields are missing or types are not okay.
// You can use the errors to provide good errors in you API's that help developers to
// point out the missing fields or types.

// Now all methods are available again
instance.writeName();

Now, say we want to change the type of id to a string and we want to have an extra field called createdAt. We can use versioning for this:

class MyClass extends AutoEncoder {
    @field({ decoder: NumberDecoder })
    @field({ decoder: StringDecoder, version: 2, upgrade: (old: number) => old.toString(), downgrade: (n: string) => parseInt(n) })
    id: number;

    @field({ decoder: StringDecoder }) // not specifying the version equals version 0
    name: string;

    @field({ decoder: MyClass, optional: true })
    other?: MyClass;

    @field({ decoder: DateDecoder, version: 2, upgrade: () => new Date() })
    createdAt: Date;

    writeName() {
        console.log(this.name);
    }
}

You must update your 'create' methods everywhere, but the encoding and decoding in the older versions keep working:

// This code ran before version 2 (with the current changes this would throw an error since createdAt is missing)
const myClass = MyClass.create({ id: 123, name: "Hello world" });
const json = JSON.stringify(myClass.encode({ version: 1 }));

// The JSON is stored here for some time on a storage medium (e.g. in a file), during which we release version 2 and need to decode the data again.

// This code ran after making the version changes
const plainObject = JSON.parse(json);
const data = new ObjectData(plainObject, { version: 1 }); // note: createdAt is determined via the upgrade method = the current date
const instance = MyClass.decode(data);

// We can use our latest methods and functions that require createdAt

The versions in encode an decode should always match. You'll need to store the version depending on the usage. E.g. in an API you'll need to send the version in the URL or in a separate header. In your database you can add an extra field 'version' to save the latest version you used for encoding, same for localstorage, disk storage...

The versioning system allows your API to update the backend before updating the frontend, because the newer version will always be able to communicate with older versions.

const myClass = MyClass.create({ id: "123", name: "This example keeps all data", createdAt: new Date() });
const json = JSON.stringify(myClass.encode({ version: 2 }));
const plainObject = JSON.parse(json);
const data = new ObjectData(plainObject, { version: 2 });
const instance = MyClass.decode(data);

Vanilla (without decorators)

This version makes it possible to do more customization, at the cost of more boilerplate code. This is very usefull when building custom encoders and decoders that have complex behaviour.

import { Encodeable, Data, ObjectData } from "@simonbackx/simple-encoding";

class MyClass implements Encodeable {
    id: number;
    name: string;
    other?: MyClass;

    constructor(data: { id: number; name: string; other?: MyClass }) {
        this.id = data.id;
        this.name = data.name;
        this.other = data?.other;
    }

    writeName() {
        console.log(this.name);
    }

    /**
     * Note that the static MyClass implements Decoder.
     * -> static inheritance is not supported in TypeScript
     */
    static decode(data: Data): MyClass {
        // You can read data.context.version to change decoding behaviour depending on the version
        return new MyClass({
            // 'walk' the data you receive
            // data.field('fieldname') returns a new Data and throws if the field does not exist
            // The returned Data object can get decoded with a Decoder using .decode(Decoder)
            // or with a convenience getter: number, string
            id: data.field("id").number,
            name: data.field("name").string,
            other: data.optionalField("other")?.decode(MyClass),
        });
    }

    encode(context) {
        // You can read context.version to change encoding behaviour depending on the version.
        return {
            id: this.id,
            name: this.name,
            other: this.other?.encode(context),
        };
    }
}

const test = new MyClass({ id: 123, name: "Hello world" });
const test2 = new MyClass({ id: 123, name: "Hello world", other: test });

// Simulate a network or file storage by converting to JSON
// You can also convert it to a different storage type
const json = JSON.stringify(test2.encode({ version: 1 }));

// ... Store or send the JSON somewhere

// Decode from JSON
const plainObject = JSON.parse(json);
const data = new ObjectData(plainObject, { version: 1 });
const instance = MyClass.decode(data);
// Decode throws if fields are missing or types are not okay.
// You can use the errors to provide good errors in you API's that help developers to
// point out the missing fields or types.

// Now all methods are available again
instance.writeName();