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

sans-schema

v1.1.0

Published

A schemaless data normalizer. Flattens data to enable a single source of truth. Allows exceptions through configuration.

Downloads

7

Readme

From pixabay

Sans Schema

Build Status codecov dependencies dependencies Maintainability Contributions

Why- Getting Started - Simplest Use Case - Format - Docs

Contributing - Versioning - Authors

Why?

I wanted a single source of truth, and I wanted it without having to create schemas. You don't need to define every square inch of your data in a library domain specific way (ie. Typescript), instead just flatten and expand your data, nothing more. If you have many to many relationships, this is impossible to do without some configuration, so you will have to define which models are many-to-many relationships. There are many other configurations like aliases, but nothing is required. It's zero-config by nature.

Getting Started

yarn add sans-schema
import { flatten, expandModel, removeModel } from 'sans-schema';

Disclaimer

This library has been used in production, but error handling is dealt with outside the library. If you don't provide the required parameters in the correct format, the code well crash (fail fast).

If you have mission critical code, feel free to wrap this code in exceptional handling. The code doesn't throw any exceptions currently. If anyone wants to contribute and add the throws, please feel free in a PR :)

simplest use-case

import { configureStore } from 'redux-toolkit';
import { mergeModelReducer, flatten, expandModel, removeModel, mergeNormalizedModels } from 'sans-schema';

const store = configureStore({
    companies: mergeModelReducer('companies'),
    users: mergeModelReducer('users'),
});

const data = { id: 1, name: 'Jason', company: { id: 1, city: 'Montreal' } };

const flatData = flatten('users')(data);
const expandedFlatData = expandModel('users', { id: 1 }, flatData);
const flatDataSansCompanyWithId1 = removeModel('companies', data.company, flatData);

// Using the data with Redux
store.dispatch(mergeNormalizeModels(flatData));
const expandedUser = expandModel('users', { id: 1 }, store.getState());

// removing a model reference from all other models. ie. user: { id: 123, company: { id: 1 } } becomes { id: 123, company: null }, and the same for all other models referencing company = { id: 1 }
store.dispatch(mergeNormalizedModels(flatDataSansCompanyWithId1));
// ...then your action to remove the model from it's slice
store.disaptch(RemoveCompany({ id: 1 }));

What's the deal with loadNormalizedData action...

flatten and removeModel return flattened data that should be merged in your reducers. By using a single action for all data changes you end up with a single render.

Format of flattened data:

This is an example result from flatten(modelName, config)(modelInstance); Also see sampleData.js for examples

{
    modelNames: {   // model names must always be plural
        1: {
            // note: id is the only required data for EVERY model
            id: 1,  //the key is the id
            name: 'hello',  //arbitrary primitives
            secondModelNames: {
                id: 3,  //Only the id well be here. expandModel well expand this.
            },
        }
    },
    secondModelNames: { ... },
    thirdModelNames: { ... },
    // Note: modelNames doesn't reference thirdModelNames, yet it's related through this m2m object
    modelNamesXthirdModelNames: {   // This key can be anything you want.
        modelNames: {
            1: {
                id: 1,  //Every model always has an id, even in m2m
                thirdModelNames: [  //m2m only includes reference instances with ids only
                    { id: 2 },
                    { id: 3 },
                    ...
                ],
            }
            ...
        },
        thirdModelNames: {
            2: {
                id: 2,
                modelNames: [
                    { id: 3 },
                    ...
                ],
            },
            ...
        },
    },
    ...
}

Full example with React and Redux

Sans Schema demo [OUTDATED, the library is easier to use now]

... Note: I well eventually make another demo with commit by commit tutorial on using Sans Schema...

Function definitions

flatten(modelName, config?)(data) : function

Flattens hierarchical data to be placed in a data store representing a single source of truth.

  • modelName : string (plural form) - required - Specifies which model the object, or array of objects represents.
  • config : Config Object - optional - Passed to every function, that defines custom relationships between models.
  • data : object - required - Hierarchical data to be flattened returns object - flat single-source data.

expandModel(modelName, model, state, deepness?, config?) : function

Flattens hierarchical data to be placed in a data store representing a single source of truth.

  • modelName : string (plural form) - required - Specifies which model the object, or array of objects represents.
  • model : object - required - The model to be expanded
  • state : object - required - Single source flattened data (ie. store.getState())
  • deepness : integer - optional - The depth the model is expanded
  • config : Config Object - optional - Passed to every function, that defines custom relationships between models. returns object - Hierarchical data

removeModel(modelName, model, state, config?) : function

Searches the state for references of model and nullifies them. This returns the flattened data representing the updated state of models that were changed, to be merged with the original state.

  • modelName : string (plural form) - required - Specifies which model the object, or array of objects represents.
  • model : object - required - The model to be expanded
  • state : object - required - Single source flattened data (ie. store.getState())
  • config : Config Object - optional - Passed to every function, that defines custom relationships between models. returns object - flat single-source data

Also, for Redux mergeModelReducer(modelName), mergeNormalizedModels(Action<{ models }>)

Also, mergeData(oldObj, newObj), which is a generic merging function that uses immer. It probably exists somewhere, and if it doesn't... someone should publish it =P

Config

All keys in the config object are optional

{
    models: [pluralModelName:string],
    oneToOne: {
        subjectModel: [referenceModel:string],
    }
    keyToModel: {
        subjectModel: {
            referenceKey: referenceModelName:string
        }
    },
    manyToMany: {
        m2mModelName: [
            {
                modelName:string: secondModelNameAlias:string
                secondModelName:string: modelNameAlias:string
            }
        ]
    },
};

Note: You can name your manyToMany relationship whatever you want. In our examples and the tests we combine the names of the two models and put a capital X inbetween them.

Contributing

Please read CONTRIBUTING.md for details on helping the project. Basically, make sure the tests pass and try to write nice code. If you're not sure whether you should submit a pull request, just do it, why not? ;)

Versioning

We use SemVer for versioning. For the versions available, see the tags on this repository.

Authors

  • Jason McCarrell - consultant - github
  • Your name here