[{"data":1,"prerenderedAt":6924},["ShallowReactive",2],{"navigation_docs":3,"-tu-hoc-database-supabase-storage-realtime":420,"-tu-hoc-database-supabase-storage-realtime-surround":6919},[4,153],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":26},"Phỏng vấn .NET","i-lucide-briefcase","\u002Fphong-van","1.phong-van",[10,27,66,111,128],{"title":11,"icon":12,"path":13,"stem":14,"children":15,"page":26},"Tổng quan","i-lucide-compass","\u002Fphong-van\u002Ftong-quan","1.phong-van\u002F1.tong-quan",[16,21],{"title":17,"path":18,"stem":19,"icon":20},"Thị trường HCM","\u002Fphong-van\u002Ftong-quan\u002Fthi-truong","1.phong-van\u002F1.tong-quan\u002F1.thi-truong","i-lucide-trending-up",{"title":22,"path":23,"stem":24,"icon":25},"Lộ trình 7 ngày","\u002Fphong-van\u002Ftong-quan\u002Flo-trinh","1.phong-van\u002F1.tong-quan\u002F2.lo-trinh","i-lucide-calendar-days",false,{"title":22,"icon":25,"path":28,"stem":29,"children":30,"page":26},"\u002Fphong-van\u002Flo-trinh","1.phong-van\u002F2.lo-trinh",[31,36,41,46,51,56,61],{"title":32,"path":33,"stem":34,"icon":35},"Ngày 1 — C# cơ bản","\u002Fphong-van\u002Flo-trinh\u002Fngay-1-csharp-co-ban","1.phong-van\u002F2.lo-trinh\u002F1.ngay-1-csharp-co-ban","i-lucide-1",{"title":37,"path":38,"stem":39,"icon":40},"Ngày 2 — OOP và SOLID","\u002Fphong-van\u002Flo-trinh\u002Fngay-2-oop-solid","1.phong-van\u002F2.lo-trinh\u002F2.ngay-2-oop-solid","i-lucide-2",{"title":42,"path":43,"stem":44,"icon":45},"Ngày 3 — C# nâng cao","\u002Fphong-van\u002Flo-trinh\u002Fngay-3-csharp-nang-cao","1.phong-van\u002F2.lo-trinh\u002F3.ngay-3-csharp-nang-cao","i-lucide-3",{"title":47,"path":48,"stem":49,"icon":50},"Ngày 4 — Database và SQL","\u002Fphong-van\u002Flo-trinh\u002Fngay-4-database-sql","1.phong-van\u002F2.lo-trinh\u002F4.ngay-4-database-sql","i-lucide-4",{"title":52,"path":53,"stem":54,"icon":55},"Ngày 5 — ASP.NET Core và EF Core","\u002Fphong-van\u002Flo-trinh\u002Fngay-5-aspnet-efcore","1.phong-van\u002F2.lo-trinh\u002F5.ngay-5-aspnet-efcore","i-lucide-5",{"title":57,"path":58,"stem":59,"icon":60},"Ngày 6 — DSA coding","\u002Fphong-van\u002Flo-trinh\u002Fngay-6-dsa","1.phong-van\u002F2.lo-trinh\u002F6.ngay-6-dsa","i-lucide-6",{"title":62,"path":63,"stem":64,"icon":65},"Ngày 7 — Mock và HR","\u002Fphong-van\u002Flo-trinh\u002Fngay-7-mock-interview","1.phong-van\u002F2.lo-trinh\u002F7.ngay-7-mock-interview","i-lucide-7",{"title":67,"icon":68,"path":69,"stem":70,"children":71,"page":26},"Ngân hàng câu hỏi","i-lucide-database","\u002Fphong-van\u002Fngan-hang-cau-hoi","1.phong-van\u002F3.ngan-hang-cau-hoi",[72,77,82,87,92,97,102,107],{"title":73,"path":74,"stem":75,"icon":76},"C# (60+ câu)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fcsharp","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F1.csharp","i-lucide-hash",{"title":78,"path":79,"stem":80,"icon":81},"OOP và SOLID (40+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Foop","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F2.oop","i-lucide-shapes",{"title":83,"path":84,"stem":85,"icon":86},"SQL (40+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fsql","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F3.sql","i-lucide-database-zap",{"title":88,"path":89,"stem":90,"icon":91},"ASP.NET và EF (50+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Faspnet-ef","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F4.aspnet-ef","i-lucide-server",{"title":93,"path":94,"stem":95,"icon":96},"DSA và Coding (30+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fdsa-coding","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F5.dsa-coding","i-lucide-binary",{"title":98,"path":99,"stem":100,"icon":101},"HR và Soft skill (25+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fhr-behavior","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F6.hr-behavior","i-lucide-users",{"title":103,"path":104,"stem":105,"icon":106},"Design Pattern (55+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fdesign-pattern","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F7.design-pattern","i-lucide-puzzle",{"title":108,"path":109,"stem":110,"icon":91},"ASP.NET MVC và Web API","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Faspnet-mvc-webapi","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F8.aspnet-mvc-webapi",{"title":112,"itle":113,"icon":114,"path":115,"stem":116,"children":117,"page":26},"Tai Lieu","Tài liệu và Cheat sheet","i-lucide-book-marked","\u002Fphong-van\u002Ftai-lieu","1.phong-van\u002F4.tai-lieu",[118,123],{"title":119,"path":120,"stem":121,"icon":122},"Cheat Sheet","\u002Fphong-van\u002Ftai-lieu\u002Fcheatsheet","1.phong-van\u002F4.tai-lieu\u002F1.cheatsheet","i-lucide-clipboard-list",{"title":124,"path":125,"stem":126,"icon":127},"Nguồn tham khảo","\u002Fphong-van\u002Ftai-lieu\u002Fnguon","1.phong-van\u002F4.tai-lieu\u002F2.nguon","i-lucide-link",{"title":129,"icon":130,"path":131,"stem":132,"children":133,"page":26},"Phỏng vấn English","i-lucide-globe","\u002Fphong-van\u002Fenglish-interview","1.phong-van\u002F5.english-interview",[134,138,143,148],{"title":135,"path":136,"stem":137,"icon":130},"Tổng quan English","\u002Fphong-van\u002Fenglish-interview\u002Ftong-quan","1.phong-van\u002F5.english-interview\u002F1.tong-quan",{"title":139,"path":140,"stem":141,"icon":142},"Vocabulary","\u002Fphong-van\u002Fenglish-interview\u002Fvocabulary","1.phong-van\u002F5.english-interview\u002F2.vocabulary","i-lucide-book-a",{"title":144,"path":145,"stem":146,"icon":147},"Technical Q và A (EN)","\u002Fphong-van\u002Fenglish-interview\u002Ftechnical-en","1.phong-van\u002F5.english-interview\u002F3.technical-en","i-lucide-code",{"title":149,"path":150,"stem":151,"icon":152},"Behavioral (EN)","\u002Fphong-van\u002Fenglish-interview\u002Fbehavioral-en","1.phong-van\u002F5.english-interview\u002F4.behavioral-en","i-lucide-message-square-text",{"title":154,"icon":155,"path":156,"stem":157,"children":158},"Tự học","i-lucide-graduation-cap","\u002Ftu-hoc","2.tu-hoc",[159,161,287,310,333,357,384],{"title":154,"path":156,"stem":160,"icon":155},"2.tu-hoc\u002Findex",{"title":162,"icon":68,"path":163,"stem":164,"children":165},"Database","\u002Ftu-hoc\u002Fdatabase","2.tu-hoc\u002F1.database\u002Findex",[166,167,197,222,243,248],{"title":162,"path":163,"stem":164,"icon":68},{"title":168,"icon":91,"path":169,"stem":170,"children":171,"page":26},"SQL Server","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server","2.tu-hoc\u002F1.database\u002F2.sql-server",[172,177,182,187,192],{"title":173,"path":174,"stem":175,"icon":176},"1. Giới thiệu và Cài đặt","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F1.gioi-thieu-cai-dat","i-lucide-play-circle",{"title":178,"path":179,"stem":180,"icon":181},"2. Cú pháp cơ bản","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Fcu-phap-co-ban","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F2.cu-phap-co-ban","i-lucide-code-2",{"title":183,"path":184,"stem":185,"icon":186},"3. Truy vấn nâng cao","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Ftruy-van-nang-cao","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F3.truy-van-nang-cao","i-lucide-git-merge",{"title":188,"path":189,"stem":190,"icon":191},"4. Đặc thù và Performance","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Fdac-thu-performance","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F4.dac-thu-performance","i-lucide-gauge",{"title":193,"path":194,"stem":195,"icon":196},"5. Q và A phỏng vấn","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Fcau-hoi-phong-van","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F5.cau-hoi-phong-van","i-lucide-message-circle-question",{"title":198,"icon":199,"path":200,"stem":201,"children":202,"page":26},"PostgreSQL","i-simple-icons-postgresql","\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql","2.tu-hoc\u002F1.database\u002F3.postgresql",[203,206,209,212,215,218],{"title":173,"path":204,"stem":205,"icon":176},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F1.gioi-thieu-cai-dat",{"title":178,"path":207,"stem":208,"icon":181},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Fcu-phap-co-ban","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F2.cu-phap-co-ban",{"title":183,"path":210,"stem":211,"icon":186},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Ftruy-van-nang-cao","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F3.truy-van-nang-cao",{"title":188,"path":213,"stem":214,"icon":191},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Fdac-thu-performance","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F4.dac-thu-performance",{"title":193,"path":216,"stem":217,"icon":196},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Fcau-hoi-phong-van","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F5.cau-hoi-phong-van",{"title":219,"path":220,"stem":221,"icon":96},"6. Tích hợp C# và Postgres","\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Ftich-hop-csharp","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F6.tich-hop-csharp",{"title":223,"icon":224,"path":225,"stem":226,"children":227,"page":26},"MySQL","i-simple-icons-mysql","\u002Ftu-hoc\u002Fdatabase\u002Fmysql","2.tu-hoc\u002F1.database\u002F4.mysql",[228,231,234,237,240],{"title":173,"path":229,"stem":230,"icon":176},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F1.database\u002F4.mysql\u002F1.gioi-thieu-cai-dat",{"title":178,"path":232,"stem":233,"icon":181},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Fcu-phap-co-ban","2.tu-hoc\u002F1.database\u002F4.mysql\u002F2.cu-phap-co-ban",{"title":183,"path":235,"stem":236,"icon":186},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Ftruy-van-nang-cao","2.tu-hoc\u002F1.database\u002F4.mysql\u002F3.truy-van-nang-cao",{"title":188,"path":238,"stem":239,"icon":191},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Fdac-thu-performance","2.tu-hoc\u002F1.database\u002F4.mysql\u002F4.dac-thu-performance",{"title":193,"path":241,"stem":242,"icon":196},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Fcau-hoi-phong-van","2.tu-hoc\u002F1.database\u002F4.mysql\u002F5.cau-hoi-phong-van",{"title":244,"path":245,"stem":246,"icon":247},"So sánh 3 RDBMS","\u002Ftu-hoc\u002Fdatabase\u002Fso-sanh","2.tu-hoc\u002F1.database\u002F5.so-sanh","i-lucide-columns-3",{"title":249,"path":250,"stem":251,"children":252,"icon":254},"Supabase (BaaS)","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase","2.tu-hoc\u002F1.database\u002F6.supabase\u002Findex",[253,255,259,264,269,274,278,283],{"title":249,"path":250,"stem":251,"icon":254},"i-simple-icons-supabase",{"title":256,"path":257,"stem":258,"icon":68},"1. Database","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fdatabase","2.tu-hoc\u002F1.database\u002F6.supabase\u002F1.database",{"title":260,"path":261,"stem":262,"icon":263},"2. Authentication","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fauthentication","2.tu-hoc\u002F1.database\u002F6.supabase\u002F2.authentication","i-lucide-key",{"title":265,"path":266,"stem":267,"icon":268},"3. Storage & Realtime","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fstorage-realtime","2.tu-hoc\u002F1.database\u002F6.supabase\u002F3.storage-realtime","i-lucide-zap",{"title":270,"path":271,"stem":272,"icon":273},"4. Edge Functions & AI","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Ffunctions-ai","2.tu-hoc\u002F1.database\u002F6.supabase\u002F4.functions-ai","i-lucide-brain",{"title":275,"path":276,"stem":277,"icon":181},"5. API & SDKs","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fapi-sdk","2.tu-hoc\u002F1.database\u002F6.supabase\u002F5.api-sdk",{"title":279,"path":280,"stem":281,"icon":282},"6. Platform & Deploy","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fplatform-deploy","2.tu-hoc\u002F1.database\u002F6.supabase\u002F6.platform-deploy","i-lucide-cloud",{"title":284,"path":285,"stem":286,"icon":196},"7. Interview & Q&A","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Finterview","2.tu-hoc\u002F1.database\u002F6.supabase\u002F7.interview",{"title":288,"icon":289,"path":290,"stem":291,"children":292},"TypeScript","i-simple-icons-typescript","\u002Ftu-hoc\u002Ftypescript","2.tu-hoc\u002F2.typescript\u002Findex",[293,294,297,301,306],{"title":288,"path":290,"stem":291,"icon":289},{"title":173,"path":295,"stem":296,"icon":176},"\u002Ftu-hoc\u002Ftypescript\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F2.typescript\u002F1.gioi-thieu-cai-dat",{"title":298,"path":299,"stem":300,"icon":81},"2. Type System","\u002Ftu-hoc\u002Ftypescript\u002Ftype-system","2.tu-hoc\u002F2.typescript\u002F2.type-system",{"title":302,"path":303,"stem":304,"icon":305},"3. Advanced types","\u002Ftu-hoc\u002Ftypescript\u002Fadvanced-types","2.tu-hoc\u002F2.typescript\u002F3.advanced-types","i-lucide-braces",{"title":307,"path":308,"stem":309,"icon":196},"4. Q và A phỏng vấn","\u002Ftu-hoc\u002Ftypescript\u002Fcau-hoi-phong-van","2.tu-hoc\u002F2.typescript\u002F4.cau-hoi-phong-van",{"title":311,"icon":81,"path":312,"stem":313,"children":314},"OOP","\u002Ftu-hoc\u002Foop","2.tu-hoc\u002F3.oop\u002Findex",[315,316,321,326,330],{"title":311,"path":312,"stem":313,"icon":81},{"title":317,"path":318,"stem":319,"icon":320},"1. Bốn tính chất","\u002Ftu-hoc\u002Foop\u002F4-tinh-chat","2.tu-hoc\u002F3.oop\u002F1.4-tinh-chat","i-lucide-circle-dot",{"title":322,"path":323,"stem":324,"icon":325},"2. SOLID","\u002Ftu-hoc\u002Foop\u002Fsolid","2.tu-hoc\u002F3.oop\u002F2.solid","i-lucide-shield",{"title":327,"path":328,"stem":329,"icon":106},"3. Design Pattern","\u002Ftu-hoc\u002Foop\u002Fdesign-pattern","2.tu-hoc\u002F3.oop\u002F3.design-pattern",{"title":307,"path":331,"stem":332,"icon":196},"\u002Ftu-hoc\u002Foop\u002Fcau-hoi-phong-van","2.tu-hoc\u002F3.oop\u002F4.cau-hoi-phong-van",{"title":334,"icon":305,"path":335,"stem":336,"children":337},"Generic","\u002Ftu-hoc\u002Fgeneric","2.tu-hoc\u002F4.generic\u002Findex",[338,339,344,349,354],{"title":334,"path":335,"stem":336,"icon":305},{"title":340,"path":341,"stem":342,"icon":343},"1. Khái niệm và cú pháp","\u002Ftu-hoc\u002Fgeneric\u002Fkhai-niem","2.tu-hoc\u002F4.generic\u002F1.khai-niem","i-lucide-book",{"title":345,"path":346,"stem":347,"icon":348},"2. Constraint và Variance","\u002Ftu-hoc\u002Fgeneric\u002Fconstraint-variance","2.tu-hoc\u002F4.generic\u002F2.constraint-variance","i-lucide-filter",{"title":350,"path":351,"stem":352,"icon":353},"3. C# vs TS","\u002Ftu-hoc\u002Fgeneric\u002Fso-sanh","2.tu-hoc\u002F4.generic\u002F3.so-sanh","i-lucide-git-compare",{"title":307,"path":355,"stem":356,"icon":196},"\u002Ftu-hoc\u002Fgeneric\u002Fcau-hoi-phong-van","2.tu-hoc\u002F4.generic\u002F4.cau-hoi-phong-van",{"title":358,"icon":359,"path":360,"stem":361,"children":362},"Nuxt 4","i-simple-icons-nuxt","\u002Ftu-hoc\u002Fnuxt-4","2.tu-hoc\u002F5.nuxt-4\u002Findex",[363,364,367,372,376,379],{"title":358,"path":360,"stem":361,"icon":359},{"title":173,"path":365,"stem":366,"icon":176},"\u002Ftu-hoc\u002Fnuxt-4\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F5.nuxt-4\u002F1.gioi-thieu-cai-dat",{"title":368,"path":369,"stem":370,"icon":371},"2. Routing và Rendering","\u002Ftu-hoc\u002Fnuxt-4\u002Frouting-rendering","2.tu-hoc\u002F5.nuxt-4\u002F2.routing-rendering","i-lucide-route",{"title":373,"path":374,"stem":375,"icon":91},"3. Data và Server","\u002Ftu-hoc\u002Fnuxt-4\u002Fdata-server","2.tu-hoc\u002F5.nuxt-4\u002F3.data-server",{"title":307,"path":377,"stem":378,"icon":196},"\u002Ftu-hoc\u002Fnuxt-4\u002Fcau-hoi-phong-van","2.tu-hoc\u002F5.nuxt-4\u002F4.cau-hoi-phong-van",{"title":380,"path":381,"stem":382,"icon":383},"5. Practical Todo App","\u002Ftu-hoc\u002Fnuxt-4\u002Fpractical-todo-app","2.tu-hoc\u002F5.nuxt-4\u002F5.practical-todo-app","i-lucide-check-square",{"title":385,"path":386,"stem":387,"children":388,"icon":390},"Tailwind CSS","\u002Ftu-hoc\u002Ftailwindcss","2.tu-hoc\u002F6.tailwindcss\u002Findex",[389,391,396,401,406,411,416],{"title":385,"path":386,"stem":387,"icon":390},"i-simple-icons-tailwindcss",{"title":392,"path":393,"stem":394,"icon":395},"1. Layout & Spacing","\u002Ftu-hoc\u002Ftailwindcss\u002Flayout-spacing","2.tu-hoc\u002F6.tailwindcss\u002F1.layout-spacing","i-lucide-layout",{"title":397,"path":398,"stem":399,"icon":400},"2. Typography & Colors","\u002Ftu-hoc\u002Ftailwindcss\u002Ftypography-colors","2.tu-hoc\u002F6.tailwindcss\u002F2.typography-colors","i-lucide-type",{"title":402,"path":403,"stem":404,"icon":405},"3. Interactive & Effects","\u002Ftu-hoc\u002Ftailwindcss\u002Finteractive-effects","2.tu-hoc\u002F6.tailwindcss\u002F3.interactive-effects","i-lucide-sparkles",{"title":407,"path":408,"stem":409,"icon":410},"4. Responsive & Dark Mode","\u002Ftu-hoc\u002Ftailwindcss\u002Fresponsive-darkmode","2.tu-hoc\u002F6.tailwindcss\u002F4.responsive-darkmode","i-lucide-monitor-smartphone",{"title":412,"path":413,"stem":414,"icon":415},"5. Customization & Theme","\u002Ftu-hoc\u002Ftailwindcss\u002Fcustomization","2.tu-hoc\u002F6.tailwindcss\u002F5.customization","i-lucide-settings-2",{"title":417,"path":418,"stem":419,"icon":181},"6. Practical Projects","\u002Ftu-hoc\u002Ftailwindcss\u002Fpractical-projects","2.tu-hoc\u002F6.tailwindcss\u002F6.practical-projects",{"id":421,"title":422,"body":423,"description":6912,"extension":6913,"links":6914,"meta":6915,"navigation":6916,"path":266,"seo":6917,"stem":267,"__hash__":6918},"docs\u002F2.tu-hoc\u002F1.database\u002F6.supabase\u002F3.storage-realtime.md","Supabase Storage & Realtime — File Storage và WebSocket",{"type":424,"value":425,"toc":6880},"minimark",[426,431,435,453,462,466,469,557,565,569,572,628,632,635,1035,1039,1042,1918,1922,1925,2008,2011,2015,2022,2030,2038,2060,2064,2067,2304,2308,2311,2549,2563,2575,2579,2582,2796,2799,2803,2806,2969,2972,2976,2995,2998,3002,3005,3008,3064,3070,3074,3077,3083,3375,3384,3388,3391,4005,4010,4140,4150,4155,4158,4247,4250,4254,4257,4351,4361,4364,4367,4796,4799,4802,5481,5486,5559,5568,5572,5575,5614,5617,5621,5624,5716,5721,5807,5813,5825,5829,5925,5927,5931,5935,5940,5960,5965,6457,6461,6465,6482,6486,6490,6507,6511,6844,6848,6852,6869,6871,6876],[427,428,430],"h2",{"id":429},"storage-lưu-trữ-file","Storage — Lưu trữ file",[432,433,11],"h3",{"id":434},"tổng-quan",[436,437,438,439,443,444,447,448,452],"p",{},"Supabase Storage là giải pháp lưu trữ file mạnh mẽ, có khả năng mở rộng với ",[440,441,442],"strong",{},"kiểm soát truy cập chi tiết"," và ",[440,445,446],{},"phân phối tối ưu qua CDN toàn cầu",". File được lưu trữ trên hạ tầng S3, metadata được quản lý trong PostgreSQL (bảng ",[449,450,451],"code",{},"storage.objects","), và RLS có thể áp dụng trực tiếp cho storage objects -- cùng cơ chế bảo mật với database.",[454,455,458,461],"callout",{"color":456,"icon":457},"primary","i-lucide-star",[440,459,460],{},"Điểm cốt lõi:"," Supabase Storage tương thích S3 protocol, metadata được lưu trong Postgres, và file thực tế được lưu trên S3 backend. RLS có thể áp dụng cho storage objects -- cùng cơ chế bảo mật với database.",[432,463,465],{"id":464},"file-size-limits-giới-hạn-dung-lượng-upload","File Size Limits — Giới hạn dung lượng upload",[436,467,468],{},"Supabase Storage hỗ trợ nhiều phương thức upload với giới hạn dung lượng khác nhau:",[470,471,472,491],"table",{},[473,474,475],"thead",{},[476,477,478,482,485,488],"tr",{},[479,480,481],"th",{},"Phương thức Upload",[479,483,484],{},"Giới hạn",[479,486,487],{},"Protocol",[479,489,490],{},"Use case",[492,493,494,511,527,542],"tbody",{},[476,495,496,502,505,508],{},[497,498,499],"td",{},[440,500,501],{},"Standard Upload",[497,503,504],{},"50 MB",[497,506,507],{},"HTTP multipart\u002Fform-data",[497,509,510],{},"Ảnh, documents, file nhỏ",[476,512,513,518,521,524],{},[497,514,515],{},[440,516,517],{},"TUS Resumable Upload",[497,519,520],{},"5 GB",[497,522,523],{},"TUS protocol (chunked)",[497,525,526],{},"Video, file nén lớn, backup",[476,528,529,534,536,539],{},[497,530,531],{},[440,532,533],{},"Signed Upload",[497,535,520],{},[497,537,538],{},"HTTP PUT với signed URL",[497,540,541],{},"Cho phép upload không cần auth",[476,543,544,549,551,554],{},[497,545,546],{},[440,547,548],{},"S3-Compatible Upload",[497,550,520],{},[497,552,553],{},"S3 multipart upload",[497,555,556],{},"Tích hợp công cụ S3 hiện có",[454,558,561,564],{"color":559,"icon":560},"info","i-lucide-info",[440,562,563],{},"Tại sao cần TUS?"," Với file > 50MB, standard upload dễ bị fail do timeout hoặc mạng yếu. TUS chia file thành chunks (~6MB mỗi chunk), upload tuần tự, có thể pause\u002Fresume\u002Fretry từng chunk. Đặc biệt quan trọng với người dùng ở Việt Nam hoặc khu vực có mạng không ổn định.",[432,566,568],{"id":567},"ba-loại-bucket","Ba loại Bucket",[436,570,571],{},"Supabase cung cấp 3 loại bucket, mỗi loại phục vụ mục đích khác nhau:",[470,573,574,587],{},[473,575,576],{},[476,577,578,581,584],{},[479,579,580],{},"Loại Bucket",[479,582,583],{},"Công dụng",[479,585,586],{},"Đặc điểm",[492,588,589,602,615],{},[476,590,591,596,599],{},[497,592,593],{},[440,594,595],{},"File Buckets",[497,597,598],{},"Lưu ảnh, video, document, general-purpose files",[497,600,601],{},"Direct URL access với RLS, CDN toàn cầu, image transformation",[476,603,604,609,612],{},[497,605,606],{},[440,607,608],{},"Analytics Buckets",[497,610,611],{},"Lưu dữ liệu trong Apache Iceberg tables",[497,613,614],{},"Dành cho data lakes, logs, Supabase Pipelines; query từ Postgres qua foreign tables",[476,616,617,622,625],{},[497,618,619],{},[440,620,621],{},"Vector Buckets",[497,623,624],{},"Lưu embeddings cho similarity search",[497,626,627],{},"Dùng HNSW indexing, distance metrics, metadata filtering",[432,629,631],{"id":630},"thao-tác-với-bucket","Thao tác với Bucket",[436,633,634],{},"Các thao tác quản lý bucket cơ bản:",[636,637,642],"pre",{"className":638,"code":639,"language":640,"meta":641,"style":641},"language-javascript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F Tạo bucket mới\nconst { data, error } = await supabase.storage.createBucket('avatars', {\n  public: true,          \u002F\u002F bucket public\n  fileSizeLimit: 5242880 \u002F\u002F giới hạn 5MB\n})\n\n\u002F\u002F Liệt kê tất cả buckets\nconst { data: buckets, error } = await supabase.storage.listBuckets()\n\n\u002F\u002F Lấy thông tin bucket\nconst { data, error } = await supabase.storage.getBucket('avatars')\n\n\u002F\u002F Cập nhật bucket (ví dụ: đổi giới hạn file size)\nconst { data, error } = await supabase.storage.updateBucket('avatars', {\n  fileSizeLimit: 10485760 \u002F\u002F tăng lên 10MB\n})\n\n\u002F\u002F Xóa bucket (phải empty trước)\nconst { data, error } = await supabase.storage.deleteBucket('avatars')\n\n\u002F\u002F Xóa toàn bộ file trong bucket (empty)\nconst { data, error } = await supabase.storage.emptyBucket('avatars')\n","javascript","",[449,643,644,653,716,735,750,758,765,771,809,814,820,860,865,871,913,926,933,938,944,984,989,995],{"__ignoreMap":641},[645,646,649],"span",{"class":647,"line":648},"line",1,[645,650,652],{"class":651},"sHwdD","\u002F\u002F Tạo bucket mới\n",[645,654,656,660,664,668,671,674,677,680,684,687,690,693,695,699,702,705,709,711,713],{"class":647,"line":655},2,[645,657,659],{"class":658},"spNyl","const",[645,661,663],{"class":662},"sMK4o"," {",[645,665,667],{"class":666},"sTEyZ"," data",[645,669,670],{"class":662},",",[645,672,673],{"class":666}," error ",[645,675,676],{"class":662},"}",[645,678,679],{"class":662}," =",[645,681,683],{"class":682},"s7zQu"," await",[645,685,686],{"class":666}," supabase",[645,688,689],{"class":662},".",[645,691,692],{"class":666},"storage",[645,694,689],{"class":662},[645,696,698],{"class":697},"s2Zo4","createBucket",[645,700,701],{"class":666},"(",[645,703,704],{"class":662},"'",[645,706,708],{"class":707},"sfazB","avatars",[645,710,704],{"class":662},[645,712,670],{"class":662},[645,714,715],{"class":662}," {\n",[645,717,719,723,726,730,732],{"class":647,"line":718},3,[645,720,722],{"class":721},"swJcz","  public",[645,724,725],{"class":662},":",[645,727,729],{"class":728},"sfNiH"," true",[645,731,670],{"class":662},[645,733,734],{"class":651},"          \u002F\u002F bucket public\n",[645,736,738,741,743,747],{"class":647,"line":737},4,[645,739,740],{"class":721},"  fileSizeLimit",[645,742,725],{"class":662},[645,744,746],{"class":745},"sbssI"," 5242880",[645,748,749],{"class":651}," \u002F\u002F giới hạn 5MB\n",[645,751,753,755],{"class":647,"line":752},5,[645,754,676],{"class":662},[645,756,757],{"class":666},")\n",[645,759,761],{"class":647,"line":760},6,[645,762,764],{"emptyLinePlaceholder":763},true,"\n",[645,766,768],{"class":647,"line":767},7,[645,769,770],{"class":651},"\u002F\u002F Liệt kê tất cả buckets\n",[645,772,774,776,778,780,782,785,787,789,791,793,795,797,799,801,803,806],{"class":647,"line":773},8,[645,775,659],{"class":658},[645,777,663],{"class":662},[645,779,667],{"class":721},[645,781,725],{"class":662},[645,783,784],{"class":666}," buckets",[645,786,670],{"class":662},[645,788,673],{"class":666},[645,790,676],{"class":662},[645,792,679],{"class":662},[645,794,683],{"class":682},[645,796,686],{"class":666},[645,798,689],{"class":662},[645,800,692],{"class":666},[645,802,689],{"class":662},[645,804,805],{"class":697},"listBuckets",[645,807,808],{"class":666},"()\n",[645,810,812],{"class":647,"line":811},9,[645,813,764],{"emptyLinePlaceholder":763},[645,815,817],{"class":647,"line":816},10,[645,818,819],{"class":651},"\u002F\u002F Lấy thông tin bucket\n",[645,821,823,825,827,829,831,833,835,837,839,841,843,845,847,850,852,854,856,858],{"class":647,"line":822},11,[645,824,659],{"class":658},[645,826,663],{"class":662},[645,828,667],{"class":666},[645,830,670],{"class":662},[645,832,673],{"class":666},[645,834,676],{"class":662},[645,836,679],{"class":662},[645,838,683],{"class":682},[645,840,686],{"class":666},[645,842,689],{"class":662},[645,844,692],{"class":666},[645,846,689],{"class":662},[645,848,849],{"class":697},"getBucket",[645,851,701],{"class":666},[645,853,704],{"class":662},[645,855,708],{"class":707},[645,857,704],{"class":662},[645,859,757],{"class":666},[645,861,863],{"class":647,"line":862},12,[645,864,764],{"emptyLinePlaceholder":763},[645,866,868],{"class":647,"line":867},13,[645,869,870],{"class":651},"\u002F\u002F Cập nhật bucket (ví dụ: đổi giới hạn file size)\n",[645,872,874,876,878,880,882,884,886,888,890,892,894,896,898,901,903,905,907,909,911],{"class":647,"line":873},14,[645,875,659],{"class":658},[645,877,663],{"class":662},[645,879,667],{"class":666},[645,881,670],{"class":662},[645,883,673],{"class":666},[645,885,676],{"class":662},[645,887,679],{"class":662},[645,889,683],{"class":682},[645,891,686],{"class":666},[645,893,689],{"class":662},[645,895,692],{"class":666},[645,897,689],{"class":662},[645,899,900],{"class":697},"updateBucket",[645,902,701],{"class":666},[645,904,704],{"class":662},[645,906,708],{"class":707},[645,908,704],{"class":662},[645,910,670],{"class":662},[645,912,715],{"class":662},[645,914,916,918,920,923],{"class":647,"line":915},15,[645,917,740],{"class":721},[645,919,725],{"class":662},[645,921,922],{"class":745}," 10485760",[645,924,925],{"class":651}," \u002F\u002F tăng lên 10MB\n",[645,927,929,931],{"class":647,"line":928},16,[645,930,676],{"class":662},[645,932,757],{"class":666},[645,934,936],{"class":647,"line":935},17,[645,937,764],{"emptyLinePlaceholder":763},[645,939,941],{"class":647,"line":940},18,[645,942,943],{"class":651},"\u002F\u002F Xóa bucket (phải empty trước)\n",[645,945,947,949,951,953,955,957,959,961,963,965,967,969,971,974,976,978,980,982],{"class":647,"line":946},19,[645,948,659],{"class":658},[645,950,663],{"class":662},[645,952,667],{"class":666},[645,954,670],{"class":662},[645,956,673],{"class":666},[645,958,676],{"class":662},[645,960,679],{"class":662},[645,962,683],{"class":682},[645,964,686],{"class":666},[645,966,689],{"class":662},[645,968,692],{"class":666},[645,970,689],{"class":662},[645,972,973],{"class":697},"deleteBucket",[645,975,701],{"class":666},[645,977,704],{"class":662},[645,979,708],{"class":707},[645,981,704],{"class":662},[645,983,757],{"class":666},[645,985,987],{"class":647,"line":986},20,[645,988,764],{"emptyLinePlaceholder":763},[645,990,992],{"class":647,"line":991},21,[645,993,994],{"class":651},"\u002F\u002F Xóa toàn bộ file trong bucket (empty)\n",[645,996,998,1000,1002,1004,1006,1008,1010,1012,1014,1016,1018,1020,1022,1025,1027,1029,1031,1033],{"class":647,"line":997},22,[645,999,659],{"class":658},[645,1001,663],{"class":662},[645,1003,667],{"class":666},[645,1005,670],{"class":662},[645,1007,673],{"class":666},[645,1009,676],{"class":662},[645,1011,679],{"class":662},[645,1013,683],{"class":682},[645,1015,686],{"class":666},[645,1017,689],{"class":662},[645,1019,692],{"class":666},[645,1021,689],{"class":662},[645,1023,1024],{"class":697},"emptyBucket",[645,1026,701],{"class":666},[645,1028,704],{"class":662},[645,1030,708],{"class":707},[645,1032,704],{"class":662},[645,1034,757],{"class":666},[432,1036,1038],{"id":1037},"upload-download-file","Upload & Download File",[436,1040,1041],{},"Đây là các thao tác cốt lõi khi làm việc với Storage:",[636,1043,1045],{"className":638,"code":1044,"language":640,"meta":641,"style":641},"\u002F\u002F Upload file với upsert\nconst avatarFile = event.target.files[0]\nconst { data, error } = await supabase.storage\n  .from('avatars')\n  .upload(`${userId}\u002Fprofile.png`, avatarFile, {\n    cacheControl: '3600',   \u002F\u002F cache 1 giờ trên CDN\n    upsert: true,            \u002F\u002F ghi đè nếu file đã tồn tại\n    contentType: 'image\u002Fpng' \u002F\u002F set content type rõ ràng\n  })\n\n\u002F\u002F Upload với progress tracking\nconst { data, error } = await supabase.storage\n  .from('videos')\n  .upload('tutorial.mp4', file, {\n    onProgress: (progress) => {\n      console.log(`Uploaded: ${progress.percent}%`)\n    }\n  })\n\n\u002F\u002F Download file\nconst { data, error } = await supabase.storage\n  .from('avatars')\n  .download(`${userId}\u002Fprofile.png`)\n\n\u002F\u002F Get public URL (không hết hạn)\nconst { data } = supabase.storage\n  .from('avatars')\n  .getPublicUrl(`${userId}\u002Fprofile.png`)\n\u002F\u002F data.publicUrl -> https:\u002F\u002F[project].supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Favatars\u002F...\n\n\u002F\u002F List files trong thư mục\nconst { data, error } = await supabase.storage\n  .from('avatars')\n  .list(userId, {\n    limit: 100,\n    offset: 0,\n    sortBy: { column: 'created_at', order: 'desc' }\n  })\n\n\u002F\u002F Di chuyển \u002F đổi tên file\nconst { data, error } = await supabase.storage\n  .from('avatars')\n  .move(`${userId}\u002Fold.png`, `${userId}\u002Fnew.png`)\n\n\u002F\u002F Copy file\nconst { data, error } = await supabase.storage\n  .from('avatars')\n  .copy(`${userId}\u002Fprofile.png`, `backup\u002F${userId}\u002Fprofile.png`)\n\n\u002F\u002F Xóa file\nconst { data, error } = await supabase.storage\n  .from('avatars')\n  .remove([`${userId}\u002Fprofile.png`, `${userId}\u002Fbanner.png`])\n",[449,1046,1047,1052,1081,1106,1124,1156,1176,1190,1207,1214,1218,1223,1247,1264,1288,1310,1346,1351,1357,1361,1366,1390,1406,1428,1433,1439,1459,1476,1498,1504,1509,1515,1540,1557,1572,1586,1599,1638,1645,1650,1656,1681,1698,1735,1740,1746,1771,1788,1828,1833,1839,1864,1881],{"__ignoreMap":641},[645,1048,1049],{"class":647,"line":648},[645,1050,1051],{"class":651},"\u002F\u002F Upload file với upsert\n",[645,1053,1054,1056,1059,1062,1065,1067,1070,1072,1075,1078],{"class":647,"line":655},[645,1055,659],{"class":658},[645,1057,1058],{"class":666}," avatarFile ",[645,1060,1061],{"class":662},"=",[645,1063,1064],{"class":666}," event",[645,1066,689],{"class":662},[645,1068,1069],{"class":666},"target",[645,1071,689],{"class":662},[645,1073,1074],{"class":666},"files[",[645,1076,1077],{"class":745},"0",[645,1079,1080],{"class":666},"]\n",[645,1082,1083,1085,1087,1089,1091,1093,1095,1097,1099,1101,1103],{"class":647,"line":718},[645,1084,659],{"class":658},[645,1086,663],{"class":662},[645,1088,667],{"class":666},[645,1090,670],{"class":662},[645,1092,673],{"class":666},[645,1094,676],{"class":662},[645,1096,679],{"class":662},[645,1098,683],{"class":682},[645,1100,686],{"class":666},[645,1102,689],{"class":662},[645,1104,1105],{"class":666},"storage\n",[645,1107,1108,1111,1114,1116,1118,1120,1122],{"class":647,"line":737},[645,1109,1110],{"class":662},"  .",[645,1112,1113],{"class":697},"from",[645,1115,701],{"class":666},[645,1117,704],{"class":662},[645,1119,708],{"class":707},[645,1121,704],{"class":662},[645,1123,757],{"class":666},[645,1125,1126,1128,1131,1133,1136,1139,1141,1144,1147,1149,1152,1154],{"class":647,"line":752},[645,1127,1110],{"class":662},[645,1129,1130],{"class":697},"upload",[645,1132,701],{"class":666},[645,1134,1135],{"class":662},"`${",[645,1137,1138],{"class":666},"userId",[645,1140,676],{"class":662},[645,1142,1143],{"class":707},"\u002Fprofile.png",[645,1145,1146],{"class":662},"`",[645,1148,670],{"class":662},[645,1150,1151],{"class":666}," avatarFile",[645,1153,670],{"class":662},[645,1155,715],{"class":662},[645,1157,1158,1161,1163,1166,1169,1171,1173],{"class":647,"line":760},[645,1159,1160],{"class":721},"    cacheControl",[645,1162,725],{"class":662},[645,1164,1165],{"class":662}," '",[645,1167,1168],{"class":707},"3600",[645,1170,704],{"class":662},[645,1172,670],{"class":662},[645,1174,1175],{"class":651},"   \u002F\u002F cache 1 giờ trên CDN\n",[645,1177,1178,1181,1183,1185,1187],{"class":647,"line":767},[645,1179,1180],{"class":721},"    upsert",[645,1182,725],{"class":662},[645,1184,729],{"class":728},[645,1186,670],{"class":662},[645,1188,1189],{"class":651},"            \u002F\u002F ghi đè nếu file đã tồn tại\n",[645,1191,1192,1195,1197,1199,1202,1204],{"class":647,"line":773},[645,1193,1194],{"class":721},"    contentType",[645,1196,725],{"class":662},[645,1198,1165],{"class":662},[645,1200,1201],{"class":707},"image\u002Fpng",[645,1203,704],{"class":662},[645,1205,1206],{"class":651}," \u002F\u002F set content type rõ ràng\n",[645,1208,1209,1212],{"class":647,"line":811},[645,1210,1211],{"class":662},"  }",[645,1213,757],{"class":666},[645,1215,1216],{"class":647,"line":816},[645,1217,764],{"emptyLinePlaceholder":763},[645,1219,1220],{"class":647,"line":822},[645,1221,1222],{"class":651},"\u002F\u002F Upload với progress tracking\n",[645,1224,1225,1227,1229,1231,1233,1235,1237,1239,1241,1243,1245],{"class":647,"line":862},[645,1226,659],{"class":658},[645,1228,663],{"class":662},[645,1230,667],{"class":666},[645,1232,670],{"class":662},[645,1234,673],{"class":666},[645,1236,676],{"class":662},[645,1238,679],{"class":662},[645,1240,683],{"class":682},[645,1242,686],{"class":666},[645,1244,689],{"class":662},[645,1246,1105],{"class":666},[645,1248,1249,1251,1253,1255,1257,1260,1262],{"class":647,"line":867},[645,1250,1110],{"class":662},[645,1252,1113],{"class":697},[645,1254,701],{"class":666},[645,1256,704],{"class":662},[645,1258,1259],{"class":707},"videos",[645,1261,704],{"class":662},[645,1263,757],{"class":666},[645,1265,1266,1268,1270,1272,1274,1277,1279,1281,1284,1286],{"class":647,"line":873},[645,1267,1110],{"class":662},[645,1269,1130],{"class":697},[645,1271,701],{"class":666},[645,1273,704],{"class":662},[645,1275,1276],{"class":707},"tutorial.mp4",[645,1278,704],{"class":662},[645,1280,670],{"class":662},[645,1282,1283],{"class":666}," file",[645,1285,670],{"class":662},[645,1287,715],{"class":662},[645,1289,1290,1293,1295,1298,1302,1305,1308],{"class":647,"line":915},[645,1291,1292],{"class":697},"    onProgress",[645,1294,725],{"class":662},[645,1296,1297],{"class":662}," (",[645,1299,1301],{"class":1300},"sHdIc","progress",[645,1303,1304],{"class":662},")",[645,1306,1307],{"class":658}," =>",[645,1309,715],{"class":662},[645,1311,1312,1315,1317,1320,1322,1324,1327,1330,1332,1334,1337,1339,1342,1344],{"class":647,"line":928},[645,1313,1314],{"class":666},"      console",[645,1316,689],{"class":662},[645,1318,1319],{"class":697},"log",[645,1321,701],{"class":721},[645,1323,1146],{"class":662},[645,1325,1326],{"class":707},"Uploaded: ",[645,1328,1329],{"class":662},"${",[645,1331,1301],{"class":666},[645,1333,689],{"class":662},[645,1335,1336],{"class":666},"percent",[645,1338,676],{"class":662},[645,1340,1341],{"class":707},"%",[645,1343,1146],{"class":662},[645,1345,757],{"class":721},[645,1347,1348],{"class":647,"line":935},[645,1349,1350],{"class":662},"    }\n",[645,1352,1353,1355],{"class":647,"line":940},[645,1354,1211],{"class":662},[645,1356,757],{"class":666},[645,1358,1359],{"class":647,"line":946},[645,1360,764],{"emptyLinePlaceholder":763},[645,1362,1363],{"class":647,"line":986},[645,1364,1365],{"class":651},"\u002F\u002F Download file\n",[645,1367,1368,1370,1372,1374,1376,1378,1380,1382,1384,1386,1388],{"class":647,"line":991},[645,1369,659],{"class":658},[645,1371,663],{"class":662},[645,1373,667],{"class":666},[645,1375,670],{"class":662},[645,1377,673],{"class":666},[645,1379,676],{"class":662},[645,1381,679],{"class":662},[645,1383,683],{"class":682},[645,1385,686],{"class":666},[645,1387,689],{"class":662},[645,1389,1105],{"class":666},[645,1391,1392,1394,1396,1398,1400,1402,1404],{"class":647,"line":997},[645,1393,1110],{"class":662},[645,1395,1113],{"class":697},[645,1397,701],{"class":666},[645,1399,704],{"class":662},[645,1401,708],{"class":707},[645,1403,704],{"class":662},[645,1405,757],{"class":666},[645,1407,1409,1411,1414,1416,1418,1420,1422,1424,1426],{"class":647,"line":1408},23,[645,1410,1110],{"class":662},[645,1412,1413],{"class":697},"download",[645,1415,701],{"class":666},[645,1417,1135],{"class":662},[645,1419,1138],{"class":666},[645,1421,676],{"class":662},[645,1423,1143],{"class":707},[645,1425,1146],{"class":662},[645,1427,757],{"class":666},[645,1429,1431],{"class":647,"line":1430},24,[645,1432,764],{"emptyLinePlaceholder":763},[645,1434,1436],{"class":647,"line":1435},25,[645,1437,1438],{"class":651},"\u002F\u002F Get public URL (không hết hạn)\n",[645,1440,1442,1444,1446,1449,1451,1453,1455,1457],{"class":647,"line":1441},26,[645,1443,659],{"class":658},[645,1445,663],{"class":662},[645,1447,1448],{"class":666}," data ",[645,1450,676],{"class":662},[645,1452,679],{"class":662},[645,1454,686],{"class":666},[645,1456,689],{"class":662},[645,1458,1105],{"class":666},[645,1460,1462,1464,1466,1468,1470,1472,1474],{"class":647,"line":1461},27,[645,1463,1110],{"class":662},[645,1465,1113],{"class":697},[645,1467,701],{"class":666},[645,1469,704],{"class":662},[645,1471,708],{"class":707},[645,1473,704],{"class":662},[645,1475,757],{"class":666},[645,1477,1479,1481,1484,1486,1488,1490,1492,1494,1496],{"class":647,"line":1478},28,[645,1480,1110],{"class":662},[645,1482,1483],{"class":697},"getPublicUrl",[645,1485,701],{"class":666},[645,1487,1135],{"class":662},[645,1489,1138],{"class":666},[645,1491,676],{"class":662},[645,1493,1143],{"class":707},[645,1495,1146],{"class":662},[645,1497,757],{"class":666},[645,1499,1501],{"class":647,"line":1500},29,[645,1502,1503],{"class":651},"\u002F\u002F data.publicUrl -> https:\u002F\u002F[project].supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Favatars\u002F...\n",[645,1505,1507],{"class":647,"line":1506},30,[645,1508,764],{"emptyLinePlaceholder":763},[645,1510,1512],{"class":647,"line":1511},31,[645,1513,1514],{"class":651},"\u002F\u002F List files trong thư mục\n",[645,1516,1518,1520,1522,1524,1526,1528,1530,1532,1534,1536,1538],{"class":647,"line":1517},32,[645,1519,659],{"class":658},[645,1521,663],{"class":662},[645,1523,667],{"class":666},[645,1525,670],{"class":662},[645,1527,673],{"class":666},[645,1529,676],{"class":662},[645,1531,679],{"class":662},[645,1533,683],{"class":682},[645,1535,686],{"class":666},[645,1537,689],{"class":662},[645,1539,1105],{"class":666},[645,1541,1543,1545,1547,1549,1551,1553,1555],{"class":647,"line":1542},33,[645,1544,1110],{"class":662},[645,1546,1113],{"class":697},[645,1548,701],{"class":666},[645,1550,704],{"class":662},[645,1552,708],{"class":707},[645,1554,704],{"class":662},[645,1556,757],{"class":666},[645,1558,1560,1562,1565,1568,1570],{"class":647,"line":1559},34,[645,1561,1110],{"class":662},[645,1563,1564],{"class":697},"list",[645,1566,1567],{"class":666},"(userId",[645,1569,670],{"class":662},[645,1571,715],{"class":662},[645,1573,1575,1578,1580,1583],{"class":647,"line":1574},35,[645,1576,1577],{"class":721},"    limit",[645,1579,725],{"class":662},[645,1581,1582],{"class":745}," 100",[645,1584,1585],{"class":662},",\n",[645,1587,1589,1592,1594,1597],{"class":647,"line":1588},36,[645,1590,1591],{"class":721},"    offset",[645,1593,725],{"class":662},[645,1595,1596],{"class":745}," 0",[645,1598,1585],{"class":662},[645,1600,1602,1605,1607,1609,1612,1614,1616,1619,1621,1623,1626,1628,1630,1633,1635],{"class":647,"line":1601},37,[645,1603,1604],{"class":721},"    sortBy",[645,1606,725],{"class":662},[645,1608,663],{"class":662},[645,1610,1611],{"class":721}," column",[645,1613,725],{"class":662},[645,1615,1165],{"class":662},[645,1617,1618],{"class":707},"created_at",[645,1620,704],{"class":662},[645,1622,670],{"class":662},[645,1624,1625],{"class":721}," order",[645,1627,725],{"class":662},[645,1629,1165],{"class":662},[645,1631,1632],{"class":707},"desc",[645,1634,704],{"class":662},[645,1636,1637],{"class":662}," }\n",[645,1639,1641,1643],{"class":647,"line":1640},38,[645,1642,1211],{"class":662},[645,1644,757],{"class":666},[645,1646,1648],{"class":647,"line":1647},39,[645,1649,764],{"emptyLinePlaceholder":763},[645,1651,1653],{"class":647,"line":1652},40,[645,1654,1655],{"class":651},"\u002F\u002F Di chuyển \u002F đổi tên file\n",[645,1657,1659,1661,1663,1665,1667,1669,1671,1673,1675,1677,1679],{"class":647,"line":1658},41,[645,1660,659],{"class":658},[645,1662,663],{"class":662},[645,1664,667],{"class":666},[645,1666,670],{"class":662},[645,1668,673],{"class":666},[645,1670,676],{"class":662},[645,1672,679],{"class":662},[645,1674,683],{"class":682},[645,1676,686],{"class":666},[645,1678,689],{"class":662},[645,1680,1105],{"class":666},[645,1682,1684,1686,1688,1690,1692,1694,1696],{"class":647,"line":1683},42,[645,1685,1110],{"class":662},[645,1687,1113],{"class":697},[645,1689,701],{"class":666},[645,1691,704],{"class":662},[645,1693,708],{"class":707},[645,1695,704],{"class":662},[645,1697,757],{"class":666},[645,1699,1701,1703,1706,1708,1710,1712,1714,1717,1719,1721,1724,1726,1728,1731,1733],{"class":647,"line":1700},43,[645,1702,1110],{"class":662},[645,1704,1705],{"class":697},"move",[645,1707,701],{"class":666},[645,1709,1135],{"class":662},[645,1711,1138],{"class":666},[645,1713,676],{"class":662},[645,1715,1716],{"class":707},"\u002Fold.png",[645,1718,1146],{"class":662},[645,1720,670],{"class":662},[645,1722,1723],{"class":662}," `${",[645,1725,1138],{"class":666},[645,1727,676],{"class":662},[645,1729,1730],{"class":707},"\u002Fnew.png",[645,1732,1146],{"class":662},[645,1734,757],{"class":666},[645,1736,1738],{"class":647,"line":1737},44,[645,1739,764],{"emptyLinePlaceholder":763},[645,1741,1743],{"class":647,"line":1742},45,[645,1744,1745],{"class":651},"\u002F\u002F Copy file\n",[645,1747,1749,1751,1753,1755,1757,1759,1761,1763,1765,1767,1769],{"class":647,"line":1748},46,[645,1750,659],{"class":658},[645,1752,663],{"class":662},[645,1754,667],{"class":666},[645,1756,670],{"class":662},[645,1758,673],{"class":666},[645,1760,676],{"class":662},[645,1762,679],{"class":662},[645,1764,683],{"class":682},[645,1766,686],{"class":666},[645,1768,689],{"class":662},[645,1770,1105],{"class":666},[645,1772,1774,1776,1778,1780,1782,1784,1786],{"class":647,"line":1773},47,[645,1775,1110],{"class":662},[645,1777,1113],{"class":697},[645,1779,701],{"class":666},[645,1781,704],{"class":662},[645,1783,708],{"class":707},[645,1785,704],{"class":662},[645,1787,757],{"class":666},[645,1789,1791,1793,1796,1798,1800,1802,1804,1806,1808,1810,1813,1816,1818,1820,1822,1824,1826],{"class":647,"line":1790},48,[645,1792,1110],{"class":662},[645,1794,1795],{"class":697},"copy",[645,1797,701],{"class":666},[645,1799,1135],{"class":662},[645,1801,1138],{"class":666},[645,1803,676],{"class":662},[645,1805,1143],{"class":707},[645,1807,1146],{"class":662},[645,1809,670],{"class":662},[645,1811,1812],{"class":662}," `",[645,1814,1815],{"class":707},"backup\u002F",[645,1817,1329],{"class":662},[645,1819,1138],{"class":666},[645,1821,676],{"class":662},[645,1823,1143],{"class":707},[645,1825,1146],{"class":662},[645,1827,757],{"class":666},[645,1829,1831],{"class":647,"line":1830},49,[645,1832,764],{"emptyLinePlaceholder":763},[645,1834,1836],{"class":647,"line":1835},50,[645,1837,1838],{"class":651},"\u002F\u002F Xóa file\n",[645,1840,1842,1844,1846,1848,1850,1852,1854,1856,1858,1860,1862],{"class":647,"line":1841},51,[645,1843,659],{"class":658},[645,1845,663],{"class":662},[645,1847,667],{"class":666},[645,1849,670],{"class":662},[645,1851,673],{"class":666},[645,1853,676],{"class":662},[645,1855,679],{"class":662},[645,1857,683],{"class":682},[645,1859,686],{"class":666},[645,1861,689],{"class":662},[645,1863,1105],{"class":666},[645,1865,1867,1869,1871,1873,1875,1877,1879],{"class":647,"line":1866},52,[645,1868,1110],{"class":662},[645,1870,1113],{"class":697},[645,1872,701],{"class":666},[645,1874,704],{"class":662},[645,1876,708],{"class":707},[645,1878,704],{"class":662},[645,1880,757],{"class":666},[645,1882,1884,1886,1889,1892,1894,1896,1898,1900,1902,1904,1906,1908,1910,1913,1915],{"class":647,"line":1883},53,[645,1885,1110],{"class":662},[645,1887,1888],{"class":697},"remove",[645,1890,1891],{"class":666},"([",[645,1893,1135],{"class":662},[645,1895,1138],{"class":666},[645,1897,676],{"class":662},[645,1899,1143],{"class":707},[645,1901,1146],{"class":662},[645,1903,670],{"class":662},[645,1905,1723],{"class":662},[645,1907,1138],{"class":666},[645,1909,676],{"class":662},[645,1911,1912],{"class":707},"\u002Fbanner.png",[645,1914,1146],{"class":662},[645,1916,1917],{"class":666},"])\n",[432,1919,1921],{"id":1920},"s3-compatible-api","S3-Compatible API",[436,1923,1924],{},"Supabase Storage tương thích S3 protocol, cho phép dùng bất kỳ S3 client SDK hoặc CLI tool nào để truy cập:",[636,1926,1930],{"className":1927,"code":1928,"language":1929,"meta":641,"style":641},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","# Dùng AWS CLI với Supabase làm S3 endpoint\naws s3 ls s3:\u002F\u002Favatars\u002F --endpoint-url https:\u002F\u002F[PROJECT_REF].supabase.co\u002Fstorage\u002Fv1\u002Fs3\n\n# Dùng MinIO client\nmc alias set supabase https:\u002F\u002F[PROJECT_REF].supabase.co\u002Fstorage\u002Fv1\u002Fs3 \\\n  [ACCESS_KEY] [SECRET_KEY]\nmc ls supabase\u002Favatars\u002F\n","bash",[449,1931,1932,1937,1958,1962,1967,1986,1999],{"__ignoreMap":641},[645,1933,1934],{"class":647,"line":648},[645,1935,1936],{"class":651},"# Dùng AWS CLI với Supabase làm S3 endpoint\n",[645,1938,1939,1943,1946,1949,1952,1955],{"class":647,"line":655},[645,1940,1942],{"class":1941},"sBMFI","aws",[645,1944,1945],{"class":707}," s3",[645,1947,1948],{"class":707}," ls",[645,1950,1951],{"class":707}," s3:\u002F\u002Favatars\u002F",[645,1953,1954],{"class":707}," --endpoint-url",[645,1956,1957],{"class":707}," https:\u002F\u002F[PROJECT_REF].supabase.co\u002Fstorage\u002Fv1\u002Fs3\n",[645,1959,1960],{"class":647,"line":718},[645,1961,764],{"emptyLinePlaceholder":763},[645,1963,1964],{"class":647,"line":737},[645,1965,1966],{"class":651},"# Dùng MinIO client\n",[645,1968,1969,1972,1975,1978,1980,1983],{"class":647,"line":752},[645,1970,1971],{"class":1941},"mc",[645,1973,1974],{"class":707}," alias",[645,1976,1977],{"class":707}," set",[645,1979,686],{"class":707},[645,1981,1982],{"class":707}," https:\u002F\u002F[PROJECT_REF].supabase.co\u002Fstorage\u002Fv1\u002Fs3",[645,1984,1985],{"class":666}," \\\n",[645,1987,1988,1991,1994,1997],{"class":647,"line":760},[645,1989,1990],{"class":666},"  [ACCESS_KEY] ",[645,1992,1993],{"class":662},"[",[645,1995,1996],{"class":666},"SECRET_KEY",[645,1998,1080],{"class":662},[645,2000,2001,2003,2005],{"class":647,"line":767},[645,2002,1971],{"class":1941},[645,2004,1948],{"class":707},[645,2006,2007],{"class":707}," supabase\u002Favatars\u002F\n",[436,2009,2010],{},"Điều này cho phép tích hợp với các công cụ xử lý media, backup automation, và hệ thống hiện có đã dùng S3.",[432,2012,2014],{"id":2013},"cdn-image-transformation","CDN & Image Transformation",[436,2016,2017,2018,2021],{},"Toàn bộ file được phục vụ qua CDN toàn cầu với 285+ điểm hiện diện. Đặc biệt, Supabase hỗ trợ ",[440,2019,2020],{},"transform ảnh on-the-fly"," qua URL parameters:",[636,2023,2028],{"className":2024,"code":2026,"language":2027},[2025],"language-text","# URL format đầy đủ cho image transformation\n\n# Base URL pattern:\nhttps:\u002F\u002F\u003Cproject>.supabase.co\u002Fstorage\u002Fv1\u002Frender\u002Fimage\u002Fpublic\u002Fbucket\u002Favatar.png\n\n# Resize theo width (giữ aspect ratio)\n?width=500\n\n# Resize theo height (giữ aspect ratio)\n?height=300\n\n# Resize khớp khung với các chế độ\n?width=500&height=500&resize=cover       # Cắt để lấp đầy khung (có thể mất phần rìa)\n?width=500&height=500&resize=contain     # Giữ trọn ảnh trong khung (có thể có khoảng trống)\n?width=500&height=500&resize=fill        # Bóp méo để lấp đầy khung (không giữ aspect ratio)\n\n# Thay đổi chất lượng ảnh (0-100, mặc định 80)\n?width=500&quality=85\n\n# Chuyển đổi định dạng ảnh\n?width=500&format=webp                   # WebP — nhẹ hơn JPEG ~30%, trình duyệt hiện đại đều hỗ trợ\n?width=500&format=avif                   # AVIF — nhẹ hơn WebP ~20%, hỗ trợ HDR\n?width=500&format=png                    # PNG — lossless, dùng cho ảnh có transparency\n?width=500&format=jpg                    # JPEG — tương thích rộng nhất\n\n# Kết hợp nhiều tham số — ví dụ thực tế:\n\u002Fstorage\u002Fv1\u002Frender\u002Fimage\u002Fpublic\u002Fbucket\u002Fphoto.jpg\n  ?width=400\n  &height=400\n  &resize=cover\n  &quality=80\n  &format=webp\n\n# Kết quả: Ảnh 400x400px, crop center, chất lượng 80%, định dạng WebP\n# File size giảm từ ~2MB (ảnh gốc) xuống còn ~15KB\n","text",[449,2029,2026],{"__ignoreMap":641},[454,2031,2034,2037],{"color":2032,"icon":2033},"warning","i-lucide-lightbulb",[440,2035,2036],{},"Best practice:"," Luôn tạo thumbnail qua CDN transform thay vì upload nhiều kích thước. Chỉ cần upload ảnh gốc chất lượng cao, các biến thể được sinh on-demand và cache trên CDN. Điều này tiết kiệm storage và đơn giản hóa logic upload.",[454,2039,2040,2043,2044,2047,2048,2051,2052,2055,2056,2059],{"color":456,"icon":457},[440,2041,2042],{},"Interview point — Image Transformation:"," CDN transform hoạt động thông qua ",[449,2045,2046],{},"imgproxy"," service. Ảnh gốc được lưu một lần, các biến thể (thumbnail, webp, avif) được tạo on-the-fly khi request đầu tiên và cache trên CDN edge. Không tốn thêm storage. Format ",[449,2049,2050],{},"webp"," recommended cho web, ",[449,2053,2054],{},"avif"," cho ứng dụng modern. ",[449,2057,2058],{},"resize=cover"," phổ biến nhất cho avatar\u002Fthumbnail.",[432,2061,2063],{"id":2062},"signed-urls-truy-cập-có-thời-hạn","Signed URLs — Truy cập có thời hạn",[436,2065,2066],{},"Với file private, bạn có thể tạo URL tạm thời cho phép truy cập trong khoảng thời gian giới hạn:",[636,2068,2070],{"className":638,"code":2069,"language":640,"meta":641,"style":641},"\u002F\u002F Tạo signed URL (hết hạn sau 60 giây)\nconst { data, error } = await supabase.storage\n  .from('private-docs')\n  .createSignedUrl('contract-2024.pdf', 60)\n\u002F\u002F data.signedUrl -> URL có ?token=xxx, hết hạn sau 60s\n\n\u002F\u002F Tạo signed URL cho upload (cho phép upload mà không cần auth)\nconst { data, error } = await supabase.storage\n  .from('submissions')\n  .createSignedUploadUrl(`${taskId}\u002Fsubmission.pdf`)\n\n\u002F\u002F Signed URL cho nhiều file cùng lúc\nconst { data, error } = await supabase.storage\n  .from('exports')\n  .createSignedUrls(['report-q1.pdf', 'report-q2.pdf'], 300)\n",[449,2071,2072,2077,2101,2118,2141,2146,2150,2155,2179,2196,2219,2223,2228,2252,2269],{"__ignoreMap":641},[645,2073,2074],{"class":647,"line":648},[645,2075,2076],{"class":651},"\u002F\u002F Tạo signed URL (hết hạn sau 60 giây)\n",[645,2078,2079,2081,2083,2085,2087,2089,2091,2093,2095,2097,2099],{"class":647,"line":655},[645,2080,659],{"class":658},[645,2082,663],{"class":662},[645,2084,667],{"class":666},[645,2086,670],{"class":662},[645,2088,673],{"class":666},[645,2090,676],{"class":662},[645,2092,679],{"class":662},[645,2094,683],{"class":682},[645,2096,686],{"class":666},[645,2098,689],{"class":662},[645,2100,1105],{"class":666},[645,2102,2103,2105,2107,2109,2111,2114,2116],{"class":647,"line":718},[645,2104,1110],{"class":662},[645,2106,1113],{"class":697},[645,2108,701],{"class":666},[645,2110,704],{"class":662},[645,2112,2113],{"class":707},"private-docs",[645,2115,704],{"class":662},[645,2117,757],{"class":666},[645,2119,2120,2122,2125,2127,2129,2132,2134,2136,2139],{"class":647,"line":737},[645,2121,1110],{"class":662},[645,2123,2124],{"class":697},"createSignedUrl",[645,2126,701],{"class":666},[645,2128,704],{"class":662},[645,2130,2131],{"class":707},"contract-2024.pdf",[645,2133,704],{"class":662},[645,2135,670],{"class":662},[645,2137,2138],{"class":745}," 60",[645,2140,757],{"class":666},[645,2142,2143],{"class":647,"line":752},[645,2144,2145],{"class":651},"\u002F\u002F data.signedUrl -> URL có ?token=xxx, hết hạn sau 60s\n",[645,2147,2148],{"class":647,"line":760},[645,2149,764],{"emptyLinePlaceholder":763},[645,2151,2152],{"class":647,"line":767},[645,2153,2154],{"class":651},"\u002F\u002F Tạo signed URL cho upload (cho phép upload mà không cần auth)\n",[645,2156,2157,2159,2161,2163,2165,2167,2169,2171,2173,2175,2177],{"class":647,"line":773},[645,2158,659],{"class":658},[645,2160,663],{"class":662},[645,2162,667],{"class":666},[645,2164,670],{"class":662},[645,2166,673],{"class":666},[645,2168,676],{"class":662},[645,2170,679],{"class":662},[645,2172,683],{"class":682},[645,2174,686],{"class":666},[645,2176,689],{"class":662},[645,2178,1105],{"class":666},[645,2180,2181,2183,2185,2187,2189,2192,2194],{"class":647,"line":811},[645,2182,1110],{"class":662},[645,2184,1113],{"class":697},[645,2186,701],{"class":666},[645,2188,704],{"class":662},[645,2190,2191],{"class":707},"submissions",[645,2193,704],{"class":662},[645,2195,757],{"class":666},[645,2197,2198,2200,2203,2205,2207,2210,2212,2215,2217],{"class":647,"line":816},[645,2199,1110],{"class":662},[645,2201,2202],{"class":697},"createSignedUploadUrl",[645,2204,701],{"class":666},[645,2206,1135],{"class":662},[645,2208,2209],{"class":666},"taskId",[645,2211,676],{"class":662},[645,2213,2214],{"class":707},"\u002Fsubmission.pdf",[645,2216,1146],{"class":662},[645,2218,757],{"class":666},[645,2220,2221],{"class":647,"line":822},[645,2222,764],{"emptyLinePlaceholder":763},[645,2224,2225],{"class":647,"line":862},[645,2226,2227],{"class":651},"\u002F\u002F Signed URL cho nhiều file cùng lúc\n",[645,2229,2230,2232,2234,2236,2238,2240,2242,2244,2246,2248,2250],{"class":647,"line":867},[645,2231,659],{"class":658},[645,2233,663],{"class":662},[645,2235,667],{"class":666},[645,2237,670],{"class":662},[645,2239,673],{"class":666},[645,2241,676],{"class":662},[645,2243,679],{"class":662},[645,2245,683],{"class":682},[645,2247,686],{"class":666},[645,2249,689],{"class":662},[645,2251,1105],{"class":666},[645,2253,2254,2256,2258,2260,2262,2265,2267],{"class":647,"line":873},[645,2255,1110],{"class":662},[645,2257,1113],{"class":697},[645,2259,701],{"class":666},[645,2261,704],{"class":662},[645,2263,2264],{"class":707},"exports",[645,2266,704],{"class":662},[645,2268,757],{"class":666},[645,2270,2271,2273,2276,2278,2280,2283,2285,2287,2289,2292,2294,2297,2299,2302],{"class":647,"line":915},[645,2272,1110],{"class":662},[645,2274,2275],{"class":697},"createSignedUrls",[645,2277,1891],{"class":666},[645,2279,704],{"class":662},[645,2281,2282],{"class":707},"report-q1.pdf",[645,2284,704],{"class":662},[645,2286,670],{"class":662},[645,2288,1165],{"class":662},[645,2290,2291],{"class":707},"report-q2.pdf",[645,2293,704],{"class":662},[645,2295,2296],{"class":666},"]",[645,2298,670],{"class":662},[645,2300,2301],{"class":745}," 300",[645,2303,757],{"class":666},[432,2305,2307],{"id":2306},"row-level-security-cho-storage","Row Level Security cho Storage",[436,2309,2310],{},"RLS có thể áp dụng cho storage objects giống như database tables. Điều này cho phép kiểm soát chính xác ai được xem, upload, hay xóa file nào.",[636,2312,2316],{"className":2313,"code":2314,"language":2315,"meta":641,"style":641},"language-sql shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","-- Pattern 1: User chỉ truy cập được file trong thư mục của mình\n-- File được tổ chức theo cấu trúc: {userId}\u002Ffilename.png\n-- (storage.foldername(name))[1] trả về phần đầu tiên của path = userId\n\nCREATE POLICY \"Users can view own avatars\"\nON storage.objects FOR SELECT\nUSING (\n  bucket_id = 'avatars'\n  AND auth.uid()::text = (storage.foldername(name))[1]\n);\n\nCREATE POLICY \"Users can upload own avatars\"\nON storage.objects FOR INSERT\nWITH CHECK (\n  bucket_id = 'avatars'\n  AND auth.uid()::text = (storage.foldername(name))[1]\n);\n\nCREATE POLICY \"Users can update own avatars\"\nON storage.objects FOR UPDATE\nUSING (\n  bucket_id = 'avatars'\n  AND auth.uid()::text = (storage.foldername(name))[1]\n);\n\nCREATE POLICY \"Users can delete own avatars\"\nON storage.objects FOR DELETE\nUSING (\n  bucket_id = 'avatars'\n  AND auth.uid()::text = (storage.foldername(name))[1]\n);\n\n-- Pattern 2: Public read + authenticated write\nCREATE POLICY \"Public read access\"\nON storage.objects FOR SELECT\nUSING (bucket_id = 'public-assets');\n\nCREATE POLICY \"Authenticated users can upload\"\nON storage.objects FOR INSERT\nWITH CHECK (\n  bucket_id = 'public-assets'\n  AND auth.role() = 'authenticated'\n);\n\n-- Pattern 3: Admin-only access (dùng service role)\nCREATE POLICY \"Admin full access\"\nON storage.objects FOR ALL\nUSING (\n  bucket_id = 'admin-files'\n  AND auth.jwt()->>'role' = 'admin'  -- Custom claim từ JWT\n);\n","sql",[449,2317,2318,2323,2328,2333,2337,2342,2347,2352,2357,2362,2367,2371,2376,2381,2386,2390,2394,2398,2402,2407,2412,2416,2420,2424,2428,2432,2437,2442,2446,2450,2454,2458,2462,2467,2472,2476,2481,2485,2490,2494,2498,2503,2508,2512,2516,2521,2526,2531,2535,2540,2545],{"__ignoreMap":641},[645,2319,2320],{"class":647,"line":648},[645,2321,2322],{},"-- Pattern 1: User chỉ truy cập được file trong thư mục của mình\n",[645,2324,2325],{"class":647,"line":655},[645,2326,2327],{},"-- File được tổ chức theo cấu trúc: {userId}\u002Ffilename.png\n",[645,2329,2330],{"class":647,"line":718},[645,2331,2332],{},"-- (storage.foldername(name))[1] trả về phần đầu tiên của path = userId\n",[645,2334,2335],{"class":647,"line":737},[645,2336,764],{"emptyLinePlaceholder":763},[645,2338,2339],{"class":647,"line":752},[645,2340,2341],{},"CREATE POLICY \"Users can view own avatars\"\n",[645,2343,2344],{"class":647,"line":760},[645,2345,2346],{},"ON storage.objects FOR SELECT\n",[645,2348,2349],{"class":647,"line":767},[645,2350,2351],{},"USING (\n",[645,2353,2354],{"class":647,"line":773},[645,2355,2356],{},"  bucket_id = 'avatars'\n",[645,2358,2359],{"class":647,"line":811},[645,2360,2361],{},"  AND auth.uid()::text = (storage.foldername(name))[1]\n",[645,2363,2364],{"class":647,"line":816},[645,2365,2366],{},");\n",[645,2368,2369],{"class":647,"line":822},[645,2370,764],{"emptyLinePlaceholder":763},[645,2372,2373],{"class":647,"line":862},[645,2374,2375],{},"CREATE POLICY \"Users can upload own avatars\"\n",[645,2377,2378],{"class":647,"line":867},[645,2379,2380],{},"ON storage.objects FOR INSERT\n",[645,2382,2383],{"class":647,"line":873},[645,2384,2385],{},"WITH CHECK (\n",[645,2387,2388],{"class":647,"line":915},[645,2389,2356],{},[645,2391,2392],{"class":647,"line":928},[645,2393,2361],{},[645,2395,2396],{"class":647,"line":935},[645,2397,2366],{},[645,2399,2400],{"class":647,"line":940},[645,2401,764],{"emptyLinePlaceholder":763},[645,2403,2404],{"class":647,"line":946},[645,2405,2406],{},"CREATE POLICY \"Users can update own avatars\"\n",[645,2408,2409],{"class":647,"line":986},[645,2410,2411],{},"ON storage.objects FOR UPDATE\n",[645,2413,2414],{"class":647,"line":991},[645,2415,2351],{},[645,2417,2418],{"class":647,"line":997},[645,2419,2356],{},[645,2421,2422],{"class":647,"line":1408},[645,2423,2361],{},[645,2425,2426],{"class":647,"line":1430},[645,2427,2366],{},[645,2429,2430],{"class":647,"line":1435},[645,2431,764],{"emptyLinePlaceholder":763},[645,2433,2434],{"class":647,"line":1441},[645,2435,2436],{},"CREATE POLICY \"Users can delete own avatars\"\n",[645,2438,2439],{"class":647,"line":1461},[645,2440,2441],{},"ON storage.objects FOR DELETE\n",[645,2443,2444],{"class":647,"line":1478},[645,2445,2351],{},[645,2447,2448],{"class":647,"line":1500},[645,2449,2356],{},[645,2451,2452],{"class":647,"line":1506},[645,2453,2361],{},[645,2455,2456],{"class":647,"line":1511},[645,2457,2366],{},[645,2459,2460],{"class":647,"line":1517},[645,2461,764],{"emptyLinePlaceholder":763},[645,2463,2464],{"class":647,"line":1542},[645,2465,2466],{},"-- Pattern 2: Public read + authenticated write\n",[645,2468,2469],{"class":647,"line":1559},[645,2470,2471],{},"CREATE POLICY \"Public read access\"\n",[645,2473,2474],{"class":647,"line":1574},[645,2475,2346],{},[645,2477,2478],{"class":647,"line":1588},[645,2479,2480],{},"USING (bucket_id = 'public-assets');\n",[645,2482,2483],{"class":647,"line":1601},[645,2484,764],{"emptyLinePlaceholder":763},[645,2486,2487],{"class":647,"line":1640},[645,2488,2489],{},"CREATE POLICY \"Authenticated users can upload\"\n",[645,2491,2492],{"class":647,"line":1647},[645,2493,2380],{},[645,2495,2496],{"class":647,"line":1652},[645,2497,2385],{},[645,2499,2500],{"class":647,"line":1658},[645,2501,2502],{},"  bucket_id = 'public-assets'\n",[645,2504,2505],{"class":647,"line":1683},[645,2506,2507],{},"  AND auth.role() = 'authenticated'\n",[645,2509,2510],{"class":647,"line":1700},[645,2511,2366],{},[645,2513,2514],{"class":647,"line":1737},[645,2515,764],{"emptyLinePlaceholder":763},[645,2517,2518],{"class":647,"line":1742},[645,2519,2520],{},"-- Pattern 3: Admin-only access (dùng service role)\n",[645,2522,2523],{"class":647,"line":1748},[645,2524,2525],{},"CREATE POLICY \"Admin full access\"\n",[645,2527,2528],{"class":647,"line":1773},[645,2529,2530],{},"ON storage.objects FOR ALL\n",[645,2532,2533],{"class":647,"line":1790},[645,2534,2351],{},[645,2536,2537],{"class":647,"line":1830},[645,2538,2539],{},"  bucket_id = 'admin-files'\n",[645,2541,2542],{"class":647,"line":1835},[645,2543,2544],{},"  AND auth.jwt()->>'role' = 'admin'  -- Custom claim từ JWT\n",[645,2546,2547],{"class":647,"line":1841},[645,2548,2366],{},[436,2550,2551,2552,2555,2556,2559,2560,689],{},"Hàm ",[449,2553,2554],{},"storage.foldername(name)"," trả về mảng các phần của path, ",[449,2557,2558],{},"[1]"," là thư mục gốc đầu tiên -- đây là convention phổ biến để tổ chức file theo ",[449,2561,2562],{},"{userId}\u002F...",[454,2564,2565,2568,2569,2571,2572,2574],{"color":456,"icon":457},[440,2566,2567],{},"Interview point — Storage RLS:"," RLS trên ",[449,2570,451],{}," hoạt động giống hệt RLS trên database tables. ",[449,2573,2554],{}," là helper function trả về mảng path segments -- dùng để parse userId từ file path. Luôn tạo policy cho cả 4 operations (SELECT, INSERT, UPDATE, DELETE) để bảo mật toàn diện.",[432,2576,2578],{"id":2577},"tus-resumable-uploads","TUS Resumable Uploads",[436,2580,2581],{},"Hỗ trợ upload file lớn với TUS protocol (pause, resume, retry):",[636,2583,2585],{"className":638,"code":2584,"language":640,"meta":641,"style":641},"\u002F\u002F Dùng Uppy với TUS\nimport Uppy from '@uppy\u002Fcore'\nimport Tus from '@uppy\u002Ftus'\n\nconst uppy = new Uppy()\n  .use(Tus, {\n    endpoint: `https:\u002F\u002F[PROJECT_REF].supabase.co\u002Fstorage\u002Fv1\u002Fupload\u002Fresumable`,\n    headers: {\n      authorization: `Bearer ${supabaseKey}`,\n      'x-upsert': 'true'\n    },\n    chunkSize: 6 * 1024 * 1024, \u002F\u002F 6MB chunks\n    retryDelays: [0, 1000, 3000, 5000] \u002F\u002F retry delays\n  })\n",[449,2586,2587,2592,2610,2626,2630,2647,2661,2677,2686,2708,2727,2732,2757,2790],{"__ignoreMap":641},[645,2588,2589],{"class":647,"line":648},[645,2590,2591],{"class":651},"\u002F\u002F Dùng Uppy với TUS\n",[645,2593,2594,2597,2600,2602,2604,2607],{"class":647,"line":655},[645,2595,2596],{"class":682},"import",[645,2598,2599],{"class":666}," Uppy ",[645,2601,1113],{"class":682},[645,2603,1165],{"class":662},[645,2605,2606],{"class":707},"@uppy\u002Fcore",[645,2608,2609],{"class":662},"'\n",[645,2611,2612,2614,2617,2619,2621,2624],{"class":647,"line":718},[645,2613,2596],{"class":682},[645,2615,2616],{"class":666}," Tus ",[645,2618,1113],{"class":682},[645,2620,1165],{"class":662},[645,2622,2623],{"class":707},"@uppy\u002Ftus",[645,2625,2609],{"class":662},[645,2627,2628],{"class":647,"line":737},[645,2629,764],{"emptyLinePlaceholder":763},[645,2631,2632,2634,2637,2639,2642,2645],{"class":647,"line":752},[645,2633,659],{"class":658},[645,2635,2636],{"class":666}," uppy ",[645,2638,1061],{"class":662},[645,2640,2641],{"class":662}," new",[645,2643,2644],{"class":697}," Uppy",[645,2646,808],{"class":666},[645,2648,2649,2651,2654,2657,2659],{"class":647,"line":760},[645,2650,1110],{"class":662},[645,2652,2653],{"class":697},"use",[645,2655,2656],{"class":666},"(Tus",[645,2658,670],{"class":662},[645,2660,715],{"class":662},[645,2662,2663,2666,2668,2670,2673,2675],{"class":647,"line":767},[645,2664,2665],{"class":721},"    endpoint",[645,2667,725],{"class":662},[645,2669,1812],{"class":662},[645,2671,2672],{"class":707},"https:\u002F\u002F[PROJECT_REF].supabase.co\u002Fstorage\u002Fv1\u002Fupload\u002Fresumable",[645,2674,1146],{"class":662},[645,2676,1585],{"class":662},[645,2678,2679,2682,2684],{"class":647,"line":773},[645,2680,2681],{"class":721},"    headers",[645,2683,725],{"class":662},[645,2685,715],{"class":662},[645,2687,2688,2691,2693,2695,2698,2700,2703,2706],{"class":647,"line":811},[645,2689,2690],{"class":721},"      authorization",[645,2692,725],{"class":662},[645,2694,1812],{"class":662},[645,2696,2697],{"class":707},"Bearer ",[645,2699,1329],{"class":662},[645,2701,2702],{"class":666},"supabaseKey",[645,2704,2705],{"class":662},"}`",[645,2707,1585],{"class":662},[645,2709,2710,2713,2716,2718,2720,2722,2725],{"class":647,"line":816},[645,2711,2712],{"class":662},"      '",[645,2714,2715],{"class":721},"x-upsert",[645,2717,704],{"class":662},[645,2719,725],{"class":662},[645,2721,1165],{"class":662},[645,2723,2724],{"class":707},"true",[645,2726,2609],{"class":662},[645,2728,2729],{"class":647,"line":822},[645,2730,2731],{"class":662},"    },\n",[645,2733,2734,2737,2739,2742,2745,2748,2750,2752,2754],{"class":647,"line":862},[645,2735,2736],{"class":721},"    chunkSize",[645,2738,725],{"class":662},[645,2740,2741],{"class":745}," 6",[645,2743,2744],{"class":662}," *",[645,2746,2747],{"class":745}," 1024",[645,2749,2744],{"class":662},[645,2751,2747],{"class":745},[645,2753,670],{"class":662},[645,2755,2756],{"class":651}," \u002F\u002F 6MB chunks\n",[645,2758,2759,2762,2764,2767,2769,2771,2774,2776,2779,2781,2784,2787],{"class":647,"line":867},[645,2760,2761],{"class":721},"    retryDelays",[645,2763,725],{"class":662},[645,2765,2766],{"class":666}," [",[645,2768,1077],{"class":745},[645,2770,670],{"class":662},[645,2772,2773],{"class":745}," 1000",[645,2775,670],{"class":662},[645,2777,2778],{"class":745}," 3000",[645,2780,670],{"class":662},[645,2782,2783],{"class":745}," 5000",[645,2785,2786],{"class":666},"] ",[645,2788,2789],{"class":651},"\u002F\u002F retry delays\n",[645,2791,2792,2794],{"class":647,"line":873},[645,2793,1211],{"class":662},[645,2795,757],{"class":666},[436,2797,2798],{},"TUS đặc biệt hữu ích khi upload video, file nén lớn, hoặc trong môi trường mạng không ổn định.",[432,2800,2802],{"id":2801},"multi-tenancy-patterns","Multi-Tenancy Patterns",[436,2804,2805],{},"Khi xây dựng ứng dụng multi-tenant (SaaS), bạn có thể tổ chức storage theo nhiều cách:",[636,2807,2809],{"className":638,"code":2808,"language":640,"meta":641,"style":641},"\u002F\u002F Pattern 1: Phân tách theo thư mục (đơn giản nhất)\nconst path = `${tenantId}\u002F${userId}\u002F${fileName}`\n\n\u002F\u002F Pattern 2: Mỗi tenant một bucket riêng\nconst bucketName = `tenant-${tenantId}`\nawait supabase.storage.createBucket(bucketName, { public: false })\n\n\u002F\u002F Pattern 3: Bucket dùng chung + RLS policy cho tenant\n\u002F\u002F Policy trong SQL:\n\u002F\u002F CREATE POLICY \"Tenant isolation\"\n\u002F\u002F ON storage.objects FOR SELECT\n\u002F\u002F USING (\n\u002F\u002F   bucket_id = 'documents'\n\u002F\u002F   AND (storage.foldername(name))[1] = (\n\u002F\u002F     SELECT tenant_id::text FROM profiles WHERE id = auth.uid()\n\u002F\u002F   )\n\u002F\u002F );\n",[449,2810,2811,2816,2851,2855,2860,2880,2915,2919,2924,2929,2934,2939,2944,2949,2954,2959,2964],{"__ignoreMap":641},[645,2812,2813],{"class":647,"line":648},[645,2814,2815],{"class":651},"\u002F\u002F Pattern 1: Phân tách theo thư mục (đơn giản nhất)\n",[645,2817,2818,2820,2823,2825,2827,2830,2832,2835,2837,2839,2841,2843,2845,2848],{"class":647,"line":655},[645,2819,659],{"class":658},[645,2821,2822],{"class":666}," path ",[645,2824,1061],{"class":662},[645,2826,1723],{"class":662},[645,2828,2829],{"class":666},"tenantId",[645,2831,676],{"class":662},[645,2833,2834],{"class":707},"\u002F",[645,2836,1329],{"class":662},[645,2838,1138],{"class":666},[645,2840,676],{"class":662},[645,2842,2834],{"class":707},[645,2844,1329],{"class":662},[645,2846,2847],{"class":666},"fileName",[645,2849,2850],{"class":662},"}`\n",[645,2852,2853],{"class":647,"line":718},[645,2854,764],{"emptyLinePlaceholder":763},[645,2856,2857],{"class":647,"line":737},[645,2858,2859],{"class":651},"\u002F\u002F Pattern 2: Mỗi tenant một bucket riêng\n",[645,2861,2862,2864,2867,2869,2871,2874,2876,2878],{"class":647,"line":752},[645,2863,659],{"class":658},[645,2865,2866],{"class":666}," bucketName ",[645,2868,1061],{"class":662},[645,2870,1812],{"class":662},[645,2872,2873],{"class":707},"tenant-",[645,2875,1329],{"class":662},[645,2877,2829],{"class":666},[645,2879,2850],{"class":662},[645,2881,2882,2885,2887,2889,2891,2893,2895,2898,2900,2902,2905,2907,2910,2913],{"class":647,"line":760},[645,2883,2884],{"class":682},"await",[645,2886,686],{"class":666},[645,2888,689],{"class":662},[645,2890,692],{"class":666},[645,2892,689],{"class":662},[645,2894,698],{"class":697},[645,2896,2897],{"class":666},"(bucketName",[645,2899,670],{"class":662},[645,2901,663],{"class":662},[645,2903,2904],{"class":721}," public",[645,2906,725],{"class":662},[645,2908,2909],{"class":728}," false",[645,2911,2912],{"class":662}," }",[645,2914,757],{"class":666},[645,2916,2917],{"class":647,"line":767},[645,2918,764],{"emptyLinePlaceholder":763},[645,2920,2921],{"class":647,"line":773},[645,2922,2923],{"class":651},"\u002F\u002F Pattern 3: Bucket dùng chung + RLS policy cho tenant\n",[645,2925,2926],{"class":647,"line":811},[645,2927,2928],{"class":651},"\u002F\u002F Policy trong SQL:\n",[645,2930,2931],{"class":647,"line":816},[645,2932,2933],{"class":651},"\u002F\u002F CREATE POLICY \"Tenant isolation\"\n",[645,2935,2936],{"class":647,"line":822},[645,2937,2938],{"class":651},"\u002F\u002F ON storage.objects FOR SELECT\n",[645,2940,2941],{"class":647,"line":862},[645,2942,2943],{"class":651},"\u002F\u002F USING (\n",[645,2945,2946],{"class":647,"line":867},[645,2947,2948],{"class":651},"\u002F\u002F   bucket_id = 'documents'\n",[645,2950,2951],{"class":647,"line":873},[645,2952,2953],{"class":651},"\u002F\u002F   AND (storage.foldername(name))[1] = (\n",[645,2955,2956],{"class":647,"line":915},[645,2957,2958],{"class":651},"\u002F\u002F     SELECT tenant_id::text FROM profiles WHERE id = auth.uid()\n",[645,2960,2961],{"class":647,"line":928},[645,2962,2963],{"class":651},"\u002F\u002F   )\n",[645,2965,2966],{"class":647,"line":935},[645,2967,2968],{"class":651},"\u002F\u002F );\n",[436,2970,2971],{},"Pattern 1 phù hợp với hầu hết ứng dụng. Pattern 2 khi cần isolation cao. Pattern 3 linh hoạt nhất nhưng phức tạp hơn về policy.",[432,2973,2975],{"id":2974},"pricing-storage","Pricing Storage",[2977,2978,2979,2986,2992],"ul",{},[2980,2981,2982,2985],"li",{},[440,2983,2984],{},"Free",": 1 GB file storage, 5 GB băng thông",[2980,2987,2988,2991],{},[440,2989,2990],{},"Pro",": 100 GB file storage + $0.0213\u002FGB vượt, 250 GB băng thông + $0.09\u002FGB vượt",[2980,2993,2994],{},"Billing dựa trên: Storage (GB stored\u002Ftháng) + Egress (GB transferred ra ngoài)",[2996,2997],"hr",{},[427,2999,3001],{"id":3000},"realtime-websocket-subscriptions","Realtime — WebSocket Subscriptions",[432,3003,11],{"id":3004},"tổng-quan-1",[436,3006,3007],{},"Supabase Realtime cung cấp WebSocket subscriptions phân phối toàn cầu với 3 chế độ hoạt động:",[470,3009,3010,3023],{},[473,3011,3012],{},[476,3013,3014,3017,3020],{},[479,3015,3016],{},"Chế độ",[479,3018,3019],{},"Mô tả",[479,3021,3022],{},"Use case điển hình",[492,3024,3025,3038,3051],{},[476,3026,3027,3032,3035],{},[497,3028,3029],{},[440,3030,3031],{},"Broadcast",[497,3033,3034],{},"Gửi message low-latency giữa các client, ephemeral",[497,3036,3037],{},"Chat typing indicators, cursor tracking, game events",[476,3039,3040,3045,3048],{},[497,3041,3042],{},[440,3043,3044],{},"Presence",[497,3046,3047],{},"Theo dõi và đồng bộ trạng thái người dùng",[497,3049,3050],{},"Online status, active participants, collaborative editing",[476,3052,3053,3058,3061],{},[497,3054,3055],{},[440,3056,3057],{},"Postgres Changes",[497,3059,3060],{},"Lắng nghe thay đổi database realtime (CDC)",[497,3062,3063],{},"Live dashboards, real-time feeds, thông báo",[454,3065,3066,3069],{"color":456,"icon":457},[440,3067,3068],{},"Cơ chế hoạt động:"," Postgres Changes dùng PostgreSQL logical replication (WAL -- Write-Ahead Log). Khi có thay đổi trong bảng, WAL records được đọc và broadcast qua WebSocket đến subscribed clients. Filter events trên server side giúp giảm băng thông. Có thể kết hợp RLS để đảm bảo user chỉ nhận events từ dữ liệu họ được phép xem.",[432,3071,3073],{"id":3072},"channel-lifecycle-vòng-đời-của-một-channel","Channel Lifecycle — Vòng đời của một Channel",[436,3075,3076],{},"Mỗi WebSocket channel trong Supabase Realtime trải qua các trạng thái sau:",[636,3078,3081],{"className":3079,"code":3080,"language":2027},[2025],"subscribe() gọi\n     |\n     v\n  [JOINING] --> Gửi Join request đến Realtime server\n     |\n     v\n  [JOINED]  --> Channel được tạo, sẵn sàng nhận events\n     |         (Lúc này mới có thể subscribe Postgres Changes, Broadcast, Presence)\n     |\n     v\n  [SUBSCRIBED] --> Client đã subscribe và đang nhận events\n     |             (Presence.track() chỉ hoạt động ở trạng thái này)\n     |\n     v\n  [CLOSED]  --> Channel bị đóng do:\n     |         - unsubscribe() được gọi -> chuyển về [CLOSED] sạch\n     |         - Lỗi mạng, timeout -> tự động reconnect -> [JOINING]\n     |         - Server đóng channel -> tự động reconnect -> [JOINING]\n     |\n     v\n  [CLOSED] cuối cùng -> cleanup resources\n",[449,3082,3080],{"__ignoreMap":641},[636,3084,3086],{"className":638,"code":3085,"language":640,"meta":641,"style":641},"\u002F\u002F Theo dõi trạng thái channel\nconst channel = supabase.channel('room-1')\n\nchannel.subscribe((status) => {\n  switch (status) {\n    case 'SUBSCRIBED':\n      console.log('Channel ready, có thể track presence và gửi broadcast')\n      channel.track({ user: 'Alice', online_at: new Date().toISOString() })\n      break\n    case 'CHANNEL_ERROR':\n      console.error('Channel error, sẽ tự động retry...')\n      break\n    case 'TIMED_OUT':\n      console.warn('Channel timeout, đang reconnect...')\n      break\n    case 'CLOSED':\n      console.log('Channel đóng hoàn toàn')\n      break\n  }\n})\n",[449,3087,3088,3093,3120,3124,3146,3161,3176,3195,3249,3254,3267,3287,3291,3304,3324,3328,3341,3360,3364,3369],{"__ignoreMap":641},[645,3089,3090],{"class":647,"line":648},[645,3091,3092],{"class":651},"\u002F\u002F Theo dõi trạng thái channel\n",[645,3094,3095,3097,3100,3102,3104,3106,3109,3111,3113,3116,3118],{"class":647,"line":655},[645,3096,659],{"class":658},[645,3098,3099],{"class":666}," channel ",[645,3101,1061],{"class":662},[645,3103,686],{"class":666},[645,3105,689],{"class":662},[645,3107,3108],{"class":697},"channel",[645,3110,701],{"class":666},[645,3112,704],{"class":662},[645,3114,3115],{"class":707},"room-1",[645,3117,704],{"class":662},[645,3119,757],{"class":666},[645,3121,3122],{"class":647,"line":718},[645,3123,764],{"emptyLinePlaceholder":763},[645,3125,3126,3128,3130,3133,3135,3137,3140,3142,3144],{"class":647,"line":737},[645,3127,3108],{"class":666},[645,3129,689],{"class":662},[645,3131,3132],{"class":697},"subscribe",[645,3134,701],{"class":666},[645,3136,701],{"class":662},[645,3138,3139],{"class":1300},"status",[645,3141,1304],{"class":662},[645,3143,1307],{"class":658},[645,3145,715],{"class":662},[645,3147,3148,3151,3153,3155,3158],{"class":647,"line":752},[645,3149,3150],{"class":682},"  switch",[645,3152,1297],{"class":721},[645,3154,3139],{"class":666},[645,3156,3157],{"class":721},") ",[645,3159,3160],{"class":662},"{\n",[645,3162,3163,3166,3168,3171,3173],{"class":647,"line":760},[645,3164,3165],{"class":682},"    case",[645,3167,1165],{"class":662},[645,3169,3170],{"class":707},"SUBSCRIBED",[645,3172,704],{"class":662},[645,3174,3175],{"class":662},":\n",[645,3177,3178,3180,3182,3184,3186,3188,3191,3193],{"class":647,"line":767},[645,3179,1314],{"class":666},[645,3181,689],{"class":662},[645,3183,1319],{"class":697},[645,3185,701],{"class":721},[645,3187,704],{"class":662},[645,3189,3190],{"class":707},"Channel ready, có thể track presence và gửi broadcast",[645,3192,704],{"class":662},[645,3194,757],{"class":721},[645,3196,3197,3200,3202,3205,3207,3210,3213,3215,3217,3220,3222,3224,3227,3229,3231,3234,3237,3239,3242,3245,3247],{"class":647,"line":773},[645,3198,3199],{"class":666},"      channel",[645,3201,689],{"class":662},[645,3203,3204],{"class":697},"track",[645,3206,701],{"class":721},[645,3208,3209],{"class":662},"{",[645,3211,3212],{"class":721}," user",[645,3214,725],{"class":662},[645,3216,1165],{"class":662},[645,3218,3219],{"class":707},"Alice",[645,3221,704],{"class":662},[645,3223,670],{"class":662},[645,3225,3226],{"class":721}," online_at",[645,3228,725],{"class":662},[645,3230,2641],{"class":662},[645,3232,3233],{"class":697}," Date",[645,3235,3236],{"class":721},"()",[645,3238,689],{"class":662},[645,3240,3241],{"class":697},"toISOString",[645,3243,3244],{"class":721},"() ",[645,3246,676],{"class":662},[645,3248,757],{"class":721},[645,3250,3251],{"class":647,"line":811},[645,3252,3253],{"class":682},"      break\n",[645,3255,3256,3258,3260,3263,3265],{"class":647,"line":816},[645,3257,3165],{"class":682},[645,3259,1165],{"class":662},[645,3261,3262],{"class":707},"CHANNEL_ERROR",[645,3264,704],{"class":662},[645,3266,3175],{"class":662},[645,3268,3269,3271,3273,3276,3278,3280,3283,3285],{"class":647,"line":822},[645,3270,1314],{"class":666},[645,3272,689],{"class":662},[645,3274,3275],{"class":697},"error",[645,3277,701],{"class":721},[645,3279,704],{"class":662},[645,3281,3282],{"class":707},"Channel error, sẽ tự động retry...",[645,3284,704],{"class":662},[645,3286,757],{"class":721},[645,3288,3289],{"class":647,"line":862},[645,3290,3253],{"class":682},[645,3292,3293,3295,3297,3300,3302],{"class":647,"line":867},[645,3294,3165],{"class":682},[645,3296,1165],{"class":662},[645,3298,3299],{"class":707},"TIMED_OUT",[645,3301,704],{"class":662},[645,3303,3175],{"class":662},[645,3305,3306,3308,3310,3313,3315,3317,3320,3322],{"class":647,"line":873},[645,3307,1314],{"class":666},[645,3309,689],{"class":662},[645,3311,3312],{"class":697},"warn",[645,3314,701],{"class":721},[645,3316,704],{"class":662},[645,3318,3319],{"class":707},"Channel timeout, đang reconnect...",[645,3321,704],{"class":662},[645,3323,757],{"class":721},[645,3325,3326],{"class":647,"line":915},[645,3327,3253],{"class":682},[645,3329,3330,3332,3334,3337,3339],{"class":647,"line":928},[645,3331,3165],{"class":682},[645,3333,1165],{"class":662},[645,3335,3336],{"class":707},"CLOSED",[645,3338,704],{"class":662},[645,3340,3175],{"class":662},[645,3342,3343,3345,3347,3349,3351,3353,3356,3358],{"class":647,"line":935},[645,3344,1314],{"class":666},[645,3346,689],{"class":662},[645,3348,1319],{"class":697},[645,3350,701],{"class":721},[645,3352,704],{"class":662},[645,3354,3355],{"class":707},"Channel đóng hoàn toàn",[645,3357,704],{"class":662},[645,3359,757],{"class":721},[645,3361,3362],{"class":647,"line":940},[645,3363,3253],{"class":682},[645,3365,3366],{"class":647,"line":946},[645,3367,3368],{"class":662},"  }\n",[645,3370,3371,3373],{"class":647,"line":986},[645,3372,676],{"class":662},[645,3374,757],{"class":666},[454,3376,3377,3380,3381,3383],{"color":456,"icon":457},[440,3378,3379],{},"Interview point — Channel lifecycle:"," Channel tự động reconnect khi mất kết nối. Bạn chỉ cần xử lý ",[449,3382,3170],{}," để bắt đầu track presence\u002Fsend broadcast. Khi reconnect thành công, channel tự động re-subscribe tất cả listeners đã đăng ký trước đó. Không cần code reconnect logic thủ công.",[432,3385,3387],{"id":3386},"postgres-changes-cdc","Postgres Changes (CDC)",[436,3389,3390],{},"Đây là chế độ mạnh mẽ nhất -- subscribe vào các thay đổi INSERT, UPDATE, DELETE trên bảng cụ thể:",[636,3392,3394],{"className":638,"code":3393,"language":640,"meta":641,"style":641},"\u002F\u002F Tạo channel và subscribe lắng nghe INSERT\nconst channel = supabase.channel('db-changes')\n\nchannel.on('postgres_changes',\n  { event: 'INSERT', schema: 'public', table: 'messages' },\n  (payload) => {\n    console.log('New record:', payload.new)\n    console.log('Event type:', payload.eventType)\n    \u002F\u002F payload.new chứa toàn bộ row mới\n  }\n).subscribe()\n\n\u002F\u002F Lắng nghe UPDATE với filter server-side\nchannel.on('postgres_changes',\n  {\n    event: 'UPDATE',\n    schema: 'public',\n    table: 'messages',\n    filter: 'room_id=eq.123' \u002F\u002F filter ngay trên server\n  },\n  (payload) => {\n    console.log('Old data:', payload.old)  \u002F\u002F dữ liệu cũ\n    console.log('New data:', payload.new)  \u002F\u002F dữ liệu mới\n  }\n)\n\n\u002F\u002F Lắng nghe DELETE\nchannel.on('postgres_changes',\n  {\n    event: 'DELETE',\n    schema: 'public',\n    table: 'messages',\n    filter: 'room_id=eq.123'\n  },\n  (payload) => {\n    console.log('Deleted record id:', payload.old.id)\n  }\n)\n\n\u002F\u002F Lắng nghe tất cả events: dùng '*'\nchannel.on('postgres_changes',\n  { event: '*', schema: 'public', table: 'messages' },\n  (payload) => {\n    \u002F\u002F payload.eventType -> 'INSERT' | 'UPDATE' | 'DELETE'\n  }\n)\n",[449,3395,3396,3401,3426,3430,3450,3497,3511,3541,3569,3574,3578,3588,3592,3597,3615,3620,3636,3651,3666,3683,3688,3700,3732,3762,3766,3770,3774,3779,3797,3801,3816,3830,3844,3856,3860,3872,3904,3908,3912,3916,3921,3939,3980,3992,3997,4001],{"__ignoreMap":641},[645,3397,3398],{"class":647,"line":648},[645,3399,3400],{"class":651},"\u002F\u002F Tạo channel và subscribe lắng nghe INSERT\n",[645,3402,3403,3405,3407,3409,3411,3413,3415,3417,3419,3422,3424],{"class":647,"line":655},[645,3404,659],{"class":658},[645,3406,3099],{"class":666},[645,3408,1061],{"class":662},[645,3410,686],{"class":666},[645,3412,689],{"class":662},[645,3414,3108],{"class":697},[645,3416,701],{"class":666},[645,3418,704],{"class":662},[645,3420,3421],{"class":707},"db-changes",[645,3423,704],{"class":662},[645,3425,757],{"class":666},[645,3427,3428],{"class":647,"line":718},[645,3429,764],{"emptyLinePlaceholder":763},[645,3431,3432,3434,3436,3439,3441,3443,3446,3448],{"class":647,"line":737},[645,3433,3108],{"class":666},[645,3435,689],{"class":662},[645,3437,3438],{"class":697},"on",[645,3440,701],{"class":666},[645,3442,704],{"class":662},[645,3444,3445],{"class":707},"postgres_changes",[645,3447,704],{"class":662},[645,3449,1585],{"class":662},[645,3451,3452,3455,3457,3459,3461,3464,3466,3468,3471,3473,3475,3478,3480,3482,3485,3487,3489,3492,3494],{"class":647,"line":752},[645,3453,3454],{"class":662},"  {",[645,3456,1064],{"class":721},[645,3458,725],{"class":662},[645,3460,1165],{"class":662},[645,3462,3463],{"class":707},"INSERT",[645,3465,704],{"class":662},[645,3467,670],{"class":662},[645,3469,3470],{"class":721}," schema",[645,3472,725],{"class":662},[645,3474,1165],{"class":662},[645,3476,3477],{"class":707},"public",[645,3479,704],{"class":662},[645,3481,670],{"class":662},[645,3483,3484],{"class":721}," table",[645,3486,725],{"class":662},[645,3488,1165],{"class":662},[645,3490,3491],{"class":707},"messages",[645,3493,704],{"class":662},[645,3495,3496],{"class":662}," },\n",[645,3498,3499,3502,3505,3507,3509],{"class":647,"line":760},[645,3500,3501],{"class":662},"  (",[645,3503,3504],{"class":1300},"payload",[645,3506,1304],{"class":662},[645,3508,1307],{"class":658},[645,3510,715],{"class":662},[645,3512,3513,3516,3518,3520,3522,3524,3527,3529,3531,3534,3536,3539],{"class":647,"line":767},[645,3514,3515],{"class":666},"    console",[645,3517,689],{"class":662},[645,3519,1319],{"class":697},[645,3521,701],{"class":721},[645,3523,704],{"class":662},[645,3525,3526],{"class":707},"New record:",[645,3528,704],{"class":662},[645,3530,670],{"class":662},[645,3532,3533],{"class":666}," payload",[645,3535,689],{"class":662},[645,3537,3538],{"class":666},"new",[645,3540,757],{"class":721},[645,3542,3543,3545,3547,3549,3551,3553,3556,3558,3560,3562,3564,3567],{"class":647,"line":773},[645,3544,3515],{"class":666},[645,3546,689],{"class":662},[645,3548,1319],{"class":697},[645,3550,701],{"class":721},[645,3552,704],{"class":662},[645,3554,3555],{"class":707},"Event type:",[645,3557,704],{"class":662},[645,3559,670],{"class":662},[645,3561,3533],{"class":666},[645,3563,689],{"class":662},[645,3565,3566],{"class":666},"eventType",[645,3568,757],{"class":721},[645,3570,3571],{"class":647,"line":811},[645,3572,3573],{"class":651},"    \u002F\u002F payload.new chứa toàn bộ row mới\n",[645,3575,3576],{"class":647,"line":816},[645,3577,3368],{"class":662},[645,3579,3580,3582,3584,3586],{"class":647,"line":822},[645,3581,1304],{"class":666},[645,3583,689],{"class":662},[645,3585,3132],{"class":697},[645,3587,808],{"class":666},[645,3589,3590],{"class":647,"line":862},[645,3591,764],{"emptyLinePlaceholder":763},[645,3593,3594],{"class":647,"line":867},[645,3595,3596],{"class":651},"\u002F\u002F Lắng nghe UPDATE với filter server-side\n",[645,3598,3599,3601,3603,3605,3607,3609,3611,3613],{"class":647,"line":873},[645,3600,3108],{"class":666},[645,3602,689],{"class":662},[645,3604,3438],{"class":697},[645,3606,701],{"class":666},[645,3608,704],{"class":662},[645,3610,3445],{"class":707},[645,3612,704],{"class":662},[645,3614,1585],{"class":662},[645,3616,3617],{"class":647,"line":915},[645,3618,3619],{"class":662},"  {\n",[645,3621,3622,3625,3627,3629,3632,3634],{"class":647,"line":928},[645,3623,3624],{"class":721},"    event",[645,3626,725],{"class":662},[645,3628,1165],{"class":662},[645,3630,3631],{"class":707},"UPDATE",[645,3633,704],{"class":662},[645,3635,1585],{"class":662},[645,3637,3638,3641,3643,3645,3647,3649],{"class":647,"line":935},[645,3639,3640],{"class":721},"    schema",[645,3642,725],{"class":662},[645,3644,1165],{"class":662},[645,3646,3477],{"class":707},[645,3648,704],{"class":662},[645,3650,1585],{"class":662},[645,3652,3653,3656,3658,3660,3662,3664],{"class":647,"line":940},[645,3654,3655],{"class":721},"    table",[645,3657,725],{"class":662},[645,3659,1165],{"class":662},[645,3661,3491],{"class":707},[645,3663,704],{"class":662},[645,3665,1585],{"class":662},[645,3667,3668,3671,3673,3675,3678,3680],{"class":647,"line":946},[645,3669,3670],{"class":721},"    filter",[645,3672,725],{"class":662},[645,3674,1165],{"class":662},[645,3676,3677],{"class":707},"room_id=eq.123",[645,3679,704],{"class":662},[645,3681,3682],{"class":651}," \u002F\u002F filter ngay trên server\n",[645,3684,3685],{"class":647,"line":986},[645,3686,3687],{"class":662},"  },\n",[645,3689,3690,3692,3694,3696,3698],{"class":647,"line":991},[645,3691,3501],{"class":662},[645,3693,3504],{"class":1300},[645,3695,1304],{"class":662},[645,3697,1307],{"class":658},[645,3699,715],{"class":662},[645,3701,3702,3704,3706,3708,3710,3712,3715,3717,3719,3721,3723,3726,3729],{"class":647,"line":997},[645,3703,3515],{"class":666},[645,3705,689],{"class":662},[645,3707,1319],{"class":697},[645,3709,701],{"class":721},[645,3711,704],{"class":662},[645,3713,3714],{"class":707},"Old data:",[645,3716,704],{"class":662},[645,3718,670],{"class":662},[645,3720,3533],{"class":666},[645,3722,689],{"class":662},[645,3724,3725],{"class":666},"old",[645,3727,3728],{"class":721},")  ",[645,3730,3731],{"class":651},"\u002F\u002F dữ liệu cũ\n",[645,3733,3734,3736,3738,3740,3742,3744,3747,3749,3751,3753,3755,3757,3759],{"class":647,"line":1408},[645,3735,3515],{"class":666},[645,3737,689],{"class":662},[645,3739,1319],{"class":697},[645,3741,701],{"class":721},[645,3743,704],{"class":662},[645,3745,3746],{"class":707},"New data:",[645,3748,704],{"class":662},[645,3750,670],{"class":662},[645,3752,3533],{"class":666},[645,3754,689],{"class":662},[645,3756,3538],{"class":666},[645,3758,3728],{"class":721},[645,3760,3761],{"class":651},"\u002F\u002F dữ liệu mới\n",[645,3763,3764],{"class":647,"line":1430},[645,3765,3368],{"class":662},[645,3767,3768],{"class":647,"line":1435},[645,3769,757],{"class":666},[645,3771,3772],{"class":647,"line":1441},[645,3773,764],{"emptyLinePlaceholder":763},[645,3775,3776],{"class":647,"line":1461},[645,3777,3778],{"class":651},"\u002F\u002F Lắng nghe DELETE\n",[645,3780,3781,3783,3785,3787,3789,3791,3793,3795],{"class":647,"line":1478},[645,3782,3108],{"class":666},[645,3784,689],{"class":662},[645,3786,3438],{"class":697},[645,3788,701],{"class":666},[645,3790,704],{"class":662},[645,3792,3445],{"class":707},[645,3794,704],{"class":662},[645,3796,1585],{"class":662},[645,3798,3799],{"class":647,"line":1500},[645,3800,3619],{"class":662},[645,3802,3803,3805,3807,3809,3812,3814],{"class":647,"line":1506},[645,3804,3624],{"class":721},[645,3806,725],{"class":662},[645,3808,1165],{"class":662},[645,3810,3811],{"class":707},"DELETE",[645,3813,704],{"class":662},[645,3815,1585],{"class":662},[645,3817,3818,3820,3822,3824,3826,3828],{"class":647,"line":1511},[645,3819,3640],{"class":721},[645,3821,725],{"class":662},[645,3823,1165],{"class":662},[645,3825,3477],{"class":707},[645,3827,704],{"class":662},[645,3829,1585],{"class":662},[645,3831,3832,3834,3836,3838,3840,3842],{"class":647,"line":1517},[645,3833,3655],{"class":721},[645,3835,725],{"class":662},[645,3837,1165],{"class":662},[645,3839,3491],{"class":707},[645,3841,704],{"class":662},[645,3843,1585],{"class":662},[645,3845,3846,3848,3850,3852,3854],{"class":647,"line":1542},[645,3847,3670],{"class":721},[645,3849,725],{"class":662},[645,3851,1165],{"class":662},[645,3853,3677],{"class":707},[645,3855,2609],{"class":662},[645,3857,3858],{"class":647,"line":1559},[645,3859,3687],{"class":662},[645,3861,3862,3864,3866,3868,3870],{"class":647,"line":1574},[645,3863,3501],{"class":662},[645,3865,3504],{"class":1300},[645,3867,1304],{"class":662},[645,3869,1307],{"class":658},[645,3871,715],{"class":662},[645,3873,3874,3876,3878,3880,3882,3884,3887,3889,3891,3893,3895,3897,3899,3902],{"class":647,"line":1588},[645,3875,3515],{"class":666},[645,3877,689],{"class":662},[645,3879,1319],{"class":697},[645,3881,701],{"class":721},[645,3883,704],{"class":662},[645,3885,3886],{"class":707},"Deleted record id:",[645,3888,704],{"class":662},[645,3890,670],{"class":662},[645,3892,3533],{"class":666},[645,3894,689],{"class":662},[645,3896,3725],{"class":666},[645,3898,689],{"class":662},[645,3900,3901],{"class":666},"id",[645,3903,757],{"class":721},[645,3905,3906],{"class":647,"line":1601},[645,3907,3368],{"class":662},[645,3909,3910],{"class":647,"line":1640},[645,3911,757],{"class":666},[645,3913,3914],{"class":647,"line":1647},[645,3915,764],{"emptyLinePlaceholder":763},[645,3917,3918],{"class":647,"line":1652},[645,3919,3920],{"class":651},"\u002F\u002F Lắng nghe tất cả events: dùng '*'\n",[645,3922,3923,3925,3927,3929,3931,3933,3935,3937],{"class":647,"line":1658},[645,3924,3108],{"class":666},[645,3926,689],{"class":662},[645,3928,3438],{"class":697},[645,3930,701],{"class":666},[645,3932,704],{"class":662},[645,3934,3445],{"class":707},[645,3936,704],{"class":662},[645,3938,1585],{"class":662},[645,3940,3941,3943,3945,3947,3949,3952,3954,3956,3958,3960,3962,3964,3966,3968,3970,3972,3974,3976,3978],{"class":647,"line":1683},[645,3942,3454],{"class":662},[645,3944,1064],{"class":721},[645,3946,725],{"class":662},[645,3948,1165],{"class":662},[645,3950,3951],{"class":707},"*",[645,3953,704],{"class":662},[645,3955,670],{"class":662},[645,3957,3470],{"class":721},[645,3959,725],{"class":662},[645,3961,1165],{"class":662},[645,3963,3477],{"class":707},[645,3965,704],{"class":662},[645,3967,670],{"class":662},[645,3969,3484],{"class":721},[645,3971,725],{"class":662},[645,3973,1165],{"class":662},[645,3975,3491],{"class":707},[645,3977,704],{"class":662},[645,3979,3496],{"class":662},[645,3981,3982,3984,3986,3988,3990],{"class":647,"line":1700},[645,3983,3501],{"class":662},[645,3985,3504],{"class":1300},[645,3987,1304],{"class":662},[645,3989,1307],{"class":658},[645,3991,715],{"class":662},[645,3993,3994],{"class":647,"line":1737},[645,3995,3996],{"class":651},"    \u002F\u002F payload.eventType -> 'INSERT' | 'UPDATE' | 'DELETE'\n",[645,3998,3999],{"class":647,"line":1742},[645,4000,3368],{"class":662},[645,4002,4003],{"class":647,"line":1748},[645,4004,757],{"class":666},[436,4006,4007],{},[440,4008,4009],{},"Payload chi tiết cho từng event type:",[470,4011,4012,4037],{},[473,4013,4014],{},[476,4015,4016,4019,4024,4029,4034],{},[479,4017,4018],{},"Event",[479,4020,4021],{},[449,4022,4023],{},"payload.new",[479,4025,4026],{},[449,4027,4028],{},"payload.old",[479,4030,4031],{},[449,4032,4033],{},"payload.eventType",[479,4035,4036],{},"Ghi chú",[492,4038,4039,4064,4089,4116],{},[476,4040,4041,4045,4048,4054,4059],{},[497,4042,4043],{},[440,4044,3463],{},[497,4046,4047],{},"Row mới (full)",[497,4049,4050,4053],{},[449,4051,4052],{},"{}"," (object rỗng)",[497,4055,4056],{},[449,4057,4058],{},"'INSERT'",[497,4060,4061,4063],{},[449,4062,4023],{}," chứa tất cả columns của row",[476,4065,4066,4070,4073,4076,4081],{},[497,4067,4068],{},[440,4069,3631],{},[497,4071,4072],{},"Row sau khi update (full)",[497,4074,4075],{},"Row trước khi update (full)",[497,4077,4078],{},[449,4079,4080],{},"'UPDATE'",[497,4082,4083,4084,443,4086,4088],{},"So sánh ",[449,4085,3538],{},[449,4087,3725],{}," để biết column nào thay đổi",[476,4090,4091,4095,4099,4102,4107],{},[497,4092,4093],{},[440,4094,3811],{},[497,4096,4097,4053],{},[449,4098,4052],{},[497,4100,4101],{},"Row bị xóa",[497,4103,4104],{},[449,4105,4106],{},"'DELETE'",[497,4108,4109,4110,4112,4113,4115],{},"Trong RLS mode, ",[449,4111,4028],{}," chỉ chứa ",[449,4114,3901],{}," (primary key)",[476,4117,4118,4123,4126,4128,4137],{},[497,4119,4120,4122],{},[449,4121,3951],{}," (tất cả)",[497,4124,4125],{},"Tùy event",[497,4127,4125],{},[497,4129,4130,4132,4133,4132,4135],{},[449,4131,4058],{}," \u002F ",[449,4134,4080],{},[449,4136,4106],{},[497,4138,4139],{},"Dùng chung một handler cho mọi thay đổi",[454,4141,4143,4146,4147,4149],{"color":2032,"icon":4142},"i-lucide-shield-alert",[440,4144,4145],{},"Lưu ý về DELETE + RLS:"," Khi RLS được enable, ",[449,4148,4028],{}," trong DELETE event chỉ chứa primary key (id). Điều này là do RLS filter được áp dụng ở database level, và sau khi row bị xóa, hệ thống không thể đọc full data của row đó (vì nó không còn tồn tại để kiểm tra RLS policy).",[4151,4152,4154],"h4",{"id":4153},"server-side-filtering","Server-Side Filtering",[436,4156,4157],{},"Filter được thực thi trên server, giúp giảm đáng kể băng thông:",[636,4159,4161],{"className":638,"code":4160,"language":640,"meta":641,"style":641},"\u002F\u002F Filter đơn\nfilter: 'room_id=eq.123'\n\n\u002F\u002F Filter kết hợp\nfilter: 'room_id=eq.123 AND user_id=eq.456'\n\n\u002F\u002F Filter với nhiều điều kiện\nfilter: 'status=in.(active,pending)'\n\n\u002F\u002F Filter IS NULL\nfilter: 'deleted_at=is.null'\n",[449,4162,4163,4168,4181,4185,4190,4203,4207,4212,4225,4229,4234],{"__ignoreMap":641},[645,4164,4165],{"class":647,"line":648},[645,4166,4167],{"class":651},"\u002F\u002F Filter đơn\n",[645,4169,4170,4173,4175,4177,4179],{"class":647,"line":655},[645,4171,4172],{"class":1941},"filter",[645,4174,725],{"class":662},[645,4176,1165],{"class":662},[645,4178,3677],{"class":707},[645,4180,2609],{"class":662},[645,4182,4183],{"class":647,"line":718},[645,4184,764],{"emptyLinePlaceholder":763},[645,4186,4187],{"class":647,"line":737},[645,4188,4189],{"class":651},"\u002F\u002F Filter kết hợp\n",[645,4191,4192,4194,4196,4198,4201],{"class":647,"line":752},[645,4193,4172],{"class":1941},[645,4195,725],{"class":662},[645,4197,1165],{"class":662},[645,4199,4200],{"class":707},"room_id=eq.123 AND user_id=eq.456",[645,4202,2609],{"class":662},[645,4204,4205],{"class":647,"line":760},[645,4206,764],{"emptyLinePlaceholder":763},[645,4208,4209],{"class":647,"line":767},[645,4210,4211],{"class":651},"\u002F\u002F Filter với nhiều điều kiện\n",[645,4213,4214,4216,4218,4220,4223],{"class":647,"line":773},[645,4215,4172],{"class":1941},[645,4217,725],{"class":662},[645,4219,1165],{"class":662},[645,4221,4222],{"class":707},"status=in.(active,pending)",[645,4224,2609],{"class":662},[645,4226,4227],{"class":647,"line":811},[645,4228,764],{"emptyLinePlaceholder":763},[645,4230,4231],{"class":647,"line":816},[645,4232,4233],{"class":651},"\u002F\u002F Filter IS NULL\n",[645,4235,4236,4238,4240,4242,4245],{"class":647,"line":822},[645,4237,4172],{"class":1941},[645,4239,725],{"class":662},[645,4241,1165],{"class":662},[645,4243,4244],{"class":707},"deleted_at=is.null",[645,4246,2609],{"class":662},[436,4248,4249],{},"Không gửi toàn bộ data về client rồi lọc -- server chỉ gửi những events khớp filter.",[4151,4251,4253],{"id":4252},"enable-realtime-cho-bảng","Enable Realtime cho bảng",[436,4255,4256],{},"Để Postgres Changes hoạt động, cần enable Realtime cho bảng:",[636,4258,4260],{"className":2313,"code":4259,"language":2315,"meta":641,"style":641},"-- Enable cho một bảng cụ thể\nALTER PUBLICATION supabase_realtime ADD TABLE messages;\n\n-- Enable cho nhiều bảng cùng lúc\nALTER PUBLICATION supabase_realtime ADD TABLE messages, notifications;\n\n-- Enable cho toàn bộ schema (ít dùng vì gây nhiễu)\nALTER PUBLICATION supabase_realtime ADD TABLE public.*;\n\n-- Kiểm tra bảng nào đã được enable\nSELECT * FROM pg_publication_tables\nWHERE pubname = 'supabase_realtime';\n\n-- Kiểm tra trạng thái publication\nSELECT * FROM pg_publication\nWHERE pubname = 'supabase_realtime';\n\n-- Xóa bảng khỏi publication (disable realtime)\nALTER PUBLICATION supabase_realtime DROP TABLE old_table;\n",[449,4261,4262,4267,4272,4276,4281,4286,4290,4295,4300,4304,4309,4314,4319,4323,4328,4333,4337,4341,4346],{"__ignoreMap":641},[645,4263,4264],{"class":647,"line":648},[645,4265,4266],{},"-- Enable cho một bảng cụ thể\n",[645,4268,4269],{"class":647,"line":655},[645,4270,4271],{},"ALTER PUBLICATION supabase_realtime ADD TABLE messages;\n",[645,4273,4274],{"class":647,"line":718},[645,4275,764],{"emptyLinePlaceholder":763},[645,4277,4278],{"class":647,"line":737},[645,4279,4280],{},"-- Enable cho nhiều bảng cùng lúc\n",[645,4282,4283],{"class":647,"line":752},[645,4284,4285],{},"ALTER PUBLICATION supabase_realtime ADD TABLE messages, notifications;\n",[645,4287,4288],{"class":647,"line":760},[645,4289,764],{"emptyLinePlaceholder":763},[645,4291,4292],{"class":647,"line":767},[645,4293,4294],{},"-- Enable cho toàn bộ schema (ít dùng vì gây nhiễu)\n",[645,4296,4297],{"class":647,"line":773},[645,4298,4299],{},"ALTER PUBLICATION supabase_realtime ADD TABLE public.*;\n",[645,4301,4302],{"class":647,"line":811},[645,4303,764],{"emptyLinePlaceholder":763},[645,4305,4306],{"class":647,"line":816},[645,4307,4308],{},"-- Kiểm tra bảng nào đã được enable\n",[645,4310,4311],{"class":647,"line":822},[645,4312,4313],{},"SELECT * FROM pg_publication_tables\n",[645,4315,4316],{"class":647,"line":862},[645,4317,4318],{},"WHERE pubname = 'supabase_realtime';\n",[645,4320,4321],{"class":647,"line":867},[645,4322,764],{"emptyLinePlaceholder":763},[645,4324,4325],{"class":647,"line":873},[645,4326,4327],{},"-- Kiểm tra trạng thái publication\n",[645,4329,4330],{"class":647,"line":915},[645,4331,4332],{},"SELECT * FROM pg_publication\n",[645,4334,4335],{"class":647,"line":928},[645,4336,4318],{},[645,4338,4339],{"class":647,"line":935},[645,4340,764],{"emptyLinePlaceholder":763},[645,4342,4343],{"class":647,"line":940},[645,4344,4345],{},"-- Xóa bảng khỏi publication (disable realtime)\n",[645,4347,4348],{"class":647,"line":946},[645,4349,4350],{},"ALTER PUBLICATION supabase_realtime DROP TABLE old_table;\n",[454,4352,4353,4356,4357,4360],{"color":456,"icon":457},[440,4354,4355],{},"Interview point — Realtime Extension:"," Supabase Realtime dùng PostgreSQL publication ",[449,4358,4359],{},"supabase_realtime"," để xác định bảng nào được replicate qua WAL. Chỉ các bảng trong publication này mới có thể subscribe Postgres Changes. Việc này giúp kiểm soát chính xác bảng nào được expose qua realtime, tránh lộ dữ liệu nhạy cảm.",[432,4362,3031],{"id":4363},"broadcast",[436,4365,4366],{},"Broadcast gửi message tạm thời (ephemeral) giữa các client. Message không được lưu trữ -- nếu client offline, họ sẽ bỏ lỡ message. Phù hợp cho dữ liệu không cần persistence:",[636,4368,4370],{"className":638,"code":4369,"language":640,"meta":641,"style":641},"const channel = supabase.channel('room-1', {\n  config: {\n    broadcast: { self: false } \u002F\u002F không nhận lại message của chính mình\n  }\n})\n\n\u002F\u002F Gửi message\nchannel.send({\n  type: 'broadcast',\n  event: 'cursor-move',\n  payload: { x: 100, y: 200, userId: 'user-123' }\n})\n\n\u002F\u002F Gửi typing indicator\nchannel.send({\n  type: 'broadcast',\n  event: 'typing',\n  payload: { userId: 'user-123', isTyping: true }\n})\n\n\u002F\u002F Nhận message\nchannel.on('broadcast', { event: 'cursor-move' }, (payload) => {\n  console.log('Cursor moved:', payload)\n})\n\nchannel.on('broadcast', { event: 'typing' }, (payload) => {\n  showTypingIndicator(payload.userId, payload.isTyping)\n})\n\nchannel.subscribe()\n",[449,4371,4372,4398,4407,4428,4432,4438,4442,4447,4460,4475,4491,4533,4539,4543,4548,4560,4574,4589,4618,4624,4628,4633,4676,4700,4706,4710,4752,4776,4782,4786],{"__ignoreMap":641},[645,4373,4374,4376,4378,4380,4382,4384,4386,4388,4390,4392,4394,4396],{"class":647,"line":648},[645,4375,659],{"class":658},[645,4377,3099],{"class":666},[645,4379,1061],{"class":662},[645,4381,686],{"class":666},[645,4383,689],{"class":662},[645,4385,3108],{"class":697},[645,4387,701],{"class":666},[645,4389,704],{"class":662},[645,4391,3115],{"class":707},[645,4393,704],{"class":662},[645,4395,670],{"class":662},[645,4397,715],{"class":662},[645,4399,4400,4403,4405],{"class":647,"line":655},[645,4401,4402],{"class":721},"  config",[645,4404,725],{"class":662},[645,4406,715],{"class":662},[645,4408,4409,4412,4414,4416,4419,4421,4423,4425],{"class":647,"line":718},[645,4410,4411],{"class":721},"    broadcast",[645,4413,725],{"class":662},[645,4415,663],{"class":662},[645,4417,4418],{"class":721}," self",[645,4420,725],{"class":662},[645,4422,2909],{"class":728},[645,4424,2912],{"class":662},[645,4426,4427],{"class":651}," \u002F\u002F không nhận lại message của chính mình\n",[645,4429,4430],{"class":647,"line":737},[645,4431,3368],{"class":662},[645,4433,4434,4436],{"class":647,"line":752},[645,4435,676],{"class":662},[645,4437,757],{"class":666},[645,4439,4440],{"class":647,"line":760},[645,4441,764],{"emptyLinePlaceholder":763},[645,4443,4444],{"class":647,"line":767},[645,4445,4446],{"class":651},"\u002F\u002F Gửi message\n",[645,4448,4449,4451,4453,4456,4458],{"class":647,"line":773},[645,4450,3108],{"class":666},[645,4452,689],{"class":662},[645,4454,4455],{"class":697},"send",[645,4457,701],{"class":666},[645,4459,3160],{"class":662},[645,4461,4462,4465,4467,4469,4471,4473],{"class":647,"line":811},[645,4463,4464],{"class":721},"  type",[645,4466,725],{"class":662},[645,4468,1165],{"class":662},[645,4470,4363],{"class":707},[645,4472,704],{"class":662},[645,4474,1585],{"class":662},[645,4476,4477,4480,4482,4484,4487,4489],{"class":647,"line":816},[645,4478,4479],{"class":721},"  event",[645,4481,725],{"class":662},[645,4483,1165],{"class":662},[645,4485,4486],{"class":707},"cursor-move",[645,4488,704],{"class":662},[645,4490,1585],{"class":662},[645,4492,4493,4496,4498,4500,4503,4505,4507,4509,4512,4514,4517,4519,4522,4524,4526,4529,4531],{"class":647,"line":822},[645,4494,4495],{"class":721},"  payload",[645,4497,725],{"class":662},[645,4499,663],{"class":662},[645,4501,4502],{"class":721}," x",[645,4504,725],{"class":662},[645,4506,1582],{"class":745},[645,4508,670],{"class":662},[645,4510,4511],{"class":721}," y",[645,4513,725],{"class":662},[645,4515,4516],{"class":745}," 200",[645,4518,670],{"class":662},[645,4520,4521],{"class":721}," userId",[645,4523,725],{"class":662},[645,4525,1165],{"class":662},[645,4527,4528],{"class":707},"user-123",[645,4530,704],{"class":662},[645,4532,1637],{"class":662},[645,4534,4535,4537],{"class":647,"line":862},[645,4536,676],{"class":662},[645,4538,757],{"class":666},[645,4540,4541],{"class":647,"line":867},[645,4542,764],{"emptyLinePlaceholder":763},[645,4544,4545],{"class":647,"line":873},[645,4546,4547],{"class":651},"\u002F\u002F Gửi typing indicator\n",[645,4549,4550,4552,4554,4556,4558],{"class":647,"line":915},[645,4551,3108],{"class":666},[645,4553,689],{"class":662},[645,4555,4455],{"class":697},[645,4557,701],{"class":666},[645,4559,3160],{"class":662},[645,4561,4562,4564,4566,4568,4570,4572],{"class":647,"line":928},[645,4563,4464],{"class":721},[645,4565,725],{"class":662},[645,4567,1165],{"class":662},[645,4569,4363],{"class":707},[645,4571,704],{"class":662},[645,4573,1585],{"class":662},[645,4575,4576,4578,4580,4582,4585,4587],{"class":647,"line":935},[645,4577,4479],{"class":721},[645,4579,725],{"class":662},[645,4581,1165],{"class":662},[645,4583,4584],{"class":707},"typing",[645,4586,704],{"class":662},[645,4588,1585],{"class":662},[645,4590,4591,4593,4595,4597,4599,4601,4603,4605,4607,4609,4612,4614,4616],{"class":647,"line":940},[645,4592,4495],{"class":721},[645,4594,725],{"class":662},[645,4596,663],{"class":662},[645,4598,4521],{"class":721},[645,4600,725],{"class":662},[645,4602,1165],{"class":662},[645,4604,4528],{"class":707},[645,4606,704],{"class":662},[645,4608,670],{"class":662},[645,4610,4611],{"class":721}," isTyping",[645,4613,725],{"class":662},[645,4615,729],{"class":728},[645,4617,1637],{"class":662},[645,4619,4620,4622],{"class":647,"line":946},[645,4621,676],{"class":662},[645,4623,757],{"class":666},[645,4625,4626],{"class":647,"line":986},[645,4627,764],{"emptyLinePlaceholder":763},[645,4629,4630],{"class":647,"line":991},[645,4631,4632],{"class":651},"\u002F\u002F Nhận message\n",[645,4634,4635,4637,4639,4641,4643,4645,4647,4649,4651,4653,4655,4657,4659,4661,4663,4666,4668,4670,4672,4674],{"class":647,"line":997},[645,4636,3108],{"class":666},[645,4638,689],{"class":662},[645,4640,3438],{"class":697},[645,4642,701],{"class":666},[645,4644,704],{"class":662},[645,4646,4363],{"class":707},[645,4648,704],{"class":662},[645,4650,670],{"class":662},[645,4652,663],{"class":662},[645,4654,1064],{"class":721},[645,4656,725],{"class":662},[645,4658,1165],{"class":662},[645,4660,4486],{"class":707},[645,4662,704],{"class":662},[645,4664,4665],{"class":662}," },",[645,4667,1297],{"class":662},[645,4669,3504],{"class":1300},[645,4671,1304],{"class":662},[645,4673,1307],{"class":658},[645,4675,715],{"class":662},[645,4677,4678,4681,4683,4685,4687,4689,4692,4694,4696,4698],{"class":647,"line":1408},[645,4679,4680],{"class":666},"  console",[645,4682,689],{"class":662},[645,4684,1319],{"class":697},[645,4686,701],{"class":721},[645,4688,704],{"class":662},[645,4690,4691],{"class":707},"Cursor moved:",[645,4693,704],{"class":662},[645,4695,670],{"class":662},[645,4697,3533],{"class":666},[645,4699,757],{"class":721},[645,4701,4702,4704],{"class":647,"line":1430},[645,4703,676],{"class":662},[645,4705,757],{"class":666},[645,4707,4708],{"class":647,"line":1435},[645,4709,764],{"emptyLinePlaceholder":763},[645,4711,4712,4714,4716,4718,4720,4722,4724,4726,4728,4730,4732,4734,4736,4738,4740,4742,4744,4746,4748,4750],{"class":647,"line":1441},[645,4713,3108],{"class":666},[645,4715,689],{"class":662},[645,4717,3438],{"class":697},[645,4719,701],{"class":666},[645,4721,704],{"class":662},[645,4723,4363],{"class":707},[645,4725,704],{"class":662},[645,4727,670],{"class":662},[645,4729,663],{"class":662},[645,4731,1064],{"class":721},[645,4733,725],{"class":662},[645,4735,1165],{"class":662},[645,4737,4584],{"class":707},[645,4739,704],{"class":662},[645,4741,4665],{"class":662},[645,4743,1297],{"class":662},[645,4745,3504],{"class":1300},[645,4747,1304],{"class":662},[645,4749,1307],{"class":658},[645,4751,715],{"class":662},[645,4753,4754,4757,4759,4761,4763,4765,4767,4769,4771,4774],{"class":647,"line":1461},[645,4755,4756],{"class":697},"  showTypingIndicator",[645,4758,701],{"class":721},[645,4760,3504],{"class":666},[645,4762,689],{"class":662},[645,4764,1138],{"class":666},[645,4766,670],{"class":662},[645,4768,3533],{"class":666},[645,4770,689],{"class":662},[645,4772,4773],{"class":666},"isTyping",[645,4775,757],{"class":721},[645,4777,4778,4780],{"class":647,"line":1478},[645,4779,676],{"class":662},[645,4781,757],{"class":666},[645,4783,4784],{"class":647,"line":1500},[645,4785,764],{"emptyLinePlaceholder":763},[645,4787,4788,4790,4792,4794],{"class":647,"line":1506},[645,4789,3108],{"class":666},[645,4791,689],{"class":662},[645,4793,3132],{"class":697},[645,4795,808],{"class":666},[432,4797,3044],{"id":4798},"presence",[436,4800,4801],{},"Presence tự động theo dõi và đồng bộ trạng thái người dùng. Khi một user disconnect (đóng tab, mất mạng), Presence tự động timeout và xóa user đó khỏi danh sách:",[636,4803,4805],{"className":638,"code":4804,"language":640,"meta":641,"style":641},"const channel = supabase.channel('room-1')\n\n\u002F\u002F Lắng nghe thay đổi presence\nchannel.on('presence', { event: 'sync' }, () => {\n  const state = channel.presenceState()\n  console.log('Online users:', Object.keys(state).length)\n  \u002F\u002F state có dạng:\n  \u002F\u002F {\n  \u002F\u002F   'user-123': [{ online_at: '2024-01-01T00:00:00Z', name: 'Vinh' }],\n  \u002F\u002F   'user-456': [{ online_at: '2024-01-01T00:00:01Z', name: 'Alice' }]\n  \u002F\u002F }\n})\n\nchannel.on('presence', { event: 'join' }, ({ key, newPresences }) => {\n  console.log('User joined:', key, newPresences)\n  \u002F\u002F key: 'user-123'\n  \u002F\u002F newPresences: [{ online_at: '...', name: 'Vinh' }]\n})\n\nchannel.on('presence', { event: 'leave' }, ({ key, leftPresences }) => {\n  console.log('User left:', key)\n  \u002F\u002F key: 'user-123'\n  \u002F\u002F leftPresences: [{ online_at: '...', name: 'Vinh' }]\n})\n\n\u002F\u002F Subscribe và track presence\nchannel.subscribe(async (status) => {\n  if (status === 'SUBSCRIBED') {\n    \u002F\u002F Bắt đầu track — server sẽ sync state cho tất cả clients khác\n    await channel.track({\n      user_id: 'user-123',\n      name: 'Vinh',\n      online_at: new Date().toISOString(),\n      cursor: { x: 0, y: 0 }\n    })\n  }\n})\n\n\u002F\u002F Cập nhật presence state (ví dụ: khi di chuyển cursor)\ndocument.addEventListener('mousemove', (e) => {\n  channel.track({\n    user_id: 'user-123',\n    name: 'Vinh',\n    online_at: new Date().toISOString(),\n    cursor: { x: e.clientX, y: e.clientY }\n  })\n})\n",[449,4806,4807,4831,4835,4840,4880,4900,4941,4946,4951,4956,4961,4966,4972,4976,5027,5054,5059,5064,5070,5074,5122,5145,5149,5154,5160,5164,5169,5192,5214,5219,5234,5249,5265,5286,5311,5318,5322,5328,5332,5337,5369,5382,5397,5412,5433,5469,5475],{"__ignoreMap":641},[645,4808,4809,4811,4813,4815,4817,4819,4821,4823,4825,4827,4829],{"class":647,"line":648},[645,4810,659],{"class":658},[645,4812,3099],{"class":666},[645,4814,1061],{"class":662},[645,4816,686],{"class":666},[645,4818,689],{"class":662},[645,4820,3108],{"class":697},[645,4822,701],{"class":666},[645,4824,704],{"class":662},[645,4826,3115],{"class":707},[645,4828,704],{"class":662},[645,4830,757],{"class":666},[645,4832,4833],{"class":647,"line":655},[645,4834,764],{"emptyLinePlaceholder":763},[645,4836,4837],{"class":647,"line":718},[645,4838,4839],{"class":651},"\u002F\u002F Lắng nghe thay đổi presence\n",[645,4841,4842,4844,4846,4848,4850,4852,4854,4856,4858,4860,4862,4864,4866,4869,4871,4873,4876,4878],{"class":647,"line":737},[645,4843,3108],{"class":666},[645,4845,689],{"class":662},[645,4847,3438],{"class":697},[645,4849,701],{"class":666},[645,4851,704],{"class":662},[645,4853,4798],{"class":707},[645,4855,704],{"class":662},[645,4857,670],{"class":662},[645,4859,663],{"class":662},[645,4861,1064],{"class":721},[645,4863,725],{"class":662},[645,4865,1165],{"class":662},[645,4867,4868],{"class":707},"sync",[645,4870,704],{"class":662},[645,4872,4665],{"class":662},[645,4874,4875],{"class":662}," ()",[645,4877,1307],{"class":658},[645,4879,715],{"class":662},[645,4881,4882,4885,4888,4890,4893,4895,4898],{"class":647,"line":752},[645,4883,4884],{"class":658},"  const",[645,4886,4887],{"class":666}," state",[645,4889,679],{"class":662},[645,4891,4892],{"class":666}," channel",[645,4894,689],{"class":662},[645,4896,4897],{"class":697},"presenceState",[645,4899,808],{"class":721},[645,4901,4902,4904,4906,4908,4910,4912,4915,4917,4919,4922,4924,4927,4929,4932,4934,4936,4939],{"class":647,"line":760},[645,4903,4680],{"class":666},[645,4905,689],{"class":662},[645,4907,1319],{"class":697},[645,4909,701],{"class":721},[645,4911,704],{"class":662},[645,4913,4914],{"class":707},"Online users:",[645,4916,704],{"class":662},[645,4918,670],{"class":662},[645,4920,4921],{"class":666}," Object",[645,4923,689],{"class":662},[645,4925,4926],{"class":697},"keys",[645,4928,701],{"class":721},[645,4930,4931],{"class":666},"state",[645,4933,1304],{"class":721},[645,4935,689],{"class":662},[645,4937,4938],{"class":666},"length",[645,4940,757],{"class":721},[645,4942,4943],{"class":647,"line":767},[645,4944,4945],{"class":651},"  \u002F\u002F state có dạng:\n",[645,4947,4948],{"class":647,"line":773},[645,4949,4950],{"class":651},"  \u002F\u002F {\n",[645,4952,4953],{"class":647,"line":811},[645,4954,4955],{"class":651},"  \u002F\u002F   'user-123': [{ online_at: '2024-01-01T00:00:00Z', name: 'Vinh' }],\n",[645,4957,4958],{"class":647,"line":816},[645,4959,4960],{"class":651},"  \u002F\u002F   'user-456': [{ online_at: '2024-01-01T00:00:01Z', name: 'Alice' }]\n",[645,4962,4963],{"class":647,"line":822},[645,4964,4965],{"class":651},"  \u002F\u002F }\n",[645,4967,4968,4970],{"class":647,"line":862},[645,4969,676],{"class":662},[645,4971,757],{"class":666},[645,4973,4974],{"class":647,"line":867},[645,4975,764],{"emptyLinePlaceholder":763},[645,4977,4978,4980,4982,4984,4986,4988,4990,4992,4994,4996,4998,5000,5002,5005,5007,5009,5012,5015,5017,5020,5023,5025],{"class":647,"line":873},[645,4979,3108],{"class":666},[645,4981,689],{"class":662},[645,4983,3438],{"class":697},[645,4985,701],{"class":666},[645,4987,704],{"class":662},[645,4989,4798],{"class":707},[645,4991,704],{"class":662},[645,4993,670],{"class":662},[645,4995,663],{"class":662},[645,4997,1064],{"class":721},[645,4999,725],{"class":662},[645,5001,1165],{"class":662},[645,5003,5004],{"class":707},"join",[645,5006,704],{"class":662},[645,5008,4665],{"class":662},[645,5010,5011],{"class":662}," ({",[645,5013,5014],{"class":1300}," key",[645,5016,670],{"class":662},[645,5018,5019],{"class":1300}," newPresences",[645,5021,5022],{"class":662}," })",[645,5024,1307],{"class":658},[645,5026,715],{"class":662},[645,5028,5029,5031,5033,5035,5037,5039,5042,5044,5046,5048,5050,5052],{"class":647,"line":915},[645,5030,4680],{"class":666},[645,5032,689],{"class":662},[645,5034,1319],{"class":697},[645,5036,701],{"class":721},[645,5038,704],{"class":662},[645,5040,5041],{"class":707},"User joined:",[645,5043,704],{"class":662},[645,5045,670],{"class":662},[645,5047,5014],{"class":666},[645,5049,670],{"class":662},[645,5051,5019],{"class":666},[645,5053,757],{"class":721},[645,5055,5056],{"class":647,"line":928},[645,5057,5058],{"class":651},"  \u002F\u002F key: 'user-123'\n",[645,5060,5061],{"class":647,"line":935},[645,5062,5063],{"class":651},"  \u002F\u002F newPresences: [{ online_at: '...', name: 'Vinh' }]\n",[645,5065,5066,5068],{"class":647,"line":940},[645,5067,676],{"class":662},[645,5069,757],{"class":666},[645,5071,5072],{"class":647,"line":946},[645,5073,764],{"emptyLinePlaceholder":763},[645,5075,5076,5078,5080,5082,5084,5086,5088,5090,5092,5094,5096,5098,5100,5103,5105,5107,5109,5111,5113,5116,5118,5120],{"class":647,"line":986},[645,5077,3108],{"class":666},[645,5079,689],{"class":662},[645,5081,3438],{"class":697},[645,5083,701],{"class":666},[645,5085,704],{"class":662},[645,5087,4798],{"class":707},[645,5089,704],{"class":662},[645,5091,670],{"class":662},[645,5093,663],{"class":662},[645,5095,1064],{"class":721},[645,5097,725],{"class":662},[645,5099,1165],{"class":662},[645,5101,5102],{"class":707},"leave",[645,5104,704],{"class":662},[645,5106,4665],{"class":662},[645,5108,5011],{"class":662},[645,5110,5014],{"class":1300},[645,5112,670],{"class":662},[645,5114,5115],{"class":1300}," leftPresences",[645,5117,5022],{"class":662},[645,5119,1307],{"class":658},[645,5121,715],{"class":662},[645,5123,5124,5126,5128,5130,5132,5134,5137,5139,5141,5143],{"class":647,"line":991},[645,5125,4680],{"class":666},[645,5127,689],{"class":662},[645,5129,1319],{"class":697},[645,5131,701],{"class":721},[645,5133,704],{"class":662},[645,5135,5136],{"class":707},"User left:",[645,5138,704],{"class":662},[645,5140,670],{"class":662},[645,5142,5014],{"class":666},[645,5144,757],{"class":721},[645,5146,5147],{"class":647,"line":997},[645,5148,5058],{"class":651},[645,5150,5151],{"class":647,"line":1408},[645,5152,5153],{"class":651},"  \u002F\u002F leftPresences: [{ online_at: '...', name: 'Vinh' }]\n",[645,5155,5156,5158],{"class":647,"line":1430},[645,5157,676],{"class":662},[645,5159,757],{"class":666},[645,5161,5162],{"class":647,"line":1435},[645,5163,764],{"emptyLinePlaceholder":763},[645,5165,5166],{"class":647,"line":1441},[645,5167,5168],{"class":651},"\u002F\u002F Subscribe và track presence\n",[645,5170,5171,5173,5175,5177,5179,5182,5184,5186,5188,5190],{"class":647,"line":1461},[645,5172,3108],{"class":666},[645,5174,689],{"class":662},[645,5176,3132],{"class":697},[645,5178,701],{"class":666},[645,5180,5181],{"class":658},"async",[645,5183,1297],{"class":662},[645,5185,3139],{"class":1300},[645,5187,1304],{"class":662},[645,5189,1307],{"class":658},[645,5191,715],{"class":662},[645,5193,5194,5197,5199,5201,5204,5206,5208,5210,5212],{"class":647,"line":1478},[645,5195,5196],{"class":682},"  if",[645,5198,1297],{"class":721},[645,5200,3139],{"class":666},[645,5202,5203],{"class":662}," ===",[645,5205,1165],{"class":662},[645,5207,3170],{"class":707},[645,5209,704],{"class":662},[645,5211,3157],{"class":721},[645,5213,3160],{"class":662},[645,5215,5216],{"class":647,"line":1500},[645,5217,5218],{"class":651},"    \u002F\u002F Bắt đầu track — server sẽ sync state cho tất cả clients khác\n",[645,5220,5221,5224,5226,5228,5230,5232],{"class":647,"line":1506},[645,5222,5223],{"class":682},"    await",[645,5225,4892],{"class":666},[645,5227,689],{"class":662},[645,5229,3204],{"class":697},[645,5231,701],{"class":721},[645,5233,3160],{"class":662},[645,5235,5236,5239,5241,5243,5245,5247],{"class":647,"line":1511},[645,5237,5238],{"class":721},"      user_id",[645,5240,725],{"class":662},[645,5242,1165],{"class":662},[645,5244,4528],{"class":707},[645,5246,704],{"class":662},[645,5248,1585],{"class":662},[645,5250,5251,5254,5256,5258,5261,5263],{"class":647,"line":1517},[645,5252,5253],{"class":721},"      name",[645,5255,725],{"class":662},[645,5257,1165],{"class":662},[645,5259,5260],{"class":707},"Vinh",[645,5262,704],{"class":662},[645,5264,1585],{"class":662},[645,5266,5267,5270,5272,5274,5276,5278,5280,5282,5284],{"class":647,"line":1542},[645,5268,5269],{"class":721},"      online_at",[645,5271,725],{"class":662},[645,5273,2641],{"class":662},[645,5275,3233],{"class":697},[645,5277,3236],{"class":721},[645,5279,689],{"class":662},[645,5281,3241],{"class":697},[645,5283,3236],{"class":721},[645,5285,1585],{"class":662},[645,5287,5288,5291,5293,5295,5297,5299,5301,5303,5305,5307,5309],{"class":647,"line":1559},[645,5289,5290],{"class":721},"      cursor",[645,5292,725],{"class":662},[645,5294,663],{"class":662},[645,5296,4502],{"class":721},[645,5298,725],{"class":662},[645,5300,1596],{"class":745},[645,5302,670],{"class":662},[645,5304,4511],{"class":721},[645,5306,725],{"class":662},[645,5308,1596],{"class":745},[645,5310,1637],{"class":662},[645,5312,5313,5316],{"class":647,"line":1574},[645,5314,5315],{"class":662},"    }",[645,5317,757],{"class":721},[645,5319,5320],{"class":647,"line":1588},[645,5321,3368],{"class":662},[645,5323,5324,5326],{"class":647,"line":1601},[645,5325,676],{"class":662},[645,5327,757],{"class":666},[645,5329,5330],{"class":647,"line":1640},[645,5331,764],{"emptyLinePlaceholder":763},[645,5333,5334],{"class":647,"line":1647},[645,5335,5336],{"class":651},"\u002F\u002F Cập nhật presence state (ví dụ: khi di chuyển cursor)\n",[645,5338,5339,5342,5344,5347,5349,5351,5354,5356,5358,5360,5363,5365,5367],{"class":647,"line":1652},[645,5340,5341],{"class":666},"document",[645,5343,689],{"class":662},[645,5345,5346],{"class":697},"addEventListener",[645,5348,701],{"class":666},[645,5350,704],{"class":662},[645,5352,5353],{"class":707},"mousemove",[645,5355,704],{"class":662},[645,5357,670],{"class":662},[645,5359,1297],{"class":662},[645,5361,5362],{"class":1300},"e",[645,5364,1304],{"class":662},[645,5366,1307],{"class":658},[645,5368,715],{"class":662},[645,5370,5371,5374,5376,5378,5380],{"class":647,"line":1658},[645,5372,5373],{"class":666},"  channel",[645,5375,689],{"class":662},[645,5377,3204],{"class":697},[645,5379,701],{"class":721},[645,5381,3160],{"class":662},[645,5383,5384,5387,5389,5391,5393,5395],{"class":647,"line":1683},[645,5385,5386],{"class":721},"    user_id",[645,5388,725],{"class":662},[645,5390,1165],{"class":662},[645,5392,4528],{"class":707},[645,5394,704],{"class":662},[645,5396,1585],{"class":662},[645,5398,5399,5402,5404,5406,5408,5410],{"class":647,"line":1700},[645,5400,5401],{"class":721},"    name",[645,5403,725],{"class":662},[645,5405,1165],{"class":662},[645,5407,5260],{"class":707},[645,5409,704],{"class":662},[645,5411,1585],{"class":662},[645,5413,5414,5417,5419,5421,5423,5425,5427,5429,5431],{"class":647,"line":1737},[645,5415,5416],{"class":721},"    online_at",[645,5418,725],{"class":662},[645,5420,2641],{"class":662},[645,5422,3233],{"class":697},[645,5424,3236],{"class":721},[645,5426,689],{"class":662},[645,5428,3241],{"class":697},[645,5430,3236],{"class":721},[645,5432,1585],{"class":662},[645,5434,5435,5438,5440,5442,5444,5446,5449,5451,5454,5456,5458,5460,5462,5464,5467],{"class":647,"line":1742},[645,5436,5437],{"class":721},"    cursor",[645,5439,725],{"class":662},[645,5441,663],{"class":662},[645,5443,4502],{"class":721},[645,5445,725],{"class":662},[645,5447,5448],{"class":666}," e",[645,5450,689],{"class":662},[645,5452,5453],{"class":666},"clientX",[645,5455,670],{"class":662},[645,5457,4511],{"class":721},[645,5459,725],{"class":662},[645,5461,5448],{"class":666},[645,5463,689],{"class":662},[645,5465,5466],{"class":666},"clientY",[645,5468,1637],{"class":662},[645,5470,5471,5473],{"class":647,"line":1748},[645,5472,1211],{"class":662},[645,5474,757],{"class":721},[645,5476,5477,5479],{"class":647,"line":1773},[645,5478,676],{"class":662},[645,5480,757],{"class":666},[436,5482,5483],{},[440,5484,5485],{},"Presence API đầy đủ:",[470,5487,5488,5497],{},[473,5489,5490],{},[476,5491,5492,5495],{},[479,5493,5494],{},"Method",[479,5496,3019],{},[492,5498,5499,5509,5519,5529,5539,5549],{},[476,5500,5501,5506],{},[497,5502,5503],{},[449,5504,5505],{},"channel.presenceState()",[497,5507,5508],{},"Trả về toàn bộ state hiện tại của tất cả users trong channel",[476,5510,5511,5516],{},[497,5512,5513],{},[449,5514,5515],{},"channel.track(data)",[497,5517,5518],{},"Đăng ký \u002F cập nhật presence của user hiện tại",[476,5520,5521,5526],{},[497,5522,5523],{},[449,5524,5525],{},"channel.untrack()",[497,5527,5528],{},"Hủy presence tracking (user được coi là offline)",[476,5530,5531,5536],{},[497,5532,5533],{},[449,5534,5535],{},"on('presence', { event: 'sync' })",[497,5537,5538],{},"Fire khi state được đồng bộ đầy đủ (initial + định kỳ)",[476,5540,5541,5546],{},[497,5542,5543],{},[449,5544,5545],{},"on('presence', { event: 'join' })",[497,5547,5548],{},"Fire khi có user mới tham gia channel",[476,5550,5551,5556],{},[497,5552,5553],{},[449,5554,5555],{},"on('presence', { event: 'leave' })",[497,5557,5558],{},"Fire khi user rời channel (timeout hoặc untrack)",[454,5560,5561,5564,5565,5567],{"color":456,"icon":457},[440,5562,5563],{},"Interview point — Presence:"," Presence dùng heartbeat mechanism: mỗi client gửi heartbeat định kỳ (mặc định 30s). Nếu server không nhận được heartbeat trong timeout window (mặc định 60s), user được coi là offline và tự động bị xóa. ",[449,5566,4868],{}," event gửi toàn bộ state (không phải delta), đảm bảo client luôn có state chính xác ngay cả khi bỏ lỡ events trước đó. Không cần gửi message thủ công.",[432,5569,5571],{"id":5570},"authorization-qua-rls","Authorization qua RLS",[436,5573,5574],{},"Realtime tôn trọng RLS policies -- user chỉ nhận được events từ dữ liệu họ được phép xem:",[636,5576,5578],{"className":2313,"code":5577,"language":2315,"meta":641,"style":641},"-- Nếu bảng có RLS policy:\nCREATE POLICY \"Users can only see own messages\"\nON messages FOR SELECT\nUSING (auth.uid() = user_id);\n\n-- Khi subscribe Postgres Changes, user chỉ nhận events\n-- từ messages của chính họ (tự động lọc bởi RLS)\n",[449,5579,5580,5585,5590,5595,5600,5604,5609],{"__ignoreMap":641},[645,5581,5582],{"class":647,"line":648},[645,5583,5584],{},"-- Nếu bảng có RLS policy:\n",[645,5586,5587],{"class":647,"line":655},[645,5588,5589],{},"CREATE POLICY \"Users can only see own messages\"\n",[645,5591,5592],{"class":647,"line":718},[645,5593,5594],{},"ON messages FOR SELECT\n",[645,5596,5597],{"class":647,"line":737},[645,5598,5599],{},"USING (auth.uid() = user_id);\n",[645,5601,5602],{"class":647,"line":752},[645,5603,764],{"emptyLinePlaceholder":763},[645,5605,5606],{"class":647,"line":760},[645,5607,5608],{},"-- Khi subscribe Postgres Changes, user chỉ nhận events\n",[645,5610,5611],{"class":647,"line":767},[645,5612,5613],{},"-- từ messages của chính họ (tự động lọc bởi RLS)\n",[436,5615,5616],{},"Điều này đảm bảo bảo mật ngay cả với realtime subscriptions.",[432,5618,5620],{"id":5619},"rate-limiting-limits","Rate Limiting & Limits",[436,5622,5623],{},"Supabase Realtime áp dụng giới hạn theo plan để đảm bảo chất lượng dịch vụ:",[470,5625,5626,5645],{},[473,5627,5628],{},[476,5629,5630,5633,5636,5639,5642],{},[479,5631,5632],{},"Plan",[479,5634,5635],{},"Concurrent Clients",[479,5637,5638],{},"Messages \u002F Client \u002F Giây",[479,5640,5641],{},"Broadcast Msg \u002F Giây",[479,5643,5644],{},"Presence Track \u002F Giây",[492,5646,5647,5665,5682,5700],{},[476,5648,5649,5653,5656,5659,5662],{},[497,5650,5651],{},[440,5652,2984],{},[497,5654,5655],{},"200",[497,5657,5658],{},"2 msg\u002Fs",[497,5660,5661],{},"5 msg\u002Fs",[497,5663,5664],{},"5 track\u002Fs",[476,5666,5667,5671,5674,5676,5679],{},[497,5668,5669],{},[440,5670,2990],{},[497,5672,5673],{},"500",[497,5675,5661],{},[497,5677,5678],{},"10 msg\u002Fs",[497,5680,5681],{},"10 track\u002Fs",[476,5683,5684,5689,5692,5694,5697],{},[497,5685,5686],{},[440,5687,5688],{},"Team",[497,5690,5691],{},"1,000",[497,5693,5678],{},[497,5695,5696],{},"20 msg\u002Fs",[497,5698,5699],{},"20 track\u002Fs",[476,5701,5702,5707,5710,5712,5714],{},[497,5703,5704],{},[440,5705,5706],{},"Enterprise",[497,5708,5709],{},"Custom",[497,5711,5709],{},[497,5713,5709],{},[497,5715,5709],{},[436,5717,5718],{},[440,5719,5720],{},"Chi tiết từng loại rate limit:",[470,5722,5723,5739],{},[473,5724,5725],{},[476,5726,5727,5730,5733,5736],{},[479,5728,5729],{},"Loại Limit",[479,5731,5732],{},"Áp dụng cho",[479,5734,5735],{},"Khi vượt quá",[479,5737,5738],{},"Cách tối ưu",[492,5740,5741,5756,5772,5788],{},[476,5742,5743,5747,5750,5753],{},[497,5744,5745],{},[440,5746,5635],{},[497,5748,5749],{},"Số WebSocket connections đồng thời",[497,5751,5752],{},"Connection mới bị từ chối",[497,5754,5755],{},"Dùng filter server-side để giảm unnecessary subscriptions",[476,5757,5758,5763,5766,5769],{},[497,5759,5760],{},[440,5761,5762],{},"Messages\u002FClient\u002FSecond",[497,5764,5765],{},"Tổng số message gửi qua WebSocket từ 1 client (broadcast + presence track)",[497,5767,5768],{},"Message bị throttle\u002Fdrop",[497,5770,5771],{},"Batch updates, debounce cursor tracking",[476,5773,5774,5779,5782,5785],{},[497,5775,5776],{},[440,5777,5778],{},"Broadcast Msg\u002FSecond",[497,5780,5781],{},"Tổng số broadcast messages trong channel",[497,5783,5784],{},"Message bị drop",[497,5786,5787],{},"Gửi ít thường xuyên hơn, gom nhóm events",[476,5789,5790,5795,5801,5804],{},[497,5791,5792],{},[440,5793,5794],{},"Presence Track\u002FSecond",[497,5796,5797,5798],{},"Số lần gọi ",[449,5799,5800],{},"track()",[497,5802,5803],{},"Track bị bỏ qua",[497,5805,5806],{},"Chỉ track khi state thực sự thay đổi",[454,5808,5809,5812],{"color":2032,"icon":4142},[440,5810,5811],{},"Lưu ý quan trọng:"," Cần tính toán kỹ số lượng subscriptions và broadcast frequency. Mỗi WebSocket connection đều có cost. Với ứng dụng có nhiều user, hãy dùng filter server-side để giảm số lượng events được gửi đến client.",[454,5814,5815,5818,5819,5821,5822,5824],{"color":456,"icon":457},[440,5816,5817],{},"Interview point — Rate Limiting:"," ",[449,5820,5635],{}," là giới hạn WebSocket connections đồng thời, không phải số users. Mỗi tab browser = 1 connection. ",[449,5823,5762],{}," áp dụng cho tổng broadcast + presence track — nếu cần gửi nhiều hơn, xem xét batch updates hoặc nâng cấp plan.",[432,5826,5828],{"id":5827},"so-sánh-3-chế-độ","So sánh 3 chế độ",[470,5830,5831,5844],{},[473,5832,5833],{},[476,5834,5835,5838,5840,5842],{},[479,5836,5837],{},"Tiêu chí",[479,5839,3057],{},[479,5841,3031],{},[479,5843,3044],{},[492,5845,5846,5862,5878,5894,5910],{},[476,5847,5848,5853,5856,5859],{},[497,5849,5850],{},[440,5851,5852],{},"Dữ liệu",[497,5854,5855],{},"Persistent (từ DB)",[497,5857,5858],{},"Ephemeral (tạm thời)",[497,5860,5861],{},"Stateful (đồng bộ)",[476,5863,5864,5869,5872,5875],{},[497,5865,5866],{},[440,5867,5868],{},"Persistence",[497,5870,5871],{},"Lưu trong Postgres",[497,5873,5874],{},"Không lưu",[497,5876,5877],{},"Không lưu, nhưng sync",[476,5879,5880,5885,5888,5891],{},[497,5881,5882],{},[440,5883,5884],{},"Client offline",[497,5886,5887],{},"Nhận lại khi reconnect",[497,5889,5890],{},"Bỏ lỡ message",[497,5892,5893],{},"Tự động leave\u002Fjoin",[476,5895,5896,5901,5904,5907],{},[497,5897,5898],{},[440,5899,5900],{},"Filter",[497,5902,5903],{},"Server-side (WAL)",[497,5905,5906],{},"Client phải tự filter",[497,5908,5909],{},"Không cần",[476,5911,5912,5916,5919,5922],{},[497,5913,5914],{},[440,5915,490],{},[497,5917,5918],{},"Live feed, dashboard",[497,5920,5921],{},"Chat typing, game events",[497,5923,5924],{},"Online status, collab",[2996,5926],{},[427,5928,5930],{"id":5929},"bài-tập","Bài tập",[432,5932,5934],{"id":5933},"bài-tập-1-avatar-upload-với-preview-và-progress","Bài tập 1: Avatar Upload với Preview và Progress",[436,5936,5937],{},[440,5938,5939],{},"Yêu cầu:",[2977,5941,5942,5945,5948,5951,5957],{},[2980,5943,5944],{},"Cho phép người dùng chọn ảnh từ máy tính",[2980,5946,5947],{},"Hiển thị preview ảnh trước khi upload",[2980,5949,5950],{},"Hiển thị thanh tiến trình upload (progress bar)",[2980,5952,5953,5954],{},"Upload lên Supabase Storage vào thư mục ",[449,5955,5956],{},"{userId}\u002Favatar.png",[2980,5958,5959],{},"Sau khi upload, hiển thị ảnh từ CDN với image transformation (resize + crop)",[436,5961,5962],{},[440,5963,5964],{},"Gợi ý triển khai:",[636,5966,5970],{"className":5967,"code":5968,"language":5969,"meta":641,"style":641},"language-vue shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003Cscript setup>\nconst supabase = useSupabaseClient()\nconst user = useSupabaseUser()\n\nconst file = ref(null)\nconst previewUrl = ref(null)\nconst progress = ref(0)\nconst uploadedUrl = ref(null)\n\nfunction onFileChange(event) {\n  file.value = event.target.files[0]\n  previewUrl.value = URL.createObjectURL(file.value)\n}\n\nasync function uploadAvatar() {\n  if (!file.value) return\n\n  const { data, error } = await supabase.storage\n    .from('avatars')\n    .upload(`${user.value.id}\u002Favatar.png`, file.value, {\n      upsert: true,\n      contentType: file.value.type\n    })\n\n  if (error) throw error\n\n  \u002F\u002F URL ảnh đã resize qua CDN\n  uploadedUrl.value = supabase.storage\n    .from('avatars')\n    .getPublicUrl(`${user.value.id}\u002Favatar.png`).data.publicUrl\n    + '?width=200&height=200&resize=cover'\n}\n\u003C\u002Fscript>\n","vue",[449,5971,5972,5986,6000,6014,6018,6037,6054,6071,6088,6092,6109,6138,6168,6173,6177,6191,6211,6215,6240,6257,6297,6308,6326,6332,6336,6352,6356,6361,6378,6394,6432,6444,6448],{"__ignoreMap":641},[645,5973,5974,5977,5980,5983],{"class":647,"line":648},[645,5975,5976],{"class":662},"\u003C",[645,5978,5979],{"class":721},"script",[645,5981,5982],{"class":658}," setup",[645,5984,5985],{"class":662},">\n",[645,5987,5988,5990,5993,5995,5998],{"class":647,"line":655},[645,5989,659],{"class":658},[645,5991,5992],{"class":666}," supabase ",[645,5994,1061],{"class":662},[645,5996,5997],{"class":697}," useSupabaseClient",[645,5999,808],{"class":666},[645,6001,6002,6004,6007,6009,6012],{"class":647,"line":718},[645,6003,659],{"class":658},[645,6005,6006],{"class":666}," user ",[645,6008,1061],{"class":662},[645,6010,6011],{"class":697}," useSupabaseUser",[645,6013,808],{"class":666},[645,6015,6016],{"class":647,"line":737},[645,6017,764],{"emptyLinePlaceholder":763},[645,6019,6020,6022,6025,6027,6030,6032,6035],{"class":647,"line":752},[645,6021,659],{"class":658},[645,6023,6024],{"class":666}," file ",[645,6026,1061],{"class":662},[645,6028,6029],{"class":697}," ref",[645,6031,701],{"class":666},[645,6033,6034],{"class":662},"null",[645,6036,757],{"class":666},[645,6038,6039,6041,6044,6046,6048,6050,6052],{"class":647,"line":760},[645,6040,659],{"class":658},[645,6042,6043],{"class":666}," previewUrl ",[645,6045,1061],{"class":662},[645,6047,6029],{"class":697},[645,6049,701],{"class":666},[645,6051,6034],{"class":662},[645,6053,757],{"class":666},[645,6055,6056,6058,6061,6063,6065,6067,6069],{"class":647,"line":767},[645,6057,659],{"class":658},[645,6059,6060],{"class":666}," progress ",[645,6062,1061],{"class":662},[645,6064,6029],{"class":697},[645,6066,701],{"class":666},[645,6068,1077],{"class":745},[645,6070,757],{"class":666},[645,6072,6073,6075,6078,6080,6082,6084,6086],{"class":647,"line":773},[645,6074,659],{"class":658},[645,6076,6077],{"class":666}," uploadedUrl ",[645,6079,1061],{"class":662},[645,6081,6029],{"class":697},[645,6083,701],{"class":666},[645,6085,6034],{"class":662},[645,6087,757],{"class":666},[645,6089,6090],{"class":647,"line":811},[645,6091,764],{"emptyLinePlaceholder":763},[645,6093,6094,6097,6100,6102,6105,6107],{"class":647,"line":816},[645,6095,6096],{"class":658},"function",[645,6098,6099],{"class":697}," onFileChange",[645,6101,701],{"class":662},[645,6103,6104],{"class":1300},"event",[645,6106,1304],{"class":662},[645,6108,715],{"class":662},[645,6110,6111,6114,6116,6119,6121,6123,6125,6127,6129,6132,6134,6136],{"class":647,"line":822},[645,6112,6113],{"class":666},"  file",[645,6115,689],{"class":662},[645,6117,6118],{"class":666},"value",[645,6120,679],{"class":662},[645,6122,1064],{"class":666},[645,6124,689],{"class":662},[645,6126,1069],{"class":666},[645,6128,689],{"class":662},[645,6130,6131],{"class":666},"files",[645,6133,1993],{"class":721},[645,6135,1077],{"class":745},[645,6137,1080],{"class":721},[645,6139,6140,6143,6145,6147,6149,6152,6154,6157,6159,6162,6164,6166],{"class":647,"line":862},[645,6141,6142],{"class":666},"  previewUrl",[645,6144,689],{"class":662},[645,6146,6118],{"class":666},[645,6148,679],{"class":662},[645,6150,6151],{"class":666}," URL",[645,6153,689],{"class":662},[645,6155,6156],{"class":697},"createObjectURL",[645,6158,701],{"class":721},[645,6160,6161],{"class":666},"file",[645,6163,689],{"class":662},[645,6165,6118],{"class":666},[645,6167,757],{"class":721},[645,6169,6170],{"class":647,"line":867},[645,6171,6172],{"class":662},"}\n",[645,6174,6175],{"class":647,"line":873},[645,6176,764],{"emptyLinePlaceholder":763},[645,6178,6179,6181,6184,6187,6189],{"class":647,"line":915},[645,6180,5181],{"class":658},[645,6182,6183],{"class":658}," function",[645,6185,6186],{"class":697}," uploadAvatar",[645,6188,3236],{"class":662},[645,6190,715],{"class":662},[645,6192,6193,6195,6197,6200,6202,6204,6206,6208],{"class":647,"line":928},[645,6194,5196],{"class":682},[645,6196,1297],{"class":721},[645,6198,6199],{"class":662},"!",[645,6201,6161],{"class":666},[645,6203,689],{"class":662},[645,6205,6118],{"class":666},[645,6207,3157],{"class":721},[645,6209,6210],{"class":682},"return\n",[645,6212,6213],{"class":647,"line":935},[645,6214,764],{"emptyLinePlaceholder":763},[645,6216,6217,6219,6221,6223,6225,6228,6230,6232,6234,6236,6238],{"class":647,"line":940},[645,6218,4884],{"class":658},[645,6220,663],{"class":662},[645,6222,667],{"class":666},[645,6224,670],{"class":662},[645,6226,6227],{"class":666}," error",[645,6229,2912],{"class":662},[645,6231,679],{"class":662},[645,6233,683],{"class":682},[645,6235,686],{"class":666},[645,6237,689],{"class":662},[645,6239,1105],{"class":666},[645,6241,6242,6245,6247,6249,6251,6253,6255],{"class":647,"line":946},[645,6243,6244],{"class":662},"    .",[645,6246,1113],{"class":697},[645,6248,701],{"class":721},[645,6250,704],{"class":662},[645,6252,708],{"class":707},[645,6254,704],{"class":662},[645,6256,757],{"class":721},[645,6258,6259,6261,6263,6265,6267,6270,6272,6274,6276,6278,6280,6283,6285,6287,6289,6291,6293,6295],{"class":647,"line":986},[645,6260,6244],{"class":662},[645,6262,1130],{"class":697},[645,6264,701],{"class":721},[645,6266,1135],{"class":662},[645,6268,6269],{"class":666},"user",[645,6271,689],{"class":662},[645,6273,6118],{"class":666},[645,6275,689],{"class":662},[645,6277,3901],{"class":666},[645,6279,676],{"class":662},[645,6281,6282],{"class":707},"\u002Favatar.png",[645,6284,1146],{"class":662},[645,6286,670],{"class":662},[645,6288,1283],{"class":666},[645,6290,689],{"class":662},[645,6292,6118],{"class":666},[645,6294,670],{"class":662},[645,6296,715],{"class":662},[645,6298,6299,6302,6304,6306],{"class":647,"line":991},[645,6300,6301],{"class":721},"      upsert",[645,6303,725],{"class":662},[645,6305,729],{"class":728},[645,6307,1585],{"class":662},[645,6309,6310,6313,6315,6317,6319,6321,6323],{"class":647,"line":997},[645,6311,6312],{"class":721},"      contentType",[645,6314,725],{"class":662},[645,6316,1283],{"class":666},[645,6318,689],{"class":662},[645,6320,6118],{"class":666},[645,6322,689],{"class":662},[645,6324,6325],{"class":666},"type\n",[645,6327,6328,6330],{"class":647,"line":1408},[645,6329,5315],{"class":662},[645,6331,757],{"class":721},[645,6333,6334],{"class":647,"line":1430},[645,6335,764],{"emptyLinePlaceholder":763},[645,6337,6338,6340,6342,6344,6346,6349],{"class":647,"line":1435},[645,6339,5196],{"class":682},[645,6341,1297],{"class":721},[645,6343,3275],{"class":666},[645,6345,3157],{"class":721},[645,6347,6348],{"class":682},"throw",[645,6350,6351],{"class":666}," error\n",[645,6353,6354],{"class":647,"line":1441},[645,6355,764],{"emptyLinePlaceholder":763},[645,6357,6358],{"class":647,"line":1461},[645,6359,6360],{"class":651},"  \u002F\u002F URL ảnh đã resize qua CDN\n",[645,6362,6363,6366,6368,6370,6372,6374,6376],{"class":647,"line":1478},[645,6364,6365],{"class":666},"  uploadedUrl",[645,6367,689],{"class":662},[645,6369,6118],{"class":666},[645,6371,679],{"class":662},[645,6373,686],{"class":666},[645,6375,689],{"class":662},[645,6377,1105],{"class":666},[645,6379,6380,6382,6384,6386,6388,6390,6392],{"class":647,"line":1500},[645,6381,6244],{"class":662},[645,6383,1113],{"class":697},[645,6385,701],{"class":721},[645,6387,704],{"class":662},[645,6389,708],{"class":707},[645,6391,704],{"class":662},[645,6393,757],{"class":721},[645,6395,6396,6398,6400,6402,6404,6406,6408,6410,6412,6414,6416,6418,6420,6422,6424,6427,6429],{"class":647,"line":1506},[645,6397,6244],{"class":662},[645,6399,1483],{"class":697},[645,6401,701],{"class":721},[645,6403,1135],{"class":662},[645,6405,6269],{"class":666},[645,6407,689],{"class":662},[645,6409,6118],{"class":666},[645,6411,689],{"class":662},[645,6413,3901],{"class":666},[645,6415,676],{"class":662},[645,6417,6282],{"class":707},[645,6419,1146],{"class":662},[645,6421,1304],{"class":721},[645,6423,689],{"class":662},[645,6425,6426],{"class":666},"data",[645,6428,689],{"class":662},[645,6430,6431],{"class":666},"publicUrl\n",[645,6433,6434,6437,6439,6442],{"class":647,"line":1511},[645,6435,6436],{"class":662},"    +",[645,6438,1165],{"class":662},[645,6440,6441],{"class":707},"?width=200&height=200&resize=cover",[645,6443,2609],{"class":662},[645,6445,6446],{"class":647,"line":1517},[645,6447,6172],{"class":662},[645,6449,6450,6453,6455],{"class":647,"line":1542},[645,6451,6452],{"class":662},"\u003C\u002F",[645,6454,5979],{"class":721},[645,6456,5985],{"class":662},[432,6458,6460],{"id":6459},"bài-tập-2-image-gallery-với-cdn-transform","Bài tập 2: Image Gallery với CDN Transform",[436,6462,6463],{},[440,6464,5939],{},[2977,6466,6467,6470,6473,6476,6479],{},[2980,6468,6469],{},"Hiển thị danh sách ảnh (grid layout) từ một bucket",[2980,6471,6472],{},"Mỗi ảnh hiển thị thumbnail 300x300 qua CDN transform",[2980,6474,6475],{},"Khi click vào ảnh, hiển thị ảnh full-size trong modal\u002Flightbox",[2980,6477,6478],{},"Hỗ trợ lazy loading cho thumbnails",[2980,6480,6481],{},"Có nút tải ảnh với các kích thước khác nhau (origin, 800px, 400px)",[432,6483,6485],{"id":6484},"bài-tập-3-real-time-chat-với-message-subscription","Bài tập 3: Real-Time Chat với Message Subscription",[436,6487,6488],{},[440,6489,5939],{},[2977,6491,6492,6495,6498,6501,6504],{},[2980,6493,6494],{},"Tạo một chat room đơn giản với danh sách messages",[2980,6496,6497],{},"Subscribe Postgres Changes để nhận messages mới real-time",[2980,6499,6500],{},"Tự động scroll xuống khi có message mới",[2980,6502,6503],{},"Hiển thị thời gian gửi, tên người gửi",[2980,6505,6506],{},"Xử lý optimistic UI khi gửi message",[436,6508,6509],{},[440,6510,5964],{},[636,6512,6514],{"className":638,"code":6513,"language":640,"meta":641,"style":641},"\u002F\u002F Subscribe messages real-time\nconst channel = supabase.channel('chat-room')\n\nchannel.on('postgres_changes', {\n  event: 'INSERT',\n  schema: 'public',\n  table: 'messages',\n  filter: `room_id=eq.${roomId}`\n}, (payload) => {\n  messages.value.push(payload.new)\n  nextTick(() => scrollToBottom())\n}).subscribe()\n\n\u002F\u002F Gửi message mới\nasync function sendMessage(content) {\n  const { data, error } = await supabase\n    .from('messages')\n    .insert({\n      room_id: roomId,\n      user_id: user.value.id,\n      content: content\n    })\n    .select()\n    .single()\n}\n",[449,6515,6516,6521,6546,6550,6570,6584,6599,6614,6633,6648,6672,6689,6701,6705,6710,6728,6749,6765,6776,6788,6806,6816,6822,6831,6840],{"__ignoreMap":641},[645,6517,6518],{"class":647,"line":648},[645,6519,6520],{"class":651},"\u002F\u002F Subscribe messages real-time\n",[645,6522,6523,6525,6527,6529,6531,6533,6535,6537,6539,6542,6544],{"class":647,"line":655},[645,6524,659],{"class":658},[645,6526,3099],{"class":666},[645,6528,1061],{"class":662},[645,6530,686],{"class":666},[645,6532,689],{"class":662},[645,6534,3108],{"class":697},[645,6536,701],{"class":666},[645,6538,704],{"class":662},[645,6540,6541],{"class":707},"chat-room",[645,6543,704],{"class":662},[645,6545,757],{"class":666},[645,6547,6548],{"class":647,"line":718},[645,6549,764],{"emptyLinePlaceholder":763},[645,6551,6552,6554,6556,6558,6560,6562,6564,6566,6568],{"class":647,"line":737},[645,6553,3108],{"class":666},[645,6555,689],{"class":662},[645,6557,3438],{"class":697},[645,6559,701],{"class":666},[645,6561,704],{"class":662},[645,6563,3445],{"class":707},[645,6565,704],{"class":662},[645,6567,670],{"class":662},[645,6569,715],{"class":662},[645,6571,6572,6574,6576,6578,6580,6582],{"class":647,"line":752},[645,6573,4479],{"class":721},[645,6575,725],{"class":662},[645,6577,1165],{"class":662},[645,6579,3463],{"class":707},[645,6581,704],{"class":662},[645,6583,1585],{"class":662},[645,6585,6586,6589,6591,6593,6595,6597],{"class":647,"line":760},[645,6587,6588],{"class":721},"  schema",[645,6590,725],{"class":662},[645,6592,1165],{"class":662},[645,6594,3477],{"class":707},[645,6596,704],{"class":662},[645,6598,1585],{"class":662},[645,6600,6601,6604,6606,6608,6610,6612],{"class":647,"line":767},[645,6602,6603],{"class":721},"  table",[645,6605,725],{"class":662},[645,6607,1165],{"class":662},[645,6609,3491],{"class":707},[645,6611,704],{"class":662},[645,6613,1585],{"class":662},[645,6615,6616,6619,6621,6623,6626,6628,6631],{"class":647,"line":773},[645,6617,6618],{"class":721},"  filter",[645,6620,725],{"class":662},[645,6622,1812],{"class":662},[645,6624,6625],{"class":707},"room_id=eq.",[645,6627,1329],{"class":662},[645,6629,6630],{"class":666},"roomId",[645,6632,2850],{"class":662},[645,6634,6635,6638,6640,6642,6644,6646],{"class":647,"line":811},[645,6636,6637],{"class":662},"},",[645,6639,1297],{"class":662},[645,6641,3504],{"class":1300},[645,6643,1304],{"class":662},[645,6645,1307],{"class":658},[645,6647,715],{"class":662},[645,6649,6650,6653,6655,6657,6659,6662,6664,6666,6668,6670],{"class":647,"line":816},[645,6651,6652],{"class":666},"  messages",[645,6654,689],{"class":662},[645,6656,6118],{"class":666},[645,6658,689],{"class":662},[645,6660,6661],{"class":697},"push",[645,6663,701],{"class":721},[645,6665,3504],{"class":666},[645,6667,689],{"class":662},[645,6669,3538],{"class":666},[645,6671,757],{"class":721},[645,6673,6674,6677,6679,6681,6683,6686],{"class":647,"line":822},[645,6675,6676],{"class":697},"  nextTick",[645,6678,701],{"class":721},[645,6680,3236],{"class":662},[645,6682,1307],{"class":658},[645,6684,6685],{"class":697}," scrollToBottom",[645,6687,6688],{"class":721},"())\n",[645,6690,6691,6693,6695,6697,6699],{"class":647,"line":862},[645,6692,676],{"class":662},[645,6694,1304],{"class":666},[645,6696,689],{"class":662},[645,6698,3132],{"class":697},[645,6700,808],{"class":666},[645,6702,6703],{"class":647,"line":867},[645,6704,764],{"emptyLinePlaceholder":763},[645,6706,6707],{"class":647,"line":873},[645,6708,6709],{"class":651},"\u002F\u002F Gửi message mới\n",[645,6711,6712,6714,6716,6719,6721,6724,6726],{"class":647,"line":915},[645,6713,5181],{"class":658},[645,6715,6183],{"class":658},[645,6717,6718],{"class":697}," sendMessage",[645,6720,701],{"class":662},[645,6722,6723],{"class":1300},"content",[645,6725,1304],{"class":662},[645,6727,715],{"class":662},[645,6729,6730,6732,6734,6736,6738,6740,6742,6744,6746],{"class":647,"line":928},[645,6731,4884],{"class":658},[645,6733,663],{"class":662},[645,6735,667],{"class":666},[645,6737,670],{"class":662},[645,6739,6227],{"class":666},[645,6741,2912],{"class":662},[645,6743,679],{"class":662},[645,6745,683],{"class":682},[645,6747,6748],{"class":666}," supabase\n",[645,6750,6751,6753,6755,6757,6759,6761,6763],{"class":647,"line":935},[645,6752,6244],{"class":662},[645,6754,1113],{"class":697},[645,6756,701],{"class":721},[645,6758,704],{"class":662},[645,6760,3491],{"class":707},[645,6762,704],{"class":662},[645,6764,757],{"class":721},[645,6766,6767,6769,6772,6774],{"class":647,"line":940},[645,6768,6244],{"class":662},[645,6770,6771],{"class":697},"insert",[645,6773,701],{"class":721},[645,6775,3160],{"class":662},[645,6777,6778,6781,6783,6786],{"class":647,"line":946},[645,6779,6780],{"class":721},"      room_id",[645,6782,725],{"class":662},[645,6784,6785],{"class":666}," roomId",[645,6787,1585],{"class":662},[645,6789,6790,6792,6794,6796,6798,6800,6802,6804],{"class":647,"line":986},[645,6791,5238],{"class":721},[645,6793,725],{"class":662},[645,6795,3212],{"class":666},[645,6797,689],{"class":662},[645,6799,6118],{"class":666},[645,6801,689],{"class":662},[645,6803,3901],{"class":666},[645,6805,1585],{"class":662},[645,6807,6808,6811,6813],{"class":647,"line":991},[645,6809,6810],{"class":721},"      content",[645,6812,725],{"class":662},[645,6814,6815],{"class":666}," content\n",[645,6817,6818,6820],{"class":647,"line":997},[645,6819,5315],{"class":662},[645,6821,757],{"class":721},[645,6823,6824,6826,6829],{"class":647,"line":1408},[645,6825,6244],{"class":662},[645,6827,6828],{"class":697},"select",[645,6830,808],{"class":721},[645,6832,6833,6835,6838],{"class":647,"line":1430},[645,6834,6244],{"class":662},[645,6836,6837],{"class":697},"single",[645,6839,808],{"class":721},[645,6841,6842],{"class":647,"line":1435},[645,6843,6172],{"class":662},[432,6845,6847],{"id":6846},"bài-tập-4-collaborative-cursor-tracking-với-presence","Bài tập 4: Collaborative Cursor Tracking với Presence",[436,6849,6850],{},[440,6851,5939],{},[2977,6853,6854,6857,6860,6863,6866],{},[2980,6855,6856],{},"Sử dụng Presence để theo dõi vị trí chuột của tất cả users trong cùng channel",[2980,6858,6859],{},"Hiển thị cursor của các users khác với màu sắc khác nhau",[2980,6861,6862],{},"Hiển thị danh sách users đang online",[2980,6864,6865],{},"Khi user thoát, tự động xóa cursor của họ",[2980,6867,6868],{},"Hiển thị typing indicator khi user đang gõ phím (dùng Broadcast)",[2996,6870],{},[6872,6873],"navigation",{"next":6874,"prev":6875},"4.functions-ai.md","2.auth-rls.md",[6877,6878,6879],"style",{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":641,"searchDepth":655,"depth":655,"links":6881},[6882,6896,6906],{"id":429,"depth":655,"text":430,"children":6883},[6884,6885,6886,6887,6888,6889,6890,6891,6892,6893,6894,6895],{"id":434,"depth":718,"text":11},{"id":464,"depth":718,"text":465},{"id":567,"depth":718,"text":568},{"id":630,"depth":718,"text":631},{"id":1037,"depth":718,"text":1038},{"id":1920,"depth":718,"text":1921},{"id":2013,"depth":718,"text":2014},{"id":2062,"depth":718,"text":2063},{"id":2306,"depth":718,"text":2307},{"id":2577,"depth":718,"text":2578},{"id":2801,"depth":718,"text":2802},{"id":2974,"depth":718,"text":2975},{"id":3000,"depth":655,"text":3001,"children":6897},[6898,6899,6900,6901,6902,6903,6904,6905],{"id":3004,"depth":718,"text":11},{"id":3072,"depth":718,"text":3073},{"id":3386,"depth":718,"text":3387},{"id":4363,"depth":718,"text":3031},{"id":4798,"depth":718,"text":3044},{"id":5570,"depth":718,"text":5571},{"id":5619,"depth":718,"text":5620},{"id":5827,"depth":718,"text":5828},{"id":5929,"depth":655,"text":5930,"children":6907},[6908,6909,6910,6911],{"id":5933,"depth":718,"text":5934},{"id":6459,"depth":718,"text":6460},{"id":6484,"depth":718,"text":6485},{"id":6846,"depth":718,"text":6847},"Supabase Storage (S3-compatible, CDN, image transform) và Realtime (Broadcast, Presence, Postgres Changes CDC).","md",null,{},{"title":265,"icon":268},{"title":422,"description":6912},"45LvKF9Hnq1jyW7F_vMqEQyUjqDdPvUHJJgdNZg_620",[6920,6922],{"title":260,"path":261,"stem":262,"description":6921,"icon":263,"children":-1},"Supabase Auth — email\u002Fpassword, magic link, OTP, 50+ OAuth providers, SSO, MFA, passkey, auth hooks.",{"title":270,"path":271,"stem":272,"description":6923,"icon":273,"children":-1},"Supabase Edge Functions (Deno, TypeScript, global) và AI\u002FVector (pgvector, RAG, semantic search, embeddings).",1783490368241]