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

sirano

v0.1.4

Published

A TypeScript database built on top of LevelDB - a fast and efficient C++ database.

Downloads

4

Readme

💥 Sirano

star this repo fork this repo License Version Downloads

A TypeScript embedded database built on top of LevelDB - a fast and efficient C++ database.

❗ Attention

I am still working on this project, and many things might change in future.

💾 Installation

Download via NPM

cd my-awesome-project
npm install --save sirano

❓ Why in the world do I need another database?

During my experience writing backend services, I often cannot find a database that is both fast and easy to use.

Sirano is fully typed and uses TypeScript under the hood to make the development process a blast.

🔨 How do I use it?

📕 Documentation

You can find the full TypeDoc documentation here.

📋 Table of contents

  1. Creating models
  2. Creating Datastore
  3. Pushing objects
  4. Getting objects
  5. Editing objects
  6. Deleting objects
  7. Additional features

Creating models

Sirano uses a concept of Models, that I ~~stole~~ borrowed from various other databases (MongoDB and mongoose) and ORMs (TypeORM).

Let's say we want to create a model called Human, that has a name and an age.

To do that, we simply write a class for the object, and extend the class from Model class.

//Do not forget to extend from Model, passing the class itself as its generic.
export class Human extends Model<Human> {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    super()
    this.name = name
    this.age = age
  }
}

This model will now inherit few methods and fields from Model superclass. This includes the special meta field, that contains the id and created/last edited time as UNIX timestamp.

Creating Datastore

Sirano uses low-level LevelDB database and provides higher-level abstraction for your objects. To start, you have to create a Datastore object.

Now, lets create a Datastore for our Human model. To do that, you create a new Datastore object, passing the name of database first, then the location of it, and then creating a function that returns the type of the model.

//new Datastore<T>(databaseName, databaseLocation, () => Type)
const store = new Datastore<Human>('human', './database', () => Human)

Pushing objects

To push object, you call .push() method on the datastore, then pass an object to push via .item() method.

let human = new Human(`John`, 19);
human = await store.push().item(human).run()
  
// Now our human will contain field called 'meta'.
// {created: number, updated: number, id: string}
console.log(human.meta)

You can also push multiple objects at once using .pushBatched() method. This will increase the performance significantly if the number of objects is over 100.

const operation = await store.pushBatched()
for(let i = 0; i < 100; i++) {
  operation.item(new Human(`Human ${i}`, i))
}
const humans = await operation.run()

Or, you can add an array of objects via .items().

let humans: Human[] = []
for(let i = 0; i < 100; i++) {
  humans.push(new Human(`Human ${i}`, i))
}
humans = await store.pushBatched().items(humans).run()

Getting objects

Now this is the interesting part. To get objects from Sirano datastore, you should call get() method on the datastore.

const operation = store.get()

This will return a new GetOperation object, that you can modify by calling methods like skip(), take(), filter(), etc. To get the results, you can call method result() or run().

Getting all objects

If you simply want to get all the objects, you can call .result() on the operation.

// Returns all humans.
const data: Human[] = await store.get().result()

Skip and take (limit and skip)

If you want to set skip and take parameters, you can call the corresponding methods on the GetOperation object. By default, skip is set to zero, while take is Infinity.

Alternatively, you can call method paginate(), passing the data in form of an object.

// Returns the first 5 humans, sorted by their id.
const data: Human[] = await store.get()
                                 .skip(0)
                                 .take(5)
                                 .result()

// Alternatively, you can use .paginate() instead.
//Those two methods will have the same result.
const data: Human[] = await store.get()
                                 .paginate({skip: 0, take: 5})
                                 .result()

Filtering

Sirano's filtering is very easy to write, unlike other databases or ORMs. Also, all of it is typed, so IntelliSense in, for example, Visual Studio Code will show you autocompletion suggestions.

To filter, you simply call a .filter() method, where you pass a function to call on each object.

// Get all humans with age over 18
const data: Human[] = await store.get()
  .filter((human) => human.age > 18)
  .result()

// Get all humans with name John
const data: Human[] = await store.get()
  .filter((human) => human.name == 'John')
  .result()

// Get humans with name Albert that are over 18
const data: Human[] = await store.get()
  .filter((human) => human.name == 'Albert' && human.age > 18)
  .result()

In the filtering function you can do whatever you want.

Sorting

You can sort the results too. To do that, simply call the .sort() method on GetOperation object, and pass a SortOperator object as an argument.

For example:

// Sort by age in ascending order
const data: Human[] = await store.get()
  .sort(new SortOperator({
    age: {sort: SortDirection.Ascending}
  }))
  .result()

// You can add complex sorting mechanisms, involving priorities, custom comparators, etc.

// For the keys you want to sort you have to describe the sorting process via a ISortingField object. You can set its priority (by default is 0), and the sort field can take either SortDirection enum, or your own comparator.
const data: Human[] = await store.get()
  .sort(new SortOperator({
    age: {priority: 1, sort: SortDirection.Descending},
    name: {sort: (a, b) => myComparatorFunction(a, b)} // Priority is 0 by default
  }))
  .result()

Everything together

The cool part is that you can add everything together. The order of operations is as follows:

  1. Filtering
  2. Paginating
  3. Sorting
const data: Human[] = await store.get()
  .sort(new SortOperator({
    name: {
      sort: SortDirection.Descending,
    }
  }))
  .filter((human) => human.name != 'Andrew')
  .skip(0)
  .take(3)
  .result()

Editing objects

In Sirano, there are two ways to edit an object.

The first way is to create new EditOperation object, then call required functions.

// Set the age of an object with id 'my-id' to 19.
await store.edit().id('my-id').with({age: 19}).run()

// Or, you can do the same with passing an object instead of it
const human = await store.get().first()
await store.edit().item(human).with({age: human.age + 1}).run()

The second way is to get an object via GetOperation, then edit its fields and call .save() on it.

const human = await store.get().first()
human.age++
await human.save()

Deleting objects

Again, there are two ways to delete an object.

You can call .delete() method on the Datastore object and pass either the object or id.

await store.delete().id('my-id').run()

// Or:
const human = await store.get().first()
await store.delete().item(human).run()

Just as with the Push operation, there is also the batched version.

await store.deleteBatched().ids(['a', 'b']).run()

// Or:
// This will delete all items.
const items = await store.get().run()
await store.deleteBatched().items(items).run()

And, you can call the .delete() method on the Model object itself.

const item = await store.get().first()
await item.delete()

Of course, if you need to delete large amounts of data, using batched version will be faster.

Additional features

Coming soon: hooks, server, etc.

📧 Contact me

E-Mail: [email protected]