You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Canonical inventory of every export from every published BPMCore package. This file is the contract. Any change to a libs/*/src/** export — adding, removing, renaming, or changing the visibility of a symbol — must update this file in the same commit.
Last verified against (2026-08-13, issues #7–#11): libs/shared@0.7.0, libs/bpm-core-client@0.7.0, libs/bpm-core@0.7.0 (@rytass/bpm-core-nestjs-module), libs/bpm-core-react@0.8.0. This change set adds form option source contracts, autocomplete schema support, source normalization, structural DataSource publish lint, the host registry contract, guarded GraphQL option queries, typed client catalog/preview/runtime wrappers, immutable client option-state and builder binding helpers, Mezzanine async renderer controls, runtime context wiring, server-side submit/resubmit resolution, persisted option snapshots, the reversible snapshot migration, the visual builder's catalog/binding/confirmation flow, explicit API-base URL normalization for the client GraphQL endpoint, legacy workflow edge-data normalization in the designer, a distinct unresolvable-value error code with client-side message mapping, and registry-less publish/submit guards for DataSource-backed fields. Versions are bumped by nx release at publish time — the numbers above are the last published ones, not the pending release.
Maintenance Contract
When you edit ANY of the following, you MUST also edit this file:
libs/shared/src/**
libs/bpm-core-client/src/**
libs/bpm-core/src/** (exposed as @rytass/bpm-core-nestjs-module)
libs/bpm-core-react/src/**
Any package.jsonexports map, tsconfig.base.json paths, or vite.config.tsPLANNED_ENTRIES for the four libs above
If a symbol moves between packages, both the source and destination sections need updating.
Bump the "Last verified against" line at the top to the current version of each affected package after editing.
Publish Procedure (DO NOT SKIP)
Versioning and changelogs — always nx release, never a manual bump
Do not hand-edit version in libs/*/package.json.nx release (config in
nx.json) owns the version numbers, the per-package CHANGELOG.md files, the git
tags and the GitHub releases for all four published packages. A manual bump
silently skips the changelog, so consumers get a release whose CHANGELOG.md
still ends at the previous version.
# Determines each group's bump from Conventional Commits since its last tag,# writes CHANGELOGs, commits and tags. Add --dry-run first to review.
npx nx release --skip-publish
All four packages form one fixed version set: a shared version number, a
v{version} tag, one workspace CHANGELOG.md, one GitHub release, and a
per-package CHANGELOG.md each. prepublish-check (typecheck + lint + test +
build + publint) runs for all four before versioning.
The trade-off of a fixed set is that any change bumps every package, so
bpm-core-react gets a new version even for a backend-only change. That is
deliberate: versioning it on its own cadence is what let it drift to 0.8.0 while
the core packages sat at 0.7.0.
Inter-package peerDependencies ranges are rewritten automatically, because
version.preserveMatchingDependencyRanges is false. Do not hand-maintain the
@rytass/bpm-core-* ranges. Aligning the set at 0.9.0 also collapsed
bpm-core-react's accumulated ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 into a
single ^0.9.0.
release.conventionalCommits.useCommitScope is false on purpose. With
nx's default (true), only commits whose scope matches a project name count
toward the bump and everything else is forced to patch — and this repo scopes
commits by domain (feat(template), fix(calendar)), which made a three-feature
release resolve to patch. Do not remove it.
Publishing — one command for all four
npx nx release publish # add --dry-run first
Each project declares its own nx-release-publish.packageRoot, so this publishes
every package from the correct directory. The two builders still emit to
different places, and that difference is now expressed in config rather than in
two hand-run procedures — mixing them up by hand has already caused a broken
0.1.3 release that had to be deprecated.
Package
Builder
packageRoot
@rytass/bpm-core-shared
@nx/js:tsc
dist/libs/shared
@rytass/bpm-core-nestjs-module
@nx/js:tsc
dist/libs/bpm-core
@rytass/bpm-core-client
@nx/js:tsc
dist/libs/bpm-core-client
@rytass/bpm-core-react
Vite library mode
libs/bpm-core-react
The three @nx/js:tsc packages publish from dist/libs/<pkg> because
generatePackageJson: true writes the consumer-facing manifest there, with
main pointing at compiled .js. Nevercd libs/<pkg> && npm publish for
those three — the source manifest points main at ./src/index.js but the
directory ships only .ts, so the tarball is non-functional. bpm-core-react is
the opposite: Vite emits into libs/bpm-core-react/dist/ and its in-tree
manifest already has files and main pointing there, so the lib directory is
the package root.
There is no post-build manifest fixup step. tools/publish/finalize-dist-package.mjs
used to inject "type": "commonjs" into the dist manifest; as of nx 22.7.1
generatePackageJson emits that field itself, verified by clean-rebuilding all
three packages, so the script and its finalize-dist targets were removed.
pkg-quality (publint) now depends directly on build.
Consumer setup gotchas
Hosts that consume @rytass/bpm-core-react through pnpm (strict mode) need this in their next.config.js, otherwise Next 16 Turbopack fails to resolve transitive peer deps like @rytass/bpm-core-client/workflow from inside the pnpm-isolated bpm-core-react dir:
SINGLE / SEQUENTIAL / PARALLEL_ALL / PARALLEL_ANY / QUORUM
ReturnBehavior
interface
Return-handling settings (incl. requireComment)
ReturnResubmitStrategy
type
'FROM_RETURN_POINT' | 'RESTART'
SlaConfig
interface
SLA timer config (incl. calendar)
SlaCalendarMode
type
'CALENDAR' | 'BUSINESS_DAY'
FieldPermission
interface
Per-node read/write permission
NotificationOverride
interface
Node-level notification overrides
NotificationChannel
type
'IN_APP' | 'EMAIL' | 'WEBHOOK'
ServiceAction
type
Action runnable by a serviceTask
GatewayDirection
type
'split' | 'join'
WorkflowEdge / WorkflowEdgeData
interface
Edge with optional condition
WorkflowEdgeConditionOperator
type
Edge condition operators
@rytass/bpm-core-shared/workflow-graph
Pure, framework-agnostic structural transforms over a WorkflowDefinition (no React, DOM, or dagre). Canonical home for the designer's graph operations; reused by the command layer below and the frontend.
Name
Kind
Purpose
NodePaletteType
type
'userTask' | 'serviceTask' | 'exclusiveGateway'
WorkflowConnectionCandidate
interface
Source/target/handle candidate for connection validation
FormService.createFormDefinition / updateFormDefinitionDraft /
publishFormDefinitionVersion / publishFormDefinitionContent accept an
optional trailing manager?: EntityManager (backward-compatible) so they can
join an outer transaction. Used by
TemplateService.composeApprovalTemplateWithForm.
Form definitions keep no draft in parallel with a published version: before
the first publish the single draft is updated in place; afterwards
publishFormDefinitionContent publishes a brand-new version directly
(content-identical saves are a no-op returning the current version).
@rytass/bpm-core-nestjs-module/form-data-source
Versioned host registry and guarded runtime boundary for dynamic form options.
formDataSources and previewFormFieldOptions require designer permission.
FORM_DATA_SOURCE_VALUE_NOT_RESOLVED marks a submitted value that is no longer
selectable, and stays distinct from FORM_DATA_SOURCE_INVALID_PROVIDER_RESULT
(a host provider contract breach) so renderers can tell a bad selection apart
from a broken source. A host without a registered registry keeps working for
static forms, but publishing or submitting a DataSource-backed field fails with
FORM_DATA_SOURCE_MISSING instead of silently skipping validation.
formFieldOptions requires authentication and derives the referenced source from
the published template version or returned-instance snapshot; clients cannot
submit an arbitrary source reference or binding definition.
composeApprovalTemplateWithForm mutation (ComposeTemplateMutations) builds
and optionally publishes a form definition together with the approval template
that binds it, atomically in a single DB transaction. The publish flag
toggles draft-only vs. publish-both. TemplateService methods
(createApprovalTemplate / updateApprovalTemplateDraft /
publishApprovalTemplateVersion / forkApprovalTemplate) accept an optional
trailing manager?: EntityManager for this composition (backward-compatible).
Template lifecycle.ApprovalTemplateEntity.isActive (column is_active,
NOT NULL DEFAULT true, matching ApprovalTemplateCategoryEntity) takes a
template out of service without deleting it or archiving its published version.
TemplateService adds activateApprovalTemplate(id) /
deactivateApprovalTemplate(id), mirroring the existing category pair, and
listApprovalTemplates accepts activationStatus typed as the new
ApprovalTemplateActivationStatusEnum (ACTIVE / ALL / INACTIVE, shaped
like ApprovalTemplateCategoryStatusEnum). This is deliberately notApprovalTemplateListStatusEnum — DRAFT / PUBLISHED is derived from version
state, an orthogonal dimension. Omitting activationStatus means ALL, so
existing callers are unaffected and admin screens still see deactivated
templates in order to reactivate them.
A deactivated template rejects submitApprovalInstanceandresubmitApprovalInstance with ConflictException('Approval template is deactivated'). The guard runs right after the template is loaded, ahead of
form-data validation, so callers get the lifecycle reason rather than a
misleading field error. launchableApprovalTemplates filters deactivated
templates out. Instances already in flight are unaffected — they run from
workflowSnapshot.
GraphQL surface added by the ad-hoc feature: mutations requestAdhocCountersign, requestAdhocPreApproval, configureAdhocStageNotification, configureAdhocCompletionNotification, cancelAdhocDirective; query adhocDirectives(instanceId). Countersign / pre-approval are gated by the node's allowAddSigner flag and only affect the single instance (never the template).
@rytass/bpm-core-nestjs-module/condition
Name
Purpose
ConditionService
Condition-expression evaluation engine
ConditionModule
Module wrapper
@rytass/bpm-core-nestjs-module/delegation
Category
Names
Entity
DelegationRuleEntity
DTOs
DelegationRuleInput
Enums
DelegationEnums
Service
DelegationService
Module
DelegationModule
@rytass/bpm-core-nestjs-module/calendar
Business-day SLA scheduling. BPMCore ships no national holiday data — hosts that need real working-day semantics register their own calendar.
Category
Names
Service
BPMSlaScheduleService (resolveTaskSlaDueAt({ node, now }))
BPM_BUSINESS_CALENDAR (host injects the calendar source)
Default
BPMWeekdayBusinessCalendar, defaultBusinessCalendarProvider (Mon–Fri, no holidays)
Module
CalendarModule, CalendarModuleOptions (global; wired by BPMRootModule). Options now extend Pick<ModuleMetadata, 'imports'>, and BPMRootModule threads its own imports through, so a useClass / useFactorybusinessCalendarProvider can depend on host repositories or config services without a host-side @Global() module
SlaConfig.calendar: 'BUSINESS_DAY' advances only the duration's day component across business days; an hour/minute component is added afterwards as plain elapsed time (the template linter warns when both are combined). Omitting calendar keeps the pre-0.7.0 elapsed-time behaviour.
@rytass/bpm-core-nestjs-module/notification
Category
Names
Services
NotificationService, NotificationDeliveryService
Entities
NotificationEntity, NotificationPreferenceEntity — hosts needing cross-recipient reads (delivery statistics, audit) can now get the repository type-safely instead of looking it up by entity-name string
SLA_ESCALATION_DELEGATION_REASON (delegation-chain marker that makes SLA ESCALATE idempotent)
Module
NotificationModule
Archiving.NotificationEntity.archivedAt separates cleared from my list
from read and from deleted — the row survives for statistics and audit.
NotificationService adds archiveNotifications({ ids, memberId }) /
unarchiveNotifications({ ids, memberId }) (both scoped to
recipientMemberId, so one member cannot archive another's notifications, and
both idempotent — re-archiving an archived row affects 0 rows).
listNotifications / countNotifications take includeArchived?: boolean,
defaulting to false, so existing callers are unaffected.
countUnreadNotificationsexcludes archived rows: otherwise the bell keeps
its badge after archiving and the action would be pointless. Archived and read
are independent dimensions — the archive filter is archivedAt IS NULL, not a
status value. GraphQL exposes archiveNotifications(ids) /
unarchiveNotifications(ids) mutations (member taken from
@BPMCurrentMemberId(), never a client argument) and an includeArchived
argument on the notifications / notificationCount queries.
React UI library. Four export families: root barrel (foundation + host integration widgets), next (Next.js wrapper), views/* (pure React page bodies — no layout shell), pages/* (Next.js Server Component shims).
Integration model (0.4.0+): BPMCore does not ship a navigation shell or sidebar. Hosts own the <Layout> / <Navigation> chrome and mount BPM views inside their existing layout. The root barrel exports building blocks the host wires into its own nav (useBPMMember, useBPMLogout, <BPMNotificationBellButton />). See docs/integration-guide.md and apps/client/src/app/_components/host-layout.tsx for a reference host layout.
Server route handler for the template-designer LLM assistant. The host wires it in one line (apps/client/src/app/api/chat/route.ts → export const POST = createWorkflowChatPOST()). Holds the OpenAI key, runs streamText, declares WORKFLOW_TOOLSET as tools with no execute (forwarded to the browser). Talks to OpenAI directly via @ai-sdk/openai (no AI Gateway); reads OPENAI_API_KEY (server-only) and BPM_LLM_MODEL (optional OpenAI model id, default gpt-5.4-mini).
Name
Kind
Purpose
createWorkflowChatPOST
function
Build the Next.js POST(request) handler; opts { model?, system? }
buildWorkflowAiSdkTools
function
Convert WORKFLOW_TOOLSET (JSON Schema) → AI SDK ToolSet (no execute)
WORKFLOW_CHAT_SYSTEM_PROMPT
const
Strict design-only guardrail prompt (zh-TW)
WorkflowChatRouteOptions
interface
{ model?, system? }
The chat UI itself (WorkflowChatDrawer, useWorkflowChat, useWorkflowDesignerController) is internal to the isolated views/templates/designer entry and not separately exported.
Views (pure React, require a RouterAdapter)
Group barrels (preferred for most consumers)
Subpath
Includes
views/workflow
InboxView, SentView, CcView, SearchView
views/instances
InstanceNewView (detail stays isolated due to weight)
Heavy views (must stay isolated, fat dependencies)
The packages below are declared as optionalpeerDependencies in
libs/bpm-core-react/package.json (peerDependenciesMeta). A host only has to
install the ones matching the heavy views it actually mounts; hosts that mount
none are not warned about missing peers. The version ranges pin what the
monorepo builds against, so upgrading @rytass/bpm-core-react now signals when
a peer needs bumping too.
FormBuilderView — controlled panel (value / onChange only; no standalone page mode). Embedded by the template designer and compose wizard; its option-field editor loads the host catalog, filters by capability, edits field/constant bindings, preserves references on field rename, and requires confirmation for source/mode/dependent-field impact before applying changes