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

@next-model/redis-connector

v1.2.1

Published

Redis connector for next-model. Stores rows as HASH per id with a ZSET of ids per table.

Downloads

428

Readme

@next-model/redis-connector

@next-model/core connector backed by Redis (via node-redis).

Heads-up. Redis is a key-value store, not a relational database. This connector loads a table's id set and fetches every row to evaluate filters / aggregates client-side. It's a great fit for fixture-style tables, queues, and small projection caches; not the right choice as the primary store for million-row queries that need indexed predicates.

Installation

pnpm add @next-model/redis-connector redis
# or: npm install @next-model/redis-connector redis

Constructing the connector

import { RedisConnector } from '@next-model/redis-connector';

// node-redis options (url / socket / etc.)
const c1 = new RedisConnector({ client: { url: 'redis://localhost:6379' } });

// Bring your own client (caller controls connect/disconnect)
import { createClient } from 'redis';
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();
const c2 = new RedisConnector({ redis, prefix: 'myapp:' });

// Programmatic shutdown
await c1.connect();    // owns its client → safe to call repeatedly
await c1.destroy();    // .quit() the client we created; injected clients are left alone

The underlying client is exposed as connector.client.

Attaching a typed schema

Pass a DatabaseSchema (from @next-model/core's defineSchema(...)) as the optional second arg so Model({ connector, tableName: 'users' }) can infer per-table props at the type level:

import { defineSchema } from '@next-model/core';

const schema = defineSchema({
  users: { columns: { id: { type: 'integer', primary: true }, email: { type: 'string' } } },
});

const connector = new RedisConnector({ client: { url: process.env.REDIS_URL } }, { schema });

Existing call sites without { schema } keep working unchanged. Redis enforces no DB schema at runtime, so this is purely a type-level convenience for inferring Model props.

Wiring a Model

import { Model } from '@next-model/core';
import { RedisConnector } from '@next-model/redis-connector';

const connector = new RedisConnector({ client: { url: process.env.REDIS_URL } });
await connector.connect();

class Note extends Model({
  tableName: 'notes',
  connector,
  init: (props: { title: string; body: string }) => props,
}) {}

await Note.create({ title: 'Hello', body: 'world' });
await Note.count(); // 1

Storage layout

All keys are prefixed with the configured prefix (default nm:):

| Key | Purpose | |-----|---------| | {prefix}{table}:meta | JSON-serialised table definition. Acts as the existence flag for hasTable. | | {prefix}{table}:nextid | INCR counter for auto-increment numeric ids. | | {prefix}{table}:ids | ZSET of all row ids (score = numeric id) for ordered iteration. | | {prefix}{table}:row:{id} | HASH of column → JSON-encoded value. |

Values inside the row hash are JSON-encoded so booleans, numbers, Date (round-tripped via { __date__: ISO }), null, arrays, and nested objects all survive a round-trip.

Feature → connector specifics

Filter operators

The connector loads the entire id set for the table, fetches every row, then evaluates the filter via core's filterList. Every operator from the core DSL ($and, $or, $not, $in, $notIn, $null, $notNull, $between, $notBetween, $gt/$gte/$lt/$lte, $like, $async, $raw) works the same way it does on MemoryConnector.

Ordering / limit / skip

Applied client-side after filtering. Default order matches insert order (ZSET score = id).

Aggregates

SUM/MIN/MAX/AVG are computed client-side over the filtered rows.

batchInsert

Each row gets:

  1. An id assigned from INCR {prefix}{table}:nextid (for KeyType.number), crypto.randomUUID() (for KeyType.uuid), or the caller-supplied value (for KeyType.manual).
  2. An HSET writing every column.
  3. A ZADD to {prefix}{table}:ids so the row participates in the table scan.

updateAll / deleteAll

Resolve the scope (filter + load), then iterate the matching rows and issue per-row HSET (update) or DEL + ZREM (delete). Returns the affected rows.

execute(query, bindings)

Raw escape hatch — sends [query, ...bindings] via client.sendCommand. Use it for SET/GET/PUBLISH/SUBSCRIBE/etc.

Transactions

transaction(fn) snapshots every table currently in the keyspace at the start of the block, runs fn, and on throw drops every table that exists at the end + restores from snapshot. Concretely:

  • It is not MULTI/EXEC. Reads inside fn see writes inside fn immediately (which MULTI/EXEC doesn't allow).
  • It is not isolated against concurrent clients — another connection writing to the same prefix during the block will be silently overwritten on rollback.

This matches the in-memory / sqlite snapshot semantics, which is what the conformance suite expects. Pick @next-model/postgres-connector (or another SQL connector) if you need real ACID isolation.

Schema DDL

createTable writes the JSON-serialised defineTable(...) output to {prefix}{table}:meta. Columns are advisory (Redis is schemaless) but defineTable still validates the blueprint at create time.

dropTable SCANs for keys matching {prefix}{table}:* and DELs them.

Testing matrix

CI runs the shared runModelConformance suite plus driver-specific tests against a real Redis 7 service container.

Locally:

REDIS_URL=redis://127.0.0.1:6379 pnpm --filter @next-model/redis-connector test

Changelog

See HISTORY.md.