[{"data":1,"prerenderedAt":3032},["ShallowReactive",2],{"blog:\u002Fblog\u002Fnuxt\u002Ftypescript-type-safety":3},{"id":4,"title":5,"author":6,"body":7,"category":3016,"date":3017,"description":3018,"draft":3019,"extension":3020,"image":3021,"meta":3022,"navigation":567,"path":3023,"seo":3024,"series":3025,"seriesOrder":182,"seriesTitle":3026,"stem":3027,"tags":3028,"updatedAt":3021,"__hash__":3031},"blog\u002Fblog\u002Fnuxt\u002Ftypescript-type-safety\u002Findex.md","TypeScript 類型安全實戰","charles",{"type":8,"value":9,"toc":2983},"minimark",[10,14,23,39,42,45,56,58,62,67,70,91,95,148,151,528,531,838,842,1228,1230,1234,1238,1270,1274,1294,1298,1328,1332,1372,1374,1377,1381,1387,1391,1485,1489,1697,1699,1703,1706,2021,2024,2301,2303,2306,2310,2316,2322,2435,2441,2537,2541,2546,2702,2706,2711,2716,2784,2786,2789,2878,2880,2883,2935,2937,2940,2979],[11,12,13],"h2",{"id":13},"這篇要解決什麼問題",[15,16,17,18,22],"p",{},"TypeScript 的威力在於",[19,20,21],"strong",{},"編譯時期就能發現錯誤","。但要發揮這個優勢，需要：",[24,25,26,30,33,36],"ul",{},[27,28,29],"li",{},"資料庫類型與程式碼同步",[27,31,32],{},"Vue 元件的 Props\u002FEmits 嚴格類型",[27,34,35],{},"處理 null\u002Fundefined 的優雅方式",[27,37,38],{},"Client 與 Server 共享類型",[40,41],"hr",{},[11,43,44],{"id":44},"端到端類型安全的架構",[46,47,52],"pre",{"className":48,"code":50,"language":51},[49],"language-text","┌─────────────────────────────────────────────────────────┐\n│                    Type Sources                         │\n├─────────────────────────────────────────────────────────┤\n│  Supabase Migration ──→ database.types.ts (自動產生)    │\n│  shared\u002Ftypes\u002F      ──→ 全域共用類型                    │\n│  app\u002Ftypes\u002F         ──→ Client 專用類型                 │\n│  server\u002Ftypes\u002F      ──→ Server 專用類型                 │\n└─────────────────────────────────────────────────────────┘\n           │\n           ▼\n┌─────────────────────────────────────────────────────────┐\n│                    Type Consumers                       │\n├─────────────────────────────────────────────────────────┤\n│  Vue Components     ←── Props, Emits, defineModel       │\n│  Composables        ←── 回傳值類型                       │\n│  Server API         ←── 請求\u002F回應類型                    │\n│  Pinia Stores       ←── State 類型                      │\n└─────────────────────────────────────────────────────────┘\n","text",[53,54,50],"code",{"__ignoreMap":55},"",[40,57],{},[11,59,61],{"id":60},"supabase-類型自動產生","Supabase 類型自動產生",[63,64,66],"h3",{"id":65},"為什麼自動產生","為什麼自動產生？",[15,68,69],{},"手動維護資料庫類型有三大問題：",[71,72,73,79,85],"ol",{},[27,74,75,78],{},[19,76,77],{},"同步困難","：Migration 修改後忘記更新類型",[27,80,81,84],{},[19,82,83],{},"人為錯誤","：欄位名稱打錯、類型不對",[27,86,87,90],{},[19,88,89],{},"維護成本","：每次改表都要手動修改",[63,92,94],{"id":93},"自動產生-databasetypests","自動產生 database.types.ts",[46,96,100],{"className":97,"code":98,"language":99,"meta":55,"style":55},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","# 從本地 Supabase 產生類型\nsupabase gen types typescript --local | tee app\u002Ftypes\u002Fdatabase.types.ts > \u002Fdev\u002Fnull\n","bash",[53,101,102,111],{"__ignoreMap":55},[103,104,107],"span",{"class":105,"line":106},"line",1,[103,108,110],{"class":109},"sutJx","# 從本地 Supabase 產生類型\n",[103,112,114,118,122,125,128,132,136,139,142,145],{"class":105,"line":113},2,[103,115,117],{"class":116},"sbgvK","supabase",[103,119,121],{"class":120},"s_sjI"," gen",[103,123,124],{"class":120}," types",[103,126,127],{"class":120}," typescript",[103,129,131],{"class":130},"stzsN"," --local",[103,133,135],{"class":134},"smGrS"," |",[103,137,138],{"class":116}," tee",[103,140,141],{"class":120}," app\u002Ftypes\u002Fdatabase.types.ts",[103,143,144],{"class":134}," >",[103,146,147],{"class":120}," \u002Fdev\u002Fnull\n",[15,149,150],{},"產生的檔案結構：",[46,152,156],{"className":153,"code":154,"language":155,"meta":55,"style":55},"language-typescript shiki shiki-themes material-theme-lighter github-light github-dark","\u002F\u002F app\u002Ftypes\u002Fdatabase.types.ts（自動產生，勿手動編輯）\nexport interface Database {\n  public: {\n    Tables: {\n      users: {\n        Row: {\n          id: string\n          email: string\n          name: string | null\n          created_at: string\n        }\n        Insert: {\n          id?: string\n          email: string\n          name?: string | null\n          created_at?: string\n        }\n        Update: {\n          id?: string\n          email?: string\n          name?: string | null\n          created_at?: string\n        }\n      }\n    }\n    Views: Record&lt;string, never&gt;\n    Functions: Record&lt;string, never&gt;\n    Enums: {\n      user_role: 'admin' | 'manager' | 'staff'\n    }\n  }\n}\n","typescript",[53,157,158,163,180,192,202,212,222,234,244,260,270,276,286,296,305,318,327,332,342,351,360,373,382,387,393,399,436,464,474,511,516,522],{"__ignoreMap":55},[103,159,160],{"class":105,"line":106},[103,161,162],{"class":109},"\u002F\u002F app\u002Ftypes\u002Fdatabase.types.ts（自動產生，勿手動編輯）\n",[103,164,165,169,173,176],{"class":105,"line":113},[103,166,168],{"class":167},"sVHd0","export",[103,170,172],{"class":171},"sbsja"," interface",[103,174,175],{"class":116}," Database",[103,177,179],{"class":178},"sP7_E"," {\n",[103,181,183,187,190],{"class":105,"line":182},3,[103,184,186],{"class":185},"sucvu","  public",[103,188,189],{"class":134},":",[103,191,179],{"class":178},[103,193,195,198,200],{"class":105,"line":194},4,[103,196,197],{"class":185},"    Tables",[103,199,189],{"class":134},[103,201,179],{"class":178},[103,203,205,208,210],{"class":105,"line":204},5,[103,206,207],{"class":185},"      users",[103,209,189],{"class":134},[103,211,179],{"class":178},[103,213,215,218,220],{"class":105,"line":214},6,[103,216,217],{"class":185},"        Row",[103,219,189],{"class":134},[103,221,179],{"class":178},[103,223,225,228,230],{"class":105,"line":224},7,[103,226,227],{"class":185},"          id",[103,229,189],{"class":134},[103,231,233],{"class":232},"sZMiF"," string\n",[103,235,237,240,242],{"class":105,"line":236},8,[103,238,239],{"class":185},"          email",[103,241,189],{"class":134},[103,243,233],{"class":232},[103,245,247,250,252,255,257],{"class":105,"line":246},9,[103,248,249],{"class":185},"          name",[103,251,189],{"class":134},[103,253,254],{"class":232}," string",[103,256,135],{"class":134},[103,258,259],{"class":232}," null\n",[103,261,263,266,268],{"class":105,"line":262},10,[103,264,265],{"class":185},"          created_at",[103,267,189],{"class":134},[103,269,233],{"class":232},[103,271,273],{"class":105,"line":272},11,[103,274,275],{"class":178},"        }\n",[103,277,279,282,284],{"class":105,"line":278},12,[103,280,281],{"class":185},"        Insert",[103,283,189],{"class":134},[103,285,179],{"class":178},[103,287,289,291,294],{"class":105,"line":288},13,[103,290,227],{"class":185},[103,292,293],{"class":134},"?:",[103,295,233],{"class":232},[103,297,299,301,303],{"class":105,"line":298},14,[103,300,239],{"class":185},[103,302,189],{"class":134},[103,304,233],{"class":232},[103,306,308,310,312,314,316],{"class":105,"line":307},15,[103,309,249],{"class":185},[103,311,293],{"class":134},[103,313,254],{"class":232},[103,315,135],{"class":134},[103,317,259],{"class":232},[103,319,321,323,325],{"class":105,"line":320},16,[103,322,265],{"class":185},[103,324,293],{"class":134},[103,326,233],{"class":232},[103,328,330],{"class":105,"line":329},17,[103,331,275],{"class":178},[103,333,335,338,340],{"class":105,"line":334},18,[103,336,337],{"class":185},"        Update",[103,339,189],{"class":134},[103,341,179],{"class":178},[103,343,345,347,349],{"class":105,"line":344},19,[103,346,227],{"class":185},[103,348,293],{"class":134},[103,350,233],{"class":232},[103,352,354,356,358],{"class":105,"line":353},20,[103,355,239],{"class":185},[103,357,293],{"class":134},[103,359,233],{"class":232},[103,361,363,365,367,369,371],{"class":105,"line":362},21,[103,364,249],{"class":185},[103,366,293],{"class":134},[103,368,254],{"class":232},[103,370,135],{"class":134},[103,372,259],{"class":232},[103,374,376,378,380],{"class":105,"line":375},22,[103,377,265],{"class":185},[103,379,293],{"class":134},[103,381,233],{"class":232},[103,383,385],{"class":105,"line":384},23,[103,386,275],{"class":178},[103,388,390],{"class":105,"line":389},24,[103,391,392],{"class":178},"      }\n",[103,394,396],{"class":105,"line":395},25,[103,397,398],{"class":178},"    }\n",[103,400,402,405,407,410,413,416,419,422,425,428,430,433],{"class":105,"line":401},26,[103,403,404],{"class":185},"    Views",[103,406,189],{"class":134},[103,408,409],{"class":116}," Record",[103,411,412],{"class":134},"&",[103,414,415],{"class":116},"lt",[103,417,418],{"class":178},";",[103,420,421],{"class":185},"string",[103,423,424],{"class":178},",",[103,426,427],{"class":232}," never",[103,429,412],{"class":134},[103,431,432],{"class":185},"gt",[103,434,435],{"class":178},";\n",[103,437,439,442,444,446,448,450,452,454,456,458,460,462],{"class":105,"line":438},27,[103,440,441],{"class":185},"    Functions",[103,443,189],{"class":134},[103,445,409],{"class":116},[103,447,412],{"class":134},[103,449,415],{"class":116},[103,451,418],{"class":178},[103,453,421],{"class":185},[103,455,424],{"class":178},[103,457,427],{"class":232},[103,459,412],{"class":134},[103,461,432],{"class":185},[103,463,435],{"class":178},[103,465,467,470,472],{"class":105,"line":466},28,[103,468,469],{"class":185},"    Enums",[103,471,189],{"class":134},[103,473,179],{"class":178},[103,475,477,480,482,486,489,492,494,496,499,501,503,505,508],{"class":105,"line":476},29,[103,478,479],{"class":185},"      user_role",[103,481,189],{"class":134},[103,483,485],{"class":484},"sjJ54"," '",[103,487,488],{"class":120},"admin",[103,490,491],{"class":484},"'",[103,493,135],{"class":134},[103,495,485],{"class":484},[103,497,498],{"class":120},"manager",[103,500,491],{"class":484},[103,502,135],{"class":134},[103,504,485],{"class":484},[103,506,507],{"class":120},"staff",[103,509,510],{"class":484},"'\n",[103,512,514],{"class":105,"line":513},30,[103,515,398],{"class":178},[103,517,519],{"class":105,"line":518},31,[103,520,521],{"class":178},"  }\n",[103,523,525],{"class":105,"line":524},32,[103,526,527],{"class":178},"}\n",[63,529,530],{"id":530},"使用自動產生的類型",[46,532,534],{"className":153,"code":533,"language":155,"meta":55,"style":55},"import type { Database } from '~\u002Ftypes\u002Fdatabase.types'\n\n\u002F\u002F Client 端：使用泛型傳入 Database 類型\nconst client = useSupabaseClient&lt;Database&gt;()\n\nconst { data } = await client\n  .schema('public')\n  .from('users')\n  .select('id, name, email')\n\u002F\u002F data 的類型：{ id: string; name: string | null; email: string }[] | null\n\n\u002F\u002F 提取單表類型\ntype User = Database['public']['Tables']['users']['Row']\ntype UserInsert = Database['public']['Tables']['users']['Insert']\ntype UserRole = Database['public']['Enums']['user_role']\n",[53,535,536,563,569,574,607,611,630,652,670,688,693,697,702,753,799],{"__ignoreMap":55},[103,537,538,541,544,547,550,553,556,558,561],{"class":105,"line":106},[103,539,540],{"class":167},"import",[103,542,543],{"class":167}," type",[103,545,546],{"class":178}," {",[103,548,175],{"class":549},"su5hD",[103,551,552],{"class":178}," }",[103,554,555],{"class":167}," from",[103,557,485],{"class":484},[103,559,560],{"class":120},"~\u002Ftypes\u002Fdatabase.types",[103,562,510],{"class":484},[103,564,565],{"class":105,"line":113},[103,566,568],{"emptyLinePlaceholder":567},true,"\n",[103,570,571],{"class":105,"line":182},[103,572,573],{"class":109},"\u002F\u002F Client 端：使用泛型傳入 Database 類型\n",[103,575,576,579,583,586,589,591,593,595,598,600,602,604],{"class":105,"line":194},[103,577,578],{"class":171},"const",[103,580,582],{"class":581},"s_hVV"," client",[103,584,585],{"class":134}," =",[103,587,588],{"class":549}," useSupabaseClient",[103,590,412],{"class":134},[103,592,415],{"class":549},[103,594,418],{"class":178},[103,596,597],{"class":549},"Database",[103,599,412],{"class":134},[103,601,432],{"class":549},[103,603,418],{"class":178},[103,605,606],{"class":549},"()\n",[103,608,609],{"class":105,"line":204},[103,610,568],{"emptyLinePlaceholder":567},[103,612,613,615,617,620,622,624,627],{"class":105,"line":214},[103,614,578],{"class":171},[103,616,546],{"class":178},[103,618,619],{"class":581}," data",[103,621,552],{"class":178},[103,623,585],{"class":134},[103,625,626],{"class":167}," await",[103,628,629],{"class":549}," client\n",[103,631,632,635,639,642,644,647,649],{"class":105,"line":224},[103,633,634],{"class":178},"  .",[103,636,638],{"class":637},"sGLFI","schema",[103,640,641],{"class":549},"(",[103,643,491],{"class":484},[103,645,646],{"class":120},"public",[103,648,491],{"class":484},[103,650,651],{"class":549},")\n",[103,653,654,656,659,661,663,666,668],{"class":105,"line":236},[103,655,634],{"class":178},[103,657,658],{"class":637},"from",[103,660,641],{"class":549},[103,662,491],{"class":484},[103,664,665],{"class":120},"users",[103,667,491],{"class":484},[103,669,651],{"class":549},[103,671,672,674,677,679,681,684,686],{"class":105,"line":246},[103,673,634],{"class":178},[103,675,676],{"class":637},"select",[103,678,641],{"class":549},[103,680,491],{"class":484},[103,682,683],{"class":120},"id, name, email",[103,685,491],{"class":484},[103,687,651],{"class":549},[103,689,690],{"class":105,"line":262},[103,691,692],{"class":109},"\u002F\u002F data 的類型：{ id: string; name: string | null; email: string }[] | null\n",[103,694,695],{"class":105,"line":272},[103,696,568],{"emptyLinePlaceholder":567},[103,698,699],{"class":105,"line":278},[103,700,701],{"class":109},"\u002F\u002F 提取單表類型\n",[103,703,704,707,710,712,714,717,719,721,723,726,728,731,733,735,737,739,741,743,745,748,750],{"class":105,"line":288},[103,705,706],{"class":171},"type",[103,708,709],{"class":116}," User",[103,711,585],{"class":134},[103,713,175],{"class":116},[103,715,716],{"class":549},"[",[103,718,491],{"class":484},[103,720,646],{"class":120},[103,722,491],{"class":484},[103,724,725],{"class":549},"][",[103,727,491],{"class":484},[103,729,730],{"class":120},"Tables",[103,732,491],{"class":484},[103,734,725],{"class":549},[103,736,491],{"class":484},[103,738,665],{"class":120},[103,740,491],{"class":484},[103,742,725],{"class":549},[103,744,491],{"class":484},[103,746,747],{"class":120},"Row",[103,749,491],{"class":484},[103,751,752],{"class":549},"]\n",[103,754,755,757,760,762,764,766,768,770,772,774,776,778,780,782,784,786,788,790,792,795,797],{"class":105,"line":298},[103,756,706],{"class":171},[103,758,759],{"class":116}," UserInsert",[103,761,585],{"class":134},[103,763,175],{"class":116},[103,765,716],{"class":549},[103,767,491],{"class":484},[103,769,646],{"class":120},[103,771,491],{"class":484},[103,773,725],{"class":549},[103,775,491],{"class":484},[103,777,730],{"class":120},[103,779,491],{"class":484},[103,781,725],{"class":549},[103,783,491],{"class":484},[103,785,665],{"class":120},[103,787,491],{"class":484},[103,789,725],{"class":549},[103,791,491],{"class":484},[103,793,794],{"class":120},"Insert",[103,796,491],{"class":484},[103,798,752],{"class":549},[103,800,801,803,806,808,810,812,814,816,818,820,822,825,827,829,831,834,836],{"class":105,"line":307},[103,802,706],{"class":171},[103,804,805],{"class":116}," UserRole",[103,807,585],{"class":134},[103,809,175],{"class":116},[103,811,716],{"class":549},[103,813,491],{"class":484},[103,815,646],{"class":120},[103,817,491],{"class":484},[103,819,725],{"class":549},[103,821,491],{"class":484},[103,823,824],{"class":120},"Enums",[103,826,491],{"class":484},[103,828,725],{"class":549},[103,830,491],{"class":484},[103,832,833],{"class":120},"user_role",[103,835,491],{"class":484},[103,837,752],{"class":549},[63,839,841],{"id":840},"server-端使用類型","Server 端使用類型",[46,843,845],{"className":153,"code":844,"language":155,"meta":55,"style":55},"\u002F\u002F server\u002Futils\u002Fsupabase.ts\nimport { createClient, type SupabaseClient } from '@supabase\u002Fsupabase-js'\nimport type { Database } from '~~\u002Fapp\u002Ftypes\u002Fdatabase.types'\n\nlet serviceClient: SupabaseClient&lt;Database&gt; | null = null\n\nexport function getServerSupabaseClient(): SupabaseClient&lt;Database&gt; {\n  if (serviceClient) return serviceClient\n\n  const supabaseUrl = process.env.SUPABASE_URL\n  const serviceKey = process.env.SUPABASE_SECRET_KEY\n\n  if (!supabaseUrl || !serviceKey) {\n    throw createError({\n      statusCode: 500,\n      message: '伺服器設定錯誤：缺少 Supabase 環境變數',\n    })\n  }\n\n  serviceClient = createClient&lt;Database&gt;(supabaseUrl, serviceKey, {\n    auth: {\n      autoRefreshToken: false,\n      persistSession: false,\n    },\n  })\n\n  return serviceClient\n}\n",[53,846,847,852,879,900,904,940,944,977,998,1002,1026,1046,1050,1076,1088,1102,1118,1125,1129,1133,1168,1177,1190,1201,1206,1213,1217,1224],{"__ignoreMap":55},[103,848,849],{"class":105,"line":106},[103,850,851],{"class":109},"\u002F\u002F server\u002Futils\u002Fsupabase.ts\n",[103,853,854,856,858,861,863,865,868,870,872,874,877],{"class":105,"line":113},[103,855,540],{"class":167},[103,857,546],{"class":178},[103,859,860],{"class":549}," createClient",[103,862,424],{"class":178},[103,864,543],{"class":167},[103,866,867],{"class":549}," SupabaseClient",[103,869,552],{"class":178},[103,871,555],{"class":167},[103,873,485],{"class":484},[103,875,876],{"class":120},"@supabase\u002Fsupabase-js",[103,878,510],{"class":484},[103,880,881,883,885,887,889,891,893,895,898],{"class":105,"line":182},[103,882,540],{"class":167},[103,884,543],{"class":167},[103,886,546],{"class":178},[103,888,175],{"class":549},[103,890,552],{"class":178},[103,892,555],{"class":167},[103,894,485],{"class":484},[103,896,897],{"class":120},"~~\u002Fapp\u002Ftypes\u002Fdatabase.types",[103,899,510],{"class":484},[103,901,902],{"class":105,"line":194},[103,903,568],{"emptyLinePlaceholder":567},[103,905,906,909,912,914,916,918,920,922,924,926,928,930,932,936,938],{"class":105,"line":204},[103,907,908],{"class":171},"let",[103,910,911],{"class":549}," serviceClient",[103,913,189],{"class":134},[103,915,867],{"class":116},[103,917,412],{"class":134},[103,919,415],{"class":116},[103,921,418],{"class":178},[103,923,597],{"class":549},[103,925,412],{"class":134},[103,927,432],{"class":549},[103,929,418],{"class":178},[103,931,135],{"class":134},[103,933,935],{"class":934},"s39Yj"," null",[103,937,585],{"class":134},[103,939,259],{"class":934},[103,941,942],{"class":105,"line":214},[103,943,568],{"emptyLinePlaceholder":567},[103,945,946,948,951,954,957,959,961,963,965,967,969,971,973,975],{"class":105,"line":224},[103,947,168],{"class":167},[103,949,950],{"class":171}," function",[103,952,953],{"class":637}," getServerSupabaseClient",[103,955,956],{"class":178},"()",[103,958,189],{"class":134},[103,960,867],{"class":116},[103,962,412],{"class":134},[103,964,415],{"class":116},[103,966,418],{"class":178},[103,968,597],{"class":549},[103,970,412],{"class":134},[103,972,432],{"class":549},[103,974,418],{"class":178},[103,976,179],{"class":178},[103,978,979,982,986,989,992,995],{"class":105,"line":236},[103,980,981],{"class":167},"  if",[103,983,985],{"class":984},"skxfh"," (",[103,987,988],{"class":549},"serviceClient",[103,990,991],{"class":984},") ",[103,993,994],{"class":167},"return",[103,996,997],{"class":549}," serviceClient\n",[103,999,1000],{"class":105,"line":246},[103,1001,568],{"emptyLinePlaceholder":567},[103,1003,1004,1007,1010,1012,1015,1018,1021,1023],{"class":105,"line":262},[103,1005,1006],{"class":171},"  const",[103,1008,1009],{"class":581}," supabaseUrl",[103,1011,585],{"class":134},[103,1013,1014],{"class":549}," process",[103,1016,1017],{"class":178},".",[103,1019,1020],{"class":549},"env",[103,1022,1017],{"class":178},[103,1024,1025],{"class":581},"SUPABASE_URL\n",[103,1027,1028,1030,1033,1035,1037,1039,1041,1043],{"class":105,"line":272},[103,1029,1006],{"class":171},[103,1031,1032],{"class":581}," serviceKey",[103,1034,585],{"class":134},[103,1036,1014],{"class":549},[103,1038,1017],{"class":178},[103,1040,1020],{"class":549},[103,1042,1017],{"class":178},[103,1044,1045],{"class":581},"SUPABASE_SECRET_KEY\n",[103,1047,1048],{"class":105,"line":278},[103,1049,568],{"emptyLinePlaceholder":567},[103,1051,1052,1054,1056,1059,1062,1065,1068,1071,1073],{"class":105,"line":288},[103,1053,981],{"class":167},[103,1055,985],{"class":984},[103,1057,1058],{"class":134},"!",[103,1060,1061],{"class":549},"supabaseUrl",[103,1063,1064],{"class":134}," ||",[103,1066,1067],{"class":134}," !",[103,1069,1070],{"class":549},"serviceKey",[103,1072,991],{"class":984},[103,1074,1075],{"class":178},"{\n",[103,1077,1078,1081,1084,1086],{"class":105,"line":298},[103,1079,1080],{"class":167},"    throw",[103,1082,1083],{"class":637}," createError",[103,1085,641],{"class":984},[103,1087,1075],{"class":178},[103,1089,1090,1093,1095,1099],{"class":105,"line":307},[103,1091,1092],{"class":984},"      statusCode",[103,1094,189],{"class":178},[103,1096,1098],{"class":1097},"srdBf"," 500",[103,1100,1101],{"class":178},",\n",[103,1103,1104,1107,1109,1111,1114,1116],{"class":105,"line":320},[103,1105,1106],{"class":984},"      message",[103,1108,189],{"class":178},[103,1110,485],{"class":484},[103,1112,1113],{"class":120},"伺服器設定錯誤：缺少 Supabase 環境變數",[103,1115,491],{"class":484},[103,1117,1101],{"class":178},[103,1119,1120,1123],{"class":105,"line":329},[103,1121,1122],{"class":178},"    }",[103,1124,651],{"class":984},[103,1126,1127],{"class":105,"line":334},[103,1128,521],{"class":178},[103,1130,1131],{"class":105,"line":344},[103,1132,568],{"emptyLinePlaceholder":567},[103,1134,1135,1138,1140,1142,1144,1146,1148,1150,1152,1154,1156,1158,1160,1162,1164,1166],{"class":105,"line":353},[103,1136,1137],{"class":549},"  serviceClient",[103,1139,585],{"class":134},[103,1141,860],{"class":549},[103,1143,412],{"class":134},[103,1145,415],{"class":549},[103,1147,418],{"class":178},[103,1149,597],{"class":549},[103,1151,412],{"class":134},[103,1153,432],{"class":549},[103,1155,418],{"class":178},[103,1157,641],{"class":984},[103,1159,1061],{"class":549},[103,1161,424],{"class":178},[103,1163,1032],{"class":549},[103,1165,424],{"class":178},[103,1167,179],{"class":178},[103,1169,1170,1173,1175],{"class":105,"line":362},[103,1171,1172],{"class":984},"    auth",[103,1174,189],{"class":178},[103,1176,179],{"class":178},[103,1178,1179,1182,1184,1188],{"class":105,"line":375},[103,1180,1181],{"class":984},"      autoRefreshToken",[103,1183,189],{"class":178},[103,1185,1187],{"class":1186},"syTEX"," false",[103,1189,1101],{"class":178},[103,1191,1192,1195,1197,1199],{"class":105,"line":384},[103,1193,1194],{"class":984},"      persistSession",[103,1196,189],{"class":178},[103,1198,1187],{"class":1186},[103,1200,1101],{"class":178},[103,1202,1203],{"class":105,"line":389},[103,1204,1205],{"class":178},"    },\n",[103,1207,1208,1211],{"class":105,"line":395},[103,1209,1210],{"class":178},"  }",[103,1212,651],{"class":984},[103,1214,1215],{"class":105,"line":401},[103,1216,568],{"emptyLinePlaceholder":567},[103,1218,1219,1222],{"class":105,"line":438},[103,1220,1221],{"class":167},"  return",[103,1223,997],{"class":549},[103,1225,1226],{"class":105,"line":466},[103,1227,527],{"class":178},[40,1229],{},[11,1231,1233],{"id":1232},"vue-元件類型定義","Vue 元件類型定義",[63,1235,1237],{"id":1236},"props-最佳實踐","Props 最佳實踐",[46,1239,1243],{"className":1240,"code":1241,"language":1242,"meta":55,"style":55},"language-vue shiki shiki-themes material-theme-lighter github-light github-dark","&lt;script setup lang=\"ts\"&gt; \u002F\u002F ✅ 推薦：使用 interface 定義 Props interface\nProps { userId: string showAvatar?: boolean role: 'admin' | 'manager' | 'staff'\n} \u002F\u002F 不需要 const props =，除非在 script 中使用 defineProps&lt;Props&gt;() \u002F\u002F\n如果需要在 script 中使用 props const props = defineProps&lt;Props&gt;()\nconsole.log(props.userId) &lt;\u002Fscript&gt;\n","vue",[53,1244,1245,1250,1255,1260,1265],{"__ignoreMap":55},[103,1246,1247],{"class":105,"line":106},[103,1248,1249],{"class":549},"&lt;script setup lang=\"ts\"&gt; \u002F\u002F ✅ 推薦：使用 interface 定義 Props interface\n",[103,1251,1252],{"class":105,"line":113},[103,1253,1254],{"class":549},"Props { userId: string showAvatar?: boolean role: 'admin' | 'manager' | 'staff'\n",[103,1256,1257],{"class":105,"line":182},[103,1258,1259],{"class":549},"} \u002F\u002F 不需要 const props =，除非在 script 中使用 defineProps&lt;Props&gt;() \u002F\u002F\n",[103,1261,1262],{"class":105,"line":194},[103,1263,1264],{"class":549},"如果需要在 script 中使用 props const props = defineProps&lt;Props&gt;()\n",[103,1266,1267],{"class":105,"line":204},[103,1268,1269],{"class":549},"console.log(props.userId) &lt;\u002Fscript&gt;\n",[63,1271,1273],{"id":1272},"props-解構與預設值","Props 解構與預設值",[46,1275,1277],{"className":1240,"code":1276,"language":1242,"meta":55,"style":55},"&lt;script setup lang=\"ts\"&gt; interface Props { title: string count?: number\nitems?: string[] } \u002F\u002F Vue 3.5+ 可以直接解構並設定預設值 const { title, count =\n0, items = [] } = defineProps&lt;Props&gt;() &lt;\u002Fscript&gt;\n",[53,1278,1279,1284,1289],{"__ignoreMap":55},[103,1280,1281],{"class":105,"line":106},[103,1282,1283],{"class":549},"&lt;script setup lang=\"ts\"&gt; interface Props { title: string count?: number\n",[103,1285,1286],{"class":105,"line":113},[103,1287,1288],{"class":549},"items?: string[] } \u002F\u002F Vue 3.5+ 可以直接解構並設定預設值 const { title, count =\n",[103,1290,1291],{"class":105,"line":182},[103,1292,1293],{"class":549},"0, items = [] } = defineProps&lt;Props&gt;() &lt;\u002Fscript&gt;\n",[63,1295,1297],{"id":1296},"emits-類型定義","Emits 類型定義",[46,1299,1301],{"className":1240,"code":1300,"language":1242,"meta":55,"style":55},"&lt;script setup lang=\"ts\"&gt; \u002F\u002F ✅ 推薦：使用元組語法定義參數 const emit =\ndefineEmits&lt;{ update: [value: string] delete: [id: string, reason?: string]\nclose: [] \u002F\u002F 無參數事件 }&gt;() \u002F\u002F 使用時有完整類型檢查 emit('update', 'new\nvalue') \u002F\u002F ✅ emit('update', 123) \u002F\u002F ❌ 類型錯誤 emit('delete', 'id-123') \u002F\u002F ✅\nemit('close') \u002F\u002F ✅ &lt;\u002Fscript&gt;\n",[53,1302,1303,1308,1313,1318,1323],{"__ignoreMap":55},[103,1304,1305],{"class":105,"line":106},[103,1306,1307],{"class":549},"&lt;script setup lang=\"ts\"&gt; \u002F\u002F ✅ 推薦：使用元組語法定義參數 const emit =\n",[103,1309,1310],{"class":105,"line":113},[103,1311,1312],{"class":549},"defineEmits&lt;{ update: [value: string] delete: [id: string, reason?: string]\n",[103,1314,1315],{"class":105,"line":182},[103,1316,1317],{"class":549},"close: [] \u002F\u002F 無參數事件 }&gt;() \u002F\u002F 使用時有完整類型檢查 emit('update', 'new\n",[103,1319,1320],{"class":105,"line":194},[103,1321,1322],{"class":549},"value') \u002F\u002F ✅ emit('update', 123) \u002F\u002F ❌ 類型錯誤 emit('delete', 'id-123') \u002F\u002F ✅\n",[103,1324,1325],{"class":105,"line":204},[103,1326,1327],{"class":549},"emit('close') \u002F\u002F ✅ &lt;\u002Fscript&gt;\n",[63,1329,1331],{"id":1330},"definemodel-雙向綁定","defineModel 雙向綁定",[46,1333,1335],{"className":1240,"code":1334,"language":1242,"meta":55,"style":55},"&lt;script setup lang=\"ts\"&gt; \u002F\u002F 基本用法 const modelValue =\ndefineModel&lt;string&gt;() \u002F\u002F 具名 v-model const firstName =\ndefineModel&lt;string&gt;('firstName') const lastName =\ndefineModel&lt;string&gt;('lastName') \u002F\u002F 帶預設值 const count =\ndefineModel&lt;number&gt;('count', { default: 0 }) &lt;\u002Fscript&gt; &lt;!--\n父元件使用 --&gt; &lt;UserForm v-model:first-name=\"user.firstName\"\nv-model:last-name=\"user.lastName\" \u002F&gt;\n",[53,1336,1337,1342,1347,1352,1357,1362,1367],{"__ignoreMap":55},[103,1338,1339],{"class":105,"line":106},[103,1340,1341],{"class":549},"&lt;script setup lang=\"ts\"&gt; \u002F\u002F 基本用法 const modelValue =\n",[103,1343,1344],{"class":105,"line":113},[103,1345,1346],{"class":549},"defineModel&lt;string&gt;() \u002F\u002F 具名 v-model const firstName =\n",[103,1348,1349],{"class":105,"line":182},[103,1350,1351],{"class":549},"defineModel&lt;string&gt;('firstName') const lastName =\n",[103,1353,1354],{"class":105,"line":194},[103,1355,1356],{"class":549},"defineModel&lt;string&gt;('lastName') \u002F\u002F 帶預設值 const count =\n",[103,1358,1359],{"class":105,"line":204},[103,1360,1361],{"class":549},"defineModel&lt;number&gt;('count', { default: 0 }) &lt;\u002Fscript&gt; &lt;!--\n",[103,1363,1364],{"class":105,"line":214},[103,1365,1366],{"class":549},"父元件使用 --&gt; &lt;UserForm v-model:first-name=\"user.firstName\"\n",[103,1368,1369],{"class":105,"line":224},[103,1370,1371],{"class":549},"v-model:last-name=\"user.lastName\" \u002F&gt;\n",[40,1373],{},[11,1375,1376],{"id":1376},"全域類型定義",[63,1378,1380],{"id":1379},"sharedtypes-目錄結構","shared\u002Ftypes 目錄結構",[46,1382,1385],{"className":1383,"code":1384,"language":51},[49],"shared\u002F\n└── types\u002F\n    └── index.d.ts    # 全域類型宣告\n",[53,1386,1384],{"__ignoreMap":55},[63,1388,1390],{"id":1389},"maybe-類型處理-nullundefined","Maybe 類型處理 null\u002Fundefined",[46,1392,1394],{"className":153,"code":1393,"language":155,"meta":55,"style":55},"\u002F\u002F shared\u002Ftypes\u002Findex.d.ts\ndeclare global {\n  \u002F**\n   * 表示可能為 null 或 undefined 的值\n   * 常用於 API 回應或可選欄位\n   *\u002F\n  type Maybe&lt;T&gt; = T | null | undefined\n}\n\nexport {}  \u002F\u002F 確保這是一個模組\n",[53,1395,1396,1401,1411,1416,1421,1426,1431,1467,1471,1475],{"__ignoreMap":55},[103,1397,1398],{"class":105,"line":106},[103,1399,1400],{"class":109},"\u002F\u002F shared\u002Ftypes\u002Findex.d.ts\n",[103,1402,1403,1406,1409],{"class":105,"line":113},[103,1404,1405],{"class":171},"declare",[103,1407,1408],{"class":549}," global ",[103,1410,1075],{"class":178},[103,1412,1413],{"class":105,"line":182},[103,1414,1415],{"class":109},"  \u002F**\n",[103,1417,1418],{"class":105,"line":194},[103,1419,1420],{"class":109},"   * 表示可能為 null 或 undefined 的值\n",[103,1422,1423],{"class":105,"line":204},[103,1424,1425],{"class":109},"   * 常用於 API 回應或可選欄位\n",[103,1427,1428],{"class":105,"line":214},[103,1429,1430],{"class":109},"   *\u002F\n",[103,1432,1433,1436,1439,1442,1444,1447,1449,1451,1453,1455,1458,1460,1462,1464],{"class":105,"line":224},[103,1434,1435],{"class":171},"  type",[103,1437,1438],{"class":116}," Maybe",[103,1440,1441],{"class":984},"&lt",[103,1443,418],{"class":178},[103,1445,1446],{"class":581},"T",[103,1448,412],{"class":134},[103,1450,432],{"class":549},[103,1452,418],{"class":178},[103,1454,585],{"class":134},[103,1456,1457],{"class":581}," T",[103,1459,135],{"class":134},[103,1461,935],{"class":934},[103,1463,135],{"class":134},[103,1465,1466],{"class":934}," undefined\n",[103,1468,1469],{"class":105,"line":236},[103,1470,527],{"class":178},[103,1472,1473],{"class":105,"line":246},[103,1474,568],{"emptyLinePlaceholder":567},[103,1476,1477,1479,1482],{"class":105,"line":262},[103,1478,168],{"class":167},[103,1480,1481],{"class":178}," {}",[103,1483,1484],{"class":109},"  \u002F\u002F 確保這是一個模組\n",[63,1486,1488],{"id":1487},"使用-maybe-類型","使用 Maybe 類型",[46,1490,1492],{"className":153,"code":1491,"language":155,"meta":55,"style":55},"\u002F\u002F 處理可能為空的資料\nfunction processUser(user: Maybe&lt;User&gt;): string | null {\n  if (!user) return null\n  return user.name\n}\n\n\u002F\u002F API 回應類型\ninterface ApiResponse&lt;T&gt; {\n  data: Maybe&lt;T&gt;\n  error: Maybe&lt;string&gt;\n}\n\n\u002F\u002F 搭配 Optional Chaining\nconst userName = user?.name ?? '未知使用者'\n",[53,1493,1494,1499,1545,1561,1573,1577,1581,1586,1611,1634,1657,1661,1665,1670],{"__ignoreMap":55},[103,1495,1496],{"class":105,"line":106},[103,1497,1498],{"class":109},"\u002F\u002F 處理可能為空的資料\n",[103,1500,1501,1504,1507,1509,1513,1515,1517,1519,1521,1523,1526,1528,1530,1532,1535,1537,1539,1541,1543],{"class":105,"line":113},[103,1502,1503],{"class":171},"function",[103,1505,1506],{"class":637}," processUser",[103,1508,641],{"class":178},[103,1510,1512],{"class":1511},"s99_P","user",[103,1514,189],{"class":134},[103,1516,1438],{"class":116},[103,1518,412],{"class":134},[103,1520,415],{"class":116},[103,1522,418],{"class":549},[103,1524,1525],{"class":116},"User",[103,1527,412],{"class":134},[103,1529,432],{"class":116},[103,1531,418],{"class":549},[103,1533,1534],{"class":178},")",[103,1536,189],{"class":134},[103,1538,254],{"class":232},[103,1540,135],{"class":134},[103,1542,935],{"class":232},[103,1544,179],{"class":178},[103,1546,1547,1549,1551,1553,1555,1557,1559],{"class":105,"line":182},[103,1548,981],{"class":167},[103,1550,985],{"class":984},[103,1552,1058],{"class":134},[103,1554,1512],{"class":549},[103,1556,991],{"class":984},[103,1558,994],{"class":167},[103,1560,259],{"class":934},[103,1562,1563,1565,1568,1570],{"class":105,"line":194},[103,1564,1221],{"class":167},[103,1566,1567],{"class":549}," user",[103,1569,1017],{"class":178},[103,1571,1572],{"class":549},"name\n",[103,1574,1575],{"class":105,"line":204},[103,1576,527],{"class":178},[103,1578,1579],{"class":105,"line":214},[103,1580,568],{"emptyLinePlaceholder":567},[103,1582,1583],{"class":105,"line":224},[103,1584,1585],{"class":109},"\u002F\u002F API 回應類型\n",[103,1587,1588,1591,1594,1596,1598,1600,1602,1604,1606,1609],{"class":105,"line":236},[103,1589,1590],{"class":171},"interface",[103,1592,1593],{"class":116}," ApiResponse",[103,1595,412],{"class":549},[103,1597,415],{"class":116},[103,1599,418],{"class":549},[103,1601,1446],{"class":116},[103,1603,412],{"class":549},[103,1605,432],{"class":116},[103,1607,1608],{"class":549},"; ",[103,1610,1075],{"class":178},[103,1612,1613,1616,1618,1620,1622,1624,1626,1628,1630,1632],{"class":105,"line":246},[103,1614,1615],{"class":185},"  data",[103,1617,189],{"class":134},[103,1619,1438],{"class":116},[103,1621,412],{"class":134},[103,1623,415],{"class":116},[103,1625,418],{"class":178},[103,1627,1446],{"class":581},[103,1629,412],{"class":134},[103,1631,432],{"class":185},[103,1633,435],{"class":178},[103,1635,1636,1639,1641,1643,1645,1647,1649,1651,1653,1655],{"class":105,"line":262},[103,1637,1638],{"class":185},"  error",[103,1640,189],{"class":134},[103,1642,1438],{"class":116},[103,1644,412],{"class":134},[103,1646,415],{"class":116},[103,1648,418],{"class":178},[103,1650,421],{"class":549},[103,1652,412],{"class":134},[103,1654,432],{"class":185},[103,1656,435],{"class":178},[103,1658,1659],{"class":105,"line":272},[103,1660,527],{"class":178},[103,1662,1663],{"class":105,"line":278},[103,1664,568],{"emptyLinePlaceholder":567},[103,1666,1667],{"class":105,"line":288},[103,1668,1669],{"class":109},"\u002F\u002F 搭配 Optional Chaining\n",[103,1671,1672,1674,1677,1679,1681,1684,1687,1690,1692,1695],{"class":105,"line":298},[103,1673,578],{"class":171},[103,1675,1676],{"class":581}," userName",[103,1678,585],{"class":134},[103,1680,1567],{"class":549},[103,1682,1683],{"class":178},"?.",[103,1685,1686],{"class":549},"name ",[103,1688,1689],{"class":134},"??",[103,1691,485],{"class":484},[103,1693,1694],{"class":120},"未知使用者",[103,1696,510],{"class":484},[40,1698],{},[11,1700,1702],{"id":1701},"server-api-類型定義","Server API 類型定義",[63,1704,1705],{"id":1705},"請求驗證與類型",[46,1707,1709],{"className":153,"code":1708,"language":155,"meta":55,"style":55},"\u002F\u002F server\u002Fapi\u002Fv1\u002Fusers\u002Findex.post.ts\nimport { z } from 'zod'\n\n\u002F\u002F 定義請求 Schema（同時是類型和驗證器）\nconst createUserSchema = z.object({\n  email: z.string().email('請輸入有效的 Email'),\n  name: z.string().min(2, '名稱至少 2 個字元'),\n  role: z.enum(['admin', 'manager', 'staff']),\n})\n\n\u002F\u002F 從 Schema 推導類型\ntype CreateUserRequest = z.infer&lt;typeof createUserSchema&gt;\n\nexport default defineEventHandler(async (event) =&gt; {\n  const body = await readBody(event)\n\n  \u002F\u002F 驗證並取得類型安全的資料\n  const validated = createUserSchema.parse(body)\n  \u002F\u002F validated 的類型：{ email: string; name: string; role: 'admin' | 'manager' | 'staff' }\n\n  \u002F\u002F ... 處理邏輯\n})\n",[53,1710,1711,1716,1736,1740,1745,1765,1798,1836,1880,1887,1891,1896,1929,1933,1959,1975,1979,1984,2001,2006,2010,2015],{"__ignoreMap":55},[103,1712,1713],{"class":105,"line":106},[103,1714,1715],{"class":109},"\u002F\u002F server\u002Fapi\u002Fv1\u002Fusers\u002Findex.post.ts\n",[103,1717,1718,1720,1722,1725,1727,1729,1731,1734],{"class":105,"line":113},[103,1719,540],{"class":167},[103,1721,546],{"class":178},[103,1723,1724],{"class":549}," z",[103,1726,552],{"class":178},[103,1728,555],{"class":167},[103,1730,485],{"class":484},[103,1732,1733],{"class":120},"zod",[103,1735,510],{"class":484},[103,1737,1738],{"class":105,"line":182},[103,1739,568],{"emptyLinePlaceholder":567},[103,1741,1742],{"class":105,"line":194},[103,1743,1744],{"class":109},"\u002F\u002F 定義請求 Schema（同時是類型和驗證器）\n",[103,1746,1747,1749,1752,1754,1756,1758,1761,1763],{"class":105,"line":204},[103,1748,578],{"class":171},[103,1750,1751],{"class":581}," createUserSchema",[103,1753,585],{"class":134},[103,1755,1724],{"class":549},[103,1757,1017],{"class":178},[103,1759,1760],{"class":637},"object",[103,1762,641],{"class":549},[103,1764,1075],{"class":178},[103,1766,1767,1770,1772,1774,1776,1778,1780,1782,1785,1787,1789,1792,1794,1796],{"class":105,"line":214},[103,1768,1769],{"class":984},"  email",[103,1771,189],{"class":178},[103,1773,1724],{"class":549},[103,1775,1017],{"class":178},[103,1777,421],{"class":637},[103,1779,956],{"class":549},[103,1781,1017],{"class":178},[103,1783,1784],{"class":637},"email",[103,1786,641],{"class":549},[103,1788,491],{"class":484},[103,1790,1791],{"class":120},"請輸入有效的 Email",[103,1793,491],{"class":484},[103,1795,1534],{"class":549},[103,1797,1101],{"class":178},[103,1799,1800,1803,1805,1807,1809,1811,1813,1815,1818,1820,1823,1825,1827,1830,1832,1834],{"class":105,"line":224},[103,1801,1802],{"class":984},"  name",[103,1804,189],{"class":178},[103,1806,1724],{"class":549},[103,1808,1017],{"class":178},[103,1810,421],{"class":637},[103,1812,956],{"class":549},[103,1814,1017],{"class":178},[103,1816,1817],{"class":637},"min",[103,1819,641],{"class":549},[103,1821,1822],{"class":1097},"2",[103,1824,424],{"class":178},[103,1826,485],{"class":484},[103,1828,1829],{"class":120},"名稱至少 2 個字元",[103,1831,491],{"class":484},[103,1833,1534],{"class":549},[103,1835,1101],{"class":178},[103,1837,1838,1841,1843,1845,1847,1850,1853,1855,1857,1859,1861,1863,1865,1867,1869,1871,1873,1875,1878],{"class":105,"line":236},[103,1839,1840],{"class":984},"  role",[103,1842,189],{"class":178},[103,1844,1724],{"class":549},[103,1846,1017],{"class":178},[103,1848,1849],{"class":637},"enum",[103,1851,1852],{"class":549},"([",[103,1854,491],{"class":484},[103,1856,488],{"class":120},[103,1858,491],{"class":484},[103,1860,424],{"class":178},[103,1862,485],{"class":484},[103,1864,498],{"class":120},[103,1866,491],{"class":484},[103,1868,424],{"class":178},[103,1870,485],{"class":484},[103,1872,507],{"class":120},[103,1874,491],{"class":484},[103,1876,1877],{"class":549},"])",[103,1879,1101],{"class":178},[103,1881,1882,1885],{"class":105,"line":246},[103,1883,1884],{"class":178},"}",[103,1886,651],{"class":549},[103,1888,1889],{"class":105,"line":262},[103,1890,568],{"emptyLinePlaceholder":567},[103,1892,1893],{"class":105,"line":272},[103,1894,1895],{"class":109},"\u002F\u002F 從 Schema 推導類型\n",[103,1897,1898,1900,1903,1905,1907,1909,1912,1914,1916,1918,1921,1923,1925,1927],{"class":105,"line":278},[103,1899,706],{"class":171},[103,1901,1902],{"class":116}," CreateUserRequest",[103,1904,585],{"class":134},[103,1906,1724],{"class":116},[103,1908,1017],{"class":178},[103,1910,1911],{"class":116},"infer",[103,1913,412],{"class":134},[103,1915,415],{"class":116},[103,1917,418],{"class":178},[103,1919,1920],{"class":134},"typeof",[103,1922,1751],{"class":549},[103,1924,412],{"class":134},[103,1926,432],{"class":549},[103,1928,435],{"class":178},[103,1930,1931],{"class":105,"line":288},[103,1932,568],{"emptyLinePlaceholder":567},[103,1934,1935,1937,1940,1943,1945,1948,1951,1954,1957],{"class":105,"line":298},[103,1936,168],{"class":167},[103,1938,1939],{"class":167}," default",[103,1941,1942],{"class":637}," defineEventHandler",[103,1944,641],{"class":549},[103,1946,1947],{"class":637},"async",[103,1949,1950],{"class":549}," (event) ",[103,1952,1953],{"class":134},"=&",[103,1955,1956],{"class":549},"gt; ",[103,1958,1075],{"class":178},[103,1960,1961,1964,1967,1969,1972],{"class":105,"line":307},[103,1962,1963],{"class":549},"  const body ",[103,1965,1966],{"class":134},"=",[103,1968,626],{"class":167},[103,1970,1971],{"class":637}," readBody",[103,1973,1974],{"class":549},"(event)\n",[103,1976,1977],{"class":105,"line":320},[103,1978,568],{"emptyLinePlaceholder":567},[103,1980,1981],{"class":105,"line":329},[103,1982,1983],{"class":109},"  \u002F\u002F 驗證並取得類型安全的資料\n",[103,1985,1986,1989,1991,1993,1995,1998],{"class":105,"line":334},[103,1987,1988],{"class":549},"  const validated ",[103,1990,1966],{"class":134},[103,1992,1751],{"class":549},[103,1994,1017],{"class":178},[103,1996,1997],{"class":637},"parse",[103,1999,2000],{"class":549},"(body)\n",[103,2002,2003],{"class":105,"line":344},[103,2004,2005],{"class":109},"  \u002F\u002F validated 的類型：{ email: string; name: string; role: 'admin' | 'manager' | 'staff' }\n",[103,2007,2008],{"class":105,"line":353},[103,2009,568],{"emptyLinePlaceholder":567},[103,2011,2012],{"class":105,"line":362},[103,2013,2014],{"class":109},"  \u002F\u002F ... 處理邏輯\n",[103,2016,2017,2019],{"class":105,"line":375},[103,2018,1884],{"class":178},[103,2020,651],{"class":549},[63,2022,2023],{"id":2023},"回應類型定義",[46,2025,2027],{"className":153,"code":2026,"language":155,"meta":55,"style":55},"\u002F\u002F server\u002Ftypes\u002Fapi.d.ts\nexport interface PaginatedResponse&lt;T&gt; {\n  data: T[]\n  pagination: {\n    page: number\n    pageSize: number\n    total: number\n    totalPages: number\n  }\n}\n\nexport interface SingleResponse&lt;T&gt; {\n  data: T\n}\n\n\u002F\u002F 使用範例\nexport default defineEventHandler(async (event): Promise&lt;PaginatedResponse&lt;User&gt;&gt; =&gt; {\n  \u002F\u002F ... 查詢邏輯\n  return {\n    data: users,\n    pagination: { page: 1, pageSize: 10, total: 100, totalPages: 10 },\n  }\n})\n",[53,2028,2029,2034,2059,2070,2079,2089,2098,2107,2116,2120,2124,2128,2153,2162,2166,2170,2175,2218,2223,2230,2242,2291,2295],{"__ignoreMap":55},[103,2030,2031],{"class":105,"line":106},[103,2032,2033],{"class":109},"\u002F\u002F server\u002Ftypes\u002Fapi.d.ts\n",[103,2035,2036,2038,2040,2043,2045,2047,2049,2051,2053,2055,2057],{"class":105,"line":113},[103,2037,168],{"class":167},[103,2039,172],{"class":171},[103,2041,2042],{"class":116}," PaginatedResponse",[103,2044,412],{"class":549},[103,2046,415],{"class":116},[103,2048,418],{"class":549},[103,2050,1446],{"class":116},[103,2052,412],{"class":549},[103,2054,432],{"class":116},[103,2056,1608],{"class":549},[103,2058,1075],{"class":178},[103,2060,2061,2063,2065,2067],{"class":105,"line":182},[103,2062,1615],{"class":185},[103,2064,189],{"class":134},[103,2066,1457],{"class":116},[103,2068,2069],{"class":549},"[]\n",[103,2071,2072,2075,2077],{"class":105,"line":194},[103,2073,2074],{"class":185},"  pagination",[103,2076,189],{"class":134},[103,2078,179],{"class":178},[103,2080,2081,2084,2086],{"class":105,"line":204},[103,2082,2083],{"class":185},"    page",[103,2085,189],{"class":134},[103,2087,2088],{"class":232}," number\n",[103,2090,2091,2094,2096],{"class":105,"line":214},[103,2092,2093],{"class":185},"    pageSize",[103,2095,189],{"class":134},[103,2097,2088],{"class":232},[103,2099,2100,2103,2105],{"class":105,"line":224},[103,2101,2102],{"class":185},"    total",[103,2104,189],{"class":134},[103,2106,2088],{"class":232},[103,2108,2109,2112,2114],{"class":105,"line":236},[103,2110,2111],{"class":185},"    totalPages",[103,2113,189],{"class":134},[103,2115,2088],{"class":232},[103,2117,2118],{"class":105,"line":246},[103,2119,521],{"class":178},[103,2121,2122],{"class":105,"line":262},[103,2123,527],{"class":178},[103,2125,2126],{"class":105,"line":272},[103,2127,568],{"emptyLinePlaceholder":567},[103,2129,2130,2132,2134,2137,2139,2141,2143,2145,2147,2149,2151],{"class":105,"line":278},[103,2131,168],{"class":167},[103,2133,172],{"class":171},[103,2135,2136],{"class":116}," SingleResponse",[103,2138,412],{"class":549},[103,2140,415],{"class":116},[103,2142,418],{"class":549},[103,2144,1446],{"class":116},[103,2146,412],{"class":549},[103,2148,432],{"class":116},[103,2150,1608],{"class":549},[103,2152,1075],{"class":178},[103,2154,2155,2157,2159],{"class":105,"line":288},[103,2156,1615],{"class":185},[103,2158,189],{"class":134},[103,2160,2161],{"class":116}," T\n",[103,2163,2164],{"class":105,"line":298},[103,2165,527],{"class":178},[103,2167,2168],{"class":105,"line":307},[103,2169,568],{"emptyLinePlaceholder":567},[103,2171,2172],{"class":105,"line":320},[103,2173,2174],{"class":109},"\u002F\u002F 使用範例\n",[103,2176,2177,2179,2181,2183,2185,2187,2190,2193,2195,2198,2200,2203,2205,2208,2210,2212,2214,2216],{"class":105,"line":329},[103,2178,168],{"class":167},[103,2180,1939],{"class":167},[103,2182,1942],{"class":637},[103,2184,641],{"class":549},[103,2186,1947],{"class":637},[103,2188,2189],{"class":549}," (event): ",[103,2191,2192],{"class":232},"Promise",[103,2194,412],{"class":134},[103,2196,2197],{"class":549},"lt;PaginatedResponse",[103,2199,412],{"class":134},[103,2201,2202],{"class":549},"lt;User",[103,2204,412],{"class":134},[103,2206,2207],{"class":549},"gt;",[103,2209,412],{"class":134},[103,2211,1956],{"class":549},[103,2213,1953],{"class":134},[103,2215,1956],{"class":549},[103,2217,1075],{"class":178},[103,2219,2220],{"class":105,"line":334},[103,2221,2222],{"class":109},"  \u002F\u002F ... 查詢邏輯\n",[103,2224,2225,2228],{"class":105,"line":344},[103,2226,2227],{"class":549},"  return ",[103,2229,1075],{"class":178},[103,2231,2232,2235,2237,2240],{"class":105,"line":353},[103,2233,2234],{"class":116},"    data",[103,2236,189],{"class":178},[103,2238,2239],{"class":549}," users",[103,2241,1101],{"class":178},[103,2243,2244,2247,2249,2251,2254,2256,2259,2261,2264,2266,2269,2271,2274,2276,2279,2281,2284,2286,2288],{"class":105,"line":362},[103,2245,2246],{"class":116},"    pagination",[103,2248,189],{"class":178},[103,2250,546],{"class":178},[103,2252,2253],{"class":116}," page",[103,2255,189],{"class":178},[103,2257,2258],{"class":1097}," 1",[103,2260,424],{"class":178},[103,2262,2263],{"class":116}," pageSize",[103,2265,189],{"class":178},[103,2267,2268],{"class":1097}," 10",[103,2270,424],{"class":178},[103,2272,2273],{"class":116}," total",[103,2275,189],{"class":178},[103,2277,2278],{"class":1097}," 100",[103,2280,424],{"class":178},[103,2282,2283],{"class":116}," totalPages",[103,2285,189],{"class":178},[103,2287,2268],{"class":1097},[103,2289,2290],{"class":178}," },\n",[103,2292,2293],{"class":105,"line":375},[103,2294,521],{"class":178},[103,2296,2297,2299],{"class":105,"line":384},[103,2298,1884],{"class":178},[103,2300,651],{"class":549},[40,2302],{},[11,2304,2305],{"id":2305},"踩坑經驗",[63,2307,2309],{"id":2308},"類型不一致的-runtime-error","類型不一致的 Runtime Error",[15,2311,2312,2315],{},[19,2313,2314],{},"問題","：TypeScript 編譯通過，但執行時出現 undefined 錯誤。",[15,2317,2318,2321],{},[19,2319,2320],{},"原因","：類型定義與實際資料不符。",[46,2323,2325],{"className":153,"code":2324,"language":155,"meta":55,"style":55},"\u002F\u002F ❌ 錯誤：假設所有欄位都存在\ninterface User {\n  id: string;\n  name: string; \u002F\u002F 實際上資料庫允許 null\n  email: string;\n}\n\n\u002F\u002F API 回傳 { id: '1', name: null, email: 'test@example.com' }\nconst user = await fetchUser();\nconsole.log(user.name.toUpperCase()); \u002F\u002F Runtime Error: Cannot read property 'toUpperCase' of null\n",[53,2326,2327,2332,2340,2351,2364,2374,2378,2382,2387,2404],{"__ignoreMap":55},[103,2328,2329],{"class":105,"line":106},[103,2330,2331],{"class":109},"\u002F\u002F ❌ 錯誤：假設所有欄位都存在\n",[103,2333,2334,2336,2338],{"class":105,"line":113},[103,2335,1590],{"class":171},[103,2337,709],{"class":116},[103,2339,179],{"class":178},[103,2341,2342,2345,2347,2349],{"class":105,"line":182},[103,2343,2344],{"class":185},"  id",[103,2346,189],{"class":134},[103,2348,254],{"class":232},[103,2350,435],{"class":178},[103,2352,2353,2355,2357,2359,2361],{"class":105,"line":194},[103,2354,1802],{"class":185},[103,2356,189],{"class":134},[103,2358,254],{"class":232},[103,2360,418],{"class":178},[103,2362,2363],{"class":109}," \u002F\u002F 實際上資料庫允許 null\n",[103,2365,2366,2368,2370,2372],{"class":105,"line":204},[103,2367,1769],{"class":185},[103,2369,189],{"class":134},[103,2371,254],{"class":232},[103,2373,435],{"class":178},[103,2375,2376],{"class":105,"line":214},[103,2377,527],{"class":178},[103,2379,2380],{"class":105,"line":224},[103,2381,568],{"emptyLinePlaceholder":567},[103,2383,2384],{"class":105,"line":236},[103,2385,2386],{"class":109},"\u002F\u002F API 回傳 { id: '1', name: null, email: 'test@example.com' }\n",[103,2388,2389,2391,2393,2395,2397,2400,2402],{"class":105,"line":246},[103,2390,578],{"class":171},[103,2392,1567],{"class":581},[103,2394,585],{"class":134},[103,2396,626],{"class":167},[103,2398,2399],{"class":637}," fetchUser",[103,2401,956],{"class":549},[103,2403,435],{"class":178},[103,2405,2406,2409,2411,2414,2417,2419,2422,2424,2427,2430,2432],{"class":105,"line":262},[103,2407,2408],{"class":549},"console",[103,2410,1017],{"class":178},[103,2412,2413],{"class":637},"log",[103,2415,2416],{"class":549},"(user",[103,2418,1017],{"class":178},[103,2420,2421],{"class":549},"name",[103,2423,1017],{"class":178},[103,2425,2426],{"class":637},"toUpperCase",[103,2428,2429],{"class":549},"())",[103,2431,418],{"class":178},[103,2433,2434],{"class":109}," \u002F\u002F Runtime Error: Cannot read property 'toUpperCase' of null\n",[15,2436,2437,2440],{},[19,2438,2439],{},"解決","：類型定義要反映真實資料結構。",[46,2442,2444],{"className":153,"code":2443,"language":155,"meta":55,"style":55},"\u002F\u002F ✅ 正確：使用自動產生的類型或明確標記 nullable\ninterface User {\n  id: string;\n  name: string | null; \u002F\u002F 明確標記可為 null\n  email: string;\n}\n\n\u002F\u002F 使用時檢查\nconst displayName = user.name ?? \"未設定名稱\";\n",[53,2445,2446,2451,2459,2469,2486,2496,2500,2504,2509],{"__ignoreMap":55},[103,2447,2448],{"class":105,"line":106},[103,2449,2450],{"class":109},"\u002F\u002F ✅ 正確：使用自動產生的類型或明確標記 nullable\n",[103,2452,2453,2455,2457],{"class":105,"line":113},[103,2454,1590],{"class":171},[103,2456,709],{"class":116},[103,2458,179],{"class":178},[103,2460,2461,2463,2465,2467],{"class":105,"line":182},[103,2462,2344],{"class":185},[103,2464,189],{"class":134},[103,2466,254],{"class":232},[103,2468,435],{"class":178},[103,2470,2471,2473,2475,2477,2479,2481,2483],{"class":105,"line":194},[103,2472,1802],{"class":185},[103,2474,189],{"class":134},[103,2476,254],{"class":232},[103,2478,135],{"class":134},[103,2480,935],{"class":232},[103,2482,418],{"class":178},[103,2484,2485],{"class":109}," \u002F\u002F 明確標記可為 null\n",[103,2487,2488,2490,2492,2494],{"class":105,"line":204},[103,2489,1769],{"class":185},[103,2491,189],{"class":134},[103,2493,254],{"class":232},[103,2495,435],{"class":178},[103,2497,2498],{"class":105,"line":214},[103,2499,527],{"class":178},[103,2501,2502],{"class":105,"line":224},[103,2503,568],{"emptyLinePlaceholder":567},[103,2505,2506],{"class":105,"line":236},[103,2507,2508],{"class":109},"\u002F\u002F 使用時檢查\n",[103,2510,2511,2513,2516,2518,2520,2522,2524,2526,2529,2532,2535],{"class":105,"line":246},[103,2512,578],{"class":171},[103,2514,2515],{"class":581}," displayName",[103,2517,585],{"class":134},[103,2519,1567],{"class":549},[103,2521,1017],{"class":178},[103,2523,1686],{"class":549},[103,2525,1689],{"class":134},[103,2527,2528],{"class":484}," \"",[103,2530,2531],{"class":120},"未設定名稱",[103,2533,2534],{"class":484},"\"",[103,2536,435],{"class":178},[63,2538,2540],{"id":2539},"supabase-查詢類型遺失","Supabase 查詢類型遺失",[15,2542,2543,2545],{},[19,2544,2314],{},"：忘記傳入 Database 泛型，導致查詢結果無類型。",[46,2547,2549],{"className":153,"code":2548,"language":155,"meta":55,"style":55},"\u002F\u002F ❌ 錯誤：無類型提示\nconst client = useSupabaseClient()\nconst { data } = await client.from('users').select('*')\n\u002F\u002F data 的類型：any\n\n\u002F\u002F ✅ 正確：傳入 Database 泛型\nconst client = useSupabaseClient&lt;Database&gt;()\nconst { data } = await client.from('users').select('*')\n\u002F\u002F data 的類型：完整的 User 類型\n",[53,2550,2551,2556,2568,2613,2618,2622,2627,2653,2697],{"__ignoreMap":55},[103,2552,2553],{"class":105,"line":106},[103,2554,2555],{"class":109},"\u002F\u002F ❌ 錯誤：無類型提示\n",[103,2557,2558,2560,2562,2564,2566],{"class":105,"line":113},[103,2559,578],{"class":171},[103,2561,582],{"class":581},[103,2563,585],{"class":134},[103,2565,588],{"class":637},[103,2567,606],{"class":549},[103,2569,2570,2572,2574,2576,2578,2580,2582,2584,2586,2588,2590,2592,2594,2596,2598,2600,2602,2604,2606,2609,2611],{"class":105,"line":182},[103,2571,578],{"class":171},[103,2573,546],{"class":178},[103,2575,619],{"class":581},[103,2577,552],{"class":178},[103,2579,585],{"class":134},[103,2581,626],{"class":167},[103,2583,582],{"class":549},[103,2585,1017],{"class":178},[103,2587,658],{"class":637},[103,2589,641],{"class":549},[103,2591,491],{"class":484},[103,2593,665],{"class":120},[103,2595,491],{"class":484},[103,2597,1534],{"class":549},[103,2599,1017],{"class":178},[103,2601,676],{"class":637},[103,2603,641],{"class":549},[103,2605,491],{"class":484},[103,2607,2608],{"class":120},"*",[103,2610,491],{"class":484},[103,2612,651],{"class":549},[103,2614,2615],{"class":105,"line":194},[103,2616,2617],{"class":109},"\u002F\u002F data 的類型：any\n",[103,2619,2620],{"class":105,"line":204},[103,2621,568],{"emptyLinePlaceholder":567},[103,2623,2624],{"class":105,"line":214},[103,2625,2626],{"class":109},"\u002F\u002F ✅ 正確：傳入 Database 泛型\n",[103,2628,2629,2631,2633,2635,2637,2639,2641,2643,2645,2647,2649,2651],{"class":105,"line":224},[103,2630,578],{"class":171},[103,2632,582],{"class":581},[103,2634,585],{"class":134},[103,2636,588],{"class":549},[103,2638,412],{"class":134},[103,2640,415],{"class":549},[103,2642,418],{"class":178},[103,2644,597],{"class":549},[103,2646,412],{"class":134},[103,2648,432],{"class":549},[103,2650,418],{"class":178},[103,2652,606],{"class":549},[103,2654,2655,2657,2659,2661,2663,2665,2667,2669,2671,2673,2675,2677,2679,2681,2683,2685,2687,2689,2691,2693,2695],{"class":105,"line":236},[103,2656,578],{"class":171},[103,2658,546],{"class":178},[103,2660,619],{"class":581},[103,2662,552],{"class":178},[103,2664,585],{"class":134},[103,2666,626],{"class":167},[103,2668,582],{"class":549},[103,2670,1017],{"class":178},[103,2672,658],{"class":637},[103,2674,641],{"class":549},[103,2676,491],{"class":484},[103,2678,665],{"class":120},[103,2680,491],{"class":484},[103,2682,1534],{"class":549},[103,2684,1017],{"class":178},[103,2686,676],{"class":637},[103,2688,641],{"class":549},[103,2690,491],{"class":484},[103,2692,2608],{"class":120},[103,2694,491],{"class":484},[103,2696,651],{"class":549},[103,2698,2699],{"class":105,"line":246},[103,2700,2701],{"class":109},"\u002F\u002F data 的類型：完整的 User 類型\n",[63,2703,2705],{"id":2704},"migration-後忘記更新類型","Migration 後忘記更新類型",[15,2707,2708,2710],{},[19,2709,2314],{},"：新增欄位後，TypeScript 沒有報錯，但 IDE 無法提示。",[15,2712,2713,2715],{},[19,2714,2439],{},"：將類型產生加入自動化流程。",[46,2717,2719],{"className":97,"code":2718,"language":99,"meta":55,"style":55},"# 每次 migration 後執行\nsupabase db reset\nsupabase db lint --level warning\nsupabase gen types typescript --local | tee app\u002Ftypes\u002Fdatabase.types.ts > \u002Fdev\u002Fnull\npnpm typecheck  # 驗證類型正確性\n",[53,2720,2721,2726,2736,2751,2773],{"__ignoreMap":55},[103,2722,2723],{"class":105,"line":106},[103,2724,2725],{"class":109},"# 每次 migration 後執行\n",[103,2727,2728,2730,2733],{"class":105,"line":113},[103,2729,117],{"class":116},[103,2731,2732],{"class":120}," db",[103,2734,2735],{"class":120}," reset\n",[103,2737,2738,2740,2742,2745,2748],{"class":105,"line":182},[103,2739,117],{"class":116},[103,2741,2732],{"class":120},[103,2743,2744],{"class":120}," lint",[103,2746,2747],{"class":130}," --level",[103,2749,2750],{"class":120}," warning\n",[103,2752,2753,2755,2757,2759,2761,2763,2765,2767,2769,2771],{"class":105,"line":194},[103,2754,117],{"class":116},[103,2756,121],{"class":120},[103,2758,124],{"class":120},[103,2760,127],{"class":120},[103,2762,131],{"class":130},[103,2764,135],{"class":134},[103,2766,138],{"class":116},[103,2768,141],{"class":120},[103,2770,144],{"class":134},[103,2772,147],{"class":120},[103,2774,2775,2778,2781],{"class":105,"line":204},[103,2776,2777],{"class":116},"pnpm",[103,2779,2780],{"class":120}," typecheck",[103,2782,2783],{"class":109},"  # 驗證類型正確性\n",[40,2785],{},[11,2787,2788],{"id":2788},"類型檔案位置規範",[2790,2791,2792,2808],"table",{},[2793,2794,2795],"thead",{},[2796,2797,2798,2802,2805],"tr",{},[2799,2800,2801],"th",{},"類型種類",[2799,2803,2804],{},"位置",[2799,2806,2807],{},"說明",[2809,2810,2811,2825,2841,2854,2867],"tbody",{},[2796,2812,2813,2817,2822],{},[2814,2815,2816],"td",{},"資料庫類型",[2814,2818,2819],{},[53,2820,2821],{},"app\u002Ftypes\u002Fdatabase.types.ts",[2814,2823,2824],{},"自動產生，勿手動編輯",[2796,2826,2827,2830,2835],{},[2814,2828,2829],{},"全域通用",[2814,2831,2832],{},[53,2833,2834],{},"shared\u002Ftypes\u002Findex.d.ts",[2814,2836,2837,2840],{},[53,2838,2839],{},"Maybe\u003CT>"," 等全域類型",[2796,2842,2843,2846,2851],{},[2814,2844,2845],{},"Client 專用",[2814,2847,2848],{},[53,2849,2850],{},"app\u002Ftypes\u002F",[2814,2852,2853],{},"元件、Store 相關類型",[2796,2855,2856,2859,2864],{},[2814,2857,2858],{},"Server 專用",[2814,2860,2861],{},[53,2862,2863],{},"server\u002Ftypes\u002F",[2814,2865,2866],{},"API、認證相關類型",[2796,2868,2869,2872,2875],{},[2814,2870,2871],{},"Zod Schema",[2814,2873,2874],{},"與 API 同檔",[2814,2876,2877],{},"請求驗證 Schema",[40,2879],{},[11,2881,2882],{"id":2882},"最佳實踐總結",[71,2884,2885,2895,2901,2911,2923,2929],{},[27,2886,2887,2890,2891,2894],{},[19,2888,2889],{},"自動產生優先","：資料庫類型使用 ",[53,2892,2893],{},"supabase gen types"," 自動產生",[27,2896,2897,2900],{},[19,2898,2899],{},"嚴格 Props","：Vue 元件 Props 使用 interface 定義",[27,2902,2903,2906,2907,2910],{},[19,2904,2905],{},"統一處理 null","：使用 ",[53,2908,2909],{},"Maybe&lt;T&gt;"," 全域類型",[27,2912,2913,2916,2917,2920,2921],{},[19,2914,2915],{},"類型放對位置","：共用類型放 ",[53,2918,2919],{},"shared\u002F","，元件類型放 ",[53,2922,2850],{},[27,2924,2925,2928],{},[19,2926,2927],{},"Zod 雙重保障","：Server API 使用 Zod 同時驗證和推導類型",[27,2930,2931,2934],{},[19,2932,2933],{},"Migration 後更新","：每次資料庫變更後重新產生類型",[40,2936],{},[11,2938,2939],{"id":2939},"延伸閱讀",[24,2941,2942,2951,2958,2965,2972],{},[27,2943,2944],{},[2945,2946,2950],"a",{"href":2947,"rel":2948},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fapi\u002Frest\u002Fgenerating-types",[2949],"nofollow","Supabase TypeScript 支援",[27,2952,2953],{},[2945,2954,2957],{"href":2955,"rel":2956},"https:\u002F\u002Fvuejs.org\u002Fguide\u002Ftypescript\u002Foverview.html",[2949],"Vue 3 TypeScript 指南",[27,2959,2960],{},[2945,2961,2964],{"href":2962,"rel":2963},"https:\u002F\u002Fzod.dev\u002F",[2949],"Zod 文件",[27,2966,2967,2968],{},"上一篇：",[2945,2969,2971],{"href":2970},"\u002Fblog\u002Fnuxt\u002Fnuxt-ui-dashboard","Nuxt UI Dashboard 實戰",[27,2973,2974,2975],{},"下一篇：",[2945,2976,2978],{"href":2977},"\u002Fblog\u002Fnuxt\u002Fbetter-auth-integration","nuxt-better-auth 認證整合",[2980,2981,2982],"style",{},"html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .stzsN, html code.shiki .stzsN{--shiki-light:#91B859;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}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 .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sucvu, html code.shiki .sucvu{--shiki-light:#E53935;--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .syTEX, html code.shiki .syTEX{--shiki-light:#FF5370;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}",{"title":55,"searchDepth":182,"depth":182,"links":2984},[2985,2986,2987,2993,2999,3004,3008,3013,3014,3015],{"id":13,"depth":113,"text":13},{"id":44,"depth":113,"text":44},{"id":60,"depth":113,"text":61,"children":2988},[2989,2990,2991,2992],{"id":65,"depth":182,"text":66},{"id":93,"depth":182,"text":94},{"id":530,"depth":182,"text":530},{"id":840,"depth":182,"text":841},{"id":1232,"depth":113,"text":1233,"children":2994},[2995,2996,2997,2998],{"id":1236,"depth":182,"text":1237},{"id":1272,"depth":182,"text":1273},{"id":1296,"depth":182,"text":1297},{"id":1330,"depth":182,"text":1331},{"id":1376,"depth":113,"text":1376,"children":3000},[3001,3002,3003],{"id":1379,"depth":182,"text":1380},{"id":1389,"depth":182,"text":1390},{"id":1487,"depth":182,"text":1488},{"id":1701,"depth":113,"text":1702,"children":3005},[3006,3007],{"id":1705,"depth":182,"text":1705},{"id":2023,"depth":182,"text":2023},{"id":2305,"depth":113,"text":2305,"children":3009},[3010,3011,3012],{"id":2308,"depth":182,"text":2309},{"id":2539,"depth":182,"text":2540},{"id":2704,"depth":182,"text":2705},{"id":2788,"depth":113,"text":2788},{"id":2882,"depth":113,"text":2882},{"id":2939,"depth":113,"text":2939},"Nuxt","2026-01-22","在 Nuxt 4 專案中實現端到端類型安全，從 Supabase 自動產生類型到 Vue 元件的嚴格類型定義。",false,"md",null,{},"\u002Fblog\u002Fnuxt\u002Ftypescript-type-safety",{"title":5,"description":3018},"nuxt-fullstack","Nuxt 4 全棧實戰筆記","blog\u002Fnuxt\u002Ftypescript-type-safety\u002Findex",[3016,3029,3030],"TypeScript","Architecture","8r6OI7snpYJC0d5aDzoF3oZb4hzgAYnu_yvBcy5ysKo",1780512500196]