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

@assemora/core

v0.2.6

Published

Application kernel: modules, container, context, Command Bus, Event Bus, errors

Readme

@assemora/core

Application kernel: modules, container, context, Command Bus, Event Bus, errors.

Implementation phase: 1 — implemented.

Core owns the single mutation path of SPEC.md §14 and knows nothing about HTTP or a database.

const PublishPage = command('pages.publish', {
  input: { id: uuid() },
  handle: async ({ id }, context) => {
    context.revise({ entityType: 'page', entityId: id, before, after })
    context.emit('page.published', { pageId: id })
    return { id }
  },
})

const app = createApplication({
  modules: [module('pages').commands(PublishPage)],
  authorization: permitAll(), // development only — the default denies everything
})

await app.run({ source: 'mcp', actor: { type: 'agent', id: 'writer' } }, () =>
  app.commands.execute(PublishPage, { id }),
)

Validation, authorization, transaction, handler, revisions, events and audit happen in that order for every caller — Studio, REST, the SDK, the CLI and MCP alike.

Jobs

A job is the third member of the family beside command() and query(): work that must happen, must survive a restart, and must not happen inside the request (SPEC.md §82).

const GenerateSitemap = job('sitemap.generate', {
  description: 'Rebuilds the sitemap after a page changes',
  input: { pageId: uuid() },
  retries: 3,
  handle: async ({ pageId }, context) => {
    await context.commands.execute(RebuildSitemap, { pageId })
  },
})

const app = createApplication({
  modules: [module('pages').commands(PublishPage).jobs(GenerateSitemap)],
  authorization: permitAll(),
  // Omitted, jobs run in this process, awaited. `@assemora/queue-bullmq` is the
  // production adapter.
  queue: bullmqQueue({ connection }),
})

GenerateSitemap({ pageId }) validates the payload and runs nothing, so a wrong payload is a ValidationError where it was written — including a payload no queue could carry, such as one holding undefined.

await dispatch(...) holds the job until the outermost transaction commits. That is TransactionPort.afterCommit, and it is a transaction concept rather than a command one: a command's own transaction may be a savepoint inside one that is still free to undo everything it wrote, and a job that ran against that would run against a world that never existed. So a command that rolls back queues nothing, a command inside a transaction() that rolls back queues nothing, a nested command whose caller survives its failure queues nothing, and a dry run queues nothing at all. Events are emitted at the same moment and by the same rule.

Outside a command the job is handed over immediately, unless a transaction() is open — then it waits for that one.

A worker calls runJob(queued), which restores the actor and the request id of the operation that scheduled the work and runs the job with source: 'job'.

Where a stage needs a layer above core, core owns the interface and the other package registers an implementation (ADR-0008). Authorization defaults to denyAll(): an application with no policy provider refuses every command instead of running unauthorized.

Settings

What a module wants the settings screen to say about it (ADR-0031). A group is declarative data — a section, a label, blocks of rows that are a value or a link — registered under the module's name in the settings section of the Schema Registry, where Studio draws it and assemora.describe answers with it.

module('search').settings({
  name: 'search',
  section: 'platform',
  label: { en: 'Search', uk: 'Пошук' },
  icon: 'gauge',
  blocks: [
    {
      title: 'Index',
      locked: true,
      rows: [{ key: 'search.engine', kind: 'value', label: 'Engine', value: 'Meilisearch' }],
    },
  ],
})

A group written out is checked by settingsGroup() where it is written; a group given as a function is called at boot, for a module whose values are handed to it after it was written. A word may be a string or a map keyed by language tag — Studio picks and never translates. There is no input row: a setting somebody changes is a command.

Workspace dependencies

  • @assemora/schema

Dependency direction is fixed in docs/architecture/package-graph.md and enforced by pnpm boundaries.