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

@primafuture/contrib-kit-react

v1.0.0

Published

React adapter for Contrib Kit with headless outlets, scoped registries, managed lazy loading, and SSR support.

Readme

@primafuture/contrib-kit-react

Typed React contribution components and synchronous resolved Core views for Contrib Kit. The package provides typed point helpers, a scoped registry provider, useResolvedContributions(), and a headless ExtensionOutlet with logical placement, isolated error boundaries, policy, retry, and commit-only managed-lazy loading.

Install the framework-neutral Core and React peers together:

pnpm add @primafuture/contrib-kit-core @primafuture/contrib-kit-react react react-dom
import {
  defineReactContributionComponent,
  defineReactExtensionPoint,
  defineReactLazyContribution,
  ExtensionOutlet,
  ExtensionRegistryProvider,
  useResolvedContributions,
} from "@primafuture/contrib-kit-react";
import { createExtensionRegistry } from "@primafuture/contrib-kit-core";

interface PanelContext {
  readonly locale: string;
}

interface PanelData {
  readonly title: string;
}

const Panel = defineReactContributionComponent(
  function Panel(props: {
    readonly context: PanelContext;
    readonly contribution: {
      readonly contributionId: string;
      readonly data: PanelData;
    };
    readonly placement: {
      readonly index: number;
      readonly count: number;
      readonly isFirst: boolean;
      readonly isLast: boolean;
    };
  }) {
    return `${props.context.locale}: ${props.contribution.data.title}`;
  },
);

const panels = defineReactExtensionPoint<PanelContext, PanelData>()({
  pointId: "example.panels",
  contractVersion: 1,
  cardinality: "many",
});

const lazyPanel = defineReactLazyContribution({
  async load() {
    return Panel;
  },
});

const registry = createExtensionRegistry();
registry.rootScope.registerPoint(panels);
const registration = registry.registerContribution(panels, {
	contributionId: "example.panel",
	expectedContractVersion: 1,
	implementation: lazyPanel,
  data: { title: "Overview" },
});

await registration.whenActive();

function PanelList() {
  const { state, refresh } = useResolvedContributions({
    point: panels,
    context: { locale: "en" },
  });

  if (state.status === "failed") {
    return <button onClick={refresh}>Retry resolution</button>;
  }

  return (
    <ul>
      {state.contributions.map((contribution) => (
        <li key={contribution.registrationId}>{contribution.data.title}</li>
      ))}
    </ul>
  );
}

const app = (
  <ExtensionRegistryProvider registry={registry}>
    <PanelList />
    <ExtensionOutlet
      point={panels}
      context={{ locale: "en" }}
      renderLoading={({ contribution }) => (
        <span>Loading {contribution.data.title}…</span>
      )}
      renderContributionError={({ error, retry }) => (
        <button onClick={retry}>{error.code}</button>
      )}
    />
  </ExtensionRegistryProvider>
);

await registry.dispose();

The provider owns only React adapter subscriptions; the host still owns and disposes the Core registry and contribution handles. useResolvedContributions() returns a frozen synchronous view, classifies empty results as noRegistrations or excludedByPolicy, and exposes a local refresh() that never commits Core state. ExtensionOutlet uses no mandatory DOM wrapper, derives frozen placement from the final Core order, preserves retained membership state across reorder, and confines each contribution behind its own policy-controlled boundary. The lazyPanel descriptor starts its loader only after a committed client passive effect. One retry generation owns at most one loader attempt and AbortController; fulfillment renders with the latest logical placement, while loader failures use the same contribution policy and retry pipeline as render failures. Lifecycle loss always removes UI and callback authority before dispatching an idempotent abort. A later loader settlement cannot change the UI; while its original outlet and scope epoch are still current, it may emit one safe lateLazyResult diagnostic without the value or rejection cause. A current rejection named AbortError remains a normal REACT_LAZY_LOAD_FAILED outcome. The host still owns any data fetching or module-loader strategy used inside load().

In React 19, placing a provider subtree inside <Activity> closes adapter-owned subscriptions and pending lazy work while hidden without discarding React membership state. Reveal catches up Core changes before the first visible view, preserves ready components and failed presentation, and restarts only a still-pending lazy attempt with a new abort signal. React 18 keeps the same Strict Mode lifecycle without an Activity-specific API requirement.

Server rendering

Traditional renderToString() uses the same public provider and outlet. Create one mutable Core registry per request, register the shared immutable point contracts, await every contribution that the response requires, and dispose the registry in the request cleanup path:

import { renderToString } from "react-dom/server";

async function renderRequest(locale: string): Promise<string> {
  const identifierPrefix = "request-a-";
  const requestRegistry = createExtensionRegistry();
  requestRegistry.rootScope.registerPoint(panels);
  const requestPanel = requestRegistry.registerContribution(panels, {
    contributionId: "request.panel",
    expectedContractVersion: 1,
    implementation: lazyPanel,
    data: { title: "Overview" },
  });

  try {
    await requestPanel.whenActive();
    return renderToString(
      <ExtensionRegistryProvider registry={requestRegistry}>
        <ExtensionOutlet
          point={panels}
          context={{ locale }}
          renderLoading={({ contribution, placement }) => (
            <span data-position={`${placement.index}/${placement.count}`}>
              Loading {contribution.data.title}…
            </span>
          )}
        />
      </ExtensionRegistryProvider>,
      { identifierPrefix },
    );
  } finally {
    await requestRegistry.dispose();
  }
}

Server rendering synchronously reads the already committed Core view. Eager contributions render normally; managed-lazy contributions render their deterministic loading state without starting the loader or creating an AbortController. No adapter subscriber, effect lease, sink callback, or error policy runs on the server. Descendant render failures pass through to the host's server error handling.

Hydration

Before hydrateRoot(), reconstruct the same committed logical view in a new client-owned Core registry. Point and contribution IDs, contract versions, data, context, order, selection result, eager/lazy kind, and render props must match the server response. Runtime registration IDs do not need to match and must not be serialized into HTML or bootstrap data.

import { hydrateRoot } from "react-dom/client";

const clientRegistry = createExtensionRegistry();
clientRegistry.rootScope.registerPoint(panels);
const clientPanel = clientRegistry.registerContribution(panels, {
  contributionId: "request.panel",
  expectedContractVersion: 1,
  implementation: lazyPanel,
  data: { title: "Overview" },
});

await clientPanel.whenActive();

const clientRoot = hydrateRoot(
  document.querySelector("#app")!,
  <ExtensionRegistryProvider registry={clientRegistry}>
    <ExtensionOutlet
      point={panels}
      context={{ locale: "en" }}
      renderLoading={({ contribution, placement }) => (
        <span data-position={`${placement.index}/${placement.count}`}>
          Loading {contribution.data.title}…
        </span>
      )}
    />
  </ExtensionRegistryProvider>,
  { identifierPrefix: "request-a-" },
);

async function disposeClient(): Promise<void> {
  clientRoot.unmount();
  await clientRegistry.dispose();
}

The identifierPrefix must exactly match the corresponding server renderToString() call; simultaneous roots need distinct prefixes. The first hydration render uses the same logical snapshot and loading placement as the server. A managed-lazy loader starts only after the client tree commits. If setup or hydration fails before the host transfers ownership to its normal lifecycle, attempt React root unmount first and Core registry disposal second, preserving the primary error and all cleanup failures.

Fast Refresh and Core HMR

React component refresh and Core point refresh are two independent development lifecycles. Keep a component-only React Refresh boundary separate from point definition and registration orchestration. A valid component-only edit can preserve eligible local React state without committing Core state, re-registering the contribution, or changing its activation identity.

For a point definition edit, pass the real bundler hot context and a stable hotKey through defineReactExtensionPoint():

export const panels = defineReactExtensionPoint<PanelContext, PanelData>()({
  pointId: "app.panels",
  contractVersion: 1,
  cardinality: "many",
  filter(panel, context) {
    return panel.data.area === context.area;
  },
}, {
  hot: import.meta.hot,
  hotKey: "app.panels",
});

import.meta.hot?.accept();

A compatible Core refresh keeps the canonical point and active registration IDs, emits pointDefinitionRefreshed, and lets exact React consumers resolve the new filter, ordering, or selection result atomically. An incompatible point contract is rejected by Core with INCOMPATIBLE_HOT_UPDATE and delegates to the host invalidation path without publishing a partial adapter view.

React Refresh state preservation remains a development-time best effort. Changing a module's non-component exports can invalidate its refresh boundary and legitimately produce a full reload or importer invalidation. Changing a managed-lazy loader or its descriptor is likewise not an adapter Fast Refresh contract: use an explicit Core contribution replacement or re-registration when the loader identity must change.

Workspace examples

  • The readable React SPA combines the provider, resolved hook, headless outlet, Core ordering and selection, logical placement, isolated failures, retry, managed lazy loading, Activity hide/reveal, and component-only Fast Refresh.
  • The React SSR and hydration example demonstrates request-local registries, activation readiness, renderToString(), matching hydrateRoot() ownership, post-commit lazy loading, and root-before-registry cleanup.

These applications provide readable development evidence. The exact-version documentation and isolated compatibility fixtures cover the package and release boundaries independently.

License

ISC