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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@opaquejs/opaque

v0.20.1

Published

A client-side Javascript ORM

Readme

Opaque JS

Take a look at the documentation for more information.

User stories

  • What is this? -> Introduction
  • This is cool, lets get started! -> Quickstart
  • I don't remember how x is done / I want to do y, is this possible? -> Main Part
  • I want to get a deeper understanding of z / How does z actually works (advanced users or adapter authors) -> Digging deeper / Reference

Introduction

You may know the concept of an ORM from server side frameworks like Laravels with the Eloquent ORM or Adonisjs with the Lucid ORM. Opaquejs tries to bring this experience of a unified way of handling your data to your client side Javascript. Opaquejs let's you rewrite this:

let tasks = undefined;
try {
  const res = await axios.post("http://some.website/graphql", {
    query: "{ tasks(done: false) { title createdAt } }",
  });
  tasks = res.data.data.tasks;
} catch (e) {
  // Handle the error somehow
}
for (const task of tasks) {
  const createdAt = Date.parse(task.createdAt);
  if (createdAt > new Date()) {
    // Do something
  }
}

To this:

import Task from "./models/Task";

const tasks = await Task.query().where("done", false).get();

for (const task of tasks) {
  if (task.isCreatedInFuture()) {
    // Do something
  }
}

Opaquejs is framework independent, you can use it with any frontend framework like vue, react and any backend such as GraphQL, a REST API, or even an SQL Database directly. If no integration exists for your intended use case, you can easily develop your own custom adapter to support any possible data source.

Additionally, Opaquejs is completely written in typescript to get the most out of intellisense autocompletion and error detection. But if you are sticking with traditional "vanilla" javascript, you can totally do this. The only difference will be the lack of decorators in plain javascript.

Quickstart

npm i @opaquejs/opaque

The most minimal setup would be a model stored in-memory:

// .../models/Task.ts
import { InMemoryAdapter, OpaqueModel, attribute } from "@opaquejs/opaque";
import { DateTime } from "luxon";

export class Task extends OpaqueModel {
  static adapter = new InMemoryAdapter();

  @attribute({ primary: true })
  public id: string;

  @attribute()
  public title: string;

  @attribute()
  public done: boolean = false;

  @attribute.dateTime()
  public createdAt: DateTime = DateTime.now();
}

You are now ready to use your Model to store tasks in memory!

import { Task } from "./Task";

const task = await Task.create({ title: "My first Task!" });

task.done = true;
await task.save();

const doneTasks = await Task.query().where("done", true).get();

CRUD Operations

Create

Create a model using the normal constructor approach:

import { Task } from "./Task";

const task = new Task();
task.title = "Buy Milk";
await task.save();

The static create method

There is also a helper method to make this task easier:

import { Task } from "./Task";

const task = await Task.create({ title: "Buy Milk" });

Read

all

Fetch all tasks from your defined adapter.

import { Task } from "./Task";

const allTasks = await Task.all();

find

Fetch only one task with the defined primary key. This returns a single task instead of an array.

import { Task } from "./Task";

const task = await Task.find(12);

first

Fetch only the first task. This returns a single task instead of an array.

import { Task } from "./Task";

const task = await Task.query().first();

using the query builder

Sometimes, you need more control over the fetched records. The query builder is here to help you!

import { DateTime } from "luxon";
import { Task } from "./Task";

const task = await Task.query()
  .where("done", false)
  .orWhere("createdAt", "<", DateTime.local())
  .limit(10)
  .skip(20)
  .get();

Update

Updating is as straight forward as creating:

import { Task } from "./Task";

const task = await Task.first();
task.done = true;
await task.save();

Deleting

Deleting your models also isn't a problem.

import { Task } from "./Task";

const task = await Task.first();
await task.delete();

Connecting to a Real Backend

There are two main approaches when connecting to a backend:

  1. Connecting to an existing backend and using the query language this backend offers
    • You will need to use some kind of translator between the opaque query language and your api query language or implement your own query builder
  2. Developing an endpoint specifically for Opaquejs query language.
    • You can pass the Opaque queries directly to the API and translate those queries on the server side with one of the translators

Adapters

An adapter is a collection of functions that fetches and pushes data to and from a data source. This is used to connect a model to a backend.

For further information about how to use an adapter, please read through the adapter documentation. Most of the time, it will look like this on the client side:

import { OpaqueModel } from "@opaquejs/opaque";
import { SomeAdapter } from "somebackend-opaque-adapter";

export class BaseModel extends OpaqueModel {
  static adapter = new SomeAdapter({ someOption: true });
}

Here is a list of first party adapters:

  • GraphQL

Translators

If you want to integrate Opaquejs deeper in your infrastructure, you can also use one of the translators to parse Opaque Queries on the server side. This lets you translate Opaque Queries to other representations like knex.js queries.

The basic functionality of translators is the following: You give them an Opaque Query, and the translator function returns the query in another "language", like an SQL query.

Here is a list of the first-party translators:

  • Knex.js

Relationships

Belongs To

You can define a belongsTo relationship using the belongsTo Method on a model instance:

import { OpaqueModel } from "@opaquejs/opaque";
import { User } from "./User";

export class Task extends OpaqueModel {
  public user() {
    return this.belongsTo(User);
  }
}

You should then use the relationship using the exec method:

import { Task } from "./Task";

const user = await Task.first().user().exec();

Has Many

import { OpaqueModel } from "@opaquejs/opaque";
import { Task } from "./Task";

export class User extends OpaqueModel {
  public tasks() {
    return this.hasMany(Task);
  }
}

You should then use the relationship using the exec method:

import { User } from "./User";

const tasks = await User.first().user().exec();