{"id":444508,"date":"2025-01-09T09:55:07","date_gmt":"2025-01-09T09:55:07","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=444508"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=444508","title":{"rendered":"<span>\u0420\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u043e \u043f\u043e Convex. \u0427\u0430\u0441\u0442\u044c 3<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/lq\/qm\/e7\/lqqme7wqqxhte55gsiz94gwwh4u.png\" data-src=\"https:\/\/habrastorage.org\/webt\/lq\/qm\/e7\/lqqme7wqqxhte55gsiz94gwwh4u.png\"\/> <\/p>\n<p> \u041f\u0440\u0438\u0432\u0435\u0442, \u0434\u0440\u0443\u0437\u044c\u044f!<\/p>\n<p> <\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0435\u0440\u0438\u0438 \u0441\u0442\u0430\u0442\u0435\u0439 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u044e \u043e <a href=\"https:\/\/www.convex.dev\/\">Convex<\/a> \u2014 \u043d\u043e\u0432\u043e\u043c \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u043c \u0438 \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u043e\u043c \u0440\u0435\u0448\u0435\u043d\u0438\u0438 BaaS (Backend as a Service \u2014 \u0431\u044d\u043a\u0435\u043d\u0434 \u043a\u0430\u043a \u0443\u0441\u043b\u0443\u0433\u0430), \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043e\u0447\u0435\u043d\u044c \u043c\u043d\u043e\u0433\u043e\u043e\u0431\u0435\u0449\u0430\u044e\u0449\u0435 \u0438 \u0431\u044b\u0441\u0442\u0440\u043e \u043d\u0430\u0431\u0438\u0440\u0430\u0435\u0442 \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u043e\u0441\u0442\u044c \u0441\u0440\u0435\u0434\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432.<\/p>\n<p> <\/p>\n<p>\u041d\u0430 \u0441\u0435\u0433\u043e\u0434\u043d\u044f\u0448\u043d\u0438\u0439 \u0434\u0435\u043d\u044c Convex \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u0443\u044e \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u043c\u0435\u0448\u0430\u043d\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0430, \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438\/\u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438, \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0435 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435, \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a \u0437\u0430\u0434\u0430\u0447 \u0438 \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u043e \u0438\u043d\u0442\u0435\u043b\u043b\u0435\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430.<\/p>\n<p> <\/p>\n<p>\u042d\u0442\u0430 \u0442\u0440\u0435\u0442\u044c\u044f \u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u044e\u0449\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0441\u0435\u0440\u0438\u0438, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043c\u044b \u043f\u043e\u0433\u043e\u0432\u043e\u0440\u0438\u043c \u043e \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u0437\u0430\u0434\u0430\u0447, \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u0444\u0430\u0439\u043b\u043e\u0432 \u0438 \u043f\u043e\u0438\u0441\u043a\u0435.<\/p>\n<p> <\/p>\n<p>\u0412 \u043a\u043e\u043d\u0446\u0435 \u043c\u044b \u0442\u0430\u043a\u0436\u0435 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u044b\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f Convex \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u043e\u0433\u043e \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p> <\/p>\n<ul>\n<li><a href=\"https:\/\/habr.com\/ru\/companies\/timeweb\/articles\/844878\/\">\u041f\u0435\u0440\u0432\u0430\u044f \u0447\u0430\u0441\u0442\u044c<\/a><\/li>\n<li><a href=\"https:\/\/habr.com\/ru\/companies\/timeweb\/articles\/851244\/\">\u0412\u0442\u043e\u0440\u0430\u044f \u0447\u0430\u0441\u0442\u044c<\/a><\/li>\n<\/ul>\n<p><a name=\"habracut\"><\/a> <\/p>\n<h1 id=\"planirovanie-zadach\">\u041f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0437\u0430\u0434\u0430\u0447<\/h1>\n<p> <\/p>\n<p>Convex \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043b\u0435\u0433\u043a\u043e \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0440\u0430\u0437\u043e\u0432\u043e\u0435 \u0438\u043b\u0438 \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0439 \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0434\u043b\u044f\u0449\u0438\u0435\u0441\u044f \u0440\u0430\u0431\u043e\u0447\u0438\u0435 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b, \u0442\u0430\u043a\u0438\u0435 \u043a\u0430\u043a \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u043f\u0440\u0438\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0433\u043e email \u0447\u0435\u0440\u0435\u0437 \u0434\u0435\u043d\u044c \u043f\u043e\u0441\u043b\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0438\u043b\u0438 \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u043e\u0432 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e Stripe. Convex \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0434\u0432\u0435 \u0440\u0430\u0437\u043d\u044b\u0445 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f:<\/p>\n<p> <\/p>\n<ul>\n<li>\u0437\u0430\u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 (scheduled functions) \u043c\u043e\u0433\u0443\u0442 \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043a \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044e \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u0434\u0440\u0443\u0433\u0438\u043c\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u043c\u0438. \u0424\u0443\u043d\u043a\u0446\u0438\u0438 \u043c\u043e\u0433\u0443\u0442 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u043c\u0438\u043d\u0443\u0442\u044b, \u0434\u043d\u0438 \u0438 \u0434\u0430\u0436\u0435 \u043c\u0435\u0441\u044f\u0446\u044b<\/li>\n<li>\u0437\u0430\u0434\u0430\u0447\u0438 Cron (cron jobs) \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u044e\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043d\u0430 \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u043e\u0439 \u043e\u0441\u043d\u043e\u0432\u0435, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0435\u0436\u0435\u043d\u0435\u0434\u0435\u043b\u044c\u043d\u043e<\/li>\n<\/ul>\n<p> <\/p>\n<h2 id=\"zaplanirovannye-zadachi\">\u0417\u0430\u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0437\u0430\u0434\u0430\u0447\u0438<\/h2>\n<p> <\/p>\n<p>Convex \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0439 \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u0442\u044f\u0436\u0435\u043d\u043d\u044b\u0435 \u0432\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0440\u0430\u0431\u043e\u0447\u0438\u0435 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b \u0431\u0435\u0437 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 \u043e\u0447\u0435\u0440\u0435\u0434\u0435\u0439 (queues) \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u043e\u0439 \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b.<\/p>\n<p> <\/p>\n<p>\u0417\u0430\u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u0432 \u0411\u0414. \u042d\u0442\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u0438\u0445 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043c\u043e\u0436\u0435\u0442 \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u043c\u0438\u043d\u0443\u0442\u044b, \u0434\u043d\u0438 \u0438 \u0434\u0430\u0436\u0435 \u043c\u0435\u0441\u044f\u0446\u044b. \u041f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0443\u0441\u0442\u043e\u0439\u0447\u0438\u0432\u043e \u043a \u043d\u0435\u043e\u0436\u0438\u0434\u0430\u043d\u043d\u044b\u043c \u0441\u0431\u043e\u044f\u043c \u0438 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0430\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u044b.<\/p>\n<p> <\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/get-convex\/convex-demos\/tree\/main\/scheduling\">\u041f\u0440\u0438\u043c\u0435\u0440<\/a><\/li>\n<\/ul>\n<p> <\/p>\n<p><strong>\u041f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0439<\/strong><\/p>\n<p> <\/p>\n<p>\u041f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0435 \u0438 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043c\u043e\u0436\u043d\u043e \u0432 \u043c\u0443\u0442\u0430\u0446\u0438\u044f\u0445 \u0438 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f\u0445 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a\u0430 (<code>scheduler<\/code>), \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0435\u0433\u043e\u0441\u044f \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435.<\/p>\n<p> <\/p>\n<ul>\n<li>\u041c\u0435\u0442\u043e\u0434 <code>runAfter<\/code> \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u043a \u0437\u0430\u043f\u0443\u0441\u043a\u0443 \u0447\u0435\u0440\u0435\u0437 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f (delay) (\u0438\u0437\u043c\u0435\u0440\u044f\u0435\u043c\u043e\u0435 \u0432 \u043c\u0441)<\/li>\n<li>\u043c\u0435\u0442\u043e\u0434 <code>runAt<\/code> \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u043a \u0437\u0430\u043f\u0443\u0441\u043a\u0443 \u0432 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f (\u0438\u0437\u043c\u0435\u0440\u044f\u0435\u043c\u043e\u0435 \u0432 \u043c\u0441 \u0441 \u043d\u0430\u0447\u0430\u043b\u0430 \u044d\u043f\u043e\u0445\u0438)<\/li>\n<\/ul>\n<p> <\/p>\n<p>\u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u043c\u0438 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u0430\u043c\u0438 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043f\u0443\u0442\u044c \u043a \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0438 \u0435\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b.<\/p>\n<p> <\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0441 \u0435\u0433\u043e \u0443\u043d\u0438\u0447\u0442\u043e\u0436\u0435\u043d\u0438\u0435\u043c \u0447\u0435\u0440\u0435\u0437 5 \u0441\u0435\u043a:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">import { mutation, internalMutation } from \".\/_generated\/server\"; import { internal } from \".\/_generated\/api\"; import { v } from \"convex\/values\";  export const sendExpiringMessage = mutation({   args: { body: v.string(), author: v.string() },   handler: async (ctx, args) =&gt; {     const { body, author } = args;     const id = await ctx.db.insert(\"messages\", { body, author });     \/\/ \u0423\u0434\u0430\u043b\u044f\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0447\u0435\u0440\u0435\u0437 5 \u0441     await ctx.scheduler.runAfter(5000, internal.messages.destruct, {       messageId: id,     });   }, });  export const destruct = internalMutation({   args: {     messageId: v.id(\"messages\"),   },   handler: async (ctx, args) =&gt; {     await ctx.db.delete(args.messageId);   }, });<\/code><\/pre>\n<p> <\/p>\n<p>\u041e\u0434\u043d\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043c\u043e\u0436\u0435\u0442 \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u043e 1000 \u0444\u0443\u043d\u043a\u0446\u0438\u0439 \u0441 \u043e\u0431\u0449\u0438\u043c \u0440\u0430\u0437\u043c\u0435\u0440\u043e\u043c \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0434\u043e 8 \u041c\u0431.<\/p>\n<p> <\/p>\n<p><strong>\u041f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0432 \u043c\u0443\u0442\u0430\u0446\u0438\u044f\u0445<\/strong><\/p>\n<p> <\/p>\n<p>\u041f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0439 \u0432 \u043c\u0443\u0442\u0430\u0446\u0438\u044f\u0445 \u0441\u0432\u044f\u0437\u0430\u043d\u043e \u0441 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0439 \u043c\u0443\u0442\u0430\u0446\u0438\u0435\u0439. \u042d\u0442\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u043c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u0432\u0441\u0435\u0439 \u043c\u0443\u0442\u0430\u0446\u0438\u0438. \u0415\u0441\u043b\u0438 \u043c\u0443\u0442\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0432\u0430\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f, \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043d\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f, \u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u0431\u044b\u043b\u0430 &#171;\u0437\u0430\u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0430&#187;.<\/p>\n<p> <\/p>\n<p><strong>\u041f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0432 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f\u0445<\/strong><\/p>\n<p> <\/p>\n<p>\u0412 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 \u043c\u0443\u0442\u0430\u0446\u0438\u0439, \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u043d\u0435 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f\u043c\u0438 \u0438 \u043c\u043e\u0433\u0443\u0442 \u0438\u043c\u0435\u0442\u044c \u043f\u043e\u0431\u043e\u0447\u043d\u044b\u0435 \u044d\u0444\u0444\u0435\u043a\u0442\u044b. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0437\u0430\u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0444\u0443\u043d\u043a\u0446\u0438\u0439 \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0441\u0442\u0438 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439.<\/p>\n<p> <\/p>\n<p><strong>\u041d\u0435\u043c\u0435\u0434\u043b\u0435\u043d\u043d\u043e\u0435 \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435<\/strong><\/p>\n<p> <\/p>\n<p><code>runAfter(0, fn)<\/code> \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u043d\u0435\u043c\u0435\u0434\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0441\u043e\u0431\u044b\u0442\u0438\u0439. \u042d\u0442\u043e \u043f\u043e\u0445\u043e\u0436\u0435 \u043d\u0430 <code>setTimeout(fn, 0)<\/code>.<\/p>\n<p> <\/p>\n<p>\u042d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u0433\u043e\u0434\u0438\u0442\u044c\u0441\u044f, \u043a\u043e\u0433\u0434\u0430 \u043d\u0443\u0436\u043d\u043e \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044e, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0443\u0441\u043f\u0435\u0445\u0430 \u043c\u0443\u0442\u0430\u0446\u0438\u0438.<\/p>\n<p> <\/p>\n<p><strong>\u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \u0437\u0430\u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438<\/strong><\/p>\n<p> <\/p>\n<p>\u041a\u0430\u0436\u0434\u0430\u044f \u0437\u0430\u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 <code>\"_scheduled_functions\"<\/code>. <code>runAfter()<\/code> \u0438 <code>runAt()<\/code> \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u044e\u0442 \u0418\u0414 \u0437\u0430\u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438. \u0414\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0445 \u0442\u0430\u0431\u043b\u0438\u0446 \u043c\u043e\u0436\u043d\u043e \u0447\u0438\u0442\u0430\u0442\u044c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0435\u0442\u043e\u0434\u043e\u0432 <code>db.system.get<\/code> \u0438 <code>db.system.query<\/code>:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">export const listScheduledMessages = query({   args: {},   handler: async (ctx, args) =&gt; {     return await ctx.db.system.query(\"_scheduled_functions\").collect();   }, });  export const getScheduledMessage = query({   args: {     id: v.id(\"_scheduled_functions\"),   },   handler: async (ctx, args) =&gt; {     return await ctx.db.system.get(args.id);   }, });<\/code><\/pre>\n<p> <\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u043e\u0433\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">{   \"_creationTime\": 1699931054642.111,   \"_id\": \"3ep33196167235462543626ss0scq09aj4gqn9kdxrdr\",   \"args\": [{}],   \"completedTime\": 1699931054690.366,   \"name\": \"messages.js:destruct\",   \"scheduledTime\": 1699931054657,   \"state\": { \"kind\": \"success\" } }<\/code><\/pre>\n<p> <\/p>\n<ul>\n<li><code>name<\/code> \u2014 \u043f\u0443\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u0438<\/li>\n<li><code>args<\/code> \u2014 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u044b \u0444\u0443\u043d\u043a\u0446\u0438\u0438<\/li>\n<li><code>scheduledTime<\/code> \u2014 \u0432\u0440\u0435\u043c\u044f \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u0438 (\u0432 \u043c\u0441 \u0441 \u043d\u0430\u0447\u0430\u043b\u0430 \u044d\u043f\u043e\u0445\u0438)<\/li>\n<li><code>completedTime<\/code> \u2014 \u0432\u0440\u0435\u043c\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0433\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u0438 (\u0432 \u043c\u0441 \u0441 \u043d\u0430\u0447\u0430\u043b\u0430 \u044d\u043f\u043e\u0445\u0438)<\/li>\n<li><code>state<\/code> \u2014 \u0441\u0442\u0430\u0442\u0443\u0441 \u0444\u0443\u043d\u043a\u0446\u0438\u0438:<br \/> \n<ul>\n<li><code>Pending<\/code> \u2014 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043d\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u043b\u0430\u0441\u044c<\/li>\n<li><code>InProgress<\/code> \u2014 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u0430, \u043d\u043e \u0435\u0449\u0435 \u043d\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0430 (\u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043a \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f\u043c)<\/li>\n<li><code>Success<\/code> \u2014 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430<\/li>\n<li><code>Error<\/code> \u2014 \u043f\u0440\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0432\u043e\u0437\u043d\u0438\u043a\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430<\/li>\n<li><code>Cancelled<\/code> \u2014 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0431\u044b\u043b\u0430 \u043e\u0442\u043c\u0435\u043d\u0435\u043d\u0430 \u0447\u0435\u0440\u0435\u0437 \u043f\u0430\u043d\u0435\u043b\u044c \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f, <code>ctx.scheduler.cancel()<\/code> \u0438\u043b\u0438 \u0440\u0435\u043a\u0443\u0440\u0441\u0438\u0432\u043d\u043e \u0440\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0435\u0439<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p> <\/p>\n<p>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0437\u0430\u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b \u0432 \u0442\u0435\u0447\u0435\u043d\u0438\u0435 7 \u0434\u043d\u0435\u0439.<\/p>\n<p> <\/p>\n<p><strong>\u041e\u0442\u043c\u0435\u043d\u0430 \u0437\u0430\u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438<\/strong><\/p>\n<p> <\/p>\n<p>\u0420\u0430\u043d\u0435\u0435 \u0437\u0430\u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043e\u0442\u043c\u0435\u043d\u0435\u043d\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0435\u0442\u043e\u0434\u0430 <code>cancel<\/code>:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">export const cancelMessage = mutation({   args: {     id: v.id(\"_scheduled_functions\"),   },   handler: async (ctx, args) =&gt; {     await ctx.scheduler.cancel(args.id);   }, });<\/code><\/pre>\n<p> <\/p>\n<p>\u042d\u0444\u0444\u0435\u043a\u0442 \u0432\u044b\u0437\u043e\u0432\u0430 <code>cancel()<\/code> \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u0438:<\/p>\n<p> <\/p>\n<ul>\n<li>\u0435\u0441\u043b\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043d\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u043b\u0430\u0441\u044c, \u043e\u043d\u0430 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u0430<\/li>\n<li>\u0435\u0441\u043b\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u0430, \u043e\u043d\u0430 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c\u0441\u044f, \u043d\u043e \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u0435\u043c\u044b\u0435 \u0435\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043d\u0435 \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u044b<\/li>\n<\/ul>\n<p> <\/p>\n<h2 id=\"zadachi-cron\">\u0417\u0430\u0434\u0430\u0447\u0438 Cron<\/h2>\n<p> <\/p>\n<p>Convex \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0439 \u043d\u0430 \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u043e\u0439 \u043e\u0441\u043d\u043e\u0432\u0435. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0437\u0430\u0434\u0430\u0447\u0438 cron \u043c\u043e\u0433\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u043b\u044f \u043e\u0447\u0438\u0441\u0442\u043a\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0441 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0439 \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u043d\u043e\u0441\u0442\u044c\u044e, \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 email \u0441 \u043d\u0430\u043f\u043e\u043c\u0438\u043d\u0430\u043d\u0438\u0435\u043c \u0432 \u043e\u0434\u043d\u043e \u0438 \u0442\u043e\u0436\u0435 \u0432\u0440\u0435\u043c\u044f \u043a\u0430\u0436\u0434\u044b\u0439 \u043c\u0435\u0441\u044f\u0446 \u0438\u043b\u0438 \u0440\u0435\u0437\u0435\u0440\u0432\u043d\u043e\u0435 \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u043a\u0430\u0436\u0434\u0443\u044e \u0441\u0431.<\/p>\n<p> <\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/get-convex\/convex-demos\/tree\/main\/cron-jobs\">\u041f\u0440\u0438\u043c\u0435\u0440<\/a><\/li>\n<\/ul>\n<p> <\/p>\n<p><strong>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 cron-\u0437\u0430\u0434\u0430\u0447\u0438<\/strong><\/p>\n<p> <\/p>\n<p>\u0417\u0430\u0434\u0430\u0447\u0438 cron \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0442\u0441\u044f \u0432 \u0444\u0430\u0439\u043b\u0435 <code>cron.ts<\/code> \u0432 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 <code>convex<\/code>:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">import { cronJobs } from \"convex\/server\"; import { internal } from \".\/_generated\/api\";  const crons = cronJobs();  crons.interval(   \"clear messages table\",   { minutes: 1 }, \/\/ \u043a\u0430\u0436\u0434\u0443\u044e \u043c\u0438\u043d   internal.messages.clearAll, );  crons.monthly(   \"payment reminder\",   { day: 1, hourUTC: 16, minuteUTC: 0 }, \/\/ \u043a\u0430\u0436\u0434\u044b\u0439 \u043c\u0435\u0441 \u0432 \u043f\u0435\u0440\u0432\u044b\u0439 \u0434\u0435\u043d\u044c \u0432 8 \u0443\u0442\u0440\u0430   internal.payments.sendPaymentEmail,   { email: \"my_email@gmail.com\" }, \/\/ \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442 `sendPaymentEmail()` );  \/\/ \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441\u0430 cron crons.cron(   \"payment reminder duplicate\",   \"0 16 1 * *\",   internal.payments.sendPaymentEmail,   { email: \"my_email@gmail.com\" }, );  export default crons;<\/code><\/pre>\n<p> <\/p>\n<p>\u041f\u0435\u0440\u0432\u044b\u0439 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442 \u2014 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0418\u0414 \u0437\u0430\u0434\u0430\u0447\u0438 cron.<\/p>\n<p> <\/p>\n<p>\u0412\u0442\u043e\u0440\u043e\u0439 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442 \u2014 \u0432\u0440\u0435\u043c\u044f, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u043e\u0433\u043e \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a\u0430.<\/p>\n<p> <\/p>\n<p>\u0422\u0440\u0435\u0442\u0438\u0439 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442 \u2014 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u043e\u0439 \u0438\u043b\u0438 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u043c\u0443\u0442\u0430\u0446\u0438\u0438 \u0438\u043b\u0438 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438.<\/p>\n<p> <\/p>\n<p><strong>\u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0435 \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a\u0438<\/strong><\/p>\n<p> <\/p>\n<ul>\n<li><code>crons.interval()<\/code> \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u0435\u0441\u043a\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0447\u0435\u0440\u0435\u0437 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0439 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u043a \u0432\u0440\u0435\u043c\u0435\u043d\u0438, \u0438\u0437\u043c\u0435\u0440\u044f\u0435\u043c\u044b\u0439 \u0432 <code>seconds<\/code>, <code>minutes<\/code> \u0438 <code>hours<\/code>. \u041f\u0435\u0440\u0432\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u0434\u0435\u043f\u043b\u043e\u0435<\/li>\n<li><code>crons.cron()<\/code> \u2014 \u0442\u0440\u0430\u0434\u0438\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0437\u0430\u0434\u0430\u0447 cron \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0441\u0442\u0440\u043e\u043a\u0438, \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0435\u0439 5 \u043f\u043e\u043b\u0435\u0439, \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0445 \u043f\u0440\u043e\u0431\u0435\u043b\u0430\u043c\u0438 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, <code>\"* * * * *\"<\/code>). \u0412\u0440\u0435\u043c\u044f \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 UTC. <a href=\"https:\/\/crontab.guru\/\">Crontab Guru<\/a> \u2014 \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u0439 \u0440\u0435\u0441\u0443\u0440\u0441 \u0434\u043b\u044f \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u044f \u0442\u0430\u043a\u043e\u0433\u043e \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441\u0430<\/li>\n<li><code>crons.hourly()<\/code>, <code>crons.daily()<\/code>, <code>crons.weekly()<\/code>, <code>crons.monthly()<\/code> \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442 \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441 \u0434\u043b\u044f \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u044b\u0445 \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a\u043e\u0432 \u0441 \u044f\u0432\u043d\u044b\u043c\u0438 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f\u043c\u0438 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u043e\u0432<\/li>\n<\/ul>\n<p> <\/p>\n<h1 id=\"hranilische-faylov\">\u0425\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u0444\u0430\u0439\u043b\u043e\u0432<\/h1>\n<p> <\/p>\n<p>\u0424\u0430\u0439\u043b\u043e\u0432\u043e\u0435 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043b\u0435\u0433\u043a\u043e \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0444\u0430\u0439\u043b\u044b \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u0442\u044c \u0444\u0430\u0439\u043b\u044b \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0438\u0445 \u0432 \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0435 \u0430\u043f\u0438, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u0444\u0430\u0439\u043b\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c. \u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0432\u0441\u0435 \u0442\u0438\u043f\u044b \u0444\u0430\u0439\u043b\u043e\u0432.<\/p>\n<p> <\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/get-convex\/convex-demos\/tree\/main\/file-storage-with-http\">\u041f\u0440\u0438\u043c\u0435\u0440 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0444\u0430\u0439\u043b\u0430\u043c\u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439 HTTP<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/get-convex\/convex-demos\/tree\/main\/file-storage\">\u041f\u0440\u0438\u043c\u0435\u0440 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0444\u0430\u0439\u043b\u0430\u043c\u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0438 \u043c\u0443\u0442\u0430\u0446\u0438\u0439<\/a><\/li>\n<\/ul>\n<p> <\/p>\n<p><em>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435:<\/em> \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u0444\u0430\u0439\u043b\u043e\u0432 \u0432 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u0439 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e. \u042d\u0442\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u043a\u043e\u0434 \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0435\u0442\u0435\u0440\u043f\u0435\u0442\u044c \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f.<\/p>\n<p> <\/p>\n<h2 id=\"zagruzka-faylov\">\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0444\u0430\u0439\u043b\u043e\u0432<\/h2>\n<p> <\/p>\n<p>\u0424\u0430\u0439\u043b\u044b \u043c\u043e\u0433\u0443\u0442 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c\u0441\u044f \u0432 Convex \u043b\u0438\u0431\u043e \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c\u044b\u0445 \u0443\u0440\u043b\u043e\u0432 \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 (upload urls), \u043b\u0438\u0431\u043e \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0445 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439 HTTP.<\/p>\n<p> <\/p>\n<p><strong>\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0444\u0430\u0439\u043b\u043e\u0432 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0443\u0440\u043b\u043e\u0432<\/strong><\/p>\n<p> <\/p>\n<p>\u0411\u043e\u043b\u044c\u0448\u0438\u0435 \u0444\u0430\u0439\u043b\u044b \u043c\u043e\u0433\u0443\u0442 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c\u0441\u044f \u043f\u0440\u044f\u043c\u043e \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c\u044b\u0445 \u0443\u0440\u043b\u043e\u0432 \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043a\u043b\u0438\u0435\u043d\u0442 \u0434\u043e\u043b\u0436\u0435\u043d \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<p> <\/p>\n<ol>\n<li>\u0421\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0443\u0440\u043b \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0443\u0442\u0430\u0446\u0438\u0438, \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0449\u0435\u0439 <code>storage.generateUploadUrl()<\/code>.<\/li>\n<li>\u041e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c POST-\u0437\u0430\u043f\u0440\u043e\u0441 \u0441 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u044b\u043c \u0444\u0430\u0439\u043b\u0430 \u043f\u043e \u0443\u0440\u043b\u0443 \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0418\u0414 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 (storage ID).<\/li>\n<li>\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0418\u0414 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 \u0432 \u0411\u0414 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0434\u0440\u0443\u0433\u043e\u0439 \u043c\u0443\u0442\u0430\u0446\u0438\u0438.<\/li>\n<\/ol>\n<p> <\/p>\n<p>\u0412 \u043f\u0435\u0440\u0432\u043e\u0439 \u043c\u0443\u0442\u0430\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 \u0443\u0440\u043b \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438, \u043c\u043e\u0436\u043d\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0442\u0435\u043c, \u043a\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0444\u0430\u0439\u043b\u044b.<\/p>\n<p> <\/p>\n<p><em>\u0412\u044b\u0437\u043e\u0432 \u0430\u043f\u0438 \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u043e\u0432 \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b<\/em><\/p>\n<p> <\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043f\u0440\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0435 \u0444\u043e\u0440\u043c\u044b \u0432 \u0443\u0440\u043b \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438, \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c\u044b\u0439 \u043c\u0443\u0442\u0430\u0446\u0438\u0435\u0439:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">import { FormEvent, useRef, useState } from \"react\"; import { useMutation } from \"convex\/react\"; import { api } from \"..\/convex\/_generated\/api\";  export default function App() {   const generateUploadUrl = useMutation(api.messages.generateUploadUrl);   const sendImage = useMutation(api.messages.sendImage);    const imageInput = useRef&lt;HTMLInputElement&gt;(null);   const [selectedImage, setSelectedImage] = useState&lt;File | null&gt;(null);    async function handleSendImage(event: FormEvent) {     event.preventDefault();      \/\/ \u0428\u0430\u0433 1: \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043a\u043e\u0440\u043e\u0442\u043a\u043e\u0436\u0438\u0432\u0443\u0449\u0438\u0439 \u0443\u0440\u043b \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438     const postUrl = await generateUploadUrl();     \/\/ \u0428\u0430\u0433 2: \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c POST-\u0437\u0430\u043f\u0440\u043e\u0441 \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u0430     const result = await fetch(postUrl, {       method: \"POST\",       headers: { \"Content-Type\": selectedImage!.type },       body: selectedImage,     });     const { storageId } = await result.json();     \/\/ \u0428\u0430\u0433 3: \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0418\u0414 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 \u0432 \u0411\u0414     await sendImage({ storageId, author: `User ${Math.floor(Math.random() * 10000)}` });      setSelectedImage(null);     imageInput.current!.value = \"\";   }    return (     &lt;form onSubmit={handleSendImage}&gt;       &lt;input         type=\"file\"         accept=\"image\/*\"         ref={imageInput}         onChange={(event) =&gt; setSelectedImage(event.target.files![0])}         disabled={selectedImage !== null}       \/&gt;       &lt;input         type=\"submit\"         value=\"\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c\"         disabled={selectedImage === null}       \/&gt;     &lt;\/form&gt;   ); }<\/code><\/pre>\n<p> <\/p>\n<p><em>\u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0443\u0440\u043b\u0430 \u0434\u044f\u043b \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438<\/em><\/p>\n<p> <\/p>\n<p>\u0423\u0440\u043b \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0435\u0442\u043e\u0434\u0430 <code>storage.generateUploadUrl<\/code> \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u043c\u0443\u0442\u0430\u0446\u0438\u0438:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">import { mutation } from \".\/_generated\/server\";  export const generateUploadUrl = mutation(async (ctx) =&gt; {   return await ctx.storage.generateUploadUrl(); });<\/code><\/pre>\n<p> <\/p>\n<p>\u042d\u0442\u0430 \u043c\u0443\u0442\u0430\u0446\u0438\u0438 \u043c\u043e\u0436\u0435\u0442 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0442\u0435\u043c, \u043a\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0444\u0430\u0439\u043b\u044b.<\/p>\n<p> <\/p>\n<p>\u0423\u0440\u043b \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 &#171;\u0436\u0438\u0432\u0435\u0442&#187; 1 \u0447\u0430\u0441.<\/p>\n<p> <\/p>\n<p><em>\u0417\u0430\u043f\u0438\u0441\u044c \u043d\u043e\u0432\u043e\u0433\u043e \u0418\u0414 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 \u0432 \u0411\u0414<\/em><\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">import { mutation } from \".\/_generated\/server\";  export const sendImage = mutation({   args: { storageId: v.id(\"_storage\"), author: v.string() },   handler: async (ctx, args) =&gt; {     await ctx.db.insert(\"messages\", {       body: args.storageId,       author: args.author,       format: \"image\",     });   }, });<\/code><\/pre>\n<p> <\/p>\n<p><em>\u041e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f<\/em><\/p>\n<p> <\/p>\n<p>\u0420\u0430\u0437\u043c\u0435\u0440 \u0444\u0430\u0439\u043b\u043e\u0432 \u043d\u0435 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d, \u043d\u043e \u0442\u0430\u0439\u043c\u0430\u0443\u0442 POST-\u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0441\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 2 \u043c\u0438\u043d.<\/p>\n<p> <\/p>\n<p><strong>\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0444\u0430\u0439\u043b\u043e\u0432 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 HTTP<\/strong><\/p>\n<p> <\/p>\n<p>\u041f\u0440\u043e\u0446\u0435\u0441\u0441 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u0430 \u043c\u043e\u0436\u043d\u043e \u0441\u0432\u0435\u0441\u0442\u0438 \u043a \u043e\u0434\u043d\u043e\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 HTTP, \u043d\u043e \u044d\u0442\u043e \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0439 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u0432 CORS.<\/p>\n<p> <\/p>\n<p>\u041e\u043f\u0435\u0440\u0430\u0446\u0438\u044f HTTP \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u043e\u0432 \u043c\u043e\u0436\u0435\u0442 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0442\u0435\u043c, \u043a\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0444\u0430\u0439\u043b\u044b. \u041e\u0434\u043d\u0430\u043a\u043e \u0432 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u0440\u0430\u0437\u043c\u0435\u0440 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d 20 \u041c\u0431. \u0414\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u043e\u0432 \u0431\u043e\u043b\u044c\u0448\u0435\u0433\u043e \u0440\u0430\u0437\u043c\u0435\u0440\u0430 \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0443\u0440\u043b\u044b \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438.<\/p>\n<p> <\/p>\n<p><em>\u0412\u044b\u0437\u043e\u0432 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 HTTP \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u043e\u0432 \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b<\/em><\/p>\n<p> <\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043f\u0440\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0435 \u0444\u043e\u0440\u043c\u044b \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 HTTP:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">import { FormEvent, useRef, useState } from \"react\";  const convexSiteUrl = import.meta.env.VITE_CONVEX_SITE_URL;  export default function App() {   const imageInput = useRef&lt;HTMLInputElement&gt;(null);   const [selectedImage, setSelectedImage] = useState&lt;File | null&gt;(null);    async function handleSendImage(event: FormEvent) {     event.preventDefault();      \/\/ \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, https:\/\/happy-animal-123.convex.site\/sendImage?author=User+123     const sendImageUrl = new URL(`${convexSiteUrl}\/sendImage`);     sendImageUrl.searchParams.set(\"author\", `User ${Math.floor(Math.random() * 10000)}`);      await fetch(sendImageUrl, {       method: \"POST\",       headers: { \"Content-Type\": selectedImage!.type },       body: selectedImage,     });      setSelectedImage(null);     imageInput.current!.value = \"\";   }    return (     &lt;form onSubmit={handleSendImage}&gt;       &lt;input         type=\"file\"         accept=\"image\/*\"         ref={imageInput}         onChange={(event) =&gt; setSelectedImage(event.target.files![0])}         disabled={selectedImage !== null}       \/&gt;       &lt;input         type=\"submit\"         value=\"\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c\"         disabled={selectedImage === null}       \/&gt;     &lt;\/form&gt;   ); }<\/code><\/pre>\n<p> <\/p>\n<p><em>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 HTTP \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u043e\u0432<\/em><\/p>\n<p> <\/p>\n<p>\u0424\u0430\u0439\u043b, \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0438\u0439\u0441\u044f \u0432 \u0442\u0435\u043b\u0435 HTTP-\u0437\u0430\u043f\u0440\u043e\u0441\u0430, \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <code>storage.store<\/code> \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438. \u042d\u0442\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 <code>Id&lt;\"_storage\"&gt;<\/code> \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430.<\/p>\n<p> <\/p>\n<p>\u0414\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0418\u0414 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 \u0432 \u0411\u0414 \u0432 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u043c\u043e\u0436\u043d\u043e \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u043c\u0443\u0442\u0430\u0446\u0438\u044e:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">\/\/ convex\/https.ts import { httpRouter } from \"convex\/server\"; import { httpAction } from \".\/_generated\/server\"; import { api } from \".\/_generated\/api\"; import { Id } from \".\/_generated\/dataModel\";  const http = httpRouter();  http.route({   path: \"\/sendImage\",   method: \"POST\",   handler: httpAction(async (ctx, request) =&gt; {     \/\/ \u0428\u0430\u0433 1: \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0444\u0430\u0439\u043b     const blob = await request.blob();     const storageId = await ctx.storage.store(blob);      \/\/ \u0428\u0430\u0433 2: \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0418\u0414 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 \u0432 \u0411\u0414 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0443\u0442\u0430\u0446\u0438\u0438     const author = new URL(request.url).searchParams.get(\"author\");     await ctx.runMutation(api.messages.sendImage, { storageId, author });      \/\/ \u0428\u0430\u0433 3: \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043e\u0442\u0432\u0435\u0442 \u0441 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u043c\u0438 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430\u043c\u0438 CORS     return new Response(null, {       status: 200,       \/\/ \u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 CORS       headers: new Headers({         \"Access-Control-Allow-Origin\": process.env.CLIENT_ORIGIN!,         Vary: \"origin\",       }),     });   }), });<\/code><\/pre>\n<p> <\/p>\n<p>\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0442\u0430\u043a\u0436\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 OPTIONS:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">http.route({   path: \"\/sendImage\",   method: \"OPTIONS\",   handler: httpAction(async (_, request) =&gt; {     \/\/ \u0423\u0431\u0435\u0436\u0434\u0430\u0435\u043c\u0441\u044f \u0432 \u043d\u0430\u043b\u0438\u0447\u0438\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0445 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u0432     const headers = request.headers;     if (       headers.get(\"Origin\") !== null &amp;&amp;       headers.get(\"Access-Control-Request-Method\") !== null &amp;&amp;       headers.get(\"Access-Control-Request-Headers\") !== null     ) {       return new Response(null, {         headers: new Headers({           \"Access-Control-Allow-Origin\": process.env.CLIENT_ORIGIN!,           \"Access-Control-Allow-Methods\": \"POST\",           \"Access-Control-Allow-Headers\": \"Content-Type, Digest\",           \"Access-Control-Max-Age\": \"86400\",         }),       });     } else {       return new Response();     }   }), });<\/code><\/pre>\n<p> <\/p>\n<h2 id=\"hranenie-faylov\">\u0425\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u043e\u0432<\/h2>\n<p> <\/p>\n<p>\u0424\u0430\u0439\u043b\u044b \u043c\u043e\u0433\u0443\u0442 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c\u0441\u044f \u0432 Convex \u043f\u0440\u044f\u043c\u043e \u0441 \u043a\u043b\u0438\u0435\u043d\u0442\u0430, \u043a\u0430\u043a \u043c\u044b \u0432\u0438\u0434\u0435\u043b\u0438 \u0432 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u043c \u0440\u0430\u0437\u0434\u0435\u043b\u0435<\/p>\n<p> <\/p>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u044b \u0444\u0430\u0439\u043b\u044b \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0433\u0443\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c\u0441\u044f \u0432 Convex \u043f\u043e\u0441\u043b\u0435 \u0438\u0445 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0438\u043b\u0438 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0432 \u043e\u0431\u044b\u0447\u043d\u044b\u0445 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f\u0445 \u0438 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f\u0445 HTTP. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0439 \u0430\u043f\u0438 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0432 Convex.<\/p>\n<p> <\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/get-convex\/convex-demos\/tree\/main\/dall-e-storage-action\">\u041f\u0440\u0438\u043c\u0435\u0440<\/a><\/li>\n<\/ul>\n<p> <\/p>\n<p><strong>\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u043e\u0432 \u0432 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f\u0445<\/strong><\/p>\n<p> <\/p>\n<p>\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u043e\u0432 \u0432 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f\u0445 \u043f\u043e\u0445\u043e\u0436\u0435 \u043d\u0430 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u0444\u0430\u0439\u043b\u043e\u0432 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439 HTTP.<\/p>\n<p> <\/p>\n<p>\u0422\u0430\u043a\u0430\u044f \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u044d\u0442\u0430\u043f\u043e\u0432:<\/p>\n<p> <\/p>\n<ol>\n<li>\u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0438\u043b\u0438 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f.<\/li>\n<li>\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <code>storage.store()<\/code> \u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0418\u0414 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430.<\/li>\n<li>\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0418\u0414 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 \u0432 \u0411\u0414 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0443\u0442\u0430\u0446\u0438\u0438.<\/li>\n<\/ol>\n<p> <\/p>\n<p>\u0418\u0414 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0442 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043c \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 <code>\"_storage\"<\/code>, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043e\u043d\u0438 \u043c\u043e\u0433\u0443\u0442 \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <code>v.id(\"_storage\")<\/code> \u0438 \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <code>Id&lt;\"_storage\"&gt;<\/code>.<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">import { action, internalMutation, query } from \".\/_generated\/server\"; import { internal } from \".\/_generated\/api\"; import { v } from \"convex\/values\"; import { Id } from \".\/_generated\/dataModel\";  export const generateAndStore = action({   args: { prompt: v.string() },   handler: async (ctx, args) =&gt; {     \/\/ \u0413\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c `imageUrl` \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 `prompt`     const imageUrl = \"https:\/\/....\";      \/\/ \u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435     const response = await fetch(imageUrl);     const image = await response.blob();      \/\/ \u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0432 Convex     const storageId: Id&lt;\"_storage\"&gt; = await ctx.storage.store(image);      \/\/ \u0417\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c `storageId` \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442     await ctx.runMutation(internal.images.storeResult, {       storageId,       prompt: args.prompt,     });   }, });  export const storeResult = internalMutation({   args: {     storageId: v.id(\"_storage\"),     prompt: v.string(),   },   handler: async (ctx, args) =&gt; {     const { storageId, prompt } = args;     await ctx.db.insert(\"images\", { storageId, prompt });   }, });<\/code><\/pre>\n<p> <\/p>\n<h2 id=\"obsluzhivanie-faylov\">\u041e\u0431\u0441\u043b\u0443\u0436\u0438\u0432\u0430\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u043e\u0432<\/h2>\n<p> <\/p>\n<p>\u0424\u0430\u0439\u043b\u044b, \u0445\u0440\u0430\u043d\u044f\u0449\u0438\u0435\u0441\u044f \u0432 Convex, \u043c\u043e\u0433\u0443\u0442 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c \u043f\u0443\u0442\u0435\u043c \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0443\u0440\u043b\u0430, \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0449\u0435\u0433\u043e \u043d\u0430 \u0444\u0430\u0439\u043b.<\/p>\n<p> <\/p>\n<p><strong>\u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0443\u0440\u043b\u043e\u0432 \u0444\u0430\u0439\u043b\u043e\u0432 \u0432 \u0437\u0430\u043f\u0440\u043e\u0441\u0430\u0445<\/strong><\/p>\n<p> <\/p>\n<p>\u041f\u0440\u043e\u0441\u0442\u0435\u0439\u0448\u0438\u043c \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0438 \u0444\u0430\u0439\u043b\u043e\u0432 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432\u043e\u0437\u0432\u0440\u0430\u0442 \u0443\u0440\u043b\u043e\u0432 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0442\u0440\u0435\u0431\u0443\u044e\u0442\u0441\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044e, \u0438\u0437 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0438 \u043c\u0443\u0442\u0430\u0446\u0438\u0439.<\/p>\n<p> <\/p>\n<p>\u0423\u0440\u043b \u0444\u0430\u0439\u043b\u0430 \u043c\u043e\u0436\u0435\u0442 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0438\u0437 \u0418\u0414 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <code>storage.getUrl<\/code>:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">import { query } from \".\/_generated\/server\";  export const list = query({   args: {},   handler: async (ctx) =&gt; {     const messages = await ctx.db.query(\"messages\").collect();     return Promise.all(       messages.map(async (message) =&gt; ({         ...message,         \/\/ \u0415\u0441\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043c, \u0435\u0433\u043e `body` - \u044d\u0442\u043e `Id&lt;\"_storage\"&gt;`         ...(message.format === \"image\"           ? { url: await ctx.storage.getUrl(message.body) }           : {}),       })),     );   }, });<\/code><\/pre>\n<p> <\/p>\n<p>\u0423\u0440\u043b\u044b \u0444\u0430\u0439\u043b\u043e\u0432 \u043c\u043e\u0433\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0432 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0445 <code>img<\/code> \u0434\u043b\u044f \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043d\u0433\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">function Image({ message }: { message: { url: string } }) {   return &lt;img src={message.url} height=\"300px\" width=\"auto\" \/&gt;; }<\/code><\/pre>\n<p> <\/p>\n<p><strong>\u041e\u0431\u0441\u043b\u0443\u0436\u0438\u0432\u0430\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u043e\u0432 \u0432 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f\u0445 HTTP<\/strong><\/p>\n<p> <\/p>\n<p>\u0424\u0430\u0439\u043b\u044b \u043c\u043e\u0433\u0443\u0442 \u043e\u0431\u0441\u043b\u0443\u0436\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u043f\u0440\u044f\u043c\u043e \u0432 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f\u0445 HTTP.<\/p>\n<p> <\/p>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u044f <code>storage.get<\/code> \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Blob\">Blob<\/a>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u0432 \u043e\u0442\u0432\u0435\u0442\u0435:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">import { httpRouter } from \"convex\/server\"; import { httpAction } from \".\/_generated\/server\"; import { Id } from \".\/_generated\/dataModel\";  const http = httpRouter();  http.route({   path: \"\/getImage\",   method: \"GET\",   handler: httpAction(async (ctx, request) =&gt; {     const { searchParams } = new URL(request.url);     const storageId = searchParams.get(\"storageId\")! as Id&lt;\"_storage\"&gt;;     const blob = await ctx.storage.get(storageId);     if (blob === null) {       return new Response(\"Image not found\", {         status: 404,       });     }     return new Response(blob);   }), });  export default http;<\/code><\/pre>\n<p> <\/p>\n<p>\u0423\u0440\u043b \u0442\u0430\u043a\u043e\u0439 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0435 <code>img<\/code> \u0434\u043b\u044f \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043d\u0433\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">const convexSiteUrl = import.meta.env.VITE_CONVEX_SITE_URL;  function Image({ storageId }: { storageId: string }) {   const getImageUrl = new URL(`${convexSiteUrl}\/getImage`);   getImageUrl.searchParams.set(\"storageId\", storageId);    return &lt;img src={getImageUrl.href} height=\"300px\" width=\"auto\" \/&gt;; }<\/code><\/pre>\n<p> <\/p>\n<h2 id=\"udalenie-faylov\">\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u043e\u0432<\/h2>\n<p> <\/p>\n<p>\u0424\u0430\u0439\u043b\u044b, \u0445\u0440\u0430\u043d\u044f\u0449\u0438\u0435\u0441\u044f \u0432 Convex, \u0443\u0434\u0430\u043b\u044f\u044e\u0442\u0441\u044f \u0432 \u043c\u0443\u0442\u0430\u0446\u0438\u044f\u0445 \u0438 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f\u0445 HTTP \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <code>storage.delete<\/code>, \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u044e\u0449\u0435\u0439 \u0418\u0414 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">import { v } from \"convex\/values\"; import { Id } from \".\/_generated\/dataModel\"; import { mutation } from \".\/_generated\/server\";  export const deleteById = mutation({   args: {     storageId: v.id(\"_storage\"),   },   handler: async (ctx, args) =&gt; {     return await ctx.storage.delete(args.storageId);   }, });<\/code><\/pre>\n<p> <\/p>\n<h2 id=\"metadannye-faylov\">\u041c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u0444\u0430\u0439\u043b\u043e\u0432<\/h2>\n<p> <\/p>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0439 \u0444\u0430\u0439\u043b \u043e\u0442\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 <code>\"_storage\"<\/code>. \u041c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u0444\u0430\u0439\u043b\u0430 \u043c\u043e\u0433\u0443\u0442 \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043c\u0438 \u0438 \u043c\u0443\u0442\u0430\u0446\u0438\u044f\u043c\u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0435\u0442\u043e\u0434\u043e\u0432 <code>db.system.get<\/code> \u0438 <code>db.system.query<\/code>:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">import { v } from \"convex\/values\"; import { query } from \".\/_generated\/server\";  export const getMetadata = query({   args: {     storageId: v.id(\"_storage\"),   },   handler: async (ctx, args) =&gt; {     return await ctx.db.system.get(args.storageId);   }, });  export const listAllFiles = query({   handler: async (ctx) =&gt; {     \/\/ \u0417\u0434\u0435\u0441\u044c \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c `.paginate()`     return await ctx.db.system.query(\"_storage\").collect();   }, });<\/code><\/pre>\n<p> <\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u043e\u0433\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430:<\/p>\n<p> <\/p>\n<pre><code class=\"json\">{   \"_creationTime\": 1700697415295.742,   \"_id\": \"3k7ty84apk2zy00ay4st1n5p9kh7tf8\",   \"contentType\": \"image\/jpeg\",   \"sha256\": \"cb58f529b2ed5a1b8b6681d91126265e919ac61fff6a367b8341c0f46b06a5bd\",   \"size\": 125338 }<\/code><\/pre>\n<p> <\/p>\n<ul>\n<li><code>sha256<\/code> \u2014 \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f \u0432 base16 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c\u043d\u0430\u044f \u0441\u0443\u043c\u043c\u0430 sha256 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430<\/li>\n<li><code>size<\/code> \u2014 \u0440\u0430\u0437\u043c\u0435\u0440 \u0444\u0430\u0439\u043b\u0430 \u0432 \u0431\u0430\u0439\u0442\u0430\u0445<\/li>\n<li><code>contentType<\/code> \u2014 \u0442\u0438\u043f \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430 (<code>ContentType<\/code>) \u0444\u0430\u0439\u043b\u0430, \u0435\u0441\u043b\u0438 \u043e\u043d \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u043b\u0441\u044f \u043f\u0440\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435<\/li>\n<\/ul>\n<p> <\/p>\n<h1 id=\"iskusstvennyy-intellekt-i-poisk\">\u0418\u0441\u043a\u0443\u0441\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u043b\u043b\u0435\u043a\u0442 \u0438 \u043f\u043e\u0438\u0441\u043a<\/h1>\n<p> <\/p>\n<p>Convex \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043f\u0440\u043e\u0441\u0442\u044b\u0435 \u0430\u043f\u0438 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0418\u0418 \u0438 \u043f\u043e\u0438\u0441\u043a\u0430.<\/p>\n<p> <\/p>\n<p>\u0412\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a (vector search) \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0438\u0441\u043a\u0430\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0441\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f. \u041e\u043d \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0435 \u0432\u043b\u043e\u0436\u0435\u043d\u0438\u044f (vector embeddings) \u0434\u043b\u044f \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u0445\u043e\u0436\u0435\u0441\u0442\u0438 \u0438 \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u044f \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432, \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u043f\u043e\u0434 \u0437\u0430\u043f\u0440\u043e\u0441. \u0412\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432\u0430\u0436\u043d\u043e\u0439 \u0447\u0430\u0441\u0442\u044c\u044e \u0442\u0435\u0445\u043d\u0438\u043a, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0445 \u043f\u0440\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0418\u0418, \u0442\u0430\u043a\u0438\u0445 \u043a\u0430\u043a <a href=\"https:\/\/habr.com\/ru\/articles\/779526\/\">RAG<\/a>.<\/p>\n<p> <\/p>\n<p>\u041f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439 \u043f\u043e\u0438\u0441\u043a (full text search) \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0438\u0441\u043a\u0430\u0442\u044c \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0445 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0441\u043b\u043e\u0432\u0430 \u0438 \u0444\u0440\u0430\u0437\u044b. \u041e\u043d \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u043a\u0430\u043a \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043d\u043e\u0435, \u0442\u0430\u043a \u0438 \u043d\u0435\u0442\u043e\u0447\u043d\u043e\u0435 (fuzzy) \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u044f. \u041f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439 \u043f\u043e\u0438\u0441\u043a, \u043a\u0430\u043a \u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u044b, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u043c \u0438 \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u0441\u0435\u0433\u0434\u0430 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u043c.<\/p>\n<p> <\/p>\n<p>\u041e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 Convex \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0442 \u043e\u0431\u0440\u0430\u0449\u0430\u0442\u044c\u0441\u044f \u043a \u0430\u043f\u0438 \u0418\u0418, \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u0411\u0414 \u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c UI.<\/p>\n<p> <\/p>\n<h2 id=\"vektornyy-poisk\">\u0412\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a<\/h2>\n<p> <\/p>\n<p>\u0412\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0438\u0441\u043a\u0430\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b, \u043f\u043e\u0445\u043e\u0436\u0438\u0435 \u043d\u0430 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u044b\u0439 \u0432\u0435\u043a\u0442\u043e\u0440. \u041a\u0430\u043a \u043f\u0440\u0430\u0432\u0438\u043b\u043e, \u0432\u0435\u043a\u0442\u043e\u0440\u0430\u043c\u0438 \u0431\u0443\u0434\u0443\u0442 \u0432\u043b\u043e\u0436\u0435\u043d\u0438\u044f (embeddings) \u2014 \u0447\u0438\u0441\u043b\u043e\u0432\u044b\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u0435\u043a\u0441\u0442\u0430, \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439 \u0438\u043b\u0438 \u0430\u0443\u0434\u0438\u043e.<\/p>\n<p> <\/p>\n<p>\u0412\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0442 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0434\u043b\u044f \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u044f\u0437\u044b\u043a\u043e\u0432\u044b\u0445 \u043c\u043e\u0434\u0435\u043b\u0435\u0439 (large language models, LLM) \u0434\u043b\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0441 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u043e\u0439 \u0418\u0418, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0441\u0438\u0441\u0442\u0435\u043c \u0438 \u0442.\u043f.<\/p>\n<p> <\/p>\n<p>\u0412\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u0432\u0441\u0435\u0433\u0434\u0430 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0441\u043e\u0433\u043b\u0430\u0441\u043e\u0432\u0430\u043d\u043d\u044b\u043c \u0438 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u043c. \u041c\u044b \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0432\u0435\u043a\u0442\u043e\u0440 \u0438 \u0441\u0440\u0430\u0437\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0435\u0433\u043e \u0432 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u043e\u043c \u043f\u043e\u0438\u0441\u043a\u0435. \u041e\u0434\u043d\u0430\u043a\u043e, \u0432 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 \u043f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430, \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f\u0445.<\/p>\n<p> <\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/get-convex\/convex-demos\/tree\/main\/vector-search\">\u041f\u0440\u0438\u043c\u0435\u0440<\/a><\/li>\n<\/ul>\n<p> <\/p>\n<p>\u0414\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<p> <\/p>\n<ol>\n<li>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0439 \u0438\u043d\u0434\u0435\u043a\u0441.<\/li>\n<li>\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u0432 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438.<\/li>\n<\/ol>\n<p> <\/p>\n<p><strong>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u0430<\/strong><\/p>\n<p> <\/p>\n<p>\u041a\u0430\u043a \u0438 \u0438\u043d\u0434\u0435\u043a\u0441\u044b \u0411\u0414, \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u044b \u2014 \u044d\u0442\u043e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u0434\u0430\u043d\u043d\u044b\u0445, \u043f\u0440\u0435\u0434\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430. \u0412\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0442\u0441\u044f \u043a\u0430\u043a \u0447\u0430\u0441\u0442\u044c \u0441\u0445\u0435\u043c\u044b.<\/p>\n<p> <\/p>\n<p>\u0414\u043b\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u0430 \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434 <code>vectorIndex<\/code>. \u041a\u0430\u0436\u0434\u044b\u0439 \u0438\u043d\u0434\u0435\u043a\u0441 \u0438\u043c\u0435\u0435\u0442 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435, \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0435\u0435:<\/p>\n<p> <\/p>\n<ul>\n<li><code>vectorField: string<\/code> \u2014 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u043e\u043b\u044f, \u0438\u043d\u0434\u0435\u043a\u0441\u0438\u0440\u0443\u0435\u043c\u043e\u0433\u043e \u0434\u043b\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430<\/li>\n<li><code>dimensions: number<\/code> \u2014 \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0440\u0430\u0437\u043c\u0435\u0440 \u0438\u043d\u0434\u0435\u043a\u0441\u0430 \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u0432. \u041f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u0432\u043b\u043e\u0436\u0435\u043d\u0438\u0439, \u044d\u0442\u043e\u0442 \u0440\u0430\u0437\u043c\u0435\u0440 \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0442\u044c \u0441 \u0438\u0445 \u0440\u0430\u0437\u043c\u0435\u0440\u043e\u043c (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, <code>1536<\/code> \u0434\u043b\u044f OpenAI)<\/li>\n<li><code>filterFields?: string[]<\/code> \u2014 \u043c\u0430\u0441\u0441\u0438\u0432 \u043f\u043e\u043b\u0435\u0439, \u0438\u043d\u0434\u0435\u043a\u0441\u0438\u0440\u0443\u0435\u043c\u044b\u0445 \u0434\u043b\u044f \u0431\u044b\u0441\u0442\u0440\u043e\u0439 \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u0430<\/li>\n<\/ul>\n<p> <\/p>\n<p>\u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0435\u0441\u043b\u0438 \u043c\u044b \u0445\u043e\u0442\u0438\u043c \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0438\u043d\u0434\u0435\u043a\u0441 \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u0435\u0434\u044b \u043f\u043e\u0445\u043e\u0436\u0435\u0439 \u043a\u0443\u0445\u043d\u0438, \u043d\u0430\u0448\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0442\u0430\u043a:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">foods: defineTable({   description: v.string(),   cuisine: v.string(),   embedding: v.array(v.float64()), }).vectorIndex(\"by_embedding\", {   vectorField: \"embedding\",   dimensions: 1536,   filterFields: [\"cuisine\"], }),<\/code><\/pre>\n<p> <\/p>\n<p>\u0412\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0435 \u0438 \u0444\u0438\u043b\u044c\u0442\u0440\u0443\u0435\u043c\u044b\u0435 \u043f\u043e\u043b\u044f \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0445 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u043c\u043e\u0433\u0443\u0442 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0442\u043e\u0447\u043a\u0438 \u2014 <code>properties.name<\/code>.<\/p>\n<p> <\/p>\n<p><strong>\u0417\u0430\u043f\u0443\u0441\u043a \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430<\/strong><\/p>\n<p> <\/p>\n<p>\u0412 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0438\u043b\u0438 \u043f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430, \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f\u0445.<\/p>\n<p> <\/p>\n<p>\u042d\u0442\u043e \u043e\u0431\u044b\u0447\u043d\u043e \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0432 \u0441\u0435\u0431\u044f 3 \u044d\u0442\u0430\u043f\u0430:<\/p>\n<p> <\/p>\n<ol>\n<li>\u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u0430 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e OpenAI).<\/li>\n<li>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043c\u0435\u0442\u043e\u0434\u0430 <code>ctx.vectorSearch<\/code> \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0418\u0414 \u043f\u043e\u0445\u043e\u0436\u0438\u0445 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432.<\/li>\n<li>\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432.<\/li>\n<\/ol>\n<p> <\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440 \u043f\u0435\u0440\u0432\u044b\u0445 \u0434\u0432\u0443\u0445 \u044d\u0442\u0430\u043f\u043e\u0432 \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u043f\u043e\u0445\u043e\u0436\u0435\u0439 \u0444\u0440\u0430\u043d\u0446\u0443\u0437\u0441\u043a\u043e\u0439 \u0435\u0434\u044b \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">\/\/  convex\/foods.ts import { v } from \"convex\/values\"; import { action } from \".\/_generated\/server\";  export const similarFoods = action({   args: {     descriptionQuery: v.string(),   },   handler: async (ctx, args) =&gt; {     \/\/ 1. \u0413\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u0432\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0435\u0433\u043e \u0430\u043f\u0438     const embedding = await embed(args.descriptionQuery);     \/\/ 2. \u0418\u0449\u0435\u043c \u043f\u043e\u0445\u043e\u0436\u0443\u044e \u0435\u0434\u0443     const results = await ctx.vectorSearch(\"foods\", \"by_embedding\", {       vector: embedding,       limit: 16,       filter: (q) =&gt; q.eq(\"cuisine\", \"French\"),     });     \/\/ ...   }, });<\/code><\/pre>\n<p> <\/p>\n<p><code>vectorSearch()<\/code> \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b, \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u0430 \u0438 \u043e\u0431\u044a\u0435\u043a\u0442 <a href=\"https:\/\/docs.convex.dev\/api\/interfaces\/server.VectorSearchQuery\">VectorSearchQuery<\/a>, \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0449\u0438\u0439 \u043f\u043e\u0438\u0441\u043a. \u042d\u0442\u043e\u0442 \u043e\u0431\u044a\u0435\u043a\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u043f\u043e\u043b\u044f:<\/p>\n<p> <\/p>\n<ul>\n<li><code>vector: number[]<\/code> \u2014 \u043c\u0430\u0441\u0441\u0438\u0432 \u0447\u0438\u0441\u043b\u0435 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0432\u043b\u043e\u0436\u0435\u043d\u0438\u0439) \u0434\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0432 \u043f\u043e\u0438\u0441\u043a\u0435<\/li>\n<li><code>limit?: number<\/code> \u2014 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u044b\u0445 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 (\u043e\u0442 1 \u0434\u043e 256)<\/li>\n<li><code>filter?: Function<\/code> \u2014 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435, \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0438\u0432\u0430\u044e\u0449\u0435\u0435 \u043d\u0430\u0431\u043e\u0440 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 <code>filterFields<\/code> \u0432 <code>vectorIndex()<\/code> \u0432 \u0441\u0445\u0435\u043c\u0435<\/li>\n<\/ul>\n<p> <\/p>\n<p><code>vectorSearch()<\/code> \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043c\u0430\u0441\u0441\u0438\u0432 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0441 \u0434\u0432\u0443\u043c\u044f \u043f\u043e\u043b\u044f\u043c\u0438:<\/p>\n<p> <\/p>\n<ul>\n<li><code>_id<\/code> \u2014 \u0418\u0414 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430<\/li>\n<li><code>_score<\/code> \u2014 \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u043f\u043e\u0445\u043e\u0436\u0435\u0441\u0442\u0438 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 (\u043e\u0442 -1 \u0434\u043e 1)<\/li>\n<\/ul>\n<p> <\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">\/\/ convex\/foods.ts export const fetchResults = internalQuery({   args: { ids: v.array(v.id(\"foods\")) },   handler: async (ctx, args) =&gt; {     const results = [];     for (const id of args.ids) {       const doc = await ctx.db.get(id);       if (doc === null) {         continue;       }       results.push(doc);     }     return results;   }, });  export const similarFoods = action({   args: {     descriptionQuery: v.string(),   },   handler: async (ctx, args) =&gt; {     const embedding = await embed(args.descriptionQuery);     const results = await ctx.vectorSearch(\"foods\", \"by_embedding\", {       vector: embedding,       limit: 16,       filter: (q) =&gt; q.eq(\"cuisine\", \"French\"),     });     \/\/ 3. \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b     const foods: Array&lt;Doc&lt;\"foods\"&gt;&gt; = await ctx.runQuery(       internal.foods.fetchResults,       { ids: results.map((result) =&gt; result._id) },     );     return foods;   }, });<\/code><\/pre>\n<p> <\/p>\n<p><strong>\u0424\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u044f<\/strong><\/p>\n<p> <\/p>\n<p>\u0424\u0438\u043b\u044c\u0442\u0440 \u0444\u0440\u0430\u043d\u0446\u0443\u0437\u0441\u043a\u043e\u0439 \u0435\u0434\u044b:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">filter: (q) =&gt; q.eq(\"cuisine\", \"French\"),<\/code><\/pre>\n<p> <\/p>\n<p>\u0424\u0438\u043b\u044c\u0442\u0440 \u0444\u0440\u0430\u043d\u0446\u0443\u0437\u0441\u043a\u0438\u0445 \u0418\u041b\u0418 \u0438\u043d\u0434\u043e\u043d\u0435\u0437\u0438\u0439\u0441\u043a\u0438\u0445 \u0431\u043b\u044e\u0434:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">filter: (q) =&gt;   q.or(q.eq(\"cuisine\", \"French\"), q.eq(\"cuisine\", \"Indonesian\")),<\/code><\/pre>\n<p> <\/p>\n<p>\u0424\u0438\u043b\u044c\u0442\u0440 \u0444\u0440\u0430\u043d\u0446\u0443\u0437\u0441\u043a\u0438\u0445 \u0431\u043b\u044e\u0434, \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u043c \u0438\u043d\u0433\u0440\u0435\u0434\u0438\u0435\u043d\u0442\u043e\u043c \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043c\u0430\u0441\u043b\u043e:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">filter: (q) =&gt;   q.or(q.eq(\"cuisine\", \"French\"), q.eq(\"mainIngredient\", \"butter\")),<\/code><\/pre>\n<p> <\/p>\n<p>\u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 <code>cuisine<\/code> \u0438 <code>mainIngredient<\/code> \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u0432 <code>filterFields<\/code> \u0432 <code>vectorIndex()<\/code>.<\/p>\n<p> <\/p>\n<p><strong>\u0421\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0430<\/strong><\/p>\n<p> <\/p>\n<p>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0432\u0441\u0435\u0433\u0434\u0430 \u0441\u043e\u0440\u0442\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u043f\u043e \u043f\u043e\u0445\u043e\u0436\u0435\u0441\u0442\u0438.<\/p>\n<p> <\/p>\n<p>\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u0441 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u043c <code>_score<\/code> \u0441\u043e\u0440\u0442\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u043f\u043e \u0418\u0414.<\/p>\n<p> <\/p>\n<p><strong>\u041f\u0440\u043e\u0434\u0432\u0438\u043d\u0443\u0442\u044b\u0435 \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u044b<\/strong><\/p>\n<p> <\/p>\n<p><em>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u0432<\/em><\/p>\n<p> <\/p>\n<p>\u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 2 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u044b \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u0430:<\/p>\n<p> <\/p>\n<ol>\n<li>\u0425\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u0432 \u0432 \u0442\u043e\u0439 \u0436\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u0435, \u0447\u0442\u043e \u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435.<\/li>\n<li>\u0425\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0432\u0435\u043a\u0442\u043e\u0440\u043e\u0432 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 \u0441\u043e \u0441\u0441\u044b\u043b\u043a\u0430\u043c\u0438.<\/li>\n<\/ol>\n<p> <\/p>\n<p>\u041c\u044b \u0443\u0436\u0435 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043b\u0438 \u043f\u0435\u0440\u0432\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442. \u041e\u043d \u043f\u0440\u043e\u0449\u0435 \u0438 \u0445\u043e\u0440\u043e\u0448\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043f\u0440\u0438 \u0447\u0442\u0435\u043d\u0438\u0438 \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432. \u0412\u0442\u043e\u0440\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0431\u043e\u043b\u0435\u0435 \u0441\u043b\u043e\u0436\u043d\u044b\u043c, \u043d\u043e \u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u043c \u043f\u0440\u0438 \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 \u0431\u043e\u043b\u044c\u0448\u0438\u043c \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e\u043c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432.<\/p>\n<p> <\/p>\n<p>\u0422\u0430\u0431\u043b\u0438\u0446\u0430 \u0434\u043b\u044f \u0444\u0438\u043b\u044c\u043c\u043e\u0432 \u0438 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0439 \u0438\u043d\u0434\u0435\u043a\u0441, \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0449\u0438\u0439 \u043f\u043e\u0438\u0441\u043a \u043f\u043e\u0445\u043e\u0436\u0438\u0445 \u0444\u0438\u043b\u044c\u043c\u043e\u0432, \u0444\u0438\u043b\u044c\u0442\u0440\u0443\u0435\u043c\u044b\u0445 \u043f\u043e \u0436\u0430\u043d\u0440\u0443, \u043c\u043e\u0433\u0443\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0442\u0430\u043a:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">movieEmbeddings: defineTable({   embedding: v.array(v.float64()),   genre: v.string(), }).vectorIndex(\"by_embedding\", {   vectorField: \"embedding\",   dimensions: 1536,   filterFields: [\"genre\"], }), movies: defineTable({   title: v.string(),   genre: v.string(),   description: v.string(),   votes: v.number(),   embeddingId: v.optional(v.id(\"movieEmbeddings\")), }).index(\"by_embedding\", [\"embeddingId\"]),<\/code><\/pre>\n<p> <\/p>\n<p>\u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0432\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430 \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044e \u043e\u0434\u043d\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u044b. \u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">export const fetchMovies = query({   args: {     ids: v.array(v.id(\"movieEmbeddings\")),   },   handler: async (ctx, args) =&gt; {     const results = [];     for (const id of args.ids) {       const doc = await ctx.db         .query(\"movies\")         .withIndex(\"by_embedding\", (q) =&gt; q.eq(\"embeddingId\", id))         .unique();       if (doc === null) {         continue;       }       results.push(doc);     }     return results;   }, });<\/code><\/pre>\n<p> <\/p>\n<p>\u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044e \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u0438 \u0437\u0430\u043f\u0440\u043e\u0441 \u0438\u043b\u0438 \u043c\u0443\u0442\u0430\u0446\u0438\u044e \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0434\u0430\u043d\u043d\u044b\u0445.<\/p>\n<p> <\/p>\n<p>\u0414\u0430\u043d\u043d\u044b\u0435, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u044b\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0435\u0439, \u043d\u0435 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u043c\u0438. \u0420\u0435\u0448\u0435\u043d\u0438\u0435\u043c \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0432\u043e\u0437\u0432\u0440\u0430\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430 \u0438\u0437 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0437\u0430\u043f\u0440\u043e\u0441\u0430. \u0412 \u044d\u0442\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u043f\u043e\u0438\u0441\u043a\u0430 \u043d\u0435 \u0431\u0443\u0434\u0443\u0442 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438, \u0430 \u0434\u0430\u043d\u043d\u044b\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 \u2014 \u0431\u0443\u0434\u0443\u0442.<\/p>\n<p> <\/p>\n<p><strong>\u041b\u0438\u043c\u0438\u0442\u044b<\/strong><\/p>\n<p> <\/p>\n<p>\u0412\u0435\u043a\u0442\u043e\u0440\u043d\u044b\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c:<\/p>\n<p> <\/p>\n<ul>\n<li>\u0440\u043e\u0432\u043d\u043e 1 \u043f\u043e\u043b\u0435 \u0434\u043b\u044f \u0432\u0435\u043a\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430<br \/> \n<ul>\n<li>\u043e\u043d\u043e \u0434\u043e\u043b\u0436\u043d\u043e \u0438\u043c\u0435\u0442\u044c \u0442\u0438\u043f <code>v.array(v.float64)<\/code> (\u0438\u043b\u0438 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435\u043c \u0441 \u0442\u0430\u043a\u0438\u043c \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u043c)<\/li>\n<\/ul>\n<\/li>\n<li>\u0440\u043e\u0432\u043d\u043e 1 \u043f\u043e\u043b\u0435 <code>dimension<\/code> \u0441\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043c \u043c\u0435\u0436\u0434\u0443 2 \u0438 4096<\/li>\n<li>\u0434\u043e 16 \u0444\u0438\u043b\u044c\u0442\u0440\u0443\u0435\u043c\u044b\u0445 \u043f\u043e\u043b\u0435\u0439<\/li>\n<\/ul>\n<p> <\/p>\n<h2 id=\"polnotekstovyy-poisk\">\u041f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439 \u043f\u043e\u0438\u0441\u043a<\/h2>\n<p> <\/p>\n<p>\u041f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0438\u0441\u043a\u0430\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u044e\u0442 \u0441 \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u043c.<\/p>\n<p> <\/p>\n<p>\u0412 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 \u043e\u0431\u044b\u0447\u043d\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432, \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u0441\u043a\u0430\u043d\u0438\u0440\u0443\u044e\u0442 \u0441\u0442\u0440\u043e\u043a\u043e\u0432\u044b\u0435 \u043f\u043e\u043b\u044f \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0445 \u0441\u043b\u043e\u0432. \u042d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u043c \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u043e\u0433\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u043e\u0438\u0441\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043f\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u043c \u0441\u043b\u043e\u0432\u0430\u043c.<\/p>\n<p> <\/p>\n<p>\u041f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u043c\u0438, \u0441\u043e\u0433\u043b\u0430\u0441\u043e\u0432\u0430\u043d\u043d\u044b\u043c\u0438, \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u043e\u043d\u043d\u044b\u043c\u0438 \u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442 \u043f\u0430\u0433\u0438\u043d\u0430\u0446\u0438\u044e. \u0418\u0445 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0434\u0430\u0436\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043d\u043e\u0432\u044b\u0445 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0443\u0442\u0430\u0446\u0438\u0439.<\/p>\n<p> <\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/get-convex\/convex-demos\/tree\/main\/search\">\u041f\u0440\u0438\u043c\u0435\u0440<\/a><\/li>\n<\/ul>\n<p> <\/p>\n<p>\u0414\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430 \u043d\u0443\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<p> <\/p>\n<ol>\n<li>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0439 \u0438\u043d\u0434\u0435\u043a\u0441.<\/li>\n<li>\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441.<\/li>\n<\/ol>\n<p> <\/p>\n<p><em>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435<\/em>, \u0447\u0442\u043e \u043f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u0432 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u0439 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e. \u042d\u0442\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u043a\u043e\u0434 \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0435\u0442\u0435\u0440\u043f\u0435\u0442\u044c \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f.<\/p>\n<p> <\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 <em>\u043e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435<\/em>, \u0447\u0442\u043e \u043f\u043e\u043a\u0430 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0439 \u044f\u0437\u044b\u043a. \u0412 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u0431\u0443\u0434\u0443\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u044f\u0437\u044b\u043a\u0438.<\/p>\n<p> <\/p>\n<p><strong>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u043e\u0433\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u0430<\/strong><\/p>\n<p> <\/p>\n<p>\u041a\u0430\u043a \u0438 \u043e\u0431\u044b\u0447\u043d\u044b\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u044b, \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u044b \u2014 \u044d\u0442\u043e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u0434\u0430\u043d\u043d\u044b\u0445, \u043f\u0440\u0435\u0434\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432. \u041e\u043d\u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0442\u0441\u044f \u043a\u0430\u043a \u0447\u0430\u0441\u0442\u044c \u0441\u0445\u0435\u043c\u044b.<\/p>\n<p> <\/p>\n<p>\u041a\u0430\u0436\u0434\u043e\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u0430 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437:<\/p>\n<p> <\/p>\n<ul>\n<li><code>name: string<\/code> \u2014 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u043c \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u0442\u0430\u0431\u043b\u0438\u0446\u044b<\/li>\n<li><code>searchField: string<\/code> \u2014 \u043f\u043e\u043b\u0435, \u0438\u043d\u0434\u0435\u043a\u0441\u0438\u0440\u0443\u0435\u043c\u043e\u0435 \u0434\u043b\u044f \u043f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430<\/li>\n<li><code>filterFields: string[]<\/code> \u2014 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u043e\u043b\u044f \u0434\u043b\u044f \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438<\/li>\n<\/ul>\n<p> <\/p>\n<p>\u0414\u043b\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u043e\u0433\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u0430 \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434 <code>searchIndex<\/code>. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0438\u043d\u0434\u0435\u043a\u0441 \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0432 \u043a\u0430\u043d\u0430\u043b\u0435, \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0438\u0445 \u043a\u043b\u044e\u0447\u0435\u0432\u043e\u0435 \u0441\u043b\u043e\u0432\u043e, \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0442\u0430\u043a:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">import { defineSchema, defineTable } from \"convex\/server\"; import { v } from \"convex\/values\";  export default defineSchema({   messages: defineTable({     body: v.string(),     channel: v.string(),   }).searchIndex(\"search_body\", {     searchField: \"body\",     filterFields: [\"channel\"],   }), });<\/code><\/pre>\n<p> <\/p>\n<p>\u041f\u043e\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u0438 \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438 \u0432\u043e \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0445 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0445 \u043c\u043e\u0433\u0443\u0442 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0442\u043e\u0447\u043a\u0438 \u2014 <code>properties.name<\/code>.<\/p>\n<p> <\/p>\n<p><strong>\u0417\u0430\u043f\u0443\u0441\u043a \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430<\/strong><\/p>\n<p> <\/p>\n<p>\u0417\u0430\u043f\u0440\u043e\u0441 10 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0432 \u043a\u0430\u043d\u0430\u043b\u0435 <code>#general<\/code>, \u043b\u0443\u0447\u0448\u0435 \u0432\u0441\u0435\u0433\u043e \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u044e\u0449\u0438\u0445 \u0441 <code>hello hi<\/code> \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0442\u0430\u043a:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">const messages = await ctx.db   .query(\"messages\")   .withSearchIndex(\"search_body\", (q) =&gt;     q.search(\"body\", \"hello hi\").eq(\"channel\", \"#general\"),   )   .take(10);<\/code><\/pre>\n<p> <\/p>\n<p>\u041c\u0435\u0442\u043e\u0434 <code>withSearchIndex<\/code> \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442, \u043a\u0430\u043a\u043e\u0439 \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0439 \u0438\u043d\u0434\u0435\u043a\u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043b\u044f \u0432\u044b\u0431\u043e\u0440\u043a\u0438 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432. \u041f\u0435\u0440\u0432\u044b\u0439 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442 \u2014 \u044d\u0442\u043e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u0430, \u0432\u0442\u043e\u0440\u043e\u0439 \u2014 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u043e\u0433\u043e \u0444\u0438\u043b\u044c\u0442\u0440\u0430 (search filter expression). \u0412\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u043e\u0433\u043e \u0444\u0438\u043b\u044c\u0442\u0440\u0430 \u2014 \u044d\u0442\u043e \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0442\u043e\u0433\u043e, \u043a\u0430\u043a\u0438\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b Convex \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0440\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430.<\/p>\n<p> <\/p>\n<p>\u0412\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u043e\u0433\u043e \u0444\u0438\u043b\u044c\u0442\u0440\u0430 \u2014 \u044d\u0442\u043e \u0432\u0441\u0435\u0433\u0434\u0430 \u0446\u0435\u043f\u043e\u0447\u043a\u0430 \u0438\u0437:<\/p>\n<p> <\/p>\n<ul>\n<li>\u043e\u0434\u043d\u043e\u0433\u043e \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043f\u043e\u0438\u0441\u043a\u0430 (search expression) \u0434\u043b\u044f \u0432\u044b\u0431\u043e\u0440\u0430 \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u043e\u0433\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u0430<\/li>\n<li>0 \u0438\u043b\u0438 \u0431\u043e\u043b\u0435\u0435 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0439 \u0440\u0430\u0432\u0435\u043d\u0441\u0442\u0432\u0430 (equality expressions) \u0434\u043b\u044f \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432<\/li>\n<\/ul>\n<p> <\/p>\n<p><em>\u0412\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043f\u043e\u0438\u0441\u043a\u0430<\/em><\/p>\n<p> <\/p>\n<p>\u0412\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u0432\u044b\u0431\u0438\u0440\u0430\u044e\u0442 \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u044b, \u0444\u0438\u043b\u044c\u0442\u0440\u0443\u044e\u0442 \u0438 \u0440\u0430\u043d\u0436\u0438\u0440\u0443\u044e\u0442 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u043f\u043e \u0438\u0445 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u044e \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u043e\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443. Convex \u0440\u0430\u0437\u0431\u0438\u0432\u0430\u0435\u0442 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043f\u043e\u0438\u0441\u043a\u0430 \u043d\u0430 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u0441\u043b\u043e\u0432\u0430 (\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0430\u0437\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043c\u0438 (terms)) \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u043d\u0430 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0435 \u0438\u043c.<\/p>\n<p> <\/p>\n<p>\u0412 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u043d\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435 <code>\"hello hi\"<\/code> \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0437\u0431\u0438\u0442\u043e \u043d\u0430 <code>\"hi\"<\/code> \u0438 <code>\"hello\"<\/code>.<\/p>\n<p> <\/p>\n<p><em>\u0412\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0440\u0430\u0432\u0435\u043d\u0441\u0442\u0432\u0430<\/em><\/p>\n<p> <\/p>\n<p>\u0412 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0439 \u043f\u043e\u0438\u0441\u043a\u0430, \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0440\u0430\u0432\u0435\u043d\u0441\u0442\u0432\u0430 \u0442\u0440\u0435\u0431\u0443\u044e\u0442 \u0442\u043e\u0447\u043d\u043e\u0433\u043e \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u044f \u0441 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u043c \u043f\u043e\u043b\u0435\u043c. \u0412 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u043d\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435 <code>eq(\"channel\", \"#general\")<\/code> \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0431\u0438\u0440\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b, \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0438\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 <code>\"#general\"<\/code> \u0432 \u043f\u043e\u043b\u0435 <code>channel<\/code>.<\/p>\n<p> <\/p>\n<p>\u0412\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0440\u0430\u0432\u0435\u043d\u0441\u0442\u0432\u0430 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442 \u043f\u043e\u043b\u044f \u043b\u044e\u0431\u044b\u0445 \u0442\u0438\u043f\u043e\u0432 (\u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0435).<\/p>\n<p> <\/p>\n<p>\u0414\u043b\u044f \u0432\u044b\u0431\u043e\u0440\u0430 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0441 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u043c \u043f\u043e\u043b\u0435\u043c \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <code>q.eq(\"fieldName\", undefined)<\/code>.<\/p>\n<p> <\/p>\n<p>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u043d\u043e \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432\u0430\u0442\u044c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0435\u0442\u043e\u0434\u0430 <code>filter<\/code>. \u041f\u0440\u0438\u043c\u0435\u0440 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439, \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0438\u0445 <code>\"hi\"<\/code>, \u0437\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 10 \u043c\u0438\u043d:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">const messages = await ctx.db   .query(\"messages\")   .withSearchIndex(\"search_body\", (q) =&gt; q.search(\"body\", \"hi\"))   .filter((q) =&gt; q.gt(q.field(\"_creationTime\", Date.now() - 10 * 60000)))   .take(10);<\/code><\/pre>\n<p> <\/p>\n<p><em>\u0418\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u0435 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u0438 \u043f\u0430\u0433\u0438\u043d\u0430\u0446\u0438\u044f<\/em><\/p>\n<p> <\/p>\n<p>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432, \u043a\u0430\u043a \u0438 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u043e\u0431\u044b\u0447\u043d\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432, \u043c\u043e\u0433\u0443\u0442 \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0442\u044c\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0435\u0442\u043e\u0434\u043e\u0432 <code>collect()<\/code>, <code>take(n)<\/code>, <code>first()<\/code> \u0438 <code>unique()<\/code>.<\/p>\n<p> <\/p>\n<p>\u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u043c\u043e\u0433\u0443\u0442 \u043f\u0430\u0433\u0438\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <code>paginate(paginationOpts)<\/code>.<\/p>\n<p> <\/p>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e <code>collect()<\/code> \u0432\u044b\u0431\u0440\u043e\u0441\u0438\u0442 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043f\u0440\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u0438\u0437\u0432\u043b\u0435\u0447\u044c \u0431\u043e\u043b\u044c\u0448\u0435 1024 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432. \u041b\u0443\u0447\u0448\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <code>take(n)<\/code> \u0438\u043b\u0438 \u043f\u0430\u0433\u0438\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b.<\/p>\n<p> <\/p>\n<p><strong>\u0421\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0430<\/strong><\/p>\n<p> <\/p>\n<p>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0432\u0441\u0435\u0433\u0434\u0430 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u044e\u0442\u0441\u044f \u0432 \u043f\u043e\u0440\u044f\u0434\u043a\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u044f \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0435.<\/p>\n<p> <\/p>\n<p><strong>\u041f\u043e\u0438\u0441\u043a<\/strong><\/p>\n<p> <\/p>\n<p><em>\u041d\u0435\u0442\u043e\u0447\u043d\u044b\u0439 \u0438 \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a<\/em><\/p>\n<p> <\/p>\n<p>\u041f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439 \u043f\u043e\u0438\u0441\u043a Convex \u0441\u043f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d \u0434\u043b\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 \u043f\u043e\u0438\u0441\u043a\u0430 \u043f\u043e \u043c\u0435\u0440\u0435 \u0432\u0432\u043e\u0434\u0430. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043a \u0438\u0441\u043a\u043e\u043c\u044b\u043c \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043c \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u044e\u0442\u0441\u044f \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u043d\u0435\u0442\u043e\u0447\u043d\u043e\u0433\u043e \u0438 \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043d\u043e\u0433\u043e \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u044f. \u042d\u0442\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u043e\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443, \u043c\u043e\u0433\u0443\u0442 \u043d\u0435\u0442\u043e\u0447\u043d\u043e \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0442\u044c \u0441 \u0438\u0441\u043a\u043e\u043c\u044b\u043c\u0438 \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043c\u0438.<\/p>\n<p> <\/p>\n<p>\u0412 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0434\u043b\u0438\u043d\u044b \u0442\u0435\u0440\u043c\u0438\u043d\u0430, \u0434\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0435 \u0447\u0438\u0441\u043b\u043e \u043e\u043f\u0435\u0447\u0430\u0442\u043e\u043a \u0432 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u044f\u0445. \u041e\u043f\u0435\u0447\u0430\u0442\u043a\u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%A0%D0%B0%D1%81%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5_%D0%9B%D0%B5%D0%B2%D0%B5%D0%BD%D1%88%D1%82%D0%B5%D0%B9%D0%BD%D0%B0\">\u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u041b\u0435\u0432\u0435\u043d\u0448\u0442\u0435\u0439\u043d\u0430<\/a>. \u041f\u0440\u0430\u0432\u0438\u043b\u0430 \u0442\u0435\u0440\u043f\u0438\u043c\u043e\u0441\u0442\u0438 \u043a \u043e\u043f\u0435\u0447\u0430\u0442\u043a\u0430\u043c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435:<\/p>\n<p> <\/p>\n<ul>\n<li>\u0432 \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u0445, \u0434\u043b\u0438\u043d\u043e\u0439 <code>&lt;=<\/code> 4, \u043d\u0435 \u0434\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043e\u043f\u0435\u0447\u0430\u0442\u043e\u043a<\/li>\n<li>\u0432 \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u0445, \u0434\u043b\u0438\u043d\u043e\u0439 <code>&lt;<\/code> 5 <code>&lt;=<\/code> 8, \u0434\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f 1 \u043e\u043f\u0435\u0447\u0430\u0442\u043a\u0430<\/li>\n<li>\u0432 \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u0445, \u0434\u043b\u0438\u043d\u043e\u0439 <code>&gt;<\/code> 8, \u0434\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f 2 \u043e\u043f\u0435\u0447\u0430\u0442\u043a\u0438<\/li>\n<\/ul>\n<p> <\/p>\n<p>\u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435 <code>search(\"body\", \"hello something\")<\/code> \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0442\u044c \u0441\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c\u0438 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043c\u0438:<\/p>\n<p> <\/p>\n<ul>\n<li><code>\"hillo\"<\/code><\/li>\n<li><code>\"somethan\"<\/code><\/li>\n<li><code>\"hallo somethan\"<\/code><\/li>\n<li><code>\"I left something in my car\"<\/code><\/li>\n<\/ul>\n<p> <\/p>\n<p>\u041a \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043c \u0442\u0430\u043a\u0436\u0435 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u0441\u044f \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435 <code>search(\"body\", \"r\")<\/code> \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0442\u044c \u0441\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c\u0438 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043c\u0438:<\/p>\n<p> <\/p>\n<ul>\n<li><code>\"rabbit\"<\/code><\/li>\n<li><code>\"Rakeeb searches\"<\/code><\/li>\n<li><code>\"send request\"<\/code><\/li>\n<\/ul>\n<p> <\/p>\n<p><strong>\u041b\u0438\u043c\u0438\u0442\u044b<\/strong><\/p>\n<p> <\/p>\n<p>\u041f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0439 \u0438\u043d\u0434\u0435\u043a\u0441 \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c:<\/p>\n<p> <\/p>\n<ul>\n<li>\u0440\u043e\u0432\u043d\u043e 1 \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u043e\u0435 \u043f\u043e\u043b\u0435<\/li>\n<li>\u0434\u043e 16 \u0444\u0438\u043b\u044c\u0442\u0440\u0443\u0435\u043c\u044b\u0445 \u043f\u043e\u043b\u0435\u0439<\/li>\n<\/ul>\n<p> <\/p>\n<p>\u041f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0439 \u0438\u043d\u0434\u0435\u043a\u0441 \u043c\u043e\u0436\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c:<\/p>\n<p> <\/p>\n<ul>\n<li>\u0434\u043e 16 \u0442\u0435\u0440\u043c\u0438\u043d\u043e\u0432 (\u0441\u043b\u043e\u0432) \u0432 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0438 \u043f\u043e\u0438\u0441\u043a\u0430<\/li>\n<li>\u0434\u043e 8 \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432<\/li>\n<\/ul>\n<p> <\/p>\n<h1 id=\"primer-ispolzovaniya-convex\">\u041f\u0440\u0438\u043c\u0435\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f Convex<\/h1>\n<p> <\/p>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f Convex \u043c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0447\u0430\u0441\u0442\u044c \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430 \u043a\u043b\u043e\u043d\u0430 Slack, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 <a href=\"https:\/\/www.youtube.com\/watch?v=lXITA5MZIiI\">\u044d\u0442\u043e\u043c \u0437\u0430\u043c\u0435\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u043c \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u0435<\/a>.<\/p>\n<p> <\/p>\n<p>\u0422\u043e, \u0447\u0442\u043e \u0443 \u043c\u0435\u043d\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c (\u043c\u043e\u0439 \u043a\u043e\u0434 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u043e\u0442 \u043a\u043e\u0434\u0430 \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u0430), \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 <a href=\"https:\/\/github.com\/harryheman\/slack-clone\">\u0437\u0434\u0435\u0441\u044c<\/a>.<\/p>\n<p> <\/p>\n<p>\u0412\u0435\u0441\u044c \u043a\u043e\u0434 Convex \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 <code>convex<\/code> \u0432 \u043a\u043e\u0440\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430. \u041e\u043d\u0430 \u0438\u043c\u0435\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443:<\/p>\n<p> <\/p>\n<pre><code class=\"plaintext\">auth.config.ts auth.ts channels.ts conversations.ts http.ts members.ts messages.ts reactions.ts schema.ts tsconfig.json upload.ts users.ts workspaces.ts<\/code><\/pre>\n<p> <\/p>\n<p>\u0412 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0434\u043b\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438\/\u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f Convex Auth. \u0421\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0442\u0441\u044f \u0432 \u0444\u0430\u0439\u043b\u0430\u0445 <code>auth.ts<\/code>, <code>auth.config.ts<\/code> \u0438 <code>http.ts<\/code>. \u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u043a\u0430\u0441\u0442\u043e\u043c\u0438\u0437\u0430\u0446\u0438\u044e \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430 <code>Password<\/code> \u0432 <code>auth.ts<\/code>:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">import { Password } from '@convex-dev\/auth\/providers\/Password' import { DataModel } from '.\/_generated\/dataModel'  const CustomPassword = Password&lt;DataModel&gt;({   profile(params) {     return {       email: params.email as string,       name: params.name as string,     }   }, })<\/code><\/pre>\n<p> <\/p>\n<p>\u0422\u0430\u043c \u0436\u0435 \u043c\u044b \u0432\u0438\u0434\u0438\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0445 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u043e\u0432 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 GitHub \u0438 Google:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">import { convexAuth } from '@convex-dev\/auth\/server' import GitHub from '@auth\/core\/providers\/github' import Google from '@auth\/core\/providers\/google'  export const { auth, signIn, signOut, store } = convexAuth({   providers: [CustomPassword, GitHub, Google], })<\/code><\/pre>\n<p> <\/p>\n<p>\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u043b\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">\/\/ features\/auth\/components\/sign-up-card.tsx import { Button } from '@\/components\/ui\/button' import {   Card,   CardContent,   CardDescription,   CardHeader,   CardTitle, } from '@\/components\/ui\/card' import { Input } from '@\/components\/ui\/input' import { Separator } from '@\/components\/ui\/separator' import { FaGithub } from 'react-icons\/fa' import { FcGoogle } from 'react-icons\/fc' import { SignInFlow } from '..\/types' import { useState } from 'react' import { TriangleAlert } from 'lucide-react' import { useAuthActions } from '@convex-dev\/auth\/react'  type SignUpCardProps = {   setState: (state: SignInFlow) =&gt; void }  export const SignUpCard = ({ setState }: SignUpCardProps) =&gt; {   const [name, setName] = useState('')   const [email, setEmail] = useState('')   const [password, setPassword] = useState('')   const [confirmPassword, setConfirmPassword] = useState('')   const [pending, setPending] = useState(false)   const [error, setError] = useState('')    \/\/ \u041c\u0435\u0442\u043e\u0434 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438\/\u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438   const { signIn } = useAuthActions()    \/\/ \u041c\u0435\u0442\u043e\u0434 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0438\u043c\u0435\u043d\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, email \u0438 \u043f\u0430\u0440\u043e\u043b\u044f   const onPasswordSignUp = (e: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {     e.preventDefault()     if (password !== confirmPassword) {       setError('Passwords do not match')       return     }     setPending(true)     \/\/ \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.     \/\/ \u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e `flow`     signIn('password', { name, email, password, flow: 'signUp' })       .catch((e) =&gt; {         console.error(e)         setError('Something went wrong')       })       .finally(() =&gt; setPending(false))   }    \/\/ \u041c\u0435\u0442\u043e\u0434 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0445 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u043e\u0432   const onProviderSignUp = (value: 'github' | 'google') =&gt; {     setPending(true)     signIn(value).finally(() =&gt; setPending(false))   }    return (     &lt;Card className='w-full h-full p-8'&gt;       &lt;CardHeader className='px-0 pt-0'&gt;         &lt;CardTitle&gt;Sign up to continue&lt;\/CardTitle&gt;         &lt;CardDescription&gt;           Use your email or another service to continue         &lt;\/CardDescription&gt;       &lt;\/CardHeader&gt;       {!!error &amp;&amp; (         &lt;div className='bg-destructive\/15 p-3 rounded-md flex items-center gap-x-2 text-sm text-destructive mb-6'&gt;           &lt;TriangleAlert className='size-4' \/&gt;           &lt;p&gt;{error}&lt;\/p&gt;         &lt;\/div&gt;       )}       &lt;CardContent className='space-y-5 px-0 pb-0'&gt;         &lt;form className='space-y-2.5' onSubmit={onPasswordSignUp}&gt;           &lt;Input             disabled={pending}             value={name}             onChange={(e) =&gt; setName(e.target.value)}             placeholder='Full name'             required           \/&gt;           &lt;Input             disabled={pending}             value={email}             onChange={(e) =&gt; setEmail(e.target.value)}             placeholder='Email'             type='email'             required           \/&gt;           &lt;Input             disabled={pending}             value={password}             onChange={(e) =&gt; setPassword(e.target.value)}             placeholder='Password'             type='password'             required           \/&gt;           &lt;Input             disabled={pending}             value={confirmPassword}             onChange={(e) =&gt; setConfirmPassword(e.target.value)}             placeholder='Confirm password'             type='password'             required           \/&gt;           &lt;Button type='submit' className='w-full' size='lg' disabled={pending}&gt;             Continue           &lt;\/Button&gt;         &lt;\/form&gt;         &lt;Separator \/&gt;         &lt;div className='flex flex-col gap-y-2.5'&gt;           &lt;Button             disabled={pending}             onClick={() =&gt; onProviderSignUp('google')}             variant='outline'             size='lg'             className='w-full relative'           &gt;             &lt;FcGoogle className='size-5 absolute top-2.5 left-2.5' \/&gt;             Continue with Google           &lt;\/Button&gt;           &lt;Button             disabled={pending}             onClick={() =&gt; onProviderSignUp('github')}             variant='outline'             size='lg'             className='w-full relative'           &gt;             &lt;FaGithub className='size-5 absolute top-2.5 left-2.5' \/&gt;             Continue with GitHub           &lt;\/Button&gt;         &lt;\/div&gt;         &lt;p className='text-xs text-muted-foreground'&gt;           Already have an account?{' '}           &lt;span             className='text-sky-700 hover:underline cursor-pointer'             onClick={() =&gt; setState('signIn')}           &gt;             Sign in           &lt;\/span&gt;         &lt;\/p&gt;       &lt;\/CardContent&gt;     &lt;\/Card&gt;   ) }<\/code><\/pre>\n<p> <\/p>\n<p>\u0424\u0430\u0439\u043b <code>schema.ts<\/code> \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043c\u043e\u0434\u0435\u043b\u044c \u0411\u0414 \u2014 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0442\u0430\u0431\u043b\u0438\u0446:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">import { authTables } from '@convex-dev\/auth\/server' import { defineSchema, defineTable } from 'convex\/server' import { v } from 'convex\/values'  export default defineSchema({   \/\/ \u0422\u0430\u0431\u043b\u0438\u0446\u044b \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438   ...authTables,   \/\/ \u0420\u0430\u0431\u043e\u0447\u0438\u0435 \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0430   workspaces: defineTable({     name: v.string(),     userId: v.id('users'),     joinCode: v.string(),   }),   \/\/ \u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438 \u0440\u0430\u0431\u043e\u0447\u0435\u0433\u043e \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0430   members: defineTable({     userId: v.id('users'),     workspaceId: v.id('workspaces'),     role: v.union(v.literal('admin'), v.literal('member')),   })     .index('by_user_id', ['userId'])     .index('by_workspace_id', ['workspaceId'])     .index('by_workspace_id_user_id', ['workspaceId', 'userId']),   \/\/ \u041a\u0430\u043d\u0430\u043b\u044b \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0430   channels: defineTable({     name: v.string(),     workspaceId: v.id('workspaces'),   }).index('by_workspace_id', ['workspaceId']),   \/\/ \u0411\u0435\u0441\u0435\u0434\u044b \u043e\u0434\u0438\u043d \u043d\u0430 \u043e\u0434\u0438\u043d \u043c\u0435\u0436\u0434\u0443 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0430\u043c\u0438 \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0430   conversations: defineTable({     workspaceId: v.id('workspaces'),     memberOneId: v.id('members'),     memberTwoId: v.id('members'),   }).index('by_workspace_id', ['workspaceId']),   \/\/ \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0430 \u0438 \u043a\u0430\u043d\u0430\u043b\u0430, \u043f\u043e\u0442\u043e\u043a\u0430 (\u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u043a \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044e) \u0438\u043b\u0438 \u0431\u0435\u0441\u0435\u0434\u044b   messages: defineTable({     body: v.string(),     image: v.optional(v.id('_storage')),     memberId: v.id('members'),     workspaceId: v.id('workspaces'),     channelId: v.optional(v.id('channels')),     parentMessageId: v.optional(v.id('messages')),     conversationId: v.optional(v.id('conversations')),     updatedAt: v.optional(v.number()),   })     \/\/ \u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u043e\u0432,     \/\/ \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u044e\u0449\u0438\u0445 \u0432\u044b\u0441\u043e\u043a\u0443\u044e \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0442\u0430\u0431\u043b\u0438\u0446\u044b     .index('by_workspace_id', ['workspaceId'])     .index('by_member_id', ['memberId'])     .index('by_channel_id', ['channelId'])     .index('by_conversation_id', ['conversationId'])     .index('by_parent_message_id', ['parentMessageId'])     .index('by_channel_id_parent_message_id_conversation_id', [       'channelId',       'parentMessageId',       'conversationId',     ]),   \/\/ \u0420\u0435\u0430\u043a\u0446\u0438\u0438 \u043d\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435   reactions: defineTable({     workspaceId: v.id('workspaces'),     messageId: v.id('messages'),     memberId: v.id('members'),     value: v.string(),   })     .index('by_workspace_id', ['workspaceId'])     .index('by_message_id', ['messageId'])     .index('by_member_id', ['memberId']), })<\/code><\/pre>\n<p> <\/p>\n<p>\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u043c\u043e\u0433\u0443\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0438 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f. \u0414\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0443\u0440\u043b\u043e\u0432 \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438. \u0421\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u043c\u0435\u0442\u043e\u0434 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442\u0441\u044f \u0432 \u0444\u0430\u0439\u043b\u0435 <code>upload.ts<\/code>:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">import { mutation } from '.\/_generated\/server'  export const generateUploadUrl = mutation(async (ctx) =&gt; {   return await ctx.storage.generateUploadUrl() })<\/code><\/pre>\n<p> <\/p>\n<p>\u041c\u0435\u0442\u043e\u0434\u044b \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u043c\u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0442\u0441\u044f \u0432 \u0444\u0430\u0439\u043b\u0430\u0445 <code>channels.ts<\/code>, <code>conversations.ts<\/code>, <code>members.ts<\/code>, <code>messages.ts<\/code>, <code>reactions.ts<\/code>, <code>users.ts<\/code> \u0438 <code>workspaces.ts<\/code>. \u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043c\u0435\u0442\u043e\u0434\u044b \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043a\u0430\u043d\u0430\u043b\u0430\u043c\u0438 (\u0442\u0430\u0431\u043b\u0438\u0446\u0430 <code>\"channels\"<\/code>):<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">import { getAuthUserId } from '@convex-dev\/auth\/server' import { mutation, query } from '.\/_generated\/server' import { ConvexError, v } from 'convex\/values'  \/\/ \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0432\u0441\u0435 \u043a\u0430\u043d\u0430\u043b\u044b \u0440\u0430\u0431\u043e\u0447\u0435\u0433\u043e \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0430 export const get = query({   args: {     \/\/ \u0418\u0414 \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0430     workspaceId: v.id('workspaces'),   },   handler: async (ctx, args) =&gt; {     \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f     const userId = await getAuthUserId(ctx)     if (!userId) {       return []     }      \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u043c \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0430     const member = await ctx.db       .query('members')       .withIndex('by_workspace_id_user_id', (q) =&gt;         q.eq('workspaceId', args.workspaceId).eq('userId', userId),       )       .unique()     if (!member) {       return []     }      \/\/ \u0418\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u043a\u0430\u043d\u0430\u043b\u044b \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0438\u0445 \u043a\u043b\u0438\u0435\u043d\u0442\u0443     const channels = await ctx.db       .query('channels')       .withIndex('by_workspace_id', (q) =&gt;         q.eq('workspaceId', args.workspaceId),       )       .collect()      return channels   }, })  \/\/ \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043a\u0430\u043d\u0430\u043b \u043f\u043e \u0435\u0433\u043e \u0418\u0414 export const getById = query({   args: {     \/\/ \u0418\u0414 \u043a\u0430\u043d\u0430\u043b\u0430     id: v.id('channels'),   },   handler: async (ctx, args) =&gt; {     const userId = await getAuthUserId(ctx)     if (!userId) {       return null     }      \/\/ \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u043a\u0430\u043d\u0430\u043b, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e     \/\/ \u043d\u0430\u043c \u043d\u0443\u0436\u0435\u043d `workspaceId` \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0447\u043b\u0435\u043d\u0441\u0442\u0432\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f     const channel = await ctx.db.get(args.id)     if (!channel) {       return null     }      const member = await ctx.db       .query('members')       .withIndex('by_workspace_id_user_id', (q) =&gt;         q.eq('workspaceId', channel.workspaceId).eq('userId', userId),       )       .unique()     if (!member) {       return null     }      return channel   }, })  \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u0442 \u043a\u0430\u043d\u0430\u043b \u0441 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u043c \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c export const create = mutation({   args: {     \/\/ \u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043a\u0430\u043d\u0430\u043b\u0430     name: v.string(),     \/\/ \u0418\u0414 \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0430     workspaceId: v.id('workspaces'),   },   handler: async (ctx, args) =&gt; {     const userId = await getAuthUserId(ctx)     if (!userId) {       \/\/ \u041c\u0443\u0442\u0430\u0446\u0438\u0438 \u0432\u044b\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u044e\u0442 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f       throw new ConvexError('Unauthorized')     }      const member = await ctx.db       .query('members')       .withIndex('by_workspace_id_user_id', (q) =&gt;         q.eq('workspaceId', args.workspaceId).eq('userId', userId),       )       .unique()     \/\/ \u041a\u0430\u043d\u0430\u043b\u044b \u043c\u043e\u0433\u0443\u0442 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430\u043c\u0438 \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0430     if (!member || member.role !== 'admin') {       throw new ConvexError('Unauthorized')     }      \/\/ \u041f\u0440\u043e\u0431\u0435\u043b\u044b \u0432 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0438 \u043a\u0430\u043d\u0430\u043b\u0430 \u0437\u0430\u043c\u0435\u043d\u044f\u044e\u0442\u0441\u044f \u043d\u0430 \u0434\u0435\u0444\u0438\u0441\u044b     const name = args.name.replace(\/\\s+\/g, '-').toLowerCase()      \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043a\u0430\u043d\u0430\u043b \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0435\u0433\u043e \u0418\u0414 \u043a\u043b\u0438\u0435\u043d\u0442\u0443     const channelId = await ctx.db.insert('channels', {       name,       workspaceId: args.workspaceId,     })      return channelId   }, })  \/\/ \u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043a\u0430\u043d\u0430\u043b\u0430 \u043f\u043e \u0435\u0433\u043e \u0418\u0414 export const update = mutation({   args: {     \/\/ \u0418\u0414 \u043a\u0430\u043d\u0430\u043b\u0430     id: v.id('channels'),     \/\/ \u041d\u043e\u0432\u043e\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043a\u0430\u043d\u0430\u043b\u0430     name: v.string(),   },   handler: async (ctx, args) =&gt; {     const userId = await getAuthUserId(ctx)     if (!userId) {       throw new ConvexError('Unauthorized')     }      const channel = await ctx.db.get(args.id)     if (!channel) {       throw new ConvexError('Channel not found')     }      const member = await ctx.db       .query('members')       .withIndex('by_workspace_id_user_id', (q) =&gt;         q.eq('workspaceId', channel.workspaceId).eq('userId', userId),       )       .unique()     if (!member || member.role !== 'admin') {       throw new ConvexError('Unauthorized')     }      const name = args.name.replace(\/\\s+\/g, '-').toLowerCase()      \/\/ \u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043a\u0430\u043d\u0430\u043b\u0430 \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0435\u0433\u043e \u0418\u0414 \u043a\u043b\u0438\u0435\u043d\u0442\u0443     await ctx.db.patch(args.id, { name })      return args.id   }, })  \/\/ \u0423\u0434\u0430\u043b\u044f\u0435\u0442 \u043a\u0430\u043d\u0430\u043b \u043f\u043e \u0435\u0433\u043e \u0418\u0414 export const remove = mutation({   args: {     \/\/ \u0418\u0414 \u043a\u0430\u043d\u0430\u043b\u0430     id: v.id('channels'),   },   handler: async (ctx, args) =&gt; {     const userId = await getAuthUserId(ctx)     if (!userId) {       throw new ConvexError('Unauthorized')     }      const channel = await ctx.db.get(args.id)     if (!channel) {       throw new ConvexError('Channel not found')     }      const member = await ctx.db       .query('members')       .withIndex('by_workspace_id_user_id', (q) =&gt;         q.eq('workspaceId', channel.workspaceId).eq('userId', userId),       )       .unique()     \/\/ \u0423\u0434\u0430\u043b\u044f\u0442\u044c \u043a\u0430\u043d\u0430\u043b\u044b \u043c\u043e\u0433\u0443\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0430\u0434\u043c\u0438\u043d\u044b     if (!member || member.role !== 'admin') {       throw new ConvexError('Unauthorized')     }      \/\/ \u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u043a\u0430\u043d\u0430\u043b\u0430 \u0432\u043b\u0435\u0447\u0435\u0442 \u0437\u0430 \u0441\u043e\u0431\u043e\u0439 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u0435\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439     const messages = await ctx.db       .query('messages')       .withIndex('by_channel_id', (q) =&gt; q.eq('channelId', args.id))       .collect()      \/\/ \u0423\u0434\u0430\u043b\u044f\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043a\u0430\u043d\u0430\u043b\u0430     for (const message of messages) {       await ctx.db.delete(message._id)     }      \/\/ \u0423\u0434\u0430\u043b\u044f\u0435\u043c \u043a\u0430\u043d\u0430\u043b \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0435\u0433\u043e \u0418\u0414 \u043a\u043b\u0438\u0435\u043d\u0442\u0443     await ctx.db.delete(args.id)      return args.id   }, })<\/code><\/pre>\n<p> <\/p>\n<p>\u0414\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430 (\u0444\u0443\u043d\u043a\u0446\u0438\u0438 Convex) \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0438\u0439 \u0445\u0443\u043a React. \u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0445\u0443\u043a\u043e\u0432 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438 (<code>features\/messages\/api<\/code>).<\/p>\n<p> <\/p>\n<p>\u0425\u0443\u043a \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u043e \u0418\u0414:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">\/\/ use-get-message.ts import { useQuery } from 'convex\/react' import { api } from '..\/..\/..\/..\/convex\/_generated\/api' import { Id } from '..\/..\/..\/..\/convex\/_generated\/dataModel'  export const useGetMessage = (id: Id&lt;'messages'&gt;) =&gt; {   const data = useQuery(api.messages.getById, { id })   const isLoading = data === undefined   return { data, isLoading } }<\/code><\/pre>\n<p> <\/p>\n<p>\u0425\u0443\u043a \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u043f\u0430\u0433\u0438\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">\/\/ use-get-messages.ts import { usePaginatedQuery } from 'convex\/react' import { FunctionReturnType } from 'convex\/server' import { api } from '..\/..\/..\/..\/convex\/_generated\/api' import { Id } from '..\/..\/..\/..\/convex\/_generated\/dataModel'  \/\/ \u0420\u0430\u0437\u043c\u0435\u0440 \u043f\u0430\u043a\u0435\u0442\u0430 - \u043a\u0430\u0436\u0434\u044b\u0439 \u0432\u044b\u0437\u043e\u0432 \u0445\u0443\u043a\u0430 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 20 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 const BATCH_SIZE = 20  type Props = {   channelId?: Id&lt;'channels'&gt;   conversationId?: Id&lt;'conversations'&gt;   parentMessageId?: Id&lt;'messages'&gt; }  export type GetMessagesReturnT = FunctionReturnType&lt;   typeof api.messages.get &gt;['page']  export const useGetMessages = ({   channelId,   conversationId,   parentMessageId, }: Props) =&gt; {   const { results, status, loadMore } = usePaginatedQuery(     api.messages.get,     { channelId, conversationId, parentMessageId },     { initialNumItems: BATCH_SIZE },   )    return { results, status, loadMore: () =&gt; loadMore(BATCH_SIZE) } }<\/code><\/pre>\n<p> <\/p>\n<p>\u0425\u0443\u043a \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">\/\/ use-create-message.ts import { useMutation } from 'convex\/react' import { api } from '..\/..\/..\/..\/convex\/_generated\/api' import { useCallback, useState } from 'react' import { Id } from '..\/..\/..\/..\/convex\/_generated\/dataModel'  type RequestT = {   \/\/ \u0422\u0435\u043a\u0441\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f   body: string   workspaceId: Id&lt;'workspaces'&gt;   \/\/ \u0418\u0414 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f   image?: Id&lt;'_storage'&gt;   channelId?: Id&lt;'channels'&gt;   parentMessageId?: Id&lt;'messages'&gt;   conversationId?: Id&lt;'conversations'&gt; } type ResponseT = Id&lt;'messages'&gt; | null  type Options = {   onSuccess?: (data: ResponseT) =&gt; void   onError?: (e: Error) =&gt; void   onSettled?: () =&gt; void   throwError?: boolean }  export const useCreateMessage = () =&gt; {   const [data, setData] = useState&lt;ResponseT&gt;(null)   const [error, setError] = useState&lt;Error | null&gt;(null)   const [status, setStatus] = useState&lt;     'success' | 'error' | 'settled' | 'pending' | null   &gt;(null)    const isSuccess = status === 'success'   const isError = status === 'error'   const isSettled = status === 'settled'   const isPending = status === 'pending'    const mutation = useMutation(api.messages.create)    const mutate = useCallback(     async (values: RequestT, options?: Options) =&gt; {       setData(null)       setError(null)       setStatus('pending')       try {         const response = await mutation(values)         setData(response)         setStatus('success')         options?.onSuccess?.(response)         return response       } catch (e) {         setError(e as Error)         setStatus('error')         options?.onError?.(e as Error)         if (options?.throwError) {           throw e         }       } finally {         setStatus('settled')         options?.onSettled?.()       }     },     [mutation],   )    return { mutate, data, error, isPending, isSuccess, isError, isSettled } }<\/code><\/pre>\n<p> <\/p>\n<p>\u0412 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u043a\u0430\u043a \u044d\u0442\u0438 \u0445\u0443\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430\u0445.<\/p>\n<p> <\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f <code>useGetMessages()<\/code> \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u043a\u0430\u043d\u0430\u043b\u0430:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">\/\/ app\/workspace\/[workspaceId]\/channel\/[channelId]\/page.tsx 'use client'  import { useGetChannel } from '@\/features\/channels\/api\/use-get-channel' import { useChannelId } from '@\/hooks\/use-channel-id' import { Loader, TriangleAlert } from 'lucide-react' import { Header } from '.\/header' import { ChatInput } from '.\/chat-input' import { useGetMessages } from '@\/features\/messages\/api\/use-get-messages' import { MessageList } from '@\/components\/message-list'  export default function ChannelPage() {   const channelId = useChannelId()    \/\/ \u0418\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043a\u0430\u043d\u0430\u043b\u0430, \u0441\u0442\u0430\u0442\u0443\u0441 \u0438 \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439   const { results, status, loadMore } = useGetMessages({ channelId })   const { data: channel, isLoading: channelLoading } = useGetChannel(channelId)    \/\/ \u0415\u0441\u043b\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u043a\u0430\u043d\u0430\u043b\u0430 \u0438\u043b\u0438 \u043f\u0435\u0440\u0432\u0430\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439   if (channelLoading || status === 'LoadingFirstPage') {     return (       &lt;div className='h-full flex-1 flex items-center justify-center'&gt;         &lt;Loader className='size-5 animate-spin text-muted-foreground' \/&gt;       &lt;\/div&gt;     )   }    \/\/ \u0415\u0441\u043b\u0438 \u043a\u0430\u043d\u0430\u043b \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 (\u044d\u0442\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u043a\u0430\u043d\u0430\u043b\u0430 \u0430\u0434\u043c\u0438\u043d\u043e\u043c)   if (!channel) {     return (       &lt;div className='h-full flex-1 flex items-center justify-center flex-col gap-2'&gt;         &lt;TriangleAlert className='size-5 text-muted-foreground' \/&gt;         &lt;span className='text-muted-foreground text-sm'&gt;Channel not found&lt;\/span&gt;       &lt;\/div&gt;     )   }    return (     &lt;div className='flex flex-col h-full'&gt;       &lt;Header title={channel.name} \/&gt;       &lt;MessageList         channelName={channel.name}         channelCreationTime={channel._creationTime}         data={results}         loadMore={loadMore}         isLoadingMore={status === 'LoadingMore'}         canLoadMore={status === 'CanLoadMore'}       \/&gt;       &lt;ChatInput placeholder={`Message # ${channel.name}`} \/&gt;     &lt;\/div&gt;   ) }<\/code><\/pre>\n<p> <\/p>\n<p>\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b <code>MessageList<\/code> \u0438 <code>Message<\/code> \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043e\u0431\u044a\u0435\u043c\u043d\u044b\u0435 \u0438 \u0441\u043b\u043e\u0436\u043d\u044b\u0435, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u044b \u043d\u0435 \u0431\u0443\u0434\u0435\u043c \u0437\u0434\u0435\u0441\u044c \u0438\u0445 \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u0442\u044c (\u043f\u0443\u0441\u0442\u044c \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u0432\u0430\u0448\u0438\u043c \u0414\u0417 ;)).<\/p>\n<p> <\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f <code>useCreateMessage()<\/code> \u0438 <code>useGenerateUploadUrl()<\/code> \u0432 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0435 \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439:<\/p>\n<p> <\/p>\n<pre><code class=\"javascript\">import { EditorValue } from '@\/components\/editor' import { useCreateMessage } from '@\/features\/messages\/api\/use-create-message' import { useGenerateUploadUrl } from '@\/features\/upload\/api\/use-generate-upload-url' import { useChannelId } from '@\/hooks\/use-channel-id' import { useWorkspaceId } from '@\/hooks\/use-workspace-id' import dynamic from 'next\/dynamic' import Quill from 'quill' import { useRef, useState } from 'react' import { toast } from 'sonner' import { Id } from '..\/..\/..\/..\/..\/..\/convex\/_generated\/dataModel'  const Editor = dynamic(() =&gt; import('@\/components\/editor'), {   ssr: false, })  type Props = {   placeholder: string }  type CreateMessageValues = {   body: string   channelId: Id&lt;'channels'&gt;   workspaceId: Id&lt;'workspaces'&gt;   image?: Id&lt;'_storage'&gt; }  export const ChatInput = ({ placeholder }: Props) =&gt; {   const [editorKey, setEditorKey] = useState(0)   const [isPending, setIsPending] = useState(false)    const editorRef = useRef&lt;Quill | null&gt;(null)    const workspaceId = useWorkspaceId()   const channelId = useChannelId()    \/\/ \u041c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0443\u0440\u043b\u0430 \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u0430   const { mutate: generateUploadUrl } = useGenerateUploadUrl()   \/\/ \u041c\u0435\u0442\u043e\u0434 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f   const { mutate: createMessage } = useCreateMessage()    \/\/ \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0444\u043e\u0440\u043c\u044b \u0441 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435\u043c   const handleSubmit = async ({ body, image }: EditorValue) =&gt; {     setIsPending(true)     editorRef.current?.enable(false)      try {       \/\/ \u0414\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f       const values: CreateMessageValues = {         body,         channelId,         workspaceId,         image: undefined,       }        \/\/ \u0415\u0441\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435       if (image) {         \/\/ \u0413\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u0443\u0440\u043b \u0434\u043b\u044f \u0435\u0433\u043e \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438         const url = await generateUploadUrl(undefined, { throwError: true })         if (!url) {           throw new Error('Failed to generate upload URL')         }         \/\/ \u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435         const result = await fetch(url, {           method: 'POST',           headers: {             'Content-Type': image.type,           },           body: image,         })         if (!result.ok) {           throw new Error('Failed to upload image')         }         \/\/ \u0418\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u0418\u0414 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430         const { storageId } = await result.json()         if (storageId) {           \/\/ \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0435\u0433\u043e \u0432 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f           values.image = storageId         }       }        \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435       await createMessage(values, {         throwError: true,       })        setEditorKey((k) =&gt; k + 1)     } catch (e) {       console.error(e)       toast.error('Failed to send message')     } finally {       setIsPending(false)       editorRef.current?.enable(true)     }   }    return (     &lt;div className='w-full px-5'&gt;       &lt;Editor         key={editorKey}         placeholder={placeholder}         onSubmit={handleSubmit}         disabled={isPending}         innerRef={editorRef}       \/&gt;     &lt;\/div&gt;   ) }<\/code><\/pre>\n<p> <\/p>\n<p>\u041e\u043f\u044f\u0442\u044c \u0436\u0435 \u0441 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u043c <code>Editor<\/code> \u044f \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u0432\u0430\u043c \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e.<\/p>\n<p> <\/p>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u0442\u0440\u0435\u0442\u044c\u044f \u0447\u0430\u0441\u0442\u044c \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0430. Happy coding!<\/p>\n<p> <\/p>\n<hr\/>\n<p> <\/p>\n<blockquote><p><a href=\"https:\/\/t.me\/timewebru\"><b>\u041d\u043e\u0432\u043e\u0441\u0442\u0438, \u043e\u0431\u0437\u043e\u0440\u044b \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432 \u0438 \u043a\u043e\u043d\u043a\u0443\u0440\u0441\u044b \u043e\u0442 \u043a\u043e\u043c\u0430\u043d\u0434\u044b Timeweb.Cloud \u2014 \u0432 \u043d\u0430\u0448\u0435\u043c Telegram-\u043a\u0430\u043d\u0430\u043b\u0435<\/b><\/a> <b>\u21a9<\/b><\/p><\/blockquote>\n<p><a href=\"https:\/\/timeweb.cloud\/?i=106909\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/u9\/vg\/io\/u9vgio3hxj12h5u7j3un0wx_zpk.png\" data-src=\"https:\/\/habrastorage.org\/webt\/u9\/vg\/io\/u9vgio3hxj12h5u7j3un0wx_zpk.png\"\/><\/a><\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \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\/867458\/\"> https:\/\/habr.com\/ru\/articles\/867458\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/lq\/qm\/e7\/lqqme7wqqxhte55gsiz94gwwh4u.png\" data-src=\"https:\/\/habrastorage.org\/webt\/lq\/qm\/e7\/lqqme7wqqxhte55gsiz94gwwh4u.png\"\/> <\/p>\n<p> \u041f\u0440\u0438\u0432\u0435\u0442, \u0434\u0440\u0443\u0437\u044c\u044f!<\/p>\n<p> <\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0435\u0440\u0438\u0438 \u0441\u0442\u0430\u0442\u0435\u0439 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u044e \u043e <a href=\"https:\/\/www.convex.dev\/\">Convex<\/a> \u2014 \u043d\u043e\u0432\u043e\u043c \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u043c \u0438 \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u043e\u043c \u0440\u0435\u0448\u0435\u043d\u0438\u0438 BaaS (Backend as a Service \u2014 \u0431\u044d\u043a\u0435\u043d\u0434 \u043a\u0430\u043a \u0443\u0441\u043b\u0443\u0433\u0430), \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043e\u0447\u0435\u043d\u044c \u043c\u043d\u043e\u0433\u043e\u043e\u0431\u0435\u0449\u0430\u044e\u0449\u0435 \u0438 \u0431\u044b\u0441\u0442\u0440\u043e \u043d\u0430\u0431\u0438\u0440\u0430\u0435\u0442 \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u043e\u0441\u0442\u044c \u0441\u0440\u0435\u0434\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432.<\/p>\n<p> <\/p>\n<p>\u041d\u0430 \u0441\u0435\u0433\u043e\u0434\u043d\u044f\u0448\u043d\u0438\u0439 \u0434\u0435\u043d\u044c Convex \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u0443\u044e \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u043c\u0435\u0448\u0430\u043d\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0430, \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438\/\u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438, \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0435 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435, \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a \u0437\u0430\u0434\u0430\u0447 \u0438 \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u043e \u0438\u043d\u0442\u0435\u043b\u043b\u0435\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430.<\/p>\n<p> <\/p>\n<p>\u042d\u0442\u0430 \u0442\u0440\u0435\u0442\u044c\u044f \u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u044e\u0449\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0441\u0435\u0440\u0438\u0438, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043c\u044b \u043f\u043e\u0433\u043e\u0432\u043e\u0440\u0438\u043c \u043e \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u0437\u0430\u0434\u0430\u0447, \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u0444\u0430\u0439\u043b\u043e\u0432 \u0438 \u043f\u043e\u0438\u0441\u043a\u0435.<\/p>\n<p> <\/p>\n<p>\u0412 \u043a\u043e\u043d\u0446\u0435 \u043c\u044b \u0442\u0430\u043a\u0436\u0435 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u044b\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f Convex \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u043e\u0433\u043e \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p> <\/p>\n<ul>\n<li><a href=\"https:\/\/habr.com\/ru\/companies\/timeweb\/articles\/844878\/\">\u041f\u0435\u0440\u0432\u0430\u044f \u0447\u0430\u0441\u0442\u044c<\/a><\/li>\n<li><a href=\"https:\/\/habr.com\/ru\/companies\/timeweb\/articles\/851244\/\">\u0412\u0442\u043e\u0440\u0430\u044f \u0447\u0430\u0441\u0442\u044c<\/a><\/li>\n<\/ul>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-444508","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/444508","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=444508"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/444508\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=444508"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=444508"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=444508"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}