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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aginix/adonis-object-id

v0.1.1

Published

Stable string identifiers for AdonisJS Lucid models — reference any record by a namespaced key instead of its auto-increment id

Readme

@aginix/adonis-object-id

Stable string identifiers for AdonisJS Lucid models.

Master-data rows usually have auto-incremented IDs, which makes them hard to reference from code, seeders, or cross-environment imports — the ID changes depending on insert order. Adding a code / slug / reference column to every table that needs one is repetitive and rigid. This package solves the problem with a single polymorphic object_ids table that maps a string reference ("namespace.name") to a record in any model.

Features

  • One table, every model. No per-table boilerplate.
  • Mixin that adds object_id utilities to any Lucid model.
  • Static service for cross-cutting access (seeders, scripts).
  • Cascade delete of references when a row is removed (opt-out).
  • Ace command to scaffold the table migration.

Install

npm install @aginix/adonis-object-id
node ace configure @aginix/adonis-object-id
node ace migration:run

The configure hook registers the package's provider and drops a timestamped migration into database/migrations that creates the object_ids table. Re-running configure is safe — it detects an existing *_create_object_ids_table.ts file and skips regeneration.

Reference format

References are strings of the form namespace.name (e.g. app.admin_user). The namespace is required for clarity but defaults to app when omitted, so "admin_user" is normalized to "app.admin_user". Only the first . is treated as a separator — web.editor.iframe parses as namespace=web, name=editor.iframe.

Usage — HasObjectId mixin

Compose the mixin alongside BaseModel:

import { compose } from '@adonisjs/core/helpers'
import { BaseModel, column } from '@adonisjs/lucid/orm'
import { HasObjectId } from '@aginix/adonis-object-id'

export default class Faculty extends compose(BaseModel, HasObjectId) {
  @column({ isPrimary: true })
  declare id: number

  @column()
  declare name: string

  // Optional. Defaults to the Lucid table name ("faculties" here).
  // static objectIdModelKey = 'school.faculty'

  // Optional. Default true — wipes orphan references on delete.
  // static cascadeObjectIdsOnDelete = false
}

Static methods

// Create a row AND bind a reference to it atomically. Equivalent to
// `Faculty.create({...})` + `faculty.assignObjectId('...')`, but
// wrapped in a transaction so a duplicate-reference error rolls the
// new row back instead of leaving it orphaned. Pass `{ client: trx }`
// to piggyback on an existing transaction.
const faculty = await Faculty.createWithObjectId(
  { name: 'Engineering' },
  'app.engineering_faculty'
)

// Resolve a reference to a row of this model (null if missing /
// pointing at a different model / row deleted)
const found = await Faculty.findByObjectId('app.engineering_faculty')

// Same but throws ObjectIdNotFoundError on miss
const required = await Faculty.findByObjectIdOrFail('app.engineering_faculty')

// Just the primary-key value — useful for FK wiring in seeders
const id = await Faculty.refObjectId('app.engineering_faculty')
const idStrict = await Faculty.refObjectIdOrFail('app.engineering_faculty')

Instance methods

const faculty = await Faculty.create({ name: 'Engineering' })

// Bind a reference (upsert; idempotent if it already points here)
await faculty.assignObjectId('app.engineering_faculty')

// Read back
await faculty.getObjectId() // "app.engineering_faculty"
await faculty.getObjectIds() // ["app.engineering_faculty"]

// Add another reference (e.g. legacy alias)
await faculty.assignObjectId('legacy.fac_eng')

// Rename a reference that points at this row
await faculty.renameObjectId('legacy.fac_eng', 'archive.fac_eng')

// Remove a specific reference (only if it points here)
await faculty.removeObjectId('archive.fac_eng')

// Remove every reference for this row
await faculty.removeAllObjectIds()

Usage — ObjectIds service

Use the static service when you don't want to mix in the model (e.g. in seeders, scripts, or when the model has no HasObjectId):

import { ObjectIds } from '@aginix/adonis-object-id'
import Faculty from '#models/faculty'

// Resolve a reference to its (model, recordId) target
const target = await ObjectIds.ref('app.engineering_faculty')
//   => { model: 'faculties', recordId: '42' } | null

// Throws on miss
const target2 = await ObjectIds.refOrFail('app.engineering_faculty')

// Resolve directly to a row of a given model
const faculty = await ObjectIds.find(Faculty, 'app.engineering_faculty')
const facultyStrict = await ObjectIds.findOrFail(Faculty, 'app.engineering_faculty')

// Assign a reference. Accepts either a Lucid row or an explicit target.
await ObjectIds.assign(faculty, 'app.engineering_faculty')
await ObjectIds.assign({ model: 'faculties', recordId: 42 }, 'app.engineering_faculty')

// Remove a reference
await ObjectIds.remove('app.engineering_faculty')

// Remove every reference for a target (called automatically by the
// mixin's beforeDelete hook)
await ObjectIds.removeAllFor(faculty)

// List every reference pointing at a target
await ObjectIds.listFor(faculty) // => ["app.engineering_faculty", ...]

All service methods accept an optional { connection?, client? } argument to override the database connection or piggyback on an open transaction.

Seeders

Object_ids shine in seeders — bind every master record to a stable reference once, then look up by reference everywhere else:

// database/seeders/01_faculties.ts
import { BaseSeeder } from '@adonisjs/lucid/seeders'
import Faculty from '#models/faculty'

export default class extends BaseSeeder {
  async run() {
    const engineering = await Faculty.updateOrCreate(
      { name: 'Engineering' },
      { name: 'Engineering' }
    )
    await engineering.assignObjectId('seed.faculty_engineering')

    const science = await Faculty.updateOrCreate({ name: 'Science' }, { name: 'Science' })
    await science.assignObjectId('seed.faculty_science')
  }
}
// database/seeders/02_courses.ts
import { BaseSeeder } from '@adonisjs/lucid/seeders'
import Course from '#models/course'
import Faculty from '#models/faculty'

export default class extends BaseSeeder {
  async run() {
    const facultyId = await Faculty.refObjectIdOrFail('seed.faculty_engineering')
    await Course.create({ name: 'CS 101', facultyId: Number(facultyId) })
  }
}

The second seeder no longer cares about insertion order or auto-IDs.

Backfilling existing data

Adding object_ids to a record that already exists is a one-liner — they're stored in a separate table, so no schema change is needed:

const admin = await User.findByOrFail('email', '[email protected]')
await admin.assignObjectId('app.admin_user')

Model key

The model column in object_ids defaults to the consumer model's Lucid table name. This is stable across class renames but changes if you rename the table — override it with static objectIdModelKey if you want a portable key:

class Faculty extends compose(BaseModel, HasObjectId) {
  static objectIdModelKey = 'school.faculty'
}

Errors

  • InvalidObjectIdError (status 400) — reference fails parsing.
  • ObjectIdNotFoundError (status 404) — *OrFail method couldn't resolve.

Both extend Error and expose a status field, so AdonisJS's default exception handler turns uncaught instances into appropriate HTTP responses.

License

MIT — see LICENSE.md.