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

@absolutejs/sync-pack-utils

v0.1.1

Published

Shared helpers for @absolutejs/sync packs — resolveActor, requireRowOwner/Moderator, createInMemoryStore, and other patterns extracted from the official packs

Downloads

358

Readme

@absolutejs/sync-pack-utils

Shared helpers for @absolutejs/sync packs. Each helper closes one repeated pattern that showed up across the six official packs (presence, comments, digest, notifications, favorites, counters). The goal: make new packs — first- or third-party — cheaper to write and consistent with what's already shipped.

bun add @absolutejs/sync-pack-utils

API

Actor resolution

import {
	defaultGetActorId,
	resolveActor,
} from '@absolutejs/sync-pack-utils';

// (ctx) => ctx.userId — the conventional default.
const getActorId = config.getActorId ?? defaultGetActorId<MyCtx>();

// Inside a mutation handler: throws UnauthorizedError if no actor id.
// The context string ends up in the error message so logs name the op.
const actorId = resolveActor(getActorId, ctx, 'mypack:create');

Permission builders

import {
	requireRowOwner,
	requireOwnerOrModerator,
} from '@absolutejs/sync-pack-utils';

permissions: {
	[table]: {
		read: ...,
		// Owner-only writes — uses row.actorId by default; override the
		// field name for packs that use authorId, userId, etc.
		insert: requireRowOwner(getActorId, 'actorId'),
		update: requireRowOwner(getActorId, 'actorId'),
		// Owner OR moderator delete. Falls back to store.getById when
		// actions.delete supplied only the row key (the common case).
		delete: requireOwnerOrModerator({
			actorIdField: 'actorId',
			canModerate,        // optional
			getActorId,
			store,              // { getById }
		}),
	},
},

requireRowOwner and requireOwnerOrModerator are higher-order — call them once at pack-build time to receive the per-row predicate the engine expects. Both reject when getActorId(ctx) is undefined (anonymous callers can't own a row).

In-memory store

import {
	createInMemoryStore,
	type InMemoryStore,
} from '@absolutejs/sync-pack-utils';

type FavoriteRow = { id: string; actorId: string; /* ... */ };

const store: InMemoryStore<FavoriteRow> =
	config.store ?? createInMemoryStore<FavoriteRow>();

engine.registerPack(defineSyncPack({
	// ...
	readers: { favorites: store.reader },
	writers: { favorites: store.writer },
	// store.getById is available for permission + mutation handler use.
}));

createInMemoryStore<Row>() returns:

  • reader: TableReader<CollectionContext>all() returns [...rows.values()]
  • writer: TableWriter<Row> — upsert-by-id; delete removes by row.id
  • getById(id) — point lookup
  • rows: Map<string, Row> — direct access (treat as read-only)

Every official pack uses this as its default backing store and exposes a store?: ... config field so consumers can swap in a Postgres / Drizzle / Redis implementation when they need persistence.

What's intentionally not here

  • prefixed(prefix, ...): just use template literals. ${prefix}<my-collection> is fine.
  • defineActorPack(...): a Convex-style abstraction that wraps defineSyncPack with permission defaults. Decided against — it'd hide the engine surface, which is the thing pack authors learn first.
  • Validators / Zod adapters: out of scope; sync's field helper + schema is the validation layer.

Pairing with @absolutejs/sync/testing

Pack tests typically pair with @absolutejs/sync/testing (added in sync 1.9.2):

import { expectRejection } from '@absolutejs/sync/testing';
import { resolveActor } from '@absolutejs/sync-pack-utils';

const error = await expectRejection(() => {
	resolveActor(getActorId, {}, 'mypack:do');
	return Promise.resolve();
});
expect(error).toBeInstanceOf(UnauthorizedError);

Versioning

This package stays 0.x indefinitely while the ecosystem settles. When the six official packs are stable, the helpers will follow.