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

@equinor/fusion-framework-module-context

v9.0.0

Published

Readme

@equinor/fusion-framework-module-context

Context module for the Fusion Framework. Manages the active context (project, facility, contract, etc.) within Fusion-based applications and portals, providing query, validation, resolution, and parent–child synchronization out of the box.

When to use

Use this module when your application or portal needs to:

  • set, read, or clear the active context (e.g. selecting a project)
  • search and filter available context items from the Fusion context API
  • validate whether a given context item matches the allowed context types
  • resolve context across parent–child provider boundaries (portal → app)
  • deep-link by extracting a context ID from the URL path

Application developers will typically use the higher-level @equinor/fusion-framework-react-app/context package, which wraps this module with React hooks and providers.

Portal developers will use @equinor/fusion-framework-react/context for the same purpose at the portal level.

How it fits together

A context module instance never lives in isolation. It depends on the @equinor/fusion-framework-module-services module for its default HTTP client, optionally uses @equinor/fusion-framework-module-event to dispatch and listen for context change events, and optionally reads the current URL through @equinor/fusion-framework-module-navigation to resolve a context from the path. None of these need manual setup — enableContext requires only services; event and navigation are picked up automatically when present.

The module is also hierarchy-aware by default: when a Portal and an App both enable context, the App's instance automatically connects to the Portal's and mirrors its context — no wiring required beyond enabling the module on both sides. See Lifecycle for exactly how that sync works, and when it can be overridden or disabled.

Documentation

| Topic | Description | |---|---| | Data model | The shape of a ContextItem, how context types relate to each other, and query/related parameters | | Lifecycle | setCurrentContext's validate/resolve decision flow, automatic initial-context resolution on startup, and parent/child context propagation | | Recipes | OData query parameters, path rewriting, accepting a family of context types, and custom search errors |

Key concepts

| Concept | Description | |---|---| | ContextItem | A typed record representing a single context entity (project, facility, etc.). | | ContextProvider | Runtime service exposing query, set, validate, resolve, and event APIs. | | ContextModuleConfigurator | Fluent builder for configuring context types, filters, clients, and hooks. | | enableContext | Helper that registers the module on a modules configurator. | | Context resolution | Automatic lookup of related context items when a context type does not match the configured types. | | Parent connection | Bi-directional sync between a parent portal context and a child app context. |

Quick start

import { enableContext } from '@equinor/fusion-framework-module-context';

export const configure = (configurator) => {
  enableContext(configurator, (builder) => {
    // only accept ProjectMaster context items
    builder.setContextType(['ProjectMaster']);
  });
};

Once initialized, access the provider from the module instance:

// observe context changes
modules.context.currentContext$.subscribe((ctx) => {
  console.log('context changed', ctx);
});

// search for context items
const items = await modules.context.queryContextAsync('Johan');

// set context by item
await modules.context.setCurrentContextAsync(items[0]);

// set context by ID
await modules.context.setCurrentContextByIdAsync('7fd97952-...');

// clear the active context
modules.context.clearCurrentContext();

Configuration

All configuration flows through enableContextContextModuleConfigurator:

enableContext(configurator, (builder) => {
  // restrict accepted context types
  builder.setContextType(['ProjectMaster', 'Facility']);

  // post-query filter
  builder.setContextFilter((items) => items.filter((i) => i.isActive));

  // custom parameter mapping for the search API
  builder.setContextParameterFn(({ search, type }) => ({
    search,
    filter: { type },
  }));

  // custom validation logic
  builder.setValidateContext(function (item) {
    return item !== null && this.validateContext(item);
  });

  // custom context resolution
  builder.setResolveContext(function (item) {
    return this.relatedContexts({ item, filter: { type: ['ProjectMaster'] } });
  });

  // connect (or disconnect) from parent context
  // when enabled (default), onParentContextChanged fires before mirroring the parent's context locally
  builder.connectParentContext(false);

  // path ↔ context integration
  // the default resolver uses extractContextIdFromPath to pull a GUID from the URL,
  // then fetches the matching context item — see resolveInitialContext in utils
  builder.setContextPathExtractor((path) => path.split('/')[2]);
  builder.setContextPathGenerator((ctx, path) =>
    path.replace(/\/context\/[^/]+/, `/context/${ctx.id}`),
  );

  // provide a fully custom context client
  builder.setContextClient({
    get: (args) => fetch(`/api/context/${args.id}`).then((r) => r.json()),
    query: (args) => fetch(`/api/context?q=${args.search}`).then((r) => r.json()),
  });
});

Configuration reference

| Builder method | Purpose | |---|---| | setContextType(types) | Allowed context type IDs for validation | | setContextFilter(fn) | Post-query result filter | | setContextParameterFn(fn) | Maps search + type to API query params | | setValidateContext(fn) | Custom validation (this = provider) | | setResolveContext(fn) | Custom resolution (this = provider) | | connectParentContext(bool) | Enable/disable parent context sync (fires onParentContextChanged on change) | | setContextPathExtractor(fn) | Extract context ID from URL path | | setContextPathGenerator(fn) | Generate URL path from context item | | setResolveInitialContext(fn) | Override initial context resolution (default uses extractContextIdFromPathresolveContextFromPath) | | setContextClient(client) | Custom get/query/related clients |

Context Routing in the URL

The portal keeps the active context synchronized with the browser URL automatically via the @equinor/fusion-framework-plugin-context-navigation plugin. As an app developer, you control how context appears in your URL by declaring a routing strategy in your app manifest.

Declare a routing strategy

Set contextRouting in your manifest's build.options:

// app.manifest.config.ts
{
  "build": {
    "options": {
      "contextRouting": "query"
    }
  }
}

| Value | URL Shape | When to Use | |---|---|---| | 'path' (or omitted) | /apps/{appKey}/{contextId}/sub-route | Default — simple apps without complex path routing | | 'query' | /apps/{appKey}/route?$contextId={id} | Apps with nested routes that would conflict with a context path segment |

Apps with custom URL shapes should omit contextRouting and register custom hooks instead (see below).

[!NOTE] When contextRouting is not set (or set to null), the portal defaults to the path adapter which encodes context as a path segment after the app key. If your app also registers custom hooks (setContextPathExtractor / setContextPathGenerator), the custom adapter takes priority over the path adapter regardless of the contextRouting value.

Custom URL shapes

If your app uses a non-standard URL layout for context (e.g. /route-a/{contextId}), register custom hooks instead of (or in addition to) declaring contextRouting:

enableContext(configurator, (builder) => {
  builder.setContextType(['ProjectMaster']);

  // Tell the portal how to find the context id in your URL
  builder.setContextPathExtractor((path) => {
    const segments = path.split('/').filter(Boolean);
    return segments[1]; // e.g. /route-a/{contextId}
  });

  // Tell the portal how to build a URL with context
  builder.setContextPathGenerator((context, path) => {
    const segments = path.split('/').filter(Boolean);
    const route = segments[0] ?? '';
    return `/${route}/${context.id}`;
  });
});

When these hooks are registered, the portal's custom adapter uses them automatically — no contextRouting declaration needed in the manifest.

How it works (for reference)

The portal's context-navigation plugin reads contextRouting from your app's manifest at runtime and selects the appropriate URL adapter. You don't install or configure the plugin yourself — the portal handles it. Your app only needs to declare its preference.

Events

The context module dispatches events via the @equinor/fusion-framework-module-event system. All events are scoped to the ContextProvider source.

| Event | When | Cancelable | |---|---|---| | onCurrentContextChange | Before the current context is updated | Yes | | onCurrentContextChanged | After the current context has changed | No | | onParentContextChanged | Before a parent context change is mirrored locally | Yes | | onSetContextResolve | Before context resolution begins | Yes | | onSetContextResolved | After context resolution completes | Yes | | onSetContextValidationFailed | When validation fails (no resolution) | No | | onSetContextResolveFailed | When resolution fails with an error | No |

modules.event.addEventListener('onCurrentContextChanged', (e) => {
  console.log('previous:', e.detail.previous);
  console.log('next:', e.detail.next);
});

Errors

The module exports FusionContextSearchError (from @equinor/fusion-framework-module-context/errors.js) for search-related failures:

import { FusionContextSearchError } from '@equinor/fusion-framework-module-context/errors.js';

try {
  await modules.context.queryContextAsync('...');
} catch (err) {
  if (err instanceof FusionContextSearchError) {
    console.error(err.title, err.description);
  }
}

Testing

@equinor/fusion-framework-module-context/mock provides enableContextMock, a purpose-built test double backed by an in-memory pool of seeded context items — no context API, no HTTP mock, and no service-discovery mock required. Real ContextProvider behaviour (validateContext, resolveContext, parent-context propagation) still runs against the seeded data; only the data source is substituted.

Defaults

  • The context pool starts empty and no current context is selected.
  • Querying the empty pool returns no items.
  • Looking up an unseeded id throws an error that names the id and the seeding methods.
  • Related-context lookup uses the seeded pool, excludes the source item, and filters by the requested type.
  • setCurrentContext both seeds the item and selects it for initial app resolution; setContexts only seeds items.
import { enableContextMock } from '@equinor/fusion-framework-module-context/mock';

enableContextMock(configurator, (mock) => {
  mock.setCurrentContext({ id: 'my-ctx', type: { id: 'ProjectMaster' }, value: {} });
});
  • Friendly layersetCurrentContext, setContexts, addContext, setRelatedContexts — a context-domain vocabulary for the common case: seed a known item, get it back.
  • Escape hatchsetResolver — a raw resolution function for a custom resolveContext strategy or a shape the friendly layer did not cover.

When used with @equinor/fusion-framework/mock, FrameworkMockConfigurator.context returns this same configurator, so seeding happens directly through configurator.context with no callback needed.

This is one of two ways to fake context data in tests. The other is mocking the context API's HTTP responses directly (with configurator.http.addMiddleware(...), optionally paired with @equinor/fusion-framework-module-http/mock's createOpenApiMockMiddleware for faker-generated data straight from context's OpenAPI spec) — which exercises the real ContextModuleConfigurator/services/HTTP pipeline instead of substituting it. Reach for enableContextMock to seed one known item with no transport involved; reach for the HTTP middleware when a test needs to cover that pipeline itself. Fixture generators for realistic seeded data (createContextItemFactory, createContextItems) are available from @equinor/fusion-framework-module-context/mock/fixtures.

Generate deterministic context fixtures

The optional /mock/fixtures entry point uses @faker-js/faker to produce realistic but repeatable titles and readable ids. Install Faker only when importing this entry point.

import { enableContextMock } from '@equinor/fusion-framework-module-context/mock';
import { createContextItems } from '@equinor/fusion-framework-module-context/mock/fixtures';

const [project, contract] = createContextItems([
  { type: 'ProjectMaster' },
  { type: 'Contract', parentTypeIds: ['ProjectMaster'] },
]);

enableContextMock(configurator, (mock) => {
  mock.setContexts([project, contract]);
  mock.setCurrentContext(project);
});

Use createContextItemFactory(prefix?) for sequential fixtures of one type. Use createContextItems for multiple types and parent/child type metadata. Generated values are deterministic per id; use setRelatedContexts when relations must differ per specific item.

Utilities

Additional helpers are available from @equinor/fusion-framework-module-context/utils:

| Export | Purpose | |---|---| | enableContext | Register the context module on a configurator | | resolveInitialContext | Default initial-context resolver (path → parent fallback) | | extractContextIdFromPath | Extract a GUID-format context ID from a URL path | | resolveContextFromPath | Create a resolver function that fetches a context item from a URL path |

Package exports

| Specifier | Description | |---|---| | @equinor/fusion-framework-module-context | Main entry — module, provider, configurator, types | | @equinor/fusion-framework-module-context/errors.js | FusionContextSearchError | | @equinor/fusion-framework-module-context/utils | Utility functions for path resolution and enablement | | @equinor/fusion-framework-module-context/mock | enableContextMock, ContextMockConfigurator — in-memory test double | | @equinor/fusion-framework-module-context/mock/fixtures | Fixture factories for generating seeded context items |