@ssi-lib/flow-stepper
v2.0.5
Published
Stepper de flujo para orquestar micro-frontends remotos paso a paso.
Downloads
499
Readme
@ssi-lib/flow-stepper
Stepper de flujo para orquestar micro-frontends remotos paso a paso (ESM + CJS + tipos).
La librería no carga remotos: el consumidor inyecta renderRemote. La persistencia
usa @ssi-lib/store-management: el host pasa el adaptador en storeConfig;
FlowStepper registra el store y, al Continuar, dispara los callbacks del store.
Persistencia (mismo modelo que store-management)
| Capa | Quién | Nombres |
|------|--------|---------|
| Adaptador | Host en storeConfig | getStoredData, saveData, onError? |
| Callbacks del store | FlowStepper / leaf | getSavedData, saveData (saveSlots disponible en el store) |
| Memoria | FlowStepper / leaf | setSlotData, getSlotData |
onResultData (leaf) ──▶ snapshot en memoria ──▶ NO red
Continue / auto-avance ──▶ setSlotData + saveData(key) ──▶ adaptador saveData → backend
useSlotData (montaje) ──▶ getSavedData(key) ──▶ adaptador getStoredData → backendDetalle del store: ssi-store-management/README.md y
customFlowStorage.md.
Requisitos
Antes de montar FlowStepper, el consumidor debe:
- Instalar ambos paquetes (mismas versiones en host y remotes):
# registry
npm i @ssi-lib/flow-stepper @ssi-lib/store-management
# tarballs locales (desde la raíz del monorepo)
./createZip.sh
# en cada app de vivabox-project-core:
npm i ./ssi-lib-store-management-<ver>.tgz ./ssi-lib-flow-stepper-<ver>.tgz- Tener
react/react-dom(>=18) (peer deps). - Compartir
@ssi-lib/store-managementcomo MFsingleton(eager: truerecomendado) para que host y remotes vean el mismo registry pororderId. - Proveer
renderRemotey, si hay persistencia, el adaptador enstoreConfig(getStoredData/saveData). Sin adaptador, los callbacks del store no tienen backend.
Uso
import { FlowStepper, type InitSettings, type RenderRemote } from '@ssi-lib/flow-stepper';
import { LoadComponent } from '@ssi-lib/mf-loader';
const renderRemote: RenderRemote = (mfe, props) => (
<LoadComponent
lazyElement={{ mfe: mfe.name, url: mfe.url, component: mfe.component }}
{...props}
/>
);
const orderId = 1;
const initSettings: InitSettings = {
partyId: 1,
orderId,
configId: 'my-flow',
// Adaptador → lo consumen los callbacks getSavedData / saveData del store
storeConfig: {
getStoredData: (key) =>
orderViewService.get(orderId, key).then((s) => s[0]?.mfeData),
saveData: (key, data) =>
orderViewService.save({ orderId, mfeId: key, mfeData: data }).then(() => {}),
onError: (key, phase, err) => console.error(phase, key, err),
},
footer: {
mode: 'continue-only', // o 'go-back'
// vars / labels / remote / allSteps / notIncludedContinue opcionales
},
stepList: [/* cada step: MFE + mfeId + title (+ subscribeTo?) */],
};
export function MyFlow({ router, onResultData }) {
return (
<FlowStepper
initSettings={initSettings}
router={router}
onResultData={onResultData}
renderRemote={renderRemote}
basePath="/my-flow"
/>
);
}FlowStepper llama internamente initStore(storeConfig, orderId). La instancia no
se pasa a los remotes.
API
Props de FlowStepper (FlowStepperConfig)
| Prop | Requerido | Descripción |
| --- | --- | --- |
| initSettings | Sí | Configuración del flujo |
| onResultData | Sí | Callback al completar el último paso (payload agregado) |
| router | Sí | { pathname, navigate } — sync URL ↔ paso activo |
| renderRemote | Sí | (mfe, props) => ReactNode |
| basePath | No | Prefijo de URL (default /flow) |
InitSettings
| Campo | Requerido | Descripción |
| --- | --- | --- |
| partyId / orderId / configId | Sí | Sesión; orderId = referencia del store registrado |
| storeConfig | Recomendado | Adaptador { getStoredData, saveData, onError? }. Omitido → memoria (no-op) |
| stepList | Sí | Pasos planos con mfeId obligatorio (sin wrapper mfeList) |
| header / footer / styles | No | Shell |
Lo que recibe cada leaf
| Campo | Notas |
| --- | --- |
| partyId, orderId | Sesión + referencia del store |
| mfeId | Única clave del slot |
| subscribeTo | Otros mfeIds — useSlotData(orderId, subscribeTo) |
| mfeSaveFn? | Mismo camino que Continue (setSlotData + callback saveData), sin avanzar |
| onResultData | Emite snapshot; no dispara persistencia |
Guardado y avance
| Evento | Efecto |
| --- | --- |
| onResultData del leaf | Solo snapshot (mfeStatus / mfeData). Invalida mfeSaved si el dato cambió. No llama callbacks del store |
| Continuar / Finalizar | tryPersistStep: si mfeStatus y no mfeSaved → setSlotData + callback saveData(mfeId) + wait ready → avanza / finaliza |
| Auto-avance (notIncludedContinue / allSteps: false) | Simula Continue (mismo camino) al emitir mfeStatus: true |
| mfeSaveFn | setSlotData + callback saveData, sin navegar |
| Montaje leaf (useSlotData) | Callback getSavedData(mfeId) → adaptador getStoredData |
Si el slot va a error, no avanza. Si ya estaba mfeSaved, Continue solo navega.
Exports
| Export | Descripción |
| --- | --- |
| FlowStepper | Composite principal |
| StepFooter | Footer built-in |
| Stepper, useStepper, useStepperContext | Primitivas |
| buildSteps, buildResultData, resolveStepIndex | Lógica pura |
| MFE_FLOW_EXIT_EVENT | "mfe-flow-exit" al completar |
| useSlotData, useSlotStatus | Re-export store-management |
| initStore, getStore, hasStore, removeStore | Registry |
| Tipos | InitSettings, FlowStorePersistenceConfig, mfeResultData, StepSnapshot, … |
Datos entre pasos
Continuar Leaf A (mfeStatus true)
──▶ tryPersistStep
setSlotData('individual-form', mfeData) // memoria
saveData('individual-form') // callback → adaptador saveData
▼
slots['individual-form']
│
Leaf B (subscribeTo) ◀── useSlotData(orderId, ['individual-form'])
(montaje → getSavedData si está frío)- Store registrado una vez:
initStore(storeConfig, orderId). - Remotos resuelven por
orderId+ hooks (sinstore/storeKey). - Clave canónica del slot =
mfeId.
import { useSlotData } from '@ssi-lib/flow-stepper';
function AddressForm({ initSettings, onResultData }) {
const { orderId, mfeId, subscribeTo } = initSettings;
const { data } = useSlotData(orderId, mfeId); // propio + getSavedData al montar
const { datas } = useSlotData(orderId, subscribeTo); // otros mfeIds
// Emitir siempre: Continue lee el snapshot, no el store, para mfeStatus/mfeData
// onResultData({ partyId, orderId, mfeId, mfeStatus, mfeSaved: false, mfeData });
}Footer — initSettings.footer
interface FlowFooterConfig {
mode?: 'continue-only' | 'go-back'; // default: 'continue-only'
vars?: FlowFooterVars;
labels?: FlowFooterLabels;
allSteps?: boolean; // default true
notIncludedContinue?: Array<string | number>; // mfeIds sin botón → auto-avance
remote?: FlowMfeItem;
}Pasos en notIncludedContinue (o allSteps: false) no muestran footer: al emitir
onResultData con mfeStatus === true simulan Continuar (callback saveData + avance).
| mode | UI |
| --- | --- |
| 'continue-only' (default) | Un botón Continuar/Finalizar |
| 'go-back' | Atrás + Continuar/Finalizar |
Sin footer.remote (o incompleto) → StepFooter built-in. Con remoto completo → renderRemote.
Estilo — initSettings.styles
styles?: {
content?: { padding?: string; background?: string };
footer?: { padding?: string; radius?: string; background?: string };
header?: { padding?: string; background?: string };
}Overrides del botón: footer.vars (buttonBg, buttonRadius, … → CSS --fs-footer-*).
Desarrollo
npm install
npm run build
npm run typecheck
npm test
npm run pack:localDev harness
npm run dev:app levanta un host en :7102 importando FlowStepper desde src/.
Requisitos en paralelo: mock de config, commons-management-mfe, customer-management-mfe.
Nombres deprecados (no usar)
| Antiguo | Actual |
| --- | --- |
| getMfeStoredData / saveMfeData | Adaptador getStoredData / saveData |
| setMfeData (write-through) | setSlotData + callback saveData |
| usePersistedData / useSlots | useSlotData / useSlotStatus |
| Guardar en cada onResultData | Solo Continue / auto-avance / mfeSaveFn |
| store / storeKey en el leaf | orderId + mfeId |
| Slot key mfeId ?? key | mfeId solo |
