{"id":491079,"date":"2026-08-16T15:08:39","date_gmt":"2026-08-16T15:08:39","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=491079"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=491079","title":{"rendered":"\u041a\u043e\u0433\u0434\u0430 setState \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e: \u043a\u0430\u043a \u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043b \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u0441\u0442\u0435\u0439\u0442-\u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440 \u043d\u0430 TypeScript"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0421\u0442\u0435\u0439\u0442-\u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440\u043e\u0432 \u0432 JavaScript \u0443\u0436\u0435 \u0441\u0442\u043e\u043b\u044c\u043a\u043e, \u0447\u0442\u043e \u0438\u0434\u0435\u044f \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0435\u0449\u0451 \u043e\u0434\u0438\u043d \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0441\u043e\u043c\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e. \u0415\u0441\u0442\u044c Redux Toolkit, Zustand, MobX, Jotai, XState \u0438 \u0434\u0435\u0441\u044f\u0442\u043a\u0438 \u043c\u0435\u043d\u0435\u0435 \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0445 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a. \u041f\u043e\u0447\u0442\u0438 \u0434\u043b\u044f \u043b\u044e\u0431\u043e\u0433\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u0430 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u0433\u043e\u0442\u043e\u0432\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435. \u041d\u043e \u0432\u0441\u0435 \u0436\u0435 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430, \u0440\u0435\u0448\u0438\u043b \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0432\u043e\u044e \u0438\u0434\u0435\u044e.<\/p>\n<p>\u0418\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u044f \u043d\u0435 \u0441\u043e\u0431\u0438\u0440\u0430\u043b\u0441\u044f \u0434\u0435\u043b\u0430\u0442\u044c \u0435\u0449\u0451 \u043e\u0434\u0438\u043d store \u0441 \u043c\u0435\u0442\u043e\u0434\u0430\u043c\u0438 <code>getState<\/code>, <code>setState<\/code> \u0438 <code>subscribe<\/code>.<\/p>\n<p>\u041c\u0435\u043d\u044f \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043e\u0432\u0430\u043b\u0430 \u0434\u0440\u0443\u0433\u0430\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430. \u0412\u043e \u043c\u043d\u043e\u0433\u0438\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u0435 \u043e\u0434\u043d\u043e\u0439 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0435\u0439, \u0430 \u0446\u0435\u043b\u044b\u043c \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u043c:<\/p>\n<ol>\n<li>\n<p>\u0437\u0430\u0440\u0435\u0437\u0435\u0440\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u043e\u0432\u0430\u0440;<\/p>\n<\/li>\n<li>\n<p>\u0441\u043f\u0438\u0441\u0430\u0442\u044c \u0434\u0435\u043d\u044c\u0433\u0438;<\/p>\n<\/li>\n<li>\n<p>\u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0437\u0430\u043a\u0430\u0437;<\/p>\n<\/li>\n<li>\n<p>\u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435;<\/p>\n<\/li>\n<li>\n<p>\u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441.<\/p>\n<\/li>\n<\/ol>\n<p>\u041f\u043e\u043a\u0430 \u0432\u0441\u0451 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u0442\u0430\u043a\u043e\u0439 \u043a\u043e\u0434 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u0430\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c <code>await<\/code>. \u041d\u043e \u0435\u0441\u043b\u0438 \u0442\u0440\u0435\u0442\u0438\u0439 \u0448\u0430\u0433 \u043f\u0430\u0434\u0430\u0435\u0442, \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u043e\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438. \u0422\u043e\u0432\u0430\u0440 \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0437\u0435\u0440\u0432\u0438\u0440\u043e\u0432\u0430\u043d, \u0434\u0435\u043d\u044c\u0433\u0438 \u0441\u043f\u0438\u0441\u0430\u043d\u044b, \u0430 \u0437\u0430\u043a\u0430\u0437\u0430 \u043d\u0435\u0442.<\/p>\n<p>\u041c\u043e\u0436\u043d\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e <code>try\/catch<\/code>, \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u0437\u0430\u043f\u043e\u043c\u043d\u0438\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0438 \u043e\u0442\u043a\u0430\u0442\u0438\u0442\u044c \u0438\u0445 \u0432 \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435. \u041f\u043e\u0442\u043e\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0438, \u0442\u0430\u0439\u043c\u0430\u0443\u0442\u044b, \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u0438 \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439. \u0412 \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u043c\u043e\u043c\u0435\u043d\u0442 \u043e\u0431\u044b\u0447\u043d\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u0432 \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0439 workflow engine.<\/p>\n<p>\u0422\u0430\u043a \u043f\u043e\u044f\u0432\u0438\u043b\u0441\u044f <a href=\"https:\/\/www.npmjs.com\/package\/@staga\/core\" rel=\"noopener noreferrer nofollow\">Staga<\/a>, TypeScript-\u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u0435\u0442 \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0438 \u043c\u043d\u043e\u0433\u043e\u0448\u0430\u0433\u043e\u0432\u044b\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u0441 \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u0435\u0439.<\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0440\u0430\u0437\u0431\u0435\u0440\u0443, \u0437\u0430\u0447\u0435\u043c \u044d\u0442\u043e \u043d\u0443\u0436\u043d\u043e, \u043a\u0430\u043a \u0443\u0441\u0442\u0440\u043e\u0435\u043d \u043f\u0430\u043a\u0435\u0442 \u0432\u043d\u0443\u0442\u0440\u0438, \u0433\u0434\u0435 \u0437\u0430\u043a\u0430\u043d\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043e\u0431\u044b\u0447\u043d\u044b\u0439 state management \u0438 \u043f\u043e\u0447\u0435\u043c\u0443 \u0441\u043b\u043e\u0432\u043e \u00ab\u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f\u00bb \u0437\u0434\u0435\u0441\u044c \u043d\u0443\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043e\u0441\u0442\u043e\u0440\u043e\u0436\u043d\u043e.<\/p>\n<h3>TL;DR<\/h3>\n<p>Staga \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043e\u043f\u0438\u0441\u0430\u0442\u044c \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044e \u043a\u0430\u043a \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0448\u0430\u0433\u043e\u0432. \u0423 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0448\u0430\u0433\u0430 \u0435\u0441\u0442\u044c \u043e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0438 \u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u044f:<\/p>\n<pre><code class=\"typescript\">import { SagaManager } from '@staga\/core';type State = {  balance: number;  orders: string[];};const saga = SagaManager.create&lt;State&gt;({  balance: 1000,  orders: [],});const purchase = saga  .createTransaction&lt;{ orderId: string; amount: number }&gt;('purchase')  .addStep(    'debit',    (state, payload) =&gt; {      state.balance -= payload.amount;    },    (state, payload) =&gt; {      state.balance += payload.amount;    },  )  .addStep(    'create-order',    (state, payload) =&gt; {      state.orders.push(payload.orderId);    },    (state, payload) =&gt; {      state.orders = state.orders.filter(id =&gt; id !== payload.orderId);    },  );await purchase.run({ orderId: 'order-42', amount: 100 });<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u0432\u0442\u043e\u0440\u043e\u0439 \u0448\u0430\u0433 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0441\u044f \u043e\u0448\u0438\u0431\u043a\u043e\u0439, \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0435 \u0448\u0430\u0433\u0438 \u0431\u0443\u0434\u0443\u0442 \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u044b \u0432 \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435, \u0430 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0432\u0435\u0440\u043d\u0451\u0442\u0441\u044f \u043a \u0441\u043d\u0438\u043c\u043a\u0443, \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u043e\u043c\u0443 \u043f\u0435\u0440\u0435\u0434 \u0437\u0430\u043f\u0443\u0441\u043a\u043e\u043c \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438.<\/p>\n<p>\u041a\u0440\u043e\u043c\u0435 \u044d\u0442\u043e\u0433\u043e, \u0432 \u043f\u0430\u043a\u0435\u0442\u0435 \u0435\u0441\u0442\u044c:<\/p>\n<ul>\n<li>\n<p>\u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440\u044b;<\/p>\n<\/li>\n<li>\n<p>undo\/redo \u0438 \u0441\u043d\u0438\u043c\u043a\u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f;<\/p>\n<\/li>\n<li>\n<p>retries \u0438 \u0442\u0430\u0439\u043c\u0430\u0443\u0442\u044b \u0434\u043b\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u0448\u0430\u0433\u043e\u0432;<\/p>\n<\/li>\n<li>\n<p>\u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u044f;<\/p>\n<\/li>\n<li>\n<p>middleware;<\/p>\n<\/li>\n<li>\n<p>\u0437\u0430\u043f\u0438\u0441\u044c \u0438 \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0439;<\/p>\n<\/li>\n<li>\n<p>\u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u043d\u0438\u0437\u043a\u043e\u0443\u0440\u043e\u0432\u043d\u0435\u0432\u044b\u0439 \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0439 \u043c\u043e\u0434\u0443\u043b\u044c <code>statekit<\/code>.<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u0440\u0438 \u044d\u0442\u043e\u043c Staga \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432\u043d\u0443\u0442\u0440\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430. \u041e\u043d\u0430 \u043d\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0438 \u043d\u0435 \u0437\u0430\u043c\u0435\u043d\u044f\u0435\u0442 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u043b\u0438 Temporal.<\/p>\n<h3>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u043d\u0435 \u0432 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f<\/h3>\n<p>\u041f\u0440\u043e\u0441\u0442\u0435\u0439\u0448\u0438\u0439 state manager \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0437\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0438\u043d\u0443\u0442:<\/p>\n<pre><code class=\"typescript\">class Store&lt;T&gt; {  private state: T;  private listeners = new Set&lt;(state: T) =&gt; void&gt;();  constructor(initialState: T) {    this.state = initialState;  }  getState(): T {    return this.state;  }  setState(nextState: T): void {    this.state = nextState;    for (const listener of this.listeners) {      listener(this.state);    }  }  subscribe(listener: (state: T) =&gt; void): () =&gt; void {    this.listeners.add(listener);    return () =&gt; this.listeners.delete(listener);  }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0414\u043b\u044f \u043c\u043d\u043e\u0433\u0438\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e. \u0421\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u043d\u0435 \u0442\u043e\u0433\u0434\u0430, \u043a\u043e\u0433\u0434\u0430 \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043d\u043e\u0432\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435, \u0430 \u0442\u043e\u0433\u0434\u0430, \u043a\u043e\u0433\u0434\u0430 \u043e\u0434\u043d\u043e \u0431\u0438\u0437\u043d\u0435\u0441-\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u043c\u0435\u043d\u044f\u0435\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u0437 \u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u0432\u043d\u0435\u0448\u043d\u0438\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u044b.<\/p>\n<p>\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043e\u0431\u044b\u0447\u043d\u044b\u0439 checkout:<\/p>\n<pre><code class=\"typescript\">async function checkout(input: CheckoutInput) {  const reservation = await inventory.reserve(input.items);  const payment = await payments.charge(input.amount);  const order = await orders.create(input);  return order;}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0423 \u044d\u0442\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0435\u0441\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043d\u0435\u043f\u0440\u0438\u044f\u0442\u043d\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u0432:<\/p>\n<ul>\n<li>\n<p>\u0440\u0435\u0437\u0435\u0440\u0432 \u0441\u043e\u0437\u0434\u0430\u043d, \u043d\u043e \u043f\u043b\u0430\u0442\u0451\u0436 \u043d\u0435 \u043f\u0440\u043e\u0448\u0451\u043b;<\/p>\n<\/li>\n<li>\n<p>\u043f\u043b\u0430\u0442\u0451\u0436 \u043f\u0440\u043e\u0448\u0451\u043b, \u043d\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0437\u0430\u043a\u0430\u0437\u0430 \u0443\u043f\u0430\u043b\u043e;<\/p>\n<\/li>\n<li>\n<p>\u0437\u0430\u043f\u0440\u043e\u0441 \u043a \u043f\u043b\u0430\u0442\u0451\u0436\u043d\u043e\u043c\u0443 \u0441\u0435\u0440\u0432\u0438\u0441\u0443 \u0437\u0430\u0432\u0438\u0441;<\/p>\n<\/li>\n<li>\n<p>\u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043f\u043e\u043b\u0443\u0447\u0438\u043b \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u0441\u0435\u0442\u0435\u0432\u0443\u044e \u043e\u0448\u0438\u0431\u043a\u0443;<\/p>\n<\/li>\n<li>\n<p>\u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a \u0441\u043f\u0438\u0441\u0430\u043b \u0434\u0435\u043d\u044c\u0433\u0438 \u0432\u0442\u043e\u0440\u043e\u0439 \u0440\u0430\u0437;<\/p>\n<\/li>\n<li>\n<p>UI \u0443\u0441\u043f\u0435\u043b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435.<\/p>\n<\/li>\n<\/ul>\n<p>\u041e\u0431\u044b\u0447\u043d\u044b\u0439 store \u043d\u0435 \u0440\u0435\u0448\u0430\u0435\u0442 \u044d\u0442\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b. \u041e\u043d \u0443\u043c\u0435\u0435\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442, \u043d\u043e \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0437\u043d\u0430\u0435\u0442 \u043e \u0436\u0438\u0437\u043d\u0435\u043d\u043d\u043e\u043c \u0446\u0438\u043a\u043b\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438, \u043f\u043e\u0440\u044f\u0434\u043a\u0435 \u0448\u0430\u0433\u043e\u0432 \u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u0430\u0445 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f.<\/p>\n<h3>\u041e\u0442\u043a\u0443\u0434\u0430 \u0437\u0434\u0435\u0441\u044c \u0432\u0437\u044f\u043b\u0430\u0441\u044c saga<\/h3>\n<p>\u0418\u0434\u0435\u044f saga \u043f\u043e\u044f\u0432\u0438\u043b\u0430\u0441\u044c \u043d\u0435 \u0432 Redux \u0438 \u043d\u0435 \u0432\u043e \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0435. \u0415\u0451 \u043e\u043f\u0438\u0441\u0430\u043b\u0438 \u0413\u0435\u043a\u0442\u043e\u0440 \u0413\u0430\u0440\u0441\u0438\u044f-\u041c\u043e\u043b\u0438\u043d\u0430 \u0438 \u041a\u0435\u043d\u043d\u0435\u0442 \u0421\u0430\u043b\u0435\u043c \u0432 \u0440\u0430\u0431\u043e\u0442\u0435 <code>SAGAS<\/code> \u0432 1987 \u0433\u043e\u0434\u0443.<\/p>\n<p>\u0421\u0443\u0442\u044c \u0438\u0434\u0435\u0438 \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u0430\u044f. \u0414\u043b\u0438\u043d\u043d\u0430\u044f \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f \u0440\u0430\u0437\u0431\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0439:<\/p>\n<pre><code>T1 -&gt; T2 -&gt; T3 -&gt; T4<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0414\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u043e\u0439 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u043c\u043e\u0436\u0435\u0442 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0438\u0440\u0443\u044e\u0449\u0435\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435:<\/p>\n<pre><code>C1 &lt;- C2 &lt;- C3 &lt;- C4<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0415\u0441\u043b\u0438 <code>T3<\/code> \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043b\u0430\u0441\u044c \u043e\u0448\u0438\u0431\u043a\u043e\u0439, \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u0438 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0445 \u0448\u0430\u0433\u043e\u0432 \u0432 \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435:<\/p>\n<pre><code>T1 -&gt; T2 -&gt; T3 X      C2 &lt;- C1<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u044f \u043d\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043c\u0438\u0440 \u0432 \u0430\u0431\u0441\u043e\u043b\u044e\u0442\u043d\u043e \u043f\u0440\u0435\u0436\u043d\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u043e\u0435 \u043f\u0438\u0441\u044c\u043c\u043e \u043d\u0435\u043b\u044c\u0437\u044f \u00ab\u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u00bb. \u041c\u043e\u0436\u043d\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0434\u0440\u0443\u0433\u043e\u0435 \u043f\u0438\u0441\u044c\u043c\u043e, \u043f\u043e\u043c\u0435\u0442\u0438\u0442\u044c \u043f\u0435\u0440\u0432\u043e\u0435 \u043a\u0430\u043a \u043d\u0435\u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0435 \u0438\u043b\u0438 \u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u0438\u0440\u0443\u044e\u0449\u0435\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435.<\/p>\n<p>\u042d\u0442\u043e \u0432\u0430\u0436\u043d\u043e\u0435 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 saga \u043e\u0442 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445.<\/p>\n<h3>\u0427\u0442\u043e \u044f \u0445\u043e\u0442\u0435\u043b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c<\/h3>\n<p>\u041f\u0435\u0440\u0435\u0434 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u044f \u0437\u0430\u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043b \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u0439.<\/p>\n<p>\u0412\u043e-\u043f\u0435\u0440\u0432\u044b\u0445, \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0438 payload \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u044b. \u0415\u0441\u043b\u0438 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 <code>{ orderId: string }<\/code>, \u0432\u0441\u0435 \u0448\u0430\u0433\u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0438\u043c\u0435\u043d\u043d\u043e \u044d\u0442\u043e\u0442 \u0442\u0438\u043f.<\/p>\n<p>\u0412\u043e-\u0432\u0442\u043e\u0440\u044b\u0445, \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u0434\u043e\u043b\u0436\u0435\u043d \u0447\u0438\u0442\u0430\u0442\u044c\u0441\u044f \u0441\u0432\u0435\u0440\u0445\u0443 \u0432\u043d\u0438\u0437 \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u044b\u0439 \u0431\u0438\u0437\u043d\u0435\u0441-\u043f\u0440\u043e\u0446\u0435\u0441\u0441. \u0411\u0435\u0437 \u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u0432, \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 action type \u0438 \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0441\u043b\u0443\u0436\u0435\u0431\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430.<\/p>\n<p>\u0412-\u0442\u0440\u0435\u0442\u044c\u0438\u0445, \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438, \u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u0434\u043b\u044f \u043a\u0430\u043a\u043e\u0433\u043e-\u0442\u043e \u0448\u0430\u0433\u0430 \u043d\u0435 \u0431\u044b\u043b\u0430 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0430 \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u044f.<\/p>\n<p>\u0412-\u0447\u0435\u0442\u0432\u0451\u0440\u0442\u044b\u0445, \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c\u0441\u044f \u0432 \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435.<\/p>\n<p>\u0412-\u043f\u044f\u0442\u044b\u0445, retry, timeout, \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0438 middleware \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u044b \u0441\u043c\u0435\u0448\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u0441 \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u043e\u0439 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0448\u0430\u0433\u0430.<\/p>\n<p>\u041f\u043e\u043b\u0443\u0447\u0438\u043b\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 API:<\/p>\n<pre><code class=\"typescript\">const transaction = saga  .createTransaction&lt;Payload&gt;('transaction-name')  .addStep(    'step-name',    async (state, payload) =&gt; {      \/\/ \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0448\u0430\u0433    },    async (state, payload) =&gt; {      \/\/ \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0448\u0430\u0433    },    {      retries: 2,      timeout: 3000,    },  );await transaction.run(payload);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c <code>retries: 2<\/code> \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u0434\u0432\u0435 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0438 \u043f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0432\u043e\u0439. \u0412\u0441\u0435\u0433\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0432\u044b\u0437\u0432\u0430\u043d\u0430 \u0442\u0440\u0438 \u0440\u0430\u0437\u0430.<\/p>\n<h3>\u041f\u043e\u043b\u043d\u044b\u0439 \u043f\u0440\u0438\u043c\u0435\u0440: checkout \u0441 \u043e\u0442\u043a\u0430\u0442\u043e\u043c<\/h3>\n<p>\u041d\u0438\u0436\u0435 \u043f\u0440\u0438\u043c\u0435\u0440, \u0431\u043b\u0438\u0437\u043a\u0438\u0439 \u043a \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c\u0443 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044e. \u0412 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438 \u043c\u044b \u0445\u0440\u0430\u043d\u0438\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u0430\u043d\u043d\u044b\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0443\u0436\u043d\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044e \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430.<\/p>\n<pre><code class=\"typescript\">import {  SagaManager,  createLoggingMiddleware,  createTimingMiddleware,} from '@staga\/core';type CheckoutState = {  status: 'idle' | 'processing' | 'completed' | 'failed';  reservationId: string | null;  paymentId: string | null;  orderId: string | null;  error: string | null;};type CheckoutPayload = {  requestId: string;  userId: string;  items: Array&lt;{    sku: string;    quantity: number;  }&gt;;  amount: number;};const saga = SagaManager.create&lt;CheckoutState&gt;({  status: 'idle',  reservationId: null,  paymentId: null,  orderId: null,  error: null,});saga.use(createLoggingMiddleware());saga.use(  createTimingMiddleware((transactionName, duration) =&gt; {    console.log(`${transactionName}: ${duration} ms`);  }),);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u043f\u0438\u0448\u0435\u043c \u0448\u0430\u0433\u0438.<\/p>\n<pre><code class=\"typescript\">const checkout = saga  .createTransaction&lt;CheckoutPayload&gt;('checkout')  .addStep(    'start',    state =&gt; {      state.status = 'processing';      state.error = null;    },    state =&gt; {      state.status = 'idle';    },  )  .addStep(    'reserve-stock',    async (state, payload) =&gt; {      const reservation = await inventory.reserve({        items: payload.items,        idempotencyKey: `reserve:${payload.requestId}`,      });      state.reservationId = reservation.id;    },    async state =&gt; {      if (!state.reservationId) {        return;      }      await inventory.release(state.reservationId);      state.reservationId = null;    },    {      retries: 2,      timeout: 3000,    },  )  .addStep(    'charge-payment',    async (state, payload) =&gt; {      const payment = await payments.charge({        userId: payload.userId,        amount: payload.amount,        idempotencyKey: `charge:${payload.requestId}`,      });      state.paymentId = payment.id;    },    async (state, payload) =&gt; {      if (!state.paymentId) {        return;      }      await payments.refund({        paymentId: state.paymentId,        idempotencyKey: `refund:${payload.requestId}`,      });      state.paymentId = null;    },    {      timeout: 5000,    },  )  .addStep(    'create-order',    async (state, payload) =&gt; {      const order = await orders.create({        userId: payload.userId,        items: payload.items,        paymentId: state.paymentId,        reservationId: state.reservationId,        idempotencyKey: `order:${payload.requestId}`,      });      state.orderId = order.id;    },    async state =&gt; {      if (!state.orderId) {        return;      }      await orders.cancel(state.orderId);      state.orderId = null;    },  )  .addStep('complete', state =&gt; {    state.status = 'completed';  });<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0417\u0430\u043f\u0443\u0441\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"typescript\">try {  await checkout.run({    requestId: crypto.randomUUID(),    userId: 'user-42',    items: [      { sku: 'keyboard', quantity: 1 },      { sku: 'mouse', quantity: 1 },    ],    amount: 15000,  });} catch (error) {  const message = error instanceof Error    ? error.message    : String(error);  saga.stateManager.setState({    ...saga.getState(),    status: 'failed',    error: message,  });}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0414\u043e\u043f\u0443\u0441\u0442\u0438\u043c, \u0440\u0435\u0437\u0435\u0440\u0432\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438 \u043f\u043b\u0430\u0442\u0451\u0436 \u043f\u0440\u043e\u0448\u043b\u0438, \u043d\u043e <code>orders.create()<\/code> \u0432\u044b\u0431\u0440\u043e\u0441\u0438\u043b \u043e\u0448\u0438\u0431\u043a\u0443.<\/p>\n<p>Staga \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<pre><code>start               successreserve-stock       successcharge-payment      successcreate-order        failedrollback:charge-payment      refundreserve-stock       releasestart               status = idlerestore snapshot<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044e\u0442\u0441\u044f \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u0438 \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0445 \u0448\u0430\u0433\u043e\u0432 \u0432 \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435. \u0417\u0430\u0442\u0435\u043c \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0438\u0437 \u0441\u043d\u0438\u043c\u043a\u0430, \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u0435\u0440\u0435\u0434 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0435\u0439.<\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u0432\u043d\u0435\u0448\u043d\u0438\u0439 <code>catch<\/code> \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0443\u0436\u0435 \u043d\u043e\u0432\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043e\u0448\u0438\u0431\u043a\u0438. \u042d\u0442\u043e \u043f\u043e\u043b\u0435\u0437\u043d\u043e, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0441\u0430\u043c\u0430 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445, \u0430 \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0449\u0438\u0439 \u043a\u043e\u0434 \u0440\u0435\u0448\u0430\u0435\u0442, \u0447\u0442\u043e \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e.<\/p>\n<h3>\u0417\u0430\u0447\u0435\u043c \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e snapshot \u0438 compensation<\/h3>\n<p>\u041d\u0430 \u043f\u0435\u0440\u0432\u044b\u0439 \u0432\u0437\u0433\u043b\u044f\u0434 \u043a\u0430\u0436\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043e\u0434\u043d\u043e\u0433\u043e \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c\u0430. \u041d\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435 \u043e\u043d\u0438 \u0440\u0435\u0448\u0430\u044e\u0442 \u0440\u0430\u0437\u043d\u044b\u0435 \u0437\u0430\u0434\u0430\u0447\u0438.<\/p>\n<p>Snapshot \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435:<\/p>\n<pre><code class=\"typescript\">{  reservationId: null,  paymentId: null,  orderId: null}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u043e snapshot \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u043c\u0438\u0440\u043e\u043c. \u0415\u0441\u043b\u0438 \u043f\u043b\u0430\u0442\u0451\u0436\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0443\u0436\u0435 \u0441\u043f\u0438\u0441\u0430\u043b \u0434\u0435\u043d\u044c\u0433\u0438, \u043f\u0440\u043e\u0441\u0442\u043e\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 JavaScript-\u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u0438\u0445 \u043d\u0435 \u0432\u0435\u0440\u043d\u0451\u0442.<\/p>\n<p>\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043d\u0443\u0436\u043d\u0430 \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u044f:<\/p>\n<pre><code class=\"typescript\">async (state, payload) =&gt; {  await payments.refund({    paymentId: state.paymentId,    idempotencyKey: `refund:${payload.requestId}`,  });}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418 \u043d\u0430\u043e\u0431\u043e\u0440\u043e\u0442, \u043e\u0434\u043d\u043e\u0439 \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u0438 \u0442\u043e\u0436\u0435 \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e. \u0420\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0431\u044b\u0442\u044c \u043e\u0447\u0438\u0441\u0442\u0438\u0442\u044c \u043e\u0434\u043d\u043e \u0438\u0437 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0445 \u043f\u043e\u043b\u0435\u0439 \u0438\u043b\u0438 \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0438\u0440\u0443\u044e\u0449\u0438\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043c\u043e\u0436\u0435\u0442 \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043d\u0435 \u0432\u0441\u0435 \u0434\u0430\u043d\u043d\u044b\u0435. \u0424\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u0439 snapshot \u0441\u043b\u0443\u0436\u0438\u0442 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0441\u0442\u0440\u0430\u0445\u043e\u0432\u043a\u043e\u0439 \u0434\u043b\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0432\u043d\u0443\u0442\u0440\u0438 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430.<\/p>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0441\u0445\u0435\u043c\u0430 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code>external side effects  -&gt; compensate()local state            -&gt; restore snapshot<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h3>\u0420\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0431\u0435\u0437 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438 \u043a \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0443<\/h3>\n<p>Staga \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 React, Vue \u0438\u043b\u0438 Angular. \u0412 \u043e\u0441\u043d\u043e\u0432\u0435 \u043b\u0435\u0436\u0430\u0442 \u043e\u0431\u044b\u0447\u043d\u044b\u0435 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u0438 \u0441\u0438\u0433\u043d\u0430\u043b\u044b.<\/p>\n<pre><code class=\"typescript\">const status$ = saga.select(state =&gt; state.status);console.log(status$.value);const unsubscribe = status$.subscribe(status =&gt; {  console.log('checkout status:', status);});<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0421\u0435\u043b\u0435\u043a\u0442\u043e\u0440 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0447\u0435\u0440\u0435\u0437 <code>.value<\/code> \u0438\u043b\u0438 <code>.get()<\/code> \u0438 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u0442\u044c\u0441\u044f \u0447\u0435\u0440\u0435\u0437 <code>.subscribe()<\/code>.<\/p>\n<p>\u041c\u043e\u0436\u043d\u043e \u0432\u044b\u0431\u0440\u0430\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e:<\/p>\n<pre><code class=\"typescript\">const orderId$ = saga.selectProperty('orderId');<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418\u043b\u0438 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0438\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0447\u0430\u0441\u0442\u0435\u0439 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f:<\/p>\n<pre><code class=\"typescript\">const summary$ = saga.stateManager.combine(  state =&gt; state.status,  state =&gt; state.orderId,  (status, orderId) =&gt; ({ status, orderId }),);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0438\u0435 \u0430\u0434\u0430\u043f\u0442\u0435\u0440\u044b \u0434\u043b\u044f \u043b\u044e\u0431\u043e\u0433\u043e UI-\u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430, \u043d\u0435 \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u0438\u0445 \u0432 core-\u043f\u0430\u043a\u0435\u0442.<\/p>\n<h3>\u041a\u0430\u043a \u0443\u0441\u0442\u0440\u043e\u0435\u043d\u0430 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430<\/h3>\n<p>\u041d\u0430 \u0432\u0435\u0440\u0445\u043d\u0435\u043c \u0443\u0440\u043e\u0432\u043d\u0435 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code>                         +--------------------+                         |    SagaManager     |                         +----------+---------+                                    |          +-------------------------+-------------------------+          |                         |                         |          v                         v                         v+-------------------+     +-------------------+     +-------------------+|    Transaction    |     |   StateManager    |     |   EventManager    |+---------+---------+     +---------+---------+     +-------------------+          |                         |          v                         v+-------------------+     +-------------------+| statekit tx core  |     |  statekit Store   || retries\/timeouts  |     | Signals\/Streams   |+---------+---------+     +---------+---------+          |                         |          +-------------+-----------+                        v              +--------------------+              | ReactiveStateProxy |              +--------------------+<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>SagaManager<\/code> \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0444\u0430\u0441\u0430\u0434\u043e\u043c. \u0427\u0435\u0440\u0435\u0437 \u043d\u0435\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u044e\u0442\u0441\u044f \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438, \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440\u044b, \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438, middleware \u0438 \u0437\u0430\u043f\u0438\u0441\u044c \u0441\u043e\u0431\u044b\u0442\u0438\u0439.<\/p>\n<p><code>StateManager<\/code> \u0445\u0440\u0430\u043d\u0438\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435, \u0438\u0441\u0442\u043e\u0440\u0438\u044e undo\/redo, snapshots \u0438 \u0441\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u0442 \u0438\u0445 \u0441 \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u043c <code>Store<\/code>.<\/p>\n<p><code>Transaction<\/code> \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0431\u0438\u0437\u043d\u0435\u0441-\u0448\u0430\u0433\u0438, \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u0441\u043d\u0438\u043c\u043a\u0430, \u043f\u043e\u0440\u044f\u0434\u043e\u043a \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u0439 \u0438 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043d\u0438\u0437\u043a\u043e\u0443\u0440\u043e\u0432\u043d\u0435\u0432\u044b\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 \u0441\u043e\u0431\u044b\u0442\u0438\u044f Staga.<\/p>\n<p><code>ReactiveStateProxy<\/code> \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u0438\u0441\u0430\u0442\u044c \u043e\u0431\u044b\u0447\u043d\u044b\u0435 \u043c\u0443\u0442\u0430\u0446\u0438\u0438:<\/p>\n<pre><code class=\"typescript\">state.items.push(item);state.count += 1;<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412\u043d\u0443\u0442\u0440\u0438 \u043e\u043d\u0438 \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 JavaScript <code>Proxy<\/code>.<\/p>\n<p>\u041d\u0430\u043a\u043e\u043d\u0435\u0446, <code>statekit<\/code> \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0435 \u043f\u0440\u0438\u043c\u0438\u0442\u0438\u0432\u044b: store, signals, streams, batching, scheduler \u0438 \u043d\u0438\u0437\u043a\u043e\u0443\u0440\u043e\u0432\u043d\u0435\u0432\u044b\u0439 \u0438\u0441\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0439.<\/p>\n<h3>\u0411\u0430\u0437\u043e\u0432\u044b\u0439 Store<\/h3>\n<p>\u0423\u043f\u0440\u043e\u0449\u0451\u043d\u043d\u043e \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0439 store \u0443\u0441\u0442\u0440\u043e\u0435\u043d \u0442\u0430\u043a:<\/p>\n<pre><code class=\"typescript\">type Unsubscribe = () =&gt; void;type EqualityFn&lt;T&gt; = (a: T, b: T) =&gt; boolean;class Store&lt;S extends object&gt; {  private state: S;  private listeners = new Set&lt;() =&gt; void&gt;();  constructor(initialState: S) {    this.state = initialState;  }  getState(): S {    return this.state;  }  setState(    updater: Partial&lt;S&gt; | ((previous: S) =&gt; S | Partial&lt;S&gt;),  ): void {    const previous = this.state;    const patch = typeof updater === 'function'      ? updater(previous)      : updater;    const next = patch === previous      ? patch as S      : { ...previous, ...patch };    if (Object.is(previous, next)) {      return;    }    this.state = next;    this.emit();  }  subscribe(listener: () =&gt; void): Unsubscribe {    this.listeners.add(listener);    return () =&gt; this.listeners.delete(listener);  }  watch&lt;T&gt;(    selector: (state: S) =&gt; T,    callback: (next: T, previous: T) =&gt; void,    equality: EqualityFn&lt;T&gt; = Object.is,  ): Unsubscribe {    let previous = selector(this.state);    return this.subscribe(() =&gt; {      const next = selector(this.state);      if (equality(previous, next)) {        return;      }      const oldValue = previous;      previous = next;      callback(next, oldValue);    });  }  private emit(): void {    for (const listener of [...this.listeners]) {      listener();    }  }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c <code>statekit.Store<\/code> \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0435\u0441\u0442\u044c:<\/p>\n<ul>\n<li>\n<p>middleware \u0434\u043e \u0438 \u043f\u043e\u0441\u043b\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f;<\/p>\n<\/li>\n<li>\n<p>batching;<\/p>\n<\/li>\n<li>\n<p>\u043f\u0440\u0438\u043e\u0440\u0438\u0442\u0435\u0442\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439 \u0447\u0435\u0440\u0435\u0437 scheduler;<\/p>\n<\/li>\n<li>\n<p>\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 middleware \u0434\u043b\u044f \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0439;<\/p>\n<\/li>\n<li>\n<p>\u043f\u043e\u043b\u043d\u0430\u044f \u0437\u0430\u043c\u0435\u043d\u0430 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0431\u0435\u0437 merge-\u0441\u0435\u043c\u0430\u043d\u0442\u0438\u043a\u0438.<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u043f\u0443\u043d\u043a\u0442 \u043d\u0443\u0436\u0435\u043d \u0434\u043b\u044f undo, redo \u0438 rollback. \u041f\u0440\u0438 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438 \u0441\u043d\u0438\u043c\u043a\u0430 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u0432\u0441\u0451 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435, \u0430 \u043d\u0435 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0438\u0442\u044c \u0435\u0433\u043e \u0441 \u0442\u0435\u043a\u0443\u0449\u0438\u043c \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u043c.<\/p>\n<h3>\u0421\u0438\u0433\u043d\u0430\u043b\u044b \u0438 \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u043c\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f<\/h3>\n<p>\u0414\u043b\u044f \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440\u043e\u0432 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 signal-\u0433\u0440\u0430\u0444.<\/p>\n<p>\u041e\u0431\u044b\u0447\u043d\u044b\u0439 signal \u0445\u0440\u0430\u043d\u0438\u0442 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438 \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u0434\u043f\u0438\u0441\u0447\u0438\u043a\u043e\u0432:<\/p>\n<pre><code class=\"typescript\">class SignalNode&lt;T&gt; {  private subscribers = new Set&lt;() =&gt; void&gt;();  constructor(private value: T) {}  get(): T {    track(this);    return this.value;  }  set(next: T): void {    if (Object.is(this.value, next)) {      return;    }    this.value = next;    for (const subscriber of [...this.subscribers]) {      subscriber();    }  }  subscribe(subscriber: () =&gt; void): () =&gt; void {    this.subscribers.add(subscriber);    return () =&gt; this.subscribers.delete(subscriber);  }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0421\u0430\u043c\u0430\u044f \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0441 derived-\u0441\u0438\u0433\u043d\u0430\u043b\u043e\u0432:<\/p>\n<pre><code class=\"typescript\">const firstName = signal('Ada');const lastName = signal('Lovelace');const fullName = derived(  () =&gt; `${firstName.get()} ${lastName.get()}`,);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412\u043e \u0432\u0440\u0435\u043c\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f <code>compute()<\/code> \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0437\u0430\u043f\u043e\u043c\u0438\u043d\u0430\u0435\u0442 \u0432\u0441\u0435 \u0441\u0438\u0433\u043d\u0430\u043b\u044b, \u0443 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0431\u044b\u043b \u0432\u044b\u0437\u0432\u0430\u043d <code>get()<\/code>. \u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e derived \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438.<\/p>\n<p>\u0417\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u043c\u0438:<\/p>\n<pre><code class=\"typescript\">const usePrimary = signal(true);const primary = signal('primary');const secondary = signal('secondary');const selected = derived(() =&gt; {  return usePrimary.get()    ? primary.get()    : secondary.get();});<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041a\u043e\u0433\u0434\u0430 <code>usePrimary<\/code> \u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f, derived \u0434\u043e\u043b\u0436\u0435\u043d \u043e\u0442\u043f\u0438\u0441\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u0441\u0442\u0430\u0440\u043e\u0433\u043e \u043d\u0430\u0431\u043e\u0440\u0430 \u0441\u0438\u0433\u043d\u0430\u043b\u043e\u0432 \u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u043d\u043e\u0432\u044b\u0439. \u0418\u043d\u0430\u0447\u0435 \u043e\u043d \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442 \u0440\u0435\u0430\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430 \u0443\u0436\u0435 \u043d\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0443\u044e \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c \u0438\u043b\u0438, \u043d\u0430\u043e\u0431\u043e\u0440\u043e\u0442, \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u043d\u0435\u0442 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c\u0441\u044f.<\/p>\n<p>\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0438 \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u043d\u043e\u0432\u044b\u0439 \u043d\u0430\u0431\u043e\u0440 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439, \u0437\u0430\u0442\u0435\u043c \u043e\u043d \u0441\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0441 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u043c:<\/p>\n<pre><code class=\"typescript\">for (const [dependency, unsubscribe] of subscriptions) {  if (!nextDependencies.has(dependency)) {    unsubscribe();    subscriptions.delete(dependency);  }}for (const dependency of nextDependencies) {  if (!subscriptions.has(dependency)) {    subscriptions.set(      dependency,      dependency.subscribe(recompute),    );  }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u042d\u0442\u043e \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c, \u043d\u043e \u0438\u043c\u0435\u043d\u043d\u043e \u043e\u043d \u0434\u0435\u043b\u0430\u0435\u0442 \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u043c\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u043c\u0438.<\/p>\n<h3>\u041f\u043e\u0447\u0435\u043c\u0443 \u044f \u0440\u0430\u0437\u0440\u0435\u0448\u0438\u043b \u043f\u0440\u044f\u043c\u044b\u0435 \u043c\u0443\u0442\u0430\u0446\u0438\u0438<\/h3>\n<p>\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u043a\u043e\u0434 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"typescript\">state.users.push(user);state.count += 1;<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0421\u0430\u043c state \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043e\u0431\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0432 <code>Proxy<\/code>.<\/p>\n<p>\u0423\u043f\u0440\u043e\u0449\u0451\u043d\u043d\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"typescript\">function createReactiveProxy&lt;T extends object&gt;(  target: T,  notify: () =&gt; void,): T {  return new Proxy(target, {    get(object, property, receiver) {      const value = Reflect.get(object, property, receiver);      if (value !== null &amp;&amp; typeof value === 'object') {        return createReactiveProxy(value, notify);      }      return value;    },    set(object, property, value) {      const previous = Reflect.get(object, property);      const changed = !Object.is(previous, value);      const result = Reflect.set(object, property, value);      if (result &amp;&amp; changed) {        notify();      }      return result;    },  });}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412 production-\u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0435\u0441\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0434\u0435\u0442\u0430\u043b\u0435\u0439.<\/p>\n<h4>\u041a\u044d\u0448 \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0445 Proxy<\/h4>\n<p>\u0415\u0441\u043b\u0438 \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0447\u0442\u0435\u043d\u0438\u0438 \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u043e\u0433\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0439 <code>Proxy<\/code>, \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043d\u0438\u0436\u0435 \u0431\u0443\u0434\u0435\u0442 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0440\u0430\u0437\u043d\u044b\u0435 \u0441\u0441\u044b\u043b\u043a\u0438:<\/p>\n<pre><code class=\"typescript\">proxy.user === proxy.user; \/\/ false<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u042d\u0442\u043e \u043b\u043e\u043c\u0430\u0435\u0442 equality checks \u0438 \u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u043b\u0438\u0448\u043d\u0438\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 Staga \u0445\u0440\u0430\u043d\u0438\u0442 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435 <code>original object -&gt; proxy<\/code> \u0432 <code>WeakMap<\/code>.<\/p>\n<pre><code class=\"typescript\">private proxyCache = new WeakMap&lt;object, object&gt;();<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0439 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043e\u0434\u043d\u043e\u043c\u0443 \u043e\u0431\u044a\u0435\u043a\u0442\u0443 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0442\u043e\u0442 \u0436\u0435 proxy.<\/p>\n<h4>\u041c\u0430\u0441\u0441\u0438\u0432\u044b \u0442\u043e\u0436\u0435 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043e\u0431\u044a\u0435\u043a\u0442\u0430\u043c\u0438<\/h4>\n<p>\u0412\u044b\u0437\u043e\u0432 <code>state.items.push(item)<\/code> \u0432\u043d\u0443\u0442\u0440\u0438 \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u0442 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0432\u043e\u0439\u0441\u0442\u0432 \u043c\u0430\u0441\u0441\u0438\u0432\u0430, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u0438\u043d\u0434\u0435\u043a\u0441 \u0438 <code>length<\/code>. Proxy \u0434\u043e\u043b\u0436\u0435\u043d \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u0442\u0430\u043a\u0438\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0438 \u043d\u0435 \u0442\u0435\u0440\u044f\u0442\u044c \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c \u0434\u043b\u044f \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0445 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439.<\/p>\n<h4>\u0423\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u043d\u0435 \u0440\u0430\u0432\u043d\u043e commit<\/h4>\n<p>\u0412\u043e \u0432\u0440\u0435\u043c\u044f \u043c\u0443\u0442\u0430\u0446\u0438\u0439 \u043f\u043e\u0434\u043f\u0438\u0441\u0447\u0438\u043a\u0438 \u043c\u043e\u0433\u0443\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f. \u041f\u043e\u0441\u043b\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0448\u0430\u0433\u0430 \u0438\u0442\u043e\u0433\u043e\u0432\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u0442\u0441\u044f \u0432 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 <code>Store<\/code>, \u0447\u0442\u043e\u0431\u044b \u043e\u0431\u043d\u043e\u0432\u0438\u043b\u0438\u0441\u044c selectors \u0438 signals.<\/p>\n<p>\u042d\u0442\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e Staga \u043d\u0435 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 isolation \u0432 \u0441\u043c\u044b\u0441\u043b\u0435 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445. \u041e\u043d\u0430 \u0443\u043c\u0435\u0435\u0442 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0444\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0435 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435, \u043d\u043e \u043d\u0435 \u0441\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0432\u0441\u0435 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043e\u0442 \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0442\u0435\u043b\u0435\u0439.<\/p>\n<h3>\u041a\u0430\u043a \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f<\/h3>\n<p>\u0412 \u0441\u0438\u043b\u044c\u043d\u043e \u0443\u043f\u0440\u043e\u0449\u0451\u043d\u043d\u043e\u043c \u0432\u0438\u0434\u0435 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"typescript\">async function runTransaction&lt;State, Payload&gt;(  state: State,  payload: Payload,  steps: Step&lt;State, Payload&gt;[],) {  const snapshot = clone(state);  const completed: Step&lt;State, Payload&gt;[] = [];  try {    for (const step of steps) {      await executeWithRetryAndTimeout(        () =&gt; step.execute(state, payload),        step.options,      );      completed.push(step);    }  } catch (error) {    for (const step of completed.reverse()) {      await step.compensate?.(state, payload);    }    restore(snapshot);    throw error;  }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412 Staga \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0430 \u043d\u0430 \u0434\u0432\u0430 \u0443\u0440\u043e\u0432\u043d\u044f.<\/p>\n<p>\u041d\u0438\u0437\u043a\u043e\u0443\u0440\u043e\u0432\u043d\u0435\u0432\u044b\u0439 <code>statekit<\/code> executor \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430:<\/p>\n<ul>\n<li>\n<p>\u043f\u043e\u0440\u044f\u0434\u043e\u043a \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0448\u0430\u0433\u043e\u0432;<\/p>\n<\/li>\n<li>\n<p>\u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u043f\u044b\u0442\u043e\u043a;<\/p>\n<\/li>\n<li>\n<p>timeout;<\/p>\n<\/li>\n<li>\n<p>\u043d\u0438\u0437\u043a\u043e\u0443\u0440\u043e\u0432\u043d\u0435\u0432\u044b\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u044f;<\/p>\n<\/li>\n<li>\n<p><code>AbortController<\/code> \u0432\u043d\u0443\u0442\u0440\u0438 transaction core.<\/p>\n<\/li>\n<\/ul>\n<p>\u0412\u044b\u0441\u043e\u043a\u043e\u0443\u0440\u043e\u0432\u043d\u0435\u0432\u044b\u0439 <code>Transaction<\/code> \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430:<\/p>\n<ul>\n<li>\n<p>snapshot \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f;<\/p>\n<\/li>\n<li>\n<p>\u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 reactive proxy;<\/p>\n<\/li>\n<li>\n<p>\u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 payload;<\/p>\n<\/li>\n<li>\n<p>\u0432\u044b\u0437\u043e\u0432 compensation \u0432 \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435;<\/p>\n<\/li>\n<li>\n<p>\u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 <code>StateManager<\/code>;<\/p>\n<\/li>\n<li>\n<p>\u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 API;<\/p>\n<\/li>\n<li>\n<p>\u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 middleware.<\/p>\n<\/li>\n<\/ul>\n<p>\u0422\u0430\u043a\u043e\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u043c. \u041d\u0438\u0437\u043a\u043e\u0443\u0440\u043e\u0432\u043d\u0435\u0432\u044b\u0439 executor \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e, \u0430 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 API \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0434\u0435\u0442\u0430\u043b\u0435\u0439 retry-\u0446\u0438\u043a\u043b\u0430.<\/p>\n<h3>Retry: \u043f\u0440\u043e\u0441\u0442\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0441 \u043d\u0435\u043f\u0440\u043e\u0441\u0442\u044b\u043c\u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u0441\u0442\u0432\u0438\u044f\u043c\u0438<\/h3>\n<p>\u041f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u043d\u0435\u0441\u043b\u043e\u0436\u043d\u043e:<\/p>\n<pre><code class=\"typescript\">async function retry&lt;T&gt;(  operation: () =&gt; Promise&lt;T&gt;,  retries: number,): Promise&lt;T&gt; {  let attempt = 0;  while (true) {    try {      return await operation();    } catch (error) {      if (attempt &gt;= retries) {        throw error;      }      attempt += 1;    }  }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u043d\u0435 \u0432 \u0446\u0438\u043a\u043b\u0435, \u0430 \u0432 \u0441\u0435\u043c\u0430\u043d\u0442\u0438\u043a\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438.<\/p>\n<p>\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0442\u0430\u043a\u043e\u0439 \u0448\u0430\u0433:<\/p>\n<pre><code class=\"typescript\">.addStep(  'charge',  async state =&gt; {    const payment = await payments.charge(100);    state.paymentId = payment.id;    throw new Error('network response parsing failed');  },  undefined,  { retries: 2 },)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043b\u0430\u0442\u0451\u0436 \u043c\u043e\u0433 \u043f\u0440\u043e\u0439\u0442\u0438, \u0445\u043e\u0442\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043b\u0430\u0441\u044c \u043e\u0448\u0438\u0431\u043a\u043e\u0439. \u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u043f\u043e\u043f\u044b\u0442\u043a\u0430 \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u0430 \u0441\u043f\u0438\u0441\u0430\u0442\u044c \u0434\u0435\u043d\u044c\u0433\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e.<\/p>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443 retry \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u0435\u043d \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u0445\u043e\u0442\u044f \u0431\u044b \u043e\u0434\u043d\u043e\u0433\u043e \u0443\u0441\u043b\u043e\u0432\u0438\u044f:<\/p>\n<ul>\n<li>\n<p>\u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u0430;<\/p>\n<\/li>\n<li>\n<p>\u0432\u043d\u0435\u0448\u043d\u0438\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 idempotency key;<\/p>\n<\/li>\n<li>\n<p>\u0448\u0430\u0433 \u0443\u043c\u0435\u0435\u0442 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c, \u0431\u044b\u043b \u043b\u0438 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0439 \u0432\u044b\u0437\u043e\u0432 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d;<\/p>\n<\/li>\n<li>\n<p>\u043f\u043e\u0432\u0442\u043e\u0440\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u043e\u0448\u0438\u0431\u043e\u043a, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e \u0432\u043e\u0437\u043d\u0438\u043a\u043b\u0438 \u0434\u043e side effect.<\/p>\n<\/li>\n<\/ul>\n<p>\u0418\u043c\u0435\u043d\u043d\u043e \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432 checkout-\u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f <code>requestId<\/code>:<\/p>\n<pre><code class=\"typescript\">idempotencyKey: `charge:${payload.requestId}`<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>Staga \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u044e, \u043d\u043e \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u044b\u0439 \u0432\u043d\u0435\u0448\u043d\u0438\u0439 API \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u044b\u043c.<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0432\u0430\u0436\u043d\u043e, \u0447\u0442\u043e \u043d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u0430\u044f \u043f\u043e\u043f\u044b\u0442\u043a\u0430 \u043c\u043e\u0436\u0435\u0442 \u0443\u0441\u043f\u0435\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442 \u0434\u043e \u0432\u044b\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u043d\u0438\u044f \u043e\u0448\u0438\u0431\u043a\u0438. \u0424\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u0439 rollback \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442 snapshot, \u043d\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u043f\u043e\u043f\u044b\u0442\u043a\u0430 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u0430 \u0432 \u0442\u043e\u043c \u0436\u0435 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 retryable-\u0448\u0430\u0433\u0438 \u043b\u0443\u0447\u0448\u0435 \u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0442\u0430\u043a:<\/p>\n<ol>\n<li>\n<p>\u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0432\u043d\u0435\u0448\u043d\u0438\u0439 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442;<\/p>\n<\/li>\n<li>\n<p>\u0437\u0430\u0442\u0435\u043c \u043e\u0434\u043d\u0438\u043c \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u043c \u0431\u043b\u043e\u043a\u043e\u043c \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435;<\/p>\n<\/li>\n<li>\n<p>\u043d\u0435 \u043c\u0443\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c state \u0434\u043e \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c\u0441\u044f \u043e\u0448\u0438\u0431\u043a\u043e\u0439.<\/p>\n<\/li>\n<\/ol>\n<h3>Timeout \u043d\u0435 \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u043a\u043e\u0434 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438<\/h3>\n<p>\u0414\u043b\u044f \u0442\u0430\u0439\u043c\u0430\u0443\u0442\u0430 \u043e\u0431\u044b\u0447\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0433\u043e\u043d\u043a\u0430 \u043c\u0435\u0436\u0434\u0443 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0435\u0439 \u0438 \u0442\u0430\u0439\u043c\u0435\u0440\u043e\u043c:<\/p>\n<pre><code class=\"typescript\">function withTimeout&lt;T&gt;(  operation: Promise&lt;T&gt;,  timeoutMs: number,): Promise&lt;T&gt; {  return Promise.race([    operation,    new Promise&lt;never&gt;((_, reject) =&gt; {      setTimeout(() =&gt; {        reject(new Error(`Timeout after ${timeoutMs} ms`));      }, timeoutMs);    }),  ]);}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0438\u0441\u0442\u0435\u0447\u0435\u043d\u0438\u044f \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0449\u0438\u0439 \u043a\u043e\u0434 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043e\u0448\u0438\u0431\u043a\u0443. \u041d\u043e \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 Promise \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c\u0441\u044f, \u0435\u0441\u043b\u0438 \u0441\u0430\u043c\u0430 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u043e\u0442\u043c\u0435\u043d\u0443.<\/p>\n<p>\u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0442\u0430\u043a\u043e\u0439 \u043a\u043e\u0434 \u043d\u0435 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u0443\u0435\u0442, \u0447\u0442\u043e \u0437\u0430\u043f\u0440\u043e\u0441 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d:<\/p>\n<pre><code class=\"typescript\">await withTimeout(fetch('\/api\/payment'), 1000);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0427\u0442\u043e\u0431\u044b \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u043f\u0440\u0435\u0440\u0432\u0430\u0442\u044c <code>fetch<\/code>, \u043d\u0443\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c <code>AbortSignal<\/code>:<\/p>\n<pre><code class=\"typescript\">const controller = new AbortController();await fetch('\/api\/payment', {  signal: controller.signal,});controller.abort();<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443 timeout \u0432 Staga \u0441\u0442\u043e\u0438\u0442 \u0432\u043e\u0441\u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u043a\u0430\u043a \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f, \u0430 \u043d\u0435 \u043a\u0430\u043a \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u043e\u0435 \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438. \u0412\u043d\u0435\u0448\u043d\u0438\u0435 \u0432\u044b\u0437\u043e\u0432\u044b \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u0434\u043e\u043b\u0436\u043d\u044b \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c cancellation \u0438\u043b\u0438 \u0431\u044b\u0442\u044c \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u043c\u0438 \u043f\u0440\u0438 \u043f\u043e\u0437\u0434\u043d\u0435\u043c \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0438.<\/p>\n<h3>\u0421\u043e\u0431\u044b\u0442\u0438\u044f \u043a\u0430\u043a \u043e\u0441\u043d\u043e\u0432\u0430 \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0435\u043c\u043e\u0441\u0442\u0438<\/h3>\n<p>\u0422\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u044f:<\/p>\n<pre><code>transaction:startstep:startstep:successstep:retrytransaction:successtransaction:failstep:rollbacktransaction:rollback<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043e\u0434\u043f\u0438\u0441\u043a\u0430 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"typescript\">const unsubscribeRetry = saga.onSagaEvent(  'step:retry',  event =&gt; {    console.log({      step: event.stepName,      attempt: event.attempt,      error: event.lastError.message,    });  },);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041c\u043e\u0436\u043d\u043e \u0441\u043b\u0443\u0448\u0430\u0442\u044c \u0432\u0441\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u044f:<\/p>\n<pre><code class=\"typescript\">const unsubscribeAll = saga.onAnyEvent(event =&gt; {  console.log(event.type, event.timestamp);});<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418\u043b\u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c <code>Stream<\/code>:<\/p>\n<pre><code class=\"typescript\">const failures = saga.onEventStream('transaction:fail');const unsubscribe = failures.subscribe(event =&gt; {  console.error(event);});<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c API \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u043c\u0435\u0442\u0440\u0438\u043a\u0438, devtools \u0438\u043b\u0438 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0441 OpenTelemetry.<\/p>\n<h3>Middleware<\/h3>\n<p>Middleware \u043e\u0431\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442 \u0432\u0441\u044e \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044e \u043f\u043e \u0442\u043e\u043c\u0443 \u0436\u0435 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0443, \u0447\u0442\u043e \u0438 Koa:<\/p>\n<pre><code class=\"typescript\">const middleware = async (context, next) =&gt; {  console.log('before', context.transaction.name);  await next();  console.log('after', context.transaction.name);};saga.use(middleware);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412 \u043f\u0430\u043a\u0435\u0442\u0435 \u0435\u0441\u0442\u044c \u0433\u043e\u0442\u043e\u0432\u044b\u0435 middleware \u0434\u043b\u044f \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f, \u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0432 <code>localStorage<\/code>.<\/p>\n<pre><code class=\"typescript\">saga.use(createLoggingMiddleware());saga.use(  createTimingMiddleware((name, duration, state) =&gt; {    metrics.observe('staga_transaction_duration', duration, {      transaction: name,    });  }),);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>Persistence middleware \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u0441\u043b\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0433\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438:<\/p>\n<pre><code class=\"typescript\">saga.use(  createPersistenceMiddleware('checkout-state'),);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u042d\u0442\u043e \u0443\u0434\u043e\u0431\u043d\u044b\u0439 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u0434\u043b\u044f \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043d\u044b\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439, \u043d\u043e \u043e\u043d \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f durable storage \u0434\u043b\u044f \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0445 workflow. <code>localStorage<\/code> \u043d\u0435 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u0441\u043b\u0435 \u043f\u0430\u0434\u0435\u043d\u0438\u044f Node.js-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u043d\u0430 \u0434\u0440\u0443\u0433\u043e\u0439 \u043c\u0430\u0448\u0438\u043d\u0435.<\/p>\n<h3>Undo, redo \u0438 snapshots<\/h3>\n<p><code>StateManager<\/code> \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u043e\u0442 saga:<\/p>\n<pre><code class=\"typescript\">import { StateManager } from '@staga\/core';const state = new StateManager(  { count: 0 },  {    maxUndoHistory: 50,    maxSnapshots: 10,    snapshotInterval: 5,  },);state.setState({ count: 1 });state.setState({ count: 2 });state.undo();console.log(state.getState()); \/\/ { count: 1 }state.redo();console.log(state.getState()); \/\/ { count: 2 }<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0414\u043b\u044f transaction-style \u043c\u0443\u0442\u0430\u0446\u0438\u0439 \u0443\u0441\u043f\u0435\u0448\u043d\u0430\u044f \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0432 undo history \u043a\u0430\u043a \u043e\u0434\u043d\u0443 \u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u0442\u043e\u0447\u043a\u0443 \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0430.<\/p>\n<p>Snapshots \u043e\u0442\u043b\u0438\u0447\u0430\u044e\u0442\u0441\u044f \u043e\u0442 undo history. Undo \u043d\u0443\u0436\u0435\u043d \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u0438\u043b\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044e \u0434\u043b\u044f \u043f\u043e\u0448\u0430\u0433\u043e\u0432\u043e\u0433\u043e \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f \u043d\u0430\u0437\u0430\u0434. Snapshot \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0442\u043e\u0447\u043a\u0430 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438.<\/p>\n<p>\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0438\u0441\u0442\u043e\u0440\u0438\u044f \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0430, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0440\u0430\u0441\u0442\u0438 \u0431\u0435\u0441\u043a\u043e\u043d\u0435\u0447\u043d\u043e. \u0422\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043a\u043b\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044f.<\/p>\n<p>\u042d\u0442\u043e \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e \u0432\u0430\u0436\u043d\u043e \u0434\u043b\u044f \u043d\u0435\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0445 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439:<\/p>\n<pre><code class=\"typescript\">const state = new StateManager(initialState, {  clone: value =&gt; structuredClone(value),  equalityFn: (a, b) =&gt; deepEqual(a, b),});<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u043e\u0435 \u043a\u043b\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d\u043e \u0432 \u043f\u0435\u0440\u0432\u0443\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u043d\u0430 \u043e\u0431\u044b\u0447\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0438 \u043c\u0430\u0441\u0441\u0438\u0432\u044b. \u0414\u043b\u044f <code>Date<\/code>, <code>Map<\/code>, <code>Set<\/code> \u0438 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u043e\u0432 \u043a\u043b\u0430\u0441\u0441\u043e\u0432 \u043b\u0443\u0447\u0448\u0435 \u044f\u0432\u043d\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044e.<\/p>\n<h3>\u0417\u0430\u043f\u0438\u0441\u044c \u0438 replay<\/h3>\n<p>Staga \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u044f:<\/p>\n<pre><code class=\"typescript\">saga.startRecording();await checkout.run(payload);saga.stopRecording();const events = saga.getRecordedEvents();<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0417\u0430\u0442\u0435\u043c \u0438\u0445 \u043c\u043e\u0436\u043d\u043e \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0441\u0442\u0438:<\/p>\n<pre><code class=\"typescript\">await saga.startReplay({ delay: 50 });<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u042d\u0442\u043e \u043f\u043e\u043b\u0435\u0437\u043d\u043e \u0434\u043b\u044f \u0434\u0435\u043c\u043e, \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u043e\u0439 \u043e\u0442\u043b\u0430\u0434\u043a\u0438 \u0438 \u0430\u043d\u0430\u043b\u0438\u0437\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439.<\/p>\n<p>\u041d\u043e \u0437\u0434\u0435\u0441\u044c \u0432\u0430\u0436\u043d\u043e \u043d\u0435 \u043f\u0435\u0440\u0435\u043e\u0446\u0435\u043d\u0438\u0432\u0430\u0442\u044c \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c. \u0422\u0435\u043a\u0443\u0449\u0438\u0439 replay \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u043c event sourcing \u0438 \u043d\u0435 \u0434\u0435\u043b\u0430\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u043e\u0439\u0447\u0438\u0432\u044b\u043c \u043a \u043f\u0430\u0434\u0435\u043d\u0438\u044e \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430. \u0417\u0430\u043f\u0438\u0441\u044c \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0432 \u043f\u0430\u043c\u044f\u0442\u0438, \u0430 \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u0435\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u0451\u043d\u043d\u044b\u0435 state changes \u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u044f. \u0412\u043d\u0435\u0448\u043d\u0438\u0435 API-\u0432\u044b\u0437\u043e\u0432\u044b \u0442\u0430\u043a\u0438\u043c \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c \u043d\u0435 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u044e\u0442\u0441\u044f.<\/p>\n<p>\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0435\u0433\u043e durable execution \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u044f\u0442\u0441\u044f \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u044b\u0439 \u0436\u0443\u0440\u043d\u0430\u043b, versioning \u0441\u043e\u0431\u044b\u0442\u0438\u0439, \u0434\u0435\u0442\u0435\u0440\u043c\u0438\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0435 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0438 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 runtime.<\/p>\n<h3>\u041a\u0430\u043a \u044f \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u044e rollback<\/h3>\n<p>\u0414\u043b\u044f \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u0430\u043a\u0435\u0442\u0430 happy path \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0441\u0430\u043c\u043e\u0439 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0447\u0430\u0441\u0442\u044c\u044e. \u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u043d\u0443\u0436\u043d\u044b \u0432\u043e\u043a\u0440\u0443\u0433 \u043e\u0448\u0438\u0431\u043e\u043a.<\/p>\n<p>\u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u0433\u043e \u043f\u043e\u0440\u044f\u0434\u043a\u0430 \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u0439:<\/p>\n<pre><code class=\"typescript\">import { describe, expect, it } from 'vitest';import { SagaManager } from '@staga\/core';describe('transaction rollback', () =&gt; {  it('compensates completed steps in reverse order', async () =&gt; {    const saga = SagaManager.create({ count: 0 });    const rollbackOrder: string[] = [];    const transaction = saga      .createTransaction&lt;void&gt;('test')      .addStep(        'first',        state =&gt; {          state.count += 1;        },        state =&gt; {          rollbackOrder.push('first');          state.count -= 1;        },      )      .addStep(        'second',        state =&gt; {          state.count += 10;        },        state =&gt; {          rollbackOrder.push('second');          state.count -= 10;        },      )      .addStep('fail', () =&gt; {        throw new Error('boom');      });    await expect(      transaction.run(undefined),    ).rejects.toThrow('rolled back');    expect(rollbackOrder).toEqual([      'second',      'first',    ]);    expect(saga.getState()).toEqual({      count: 0,    });  });});<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041a\u0440\u043e\u043c\u0435 \u044d\u0442\u043e\u0433\u043e, \u0441\u0442\u043e\u0438\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c:<\/p>\n<ul>\n<li>\n<p>\u043e\u0448\u0438\u0431\u043a\u0430 \u043d\u0430 \u043f\u0435\u0440\u0432\u043e\u043c \u0448\u0430\u0433\u0435;<\/p>\n<\/li>\n<li>\n<p>\u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0441\u043b\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0443\u0441\u043f\u0435\u0448\u043d\u044b\u0445 \u0448\u0430\u0433\u043e\u0432;<\/p>\n<\/li>\n<li>\n<p>compensation, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0441\u0430\u043c\u0430 \u043f\u0430\u0434\u0430\u0435\u0442;<\/p>\n<\/li>\n<li>\n<p>timeout;<\/p>\n<\/li>\n<li>\n<p>\u0442\u043e\u0447\u043d\u043e\u0435 \u0447\u0438\u0441\u043b\u043e retry;<\/p>\n<\/li>\n<li>\n<p>undo \u043f\u043e\u0441\u043b\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0439 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438;<\/p>\n<\/li>\n<li>\n<p>\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 selectors \u043f\u043e\u0441\u043b\u0435 rollback;<\/p>\n<\/li>\n<li>\n<p>\u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0435 \u043c\u0443\u0442\u0430\u0446\u0438\u0438 \u043c\u0430\u0441\u0441\u0438\u0432\u043e\u0432 \u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432;<\/p>\n<\/li>\n<li>\n<p>\u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0439;<\/p>\n<\/li>\n<li>\n<p>\u043e\u0447\u0438\u0441\u0442\u043a\u0443 \u043f\u043e\u0434\u043f\u0438\u0441\u043e\u043a \u0438 \u0442\u0430\u0439\u043c\u0435\u0440\u043e\u0432.<\/p>\n<\/li>\n<\/ul>\n<p>\u0418\u043c\u0435\u043d\u043d\u043e edge cases \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0442 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u043e \u0442\u0430\u043a\u043e\u0433\u043e \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430. \u0420\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c <code>setState<\/code> \u043b\u0435\u0433\u043a\u043e. \u0413\u043e\u0440\u0430\u0437\u0434\u043e \u0441\u043b\u043e\u0436\u043d\u0435\u0435 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u0438\u0441\u0442\u043e\u0440\u0438\u044f, proxy, selectors \u0438 rollback \u043d\u0435 \u0440\u0430\u0441\u0445\u043e\u0434\u0438\u043b\u0438\u0441\u044c \u043f\u043e\u0441\u043b\u0435 \u043e\u0448\u0438\u0431\u043a\u0438.<\/p>\n<h3>\u0427\u0435\u043c Staga \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u043e\u0442 \u043e\u0431\u044b\u0447\u043d\u043e\u0433\u043e state manager<\/h3>\n<p>Redux Toolkit \u0438 Zustand \u043e\u0442\u043b\u0438\u0447\u043d\u043e \u0440\u0435\u0448\u0430\u044e\u0442 \u0437\u0430\u0434\u0430\u0447\u0443 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0438 \u0440\u0435\u0430\u043a\u0446\u0438\u0438 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u043d\u0430 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f. Redux Saga \u0438 listener middleware \u043f\u043e\u043c\u043e\u0433\u0430\u044e\u0442 \u043e\u0440\u043a\u0435\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c side effects. Workflow engines \u0440\u0435\u0448\u0430\u044e\u0442 \u0434\u043e\u043b\u0433\u043e\u0436\u0438\u0432\u0443\u0449\u0438\u0435 \u0438 \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u044b\u0435 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b.<\/p>\n<p>Staga \u0437\u0430\u043d\u0438\u043c\u0430\u0435\u0442 \u0431\u043e\u043b\u0435\u0435 \u0443\u0437\u043a\u0443\u044e \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u0443\u044e \u043d\u0438\u0448\u0443:<\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">\u0418\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u041a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u044f \u0448\u0430\u0433\u043e\u0432<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">Redux Toolkit \/ Zustand<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0412\u0440\u0443\u0447\u043d\u0443\u044e<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">Redux Saga<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041e\u0440\u043a\u0435\u0441\u0442\u0440\u0430\u0446\u0438\u044f side effects \u0432\u043e\u043a\u0440\u0443\u0433 Redux<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0412\u0440\u0443\u0447\u043d\u0443\u044e<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">Staga<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043f\u043b\u044e\u0441 \u043c\u043d\u043e\u0433\u043e\u0448\u0430\u0433\u043e\u0432\u044b\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0415\u0441\u0442\u044c<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">Temporal \u0438 \u043f\u043e\u0445\u043e\u0436\u0438\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b<\/p>\n<\/td>\n<td>\n<p align=\"left\">Durable workflows<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0415\u0441\u0442\u044c \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 workflow-\u043f\u0430\u0442\u0442\u0435\u0440\u043d\u043e\u0432<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>\u042f \u0431\u044b \u043d\u0435 \u0441\u0442\u0430\u043b \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0438\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0435 Redux-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 Staga \u0442\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u0434\u0438 \u0434\u0440\u0443\u0433\u043e\u0433\u043e API.<\/p>\n<p>\u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u043f\u043e\u043b\u0435\u0437\u043d\u0435\u0435 \u0442\u0430\u043c, \u0433\u0434\u0435 \u043c\u043d\u043e\u0433\u043e \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439 \u0432\u0438\u0434\u0430:<\/p>\n<pre><code>\u0441\u0434\u0435\u043b\u0430\u0442\u044c A\u0437\u0430\u0442\u0435\u043c B\u0437\u0430\u0442\u0435\u043c C\u0435\u0441\u043b\u0438 \u0447\u0442\u043e-\u0442\u043e \u0443\u043f\u0430\u043b\u043e, \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u043e\u0435<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440:<\/p>\n<ul>\n<li>\n<p>checkout;<\/p>\n<\/li>\n<li>\n<p>\u043c\u0430\u0441\u0442\u0435\u0440 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f;<\/p>\n<\/li>\n<li>\n<p>\u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u0435\u0433\u043e \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432;<\/p>\n<\/li>\n<li>\n<p>\u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 deployment pipeline;<\/p>\n<\/li>\n<li>\n<p>\u043c\u043d\u043e\u0433\u043e\u0448\u0430\u0433\u043e\u0432\u0430\u044f \u0444\u043e\u0440\u043c\u0430;<\/p>\n<\/li>\n<li>\n<p>\u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430\u043c\u0438 AI-\u0430\u0433\u0435\u043d\u0442\u0430 \u0432\u043d\u0443\u0442\u0440\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430;<\/p>\n<\/li>\n<li>\n<p>\u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0430 \u0441 undo\/redo.<\/p>\n<\/li>\n<\/ul>\n<h3>\u0427\u0442\u043e \u044f \u043f\u043e\u043d\u044f\u043b, \u043f\u043e\u043a\u0430 \u043f\u0438\u0441\u0430\u043b \u0441\u0432\u043e\u0439 state manager<\/h3>\n<p>\u0413\u043b\u0430\u0432\u043d\u044b\u0439 \u0432\u044b\u0432\u043e\u0434 \u043e\u043a\u0430\u0437\u0430\u043b\u0441\u044f \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043d\u0435\u043e\u0436\u0438\u0434\u0430\u043d\u043d\u044b\u043c. \u0421\u0430\u043c\u0430\u044f \u043f\u0440\u043e\u0441\u0442\u0430\u044f \u0447\u0430\u0441\u0442\u044c state manager \u0437\u0430\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0432 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0438 state.<\/p>\n<p>\u041d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0435 \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0432\u043e\u043a\u0440\u0443\u0433 \u0432\u0440\u0435\u043c\u0435\u043d\u0438:<\/p>\n<ul>\n<li>\n<p>\u043a\u043e\u0433\u0434\u0430 \u043f\u043e\u0434\u043f\u0438\u0441\u0447\u0438\u043a \u0434\u043e\u043b\u0436\u0435\u043d \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435;<\/p>\n<\/li>\n<li>\n<p>\u0441\u0447\u0438\u0442\u0430\u0442\u044c \u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0443\u0442\u0430\u0446\u0438\u0439 \u043e\u0434\u043d\u043e\u0439 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0435\u0439;<\/p>\n<\/li>\n<li>\n<p>\u0447\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u0441 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u043c\u0438 \u0448\u0430\u0433\u0430\u043c\u0438;<\/p>\n<\/li>\n<li>\n<p>\u043a\u0430\u043a \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u0442\u044c \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044e \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e;<\/p>\n<\/li>\n<li>\n<p>\u0447\u0442\u043e \u043f\u0440\u043e\u0438\u0437\u043e\u0439\u0434\u0451\u0442 \u043f\u043e\u0441\u043b\u0435 timeout;<\/p>\n<\/li>\n<li>\n<p>\u0432 \u043a\u0430\u043a\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c compensation;<\/p>\n<\/li>\n<li>\n<p>\u043a\u0430\u043a \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c rollback \u0441 selectors;<\/p>\n<\/li>\n<li>\n<p>\u0447\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c, \u0435\u0441\u043b\u0438 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0442\u043e\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043b\u043e\u0441\u044c \u043e\u0448\u0438\u0431\u043a\u043e\u0439.<\/p>\n<\/li>\n<\/ul>\n<p>\u0422\u043e \u0435\u0441\u0442\u044c state management \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0431\u044b\u0441\u0442\u0440\u043e \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u0432 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430\u043c\u0438 \u0438 \u0436\u0438\u0437\u043d\u0435\u043d\u043d\u044b\u043c \u0446\u0438\u043a\u043b\u043e\u043c \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439.<\/p>\n<p>\u0418\u043c\u0435\u043d\u043d\u043e \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u043e\u0441\u0442\u0430\u0432\u0438\u043b \u0432 Staga \u0434\u0432\u0430 \u0443\u0440\u043e\u0432\u043d\u044f.<\/p>\n<p>\u041d\u0438\u0437\u043a\u043e\u0443\u0440\u043e\u0432\u043d\u0435\u0432\u044b\u0439 <code>statekit<\/code> \u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0438\u043c \u043d\u0430\u0431\u043e\u0440\u043e\u043c \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0445 \u043f\u0440\u0438\u043c\u0438\u0442\u0438\u0432\u043e\u0432. \u041f\u043e\u0432\u0435\u0440\u0445 \u043d\u0435\u0433\u043e <code>SagaManager<\/code> \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438, \u0441\u043e\u0431\u044b\u0442\u0438\u044f, middleware \u0438 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435.<\/p>\n<p>\u0422\u0430\u043a \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u044b\u0439 store, \u043a\u0430\u043a transaction runner \u0438\u043b\u0438 \u043a\u0430\u043a \u0441\u043e\u0447\u0435\u0442\u0430\u043d\u0438\u0435 \u043e\u0431\u043e\u0438\u0445 \u043f\u043e\u0434\u0445\u043e\u0434\u043e\u0432.<\/p>\n<h3>\u0418\u0442\u043e\u0433<\/h3>\n<p>Staga \u043d\u0435 \u043f\u044b\u0442\u0430\u0435\u0442\u0441\u044f \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u0432\u0441\u0435 state managers \u0438 workflow engines \u0441\u0440\u0430\u0437\u0443.<\/p>\n<p>\u042d\u0442\u043e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f \u0441\u043b\u0443\u0447\u0430\u044f, \u043a\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044e \u043c\u0430\u043b\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c state. \u041d\u0443\u0436\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439, \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0442\u044c \u0437\u0430 \u0438\u0445 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435\u043c \u0438 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043f\u043e\u0441\u043b\u0435 \u043e\u0448\u0438\u0431\u043a\u0438.<\/p>\n<p>\u041c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u043f\u043e-\u043f\u0440\u0435\u0436\u043d\u0435\u043c\u0443 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043a\u043e\u043c\u043f\u0430\u043a\u0442\u043d\u043e:<\/p>\n<pre><code class=\"typescript\">const transaction = saga  .createTransaction&lt;Payload&gt;('operation')  .addStep('first', executeFirst, compensateFirst)  .addStep('second', executeSecond, compensateSecond, {    retries: 2,    timeout: 3000,  });await transaction.run(payload);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u043e \u0437\u0430 \u044d\u0442\u0438\u043c API \u0441\u043a\u0440\u044b\u0432\u0430\u044e\u0442\u0441\u044f snapshots, proxy, reactive selectors, retry-\u0446\u0438\u043a\u043b, \u0442\u0430\u0439\u043c\u0435\u0440\u044b, middleware, \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0438 \u043e\u0431\u0440\u0430\u0442\u043d\u044b\u0439 \u043f\u043e\u0440\u044f\u0434\u043e\u043a \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u0439.<\/p>\n<p>\u0414\u043b\u044f \u043c\u0435\u043d\u044f \u044d\u0442\u043e\u0442 \u043f\u0440\u043e\u0435\u043a\u0442 \u0441\u0442\u0430\u043b \u0445\u043e\u0440\u043e\u0448\u0438\u043c \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c \u0433\u043b\u0443\u0431\u0436\u0435 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 state management, \u043d\u043e \u0438 \u0432 \u0442\u043e\u043c, \u043a\u0430\u043a \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432\u0435\u0434\u0451\u0442 \u0441\u0435\u0431\u044f \u043c\u0435\u0436\u0434\u0443 \u0434\u0432\u0443\u043c\u044f \u0443\u0441\u0442\u043e\u0439\u0447\u0438\u0432\u044b\u043c\u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f\u043c\u0438, \u043a\u043e\u0433\u0434\u0430 \u0447\u0430\u0441\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u044b \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430, \u0430 \u0447\u0430\u0441\u0442\u044c \u0435\u0449\u0451 \u043c\u043e\u0436\u0435\u0442 \u0443\u043f\u0430\u0441\u0442\u044c.<\/p>\n<p>\u0418\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u044b, \u043f\u0430\u043a\u0435\u0442 \u043d\u0435 \u0438\u043c\u0435\u0435\u0442 runtime-\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u0438 \u043e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u043d \u043f\u043e\u0434 MIT.<\/p>\n<h3>\u0421\u0441\u044b\u043b\u043a\u0438<\/h3>\n<ul>\n<li>\n<p><a href=\"https:\/\/www.npmjs.com\/package\/@staga\/core\" rel=\"noopener noreferrer nofollow\">@staga\/core \u0432 npm<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/kubarskii\/staga\" rel=\"noopener noreferrer nofollow\">\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434 \u043d\u0430 GitHub<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/www.cs.princeton.edu\/research\/techreps\/598\" rel=\"noopener noreferrer nofollow\">\u041e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u0430\u044f \u0440\u0430\u0431\u043e\u0442\u0430 SAGAS<\/a><\/p>\n<\/li>\n<\/ul>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1070960\/\">https:\/\/habr.com\/ru\/articles\/1070960\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u0421\u0442\u0435\u0439\u0442-\u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440\u043e\u0432 \u0432 JavaScript \u0443\u0436\u0435 \u0441\u0442\u043e\u043b\u044c\u043a\u043e, \u0447\u0442\u043e \u0438\u0434\u0435\u044f \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0435\u0449\u0451 \u043e\u0434\u0438\u043d \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0441\u043e\u043c\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e. \u0415\u0441\u0442\u044c Redux Toolkit, Zustand, MobX, Jotai, XState \u0438 \u0434\u0435\u0441\u044f\u0442\u043a\u0438 \u043c\u0435\u043d\u0435\u0435 \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0445 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a. \u041f\u043e\u0447\u0442\u0438 \u0434\u043b\u044f \u043b\u044e\u0431\u043e\u0433\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u0430 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u0433\u043e\u0442\u043e\u0432\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435. \u041d\u043e \u0432\u0441\u0435 \u0436\u0435 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430, \u0440\u0435\u0448\u0438\u043b \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0432\u043e\u044e \u0438\u0434\u0435\u044e.\u0418\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u044f \u043d\u0435 \u0441\u043e\u0431\u0438\u0440\u0430\u043b\u0441\u044f \u0434\u0435\u043b\u0430\u0442\u044c \u0435\u0449\u0451 \u043e\u0434\u0438\u043d store \u0441 \u043c\u0435\u0442\u043e\u0434\u0430\u043c\u0438 getState, setState \u0438 subscribe.\u041c\u0435\u043d\u044f \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043e\u0432\u0430\u043b\u0430 \u0434\u0440\u0443\u0433\u0430\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430. \u0412\u043e \u043c\u043d\u043e\u0433\u0438\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u0435 \u043e\u0434\u043d\u043e\u0439 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0435\u0439, \u0430 \u0446\u0435\u043b\u044b\u043c \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u043c:\u0437\u0430\u0440\u0435\u0437\u0435\u0440\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u043e\u0432\u0430\u0440;\u0441\u043f\u0438\u0441\u0430\u0442\u044c \u0434\u0435\u043d\u044c\u0433\u0438;\u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0437\u0430\u043a\u0430\u0437;\u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435;\u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441.\u041f\u043e\u043a\u0430 \u0432\u0441\u0451 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u0442\u0430\u043a\u043e\u0439 \u043a\u043e\u0434 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u0430\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c await. \u041d\u043e \u0435\u0441\u043b\u0438 \u0442\u0440\u0435\u0442\u0438\u0439 \u0448\u0430\u0433 \u043f\u0430\u0434\u0430\u0435\u0442, \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u043e\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438. \u0422\u043e\u0432\u0430\u0440 \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0437\u0435\u0440\u0432\u0438\u0440\u043e\u0432\u0430\u043d, \u0434\u0435\u043d\u044c\u0433\u0438 \u0441\u043f\u0438\u0441\u0430\u043d\u044b, \u0430 \u0437\u0430\u043a\u0430\u0437\u0430 \u043d\u0435\u0442.\u041c\u043e\u0436\u043d\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e try\/catch, \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u0437\u0430\u043f\u043e\u043c\u043d\u0438\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0438 \u043e\u0442\u043a\u0430\u0442\u0438\u0442\u044c \u0438\u0445 \u0432 \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435. \u041f\u043e\u0442\u043e\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0438, \u0442\u0430\u0439\u043c\u0430\u0443\u0442\u044b, \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u0438 \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439. \u0412 \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u043c\u043e\u043c\u0435\u043d\u0442 \u043e\u0431\u044b\u0447\u043d\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u0432 \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0439 workflow engine.\u0422\u0430\u043a \u043f\u043e\u044f\u0432\u0438\u043b\u0441\u044f Staga, TypeScript-\u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u0435\u0442 \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0438 \u043c\u043d\u043e\u0433\u043e\u0448\u0430\u0433\u043e\u0432\u044b\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u0441 \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u0435\u0439.\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0440\u0430\u0437\u0431\u0435\u0440\u0443, \u0437\u0430\u0447\u0435\u043c \u044d\u0442\u043e \u043d\u0443\u0436\u043d\u043e, \u043a\u0430\u043a \u0443\u0441\u0442\u0440\u043e\u0435\u043d \u043f\u0430\u043a\u0435\u0442 \u0432\u043d\u0443\u0442\u0440\u0438, \u0433\u0434\u0435 \u0437\u0430\u043a\u0430\u043d\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043e\u0431\u044b\u0447\u043d\u044b\u0439 state management \u0438 \u043f\u043e\u0447\u0435\u043c\u0443 \u0441\u043b\u043e\u0432\u043e \u00ab\u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f\u00bb \u0437\u0434\u0435\u0441\u044c \u043d\u0443\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043e\u0441\u0442\u043e\u0440\u043e\u0436\u043d\u043e.TL;DRStaga \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043e\u043f\u0438\u0441\u0430\u0442\u044c \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044e \u043a\u0430\u043a \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0448\u0430\u0433\u043e\u0432. \u0423 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0448\u0430\u0433\u0430 \u0435\u0441\u0442\u044c \u043e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0438 \u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u044f:import { SagaManager } from &#8216;@staga\/core&#8217;;type State = {  balance: number;  orders: string[];};const saga = SagaManager.create&lt;State&gt;({  balance: 1000,  orders: [],});const purchase = saga  .createTransaction&lt;{ orderId: string; amount: number }&gt;(&#8216;purchase&#8217;)  .addStep(    &#8216;debit&#8217;,    (state, payload) =&gt; {      state.balance -= payload.amount;    },    (state, payload) =&gt; {      state.balance += payload.amount;    },  )  .addStep(    &#8216;create-order&#8217;,    (state, payload) =&gt; {      state.orders.push(payload.orderId);    },    (state, payload) =&gt; {      state.orders = state.orders.filter(id =&gt; id !== payload.orderId);    },  );await purchase.run({ orderId: &#8216;order-42&#8217;, amount: 100 });\u0415\u0441\u043b\u0438 \u0432\u0442\u043e\u0440\u043e\u0439 \u0448\u0430\u0433 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0441\u044f \u043e\u0448\u0438\u0431\u043a\u043e\u0439, \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0435 \u0448\u0430\u0433\u0438 \u0431\u0443\u0434\u0443\u0442 \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u044b \u0432 \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435, \u0430 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0432\u0435\u0440\u043d\u0451\u0442\u0441\u044f \u043a \u0441\u043d\u0438\u043c\u043a\u0443, \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u043e\u043c\u0443 \u043f\u0435\u0440\u0435\u0434 \u0437\u0430\u043f\u0443\u0441\u043a\u043e\u043c \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438.\u041a\u0440\u043e\u043c\u0435 \u044d\u0442\u043e\u0433\u043e, \u0432 \u043f\u0430\u043a\u0435\u0442\u0435 \u0435\u0441\u0442\u044c:\u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440\u044b;undo\/redo \u0438 \u0441\u043d\u0438\u043c\u043a\u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f;retries \u0438 \u0442\u0430\u0439\u043c\u0430\u0443\u0442\u044b \u0434\u043b\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u0448\u0430\u0433\u043e\u0432;\u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u044f;middleware;\u0437\u0430\u043f\u0438\u0441\u044c \u0438 \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0439;\u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u043d\u0438\u0437\u043a\u043e\u0443\u0440\u043e\u0432\u043d\u0435\u0432\u044b\u0439 \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0439 \u043c\u043e\u0434\u0443\u043b\u044c statekit.\u041f\u0440\u0438 \u044d\u0442\u043e\u043c Staga \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432\u043d\u0443\u0442\u0440\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430. \u041e\u043d\u0430 \u043d\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0438 \u043d\u0435 \u0437\u0430\u043c\u0435\u043d\u044f\u0435\u0442 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u043b\u0438 Temporal.\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u043d\u0435 \u0432 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f\u041f\u0440\u043e\u0441\u0442\u0435\u0439\u0448\u0438\u0439 state manager \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0437\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0438\u043d\u0443\u0442:class Store&lt;T&gt; {  private state: T;  private listeners = new Set&lt;(state: T) =&gt; void&gt;();  constructor(initialState: T) {    this.state = initialState;  }  getState(): T {    return this.state;  }  setState(nextState: T): void {    this.state = nextState;    for (const listener of this.listeners) {      listener(this.state);    }  }  subscribe(listener: (state: T) =&gt; void): () =&gt; void {    this.listeners.add(listener);    return () =&gt; this.listeners.delete(listener);  }}\u0414\u043b\u044f \u043c\u043d\u043e\u0433\u0438\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e. \u0421\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u043d\u0435 \u0442\u043e\u0433\u0434\u0430, \u043a\u043e\u0433\u0434\u0430 \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043d\u043e\u0432\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435, \u0430 \u0442\u043e\u0433\u0434\u0430, \u043a\u043e\u0433\u0434\u0430 \u043e\u0434\u043d\u043e \u0431\u0438\u0437\u043d\u0435\u0441-\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u043c\u0435\u043d\u044f\u0435\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u0437 \u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u0432\u043d\u0435\u0448\u043d\u0438\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u044b.\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043e\u0431\u044b\u0447\u043d\u044b\u0439 checkout:async function checkout(input: CheckoutInput) {  const reservation = await inventory.reserve(input.items);  const payment = await payments.charge(input.amount);  const order = await orders.create(input);  return order;}\u0423 \u044d\u0442\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0435\u0441\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043d\u0435\u043f\u0440\u0438\u044f\u0442\u043d\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u0432:\u0440\u0435\u0437\u0435\u0440\u0432 \u0441\u043e\u0437\u0434\u0430\u043d, \u043d\u043e \u043f\u043b\u0430\u0442\u0451\u0436 \u043d\u0435 \u043f\u0440\u043e\u0448\u0451\u043b;\u043f\u043b\u0430\u0442\u0451\u0436 \u043f\u0440\u043e\u0448\u0451\u043b, \u043d\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0437\u0430\u043a\u0430\u0437\u0430 \u0443\u043f\u0430\u043b\u043e;\u0437\u0430\u043f\u0440\u043e\u0441 \u043a \u043f\u043b\u0430\u0442\u0451\u0436\u043d\u043e\u043c\u0443 \u0441\u0435\u0440\u0432\u0438\u0441\u0443 \u0437\u0430\u0432\u0438\u0441;\u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043f\u043e\u043b\u0443\u0447\u0438\u043b \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u0441\u0435\u0442\u0435\u0432\u0443\u044e \u043e\u0448\u0438\u0431\u043a\u0443;\u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a \u0441\u043f\u0438\u0441\u0430\u043b \u0434\u0435\u043d\u044c\u0433\u0438 \u0432\u0442\u043e\u0440\u043e\u0439 \u0440\u0430\u0437;UI \u0443\u0441\u043f\u0435\u043b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435.\u041e\u0431\u044b\u0447\u043d\u044b\u0439 store \u043d\u0435 \u0440\u0435\u0448\u0430\u0435\u0442 \u044d\u0442\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b. \u041e\u043d \u0443\u043c\u0435\u0435\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442, \u043d\u043e \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0437\u043d\u0430\u0435\u0442 \u043e \u0436\u0438\u0437\u043d\u0435\u043d\u043d\u043e\u043c \u0446\u0438\u043a\u043b\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438, \u043f\u043e\u0440\u044f\u0434\u043a\u0435 \u0448\u0430\u0433\u043e\u0432 \u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u0430\u0445 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f.\u041e\u0442\u043a\u0443\u0434\u0430 \u0437\u0434\u0435\u0441\u044c \u0432\u0437\u044f\u043b\u0430\u0441\u044c saga\u0418\u0434\u0435\u044f saga \u043f\u043e\u044f\u0432\u0438\u043b\u0430\u0441\u044c \u043d\u0435 \u0432 Redux \u0438 \u043d\u0435 \u0432\u043e \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0435. \u0415\u0451 \u043e\u043f\u0438\u0441\u0430\u043b\u0438 \u0413\u0435\u043a\u0442\u043e\u0440 \u0413\u0430\u0440\u0441\u0438\u044f-\u041c\u043e\u043b\u0438\u043d\u0430 \u0438 \u041a\u0435\u043d\u043d\u0435\u0442 \u0421\u0430\u043b\u0435\u043c \u0432 \u0440\u0430\u0431\u043e\u0442\u0435 SAGAS \u0432 1987 \u0433\u043e\u0434\u0443.\u0421\u0443\u0442\u044c \u0438\u0434\u0435\u0438 \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u0430\u044f. \u0414\u043b\u0438\u043d\u043d\u0430\u044f \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f \u0440\u0430\u0437\u0431\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0439:T1 -&gt; T2 -&gt; T3 -&gt; T4\u0414\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u043e\u0439 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u043c\u043e\u0436\u0435\u0442 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0438\u0440\u0443\u044e\u0449\u0435\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435:C1 &lt;- C2 &lt;- C3 &lt;- C4\u0415\u0441\u043b\u0438 T3 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043b\u0430\u0441\u044c \u043e\u0448\u0438\u0431\u043a\u043e\u0439, \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u0438 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0445 \u0448\u0430\u0433\u043e\u0432 \u0432 \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435:T1 -&gt; T2 -&gt; T3 X      C2 &lt;- C1\u041a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u044f \u043d\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043c\u0438\u0440 \u0432 \u0430\u0431\u0441\u043e\u043b\u044e\u0442\u043d\u043e \u043f\u0440\u0435\u0436\u043d\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u043e\u0435 \u043f\u0438\u0441\u044c\u043c\u043e \u043d\u0435\u043b\u044c\u0437\u044f \u00ab\u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u00bb. \u041c\u043e\u0436\u043d\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0434\u0440\u0443\u0433\u043e\u0435 \u043f\u0438\u0441\u044c\u043c\u043e, \u043f\u043e\u043c\u0435\u0442\u0438\u0442\u044c \u043f\u0435\u0440\u0432\u043e\u0435 \u043a\u0430\u043a \u043d\u0435\u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0435 \u0438\u043b\u0438 \u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u0438\u0440\u0443\u044e\u0449\u0435\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435.\u042d\u0442\u043e \u0432\u0430\u0436\u043d\u043e\u0435 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 saga \u043e\u0442 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445.\u0427\u0442\u043e \u044f \u0445\u043e\u0442\u0435\u043b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c\u041f\u0435\u0440\u0435\u0434 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u044f \u0437\u0430\u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043b \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u0439.\u0412\u043e-\u043f\u0435\u0440\u0432\u044b\u0445, \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0438 payload \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u044b. \u0415\u0441\u043b\u0438 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 { orderId: string }, \u0432\u0441\u0435 \u0448\u0430\u0433\u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0438\u043c\u0435\u043d\u043d\u043e \u044d\u0442\u043e\u0442 \u0442\u0438\u043f.\u0412\u043e-\u0432\u0442\u043e\u0440\u044b\u0445, \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u0434\u043e\u043b\u0436\u0435\u043d \u0447\u0438\u0442\u0430\u0442\u044c\u0441\u044f \u0441\u0432\u0435\u0440\u0445\u0443 \u0432\u043d\u0438\u0437 \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u044b\u0439 \u0431\u0438\u0437\u043d\u0435\u0441-\u043f\u0440\u043e\u0446\u0435\u0441\u0441. \u0411\u0435\u0437 \u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u0432, \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 action type \u0438 \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0441\u043b\u0443\u0436\u0435\u0431\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430.\u0412-\u0442\u0440\u0435\u0442\u044c\u0438\u0445, \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438, \u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u0434\u043b\u044f \u043a\u0430\u043a\u043e\u0433\u043e-\u0442\u043e \u0448\u0430\u0433\u0430 \u043d\u0435 \u0431\u044b\u043b\u0430 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0430 \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u044f.\u0412-\u0447\u0435\u0442\u0432\u0451\u0440\u0442\u044b\u0445, \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c\u0441\u044f \u0432 \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435.\u0412-\u043f\u044f\u0442\u044b\u0445, retry, timeout, \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0438 middleware \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u044b \u0441\u043c\u0435\u0448\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u0441 \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u043e\u0439 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0448\u0430\u0433\u0430.\u041f\u043e\u043b\u0443\u0447\u0438\u043b\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 API:const transaction = saga  .createTransaction&lt;Payload&gt;(&#8216;transaction-name&#8217;)  .addStep(    &#8216;step-name&#8217;,    async (state, payload) =&gt; {      \/\/ \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0448\u0430\u0433    },    async (state, payload) =&gt; {      \/\/ \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0448\u0430\u0433    },    {      retries: 2,      timeout: 3000,    },  );await transaction.run(payload);\u0417\u0434\u0435\u0441\u044c retries: 2 \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u0434\u0432\u0435 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0438 \u043f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0432\u043e\u0439. \u0412\u0441\u0435\u0433\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0432\u044b\u0437\u0432\u0430\u043d\u0430 \u0442\u0440\u0438 \u0440\u0430\u0437\u0430.\u041f\u043e\u043b\u043d\u044b\u0439 \u043f\u0440\u0438\u043c\u0435\u0440: checkout \u0441 \u043e\u0442\u043a\u0430\u0442\u043e\u043c\u041d\u0438\u0436\u0435 \u043f\u0440\u0438\u043c\u0435\u0440, \u0431\u043b\u0438\u0437\u043a\u0438\u0439 \u043a \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c\u0443 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044e. \u0412 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438 \u043c\u044b \u0445\u0440\u0430\u043d\u0438\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u0430\u043d\u043d\u044b\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0443\u0436\u043d\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044e \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430.import {  SagaManager,  createLoggingMiddleware,  createTimingMiddleware,} from &#8216;@staga\/core&#8217;;type CheckoutState = {  status: &#8216;idle&#8217; | &#8216;processing&#8217; | &#8216;completed&#8217; | &#8216;failed&#8217;;  reservationId: string | null;  paymentId: string | null;  orderId: string | null;  error: string | null;};type CheckoutPayload = {  requestId: string;  userId: string;  items: Array&lt;{    sku: string;    quantity: number;  }&gt;;  amount: number;};const saga = SagaManager.create&lt;CheckoutState&gt;({  status: &#8216;idle&#8217;,  reservationId: null,  paymentId: null,  orderId: null,  error: null,});saga.use(createLoggingMiddleware());saga.use(  createTimingMiddleware((transactionName, duration) =&gt; {    console.log(`${transactionName}: ${duration} ms`);  }),);\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u043f\u0438\u0448\u0435\u043c \u0448\u0430\u0433\u0438.const checkout = saga  .createTransaction&lt;CheckoutPayload&gt;(&#8216;checkout&#8217;)  .addStep(    &#8216;start&#8217;,    state =&gt; {      state.status = &#8216;processing&#8217;;      state.error = null;    },    state =&gt; {      state.status = &#8216;idle&#8217;;    },  )  .addStep(    &#8216;reserve-stock&#8217;,    async (state, payload) =&gt; {      const reservation = await inventory.reserve({        items: payload.items,        idempotencyKey: `reserve:${payload.requestId}`,      });      state.reservationId = reservation.id;    },    async state =&gt; {      if (!state.reservationId) {        return;      }      await inventory.release(state.reservationId);      state.reservationId = null;    },    {      retries: 2,      timeout: 3000,    },  )  .addStep(    &#8216;charge-payment&#8217;,    async (state, payload) =&gt; {      const payment = await payments.charge({        userId: payload.userId,        amount: payload.amount,        idempotencyKey: `charge:${payload.requestId}`,      });      state.paymentId = payment.id;    },    async (state, payload) =&gt; {      if (!state.paymentId) {        return;      }      await payments.refund({        paymentId: state.paymentId,        idempotencyKey: `refund:${payload.requestId}`,      });      state.paymentId = null;    },    {      timeout: 5000,    },  )  .addStep(    &#8216;create-order&#8217;,    async (state, payload) =&gt; {      const order = await orders.create({        userId: payload.userId,        items: payload.items,        paymentId: state.paymentId,        reservationId: state.reservationId,        idempotencyKey: `order:${payload.requestId}`,      });      state.orderId = order.id;    },    async state =&gt; {      if (!state.orderId) {        return;      }      await orders.cancel(state.orderId);      state.orderId = null;    },  )  .addStep(&#8216;complete&#8217;, state =&gt; {    state.status = &#8216;completed&#8217;;  });\u0417\u0430\u043f\u0443\u0441\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:try {  await checkout.run({    requestId: crypto.randomUUID(),    userId: &#8216;user-42&#8217;,    items: [      { sku: &#8216;keyboard&#8217;, quantity: 1 },      { sku: &#8216;mouse&#8217;, quantity: 1 },    ],    amount: 15000,  });} catch (error) {  const message = error instanceof Error    ? error.message    : String(error);  saga.stateManager.setState({    &#8230;saga.getState(),    status: &#8216;failed&#8217;,    error: message,  });}\u0414\u043e\u043f\u0443\u0441\u0442\u0438\u043c, \u0440\u0435\u0437\u0435\u0440\u0432\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438 \u043f\u043b\u0430\u0442\u0451\u0436 \u043f\u0440\u043e\u0448\u043b\u0438, \u043d\u043e orders.create() \u0432\u044b\u0431\u0440\u043e\u0441\u0438\u043b \u043e\u0448\u0438\u0431\u043a\u0443.Staga \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:start               successreserve-stock       successcharge-payment      successcreate-order        failedrollback:charge-payment      refundreserve-stock       releasestart               status = idlerestore snapshot\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044e\u0442\u0441\u044f \u043a\u043e\u043c\u043f\u0435\u043d\u0441\u0430\u0446\u0438\u0438 \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0445 \u0448\u0430\u0433\u043e\u0432 \u0432 \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435. \u0417\u0430\u0442\u0435\u043c \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0438\u0437 \u0441\u043d\u0438\u043c\u043a\u0430, \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u0435\u0440\u0435\u0434 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0435\u0439.\u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u0432\u043d\u0435\u0448\u043d\u0438\u0439 catch \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0443\u0436\u0435 \u043d\u043e\u0432\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043e\u0448\u0438\u0431\u043a\u0438. \u042d\u0442\u043e \u043f\u043e\u043b\u0435\u0437\u043d\u043e, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0441\u0430\u043c\u0430 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-491079","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/491079","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=491079"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/491079\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=491079"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=491079"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=491079"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}