[{"data":1,"prerenderedAt":5564},["ShallowReactive",2],{"page-\u002Flua\u002F15-best-practices":3},{"id":4,"title":5,"body":6,"description":5555,"extension":5556,"meta":5557,"navigation":67,"path":5558,"seo":5559,"stem":5562,"__hash__":5563},"content\u002Flua\u002F15-best-practices.md","Lua 15 — Best Practices, Patterns & Production Engineering",{"type":7,"value":8,"toc":5548},"minimark",[9,14,23,28,5544],[10,11,13],"h1",{"id":12},"_15-best-practices-patterns-production-engineering","15 — Best Practices, Patterns & Production Engineering",[15,16,17,18,22],"p",{},"Production Lua demands discipline: ",[19,20,21],"code",{},"local"," everywhere (no accidental globals), strict mode to catch typos at runtime, structured error objects, deterministic resource cleanup, and module architectures that support testing and hot-reload. This chapter covers the patterns that separate hobby Lua from production Lua.",[24,25,27],"h2",{"id":26},"strict-mode-catching-global-variable-typos","Strict Mode: Catching Global Variable Typos",[29,30,33,694,699,5060,5068,5172,5180,5256,5260,5266,5275,5279],"code-wrapper",{"language":31,"filename":32},"lua","strict.lua",[34,35,39],"pre",{"className":36,"code":37,"language":31,"meta":38,"style":38},"language-lua shiki shiki-themes github-light github-dark","-- strict.lua (adapted from the original by Roberto Ierusalimschy)\n-- Makes accessing undeclared globals a runtime error.\n-- Place at the top of your entry point: require(\"strict\")\n\nlocal declared = {}\n\nlocal function what()\n  local d = debug.getinfo(3, \"S\")\n  return d and d.source or \"?\"\nend\n\nsetmetatable(_G, {\n  __index = function(t, k)\n    if not declared[k] then\n      error(\"attempt to read undeclared global: \" .. tostring(k) ..\n            \" (at \" .. what() .. \")\", 2)\n    end\n    return rawget(t, k)\n  end,\n  __newindex = function(t, k, v)\n    if not declared[k] then\n      error(\"attempt to write undeclared global: \" .. tostring(k) ..\n            \" (at \" .. what() .. \")\", 2)\n    end\n    rawset(t, k, v)\n  end,\n})\n\n-- Public API: declare a global explicitly\nlocal function strict_declare(name)\n  declared[name] = true\nend\n\n-- Allow standard library globals\nfor _, name in ipairs({\n  \"_G\", \"_VERSION\", \"assert\", \"collectgarbage\", \"coroutine\", \"debug\",\n  \"dofile\", \"error\", \"getmetatable\", \"io\", \"ipairs\", \"load\", \"loadfile\",\n  \"math\", \"next\", \"os\", \"package\", \"pairs\", \"pcall\", \"print\", \"rawequal\",\n  \"rawget\", \"rawlen\", \"rawset\", \"require\", \"select\", \"setmetatable\",\n  \"string\", \"table\", \"tonumber\", \"tostring\", \"type\", \"utf8\", \"warn\", \"xpcall\",\n}) do\n  declared[name] = true\nend\n\n-- To declare a global (rare): _G.MY_CONST = 1 won't work; use a declared function\n-- In practice, you should NOT use globals at all — use modules and locals.\n\nreturn { declare = strict_declare }\n","",[19,40,41,50,56,62,69,85,90,105,136,159,165,170,184,198,213,236,261,267,278,287,300,311,329,350,355,363,370,376,381,387,400,411,416,421,427,445,478,516,559,592,635,644,653,658,663,669,675,680],{"__ignoreMap":38},[42,43,46],"span",{"class":44,"line":45},"line",1,[42,47,49],{"class":48},"sdCPZ","-- strict.lua (adapted from the original by Roberto Ierusalimschy)\n",[42,51,53],{"class":44,"line":52},2,[42,54,55],{"class":48},"-- Makes accessing undeclared globals a runtime error.\n",[42,57,59],{"class":44,"line":58},3,[42,60,61],{"class":48},"-- Place at the top of your entry point: require(\"strict\")\n",[42,63,65],{"class":44,"line":64},4,[42,66,68],{"emptyLinePlaceholder":67},true,"\n",[42,70,72,75,79,82],{"class":44,"line":71},5,[42,73,21],{"class":74},"svdQ7",[42,76,78],{"class":77},"ssxIu"," declared ",[42,80,81],{"class":74},"=",[42,83,84],{"class":77}," {}\n",[42,86,88],{"class":44,"line":87},6,[42,89,68],{"emptyLinePlaceholder":67},[42,91,93,95,98,102],{"class":44,"line":92},7,[42,94,21],{"class":74},[42,96,97],{"class":74}," function",[42,99,101],{"class":100},"sIsaT"," what",[42,103,104],{"class":77},"()\n",[42,106,108,111,114,116,120,123,126,129,133],{"class":44,"line":107},8,[42,109,110],{"class":74},"  local",[42,112,113],{"class":77}," d ",[42,115,81],{"class":74},[42,117,119],{"class":118},"snvgF"," debug.getinfo",[42,121,122],{"class":77},"(",[42,124,125],{"class":118},"3",[42,127,128],{"class":77},", ",[42,130,132],{"class":131},"sJ6F3","\"S\"",[42,134,135],{"class":77},")\n",[42,137,139,142,144,147,150,153,156],{"class":44,"line":138},9,[42,140,141],{"class":74},"  return",[42,143,113],{"class":77},[42,145,146],{"class":74},"and",[42,148,149],{"class":77}," d.",[42,151,152],{"class":100},"source",[42,154,155],{"class":74}," or",[42,157,158],{"class":131}," \"?\"\n",[42,160,162],{"class":44,"line":161},10,[42,163,164],{"class":74},"end\n",[42,166,168],{"class":44,"line":167},11,[42,169,68],{"emptyLinePlaceholder":67},[42,171,173,176,178,181],{"class":44,"line":172},12,[42,174,175],{"class":118},"setmetatable",[42,177,122],{"class":77},[42,179,180],{"class":118},"_G",[42,182,183],{"class":77},", {\n",[42,185,187,190,193,195],{"class":44,"line":186},13,[42,188,189],{"class":100},"  __index",[42,191,192],{"class":74}," =",[42,194,97],{"class":74},[42,196,197],{"class":77},"(t, k)\n",[42,199,201,204,207,210],{"class":44,"line":200},14,[42,202,203],{"class":74},"    if",[42,205,206],{"class":74}," not",[42,208,209],{"class":77}," declared[k] ",[42,211,212],{"class":74},"then\n",[42,214,216,219,221,224,227,230,233],{"class":44,"line":215},15,[42,217,218],{"class":118},"      error",[42,220,122],{"class":77},[42,222,223],{"class":131},"\"attempt to read undeclared global: \" ",[42,225,226],{"class":74},"..",[42,228,229],{"class":118}," tostring",[42,231,232],{"class":77},"(k) ",[42,234,235],{"class":74},"..\n",[42,237,239,242,244,246,249,251,254,256,259],{"class":44,"line":238},16,[42,240,241],{"class":131},"            \" (at \" ",[42,243,226],{"class":74},[42,245,101],{"class":118},[42,247,248],{"class":77},"() ",[42,250,226],{"class":74},[42,252,253],{"class":131}," \")\"",[42,255,128],{"class":77},[42,257,258],{"class":118},"2",[42,260,135],{"class":77},[42,262,264],{"class":44,"line":263},17,[42,265,266],{"class":74},"    end\n",[42,268,270,273,276],{"class":44,"line":269},18,[42,271,272],{"class":74},"    return",[42,274,275],{"class":118}," rawget",[42,277,197],{"class":77},[42,279,281,284],{"class":44,"line":280},19,[42,282,283],{"class":74},"  end",[42,285,286],{"class":77},",\n",[42,288,290,293,295,297],{"class":44,"line":289},20,[42,291,292],{"class":100},"  __newindex",[42,294,192],{"class":74},[42,296,97],{"class":74},[42,298,299],{"class":77},"(t, k, v)\n",[42,301,303,305,307,309],{"class":44,"line":302},21,[42,304,203],{"class":74},[42,306,206],{"class":74},[42,308,209],{"class":77},[42,310,212],{"class":74},[42,312,314,316,318,321,323,325,327],{"class":44,"line":313},22,[42,315,218],{"class":118},[42,317,122],{"class":77},[42,319,320],{"class":131},"\"attempt to write undeclared global: \" ",[42,322,226],{"class":74},[42,324,229],{"class":118},[42,326,232],{"class":77},[42,328,235],{"class":74},[42,330,332,334,336,338,340,342,344,346,348],{"class":44,"line":331},23,[42,333,241],{"class":131},[42,335,226],{"class":74},[42,337,101],{"class":118},[42,339,248],{"class":77},[42,341,226],{"class":74},[42,343,253],{"class":131},[42,345,128],{"class":77},[42,347,258],{"class":118},[42,349,135],{"class":77},[42,351,353],{"class":44,"line":352},24,[42,354,266],{"class":74},[42,356,358,361],{"class":44,"line":357},25,[42,359,360],{"class":118},"    rawset",[42,362,299],{"class":77},[42,364,366,368],{"class":44,"line":365},26,[42,367,283],{"class":74},[42,369,286],{"class":77},[42,371,373],{"class":44,"line":372},27,[42,374,375],{"class":77},"})\n",[42,377,379],{"class":44,"line":378},28,[42,380,68],{"emptyLinePlaceholder":67},[42,382,384],{"class":44,"line":383},29,[42,385,386],{"class":48},"-- Public API: declare a global explicitly\n",[42,388,390,392,394,397],{"class":44,"line":389},30,[42,391,21],{"class":74},[42,393,97],{"class":74},[42,395,396],{"class":100}," strict_declare",[42,398,399],{"class":77},"(name)\n",[42,401,403,406,408],{"class":44,"line":402},31,[42,404,405],{"class":77},"  declared[name] ",[42,407,81],{"class":74},[42,409,410],{"class":118}," true\n",[42,412,414],{"class":44,"line":413},32,[42,415,164],{"class":74},[42,417,419],{"class":44,"line":418},33,[42,420,68],{"emptyLinePlaceholder":67},[42,422,424],{"class":44,"line":423},34,[42,425,426],{"class":48},"-- Allow standard library globals\n",[42,428,430,433,436,439,442],{"class":44,"line":429},35,[42,431,432],{"class":74},"for",[42,434,435],{"class":77}," _, name ",[42,437,438],{"class":74},"in",[42,440,441],{"class":118}," ipairs",[42,443,444],{"class":77},"({\n",[42,446,448,451,453,456,458,461,463,466,468,471,473,476],{"class":44,"line":447},36,[42,449,450],{"class":131},"  \"_G\"",[42,452,128],{"class":77},[42,454,455],{"class":131},"\"_VERSION\"",[42,457,128],{"class":77},[42,459,460],{"class":131},"\"assert\"",[42,462,128],{"class":77},[42,464,465],{"class":131},"\"collectgarbage\"",[42,467,128],{"class":77},[42,469,470],{"class":131},"\"coroutine\"",[42,472,128],{"class":77},[42,474,475],{"class":131},"\"debug\"",[42,477,286],{"class":77},[42,479,481,484,486,489,491,494,496,499,501,504,506,509,511,514],{"class":44,"line":480},37,[42,482,483],{"class":131},"  \"dofile\"",[42,485,128],{"class":77},[42,487,488],{"class":131},"\"error\"",[42,490,128],{"class":77},[42,492,493],{"class":131},"\"getmetatable\"",[42,495,128],{"class":77},[42,497,498],{"class":131},"\"io\"",[42,500,128],{"class":77},[42,502,503],{"class":131},"\"ipairs\"",[42,505,128],{"class":77},[42,507,508],{"class":131},"\"load\"",[42,510,128],{"class":77},[42,512,513],{"class":131},"\"loadfile\"",[42,515,286],{"class":77},[42,517,519,522,524,527,529,532,534,537,539,542,544,547,549,552,554,557],{"class":44,"line":518},38,[42,520,521],{"class":131},"  \"math\"",[42,523,128],{"class":77},[42,525,526],{"class":131},"\"next\"",[42,528,128],{"class":77},[42,530,531],{"class":131},"\"os\"",[42,533,128],{"class":77},[42,535,536],{"class":131},"\"package\"",[42,538,128],{"class":77},[42,540,541],{"class":131},"\"pairs\"",[42,543,128],{"class":77},[42,545,546],{"class":131},"\"pcall\"",[42,548,128],{"class":77},[42,550,551],{"class":131},"\"print\"",[42,553,128],{"class":77},[42,555,556],{"class":131},"\"rawequal\"",[42,558,286],{"class":77},[42,560,562,565,567,570,572,575,577,580,582,585,587,590],{"class":44,"line":561},39,[42,563,564],{"class":131},"  \"rawget\"",[42,566,128],{"class":77},[42,568,569],{"class":131},"\"rawlen\"",[42,571,128],{"class":77},[42,573,574],{"class":131},"\"rawset\"",[42,576,128],{"class":77},[42,578,579],{"class":131},"\"require\"",[42,581,128],{"class":77},[42,583,584],{"class":131},"\"select\"",[42,586,128],{"class":77},[42,588,589],{"class":131},"\"setmetatable\"",[42,591,286],{"class":77},[42,593,595,598,600,603,605,608,610,613,615,618,620,623,625,628,630,633],{"class":44,"line":594},40,[42,596,597],{"class":131},"  \"string\"",[42,599,128],{"class":77},[42,601,602],{"class":131},"\"table\"",[42,604,128],{"class":77},[42,606,607],{"class":131},"\"tonumber\"",[42,609,128],{"class":77},[42,611,612],{"class":131},"\"tostring\"",[42,614,128],{"class":77},[42,616,617],{"class":131},"\"type\"",[42,619,128],{"class":77},[42,621,622],{"class":131},"\"utf8\"",[42,624,128],{"class":77},[42,626,627],{"class":131},"\"warn\"",[42,629,128],{"class":77},[42,631,632],{"class":131},"\"xpcall\"",[42,634,286],{"class":77},[42,636,638,641],{"class":44,"line":637},41,[42,639,640],{"class":77},"}) ",[42,642,643],{"class":74},"do\n",[42,645,647,649,651],{"class":44,"line":646},42,[42,648,405],{"class":77},[42,650,81],{"class":74},[42,652,410],{"class":118},[42,654,656],{"class":44,"line":655},43,[42,657,164],{"class":74},[42,659,661],{"class":44,"line":660},44,[42,662,68],{"emptyLinePlaceholder":67},[42,664,666],{"class":44,"line":665},45,[42,667,668],{"class":48},"-- To declare a global (rare): _G.MY_CONST = 1 won't work; use a declared function\n",[42,670,672],{"class":44,"line":671},46,[42,673,674],{"class":48},"-- In practice, you should NOT use globals at all — use modules and locals.\n",[42,676,678],{"class":44,"line":677},47,[42,679,68],{"emptyLinePlaceholder":67},[42,681,683,686,689,691],{"class":44,"line":682},48,[42,684,685],{"class":74},"return",[42,687,688],{"class":77}," { declare ",[42,690,81],{"class":74},[42,692,693],{"class":77}," strict_declare }\n",[695,696,698],"h3",{"id":697},"usage","Usage",[29,700,701,787,791],{"language":31},[34,702,704],{"className":36,"code":703,"language":31,"meta":38,"style":38},"-- entry.lua\nrequire(\"strict\")  -- activate at the top, before any other code\n\nlocal x = 10       -- OK: local\nx = 20              -- OK: modifying local\n\ntypo_var = 5        -- ERROR: attempt to write undeclared global: typo_var\nprint(undeclared)   -- ERROR: attempt to read undeclared global: undeclared\n",[19,705,706,711,727,731,746,759,763,776],{"__ignoreMap":38},[42,707,708],{"class":44,"line":45},[42,709,710],{"class":48},"-- entry.lua\n",[42,712,713,716,718,721,724],{"class":44,"line":52},[42,714,715],{"class":118},"require",[42,717,122],{"class":77},[42,719,720],{"class":131},"\"strict\"",[42,722,723],{"class":77},")  ",[42,725,726],{"class":48},"-- activate at the top, before any other code\n",[42,728,729],{"class":44,"line":58},[42,730,68],{"emptyLinePlaceholder":67},[42,732,733,735,738,740,743],{"class":44,"line":64},[42,734,21],{"class":74},[42,736,737],{"class":77}," x ",[42,739,81],{"class":74},[42,741,742],{"class":118}," 10",[42,744,745],{"class":48},"       -- OK: local\n",[42,747,748,751,753,756],{"class":44,"line":71},[42,749,750],{"class":77},"x ",[42,752,81],{"class":74},[42,754,755],{"class":118}," 20",[42,757,758],{"class":48},"              -- OK: modifying local\n",[42,760,761],{"class":44,"line":87},[42,762,68],{"emptyLinePlaceholder":67},[42,764,765,768,770,773],{"class":44,"line":92},[42,766,767],{"class":77},"typo_var ",[42,769,81],{"class":74},[42,771,772],{"class":118}," 5",[42,774,775],{"class":48},"        -- ERROR: attempt to write undeclared global: typo_var\n",[42,777,778,781,784],{"class":44,"line":107},[42,779,780],{"class":118},"print",[42,782,783],{"class":77},"(undeclared)   ",[42,785,786],{"class":48},"-- ERROR: attempt to read undeclared global: undeclared\n",[24,788,790],{"id":789},"module-design-encapsulation-api","Module Design: Encapsulation & API",[29,792,794,1406,1410],{"language":31,"filename":793},"best_practices_module.lua",[34,795,797],{"className":36,"code":796,"language":31,"meta":38,"style":38},"-- Production module template: private state, explicit public API, dependency injection\n\n-- 1. Localize all standard library functions used in this module\nlocal sformat = string.format\nlocal tconcat = table.concat\nlocal assert = assert\nlocal error = error\nlocal type = type\nlocal pairs = pairs\nlocal setmetatable = setmetatable\n\n-- 2. Module table (the public API)\nlocal M = {}\n\n-- 3. Private state (module-level locals, NOT accessible from outside)\nlocal _state = {\n  initialized = false,\n  config = {},\n}\n\n-- 4. Private helper functions (local, not in M)\nlocal function validate_config(config)\n  assert(type(config) == \"table\", \"config must be a table\")\n  assert(type(config.host) == \"string\", \"config.host must be a string\")\n  assert(type(config.port) == \"number\" and config.port > 0, \"config.port must be positive\")\nend\n\nlocal function default_config()\n  return {\n    host = \"localhost\",\n    port = 8080,\n    timeout = 5000,\n  }\nend\n\n-- 5. Public API: init (with dependency injection)\nfunction M.init(config, deps)\n  validate_config(config)\n  _state.config = setmetatable(config, {__index = default_config()})\n  _state.deps = deps or {}\n  _state.initialized = true\n  return M  -- fluent: allow chaining\nend\n\n-- 6. Public API: methods\nfunction M.get_config(key)\n  if not _state.initialized then error(\"not initialized, call init() first\", 2) end\n  return _state.config[key]\nend\n\nfunction M.tostring()\n  local c = _state.config\n  return sformat(\"host=%s port=%d timeout=%d\", c.host, c.port, c.timeout)\nend\n\n-- 7. Return the module table (single return at the end)\nreturn M\n",[19,798,799,804,808,813,825,837,849,861,873,885,897,901,906,917,921,926,938,950,960,965,969,974,986,1012,1041,1081,1085,1089,1100,1106,1118,1130,1142,1147,1151,1155,1160,1177,1184,1207,1224,1235,1245,1249,1253,1258,1272,1303,1314,1319,1324,1338,1353,1382,1387,1392,1398],{"__ignoreMap":38},[42,800,801],{"class":44,"line":45},[42,802,803],{"class":48},"-- Production module template: private state, explicit public API, dependency injection\n",[42,805,806],{"class":44,"line":52},[42,807,68],{"emptyLinePlaceholder":67},[42,809,810],{"class":44,"line":58},[42,811,812],{"class":48},"-- 1. Localize all standard library functions used in this module\n",[42,814,815,817,820,822],{"class":44,"line":64},[42,816,21],{"class":74},[42,818,819],{"class":77}," sformat ",[42,821,81],{"class":74},[42,823,824],{"class":118}," string.format\n",[42,826,827,829,832,834],{"class":44,"line":71},[42,828,21],{"class":74},[42,830,831],{"class":77}," tconcat ",[42,833,81],{"class":74},[42,835,836],{"class":118}," table.concat\n",[42,838,839,841,844,846],{"class":44,"line":87},[42,840,21],{"class":74},[42,842,843],{"class":77}," assert ",[42,845,81],{"class":74},[42,847,848],{"class":118}," assert\n",[42,850,851,853,856,858],{"class":44,"line":92},[42,852,21],{"class":74},[42,854,855],{"class":77}," error ",[42,857,81],{"class":74},[42,859,860],{"class":118}," error\n",[42,862,863,865,868,870],{"class":44,"line":107},[42,864,21],{"class":74},[42,866,867],{"class":77}," type ",[42,869,81],{"class":74},[42,871,872],{"class":118}," type\n",[42,874,875,877,880,882],{"class":44,"line":138},[42,876,21],{"class":74},[42,878,879],{"class":77}," pairs ",[42,881,81],{"class":74},[42,883,884],{"class":118}," pairs\n",[42,886,887,889,892,894],{"class":44,"line":161},[42,888,21],{"class":74},[42,890,891],{"class":77}," setmetatable ",[42,893,81],{"class":74},[42,895,896],{"class":118}," setmetatable\n",[42,898,899],{"class":44,"line":167},[42,900,68],{"emptyLinePlaceholder":67},[42,902,903],{"class":44,"line":172},[42,904,905],{"class":48},"-- 2. Module table (the public API)\n",[42,907,908,910,913,915],{"class":44,"line":186},[42,909,21],{"class":74},[42,911,912],{"class":77}," M ",[42,914,81],{"class":74},[42,916,84],{"class":77},[42,918,919],{"class":44,"line":200},[42,920,68],{"emptyLinePlaceholder":67},[42,922,923],{"class":44,"line":215},[42,924,925],{"class":48},"-- 3. Private state (module-level locals, NOT accessible from outside)\n",[42,927,928,930,933,935],{"class":44,"line":238},[42,929,21],{"class":74},[42,931,932],{"class":77}," _state ",[42,934,81],{"class":74},[42,936,937],{"class":77}," {\n",[42,939,940,943,945,948],{"class":44,"line":263},[42,941,942],{"class":77},"  initialized ",[42,944,81],{"class":74},[42,946,947],{"class":118}," false",[42,949,286],{"class":77},[42,951,952,955,957],{"class":44,"line":269},[42,953,954],{"class":77},"  config ",[42,956,81],{"class":74},[42,958,959],{"class":77}," {},\n",[42,961,962],{"class":44,"line":280},[42,963,964],{"class":77},"}\n",[42,966,967],{"class":44,"line":289},[42,968,68],{"emptyLinePlaceholder":67},[42,970,971],{"class":44,"line":302},[42,972,973],{"class":48},"-- 4. Private helper functions (local, not in M)\n",[42,975,976,978,980,983],{"class":44,"line":313},[42,977,21],{"class":74},[42,979,97],{"class":74},[42,981,982],{"class":100}," validate_config",[42,984,985],{"class":77},"(config)\n",[42,987,988,991,993,996,999,1002,1005,1007,1010],{"class":44,"line":331},[42,989,990],{"class":118},"  assert",[42,992,122],{"class":77},[42,994,995],{"class":118},"type",[42,997,998],{"class":77},"(config) ",[42,1000,1001],{"class":74},"==",[42,1003,1004],{"class":131}," \"table\"",[42,1006,128],{"class":77},[42,1008,1009],{"class":131},"\"config must be a table\"",[42,1011,135],{"class":77},[42,1013,1014,1016,1018,1020,1023,1026,1029,1031,1034,1036,1039],{"class":44,"line":352},[42,1015,990],{"class":118},[42,1017,122],{"class":77},[42,1019,995],{"class":118},[42,1021,1022],{"class":77},"(config.",[42,1024,1025],{"class":100},"host",[42,1027,1028],{"class":77},") ",[42,1030,1001],{"class":74},[42,1032,1033],{"class":131}," \"string\"",[42,1035,128],{"class":77},[42,1037,1038],{"class":131},"\"config.host must be a string\"",[42,1040,135],{"class":77},[42,1042,1043,1045,1047,1049,1051,1054,1056,1058,1061,1063,1066,1068,1071,1074,1076,1079],{"class":44,"line":357},[42,1044,990],{"class":118},[42,1046,122],{"class":77},[42,1048,995],{"class":118},[42,1050,1022],{"class":77},[42,1052,1053],{"class":100},"port",[42,1055,1028],{"class":77},[42,1057,1001],{"class":74},[42,1059,1060],{"class":131}," \"number\" ",[42,1062,146],{"class":74},[42,1064,1065],{"class":77}," config.",[42,1067,1053],{"class":100},[42,1069,1070],{"class":74}," >",[42,1072,1073],{"class":118}," 0",[42,1075,128],{"class":77},[42,1077,1078],{"class":131},"\"config.port must be positive\"",[42,1080,135],{"class":77},[42,1082,1083],{"class":44,"line":365},[42,1084,164],{"class":74},[42,1086,1087],{"class":44,"line":372},[42,1088,68],{"emptyLinePlaceholder":67},[42,1090,1091,1093,1095,1098],{"class":44,"line":378},[42,1092,21],{"class":74},[42,1094,97],{"class":74},[42,1096,1097],{"class":100}," default_config",[42,1099,104],{"class":77},[42,1101,1102,1104],{"class":44,"line":383},[42,1103,141],{"class":74},[42,1105,937],{"class":77},[42,1107,1108,1111,1113,1116],{"class":44,"line":389},[42,1109,1110],{"class":77},"    host ",[42,1112,81],{"class":74},[42,1114,1115],{"class":131}," \"localhost\"",[42,1117,286],{"class":77},[42,1119,1120,1123,1125,1128],{"class":44,"line":402},[42,1121,1122],{"class":77},"    port ",[42,1124,81],{"class":74},[42,1126,1127],{"class":118}," 8080",[42,1129,286],{"class":77},[42,1131,1132,1135,1137,1140],{"class":44,"line":413},[42,1133,1134],{"class":77},"    timeout ",[42,1136,81],{"class":74},[42,1138,1139],{"class":118}," 5000",[42,1141,286],{"class":77},[42,1143,1144],{"class":44,"line":418},[42,1145,1146],{"class":77},"  }\n",[42,1148,1149],{"class":44,"line":423},[42,1150,164],{"class":74},[42,1152,1153],{"class":44,"line":429},[42,1154,68],{"emptyLinePlaceholder":67},[42,1156,1157],{"class":44,"line":447},[42,1158,1159],{"class":48},"-- 5. Public API: init (with dependency injection)\n",[42,1161,1162,1165,1168,1171,1174],{"class":44,"line":480},[42,1163,1164],{"class":74},"function",[42,1166,1167],{"class":100}," M",[42,1169,1170],{"class":77},".",[42,1172,1173],{"class":100},"init",[42,1175,1176],{"class":77},"(config, deps)\n",[42,1178,1179,1182],{"class":44,"line":518},[42,1180,1181],{"class":118},"  validate_config",[42,1183,985],{"class":77},[42,1185,1186,1189,1192,1194,1197,1200,1202,1204],{"class":44,"line":561},[42,1187,1188],{"class":77},"  _state.",[42,1190,1191],{"class":100},"config",[42,1193,192],{"class":74},[42,1195,1196],{"class":118}," setmetatable",[42,1198,1199],{"class":77},"(config, {__index ",[42,1201,81],{"class":74},[42,1203,1097],{"class":118},[42,1205,1206],{"class":77},"()})\n",[42,1208,1209,1211,1214,1216,1219,1222],{"class":44,"line":594},[42,1210,1188],{"class":77},[42,1212,1213],{"class":100},"deps",[42,1215,192],{"class":74},[42,1217,1218],{"class":77}," deps ",[42,1220,1221],{"class":74},"or",[42,1223,84],{"class":77},[42,1225,1226,1228,1231,1233],{"class":44,"line":637},[42,1227,1188],{"class":77},[42,1229,1230],{"class":100},"initialized",[42,1232,192],{"class":74},[42,1234,410],{"class":118},[42,1236,1237,1239,1242],{"class":44,"line":646},[42,1238,141],{"class":74},[42,1240,1241],{"class":77}," M  ",[42,1243,1244],{"class":48},"-- fluent: allow chaining\n",[42,1246,1247],{"class":44,"line":655},[42,1248,164],{"class":74},[42,1250,1251],{"class":44,"line":660},[42,1252,68],{"emptyLinePlaceholder":67},[42,1254,1255],{"class":44,"line":665},[42,1256,1257],{"class":48},"-- 6. Public API: methods\n",[42,1259,1260,1262,1264,1266,1269],{"class":44,"line":671},[42,1261,1164],{"class":74},[42,1263,1167],{"class":100},[42,1265,1170],{"class":77},[42,1267,1268],{"class":100},"get_config",[42,1270,1271],{"class":77},"(key)\n",[42,1273,1274,1277,1279,1282,1284,1287,1290,1292,1295,1297,1299,1301],{"class":44,"line":677},[42,1275,1276],{"class":74},"  if",[42,1278,206],{"class":74},[42,1280,1281],{"class":77}," _state.",[42,1283,1230],{"class":100},[42,1285,1286],{"class":74}," then",[42,1288,1289],{"class":118}," error",[42,1291,122],{"class":77},[42,1293,1294],{"class":131},"\"not initialized, call init() first\"",[42,1296,128],{"class":77},[42,1298,258],{"class":118},[42,1300,1028],{"class":77},[42,1302,164],{"class":74},[42,1304,1305,1307,1309,1311],{"class":44,"line":682},[42,1306,141],{"class":74},[42,1308,1281],{"class":77},[42,1310,1191],{"class":100},[42,1312,1313],{"class":77},"[key]\n",[42,1315,1317],{"class":44,"line":1316},49,[42,1318,164],{"class":74},[42,1320,1322],{"class":44,"line":1321},50,[42,1323,68],{"emptyLinePlaceholder":67},[42,1325,1327,1329,1331,1333,1336],{"class":44,"line":1326},51,[42,1328,1164],{"class":74},[42,1330,1167],{"class":100},[42,1332,1170],{"class":77},[42,1334,1335],{"class":100},"tostring",[42,1337,104],{"class":77},[42,1339,1341,1343,1346,1348,1350],{"class":44,"line":1340},52,[42,1342,110],{"class":74},[42,1344,1345],{"class":77}," c ",[42,1347,81],{"class":74},[42,1349,1281],{"class":77},[42,1351,1352],{"class":100},"config\n",[42,1354,1356,1358,1361,1363,1366,1369,1371,1373,1375,1377,1380],{"class":44,"line":1355},53,[42,1357,141],{"class":74},[42,1359,1360],{"class":118}," sformat",[42,1362,122],{"class":77},[42,1364,1365],{"class":131},"\"host=%s port=%d timeout=%d\"",[42,1367,1368],{"class":77},", c.",[42,1370,1025],{"class":100},[42,1372,1368],{"class":77},[42,1374,1053],{"class":100},[42,1376,1368],{"class":77},[42,1378,1379],{"class":100},"timeout",[42,1381,135],{"class":77},[42,1383,1385],{"class":44,"line":1384},54,[42,1386,164],{"class":74},[42,1388,1390],{"class":44,"line":1389},55,[42,1391,68],{"emptyLinePlaceholder":67},[42,1393,1395],{"class":44,"line":1394},56,[42,1396,1397],{"class":48},"-- 7. Return the module table (single return at the end)\n",[42,1399,1401,1403],{"class":44,"line":1400},57,[42,1402,685],{"class":74},[42,1404,1405],{"class":77}," M\n",[24,1407,1409],{"id":1408},"error-architecture-structured-errors-boundaries","Error Architecture: Structured Errors & Boundaries",[29,1411,1412,2258,2266],{"language":31},[34,1413,1415],{"className":36,"code":1414,"language":31,"meta":38,"style":38},"-- Error hierarchy: typed errors with categories for different handling strategies\nlocal Error = {}\nError.__index = Error\nError.__tostring = function(self)\n  return string.format(\"[%s\u002F%s] %s\", self.category, self.code, self.message)\nend\n\nfunction Error.new(opts)\n  return setmetatable({\n    code = opts.code or \"UNKNOWN\",\n    message = opts.message or \"\",\n    category = opts.category or \"GENERIC\",\n    retryable = opts.retryable or false,\n    context = opts.context or {},\n    cause = opts.cause,  -- for error chaining (wrap)\n  }, Error)\nend\n\n-- Error categories\nlocal function err_validation(msg, ctx)   return Error.new{code=\"VALIDATION\", message=msg, category=\"CLIENT\", context=ctx} end\nlocal function err_not_found(res, id)      return Error.new{code=\"NOT_FOUND\", message=res..\" not found: \"..tostring(id), category=\"CLIENT\"} end\nlocal function err_rate_limited()          return Error.new{code=\"RATE_LIMIT\", message=\"too many requests\", category=\"CLIENT\", retryable=true} end\nlocal function err_network(msg)            return Error.new{code=\"NETWORK\", message=msg, category=\"TRANSIENT\", retryable=true} end\nlocal function err_internal(msg, ctx)     return Error.new{code=\"INTERNAL\", message=msg, category=\"SERVER\", context=ctx} end\n\n-- Error boundary: catch and categorize errors from a function\nlocal function error_boundary(fn, context)\n  local ok, result = xpcall(fn, function(err)\n    if getmetatable(err) == Error then return err end\n    -- Wrap unknown errors (strings, runtime errors) in a structured Error\n    return err_internal(tostring(err), context)\n  end)\n  if ok then return result end\n  return nil, result  -- nil + Error (always structured on failure)\nend\n\n-- Usage: handler layer wraps business logic\nlocal function handle_request(req)\n  local result, err = error_boundary(function()\n    if not req.user_id then error(err_validation(\"user_id required\"), 0) end\n    local user = get_user(req.user_id)  -- may throw\n    return user\n  end, {path = req.path, method = req.method})\n\n  if err then\n    if err.category == \"CLIENT\" then return 400, tostring(err)\n    elseif err.category == \"TRANSIENT\" then return 503, tostring(err)\n    else return 500, tostring(err) end\n  end\n  return 200, result\nend\n",[19,1416,1417,1422,1433,1446,1460,1501,1505,1509,1524,1532,1551,1569,1587,1605,1621,1639,1644,1648,1652,1657,1707,1760,1811,1857,1901,1905,1910,1922,1942,1967,1972,1985,1991,2007,2020,2024,2028,2033,2045,2062,2098,2121,2128,2154,2158,2166,2194,2221,2239,2244,2254],{"__ignoreMap":38},[42,1418,1419],{"class":44,"line":45},[42,1420,1421],{"class":48},"-- Error hierarchy: typed errors with categories for different handling strategies\n",[42,1423,1424,1426,1429,1431],{"class":44,"line":52},[42,1425,21],{"class":74},[42,1427,1428],{"class":77}," Error ",[42,1430,81],{"class":74},[42,1432,84],{"class":77},[42,1434,1435,1438,1441,1443],{"class":44,"line":58},[42,1436,1437],{"class":77},"Error.",[42,1439,1440],{"class":100},"__index",[42,1442,192],{"class":74},[42,1444,1445],{"class":77}," Error\n",[42,1447,1448,1450,1453,1455,1457],{"class":44,"line":64},[42,1449,1437],{"class":77},[42,1451,1452],{"class":100},"__tostring",[42,1454,192],{"class":74},[42,1456,97],{"class":74},[42,1458,1459],{"class":77},"(self)\n",[42,1461,1462,1464,1467,1469,1472,1474,1477,1479,1482,1484,1486,1488,1490,1492,1494,1496,1499],{"class":44,"line":71},[42,1463,141],{"class":74},[42,1465,1466],{"class":118}," string.format",[42,1468,122],{"class":77},[42,1470,1471],{"class":131},"\"[%s\u002F%s] %s\"",[42,1473,128],{"class":77},[42,1475,1476],{"class":118},"self",[42,1478,1170],{"class":77},[42,1480,1481],{"class":100},"category",[42,1483,128],{"class":77},[42,1485,1476],{"class":118},[42,1487,1170],{"class":77},[42,1489,19],{"class":100},[42,1491,128],{"class":77},[42,1493,1476],{"class":118},[42,1495,1170],{"class":77},[42,1497,1498],{"class":100},"message",[42,1500,135],{"class":77},[42,1502,1503],{"class":44,"line":87},[42,1504,164],{"class":74},[42,1506,1507],{"class":44,"line":92},[42,1508,68],{"emptyLinePlaceholder":67},[42,1510,1511,1513,1516,1518,1521],{"class":44,"line":107},[42,1512,1164],{"class":74},[42,1514,1515],{"class":100}," Error",[42,1517,1170],{"class":77},[42,1519,1520],{"class":100},"new",[42,1522,1523],{"class":77},"(opts)\n",[42,1525,1526,1528,1530],{"class":44,"line":138},[42,1527,141],{"class":74},[42,1529,1196],{"class":118},[42,1531,444],{"class":77},[42,1533,1534,1537,1539,1542,1544,1546,1549],{"class":44,"line":161},[42,1535,1536],{"class":77},"    code ",[42,1538,81],{"class":74},[42,1540,1541],{"class":77}," opts.",[42,1543,19],{"class":100},[42,1545,155],{"class":74},[42,1547,1548],{"class":131}," \"UNKNOWN\"",[42,1550,286],{"class":77},[42,1552,1553,1556,1558,1560,1562,1564,1567],{"class":44,"line":167},[42,1554,1555],{"class":77},"    message ",[42,1557,81],{"class":74},[42,1559,1541],{"class":77},[42,1561,1498],{"class":100},[42,1563,155],{"class":74},[42,1565,1566],{"class":131}," \"\"",[42,1568,286],{"class":77},[42,1570,1571,1574,1576,1578,1580,1582,1585],{"class":44,"line":172},[42,1572,1573],{"class":77},"    category ",[42,1575,81],{"class":74},[42,1577,1541],{"class":77},[42,1579,1481],{"class":100},[42,1581,155],{"class":74},[42,1583,1584],{"class":131}," \"GENERIC\"",[42,1586,286],{"class":77},[42,1588,1589,1592,1594,1596,1599,1601,1603],{"class":44,"line":186},[42,1590,1591],{"class":77},"    retryable ",[42,1593,81],{"class":74},[42,1595,1541],{"class":77},[42,1597,1598],{"class":100},"retryable",[42,1600,155],{"class":74},[42,1602,947],{"class":118},[42,1604,286],{"class":77},[42,1606,1607,1610,1612,1614,1617,1619],{"class":44,"line":200},[42,1608,1609],{"class":77},"    context ",[42,1611,81],{"class":74},[42,1613,1541],{"class":77},[42,1615,1616],{"class":100},"context",[42,1618,155],{"class":74},[42,1620,959],{"class":77},[42,1622,1623,1626,1628,1630,1633,1636],{"class":44,"line":215},[42,1624,1625],{"class":77},"    cause ",[42,1627,81],{"class":74},[42,1629,1541],{"class":77},[42,1631,1632],{"class":100},"cause",[42,1634,1635],{"class":77},",  ",[42,1637,1638],{"class":48},"-- for error chaining (wrap)\n",[42,1640,1641],{"class":44,"line":238},[42,1642,1643],{"class":77},"  }, Error)\n",[42,1645,1646],{"class":44,"line":263},[42,1647,164],{"class":74},[42,1649,1650],{"class":44,"line":269},[42,1651,68],{"emptyLinePlaceholder":67},[42,1653,1654],{"class":44,"line":280},[42,1655,1656],{"class":48},"-- Error categories\n",[42,1658,1659,1661,1663,1666,1669,1671,1674,1676,1679,1681,1684,1687,1689,1692,1694,1697,1700,1702,1705],{"class":44,"line":289},[42,1660,21],{"class":74},[42,1662,97],{"class":74},[42,1664,1665],{"class":100}," err_validation",[42,1667,1668],{"class":77},"(msg, ctx)   ",[42,1670,685],{"class":74},[42,1672,1673],{"class":77}," Error.",[42,1675,1520],{"class":118},[42,1677,1678],{"class":77},"{code",[42,1680,81],{"class":74},[42,1682,1683],{"class":131},"\"VALIDATION\"",[42,1685,1686],{"class":77},", message",[42,1688,81],{"class":74},[42,1690,1691],{"class":77},"msg, category",[42,1693,81],{"class":74},[42,1695,1696],{"class":131},"\"CLIENT\"",[42,1698,1699],{"class":77},", context",[42,1701,81],{"class":74},[42,1703,1704],{"class":77},"ctx} ",[42,1706,164],{"class":74},[42,1708,1709,1711,1713,1716,1719,1721,1723,1725,1727,1729,1732,1734,1736,1739,1741,1744,1746,1748,1751,1753,1755,1758],{"class":44,"line":302},[42,1710,21],{"class":74},[42,1712,97],{"class":74},[42,1714,1715],{"class":100}," err_not_found",[42,1717,1718],{"class":77},"(res, id)      ",[42,1720,685],{"class":74},[42,1722,1673],{"class":77},[42,1724,1520],{"class":118},[42,1726,1678],{"class":77},[42,1728,81],{"class":74},[42,1730,1731],{"class":131},"\"NOT_FOUND\"",[42,1733,1686],{"class":77},[42,1735,81],{"class":74},[42,1737,1738],{"class":77},"res",[42,1740,226],{"class":74},[42,1742,1743],{"class":131},"\" not found: \"",[42,1745,226],{"class":74},[42,1747,1335],{"class":118},[42,1749,1750],{"class":77},"(id), category",[42,1752,81],{"class":74},[42,1754,1696],{"class":131},[42,1756,1757],{"class":77},"} ",[42,1759,164],{"class":74},[42,1761,1762,1764,1766,1769,1772,1774,1776,1778,1780,1782,1785,1787,1789,1792,1795,1797,1799,1802,1804,1807,1809],{"class":44,"line":313},[42,1763,21],{"class":74},[42,1765,97],{"class":74},[42,1767,1768],{"class":100}," err_rate_limited",[42,1770,1771],{"class":77},"()          ",[42,1773,685],{"class":74},[42,1775,1673],{"class":77},[42,1777,1520],{"class":118},[42,1779,1678],{"class":77},[42,1781,81],{"class":74},[42,1783,1784],{"class":131},"\"RATE_LIMIT\"",[42,1786,1686],{"class":77},[42,1788,81],{"class":74},[42,1790,1791],{"class":131},"\"too many requests\"",[42,1793,1794],{"class":77},", category",[42,1796,81],{"class":74},[42,1798,1696],{"class":131},[42,1800,1801],{"class":77},", retryable",[42,1803,81],{"class":74},[42,1805,1806],{"class":118},"true",[42,1808,1757],{"class":77},[42,1810,164],{"class":74},[42,1812,1813,1815,1817,1820,1823,1825,1827,1829,1831,1833,1836,1838,1840,1842,1844,1847,1849,1851,1853,1855],{"class":44,"line":331},[42,1814,21],{"class":74},[42,1816,97],{"class":74},[42,1818,1819],{"class":100}," err_network",[42,1821,1822],{"class":77},"(msg)            ",[42,1824,685],{"class":74},[42,1826,1673],{"class":77},[42,1828,1520],{"class":118},[42,1830,1678],{"class":77},[42,1832,81],{"class":74},[42,1834,1835],{"class":131},"\"NETWORK\"",[42,1837,1686],{"class":77},[42,1839,81],{"class":74},[42,1841,1691],{"class":77},[42,1843,81],{"class":74},[42,1845,1846],{"class":131},"\"TRANSIENT\"",[42,1848,1801],{"class":77},[42,1850,81],{"class":74},[42,1852,1806],{"class":118},[42,1854,1757],{"class":77},[42,1856,164],{"class":74},[42,1858,1859,1861,1863,1866,1869,1871,1873,1875,1877,1879,1882,1884,1886,1888,1890,1893,1895,1897,1899],{"class":44,"line":352},[42,1860,21],{"class":74},[42,1862,97],{"class":74},[42,1864,1865],{"class":100}," err_internal",[42,1867,1868],{"class":77},"(msg, ctx)     ",[42,1870,685],{"class":74},[42,1872,1673],{"class":77},[42,1874,1520],{"class":118},[42,1876,1678],{"class":77},[42,1878,81],{"class":74},[42,1880,1881],{"class":131},"\"INTERNAL\"",[42,1883,1686],{"class":77},[42,1885,81],{"class":74},[42,1887,1691],{"class":77},[42,1889,81],{"class":74},[42,1891,1892],{"class":131},"\"SERVER\"",[42,1894,1699],{"class":77},[42,1896,81],{"class":74},[42,1898,1704],{"class":77},[42,1900,164],{"class":74},[42,1902,1903],{"class":44,"line":357},[42,1904,68],{"emptyLinePlaceholder":67},[42,1906,1907],{"class":44,"line":365},[42,1908,1909],{"class":48},"-- Error boundary: catch and categorize errors from a function\n",[42,1911,1912,1914,1916,1919],{"class":44,"line":372},[42,1913,21],{"class":74},[42,1915,97],{"class":74},[42,1917,1918],{"class":100}," error_boundary",[42,1920,1921],{"class":77},"(fn, context)\n",[42,1923,1924,1926,1929,1931,1934,1937,1939],{"class":44,"line":378},[42,1925,110],{"class":74},[42,1927,1928],{"class":77}," ok, result ",[42,1930,81],{"class":74},[42,1932,1933],{"class":118}," xpcall",[42,1935,1936],{"class":77},"(fn, ",[42,1938,1164],{"class":74},[42,1940,1941],{"class":77},"(err)\n",[42,1943,1944,1946,1949,1952,1954,1956,1959,1962,1965],{"class":44,"line":383},[42,1945,203],{"class":74},[42,1947,1948],{"class":118}," getmetatable",[42,1950,1951],{"class":77},"(err) ",[42,1953,1001],{"class":74},[42,1955,1428],{"class":77},[42,1957,1958],{"class":74},"then",[42,1960,1961],{"class":74}," return",[42,1963,1964],{"class":77}," err ",[42,1966,164],{"class":74},[42,1968,1969],{"class":44,"line":389},[42,1970,1971],{"class":48},"    -- Wrap unknown errors (strings, runtime errors) in a structured Error\n",[42,1973,1974,1976,1978,1980,1982],{"class":44,"line":402},[42,1975,272],{"class":74},[42,1977,1865],{"class":118},[42,1979,122],{"class":77},[42,1981,1335],{"class":118},[42,1983,1984],{"class":77},"(err), context)\n",[42,1986,1987,1989],{"class":44,"line":413},[42,1988,283],{"class":74},[42,1990,135],{"class":77},[42,1992,1993,1995,1998,2000,2002,2005],{"class":44,"line":418},[42,1994,1276],{"class":74},[42,1996,1997],{"class":77}," ok ",[42,1999,1958],{"class":74},[42,2001,1961],{"class":74},[42,2003,2004],{"class":77}," result ",[42,2006,164],{"class":74},[42,2008,2009,2011,2014,2017],{"class":44,"line":423},[42,2010,141],{"class":74},[42,2012,2013],{"class":118}," nil",[42,2015,2016],{"class":77},", result  ",[42,2018,2019],{"class":48},"-- nil + Error (always structured on failure)\n",[42,2021,2022],{"class":44,"line":429},[42,2023,164],{"class":74},[42,2025,2026],{"class":44,"line":447},[42,2027,68],{"emptyLinePlaceholder":67},[42,2029,2030],{"class":44,"line":480},[42,2031,2032],{"class":48},"-- Usage: handler layer wraps business logic\n",[42,2034,2035,2037,2039,2042],{"class":44,"line":518},[42,2036,21],{"class":74},[42,2038,97],{"class":74},[42,2040,2041],{"class":100}," handle_request",[42,2043,2044],{"class":77},"(req)\n",[42,2046,2047,2049,2052,2054,2056,2058,2060],{"class":44,"line":561},[42,2048,110],{"class":74},[42,2050,2051],{"class":77}," result, err ",[42,2053,81],{"class":74},[42,2055,1918],{"class":118},[42,2057,122],{"class":77},[42,2059,1164],{"class":74},[42,2061,104],{"class":77},[42,2063,2064,2066,2068,2071,2074,2076,2078,2080,2083,2085,2088,2091,2094,2096],{"class":44,"line":594},[42,2065,203],{"class":74},[42,2067,206],{"class":74},[42,2069,2070],{"class":77}," req.",[42,2072,2073],{"class":100},"user_id",[42,2075,1286],{"class":74},[42,2077,1289],{"class":118},[42,2079,122],{"class":77},[42,2081,2082],{"class":118},"err_validation",[42,2084,122],{"class":77},[42,2086,2087],{"class":131},"\"user_id required\"",[42,2089,2090],{"class":77},"), ",[42,2092,2093],{"class":118},"0",[42,2095,1028],{"class":77},[42,2097,164],{"class":74},[42,2099,2100,2103,2106,2108,2111,2114,2116,2118],{"class":44,"line":637},[42,2101,2102],{"class":74},"    local",[42,2104,2105],{"class":77}," user ",[42,2107,81],{"class":74},[42,2109,2110],{"class":118}," get_user",[42,2112,2113],{"class":77},"(req.",[42,2115,2073],{"class":100},[42,2117,723],{"class":77},[42,2119,2120],{"class":48},"-- may throw\n",[42,2122,2123,2125],{"class":44,"line":646},[42,2124,272],{"class":74},[42,2126,2127],{"class":77}," user\n",[42,2129,2130,2132,2135,2137,2139,2142,2145,2147,2149,2152],{"class":44,"line":655},[42,2131,283],{"class":74},[42,2133,2134],{"class":77},", {path ",[42,2136,81],{"class":74},[42,2138,2070],{"class":77},[42,2140,2141],{"class":100},"path",[42,2143,2144],{"class":77},", method ",[42,2146,81],{"class":74},[42,2148,2070],{"class":77},[42,2150,2151],{"class":100},"method",[42,2153,375],{"class":77},[42,2155,2156],{"class":44,"line":660},[42,2157,68],{"emptyLinePlaceholder":67},[42,2159,2160,2162,2164],{"class":44,"line":665},[42,2161,1276],{"class":74},[42,2163,1964],{"class":77},[42,2165,212],{"class":74},[42,2167,2168,2170,2173,2175,2178,2181,2183,2185,2188,2190,2192],{"class":44,"line":671},[42,2169,203],{"class":74},[42,2171,2172],{"class":77}," err.",[42,2174,1481],{"class":100},[42,2176,2177],{"class":74}," ==",[42,2179,2180],{"class":131}," \"CLIENT\" ",[42,2182,1958],{"class":74},[42,2184,1961],{"class":74},[42,2186,2187],{"class":118}," 400",[42,2189,128],{"class":77},[42,2191,1335],{"class":118},[42,2193,1941],{"class":77},[42,2195,2196,2199,2201,2203,2205,2208,2210,2212,2215,2217,2219],{"class":44,"line":677},[42,2197,2198],{"class":74},"    elseif",[42,2200,2172],{"class":77},[42,2202,1481],{"class":100},[42,2204,2177],{"class":74},[42,2206,2207],{"class":131}," \"TRANSIENT\" ",[42,2209,1958],{"class":74},[42,2211,1961],{"class":74},[42,2213,2214],{"class":118}," 503",[42,2216,128],{"class":77},[42,2218,1335],{"class":118},[42,2220,1941],{"class":77},[42,2222,2223,2226,2228,2231,2233,2235,2237],{"class":44,"line":682},[42,2224,2225],{"class":74},"    else",[42,2227,1961],{"class":74},[42,2229,2230],{"class":118}," 500",[42,2232,128],{"class":77},[42,2234,1335],{"class":118},[42,2236,1951],{"class":77},[42,2238,164],{"class":74},[42,2240,2241],{"class":44,"line":1316},[42,2242,2243],{"class":74},"  end\n",[42,2245,2246,2248,2251],{"class":44,"line":1321},[42,2247,141],{"class":74},[42,2249,2250],{"class":118}," 200",[42,2252,2253],{"class":77},", result\n",[42,2255,2256],{"class":44,"line":1326},[42,2257,164],{"class":74},[24,2259,2261,2262,2265],{"id":2260},"resource-management-close-and-raii","Resource Management: ",[19,2263,2264],{},"\u003Cclose>"," and RAII",[29,2267,2268,2598,2602],{"language":31},[34,2269,2271],{"className":36,"code":2270,"language":31,"meta":38,"style":38},"-- Resource acquisition is initialization (RAII) via \u003Cclose> (Lua 5.4+)\n-- Resources are released when the scope exits — even on error\n\nlocal function acquire_resource(name)\n  local resource = {name = name, handle = open_resource(name)}\n  return setmetatable(resource, {\n    __close = function(self)\n      close_resource(self.handle)  -- deterministic cleanup\n      self.handle = nil\n    end,\n  })\nend\n\n-- Usage: resource auto-released at end of block (even on error)\nlocal function process_with_resource(name)\n  local r \u003Cclose> = acquire_resource(name)  -- acquired here\n  local data = read_from(r.handle)  -- use resource\n  if not data then error(\"read failed\") end  -- error: r still cleaned up\n  return transform(data)\n  -- r released here (deterministic, not GC-based)\nend\n\n-- Pre-5.4: manual try\u002Ffinally simulation\nlocal function with_resource(acquire_fn, use_fn)\n  local resource = acquire_fn()\n  local ok, err = pcall(use_fn, resource)\n  resource:close()  -- always cleanup\n  if not ok then error(err, 2) end\n  return resource.result\nend\n",[19,2272,2273,2278,2283,2287,2298,2323,2332,2343,2362,2376,2383,2388,2392,2396,2401,2412,2431,2453,2478,2488,2493,2497,2501,2506,2518,2531,2546,2563,2584,2594],{"__ignoreMap":38},[42,2274,2275],{"class":44,"line":45},[42,2276,2277],{"class":48},"-- Resource acquisition is initialization (RAII) via \u003Cclose> (Lua 5.4+)\n",[42,2279,2280],{"class":44,"line":52},[42,2281,2282],{"class":48},"-- Resources are released when the scope exits — even on error\n",[42,2284,2285],{"class":44,"line":58},[42,2286,68],{"emptyLinePlaceholder":67},[42,2288,2289,2291,2293,2296],{"class":44,"line":64},[42,2290,21],{"class":74},[42,2292,97],{"class":74},[42,2294,2295],{"class":100}," acquire_resource",[42,2297,399],{"class":77},[42,2299,2300,2302,2305,2307,2310,2312,2315,2317,2320],{"class":44,"line":71},[42,2301,110],{"class":74},[42,2303,2304],{"class":77}," resource ",[42,2306,81],{"class":74},[42,2308,2309],{"class":77}," {name ",[42,2311,81],{"class":74},[42,2313,2314],{"class":77}," name, handle ",[42,2316,81],{"class":74},[42,2318,2319],{"class":118}," open_resource",[42,2321,2322],{"class":77},"(name)}\n",[42,2324,2325,2327,2329],{"class":44,"line":87},[42,2326,141],{"class":74},[42,2328,1196],{"class":118},[42,2330,2331],{"class":77},"(resource, {\n",[42,2333,2334,2337,2339,2341],{"class":44,"line":92},[42,2335,2336],{"class":100},"    __close",[42,2338,192],{"class":74},[42,2340,97],{"class":74},[42,2342,1459],{"class":77},[42,2344,2345,2348,2350,2352,2354,2357,2359],{"class":44,"line":107},[42,2346,2347],{"class":118},"      close_resource",[42,2349,122],{"class":77},[42,2351,1476],{"class":118},[42,2353,1170],{"class":77},[42,2355,2356],{"class":100},"handle",[42,2358,723],{"class":77},[42,2360,2361],{"class":48},"-- deterministic cleanup\n",[42,2363,2364,2367,2369,2371,2373],{"class":44,"line":138},[42,2365,2366],{"class":118},"      self",[42,2368,1170],{"class":77},[42,2370,2356],{"class":100},[42,2372,192],{"class":74},[42,2374,2375],{"class":118}," nil\n",[42,2377,2378,2381],{"class":44,"line":161},[42,2379,2380],{"class":74},"    end",[42,2382,286],{"class":77},[42,2384,2385],{"class":44,"line":167},[42,2386,2387],{"class":77},"  })\n",[42,2389,2390],{"class":44,"line":172},[42,2391,164],{"class":74},[42,2393,2394],{"class":44,"line":186},[42,2395,68],{"emptyLinePlaceholder":67},[42,2397,2398],{"class":44,"line":200},[42,2399,2400],{"class":48},"-- Usage: resource auto-released at end of block (even on error)\n",[42,2402,2403,2405,2407,2410],{"class":44,"line":215},[42,2404,21],{"class":74},[42,2406,97],{"class":74},[42,2408,2409],{"class":100}," process_with_resource",[42,2411,399],{"class":77},[42,2413,2414,2416,2419,2421,2423,2425,2428],{"class":44,"line":238},[42,2415,110],{"class":74},[42,2417,2418],{"class":77}," r ",[42,2420,2264],{"class":74},[42,2422,192],{"class":74},[42,2424,2295],{"class":118},[42,2426,2427],{"class":77},"(name)  ",[42,2429,2430],{"class":48},"-- acquired here\n",[42,2432,2433,2435,2438,2440,2443,2446,2448,2450],{"class":44,"line":263},[42,2434,110],{"class":74},[42,2436,2437],{"class":77}," data ",[42,2439,81],{"class":74},[42,2441,2442],{"class":118}," read_from",[42,2444,2445],{"class":77},"(r.",[42,2447,2356],{"class":100},[42,2449,723],{"class":77},[42,2451,2452],{"class":48},"-- use resource\n",[42,2454,2455,2457,2459,2461,2463,2465,2467,2470,2472,2475],{"class":44,"line":269},[42,2456,1276],{"class":74},[42,2458,206],{"class":74},[42,2460,2437],{"class":77},[42,2462,1958],{"class":74},[42,2464,1289],{"class":118},[42,2466,122],{"class":77},[42,2468,2469],{"class":131},"\"read failed\"",[42,2471,1028],{"class":77},[42,2473,2474],{"class":74},"end",[42,2476,2477],{"class":48},"  -- error: r still cleaned up\n",[42,2479,2480,2482,2485],{"class":44,"line":280},[42,2481,141],{"class":74},[42,2483,2484],{"class":118}," transform",[42,2486,2487],{"class":77},"(data)\n",[42,2489,2490],{"class":44,"line":289},[42,2491,2492],{"class":48},"  -- r released here (deterministic, not GC-based)\n",[42,2494,2495],{"class":44,"line":302},[42,2496,164],{"class":74},[42,2498,2499],{"class":44,"line":313},[42,2500,68],{"emptyLinePlaceholder":67},[42,2502,2503],{"class":44,"line":331},[42,2504,2505],{"class":48},"-- Pre-5.4: manual try\u002Ffinally simulation\n",[42,2507,2508,2510,2512,2515],{"class":44,"line":352},[42,2509,21],{"class":74},[42,2511,97],{"class":74},[42,2513,2514],{"class":100}," with_resource",[42,2516,2517],{"class":77},"(acquire_fn, use_fn)\n",[42,2519,2520,2522,2524,2526,2529],{"class":44,"line":357},[42,2521,110],{"class":74},[42,2523,2304],{"class":77},[42,2525,81],{"class":74},[42,2527,2528],{"class":118}," acquire_fn",[42,2530,104],{"class":77},[42,2532,2533,2535,2538,2540,2543],{"class":44,"line":365},[42,2534,110],{"class":74},[42,2536,2537],{"class":77}," ok, err ",[42,2539,81],{"class":74},[42,2541,2542],{"class":118}," pcall",[42,2544,2545],{"class":77},"(use_fn, resource)\n",[42,2547,2548,2551,2554,2557,2560],{"class":44,"line":372},[42,2549,2550],{"class":100},"  resource",[42,2552,2553],{"class":77},":",[42,2555,2556],{"class":118},"close",[42,2558,2559],{"class":77},"()  ",[42,2561,2562],{"class":48},"-- always cleanup\n",[42,2564,2565,2567,2569,2571,2573,2575,2578,2580,2582],{"class":44,"line":378},[42,2566,1276],{"class":74},[42,2568,206],{"class":74},[42,2570,1997],{"class":77},[42,2572,1958],{"class":74},[42,2574,1289],{"class":118},[42,2576,2577],{"class":77},"(err, ",[42,2579,258],{"class":118},[42,2581,1028],{"class":77},[42,2583,164],{"class":74},[42,2585,2586,2588,2591],{"class":44,"line":383},[42,2587,141],{"class":74},[42,2589,2590],{"class":77}," resource.",[42,2592,2593],{"class":100},"result\n",[42,2595,2596],{"class":44,"line":389},[42,2597,164],{"class":74},[24,2599,2601],{"id":2600},"configuration-management","Configuration Management",[29,2603,2604,3261,3265],{"language":31},[34,2605,2607],{"className":36,"code":2606,"language":31,"meta":38,"style":38},"-- Config: layered defaults + file + environment + CLI args (precedence: CLI > env > file > default)\n\nlocal function load_config(cli_opts)\n  -- Layer 1: defaults (lowest priority)\n  local config = {\n    host = \"0.0.0.0\",\n    port = 8080,\n    log_level = \"info\",\n    max_connections = 1024,\n  }\n\n  -- Layer 2: config file (Lua table format — load with loadfile for safety)\n  local f = io.open(\"config.lua\", \"r\")\n  if f then\n    local content = f:read(\"*a\")\n    f:close()\n    local fn = load(content, \"config\", \"t\", setmetatable({}, {__index = config}))\n    if fn then\n      local file_config = fn()\n      if type(file_config) == \"table\" then\n        for k, v in pairs(file_config) do config[k] = v end  -- file overrides defaults\n      end\n    end\n  end\n\n  -- Layer 3: environment variables (override file)\n  local env_map = {\n    APP_HOST = \"host\",\n    APP_PORT = \"port\",\n    APP_LOG_LEVEL = \"log_level\",\n    APP_MAX_CONN = \"max_connections\",\n  }\n  for env_key, config_key in pairs(env_map) do\n    local value = os.getenv(env_key)\n    if value then\n      -- type coercion: numbers stay numbers\n      config[config_key] = tonumber(value) or value\n    end\n  end\n\n  -- Layer 4: CLI args (highest priority)\n  if cli_opts then\n    for k, v in pairs(cli_opts) do\n      config[k] = v\n    end\n  end\n\n  -- Validation\n  assert(type(config.port) == \"number\" and config.port > 0 and config.port \u003C 65536,\n    \"invalid port: \" .. tostring(config.port))\n  assert(config.log_level == \"debug\" or config.log_level == \"info\" or config.log_level == \"warn\" or config.log_level == \"error\",\n    \"invalid log_level: \" .. tostring(config.log_level))\n\n  return config\nend\n\nlocal config = load_config({port = 9090})  -- CLI overrides everything else\n",[19,2608,2609,2614,2618,2630,2635,2646,2657,2667,2679,2691,2695,2699,2704,2728,2736,2760,2771,2806,2814,2829,2847,2878,2883,2887,2891,2895,2900,2911,2923,2935,2947,2959,2963,2980,2995,3003,3008,3026,3030,3034,3038,3043,3052,3068,3078,3082,3086,3090,3095,3138,3154,3203,3218,3222,3229,3233,3237],{"__ignoreMap":38},[42,2610,2611],{"class":44,"line":45},[42,2612,2613],{"class":48},"-- Config: layered defaults + file + environment + CLI args (precedence: CLI > env > file > default)\n",[42,2615,2616],{"class":44,"line":52},[42,2617,68],{"emptyLinePlaceholder":67},[42,2619,2620,2622,2624,2627],{"class":44,"line":58},[42,2621,21],{"class":74},[42,2623,97],{"class":74},[42,2625,2626],{"class":100}," load_config",[42,2628,2629],{"class":77},"(cli_opts)\n",[42,2631,2632],{"class":44,"line":64},[42,2633,2634],{"class":48},"  -- Layer 1: defaults (lowest priority)\n",[42,2636,2637,2639,2642,2644],{"class":44,"line":71},[42,2638,110],{"class":74},[42,2640,2641],{"class":77}," config ",[42,2643,81],{"class":74},[42,2645,937],{"class":77},[42,2647,2648,2650,2652,2655],{"class":44,"line":87},[42,2649,1110],{"class":77},[42,2651,81],{"class":74},[42,2653,2654],{"class":131}," \"0.0.0.0\"",[42,2656,286],{"class":77},[42,2658,2659,2661,2663,2665],{"class":44,"line":92},[42,2660,1122],{"class":77},[42,2662,81],{"class":74},[42,2664,1127],{"class":118},[42,2666,286],{"class":77},[42,2668,2669,2672,2674,2677],{"class":44,"line":107},[42,2670,2671],{"class":77},"    log_level ",[42,2673,81],{"class":74},[42,2675,2676],{"class":131}," \"info\"",[42,2678,286],{"class":77},[42,2680,2681,2684,2686,2689],{"class":44,"line":138},[42,2682,2683],{"class":77},"    max_connections ",[42,2685,81],{"class":74},[42,2687,2688],{"class":118}," 1024",[42,2690,286],{"class":77},[42,2692,2693],{"class":44,"line":161},[42,2694,1146],{"class":77},[42,2696,2697],{"class":44,"line":167},[42,2698,68],{"emptyLinePlaceholder":67},[42,2700,2701],{"class":44,"line":172},[42,2702,2703],{"class":48},"  -- Layer 2: config file (Lua table format — load with loadfile for safety)\n",[42,2705,2706,2708,2711,2713,2716,2718,2721,2723,2726],{"class":44,"line":186},[42,2707,110],{"class":74},[42,2709,2710],{"class":77}," f ",[42,2712,81],{"class":74},[42,2714,2715],{"class":118}," io.open",[42,2717,122],{"class":77},[42,2719,2720],{"class":131},"\"config.lua\"",[42,2722,128],{"class":77},[42,2724,2725],{"class":131},"\"r\"",[42,2727,135],{"class":77},[42,2729,2730,2732,2734],{"class":44,"line":200},[42,2731,1276],{"class":74},[42,2733,2710],{"class":77},[42,2735,212],{"class":74},[42,2737,2738,2740,2743,2745,2748,2750,2753,2755,2758],{"class":44,"line":215},[42,2739,2102],{"class":74},[42,2741,2742],{"class":77}," content ",[42,2744,81],{"class":74},[42,2746,2747],{"class":100}," f",[42,2749,2553],{"class":77},[42,2751,2752],{"class":118},"read",[42,2754,122],{"class":77},[42,2756,2757],{"class":131},"\"*a\"",[42,2759,135],{"class":77},[42,2761,2762,2765,2767,2769],{"class":44,"line":238},[42,2763,2764],{"class":100},"    f",[42,2766,2553],{"class":77},[42,2768,2556],{"class":118},[42,2770,104],{"class":77},[42,2772,2773,2775,2778,2780,2783,2786,2789,2791,2794,2796,2798,2801,2803],{"class":44,"line":263},[42,2774,2102],{"class":74},[42,2776,2777],{"class":77}," fn ",[42,2779,81],{"class":74},[42,2781,2782],{"class":118}," load",[42,2784,2785],{"class":77},"(content, ",[42,2787,2788],{"class":131},"\"config\"",[42,2790,128],{"class":77},[42,2792,2793],{"class":131},"\"t\"",[42,2795,128],{"class":77},[42,2797,175],{"class":118},[42,2799,2800],{"class":77},"({}, {__index ",[42,2802,81],{"class":74},[42,2804,2805],{"class":77}," config}))\n",[42,2807,2808,2810,2812],{"class":44,"line":269},[42,2809,203],{"class":74},[42,2811,2777],{"class":77},[42,2813,212],{"class":74},[42,2815,2816,2819,2822,2824,2827],{"class":44,"line":280},[42,2817,2818],{"class":74},"      local",[42,2820,2821],{"class":77}," file_config ",[42,2823,81],{"class":74},[42,2825,2826],{"class":118}," fn",[42,2828,104],{"class":77},[42,2830,2831,2834,2837,2840,2842,2845],{"class":44,"line":289},[42,2832,2833],{"class":74},"      if",[42,2835,2836],{"class":118}," type",[42,2838,2839],{"class":77},"(file_config) ",[42,2841,1001],{"class":74},[42,2843,2844],{"class":131}," \"table\" ",[42,2846,212],{"class":74},[42,2848,2849,2852,2855,2857,2860,2862,2865,2868,2870,2873,2875],{"class":44,"line":302},[42,2850,2851],{"class":74},"        for",[42,2853,2854],{"class":77}," k, v ",[42,2856,438],{"class":74},[42,2858,2859],{"class":118}," pairs",[42,2861,2839],{"class":77},[42,2863,2864],{"class":74},"do",[42,2866,2867],{"class":77}," config[k] ",[42,2869,81],{"class":74},[42,2871,2872],{"class":77}," v ",[42,2874,2474],{"class":74},[42,2876,2877],{"class":48},"  -- file overrides defaults\n",[42,2879,2880],{"class":44,"line":313},[42,2881,2882],{"class":74},"      end\n",[42,2884,2885],{"class":44,"line":331},[42,2886,266],{"class":74},[42,2888,2889],{"class":44,"line":352},[42,2890,2243],{"class":74},[42,2892,2893],{"class":44,"line":357},[42,2894,68],{"emptyLinePlaceholder":67},[42,2896,2897],{"class":44,"line":365},[42,2898,2899],{"class":48},"  -- Layer 3: environment variables (override file)\n",[42,2901,2902,2904,2907,2909],{"class":44,"line":372},[42,2903,110],{"class":74},[42,2905,2906],{"class":77}," env_map ",[42,2908,81],{"class":74},[42,2910,937],{"class":77},[42,2912,2913,2916,2918,2921],{"class":44,"line":378},[42,2914,2915],{"class":77},"    APP_HOST ",[42,2917,81],{"class":74},[42,2919,2920],{"class":131}," \"host\"",[42,2922,286],{"class":77},[42,2924,2925,2928,2930,2933],{"class":44,"line":383},[42,2926,2927],{"class":77},"    APP_PORT ",[42,2929,81],{"class":74},[42,2931,2932],{"class":131}," \"port\"",[42,2934,286],{"class":77},[42,2936,2937,2940,2942,2945],{"class":44,"line":389},[42,2938,2939],{"class":77},"    APP_LOG_LEVEL ",[42,2941,81],{"class":74},[42,2943,2944],{"class":131}," \"log_level\"",[42,2946,286],{"class":77},[42,2948,2949,2952,2954,2957],{"class":44,"line":402},[42,2950,2951],{"class":77},"    APP_MAX_CONN ",[42,2953,81],{"class":74},[42,2955,2956],{"class":131}," \"max_connections\"",[42,2958,286],{"class":77},[42,2960,2961],{"class":44,"line":413},[42,2962,1146],{"class":77},[42,2964,2965,2968,2971,2973,2975,2978],{"class":44,"line":418},[42,2966,2967],{"class":74},"  for",[42,2969,2970],{"class":77}," env_key, config_key ",[42,2972,438],{"class":74},[42,2974,2859],{"class":118},[42,2976,2977],{"class":77},"(env_map) ",[42,2979,643],{"class":74},[42,2981,2982,2984,2987,2989,2992],{"class":44,"line":423},[42,2983,2102],{"class":74},[42,2985,2986],{"class":77}," value ",[42,2988,81],{"class":74},[42,2990,2991],{"class":118}," os.getenv",[42,2993,2994],{"class":77},"(env_key)\n",[42,2996,2997,2999,3001],{"class":44,"line":429},[42,2998,203],{"class":74},[42,3000,2986],{"class":77},[42,3002,212],{"class":74},[42,3004,3005],{"class":44,"line":447},[42,3006,3007],{"class":48},"      -- type coercion: numbers stay numbers\n",[42,3009,3010,3013,3015,3018,3021,3023],{"class":44,"line":480},[42,3011,3012],{"class":77},"      config[config_key] ",[42,3014,81],{"class":74},[42,3016,3017],{"class":118}," tonumber",[42,3019,3020],{"class":77},"(value) ",[42,3022,1221],{"class":74},[42,3024,3025],{"class":77}," value\n",[42,3027,3028],{"class":44,"line":518},[42,3029,266],{"class":74},[42,3031,3032],{"class":44,"line":561},[42,3033,2243],{"class":74},[42,3035,3036],{"class":44,"line":594},[42,3037,68],{"emptyLinePlaceholder":67},[42,3039,3040],{"class":44,"line":637},[42,3041,3042],{"class":48},"  -- Layer 4: CLI args (highest priority)\n",[42,3044,3045,3047,3050],{"class":44,"line":646},[42,3046,1276],{"class":74},[42,3048,3049],{"class":77}," cli_opts ",[42,3051,212],{"class":74},[42,3053,3054,3057,3059,3061,3063,3066],{"class":44,"line":655},[42,3055,3056],{"class":74},"    for",[42,3058,2854],{"class":77},[42,3060,438],{"class":74},[42,3062,2859],{"class":118},[42,3064,3065],{"class":77},"(cli_opts) ",[42,3067,643],{"class":74},[42,3069,3070,3073,3075],{"class":44,"line":660},[42,3071,3072],{"class":77},"      config[k] ",[42,3074,81],{"class":74},[42,3076,3077],{"class":77}," v\n",[42,3079,3080],{"class":44,"line":665},[42,3081,266],{"class":74},[42,3083,3084],{"class":44,"line":671},[42,3085,2243],{"class":74},[42,3087,3088],{"class":44,"line":677},[42,3089,68],{"emptyLinePlaceholder":67},[42,3091,3092],{"class":44,"line":682},[42,3093,3094],{"class":48},"  -- Validation\n",[42,3096,3097,3099,3101,3103,3105,3107,3109,3111,3113,3115,3117,3119,3121,3123,3126,3128,3130,3133,3136],{"class":44,"line":1316},[42,3098,990],{"class":118},[42,3100,122],{"class":77},[42,3102,995],{"class":118},[42,3104,1022],{"class":77},[42,3106,1053],{"class":100},[42,3108,1028],{"class":77},[42,3110,1001],{"class":74},[42,3112,1060],{"class":131},[42,3114,146],{"class":74},[42,3116,1065],{"class":77},[42,3118,1053],{"class":100},[42,3120,1070],{"class":74},[42,3122,1073],{"class":118},[42,3124,3125],{"class":74}," and",[42,3127,1065],{"class":77},[42,3129,1053],{"class":100},[42,3131,3132],{"class":74}," \u003C",[42,3134,3135],{"class":118}," 65536",[42,3137,286],{"class":77},[42,3139,3140,3143,3145,3147,3149,3151],{"class":44,"line":1321},[42,3141,3142],{"class":131},"    \"invalid port: \" ",[42,3144,226],{"class":74},[42,3146,229],{"class":118},[42,3148,1022],{"class":77},[42,3150,1053],{"class":100},[42,3152,3153],{"class":77},"))\n",[42,3155,3156,3158,3160,3163,3165,3168,3170,3172,3174,3176,3179,3181,3183,3185,3187,3190,3192,3194,3196,3198,3201],{"class":44,"line":1326},[42,3157,990],{"class":118},[42,3159,1022],{"class":77},[42,3161,3162],{"class":100},"log_level",[42,3164,2177],{"class":74},[42,3166,3167],{"class":131}," \"debug\" ",[42,3169,1221],{"class":74},[42,3171,1065],{"class":77},[42,3173,3162],{"class":100},[42,3175,2177],{"class":74},[42,3177,3178],{"class":131}," \"info\" ",[42,3180,1221],{"class":74},[42,3182,1065],{"class":77},[42,3184,3162],{"class":100},[42,3186,2177],{"class":74},[42,3188,3189],{"class":131}," \"warn\" ",[42,3191,1221],{"class":74},[42,3193,1065],{"class":77},[42,3195,3162],{"class":100},[42,3197,2177],{"class":74},[42,3199,3200],{"class":131}," \"error\"",[42,3202,286],{"class":77},[42,3204,3205,3208,3210,3212,3214,3216],{"class":44,"line":1340},[42,3206,3207],{"class":131},"    \"invalid log_level: \" ",[42,3209,226],{"class":74},[42,3211,229],{"class":118},[42,3213,1022],{"class":77},[42,3215,3162],{"class":100},[42,3217,3153],{"class":77},[42,3219,3220],{"class":44,"line":1355},[42,3221,68],{"emptyLinePlaceholder":67},[42,3223,3224,3226],{"class":44,"line":1384},[42,3225,141],{"class":74},[42,3227,3228],{"class":77}," config\n",[42,3230,3231],{"class":44,"line":1389},[42,3232,164],{"class":74},[42,3234,3235],{"class":44,"line":1394},[42,3236,68],{"emptyLinePlaceholder":67},[42,3238,3239,3241,3243,3245,3247,3250,3252,3255,3258],{"class":44,"line":1400},[42,3240,21],{"class":74},[42,3242,2641],{"class":77},[42,3244,81],{"class":74},[42,3246,2626],{"class":118},[42,3248,3249],{"class":77},"({port ",[42,3251,81],{"class":74},[42,3253,3254],{"class":118}," 9090",[42,3256,3257],{"class":77},"})  ",[42,3259,3260],{"class":48},"-- CLI overrides everything else\n",[24,3262,3264],{"id":3263},"logging-architecture","Logging Architecture",[29,3266,3267,3964,3968],{"language":31},[34,3268,3270],{"className":36,"code":3269,"language":31,"meta":38,"style":38},"-- Structured logger with levels, formatting, and output routing\nlocal Logger = {}\nLogger.__index = Logger\n\nlocal LEVELS = {debug = 1, info = 2, warn = 3, error = 4, fatal = 5 }\n\nfunction Logger.new(opts)\n  opts = opts or {}\n  return setmetatable({\n    level = LEVELS[opts.level or \"info\"],\n    output = opts.output or io.stderr,\n    format = opts.format or \"text\",  -- \"text\" or \"json\"\n  }, Logger)\nend\n\nfunction Logger:log(level, msg, context)\n  local lvl = LEVELS[level]\n  if lvl \u003C self.level then return end  -- filter below threshold\n  local entry = {\n    timestamp = os.date(\"%Y-%m-%dT%H:%M:%S\"),\n    level = string.upper(level),\n    msg = msg,\n    context = context or {},\n  }\n  if self.format == \"json\" then\n    -- simplified JSON encoding (use cjson in production)\n    local parts = {}\n    for k, v in pairs(entry) do\n      parts[#parts + 1] = string.format('\"%s\":%q', k, tostring(v))\n    end\n    self.output:write(\"{\" .. table.concat(parts, \",\") .. \"}\\n\")\n  else\n    self.output:write(string.format(\"[%s] %s %s\\n\",\n      entry.timestamp, entry.level, entry.msg))\n  end\n  self.output:flush()  -- ensure logs are written (for crash debugging)\nend\n\n-- Convenience methods\nfor level in pairs(LEVELS) do\n  Logger[level] = function(self, msg, ctx) self:log(level, msg, ctx) end\nend\n\n-- Usage\nlocal log = Logger.new{level = \"debug\"}\nlog:info(\"server started\", {port = 8080})\nlog:debug(\"config loaded\", {path = \"config.lua\"})\nlog:error(\"connection failed\", {host = \"db.example.com\", err = \"timeout\"})\n",[19,3271,3272,3277,3288,3300,3304,3355,3359,3372,3386,3394,3414,3433,3455,3460,3464,3468,3482,3494,3520,3531,3549,3561,3571,3584,3588,3605,3610,3621,3636,3672,3676,3721,3726,3754,3774,3778,3797,3801,3805,3810,3826,3849,3853,3857,3862,3886,3909,3932],{"__ignoreMap":38},[42,3273,3274],{"class":44,"line":45},[42,3275,3276],{"class":48},"-- Structured logger with levels, formatting, and output routing\n",[42,3278,3279,3281,3284,3286],{"class":44,"line":52},[42,3280,21],{"class":74},[42,3282,3283],{"class":77}," Logger ",[42,3285,81],{"class":74},[42,3287,84],{"class":77},[42,3289,3290,3293,3295,3297],{"class":44,"line":58},[42,3291,3292],{"class":77},"Logger.",[42,3294,1440],{"class":100},[42,3296,192],{"class":74},[42,3298,3299],{"class":77}," Logger\n",[42,3301,3302],{"class":44,"line":64},[42,3303,68],{"emptyLinePlaceholder":67},[42,3305,3306,3308,3311,3313,3316,3318,3321,3324,3326,3329,3332,3334,3337,3340,3342,3345,3348,3350,3352],{"class":44,"line":71},[42,3307,21],{"class":74},[42,3309,3310],{"class":77}," LEVELS ",[42,3312,81],{"class":74},[42,3314,3315],{"class":77}," {debug ",[42,3317,81],{"class":74},[42,3319,3320],{"class":118}," 1",[42,3322,3323],{"class":77},", info ",[42,3325,81],{"class":74},[42,3327,3328],{"class":118}," 2",[42,3330,3331],{"class":77},", warn ",[42,3333,81],{"class":74},[42,3335,3336],{"class":118}," 3",[42,3338,3339],{"class":77},", error ",[42,3341,81],{"class":74},[42,3343,3344],{"class":118}," 4",[42,3346,3347],{"class":77},", fatal ",[42,3349,81],{"class":74},[42,3351,772],{"class":118},[42,3353,3354],{"class":77}," }\n",[42,3356,3357],{"class":44,"line":87},[42,3358,68],{"emptyLinePlaceholder":67},[42,3360,3361,3363,3366,3368,3370],{"class":44,"line":92},[42,3362,1164],{"class":74},[42,3364,3365],{"class":100}," Logger",[42,3367,1170],{"class":77},[42,3369,1520],{"class":100},[42,3371,1523],{"class":77},[42,3373,3374,3377,3379,3382,3384],{"class":44,"line":107},[42,3375,3376],{"class":77},"  opts ",[42,3378,81],{"class":74},[42,3380,3381],{"class":77}," opts ",[42,3383,1221],{"class":74},[42,3385,84],{"class":77},[42,3387,3388,3390,3392],{"class":44,"line":138},[42,3389,141],{"class":74},[42,3391,1196],{"class":118},[42,3393,444],{"class":77},[42,3395,3396,3399,3401,3404,3407,3409,3411],{"class":44,"line":161},[42,3397,3398],{"class":77},"    level ",[42,3400,81],{"class":74},[42,3402,3403],{"class":77}," LEVELS[opts.",[42,3405,3406],{"class":100},"level",[42,3408,155],{"class":74},[42,3410,2676],{"class":131},[42,3412,3413],{"class":77},"],\n",[42,3415,3416,3419,3421,3423,3426,3428,3431],{"class":44,"line":167},[42,3417,3418],{"class":77},"    output ",[42,3420,81],{"class":74},[42,3422,1541],{"class":77},[42,3424,3425],{"class":100},"output",[42,3427,155],{"class":74},[42,3429,3430],{"class":118}," io.stderr",[42,3432,286],{"class":77},[42,3434,3435,3438,3440,3442,3445,3447,3450,3452],{"class":44,"line":172},[42,3436,3437],{"class":77},"    format ",[42,3439,81],{"class":74},[42,3441,1541],{"class":77},[42,3443,3444],{"class":100},"format",[42,3446,155],{"class":74},[42,3448,3449],{"class":131}," \"text\"",[42,3451,1635],{"class":77},[42,3453,3454],{"class":48},"-- \"text\" or \"json\"\n",[42,3456,3457],{"class":44,"line":186},[42,3458,3459],{"class":77},"  }, Logger)\n",[42,3461,3462],{"class":44,"line":200},[42,3463,164],{"class":74},[42,3465,3466],{"class":44,"line":215},[42,3467,68],{"emptyLinePlaceholder":67},[42,3469,3470,3472,3474,3476,3479],{"class":44,"line":238},[42,3471,1164],{"class":74},[42,3473,3365],{"class":100},[42,3475,2553],{"class":77},[42,3477,3478],{"class":100},"log",[42,3480,3481],{"class":77},"(level, msg, context)\n",[42,3483,3484,3486,3489,3491],{"class":44,"line":263},[42,3485,110],{"class":74},[42,3487,3488],{"class":77}," lvl ",[42,3490,81],{"class":74},[42,3492,3493],{"class":77}," LEVELS[level]\n",[42,3495,3496,3498,3500,3503,3506,3508,3510,3512,3514,3517],{"class":44,"line":269},[42,3497,1276],{"class":74},[42,3499,3488],{"class":77},[42,3501,3502],{"class":74},"\u003C",[42,3504,3505],{"class":118}," self",[42,3507,1170],{"class":77},[42,3509,3406],{"class":100},[42,3511,1286],{"class":74},[42,3513,1961],{"class":74},[42,3515,3516],{"class":74}," end",[42,3518,3519],{"class":48},"  -- filter below threshold\n",[42,3521,3522,3524,3527,3529],{"class":44,"line":280},[42,3523,110],{"class":74},[42,3525,3526],{"class":77}," entry ",[42,3528,81],{"class":74},[42,3530,937],{"class":77},[42,3532,3533,3536,3538,3541,3543,3546],{"class":44,"line":289},[42,3534,3535],{"class":77},"    timestamp ",[42,3537,81],{"class":74},[42,3539,3540],{"class":118}," os.date",[42,3542,122],{"class":77},[42,3544,3545],{"class":131},"\"%Y-%m-%dT%H:%M:%S\"",[42,3547,3548],{"class":77},"),\n",[42,3550,3551,3553,3555,3558],{"class":44,"line":302},[42,3552,3398],{"class":77},[42,3554,81],{"class":74},[42,3556,3557],{"class":118}," string.upper",[42,3559,3560],{"class":77},"(level),\n",[42,3562,3563,3566,3568],{"class":44,"line":313},[42,3564,3565],{"class":77},"    msg ",[42,3567,81],{"class":74},[42,3569,3570],{"class":77}," msg,\n",[42,3572,3573,3575,3577,3580,3582],{"class":44,"line":331},[42,3574,1609],{"class":77},[42,3576,81],{"class":74},[42,3578,3579],{"class":77}," context ",[42,3581,1221],{"class":74},[42,3583,959],{"class":77},[42,3585,3586],{"class":44,"line":352},[42,3587,1146],{"class":77},[42,3589,3590,3592,3594,3596,3598,3600,3603],{"class":44,"line":357},[42,3591,1276],{"class":74},[42,3593,3505],{"class":118},[42,3595,1170],{"class":77},[42,3597,3444],{"class":100},[42,3599,2177],{"class":74},[42,3601,3602],{"class":131}," \"json\" ",[42,3604,212],{"class":74},[42,3606,3607],{"class":44,"line":365},[42,3608,3609],{"class":48},"    -- simplified JSON encoding (use cjson in production)\n",[42,3611,3612,3614,3617,3619],{"class":44,"line":372},[42,3613,2102],{"class":74},[42,3615,3616],{"class":77}," parts ",[42,3618,81],{"class":74},[42,3620,84],{"class":77},[42,3622,3623,3625,3627,3629,3631,3634],{"class":44,"line":378},[42,3624,3056],{"class":74},[42,3626,2854],{"class":77},[42,3628,438],{"class":74},[42,3630,2859],{"class":118},[42,3632,3633],{"class":77},"(entry) ",[42,3635,643],{"class":74},[42,3637,3638,3641,3644,3647,3650,3652,3655,3657,3659,3661,3664,3667,3669],{"class":44,"line":383},[42,3639,3640],{"class":77},"      parts[",[42,3642,3643],{"class":74},"#",[42,3645,3646],{"class":77},"parts ",[42,3648,3649],{"class":74},"+",[42,3651,3320],{"class":118},[42,3653,3654],{"class":77},"] ",[42,3656,81],{"class":74},[42,3658,1466],{"class":118},[42,3660,122],{"class":77},[42,3662,3663],{"class":131},"'\"%s\":%q'",[42,3665,3666],{"class":77},", k, ",[42,3668,1335],{"class":118},[42,3670,3671],{"class":77},"(v))\n",[42,3673,3674],{"class":44,"line":389},[42,3675,266],{"class":74},[42,3677,3678,3681,3683,3685,3687,3690,3692,3695,3697,3700,3703,3706,3708,3710,3713,3716,3719],{"class":44,"line":402},[42,3679,3680],{"class":118},"    self",[42,3682,1170],{"class":77},[42,3684,3425],{"class":100},[42,3686,2553],{"class":77},[42,3688,3689],{"class":118},"write",[42,3691,122],{"class":77},[42,3693,3694],{"class":131},"\"{\" ",[42,3696,226],{"class":74},[42,3698,3699],{"class":118}," table.concat",[42,3701,3702],{"class":77},"(parts, ",[42,3704,3705],{"class":131},"\",\"",[42,3707,1028],{"class":77},[42,3709,226],{"class":74},[42,3711,3712],{"class":131}," \"}",[42,3714,3715],{"class":118},"\\n",[42,3717,3718],{"class":131},"\"",[42,3720,135],{"class":77},[42,3722,3723],{"class":44,"line":413},[42,3724,3725],{"class":74},"  else\n",[42,3727,3728,3730,3732,3734,3736,3738,3740,3743,3745,3748,3750,3752],{"class":44,"line":418},[42,3729,3680],{"class":118},[42,3731,1170],{"class":77},[42,3733,3425],{"class":100},[42,3735,2553],{"class":77},[42,3737,3689],{"class":118},[42,3739,122],{"class":77},[42,3741,3742],{"class":118},"string.format",[42,3744,122],{"class":77},[42,3746,3747],{"class":131},"\"[%s] %s %s",[42,3749,3715],{"class":118},[42,3751,3718],{"class":131},[42,3753,286],{"class":77},[42,3755,3756,3759,3762,3765,3767,3769,3772],{"class":44,"line":423},[42,3757,3758],{"class":77},"      entry.",[42,3760,3761],{"class":100},"timestamp",[42,3763,3764],{"class":77},", entry.",[42,3766,3406],{"class":100},[42,3768,3764],{"class":77},[42,3770,3771],{"class":100},"msg",[42,3773,3153],{"class":77},[42,3775,3776],{"class":44,"line":429},[42,3777,2243],{"class":74},[42,3779,3780,3783,3785,3787,3789,3792,3794],{"class":44,"line":447},[42,3781,3782],{"class":118},"  self",[42,3784,1170],{"class":77},[42,3786,3425],{"class":100},[42,3788,2553],{"class":77},[42,3790,3791],{"class":118},"flush",[42,3793,2559],{"class":77},[42,3795,3796],{"class":48},"-- ensure logs are written (for crash debugging)\n",[42,3798,3799],{"class":44,"line":480},[42,3800,164],{"class":74},[42,3802,3803],{"class":44,"line":518},[42,3804,68],{"emptyLinePlaceholder":67},[42,3806,3807],{"class":44,"line":561},[42,3808,3809],{"class":48},"-- Convenience methods\n",[42,3811,3812,3814,3817,3819,3821,3824],{"class":44,"line":594},[42,3813,432],{"class":74},[42,3815,3816],{"class":77}," level ",[42,3818,438],{"class":74},[42,3820,2859],{"class":118},[42,3822,3823],{"class":77},"(LEVELS) ",[42,3825,643],{"class":74},[42,3827,3828,3831,3833,3835,3838,3840,3842,3844,3847],{"class":44,"line":637},[42,3829,3830],{"class":77},"  Logger[level] ",[42,3832,81],{"class":74},[42,3834,97],{"class":74},[42,3836,3837],{"class":77},"(self, msg, ctx) ",[42,3839,1476],{"class":118},[42,3841,2553],{"class":77},[42,3843,3478],{"class":118},[42,3845,3846],{"class":77},"(level, msg, ctx) ",[42,3848,164],{"class":74},[42,3850,3851],{"class":44,"line":646},[42,3852,164],{"class":74},[42,3854,3855],{"class":44,"line":655},[42,3856,68],{"emptyLinePlaceholder":67},[42,3858,3859],{"class":44,"line":660},[42,3860,3861],{"class":48},"-- Usage\n",[42,3863,3864,3866,3869,3871,3874,3876,3879,3881,3884],{"class":44,"line":665},[42,3865,21],{"class":74},[42,3867,3868],{"class":77}," log ",[42,3870,81],{"class":74},[42,3872,3873],{"class":77}," Logger.",[42,3875,1520],{"class":118},[42,3877,3878],{"class":77},"{level ",[42,3880,81],{"class":74},[42,3882,3883],{"class":131}," \"debug\"",[42,3885,964],{"class":77},[42,3887,3888,3890,3892,3895,3897,3900,3903,3905,3907],{"class":44,"line":671},[42,3889,3478],{"class":100},[42,3891,2553],{"class":77},[42,3893,3894],{"class":118},"info",[42,3896,122],{"class":77},[42,3898,3899],{"class":131},"\"server started\"",[42,3901,3902],{"class":77},", {port ",[42,3904,81],{"class":74},[42,3906,1127],{"class":118},[42,3908,375],{"class":77},[42,3910,3911,3913,3915,3918,3920,3923,3925,3927,3930],{"class":44,"line":677},[42,3912,3478],{"class":100},[42,3914,2553],{"class":77},[42,3916,3917],{"class":118},"debug",[42,3919,122],{"class":77},[42,3921,3922],{"class":131},"\"config loaded\"",[42,3924,2134],{"class":77},[42,3926,81],{"class":74},[42,3928,3929],{"class":131}," \"config.lua\"",[42,3931,375],{"class":77},[42,3933,3934,3936,3938,3941,3943,3946,3949,3951,3954,3957,3959,3962],{"class":44,"line":682},[42,3935,3478],{"class":100},[42,3937,2553],{"class":77},[42,3939,3940],{"class":118},"error",[42,3942,122],{"class":77},[42,3944,3945],{"class":131},"\"connection failed\"",[42,3947,3948],{"class":77},", {host ",[42,3950,81],{"class":74},[42,3952,3953],{"class":131}," \"db.example.com\"",[42,3955,3956],{"class":77},", err ",[42,3958,81],{"class":74},[42,3960,3961],{"class":131}," \"timeout\"",[42,3963,375],{"class":77},[24,3965,3967],{"id":3966},"immutable-data-functional-updates","Immutable Data & Functional Updates",[29,3969,3970,4389,4393],{"language":31},[34,3971,3973],{"className":36,"code":3972,"language":31,"meta":38,"style":38},"-- Anti-pattern: mutable shared state (hard to reason about, hard to test)\n-- Good: functional updates (return new data, don't mutate)\n\nlocal function update_immutable(state, patch)\n  local new_state = {}\n  for k, v in pairs(state) do new_state[k] = v end  -- shallow copy\n  for k, v in pairs(patch) do new_state[k] = v end   -- apply patch\n  return new_state\nend\n\nlocal function deep_update_immutable(state, path, value)\n  -- path: {\"user\", \"address\", \"city\"} → state.user.address.city = value (immutably)\n  if #path == 0 then return value end\n  local key = path[1]\n  local new_state = {}\n  for k, v in pairs(state) do new_state[k] = v end\n  local remaining = {}\n  for i = 2, #path do remaining[i - 1] = path[i] end\n  new_state[key] = deep_update_immutable(state[key] or {}, remaining, value)\n  return new_state\nend\n\nlocal state = {user = {name = \"Alice\", address = {city = \"NYC\"}}}\nlocal state2 = deep_update_immutable(state, {\"user\", \"address\", \"city\"}, \"LA\")\nprint(state.user.address.city)      -- \"NYC\" (original unchanged)\nprint(state2.user.address.city)     -- \"LA\" (new copy)\nprint(state.user == state2.user)    -- false (different objects)\n",[19,3974,3975,3980,3985,3989,4001,4012,4039,4065,4072,4076,4080,4092,4097,4119,4137,4147,4169,4180,4216,4233,4239,4243,4247,4284,4319,4345,4368],{"__ignoreMap":38},[42,3976,3977],{"class":44,"line":45},[42,3978,3979],{"class":48},"-- Anti-pattern: mutable shared state (hard to reason about, hard to test)\n",[42,3981,3982],{"class":44,"line":52},[42,3983,3984],{"class":48},"-- Good: functional updates (return new data, don't mutate)\n",[42,3986,3987],{"class":44,"line":58},[42,3988,68],{"emptyLinePlaceholder":67},[42,3990,3991,3993,3995,3998],{"class":44,"line":64},[42,3992,21],{"class":74},[42,3994,97],{"class":74},[42,3996,3997],{"class":100}," update_immutable",[42,3999,4000],{"class":77},"(state, patch)\n",[42,4002,4003,4005,4008,4010],{"class":44,"line":71},[42,4004,110],{"class":74},[42,4006,4007],{"class":77}," new_state ",[42,4009,81],{"class":74},[42,4011,84],{"class":77},[42,4013,4014,4016,4018,4020,4022,4025,4027,4030,4032,4034,4036],{"class":44,"line":87},[42,4015,2967],{"class":74},[42,4017,2854],{"class":77},[42,4019,438],{"class":74},[42,4021,2859],{"class":118},[42,4023,4024],{"class":77},"(state) ",[42,4026,2864],{"class":74},[42,4028,4029],{"class":77}," new_state[k] ",[42,4031,81],{"class":74},[42,4033,2872],{"class":77},[42,4035,2474],{"class":74},[42,4037,4038],{"class":48},"  -- shallow copy\n",[42,4040,4041,4043,4045,4047,4049,4052,4054,4056,4058,4060,4062],{"class":44,"line":92},[42,4042,2967],{"class":74},[42,4044,2854],{"class":77},[42,4046,438],{"class":74},[42,4048,2859],{"class":118},[42,4050,4051],{"class":77},"(patch) ",[42,4053,2864],{"class":74},[42,4055,4029],{"class":77},[42,4057,81],{"class":74},[42,4059,2872],{"class":77},[42,4061,2474],{"class":74},[42,4063,4064],{"class":48},"   -- apply patch\n",[42,4066,4067,4069],{"class":44,"line":107},[42,4068,141],{"class":74},[42,4070,4071],{"class":77}," new_state\n",[42,4073,4074],{"class":44,"line":138},[42,4075,164],{"class":74},[42,4077,4078],{"class":44,"line":161},[42,4079,68],{"emptyLinePlaceholder":67},[42,4081,4082,4084,4086,4089],{"class":44,"line":167},[42,4083,21],{"class":74},[42,4085,97],{"class":74},[42,4087,4088],{"class":100}," deep_update_immutable",[42,4090,4091],{"class":77},"(state, path, value)\n",[42,4093,4094],{"class":44,"line":172},[42,4095,4096],{"class":48},"  -- path: {\"user\", \"address\", \"city\"} → state.user.address.city = value (immutably)\n",[42,4098,4099,4101,4104,4107,4109,4111,4113,4115,4117],{"class":44,"line":186},[42,4100,1276],{"class":74},[42,4102,4103],{"class":74}," #",[42,4105,4106],{"class":77},"path ",[42,4108,1001],{"class":74},[42,4110,1073],{"class":118},[42,4112,1286],{"class":74},[42,4114,1961],{"class":74},[42,4116,2986],{"class":77},[42,4118,164],{"class":74},[42,4120,4121,4123,4126,4128,4131,4134],{"class":44,"line":200},[42,4122,110],{"class":74},[42,4124,4125],{"class":77}," key ",[42,4127,81],{"class":74},[42,4129,4130],{"class":77}," path[",[42,4132,4133],{"class":118},"1",[42,4135,4136],{"class":77},"]\n",[42,4138,4139,4141,4143,4145],{"class":44,"line":215},[42,4140,110],{"class":74},[42,4142,4007],{"class":77},[42,4144,81],{"class":74},[42,4146,84],{"class":77},[42,4148,4149,4151,4153,4155,4157,4159,4161,4163,4165,4167],{"class":44,"line":238},[42,4150,2967],{"class":74},[42,4152,2854],{"class":77},[42,4154,438],{"class":74},[42,4156,2859],{"class":118},[42,4158,4024],{"class":77},[42,4160,2864],{"class":74},[42,4162,4029],{"class":77},[42,4164,81],{"class":74},[42,4166,2872],{"class":77},[42,4168,164],{"class":74},[42,4170,4171,4173,4176,4178],{"class":44,"line":263},[42,4172,110],{"class":74},[42,4174,4175],{"class":77}," remaining ",[42,4177,81],{"class":74},[42,4179,84],{"class":77},[42,4181,4182,4184,4187,4189,4191,4193,4195,4197,4199,4202,4205,4207,4209,4211,4214],{"class":44,"line":269},[42,4183,2967],{"class":74},[42,4185,4186],{"class":77}," i ",[42,4188,81],{"class":74},[42,4190,3328],{"class":118},[42,4192,128],{"class":77},[42,4194,3643],{"class":74},[42,4196,4106],{"class":77},[42,4198,2864],{"class":74},[42,4200,4201],{"class":77}," remaining[i ",[42,4203,4204],{"class":74},"-",[42,4206,3320],{"class":118},[42,4208,3654],{"class":77},[42,4210,81],{"class":74},[42,4212,4213],{"class":77}," path[i] ",[42,4215,164],{"class":74},[42,4217,4218,4221,4223,4225,4228,4230],{"class":44,"line":280},[42,4219,4220],{"class":77},"  new_state[key] ",[42,4222,81],{"class":74},[42,4224,4088],{"class":118},[42,4226,4227],{"class":77},"(state[key] ",[42,4229,1221],{"class":74},[42,4231,4232],{"class":77}," {}, remaining, value)\n",[42,4234,4235,4237],{"class":44,"line":289},[42,4236,141],{"class":74},[42,4238,4071],{"class":77},[42,4240,4241],{"class":44,"line":302},[42,4242,164],{"class":74},[42,4244,4245],{"class":44,"line":313},[42,4246,68],{"emptyLinePlaceholder":67},[42,4248,4249,4251,4254,4256,4259,4261,4263,4265,4268,4271,4273,4276,4278,4281],{"class":44,"line":331},[42,4250,21],{"class":74},[42,4252,4253],{"class":77}," state ",[42,4255,81],{"class":74},[42,4257,4258],{"class":77}," {user ",[42,4260,81],{"class":74},[42,4262,2309],{"class":77},[42,4264,81],{"class":74},[42,4266,4267],{"class":131}," \"Alice\"",[42,4269,4270],{"class":77},", address ",[42,4272,81],{"class":74},[42,4274,4275],{"class":77}," {city ",[42,4277,81],{"class":74},[42,4279,4280],{"class":131}," \"NYC\"",[42,4282,4283],{"class":77},"}}}\n",[42,4285,4286,4288,4291,4293,4295,4298,4301,4303,4306,4308,4311,4314,4317],{"class":44,"line":352},[42,4287,21],{"class":74},[42,4289,4290],{"class":77}," state2 ",[42,4292,81],{"class":74},[42,4294,4088],{"class":118},[42,4296,4297],{"class":77},"(state, {",[42,4299,4300],{"class":131},"\"user\"",[42,4302,128],{"class":77},[42,4304,4305],{"class":131},"\"address\"",[42,4307,128],{"class":77},[42,4309,4310],{"class":131},"\"city\"",[42,4312,4313],{"class":77},"}, ",[42,4315,4316],{"class":131},"\"LA\"",[42,4318,135],{"class":77},[42,4320,4321,4323,4326,4329,4331,4334,4336,4339,4342],{"class":44,"line":357},[42,4322,780],{"class":118},[42,4324,4325],{"class":77},"(state.",[42,4327,4328],{"class":100},"user",[42,4330,1170],{"class":77},[42,4332,4333],{"class":100},"address",[42,4335,1170],{"class":77},[42,4337,4338],{"class":100},"city",[42,4340,4341],{"class":77},")      ",[42,4343,4344],{"class":48},"-- \"NYC\" (original unchanged)\n",[42,4346,4347,4349,4352,4354,4356,4358,4360,4362,4365],{"class":44,"line":365},[42,4348,780],{"class":118},[42,4350,4351],{"class":77},"(state2.",[42,4353,4328],{"class":100},[42,4355,1170],{"class":77},[42,4357,4333],{"class":100},[42,4359,1170],{"class":77},[42,4361,4338],{"class":100},[42,4363,4364],{"class":77},")     ",[42,4366,4367],{"class":48},"-- \"LA\" (new copy)\n",[42,4369,4370,4372,4374,4376,4378,4381,4383,4386],{"class":44,"line":372},[42,4371,780],{"class":118},[42,4373,4325],{"class":77},[42,4375,4328],{"class":100},[42,4377,2177],{"class":74},[42,4379,4380],{"class":77}," state2.",[42,4382,4328],{"class":100},[42,4384,4385],{"class":77},")    ",[42,4387,4388],{"class":48},"-- false (different objects)\n",[24,4390,4392],{"id":4391},"anti-patterns-catalog","Anti-Patterns Catalog",[29,4394,4395,5002,5006,5017],{"language":31},[34,4396,4398],{"className":36,"code":4397,"language":31,"meta":38,"style":38},"-- 1. Globals instead of locals (performance + namespace pollution)\nx = 10              -- BAD: global\nlocal x = 10        -- GOOD: local\n\n-- 2. Global function instead of local function\nfunction foo() end       -- BAD: creates global foo\nlocal function foo() end -- GOOD: local function\n\n-- 3. String concatenation in loop (quadratic)\nlocal s = \"\"; for i = 1, N do s = s .. items[i] end  -- BAD\nlocal s = table.concat(items)                        -- GOOD\n\n-- 4. Table.insert in hot loop (function call overhead)\nfor i = 1, N do table.insert(t, v) end  -- SLOWER\nfor i = 1, N do t[N + i] = v end          -- FASTER (but pre-size if possible)\nfor i = 1, N do t[#t + 1] = v end         -- MEDIUM (# evaluation each iteration)\n\n-- 5. Not closing files\nlocal f = io.open(\"x\"); content = f:read(\"*a\")  -- BAD: never closed\nlocal f \u003Cclose> = io.open(\"x\"); content = f:read(\"*a\")  -- GOOD: auto-close (5.4+)\n\n-- 6. Relying on pairs order\nfor k, v in pairs(t) do if k == \"first\" then break end end  -- BAD: order undefined\n-- Use a separate ordered keys array if order matters\n\n-- 7. Using # on sparse arrays\nlocal t = {1, 2, nil, 4}; print(#t)  -- BAD: undefined (2 or 4)\n-- Maintain explicit length field for sparse arrays\n\n-- 8. Modifying table during iteration\nfor k in pairs(t) do t[k] = nil end  -- BAD: undefined behavior\n-- Collect keys first, then remove\n\n-- 9. Default value trap with or\nlocal x = opts.timeout or 5000  -- BAD: if timeout is 0, gets 5000\nlocal x = (opts.timeout ~= nil) and opts.timeout or 5000  -- GOOD: preserves 0\n\n-- 10. Non-tail recursion for deep data\nlocal function count_nodes(node)  -- BAD: stack overflow on deep trees\n  return 1 + (node.children and sum(count_nodes(c) for c in children) or 0)\nend\n-- Use iterative (explicit stack) or tail-recursive accumulator\n",[19,4399,4400,4405,4416,4429,4433,4438,4452,4467,4471,4476,4519,4535,4539,4544,4569,4600,4637,4641,4646,4681,4716,4720,4725,4763,4768,4772,4777,4820,4825,4829,4834,4860,4865,4869,4874,4893,4926,4930,4935,4950,4993,4997],{"__ignoreMap":38},[42,4401,4402],{"class":44,"line":45},[42,4403,4404],{"class":48},"-- 1. Globals instead of locals (performance + namespace pollution)\n",[42,4406,4407,4409,4411,4413],{"class":44,"line":52},[42,4408,750],{"class":77},[42,4410,81],{"class":74},[42,4412,742],{"class":118},[42,4414,4415],{"class":48},"              -- BAD: global\n",[42,4417,4418,4420,4422,4424,4426],{"class":44,"line":58},[42,4419,21],{"class":74},[42,4421,737],{"class":77},[42,4423,81],{"class":74},[42,4425,742],{"class":118},[42,4427,4428],{"class":48},"        -- GOOD: local\n",[42,4430,4431],{"class":44,"line":64},[42,4432,68],{"emptyLinePlaceholder":67},[42,4434,4435],{"class":44,"line":71},[42,4436,4437],{"class":48},"-- 2. Global function instead of local function\n",[42,4439,4440,4442,4445,4447,4449],{"class":44,"line":87},[42,4441,1164],{"class":74},[42,4443,4444],{"class":100}," foo",[42,4446,248],{"class":77},[42,4448,2474],{"class":74},[42,4450,4451],{"class":48},"       -- BAD: creates global foo\n",[42,4453,4454,4456,4458,4460,4462,4464],{"class":44,"line":92},[42,4455,21],{"class":74},[42,4457,97],{"class":74},[42,4459,4444],{"class":100},[42,4461,248],{"class":77},[42,4463,2474],{"class":74},[42,4465,4466],{"class":48}," -- GOOD: local function\n",[42,4468,4469],{"class":44,"line":107},[42,4470,68],{"emptyLinePlaceholder":67},[42,4472,4473],{"class":44,"line":138},[42,4474,4475],{"class":48},"-- 3. String concatenation in loop (quadratic)\n",[42,4477,4478,4480,4483,4485,4487,4490,4492,4494,4496,4498,4501,4503,4505,4507,4509,4511,4514,4516],{"class":44,"line":161},[42,4479,21],{"class":74},[42,4481,4482],{"class":77}," s ",[42,4484,81],{"class":74},[42,4486,1566],{"class":131},[42,4488,4489],{"class":77},"; ",[42,4491,432],{"class":74},[42,4493,4186],{"class":77},[42,4495,81],{"class":74},[42,4497,3320],{"class":118},[42,4499,4500],{"class":77},", N ",[42,4502,2864],{"class":74},[42,4504,4482],{"class":77},[42,4506,81],{"class":74},[42,4508,4482],{"class":77},[42,4510,226],{"class":74},[42,4512,4513],{"class":77}," items[i] ",[42,4515,2474],{"class":74},[42,4517,4518],{"class":48},"  -- BAD\n",[42,4520,4521,4523,4525,4527,4529,4532],{"class":44,"line":167},[42,4522,21],{"class":74},[42,4524,4482],{"class":77},[42,4526,81],{"class":74},[42,4528,3699],{"class":118},[42,4530,4531],{"class":77},"(items)                        ",[42,4533,4534],{"class":48},"-- GOOD\n",[42,4536,4537],{"class":44,"line":172},[42,4538,68],{"emptyLinePlaceholder":67},[42,4540,4541],{"class":44,"line":186},[42,4542,4543],{"class":48},"-- 4. Table.insert in hot loop (function call overhead)\n",[42,4545,4546,4548,4550,4552,4554,4556,4558,4561,4564,4566],{"class":44,"line":200},[42,4547,432],{"class":74},[42,4549,4186],{"class":77},[42,4551,81],{"class":74},[42,4553,3320],{"class":118},[42,4555,4500],{"class":77},[42,4557,2864],{"class":74},[42,4559,4560],{"class":118}," table.insert",[42,4562,4563],{"class":77},"(t, v) ",[42,4565,2474],{"class":74},[42,4567,4568],{"class":48},"  -- SLOWER\n",[42,4570,4571,4573,4575,4577,4579,4581,4583,4586,4588,4591,4593,4595,4597],{"class":44,"line":215},[42,4572,432],{"class":74},[42,4574,4186],{"class":77},[42,4576,81],{"class":74},[42,4578,3320],{"class":118},[42,4580,4500],{"class":77},[42,4582,2864],{"class":74},[42,4584,4585],{"class":77}," t[N ",[42,4587,3649],{"class":74},[42,4589,4590],{"class":77}," i] ",[42,4592,81],{"class":74},[42,4594,2872],{"class":77},[42,4596,2474],{"class":74},[42,4598,4599],{"class":48},"          -- FASTER (but pre-size if possible)\n",[42,4601,4602,4604,4606,4608,4610,4612,4614,4617,4619,4622,4624,4626,4628,4630,4632,4634],{"class":44,"line":238},[42,4603,432],{"class":74},[42,4605,4186],{"class":77},[42,4607,81],{"class":74},[42,4609,3320],{"class":118},[42,4611,4500],{"class":77},[42,4613,2864],{"class":74},[42,4615,4616],{"class":77}," t[",[42,4618,3643],{"class":74},[42,4620,4621],{"class":77},"t ",[42,4623,3649],{"class":74},[42,4625,3320],{"class":118},[42,4627,3654],{"class":77},[42,4629,81],{"class":74},[42,4631,2872],{"class":77},[42,4633,2474],{"class":74},[42,4635,4636],{"class":48},"         -- MEDIUM (# evaluation each iteration)\n",[42,4638,4639],{"class":44,"line":263},[42,4640,68],{"emptyLinePlaceholder":67},[42,4642,4643],{"class":44,"line":269},[42,4644,4645],{"class":48},"-- 5. Not closing files\n",[42,4647,4648,4650,4652,4654,4656,4658,4661,4664,4666,4668,4670,4672,4674,4676,4678],{"class":44,"line":280},[42,4649,21],{"class":74},[42,4651,2710],{"class":77},[42,4653,81],{"class":74},[42,4655,2715],{"class":118},[42,4657,122],{"class":77},[42,4659,4660],{"class":131},"\"x\"",[42,4662,4663],{"class":77},"); content ",[42,4665,81],{"class":74},[42,4667,2747],{"class":100},[42,4669,2553],{"class":77},[42,4671,2752],{"class":118},[42,4673,122],{"class":77},[42,4675,2757],{"class":131},[42,4677,723],{"class":77},[42,4679,4680],{"class":48},"-- BAD: never closed\n",[42,4682,4683,4685,4687,4689,4691,4693,4695,4697,4699,4701,4703,4705,4707,4709,4711,4713],{"class":44,"line":289},[42,4684,21],{"class":74},[42,4686,2710],{"class":77},[42,4688,2264],{"class":74},[42,4690,192],{"class":74},[42,4692,2715],{"class":118},[42,4694,122],{"class":77},[42,4696,4660],{"class":131},[42,4698,4663],{"class":77},[42,4700,81],{"class":74},[42,4702,2747],{"class":100},[42,4704,2553],{"class":77},[42,4706,2752],{"class":118},[42,4708,122],{"class":77},[42,4710,2757],{"class":131},[42,4712,723],{"class":77},[42,4714,4715],{"class":48},"-- GOOD: auto-close (5.4+)\n",[42,4717,4718],{"class":44,"line":302},[42,4719,68],{"emptyLinePlaceholder":67},[42,4721,4722],{"class":44,"line":313},[42,4723,4724],{"class":48},"-- 6. Relying on pairs order\n",[42,4726,4727,4729,4731,4733,4735,4738,4740,4743,4746,4748,4751,4753,4756,4758,4760],{"class":44,"line":331},[42,4728,432],{"class":74},[42,4730,2854],{"class":77},[42,4732,438],{"class":74},[42,4734,2859],{"class":118},[42,4736,4737],{"class":77},"(t) ",[42,4739,2864],{"class":74},[42,4741,4742],{"class":74}," if",[42,4744,4745],{"class":77}," k ",[42,4747,1001],{"class":74},[42,4749,4750],{"class":131}," \"first\" ",[42,4752,1958],{"class":74},[42,4754,4755],{"class":74}," break",[42,4757,3516],{"class":74},[42,4759,3516],{"class":74},[42,4761,4762],{"class":48},"  -- BAD: order undefined\n",[42,4764,4765],{"class":44,"line":352},[42,4766,4767],{"class":48},"-- Use a separate ordered keys array if order matters\n",[42,4769,4770],{"class":44,"line":357},[42,4771,68],{"emptyLinePlaceholder":67},[42,4773,4774],{"class":44,"line":365},[42,4775,4776],{"class":48},"-- 7. Using # on sparse arrays\n",[42,4778,4779,4781,4784,4786,4789,4791,4793,4795,4797,4800,4802,4805,4808,4810,4812,4814,4817],{"class":44,"line":372},[42,4780,21],{"class":74},[42,4782,4783],{"class":77}," t ",[42,4785,81],{"class":74},[42,4787,4788],{"class":77}," {",[42,4790,4133],{"class":118},[42,4792,128],{"class":77},[42,4794,258],{"class":118},[42,4796,128],{"class":77},[42,4798,4799],{"class":118},"nil",[42,4801,128],{"class":77},[42,4803,4804],{"class":118},"4",[42,4806,4807],{"class":77},"}; ",[42,4809,780],{"class":118},[42,4811,122],{"class":77},[42,4813,3643],{"class":74},[42,4815,4816],{"class":77},"t)  ",[42,4818,4819],{"class":48},"-- BAD: undefined (2 or 4)\n",[42,4821,4822],{"class":44,"line":378},[42,4823,4824],{"class":48},"-- Maintain explicit length field for sparse arrays\n",[42,4826,4827],{"class":44,"line":383},[42,4828,68],{"emptyLinePlaceholder":67},[42,4830,4831],{"class":44,"line":389},[42,4832,4833],{"class":48},"-- 8. Modifying table during iteration\n",[42,4835,4836,4838,4840,4842,4844,4846,4848,4851,4853,4855,4857],{"class":44,"line":402},[42,4837,432],{"class":74},[42,4839,4745],{"class":77},[42,4841,438],{"class":74},[42,4843,2859],{"class":118},[42,4845,4737],{"class":77},[42,4847,2864],{"class":74},[42,4849,4850],{"class":77}," t[k] ",[42,4852,81],{"class":74},[42,4854,2013],{"class":118},[42,4856,3516],{"class":74},[42,4858,4859],{"class":48},"  -- BAD: undefined behavior\n",[42,4861,4862],{"class":44,"line":413},[42,4863,4864],{"class":48},"-- Collect keys first, then remove\n",[42,4866,4867],{"class":44,"line":418},[42,4868,68],{"emptyLinePlaceholder":67},[42,4870,4871],{"class":44,"line":423},[42,4872,4873],{"class":48},"-- 9. Default value trap with or\n",[42,4875,4876,4878,4880,4882,4884,4886,4888,4890],{"class":44,"line":429},[42,4877,21],{"class":74},[42,4879,737],{"class":77},[42,4881,81],{"class":74},[42,4883,1541],{"class":77},[42,4885,1379],{"class":100},[42,4887,155],{"class":74},[42,4889,1139],{"class":118},[42,4891,4892],{"class":48},"  -- BAD: if timeout is 0, gets 5000\n",[42,4894,4895,4897,4899,4901,4904,4906,4909,4911,4913,4915,4917,4919,4921,4923],{"class":44,"line":447},[42,4896,21],{"class":74},[42,4898,737],{"class":77},[42,4900,81],{"class":74},[42,4902,4903],{"class":77}," (opts.",[42,4905,1379],{"class":100},[42,4907,4908],{"class":74}," ~=",[42,4910,2013],{"class":118},[42,4912,1028],{"class":77},[42,4914,146],{"class":74},[42,4916,1541],{"class":77},[42,4918,1379],{"class":100},[42,4920,155],{"class":74},[42,4922,1139],{"class":118},[42,4924,4925],{"class":48},"  -- GOOD: preserves 0\n",[42,4927,4928],{"class":44,"line":480},[42,4929,68],{"emptyLinePlaceholder":67},[42,4931,4932],{"class":44,"line":518},[42,4933,4934],{"class":48},"-- 10. Non-tail recursion for deep data\n",[42,4936,4937,4939,4941,4944,4947],{"class":44,"line":561},[42,4938,21],{"class":74},[42,4940,97],{"class":74},[42,4942,4943],{"class":100}," count_nodes",[42,4945,4946],{"class":77},"(node)  ",[42,4948,4949],{"class":48},"-- BAD: stack overflow on deep trees\n",[42,4951,4952,4954,4956,4959,4962,4965,4967,4970,4972,4975,4978,4980,4982,4984,4987,4989,4991],{"class":44,"line":594},[42,4953,141],{"class":74},[42,4955,3320],{"class":118},[42,4957,4958],{"class":74}," +",[42,4960,4961],{"class":77}," (node.",[42,4963,4964],{"class":100},"children",[42,4966,3125],{"class":74},[42,4968,4969],{"class":118}," sum",[42,4971,122],{"class":77},[42,4973,4974],{"class":118},"count_nodes",[42,4976,4977],{"class":77},"(c) ",[42,4979,432],{"class":74},[42,4981,1345],{"class":77},[42,4983,438],{"class":74},[42,4985,4986],{"class":77}," children) ",[42,4988,1221],{"class":74},[42,4990,1073],{"class":118},[42,4992,135],{"class":77},[42,4994,4995],{"class":44,"line":637},[42,4996,164],{"class":74},[42,4998,4999],{"class":44,"line":646},[42,5000,5001],{"class":48},"-- Use iterative (explicit stack) or tail-recursive accumulator\n",[24,5003,5005],{"id":5004},"tips-tricks","💡 Tips & Tricks",[15,5007,5008,5016],{},[5009,5010,5011,5012,5015],"strong",{},"Use ",[19,5013,5014],{},"luacheck"," as a pre-commit hook",": Catches globals, unused vars, shadowing, unreachable code.",[29,5018,5020],{"language":5019},"bash",[34,5021,5024],{"className":5022,"code":5023,"language":5019,"meta":38,"style":38},"language-bash shiki shiki-themes github-light github-dark","luacheck --globals _G --std luajit src\u002F  # strict mode, LuaJIT stdlib\nluacheck --codes src\u002F                     # show warning codes (for .luacheckrc ignores)\n",[19,5025,5026,5048],{"__ignoreMap":38},[42,5027,5028,5030,5033,5036,5039,5042,5045],{"class":44,"line":45},[42,5029,5014],{"class":100},[42,5031,5032],{"class":118}," --globals",[42,5034,5035],{"class":131}," _G",[42,5037,5038],{"class":118}," --std",[42,5040,5041],{"class":131}," luajit",[42,5043,5044],{"class":131}," src\u002F",[42,5046,5047],{"class":48},"  # strict mode, LuaJIT stdlib\n",[42,5049,5050,5052,5055,5057],{"class":44,"line":52},[42,5051,5014],{"class":100},[42,5053,5054],{"class":118}," --codes",[42,5056,5044],{"class":131},[42,5058,5059],{"class":48},"                     # show warning codes (for .luacheckrc ignores)\n",[15,5061,5062,2553],{},[5009,5063,5064,5067],{},[19,5065,5066],{},".luacheckrc"," for project conventions",[29,5069,5071],{"language":5070,"filename":5066},"ini",[34,5072,5075],{"className":5073,"code":5074,"language":5070,"meta":38,"style":38},"language-ini shiki shiki-themes github-light github-dark","std = \"luajit\"           -- or \"lua54\"\nmax_line_length = 120\nignore = { \"212\" }       -- ignore \"unused argument\" (212)\nglobals = { \"vim\" }      -- Neovim: allow vim global\nfiles[\"test\u002F\"] = {\n  globals = { \"describe\", \"it\", \"assert\" },  -- busted globals\n}\n",[19,5076,5077,5094,5102,5122,5135,5146,5168],{"__ignoreMap":38},[42,5078,5079,5082,5085,5088,5091],{"class":44,"line":45},[42,5080,5081],{"class":74},"std",[42,5083,5084],{"class":77}," = ",[42,5086,5087],{"class":131},"\"luajit\"",[42,5089,5090],{"class":77},"           -- or ",[42,5092,5093],{"class":131},"\"lua54\"\n",[42,5095,5096,5099],{"class":44,"line":52},[42,5097,5098],{"class":74},"max_line_length",[42,5100,5101],{"class":77}," = 120\n",[42,5103,5104,5107,5110,5113,5116,5119],{"class":44,"line":58},[42,5105,5106],{"class":74},"ignore",[42,5108,5109],{"class":77}," = { ",[42,5111,5112],{"class":131},"\"212\"",[42,5114,5115],{"class":77}," }       -- ignore ",[42,5117,5118],{"class":131},"\"unused argument\"",[42,5120,5121],{"class":77}," (212)\n",[42,5123,5124,5127,5129,5132],{"class":44,"line":64},[42,5125,5126],{"class":74},"globals",[42,5128,5109],{"class":77},[42,5130,5131],{"class":131},"\"vim\"",[42,5133,5134],{"class":77}," }      -- Neovim: allow vim global\n",[42,5136,5137,5140,5143],{"class":44,"line":71},[42,5138,5139],{"class":77},"files[",[42,5141,5142],{"class":131},"\"test\u002F\"",[42,5144,5145],{"class":77},"] = {\n",[42,5147,5148,5151,5153,5156,5158,5161,5163,5165],{"class":44,"line":87},[42,5149,5150],{"class":74},"  globals",[42,5152,5109],{"class":77},[42,5154,5155],{"class":131},"\"describe\"",[42,5157,128],{"class":77},[42,5159,5160],{"class":131},"\"it\"",[42,5162,128],{"class":77},[42,5164,460],{"class":131},[42,5166,5167],{"class":77}," },  -- busted globals\n",[42,5169,5170],{"class":44,"line":92},[42,5171,964],{"class":77},[15,5173,5174,2553],{},[5009,5175,5011,5176,5179],{},[19,5177,5178],{},"make"," targets for common operations",[29,5181,5184],{"language":5182,"filename":5183},"makefile","Makefile",[34,5185,5188],{"className":5186,"code":5187,"language":5182,"meta":38,"style":38},"language-makefile shiki shiki-themes github-light github-dark","LUA ?= lua\nTEST_DIR ?= test\n\ntest:\n    $(LUA) $(TEST_DIR)\u002Frun_all.lua\n\nlint:\n    luacheck src\u002F --std lua54\n\ncoverage:\n    $(LUA) -e \"require('coverage').run('$(TEST_DIR)')\"\n\nbench:\n    $(LUA) benchmarks\u002Frun.lua\n",[19,5189,5190,5195,5200,5204,5209,5214,5218,5223,5228,5232,5237,5242,5246,5251],{"__ignoreMap":38},[42,5191,5192],{"class":44,"line":45},[42,5193,5194],{},"LUA ?= lua\n",[42,5196,5197],{"class":44,"line":52},[42,5198,5199],{},"TEST_DIR ?= test\n",[42,5201,5202],{"class":44,"line":58},[42,5203,68],{"emptyLinePlaceholder":67},[42,5205,5206],{"class":44,"line":64},[42,5207,5208],{},"test:\n",[42,5210,5211],{"class":44,"line":71},[42,5212,5213],{},"    $(LUA) $(TEST_DIR)\u002Frun_all.lua\n",[42,5215,5216],{"class":44,"line":87},[42,5217,68],{"emptyLinePlaceholder":67},[42,5219,5220],{"class":44,"line":92},[42,5221,5222],{},"lint:\n",[42,5224,5225],{"class":44,"line":107},[42,5226,5227],{},"    luacheck src\u002F --std lua54\n",[42,5229,5230],{"class":44,"line":138},[42,5231,68],{"emptyLinePlaceholder":67},[42,5233,5234],{"class":44,"line":161},[42,5235,5236],{},"coverage:\n",[42,5238,5239],{"class":44,"line":167},[42,5240,5241],{},"    $(LUA) -e \"require('coverage').run('$(TEST_DIR)')\"\n",[42,5243,5244],{"class":44,"line":172},[42,5245,68],{"emptyLinePlaceholder":67},[42,5247,5248],{"class":44,"line":186},[42,5249,5250],{},"bench:\n",[42,5252,5253],{"class":44,"line":200},[42,5254,5255],{},"    $(LUA) benchmarks\u002Frun.lua\n",[24,5257,5259],{"id":5258},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[15,5261,5262,5265],{},[5009,5263,5264],{},"Strict mode can break existing code",": If you add strict mode to an existing codebase, you'll find many accidental globals. Add it incrementally or fix globals first.",[15,5267,5268,5274],{},[5009,5269,5270,5273],{},[19,5271,5272],{},"error(msg, 0)"," suppresses position info",": Useful for clean error messages, but you lose the stack location. Use sparingly.",[24,5276,5278],{"id":5277},"spot-the-bug","🧠 Spot the Bug",[29,5280,5281,5405],{"language":31},[34,5282,5284],{"className":36,"code":5283,"language":31,"meta":38,"style":38},"local function process_user(user_id)\n  local user = db.find(user_id)\n  if not user then return nil, \"not found\" end\n  user.last_login = os.time()\n  db.save(user)\n  return user\nend\n\nlocal u, err = process_user(42)\nprint(u.name)\n",[19,5285,5286,5298,5314,5335,5350,5361,5367,5371,5375,5393],{"__ignoreMap":38},[42,5287,5288,5290,5292,5295],{"class":44,"line":45},[42,5289,21],{"class":74},[42,5291,97],{"class":74},[42,5293,5294],{"class":100}," process_user",[42,5296,5297],{"class":77},"(user_id)\n",[42,5299,5300,5302,5304,5306,5309,5312],{"class":44,"line":52},[42,5301,110],{"class":74},[42,5303,2105],{"class":77},[42,5305,81],{"class":74},[42,5307,5308],{"class":77}," db.",[42,5310,5311],{"class":118},"find",[42,5313,5297],{"class":77},[42,5315,5316,5318,5320,5322,5324,5326,5328,5330,5333],{"class":44,"line":58},[42,5317,1276],{"class":74},[42,5319,206],{"class":74},[42,5321,2105],{"class":77},[42,5323,1958],{"class":74},[42,5325,1961],{"class":74},[42,5327,2013],{"class":118},[42,5329,128],{"class":77},[42,5331,5332],{"class":131},"\"not found\" ",[42,5334,164],{"class":74},[42,5336,5337,5340,5343,5345,5348],{"class":44,"line":64},[42,5338,5339],{"class":77},"  user.",[42,5341,5342],{"class":100},"last_login",[42,5344,192],{"class":74},[42,5346,5347],{"class":118}," os.time",[42,5349,104],{"class":77},[42,5351,5352,5355,5358],{"class":44,"line":71},[42,5353,5354],{"class":77},"  db.",[42,5356,5357],{"class":118},"save",[42,5359,5360],{"class":77},"(user)\n",[42,5362,5363,5365],{"class":44,"line":87},[42,5364,141],{"class":74},[42,5366,2127],{"class":77},[42,5368,5369],{"class":44,"line":92},[42,5370,164],{"class":74},[42,5372,5373],{"class":44,"line":107},[42,5374,68],{"emptyLinePlaceholder":67},[42,5376,5377,5379,5382,5384,5386,5388,5391],{"class":44,"line":138},[42,5378,21],{"class":74},[42,5380,5381],{"class":77}," u, err ",[42,5383,81],{"class":74},[42,5385,5294],{"class":118},[42,5387,122],{"class":77},[42,5389,5390],{"class":118},"42",[42,5392,135],{"class":77},[42,5394,5395,5397,5400,5403],{"class":44,"line":161},[42,5396,780],{"class":118},[42,5398,5399],{"class":77},"(u.",[42,5401,5402],{"class":100},"name",[42,5404,135],{"class":77},[5406,5407,5408,5412,5415,5449,5466,5537],"details",{},[5409,5410,5411],"summary",{},"Answer",[15,5413,5414],{},"No bug in the shown code, but the error handling at the call site is incomplete:",[34,5416,5418],{"className":36,"code":5417,"language":31,"meta":38,"style":38},"local u, err = process_user(42)\nprint(u.name)  -- if u is nil (user not found), this errors: \"attempt to index nil value\"\n",[19,5419,5420,5436],{"__ignoreMap":38},[42,5421,5422,5424,5426,5428,5430,5432,5434],{"class":44,"line":45},[42,5423,21],{"class":74},[42,5425,5381],{"class":77},[42,5427,81],{"class":74},[42,5429,5294],{"class":118},[42,5431,122],{"class":77},[42,5433,5390],{"class":118},[42,5435,135],{"class":77},[42,5437,5438,5440,5442,5444,5446],{"class":44,"line":52},[42,5439,780],{"class":118},[42,5441,5399],{"class":77},[42,5443,5402],{"class":100},[42,5445,723],{"class":77},[42,5447,5448],{"class":48},"-- if u is nil (user not found), this errors: \"attempt to index nil value\"\n",[15,5450,5451,5452,5455,5456,5459,5460,5463,5464,2553],{},"The caller doesn't check ",[19,5453,5454],{},"err"," before accessing ",[19,5457,5458],{},"u.name",". In production, every function that returns ",[19,5461,5462],{},"(nil, error)"," on failure must have its caller check for ",[19,5465,4799],{},[34,5467,5469],{"className":36,"code":5468,"language":31,"meta":38,"style":38},"local u, err = process_user(42)\nif not u then\n  return nil, \"process_user failed: \" .. tostring(err)  -- propagate or handle\nend\nprint(u.name)  -- safe: u is non-nil\n",[19,5470,5471,5487,5499,5520,5524],{"__ignoreMap":38},[42,5472,5473,5475,5477,5479,5481,5483,5485],{"class":44,"line":45},[42,5474,21],{"class":74},[42,5476,5381],{"class":77},[42,5478,81],{"class":74},[42,5480,5294],{"class":118},[42,5482,122],{"class":77},[42,5484,5390],{"class":118},[42,5486,135],{"class":77},[42,5488,5489,5492,5494,5497],{"class":44,"line":52},[42,5490,5491],{"class":74},"if",[42,5493,206],{"class":74},[42,5495,5496],{"class":77}," u ",[42,5498,212],{"class":74},[42,5500,5501,5503,5505,5507,5510,5512,5514,5517],{"class":44,"line":58},[42,5502,141],{"class":74},[42,5504,2013],{"class":118},[42,5506,128],{"class":77},[42,5508,5509],{"class":131},"\"process_user failed: \" ",[42,5511,226],{"class":74},[42,5513,229],{"class":118},[42,5515,5516],{"class":77},"(err)  ",[42,5518,5519],{"class":48},"-- propagate or handle\n",[42,5521,5522],{"class":44,"line":64},[42,5523,164],{"class":74},[42,5525,5526,5528,5530,5532,5534],{"class":44,"line":71},[42,5527,780],{"class":118},[42,5529,5399],{"class":77},[42,5531,5402],{"class":100},[42,5533,723],{"class":77},[42,5535,5536],{"class":48},"-- safe: u is non-nil\n",[15,5538,5539,5540,5543],{},"This is the Lua equivalent of Go's ",[19,5541,5542],{},"if err != nil"," pattern. Forgetting the nil check is the #1 source of \"attempt to index nil value\" errors in production Lua.",[5545,5546,5547],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}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 .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}",{"title":38,"searchDepth":52,"depth":52,"links":5549},[5550,5553,5554],{"id":26,"depth":52,"text":27,"children":5551},[5552],{"id":697,"depth":58,"text":698},{"id":5258,"depth":52,"text":5259},{"id":5277,"depth":52,"text":5278},"[object Object]","md",{},"\u002Flua\u002F15-best-practices",{"title":5,"description":5560},{"Production patterns for Lua":5561},"strict mode for global detection, module design with encapsulation and dependency injection, error architecture (structured errors, error boundaries), resource management with \u003Cclose>, configuration management, logging, and the strict.lua pattern used by the Lua community.","lua\u002F15-best-practices","CyVKDwwKHFd8z5QOSa13uv5yocvviWkdtS_4i0xwiGs",1789924650682]