@equinor/fusion-framework-module-context

中文文档
9.1.0发布于 2026/11/12ISCTypeScriptES Module近 30 天下载 1.4万
原始使用文档
文档目录19 节
  1. When to use
  2. How it fits together
  3. Documentation
  4. Key concepts
  5. Quick start
  6. Operational telemetry
  7. Configuration
  8. Configuration reference
  9. Context Routing in the URL
  10. Declare a routing strategy
  11. Custom URL shapes
  12. How it works (for reference)
  13. Events
  14. Errors
  15. Testing
  16. Defaults
  17. Generate deterministic context fixtures
  18. Utilities
  19. Package exports

@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

TopicDescription
Data modelThe shape of a ContextItem, how context types relate to each other, and query/related parameters
LifecyclesetCurrentContext's validate/resolve decision flow, automatic initial-context resolution on startup, and parent/child context propagation
RecipesOData query parameters, path rewriting, accepting a family of context types, and custom search errors

Key concepts

ConceptDescription
ContextItemA typed record representing a single context entity (project, facility, etc.).
ContextProviderRuntime service exposing query, set, validate, resolve, and event APIs.
ContextModuleConfiguratorFluent builder for configuring context types, filters, clients, and hooks.
enableContextHelper that registers the module on a modules configurator.
Context resolutionAutomatic lookup of related context items when a context type does not match the configured types.
Parent connectionBi-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();

Operational telemetry

IContextProvider extends TelemetryConnectable. The provider uses telemetry.scope('context'), falling back to the lazily loaded factory for older custom providers, and closes the shared handle on disposal; selection/resolution observation remains in the context module.

When the host registers telemetry, each queued setCurrentContext task reports one fusion.context.select.duration outcome. Each resolution attempt reports fusion.context.resolve.duration, whether it comes from selection, a direct resolveContext/resolveContextAsync call or a configured resolver (by: 'item'), or from the lookup behind setCurrentContextById (by: 'id'):

  • failure with reason validation when validation fails and resolution is disabled;
  • cancelled with reason listener when an onSetContextResolve, onSetContextResolved or onCurrentContextChange listener calls preventDefault();
  • cancelled when a newer task supersedes the task, the caller unsubscribes, or the provider is disposed (reason disposed).

Re-setting the current item without validation is skipped and reports nothing. Context ids, titles and values are never exported, and context behavior is unchanged. Synchronous resolver factory failures are recorded and rethrown synchronously without changing the error object. A final onCurrentContextChange veto records cancellation immediately, including on the existing detached-error path; telemetry does not change that path's caller behavior. See Operational telemetry.

Configuration

All configuration flows through enableContext → ContextModuleConfigurator:

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 methodPurpose
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 extractContextIdFromPath → resolveContextFromPath)
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"
    }
  }
}
ValueURL ShapeWhen to Use
'path' (or omitted)/apps/{appKey}/{contextId}/sub-routeDefault — 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 can omit contextRouting and register custom hooks instead (see below). An explicit contextRouting: 'query' always selects query routing, even when custom hooks are registered.

[!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 for path routing or when no strategy is declared. An explicit contextRouting: 'query' selects the query adapter instead.

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.

EventWhenCancelable
onCurrentContextChangeBefore the current context is updatedYes
onCurrentContextChangedAfter the current context has changedNo
onParentContextChangedBefore a parent context change is mirrored locallyYes
onSetContextResolveBefore context resolution beginsYes
onSetContextResolvedAfter context resolution completesYes
onSetContextValidationFailedWhen validation fails (no resolution)No
onSetContextResolveFailedWhen resolution fails with an errorNo
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 layer — setCurrentContext, setContexts, addContext, setRelatedContexts — a context-domain vocabulary for the common case: seed a known item, get it back.
  • Escape hatch — setResolver — 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:

ExportPurpose
enableContextRegister the context module on a configurator
resolveInitialContextDefault initial-context resolver (path → parent fallback)
extractContextIdFromPathExtract a GUID-format context ID from a URL path
resolveContextFromPathCreate a resolver function that fetches a context item from a URL path

Package exports

SpecifierDescription
@equinor/fusion-framework-module-contextMain entry — module, provider, configurator, types
@equinor/fusion-framework-module-context/errors.jsFusionContextSearchError
@equinor/fusion-framework-module-context/utilsUtility functions for path resolution and enablement
@equinor/fusion-framework-module-context/mockenableContextMock, ContextMockConfigurator — in-memory test double
@equinor/fusion-framework-module-context/mock/fixturesFixture factories for generating seeded context items

版本与发布记录

@equinor/fusion-framework-module-context 共发布 195 个版本,首次发布于 2022/10/26;过去 12 个月发布了 17 个版本。

版本发布日期距今
9.1.0latest2026/11/121 天前
9.1.0-next.1next2026/11/112 天前
9.1.0-next.02026/10/2816 天前
9.0.32026/10/2717 天前
9.0.3-trusted-publishing-20261015-9dfe7602cd2a49a8a9df00d62c26e7304a82a85atrusted-publishing-202610152026/10/1629 天前
9.0.22026/10/121 个月前

其他发布通道:apploader → 6.0.4-apploader-be21a6b60eb13eee8bf869e75002d8634e9c50a9,cli-search-index → 7.0.3-cli-search-index.0,msal-v5 → 7.0.3-msal-v5.0,trusted-publishing-20261015 → 9.0.3-trusted-publishing-20261015-9dfe7602cd2a49a8a9df00d62c26e7304a82a85a,next → 9.1.0-next.1。

关于 @equinor/fusion-framework-module-context 的常见问题

如何安装 @equinor/fusion-framework-module-context?

在项目目录运行 npm install @equinor/fusion-framework-module-context,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:rxjs(^7.0.0)、semver(^7.0.0)、@types/semver(^7.0.0)、@equinor/fusion-framework-module(^6.2.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@equinor/fusion-framework-module-context 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@equinor/fusion-framework-module-context 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@equinor/fusion-framework-module-context 可以商用吗?

@equinor/fusion-framework-module-context 使用 ISC 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@equinor/fusion-framework-module-context 的下载量有多少?

根据 npm 官方下载统计,@equinor/fusion-framework-module-context 在 2026/10/14 – 2026/11/12 这 30 天内被下载 14,276 次,日均约 476 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@equinor/fusion-framework-module-context 还在维护吗?

最新版本 9.1.0 发布于 2026/11/12(1 天前);至今共发布 195 个版本,其中过去 12 个月发布了 17 个;首次发布于 2022/10/26。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。