{"id":490693,"date":"2026-08-12T12:54:20","date_gmt":"2026-08-12T12:54:20","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=490693"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=490693","title":{"rendered":"\u0420\u0435\u0430\u043b\u0442\u0430\u0439\u043c \u043d\u0430 WebSocket \u0441\u043e \u0441\u043a\u0432\u043e\u0437\u043d\u043e\u0439 \u0442\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u0435\u0439: TypeScript, Bun, React, Point0"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0411\u044b\u0432\u0430\u0435\u0442 \u0442\u0430\u043a: \u0435\u0441\u0442\u044c \u0444\u0443\u043b\u0441\u0442\u0435\u043a \u043f\u0440\u043e\u0435\u043a\u0442, \u0438 \u0432 \u043d\u0451\u043c \u0432\u0441\u0451 \u0445\u043e\u0440\u043e\u0448\u043e. \u041e\u0442\u043a\u0443\u0434\u0430-\u0442\u043e \u0435\u0441\u0442\u044c \u0441\u043a\u0432\u043e\u0437\u043d\u044b\u0435 \u0442\u0438\u043f\u044b (tRPC, \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0438\u0437 OpenAPI), \u0435\u0441\u0442\u044c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f, \u0435\u0441\u0442\u044c \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b. \u0410 \u043f\u043e\u0442\u043e\u043c \u0432\u044b \u0440\u0435\u0448\u0430\u0435\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0440\u0435\u0430\u043b\u0442\u0430\u0439\u043c: \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u043e \u043d\u043e\u0432\u043e\u043c \u043f\u043e\u0441\u0442\u0435 \u0432 \u043b\u0435\u043d\u0442\u0435, \u0447\u0430\u0442 \u043c\u0435\u0436\u0434\u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c\u0438, \u0438\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u0443\u044e \u0434\u043e\u0441\u043a\u0443. \u0418 \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0446\u0435\u043b\u044b\u0439 \u043d\u043e\u0432\u044b\u0439 \u0441\u043b\u043e\u0439 \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u0439, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043d\u0430\u0434\u043e \u0437\u0430\u043d\u043e\u0432\u043e \u0438\u0437\u043e\u0431\u0440\u0435\u0441\u0442\u0438 \u0432\u0441\u0451, \u0447\u0442\u043e \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0443\u0436\u0435 \u0435\u0441\u0442\u044c, \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 \u043d\u043e\u0432\u044b\u0439 \u043b\u0430\u0434. \u0418 \u0434\u0430\u043b\u044c\u0448\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c \u0434\u0432\u0435 \u0440\u0430\u0437\u043d\u044b\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b.<\/p>\n<p>\u0412 \u0441\u0432\u043e\u0451\u043c \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0435 <a href=\"https:\/\/github.com\/1gr14\/point0\" rel=\"noopener noreferrer nofollow\">Point0<\/a> \u044f \u0434\u043e\u0431\u0430\u0432\u0438\u043b \u0447\u0435\u0442\u044b\u0440\u0435 \u043d\u043e\u0432\u044b\u0445 \u0440\u0435\u0430\u043b\u0442\u0430\u0439\u043c-\u043f\u043e\u0438\u043d\u0442\u0430 (\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u043d\u044b\u0435 \u0435\u0434\u0438\u043d\u0438\u0446\u044b \u043d\u0430\u0440\u0430\u0432\u043d\u0435 \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u043c\u0438, \u043b\u044d\u0439\u0430\u0443\u0442\u0430\u043c\u0438, \u043a\u0432\u0435\u0440\u0438, \u043c\u0443\u0442\u0430\u0446\u0438\u044f\u043c\u0438): \u043a\u0430\u043d\u0430\u043b, \u0441\u043f\u0435\u0439\u0441, \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0438\u0439 \u0445\u044d\u043d\u0434\u043b\u0435\u0440, \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0439 \u0445\u044d\u043d\u0434\u043b\u0435\u0440. \u041d\u0430 \u043d\u0438\u0445 \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043b\u044e\u0431\u0430\u044f \u0440\u0435\u0430\u043b\u0442\u0430\u0439\u043c-\u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c, \u043a\u043e\u0434\u0430 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u043c\u0430\u043b\u043e, \u0438 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f \u043e\u043d \u0438\u043d\u0442\u0443\u0438\u0442\u0438\u0432\u043d\u043e. \u042d\u0442\u0438 \u043f\u043e\u0438\u043d\u0442\u044b \u043d\u0435\u0441\u0443\u0442 \u0442\u0435 \u0436\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430, \u0447\u0442\u043e \u0438 \u0432\u0441\u0435 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435:<\/p>\n<ul>\n<li>\n<p>\u043a\u043e\u0434 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0438 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0436\u0438\u0432\u0443\u0442 \u0432 \u043e\u0434\u043d\u043e\u043c \u0444\u0430\u0439\u043b\u0435, \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440 \u0432\u044b\u0440\u0435\u0437\u0430\u0435\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0438\u0439 \u043a\u043e\u0434 \u0438\u0437 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u043e\u0439 \u0441\u0431\u043e\u0440\u043a\u0438, \u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0439 \u0438\u0437 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0439<\/p>\n<\/li>\n<li>\n<p>\u0442\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u044f \u0441\u043a\u0432\u043e\u0437\u043d\u0430\u044f \u0438 \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u0438\u0437 \u0434\u0436\u0435\u043d\u0435\u0440\u0438\u043a\u043e\u0432 \u0441\u0430\u043c\u043e\u0433\u043e \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430, \u0431\u0435\u0437 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0442\u0438\u043f\u043e\u0432<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u043e\u0434 \u043a\u0430\u0442\u043e\u043c \u043f\u043e\u043a\u0430\u0436\u0443 \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0430\u0445, \u043a\u0430\u043a \u044d\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u0438 \u043e\u0431\u044a\u044f\u0441\u043d\u044e \u0441\u0443\u0442\u044c \u043f\u0430\u0440\u0430\u0434\u0438\u0433\u043c\u044b, \u0447\u0442\u043e\u0431\u044b \u0432\u044b \u043c\u043e\u0433\u043b\u0438 \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u043b\u044e\u0431\u043e\u0435 \u0440\u0435\u0430\u043b\u0442\u0430\u0439\u043c-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435.<\/p>\n<p>\u0421\u0441\u044b\u043b\u043a\u0438:<\/p>\n<ul>\n<li>\n<p>\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f \u043f\u043e \u0441\u043e\u043a\u0435\u0442\u0430\u043c: <a href=\"https:\/\/1gr14.dev\/ru\/point0\/latest\/socket\" rel=\"noopener noreferrer nofollow\">https:\/\/1gr14.dev\/ru\/point0\/latest\/socket<\/a><\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f: <a href=\"https:\/\/github.com\/1gr14\/point0\/tree\/main\/examples\/socket\" rel=\"noopener noreferrer nofollow\">https:\/\/github.com\/1gr14\/point0\/tree\/main\/examples\/socket<\/a><\/p>\n<\/li>\n<li>\n<p>\u0421\u0442\u0430\u0442\u044c\u044f \u043e \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0435 \u043d\u0430 \u0425\u0430\u0431\u0440\u0435: <a href=\"https:\/\/habr.com\/ru\/articles\/1054310\/\" rel=\"noopener noreferrer nofollow\">https:\/\/habr.com\/ru\/articles\/1054310\/<\/a><\/p>\n<\/li>\n<li>\n<p>\u0412\u0441\u044f \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f: <a href=\"https:\/\/1gr14.dev\/point0\/latest\/overview\" rel=\"noopener noreferrer nofollow\">https:\/\/1gr14.dev\/point0\/latest\/overview<\/a><\/p>\n<\/li>\n<\/ul>\n<h2>\u041f\u0430\u0440\u0430\u0434\u0438\u0433\u043c\u0430<\/h2>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440\u044b \u0441\u0434\u0435\u043b\u0430\u044e\u0442 \u043f\u0430\u0440\u0430\u0434\u0438\u0433\u043c\u0443 \u043f\u043e\u043d\u044f\u0442\u043d\u043e\u0439, \u043d\u043e \u043f\u043e\u043a\u0430 \u0432\u044b \u0438\u0445 \u043d\u0435 \u0443\u0432\u0438\u0434\u0435\u043b\u0438, \u043e\u043d\u0430 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f \u0442\u0440\u0443\u0434\u043d\u043e\u0432\u0430\u0442\u043e. \u0410 \u043d\u0435 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0432 \u0435\u0451, \u0431\u0443\u0434\u0435\u0442 \u043d\u0435\u043f\u043e\u043d\u044f\u0442\u043d\u043e, \u043e \u0447\u0451\u043c \u0433\u043e\u0432\u043e\u0440\u044f\u0442 \u043f\u0440\u0438\u043c\u0435\u0440\u044b. \u041f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u0431\u0435\u0433\u043b\u043e \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f, \u0430 \u043f\u043e\u0441\u043b\u0435 \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432 \u0432\u0435\u0440\u043d\u0443\u0442\u044c\u0441\u044f \u0438 \u043f\u0435\u0440\u0435\u0447\u0438\u0442\u0430\u0442\u044c.<\/p>\n<p>\u041e\u0434\u0438\u043d WebSocket \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435. \u0412\u0441\u0451 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u044d\u0442\u043e \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u0438 \u043d\u0430\u0434 \u043d\u0438\u043c. \u0421\u043b\u043e\u0438 \u0441\u0432\u0435\u0440\u0445\u0443 \u0432\u043d\u0438\u0437.<\/p>\n<p><strong>\u041a\u0430\u043d\u0430\u043b<\/strong> \u044d\u0442\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435. \u041a\u043b\u0438\u0435\u043d\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0439 <code>.connector<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0438\u0434\u0435\u043d\u0442\u0438\u0447\u043d\u043e\u0441\u0442\u044c \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f <code>identity<\/code>. \u041e\u043d\u0430 \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435, \u043a\u043b\u0438\u0435\u043d\u0442\u0443 \u043d\u0435 \u0432\u0438\u0434\u043d\u0430 \u0438 \u043f\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0432 \u043a\u0430\u0436\u0434\u043e\u043c \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0438 \u043d\u0430 \u043a\u0430\u043d\u0430\u043b\u0435 (\u0435\u0451 \u0447\u0438\u0442\u0430\u044e\u0442 \u0432\u0441\u0435 \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u044b, joiner\u2019\u044b \u0438 \u0441\u0435\u043b\u0435\u043a\u0446\u0438\u0438). \u041a\u0430\u0436\u0434\u043e\u0435 \u0436\u0438\u0432\u043e\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u044d\u0442\u043e <code>connectionId<\/code>. \u0421\u0430\u043c \u043a\u043e\u043d\u043d\u0435\u043a\u0442 \u044d\u0442\u043e \u043e\u0431\u044b\u0447\u043d\u044b\u0439 HTTP-\u0437\u0430\u043f\u0440\u043e\u0441, \u0442\u0430\u043a \u0447\u0442\u043e \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438, \u043a\u0443\u043a\u0438 \u0438 middleware \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u043a\u0430\u043a \u0432\u0435\u0437\u0434\u0435. \u041f\u043e \u0441\u043e\u043a\u0435\u0442\u0443 \u0438\u0434\u0443\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u043e\u0441\u043b\u0435 \u043d\u0435\u0433\u043e.<\/p>\n<p><strong>\u0421\u043f\u0435\u0439\u0441<\/strong> \u0432\u044b\u0440\u0430\u0441\u0442\u0430\u0435\u0442 \u0438\u0437 \u043a\u0430\u043d\u0430\u043b\u0430. \u042d\u0442\u043e \u0441\u0435\u043c\u0435\u0439\u0441\u0442\u0432\u043e \u043a\u043e\u043c\u043d\u0430\u0442 \u043e\u0434\u043d\u043e\u0439 \u0444\u043e\u0440\u043c\u044b. \u041a\u043b\u0438\u0435\u043d\u0442 \u0432\u0445\u043e\u0434\u0438\u0442 \u0447\u0435\u0440\u0435\u0437 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0439 <code>.joiner<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u0435\u0448\u0430\u0435\u0442, \u0432 \u043a\u0430\u043a\u0438\u0435 \u043a\u043e\u043c\u043d\u0430\u0442\u044b \u043e\u043d \u043f\u043e\u043f\u0430\u0434\u0451\u0442. \u041b\u0438\u0431\u043e \u0441\u0435\u0440\u0432\u0435\u0440 \u0441\u0430\u043c \u0437\u0430\u0447\u0438\u0441\u043b\u044f\u0435\u0442 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u0432 \u043a\u043e\u043c\u043d\u0430\u0442\u044b, \u0431\u0435\u0437 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0430, \u0447\u0435\u0440\u0435\u0437 <code>.enroller<\/code>. \u041a\u043e\u043c\u043d\u0430\u0442\u0430 \u044d\u0442\u043e \u0435\u0434\u0438\u043d\u0438\u0446\u0430 \u0430\u0434\u0440\u0435\u0441\u0430\u0446\u0438\u0438, pub\/sub-\u0442\u043e\u043f\u0438\u043a, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0443\u0448\u0438 \u0446\u0435\u043b\u044f\u0442\u0441\u044f \u043f\u043e \u0438\u043c\u0435\u043d\u0438.<\/p>\n<p><strong>\u0421\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0439 \u0445\u044d\u043d\u0434\u043b\u0435\u0440<\/strong> \u044d\u0442\u043e \u0432\u044b\u0437\u043e\u0432 \u043a\u043b\u0438\u0435\u043d\u0442 \u2192 \u0441\u0435\u0440\u0432\u0435\u0440. \u041a\u043b\u0438\u0435\u043d\u0442 \u0448\u043b\u0451\u0442 \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0447\u0435\u0440\u0435\u0437 <code>sendToServer<\/code>, \u0441\u0435\u0440\u0432\u0435\u0440 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0432 <code>.serverReply<\/code>, \u0430 \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435 \u0432\u0441\u0451 \u044d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u044b\u0439 \u043a\u0432\u0435\u0440\u0438 \u0438\u043b\u0438 \u043c\u0443\u0442\u0430\u0446\u0438\u044f.<\/p>\n<p><strong>\u041a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0438\u0439 \u0445\u044d\u043d\u0434\u043b\u0435\u0440<\/strong> \u044d\u0442\u043e \u043f\u0443\u0448 \u0441\u0435\u0440\u0432\u0435\u0440 \u2192 \u043a\u043b\u0438\u0435\u043d\u0442. \u0421\u0435\u0440\u0432\u0435\u0440 \u0448\u043b\u0451\u0442 \u0447\u0435\u0440\u0435\u0437 <code>sendToClient<\/code> \u0432 \u0446\u0435\u043b\u044c (\u043a\u043e\u043c\u043d\u0430\u0442\u0443, \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435, \u0441\u0435\u043b\u0435\u043a\u0446\u0438\u044e), \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u044e\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0438 \u043f\u0440\u0438 \u0436\u0435\u043b\u0430\u043d\u0438\u0438 \u043e\u0442\u0432\u0435\u0447\u0430\u044e\u0442 \u043e\u0431\u0440\u0430\u0442\u043d\u043e.<\/p>\n<p>\u041d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435 \u0432\u0441\u0451 \u0434\u0435\u0440\u0436\u0438\u0442\u0441\u044f \u0445\u0443\u043a\u0430\u043c\u0438 \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430\u043c\u0438 (<code>useConnection<\/code>, <code>&lt;Connection&gt;<\/code>, <code>useMembership<\/code>, <code>.with(channel)<\/code>), \u0441\u043e\u043a\u0435\u0442 \u043f\u0435\u0440\u0435\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0441\u0430\u043c, \u0430 resumable-\u043a\u0430\u043d\u0430\u043b \u0434\u0435\u043b\u0430\u0435\u0442 \u0440\u0435\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u044b \u0434\u0435\u0448\u0451\u0432\u044b\u043c\u0438.<\/p>\n<p>\u041d\u0430 \u043e\u0434\u043d\u043e\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0432\u0441\u0451 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u043f\u0430\u043c\u044f\u0442\u0438. \u0414\u043b\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0432 \u043a \u0434\u0432\u0438\u0436\u043a\u0443 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f backplane: Redis \u043f\u043e URL, Postgres, \u0432\u0430\u0448 \u0443\u0436\u0435 \u043f\u043e\u0434\u043d\u044f\u0442\u044b\u0439 Redis-\u043a\u043b\u0438\u0435\u043d\u0442 \u0438\u043b\u0438 \u043b\u044e\u0431\u043e\u0439 KV \u0441 pub\/sub. \u0421\u0435\u0440\u0432\u0435\u0440 \u0442\u0430\u043a\u0436\u0435 \u0443\u043c\u0435\u0435\u0442 \u0430\u0434\u043c\u0438\u043d-\u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0438 \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f (kick, kill, refresh, \u043a\u0442\u043e \u0441\u0435\u0439\u0447\u0430\u0441 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0451\u043d) \u0438 \u043e\u0442\u0434\u0430\u0451\u0442 \u043c\u0435\u0442\u0440\u0438\u043a\u0438.<\/p>\n<h2>\u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430<\/h2>\n<p>\u0414\u0430\u043b\u044c\u0448\u0435 \u0432\u043e \u0432\u0441\u0435\u0445 \u043f\u0440\u0438\u043c\u0435\u0440\u0430\u0445 \u0441\u0447\u0438\u0442\u0430\u0435\u043c, \u0447\u0442\u043e \u0443 \u0432\u0430\u0441 \u0443\u0436\u0435 \u0435\u0441\u0442\u044c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u0438 Prisma. \u041e\u0442\u043a\u0443\u0434\u0430 \u0431\u0435\u0440\u0451\u0442\u0441\u044f <code>prisma<\/code>, \u043a\u0430\u043a \u0443\u0441\u0442\u0440\u043e\u0435\u043d <code>getUserFromRequest<\/code>, \u0447\u0442\u043e \u0437\u0430 <code>AppError<\/code>, \u0434\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432 \u043d\u0435\u0432\u0430\u0436\u043d\u043e: \u044d\u0442\u043e \u0432\u0430\u0448 \u043e\u0431\u044b\u0447\u043d\u044b\u0439 \u043a\u043e\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u044b\u043b \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0434\u043e \u0441\u043e\u043a\u0435\u0442\u043e\u0432.<\/p>\n<p>\u0421\u043e\u043a\u0435\u0442\u044b \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e, \u0432\u043a\u043b\u044e\u0447\u0430\u044e\u0442\u0441\u044f \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u043e\u0439 \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0435 \u0434\u0432\u0438\u0436\u043a\u0430. \u041f\u043e\u043a\u0430 \u043e\u043d\u0438 \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u044b, \u0438\u0445 \u043a\u043e\u0434 \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0435 \u0435\u0434\u0435\u0442 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440.<\/p>\n<pre><code class=\"typescript\">\/\/ engine.tsexport const engine = Engine.create({  server: { socket: true },})<\/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>\u041a\u0430\u043d\u0430\u043b \u043e\u0431\u044a\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u043d\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435.<\/p>\n<pre><code class=\"typescript\">\/\/ lib\/channel.tsimport { root } from '@\/lib\/root'import { getUserFromRequest } from '@\/lib\/auth' \/\/ \u0432\u0430\u0448\u0430 \u043e\u0431\u044b\u0447\u043d\u0430\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f, \u0442\u0430 \u0436\u0435, \u0447\u0442\u043e \u0438 \u0432 HTTPexport const appChannel = root.lets  .channel()  .connector(async ({ request }) =&gt; {    \/\/ \u043a\u043e\u043d\u043d\u0435\u043a\u0442 \u044d\u0442\u043e \u043e\u0431\u044b\u0447\u043d\u044b\u0439 HTTP-\u0437\u0430\u043f\u0440\u043e\u0441: \u043a\u0443\u043a\u0438, \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438, middleware \u043d\u0430 \u043c\u0435\u0441\u0442\u0435    const user = await getUserFromRequest(request)    \/\/ \u0442\u043e, \u0447\u0442\u043e \u0432\u0435\u0440\u043d\u0443\u043b \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440, \u0438 \u0435\u0441\u0442\u044c identity \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f. \u041e\u043d\u0430 \u043e\u0441\u0442\u0430\u043d\u0435\u0442\u0441\u044f \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435,    \/\/ \u043a\u043b\u0438\u0435\u043d\u0442\u0443 \u043d\u0435 \u0432\u0438\u0434\u043d\u0430, \u0438 \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u0432\u043e \u0432\u0441\u0435\u0445 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0438\u0445 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f\u0445 \u043d\u0430 \u044d\u0442\u043e\u043c \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0438.    \/\/ \u0424\u043e\u0440\u043c\u0430 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u0430\u044f: \u043a\u043b\u0430\u0434\u0438\u0442\u0435 \u0447\u0442\u043e \u0443\u0433\u043e\u0434\u043d\u043e \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u0435\u043c\u043e\u0435, \u0445\u043e\u0442\u044c \u0440\u043e\u043b\u044c, \u0445\u043e\u0442\u044c \u0442\u0430\u0440\u0438\u0444,    \/\/ \u0445\u043e\u0442\u044c \u044f\u0437\u044b\u043a \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430. \u0422\u0438\u043f \u043d\u0438\u0433\u0434\u0435 \u043d\u0435 \u043e\u0431\u044a\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f, \u043e\u043d \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u043e\u0442\u0441\u044e\u0434\u0430    return user      ? { authorized: true as const, id: user.id, name: user.name }      : { authorized: false as const, id: null, name: null }  })  .channel()<\/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>as const<\/code> \u043d\u0430 \u0444\u043b\u0430\u0433\u0435 \u0442\u0443\u0442 \u043d\u0435 \u0443\u043a\u0440\u0430\u0448\u0435\u043d\u0438\u0435: \u0431\u0435\u0437 \u043d\u0435\u0433\u043e TypeScript \u0440\u0430\u0441\u0448\u0438\u0440\u0438\u0442 <code>true<\/code> \u0434\u043e <code>boolean<\/code>, \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u043d\u0435\u0442 \u0431\u044b\u0442\u044c \u0440\u0430\u0437\u043c\u0435\u0447\u0435\u043d\u043d\u044b\u043c, \u0438 <code>identity.id<\/code> \u043f\u043e\u0441\u043b\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043d\u0435 \u0441\u0443\u0437\u0438\u0442\u0441\u044f \u0434\u043e \u0441\u0442\u0440\u043e\u043a\u0438. \u0411\u043e\u043b\u044c\u0448\u0435 \u043d\u0438\u0447\u0435\u0433\u043e \u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0435 \u043d\u0443\u0436\u043d\u043e, \u0442\u0438\u043f identity \u0440\u0430\u0437\u044a\u0435\u0434\u0435\u0442\u0441\u044f \u043f\u043e \u0432\u0441\u0435\u043c \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430\u043c, joiner\u2019\u0430\u043c \u0438 \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u0430\u043c \u0441\u0430\u043c.<\/p>\n<p>\u041a\u0430\u043d\u0430\u043b \u0434\u0435\u0440\u0436\u0438\u043c \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u043c \u0434\u043b\u044f \u0432\u0441\u0435\u0445, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u0433\u043e\u0441\u0442\u0435\u0439. \u0421\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043c\u043e\u0436\u043d\u043e \u0432\u0441\u0435\u043c, \u0430 \u043a\u0430\u0436\u0434\u043e\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0433\u0435\u0439\u0442\u0438\u0442\u0441\u044f \u0442\u0430\u043c, \u0433\u0434\u0435 \u043e\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f: \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435, \u0433\u0434\u0435 identity \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0432\u0435\u0440\u044f\u0442\u044c. \u0415\u0441\u043b\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0446\u0435\u043b\u0438\u043a\u043e\u043c \u0437\u0430\u043a\u0440\u044b\u0442\u043e\u0435, \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440 \u043c\u043e\u0436\u0435\u0442 \u0431\u0440\u043e\u0441\u0438\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0443, \u0438 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u0432\u043e\u043e\u0431\u0449\u0435.<\/p>\n<p>\u0421\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0434\u0435\u0440\u0436\u0438\u0442 \u043a\u043e\u0440\u0435\u043d\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u041e\u0434\u043d\u043e \u043d\u0430 \u0432\u043a\u043b\u0430\u0434\u043a\u0443, \u0435\u0433\u043e \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 \u0432\u0441\u0435 \u0441\u043f\u0435\u0439\u0441\u044b \u043d\u0430 \u0432\u0441\u0435\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445.<\/p>\n<pre><code class=\"typescript\">\/\/ app.client.tsx&lt;appChannel.Connection&gt;  &lt;RouterRoutes \/&gt;&lt;\/appChannel.Connection&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>\u0412\u0441\u0451. \u0414\u0430\u043b\u044c\u0448\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0444\u0438\u0447\u0438.<\/p>\n<h2>\u041f\u0440\u0438\u043c\u0435\u0440 1. \u0423\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u043c \u043e \u043d\u043e\u0432\u043e\u043c \u043f\u043e\u0441\u0442\u0435 \u0432 \u043b\u0435\u043d\u0442\u0435<\/h2>\n<p>\u0414\u043e\u043f\u0443\u0441\u0442\u0438\u043c, \u0443 \u0432\u0430\u0441 \u0441\u0432\u043e\u0439 \u043c\u0438\u043d\u0438-\u0425\u0430\u0431\u0440. \u041b\u044e\u0434\u0438 \u043f\u0438\u0448\u0443\u0442 \u043f\u043e\u0441\u0442\u044b, \u0438 \u0432\u044b \u0445\u043e\u0442\u0438\u0442\u0435, \u0447\u0442\u043e\u0431\u044b \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u043e\u043d\u043b\u0430\u0439\u043d \u0432\u0438\u0434\u0435\u043b\u0438 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u043e \u043d\u043e\u0432\u043e\u043c \u043f\u043e\u0441\u0442\u0435 \u0432 \u043c\u043e\u043c\u0435\u043d\u0442 \u0435\u0433\u043e \u043f\u043e\u044f\u0432\u043b\u0435\u043d\u0438\u044f.<\/p>\n<p>\u041a\u043e\u043c\u043d\u0430\u0442\u044b \u0442\u0443\u0442 \u043d\u0435 \u043d\u0443\u0436\u043d\u044b: \u0430\u0434\u0440\u0435\u0441\u0430\u0442 \u044d\u0442\u043e \u00ab\u0432\u0441\u0435, \u043a\u0442\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0451\u043d\u00bb. \u0417\u043d\u0430\u0447\u0438\u0442, \u0445\u0432\u0430\u0442\u0438\u0442 \u043e\u0434\u043d\u043e\u0433\u043e \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0433\u043e \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u0430, \u0440\u0430\u0441\u0442\u0443\u0449\u0435\u0433\u043e \u043f\u0440\u044f\u043c\u043e \u0438\u0437 \u043a\u0430\u043d\u0430\u043b\u0430.<\/p>\n<pre><code class=\"typescript\">\/\/ pages\/feed.tsx\/\/ \u0441\u0435\u0440\u0432\u0435\u0440 \u2192 \u043a\u043b\u0438\u0435\u043d\u0442. \u0421\u0445\u0435\u043c\u0430 \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 payload, \u043a\u043b\u0438\u0435\u043d\u0442 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0435\u0442 \u0435\u0433\u043e \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043cexport const postAddedHandler = appChannel.lets  .clientHandler()  .serverSend(    z.object({      id: z.string(),      title: z.string(),      authorId: z.string(),      authorName: z.string(),    }),  )  .clientHandler()\/\/ \u0432\u0430\u0448\u0430 \u043e\u0431\u044b\u0447\u043d\u0430\u044f HTTP-\u043c\u0443\u0442\u0430\u0446\u0438\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u0430. \u0418\u0437-\u0437\u0430 \u0440\u0435\u0430\u043b\u0442\u0430\u0439\u043c\u0430 \u0432 \u043d\u0435\u0439 \u0434\u043e\u0431\u0430\u0432\u0438\u043b\u0430\u0441\u044c \u043e\u0434\u043d\u0430 \u0441\u0442\u0440\u043e\u043a\u0430export const postCreateMutation = root.lets  .mutation()  .input(z.object({ title: z.string().min(1) }))  .loader(async ({ input, request }) =&gt; {    const user = await getUserFromRequest(request)    if (!user) throw new AppError('\u0422\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0445', { status: 401 })    const post = await prisma.post.create({      data: { title: input.title, authorId: user.id },    })    \/\/ \u0431\u0435\u0437 \u0443\u043a\u0430\u0437\u0430\u043d\u0438\u044f \u0446\u0435\u043b\u0438 \u043f\u0443\u0448 \u0443\u0445\u043e\u0434\u0438\u0442 \u0432\u0441\u0435\u043c \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f\u043c \u043a\u0430\u043d\u0430\u043b\u0430, \u0433\u043e\u0441\u0442\u044f\u043c \u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435.    \/\/ \u041d\u0435 \u0436\u0434\u0451\u043c \u0435\u0433\u043e: \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u044d\u0442\u043e \u0441\u0438\u0433\u043d\u0430\u043b, \u0430 \u043d\u0435 \u0447\u0430\u0441\u0442\u044c \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438    void postAddedHandler.sendToClient({      id: post.id,      title: post.title,      authorId: user.id,      authorName: user.name,    })    return post  })  .mutation()<\/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>\u0424\u043e\u0440\u043c\u0430 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u0430 \u044d\u0442\u043e \u043e\u0431\u044b\u0447\u043d\u0430\u044f \u0444\u043e\u0440\u043c\u0430 \u0441 \u043e\u0431\u044b\u0447\u043d\u043e\u0439 \u043c\u0443\u0442\u0430\u0446\u0438\u0435\u0439, \u0441\u043e\u043a\u0435\u0442\u044b \u0435\u0451 \u043d\u0438\u043a\u0430\u043a \u043d\u0435 \u043a\u0430\u0441\u0430\u044e\u0442\u0441\u044f:<\/p>\n<pre><code class=\"typescript\">const PostForm = () =&gt; {  const create = postCreateMutation.useMutation()  const [title, setTitle] = useState('')  return (    &lt;form      onSubmit={(e) =&gt; {        e.preventDefault()        void create          .mutateAsync({ title })          .then(() =&gt; setTitle(''))          .catch((error) =&gt; alert(error.message))      }}    &gt;      &lt;input value={title} onChange={(e) =&gt; setTitle(e.target.value)} \/&gt;      &lt;button disabled={create.isPending}&gt;\u041e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u0442\u044c&lt;\/button&gt;    &lt;\/form&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>\u041d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0430 \u044d\u0442\u043e \u043e\u0434\u0438\u043d \u0445\u0443\u043a. \u041d\u0438 join, \u043d\u0438 \u043a\u043e\u043c\u043d\u0430\u0442, \u043d\u0438 \u0440\u0443\u0447\u043d\u043e\u0433\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f: \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0443\u0436\u0435 \u0434\u0435\u0440\u0436\u0438\u0442 \u043a\u043e\u0440\u0435\u043d\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<pre><code class=\"typescript\">const NewPostsBanner = () =&gt; {  const { user } = useUser() \/\/ \u0432\u0430\u0448 \u043e\u0431\u044b\u0447\u043d\u044b\u0439 \u0445\u0443\u043a \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435  const [fresh, setFresh] = useState&lt;{ id: string; title: string }[]&gt;([])  \/\/ message \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d \u0441\u0445\u0435\u043c\u043e\u0439 \u0438\u0437 .serverSend, \u0431\u0435\u0437 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0442\u0438\u043f\u043e\u0432  postAddedHandler.useOnMessageFromServer(({ message }) =&gt; {    if (message.authorId === user?.id) return \/\/ \u0441\u0432\u043e\u0439 \u0436\u0435 \u043f\u043e\u0441\u0442, \u0431\u0430\u043d\u043d\u0435\u0440 \u043d\u0435 \u043d\u0443\u0436\u0435\u043d    setFresh((prev) =&gt; [message, ...prev])  })  if (fresh.length === 0) return null  return (    &lt;button      onClick={() =&gt; {        void feedQuery.invalidateQuery() \/\/ \u043f\u0440\u0430\u0432\u0434\u0430 \u0436\u0438\u0432\u0451\u0442 \u0432 \u043a\u0432\u0435\u0440\u0438, \u043f\u0443\u0448 \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u043a\u0430\u0437\u0430\u043b, \u0447\u0442\u043e \u043e\u043d\u0430 \u0443\u0441\u0442\u0430\u0440\u0435\u043b\u0430        setFresh([])      }}    &gt;      \u041d\u043e\u0432\u044b\u0445 \u043f\u043e\u0441\u0442\u043e\u0432: {fresh.length}. \u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c    &lt;\/button&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>\u0417\u0434\u0435\u0441\u044c \u0432\u0438\u0434\u043d\u043e \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u043e \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0438: \u043f\u0443\u0448 \u044d\u0442\u043e \u0441\u0438\u0433\u043d\u0430\u043b \u00ab\u0447\u0442\u043e-\u0442\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u043e\u0441\u044c, \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438 \u0435\u0449\u0451 \u0440\u0430\u0437\u00bb, \u0430 \u043d\u0435 \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u0430\u044f \u043a\u043e\u043f\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445. \u041a\u043e\u043f\u0438\u044f \u043b\u0435\u0436\u0438\u0442 \u0432 \u0431\u0430\u0437\u0435 \u0438 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f \u043e\u0431\u044b\u0447\u043d\u044b\u043c \u043a\u0432\u0435\u0440\u0438. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u043e\u0442\u0435\u0440\u044f\u043d\u043d\u044b\u0439 \u043f\u0443\u0448 (\u043a\u043b\u0438\u0435\u043d\u0442 \u0431\u044b\u043b \u0432 \u0440\u0435\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u0435, \u0441\u0435\u0440\u0432\u0435\u0440 \u043f\u0435\u0440\u0435\u0434\u0435\u043f\u043b\u043e\u0438\u043b\u0441\u044f) \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0441\u0442\u043e\u0438\u0442: \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0440\u0435\u0444\u0435\u0442\u0447 \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u043f\u0440\u0438\u043d\u0435\u0441\u0451\u0442 \u043f\u0440\u0430\u0432\u0434\u0443.<\/p>\n<p>\u041e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u043e \u0441\u0442\u0440\u043e\u0447\u043a\u0443 <code>if (message.authorId === user?.id) return<\/code>. \u0421\u0435\u0440\u0432\u0435\u0440 \u0442\u043e\u0436\u0435 \u0443\u043c\u0435\u0435\u0442 \u0438\u0441\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u0430\u0434\u0440\u0435\u0441\u0430\u0442\u0430, \u0443 \u043f\u0443\u0448\u0430 \u0435\u0441\u0442\u044c \u0446\u0435\u043b\u044c:<\/p>\n<pre><code class=\"typescript\">\/\/ \u0442\u0430\u043a \u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0435 \u043d\u0430\u0434\u043evoid postAddedHandler.sendToClient(payload, {  $identity: { id: { $ne: user.id } },})<\/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\u043b\u044e\u0447 \u0441 <code>$<\/code> \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u0432\u044b\u0431\u043e\u0440\u043a\u0443 \u0432 \u0441\u0442\u0438\u043b\u0435 Mongo (\u0435\u0451 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 sift), \u043a\u043b\u044e\u0447 \u0431\u0435\u0437 <code>$<\/code> \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u0442\u043e\u0447\u043d\u044b\u0439 \u0430\u0434\u0440\u0435\u0441. \u0418 \u0432\u043e\u0442 \u0432 \u044d\u0442\u043e\u043c \u0432\u0441\u044f \u0440\u0430\u0437\u043d\u0438\u0446\u0430: \u0442\u043e\u0447\u043d\u044b\u0439 \u0430\u0434\u0440\u0435\u0441 \u044d\u0442\u043e \u043f\u043e\u043f\u0430\u0434\u0430\u043d\u0438\u0435 \u0432 pub\/sub-\u0442\u043e\u043f\u0438\u043a, \u043e\u0434\u043d\u0430 \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u044f \u043d\u0430 \u043b\u044e\u0431\u043e\u0435 \u0447\u0438\u0441\u043b\u043e \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u0435\u0439, \u0430 \u0432\u044b\u0431\u043e\u0440\u043a\u0430 \u044d\u0442\u043e \u043f\u0435\u0440\u0435\u0431\u043e\u0440 \u0432\u0441\u0435\u0445 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439 \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435. \u0420\u0430\u0434\u0438 \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043e\u0434\u0438\u043d \u0447\u0435\u043b\u043e\u0432\u0435\u043a \u043d\u0435 \u0443\u0432\u0438\u0434\u0435\u043b \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u043f\u043e\u0441\u0442, \u043c\u044b \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0434\u0435\u0448\u0451\u0432\u0443\u044e \u0440\u0430\u0441\u0441\u044b\u043b\u043a\u0443 \u0432 \u0441\u043a\u0430\u043d \u043f\u043e \u0432\u0441\u0435\u0439 \u0431\u0430\u0437\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439.<\/p>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0442\u0430\u043a\u043e\u0439, \u043a\u0430\u043a \u0432 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0435 \u0432\u044b\u0448\u0435: \u043a\u043b\u0430\u0434\u0451\u043c <code>authorId<\/code> \u0432 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0438 \u0441\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u0435\u043c \u0435\u0433\u043e \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435 \u0441 \u0442\u0435\u043a\u0443\u0449\u0438\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c. \u0421\u0432\u043e\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0443 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0438 \u0442\u0430\u043a \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e, <code>useUser<\/code> \u0443\u0436\u0435 \u0435\u0441\u0442\u044c \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435. \u041a\u0441\u0442\u0430\u0442\u0438, \u0440\u043e\u0432\u043d\u043e \u0442\u0430\u043a \u0443\u0441\u0442\u0440\u043e\u0435\u043d\u043e \u0438 \u0448\u0442\u0430\u0442\u043d\u043e\u0435 \u043f\u043e\u0434\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u044d\u0445\u0430: <code>except<\/code> \u043f\u043e <code>connectionId<\/code> \u043d\u0435 \u0443\u0431\u0438\u0440\u0430\u0435\u0442 \u0444\u0440\u0435\u0439\u043c \u0438\u0437 \u0440\u0430\u0441\u0441\u044b\u043b\u043a\u0438, \u043e\u043d \u0434\u043e\u0435\u0437\u0436\u0430\u0435\u0442 \u0434\u043e \u043a\u043b\u0438\u0435\u043d\u0442\u0430, \u0438 \u043a\u043b\u0438\u0435\u043d\u0442 \u0435\u0433\u043e \u043e\u0442\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u0442.<\/p>\n<p>\u0412\u044b\u0431\u043e\u0440\u043a\u0438 \u043f\u043e <code>$identity<\/code> \u043e\u0441\u0442\u0430\u044e\u0442\u0441\u044f \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0434\u043b\u044f \u0447\u0435\u0433\u043e \u043e\u043d\u0438 \u0438 \u0441\u0434\u0435\u043b\u0430\u043d\u044b: \u0440\u0435\u0434\u043a\u0438\u0435 \u0430\u0434\u043c\u0438\u043d\u0441\u043a\u0438\u0435 \u0440\u0430\u0441\u0441\u044b\u043b\u043a\u0438, \u0433\u0434\u0435 \u043f\u0435\u0440\u0435\u0431\u043e\u0440 \u043d\u0435 \u0436\u0430\u043b\u043a\u043e.<\/p>\n<h2>\u041f\u0440\u0438\u043c\u0435\u0440 2. \u0418\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u0430\u044f \u0434\u043e\u0441\u043a\u0430 \u0441 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e\u043c \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432<\/h2>\n<p>\u0415\u0441\u0442\u044c \u0434\u043e\u0441\u043a\u0430, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043b\u044e\u0434\u0438 \u0440\u0438\u0441\u0443\u044e\u0442, \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u043f\u0443\u0441\u0442\u044c \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u0442\u0430\u0432\u044f\u0442 \u0442\u043e\u0447\u043a\u0438. \u0414\u043e\u0441\u043a\u0430 \u044d\u0444\u0435\u043c\u0435\u0440\u043d\u0430\u044f, \u0432\u0438\u0434\u043d\u044b \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u043e\u0447\u043a\u0438, \u043f\u043e\u044f\u0432\u0438\u0432\u0448\u0438\u0435\u0441\u044f \u0441 \u043c\u043e\u043c\u0435\u043d\u0442\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f. \u0421\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043c\u043e\u0433\u0443\u0442 \u0432\u0441\u0435, \u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u0447\u043a\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0435.<\/p>\n<p>\u0422\u0443\u0442 \u043d\u0443\u0436\u043d\u0430 \u043a\u043e\u043c\u043d\u0430\u0442\u0430, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0440\u0438\u0441\u0443\u044e\u0449\u0438\u0435 \u043e\u0431\u0440\u0430\u0437\u0443\u044e\u0442 \u0433\u0440\u0443\u043f\u043f\u0443, \u0432 \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0441\u0435\u0440\u0432\u0435\u0440 \u0448\u043b\u0451\u0442 \u043f\u0443\u0448\u0438. \u041a\u043e\u043c\u043d\u0430\u0442\u0430 \u0432\u0441\u0435\u0433\u043e \u043e\u0434\u043d\u0430, \u043e\u0431\u0449\u0430\u044f, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0443 \u0441\u043f\u0435\u0439\u0441\u0430 \u043d\u0435\u0442 \u0434\u0436\u0435\u043d\u0435\u0440\u0438\u043a\u0430 \u0444\u043e\u0440\u043c\u044b \u043a\u043e\u043c\u043d\u0430\u0442\u044b.<\/p>\n<pre><code class=\"typescript\">\/\/ pages\/board.tsxconst dotSchema = z.object({ x: z.number(), y: z.number(), userId: z.string() })type Dot = z.infer&lt;typeof dotSchema&gt;\/\/ \u0431\u0435\u0437 \u0434\u0436\u0435\u043d\u0435\u0440\u0438\u043a\u0430 \u0444\u043e\u0440\u043c\u0430 \u043a\u043e\u043c\u043d\u0430\u0442\u044b \u044d\u0442\u043e \u043f\u0443\u0441\u0442\u043e\u0439 \u043e\u0431\u044a\u0435\u043a\u0442. \u042d\u0442\u043e \u0447\u0430\u0441\u0442\u043d\u044b\u0439 \u0441\u043b\u0443\u0447\u0430\u0439: \u043d\u0430 \u0432\u0435\u0441\u044c \u043f\u0440\u043e\u0435\u043a\u0442\/\/ \u0432 \u0442\u0430\u043a\u043e\u043c \u0441\u043f\u0435\u0439\u0441\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u0440\u043e\u0432\u043d\u043e \u043e\u0434\u043d\u0430 \u043a\u043e\u043c\u043d\u0430\u0442\u0430. \u0412 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u043f\u0440\u0438\u043c\u0435\u0440\u0430\u0445 \u043d\u0430 \u0435\u0451 \u043c\u0435\u0441\u0442\u0435 \u0431\u0443\u0434\u0435\u0442\/\/ \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u0430\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0434\u0430\u043d\u043d\u044b\u0445, \u0438 \u043a\u043e\u043c\u043d\u0430\u0442 \u0441\u0442\u0430\u043d\u0435\u0442 \u043c\u043d\u043e\u0433\u043eexport const boardSpace = appChannel.lets  .space()  \/\/ .joiner \u044d\u0442\u043e \u0442\u043e, \u0447\u0442\u043e \u0432\u043e\u043e\u0431\u0449\u0435 \u0434\u0435\u043b\u0430\u0435\u0442 \u0441\u043f\u0435\u0439\u0441 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u043c \u0434\u043b\u044f \u0432\u0445\u043e\u0434\u0430 \u0441 \u043a\u043b\u0438\u0435\u043d\u0442\u0430.  \/\/ \u041f\u0443\u0441\u043a\u0430\u0435\u043c \u0432\u0441\u0435\u0445, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u0433\u043e\u0441\u0442\u0435\u0439: \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0434\u043e\u0441\u043a\u0443 \u043c\u043e\u0436\u043d\u043e \u0432\u0441\u0435\u043c  .joiner(() =&gt; ({}))  .space()\/\/ \u043a\u043b\u0438\u0435\u043d\u0442 \u2192 \u0441\u0435\u0440\u0432\u0435\u0440: \u044f \u043a\u043b\u0438\u043a\u043d\u0443\u043b \u0432 \u0442\u043e\u0447\u043a\u0443 (x, y)export const dotPutHandler = boardSpace.lets  .serverHandler()  .clientSend(z.object({ x: z.number(), y: z.number() }))  .serverReply(async ({ input, identity }) =&gt; {    \/\/ \u0433\u0435\u0439\u0442 \u0436\u0438\u0432\u0451\u0442 \u0437\u0434\u0435\u0441\u044c, \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435, \u0433\u0434\u0435 identity \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0432\u0435\u0440\u044f\u0442\u044c.    \/\/ \u041d\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0439 \u043a\u0443\u0440\u0441\u043e\u0440 \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435 \u044d\u0442\u043e \u0432\u0435\u0436\u043b\u0438\u0432\u043e\u0441\u0442\u044c, \u0430 \u043d\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u043e    if (!identity.authorized) {      throw new AppError('\u0412\u043e\u0439\u0434\u0438\u0442\u0435, \u0447\u0442\u043e\u0431\u044b \u0440\u0438\u0441\u043e\u0432\u0430\u0442\u044c', { status: 401 })    }    const dot = { x: input.x, y: input.y, userId: identity.id }    void dotAddedHandler.sendToClient(dot) \/\/ \u0433\u043e\u043b\u044b\u0439 send = \u0432\u0441\u0435\u043c \u0432 \u0441\u043f\u0435\u0439\u0441\u0435    return dot  })  .serverHandler()\/\/ \u0441\u0435\u0440\u0432\u0435\u0440 \u2192 \u043a\u043b\u0438\u0435\u043d\u0442: \u043d\u0430 \u0434\u043e\u0441\u043a\u0435 \u043f\u043e\u044f\u0432\u0438\u043b\u0430\u0441\u044c \u0442\u043e\u0447\u043a\u0430export const dotAddedHandler = boardSpace.lets  .clientHandler()  .serverSend(dotSchema)  .clientHandler()<\/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\u043e\u043d\u0435\u043d\u0442 \u0432\u0445\u043e\u0434\u0438\u0442 \u0432 \u043a\u043e\u043c\u043d\u0430\u0442\u0443, \u043f\u043e\u043a\u0430 \u043e\u043d \u043d\u0430 \u044d\u043a\u0440\u0430\u043d\u0435, \u0438 \u0441\u043b\u0443\u0448\u0430\u0435\u0442 \u043f\u0443\u0448\u0438.<\/p>\n<pre><code class=\"typescript\">const Board = () =&gt; {  const membership = boardSpace.useMembership()  const [dots, setDots] = useState&lt;Dot[]&gt;([])  dotAddedHandler(membership).useOnMessageFromServer(({ message }) =&gt; {    setDots((prev) =&gt; [...prev, message])  })  return (    &lt;div      className=\"board\"      onClick={(e) =&gt; {        const box = e.currentTarget.getBoundingClientRect()        void dotPutHandler(membership)          .sendToServer({            x: (e.clientX - box.left) \/ box.width,            y: (e.clientY - box.top) \/ box.height,          })          .catch((error) =&gt; alert(error.message)) \/\/ \u043e\u0448\u0438\u0431\u043a\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043f\u0440\u0438\u043b\u0435\u0442\u0430\u0435\u0442 \u0441\u044e\u0434\u0430 \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439      }}    &gt;      {dots.map((dot, i) =&gt; (        &lt;span          key={i}          style={{ left: `${dot.x * 100}%`, top: `${dot.y * 100}%` }}        \/&gt;      ))}    &lt;\/div&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>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435: \u0431\u0430\u0437\u044b \u0437\u0434\u0435\u0441\u044c \u043d\u0435\u0442 \u0432\u043e\u043e\u0431\u0449\u0435, \u0438 \u044d\u0442\u043e \u043d\u0435 \u0443\u043f\u0440\u043e\u0449\u0435\u043d\u0438\u0435 \u0440\u0430\u0434\u0438 \u0441\u0442\u0430\u0442\u044c\u0438. \u0422\u043e\u0447\u043a\u0438 \u0436\u0438\u0432\u0443\u0442 \u0432 \u0441\u0442\u0435\u0439\u0442\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043f\u043e \u0443\u0441\u043b\u043e\u0432\u0438\u044e \u0434\u043e\u0441\u043a\u0430 \u044d\u0444\u0435\u043c\u0435\u0440\u043d\u0430\u044f. \u0415\u0441\u043b\u0438 \u0431\u044b \u0442\u043e\u0447\u043a\u0438 \u043d\u0443\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c, \u0434\u043e\u0431\u0430\u0432\u0438\u043b\u0441\u044f \u0431\u044b <code>prisma.dot.create<\/code> \u0432 <code>.serverReply<\/code> \u0438 \u043e\u0431\u044b\u0447\u043d\u044b\u0439 \u043a\u0432\u0435\u0440\u0438 \u043d\u0430 \u0438\u0441\u0442\u043e\u0440\u0438\u044e, \u0440\u043e\u0432\u043d\u043e \u043a\u0430\u043a \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435.<\/p>\n<p><code>membership<\/code> \u0432 <code>dotPutHandler(membership)<\/code> \u044d\u0442\u043e \u0430\u0434\u0440\u0435\u0441\u0430\u0446\u0438\u044f: \u0445\u044d\u043d\u0434\u043b\u0435\u0440 \u0441\u043f\u0435\u0439\u0441\u0430 \u0432\u0441\u0435\u0433\u0434\u0430 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0432 \u043a\u043e\u043c\u043d\u0430\u0442\u0443. \u0423 \u043c\u0435\u043c\u0431\u0435\u0440\u0448\u0438\u043f\u0430 \u0442\u0443\u0442 \u043e\u0434\u043d\u0430 \u043a\u043e\u043c\u043d\u0430\u0442\u0430, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0435\u0433\u043e \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0432\u043c\u0435\u0441\u0442\u043e \u043d\u0435\u0451.<\/p>\n<p>\u0412\u043e\u0437\u043d\u0438\u043a\u0430\u0435\u0442 \u0440\u0435\u0437\u043e\u043d\u043d\u044b\u0439 \u0432\u043e\u043f\u0440\u043e\u0441: \u0435\u0441\u043b\u0438 \u043a\u043e\u043c\u043d\u0430\u0442\u0430 \u0432\u0441\u0435\u0433\u043e \u043e\u0434\u043d\u0430 \u0438 \u043f\u043e\u043f\u0430\u0441\u0442\u044c \u0432 \u043d\u0435\u0451 \u043c\u043e\u0436\u0435\u0442 \u043b\u044e\u0431\u043e\u0439, \u0437\u0430\u0447\u0435\u043c \u043e\u043d\u0430 \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0443\u0436\u043d\u0430, \u0432\u0435\u0434\u044c \u0442\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435 \u0443\u043c\u0435\u0435\u0442 \u043a\u0430\u043d\u0430\u043b. \u0417\u0430\u0442\u0435\u043c, \u0447\u0442\u043e \u0432\u0445\u043e\u0434 \u0432 \u043a\u043e\u043c\u043d\u0430\u0442\u0443 \u044d\u0442\u043e \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0430, \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u043a\u043b\u0438\u0435\u043d\u0442. \u041f\u043e\u043a\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043e\u0442\u043a\u0440\u044b\u043b \u0434\u043e\u0441\u043a\u0443, \u043e\u043d \u0432 \u043a\u043e\u043c\u043d\u0430\u0442\u0443 \u043d\u0435 \u0432\u043e\u0448\u0451\u043b, \u0438 \u043f\u0443\u0448\u0438 \u0441 \u0442\u043e\u0447\u043a\u0430\u043c\u0438 \u0435\u043c\u0443 \u043d\u0435 \u0438\u0434\u0443\u0442 \u0432\u043e\u043e\u0431\u0449\u0435. \u041e\u0442\u043a\u0440\u044b\u043b, \u0432\u043e\u0448\u0451\u043b, \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442. \u0423\u0448\u0451\u043b \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0440\u0430\u0437\u043c\u043e\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043b\u0441\u044f, \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0430 \u0441\u043d\u044f\u043b\u0430\u0441\u044c. \u0421 \u043a\u0430\u043d\u0430\u043b\u044c\u043d\u044b\u043c \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u043e\u043c \u0442\u0430\u043a \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0441\u044f: \u043a\u0430\u043d\u0430\u043b \u043e\u0434\u0438\u043d \u043d\u0430 \u0432\u0441\u0451 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0438 \u0435\u0433\u043e \u043f\u0443\u0448\u0438 \u043f\u0440\u0438\u0445\u043e\u0434\u044f\u0442 \u0432\u0441\u0435\u043c \u0438 \u0432\u0441\u0435\u0433\u0434\u0430.<\/p>\n<h2>\u041f\u0440\u0438\u043c\u0435\u0440 3.1. \u041e\u0431\u0449\u0438\u0439 \u0447\u0430\u0442 \u043c\u0435\u0436\u0434\u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c\u0438<\/h2>\n<p>\u0415\u0441\u0442\u044c \u0447\u0430\u0442, \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u0432 \u0411\u0414. \u041f\u0440\u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u0438 \u043f\u043e\u0434\u0433\u0440\u0443\u0436\u0430\u0435\u0442\u0441\u044f \u0438\u0441\u0442\u043e\u0440\u0438\u044f, \u0434\u0430\u043b\u044c\u0448\u0435 \u043d\u043e\u0432\u044b\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u043e\u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043f\u043e \u043c\u0435\u0440\u0435 \u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u044f. \u0427\u0438\u0442\u0430\u0442\u044c \u0447\u0430\u0442 \u043c\u043e\u0433\u0443\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0435.<\/p>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 \u0447\u0430\u0442 \u044d\u0442\u043e \u043a\u043e\u043c\u043d\u0430\u0442\u0430. \u0424\u043e\u0440\u043c\u0430 \u043a\u043e\u043c\u043d\u0430\u0442\u044b \u043e\u0431\u044a\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432 \u043e\u043f\u0435\u043d\u0435\u0440\u0435 \u0441\u043f\u0435\u0439\u0441\u0430, \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u043a\u0430\u043a \u043f\u0440\u043e\u043f\u0441\u044b \u0443 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430, \u0438 \u0434\u0430\u043b\u044c\u0448\u0435 \u0432\u0441\u0451 \u0432\u043d\u0438\u0437 \u043f\u043e \u0446\u0435\u043f\u043e\u0447\u043a\u0435 \u043f\u0440\u043e \u043d\u0435\u0451 \u0437\u043d\u0430\u0435\u0442.<\/p>\n<pre><code class=\"typescript\">\/\/ pages\/chat.tsxconst messageSchema = z.object({  id: z.number(),  chatId: z.string(),  authorId: z.string(),  text: z.string(),  createdAt: z.date(),})export const chatSpace = appChannel.lets  \/\/ \u0434\u0436\u0435\u043d\u0435\u0440\u0438\u043a \u044d\u0442\u043e \u0444\u043e\u0440\u043c\u0430 \u043a\u043e\u043c\u043d\u0430\u0442\u044b. \u041a\u043e\u043c\u043d\u0430\u0442\u0430 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043b\u044e\u0431\u044b\u043c \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u043c, \u043a\u0430\u043a\u043e\u0439 \u0443\u0434\u043e\u0431\u0435\u043d  \/\/ \u0432\u0430\u0448\u0435\u043c\u0443 \u0434\u043e\u043c\u0435\u043d\u0443, { chatId } \u0442\u0443\u0442 \u043f\u0440\u043e\u0441\u0442\u043e \u0447\u0430\u0441\u0442\u043d\u044b\u0439 \u0441\u043b\u0443\u0447\u0430\u0439  .space&lt;{ chatId: string }&gt;()  .input(z.object({ chatId: z.string() })) \/\/ \u044d\u0442\u043e \u0442\u043e, \u0447\u0442\u043e \u043a\u043b\u0438\u0435\u043d\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u0442 \u0432 join, \u0430 \u043d\u0435 \u043a\u043e\u043c\u043d\u0430\u0442\u0430  .joiner(async ({ input, identity }) =&gt; {    \/\/ \u0432\u0445\u043e\u0434 \u0432 \u043a\u043e\u043c\u043d\u0430\u0442\u0443 \u044d\u0442\u043e \u0438 \u0435\u0441\u0442\u044c \u0433\u0435\u0439\u0442 \u043d\u0430 \u0447\u0442\u0435\u043d\u0438\u0435 \u0447\u0430\u0442\u0430    if (!identity.authorized) {      throw new AppError('\u0422\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0445', { status: 401 })    }    \/\/ \u0432\u043e\u0437\u0432\u0440\u0430\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u043d\u0430 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435 \u0444\u043e\u0440\u043c\u0435 \u043a\u043e\u043c\u043d\u0430\u0442\u044b \u0438\u0437 \u0434\u0436\u0435\u043d\u0435\u0440\u0438\u043a\u0430.    \/\/ \u041b\u0438\u0448\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 \u0437\u0434\u0435\u0441\u044c \u044d\u0442\u043e \u043e\u0448\u0438\u0431\u043a\u0430 \u0442\u0438\u043f\u043e\u0432, \u0430 \u043d\u0435 \u043c\u0435\u043b\u043e\u0447\u044c: \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043a\u043e\u043c\u043d\u0430\u0442\u044b    \/\/ \u0438 \u0435\u0441\u0442\u044c \u0435\u0451 \u0430\u0434\u0440\u0435\u0441, \u0441 \u043b\u0438\u0448\u043d\u0438\u043c \u043a\u043b\u044e\u0447\u043e\u043c \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0430\u0441\u044c \u0431\u044b \u0434\u0440\u0443\u0433\u0430\u044f \u043a\u043e\u043c\u043d\u0430\u0442\u0430    return { chatId: input.chatId }  })  .space()\/\/ \u043a\u043b\u0438\u0435\u043d\u0442 \u2192 \u0441\u0435\u0440\u0432\u0435\u0440: \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0438 \u0440\u0430\u0437\u043e\u0441\u043b\u0430\u0442\u044c \u0435\u0433\u043e \u043a\u043e\u043c\u043d\u0430\u0442\u0435export const messageSendHandler = chatSpace.lets  .serverHandler()  .clientSend(z.object({ text: z.string().min(1).max(1000) }))  .serverReply(async ({ input, identity, room }) =&gt; {    \/\/ \u0433\u043e\u0441\u0442\u044f \u0441\u044e\u0434\u0430 \u043d\u0435 \u043f\u0443\u0441\u0442\u0438\u043b joiner, \u043d\u043e TypeScript \u043f\u0440\u043e \u044d\u0442\u043e \u043d\u0435 \u0437\u043d\u0430\u0435\u0442,    \/\/ \u0430 \u043b\u0438\u0448\u043d\u044f\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u043d\u0435 \u0433\u0440\u0435\u0445    if (!identity.authorized) {      throw new AppError('\u0422\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0445', { status: 401 })    }    const message = await prisma.message.create({      data: { text: input.text, chatId: room.chatId, authorId: identity.id },    })    \/\/ \u0442\u043e\u0447\u043d\u044b\u0439 \u0430\u0434\u0440\u0435\u0441 \u043a\u043e\u043c\u043d\u0430\u0442\u044b, \u044d\u0442\u043e pub\/sub-\u0442\u043e\u043f\u0438\u043a, \u0430 \u043d\u0435 \u043f\u0435\u0440\u0435\u0431\u043e\u0440 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439    void messageAddedHandler.sendToClient(message, { room })    return message \/\/ \u044d\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u0435\u043b\u044c \u0432 \u043e\u0442\u0432\u0435\u0442 \u043d\u0430 \u0441\u0432\u043e\u0439 sendToServer  })  .serverHandler()\/\/ \u0441\u0435\u0440\u0432\u0435\u0440 \u2192 \u043a\u043b\u0438\u0435\u043d\u0442: \u0432 \u043a\u043e\u043c\u043d\u0430\u0442\u0435 \u043d\u043e\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435export const messageAddedHandler = chatSpace.lets  .clientHandler()  .serverSend(messageSchema)  .clientHandler()\/\/ \u0438\u0441\u0442\u043e\u0440\u0438\u044f \u044d\u0442\u043e \u043e\u0431\u044b\u0447\u043d\u044b\u0439 HTTP-\u043a\u0432\u0435\u0440\u0438 \u0438 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u043f\u0440\u0430\u0432\u0434\u044b. \u0423 \u043d\u0435\u0433\u043e \u0441\u0432\u043e\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043f\u0440\u0430\u0432:\/\/ \u0441\u043e\u043a\u0435\u0442 \u0438 HTTP \u044d\u0442\u043e \u0434\u0432\u0435 \u0440\u0430\u0437\u043d\u044b\u0435 \u0434\u0432\u0435\u0440\u0438, \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u043d\u0430\u0434\u043e \u043e\u0431\u0435export const messagesQuery = root.lets  .query()  .input(z.object({ chatId: z.string() }))  .loader(async ({ input, request }) =&gt; {    const user = await getUserFromRequest(request)    if (!user) throw new AppError('\u0422\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0445', { status: 401 })    return {      messages: await prisma.message.findMany({        where: { chatId: input.chatId },        orderBy: { id: 'asc' },        take: 100,      }),    }  })  .query()<\/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\u043e\u043d\u0435\u043d\u0442: \u0432\u0445\u043e\u0434 \u0432 \u043a\u043e\u043c\u043d\u0430\u0442\u0443, \u0438\u0441\u0442\u043e\u0440\u0438\u044f \u0438\u0437 \u043a\u0432\u0435\u0440\u0438, \u043f\u0443\u0448 \u043f\u0438\u0448\u0435\u0442 \u043d\u043e\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043f\u0440\u044f\u043c\u043e \u0432 \u043a\u0435\u0448 \u044d\u0442\u043e\u0433\u043e \u0436\u0435 \u043a\u0432\u0435\u0440\u0438.<\/p>\n<pre><code class=\"typescript\">const Chat = ({ chatId }: { chatId: string }) =&gt; {  const membership = chatSpace.useMembership({ chatId })  const { data } = messagesQuery.useQuery({ chatId })  const [text, setText] = useState('')  messageAddedHandler(membership).useOnMessageFromServer(({ message }) =&gt; {    \/\/ \u043f\u0443\u0448 \u043f\u0440\u0438\u043d\u0451\u0441 \u0433\u043e\u0442\u043e\u0432\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435, \u0437\u043d\u0430\u0447\u0438\u0442 \u0437\u0430\u043f\u0440\u043e\u0441 \u0437\u0430 \u043d\u0438\u043c\u0438 \u043d\u0435 \u043d\u0443\u0436\u0435\u043d    messagesQuery.setQueryData({ chatId }, (old) =&gt; ({      messages: [...(old?.messages ?? []), message],    }))  })  return (    &lt;&gt;      &lt;ul&gt;        {data?.messages.map((message) =&gt; (          &lt;li key={message.id}&gt;{message.text}&lt;\/li&gt;        ))}      &lt;\/ul&gt;      &lt;form        onSubmit={(e) =&gt; {          e.preventDefault()          setText('')          void messageSendHandler(membership)            .sendToServer({ text })            .catch((error) =&gt; alert(error.message))        }}      &gt;        &lt;input value={text} onChange={(e) =&gt; setText(e.target.value)} \/&gt;        &lt;button disabled={membership.status !== 'joined'}&gt;\u041e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c&lt;\/button&gt;      &lt;\/form&gt;    &lt;\/&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>\u0415\u0441\u043b\u0438 \u0433\u043e\u0441\u0442\u044f \u0432 \u043a\u043e\u043c\u043d\u0430\u0442\u0443 \u043d\u0435 \u043f\u0443\u0441\u0442\u0438\u043b\u0438, <code>membership.status<\/code> \u0441\u0442\u0430\u043d\u0435\u0442 <code>error<\/code>, \u0430 <code>membership.error<\/code> \u0431\u0443\u0434\u0435\u0442 \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u043e\u0448\u0438\u0431\u043a\u043e\u0439 \u0438\u0437 joiner\u2019\u0430. \u041e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u00ab\u044f \u043d\u0435 \u0432 \u0447\u0430\u0442\u0435\u00bb \u0438\u0437\u043e\u0431\u0440\u0435\u0442\u0430\u0442\u044c \u043d\u0435 \u043d\u0443\u0436\u043d\u043e, \u043e\u043d\u043e \u0443\u0436\u0435 \u0435\u0441\u0442\u044c.<\/p>\n<p>\u041e\u0441\u0442\u0430\u0451\u0442\u0441\u044f \u0432\u043e\u043f\u0440\u043e\u0441, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432 \u0447\u0430\u0442\u0430\u0445 \u0432\u0441\u043f\u043b\u044b\u0432\u0430\u0435\u0442 \u0432\u0441\u0435\u0433\u0434\u0430: \u0447\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u0441 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438, \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u043c\u0438 \u0432 \u0442\u043e\u0442 \u043c\u043e\u043c\u0435\u043d\u0442, \u043a\u043e\u0433\u0434\u0430 \u043a\u043b\u0438\u0435\u043d\u0442 \u0441\u0438\u0434\u0435\u043b \u0431\u0435\u0437 \u0441\u0435\u0442\u0438. \u041f\u0443\u0448 \u044d\u0442\u043e \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u00ab\u0432 \u043e\u0434\u0438\u043d \u043a\u043e\u043d\u0435\u0446\u00bb, \u0438 \u0435\u0441\u043b\u0438 \u0441\u043e\u043a\u0435\u0442\u0430 \u0432 \u044d\u0442\u043e\u0442 \u043c\u043e\u043c\u0435\u043d\u0442 \u043d\u0435\u0442, \u0444\u0440\u0435\u0439\u043c \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u043e\u0442\u0435\u0440\u044f\u043d. \u0412 \u0442\u0430\u043a\u043e\u043c \u0432\u0438\u0434\u0435, \u043a\u0430\u043a \u0432\u044b\u0448\u0435, \u043e\u0442\u0432\u0435\u0442 \u043e\u0434\u0438\u043d: \u043f\u0435\u0440\u0435\u0447\u0438\u0442\u0430\u0442\u044c \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u043f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0435\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f.<\/p>\n<pre><code class=\"typescript\">const membership = chatSpace.useMembership(  { chatId },  \/\/ \u0432\u0445\u043e\u0434 \u0432 \u043a\u043e\u043c\u043d\u0430\u0442\u0443 \u0441\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u0437\u0430\u043d\u043e\u0432\u043e \u043f\u043e\u0441\u043b\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0440\u0435\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u0430,  \/\/ \u0442\u0430\u043a \u0447\u0442\u043e \u0437\u0434\u0435\u0441\u044c \u0436\u0435 \u0438 \u0434\u043e\u0433\u043e\u043d\u044f\u0435\u043c \u043f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043d\u043e\u0435  { onEnter: () =&gt; void messagesQuery.invalidateQuery({ chatId }) },)<\/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>\u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043d\u043e \u043f\u0435\u0440\u0435\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u0442 \u0432\u0441\u044e \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u043f\u043e\u0441\u043b\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u043e\u0440\u0433\u0430\u043d\u0438\u044f \u0441\u0435\u0442\u0438. \u0414\u0430\u043b\u044c\u0448\u0435 \u043f\u043e\u043a\u0430\u0436\u0443, \u043a\u0430\u043a \u044d\u0442\u043e\u0433\u043e \u043d\u0435 \u0434\u0435\u043b\u0430\u0442\u044c.<\/p>\n<h2>\u041f\u0440\u0438\u043c\u0435\u0440 3.2. \u0422\u043e\u0442 \u0436\u0435 \u0447\u0430\u0442, \u043d\u043e \u0431\u0435\u0437 \u043b\u0438\u0448\u043d\u0438\u0445 \u043f\u0435\u0440\u0435\u0447\u0438\u0442\u044b\u0432\u0430\u043d\u0438\u0439<\/h2>\n<p>\u0420\u0435\u043a\u043e\u043d\u043d\u0435\u043a\u0442 \u043e\u0431\u044b\u0447\u043d\u043e \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u0439: \u043c\u0435\u0442\u0440\u043e, \u043b\u0438\u0444\u0442, \u0432\u0430\u0439\u0444\u0430\u0439 \u043c\u043e\u0440\u0433\u043d\u0443\u043b. \u0420\u0430\u0437\u0440\u044b\u0432 \u043d\u0430 \u0434\u0432\u0435 \u0441\u0435\u043a\u0443\u043d\u0434\u044b, \u0437\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432 \u043a\u043e\u043c\u043d\u0430\u0442\u0435 \u043f\u043e\u044f\u0432\u0438\u043b\u043e\u0441\u044c \u043e\u0434\u043d\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435. \u041f\u0435\u0440\u0435\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u0440\u0430\u0434\u0438 \u043d\u0435\u0433\u043e \u0441\u043e\u0442\u043d\u044e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0433\u043b\u0443\u043f\u043e, \u0445\u043e\u0447\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0441\u0442\u043e \u0434\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u043f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043d\u043e\u0435.<\/p>\n<p>\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0443 \u043a\u0430\u043d\u0430\u043b\u0430 \u0435\u0441\u0442\u044c <code>resumable<\/code>. \u041a\u043b\u0438\u0435\u043d\u0442 \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043f\u0440\u0435\u0434\u044a\u044f\u0432\u043b\u044f\u0435\u0442 \u0441\u0432\u043e\u0439 \u043a\u043b\u044e\u0447 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f, \u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0438\u0437 \u0441\u0432\u043e\u0435\u0439 \u0437\u0430\u043f\u0438\u0441\u0438: \u0442\u0443 \u0436\u0435 identity, \u0442\u0435 \u0436\u0435 \u043a\u043e\u043c\u043d\u0430\u0442\u044b, \u0442\u043e\u0442 \u0436\u0435 <code>connectionId<\/code>. \u041a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440 \u0438 joiner\u2019\u044b \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043d\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044e\u0442\u0441\u044f, \u0442\u043e \u0435\u0441\u0442\u044c \u043f\u043e\u0441\u043b\u0435 \u0440\u0435\u0434\u0435\u043f\u043b\u043e\u044f \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043b\u0430\u0432\u0438\u043d\u0443 \u043f\u043e\u043b\u043d\u044b\u0445 \u043f\u0435\u0440\u0435\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439.<\/p>\n<pre><code class=\"typescript\">\/\/ lib\/channel.tsexport const appChannel = root.lets  .channel()  .connector(\/* \u0442\u043e\u0442 \u0436\u0435 \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440, \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f *\/)  .channel({ resumable: true })<\/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>\u041e\u0434\u043d\u043e\u0433\u043e <code>resumable<\/code> \u043d\u0430 \u043a\u0430\u043d\u0430\u043b\u0435 \u043c\u0430\u043b\u043e: \u043e\u043d \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435, \u043d\u043e \u043d\u0435 \u043f\u0443\u0448\u0438, \u0443\u043b\u0435\u0442\u0435\u0432\u0448\u0438\u0435 \u0432 \u0440\u0430\u0437\u0440\u044b\u0432. \u0411\u0443\u0444\u0435\u0440 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u0442\u043e\u043c \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u0435, \u0447\u044c\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0436\u0430\u043b\u043a\u043e \u0442\u0435\u0440\u044f\u0442\u044c.<\/p>\n<pre><code class=\"typescript\">export const messageAddedHandler = chatSpace.lets  .clientHandler()  .serverSend(messageSchema)  \/\/ \u0441\u0435\u0440\u0432\u0435\u0440 \u0445\u0440\u0430\u043d\u0438\u0442 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 128 \u0444\u0440\u0435\u0439\u043c\u043e\u0432 \u044d\u0442\u043e\u0433\u043e \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u0430 \u043d\u0430 \u043a\u043e\u043c\u043d\u0430\u0442\u0443  \/\/ \u0438 \u0434\u043e\u0438\u0433\u0440\u044b\u0432\u0430\u0435\u0442 \u0438\u0445 \u043f\u0440\u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u0430, \u0432 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435  .clientHandler({ resumable: true })<\/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 \u043a\u043b\u0438\u0435\u043d\u0442, \u0432\u0435\u0440\u043d\u0443\u0432\u0448\u0438\u0439\u0441\u044f \u0438\u0437 \u0440\u0430\u0437\u0440\u044b\u0432\u0430, \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u044b\u0435 \u043f\u0443\u0448\u0438. \u041e\u043d\u0438 \u043f\u043e\u043f\u0430\u0434\u0443\u0442 \u0432 \u0442\u043e\u0442 \u0436\u0435 <code>useOnMessageFromServer<\/code> \u0438 \u043b\u044f\u0433\u0443\u0442 \u0432 \u043a\u0435\u0448 \u043a\u0432\u0435\u0440\u0438. \u041f\u0435\u0440\u0435\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u043d\u0435 \u043d\u0443\u0436\u043d\u043e.<\/p>\n<p>\u041d\u043e \u0431\u0443\u0444\u0435\u0440 \u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0439, \u0438 \u044d\u0442\u043e \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0438\u0430\u043b\u044c\u043d\u043e: \u043e\u043d \u043b\u0435\u0436\u0438\u0442 \u0432 \u043f\u0430\u043c\u044f\u0442\u0438 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430. \u041e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u0441\u0435\u0442\u0438 \u043d\u0430 \u043f\u043e\u043b\u0447\u0430\u0441\u0430, \u0440\u0435\u0434\u0435\u043f\u043b\u043e\u0439, \u043f\u0435\u0440\u0435\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0431\u0443\u0444\u0435\u0440\u0430, \u0438 \u0434\u044b\u0440\u0430 \u0432\u0441\u0451-\u0442\u0430\u043a\u0438 \u0431\u0443\u0434\u0435\u0442. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 Point0 \u043d\u0435 \u0437\u0430\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0432\u0430\u0441 \u0433\u0430\u0434\u0430\u0442\u044c, \u0430 \u0441\u0430\u043c \u0433\u043e\u0432\u043e\u0440\u0438\u0442, \u043f\u043e\u043a\u0440\u044b\u0442 \u0440\u0430\u0437\u0440\u044b\u0432 \u0438\u043b\u0438 \u043d\u0435\u0442.<\/p>\n<pre><code class=\"typescript\">const membership = chatSpace.useMembership(  { chatId },  {    onEnter: ({ gapless }) =&gt; {      \/\/ gapless \u044d\u0442\u043e \u0434\u043e\u043a\u0430\u0437\u0430\u0442\u0435\u043b\u044c\u0441\u0442\u0432\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430, \u0447\u0442\u043e \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043f\u043e\u0442\u0435\u0440\u044f\u043d\u043e:      \/\/ \u043b\u0438\u0431\u043e \u044d\u0442\u043e \u043f\u0435\u0440\u0432\u044b\u0439 \u0432\u0445\u043e\u0434, \u043b\u0438\u0431\u043e \u0431\u0443\u0444\u0435\u0440 \u043f\u043e\u043a\u0440\u044b\u043b \u0432\u0435\u0441\u044c \u0440\u0430\u0437\u0440\u044b\u0432 \u0446\u0435\u043b\u0438\u043a\u043e\u043c.      \/\/ \u0415\u0441\u0442\u044c \u0434\u043e\u043a\u0430\u0437\u0430\u0442\u0435\u043b\u044c\u0441\u0442\u0432\u043e, \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0434\u0435\u043b\u0430\u0435\u043c, \u043f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043d\u043e\u0435 \u0443\u0436\u0435 \u0434\u043e\u0438\u0433\u0440\u0430\u043d\u043e.      \/\/ \u041d\u0435\u0442, \u0447\u0435\u0441\u0442\u043d\u043e \u043f\u0435\u0440\u0435\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u043c \u0438\u0441\u0442\u043e\u0440\u0438\u044e      if (!gapless) void messagesQuery.invalidateQuery({ chatId })    },  },)<\/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>\u041e\u0434\u043d\u043e \u0443\u0441\u043b\u043e\u0432\u0438\u0435 \u043f\u043e\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0432\u0441\u0435 \u0441\u043b\u0443\u0447\u0430\u0438: \u043f\u0435\u0440\u0432\u044b\u0439 \u0432\u0445\u043e\u0434, \u043a\u043e\u0440\u043e\u0442\u043a\u043e\u0435 \u043c\u043e\u0440\u0433\u0430\u043d\u0438\u0435, \u0434\u043e\u043b\u0433\u0438\u0439 \u043e\u0444\u043b\u0430\u0439\u043d, \u0440\u0435\u0434\u0435\u043f\u043b\u043e\u0439, \u0432\u043e\u0437\u0432\u0440\u0430\u0442 \u043f\u043e\u0441\u043b\u0435 \u043a\u0438\u043a\u0430. \u0412\u0435\u0441\u0442\u0438 \u0443\u0447\u0451\u0442 \u0442\u043e\u0433\u043e, \u0447\u0442\u043e \u0432\u044b \u043c\u043e\u0433\u043b\u0438 \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u044c, \u043d\u0435 \u043d\u0443\u0436\u043d\u043e, \u0437\u0430 \u0432\u0430\u0441 \u0435\u0433\u043e \u0432\u0435\u0434\u0451\u0442 \u0441\u0435\u0440\u0432\u0435\u0440.<\/p>\n<p>\u041f\u0440\u0430\u0432\u0434\u0430 \u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u0445 \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u0436\u0438\u0432\u0451\u0442 \u0432 \u0431\u0430\u0437\u0435, \u0430 \u043d\u0435 \u0432 \u0431\u0443\u0444\u0435\u0440\u0435. \u0411\u0443\u0444\u0435\u0440 \u044d\u0442\u043e \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0443\u0431\u0438\u0440\u0430\u0435\u0442 \u043b\u0438\u0448\u043d\u0438\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u0432 \u0447\u0430\u0441\u0442\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u0430 \u043d\u0435 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435.<\/p>\n<h2>\u041f\u0440\u0438\u043c\u0435\u0440 4. \u041b\u0438\u0447\u043d\u044b\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043c\u0435\u0436\u0434\u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c\u0438<\/h2>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043c\u043e\u0436\u0435\u0442 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0434\u0440\u0443\u0433\u043e\u043c\u0443. \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u0432 \u0411\u0414. \u041f\u043b\u044e\u0441 \u0445\u043e\u0447\u0435\u0442\u0441\u044f, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u044c \u0432\u0438\u0434\u0435\u043b \u0437\u043d\u0430\u0447\u043e\u043a \u043d\u043e\u0432\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u0434\u0430\u0436\u0435 \u043a\u043e\u0433\u0434\u0430 \u0434\u0438\u0430\u043b\u043e\u0433 \u0443 \u043d\u0435\u0433\u043e \u043d\u0435 \u043e\u0442\u043a\u0440\u044b\u0442.<\/p>\n<p>\u041a\u043e\u043c\u043d\u0430\u0442\u0430 \u0434\u0438\u0430\u043b\u043e\u0433\u0430 \u044d\u0442\u043e \u043f\u0430\u0440\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439. \u041a\u043e\u043c\u043d\u0430\u0442\u0430 \u044d\u0442\u043e \u043b\u044e\u0431\u043e\u0439 \u043e\u0431\u044a\u0435\u043a\u0442, \u043a\u0430\u043a\u043e\u0439 \u0432\u0430\u043c \u0443\u0434\u043e\u0431\u0435\u043d, \u043d\u043e \u0435\u0451 \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0438 \u0435\u0441\u0442\u044c \u0435\u0451 \u0430\u0434\u0440\u0435\u0441, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0441\u043f\u0438\u0441\u043e\u043a \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432 \u043d\u0430\u0434\u043e \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c: \u0438\u043d\u0430\u0447\u0435 <code>[a, b]<\/code> \u0438 <code>[b, a]<\/code> \u0434\u0430\u0434\u0443\u0442 \u0434\u0432\u0435 \u0440\u0430\u0437\u043d\u044b\u0435 \u043a\u043e\u043c\u043d\u0430\u0442\u044b.<\/p>\n<pre><code class=\"typescript\">\/\/ pages\/dm.tsx\/\/ dm \u044d\u0442\u043e direct message, \u043b\u0438\u0447\u043d\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0434\u0440\u0443\u0433\u043e\u043c\u0443const dmSchema = z.object({  id: z.number(),  fromId: z.string(),  toId: z.string(),  text: z.string(),  createdAt: z.date(),})export const dmSpace = appChannel.lets  .space&lt;{ members: string[] }&gt;()  .input(z.object({ withUserId: z.string() }))  .joiner(({ input, identity }) =&gt; {    if (!identity.authorized) {      throw new AppError('\u0422\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0445', { status: 401 })    }    \/\/ sort \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u0435\u043d: \u043a\u043e\u043c\u043d\u0430\u0442\u0430 \u0430\u0434\u0440\u0435\u0441\u0443\u0435\u0442\u0441\u044f \u0441\u0432\u043e\u0435\u0439 \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0435\u0439    return { members: [identity.id, input.withUserId].sort() }  })  .space()export const dmAddedHandler = dmSpace.lets  .clientHandler()  .serverSend(dmSchema)  .clientHandler()<\/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\u043d\u0430\u0447\u043e\u043a \u043d\u043e\u0432\u044b\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u044d\u0442\u043e \u0432\u0442\u043e\u0440\u043e\u0439 \u0441\u043f\u0435\u0439\u0441, \u0438 \u043f\u043e\u043f\u0430\u0434\u0430\u044e\u0442 \u0432 \u043d\u0435\u0433\u043e \u0441\u043e\u0432\u0441\u0435\u043c \u0438\u043d\u0430\u0447\u0435. \u0414\u043e \u0441\u0438\u0445 \u043f\u043e\u0440 \u0432\u043e \u0432\u0441\u0435 \u043a\u043e\u043c\u043d\u0430\u0442\u044b \u043a\u043b\u0438\u0435\u043d\u0442 \u0432\u0445\u043e\u0434\u0438\u043b \u0441\u0430\u043c, \u0447\u0435\u0440\u0435\u0437 <code>.joiner<\/code>: \u0437\u0430\u0445\u043e\u0442\u0435\u043b, \u0432\u043e\u0448\u0451\u043b, \u0443\u0448\u0451\u043b \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u0432\u044b\u0448\u0435\u043b. \u0417\u0434\u0435\u0441\u044c \u043d\u0430\u043e\u0431\u043e\u0440\u043e\u0442, \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 <code>.enroller<\/code>: \u0441\u0435\u0440\u0432\u0435\u0440 \u0437\u0430\u0447\u0438\u0441\u043b\u044f\u0435\u0442 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0432 \u043a\u043e\u043c\u043d\u0430\u0442\u0443 \u0432 \u043c\u043e\u043c\u0435\u043d\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u043a\u0430\u043d\u0430\u043b\u0443, \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043d\u0435 \u0441\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u044e\u0442, \u0438 \u0432\u044b\u0439\u0442\u0438 \u043e\u043d \u043d\u0435 \u043c\u043e\u0436\u0435\u0442.<\/p>\n<pre><code class=\"typescript\">\/\/ \u043b\u0438\u0447\u043d\u0430\u044f \u043a\u043e\u043c\u043d\u0430\u0442\u0430 \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044fexport const userSpace = appChannel.lets  .space&lt;{ userId: string }&gt;()  \/\/ .enroller \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u043f\u0440\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043a \u043a\u0430\u043d\u0430\u043b\u0443, \u0434\u043e \u0442\u043e\u0433\u043e,  \/\/ \u043a\u0430\u043a \u043a\u043b\u0438\u0435\u043d\u0442 \u0447\u0442\u043e-\u043b\u0438\u0431\u043e \u043e\u0442\u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043b  .enroller(({ identity }) =&gt;    \/\/ \u0443 \u0433\u043e\u0441\u0442\u044f \u043d\u0435\u0442 \u043b\u0438\u0447\u043d\u043e\u0439 \u043a\u043e\u043c\u043d\u0430\u0442\u044b, \u0438 \u044d\u0442\u043e \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e: \u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u043d\u0438\u0447\u0435\u0433\u043e, \u043d\u0438\u043a\u0443\u0434\u0430 \u043d\u0435 \u0437\u0430\u0447\u0438\u0441\u043b\u0438\u043b\u0438    identity.authorized ? { userId: identity.id } : undefined,  )  .space() \/\/ .joiner \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442, \u0437\u043d\u0430\u0447\u0438\u0442 \u0441 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0432 \u044d\u0442\u043e\u0442 \u0441\u043f\u0435\u0439\u0441 \u0432\u043e\u0439\u0442\u0438 \u043d\u0435\u043b\u044c\u0437\u044f \u0432\u043e\u043e\u0431\u0449\u0435export const dmBadgeHandler = userSpace.lets  .clientHandler()  .serverSend(z.object({ fromUserId: z.string(), preview: z.string() }))  .clientHandler()<\/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>\u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u043f\u0438\u0448\u0435\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0432 \u0431\u0430\u0437\u0443 \u0438 \u0434\u0435\u043b\u0430\u0435\u0442 \u0434\u0432\u0430 \u043f\u0443\u0448\u0430: \u043e\u0434\u0438\u043d \u0432 \u043a\u043e\u043c\u043d\u0430\u0442\u0443 \u0434\u0438\u0430\u043b\u043e\u0433\u0430, \u0447\u0442\u043e\u0431\u044b \u0441\u043e\u0431\u0435\u0441\u0435\u0434\u043d\u0438\u043a \u0443\u0432\u0438\u0434\u0435\u043b \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0432 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u043c \u043e\u043a\u043d\u0435, \u0432\u0442\u043e\u0440\u043e\u0439 \u0432 \u043b\u0438\u0447\u043d\u0443\u044e \u043a\u043e\u043c\u043d\u0430\u0442\u0443 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u044f, \u0447\u0442\u043e\u0431\u044b \u0443 \u043d\u0435\u0433\u043e \u0437\u0430\u0433\u043e\u0440\u0435\u043b\u0441\u044f \u0437\u043d\u0430\u0447\u043e\u043a \u043d\u0430 \u043b\u044e\u0431\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435.<\/p>\n<pre><code class=\"typescript\">export const dmSendHandler = dmSpace.lets  .serverHandler()  .clientSend(z.object({ text: z.string().min(1) }))  .serverReply(async ({ input, identity, room }) =&gt; {    if (!identity.authorized) {      throw new AppError('\u0422\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0445', { status: 401 })    }    const toUserId = room.members.find((id) =&gt; id !== identity.id) ?? identity.id    const dm = await prisma.dm.create({      data: { text: input.text, fromId: identity.id, toId: toUserId },    })    void dmAddedHandler.sendToClient(dm, { room }) \/\/ \u0432 \u043a\u043e\u043c\u043d\u0430\u0442\u0443 \u0434\u0438\u0430\u043b\u043e\u0433\u0430    void dmBadgeHandler.sendToClient(      { fromUserId: identity.id, preview: dm.text.slice(0, 80) },      { room: { userId: toUserId } }, \/\/ \u0432 \u043b\u0438\u0447\u043d\u0443\u044e \u043a\u043e\u043c\u043d\u0430\u0442\u0443 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u044f    )    return dm  })  .serverHandler()<\/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 \u043a\u043b\u0438\u0435\u043d\u0442\u0435 \u0437\u043d\u0430\u0447\u043e\u043a \u044d\u0442\u043e \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0432\u0435\u0441\u0438\u0442\u044c \u0432 \u0448\u0430\u043f\u043a\u0443 \u0438 \u0437\u0430\u0431\u044b\u0442\u044c. \u041d\u0438 join, \u043d\u0438 \u043a\u043e\u043c\u043d\u0430\u0442 \u0432 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u043c \u043a\u043e\u0434\u0435: \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0443\u0436\u0435 \u0437\u0430\u0447\u0438\u0441\u043b\u0435\u043d\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c.<\/p>\n<pre><code class=\"typescript\">const DmBadge = () =&gt; {  const [unread, setUnread] = useState(0)  dmBadgeHandler.useOnMessageFromServer(({ message }) =&gt; {    setUnread((n) =&gt; n + 1)    toast(`${message.fromUserId}: ${message.preview}`)  })  return unread &gt; 0 ? &lt;span className=\"badge\"&gt;{unread}&lt;\/span&gt; : 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>\u0421\u0430\u043c \u0434\u0438\u0430\u043b\u043e\u0433 \u0443\u0441\u0442\u0440\u043e\u0435\u043d \u043a\u0430\u043a \u0447\u0430\u0442 \u0438\u0437 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 3.1, \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u0445\u043e\u0434 \u043f\u043e \u0441\u043e\u0431\u0435\u0441\u0435\u0434\u043d\u0438\u043a\u0443:<\/p>\n<pre><code class=\"typescript\">const Dm = ({ withUserId }: { withUserId: string }) =&gt; {  const membership = dmSpace.useMembership({ withUserId })  const { data } = dmHistoryQuery.useQuery({ withUserId })  const [text, setText] = useState('')  dmAddedHandler(membership).useOnMessageFromServer(({ message }) =&gt; {    dmHistoryQuery.setQueryData({ withUserId }, (old) =&gt; ({      items: [...(old?.items ?? []), message],    }))  })  return (    &lt;&gt;      &lt;ul&gt;        {data?.items.map((dm) =&gt; (          &lt;li key={dm.id}&gt;{dm.text}&lt;\/li&gt;        ))}      &lt;\/ul&gt;      &lt;form        onSubmit={(e) =&gt; {          e.preventDefault()          setText('')          void dmSendHandler(membership)            .sendToServer({ text })            .catch((error) =&gt; alert(error.message))        }}      &gt;        &lt;input value={text} onChange={(e) =&gt; setText(e.target.value)} \/&gt;        &lt;button disabled={membership.status !== 'joined'}&gt;\u041e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c&lt;\/button&gt;      &lt;\/form&gt;    &lt;\/&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>\u0420\u0430\u0437\u043d\u0438\u0446\u0430 \u043c\u0435\u0436\u0434\u0443 <code>.joiner<\/code> \u0438 <code>.enroller<\/code> \u0442\u0443\u0442 \u043d\u0435 \u043a\u043e\u0441\u043c\u0435\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f. \u0412 \u043a\u043e\u043c\u043d\u0430\u0442\u0443 \u0434\u0438\u0430\u043b\u043e\u0433\u0430 \u043a\u043b\u0438\u0435\u043d\u0442 \u0432\u043e\u0448\u0451\u043b \u0441\u0430\u043c \u0438 \u043c\u043e\u0436\u0435\u0442 \u0438\u0437 \u043d\u0435\u0451 \u0432\u044b\u0439\u0442\u0438, \u0437\u0430\u043a\u0440\u044b\u0432 \u0432\u043a\u043b\u0430\u0434\u043a\u0443 \u0441 \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u043a\u043e\u0439. \u0410 \u0437\u0430\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0435 \u0447\u0435\u0440\u0435\u0437 <code>.enroller<\/code> \u044d\u0442\u043e \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u044f: <code>leave()<\/code> \u043d\u0430 \u043b\u0438\u0447\u043d\u043e\u0439 \u043a\u043e\u043c\u043d\u0430\u0442\u0435 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0441\u0434\u0435\u043b\u0430\u0435\u0442, \u0438 \u043f\u043e\u0434\u0434\u0435\u043b\u0430\u043d\u043d\u044b\u0439 \u0444\u0440\u0435\u0439\u043c \u0442\u043e\u0436\u0435, \u0443\u0431\u0440\u0430\u0442\u044c \u043e\u0442\u0442\u0443\u0434\u0430 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u043c\u043e\u0436\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u0435\u0440\u0432\u0435\u0440. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0430 \u043f\u0443\u0448 \u0432 \u043b\u0438\u0447\u043d\u0443\u044e \u043a\u043e\u043c\u043d\u0430\u0442\u0443 \u043c\u043e\u0436\u043d\u043e \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c: \u043f\u043e\u043a\u0430 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u043e\u0442\u043a\u0440\u044b\u0442\u043e, \u043e\u043d\u043e \u0442\u043e\u0447\u043d\u043e \u0432 \u0441\u0432\u043e\u0435\u0439 \u043a\u043e\u043c\u043d\u0430\u0442\u0435 \u0441\u0438\u0434\u0438\u0442.<\/p>\n<p>\u041d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0435 \u0447\u0438\u0441\u043b\u043e \u043d\u0435\u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043d\u043d\u044b\u0445, \u0440\u0430\u0437\u0443\u043c\u0435\u0435\u0442\u0441\u044f, \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f \u0438\u0437 \u0431\u0430\u0437\u044b \u043e\u0431\u044b\u0447\u043d\u044b\u043c \u043a\u0432\u0435\u0440\u0438, \u0430 \u043f\u0443\u0448\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u044e\u0442 \u0441\u0447\u0451\u0442\u0447\u0438\u043a, \u043f\u043e\u043a\u0430 \u0432\u043a\u043b\u0430\u0434\u043a\u0430 \u043e\u0442\u043a\u0440\u044b\u0442\u0430.<\/p>\n<h2>\u041f\u0440\u0438\u043c\u0435\u0440 5. \u0421\u0430\u0439\u0442, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0432\u0441\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u0438\u0434\u0443\u0442 \u0447\u0435\u0440\u0435\u0437 WebSocket<\/h2>\n<p>\u041d\u0438\u0447\u0442\u043e \u043d\u0435 \u043c\u0435\u0448\u0430\u0435\u0442 \u0441\u043e\u0432\u0441\u0435\u043c \u043e\u0442\u043a\u0430\u0437\u0430\u0442\u044c\u0441\u044f \u043e\u0442 HTTP-\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u043a API \u0438 \u0433\u043e\u043d\u044f\u0442\u044c \u0432\u0441\u0451 \u0447\u0435\u0440\u0435\u0437 \u0443\u0436\u0435 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0439 \u0441\u043e\u043a\u0435\u0442. \u0415\u0441\u043b\u0438 \u043d\u0435 \u0436\u0430\u043b\u043a\u043e \u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u043c\u0435\u0442\u043d\u043e \u0443\u0441\u043a\u043e\u0440\u0438\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u0443 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430: TLS-\u0445\u0435\u043d\u0434\u0448\u0435\u0439\u043a\u0430 \u043d\u0435\u0442, \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u0432 \u043d\u0435\u0442, \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0443\u0436\u0435 \u043f\u0440\u043e\u0433\u0440\u0435\u0442\u043e.<\/p>\n<p>\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0439 \u0445\u044d\u043d\u0434\u043b\u0435\u0440 \u0443\u043c\u0435\u0435\u0442 \u043e\u0431\u044a\u044f\u0432\u0438\u0442\u044c, \u0447\u0435\u043c \u043e\u043d \u0431\u0443\u0434\u0435\u0442 \u0434\u043b\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u0430: \u043e\u0431\u044b\u0447\u043d\u044b\u043c \u043a\u0432\u0435\u0440\u0438, \u0431\u0435\u0441\u043a\u043e\u043d\u0435\u0447\u043d\u044b\u043c \u043a\u0432\u0435\u0440\u0438 \u0438\u043b\u0438 \u043c\u0443\u0442\u0430\u0446\u0438\u0435\u0439. \u041e\u0434\u043d\u0430 \u0441\u0442\u0440\u043e\u0447\u043a\u0430 \u0432 \u0446\u0435\u043f\u043e\u0447\u043a\u0435, \u0438 \u0432\u043c\u0435\u0441\u0442\u043e <code>sendToServer<\/code> \u0443 \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u0430 \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u044b\u0439 \u043d\u0430\u0431\u043e\u0440 \u0445\u0443\u043a\u043e\u0432.<\/p>\n<pre><code class=\"typescript\">\/\/ pages\/todos.tsx\/\/ \u043a\u043b\u0438\u0435\u043d\u0442 \u2192 \u0441\u0435\u0440\u0432\u0435\u0440, \u043d\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043a\u0430\u043a \u043a\u0432\u0435\u0440\u0438: \u0442\u043e\u0442 \u0436\u0435 react-query, \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442 \u0441\u043e\u043a\u0435\u0442export const todosHandler = appChannel.lets  .serverHandler()  .clientSend(z.object({ onlyOpen: z.boolean() }))  .serverReply(async ({ input, identity }) =&gt; {    if (!identity.authorized) {      throw new AppError('\u0422\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0445', { status: 401 })    }    return {      todos: await prisma.todo.findMany({        where: {          userId: identity.id,          done: input.onlyOpen ? false : undefined,        },      }),    }  })  .query() \/\/ \u0432\u043e\u0442 \u0438 \u0432\u0441\u0451 \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u0438\u0435  .serverHandler()\/\/ \u0430 \u044d\u0442\u043e \u043c\u0443\u0442\u0430\u0446\u0438\u044f. \u041c\u0443\u0442\u0430\u0446\u0438\u044f \u044d\u0442\u043e \u0434\u0435\u0444\u043e\u043b\u0442, .mutation() \u043c\u043e\u0436\u043d\u043e \u0438 \u043d\u0435 \u043f\u0438\u0441\u0430\u0442\u044cexport const todoAddHandler = appChannel.lets  .serverHandler()  .clientSend(z.object({ title: z.string().min(1) }))  .serverReply(async ({ input, identity }) =&gt; {    if (!identity.authorized) {      throw new AppError('\u0422\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0445', { status: 401 })    }    const todo = await prisma.todo.create({      data: { title: input.title, userId: identity.id },    })    \/\/ \u0432\u0441\u0435\u043c \u0432\u043a\u043b\u0430\u0434\u043a\u0430\u043c \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u0447\u0435\u0440\u0435\u0437 \u0435\u0433\u043e \u043b\u0438\u0447\u043d\u0443\u044e \u043a\u043e\u043c\u043d\u0430\u0442\u0443 \u0438\u0437 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 4.    \/\/ \u0422\u043e\u0447\u043d\u044b\u0439 \u0430\u0434\u0440\u0435\u0441, \u043d\u0438\u043a\u0430\u043a\u043e\u0433\u043e \u043f\u0435\u0440\u0435\u0431\u043e\u0440\u0430 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439    void todosChangedHandler.sendToClient(undefined, {      room: { userId: identity.id },    })    return todo  })  .mutation()  .serverHandler()\/\/ \u0433\u043e\u043b\u044b\u0439 \u0442\u0440\u0438\u0433\u0433\u0435\u0440 \u0431\u0435\u0437 payload: \u00ab\u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0438\u0441\u044c, \u043f\u0435\u0440\u0435\u0447\u0438\u0442\u0430\u0439\u00bb.\/\/ \u0420\u0430\u0441\u0442\u0451\u0442 \u0438\u0437 userSpace, \u0442\u043e \u0435\u0441\u0442\u044c \u0438\u0437 \u043b\u0438\u0447\u043d\u043e\u0439 \u043a\u043e\u043c\u043d\u0430\u0442\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f (\u043f\u0440\u0438\u043c\u0435\u0440 4)export const todosChangedHandler = userSpace.lets.clientHandler().clientHandler()<\/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 \u043a\u043b\u0438\u0435\u043d\u0442\u0435 \u044d\u0442\u043e \u043e\u0431\u044b\u0447\u043d\u044b\u0439 react-query, \u043a \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u0432\u044b \u043f\u0440\u0438\u0432\u044b\u043a\u043b\u0438.<\/p>\n<pre><code class=\"typescript\">const Todos = () =&gt; {  const { data, isPending } = todosHandler.useSocketQuery({ onlyOpen: true })  const add = todoAddHandler.useSocketMutation()  todosChangedHandler.useOnMessageFromServer(() =&gt; {    void todosHandler.invalidateSocketQuery(true) \/\/ \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0440\u043e\u0441 \u0443\u0435\u0434\u0435\u0442 \u043f\u043e \u0441\u043e\u043a\u0435\u0442\u0443  })  if (isPending) return &lt;Spinner \/&gt;  return (    &lt;&gt;      {(data?.todos ?? []).map((todo) =&gt; (        &lt;div key={todo.id}&gt;{todo.title}&lt;\/div&gt;      ))}      &lt;button onClick={() =&gt; void add.mutateAsync({ title: '\u041d\u043e\u0432\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430' })}&gt;        \u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c      &lt;\/button&gt;    &lt;\/&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>\u041a\u0435\u0448, \u0438\u043d\u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f, \u0441\u0442\u0430\u0442\u0443\u0441\u044b, \u0434\u0435\u0432\u0442\u0443\u043b\u0437\u044b react-query \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u043e. \u0420\u0430\u0437\u043d\u0438\u0446\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u0437\u0430\u043f\u0440\u043e\u0441 \u0443\u0445\u043e\u0434\u0438\u0442 \u0444\u0440\u0435\u0439\u043c\u043e\u043c \u0432 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0439 \u0441\u043e\u043a\u0435\u0442, \u0430 \u043d\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u043c HTTP-\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u043c.<\/p>\n<p>\u0414\u0432\u0435 \u0447\u0435\u0441\u0442\u043d\u044b\u0435 \u043e\u0433\u043e\u0432\u043e\u0440\u043a\u0438. \u041f\u0435\u0440\u0432\u0430\u044f: \u0441\u043e\u043a\u0435\u0442-\u043a\u0432\u0435\u0440\u0438 \u043d\u0435 \u0443\u0447\u0430\u0441\u0442\u0432\u0443\u044e\u0442 \u0432 SSR, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u043d\u0435\u0442, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u0435\u0440\u0432\u043e\u0439 \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u043a\u0438 \u043b\u0443\u0447\u0448\u0435 \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043d\u0430 \u043e\u0431\u044b\u0447\u043d\u044b\u0445 \u043a\u0432\u0435\u0440\u0438, \u0430 \u043d\u0430 \u0441\u043e\u043a\u0435\u0442 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u0442\u044c \u0442\u043e, \u0447\u0442\u043e \u0436\u0438\u0432\u0451\u0442 \u043f\u043e\u0441\u043b\u0435 \u0433\u0438\u0434\u0440\u0430\u0446\u0438\u0438. \u0412\u0442\u043e\u0440\u0430\u044f: \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u044d\u0442\u043e \u043f\u0430\u043c\u044f\u0442\u044c \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435, \u0438 \u043d\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430\u0445 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f backplane.<\/p>\n<h2>\u0427\u0442\u043e \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0437\u0430 \u043a\u0430\u0434\u0440\u043e\u043c<\/h2>\n<p>\u0427\u0442\u043e\u0431\u044b \u0441\u0442\u0430\u0442\u044c\u044f \u043d\u0435 \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u043b\u0430\u0441\u044c \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044e, \u044f \u043e\u0441\u0442\u0430\u0432\u0438\u043b \u0437\u0430 \u0441\u043a\u043e\u0431\u043a\u0430\u043c\u0438 \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043c\u043d\u043e\u0433\u043e.<\/p>\n<p>Presence, \u0442\u043e \u0435\u0441\u0442\u044c \u00ab\u043a\u0442\u043e \u0441\u0435\u0439\u0447\u0430\u0441 \u0432 \u043a\u043e\u043c\u043d\u0430\u0442\u0435\u00bb, \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u0438\u0437 \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0439 (<code>space.memberships.server.list<\/code>) \u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432\u0445\u043e\u0434\u0430 \u0438 \u0432\u044b\u0445\u043e\u0434\u0430. \u0413\u043e\u0442\u043e\u0432\u044b\u0439 \u0440\u0435\u0446\u0435\u043f\u0442 \u0435\u0441\u0442\u044c \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 \u0438 \u0432 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p>\u0410\u0434\u043c\u0438\u043d-\u043a\u043e\u043c\u0430\u043d\u0434\u044b: <code>kill<\/code> \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f, <code>space.kick<\/code> \u0432\u044b\u043a\u0438\u0434\u044b\u0432\u0430\u0435\u0442 \u0438\u0437 \u043a\u043e\u043c\u043d\u0430\u0442, <code>space.enroll<\/code> \u043d\u0430\u043e\u0431\u043e\u0440\u043e\u0442 \u0437\u0430\u0447\u0438\u0441\u043b\u044f\u0435\u0442, <code>refresh<\/code> \u043f\u0435\u0440\u0435\u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 identity \u0431\u0435\u0437 \u0440\u0430\u0437\u0440\u044b\u0432\u0430 \u0441\u043e\u043a\u0435\u0442\u0430, <code>amendIdentity<\/code> \u043f\u0440\u0430\u0432\u0438\u0442 \u0435\u0451 \u043d\u0430 \u043c\u0435\u0441\u0442\u0435. \u0412\u0441\u0451 \u044d\u0442\u043e \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0442\u043e\u0442 \u0436\u0435 \u0441\u043b\u043e\u0432\u0430\u0440\u044c \u0446\u0435\u043b\u0435\u0439, \u0447\u0442\u043e \u0438 \u043f\u0443\u0448\u0438.<\/p>\n<p>\u041d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0432: backplane \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0432 \u043a\u043e\u043d\u0444\u0438\u0433 \u0434\u0432\u0438\u0436\u043a\u0430. \u0414\u043b\u044f Redis \u044d\u0442\u043e \u043e\u0434\u043d\u0430 \u0441\u0442\u0440\u043e\u043a\u0430 \u0441 URL. \u0415\u0441\u0442\u044c \u0433\u043e\u0442\u043e\u0432\u044b\u0435 \u0430\u0434\u0430\u043f\u0442\u0435\u0440\u044b \u0434\u043b\u044f Postgres (\u0448\u0438\u043d\u0430 \u043d\u0430 LISTEN\/NOTIFY, \u0435\u0441\u043b\u0438 Redis \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043d\u0435\u0442), ioredis \u0438 node-redis. \u041b\u0438\u0431\u043e \u0441\u0432\u043e\u0439 \u043e\u0431\u044a\u0435\u043a\u0442 \u0438\u0437 \u043f\u044f\u0442\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0439, \u0435\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0447\u0442\u043e-\u0442\u043e \u0441\u0432\u043e\u0451.<\/p>\n<p>\u0423 <code>resumable<\/code> \u0438\u0437 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 3.2 \u0435\u0441\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u044f \u043d\u0435 \u043a\u0430\u0441\u0430\u043b\u0441\u044f: \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0436\u0438\u0432\u0451\u0442 \u0437\u0430\u043f\u0438\u0441\u044c \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f, \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0444\u0440\u0435\u0439\u043c\u043e\u0432 \u0438 \u0431\u0430\u0439\u0442\u043e\u0432 \u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0432 \u0431\u0443\u0444\u0435\u0440\u0435, \u043c\u043e\u0436\u043d\u043e \u043b\u0438 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c\u0443 \u0441\u043f\u0435\u0439\u0441\u0443 \u043e\u0442\u043a\u0430\u0437\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f, \u0438 \u0447\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u0441 \u0447\u0430\u0441\u0442\u0438\u0447\u043d\u043e \u043f\u043e\u043a\u0440\u044b\u0442\u044b\u043c \u0440\u0430\u0437\u0440\u044b\u0432\u043e\u043c (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0434\u043b\u044f \u043f\u043e\u0442\u043e\u043a\u0430 \u043f\u0430\u0442\u0447\u0435\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0445\u0432\u043e\u0441\u0442 \u0431\u0435\u0437 \u043d\u0430\u0447\u0430\u043b\u0430 \u0431\u0435\u0441\u043f\u043e\u043b\u0435\u0437\u0435\u043d, \u0438 \u0435\u0433\u043e \u043b\u0443\u0447\u0448\u0435 \u043d\u0435 \u043e\u0442\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u043e\u0432\u0441\u0435).<\/p>\n<p>\u0415\u0449\u0451 \u0435\u0441\u0442\u044c \u0441\u0431\u043e\u0440 \u043e\u0442\u0432\u0435\u0442\u043e\u0432 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 \u043d\u0430 \u043f\u0443\u0448, \u0440\u0430\u043d\u043d\u0438\u0439 \u043e\u0442\u0432\u0435\u0442 \u0438\u0437 <code>.serverReply<\/code>, \u0441\u0442\u0440\u0438\u043c\u0438\u043d\u0433 \u0442\u043e\u043a\u0435\u043d\u043e\u0432 LLM \u0447\u0435\u0440\u0435\u0437 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0438\u0439 \u0445\u044d\u043d\u0434\u043b\u0435\u0440, \u0433\u0432\u0430\u0440\u0434\u044b \u0438 \u0430\u0443\u0434\u0438\u0442 \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435, \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 origin \u0434\u043b\u044f \u0445\u0435\u043d\u0434\u0448\u0435\u0439\u043a\u0430, \u043c\u0435\u0442\u0440\u0438\u043a\u0438. \u0412\u0441\u0451 \u044d\u0442\u043e \u043e\u043f\u0438\u0441\u0430\u043d\u043e \u0432 <a href=\"https:\/\/1gr14.dev\/ru\/point0\/latest\/socket\" rel=\"noopener noreferrer nofollow\">\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 \u043f\u043e \u0441\u043e\u043a\u0435\u0442\u0430\u043c<\/a>.<\/p>\n<h2>\u0418\u0442\u043e\u0433<\/h2>\n<p>\u0420\u0435\u0430\u043b\u0442\u0430\u0439\u043c \u0432 Point0 \u044d\u0442\u043e \u043d\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u043f\u043e\u0434\u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0441\u043e \u0441\u0432\u043e\u0435\u0439 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0435\u0439, \u0441\u0432\u043e\u0438\u043c\u0438 \u0442\u0438\u043f\u0430\u043c\u0438 \u0438 \u0441\u0432\u043e\u0438\u043c \u0440\u043e\u0443\u0442\u0438\u043d\u0433\u043e\u043c. \u042d\u0442\u043e \u0447\u0435\u0442\u044b\u0440\u0435 \u043d\u043e\u0432\u044b\u0445 \u0432\u0438\u0434\u0430 \u043f\u043e\u0438\u043d\u0442\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0436\u0438\u0432\u0443\u0442 \u0440\u044f\u0434\u043e\u043c \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u043c\u0438 \u0438 \u043a\u0432\u0435\u0440\u0438, \u0447\u0438\u0442\u0430\u044e\u0442 \u0442\u0443 \u0436\u0435 identity, \u0447\u0442\u043e \u0438 \u0432\u0435\u0441\u044c \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0439 \u0441\u0435\u0440\u0432\u0435\u0440, \u0438 \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0442\u0430\u043a \u0436\u0435, \u043a\u0430\u043a \u0432\u0441\u0451 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435. \u0424\u0430\u0439\u043b \u0444\u0438\u0447\u0438 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f \u0441\u0432\u0435\u0440\u0445\u0443 \u0432\u043d\u0438\u0437: \u043a\u043e\u043c\u043d\u0430\u0442\u0430, \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u044b, \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442.<\/p>\n<p>\u0421\u043e\u043a\u0435\u0442\u044b \u044d\u0442\u043e \u0441\u0430\u043c\u0430\u044f \u043c\u043e\u043b\u043e\u0434\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430, \u0438 \u044f \u044d\u0442\u043e \u0447\u0435\u0441\u0442\u043d\u043e \u043f\u0438\u0448\u0443 \u0438 \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438: \u0434\u0438\u0437\u0430\u0439\u043d API \u0443\u0441\u0442\u043e\u044f\u043b\u0441\u044f \u0438 \u043c\u0435\u043d\u044f\u0442\u044c \u044f \u0435\u0433\u043e \u043d\u0435 \u0441\u043e\u0431\u0438\u0440\u0430\u044e\u0441\u044c, \u0430 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u0435\u0449\u0451 \u0436\u0434\u0451\u0442 \u0440\u0435\u0444\u0430\u043a\u0442\u043e\u0440\u0438\u043d\u0433. \u0415\u0441\u043b\u0438 \u0447\u0442\u043e-\u0442\u043e \u0441\u043b\u043e\u043c\u0430\u0435\u0442\u0441\u044f, \u0437\u0430\u0432\u043e\u0434\u0438\u0442\u0435 <a href=\"https:\/\/github.com\/1gr14\/point0\/issues\" rel=\"noopener noreferrer nofollow\">\u0438\u0448\u044c\u044e<\/a>.<\/p>\n<p>\u0420\u0430\u0431\u043e\u0447\u0435\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441\u043e \u0432\u0441\u0435\u043c \u044d\u0442\u0438\u043c: <a href=\"https:\/\/github.com\/1gr14\/point0\/tree\/main\/examples\/socket\" rel=\"noopener noreferrer nofollow\">examples\/socket<\/a>, \u0433\u0434\u0435 \u0447\u0430\u0442, \u0434\u043e\u0441\u043a\u0430, \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u0438 presence \u043b\u0435\u0436\u0430\u0442 \u043a\u0430\u0436\u0434\u044b\u0439 \u0432 \u0441\u0432\u043e\u0451\u043c \u0444\u0430\u0439\u043b\u0435. \u0410 \u0435\u0441\u043b\u0438 \u0432\u044b \u043f\u0440\u0438\u0448\u043b\u0438 \u0441\u044e\u0434\u0430, \u043d\u0435 \u0437\u043d\u0430\u044f \u043f\u0440\u043e \u0441\u0430\u043c \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a, \u043d\u0430\u0447\u043d\u0438\u0442\u0435 \u0441 \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0438 \u043e \u043d\u0451\u043c: <a href=\"https:\/\/habr.com\/ru\/articles\/1054310\/\" rel=\"noopener noreferrer nofollow\">https:\/\/habr.com\/ru\/articles\/1054310\/<\/a><\/p>\n<p>P.S. \u042f \u0434\u043e\u0434\u0435\u043b\u0430\u043b \u0432\u043e \u0432\u0441\u0451\u043c \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0435 \u0432\u0441\u0451 \u0447\u0442\u043e \u0445\u043e\u0442\u0435\u043b \u0434\u043e\u0434\u0435\u043b\u0430\u0442\u044c. \u0420\u0430\u0434\u0438 \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0434\u043e\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a \u044f \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e 4 \u043c\u0435\u0441\u044f\u0446\u0430 \u043d\u0430\u0437\u0430\u0434 \u0443\u0432\u043e\u043b\u0438\u043b\u0441\u044f \u0441 \u0440\u0430\u0431\u043e\u0442\u044b. \u042f \u0432 \u0432\u043e\u0441\u0442\u043e\u0440\u0433\u0435 \u043e\u0442 \u044d\u0442\u043e\u0433\u043e \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430, \u0438 \u0434\u0443\u043c\u0430\u043b, \u0447\u0442\u043e \u043a\u043e\u0433\u0434\u0430 \u044f \u0435\u0433\u043e \u0432\u044b\u043b\u043e\u0436\u0443, \u0432\u0435\u0441\u044c \u043c\u0438\u0440 \u0442\u043e\u0436\u0435 \u0431\u0443\u0434\u0435\u0442 \u0432 \u0432\u043e\u0441\u0442\u043e\u0440\u0433\u0435, \u0438 \u043a\u0430\u043a\u0438\u043c-\u0442\u043e \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u044f \u0441\u043c\u043e\u0433\u0443 \u0437\u0430\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043d\u0430 \u043a\u0430\u043a\u0438\u0445-\u0442\u043e \u0441\u043c\u0435\u0436\u043d\u044b\u0445 \u0432\u0435\u0449\u0430\u0445: \u043f\u0440\u043e\u0434\u0430\u0436\u0435 \u043c\u043e\u0435\u0433\u043e \u0431\u043e\u0439\u043b\u0435\u0440\u043f\u043b\u0435\u0439\u0442\u0430, \u0438\u043b\u0438 \u0443\u0447\u0430\u0441\u0442\u0438\u044f \u0432 \u0437\u0430\u043a\u0440\u044b\u0442\u043e\u0439 \u0433\u0440\u0443\u043f\u043f\u0435. \u041d\u043e \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u0431\u043e\u043b\u0435\u0435 \u0441\u0443\u0440\u043e\u0432\u043e\u0439, \u0438 \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u0447\u0442\u043e \u0447\u0442\u043e\u0431\u044b \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a \u0437\u0430\u043d\u044f\u043b \u0441\u0432\u043e\u044e \u043d\u0438\u0448\u0443, \u043d\u0443\u0436\u043d\u043e \u043f\u043b\u0430\u043d\u043e\u043c\u0435\u0440\u043d\u043e, \u0434\u043e\u043b\u0433\u043e, \u0438 \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e \u043e \u043d\u0451\u043c \u043f\u0438\u0441\u0430\u0442\u044c, \u0434\u0435\u043b\u0430\u0442\u044c \u043f\u0440\u0438\u043c\u0435\u0440\u044b \u0438 \u043f\u0440\u043e\u0447\u0435\u0435. \u0422\u043e \u0435\u0441\u0442\u044c \u0430\u043a\u0442\u0438\u0432\u043d\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u043f\u043e\u0437\u0430\u0434\u0438, \u043d\u043e \u043e\u0441\u0442\u0430\u043b\u0430\u0441\u044c \u0434\u043e\u043b\u0433\u0430\u044f \u0438 \u0440\u0430\u0437\u043c\u0435\u0440\u0435\u043d\u043d\u0430\u044f \u0440\u0430\u0431\u043e\u0442\u0430 \u043f\u043e \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u0438\u0437\u0430\u0446\u0438\u0438. \u0412 \u043e\u0431\u0449\u0435\u043c, \u0440\u0430\u0431\u043e\u0442\u044b \u0442\u043e \u0443 \u043c\u0435\u043d\u044f \u0443\u0436\u0435 4 \u043c\u0435\u0441\u044f\u0446\u0430 \u043d\u0435\u0442, \u0438 \u044f \u0435\u0451 \u0438 \u043d\u0435 \u0438\u0441\u043a\u0430\u043b, \u0430 \u0442\u0435\u043f\u0435\u0440\u044c \u0438\u0449\u0443. \u0415\u0441\u043b\u0438 \u0432\u0434\u0440\u0443\u0433 \u0432\u0430\u043c \u043d\u0443\u0436\u0435\u043d \u0444\u0443\u043b\u0441\u0442\u0435\u043a \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0432\u0430\u0448\u0438\u0445 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432, \u0431\u0443\u0434\u0443 \u0440\u0430\u0434 \u043f\u043e\u0441\u043e\u0442\u0440\u0443\u0434\u043d\u0438\u0447\u0430\u0442\u044c<\/p>\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\/1069716\/\">https:\/\/habr.com\/ru\/articles\/1069716\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u0411\u044b\u0432\u0430\u0435\u0442 \u0442\u0430\u043a: \u0435\u0441\u0442\u044c \u0444\u0443\u043b\u0441\u0442\u0435\u043a \u043f\u0440\u043e\u0435\u043a\u0442, \u0438 \u0432 \u043d\u0451\u043c \u0432\u0441\u0451 \u0445\u043e\u0440\u043e\u0448\u043e. \u041e\u0442\u043a\u0443\u0434\u0430-\u0442\u043e \u0435\u0441\u0442\u044c \u0441\u043a\u0432\u043e\u0437\u043d\u044b\u0435 \u0442\u0438\u043f\u044b (tRPC, \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0438\u0437 OpenAPI), \u0435\u0441\u0442\u044c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f, \u0435\u0441\u0442\u044c \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b. \u0410 \u043f\u043e\u0442\u043e\u043c \u0432\u044b \u0440\u0435\u0448\u0430\u0435\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0440\u0435\u0430\u043b\u0442\u0430\u0439\u043c: \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u043e \u043d\u043e\u0432\u043e\u043c \u043f\u043e\u0441\u0442\u0435 \u0432 \u043b\u0435\u043d\u0442\u0435, \u0447\u0430\u0442 \u043c\u0435\u0436\u0434\u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c\u0438, \u0438\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u0443\u044e \u0434\u043e\u0441\u043a\u0443. \u0418 \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0446\u0435\u043b\u044b\u0439 \u043d\u043e\u0432\u044b\u0439 \u0441\u043b\u043e\u0439 \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u0439, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043d\u0430\u0434\u043e \u0437\u0430\u043d\u043e\u0432\u043e \u0438\u0437\u043e\u0431\u0440\u0435\u0441\u0442\u0438 \u0432\u0441\u0451, \u0447\u0442\u043e \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0443\u0436\u0435 \u0435\u0441\u0442\u044c, \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 \u043d\u043e\u0432\u044b\u0439 \u043b\u0430\u0434. \u0418 \u0434\u0430\u043b\u044c\u0448\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c \u0434\u0432\u0435 \u0440\u0430\u0437\u043d\u044b\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b.\u0412 \u0441\u0432\u043e\u0451\u043c \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0435 Point0 \u044f \u0434\u043e\u0431\u0430\u0432\u0438\u043b \u0447\u0435\u0442\u044b\u0440\u0435 \u043d\u043e\u0432\u044b\u0445 \u0440\u0435\u0430\u043b\u0442\u0430\u0439\u043c-\u043f\u043e\u0438\u043d\u0442\u0430 (\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u043d\u044b\u0435 \u0435\u0434\u0438\u043d\u0438\u0446\u044b \u043d\u0430\u0440\u0430\u0432\u043d\u0435 \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u043c\u0438, \u043b\u044d\u0439\u0430\u0443\u0442\u0430\u043c\u0438, \u043a\u0432\u0435\u0440\u0438, \u043c\u0443\u0442\u0430\u0446\u0438\u044f\u043c\u0438): \u043a\u0430\u043d\u0430\u043b, \u0441\u043f\u0435\u0439\u0441, \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0438\u0439 \u0445\u044d\u043d\u0434\u043b\u0435\u0440, \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0439 \u0445\u044d\u043d\u0434\u043b\u0435\u0440. \u041d\u0430 \u043d\u0438\u0445 \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043b\u044e\u0431\u0430\u044f \u0440\u0435\u0430\u043b\u0442\u0430\u0439\u043c-\u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c, \u043a\u043e\u0434\u0430 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u043c\u0430\u043b\u043e, \u0438 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f \u043e\u043d \u0438\u043d\u0442\u0443\u0438\u0442\u0438\u0432\u043d\u043e. \u042d\u0442\u0438 \u043f\u043e\u0438\u043d\u0442\u044b \u043d\u0435\u0441\u0443\u0442 \u0442\u0435 \u0436\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430, \u0447\u0442\u043e \u0438 \u0432\u0441\u0435 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435:\u043a\u043e\u0434 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0438 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0436\u0438\u0432\u0443\u0442 \u0432 \u043e\u0434\u043d\u043e\u043c \u0444\u0430\u0439\u043b\u0435, \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440 \u0432\u044b\u0440\u0435\u0437\u0430\u0435\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0438\u0439 \u043a\u043e\u0434 \u0438\u0437 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u043e\u0439 \u0441\u0431\u043e\u0440\u043a\u0438, \u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0439 \u0438\u0437 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0439\u0442\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u044f \u0441\u043a\u0432\u043e\u0437\u043d\u0430\u044f \u0438 \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u0438\u0437 \u0434\u0436\u0435\u043d\u0435\u0440\u0438\u043a\u043e\u0432 \u0441\u0430\u043c\u043e\u0433\u043e \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430, \u0431\u0435\u0437 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0442\u0438\u043f\u043e\u0432\u041f\u043e\u0434 \u043a\u0430\u0442\u043e\u043c \u043f\u043e\u043a\u0430\u0436\u0443 \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0430\u0445, \u043a\u0430\u043a \u044d\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u0438 \u043e\u0431\u044a\u044f\u0441\u043d\u044e \u0441\u0443\u0442\u044c \u043f\u0430\u0440\u0430\u0434\u0438\u0433\u043c\u044b, \u0447\u0442\u043e\u0431\u044b \u0432\u044b \u043c\u043e\u0433\u043b\u0438 \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u043b\u044e\u0431\u043e\u0435 \u0440\u0435\u0430\u043b\u0442\u0430\u0439\u043c-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435.\u0421\u0441\u044b\u043b\u043a\u0438:\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f \u043f\u043e \u0441\u043e\u043a\u0435\u0442\u0430\u043c: https:\/\/1gr14.dev\/ru\/point0\/latest\/socket\u041f\u0440\u0438\u043c\u0435\u0440 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f: https:\/\/github.com\/1gr14\/point0\/tree\/main\/examples\/socket\u0421\u0442\u0430\u0442\u044c\u044f \u043e \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0435 \u043d\u0430 \u0425\u0430\u0431\u0440\u0435: https:\/\/habr.com\/ru\/articles\/1054310\/\u0412\u0441\u044f \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f: https:\/\/1gr14.dev\/point0\/latest\/overview\u041f\u0430\u0440\u0430\u0434\u0438\u0433\u043c\u0430\u041f\u0440\u0438\u043c\u0435\u0440\u044b \u0441\u0434\u0435\u043b\u0430\u044e\u0442 \u043f\u0430\u0440\u0430\u0434\u0438\u0433\u043c\u0443 \u043f\u043e\u043d\u044f\u0442\u043d\u043e\u0439, \u043d\u043e \u043f\u043e\u043a\u0430 \u0432\u044b \u0438\u0445 \u043d\u0435 \u0443\u0432\u0438\u0434\u0435\u043b\u0438, \u043e\u043d\u0430 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f \u0442\u0440\u0443\u0434\u043d\u043e\u0432\u0430\u0442\u043e. \u0410 \u043d\u0435 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0432 \u0435\u0451, \u0431\u0443\u0434\u0435\u0442 \u043d\u0435\u043f\u043e\u043d\u044f\u0442\u043d\u043e, \u043e \u0447\u0451\u043c \u0433\u043e\u0432\u043e\u0440\u044f\u0442 \u043f\u0440\u0438\u043c\u0435\u0440\u044b. \u041f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u0431\u0435\u0433\u043b\u043e \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f, \u0430 \u043f\u043e\u0441\u043b\u0435 \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432 \u0432\u0435\u0440\u043d\u0443\u0442\u044c\u0441\u044f \u0438 \u043f\u0435\u0440\u0435\u0447\u0438\u0442\u0430\u0442\u044c.\u041e\u0434\u0438\u043d WebSocket \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435. \u0412\u0441\u0451 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u044d\u0442\u043e \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u0438 \u043d\u0430\u0434 \u043d\u0438\u043c. \u0421\u043b\u043e\u0438 \u0441\u0432\u0435\u0440\u0445\u0443 \u0432\u043d\u0438\u0437.\u041a\u0430\u043d\u0430\u043b \u044d\u0442\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435. \u041a\u043b\u0438\u0435\u043d\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0439 .connector, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0438\u0434\u0435\u043d\u0442\u0438\u0447\u043d\u043e\u0441\u0442\u044c \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f identity. \u041e\u043d\u0430 \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435, \u043a\u043b\u0438\u0435\u043d\u0442\u0443 \u043d\u0435 \u0432\u0438\u0434\u043d\u0430 \u0438 \u043f\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0432 \u043a\u0430\u0436\u0434\u043e\u043c \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0438 \u043d\u0430 \u043a\u0430\u043d\u0430\u043b\u0435 (\u0435\u0451 \u0447\u0438\u0442\u0430\u044e\u0442 \u0432\u0441\u0435 \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u044b, joiner\u2019\u044b \u0438 \u0441\u0435\u043b\u0435\u043a\u0446\u0438\u0438). \u041a\u0430\u0436\u0434\u043e\u0435 \u0436\u0438\u0432\u043e\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u044d\u0442\u043e connectionId. \u0421\u0430\u043c \u043a\u043e\u043d\u043d\u0435\u043a\u0442 \u044d\u0442\u043e \u043e\u0431\u044b\u0447\u043d\u044b\u0439 HTTP-\u0437\u0430\u043f\u0440\u043e\u0441, \u0442\u0430\u043a \u0447\u0442\u043e \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438, \u043a\u0443\u043a\u0438 \u0438 middleware \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u043a\u0430\u043a \u0432\u0435\u0437\u0434\u0435. \u041f\u043e \u0441\u043e\u043a\u0435\u0442\u0443 \u0438\u0434\u0443\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u043e\u0441\u043b\u0435 \u043d\u0435\u0433\u043e.\u0421\u043f\u0435\u0439\u0441 \u0432\u044b\u0440\u0430\u0441\u0442\u0430\u0435\u0442 \u0438\u0437 \u043a\u0430\u043d\u0430\u043b\u0430. \u042d\u0442\u043e \u0441\u0435\u043c\u0435\u0439\u0441\u0442\u0432\u043e \u043a\u043e\u043c\u043d\u0430\u0442 \u043e\u0434\u043d\u043e\u0439 \u0444\u043e\u0440\u043c\u044b. \u041a\u043b\u0438\u0435\u043d\u0442 \u0432\u0445\u043e\u0434\u0438\u0442 \u0447\u0435\u0440\u0435\u0437 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0439 .joiner, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u0435\u0448\u0430\u0435\u0442, \u0432 \u043a\u0430\u043a\u0438\u0435 \u043a\u043e\u043c\u043d\u0430\u0442\u044b \u043e\u043d \u043f\u043e\u043f\u0430\u0434\u0451\u0442. \u041b\u0438\u0431\u043e \u0441\u0435\u0440\u0432\u0435\u0440 \u0441\u0430\u043c \u0437\u0430\u0447\u0438\u0441\u043b\u044f\u0435\u0442 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u0432 \u043a\u043e\u043c\u043d\u0430\u0442\u044b, \u0431\u0435\u0437 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0430, \u0447\u0435\u0440\u0435\u0437 .enroller. \u041a\u043e\u043c\u043d\u0430\u0442\u0430 \u044d\u0442\u043e \u0435\u0434\u0438\u043d\u0438\u0446\u0430 \u0430\u0434\u0440\u0435\u0441\u0430\u0446\u0438\u0438, pub\/sub-\u0442\u043e\u043f\u0438\u043a, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0443\u0448\u0438 \u0446\u0435\u043b\u044f\u0442\u0441\u044f \u043f\u043e \u0438\u043c\u0435\u043d\u0438.\u0421\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0439 \u0445\u044d\u043d\u0434\u043b\u0435\u0440 \u044d\u0442\u043e \u0432\u044b\u0437\u043e\u0432 \u043a\u043b\u0438\u0435\u043d\u0442 \u2192 \u0441\u0435\u0440\u0432\u0435\u0440. \u041a\u043b\u0438\u0435\u043d\u0442 \u0448\u043b\u0451\u0442 \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0447\u0435\u0440\u0435\u0437 sendToServer, \u0441\u0435\u0440\u0432\u0435\u0440 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0432 .serverReply, \u0430 \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435 \u0432\u0441\u0451 \u044d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u044b\u0439 \u043a\u0432\u0435\u0440\u0438 \u0438\u043b\u0438 \u043c\u0443\u0442\u0430\u0446\u0438\u044f.\u041a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0438\u0439 \u0445\u044d\u043d\u0434\u043b\u0435\u0440 \u044d\u0442\u043e \u043f\u0443\u0448 \u0441\u0435\u0440\u0432\u0435\u0440 \u2192 \u043a\u043b\u0438\u0435\u043d\u0442. \u0421\u0435\u0440\u0432\u0435\u0440 \u0448\u043b\u0451\u0442 \u0447\u0435\u0440\u0435\u0437 sendToClient \u0432 \u0446\u0435\u043b\u044c (\u043a\u043e\u043c\u043d\u0430\u0442\u0443, \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435, \u0441\u0435\u043b\u0435\u043a\u0446\u0438\u044e), \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u044e\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0438 \u043f\u0440\u0438 \u0436\u0435\u043b\u0430\u043d\u0438\u0438 \u043e\u0442\u0432\u0435\u0447\u0430\u044e\u0442 \u043e\u0431\u0440\u0430\u0442\u043d\u043e.\u041d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435 \u0432\u0441\u0451 \u0434\u0435\u0440\u0436\u0438\u0442\u0441\u044f \u0445\u0443\u043a\u0430\u043c\u0438 \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430\u043c\u0438 (useConnection, &lt;Connection&gt;, useMembership, .with(channel)), \u0441\u043e\u043a\u0435\u0442 \u043f\u0435\u0440\u0435\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0441\u0430\u043c, \u0430 resumable-\u043a\u0430\u043d\u0430\u043b \u0434\u0435\u043b\u0430\u0435\u0442 \u0440\u0435\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u044b \u0434\u0435\u0448\u0451\u0432\u044b\u043c\u0438.\u041d\u0430 \u043e\u0434\u043d\u043e\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0432\u0441\u0451 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u043f\u0430\u043c\u044f\u0442\u0438. \u0414\u043b\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0432 \u043a \u0434\u0432\u0438\u0436\u043a\u0443 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f backplane: Redis \u043f\u043e URL, Postgres, \u0432\u0430\u0448 \u0443\u0436\u0435 \u043f\u043e\u0434\u043d\u044f\u0442\u044b\u0439 Redis-\u043a\u043b\u0438\u0435\u043d\u0442 \u0438\u043b\u0438 \u043b\u044e\u0431\u043e\u0439 KV \u0441 pub\/sub. \u0421\u0435\u0440\u0432\u0435\u0440 \u0442\u0430\u043a\u0436\u0435 \u0443\u043c\u0435\u0435\u0442 \u0430\u0434\u043c\u0438\u043d-\u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0438 \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f (kick, kill, refresh, \u043a\u0442\u043e \u0441\u0435\u0439\u0447\u0430\u0441 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0451\u043d) \u0438 \u043e\u0442\u0434\u0430\u0451\u0442 \u043c\u0435\u0442\u0440\u0438\u043a\u0438.\u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430\u0414\u0430\u043b\u044c\u0448\u0435 \u0432\u043e \u0432\u0441\u0435\u0445 \u043f\u0440\u0438\u043c\u0435\u0440\u0430\u0445 \u0441\u0447\u0438\u0442\u0430\u0435\u043c, \u0447\u0442\u043e \u0443 \u0432\u0430\u0441 \u0443\u0436\u0435 \u0435\u0441\u0442\u044c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u0438 Prisma. \u041e\u0442\u043a\u0443\u0434\u0430 \u0431\u0435\u0440\u0451\u0442\u0441\u044f prisma, \u043a\u0430\u043a \u0443\u0441\u0442\u0440\u043e\u0435\u043d getUserFromRequest, \u0447\u0442\u043e \u0437\u0430 AppError, \u0434\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432 \u043d\u0435\u0432\u0430\u0436\u043d\u043e: \u044d\u0442\u043e \u0432\u0430\u0448 \u043e\u0431\u044b\u0447\u043d\u044b\u0439 \u043a\u043e\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u044b\u043b \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0434\u043e \u0441\u043e\u043a\u0435\u0442\u043e\u0432.\u0421\u043e\u043a\u0435\u0442\u044b \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e, \u0432\u043a\u043b\u044e\u0447\u0430\u044e\u0442\u0441\u044f \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u043e\u0439 \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0435 \u0434\u0432\u0438\u0436\u043a\u0430. \u041f\u043e\u043a\u0430 \u043e\u043d\u0438 \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u044b, \u0438\u0445 \u043a\u043e\u0434 \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0435 \u0435\u0434\u0435\u0442 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440.\/\/ engine.tsexport const engine = Engine.create({  server: { socket: true },})\u041a\u0430\u043d\u0430\u043b \u043e\u0431\u044a\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u043d\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435.\/\/ lib\/channel.tsimport { root } from &#8216;@\/lib\/root&#8217;import { getUserFromRequest } from &#8216;@\/lib\/auth&#8217; \/\/ \u0432\u0430\u0448\u0430 \u043e\u0431\u044b\u0447\u043d\u0430\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f, \u0442\u0430 \u0436\u0435, \u0447\u0442\u043e \u0438 \u0432 HTTPexport const appChannel = root.lets  .channel()  .connector(async ({ request }) =&gt; {    \/\/ \u043a\u043e\u043d\u043d\u0435\u043a\u0442 \u044d\u0442\u043e \u043e\u0431\u044b\u0447\u043d\u044b\u0439 HTTP-\u0437\u0430\u043f\u0440\u043e\u0441: \u043a\u0443\u043a\u0438, \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438, middleware \u043d\u0430 \u043c\u0435\u0441\u0442\u0435    const user = await getUserFromRequest(request)    \/\/ \u0442\u043e, \u0447\u0442\u043e \u0432\u0435\u0440\u043d\u0443\u043b \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440, \u0438 \u0435\u0441\u0442\u044c identity \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f. \u041e\u043d\u0430 \u043e\u0441\u0442\u0430\u043d\u0435\u0442\u0441\u044f \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435,    \/\/ \u043a\u043b\u0438\u0435\u043d\u0442\u0443 \u043d\u0435 \u0432\u0438\u0434\u043d\u0430, \u0438 \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u0432\u043e \u0432\u0441\u0435\u0445 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0438\u0445 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f\u0445 \u043d\u0430 \u044d\u0442\u043e\u043c \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0438.    \/\/ \u0424\u043e\u0440\u043c\u0430 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u0430\u044f: \u043a\u043b\u0430\u0434\u0438\u0442\u0435 \u0447\u0442\u043e \u0443\u0433\u043e\u0434\u043d\u043e \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u0435\u043c\u043e\u0435, \u0445\u043e\u0442\u044c \u0440\u043e\u043b\u044c, \u0445\u043e\u0442\u044c \u0442\u0430\u0440\u0438\u0444,    \/\/ \u0445\u043e\u0442\u044c \u044f\u0437\u044b\u043a \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430. \u0422\u0438\u043f \u043d\u0438\u0433\u0434\u0435 \u043d\u0435 \u043e\u0431\u044a\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f, \u043e\u043d \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u043e\u0442\u0441\u044e\u0434\u0430    return user      ? { authorized: true as const, id: user.id, name: user.name }      : { authorized: false as const, id: null, name: null }  })  .channel()as const \u043d\u0430 \u0444\u043b\u0430\u0433\u0435 \u0442\u0443\u0442 \u043d\u0435 \u0443\u043a\u0440\u0430\u0448\u0435\u043d\u0438\u0435: \u0431\u0435\u0437 \u043d\u0435\u0433\u043e TypeScript \u0440\u0430\u0441\u0448\u0438\u0440\u0438\u0442 true \u0434\u043e boolean, \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u043d\u0435\u0442 \u0431\u044b\u0442\u044c \u0440\u0430\u0437\u043c\u0435\u0447\u0435\u043d\u043d\u044b\u043c, \u0438 identity.id \u043f\u043e\u0441\u043b\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043d\u0435 \u0441\u0443\u0437\u0438\u0442\u0441\u044f \u0434\u043e \u0441\u0442\u0440\u043e\u043a\u0438. \u0411\u043e\u043b\u044c\u0448\u0435 \u043d\u0438\u0447\u0435\u0433\u043e \u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0435 \u043d\u0443\u0436\u043d\u043e, \u0442\u0438\u043f identity \u0440\u0430\u0437\u044a\u0435\u0434\u0435\u0442\u0441\u044f \u043f\u043e \u0432\u0441\u0435\u043c \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430\u043c, joiner\u2019\u0430\u043c \u0438 \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u0430\u043c \u0441\u0430\u043c.\u041a\u0430\u043d\u0430\u043b \u0434\u0435\u0440\u0436\u0438\u043c \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u043c \u0434\u043b\u044f \u0432\u0441\u0435\u0445, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u0433\u043e\u0441\u0442\u0435\u0439. \u0421\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043c\u043e\u0436\u043d\u043e \u0432\u0441\u0435\u043c, \u0430 \u043a\u0430\u0436\u0434\u043e\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0433\u0435\u0439\u0442\u0438\u0442\u0441\u044f \u0442\u0430\u043c, \u0433\u0434\u0435 \u043e\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f: \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435, \u0433\u0434\u0435 identity \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0432\u0435\u0440\u044f\u0442\u044c. \u0415\u0441\u043b\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0446\u0435\u043b\u0438\u043a\u043e\u043c \u0437\u0430\u043a\u0440\u044b\u0442\u043e\u0435, \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440 \u043c\u043e\u0436\u0435\u0442 \u0431\u0440\u043e\u0441\u0438\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0443, \u0438 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u0432\u043e\u043e\u0431\u0449\u0435.\u0421\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0434\u0435\u0440\u0436\u0438\u0442 \u043a\u043e\u0440\u0435\u043d\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u041e\u0434\u043d\u043e \u043d\u0430 \u0432\u043a\u043b\u0430\u0434\u043a\u0443, \u0435\u0433\u043e \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 \u0432\u0441\u0435 \u0441\u043f\u0435\u0439\u0441\u044b \u043d\u0430 \u0432\u0441\u0435\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445.\/\/ app.client.tsx&lt;appChannel.Connection&gt;  &lt;RouterRoutes \/&gt;&lt;\/appChannel.Connection&gt;\u0412\u0441\u0451. \u0414\u0430\u043b\u044c\u0448\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0444\u0438\u0447\u0438.\u041f\u0440\u0438\u043c\u0435\u0440 1. \u0423\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u043c \u043e \u043d\u043e\u0432\u043e\u043c \u043f\u043e\u0441\u0442\u0435 \u0432 \u043b\u0435\u043d\u0442\u0435\u0414\u043e\u043f\u0443\u0441\u0442\u0438\u043c, \u0443 \u0432\u0430\u0441 \u0441\u0432\u043e\u0439 \u043c\u0438\u043d\u0438-\u0425\u0430\u0431\u0440. \u041b\u044e\u0434\u0438 \u043f\u0438\u0448\u0443\u0442 \u043f\u043e\u0441\u0442\u044b, \u0438 \u0432\u044b \u0445\u043e\u0442\u0438\u0442\u0435, \u0447\u0442\u043e\u0431\u044b \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u043e\u043d\u043b\u0430\u0439\u043d \u0432\u0438\u0434\u0435\u043b\u0438 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u043e \u043d\u043e\u0432\u043e\u043c \u043f\u043e\u0441\u0442\u0435 \u0432 \u043c\u043e\u043c\u0435\u043d\u0442 \u0435\u0433\u043e \u043f\u043e\u044f\u0432\u043b\u0435\u043d\u0438\u044f.\u041a\u043e\u043c\u043d\u0430\u0442\u044b \u0442\u0443\u0442 \u043d\u0435 \u043d\u0443\u0436\u043d\u044b: \u0430\u0434\u0440\u0435\u0441\u0430\u0442 \u044d\u0442\u043e \u00ab\u0432\u0441\u0435, \u043a\u0442\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0451\u043d\u00bb. \u0417\u043d\u0430\u0447\u0438\u0442, \u0445\u0432\u0430\u0442\u0438\u0442 \u043e\u0434\u043d\u043e\u0433\u043e \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0433\u043e \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u0430, \u0440\u0430\u0441\u0442\u0443\u0449\u0435\u0433\u043e \u043f\u0440\u044f\u043c\u043e \u0438\u0437 \u043a\u0430\u043d\u0430\u043b\u0430.\/\/ pages\/feed.tsx\/\/ \u0441\u0435\u0440\u0432\u0435\u0440 \u2192 \u043a\u043b\u0438\u0435\u043d\u0442. \u0421\u0445\u0435\u043c\u0430 \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 payload, \u043a\u043b\u0438\u0435\u043d\u0442 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0435\u0442 \u0435\u0433\u043e \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043cexport const postAddedHandler = appChannel.lets  .clientHandler()  .serverSend(    z.object({      id: z.string(),      title: z.string(),      authorId: z.string(),      authorName: z.string(),    }),  )  .clientHandler()\/\/ \u0432\u0430\u0448\u0430 \u043e\u0431\u044b\u0447\u043d\u0430\u044f HTTP-\u043c\u0443\u0442\u0430\u0446\u0438\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u0430. \u0418\u0437-\u0437\u0430 \u0440\u0435\u0430\u043b\u0442\u0430\u0439\u043c\u0430 \u0432 \u043d\u0435\u0439 \u0434\u043e\u0431\u0430\u0432\u0438\u043b\u0430\u0441\u044c \u043e\u0434\u043d\u0430 \u0441\u0442\u0440\u043e\u043a\u0430export const postCreateMutation = root.lets  .mutation()  .input(z.object({ title: z.string().min(1) }))  .loader(async ({ input, request }) =&gt; {    const user = await getUserFromRequest(request)    if (!user) throw new AppError(&#8216;\u0422\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0445&#8217;, { status: 401 })    const post = await prisma.post.create({      data: { title: input.title, authorId: user.id },    })    \/\/ \u0431\u0435\u0437 \u0443\u043a\u0430\u0437\u0430\u043d\u0438\u044f \u0446\u0435\u043b\u0438 \u043f\u0443\u0448 \u0443\u0445\u043e\u0434\u0438\u0442 \u0432\u0441\u0435\u043c \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f\u043c \u043a\u0430\u043d\u0430\u043b\u0430, \u0433\u043e\u0441\u0442\u044f\u043c \u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435.    \/\/ \u041d\u0435 \u0436\u0434\u0451\u043c \u0435\u0433\u043e: \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u044d\u0442\u043e \u0441\u0438\u0433\u043d\u0430\u043b, \u0430 \u043d\u0435 \u0447\u0430\u0441\u0442\u044c \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438    void postAddedHandler.sendToClient({      id: post.id,      title: post.title,      authorId: user.id,      authorName: user.name,    })    return post  })  .mutation()\u0424\u043e\u0440\u043c\u0430 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u0430 \u044d\u0442\u043e \u043e\u0431\u044b\u0447\u043d\u0430\u044f \u0444\u043e\u0440\u043c\u0430 \u0441 \u043e\u0431\u044b\u0447\u043d\u043e\u0439 \u043c\u0443\u0442\u0430\u0446\u0438\u0435\u0439, \u0441\u043e\u043a\u0435\u0442\u044b \u0435\u0451 \u043d\u0438\u043a\u0430\u043a \u043d\u0435 \u043a\u0430\u0441\u0430\u044e\u0442\u0441\u044f:const PostForm = () =&gt; {  const create = postCreateMutation.useMutation()  const [title, setTitle] = useState(&#187;)  return (    &lt;form      onSubmit={(e) =&gt; {        e.preventDefault()        void create          .mutateAsync({ title })          .then(() =&gt; setTitle(&#187;))          .catch((error) =&gt; alert(error.message))      }}    &gt;      &lt;input value={title} onChange={(e) =&gt; setTitle(e.target.value)} \/&gt;      &lt;button disabled={create.isPending}&gt;\u041e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u0442\u044c&lt;\/button&gt;    &lt;\/form&gt;  )}\u041d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0430 \u044d\u0442\u043e \u043e\u0434\u0438\u043d \u0445\u0443\u043a. \u041d\u0438 join, \u043d\u0438 \u043a\u043e\u043c\u043d\u0430\u0442, \u043d\u0438 \u0440\u0443\u0447\u043d\u043e\u0433\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f: \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0443\u0436\u0435 \u0434\u0435\u0440\u0436\u0438\u0442 \u043a\u043e\u0440\u0435\u043d\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.const NewPostsBanner = () =&gt; {  const { user } = useUser() \/\/ \u0432\u0430\u0448 \u043e\u0431\u044b\u0447\u043d\u044b\u0439 \u0445\u0443\u043a \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435  const [fresh, setFresh] = useState&lt;{ id: string; title: string }[]&gt;([])  \/\/ message \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d \u0441\u0445\u0435\u043c\u043e\u0439 \u0438\u0437 .serverSend, \u0431\u0435\u0437 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0442\u0438\u043f\u043e\u0432  postAddedHandler.useOnMessageFromServer(({ message }) =&gt; {    if (message.authorId === user?.id) return \/\/ \u0441\u0432\u043e\u0439 \u0436\u0435 \u043f\u043e\u0441\u0442, \u0431\u0430\u043d\u043d\u0435\u0440 \u043d\u0435 \u043d\u0443\u0436\u0435\u043d    setFresh((prev) =&gt; [message, &#8230;prev])  })  if (fresh.length === 0) return null  return (    &lt;button      onClick={() =&gt; {        void feedQuery.invalidateQuery() \/\/ \u043f\u0440\u0430\u0432\u0434\u0430 \u0436\u0438\u0432\u0451\u0442 \u0432 \u043a\u0432\u0435\u0440\u0438, \u043f\u0443\u0448 \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u043a\u0430\u0437\u0430\u043b, \u0447\u0442\u043e \u043e\u043d\u0430 \u0443\u0441\u0442\u0430\u0440\u0435\u043b\u0430        setFresh([])      }}    &gt;      \u041d\u043e\u0432\u044b\u0445 \u043f\u043e\u0441\u0442\u043e\u0432: {fresh.length}. \u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c    &lt;\/button&gt;  )}\u0417\u0434\u0435\u0441\u044c \u0432\u0438\u0434\u043d\u043e \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u043e \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0438: \u043f\u0443\u0448 \u044d\u0442\u043e \u0441\u0438\u0433\u043d\u0430\u043b \u00ab\u0447\u0442\u043e-\u0442\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u043e\u0441\u044c, \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438 \u0435\u0449\u0451 \u0440\u0430\u0437\u00bb, \u0430 \u043d\u0435 \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u0430\u044f \u043a\u043e\u043f\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445. \u041a\u043e\u043f\u0438\u044f \u043b\u0435\u0436\u0438\u0442 \u0432 \u0431\u0430\u0437\u0435 \u0438 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f \u043e\u0431\u044b\u0447\u043d\u044b\u043c \u043a\u0432\u0435\u0440\u0438. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u043e\u0442\u0435\u0440\u044f\u043d\u043d\u044b\u0439 \u043f\u0443\u0448 (\u043a\u043b\u0438\u0435\u043d\u0442 \u0431\u044b\u043b \u0432 \u0440\u0435\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u0435, \u0441\u0435\u0440\u0432\u0435\u0440 \u043f\u0435\u0440\u0435\u0434\u0435\u043f\u043b\u043e\u0438\u043b\u0441\u044f) \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0441\u0442\u043e\u0438\u0442: \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0440\u0435\u0444\u0435\u0442\u0447 \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u043f\u0440\u0438\u043d\u0435\u0441\u0451\u0442 \u043f\u0440\u0430\u0432\u0434\u0443.\u041e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u043e \u0441\u0442\u0440\u043e\u0447\u043a\u0443 if (message.authorId === user?.id) return. \u0421\u0435\u0440\u0432\u0435\u0440 \u0442\u043e\u0436\u0435 \u0443\u043c\u0435\u0435\u0442 \u0438\u0441\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u0430\u0434\u0440\u0435\u0441\u0430\u0442\u0430, \u0443 \u043f\u0443\u0448\u0430 \u0435\u0441\u0442\u044c \u0446\u0435\u043b\u044c:\/\/ \u0442\u0430\u043a \u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0435 \u043d\u0430\u0434\u043evoid postAddedHandler.sendToClient(payload, {  $identity: { id: { $ne: user.id } },})\u041a\u043b\u044e\u0447 \u0441 $ \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u0432\u044b\u0431\u043e\u0440\u043a\u0443 \u0432 \u0441\u0442\u0438\u043b\u0435 Mongo (\u0435\u0451 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 sift), \u043a\u043b\u044e\u0447 \u0431\u0435\u0437 $ \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u0442\u043e\u0447\u043d\u044b\u0439 \u0430\u0434\u0440\u0435\u0441. \u0418 \u0432\u043e\u0442 \u0432 \u044d\u0442\u043e\u043c \u0432\u0441\u044f \u0440\u0430\u0437\u043d\u0438\u0446\u0430: \u0442\u043e\u0447\u043d\u044b\u0439 \u0430\u0434\u0440\u0435\u0441 \u044d\u0442\u043e \u043f\u043e\u043f\u0430\u0434\u0430\u043d\u0438\u0435 \u0432 pub\/sub-\u0442\u043e\u043f\u0438\u043a, \u043e\u0434\u043d\u0430 \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u044f \u043d\u0430 \u043b\u044e\u0431\u043e\u0435 \u0447\u0438\u0441\u043b\u043e \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u0435\u0439, \u0430 \u0432\u044b\u0431\u043e\u0440\u043a\u0430 \u044d\u0442\u043e \u043f\u0435\u0440\u0435\u0431\u043e\u0440 \u0432\u0441\u0435\u0445 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439 \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435. \u0420\u0430\u0434\u0438 \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043e\u0434\u0438\u043d \u0447\u0435\u043b\u043e\u0432\u0435\u043a \u043d\u0435 \u0443\u0432\u0438\u0434\u0435\u043b \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u043f\u043e\u0441\u0442, \u043c\u044b \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0434\u0435\u0448\u0451\u0432\u0443\u044e \u0440\u0430\u0441\u0441\u044b\u043b\u043a\u0443 \u0432 \u0441\u043a\u0430\u043d \u043f\u043e \u0432\u0441\u0435\u0439 \u0431\u0430\u0437\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439.\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0442\u0430\u043a\u043e\u0439, \u043a\u0430\u043a \u0432 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0435 \u0432\u044b\u0448\u0435: \u043a\u043b\u0430\u0434\u0451\u043c authorId \u0432 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0438 \u0441\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u0435\u043c \u0435\u0433\u043e \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435 \u0441 \u0442\u0435\u043a\u0443\u0449\u0438\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c. \u0421\u0432\u043e\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0443 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0438 \u0442\u0430\u043a \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e, useUser \u0443\u0436\u0435 \u0435\u0441\u0442\u044c \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435. \u041a\u0441\u0442\u0430\u0442\u0438, \u0440\u043e\u0432\u043d\u043e \u0442\u0430\u043a \u0443\u0441\u0442\u0440\u043e\u0435\u043d\u043e \u0438 \u0448\u0442\u0430\u0442\u043d\u043e\u0435 \u043f\u043e\u0434\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u044d\u0445\u0430: except \u043f\u043e connectionId \u043d\u0435 \u0443\u0431\u0438\u0440\u0430\u0435\u0442 \u0444\u0440\u0435\u0439\u043c \u0438\u0437 \u0440\u0430\u0441\u0441\u044b\u043b\u043a\u0438, \u043e\u043d \u0434\u043e\u0435\u0437\u0436\u0430\u0435\u0442 \u0434\u043e \u043a\u043b\u0438\u0435\u043d\u0442\u0430, \u0438 \u043a\u043b\u0438\u0435\u043d\u0442 \u0435\u0433\u043e \u043e\u0442\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u0442.\u0412\u044b\u0431\u043e\u0440\u043a\u0438 \u043f\u043e $identity \u043e\u0441\u0442\u0430\u044e\u0442\u0441\u044f \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0434\u043b\u044f \u0447\u0435\u0433\u043e \u043e\u043d\u0438 \u0438 \u0441\u0434\u0435\u043b\u0430\u043d\u044b: \u0440\u0435\u0434\u043a\u0438\u0435 \u0430\u0434\u043c\u0438\u043d\u0441\u043a\u0438\u0435 \u0440\u0430\u0441\u0441\u044b\u043b\u043a\u0438, \u0433\u0434\u0435 \u043f\u0435\u0440\u0435\u0431\u043e\u0440 \u043d\u0435 \u0436\u0430\u043b\u043a\u043e.\u041f\u0440\u0438\u043c\u0435\u0440 2. \u0418\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u0430\u044f \u0434\u043e\u0441\u043a\u0430 \u0441 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e\u043c \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432\u0415\u0441\u0442\u044c \u0434\u043e\u0441\u043a\u0430, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043b\u044e\u0434\u0438 \u0440\u0438\u0441\u0443\u044e\u0442, \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u043f\u0443\u0441\u0442\u044c \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u0442\u0430\u0432\u044f\u0442 \u0442\u043e\u0447\u043a\u0438. \u0414\u043e\u0441\u043a\u0430 \u044d\u0444\u0435\u043c\u0435\u0440\u043d\u0430\u044f, \u0432\u0438\u0434\u043d\u044b \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u043e\u0447\u043a\u0438, \u043f\u043e\u044f\u0432\u0438\u0432\u0448\u0438\u0435\u0441\u044f \u0441 \u043c\u043e\u043c\u0435\u043d\u0442\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f. \u0421\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043c\u043e\u0433\u0443\u0442 \u0432\u0441\u0435, \u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u0447\u043a\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0435.\u0422\u0443\u0442 \u043d\u0443\u0436\u043d\u0430 \u043a\u043e\u043c\u043d\u0430\u0442\u0430, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0440\u0438\u0441\u0443\u044e\u0449\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-490693","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/490693","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=490693"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/490693\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=490693"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=490693"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=490693"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}