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

@newcms/database

v0.3.0

Published

Database schema, object cache, and repositories for NewCMS

Readme

@newcms/database

Database schema, Redis object cache, and repositories for NewCMS — a modern, TypeScript-first content management system.

Installation

npm install @newcms/database

Requirements: PostgreSQL 17+ and Redis 7+.

Database Schema

Type-safe Drizzle ORM schema with 14 tables covering the full CMS data model:

import {
  posts, postmeta,
  users, usermeta,
  comments, commentmeta,
  terms, termTaxonomy, termRelationships, termmeta,
  options,
  links,
  sessions,
  scheduledEvents,
} from '@newcms/database/schema';

Tables

| Table | Purpose | |-------|---------| | posts | All content types (posts, pages, attachments, custom types) | | postmeta | Post metadata (EAV with JSONB support) | | users | User accounts | | usermeta | User metadata | | comments | Threaded comments | | commentmeta | Comment metadata | | terms | Taxonomy terms | | term_taxonomy | Term-taxonomy relationships with hierarchy | | term_relationships | Object-term assignments | | termmeta | Term metadata | | options | Site settings (with autoload and JSONB) | | links | Blogroll / link manager | | sessions | User sessions (Redis-backed with DB fallback) | | scheduled_events | Cron-like scheduled tasks (BullMQ-backed) |

Key Design Decisions

  • JSONB columns on all meta tables — queryable structured data instead of opaque serialized text
  • GIN indexes on JSONB columns for efficient structured queries
  • Composite indexes on frequently queried combinations (e.g., post_type + status + date)
  • Dedicated tables for sessions and scheduled events (instead of serialized blobs in options/usermeta)

Connection

import { createConnection } from '@newcms/database';

// From environment variables (DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD)
const { db, client } = createConnection();

// Or with explicit config
const { db, client } = createConnection({
  host: 'localhost',
  port: 5432,
  database: 'newcms',
  user: 'newcms',
  password: 'your-secure-password',
});

// Use Drizzle query builder
const allPosts = await db.select().from(posts);

Note: DB_PASSWORD is required — there is no default. Set it in your environment or .env file.

Object Cache

Redis-backed object cache with group isolation, TTL, and multisite support:

import { ObjectCache } from '@newcms/database';

const cache = new ObjectCache({
  host: 'localhost',
  port: 6379,
});
await cache.connect();

// Basic get/set
await cache.set('my_key', { hello: 'world' }, 'my_group');
const value = await cache.get('my_key', 'my_group');

// TTL (in seconds)
await cache.set('temp', 'data', 'default', 300); // expires in 5 min

// Batch operations (uses Redis pipeline)
await cache.setMultiple({ a: 1, b: 2, c: 3 }, 'counters');
const results = await cache.getMultiple(['a', 'b', 'c'], 'counters');

// Increment/decrement
await cache.set('views', 0);
await cache.incr('views'); // => 1

// Group-level TTL
cache.setGroupTtl('transients', 3600); // 1 hour for all transients

// Multisite: global groups shared across sites
cache.addGlobalGroups(['users', 'site-options']);
cache.setSiteId(2); // switch site context

// Flush
await cache.flushGroup('posts'); // flush one group
await cache.flushAll();          // flush everything

Options Repository

CRUD for site options with integrated Redis cache:

import { createConnection, ObjectCache, OptionsRepository } from '@newcms/database';

const { db } = createConnection();
const cache = new ObjectCache({ host: 'localhost', port: 6379 });
await cache.connect();

const options = new OptionsRepository(db, cache);

// Read (with cache — hits DB only on first read)
const siteName = await options.getOption('blogname');
const perPage = await options.getOption('posts_per_page', 10); // with default

// Write (invalidates cache automatically)
await options.updateOption('blogname', 'My New Site');
await options.addOption('custom_setting', { enabled: true, threshold: 50 });

// Delete
await options.deleteOption('old_setting');

// Bootstrap: pre-load all autoloaded options into cache
const allOptions = await options.loadAutoloadedOptions();

Cache Behavior

  • Autoloaded options are bulk-loaded into Redis on bootstrap
  • Cache-aside pattern — reads check cache first, fall back to DB
  • Not-found caching — missing keys are cached to prevent repeated DB misses
  • Granular invalidation — writes invalidate only the affected key
  • JSONB — complex values (objects, arrays) stored in queryable JSONB column

Environment Variables

| Variable | Required | Default | Description | |----------|----------|---------|-------------| | DB_HOST | No | localhost | PostgreSQL host | | DB_PORT | No | 5432 | PostgreSQL port | | DB_NAME | No | newcms | Database name | | DB_USER | No | newcms | Database user | | DB_PASSWORD | Yes | — | Database password | | DB_MAX_CONNECTIONS | No | 10 | Connection pool size |

License

GPL-2.0-or-later