@collective-intelligence/extensions-sdk-frontend-react

中文文档
0.2.10发布于 2026/11/09TypeScriptES Module近 30 天下载 7667

Public hosted React contribution contracts for Collective Intelligence extensions.

原始使用文档
文档目录10 节
  1. Frontend runtime
  2. Workflow UI
  3. Studio API
  4. Studio view context
  5. Studio API on Effect
  6. Writes in a Studio Widget
  7. Building in the workspace
  8. Enum value spelling
  9. Query related records
  10. Host-provided workflow User selection

@collective-intelligence/extensions-sdk-frontend-react

Public, host-neutral contracts for React contributions rendered by Collective Intelligence Studio or Dashboard.

React and React DOM are host-provided peers. The optional @collective-intelligence/ui-react peer is required when importing the /workflow or /ui-react subpaths; declaration-only consumers do not need it.

effect (^4.1.0) is a required peer: the app installs one copy of effect, and the package build keeps it external. The /effect entrypoint exports the StudioApi service; see Studio API on Effect. It also exports fromEffect, EffectRuntimeProvider, useEffectRuntime and QueryCache, the parts of a write in a Studio Widget; see Writes in a Studio Widget. They need the required xstate peer, which the Studio and Dashboard Hosts provide.

import { declareFrontendCatalogContribution } from "@collective-intelligence/extensions-sdk-frontend"
import { declareStudioComponent } from "@collective-intelligence/extensions-sdk-frontend-react/studio"
import { customerPageProps } from "./schemas"

const sourceRef = "com.acme.crm.studio.customer-view"

export const studioCatalog = declareFrontendCatalogContribution({
  fqn: sourceRef,
  host: "studio",
  load: () => import("./studio-catalog")
})

export const customerPage = declareStudioComponent({
  definition: {
    category: "page",
    propsSchema: customerPageProps,
    sourceRef
  },
  manifest: {
    fqn: "com.acme.crm.studio.customer-page",
    version: "1.0.0"
  },
  presentation: { displayName: "Customer" }
})

Declarations are data: they do not register with Studio or Dashboard and do not own module delivery, host resolution, or error-boundary rendering. Catalog Hosted Contributions come from the React-free frontend SDK and can be passed to declarePackage({ hostedContributions: [...] }); their lazy load functions never enter the serialized package manifest. This React package intentionally does not expose the retired leaf declareStudioView or declareDashboardWorkflowField helpers.

sourceRef is required on every Studio component render leaf and remains in the serialized package manifest. It identifies the same package's hosted Studio contribution; it is not a source path or an alternate runtime URL.

Frontend runtime

The frontend entrypoint provides the shared React execution boundary for declarations from @collective-intelligence/extensions-sdk-frontend:

import {
  createHostedDiagnostic,
  HostedExtensionRender,
  loadDeclarationModule
} from "@collective-intelligence/extensions-sdk-frontend-react/frontend"

;<HostedExtensionRender
  declaration={declaration}
  componentProps={{ customerId: "customer-1" }}
  load={() => loadDeclarationModule(declaration)}
/>

The frontend entrypoint owns module loading, React component validation, and error-boundary diagnostics. It also exports createHostedDiagnostic and the HostedDiagnostic, HostedDiagnosticPhase, and SourceMapLocation types for hosts that report failures outside the provided render boundary. Host applications still own catalog activation, routing, and product-specific fallback UI.

Wrap the Host tree in HostedExtensionFailureProvider to replace the plain failure message with the Host's own design-system UI. Its render callback receives the HostedDiagnostic. Load failures are also logged with console.error together with their original cause; React logs render failures.

HostedExtensionRender keeps the mounted component when props or callback identities change, preserving local edits and focus. Loading restarts when the declaration object or implementationRole changes; hosts should keep declarations stable until the contribution changes. Pending load failures use the latest onDiagnostic callback.

Workflow UI

The workflow entrypoint provides host-neutral workflow field and node renderers, catalog activation, and query helpers:

import {
  WorkflowFieldResolver,
  activateWorkflowCatalog,
  workflowFieldOptions
} from "@collective-intelligence/extensions-sdk-frontend-react/workflow"

Field bindings use inputBinding for node inputs and parameter for node config. The exported read and patch helpers preserve sibling values and store field display labels under the same canonical binding key in metadata. Stored {mode: "fixed"} envelopes and compiled literals are unwrapped at every path segment when a field renderer or a visibleWhen rule reads a binding; expression values pass through unchanged. Visibility equals compares JSON structurally, and in matches a scalar value or any member of a multi-value field. CatalogNodeDetailsEditor renders sections and fields by ascending order (default 0, ties keep declaration order). resource_selector and credential_selector fields that resolve only to a widget fallback render as the remote-options select, which shows the field's declared placeholder.

WorkflowFieldFrame and ExpressionSlot accept optional help text, shown below the field label. Custom fixed and expression controls should forward the render prop’s describedBy to their input’s aria-describedby, preserving any existing description IDs. The built-in expression editor combines this contextual help with validation descriptions. Read-only fields show the saved Fixed or Expression mode as text.

Opening expression suggestions preserves focus in the expression input or textarea, so typing continues in the authored value. The suggestion search remains explicitly focusable for filtering and inserting available values.

Hosts provide remote workflow-field options through configureWorkflowFieldOptionsProvider. The SDK keeps that adapter outside the public package so the same workflow UI can be used with different backend clients and host applications.

Hosts can provide context.chart.presentation with an icon React component (accepting an optional className) and a tone CSS class string. Use the node type’s semantic icon and its category’s styling consistently in the catalog and canvas. CatalogNodeChartBlock applies the supplied tone to the card and icon badge, ahead of the renderer’s legacy accent. The default charts and built-in chart overrides that delegate to this component share this behavior. Without presentation, standalone consumers retain the kind-based icon and accent. The host owns identity resolution; the public SDK does not import host catalogs or private presentation registries.

Hosts give context.template.label and context.template.description to a hosted renderer as WorkflowLocalizedText: { kind: "literal", text } or { kind: "translation", default }. A Host whose catalog transport uses another shape converts it before it builds the context. CatalogNodeChartBlock shows context.chart.title and context.chart.description when the Host sets them, and the template text otherwise.

The chart exposes every declared input and output with its port name, direction, connection limit, and allowed peer constraints. Port targets are 20 pixels wide; the card grows to keep dense port lists and contextual add controls separated.

Studio API

The studio-api entrypoint is a generated client for the backend operations that hosted Studio Widgets are meant to use. The backend marks each such operation with the widget-public OpenAPI tag. The tag selects what the client generates; it is not an authorization boundary. Each request runs with the signed-in user's own permissions, and the backend authorizes it like any other request. The entrypoint exports the SDK functions, their types, TanStack Query options, mutation options and query keys, and the client instance. The Studio Host binds that client to its authenticated transport; Widgets do not configure credentials. Every Widget shares the one bound client, so the Host locks it: client.setConfig and every client.interceptors change throw. Set headers and other request options per call.

Studio view context

The Studio Host gives every placed view and Widget component a context prop next to its configured props. The /studio entrypoint types it as StudioViewComponentProps:

type StudioViewContext = Readonly<{
  appId: string
  appSlug: string
  organizationId: string
  pageId: string
  publishedRevisionId: string
  viewId: string
}>
type StudioViewComponentProps = Readonly<{
  context: StudioViewContext | null
}>

Studio generates view, page, and revision ids when an app is installed or published, so a declaration cannot name them; read them from context. context is null when the view has no published placement: in the builder preview, without an Organization, or before the app has a published revision. Hide or disable the API calls of the Widget in that case.

studioViewScope(context) returns the headers and path options of the view-data operations. studioPublishedViewScope(context) returns the headers and query options of operations on a published view, such as studioWorkflowRunsList:

import type {
  StudioViewComponentProps,
  StudioViewContext
} from "@collective-intelligence/extensions-sdk-frontend-react/studio"

import { studioViewScope } from "@collective-intelligence/extensions-sdk-frontend-react/studio"
import { studioViewDataSearchOptions } from "@collective-intelligence/extensions-sdk-frontend-react/studio-api"
import { useQuery } from "@tanstack/react-query"

const CustomerRows = ({ context }: { context: StudioViewContext }) => {
  const rows = useQuery(
    studioViewDataSearchOptions({
      ...studioViewScope(context),
      body: { limit: 10 }
    })
  )
  return <output>{rows.status}</output>
}

export const CustomerWidget = ({ context }: StudioViewComponentProps) =>
  context === null ? null : <CustomerRows context={context} />

src/studio-api/ is generated and committed. After a backend change to a marked operation, run bun run --filter @repo/backend api:openapi, then bun run api:codegen:studio-api in this package. CI runs api:codegen:studio-api:check and fails when the committed client is stale. A new operation also needs its method in StudioApi; a test fails until it has one.

Studio API on Effect

The /effect entrypoint exports the StudioApi service. It has one method per operation of the studio-api client, with the same name, request options and success type. StudioApi calls the shared studio-api client: it imports the client through ./studio-api, so a Studio Widget calls the client that the Studio Host binds. A Studio Extension release bundles /effect and keeps /studio-api external; the release build needs a ci CLI version that bundles an SDK ./effect subpath (see the CLI changelog). Only the Studio Host provides /studio-api. A Dashboard release bundles its own copy of the client, and no Host binds that copy.

/effect imports each operation of /studio-api by name. In a Studio Extension, the Studio Host provides that module. If the /studio-api of the Host does not export one of these operations, /effect does not load, also for a Widget that does not call that operation. Use /effect only with a Studio Host that ships this SDK version or a later one that keeps each operation.

import type { StudioViewContext } from "@collective-intelligence/extensions-sdk-frontend-react/studio"

import {
  layerStudioApi,
  StudioApi
} from "@collective-intelligence/extensions-sdk-frontend-react/effect"
import { studioPublishedViewScope } from "@collective-intelligence/extensions-sdk-frontend-react/studio"
import * as Effect from "effect/Effect"

const runCount = (context: StudioViewContext) =>
  Effect.gen(function* () {
    const api = yield* StudioApi
    const page = yield* api.studioWorkflowRunsList(
      studioPublishedViewScope(context)
    )
    return page.data.length
  }).pipe(
    Effect.catchTag("StudioApiError", (error) => Effect.fail(error.status)),
    Effect.provide(layerStudioApi())
  )
  • An error answer fails with StudioApiError. It holds the status and the body that the client read (JSON, else text). The client does not check body, and a status outside the generated error map, such as a 502 of a proxy, holds any body. So body is unknown: decode it with a Schema before use.
  • A request that does not reach the Backend, or an OK answer that is not readable, fails with StudioApiTransportError, which holds the cause.
  • Bad input, such as a body that JSON cannot write, is a TypeError Defect. The request does not leave.
  • An interruption of the fiber aborts the request.
  • The service sets client, fetch, baseUrl, signal, responseStyle and throwOnError. Set headers and the other request options per call.

layerStudioApi({ baseUrl }) sends each request to baseUrl. A Studio Widget gives no base URL: the Host binds the client to its Backend and refuses other URLs. A host that does not bind the client, such as a test or a preview, gives the URL of its Backend. That URL is deployment configuration, so layerStudioApiConfig({ baseUrl: Config.String("STUDIO_API_BASE_URL") }) reads it with Effect Config.

Writes in a Studio Widget

A Studio Widget follows the write convention of a detached app, and the ci-solution/effect-writes and ci-solution/effect-actors lint rules apply to it: a write runs in a fromEffect XState actor, views read with useQuery, and after the write the actor refreshes the reads through QueryCache. /effect exports these parts:

  • EffectRuntimeProvider gives its tree an Effect runtime with the QueryCache over the nearest QueryClient, which is the QueryClient of the Studio Host. The Host mounts no Effect runtime. Each widget mounts EffectRuntimeProvider at its root: the bundle of each widget has its own copy of /effect, so it does not see the provider of another bundle.
  • useEffectRuntime() gives that runtime. A view passes it to the machines it starts, in their input.
  • fromEffect(run) makes XState actor logic that runs the effect of run on input.runtime. A stop of the actor interrupts the fiber; after a stop, no result of that run gets to the actor, also in a React Strict Mode remount.
  • QueryCache has fetch, invalidate, cancel, setData, update and remove. invalidate, cancel, update and remove apply to the key and to each key below it. fetch runs its query once, without a retry, and fails with StudioApiError or StudioApiTransportError; another failure is a Defect. A cancel of the key interrupts the query; the waiting fetch then returns the cached value, or is interrupted when the key has no value. cancel cancels the running fetches, so they write no value; it keeps the cached values and starts no fetch. An optimistic write calls cancel, then update, runs the write, and calls invalidate when the write settles. The Host and all widgets share one QueryClient, so start each key with the package name.
  • makeTestQueryCache(options?) gives a machine test the QueryCache over a real QueryClient of its own: layer for ManagedRuntime.make, queryClient to seed and to assert on, and calls, one { member, queryKey } for each call that ran. options.overrides replaces only the members that it names; options.seed holds the first entries. Test with it, never with a double made with QueryCache.of({ ... }), which breaks each time QueryCache gets a member. It adds no dependency, and a widget bundle that does not import it does not contain it.
import type { EffectRunner } from "@collective-intelligence/extensions-sdk-frontend-react/effect"
import type { StudioViewContext } from "@collective-intelligence/extensions-sdk-frontend-react/studio"

import {
  EffectRuntimeProvider,
  fromEffect,
  layerStudioApi,
  QueryCache,
  StudioApi
} from "@collective-intelligence/extensions-sdk-frontend-react/effect"
import { studioViewScope } from "@collective-intelligence/extensions-sdk-frontend-react/studio"
import * as Effect from "effect/Effect"
import { setup } from "xstate"

const customersKey = ["com.acme.crm", "customers"] as const

type RenameInput = Readonly<{
  context: StudioViewContext
  instanceId: string
  name: string
  runtime: EffectRunner<QueryCache>
}>

export const renameMachine = setup({
  actors: {
    rename: fromEffect((input: RenameInput) =>
      Effect.gen(function* () {
        const api = yield* StudioApi
        const scope = studioViewScope(input.context)
        yield* api.studioViewDataUpdateItem({
          ...scope,
          body: { name: input.name },
          path: { ...scope.path, instanceId: input.instanceId }
        })
        const cache = yield* QueryCache
        yield* cache.invalidate(customersKey)
      }).pipe(Effect.provide(layerStudioApi()))
    )
  }
}).createMachine({/* states that invoke "rename" with the input above */})

// The widget root: the runtime is over the QueryClient of the Studio Host.
export const CustomersWidget = (props: CustomersViewProps) => (
  <EffectRuntimeProvider>
    <CustomersView {...props} />
  </EffectRuntimeProvider>
)

xstate (^5.33.3) is a required peer. The Studio and Dashboard Hosts provide their one copy of xstate, and the ci CLI keeps xstate external in both Hosts' artifacts, so a widget bundle carries no copy of XState. The first Hosts that provide xstate ship in the first ci-app release after @collective-intelligence/[email protected], together with the CLI that keeps it external: a release built with that CLI or a later one runs on Hosts of that ci-app release or a later one. A widget that uses @xstate/react bundles it; it imports the Host copies of react and xstate. Import XState only from xstate: the Host provides that one name, and a release bundles a subpath such as xstate/actors, which is a second copy of XState. The Host does not check the xstate version of a widget: build against the xstate 5 that the Hosts ship.

Building in the workspace

This package needs the built @collective-intelligence/ui-react declarations to compile its own declarations. From the repository root, run bunx turbo run build --filter=@collective-intelligence/extensions-sdk-frontend-react to build this package and its dependencies in order. The workspace development dependency on ui-react supplies that build ordering; it remains an optional peer for consumers of the published package.

Hosted module references may supply a load callback alongside module and exportName. loadDeclarationModule prefers that callback for the requested implementation role, falling back to the render role when absent. Without a callback it imports the module locator. Keep actual renderer functions reachable through the callback so bundlers retain them in catalog-only entrypoints. The callback is a runtime sidecar and is excluded from canonical package metadata.

Hosted workflow field, configuration, and template declarations require a load callback. Their render paths reject locator-only implementations so hosted bundles contain no computed imports outside the declared module graph. The general loadDeclarationModule API retains its locator fallback for host-owned loading outside those workflow render paths.

The package build emits production JSX while keeping React and React DOM external. The built /frontend renderer is tested with both production and development consumer React peers, including its loading state and loaded component props.

Enum value spelling

Repository-owned enum values use lower snake_case. This includes component kinds, dataset statuses, schema consumer profiles, and hosted contribution identity. Notification enum values retain their existing spelling. External protocol values, including HTTP methods and JSON Schema keywords and formats, retain the protocol spelling.

The generated /studio-api export includes studioViewDataSearchMembers. It queries POST /studio/apps/by-slug/{slug}/views/{viewId}/data/{instanceId}/relation-members/search with the published view's authorization scope. Its generated body accepts the relation field, filters, sort, cursor, limit and include paths. Filters and sort address the related schema. Forward and inverse fields use the same operation, including scalar relations (zero or one result). Preserve the generated response's pagination envelope.

A successful page and its relation IDs are complete. Per-field includes can carry the existing truncation marker; paging within nested includes is not supported. A typed KnowledgeReadLimitExceeded response requires narrowing the query, page, or includes; do not silently trim the response or retry the unchanged oversized request.

Host-provided workflow User selection

WorkflowFieldComponentContext.userSelection is an optional Host renderer for Organization member selection. It receives multiple, value, onChange, controlId, labelledBy, readOnly, and required; it returns a React node. Values and changes contain only user IDs (null for an empty single selection, [] for an empty list). The Host binds resource-authorized search and selected-ID resolution; public contributions never import the Host's private picker or data client. A contribution that requires this renderer must show an unavailable state if the Host omits it, rather than accepting arbitrary text input.

Catalog adapters preserve the explicit field.user: { multiple } descriptor in WorkflowFieldCatalogContract. A UUID schema does not activate User selection.

版本与发布记录

@collective-intelligence/extensions-sdk-frontend-react 共发布 46 个版本,首次发布于 2026/10/08;过去 12 个月发布了 46 个版本。

版本发布日期距今
0.2.10latest2026/11/09今天
0.2.92026/11/091 天前
0.2.82026/11/091 天前
0.2.72026/11/091 天前
0.2.62026/11/073 天前
0.2.52026/11/073 天前

关于 @collective-intelligence/extensions-sdk-frontend-react 的常见问题

@collective-intelligence/extensions-sdk-frontend-react 是什么?

Public hosted React contribution contracts for Collective Intelligence extensions.

如何安装 @collective-intelligence/extensions-sdk-frontend-react?

在项目目录运行 npm install @collective-intelligence/extensions-sdk-frontend-react,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(>=18 <20)、effect(^4.1.0)、xstate(^5.33.3)、react-dom(>=18 <20)、@xyflow/react(>=12 <13)、@tanstack/react-query(>=5 <6)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@collective-intelligence/extensions-sdk-frontend-react 支持 TypeScript 吗?

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

@collective-intelligence/extensions-sdk-frontend-react 支持 ESM 还是 CommonJS?

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

@collective-intelligence/extensions-sdk-frontend-react 可以商用吗?

@collective-intelligence/extensions-sdk-frontend-react 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@collective-intelligence/extensions-sdk-frontend-react 的下载量有多少?

根据 npm 官方下载统计,@collective-intelligence/extensions-sdk-frontend-react 在 2026/10/11 – 2026/11/09 这 30 天内被下载 7,667 次,日均约 256 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@collective-intelligence/extensions-sdk-frontend-react 还在维护吗?

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