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

@desolint/db-factories

v0.0.2

Published

Generic, injectable DB CRUD factories for Mongo (Mongoose) and Prisma. No root export — import only what you need: @desolint/db-factories/config/mongo, /config/prisma, /schema/mongo, /schema/prisma, /services/mongo, /services/prisma, /utils/mongo, /utils/

Downloads

344

Readme

@desolint/db-factories

Generic, injectable DB CRUD factories for Mongo (Mongoose) and Prisma.

No root export. import ... from '@desolint/db-factories' resolves to nothing — every subpath below is adapter-specific, so the import path itself is the adapter choice. There's no runtime dispatch, no type field to pass, no risk of a mongo call silently hitting the prisma branch: you import /mongo or /prisma, and that's the only adapter that code can ever talk to.

@desolint/db-factories/config/mongo     @desolint/db-factories/config/prisma
@desolint/db-factories/schema/mongo     @desolint/db-factories/schema/prisma
@desolint/db-factories/services/mongo   @desolint/db-factories/services/prisma
@desolint/db-factories/utils/mongo      @desolint/db-factories/utils/prisma
@desolint/db-factories/utils/config/mongo   @desolint/db-factories/utils/config/prisma
@desolint/db-factories/testing

| Subpath | What it's for | | ------------------------------ | ------------------------------------------------------------------------------------------- | | /config/{mongo,prisma} | Open the database connection. Nothing else. | | /schema/{mongo,prisma} | Define/reference a model. | | /services/{mongo,prisma} | The actual CRUD calls — create, find, updateOne, etc. | | /utils/{mongo,prisma} | Standalone helpers you call directly (transactions, id validation, field merging). | | /utils/config/{mongo,prisma} | Tune how the CRUD factories behave (query scoping, pagination defaults, soft-delete field). | | /testing | Spin up a real, disposable Mongo for tests. Mongo only. |


Requirements

  • Node.js 22 or newer (declared in engines)
  • npm 7 or newer

Install

# Mongo consumers
npm install @desolint/db-factories mongoose

# Prisma consumers
npm install @desolint/db-factories

Unlike most packages here, you must name your adapter explicitly. Both peers are declared optional in peerDependenciesMeta, and npm does not auto-install optional peers — that is deliberate, because a Prisma-only consumer should never be made to pull in Mongoose, and vice versa.

If you use the /testing helpers, add the in-memory Mongo server as a dev dependency:

npm install --save-dev mongodb-memory-server

Why mongoose is a peer dependency, not a regular one

mongoose holds a global model registry and connection state. If this package bundled its own copy, models you register in your application would live on a different instance than the one these factories query — so lookups would fail, or silently run against a connection you never opened.

Declaring it as a peer means npm reuses the copy your application already has instead of nesting a second one. You keep control of the version; this package just states the range it works with (mongoose@^8).

Prisma's generated client (@prisma/client) is entirely consumer-owned — this library never imports it itself, so it is not a peer at all.


Quick start

Mongo

// config/db.js
import {initializeConnection} from '@desolint/db-factories/config/mongo';

initializeConnection({
  connectionString: process.env.DATABASE_URL,
  onConnection: () => console.log('Connected to Database'),
  onError: (error) => console.error('Error connecting Database', {error}),
});
// models/Users.js
import {Schema, model} from '@desolint/db-factories/schema/mongo';

const userSchema = new Schema({
  email: {type: String, required: true, unique: true},
  password: {type: String, required: true},
});

export default model('User', userSchema);
// controllers/users.js
import * as DbFactory from '@desolint/db-factories/services/mongo';
import UsersModel from '../models/Users.js';

const {
  doc: user,
  success,
  error,
} = await DbFactory.create({
  model: UsersModel,
  data: req.body,
});

Prisma

// config/db.js
import {PrismaPg} from '@prisma/adapter-pg';
import {PrismaClient} from '@prisma/client';
import {initializeConnection} from '@desolint/db-factories/config/prisma';

const prismaClient = new PrismaClient({
  adapter: new PrismaPg({connectionString: process.env.DATABASE_URL}),
});

initializeConnection({
  client: prismaClient,
  onConnection: () => console.log('Connected to Database'),
  onError: (error) => console.error('Error connecting Database', {error}),
});

export {prismaClient};
// controllers/users.js
import {create, findOne} from '@desolint/db-factories/services/prisma';
import {prismaClient} from '../config/db.js';

const {
  doc: user,
  success,
  error,
} = await create({
  model: prismaClient.user,
  data: req.body,
});

/config/mongo

Database connection setup. Nothing else lives here — CRUD-behavior tuning is /utils/config/mongo, not this.

initializeConnection({ connectionString, options?, onConnection?, onError? })

Connects the shared mongoose singleton and returns it. Fire-and-forget — onConnection/onError are listeners on mongoose.connection, not a promise this function returns, so awaiting it doesn't mean the connection is open yet.

| Param | Type | Required | Notes | | ------------------ | --------------------------- | -------- | ----------------------------------------------- | | connectionString | string | yes | Mongo connection URI. | | options | ConnectOptions (mongoose) | no | Passed straight to mongoose.connect(). | | onConnection | () => void | no | Fires once, on the connection's 'open' event. | | onError | (error: Error) => void | no | Fires on the connection's 'error' event. |

Returns: the mongoose singleton itself (Mongoose), so you can reach mongoose.connection, mongoose.startSession(), etc. off the return value if you don't want a separate /utils/mongo import.


/config/prisma

initializeConnection({ client, onConnection?, onError? })

Wires up a consumer-owned, already-instantiated Prisma client to the same connect/onConnection/onError lifecycle as the mongo adapter. Prisma's generated client is built from your own schema.prisma, so unlike mongo this can't construct a client itself — you build it, this just connects it.

| Param | Type | Required | Notes | | -------------- | ------------------------ | -------- | ----------------------------------------- | | client | PrismaClientLike | yes | Your instantiated PrismaClient. | | onConnection | () => void | no | Fires after client.$connect() resolves. | | onError | (error: Error) => void | no | Fires if client.$connect() rejects. |

Returns: the same client you passed in.


/schema/mongo

Everything needed to define/reference a mongo model. The raw mongoose object itself is deliberately not exported here — that's /config/mongo and /utils/mongo's job (connecting, sessions), not schema definition's.

| Export | Kind | What it is | | ------------------ | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Schema | value (class) | Mongoose's schema builder — new Schema({...}). | | Types | value | Mongoose's Types namespace (Types.ObjectId, etc.). | | model | value (function) | Registers a schema against the shared mongoose singleton and returns the model — model('User', userSchema). | | ModelType | type | The type of what model() returns — for annotating, e.g. function foo(m: ModelType<IUser>). Not named Model — deliberately, so it doesn't sit next to the model function differing only by capitalization. | | IndexOptions | type | Mongoose's index-options type, for schema.index(fields, options). | | HydratedDocument | type | The type of a document mongoose hands back from a query (has instance methods, etc.), e.g. HydratedDocument<IUser>. |

import {Schema, model} from '@desolint/db-factories/schema/mongo';
import type {
  ModelType,
  HydratedDocument,
} from '@desolint/db-factories/schema/mongo';

interface IUser {
  email: string;
}

const userSchema = new Schema<IUser>({email: {type: String, required: true}});
const UsersModel: ModelType<IUser> = model('User', userSchema);

type UserDoc = HydratedDocument<IUser>;

/schema/prisma

Prisma has no schema-authoring API this library could export — prisma/schema.prisma is the only place a model is actually defined, and it's yours, compiled by the Prisma CLI into your own generated client. What this subpath gives you instead is structural typing for that generated client, so this library's own functions can type-check against it without ever importing @prisma/client itself.

| Export | Kind | What it is | | ------------------------- | ---- | ---------------------------------------------------------------------------------------------------------- | | PrismaClientLike | type | Structural stand-in for your PrismaClient — just $connect/$disconnect/$transaction. | | PrismaModelDelegate<T> | type | Structural stand-in for one table delegate off your client, e.g. prismaClient.user. | | PrismaTransactionClient | type | The type of the client a $transaction callback receives (e.g. tx in client.$transaction(tx => ...)). |

import type { PrismaModelDelegate } from '@desolint/db-factories/schema/prisma';

function findAll(model: PrismaModelDelegate<any>) { ... }

/services/mongo

Thin re-exports of MongoFactories — every function here forwards straight to the mongo implementation, no dispatch involved. model is always a mongoose Model.

| Function | Signature | Returns | What it does | | ------------------- | --------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | create | { model, data, session?, options? } | ServiceResult<T \| T[], 'doc'> | Creates one document (data an object) or many (data an array). | | find | { model, query?, options?, session? } | ServiceResult<unknown[], 'docs'> & { pagination? } | Finds all matching documents. options.pagination = { include, includeCount } to get pagination metadata back. | | findOne | { model, query?, options?, session? } | ServiceResult<unknown, 'doc'> | Finds a single matching document. | | findById | { model, _id, session?, options? } | same as findOne | findOne scoped to { _id }. | | updateOne | { model, query?, data, session?, options? } | MutationResult | Updates one matching document, doesn't return it (isDocumentUpdated). | | updateMany | { model, query?, data, session?, options? } | MutationResult | Updates every matching document (areDocumentsUpdated). | | findOneAndUpdate | { model, query?, data, session?, options? } | ServiceResult<unknown, 'doc'> & { isDocumentUpdated? } | Updates one matching document and returns it. | | findByIdAndUpdate | { model, _id, data, session?, options? } | same as findOneAndUpdate | findOneAndUpdate scoped to { _id }. | | findAllAndUpdate | { model, query?, data, session?, options? } | ServiceResult<unknown[], 'docs'> | Updates every matching document and returns them all. | | deleteOne | { model, query?, session?, options? } | MutationResult | Soft-deletes one matching document by default (sets the configured softDeleteField); pass options.hardDelete: true to actually remove it. | | deleteMany | { model, query?, session?, options? } | MutationResult | Same as deleteOne, for every matching document. | | findOneAndDelete | { model, query?, session?, options? } | ServiceResult<unknown, 'doc'> & { isDocumentDeleted? } | Deletes one matching document and returns it. | | findByIdAndDelete | { model, _id, session?, options? } | same as findOneAndDelete | Scoped to { _id }. | | findAllAndDelete | { model, query?, session?, options? } | ServiceResult<unknown[], 'docs'> | Deletes every matching document and returns them all. | | countDocuments | { model, query?, options?, session? } | { success, error?, count? } | Counts matching documents (soft-deleted ones excluded by default). | | aggregate | { model, pipeline, options? } | ServiceResult<unknown[], 'docs'> | Runs a raw aggregation pipeline. Mongo only — see below. |

query is a mongoose FilterQuery. options is IMongoOptions:

interface IMongoOptions {
  includeDeleted?: boolean; // include soft-deleted docs
  populateFields?: string; // mongoose .populate() path(s)
  fieldsInclusion?: {
    // projection
    include?: string[];
    exclude?: string[];
    includeSpecificFields?: string[];
  };
  pagination?: {include?: boolean; includeCount?: boolean};
  hardDelete?: boolean; // deleteOne/deleteMany: actually remove, don't soft-delete
}

Why aggregate is only here, not in /services/prisma: Mongo's aggregation pipeline has no Prisma equivalent this library wraps. Rather than exporting it from both and throwing at runtime for a Prisma consumer, it simply doesn't exist to import on the Prisma side — you find out at import time, not at call time.

Also exported: IFieldsInclusion, IMongoOptions, MutationResult, ServiceResult, ClientSession, FilterQuery (types, for annotating your own service functions' params/returns).


/services/prisma

Same 15 functions as /services/mongo, minus aggregate, forwarding straight to PrismaFactories. model is always a Prisma model delegate (e.g. prismaClient.user).

| Function | Signature | Notes vs. the mongo version | | ------------------------------------------------------------------------------------------ | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | create | { model, data } | No session/options — Prisma create doesn't need them. | | find | { model, query?, options?, session? } | session is accepted but ignored (Prisma has no session concept — pass a transaction client as model instead, see /utils/prisma). | | findOne | { model, query?, options?, session? } | Uses Prisma's findFirst under the hood. | | findById | { model, _id, options? } | findOne scoped to { _id }. | | updateOne / updateMany | { model, query?, data, options? } | Same shape as mongo. | | findOneAndUpdate / findByIdAndUpdate / findAllAndUpdate | same param shape as mongo, no session | Prisma has no atomic "find and update in one call" — these find the match first, then update by its id. Not atomic; wrap in /utils/prisma's wrapWithTransaction if you need atomicity. | | deleteOne / deleteMany / findOneAndDelete / findByIdAndDelete / findAllAndDelete | same shape as mongo | Soft-delete by default via the configured softDeleteField, same as mongo. | | countDocuments | { model, query?, options? } | — |

options is IPrismaOptions:

interface IPrismaOptions {
  includeDeleted?: boolean;
  pagination?: {include?: boolean; includeCount?: boolean};
  hardDelete?: boolean;
  select?: Record<string, boolean>; // passthrough straight to Prisma's own `select`
}

Also exported: IPrismaOptions, MutationResult, ServiceResult (types).


/utils/mongo

Standalone mongo helpers you call directly in your own code — not part of connecting (/config/mongo) or defining a schema (/schema/mongo).

| Export | What it does | | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | startSession() | Mongoose's own startSession — for a consumer that wants to manage a transaction session by hand instead of using wrapWithTransaction. | | wrapWithTransaction()({ fn }) | Curried helper: start a session, run fn(session), commit on success / abort on throw, always end the session. Pass the returned session into /services/mongo calls' session param to run them inside the transaction. | | isObjectIdOrHexString(value) | Validates a value looks like a Mongo ObjectId (24-hex-char string or an actual ObjectId) — for your own validators/schemas. | | mergeAndDeduplicateFields({ fields?, scopedTo? }) | Merges two field-name arrays and dedupes — building block for compound-unique-index field lists (e.g. a soft-delete plugin combining a unique field with tenant-scoping fields). |

import {wrapWithTransaction} from '@desolint/db-factories/utils/mongo';
import * as DbFactory from '@desolint/db-factories/services/mongo';

await wrapWithTransaction()({
  fn: async (session) => {
    await DbFactory.create({model: UsersModel, data: userData, session});
    await DbFactory.create({model: OrganizationsModel, data: orgData, session});
  },
});

/utils/prisma

| Export | What it does | | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | wrapWithTransaction({ client })({ fn }) | Curried helper matching mongo's shape, but Prisma's transaction model is a whole different client passed into the callback (tx), not a session object passed alongside the same model reference — use tx.user etc. inside fn, not client.user. |

import {wrapWithTransaction} from '@desolint/db-factories/utils/prisma';
import * as DbFactory from '@desolint/db-factories/services/prisma';

await wrapWithTransaction({client: prismaClient})({
  fn: async (tx) => {
    await DbFactory.create({model: tx.user, data: userData});
    await DbFactory.create({model: tx.organization, data: orgData});
  },
});

/utils/config/mongo

Global, in-memory tuning for how /services/mongo's CRUD functions behave — not connection setup (that's /config/mongo). Call once at app startup; every later /services/mongo call picks it up automatically, since MongoFactories reads the current config internally on every call.

| Export | What it does | | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | configureMongoFactories({ queryTransformer?, paginationProvider?, softDeleteField? }) | Sets any subset of the three. Unset fields keep whatever they already were. | | getMongoFactoriesConfig() | Reads the current config. | | resetMongoFactoriesConfig() | Restores the built-in defaults. Mainly for test isolation (e.g. a beforeEach), so one test's config doesn't leak into the next. |

Defaults: queryTransformer is a no-op (({query}) => query), paginationProvider returns { page: 1, limit: 20, sort: { createdAt: -1 } }, softDeleteField is 'deletedAt'.

import {configureMongoFactories} from '@desolint/db-factories/utils/config/mongo';

// Scope every query app-wide to the current tenant, without adding
// { organizationId } to every single /services/mongo call by hand.
configureMongoFactories({
  queryTransformer: ({query}) => ({
    ...query,
    organizationId: getCurrentTenantId(),
  }),
  softDeleteField: 'removedAt',
});

/utils/config/prisma

Same three functions, Prisma-shaped config, plus one extra field:

| Export | What it does | | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | configurePrismaFactories({ queryTransformer?, paginationProvider?, softDeleteField?, idField? }) | idField — Prisma has no fixed primary-key field name the way Mongo always has _id; defaults to 'id'. | | getPrismaFactoriesConfig() | — | | resetPrismaFactoriesConfig() | — |

If you don't need any of this (no soft-delete field rename, no app-wide query scoping, default pagination shape is fine) — you'll never call these, and that's fine. It's dependency injection for the CRUD factories' default behavior, not something every consumer needs to touch.


/testing

Mongo only. Spins up a real, disposable MongoDB (via mongodb-memory-server) and connects the same shared mongoose singleton /config/mongo uses — so tests exercise real Mongo behavior instead of a separate mocked path. mongodb-memory-server is an optional peer dependency; it's only required if you actually import this subpath.

| Export | What it does | | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | connectMockMongoose() | Starts a disposable in-memory MongoDB, connects mongoose to it, waits for the connection to actually open. Returns { mongoServer, mongooseInstance }. | | disconnectMockMongoose({ mongoServer, mongooseInstance }) | Drops the database, disconnects mongoose, stops the disposable server. |

import {
  connectMockMongoose,
  disconnectMockMongoose,
} from '@desolint/db-factories/testing';

let mongoServer, mongooseInstance;

beforeAll(async () => {
  ({mongoServer, mongooseInstance} = await connectMockMongoose());
});

afterAll(async () => {
  await disconnectMockMongoose({mongoServer, mongooseInstance});
});

Return-shape reference

Every /services/* function returns one of these two shapes:

// Reads (create, find, findOne, findOneAndUpdate, findOneAndDelete, aggregate, ...)
type ServiceResult<T, K extends string> = {
  success: boolean;
  error?: unknown;
} & Partial<Record<K, T | null>>;
// e.g. { success: true, doc: {...} } or { success: false, error: ... }

// Writes with no document returned (updateOne, updateMany, deleteOne, deleteMany)
interface MutationResult {
  success: boolean;
  error?: unknown;
  responseObj?: unknown;
  isDocumentUpdated?: boolean;
  areDocumentsUpdated?: boolean;
  isDocumentDeleted?: boolean;
  areDocumentsDeleted?: boolean;
  deleteType?: 'hardDelete' | 'softDelete';
}

Every function returns { success: false, error } on failure instead of throwing — check success before trusting the rest of the shape.


Soft delete

deleteOne/deleteMany/findOneAndDelete/findByIdAndDelete/findAllAndDelete soft-delete by default: they set the configured softDeleteField (default 'deletedAt') instead of removing the row/document. Every read (find/findOne/countDocuments/...) implicitly excludes soft-deleted records unless you pass options.includeDeleted: true. Pass options.hardDelete: true to a delete call to actually remove the record. Change the field name via configureMongoFactories/configurePrismaFactories (/utils/config/{mongo,prisma}).


Development

npm install     # install dependencies
npm run build   # type-check, then bundle each subpath into dist/
npm test        # jest
npm run lint    # eslint

scripts/build.mjs bundles each subpath into one self-contained JS file plus a .d.ts, then deletes everything else from dist/ — internal modules (src/mongodb/*, src/prisma/*, src/shared/*) never ship, so there is nothing for an editor or a moduleResolution: "node" consumer to resolve beyond the eleven public subpaths documented above.


License

MIT © Desolint — see LICENSE.

Free to use, modify and redistribute, commercially or otherwise. Provided "as is", without warranty or liability of any kind.