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

supabase-active-record

v0.1.0-alpha.3

Published

Supabase ActiveRecord ----------------

Readme

Supabase ActiveRecord

ActiveRecord implementation for Supabase (experimental).

Features

  • CRUD
  • Validations
  • Relationships belongsTo(), hasMany()
  • Named scopes
  • Filtering
  • Joining
  • Isomorphic

Examples

Models

A model is a class that inherits from ActiveRecord:

import { ActiveRecord } from 'supabase-active-record'

class Person extends ActiveRecord {
  // define table name and fields
  static config = {
    table: 'people',
    fields: {
      id: 'serial',
      firstName: 'string',
      lastName: 'string'
    }
  }
}

Querying

To get all records:

const people = await Person.all()

To find a single record:

const person = await Person.findBy({firstName: "Josh", lastName: "Nussbaum"})

To find by id:

const person = await Person.find(41)

ActiveRecord.find() and ActiveRecord.findBy() return null when no record is found. If you perfer to raise an NotFoundError, use ActiveRecord.get() or ActiveRecord.getBy():

try {
  const person = await Person.get(41)
} catch (error) {
  console.error(error.name) // RecordNotFound
}

Filters

Filters can be added by chaining .where() calls together:

// single filter
await Person.where({lastName: 'Jobs'})

// multiple filters
await Person
  .where({firstName: 'Steve', lastName: 'Jobs'})

// equivalent
await Person
  .where('firstName', 'Steve')
  .where('lastName', 'Jobs')

// equivalent, using operators
await Person
  .where('firstName', '=', 'Steve')
  .where('lastName', '=', 'Jobs')

// supported operators: =, !=, >, <, <=, >=, like, ilike
await Product
  .where('price', '>', 100)
  .where('name', 'like', '*shirt*')

Ordering

Data can be ordered by one or more columns:

// sort by column
await Product
  .all()
  .order('name')

// sort by multiple columns
await Product
  .all()
  .order(['price', 'name'])

// ascending and descending can be specified as `asc` and `desc`
await Product
  .all()
  .order({price: 'desc', name: 'asc'})

Limiting

To limit the number of records returned:

await Product
  .all()
  .limit(10) // 10 records max

Joining

(not yet working)

Multiple models can be joined together

import { ActiveRecord, belongsTo } from 'supabase-active-record'

class Product extends ActiveRecord {
  static config = [
    table: 'products',
    fields: {
      id: 'serial',
      name: 'string',
      category: belongsTo(Category)
    }
  ]
}

const product = await Product
  .find(1)
  .join('category')

console.log(product.category) // Category { name: 'T-Shirts' }

Scopes

Named scopes can be defined using static functions:

class Product extends ActiveRecord {
  static config = {
    table: 'products'
  }

  static expensive() {
    return this
      .where('price', '>', 100)
      .order({price: 'desc'})
      .limit(3)
  }
}

And then call the scope like this:

const products = await Product.expensive()

Validation

Multiple validations are supported. They are defined in config.validate:

import { ActiveRecord, is } from 'supabase-active-record'

class Product extends ActiveRecord {
  static config = {
    table: 'products',
    fields: {
      name: 'string',
      price: 'number'
    },
    validates: {
      name: is.required()
      price: is.type('number')
    }
  }
}

Supported validations: is.required(), is.type(), is.length(), is.format()

Custom validation

A validation is a function that takes an object (the record) and returns a string (the error message).

import { ActiveRecord, is } from 'supabase-active-record'

class Product extends ActiveRecord {
  static config = {
    // ...
    validates: {
      name: record => {
        if (record.name.length < 3)
          return 'is too short'
      }
    }
  }
}

Creating

To create a single record:

const {valid, errors, record} = await Product.create({name: 'Shirt'})

or using an instance:

const product = new Product()

product.name = 'Shirt'
product.price = 100

const { valid, errors } = await product.save()

To create multiple records at once:

await Product.create([
  {name: 'Shirt', price: 20},
  {name: 'Pants', price: 100}
])

Updating

To update a record, call record.save():

// get existing record
const product = await Product.find(1)

// change record
product.price++

// save record
const {valid, errors} = await product.save()

or update multiple records at once:

// update all records where price=1 to price=2
await Product
  .where('price', '=', 1)
  .update({price: 2})

Deleting

To delete a record:

// get existing record
const product = await Product.find(1)

// delete it
await product.delete()

or delete multiple records at once:

// delete all records where price > 1000
await Product
  .where('price', '>', 1000)
  .delete()

Instance methods

Instance methods, getters and setters can be added to the model:

class Person extends ActiveRecord {
  // define a getter
  get fullName() {
    return `${this.firstName} ${this.lastName}`
  }

  // define a setter
  set fullName(value) {
    const [firstName, lastName] = value.split(' ')

    this.firstName = firstName
    this.lastName = lastName
  }

  // define a method to update name
  anonymize() {
    this.firstName = 'Anonymous'
    this.lastName = 'Anonymous'
  }
}

const person = new Person({firstName: "Steve", lastName: "Jobs"})
console.log(person.fullName) // Steve Jobs
person.anonymize()
console.log(person.fullName) // Anonymous Anonymous

Change Tracking

When a record is changed, record.isChanged is set to true. There is also record.isPersisted which is the opposite of record.isChanged.

const product = new Product()

console.log(product.isNewRecord) // true
console.log(product.isChanged) // true
console.log(product.isPersisted) // false (opposite of isChanged)

await product.save()

// now it's no longer a new record or dirty
console.log(product.isNewRecord) // false
console.log(product.isChanged) // false
console.log(product.isPersisted) // true

Setup

Install the npm package:

yarn add supabase-active-record

Setup the supabase client:

import { createClient } from '@supabase/supabase-js'
import { ActiveRecord } from 'supabase-active-record'

ActiveRecord.client = createClient("<your supabase url>", "<your supabase key>")