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

@neuledge/engine

v0.2.2

Published

Neuledge Engine is a state machine engine for JavaScript and TypeScript that allows you to define predictable data schema and business logic definitions for databases

Downloads

469

Readme

Main features

🌍  Intuitive schema         Define your data models using a simple and intuitive schema language.

🔌  Database agnostic         Seamlessly switch between various database technologies.

🏎️  High performance         Run efficiently on serverless or edge environments.

🔍  Type-safe queries         Validate your queries at compile time, with a simple and powerful query language.

🛡️  State-based modeling         Define different states for the same entity, each with its own set of fields and mutations, allowing for precise and controlled state transitions.

🔄  Automatic query rewriting         Avoid data migrations with automatic query rewriting for each state, simplifying the migration process.

🚦  Controlled mutations         Define explicit mutations for each state, allowing only predefined alterations and maintaining control over data changes during state transitions.

📏  Precise validation         Create unique data types with custom restrictions like minimum and maximum values, regular expressions, and more.

❤️ Sponsored by

If you find Neuledge useful and would like to support its ongoing development and maintenance, please consider sponsoring us. Your sponsorship will help us to continue to improve and evolve this project. Thank you for your support!

Table of contents

👋 Introduction

Neuledge Engine is a powerful language that simplifies data management and enhances data integrity for databases. It enables you to define your data models and business logic in a precise and customizable way. The schema language supports customizable scalar types, providing type-safe data models and ensuring that you always get the data you expect.

With Neuledge, you can create different states for the same entity, each with its own set of fields and mutations. These states are stored and accessed from the same table, with an abstraction layer that defines which fields are needed for each state. For example, you can define a "DraftPost" state with a set of fields and mutations, and then create a "PublishedPost" state that inherits from "DraftPost" and adds more fields and restrictions necessary for published posts.

flowchart LR
  DraftPost -->|publish| PublishedPost
  DraftPost -->|delete| Void
  PublishedPost -->|archive| ArchivedPost

The schema language is identical for relational and non-relational databases, giving you the flexibility to use it with any database of your choice. It allows you to define precise field types, validate data mutations, and enforce business rules across different states. Whether you are working with a small or complex data model, Neuledge makes it easy to manage and maintain your data.

🤔 How it works

Below are a few examples that demonstrate how Neuledge can be utilized in contrast to a conventional approach.

Quick comparisons

Fetching entries from the database:

if (
  user.status === 'ACTIVE' &&
  user.email != null &&
  user.firstName != null
) {
  // handle user login..
  console.info(`Login ${user.firstName}`);
}
// skip null checks thanks to the schema state

if (user.$state === 'ActiveUser') {
  // handle user login..
  console.info(`Login ${user.firstName}`);
}

Validating data mutations:

// implmenet data mutations manually

await db.updateOne({
  find: {
    id: 1234,
    status: 'DRAFT',
    title: { $exists: true },
    content: { $exists: true },
  },
  set: {
    status: 'PUBLISHED',
    publishedAt: new Date(),
  },
});
// use the `publish` mutation defined 
// on the database schema

await db
  .alterUnique(DraftPost)
  .unique({ id: 1234 })
  .publish();

Handling legacy code and migrations:

let username;

if (user.username != null) {
  username = user.username;
} else if (user.migratedUsername != null) {
  username = user.migratedUsername;
} else {
  throw new Error('Username is missing');
}
// both `username` and `migratedUsername`
// are mapped to the same field by the engine
// so you can access them directly

const username = user.username;

Querying legacy code and migrations:

const user = await db.findOne({
  where: [
    {
      username: 'john',
    },
    {
      migratedUsername: 'john',
    },
  ],
});
// the engine will automatically transform
// the query to include both `username` and
// `migratedUsername` in the `where` clause

const user = await db.findUnique(...User).where({
  username: 'john',
});

Schema examples

Unique state for each status:

state RegisteredUser {
  id: Integer = 1
  email: Email = 2
  firstName?: String = 3
  lastName?: String = 4
  createdAt: DateTime = 5
}
state ActiveUser from RegisteredUser {
  firstName: String = 1
  lastName: String = 2
  passwordHash: Buffer = 3
  lastLoginAt: DateTime = 4
}

Precise data mutations by state:

register(
  email: Email,
  firstName?: String,
  lastName?: String,
): RegisteredUser => {
  createdAt: DateTime(),
}
RegisteredUser.activate(
  passwordHash: Buffer
): ActiveUser => {
  firstName: Required(value: this.firstName),
  lastName: Required(value: this.lastName),
  lastLoginAt: DateTime(),
}

Custom data validations:

state Person {
  name: String(normalize: true, trim: true, min: 3, max: 50) = 1
  email: Email(lowercase: true, trim: true, at: "gmail.com") = 2
  profilePicture?: URL(secure: true) = 3
  age: Integer(min: 18, max: 100) = 4
  createdAt: DateTime = 5
}

Seamless data migrations on the fly:

state User from LegacyUser {
  -slug
  @unique username: String = 1
}

# runtime database migration
(LegacyUser): User => {
  username: this.slug,
}
state LegacyUser {
  id: Integer = 1
  email: Email = 2
  slug: String = 3
  createdAt: DateTime = 4
}

(Runtime migrations are partially supported, will be fully supported in the future releases)

🏁 Getting started

⚠️ Beta release

Neuledge is still in beta. Help us improve it by join our community and give us a star ⭐️. If you are interested in using Neuledge in your project, please join our Discord server and we will be happy to help you.

Installation

Install the Neuledge engine and the MongoDB store:

npm install @neuledge/engine @neuledge/mongodb-store --save

Install a development dependency for the CLI:

npm install @neuledge/states-cli --save-dev

Add generate:states script on your package.json:

{
  "scripts": {
    "generate:states": "states --output \"src/states.codegen.ts\" \"states/*.states\""
  }
}

On the next step, run npm run generate:states to generate the states code from your *.states files.

This will generate a src/states.codegen.ts file with all your business logic code. You should add this file to your .gitignore file, as it will be generated automatically.

Define your schema files

Create a states folder and your first users.states file:

state CreatedUser {
  @id(auto: 'increment') id: Integer = 1
  firstName?: String = 2
  lastName?: String = 3
  @unique email: Email = 4
  @index createdAt: DateTime = 6
}

state ActiveUser from CreatedUser {
  firstName: String = 1
  lastName: String = 2
  passwordHash?: Buffer = 3
  updatedAt: DateTime = 4
}

state SuspendedUser from ActiveUser {
  suspendedAt: DateTime = 1
}

state DeletedUser from CreatedUser {
  -firstName
  -lastName
  -email

  deletedAt: DateTime = 1
}

create(
  firstName: String,
  lastName: String,
  email: Email,
): CreatedUser => {
  createdAt: DateTime(),
}

CreatedUser.activate(
  firstName: String,
  lastName: String,
  passwordHash?: Buffer,
): ActiveUser => {
  updatedAt: DateTime(),
}

create(
  firstName: String,
  lastName: String,
  email: Email,
  passwordHash?: Buffer,
): ActiveUser => {
  createdAt: DateTime(),
  updatedAt: DateTime(),
}

ActiveUser.update(
  firstName: String,
  lastName: String,
  email: Email,
  passwordHash?: Buffer,
): ActiveUser => {
  updatedAt: DateTime(),
}

ActiveUser.suspend(): SuspendedUser => {
  suspendedAt: DateTime(),
}

SuspendedUser.activate(): ActiveUser => {
  updatedAt: DateTime(),
}

either User = ActiveUser | SuspendedUser

User.delete(): DeletedUser => {
  deletedAt: DateTime(),
}

CreatedUser.delete(): Void

Initialize your database

import { NeuledgeEngine } from '@neuledge/engine';
import { MongoDBStore } from '@neuledge/mongodb-store';

// import your generated code for the engine to use before initializing the engine
import `./states.codegen`;

// use the MongoDBStore to connect to your database
const store = new MongoDBStore({
  url: 'mongodb://localhost:27017',
  name: 'example',
});

// initialize the engine with the store and syncing the database schema
const engine = new NeuledgeEngine({
  store,
});

Query the database

import { CreatedUser, User } from './states.codegen';

// create a new user
const createdUser = await engine
  .initOne(CreatedUser)
  .create({
    firstName: 'John',
    lastName: 'Doe',
    email: '[email protected]',
  })
  .select();

// activate the user
const activeUser = await engine
  .alterUniqueOrThrow(CreatedUser)
  .activate()
  .unique({ id: createdUser.id })
  .select();

// update the user information
const updatedUser = await engine
  .alterUniqueOrThrow(ActiveUser)
  .update({
    firstName: 'Jane',
    lastName: 'Doe',
    email: '[email protected]',
    passwordHash: Buffer.from('password'),
  })
  .unique({ id: activeUser.id })
  .select();

// suspend the user
const suspendedUser = await engine
  .alterUniqueOrThrow(ActiveUser)
  .suspend()
  .unique({ id: updatedUser.id })
  .select();

// list active and suspended users
const users = await engine.findMany(...User).limit(10);

📚 Documentation & examples

For more information, please visit docs.

For fully functional code examples, please check the examples folder.

🤝 Join the community

To get involved in the Neuledge community:

📜 License

Neuledge is Apache 2.0 licensed.