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

@decaf-ts/for-nano

v0.11.0

Published

decaf-ts persistence adapter for CouchDB via nano

Readme

Banner

Decaf's Nano (CouchDB) Module

A lightweight adapter layer and utilities to use CouchDB via the Nano client within the decaf-ts ecosystem. It provides a NanoAdapter with repository-friendly CRUD, bulk operations, indexing, user/database management helpers, and a change feed dispatcher, plus typed flags and configuration for ergonomic, testable data access.

Release docs refreshed on 2025-11-26. See workdocs/reports/RELEASE_NOTES.md for ticket summaries.

Licence GitHub language count GitHub top language

Build & Test CodeQLSnyk Analysis Pages builder .github/workflows/release-on-tag.yaml

Open Issues Closed Issues Pull Requests Maintained

Forks Stars Watchers

Node Version NPM Version

Documentation available here

Description

This package integrates CouchDB via the Nano client into the decaf-ts data stack. It exposes a focused set of primitives that make CouchDB usage consistent with other decaf-ts backends (TypeORM, HTTP, Pouch, etc.), while retaining Nano’s flexibility.

Core elements and their intents:

  • NanoAdapter

    • Bridges decaf-ts Repository operations with Nano’s DocumentScope API.
    • Implements repository-friendly CRUD: create, read, update, delete, plus bulk variants (createAll, readAll, updateAll, deleteAll).
    • Preserves and manages CouchDB revision metadata transparently via PersistenceKeys.METADATA and CouchDBKeys.REV.
    • Provides raw Mango query capability (raw) and index management (index).
    • Offers connection and administration helpers: connect, createDatabase, deleteDatabase, createUser, deleteUser.
    • Exposes Dispatch() to construct a NanoDispatch for change feed observation.
    • Normalizes operation flags via flags(), ensuring user context is propagated from NanoFlags to the underlying operations.
  • NanoDispatch

    • A Dispatch implementation that subscribes to CouchDB’s continuous changes feed through Nano.
    • Parses change feed frames, groups them by table and operation (CREATE/UPDATE/DELETE), and notifies observers through updateObservers.
    • Handles reconnection attempts and keeps track of the last processed update step (observerLastUpdate) for resilience.
  • NanoRepository

    • A typed alias that binds Repository to the Nano-specific types: MangoQuery, NanoAdapter, NanoFlags, and Context.
    • Enables consumers to use a familiar Repository API with CouchDB when paired with NanoAdapter.
  • Types and constants

    • NanoFlags extends RepositoryFlags with a required user object (name, roles?) for consistent auth context propagation.
    • NanoConfig captures the minimal connection shape (user, password, host, dbName) for setting up adapters.
    • NanoFlavour identifies this backend for selection in multi-backend setups.
    • VERSION exposes the package version string.

Design considerations:

  • Predictable metadata management: CouchDB’s _rev is captured in internal metadata, avoiding accidental leakage into domain models.
  • Bulk operations are error-aware: mixed success/failure responses are aggregated and surfaced as InternalError, preserving the failing reasons from Nano.
  • Testability: All core behaviors (CRUD, bulk, raw, admin helpers, dispatch) are covered by unit tests and written to be easily mocked.
  • Interop-first: Reuses shared types from @decaf-ts/for-couchdb (e.g., MangoQuery, CouchDBKeys) so that query building and index generation are consistent across CouchDB-based adapters.

How to Use

This guide shows practical, non-duplicated examples for all public APIs in @decaf-ts/for-nano using the repository pattern. The adapter class is not meant to be accessed directly; instead, always obtain a repository with Repository.forModel(Model).

Prerequisites:

  • CouchDB server reachable from your app.
  • Install the package: npm i @decaf-ts/for-nano @decaf-ts/for-couchdb @decaf-ts/core @decaf-ts/db-decorators @decaf-ts/decorator-validation nano
  • Importing from @decaf-ts/for-nano registers the Nano backend with the core Repository system.

  1. Define a model and get a repository

Description: Declare a model with table/primary-key decorators and get a Nano-powered repository for it. The flavour is auto-wired by importing @decaf-ts/for-nano.

import {
  BaseModel,
  Repository,
  pk,
  uses,
} from "@decaf-ts/core";
import { model, Model, ModelArg, required } from "@decaf-ts/decorator-validation";
import type { NanoRepository } from "@decaf-ts/for-nano";

@uses("nano")
@model()
class UserModel extends BaseModel implements Model {
  @pk({ type: "String" })
  id!: string; // primary key

  @required()
  name!: string;

  constructor(arg?: ModelArg<UserModel>) {
    super(arg);
  }
}

const repo: NanoRepository<UserModel> = Repository.forModel<UserModel, NanoRepository<UserModel>>(UserModel);
  1. Flags with user context

Description: Pass NanoFlags in repository calls; the user info is propagated to operations by the adapter under the hood.

import type { NanoFlags } from "@decaf-ts/for-nano";

const flags: NanoFlags = {
  user: { name: "tester", roles: ["writer"] },
};
  1. CRUD: create and read a single document

Description: Insert a document and read it back. CouchDB revisions are stored in PersistenceKeys.METADATA transparently.

import { PersistenceKeys } from "@decaf-ts/core";

const created = await repo.create(new UserModel({ id: "user:1", name: "Ada" }));
// created[PersistenceKeys.METADATA] contains the new revision string, e.g., "1-a"

const loaded = await repo.read("user:1");
console.log(loaded.name);
  1. Bulk create and bulk read

Description: Insert multiple documents and then fetch them by IDs. Bulk operations aggregate errors.

const users = [
  new UserModel({ id: "user:2", name: "Lin" }),
  new UserModel({ id: "user:3", name: "Grace" }),
];
const createdMany = await repo.createAll(users);

const fetchedMany = await repo.readAll(["user:2", "user:3"]);
  1. Update and updateAll

Description: Update requires the previous revision in metadata. The new revision is written back into metadata.

let u = await repo.read("user:1");
// ... mutate
u.name = "Ada Lovelace";
// u already has PersistenceKeys.METADATA from read()
u = await repo.update(u);

// Bulk update requires each model to carry its matching metadata
const u2 = await repo.read("user:2");
const u3 = await repo.read("user:3");
const updatedMany = await repo.updateAll([u2, u3]);
  1. Delete and deleteAll

Description: Delete a single document, or delete in bulk by IDs.

const deleted = await repo.delete("user:3");

const deletedMany = await repo.deleteAll(["user:1", "user:2"]);
  1. Query with selectors (instead of raw Mango)

Description: Use the Repository query API to filter and project results.

import { Condition, OrderDirection } from "@decaf-ts/core";

// Select all as full UserModel objects
const all = await repo.select().execute();

// Select only specific attributes
const projected = await repo.select(["name"]).execute();

// Conditional queries
const nameEq = Condition.attribute<UserModel>("name").eq("Ada Lovelace");
const named = await repo.select().where(nameEq).execute();

// Ordering (requires proper indexes configured for CouchDB)
const ordered = await repo.select().orderBy(["name", OrderDirection.ASC]).execute();
  1. Observe changes via repository

Description: Subscribe to CREATE/UPDATE/DELETE events using the Observer interface. The repository wires Nano’s change feed internally.

import type { Observer } from "@decaf-ts/core";
import { OperationKeys } from "@decaf-ts/db-decorators";

const observer: Observer = {
  async refresh(table: string, operation: OperationKeys | string, ids: string[]) {
    if (operation.toString() === OperationKeys.DELETE.toString()) {
      console.log(`Deleted from ${table}:`, ids);
    }
  },
};

await repo.observe(observer);
// ... later
await repo.unObserve(observer);
  1. Choose the backend via NanoFlavour

Description: Use NanoFlavour as an identifier in multi-backend setups.

import { NanoFlavour } from "@decaf-ts/for-nano";
console.log(NanoFlavour); // "nano"
  1. Use NanoRepository typing

Description: Bind your model type to a repository powered by the Nano backend.

import type { NanoRepository } from "@decaf-ts/for-nano";
import type { Model } from "@decaf-ts/decorator-validation";

class MyModel implements Model {
  _id!: string;
}

let myRepo!: NanoRepository<MyModel>;
  1. Access package VERSION

Description: Read the module’s version string if you need it for diagnostics.

import { VERSION } from "@decaf-ts/for-nano";
console.log("for-nano version:", VERSION);

Advanced (optional): Administration helpers

Description: If you must manage CouchDB resources, @decaf-ts/for-nano exports static helpers on NanoAdapter (no direct instantiation required). These are not part of the Repository API.

import { NanoAdapter } from "@decaf-ts/for-nano";

// Build a Nano (CouchDB) connection
const url = NanoAdapter.connect("admin", "secret", "localhost:5984", "http");

// Ensure a database exists / manage users
await NanoAdapter.createDatabase(url, "mydb");
// ... createUser/deleteUser, deleteDatabase, etc.

Task Engine guardrails for migration orchestration

Migration command runners and the integration tests rely on a dedicated RamAdapter task engine that never shares an alias with the adapters being migrated. MigrationService.migrateAdapters enforces this guardrail by comparing every adapter alias and rejecting runs where the task engine would also be a migration target. Keep your task engine adapter isolated (for example new RamAdapter({}, "decaf-cli-task-engine")) and set concurrency: 1 so version steps stay sequential.

import { RamAdapter } from "@decaf-ts/core/ram";
import { TaskService } from "@decaf-ts/core/tasks";

const taskEngineAdapter = new RamAdapter({}, "decaf-cli-task-engine");
const taskService = new TaskService();
await taskService.boot({
  adapter: taskEngineAdapter,
  workerId: "nano-migration-worker",
  leaseMs: 10_000,
  logTailMax: 250,
});

Reserve leaseMs for longer-running migrations, tune pollMsBusy/pollMsIdle, and attach a TaskEventBus if you want progress logs in the CLI output. taskService.track(taskId) can then stream the same progress/log events that the integration tests already observe.

Migration lifecycle and @migration semantics for Nano

@migration metadata controls ordering and flavour targeting:

@migration("1.1.0-add-category-field", {
  precedence: "1.1.0",
  flavour: "nano",
  rules: [
    async (_, adapter) => Boolean(await adapter.exists("for_nano_migration_products")),
  ],
})
class AddCategoryMigration extends AbsMigration<NanoAdapter> { ... }
  • reference: the canonical label (usually the semver string) used for logging, precedence tokens, and version normalization.
  • precedence: a constructor, reference string, or object pointing to another migration to force ordering when version/flavour collide.
  • flavour: limits execution to the Nano flavour (the for-nano tests only execute Nano-scoped migrations).
  • rules: async predicates (qr, adapter, ctx) that gate execution; a false result skips the migration without failing the run.

MigrationService also expects handlers for retrieveLastVersion and setCurrentVersion. These functions are invoked per flavour so you can persist the head (e.g., in a VersionRepo). During the live integration test we initialize the version map at "1.0.0" and let setCurrentVersion advance it to the target version once the required property addition/backfill completes.

Use MigrationService.migrateAdapters([nanoAdapter], { toVersion: "2.0.0", handlers: {...}, taskMode: true, taskService }) once your NanoAdapter is initialized. taskMode: true queues one CompositeTask per version and calls setCurrentVersion immediately after each task resolves, which keeps the persistent version marker aligned with the latest fully applied hop. Normal mode updates the version only after the whole batch finishes.

MigrationService.retry(taskId) rewrites the failed TaskModel to PENDING, clears error, leaseOwner, and timestamps, and re-enqueues the same version so the CLI can resume from the failing point. Because the version marker wasn't advanced for the failed task, rerunning the CLI with the same toVersion continues at the correct semantic boundary. Our tests ensure that every migration adds a required property/column and fills existing documents with the default value before the next version runs.

Live migration workflow

Migration integration suites run against live CouchDB instances. for-nano tests are intentionally limited to RamAdapter + NanoAdapter so they stay independent of SQL modules. That means:

  • Every migration must add a new required property/column and backfill every existing document with the default value before continuing.
  • Use MigrationService.migrateAdapters([nanoAdapter], config) with flavour-scoped handlers so the last executed version is persisted independently per adapter.
  • If you turn on taskMode, boot a separate RamAdapter (alias distinct from the ones being migrated) before you create the TaskService. The migration guard throws if the task engine adapter alias is also a migration target.
@migration("1.1.0-add-isActive", {
  precedence: "1.1.0",
  flavour: "nano",
})
export class AddIsActiveMigration implements Migration<any, NanoAdapter> {
  async up(_, adapter) {
    const repo = new Repository(adapter, UserModel);
    const users = await repo.select().execute();
    await Promise.all(
      users.map((user) => repo.update({ ...user, isActive: true }))
    );
  }
}
const migrations = await MigrationService.migrateAdapters(
  [nanoAdapter],
  {
    toVersion: "1.1.0",
    flavours: ["nano"],
    taskMode: true,
    taskService,
    handlers: {
      nano: {
        retrieveLastVersion: async (adapter) =>
          (await versionRepo(adapter).read("nano"))?.version,
        setCurrentVersion: async (version, adapter) =>
          await versionRepo(adapter).upsert("nano", { version }),
      },
    },
  }
);
for (const migration of migrations) {
  await migration.track();
}

MigrationService starts by calling the flavour-specific retrieveLastVersion handler so it knows which version the database already holds, then filters decorated migrations whose normalized versions are strictly greater than currentVersion and less than or equal to toVersion. setCurrentVersion is invoked after every successfully completed version: inline runs update once at the very end, while task mode updates immediately after each tracked CompositeTask. This guarantees the recorded currentVersion always equals the last fully finished hop, so rerunning the command will skip completed versions and replay only the pending ones. When a task fails, call MigrationService.retry(taskId) (optionally taskService.track(id) to observe progress) to reset the TaskModel to PENDING, clear its error/lease metadata, and let the TaskEngine reclaim the same version without revisiting already finished steps.

The @migration decorator handles ordering and targeting. The key arguments are:

  • reference: the name/semver label used in logs and dependency graphs.
  • precedence: optional hint (a constructor, string, or object) that the sorter uses when two migrations share the same version and flavour.
  • flavour: restricts the migration to one adapter flavour ("nano", "type-orm", etc.). Omit it for generic migrations or to let includeGenericInTaskMode decide when to run.
  • rules: async predicates that gate execution ((qr, adapter, ctx) => Promise<boolean>). When a rule returns false the migration is skipped without error.

The CLI migrations guard enforces that the TaskEngine runs on a separate RamAdapter alias that is never one of the migrating adapters, keeping persistence targets isolated and preventing lease conflicts.

Use MigrationRules (the rules array in @migration) to gate execution based on adapter state. Keep every migration focused on one version jump so the live suites can always rerun and verify the required schema change and backfill.

Coding Principles

  • group similar functionality in folders (analog to namespaces but without any namespace declaration)
  • one class per file;
  • one interface per file (unless interface is just used as a type);
  • group types as other interfaces in a types.ts file per folder;
  • group constants or enums in a constants.ts file per folder;
  • group decorators in a decorators.ts file per folder;
  • always import from the specific file, never from a folder or index file (exceptions for dependencies on other packages);
  • prefer the usage of established design patters where applicable:
    • Singleton (can be an anti-pattern. use with care);
    • factory;
    • observer;
    • strategy;
    • builder;
    • etc;

Release Documentation Hooks

Stay aligned with the automated release pipeline by reviewing Release Notes and Dependencies after trying these recipes (updated on 2025-11-26).

Related

decaf-ts for pouch core decorator-validation db-decorators

Social

LinkedIn

Languages

TypeScript JavaScript NodeJS ShellScript

Getting help

If you have bug reports, questions or suggestions please create a new issue.

Contributing

I am grateful for any contributions made to this project. Please read this to get started.

Supporting

The first and easiest way you can support it is by Contributing. Even just finding a typo in the documentation is important.

Financial support is always welcome and helps keep both me and the project alive and healthy.

So if you can, if this project in any way. either by learning something or simply by helping you save precious time, please consider donating.

License

This project is released under the Mozilla Public License 2.0.

By developers, for developers...