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

@oniryk/dreamer

v0.0.17

Published

Speed up AdonisJS V6 development flow.

Readme

@oniryk/dreamer

@oniryk/dreamer is a collection of tools, developed over oniryk vision, that helps you build APIs with AdonisJS v6 faster and easier. It includes a code generator and a set of helper functions and abstractions to speed up your development workflow.

What is included:

  • A code generator that creates a full CRUD starting from a migration definition;
  • Abstractions for CRUD actions;
  • Support for UUID as external ID;
  • Support for soft deletes;
  • Default formatting for API responses.

Installation

node ace add @oniryk/dreamer

After installing the package, you will be prompted with questions and a dreamer config file will be generated like this:

import { defineConfig } from '@oniryk/dreamer'

const dreamerConfig = defineConfig({
  useUUID: true,
  useSoftDelete: true,
  bruno: {
    enabled: true,
    documentsDir: '/docs',
    useAuth: true,
  },
})

export default dreamerConfig

Configuring

  • useUUID: when enabled, all models generated by the dreamer command will use uuid as field instead of id in the methods find() and findOrFail() from Lucid. learn more
  • useSoftDelete: when enabled, all models generated by the dreamer command will implement soft deletes. It will add the field deleted_at in the migration file. learn more
  • bruno.enabled: when enabled, will generate bruno files for all routes generated by the dreamer command
  • bruno.documentsDir: specifies where bruno's files will be placed
  • bruno.useAuth: when enabled, will automatically add an Authorization: Bearer ... into the request file.

Code generation

Dreamer has a powerful code generation tool that automates the creation of complete CRUD operations from a single migration file. This streamlines your development process by generating all necessary components with minimal configuration.

Basic Usage

To generate a new CRUD, use the following command:

node ace dreamer [entity]

Replace [entity] with your desired entity name. For example, to create a CRUD for blog posts:

node ace dreamer posts

Workflow

  1. The command creates a migration file
  2. You'll be prompted to edit the file and define your entity fields
  3. After saving, Dreamer automatically generates all CRUD components

Generated Components

Dreamer creates a complete set of files for your entity:

app/
├── models/
│   └── post.ts
├── controllers/
│   └── posts_controller.ts
├── validators/
│   └── post.ts
├── routes/
│   └── posts.ts
└── docs/  # If Bruno is enabled
    └── posts/
        └── index.bru
        └── show.bru
        └── store.bru
        └── update.bru
        └── destroy.bru
  • Model: Lucid model with all field definitions
  • Controller: RESTful actions implementation
  • Validators: Input validation rules for store and update actions
  • Routes: API endpoint definitions
  • API Documentation: Automatically generated if Bruno is enabled in your configuration

Advanced Features

Custom Actions

You can specify which CRUD actions to generate using the --actions flag:

node ace dreamer posts --actions=index,show

Available actions:

  • index (List all)
  • show (View single)
  • store (Create)
  • update (Edit)
  • destroy (Delete)

Nested Resources

Dreamer supports nested resource generation for related entities. This is particularly useful for parent-child relationships:

node ace dreamer posts/comments

Note: Currently, dreamer doesn't have the capacity to determine relationships.

Development Note

The code generated by Dreamer is 100% compatible with AdonisJS 6 development standards. While Dreamer's primary goal is to provide abstractions for common CRUD workflows to speed up development, it's designed to work seamlessly alongside traditional AdonisJS development patterns.

You can:

  • Use Dreamer's abstractions for standard CRUD operations
  • Create custom actions for complex scenarios using regular AdonisJS patterns
  • Mix both approaches in the same controller
  • Extend or override Dreamer's generated code using standard AdonisJS features

For example:

import Post from '#models/post'
import { index, show, destroy } from '@oniryk/dreamer/extensions/crud'

export default class PostsController {
  // Using Dreamer's abstractions for standard operations
  public index = index(Post)
  public show = show(Post)
  public destroy = destroy(Post)

  // Custom action for complex business logic
  public async publish({ params, response }: HttpContext) {
    const post = await Post.findOrFail(params.id)

    await post.merge({
      status: 'published',
      publishedAt: new Date()
    }).save()

    await Event.emit('post:published', post)

    return response.status(200).send(post)
  }
}

This flexibility allows you to leverage Dreamer's convenience while maintaining the freedom to implement custom business logic when needed.

Extensions

The @oniryk/dreamer package provides several extensions to enhance your AdonisJS application with additional functionality. These extensions are designed to be composable and can be used individually or together to extend your models and controllers.

Lucid Extensions

UUID Support

The withUUID extension adds UUID support to your models. It's based on the concept of using UUID as a key to expose externally while keeping an autoincrementing integer as the primary key.

import { BaseModel } from '@adonisjs/lucid/orm'
import { compose } from '@adonisjs/core/helpers'
import { withUUID } from '@oniryk/dreamer/extensions/lucid'

export default class Post extends compose(BaseModel, withUUID()) {
  //...
}

What's changed under the hood?

  • id column keeps existing as primary key to speed up relationship queries
  • uuid column is defined and autogenerates UUIDs for new records
  • changes the default behavior of find and findOrFail methods to use the uuid column instead of id when making queries

Soft-delete Support

The withSoftDelete extension implements soft delete functionality in your models:

  • Adds a deletedAt timestamp column to your model
  • Automatically filters out soft-deleted records from queries
import { BaseModel } from '@adonisjs/lucid/orm'
import { compose } from '@adonisjs/core/helpers'
import { withSoftDelete } from '@oniryk/dreamer/extensions/lucid'

export default class Post extends compose(BaseModel, withSoftDelete()) {
  // ...
}

Searchable Fields

The searchable fields feature allows you to define which fields can be searched in your models:

  • Define exact match fields (e.g., 'author_id')
  • Define partial match fields using the 'like:' prefix (e.g., 'like:title')
  • Automatically handles search queries in the CRUD index operation
  • Supports multiple search criteria in a single query
import { BaseModel } from '@adonisjs/lucid/orm'
import { compose } from '@adonisjs/core/helpers'
import { withSoftDelete } from '@oniryk/dreamer/extensions/lucid'

export default class Post extends BaseModel {
  public static searchable = ['author_id', 'like:title']
}

CRUD

The package provides pre-built CRUD operations that can be easily integrated into your controllers. All operations take a model as the first argument and offer some options depending on your functionality.

This is a basic example of a complete RESTful controller:

import Post from '#models/post'
import { index, show, store, update, destroy } from '@oniryk/dreamer/extensions/crud'
import { validatePostCreate, validatePostUpdate } from '#validators/post'
import csv from '@oniryk/dreamer-csv'

export default class PostsController {
  public index = index(Post)
  public show = show(Post)
  public store = store(Post, validatePostCreate)
  public update = update(Post, validatePostUpdate)
  public destroy = destroy(Post)
}

index

The index method provides a flexible way to list and filter records.

import { index } from '@oniryk/dreamer/extensions/crud'
import csv from '@oniryk/dreamer-csv'
import { validatePostIndex } from '#validators/post'

export default class PostsController {
  public index = index(Post, {
    perPage: 20,
    formats: [csv()],
    scope: 'highlights',
    validator: validatePostIndex
  })
}

| Option | Type | Description | |-------------|--------------------|--------------------------------------------------------------| | perPage | number | (optional) Number of records per page | | formats | OutputFormatFn[] | (optional) Array of formatters to enable alternative output formats. When a format is added, the user can request the content in a format by passing f or format in the query string:Ex: GET /posts?f=csv | | scope | string | function | (optional) Name of model scope to apply or function compatible with withScopes method of Lucid query builderEx: (scopes) => scopes.highlights() | | validator | VineValidator | (optional) Vine validation schema for query parameters |

show

The show method provides a way to retrieve a single record. When using UUID extension, it automatically handles UUID-based lookups.

import Post from '#models/post'
import { show } from '@oniryk/dreamer/extensions/crud'

export default class PostsController {
  public show = show(Post)
}

| Option | Type | Description | |---------|-----------|-----------------------| | model | BaseModel | The Lucid model class |

store

The store method handles record creation with validation and optional data mutation.

import Post from '#models/post'
import { store } from '@oniryk/dreamer/extensions/crud'
import { validatePostUpdate } from '#validators/post'

export default class PostsController {
  public store = store(Post, validatePostUpdate, {
    mutate (row, payload) {
      row.title = payload.title.toLowerCase()
    }
  })
}

| Parameter | Type | Description | |------------------|-----------------------------------------------------|--------------------------------------------------| | model | BaseModel | The Lucid model class | | validator | VineValidator | Vine validator schema for input validation | | options.mutate | (row: Model, payload: any) => void | Promise | (optional) Callback to modify data before saving |

update

The update method handles record updates with validation and optional data mutation.

import Post from '#models/post'
import { update } from '@oniryk/dreamer/extensions/crud'
import { validatePostUpdate } from '#validators/post'

export default class PostsController {
  public update = update(Post, validatePostUpdate, {
    mutate (row, payload) {
      row.title = payload.title.toLowerCase()
    }
  })
}

| Parameter | Type | Description | |------------------|-----------------------------------------------------|--------------------------------------------------| | model | BaseModel | The Lucid model class | | validator | VineValidator | Vine validator schema for input validation | | options.mutate | (row: Model, payload: any) => void | Promise | (optional) Callback to modify data before saving |

destroy

The destroy method handles record deletion with proper error handling.

import Post from '#models/post'
import { destroy } from '@oniryk/dreamer/extensions/crud'

export default class PostsController {
  public destroy = destroy(Post)
}

| Parameter | Type | Description | |-----------|-----------|-----------------------| | model | BaseModel | The Lucid model class |

JSON Response Formatters

JSON response formatters provide a consistent way to structure your API responses. They help maintain a uniform pattern for success and error across all your routes.

success

The success method formats successful responses, supporting both simple and paginated data. It automatically structures the response with an "ok" status and includes the provided data, along with pagination metadata when applicable.

Example 1: Simple List Response

import Post from '#models/post'
import { HttpContext } from '@adonisjs/core/http'
import { success } from '@oniryk/dreamer/extensions/http'

export default class PostsController {
  public async list({ response }: HttpContext) {
    const posts = await Post.all()
    success(response, posts)
  }
}

// Response:
{
  "status": "ok",
  "data": [ ... ]
}

Example 2: Paginated Response

export default class PostsController {
  public async paginated({ response, request }: HttpContext) {
    const page = request.input('page', 1);
    const limit = 20;
    const posts = await Post.paginate(page, limit)
    success(response, posts)
  }
}

// Response:
{
  "status": "ok",
  "data": [ ... ],
  "meta": {
    "currentPage": 1,
    "firstPage": 1,
    "firstPageUrl": "/?page=1",
    "lastPage": 1,
    "lastPageUrl": "/?page=1",
    "nextPageUrl": null,
    "perPage": 10,
    "previousPageUrl": null,
    "total": 6
  }
}

error

The error method standardizes error handling in the API, providing a consistent structure for different types of errors. It can handle validation errors, custom errors, and standard system exceptions.

Example 1: Validation Error Handling

import Post from '#models/post'
import { HttpContext } from '@adonisjs/core/http'
import { error } from '@oniryk/dreamer/extensions/http'
import { validatePostCreate } from '#validators/post'

export default class PostsController {
  public async store({ response, request }: HttpContext) {
    try {
      await request.validate(validatePostCreate)
    } catch (e) {
      error(response, e);
    }
  }
}

// Response:
{
  "status": "error",
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failure",
    "issues": [
       {
         "field": "title",
         "message": "The title field must be defined",
         "rule": "required"
       }
    ]
  }
}

Example 2: Standard Error Response

export default class PostsController {
  public async check ({ response, request }: HttpContext) {
    if (1 === 1) {
      error(response, new Error('invalid option'));
    }
  }
}

// Response:
{
  "status": "error",
  "error": {
    "code": "Error",
    "message": "invalid option"
  }
}

Example 3: Custom Error Response

export default class PostsController {
  public async check2 ({ response, request }: HttpContext) {
    if (1 === 1) {
      error(response, { code: 'ERROR_CODE', message: 'invalid option'});
    }
  }
}

// Response:
{
  "status": "error",
  "error": {
    "code": "ERROR_CODE",
    "message": "invalid option"
  }
}

Output formatters

You may want to deliver a response in a specific file format like csv, as you can see in the index action from the CRUD abstraction extension.

Dreamer has optional built-in formatters for csv and xlsx. You can install and use them as needed. They come in two separate packages: @oniryk/dreamer-csv and @oniryk/dreamer-xls.

You can also create your own formatter. It must implement the following type:

type OutputFormatFn<M extends typeof BaseModel> = {
  (ctx: HttpContext, rows: InstanceType<M>[]): Promise<void> | void
  formatName: string
}

Let's create a new example formatter:

export default function pdf({ name }: { name: string }) {
  const handler = async function ({ response }: HttpContext, rows: unknown[]) {
    const content = await convertToPdf(rows); // imaginary function

    response.header("Content-Type", "application/pdf");
    response.header("Content-Disposition", `attachment; filename="${name}"`);
    response.send(content);
  };

  handler.formatName = "pdf";
  return handler;
}

Using our new formatter:

export default class PostsController {
  public index = index(Post, {
    formats: [
      pdf({ name: 'posts.pdf' })
    ]
  })
}