{"id":479214,"date":"2026-05-10T11:20:51","date_gmt":"2026-05-10T11:20:51","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=479214"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=479214","title":{"rendered":"\u041a\u0430\u043a \u044f \u0441\u0434\u0435\u043b\u0430\u043b \u0442\u0440\u0451\u0445\u0443\u0440\u043e\u0432\u043d\u0435\u0432\u044b\u0439 \u043a\u044d\u0448 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0432 \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u0435 \u043d\u0430 React Native \u2014 \u0438 \u0447\u0442\u043e \u0443\u0437\u043d\u0430\u043b \u043f\u043e \u0434\u043e\u0440\u043e\u0433\u0435"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<blockquote>\n<p>\u0423\u0440\u043e\u0432\u0435\u043d\u044c: middle\/senior \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u0430\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430, React Native, SQLite \u0421\u0442\u0435\u043a: Expo SDK 54, React Native, expo-sqlite, drizzle-orm, AsyncStorage, TypeScript \u0427\u0442\u043e \u0432\u043d\u0443\u0442\u0440\u0438: \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430, \u043a\u043e\u0434 \u0438\u0437 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u043d\u0430, \u0433\u0440\u0430\u0431\u043b\u0438, \u0446\u0438\u0444\u0440\u044b<\/p>\n<\/blockquote>\n<h3>\u0412\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0435<\/h3>\n<p>\u042f \u0434\u0435\u043b\u0430\u044e \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440 ONEMIX \u043d\u0430 React Native. \u041a \u043c\u043e\u043c\u0435\u043d\u0442\u0443, \u043a\u043e\u0433\u0434\u0430 \u044f \u043d\u0430\u0447\u0430\u043b \u043f\u0438\u0441\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u043f\u043e\u0441\u0442, \u0432 \u043d\u0451\u043c \u0443\u0436\u0435 \u0431\u043e\u043b\u044c\u0448\u0435 \u0434\u0435\u0441\u044f\u0442\u043a\u0430 \u044d\u043a\u0440\u0430\u043d\u043e\u0432, \u0433\u0440\u0443\u043f\u043f\u043e\u0432\u044b\u0435 WebRTC-\u0437\u0432\u043e\u043d\u043a\u0438 \u0447\u0435\u0440\u0435\u0437 LiveKit, E2E \u043d\u0430 Double Ratchet + Sealed Sender, push-\u043d\u043e\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0441 cold-start \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0435\u0439 \u0438 \u0434\u0435\u0441\u043a\u0442\u043e\u043f-\u0432\u0435\u0440\u0441\u0438\u044f \u043d\u0430 Electron. \u041d\u043e \u0441\u0430\u043c\u044b\u043c \u0432\u0430\u0436\u043d\u044b\u043c \u043a\u0443\u0441\u043a\u043e\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043e\u0449\u0443\u0449\u0435\u043d\u0438\u0435 \u043e\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043e\u043a\u0430\u0437\u0430\u043b\u0441\u044f \u043d\u0435 \u0437\u0432\u0443\u043a \u0438 \u043d\u0435 \u0432\u0438\u0434\u0435\u043e. \u0410 \u0442\u043e, \u043d\u0430\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0431\u044b\u0441\u0442\u0440\u043e \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0447\u0430\u0442.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u044b \u0445\u043e\u0442\u044c \u0440\u0430\u0437 \u0434\u0435\u043b\u0430\u043b\u0438 \u0441\u043f\u0438\u0441\u043e\u043a \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0430 React Native, \u0432\u044b \u0437\u043d\u0430\u0435\u0442\u0435 \u044d\u0442\u0443 \u0431\u043e\u043b\u044c: \u043e\u0442\u043a\u0440\u044b\u043b \u0447\u0430\u0442 \u2014 \u043f\u0443\u0441\u0442\u043e\u0439 \u044d\u043a\u0440\u0430\u043d \u043d\u0430 200\u2013800 \u043c\u0441, \u043f\u043e\u0442\u043e\u043c \u043f\u043e\u0434\u0433\u0440\u0443\u0437\u043a\u0430, \u043f\u043e\u0442\u043e\u043c \u0441\u043a\u0430\u0447\u043e\u043a \u043f\u0440\u0438 \u0434\u043e\u043a\u0440\u0443\u0442\u043a\u0435 \u043d\u0430\u0432\u0435\u0440\u0445. \u0412 Telegram \u0442\u0430\u043a\u043e\u0433\u043e \u043d\u0435 \u0431\u044b\u0432\u0430\u0435\u0442: \u043e\u0442\u043a\u0440\u044b\u043b \u2014 \u043c\u0433\u043d\u043e\u0432\u0435\u043d\u043d\u043e \u0443\u0432\u0438\u0434\u0435\u043b \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u043f\u0440\u043e\u043a\u0440\u0443\u0442\u0438\u043b \u043d\u0430\u0432\u0435\u0440\u0445 \u2014 \u043d\u0438\u043a\u0430\u043a\u0438\u0445 \u043f\u0443\u0441\u0442\u043e\u0442, \u0438\u0441\u0442\u043e\u0440\u0438\u044f \u0438\u0434\u0451\u0442 \u0441\u043f\u043b\u043e\u0448\u043d\u043e\u0439 \u043b\u0435\u043d\u0442\u043e\u0439.<\/p>\n<p>\u042f \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043b\u0441\u044f \u0441 \u044d\u0442\u0438\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0435\u0441\u044f\u0446\u0435\u0432. \u0412 \u0438\u0442\u043e\u0433\u0435 \u043f\u0440\u0438\u0448\u0451\u043b \u043a \u0442\u0440\u0451\u0445\u0443\u0440\u043e\u0432\u043d\u0435\u0432\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0435 \u043a\u044d\u0448\u0430, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0438 \u0445\u043e\u0447\u0443 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c. \u042d\u0442\u043e \u043d\u0435 \u0442\u0435\u043e\u0440\u0438\u044f \u2014 \u044d\u0442\u043e \u043a\u043e\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u0435\u0439\u0447\u0430\u0441 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u043d\u0435. \u041f\u043e\u043a\u0430\u0436\u0443 \u043a\u0430\u043a \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043e, \u043a\u0430\u043a\u0438\u0435 \u0431\u044b\u043b\u0438 \u0442\u0443\u043f\u0438\u043a\u0438 \u0438 \u043a\u0430\u043a\u0438\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043e\u043a\u0430\u0437\u0430\u043b\u0438\u0441\u044c \u043a\u0440\u0438\u0442\u0438\u0447\u043d\u044b\u043c\u0438.<\/p>\n<h3>\u041f\u043e\u0447\u0435\u043c\u0443 \u043e\u0434\u0438\u043d \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442<\/h3>\n<p>\u0421\u0430\u043c\u044b\u0439 \u0447\u0430\u0441\u0442\u044b\u0439 \u043f\u043e\u0434\u0445\u043e\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u044f \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u044e \u0432 \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u0430\u0445 \u0438 open-source \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u0430\u0445 \u043d\u0430 RN: \u0432\u0441\u0451 \u0445\u043e\u0434\u0438\u0442 \u0432 \u0441\u0435\u0442\u044c, \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435 \u043a\u044d\u0448\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432 AsyncStorage. \u042d\u0442\u043e\u0442 \u043f\u043e\u0434\u0445\u043e\u0434 \u0440\u0430\u0437\u0432\u0430\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u043e \u0442\u0440\u0451\u043c \u043f\u0440\u0438\u0447\u0438\u043d\u0430\u043c \u0441\u0440\u0430\u0437\u0443.<\/p>\n<p><strong>\u0421\u0435\u0442\u044c \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u0430\u044f.<\/strong> \u0414\u0430\u0436\u0435 \u043d\u0430\u00a0\u0445\u043e\u0440\u043e\u0448\u0435\u043c 4G round\u2011trip \u0434\u043e\u00a0\u0441\u0435\u0440\u0432\u0435\u0440\u0430\u00a0\u2014 \u044d\u0442\u043e 100\u2013300\u00a0\u043c\u0441. \u041d\u0430\u00a0\u043c\u0435\u0442\u0440\u043e \u0438\u043b\u0438\u00a0\u0432\u00a0\u043f\u043e\u0434\u0432\u0430\u043b\u0435\u00a0\u2014 1\u20133\u00a0\u0441\u0435\u043a\u0443\u043d\u0434\u044b. \u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0447\u0430\u0442 \u0441\u00a0\u0442\u0430\u043a\u043e\u0439 \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u043e\u0439 \u043d\u0435\u043b\u044c\u0437\u044f, \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0443\u0439\u0434\u0451\u0442.<\/p>\n<p><strong>AsyncStorage\u00a0\u2014 \u044d\u0442\u043e key\u2011value \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u0431\u0435\u0437\u00a0\u0438\u043d\u0434\u0435\u043a\u0441\u043e\u0432.<\/strong> \u041a\u043e\u0433\u0434\u0430 \u0443\u00a0\u0442\u0435\u0431\u044f \u0432\u00a0\u0447\u0430\u0442\u0435 5000\u00a0\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439, \u0442\u044b \u0432\u044b\u043d\u0443\u0436\u0434\u0435\u043d \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432\u0441\u044e \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u043e\u0439 JSON. \u0427\u0442\u043e\u0431\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435, \u0442\u044b \u0447\u0438\u0442\u0430\u0435\u0448\u044c \u0432\u0441\u044e \u0441\u0442\u0440\u043e\u043a\u0443, \u043f\u0430\u0440\u0441\u0438\u0448\u044c, \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0448\u044c, \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u0435\u0448\u044c \u043e\u0431\u0440\u0430\u0442\u043d\u043e, \u043f\u0438\u0448\u0435\u0448\u044c. \u042d\u0442\u043e \u0434\u0435\u0441\u044f\u0442\u043a\u0438 \u043c\u0438\u043b\u043b\u0438\u0441\u0435\u043a\u0443\u043d\u0434 \u043d\u0430\u00a0\u043a\u0430\u0436\u0434\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0438 \u0443\u0436\u0430\u0441\u043d\u044b\u0439 \u0438\u0437\u043d\u043e\u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430. \u0410\u00a0\u0435\u0441\u043b\u0438 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043f\u043e\u00a0\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u00a0\u2014 \u043f\u043e\u043b\u0443\u0447\u0438\u0448\u044c\u00a0\u043b\u0438\u043d\u0435\u0439\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u043f\u043e\u00a0\u0442\u044b\u0441\u044f\u0447\u0430\u043c \u043a\u043b\u044e\u0447\u0435\u0439.<\/p>\n<p><strong>\u041d\u0435\u0442 \u0443\u0440\u043e\u0432\u043d\u044f \u00ab\u043c\u0433\u043d\u043e\u0432\u0435\u043d\u043d\u043e\u00bb.<\/strong> \u041b\u044e\u0431\u0430\u044f \u0440\u0430\u0431\u043e\u0442\u0430 \u0441\u00a0\u0434\u0438\u0441\u043a\u043e\u043c \u0432\u00a0RN \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0430\u044f. \u042d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442 \u043c\u0435\u0436\u0434\u0443 \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u0435\u043c \u0447\u0430\u0442\u0430 \u0438 \u043f\u043e\u044f\u0432\u043b\u0435\u043d\u0438\u0435\u043c \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432\u0441\u0435\u0433\u0434\u0430 \u0435\u0441\u0442\u044c \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u043a, \u0432\u00a0\u043a\u043e\u0442\u043e\u0440\u043e\u043c \u044d\u043a\u0440\u0430\u043d\u00a0\u043b\u0438\u0431\u043e \u043f\u0443\u0441\u0442\u043e\u0439,\u00a0\u043b\u0438\u0431\u043e \u0441\u043e \u0441\u043f\u0438\u043d\u043d\u0435\u0440\u043e\u043c. \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u044d\u0442\u043e \u0432\u0438\u0434\u0438\u0442.<\/p>\n<p>\u0420\u0435\u0448\u0435\u043d\u0438\u0435, \u043a \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u043f\u0440\u0438\u0448\u0451\u043b Telegram \u0438 \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u044f \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b \u0432 ONEMIX \u2014 \u0442\u0440\u0438 \u0443\u0440\u043e\u0432\u043d\u044f, \u043a\u0430\u0436\u0434\u044b\u0439 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0441\u0432\u043e\u044e \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c:<\/p>\n<ul>\n<li>\n<p><strong>Level 1: In-memory LRU.<\/strong> ~0 \u043c\u0441, \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u0434\u043e\u0441\u0442\u0443\u043f, \u0434\u0435\u0440\u0436\u0438\u0442 \u0441\u0430\u043c\u044b\u0435 \u0433\u043e\u0440\u044f\u0447\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0435.<\/p>\n<\/li>\n<li>\n<p><strong>Level 2: SQLite \u0441 \u0438\u043d\u0434\u0435\u043a\u0441\u0430\u043c\u0438.<\/strong> 1\u20135 \u043c\u0441, \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439, \u043f\u0435\u0440\u0435\u0436\u0438\u0432\u0430\u0435\u0442 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<\/li>\n<li>\n<p><strong>Level 3: \u0421\u0435\u0440\u0432\u0435\u0440.<\/strong> 200\u20132000 \u043c\u0441, \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u0438\u0441\u0442\u0438\u043d\u044b, \u0437\u0430\u0431\u0438\u0440\u0430\u0435\u043c delta \u0447\u0435\u0440\u0435\u0437 pts.<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u0430\u043b\u044c\u0448\u0435 \u2014 \u043a\u0430\u043a \u044d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0435\u043d\u043e \u0432\u043d\u0443\u0442\u0440\u0438.<\/p>\n<h3>Level 1: In-memory LRU<\/h3>\n<p>\u0426\u0435\u043b\u044c \u044d\u0442\u043e\u0433\u043e \u0443\u0440\u043e\u0432\u043d\u044f \u2014 \u043e\u0442\u0434\u0430\u0432\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u043c\u0433\u043d\u043e\u0432\u0435\u043d\u043d\u043e \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e. \u0411\u0435\u0437 <code>await<\/code>, \u0431\u0435\u0437 \u043f\u0440\u043e\u043c\u0438\u0441\u043e\u0432. \u041a\u043e\u0433\u0434\u0430 React-\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u0442\u0441\u044f \u0432 \u043f\u0435\u0440\u0432\u044b\u0439 \u0440\u0430\u0437 \u0438 \u0441\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442 &#171;\u0434\u0430\u0439 \u043c\u043d\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0447\u0430\u0442\u0430 X&#187;, \u043e\u0442\u0432\u0435\u0442 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0433\u043e\u0442\u043e\u0432 \u0432 \u0442\u043e\u0439 \u0436\u0435 microtask.<\/p>\n<p>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043d\u0430 \u0433\u043e\u043b\u043e\u043c <code>Map<\/code>:<\/p>\n<pre><code class=\"typescript\">class LRUCache&lt;K, V&gt; {  private map = new Map&lt;K, V&gt;();  constructor(private maxSize: number) {}  get(key: K): V | undefined {    const v = this.map.get(key);    if (v !== undefined) {      \/\/ Move to end (most recently used)      this.map.delete(key);      this.map.set(key, v);    }    return v;  }  set(key: K, value: V): void {    if (this.map.has(key)) this.map.delete(key);    this.map.set(key, value);    if (this.map.size &gt; this.maxSize) {      \/\/ Evict least recently used (first entry)      this.map.delete(this.map.keys().next().value!);    }  }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432\u0430\u0436\u043d\u0430\u044f \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u044c ES2015+: <code>Map<\/code> \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u043f\u043e\u0440\u044f\u0434\u043e\u043a \u0432\u0441\u0442\u0430\u0432\u043a\u0438. \u0422\u043e \u0435\u0441\u0442\u044c \u043f\u0435\u0440\u0432\u044b\u0439 \u043a\u043b\u044e\u0447 \u043e\u0442 <code>map.keys().next()<\/code> \u2014 \u044d\u0442\u043e \u0441\u0430\u043c\u044b\u0439 \u0441\u0442\u0430\u0440\u044b\u0439 \u043f\u043e \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u044e. \u041d\u0430 \u043a\u0430\u0436\u0434\u043e\u043c <code>get<\/code> \u043c\u044b \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u0438 \u0441\u043d\u043e\u0432\u0430 \u0432\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u2014 \u043a\u043b\u044e\u0447 \u043f\u0435\u0440\u0435\u0435\u0437\u0436\u0430\u0435\u0442 \u0432 \u043a\u043e\u043d\u0435\u0446. \u041d\u0430 \u043a\u0430\u0436\u0434\u043e\u043c <code>set<\/code> \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u2014 \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u0441\u0430\u043c\u044b\u0439 \u0441\u0442\u0430\u0440\u044b\u0439. \u042d\u0442\u043e \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0439 LRU \u0431\u0435\u0437 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u0434\u0432\u0443\u0441\u0432\u044f\u0437\u043d\u043e\u0439 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b.<\/p>\n<p>\u0412 ONEMIX \u0434\u0432\u0430 \u0443\u0440\u043e\u0432\u043d\u044f L1: \u043e\u0434\u0438\u043d \u0434\u043b\u044f \u0441\u0430\u043c\u0438\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439, \u0432\u0442\u043e\u0440\u043e\u0439 \u2014 \u0434\u043b\u044f pts (\u0442\u043e\u0447\u043a\u0430 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438, \u043a \u043d\u0435\u0439 \u0432\u0435\u0440\u043d\u0451\u043c\u0441\u044f):<\/p>\n<pre><code class=\"typescript\">interface L1Entry {  messages: any[];  pts: number;  loadedAt: number;}const l1Messages = new LRUCache&lt;string, L1Entry&gt;(150);const l1Pts = new Map&lt;string, number&gt;();<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>150 \u0447\u0430\u0442\u043e\u0432 \u00d7 \u0434\u043e 500 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0432 \u043a\u0430\u0436\u0434\u043e\u043c \u2014 \u044d\u0442\u043e \u043f\u043e\u0442\u043e\u043b\u043e\u043a \u043f\u0430\u043c\u044f\u0442\u0438 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e 30\u201360 \u041c\u0411 \u043f\u0440\u0438 \u043e\u0431\u044b\u0447\u043d\u043e\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438, \u0438 \u044d\u0442\u043e \u0442\u0435\u0440\u043f\u0438\u043c\u043e. \u0427\u0430\u0442\u044b, \u043a \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u0434\u0430\u0432\u043d\u043e \u043d\u0435 \u043e\u0431\u0440\u0430\u0449\u0430\u043b\u0438\u0441\u044c, \u0432\u044b\u043f\u0430\u0434\u0430\u044e\u0442, \u043d\u043e \u043e\u0441\u0442\u0430\u044e\u0442\u0441\u044f \u0432 SQLite.<\/p>\n<p>\u0421\u0430\u043c\u043e\u0435 \u0432\u0430\u0436\u043d\u043e\u0435 \u0432 L1 \u2014 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u0434\u043e\u0441\u0442\u0443\u043f:<\/p>\n<pre><code class=\"typescript\">export function getMessagesSync(chatId: string): any[] | null {  const l1 = l1Messages.get(chatId);  if (l1 &amp;&amp; l1.messages.length &gt; 0) return l1.messages;  return null;}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0435 \u044d\u043a\u0440\u0430\u043d\u0430 \u0447\u0430\u0442\u0430 \u044d\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0442\u0430\u043a:<\/p>\n<pre><code class=\"typescript\">const initialMessages = getMessagesSync(chatId) ?? [];const [messages, setMessages] = useState(initialMessages);useEffect(() =&gt; {  \/\/ \u041f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e \u2014 \u0434\u043e\u0433\u0440\u0443\u0437\u043a\u0430 \u0438\u0437 L2 \u0438 L3, \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f  hydrateFromL2AndL3(chatId).then(setMessages);}, [chatId]);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0422\u043e \u0435\u0441\u0442\u044c \u043f\u0440\u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u0438 \u0447\u0430\u0442\u0430 \u043c\u044b \u043d\u0435\u043c\u0435\u0434\u043b\u0435\u043d\u043d\u043e \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u044b\u0432\u0430\u0435\u043c \u0442\u043e, \u0447\u0442\u043e \u0435\u0441\u0442\u044c \u0432 \u043f\u0430\u043c\u044f\u0442\u0438. \u0415\u0441\u043b\u0438 \u0447\u0430\u0442 \u043d\u0435\u0434\u0430\u0432\u043d\u043e \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u043b\u0438 \u2014 \u044d\u0442\u043e \u0432\u0441\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u044d\u043a\u0440\u0430\u043d \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0431\u0435\u0437 \u0435\u0434\u0438\u043d\u043e\u0433\u043e \u0441\u043f\u0438\u043d\u043d\u0435\u0440\u0430. \u041f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e \u0441\u0442\u0430\u0440\u0442\u0443\u0435\u0442 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u043f\u043e\u0445\u043e\u0434 \u0432 SQLite + \u0441\u0435\u0442\u044c, \u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0442\u0438\u0445\u043e \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u0447\u0442\u043e-\u0442\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u043e\u0441\u044c.<\/p>\n<h3>Level 2: SQLite \u0441 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u043c\u0438 \u0438\u043d\u0434\u0435\u043a\u0441\u0430\u043c\u0438<\/h3>\n<p>L1 \u2014 \u043f\u0430\u043c\u044f\u0442\u044c. \u041f\u0430\u043c\u044f\u0442\u044c \u043a\u043e\u043d\u0447\u0430\u0435\u0442\u0441\u044f, \u043a\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0443\u0431\u0438\u0442\u043e \u0441\u0438\u0441\u0442\u0435\u043c\u043e\u0439. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0443\u0436\u0435\u043d \u043f\u0435\u0440\u0441\u0438\u0441\u0442\u0435\u043d\u0442\u043d\u044b\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0435\u0440\u0435\u0436\u0438\u0432\u0430\u0435\u0442 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a.<\/p>\n<p>\u042f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e <code>expo-sqlite<\/code> \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u0438 <code>drizzle-orm<\/code> \u0434\u043b\u044f \u0441\u0445\u0435\u043c\u044b. Drizzle \u2014 \u044d\u0442\u043e \u043d\u0435 ORM \u0432 \u0441\u043c\u044b\u0441\u043b\u0435 \u0433\u0438\u0434\u0440\u0430\u0446\u0438\u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0441 \u043b\u0435\u043d\u0438\u0432\u044b\u043c\u0438 \u0441\u0432\u044f\u0437\u044f\u043c\u0438, \u044d\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0442\u0438\u043f\u043e\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u0431\u0438\u043b\u0434\u0435\u0440 DDL \u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432. \u042f \u0432 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u0435\u0433\u043e \u0434\u043b\u044f \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439, \u0430 \u0441\u0430\u043c\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043f\u0438\u0448\u0443 \u043d\u0430 \u0441\u044b\u0440\u043e\u043c SQL \u2014 \u0434\u043b\u044f \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f.<\/p>\n<h4>\u0421\u0445\u0435\u043c\u0430 \u0438 \u0438\u043d\u0434\u0435\u043a\u0441\u044b<\/h4>\n<pre><code class=\"typescript\">export const messages = sqliteTable(\"messages\", {  id:               text(\"id\").primaryKey(),  chatId:           text(\"chat_id\").notNull(),  senderId:         text(\"sender_id\"),  content:          text(\"content\"),  attachmentType:   text(\"attachment_type\"),  attachmentUrl:    text(\"attachment_url\"),  replyToId:        text(\"reply_to_id\"),  reactions:        text(\"reactions\"),       \/\/ JSON blob  pts:              integer(\"pts\"),  readAt:           text(\"read_at\"),  editedAt:         text(\"edited_at\"),  createdAt:        text(\"created_at\").notNull(),  deletedForAll:    integer(\"deleted_for_all\").default(0),  \/\/ \u2026 \u043f\u0440\u043e\u0447\u0438\u0435 \u043f\u043e\u043b\u044f}, (t) =&gt; ({  \/\/ \u0413\u043b\u0430\u0432\u043d\u044b\u0439 \u043f\u0430\u0442\u0442\u0435\u0440\u043d: \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 N \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0447\u0430\u0442\u0430  idxChatDate: index(\"idx_msg_chat_date\").on(t.chatId, t.createdAt),  \/\/ \u0414\u043b\u044f offset_id \/ around-id  idxChatId:   index(\"idx_msg_chat_id\").on(t.chatId, t.id),  \/\/ \u0414\u043b\u044f diff-sync \u043f\u043e pts  idxPts:      index(\"idx_msg_pts\").on(t.chatId, t.pts),}));<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0422\u0440\u0438 \u0438\u043d\u0434\u0435\u043a\u0441\u0430 \u2014 \u0442\u0440\u0438 \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430. \u041a\u0430\u0436\u0434\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0434\u0430\u043b\u044c\u0448\u0435 \u0431\u0443\u0434\u0435\u0442 \u0442\u043e\u0447\u043d\u043e \u043f\u043e\u043f\u0430\u0434\u0430\u0442\u044c \u0432 \u043e\u0434\u0438\u043d \u0438\u0437 \u043d\u0438\u0445.<\/p>\n<p>JSON \u0432 \u043f\u043e\u043b\u044f\u0445 <code>reactions<\/code>, <code>forwardedFrom<\/code>, <code>mediaGroup<\/code> \u2014 \u0441\u043e\u0437\u043d\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435. \u042d\u0442\u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u0447\u0438\u0442\u0430\u044e\u0442\u0441\u044f \u0432\u0441\u0435\u0433\u0434\u0430 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435\u043c, \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u0444\u0438\u043b\u044c\u0442\u0440\u0443\u044e\u0442\u0441\u044f \u043f\u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u043c\u0443, \u0438 \u043d\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u0445 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u043e\u0437\u043d\u0430\u0447\u0430\u043b\u043e \u0431\u044b JOIN \u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441. SQLite \u2014 \u043d\u0435 PostgreSQL, JOIN&#8217;\u044b \u0437\u0434\u0435\u0441\u044c \u0434\u043e\u0440\u043e\u0436\u0435, \u0447\u0435\u043c \u043a\u0430\u0436\u0435\u0442\u0441\u044f.<\/p>\n<h4>\u0427\u0442\u0435\u043d\u0438\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0445 N \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439<\/h4>\n<pre><code class=\"typescript\">const rows = await db.all(  `SELECT * FROM messages   WHERE chat_id = ? AND deleted_for_all = 0   ORDER BY created_at DESC LIMIT ?`,  [chatId, limit]);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0421 \u0438\u043d\u0434\u0435\u043a\u0441\u043e\u043c <code>(chat_id, created_at)<\/code> \u044d\u0442\u043e range-scan \u2014 \u0434\u0435\u0441\u044f\u0442\u043a\u0438 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u043a\u0443\u043d\u0434 \u043f\u0440\u0438 \u0440\u0430\u0437\u043c\u0435\u0440\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0432 \u0441\u043e\u0442\u043d\u0438 \u0442\u044b\u0441\u044f\u0447 \u0441\u0442\u0440\u043e\u043a. \u0423\u0441\u043b\u043e\u0432\u0438\u0435 <code>deleted_for_all = 0<\/code> \u043e\u0442\u0440\u0435\u0437\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u044d\u0442\u0430\u043f\u0435 \u0447\u0442\u0435\u043d\u0438\u044f \u0438\u043d\u0434\u0435\u043a\u0441\u0430, \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0430 \u043d\u0435 \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e (\u043e\u043d\u0430 \u0443\u0436\u0435 \u0432 \u043f\u043e\u0440\u044f\u0434\u043a\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u0430).<\/p>\n<h4>Cursor-based \u043f\u0430\u0433\u0438\u043d\u0430\u0446\u0438\u044f \u2014 \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435<\/h4>\n<p>\u0417\u0434\u0435\u0441\u044c \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u0435. \u041a\u043e\u0433\u0434\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043a\u0440\u0443\u0442\u0438\u0442 \u043b\u0435\u043d\u0442\u0443 \u043d\u0430\u0432\u0435\u0440\u0445, \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u0434\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0435\u0449\u0451. \u0421\u0430\u043c\u044b\u0439 \u043e\u0447\u0435\u0432\u0438\u0434\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u2014 <code>OFFSET<\/code>. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 <code>LIMIT 50 OFFSET 200<\/code>. \u042d\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0430 \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0445 \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u0445 \u0438 \u043a\u0430\u0442\u0430\u0441\u0442\u0440\u043e\u0444\u0438\u0447\u0435\u0441\u043a\u0438 \u0442\u043e\u0440\u043c\u043e\u0437\u0438\u0442 \u043d\u0430 \u0431\u043e\u043b\u044c\u0448\u0438\u0445: SQLite \u0447\u0435\u0441\u0442\u043d\u043e \u0441\u043a\u0430\u043d\u0438\u0440\u0443\u0435\u0442 \u043f\u0435\u0440\u0432\u044b\u0435 200 \u0441\u0442\u0440\u043e\u043a, \u0447\u0442\u043e\u0431\u044b \u0438\u0445 \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u044c.<\/p>\n<p>Telegram \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 cursor-based \u043f\u0430\u0433\u0438\u043d\u0430\u0446\u0438\u044e \u0447\u0435\u0440\u0435\u0437 <code>offset_id<\/code>: &#171;\u0434\u0430\u0439 \u043c\u043d\u0435 50 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0441\u0442\u0430\u0440\u0448\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0441 id X&#187;. \u042f \u0434\u0435\u043b\u0430\u044e \u0442\u0430\u043a \u0436\u0435:<\/p>\n<pre><code class=\"typescript\">export async function getMessagesBeforeId(  chatId: string,  offsetId: string,  limit = 50): Promise&lt;any[]&gt; {  const db = await getSqlite();  if (!db) return [];  try {    const rows = (await db.all(      `SELECT * FROM messages       WHERE chat_id = ? AND id &lt; ? AND deleted_for_all = 0       ORDER BY id DESC LIMIT ?`,      [chatId, offsetId, limit]    ) as any[]).reverse();    return rows.map(rowToMessage);  } catch (e) {    console.warn(\"[CacheV2] getMessagesBeforeId error:\", e);    return [];  }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0422\u0443\u0442 \u0432\u0430\u0436\u043d\u0430\u044f \u0434\u0435\u0442\u0430\u043b\u044c, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u044f \u0441\u043f\u043e\u0442\u043a\u043d\u0443\u043b\u0441\u044f \u043d\u0435 \u0441\u0440\u0430\u0437\u0443: <strong>id \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u044f \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u044e \u043a\u0430\u043a time-sortable UUID<\/strong> (\u0444\u043e\u0440\u043c\u0430\u0442, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043f\u0435\u0440\u0432\u044b\u0435 \u0431\u0430\u0439\u0442\u044b \u2014 \u043c\u0438\u043b\u043b\u0438\u0441\u0435\u043a\u0443\u043d\u0434\u043d\u044b\u0439 timestamp). \u0422\u043e \u0435\u0441\u0442\u044c \u043b\u0435\u043a\u0441\u0438\u043a\u043e\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u0435 <code>id &lt; ?<\/code> \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0435\u0442 \u0441 \u0445\u0440\u043e\u043d\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u043c. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043e\u0442\u043a\u0430\u0437\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044f <code>created_at<\/code> \u0432 WHERE \u0438 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0441 \u0438\u043d\u0434\u0435\u043a\u0441\u043e\u043c <code>(chat_id, id)<\/code> \u2014 \u043e\u0434\u043d\u0438\u043c range-scan, \u0431\u0435\u0437 \u043f\u043e\u0434\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0432\u0438\u0434\u0430 &#171;select created_at where id = ? then compare&#187;.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0431\u044b id \u0431\u044b\u043b \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u043c (\u043a\u0430\u043a UUIDv4), \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u0431\u044b \u0434\u0435\u043b\u0430\u0442\u044c \u043f\u043e\u0434\u0437\u0430\u043f\u0440\u043e\u0441, \u043b\u0438\u0431\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435 \u2014 \u044d\u0442\u043e \u043b\u0438\u0448\u043d\u0438\u0439 JOIN \u0438\u043b\u0438 \u043b\u0438\u0448\u043d\u044f\u044f \u043a\u043e\u043b\u043e\u043d\u043a\u0430 \u0432 \u0438\u043d\u0434\u0435\u043a\u0441\u0435. Time-sortable id \u2014 \u044d\u0442\u043e \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u0430\u044f \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c.<\/p>\n<h4>Around-id \u2014 \u043f\u0440\u044b\u0436\u043e\u043a \u043a \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c\u0443 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044e<\/h4>\n<p>\u041a\u043e\u0433\u0434\u0430 \u044e\u0437\u0435\u0440 \u0442\u0430\u043f\u0430\u0435\u0442 \u043f\u043e replied-\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044e, \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0435\u0433\u043e \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435: \u043f\u043e\u043b\u043e\u0432\u0438\u043d\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0434\u043e, \u043f\u043e\u043b\u043e\u0432\u0438\u043d\u0430 \u043f\u043e\u0441\u043b\u0435.<\/p>\n<pre><code class=\"typescript\">export async function getMessagesAroundId(  chatId: string,  messageId: string,  limit = 80): Promise&lt;{ messages: any[]; targetIndex: number } | null&gt; {  const db = await getSqlite();  if (!db) return null;  try {    const half = Math.floor(limit \/ 2);    const afterRows = await db.all(      `SELECT * FROM messages       WHERE chat_id = ? AND id &gt;= ? AND deleted_for_all = 0       ORDER BY id ASC LIMIT ?`,      [chatId, messageId, half + 1]    ) as any[];    const beforeRows = (await db.all(      `SELECT * FROM messages       WHERE chat_id = ? AND id &lt; ? AND deleted_for_all = 0       ORDER BY id DESC LIMIT ?`,      [chatId, messageId, half]    ) as any[]).reverse();    const messages = [...beforeRows, ...afterRows].map(rowToMessage);    const targetIndex = messages.findIndex(m =&gt; m.id === messageId);    return { messages, targetIndex };  } catch (e) {    console.warn(\"[CacheV2] getMessagesAroundId error:\", e);    return null;  }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0414\u0432\u0430 index range scan, \u043d\u0438\u043a\u0430\u043a\u0438\u0445 JOIN, \u043d\u0438\u043a\u0430\u043a\u0438\u0445 subquery. \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u043d\u043e \u0438 targetIndex \u2014 \u0447\u0442\u043e\u0431\u044b UI \u043f\u043e\u043d\u044f\u043b, \u043d\u0430 \u043a\u0430\u043a\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043d\u0443\u0436\u043d\u043e \u043f\u0440\u043e\u0441\u043a\u0440\u043e\u043b\u043b\u0438\u0442\u044c \u0438 \u043f\u043e\u0434\u0441\u0432\u0435\u0442\u0438\u0442\u044c.<\/p>\n<h4>Batch insert \u0432\u043d\u0443\u0442\u0440\u0438 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438<\/h4>\n<p>\u041a\u043e\u0433\u0434\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u043f\u0440\u0438\u0441\u043b\u0430\u043b \u043d\u0430\u043c 50 \u043d\u043e\u0432\u044b\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439, \u043d\u0430\u0438\u0432\u043d\u044b\u0439 \u043a\u043e\u0434 \u043d\u0430\u043f\u0438\u0448\u0435\u0442 \u0438\u0445 \u043f\u043e \u043e\u0434\u043d\u043e\u043c\u0443. \u041d\u0430 \u043c\u043e\u0451\u043c \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u043c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435 \u044d\u0442\u043e \u0437\u0430\u043d\u0438\u043c\u0430\u043b\u043e 150\u2013200 \u043c\u0441 \u2014 \u043a\u0430\u0436\u0434\u044b\u0439 INSERT \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438, \u043a\u0430\u0436\u0434\u044b\u0439 \u2014 fsync. \u0420\u0435\u0448\u0435\u043d\u0438\u0435:<\/p>\n<pre><code class=\"typescript\">await db.withTransactionAsync(async () =&gt; {  for (const row of rows) {    await db.runAsync(      `INSERT OR REPLACE INTO messages (        id, chat_id, sender_id, sender_name, sender_avatar,        content, attachment_type, attachment_url, file_name, file_size,        reply_to_id, forwarded_from_id, forwarded_from, media_group, reactions,        pts, read_at, edited_at, created_at, deleted_for_all, is_system, is_neuro      ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,      [        row.id, row.chat_id, row.sender_id, row.sender_name, row.sender_avatar,        row.content, row.attachment_type, row.attachment_url, row.file_name, row.file_size,        row.reply_to_id, row.forwarded_from_id, row.forwarded_from, row.media_group, row.reactions,        row.pts, row.read_at, row.edited_at, row.created_at, row.deleted_for_all, row.is_system, row.is_neuro,      ]    );  }});<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>INSERT OR REPLACE<\/code> \u043d\u0443\u0436\u0435\u043d \u043f\u043e\u0442\u043e\u043c\u0443, \u0447\u0442\u043e \u0443 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043c\u043e\u0433 \u043f\u043e\u043c\u0435\u043d\u044f\u0442\u044c\u0441\u044f \u0441\u0442\u0430\u0442\u0443\u0441 (\u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043d\u043e, \u043e\u0442\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043e). \u0422\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f \u0432\u043e\u043a\u0440\u0443\u0433 \u0431\u0430\u0442\u0447\u0430 \u0434\u0430\u0451\u0442 \u0443\u0441\u043a\u043e\u0440\u0435\u043d\u0438\u0435 \u0432 10\u201320 \u0440\u0430\u0437 \u2014 \u0432\u0441\u0435 50 INSERT \u043b\u0435\u0442\u044f\u0442 \u0432 \u043e\u0434\u0438\u043d fsync.<\/p>\n<p><code>WAL mode<\/code> SQLite (\u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 <code>PRAGMA journal_mode=WAL<\/code> \u043f\u0440\u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438) \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u0435\u0449\u0451 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0447\u0442\u0435\u043d\u0438\u044f \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0437\u0430\u043f\u0438\u0441\u0438, \u0447\u0442\u043e \u043a\u0440\u0438\u0442\u0438\u0447\u043d\u043e \u0434\u043b\u044f UX \u2014 \u043d\u0438\u043a\u0442\u043e \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u0430\u0435\u0442, \u043f\u043e\u043a\u0430 \u0438\u0434\u0451\u0442 sync.<\/p>\n<h3>Level 3: pts-based delta-sync \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c<\/h3>\n<p>L1 \u0438 L2 \u2014 \u044d\u0442\u043e \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0435 \u043a\u044d\u0448\u0438. \u0418\u0441\u0442\u0438\u043d\u0430 \u2014 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435. \u0413\u043b\u0430\u0432\u043d\u044b\u0439 \u0432\u043e\u043f\u0440\u043e\u0441: \u043a\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0438 \u0432\u0438\u0434\u0438\u0442, \u0447\u0442\u043e \u0432 L2 \u0435\u0441\u0442\u044c 1000 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e \u043c\u0435\u0441\u044f\u0446\u0430, \u043a\u0430\u043a \u043f\u043e\u043d\u044f\u0442\u044c, \u043d\u0443\u0436\u043d\u043e \u043b\u0438 \u0447\u0442\u043e-\u0442\u043e \u0434\u043e\u043a\u0430\u0447\u0438\u0432\u0430\u0442\u044c?<\/p>\n<p>\u0421\u0430\u043c\u044b\u0439 \u043f\u043b\u043e\u0445\u043e\u0439 \u043e\u0442\u0432\u0435\u0442 \u2014 &#171;\u0437\u0430\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u0432\u0441\u0451 \u0438 \u0441\u0440\u0430\u0432\u043d\u0438\u0442\u044c&#187;. \u042d\u0442\u043e \u0443\u0431\u0438\u0432\u0430\u0435\u0442 \u0431\u0430\u0442\u0430\u0440\u0435\u044e \u0438 \u0442\u0440\u0430\u0444\u0438\u043a.<\/p>\n<p>Telegram \u0438\u0437\u043e\u0431\u0440\u0451\u043b \u043c\u0435\u0445\u0430\u043d\u0438\u043a\u0443 pts (positive timestamp \/ persistent timestamp \u2014 \u0443 \u0440\u0430\u0437\u043d\u044b\u0445 \u043b\u044e\u0434\u0435\u0439 \u0440\u0430\u0437\u043d\u0430\u044f \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u0430). \u0418\u0434\u0435\u044f \u043f\u0440\u043e\u0441\u0442\u0430\u044f: \u043a\u0430\u0436\u0434\u043e\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0432 \u0447\u0430\u0442\u0435 \u2014 \u043d\u043e\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435, \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435, \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u043f\u0440\u043e\u0447\u0442\u0435\u043d\u0438\u0435 \u2014 \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0435\u0442 \u0441\u0447\u0451\u0442\u0447\u0438\u043a pts \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435. \u041a\u043b\u0438\u0435\u043d\u0442 \u0445\u0440\u0430\u043d\u0438\u0442 \u0443 \u0441\u0435\u0431\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0439 pts. \u041f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0441\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442: &#171;\u0443 \u043c\u0435\u043d\u044f pts=12345, \u0447\u0442\u043e \u043d\u043e\u0432\u043e\u0433\u043e?&#187; \u0421\u0435\u0440\u0432\u0435\u0440 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442: &#171;\u0432\u043e\u0442 \u0432\u0441\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0441 pts=12346&#187;.<\/p>\n<p>\u042d\u0442\u043e \u043b\u0451\u0433\u043a\u0438\u0439 \u0437\u0430\u043f\u0440\u043e\u0441: \u0435\u0441\u043b\u0438 \u0432 \u0447\u0430\u0442\u0435 \u0441 \u043c\u043e\u043c\u0435\u043d\u0442\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e \u0437\u0430\u0445\u043e\u0434\u0430 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0431\u044b\u043b\u043e, \u043e\u0442\u0432\u0435\u0442 \u043f\u0443\u0441\u0442\u043e\u0439. \u0415\u0441\u043b\u0438 \u0431\u044b\u043b\u043e 3 \u043d\u043e\u0432\u044b\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u2014 \u043f\u0440\u0438\u0445\u043e\u0434\u044f\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u044d\u0442\u0438 3.<\/p>\n<p>\u0425\u0440\u0430\u043d\u0435\u043d\u0438\u0435 pts \u0432 \u043c\u043e\u0435\u0439 \u0441\u0445\u0435\u043c\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043e \u0447\u0435\u0440\u0435\u0437 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0442\u0430\u0431\u043b\u0438\u0446\u0443 \u043f\u043b\u044e\u0441 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438:<\/p>\n<pre><code class=\"typescript\">interface SyncState {  pts: number;  qts: number;       \/\/ \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u0447\u0451\u0442\u0447\u0438\u043a \u0434\u043b\u044f \u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439  seq: number;       \/\/ \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u0439 seq \u0434\u043b\u044f \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0439 \u043f\u043e\u0440\u044f\u0434\u043a\u0430  maxReadId: string | null;  lastSync: string | null;  syncOk: boolean;}export async function updateSyncState(chatId: string, updates: Partial&lt;SyncState&gt;): Promise&lt;void&gt; {  const current = await getSyncState(chatId);  const next: SyncState = { ...current, ...updates };  l1SyncState.set(chatId, next);  const db = await getSqlite();  if (!db) return;  await db.runAsync(    `INSERT INTO chat_sync_state (chat_id, pts, qts, seq, max_read_id, last_sync, sync_ok)     VALUES (?, ?, ?, ?, ?, ?, ?)     ON CONFLICT(chat_id) DO UPDATE SET       pts = excluded.pts,       qts = excluded.qts,       seq = excluded.seq,       max_read_id = excluded.max_read_id,       last_sync = excluded.last_sync,       sync_ok = excluded.sync_ok`,    [chatId, next.pts, next.qts, next.seq,     next.maxReadId, next.lastSync ?? new Date().toISOString(), next.syncOk ? 1 : 0]  );}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>ON CONFLICT DO UPDATE<\/code> (UPSERT) \u2014 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 SQLite-\u043f\u0440\u0438\u0451\u043c, \u0447\u0442\u043e\u0431\u044b \u043e\u0434\u043d\u0438\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u043c \u0438 \u0432\u0441\u0442\u0430\u0432\u0438\u0442\u044c, \u0438 \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c. \u041a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 pts \u0432 <code>l1SyncState<\/code> (Map \u0432 \u043f\u0430\u043c\u044f\u0442\u0438) \u0443\u0431\u0438\u0440\u0430\u0435\u0442 \u043f\u043e\u0445\u043e\u0434\u044b \u0432 SQLite \u043d\u0430 \u0433\u043e\u0440\u044f\u0447\u0435\u043c \u043f\u0443\u0442\u0438.<\/p>\n<p>\u0415\u0441\u043b\u0438 sync \u043f\u0440\u0435\u0440\u0432\u0430\u043d (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u043e\u0442\u0435\u0440\u044f \u0441\u0435\u0442\u0438 \u0432 \u0441\u0435\u0440\u0435\u0434\u0438\u043d\u0435), <code>syncOk = false<\/code> \u043f\u043e\u043c\u0435\u0447\u0430\u0435\u0442 \u0447\u0430\u0442 \u043a\u0430\u043a \u0442\u0440\u0435\u0431\u0443\u044e\u0449\u0438\u0439 \u043f\u043e\u043b\u043d\u043e\u0439 \u0440\u0435\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u2014 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0437\u0430\u0445\u043e\u0434 \u0441\u0434\u0435\u043b\u0430\u0435\u0442 \u043d\u0435 delta, \u0430 full fetch. \u042d\u0442\u043e \u0441\u043f\u0430\u0441\u0430\u0435\u0442 \u043e\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043a\u043b\u0438\u0435\u043d\u0442 \u0434\u0443\u043c\u0430\u0435\u0442, \u0447\u0442\u043e \u0432\u0441\u0451 \u0445\u043e\u0440\u043e\u0448\u043e, \u0430 \u043d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435 \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u043b \u043f\u0430\u0447\u043a\u0443 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439.<\/p>\n<h3>\u041a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0446\u0438\u044f \u0442\u0440\u0451\u0445 \u0443\u0440\u043e\u0432\u043d\u0435\u0439<\/h3>\n<p>\u0421\u0430\u043c\u043e\u0435 \u0432\u0430\u0436\u043d\u043e\u0435 \u2014 \u043d\u0435 \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u0438 \u043d\u0435 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043b\u0438\u0448\u043d\u044e\u044e \u0440\u0430\u0431\u043e\u0442\u0443. \u041f\u043e\u043b\u043d\u044b\u0439 \u043f\u0443\u0442\u044c \u0447\u0442\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0432 <code>getMessages<\/code>:<\/p>\n<pre><code class=\"typescript\">export async function getMessages(chatId: string, limit = 80): Promise&lt;any[] | null&gt; {  \/\/ L1: instant  const l1 = l1Messages.get(chatId);  if (l1 &amp;&amp; l1.messages.length &gt; 0) {    const ago = Date.now() - l1.loadedAt;    if (ago &lt; 5 * 60 * 1000) {      return l1.messages.slice(-limit);    }  }  \/\/ L2: SQLite  const db = await getSqlite();  if (db) {    try {      const rows = await db.all(        `SELECT * FROM messages WHERE chat_id = ? AND deleted_for_all = 0         ORDER BY created_at DESC LIMIT ?`,        [chatId, limit]      );      if (rows &amp;&amp; rows.length &gt; 0) {        const msgs = rows.reverse().map(rowToMessage);        l1Messages.set(chatId, {          messages: msgs,          pts: l1Pts.get(chatId) ?? 0,          loadedAt: Date.now(),        });        return msgs;      }    } catch (e) {      console.warn(\"[CacheV2] SQLite getMessages error:\", e);    }  }  \/\/ L2 fallback: AsyncStorage (legacy \u0434\u043b\u044f \u0441\u0442\u0430\u0440\u044b\u0445 \u0432\u0435\u0440\u0441\u0438\u0439 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f)  \/\/ ... \u043e\u043f\u0443\u0441\u043a\u0430\u044e, \u0441\u043c. \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439  \/\/ null \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442: \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0449\u0438\u0439 \u043a\u043e\u0434 \u0434\u043e\u043b\u0436\u0435\u043d \u0437\u0430\u0431\u0440\u0430\u0442\u044c \u0438\u0437 \u0441\u0435\u0442\u0438 (L3)  return null;}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041b\u043e\u0433\u0438\u043a\u0430 \u0441\u0432\u0435\u0440\u0445\u0443 \u0432\u043d\u0438\u0437: \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u0430\u043c\u044f\u0442\u044c, \u043f\u043e\u0442\u043e\u043c \u0434\u0438\u0441\u043a, \u043f\u043e\u0442\u043e\u043c \u0441\u043e\u043e\u0431\u0449\u0438\u0442\u044c \u043d\u0430\u0432\u0435\u0440\u0445 &#171;\u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435\u0442, \u0438\u0434\u0438 \u0432 \u0441\u0435\u0442\u044c&#187;. L1 \u0441\u0430\u043c \u0441\u0435\u0431\u044f \u0433\u0440\u0435\u0435\u0442 \u043e\u0442 L2 \u043f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043f\u043e\u043f\u0430\u0434\u0430\u043d\u0438\u044f \u043d\u0430 L2 \u2014 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0432\u044b\u0437\u043e\u0432 \u0443\u0436\u0435 \u043c\u0433\u043d\u043e\u0432\u0435\u043d\u043d\u044b\u0439. \u0421\u0432\u0435\u0436\u0435\u0441\u0442\u044c L1 \u2014 5 \u043c\u0438\u043d\u0443\u0442; \u043f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u0441\u0447\u0438\u0442\u0430\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u0442\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u043e \u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u043c\u0438 \u0438 \u0438\u0434\u0451\u043c \u0432 L2 \u0437\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0439 \u0432\u0435\u0440\u0441\u0438\u0435\u0439.<\/p>\n<p>\u0417\u0430\u043f\u0438\u0441\u044c \u0437\u0435\u0440\u043a\u0430\u043b\u044c\u043d\u0430\u044f \u2014 \u043f\u0438\u0448\u0435\u043c \u0441\u0440\u0430\u0437\u0443 \u0432 L1 \u0438 L2:<\/p>\n<pre><code class=\"typescript\">export async function saveMessages(chatId: string, msgs: any[]): Promise&lt;void&gt; {  if (!msgs || msgs.length === 0) return;  \/\/ L1 \u2014 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e, \u043c\u0435\u0440\u0434\u0436\u0438\u043c \u0441 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u043c  const existing = l1Messages.get(chatId);  if (existing) {    const existingIds = new Set(existing.messages.map((m: any) =&gt; m.id));    const fresh = msgs.filter((m: any) =&gt; !existingIds.has(m.id));    const merged = [...existing.messages, ...fresh]      .sort((a, b) =&gt; a.created_at &lt; b.created_at ? -1 : 1)      .slice(-500);    l1Messages.set(chatId, { messages: merged, pts: existing.pts, loadedAt: Date.now() });  } else {    const sorted = [...msgs].sort((a, b) =&gt; a.created_at &lt; b.created_at ? -1 : 1);    l1Messages.set(chatId, { messages: sorted, pts: 0, loadedAt: Date.now() });  }  \/\/ L2 \u2014 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e, batch-\u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0435\u0439  const db = await getSqlite();  if (db) {    \/\/ ... batch insert \u0432\u043d\u0443\u0442\u0440\u0438 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 (\u0441\u043c. \u0432\u044b\u0448\u0435)  }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435: L1 \u0430\u043f\u0434\u0435\u0439\u0442\u0438\u0442\u0441\u044f \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e. \u042d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442, \u0447\u0442\u043e \u043f\u043e\u0441\u043b\u0435 <code>saveMessages<\/code> \u0441\u0440\u0430\u0437\u0443 \u0436\u0435 <code>getMessagesSync<\/code> \u0443\u0432\u0438\u0434\u0438\u0442 \u043d\u043e\u0432\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u2014 \u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 SQLite \u0435\u0449\u0451 \u043f\u0438\u0448\u0435\u0442 \u0432 \u0444\u043e\u043d\u0435. \u0414\u043b\u044f UI \u044d\u0442\u043e \u043a\u0440\u0438\u0442\u0438\u0447\u043d\u043e: \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0442\u044b \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043b, \u0434\u043e\u043b\u0436\u043d\u043e \u043f\u043e\u044f\u0432\u0438\u0442\u044c\u0441\u044f \u0432 \u043b\u0435\u043d\u0442\u0435 \u043c\u0433\u043d\u043e\u0432\u0435\u043d\u043d\u043e, \u0430 \u043d\u0435 \u043f\u043e\u0441\u043b\u0435 fsync.<\/p>\n<h3>\u0413\u0440\u0430\u0431\u043b\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u044f \u0441\u043e\u0431\u0440\u0430\u043b<\/h3>\n<h4>\u0413\u0440\u0430\u0431\u043b\u044f 1: \u0445\u043e\u043b\u043e\u0434\u043d\u044b\u0439 \u0441\u0442\u0430\u0440\u0442<\/h4>\n<p>\u041a\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f, L1 \u043f\u0443\u0441\u0442\u043e\u0439. \u0415\u0441\u043b\u0438 \u043f\u0440\u043e\u0441\u0442\u043e \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u0447\u0430\u0442 \u0438 \u043f\u043e\u0439\u0442\u0438 \u0432 L2, \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u043f\u0435\u0440\u0432\u044b\u0439 \u043a\u0430\u0434\u0440 \u0447\u0435\u0440\u0435\u0437 5\u201320 \u043c\u0441 \u2014 \u044d\u0442\u043e \u043e\u0449\u0443\u0449\u0430\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u043b\u0451\u0433\u043a\u0438\u0439 \u043b\u0430\u0433. \u0412 Telegram \u0442\u0430\u043a\u043e\u0433\u043e \u043d\u0435\u0442.<\/p>\n<p>\u0420\u0435\u0448\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u044f \u043f\u043e\u0434\u0441\u043c\u043e\u0442\u0440\u0435\u043b \u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b: \u043f\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, <strong>\u0434\u043e \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0440\u0435\u043d\u0434\u0435\u0440\u0430<\/strong>, \u043f\u0440\u043e\u0433\u0440\u0435\u0432\u0430\u0435\u043c L1 \u0438\u0437 AsyncStorage (\u0432 \u043c\u043e\u0435\u0439 \u0441\u0445\u0435\u043c\u0435 \u0447\u0430\u0441\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u0443\u0431\u043b\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0442\u0443\u0434\u0430 \u0434\u043b\u044f legacy-\u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e\u0441\u0442\u0438 \u0438 \u0431\u044b\u0441\u0442\u0440\u043e\u0433\u043e preload):<\/p>\n<pre><code class=\"typescript\">const _instant: {  chats: any[] | null;  channels: any[] | null;  bots: any[] | null;  folders: any[] | null;  chatById: Map&lt;string, any&gt;;  chatInfo: Map&lt;string, any&gt;;  imageSizes: Map&lt;string, any&gt;;  loaded: boolean;} = {  chats: null, channels: null, bots: null, folders: null,  chatById: new Map(), chatInfo: new Map(), imageSizes: new Map(),  loaded: false,};export async function preloadCacheInstant(): Promise&lt;void&gt; {  if (_instant.loaded) return;  \/\/ \u0427\u0438\u0442\u0430\u0435\u043c \u0432\u0441\u0435 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 AsyncStorage-\u0437\u0430\u043f\u0438\u0441\u0438 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e  const keys = [EXT_KEYS.CHATS_LIST, EXT_KEYS.CHANNELS_LIST, EXT_KEYS.BOTS_LIST, EXT_KEYS.FOLDERS_LIST];  const entries = await AsyncStorage.multiGet(keys);  for (const [key, raw] of entries) {    if (!raw) continue;    try {      const { data } = JSON.parse(raw);      if (key === EXT_KEYS.CHATS_LIST)    _instant.chats    = data;      if (key === EXT_KEYS.CHANNELS_LIST) _instant.channels = data;      if (key === EXT_KEYS.BOTS_LIST)     _instant.bots     = data;      if (key === EXT_KEYS.FOLDERS_LIST)  _instant.folders  = data;    } catch {}  }  if (_instant.chats) {    for (const c of _instant.chats) {      if (c &amp;&amp; c.id) _instant.chatById.set(c.id, c);    }  }  _instant.loaded = true;}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u042d\u0442\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0438\u0437 <code>_layout.tsx<\/code> \u0414\u041e \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0440\u0435\u043d\u0434\u0435\u0440\u0430 \u044d\u043a\u0440\u0430\u043d\u0430 \u0447\u0430\u0442\u043e\u0432. \u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e <code>getChatsSync()<\/code> \u0438 <code>getChatByIdSync()<\/code> \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e, \u0438 \u0441\u043f\u0438\u0441\u043e\u043a \u0447\u0430\u0442\u043e\u0432 \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432 \u043f\u0435\u0440\u0432\u044b\u0439 \u043a\u0430\u0434\u0440.<\/p>\n<h4>\u0413\u0440\u0430\u0431\u043b\u044f 2: AsyncStorage \u0443\u043c\u0435\u0435\u0442 \u043b\u0433\u0430\u0442\u044c \u043e \u0440\u0430\u0437\u043c\u0435\u0440\u0435<\/h4>\n<p>\u042f \u0434\u043e\u043b\u0433\u043e \u043d\u0435 \u043c\u043e\u0433 \u043f\u043e\u043d\u044f\u0442\u044c, \u043f\u043e\u0447\u0435\u043c\u0443 \u043d\u0430 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0445 \u043f\u043e\u0441\u043b\u0435 \u043c\u0435\u0441\u044f\u0446\u0430 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442 \u0442\u043e\u0440\u043c\u043e\u0437\u0438\u0442\u044c \u043d\u0430 \u0440\u043e\u0432\u043d\u043e\u043c \u043c\u0435\u0441\u0442\u0435. \u041e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c: AsyncStorage \u043d\u0430 Android \u2014 \u044d\u0442\u043e SQLite \u043f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c (\u043e\u0434\u043d\u0430 \u0442\u0430\u0431\u043b\u0438\u0446\u0430 key\/value), \u043d\u043e \u0431\u0435\u0437 \u0438\u043d\u0434\u0435\u043a\u0441\u043e\u0432 \u0438 \u0431\u0435\u0437 vacuum. \u041f\u043e\u0441\u043b\u0435 \u0442\u044b\u0441\u044f\u0447 \u0437\u0430\u043f\u0438\u0441\u0435\u0439-\u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0439 \u043e\u043d \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442 \u0447\u0438\u0442\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0447\u0435\u0440\u0435\u0437 \u0434\u0435\u0441\u044f\u0442\u043a\u0438 \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446.<\/p>\n<p>\u0420\u0435\u0448\u0435\u043d\u0438\u044f \u0434\u0432\u0430, \u044f \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u043b \u043e\u0431\u0430:<\/p>\n<ul>\n<li>\n<p>\u0412\u044b\u043d\u0435\u0441\u0442\u0438 \u0432\u0441\u0451, \u0447\u0442\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0442\u044b\u0441\u044f\u0447\u0430\u043c\u0438 (\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f), \u0432 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u0443\u044e SQLite-\u0442\u0430\u0431\u043b\u0438\u0446\u0443 \u0441 \u0438\u043d\u0434\u0435\u043a\u0441\u0430\u043c\u0438. \u042d\u0442\u043e \u0438 \u0435\u0441\u0442\u044c L2.<\/p>\n<\/li>\n<li>\n<p>\u0412 AsyncStorage \u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u043f\u0438\u0441\u043a\u0438 \u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f (\u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u0438\u043b\u043e\u0431\u0430\u0439\u0442 \u043a\u0430\u0436\u0434\u043e\u0435), \u0430 \u043d\u0435 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u043c\u0435\u043b\u043a\u0438\u0445 \u043a\u043b\u044e\u0447\u0435\u0439.<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u043e\u0441\u043b\u0435 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 AsyncStorage-\u043a\u0430\u043a-\u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0435-\u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u043d\u0430 SQLite-\u043a\u0430\u043a-\u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0435 \u0445\u043e\u043b\u043e\u0434\u043d\u044b\u0439 \u0441\u0442\u0430\u0440\u0442 \u0443\u0441\u043a\u043e\u0440\u0438\u043b\u0441\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0432 4 \u0440\u0430\u0437\u0430. \u042d\u0442\u043e \u043a\u0430\u043a \u0440\u0430\u0437 \u0442\u043e, \u0447\u0442\u043e \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u043e \u0443 \u043c\u0435\u043d\u044f \u0432 \u0440\u0435\u0444\u0430\u043a\u0442\u043e\u0440\u0438\u043d\u0433\u0435 <code>message-cache.ts<\/code> (1130 \u0441\u0442\u0440\u043e\u043a \u043c\u043e\u043d\u043e\u043b\u0438\u0442\u0430) \u043d\u0430 <code>cache-v2.ts<\/code> + <code>media-cache.ts<\/code>.<\/p>\n<h4>\u0413\u0440\u0430\u0431\u043b\u044f 3: \u0440\u0435\u0430\u043a\u0446\u0438\u0438 \u0438 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043b\u043e\u043c\u0430\u044e\u0442 \u043d\u0430\u0438\u0432\u043d\u044b\u0439 \u043c\u0435\u0440\u0434\u0436<\/h4>\n<p>\u0415\u0441\u043b\u0438 \u043a\u043e\u0434 \u043c\u0435\u0440\u0434\u0436\u0438\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u043e id \u0438 \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u00ab\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u043d\u0435\u0438\u0437\u043c\u0435\u043d\u043d\u044b\u043c\u0438\u00bb, \u0442\u044b \u043f\u043e\u0442\u0435\u0440\u044f\u0435\u0448\u044c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0440\u0435\u0430\u043a\u0446\u0438\u0439, \u0441\u0442\u0430\u0442\u0443\u0441 \u043f\u0440\u043e\u0447\u0442\u0435\u043d\u0438\u044f \u0438 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f. \u042f \u0434\u043e\u043b\u0433\u043e \u043b\u043e\u0432\u0438\u043b \u0431\u0430\u0433 &#171;\u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0441\u0442\u043e\u0438\u0442 \u043b\u0430\u0439\u043a \u043d\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0438, \u043d\u043e \u0432 \u0442\u0432\u043e\u0451\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 \u0435\u0433\u043e \u043d\u0435\u0442&#187;. \u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u0443\u0442\u044c <code>updateMessage<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u0442 \u0447\u0430\u0441\u0442\u0438\u0447\u043d\u044b\u0435 \u0430\u043f\u0434\u0435\u0439\u0442\u044b \u0438 \u0432 L1, \u0438 \u0432 L2 \u0442\u043e\u0447\u0435\u0447\u043d\u044b\u043c <code>UPDATE<\/code> \u0431\u0435\u0437 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u0438 \u0432\u0441\u0435\u0439 \u0441\u0442\u0440\u043e\u043a\u0438:<\/p>\n<pre><code class=\"typescript\">export async function updateMessage(chatId: string, messageId: string, updates: Partial&lt;any&gt;): Promise&lt;void&gt; {  \/\/ L1  const l1 = l1Messages.get(chatId);  if (l1) {    l1.messages = l1.messages.map((m: any) =&gt;      m.id === messageId ? { ...m, ...updates } : m    );  }  \/\/ L2 \u2014 \u0447\u0430\u0441\u0442\u0438\u0447\u043d\u044b\u0439 UPDATE  const db = await getSqlite();  if (db) {    const setClauses: string[] = [];    const values: any[] = [];    if (updates.content !== undefined)   { setClauses.push(\"content = ?\");   values.push(updates.content); }    if (updates.read_at !== undefined)   { setClauses.push(\"read_at = ?\");   values.push(updates.read_at); }    if (updates.edited_at !== undefined) { setClauses.push(\"edited_at = ?\"); values.push(updates.edited_at); }    if (updates.reactions !== undefined) { setClauses.push(\"reactions = ?\"); values.push(JSON.stringify(updates.reactions)); }    if (setClauses.length &gt; 0) {      await db.runAsync(        `UPDATE messages SET ${setClauses.join(\", \")} WHERE id = ?`,        [...values, messageId]      );    }  }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>saveMessages<\/code> (\u043f\u043e\u043b\u043d\u0430\u044f \u0432\u0441\u0442\u0430\u0432\u043a\u0430) \u0438 <code>updateMessage<\/code> (\u0447\u0430\u0441\u0442\u0438\u0447\u043d\u044b\u0439 \u0430\u043f\u0434\u0435\u0439\u0442) \u2014 \u044d\u0442\u043e \u0440\u0430\u0437\u043d\u044b\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u043d\u0430 \u0440\u0430\u0437\u043d\u044b\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\u0445, \u0438 \u0441\u043c\u0435\u0448\u0438\u0432\u0430\u0442\u044c \u0438\u0445 \u043d\u0435\u043b\u044c\u0437\u044f.<\/p>\n<h4>\u0413\u0440\u0430\u0431\u043b\u044f 4: lazy-init SQLite<\/h4>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043b SQLite \u043d\u0430 \u0441\u0442\u0430\u0440\u0442\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e \u0432 <code>_layout.tsx<\/code>. \u042d\u0442\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u043b\u043e 80\u2013150 \u043c\u0441 \u043a \u0445\u043e\u043b\u043e\u0434\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u0440\u0442\u0443, \u0447\u0442\u043e \u043c\u043d\u0435 \u043d\u0435 \u043d\u0440\u0430\u0432\u0438\u043b\u043e\u0441\u044c. \u042f \u043f\u0435\u0440\u0435\u043d\u0451\u0441 init \u0432 lazy-pattern:<\/p>\n<pre><code class=\"typescript\">let _sqliteReady = false;let _sqliteError = false;let _db: any = null;async function getSqlite() {  if (_sqliteReady) return _db;  if (_sqliteError) return null;  try {    const dbMod = await import(\".\/db\/index\");    await dbMod.initDb();    _db = dbMod.getDb();    _sqliteReady = true;    return _db;  } catch (e) {    console.warn(\"[CacheV2] SQLite init failed, falling back to AsyncStorage:\", e);    _sqliteError = true;    return null;  }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0435\u0440\u0432\u044b\u0439 \u043f\u043e\u0445\u043e\u0434 \u0432 L2 \u0442\u0435\u043f\u0435\u0440\u044c \u0441\u0442\u043e\u0438\u0442 80\u2013150 \u043c\u0441, \u043d\u043e \u043e\u043d \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e, \u043a\u0430\u043a L1 \u0443\u0436\u0435 \u043e\u0442\u0434\u0430\u043b \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u043f\u0435\u0440\u0432\u044b\u0439 \u043a\u0430\u0434\u0440. \u041a \u043c\u043e\u043c\u0435\u043d\u0442\u0443, \u043a\u043e\u0433\u0434\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0430\u0447\u043d\u0451\u0442 \u0447\u0442\u043e-\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c, SQLite \u0443\u0436\u0435 \u0433\u043e\u0442\u043e\u0432. \u0418 \u0435\u0441\u043b\u0438 init \u0443\u043f\u0430\u043b (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043d\u0430 \u0441\u0442\u0430\u0440\u043e\u043c Android device \u0431\u0435\u0437 \u043c\u0435\u0441\u0442\u0430 \u043d\u0430 \u0434\u0438\u0441\u043a\u0435), \u0444\u043b\u0430\u0433 <code>_sqliteError<\/code> \u043d\u0435 \u0434\u0430\u0441\u0442 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 \u043f\u044b\u0442\u0430\u0442\u044c\u0441\u044f \u0441\u043d\u043e\u0432\u0430 \u0438 \u0441\u043d\u043e\u0432\u0430 \u2014 \u043e\u043d\u0430 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0430 AsyncStorage-fallback.<\/p>\n<h3>\u0427\u0442\u043e \u0432 \u0438\u0442\u043e\u0433\u0435<\/h3>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0432\u0441\u0435\u0445 \u044d\u0442\u0438\u0445 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432\u0435\u0434\u0451\u0442 \u0441\u0435\u0431\u044f \u0442\u0430\u043a:<\/p>\n<ul>\n<li>\n<p>\u041e\u0442\u043a\u0440\u044b\u0442\u0438\u0435 \u0447\u0430\u0442\u0430 \u2014 \u043c\u0433\u043d\u043e\u0432\u0435\u043d\u043d\u043e, \u0435\u0441\u043b\u0438 \u043e\u043d \u043d\u0435\u0434\u0430\u0432\u043d\u043e \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u043b\u0441\u044f (L1 hit), \u0438 \u0437\u0430 5\u201310 \u043c\u0441 \u0432 \u0445\u043e\u043b\u043e\u0434\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 (L2 hit).<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u043e\u043a\u0440\u0443\u0442\u043a\u0430 \u0438\u0441\u0442\u043e\u0440\u0438\u0438 \u2014 \u0431\u0435\u0437 \u043f\u0443\u0441\u0442\u043e\u0442, \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0432 50 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0438\u0437 L2 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u0437\u0430 ~5 \u043c\u0441.<\/p>\n<\/li>\n<li>\n<p>\u0425\u043e\u043b\u043e\u0434\u043d\u044b\u0439 \u0441\u0442\u0430\u0440\u0442 \u2014 \u0441\u043f\u0438\u0441\u043e\u043a \u0447\u0430\u0442\u043e\u0432 \u0432 \u043f\u0435\u0440\u0432\u043e\u043c \u043a\u0430\u0434\u0440\u0435, \u0431\u0435\u0437 \u0441\u043f\u0438\u043d\u043d\u0435\u0440\u0430.<\/p>\n<\/li>\n<li>\n<p>\u041e\u0431\u044a\u0451\u043c \u0442\u0440\u0430\u0444\u0438\u043a\u0430 \u043d\u0430 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u044e \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0439 \u2014 \u043f\u0440\u0438\u0445\u043e\u0434\u044f\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u0435\u043b\u044c\u0442\u044b \u043f\u043e pts, \u0430 \u043d\u0435 \u0432\u0441\u044f \u0438\u0441\u0442\u043e\u0440\u0438\u044f.<\/p>\n<\/li>\n<\/ul>\n<p>\u0413\u043b\u0430\u0432\u043d\u044b\u0439 \u0443\u0440\u043e\u043a, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u044f \u0434\u043b\u044f \u0441\u0435\u0431\u044f \u0441\u0434\u0435\u043b\u0430\u043b: <strong>\u0432 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u0443\u0440\u043e\u0432\u0435\u043d\u044c &#171;0 \u043c\u0441 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e&#187; \u2014 \u044d\u0442\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u044b\u0439 \u0441\u043b\u043e\u0439<\/strong>, \u043d\u0435 \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446\u0438\u044f. \u0411\u0435\u0437 \u043d\u0435\u0433\u043e \u0432\u0441\u0435 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0441\u043b\u043e\u0438 \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u044b\u043c\u0438, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043c\u0435\u0436\u0434\u0443 \u043d\u0438\u043c\u0438 \u0438 \u044e\u0437\u0435\u0440\u043e\u043c \u0432\u0441\u0435\u0433\u0434\u0430 \u0435\u0441\u0442\u044c \u043f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0439 \u043a\u0430\u0434\u0440. \u0421 \u043d\u0438\u043c \u0432\u0441\u0451 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043a\u0430\u043a \u0443\u0433\u043e\u0434\u043d\u043e \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u044b\u043c \u2014 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u044d\u0442\u043e\u0433\u043e \u043d\u0435 \u0443\u0432\u0438\u0434\u0438\u0442.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0434\u0435\u043b\u0430\u0435\u0442\u0435 \u0447\u0442\u043e-\u0442\u043e \u043f\u043e\u0445\u043e\u0436\u0435\u0435 \u2014 \u0432\u043e\u0437\u044c\u043c\u0438\u0442\u0435 \u044d\u0442\u0438 \u0442\u0440\u0438 \u0443\u0440\u043e\u0432\u043d\u044f \u043a\u0430\u043a \u043e\u0442\u043f\u0440\u0430\u0432\u043d\u0443\u044e \u0442\u043e\u0447\u043a\u0443. \u041e\u043d\u0438 \u0441\u0438\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0449\u0435, \u0447\u0435\u043c \u043a\u0430\u0436\u0443\u0442\u0441\u044f, \u0438 \u0441\u0438\u043b\u044c\u043d\u043e \u043b\u0443\u0447\u0448\u0435, \u0447\u0435\u043c \u043b\u044e\u0431\u043e\u0439 \u043e\u0434\u043d\u043e\u0443\u0440\u043e\u0432\u043d\u0435\u0432\u044b\u0439 \u043f\u043e\u0434\u0445\u043e\u0434.<\/p>\n<p>\u0421\u043f\u0430\u0441\u0438\u0431\u043e, \u0447\u0442\u043e \u0434\u043e\u0447\u0438\u0442\u0430\u043b\u0438. \u042f \u0434\u0435\u043b\u0430\u044e ONEMIX \u043a\u0430\u043a \u0441\u043e\u043b\u043e-\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a, \u0438 \u043f\u0438\u0448\u0443 \u0432 Telegram-\u043a\u0430\u043d\u0430\u043b \u043f\u0440\u043e \u0438\u043d\u0436\u0435\u043d\u0435\u0440\u043d\u044b\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043f\u043e \u0445\u043e\u0434\u0443 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u2014 \u0442\u0430\u043c \u043a\u043e\u0440\u043e\u0447\u0435, \u0447\u0430\u0449\u0435 \u0438 \u0431\u0435\u0437 \u0440\u0435\u0434\u0430\u043a\u0442\u0443\u0440\u044b. \u0415\u0441\u043b\u0438 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0435\u043d \u0442\u0430\u043a\u043e\u0439 \u0444\u043e\u0440\u043c\u0430\u0442 \u2014 \u0437\u0430\u0445\u043e\u0434\u0438\u0442\u0435.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u0432\u043e\u043f\u0440\u043e\u0441\u044b \u043f\u043e \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u043c \u043a\u0443\u0441\u043a\u0430\u043c \u043a\u043e\u0434\u0430 \u0438\u043b\u0438 \u0445\u043e\u0442\u0438\u0442\u0435 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c \u0441\u043c\u0435\u0436\u043d\u0443\u044e \u0437\u0430\u0434\u0430\u0447\u0443 \u2014 \u043f\u0438\u0448\u0438\u0442\u0435 \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438, \u043d\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0441\u0442\u0430\u0442\u044c\u044e \u043f\u043e\u0439\u0434\u0451\u0442 \u0441\u0430\u043c\u0430\u044f \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u0430\u044f.<\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1033502\/\">https:\/\/habr.com\/ru\/articles\/1033502\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u0423\u0440\u043e\u0432\u0435\u043d\u044c: middle\/senior \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u0430\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430, React Native, SQLite \u0421\u0442\u0435\u043a: Expo SDK 54, React Native, expo-sqlite, drizzle-orm, AsyncStorage, TypeScript \u0427\u0442\u043e \u0432\u043d\u0443\u0442\u0440\u0438: \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430, \u043a\u043e\u0434 \u0438\u0437 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u043d\u0430, \u0433\u0440\u0430\u0431\u043b\u0438, \u0446\u0438\u0444\u0440\u044b\u0412\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0435\u042f \u0434\u0435\u043b\u0430\u044e \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440 ONEMIX \u043d\u0430 React Native. \u041a \u043c\u043e\u043c\u0435\u043d\u0442\u0443, \u043a\u043e\u0433\u0434\u0430 \u044f \u043d\u0430\u0447\u0430\u043b \u043f\u0438\u0441\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u043f\u043e\u0441\u0442, \u0432 \u043d\u0451\u043c \u0443\u0436\u0435 \u0431\u043e\u043b\u044c\u0448\u0435 \u0434\u0435\u0441\u044f\u0442\u043a\u0430 \u044d\u043a\u0440\u0430\u043d\u043e\u0432, \u0433\u0440\u0443\u043f\u043f\u043e\u0432\u044b\u0435 WebRTC-\u0437\u0432\u043e\u043d\u043a\u0438 \u0447\u0435\u0440\u0435\u0437 LiveKit, E2E \u043d\u0430 Double Ratchet + Sealed Sender, push-\u043d\u043e\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0441 cold-start \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0435\u0439 \u0438 \u0434\u0435\u0441\u043a\u0442\u043e\u043f-\u0432\u0435\u0440\u0441\u0438\u044f \u043d\u0430 Electron. \u041d\u043e \u0441\u0430\u043c\u044b\u043c \u0432\u0430\u0436\u043d\u044b\u043c \u043a\u0443\u0441\u043a\u043e\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043e\u0449\u0443\u0449\u0435\u043d\u0438\u0435 \u043e\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043e\u043a\u0430\u0437\u0430\u043b\u0441\u044f \u043d\u0435 \u0437\u0432\u0443\u043a \u0438 \u043d\u0435 \u0432\u0438\u0434\u0435\u043e. \u0410 \u0442\u043e, \u043d\u0430\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0431\u044b\u0441\u0442\u0440\u043e \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0447\u0430\u0442.\u0415\u0441\u043b\u0438 \u0432\u044b \u0445\u043e\u0442\u044c \u0440\u0430\u0437 \u0434\u0435\u043b\u0430\u043b\u0438 \u0441\u043f\u0438\u0441\u043e\u043a \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0430 React Native, \u0432\u044b \u0437\u043d\u0430\u0435\u0442\u0435 \u044d\u0442\u0443 \u0431\u043e\u043b\u044c: \u043e\u0442\u043a\u0440\u044b\u043b \u0447\u0430\u0442 \u2014 \u043f\u0443\u0441\u0442\u043e\u0439 \u044d\u043a\u0440\u0430\u043d \u043d\u0430 200\u2013800 \u043c\u0441, \u043f\u043e\u0442\u043e\u043c \u043f\u043e\u0434\u0433\u0440\u0443\u0437\u043a\u0430, \u043f\u043e\u0442\u043e\u043c \u0441\u043a\u0430\u0447\u043e\u043a \u043f\u0440\u0438 \u0434\u043e\u043a\u0440\u0443\u0442\u043a\u0435 \u043d\u0430\u0432\u0435\u0440\u0445. \u0412 Telegram \u0442\u0430\u043a\u043e\u0433\u043e \u043d\u0435 \u0431\u044b\u0432\u0430\u0435\u0442: \u043e\u0442\u043a\u0440\u044b\u043b \u2014 \u043c\u0433\u043d\u043e\u0432\u0435\u043d\u043d\u043e \u0443\u0432\u0438\u0434\u0435\u043b \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u043f\u0440\u043e\u043a\u0440\u0443\u0442\u0438\u043b \u043d\u0430\u0432\u0435\u0440\u0445 \u2014 \u043d\u0438\u043a\u0430\u043a\u0438\u0445 \u043f\u0443\u0441\u0442\u043e\u0442, \u0438\u0441\u0442\u043e\u0440\u0438\u044f \u0438\u0434\u0451\u0442 \u0441\u043f\u043b\u043e\u0448\u043d\u043e\u0439 \u043b\u0435\u043d\u0442\u043e\u0439.\u042f \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043b\u0441\u044f \u0441 \u044d\u0442\u0438\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0435\u0441\u044f\u0446\u0435\u0432. \u0412 \u0438\u0442\u043e\u0433\u0435 \u043f\u0440\u0438\u0448\u0451\u043b \u043a \u0442\u0440\u0451\u0445\u0443\u0440\u043e\u0432\u043d\u0435\u0432\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0435 \u043a\u044d\u0448\u0430, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0438 \u0445\u043e\u0447\u0443 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c. \u042d\u0442\u043e \u043d\u0435 \u0442\u0435\u043e\u0440\u0438\u044f \u2014 \u044d\u0442\u043e \u043a\u043e\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u0435\u0439\u0447\u0430\u0441 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u043d\u0435. \u041f\u043e\u043a\u0430\u0436\u0443 \u043a\u0430\u043a \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043e, \u043a\u0430\u043a\u0438\u0435 \u0431\u044b\u043b\u0438 \u0442\u0443\u043f\u0438\u043a\u0438 \u0438 \u043a\u0430\u043a\u0438\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043e\u043a\u0430\u0437\u0430\u043b\u0438\u0441\u044c \u043a\u0440\u0438\u0442\u0438\u0447\u043d\u044b\u043c\u0438.\u041f\u043e\u0447\u0435\u043c\u0443 \u043e\u0434\u0438\u043d \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442\u0421\u0430\u043c\u044b\u0439 \u0447\u0430\u0441\u0442\u044b\u0439 \u043f\u043e\u0434\u0445\u043e\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u044f \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u044e \u0432 \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u0430\u0445 \u0438 open-source \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u0430\u0445 \u043d\u0430 RN: \u0432\u0441\u0451 \u0445\u043e\u0434\u0438\u0442 \u0432 \u0441\u0435\u0442\u044c, \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435 \u043a\u044d\u0448\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432 AsyncStorage. \u042d\u0442\u043e\u0442 \u043f\u043e\u0434\u0445\u043e\u0434 \u0440\u0430\u0437\u0432\u0430\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u043e \u0442\u0440\u0451\u043c \u043f\u0440\u0438\u0447\u0438\u043d\u0430\u043c \u0441\u0440\u0430\u0437\u0443.\u0421\u0435\u0442\u044c \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u0430\u044f. \u0414\u0430\u0436\u0435 \u043d\u0430\u00a0\u0445\u043e\u0440\u043e\u0448\u0435\u043c 4G round\u2011trip \u0434\u043e\u00a0\u0441\u0435\u0440\u0432\u0435\u0440\u0430\u00a0\u2014 \u044d\u0442\u043e 100\u2013300\u00a0\u043c\u0441. \u041d\u0430\u00a0\u043c\u0435\u0442\u0440\u043e \u0438\u043b\u0438\u00a0\u0432\u00a0\u043f\u043e\u0434\u0432\u0430\u043b\u0435\u00a0\u2014 1\u20133\u00a0\u0441\u0435\u043a\u0443\u043d\u0434\u044b. \u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0447\u0430\u0442 \u0441\u00a0\u0442\u0430\u043a\u043e\u0439 \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u043e\u0439 \u043d\u0435\u043b\u044c\u0437\u044f, \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0443\u0439\u0434\u0451\u0442.AsyncStorage\u00a0\u2014 \u044d\u0442\u043e key\u2011value \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u0431\u0435\u0437\u00a0\u0438\u043d\u0434\u0435\u043a\u0441\u043e\u0432. \u041a\u043e\u0433\u0434\u0430 \u0443\u00a0\u0442\u0435\u0431\u044f \u0432\u00a0\u0447\u0430\u0442\u0435 5000\u00a0\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439, \u0442\u044b \u0432\u044b\u043d\u0443\u0436\u0434\u0435\u043d \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432\u0441\u044e \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u043e\u0439 JSON. \u0427\u0442\u043e\u0431\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435, \u0442\u044b \u0447\u0438\u0442\u0430\u0435\u0448\u044c \u0432\u0441\u044e \u0441\u0442\u0440\u043e\u043a\u0443, \u043f\u0430\u0440\u0441\u0438\u0448\u044c, \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0448\u044c, \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u0435\u0448\u044c \u043e\u0431\u0440\u0430\u0442\u043d\u043e, \u043f\u0438\u0448\u0435\u0448\u044c. \u042d\u0442\u043e \u0434\u0435\u0441\u044f\u0442\u043a\u0438 \u043c\u0438\u043b\u043b\u0438\u0441\u0435\u043a\u0443\u043d\u0434 \u043d\u0430\u00a0\u043a\u0430\u0436\u0434\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0438 \u0443\u0436\u0430\u0441\u043d\u044b\u0439 \u0438\u0437\u043d\u043e\u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430. \u0410\u00a0\u0435\u0441\u043b\u0438 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043f\u043e\u00a0\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u00a0\u2014 \u043f\u043e\u043b\u0443\u0447\u0438\u0448\u044c\u00a0\u043b\u0438\u043d\u0435\u0439\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u043f\u043e\u00a0\u0442\u044b\u0441\u044f\u0447\u0430\u043c \u043a\u043b\u044e\u0447\u0435\u0439.\u041d\u0435\u0442 \u0443\u0440\u043e\u0432\u043d\u044f \u00ab\u043c\u0433\u043d\u043e\u0432\u0435\u043d\u043d\u043e\u00bb. \u041b\u044e\u0431\u0430\u044f \u0440\u0430\u0431\u043e\u0442\u0430 \u0441\u00a0\u0434\u0438\u0441\u043a\u043e\u043c \u0432\u00a0RN \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0430\u044f. \u042d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442 \u043c\u0435\u0436\u0434\u0443 \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u0435\u043c \u0447\u0430\u0442\u0430 \u0438 \u043f\u043e\u044f\u0432\u043b\u0435\u043d\u0438\u0435\u043c \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432\u0441\u0435\u0433\u0434\u0430 \u0435\u0441\u0442\u044c \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u043a, \u0432\u00a0\u043a\u043e\u0442\u043e\u0440\u043e\u043c \u044d\u043a\u0440\u0430\u043d\u00a0\u043b\u0438\u0431\u043e \u043f\u0443\u0441\u0442\u043e\u0439,\u00a0\u043b\u0438\u0431\u043e \u0441\u043e \u0441\u043f\u0438\u043d\u043d\u0435\u0440\u043e\u043c. \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u044d\u0442\u043e \u0432\u0438\u0434\u0438\u0442.\u0420\u0435\u0448\u0435\u043d\u0438\u0435, \u043a \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u043f\u0440\u0438\u0448\u0451\u043b Telegram \u0438 \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u044f \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b \u0432 ONEMIX \u2014 \u0442\u0440\u0438 \u0443\u0440\u043e\u0432\u043d\u044f, \u043a\u0430\u0436\u0434\u044b\u0439 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0441\u0432\u043e\u044e \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c:Level 1: In-memory LRU. ~0 \u043c\u0441, \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u0434\u043e\u0441\u0442\u0443\u043f, \u0434\u0435\u0440\u0436\u0438\u0442 \u0441\u0430\u043c\u044b\u0435 \u0433\u043e\u0440\u044f\u0447\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0435.Level 2: SQLite \u0441 \u0438\u043d\u0434\u0435\u043a\u0441\u0430\u043c\u0438. 1\u20135 \u043c\u0441, \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439, \u043f\u0435\u0440\u0435\u0436\u0438\u0432\u0430\u0435\u0442 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.Level 3: \u0421\u0435\u0440\u0432\u0435\u0440. 200\u20132000 \u043c\u0441, \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u0438\u0441\u0442\u0438\u043d\u044b, \u0437\u0430\u0431\u0438\u0440\u0430\u0435\u043c delta \u0447\u0435\u0440\u0435\u0437 pts.\u0414\u0430\u043b\u044c\u0448\u0435 \u2014 \u043a\u0430\u043a \u044d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0435\u043d\u043e \u0432\u043d\u0443\u0442\u0440\u0438.Level 1: In-memory LRU\u0426\u0435\u043b\u044c \u044d\u0442\u043e\u0433\u043e \u0443\u0440\u043e\u0432\u043d\u044f \u2014 \u043e\u0442\u0434\u0430\u0432\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u043c\u0433\u043d\u043e\u0432\u0435\u043d\u043d\u043e \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e. \u0411\u0435\u0437 await, \u0431\u0435\u0437 \u043f\u0440\u043e\u043c\u0438\u0441\u043e\u0432. \u041a\u043e\u0433\u0434\u0430 React-\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u0442\u0441\u044f \u0432 \u043f\u0435\u0440\u0432\u044b\u0439 \u0440\u0430\u0437 \u0438 \u0441\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442 &#171;\u0434\u0430\u0439 \u043c\u043d\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0447\u0430\u0442\u0430 X&#187;, \u043e\u0442\u0432\u0435\u0442 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0433\u043e\u0442\u043e\u0432 \u0432 \u0442\u043e\u0439 \u0436\u0435 microtask.\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043d\u0430 \u0433\u043e\u043b\u043e\u043c Map:class LRUCache&lt;K, V&gt; {  private map = new Map&lt;K, V&gt;();  constructor(private maxSize: number) {}  get(key: K): V | undefined {    const v = this.map.get(key);    if (v !== undefined) {      \/\/ Move to end (most recently used)      this.map.delete(key);      this.map.set(key, v);    }    return v;  }  set(key: K, value: V): void {    if (this.map.has(key)) this.map.delete(key);    this.map.set(key, value);    if (this.map.size &gt; this.maxSize) {      \/\/ Evict least recently used (first entry)      this.map.delete(this.map.keys().next().value!);    }  }}\u0417\u0434\u0435\u0441\u044c \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432\u0430\u0436\u043d\u0430\u044f \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u044c ES2015+: Map \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u043f\u043e\u0440\u044f\u0434\u043e\u043a \u0432\u0441\u0442\u0430\u0432\u043a\u0438. \u0422\u043e \u0435\u0441\u0442\u044c \u043f\u0435\u0440\u0432\u044b\u0439 \u043a\u043b\u044e\u0447 \u043e\u0442 map.keys().next() \u2014 \u044d\u0442\u043e \u0441\u0430\u043c\u044b\u0439 \u0441\u0442\u0430\u0440\u044b\u0439 \u043f\u043e \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u044e. \u041d\u0430 \u043a\u0430\u0436\u0434\u043e\u043c get \u043c\u044b \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u0438 \u0441\u043d\u043e\u0432\u0430 \u0432\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u2014 \u043a\u043b\u044e\u0447 \u043f\u0435\u0440\u0435\u0435\u0437\u0436\u0430\u0435\u0442 \u0432 \u043a\u043e\u043d\u0435\u0446. \u041d\u0430 \u043a\u0430\u0436\u0434\u043e\u043c set \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u2014 \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u0441\u0430\u043c\u044b\u0439 \u0441\u0442\u0430\u0440\u044b\u0439. \u042d\u0442\u043e \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0439 LRU \u0431\u0435\u0437 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u0434\u0432\u0443\u0441\u0432\u044f\u0437\u043d\u043e\u0439 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b.\u0412 ONEMIX \u0434\u0432\u0430 \u0443\u0440\u043e\u0432\u043d\u044f L1: \u043e\u0434\u0438\u043d \u0434\u043b\u044f \u0441\u0430\u043c\u0438\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439, \u0432\u0442\u043e\u0440\u043e\u0439 \u2014 \u0434\u043b\u044f pts (\u0442\u043e\u0447\u043a\u0430 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438, \u043a \u043d\u0435\u0439 \u0432\u0435\u0440\u043d\u0451\u043c\u0441\u044f):interface L1Entry {  messages: any[];  pts: number;  loadedAt: number;}const l1Messages = new LRUCache&lt;string, L1Entry&gt;(150);const l1Pts = new Map&lt;string, number&gt;();150 \u0447\u0430\u0442\u043e\u0432 \u00d7 \u0434\u043e 500 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0432 \u043a\u0430\u0436\u0434\u043e\u043c \u2014 \u044d\u0442\u043e \u043f\u043e\u0442\u043e\u043b\u043e\u043a \u043f\u0430\u043c\u044f\u0442\u0438 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e 30\u201360 \u041c\u0411 \u043f\u0440\u0438 \u043e\u0431\u044b\u0447\u043d\u043e\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438, \u0438 \u044d\u0442\u043e \u0442\u0435\u0440\u043f\u0438\u043c\u043e. \u0427\u0430\u0442\u044b, \u043a \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u0434\u0430\u0432\u043d\u043e \u043d\u0435 \u043e\u0431\u0440\u0430\u0449\u0430\u043b\u0438\u0441\u044c, \u0432\u044b\u043f\u0430\u0434\u0430\u044e\u0442, \u043d\u043e \u043e\u0441\u0442\u0430\u044e\u0442\u0441\u044f \u0432 SQLite.\u0421\u0430\u043c\u043e\u0435 \u0432\u0430\u0436\u043d\u043e\u0435 \u0432 L1 \u2014 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u0434\u043e\u0441\u0442\u0443\u043f:export function getMessagesSync(chatId: string): any[] | null {  const l1 = l1Messages.get(chatId);  if (l1 &amp;&amp; l1.messages.length &gt; 0) return l1.messages;  return null;}\u0412 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0435 \u044d\u043a\u0440\u0430\u043d\u0430 \u0447\u0430\u0442\u0430 \u044d\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0442\u0430\u043a:const initialMessages = getMessagesSync(chatId) ?? [];const [messages, setMessages] = useState(initialMessages);useEffect(() =&gt; {  \/\/ \u041f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e \u2014 \u0434\u043e\u0433\u0440\u0443\u0437\u043a\u0430 \u0438\u0437 L2 \u0438 L3, \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f  hydrateFromL2AndL3(chatId).then(setMessages);}, [chatId]);\u0422\u043e \u0435\u0441\u0442\u044c \u043f\u0440\u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u0438 \u0447\u0430\u0442\u0430 \u043c\u044b \u043d\u0435\u043c\u0435\u0434\u043b\u0435\u043d\u043d\u043e \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u044b\u0432\u0430\u0435\u043c \u0442\u043e, \u0447\u0442\u043e \u0435\u0441\u0442\u044c \u0432 \u043f\u0430\u043c\u044f\u0442\u0438. \u0415\u0441\u043b\u0438 \u0447\u0430\u0442 \u043d\u0435\u0434\u0430\u0432\u043d\u043e \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u043b\u0438 \u2014 \u044d\u0442\u043e \u0432\u0441\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u044d\u043a\u0440\u0430\u043d \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0431\u0435\u0437 \u0435\u0434\u0438\u043d\u043e\u0433\u043e \u0441\u043f\u0438\u043d\u043d\u0435\u0440\u0430. \u041f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e \u0441\u0442\u0430\u0440\u0442\u0443\u0435\u0442 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u043f\u043e\u0445\u043e\u0434 \u0432 SQLite + \u0441\u0435\u0442\u044c, \u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0442\u0438\u0445\u043e \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u0447\u0442\u043e-\u0442\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u043e\u0441\u044c.Level 2: SQLite \u0441 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u043c\u0438 \u0438\u043d\u0434\u0435\u043a\u0441\u0430\u043c\u0438L1 \u2014 \u043f\u0430\u043c\u044f\u0442\u044c. \u041f\u0430\u043c\u044f\u0442\u044c \u043a\u043e\u043d\u0447\u0430\u0435\u0442\u0441\u044f, \u043a\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0443\u0431\u0438\u0442\u043e \u0441\u0438\u0441\u0442\u0435\u043c\u043e\u0439. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0443\u0436\u0435\u043d \u043f\u0435\u0440\u0441\u0438\u0441\u0442\u0435\u043d\u0442\u043d\u044b\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0435\u0440\u0435\u0436\u0438\u0432\u0430\u0435\u0442 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a.\u042f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e expo-sqlite \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u0438 drizzle-orm \u0434\u043b\u044f \u0441\u0445\u0435\u043c\u044b. Drizzle \u2014 \u044d\u0442\u043e \u043d\u0435 ORM \u0432 \u0441\u043c\u044b\u0441\u043b\u0435 \u0433\u0438\u0434\u0440\u0430\u0446\u0438\u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0441 \u043b\u0435\u043d\u0438\u0432\u044b\u043c\u0438 \u0441\u0432\u044f\u0437\u044f\u043c\u0438, \u044d\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0442\u0438\u043f\u043e\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u0431\u0438\u043b\u0434\u0435\u0440 DDL \u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432. \u042f \u0432 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u0435\u0433\u043e \u0434\u043b\u044f \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439, \u0430 \u0441\u0430\u043c\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043f\u0438\u0448\u0443 \u043d\u0430 \u0441\u044b\u0440\u043e\u043c SQL \u2014 \u0434\u043b\u044f \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f.\u0421\u0445\u0435\u043c\u0430 \u0438 \u0438\u043d\u0434\u0435\u043a\u0441\u044bexport const messages = sqliteTable(&#171;messages&#187;, {  id:               text(&#171;id&#187;).primaryKey(),  chatId:           text(&#171;chat_id&#187;).notNull(),  senderId:         text(&#171;sender_id&#187;),  content:          text(&#171;content&#187;),  attachmentType:   text(&#171;attachment_type&#187;),  attachmentUrl:    text(&#171;attachment_url&#187;),  replyToId:        text(&#171;reply_to_id&#187;),  reactions:        text(&#171;reactions&#187;),       \/\/ JSON blob  pts:              integer(&#171;pts&#187;),  readAt:           text(&#171;read_at&#187;),  editedAt:         text(&#171;edited_at&#187;),  createdAt:        text(&#171;created_at&#187;).notNull(),  deletedForAll:    integer(&#171;deleted_for_all&#187;).default(0),  \/\/ \u2026 \u043f\u0440\u043e\u0447\u0438\u0435 \u043f\u043e\u043b\u044f}, (t) =&gt; ({  \/\/ \u0413\u043b\u0430\u0432\u043d\u044b\u0439 \u043f\u0430\u0442\u0442\u0435\u0440\u043d: \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 N \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0447\u0430\u0442\u0430  idxChatDate: index(&#171;idx_msg_chat_date&#187;).on(t.chatId, t.createdAt),  \/\/ \u0414\u043b\u044f offset_id \/ around-id  idxChatId:   index(&#171;idx_msg_chat_id&#187;).on(t.chatId, t.id),  \/\/ \u0414\u043b\u044f diff-sync \u043f\u043e pts  idxPts:      index(&#171;idx_msg_pts&#187;).on(t.chatId, t.pts),}));\u0422\u0440\u0438 \u0438\u043d\u0434\u0435\u043a\u0441\u0430 \u2014 \u0442\u0440\u0438 \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430. \u041a\u0430\u0436\u0434\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0434\u0430\u043b\u044c\u0448\u0435 \u0431\u0443\u0434\u0435\u0442 \u0442\u043e\u0447\u043d\u043e \u043f\u043e\u043f\u0430\u0434\u0430\u0442\u044c \u0432 \u043e\u0434\u0438\u043d \u0438\u0437 \u043d\u0438\u0445.JSON \u0432 \u043f\u043e\u043b\u044f\u0445 reactions, forwardedFrom, mediaGroup \u2014 \u0441\u043e\u0437\u043d\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435. \u042d\u0442\u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u0447\u0438\u0442\u0430\u044e\u0442\u0441\u044f \u0432\u0441\u0435\u0433\u0434\u0430 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435\u043c, \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u0444\u0438\u043b\u044c\u0442\u0440\u0443\u044e\u0442\u0441\u044f \u043f\u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u043c\u0443, \u0438 \u043d\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u0445 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u043e\u0437\u043d\u0430\u0447\u0430\u043b\u043e \u0431\u044b JOIN \u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441. SQLite \u2014 \u043d\u0435 PostgreSQL, JOIN&#8217;\u044b \u0437\u0434\u0435\u0441\u044c \u0434\u043e\u0440\u043e\u0436\u0435, \u0447\u0435\u043c \u043a\u0430\u0436\u0435\u0442\u0441\u044f.\u0427\u0442\u0435\u043d\u0438\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0445 N \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439const rows = await db.all(  `SELECT * FROM messages   WHERE chat_id = ? AND deleted_for_all = 0   ORDER BY created_at DESC LIMIT ?`,  [chatId, limit]);\u0421 \u0438\u043d\u0434\u0435\u043a\u0441\u043e\u043c (chat_id, created_at) \u044d\u0442\u043e range-scan \u2014 \u0434\u0435\u0441\u044f\u0442\u043a\u0438 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u043a\u0443\u043d\u0434 \u043f\u0440\u0438 \u0440\u0430\u0437\u043c\u0435\u0440\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0432 \u0441\u043e\u0442\u043d\u0438 \u0442\u044b\u0441\u044f\u0447 \u0441\u0442\u0440\u043e\u043a. \u0423\u0441\u043b\u043e\u0432\u0438\u0435 deleted_for_all = 0 \u043e\u0442\u0440\u0435\u0437\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u044d\u0442\u0430\u043f\u0435 \u0447\u0442\u0435\u043d\u0438\u044f \u0438\u043d\u0434\u0435\u043a\u0441\u0430, \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0430 \u043d\u0435 \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e (\u043e\u043d\u0430 \u0443\u0436\u0435 \u0432 \u043f\u043e\u0440\u044f\u0434\u043a\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u0430).Cursor-based \u043f\u0430\u0433\u0438\u043d\u0430\u0446\u0438\u044f \u2014 \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435\u0417\u0434\u0435\u0441\u044c \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u0435. \u041a\u043e\u0433\u0434\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043a\u0440\u0443\u0442\u0438\u0442 \u043b\u0435\u043d\u0442\u0443 \u043d\u0430\u0432\u0435\u0440\u0445, \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u0434\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0435\u0449\u0451. \u0421\u0430\u043c\u044b\u0439 \u043e\u0447\u0435\u0432\u0438\u0434\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u2014 OFFSET. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 LIMIT 50 OFFSET 200. \u042d\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0430 \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0445 \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u0445 \u0438 \u043a\u0430\u0442\u0430\u0441\u0442\u0440\u043e\u0444\u0438\u0447\u0435\u0441\u043a\u0438 \u0442\u043e\u0440\u043c\u043e\u0437\u0438\u0442 \u043d\u0430 \u0431\u043e\u043b\u044c\u0448\u0438\u0445: SQLite \u0447\u0435\u0441\u0442\u043d\u043e \u0441\u043a\u0430\u043d\u0438\u0440\u0443\u0435\u0442 \u043f\u0435\u0440\u0432\u044b\u0435 200 \u0441\u0442\u0440\u043e\u043a, \u0447\u0442\u043e\u0431\u044b \u0438\u0445 \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u044c.Telegram \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 cursor-based \u043f\u0430\u0433\u0438\u043d\u0430\u0446\u0438\u044e \u0447\u0435\u0440\u0435\u0437 offset_id: &#171;\u0434\u0430\u0439 \u043c\u043d\u0435 50 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0441\u0442\u0430\u0440\u0448\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0441 id X&#187;. \u042f \u0434\u0435\u043b\u0430\u044e \u0442\u0430\u043a \u0436\u0435:export async function getMessagesBeforeId(  chatId: string,  offsetId: string,  limit = 50): Promise&lt;any[]&gt; {  const db = await getSqlite();  if (!db) return [];  try {    const rows = (await db.all(      `SELECT * FROM messages       WHERE chat_id = ? AND id &lt; ? AND deleted_for_all = 0       ORDER BY id DESC LIMIT ?`,      [chatId, offsetId, limit]    ) as any[]).reverse();    return rows.map(rowToMessage);  } catch (e) {    console.warn(&#171;[CacheV2] getMessagesBeforeId error:&#187;, e);    return [];  }}\u0422\u0443\u0442 \u0432\u0430\u0436\u043d\u0430\u044f \u0434\u0435\u0442\u0430\u043b\u044c, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u044f \u0441\u043f\u043e\u0442\u043a\u043d\u0443\u043b\u0441\u044f \u043d\u0435 \u0441\u0440\u0430\u0437\u0443: id \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u044f \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u044e \u043a\u0430\u043a time-sortable UUID (\u0444\u043e\u0440\u043c\u0430\u0442, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043f\u0435\u0440\u0432\u044b\u0435 \u0431\u0430\u0439\u0442\u044b \u2014 \u043c\u0438\u043b\u043b\u0438\u0441\u0435\u043a\u0443\u043d\u0434\u043d\u044b\u0439 timestamp). \u0422\u043e \u0435\u0441\u0442\u044c \u043b\u0435\u043a\u0441\u0438\u043a\u043e\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u0435 id &lt; ? \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0435\u0442 \u0441 \u0445\u0440\u043e\u043d\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u043c. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043e\u0442\u043a\u0430\u0437\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044f created_at \u0432 WHERE \u0438 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0441 \u0438\u043d\u0434\u0435\u043a\u0441\u043e\u043c (chat_id, id) \u2014 \u043e\u0434\u043d\u0438\u043c range-scan, \u0431\u0435\u0437 \u043f\u043e\u0434\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0432\u0438\u0434\u0430 &#171;select created_at where id = ? then compare&#187;.\u0415\u0441\u043b\u0438 \u0431\u044b id \u0431\u044b\u043b \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u043c (\u043a\u0430\u043a UUIDv4), \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u0431\u044b \u0434\u0435\u043b\u0430\u0442\u044c \u043f\u043e\u0434\u0437\u0430\u043f\u0440\u043e\u0441, \u043b\u0438\u0431\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435 \u2014 \u044d\u0442\u043e \u043b\u0438\u0448\u043d\u0438\u0439 JOIN \u0438\u043b\u0438 \u043b\u0438\u0448\u043d\u044f\u044f \u043a\u043e\u043b\u043e\u043d\u043a\u0430 \u0432 \u0438\u043d\u0434\u0435\u043a\u0441\u0435. Time-sortable id \u2014 \u044d\u0442\u043e \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u0430\u044f \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c.Around-id \u2014 \u043f\u0440\u044b\u0436\u043e\u043a \u043a \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c\u0443 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044e\u041a\u043e\u0433\u0434\u0430 \u044e\u0437\u0435\u0440 \u0442\u0430\u043f\u0430\u0435\u0442 \u043f\u043e replied-\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044e, \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0435\u0433\u043e \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435: \u043f\u043e\u043b\u043e\u0432\u0438\u043d\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0434\u043e, \u043f\u043e\u043b\u043e\u0432\u0438\u043d\u0430 \u043f\u043e\u0441\u043b\u0435.export async function getMessagesAroundId(  chatId: string,  messageId: string,  limit = 80): Promise&lt;{ messages: any[]; targetIndex: number } | null&gt; {  const db = await getSqlite();  if (!db) return null;  try {    const half = Math.floor(limit \/ 2);    const afterRows = await db.all(      `SELECT * FROM messages       WHERE chat_id = ? AND id &gt;= ? AND deleted_for_all = 0       ORDER BY id ASC LIMIT ?`,      [chatId, messageId, half + 1]    ) as any[];    const beforeRows = (await db.all(      `SELECT * FROM messages       WHERE chat_id = ? AND id &lt; ? AND deleted_for_all = 0       ORDER BY id DESC LIMIT ?`,      [chatId, messageId, half]    ) as any[]).reverse();    const messages = [&#8230;beforeRows, &#8230;afterRows].map(rowToMessage);    const targetIndex = messages.findIndex(m =&gt; m.id === messageId);    return { messages, targetIndex };  } catch (e) {    console.warn(&#171;[CacheV2] getMessagesAroundId error:&#187;, e);    return null;  }}\u0414\u0432\u0430 index range scan, \u043d\u0438\u043a\u0430\u043a\u0438\u0445 JOIN, \u043d\u0438\u043a\u0430\u043a\u0438\u0445 subquery. \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-479214","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/479214","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=479214"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/479214\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=479214"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=479214"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=479214"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}