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

@nizalix/arrange-nestjs-adapter

v0.1.0

Published

Server contracts for persisting Arrange moves (list reorder, Kanban, tree): request validation, rule and permission checks, optimistic-update client helpers. Works with NestJS, Express or any Node backend.

Readme

@nizalix/arrange-nestjs-adapter

Safely save drag-and-drop changes on your server. Validated move requests, server-side rules and permission checks, and optimistic-update helpers for NestJS, Express, Next.js route handlers or any Node backend.

npm types license

The browser can be wrong, stale, or malicious, so a drag-and-drop UI should never be the only place where your rules are enforced. This package gives you the server half:

  • Validates untrusted request bodies (never throws, never leaks internals)
  • Re-checks your rules on the server: Kanban WIP limits and transitions, tree depth and hierarchy
  • Authorization hook: decide who may move what
  • Conflict detection: stale state is detected and the server's canonical order is sent back
  • Client helpers for optimistic updates with rollback
  • Framework-agnostic: despite the name it has no NestJS dependency. Use it from NestJS, Express, Fastify, Hono, serverless functions or Next.js route handlers

You bring the database (through a small MoveStore) and the HTTP layer. This package never makes network calls itself.

Install

npm install @nizalix/arrange-nestjs-adapter

It uses @nizalix/arrange-kanban and @nizalix/arrange-tree for the move rules (installed automatically).

How it works

Browser                              Your server
-------                              -----------
drag a card ──► UI updates instantly
            ──► POST /api/move  ───►  handler(body, ctx)
                                       1. validate the payload
                                       2. load current state (your database)
                                       3. authorize (your permission check)
                                       4. check version / apply rules
                                       5. save (your database)
            ◄── { ok, order }  ◄───   returns the canonical order
UI matches the server order
(or rolls back if { ok: false })

Server: a Kanban board endpoint

import {
  createBoardMoveHandler,
  httpStatusFor,
  type MoveStore,
} from "@nizalix/arrange-nestjs-adapter";

type Card = { id: string; title: string };
type Column = { id: string; title: string; cards: Card[] };
type Ctx = { userId: string };

// 1. Tell the handler how to load and save the board (your database code).
const store: MoveStore<readonly Column[], Ctx> = {
  async load(_request, ctx) {
    const board = await db.boards.findForUser(ctx.userId);
    return { state: board.columns, version: board.version };
  },
  async save(next, { ctx }) {
    const version = await db.boards.replaceColumns(ctx.userId, next);
    return { version };
  },
};

// 2. Create the handler once. Rules are enforced here, on the server.
export const moveHandler = createBoardMoveHandler<Column, Ctx>({
  store,
  rules: {
    maxCards: (column) => (column.id === "doing" ? 5 : undefined),
    transitions: { todo: ["doing"], doing: ["todo", "done"] },
  },
  authorize: ({ ctx, state }) => canEditBoard(ctx.userId, state),
});

// 3. Call it from any route. The handler never throws.
export async function POST(request: Request) {
  const userId = await getUserId(request); // your auth
  const response = await moveHandler(await request.json(), { userId });
  return Response.json(response, { status: httpStatusFor(response) });
}

The example above is a Next.js route handler (app/api/board/move/route.ts). The same handler works unchanged in any framework.

NestJS controller

@Controller("board")
export class BoardController {
  @Post("move")
  async move(@Body() body: unknown, @Req() req: AuthedRequest, @Res() res: Response) {
    const response = await moveHandler(body, { userId: req.user.id });
    res.status(httpStatusFor(response)).json(response);
  }
}

Express

app.post("/api/board/move", async (req, res) => {
  const response = await moveHandler(req.body, { userId: req.user.id });
  res.status(httpStatusFor(response)).json(response);
});

Other handlers

| Handler | Request type | State you store | | --- | --- | --- | | createBoardMoveHandler | "card", "column" | Column[] with cards | | createListMoveHandler | "reorder" | Ordered id list | | createTreeMoveHandler | "tree" | Nested nodes ({ id, children? }) |

All three take { store, authorize? } (board and tree also take rules), and return a function (body: unknown, ctx) => Promise<MoveResponse>.

Client: optimistic updates with rollback (Kanban)

"use client";

import { KanbanBoard, useOptimisticBoard } from "@nizalix/arrange-kanban/react";
import { createBoardPersister, isMoveRejectedError } from "@nizalix/arrange-nestjs-adapter";

export function Board({ initial }: { initial: Column[] }) {
  const board = useOptimisticBoard(initial, {
    persist: createBoardPersister<Column>(async (request) => {
      const res = await fetch("/api/board/move", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(request),
      });
      return res.json(); // the server's MoveResponse
    }),
    onRejected: ({ error }) => {
      if (isMoveRejectedError(error)) toast(error.message); // e.g. "A column may hold at most 5 cards."
    },
  });

  return <KanbanBoard columns={board.columns} onChange={board.onChange} renderColumn={…} renderCard={…} />;
}

What happens: the UI updates instantly; the request is sent; on success the board is replaced with the server's canonical order; on rejection the board rolls back and onRejected receives a MoveRejectedError with a safe-to-show message, a code, and the server's current order.

Lists and trees

import { sortableMoveToRequest, treeMoveToRequest } from "@nizalix/arrange-nestjs-adapter";

// SortableList / useSortable:  onItemsChange={(next, move) => send(sortableMoveToRequest("my-list", move))}
// TreeDnd:                      onChange={(next, move) => send(treeMoveToRequest(move))}

Responses

Success: { ok: true, order, version? }. Rejection: { ok: false, code, message, detail?, order?, version? }.

| code | Meaning | httpStatusFor | | --- | --- | --- | | invalid-request | Payload failed validation | 400 | | forbidden | Your authorize said no | 403 | | not-found | Item, column or parent does not exist | 404 | | conflict | The client was stale, use the returned order to resync | 409 | | rule-violation | A rule such as WIP limit or hierarchy blocked it (see detail) | 422 | | internal | Your store threw. Details are not leaked | 500 |

Concurrency and security notes

  • Send expectedVersion (via the getVersion option of createBoardPersister) and return a version from your store to detect stale writes.
  • Between load and save, two requests can race. Use a database transaction or row lock if that matters; this package cannot do it for you.
  • Always run your own authentication before calling the handler, and use authorize for per-item permissions.
  • Errors thrown by your store or authorize become an internal response, never a stack trace.

Pure helpers

parseMoveRequest(body), applyBoardRequest, applyReorderRequest, applyTreeRequest, getBoardOrderData / applyBoardOrderData, getListOrder / applyListOrder, getTreeOrder / applyTreeOrder, createMoveHandler (build a handler for your own request type).

Related packages

@nizalix/arrange-kanban · @nizalix/arrange-tree · @nizalix/arrange-presets · @nizalix/arrange-react

Author

Created and maintained by salmannizam. Issues and ideas are welcome on GitHub. MIT licensed.