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

@rvoh/dream

v2.2.1

Published

dream orm

Readme

Dream is a typescript-driven, esm-first ORM built on top of kysely. It is built to operate within the Psychic web framework, but can be brought into other projects and used without the encapsulating framework (though this is theoretical, and we do not encourage it at this time). It is actively being developed to support production-grade applications in use within the RVOHealth organization, who has kindly sponsored the continued development of this ORM, as well as the Psychic web framework as a whole.

Documentation: https://psychicframework.com/docs/intro Psychic web framework github: https://github.com/rvohealth/psychic

Features

The dream ORM features:

  • static query building engine
const records = await User.where({ email: 'fred@fred' }).all()
// User[]

const user = await User.where({ email: 'fred@fred' }).first()
const user = await User.where({ email: 'fred@fred' }).last()
// User | null

const user = await User.where({ email: ops.like('%fred@%') })
  .order('id', 'desc')
  .limit(3)
  .all()

// User[]
  • model hooks
    • before create
    • before update
    • before delete
    • before save
    • after create
    • after update
    • after delete
    • after save
// models/composition.ts

class Composition {
  ...
  @deco.BeforeCreate()
  public setDefaultContent() {
    if (!this.content) this.content = 'default content'
  }

  @deco.AfterCreate()
  public conditionallyChangeContentOnCreate() {
    if (this.content === 'change me after create') this.content = 'changed after create'
  }

  @deco.AfterUpdate()
  public conditionallyChangeContentOnUpdate() {
    if (this.content === 'change me after update') this.content = 'changed after update'
  }

  @deco.AfterSave()
  public conditionallyChangeContentOnSave() {
    if (this.content === 'change me after save') this.content = 'changed after save'
  }
  ...
}
  • validations
    • presence
    • length{min, max}
    • contains{string | regex}
export default class User extends Dream {
  ...
  @deco.Validates('contains', '@')
  @deco.Validates('presence')
  public email: string

  @deco.Validates('length', { min: 4, max: 18 })
  public password: string
  ...
}
  • associations

    • belongs to
    • has one
    • has many
    • has one through
    • has many through
    • has one through (nested indefinitely)
    • has many through (nested indefinitely)
// models/user.ts
class User {
  ...
  @deco.HasMany('Composition')
  public compositions: Composition[]

  @deco.HasOne('Composition')
  public mainComposition: Composition

  @deco.HasMany('CompositionAsset', {
    through: 'compositions',
  })
  public compositionAssets: CompositionAsset[]
}

// models/composition.ts
export default class Composition extends Dream {
  ...
  @deco.BelongsTo('User')
  public user: User
}

// models/composition-asset.ts
export default class CompositionAsset extends Dream {
  ...
  @deco.BelongsTo('Composition')
  public composition: Composition
  ...
}

  • scopes

    • named
    • default
  • single table inheritance

class User {
  @deco.Scope()
  public static withFunnyName(query: Query<User>) {
    return query.where({ name: 'Chalupas jr' })
  }

  // this will always fire whenever queries are run against the model
  @deco.Scope({ default: true })
  public static hideDeleted(query: Query<User>) {
    return query.where({ deleted_at: null })
  }
}

User.scope('withFunnyName')
// will only return records with the name "Chalupas jr"

REPL

the repl will open a console, exposing access to the test app models that are built into the framework. this test app is used to run all of our assertions, and is meant to be replaced by an actual app consuming this ORM.

to get into the console, type:

pnpm console

Once inside the repl, you are able to access all the models within the test app. All the classes are automagically imported.

const user = await User.create({ email: 'hello@there', password: 'howyadoin' })
user.email = 'fred@flinstone'
await user.save()

Specs

Running specs is very easy, not much to explain here.

pnpm spec

We are using jest under the hood, and have a plugin enabled called jest-plugin-context, which allows us to branch specs out using the context function in place of describe, like so:

describe('Dream#pluck', () => {
  let user1: User
  let user2: User
  beforeEach(async () => {
    user1 = await User.create({ email: 'fred@frewd', password: 'howyadoin' })
    user2 = await User.create({ email: 'how@yadoin', password: 'howyadoin' })
  })

  it('plucks the specified attributes and returns them as raw data', async () => {
    const records = await User.pluck('id')
    expect(records).toEqual([user1.id, user2.id])
  })

  context('with multiple fields', () => {
    it('should return multi-dimensional array', async () => {
      const records = await User.order('id').pluck('id', 'createdAt')
      expect(records).toEqual([
        [user1.id, user1.created_at],
        [user2.id, user2.created_at],
      ])
    })
  })
})

Similarity matching

Dream provides a similarity searching library out of the box which allows you to implement fuzzy-searching in your app. To use it, first write a migration.

pnpm psy g:migration add_fuzzy_search_to_users

Open the generated migration, create the pg_trgm extension and create a gin index for a field on your model

import { Kysely, sql } from 'kysely'
import { createGinIndex, createExtension } from '@rvoh/dream'

export async function up(db: Kysely<any>): Promise<void> {
  // this only needs to be run once per db
  await createExtension('pg_trgm', db)

  // this is not necessary, but it will dramatically improve the search
  // speed once your db has enough records for it to matter
  await createGinIndex('users', 'name', 'users_name_gin_index', db)
}

export async function down(db: Kysely<any>): Promise<void> {
  await db.schema.dropIndex('users_name_gin_index')
}

Once done, run migrations

NODE_ENV=development pnpm psy db:migrate

Now you can take full advantage of pg_trgm using the dream adapters!

import { ops } from '@rvoh/dream'

// applies a score match of 0.3
const users = await User.where({ name: ops.similarity('steeven hawkins') }).all()

// applies a score match of 0.5
const users = await User.where({ name: ops.wordSimilarity('steeven hawkins') }).all()

// applies a score match of 0.6
const users = await User.where({ name: ops.strictWordSimilarity('steeven hawkins') }).all()

// applies a score match of 0.2
const users = await User.where({ name: ops.similarity('steeven hawkins', { score: 0.2 }) }).all()

// this will also work for plucking, updating, joining, and aggregate functions like min, max and count
const userIds = await User.where({ name: ops.similarity('steeven hawkins') }).pluck('id')
const min = await User.where({ name: ops.similarity('steeven hawkins') }).min('id')
const max = await User.where({ name: ops.similarity('steeven hawkins') }).max('id')
const count = await User.where({ name: ops.similarity('steeven hawkins') }).count()
const petIds = await User.innerJoin('pets', { name: ops.similarity('fido') }).pluck('pets.id')
const numUpdatedRecords = await User.where({ name: ops.similarity('steeven hawkins') }).update({
  name: 'Stephen Hawking',
})
const users = await User.innerJoin('pets', { name: ops.similarity('fido') }).all()

const user = await User.first()
const pets = await user
  .associationQuery('pets')
  .where({ name: ops.similarity('fido') })
  .all()

Questions?

Dream is an open source library, so we encourage you to actively contribute. Visit our Contributing guide to learn more about the processes we use for submitting pull requests or issues.

Are you trying to report a possible security vulnerability? Visit our Security Policy for guidelines about how to proceed.