[{"data":1,"prerenderedAt":5136},["ShallowReactive",2],{"navigation":3,"/upgrading":283,"/upgrading-surround":5131},[4,16,20,68,128,168,179,259,263,267,271,275,279],{"title":5,"path":6,"stem":7,"children":8},"Getting Started","/getting-started","1.getting-started",[9,12],{"title":10,"path":6,"stem":11},"Introduction","1.getting-started/index",{"title":13,"path":14,"stem":15},"Installation","/getting-started/installation","1.getting-started/1.installation",{"title":17,"path":18,"stem":19},"Upgrading","/upgrading","10.upgrading",{"title":21,"path":22,"stem":23,"children":24},"Essentials","/essentials","2.essentials",[25,28,32,36,40,44,48,52,56,60,64],{"title":26,"path":22,"stem":27},"Core concepts","2.essentials/index",{"title":29,"path":30,"stem":31},"Routing","/essentials/routing","2.essentials/1.routing",{"title":33,"path":34,"stem":35},"Typed Requests & Responses","/essentials/typed-io","2.essentials/10.typed-io",{"title":37,"path":38,"stem":39},"Authentication","/essentials/authentication","2.essentials/2.authentication",{"title":41,"path":42,"stem":43},"Identity & User Store","/essentials/identity","2.essentials/3.identity",{"title":45,"path":46,"stem":47},"Requests & Responses","/essentials/requests-responses","2.essentials/4.requests-responses",{"title":49,"path":50,"stem":51},"Controllers","/essentials/controllers","2.essentials/5.controllers",{"title":53,"path":54,"stem":55},"Database","/essentials/database","2.essentials/6.database",{"title":57,"path":58,"stem":59},"Validation","/essentials/validation","2.essentials/7.validation",{"title":61,"path":62,"stem":63},"Migrations","/essentials/migrations","2.essentials/8.migrations",{"title":65,"path":66,"stem":67},"ORM Models","/essentials/orm","2.essentials/9.orm",{"title":69,"path":70,"stem":71,"children":72},"Features","/features","3.features",[73,76,80,84,88,92,96,100,104,108,112,116,120,124],{"title":74,"path":70,"stem":75},"Built‑in Capabilities","3.features/index",{"title":77,"path":78,"stem":79},"API Keys & Scopes","/features/api-keys","3.features/api-keys",{"title":81,"path":82,"stem":83},"Browser Sessions (Cookie Auth)","/features/browser-sessions","3.features/browser-sessions",{"title":85,"path":86,"stem":87},"Caching","/features/caching","3.features/caching",{"title":89,"path":90,"stem":91},"CORS & CSRF","/features/cors-csrf","3.features/cors-csrf",{"title":93,"path":94,"stem":95},"Distributed Locks","/features/distributed-locks","3.features/distributed-locks",{"title":97,"path":98,"stem":99},"Events & Listeners","/features/events","3.features/events",{"title":101,"path":102,"stem":103},"Field Selection","/features/field-selection","3.features/field-selection",{"title":105,"path":106,"stem":107},"File Uploads","/features/file-uploads","3.features/file-uploads",{"title":109,"path":110,"stem":111},"Notifications","/features/notifications","3.features/notifications",{"title":113,"path":114,"stem":115},"Queues & Jobs","/features/queues-jobs","3.features/queues-jobs",{"title":117,"path":118,"stem":119},"Rate Limiting","/features/rate-limiting","3.features/rate-limiting",{"title":121,"path":122,"stem":123},"Task Scheduling","/features/scheduling","3.features/scheduling",{"title":125,"path":126,"stem":127},"Webhooks","/features/webhooks","3.features/webhooks",{"title":129,"path":130,"stem":131,"children":132},"Advanced","/advanced","4.advanced",[133,136,140,144,148,152,156,160,164],{"title":134,"path":130,"stem":135},"Techniques and Patterns","4.advanced/index",{"title":137,"path":138,"stem":139},"Configuration","/advanced/configuration","4.advanced/configuration",{"title":141,"path":142,"stem":143},"Dependency Injection","/advanced/dependency-injection","4.advanced/dependency-injection",{"title":145,"path":146,"stem":147},"Middleware","/advanced/middleware","4.advanced/middleware",{"title":149,"path":150,"stem":151},"OpenAPI Generation","/advanced/openapi","4.advanced/openapi",{"title":153,"path":154,"stem":155},"Performance","/advanced/performance","4.advanced/performance",{"title":157,"path":158,"stem":159},"Repositories","/advanced/repositories","4.advanced/repositories",{"title":161,"path":162,"stem":163},"Service Providers","/advanced/service-providers","4.advanced/service-providers",{"title":165,"path":166,"stem":167},"Testing","/advanced/testing","4.advanced/testing",{"title":169,"path":170,"stem":171,"children":172},"Overview","/tooling","5.tooling",[173,175],{"title":169,"path":170,"stem":174},"5.tooling/index",{"title":176,"path":177,"stem":178},"AI Skills","/tooling/ai-skills","5.tooling/1.ai-skills",{"title":180,"path":181,"stem":182,"children":183},"Cookbook","/cookbook","6.cookbook",[184,187,191,195,199,203,207,211,215,219,223,227,231,235,239,243,247,251,255],{"title":185,"path":181,"stem":186},"Recipes & How‑Tos","6.cookbook/index",{"title":188,"path":189,"stem":190},"Routing Recipes","/cookbook/routing","6.cookbook/1.routing",{"title":192,"path":193,"stem":194},"Caching Recipes","/cookbook/caching","6.cookbook/10.caching",{"title":196,"path":197,"stem":198},"Queue Infrastructure","/cookbook/queues-and-jobs","6.cookbook/11.queues-and-jobs",{"title":200,"path":201,"stem":202},"Notification Channels & Templates","/cookbook/notifications","6.cookbook/13.notifications",{"title":204,"path":205,"stem":206},"Storage","/cookbook/storage","6.cookbook/15.storage",{"title":208,"path":209,"stem":210},"Image Processing Examples","/cookbook/image-processing","6.cookbook/16.image-processing",{"title":212,"path":213,"stem":214},"Permissions and Authorization","/cookbook/permissions-and-authorization","6.cookbook/18.permissions-and-authorization",{"title":216,"path":217,"stem":218},"Session Analytics","/cookbook/sessions-analytics","6.cookbook/19.sessions-analytics",{"title":220,"path":221,"stem":222},"Writing Middleware","/cookbook/middleware","6.cookbook/2.middleware",{"title":224,"path":225,"stem":226},"API Metrics","/cookbook/api-metrics","6.cookbook/20.api-metrics",{"title":228,"path":229,"stem":230},"Performance Recipes","/cookbook/performance","6.cookbook/21.performance",{"title":232,"path":233,"stem":234},"Memory Management","/cookbook/memory-management","6.cookbook/22.memory-management",{"title":236,"path":237,"stem":238},"Writing Console Commands","/cookbook/console-commands","6.cookbook/23.console-commands",{"title":240,"path":241,"stem":242},"Glueful Extensions","/cookbook/extensions","6.cookbook/25.extensions",{"title":244,"path":245,"stem":246},"Error Handling","/cookbook/error-handling","6.cookbook/4.error-handling",{"title":248,"path":249,"stem":250},"Security Guide","/cookbook/security","6.cookbook/5.security",{"title":252,"path":253,"stem":254},"Service Options Resolver","/cookbook/configuration","6.cookbook/6.configuration",{"title":256,"path":257,"stem":258},"Logging","/cookbook/logging","6.cookbook/7.logging",{"title":260,"path":261,"stem":262},"API Reference","/api-reference","7.api-reference",{"title":264,"path":265,"stem":266},"CLI Reference","/cli-reference","8.cli-reference",{"title":268,"path":269,"stem":270},"Extensions","/extensions","9.extensions",{"title":272,"path":273,"stem":274},"Contributing","/contributing","contributing",{"title":276,"path":277,"stem":278},"Release Notes","/releases","releases",{"title":280,"path":281,"stem":282},"Release Archive","/releases-archive","releases-archive",{"id":284,"title":17,"body":285,"description":5125,"extension":5126,"links":5127,"meta":5128,"navigation":1112,"path":18,"seo":5129,"stem":19,"__hash__":5130},"docs/10.upgrading.md",{"type":286,"value":287,"toc":5078},"minimark",[288,309,314,426,438,442,449,473,478,914,918,1232,1236,1660,1664,2189,2193,2400,2404,2885,2889,2895,3013,3016,3237,3242,3246,3506,3510,3514,3545,3549,3620,3624,3754,3758,3778,3782,3829,3833,3853,3857,3894,3898,3917,3921,3954,3958,3962,4025,4029,4137,4141,4181,4185,4193,4197,4209,4213,4224,4228,4243,4247,4251,4297,4301,4314,4318,4331,4335,4360,4364,4527,4531,4588,4592,4670,4674,4738,4742,4811,4815,4826,4956,4998,5002,5008,5074],[289,290,291,292,296,297,301,302,305,306,308],"p",{},"This guide lists only what you have to ",[293,294,295],"strong",{},"do"," when you move to a newer ",[298,299,300],"code",{},"glueful/framework",": breaking\nchanges, removed APIs and config, required migrations, and subsystems that moved to their own\npackages. For the full story of each release, see ",[303,304,276],"a",{"href":277}," (v1.70 and later) and the\n",[303,307,280],{"href":281}," (v1.69 and earlier).",[310,311,313],"h2",{"id":312},"how-to-upgrade","How to upgrade",[315,316,317,348,354,404],"ol",{},[318,319,320,323],"li",{},[293,321,322],{},"Update the package.",[324,325,330],"pre",{"className":326,"code":327,"language":328,"meta":329,"style":329},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark monokai","composer update glueful/framework\n","bash","",[298,331,332],{"__ignoreMap":329},[333,334,337,341,345],"span",{"class":335,"line":336},"line",1,[333,338,340],{"class":339},"sR7ES","composer",[333,342,344],{"class":343},"sLACW"," update",[333,346,347],{"class":343}," glueful/framework\n",[318,349,350,353],{},[293,351,352],{},"Read every section between your current version and the target."," Sections are newest first;\nversions with nothing to do are left out. Jumping several versions means applying each section on\nthe way.",[318,355,356,359,392,395,396,399,400,403],{},[293,357,358],{},"Run the deploy steps.",[324,360,362],{"className":326,"code":361,"language":328,"meta":329,"style":329},"php glueful migrate:run          # apply any new core migrations\nphp glueful route:cache:clear    # drop a route cache compiled by the old version\n",[298,363,364,379],{"__ignoreMap":329},[333,365,366,369,372,375],{"class":335,"line":336},[333,367,368],{"class":339},"php",[333,370,371],{"class":343}," glueful",[333,373,374],{"class":343}," migrate:run",[333,376,378],{"class":377},"ss7Ak","          # apply any new core migrations\n",[333,380,382,384,386,389],{"class":335,"line":381},2,[333,383,368],{"class":339},[333,385,371],{"class":343},[333,387,388],{"class":343}," route:cache:clear",[333,390,391],{"class":377},"    # drop a route cache compiled by the old version\n",[393,394],"br",{},"You don't need ",[298,397,398],{},"php glueful commands:cache"," on 1.87 or later: the production command manifest is\nstamped with the framework version that wrote it and rebuilds itself on the first boot after an\nupgrade. Run it only if you want to warm the manifest as part of your deploy. ",[298,401,402],{},"cache:clear"," never\ntouches the command manifest.",[318,405,406,413,414,417,418,421,422,425],{},[293,407,408,409,412],{},"Compare your ",[298,410,411],{},"config/"," copies with the framework's."," Since 1.86, a list your app's config sets\n(a ",[298,415,416],{},"config/schedule.php"," ",[298,419,420],{},"jobs"," list, above all) ",[293,423,424],{},"replaces"," the framework's list instead of being\nmerged into it. New framework entries — scheduled jobs, CSRF exempt routes and so on — don't reach\nyour app until you add them to your own copy.",[427,428,432],"u-alert",{"color":429,"icon":430,"variant":431},"info","i-tabler-info-circle","subtle",[433,434,435],"template",{"v-slot:description":329},[289,436,437],{},"Leaving a removed config key in your own copy changes nothing — it is simply no longer read. The\nremovals below are listed so you can tidy up, not because a stale key breaks anything.",[310,439,441],{"id":440},"upgrading-to-191","Upgrading to 1.91",[289,443,444,445,448],{},"1.91 makes a large group of settings and attributes that were silently ignored actually take effect.\nMost items below are ",[293,446,447],{},"behaviour that now matches what you wrote"," — review each one, because a value\nthat used to do nothing now applies.",[427,450,453],{"color":451,"icon":452,"variant":431},"warning","i-tabler-alert-triangle",[433,454,455],{"v-slot:description":329},[289,456,457,464,465,468,469,472],{},[293,458,459,460,463],{},"Run ",[298,461,462],{},"php glueful route:cache:clear"," on deploy."," The route cache format moved to v3 (old files are\nalso invalidated on the next boot). Routes served from the old cache lost their rate limits,\n",[298,466,467],{},"#[Fields]"," and ",[298,470,471],{},"#[RequireScope]"," config — scoped routes served from the cache let requests through.",[474,475,477],"h3",{"id":476},"security-auth","Security & auth",[479,480,481,589,665,680,709,741,785,830,865,890,904],"ul",{},[318,482,483,490,491,495,496,499,500],{},[293,484,485,486,489],{},"Stacked ",[298,487,488],{},"#[RequiresRole]"," attributes mean \"any one of these roles\"."," They were enforced as \"all\nof them\". Roles from the method, its class and its parent classes form one set, so a method-level\nrole ",[492,493,494],"em",{},"widens"," a class-level one. If a route really needs several roles at once, use\n",[298,497,498],{},"#[RequiresPermission]"," (stacked permissions are still all required) or a single combined role.",[324,501,504],{"className":502,"code":503,"language":368,"meta":329,"style":329},"language-php shiki shiki-themes material-theme-lighter github-light github-dark monokai","#[RequiresRole('admin')]\n#[RequiresRole('editor')]\npublic function publish(): Response { } // 1.91: admin OR editor (was: admin AND editor)\n",[298,505,506,535,554],{"__ignoreMap":329},[333,507,508,512,516,520,524,527,529,532],{"class":335,"line":336},[333,509,511],{"class":510},"ss--_","#[",[333,513,515],{"class":514},"s91G_","RequiresRole",[333,517,519],{"class":518},"swvn1","(",[333,521,523],{"class":522},"siCPE","'",[333,525,526],{"class":343},"admin",[333,528,523],{"class":522},[333,530,531],{"class":518},")",[333,533,534],{"class":510},"]\n",[333,536,537,539,541,543,545,548,550,552],{"class":335,"line":381},[333,538,511],{"class":510},[333,540,515],{"class":514},[333,542,519],{"class":518},[333,544,523],{"class":522},[333,546,547],{"class":343},"editor",[333,549,523],{"class":522},[333,551,531],{"class":518},[333,553,534],{"class":510},[333,555,557,561,565,569,572,576,580,583,586],{"class":335,"line":556},3,[333,558,560],{"class":559},"sTNss","public",[333,562,564],{"class":563},"srJo8"," function",[333,566,568],{"class":567},"sD0ED"," publish",[333,570,571],{"class":518},"()",[333,573,575],{"class":574},"sGXK2",":",[333,577,579],{"class":578},"s_MOj"," Response",[333,581,582],{"class":518}," {",[333,584,585],{"class":518}," }",[333,587,588],{"class":377}," // 1.91: admin OR editor (was: admin AND editor)\n",[318,590,591,594,595,598,599,602,603,605,606,652,654,655,658,659,661,662,664],{},[293,592,593],{},"CSRF config is applied."," The ",[298,596,597],{},"csrf"," middleware was built from constructor defaults, so\n",[298,600,601],{},"config/security.php","'s ",[298,604,597],{}," block was ignored. With the shipped config:",[479,607,608,619,643],{},[318,609,610,611,614,615,618],{},"tokens expire after ",[293,612,613],{},"3600s"," (was 7200s) — ",[298,616,617],{},"CSRF_TOKEN_LIFETIME",";",[318,620,621,622,625,626,629,630,629,633,629,636,629,639,642],{},"the ",[298,623,624],{},"exemptRoutes"," list (",[298,627,628],{},"auth/login",", ",[298,631,632],{},"auth/register",[298,634,635],{},"webhooks/inbound/*",[298,637,638],{},"public/*",[298,640,641],{},"csrf-token",", …)\nnow skips CSRF;",[318,644,645,468,648,651],{},[298,646,647],{},"CSRF_PROTECTION_ENABLED",[298,649,650],{},"CSRF_DOUBLE_SUBMIT"," take effect.",[393,653],{},"Review your app's ",[298,656,657],{},"security.csrf"," block before upgrading. If your app has its own\n",[298,660,601],{},", its ",[298,663,624],{}," list replaces the framework's.",[318,666,667,672,673,675,676,679],{},[293,668,669,671],{},[298,670,624],{}," entries match under the API prefix."," Short entries such as ",[298,674,628],{}," now match\n",[298,677,678],{},"/api/v1/auth/login"," (full-path entries keep working). Check that no route you meant to protect now\nmatches a shipped exemption.",[318,681,682,687,688,691,692,695,696,699,700,702,703,705,706,708],{},[293,683,684,685],{},"The shipped webhook exemption is ",[298,686,635],{}," (it was ",[298,689,690],{},"webhooks/*",", which never took\neffect before 1.91 and would have exempted the authenticated management API at\n",[298,693,694],{},"/api/v1/webhooks/...","). Mount inbound third-party receivers under ",[298,697,698],{},"/webhooks/inbound/\u003Cprovider>",",\nor give them their own ",[298,701,624],{}," entry. If your app has its own ",[298,704,601],{},", change\n",[298,707,690],{}," there too.",[318,710,711,726,729,730,733,734,737,738,740],{},[293,712,713,714,717,718,721,722,725],{},"Cookie-transport writes with no ",[298,715,716],{},"Origin"," / ",[298,719,720],{},"Referer"," get ",[298,723,724],{},"403 CSRF_ORIGIN_MISMATCH",".",[298,727,728],{},"skip_for_bearer_auth"," used to skip the origin check because the ",[298,731,732],{},"Authorization"," header had been\ninjected by ",[298,735,736],{},"session_cookie",". Browsers send ",[298,739,716],{}," on cross-origin writes; non-browser clients\nusing the session cookie must send one too.",[318,742,743,746,747,468,750,753,754,575,757,774,776,777,780,781,784],{},[293,744,745],{},"A split-subdomain SPA needs an exact CORS origin with credentials"," for\n",[298,748,749],{},"/auth/session/refresh",[298,751,752],{},"/auth/session/logout"," to accept ",[298,755,756],{},"Sec-Fetch-Site: same-site",[324,758,762],{"className":759,"code":760,"language":761,"meta":329,"style":329},"language-env shiki shiki-themes material-theme-lighter github-light github-dark monokai","CORS_ALLOWED_ORIGINS=https://app.example.com\nCORS_SUPPORTS_CREDENTIALS=true\n","env",[298,763,764,769],{"__ignoreMap":329},[333,765,766],{"class":335,"line":336},[333,767,768],{},"CORS_ALLOWED_ORIGINS=https://app.example.com\n",[333,770,771],{"class":335,"line":381},[333,772,773],{},"CORS_SUPPORTS_CREDENTIALS=true\n",[393,775],{},"A wildcard (",[298,778,779],{},"*",") origin never qualifies; an exact entry (or an anchored\n",[298,782,783],{},"CORS_ALLOWED_ORIGIN_PATTERNS"," match) with credentials on does.",[318,786,787,790,791,793,794,797,798,801,802,725,804],{},[293,788,789],{},"CORS origin patterns apply to real responses, not just preflight."," Origins matching\n",[298,792,783],{}," (and every origin when ",[298,795,796],{},"development_allow_all"," is on in development)\nnow get ",[298,799,800],{},"Access-Control-Allow-Origin"," on actual responses. Patterns are comma-separated PCRE\nregexes with delimiters; make sure each is anchored. See ",[303,803,89],{"href":90},[324,805,807],{"className":759,"code":806,"language":761,"meta":329,"style":329},"# Unanchored — also matches https://app.example.com.attacker.net\nCORS_ALLOWED_ORIGIN_PATTERNS=/https:\\/\\/.*\\.example\\.com/\n# Anchored\nCORS_ALLOWED_ORIGIN_PATTERNS=/^https:\\/\\/[a-z0-9-]+\\.example\\.com$/\n",[298,808,809,814,819,824],{"__ignoreMap":329},[333,810,811],{"class":335,"line":336},[333,812,813],{},"# Unanchored — also matches https://app.example.com.attacker.net\n",[333,815,816],{"class":335,"line":381},[333,817,818],{},"CORS_ALLOWED_ORIGIN_PATTERNS=/https:\\/\\/.*\\.example\\.com/\n",[333,820,821],{"class":335,"line":556},[333,822,823],{},"# Anchored\n",[333,825,827],{"class":335,"line":826},4,[333,828,829],{},"CORS_ALLOWED_ORIGIN_PATTERNS=/^https:\\/\\/[a-z0-9-]+\\.example\\.com$/\n",[318,831,832,838,839,842,843,846,847,849,850,853,854,856,857,860,861,864],{},[293,833,834,835,837],{},"JWT requests can pass ",[298,836,471],{}," routes."," Scopes come from the token's ",[298,840,841],{},"scopes"," claim, a\n",[298,844,845],{},"scope"," array or space-delimited ",[298,848,845],{}," string (",[298,851,852],{},"\"read:posts write:posts\"","), the user record's\n",[298,855,841],{},", or an ",[298,858,859],{},"identity.claims_provider",". A JWT with ",[293,862,863],{},"no"," scopes is still denied — unlike an API\nkey, empty does not mean unrestricted. If you minted JWTs with scope claims, those users may now\nreach routes they were refused before.",[318,866,867,878,879,882,883,886,887,725],{},[293,868,869,870,873,874,877],{},"Clear ",[298,871,872],{},"SESSION_COOKIE_REFRESH_PATH=/auth/session"," from existing ",[298,875,876],{},".env"," files"," so the derived path\n(",[298,880,881],{},"api_prefix() . '/auth/session'",", e.g. ",[298,884,885],{},"/api/v1/auth/session",") applies. Keep an explicit value only\nif your session routes really are served there. See ",[303,888,889],{"href":82},"Browser Sessions",[318,891,892,899,900,903],{},[293,893,894,895,898],{},"Gate-only apps: ",[298,896,897],{},"requirePermission()"," consults the Gate."," With no permission provider installed,\nendpoints that call it (metrics, health, config, …) refused everyone with \"Permission system\nunavailable\"; they now admit users whose Gate roles or voters allow it. Check the voters in your\n",[298,901,902],{},"config/permissions.php"," before upgrading.",[318,905,906,909,910,913],{},[293,907,908],{},"Signed blob URLs issued earlier point at an unrouted path"," (",[298,911,912],{},"/blobs/{uuid}"," without the API\nprefix). Generate new ones.",[474,915,917],{"id":916},"routing-middleware","Routing & middleware",[479,919,920,951,963,975,1015,1180,1201,1215],{},[318,921,922,594,931,934,935,629,937,940,941,944,945,948,949,725],{},[293,923,924,468,927,930],{},[298,925,926],{},"->rateLimit()",[298,928,929],{},"#[RateLimit]"," are enforced.",[298,932,933],{},"rate_limit"," middleware is now attached\nautomatically (never duplicated if you added ",[298,936,933],{},[298,938,939],{},"rate_limit:…"," or ",[298,942,943],{},"enhanced_rate_limit","\nyourself), so these routes can return ",[298,946,947],{},"429"," with rate-limit headers. See\n",[303,950,117],{"href":118},[318,952,953,959,960,962],{},[293,954,955,956,958],{},"A class-level ",[298,957,929],{}," applies to every route in the controller"," that has no method-level\n",[298,961,929],{}," of its own (a method-level one replaces the class set). Check these limits before\nupgrading.",[318,964,965,971,972,725],{},[293,966,967,970],{},[298,968,969],{},"rate_limit:N,W,user|endpoint"," keys by user or endpoint"," instead of always by IP. Unknown values\nfall back to ",[298,973,974],{},"ip",[318,976,977,982,983,717,986,989,990,993,994,997,998,1001,1002,1005,1006,629,1009,1012,1013,725],{},[293,978,979,981],{},[298,980,467],{}," routes project their responses"," by ",[298,984,985],{},"?fields=",[298,987,988],{},"?expand="," (the ",[298,991,992],{},"field_selection","\nmiddleware is attached automatically) and return ",[298,995,996],{},"400"," for strict whitelist violations.\n",[298,999,1000],{},"useAdvancedPatterns: true"," now makes ",[298,1003,1004],{},"allowed"," act as patterns (",[298,1007,1008],{},"posts.*",[298,1010,1011],{},"-password","). See\n",[303,1014,101],{"href":102},[318,1016,1017,1026,1027,1177,1179],{},[293,1018,1019,468,1022,1025],{},[298,1020,1021],{},"csrf:",[298,1023,1024],{},"request_logging:"," route params take effect."," Values written in a route definition\nwere ignored and the middleware ran on defaults:",[324,1028,1030],{"className":502,"code":1029,"language":368,"meta":329,"style":329},"$router->post('/forms/contact', [FormController::class, 'store'])\n    ->middleware(['csrf:3600,false,false']);        // lifetime, double-submit, validate-origin — now honoured\n\n$router->get('/reports', [ReportController::class, 'index'])\n    ->middleware(['request_logging:both,true,true']); // now honoured\n",[298,1031,1032,1084,1108,1114,1156],{"__ignoreMap":329},[333,1033,1034,1037,1040,1043,1046,1048,1050,1053,1055,1058,1061,1064,1067,1071,1073,1076,1079,1081],{"class":335,"line":336},[333,1035,1036],{"class":518},"$",[333,1038,1039],{"class":510},"router",[333,1041,1042],{"class":574},"->",[333,1044,1045],{"class":567},"post",[333,1047,519],{"class":518},[333,1049,523],{"class":522},[333,1051,1052],{"class":343},"/forms/contact",[333,1054,523],{"class":522},[333,1056,1057],{"class":518},",",[333,1059,1060],{"class":518}," [",[333,1062,1063],{"class":578},"FormController",[333,1065,1066],{"class":574},"::",[333,1068,1070],{"class":1069},"shWJe","class",[333,1072,1057],{"class":518},[333,1074,1075],{"class":522}," '",[333,1077,1078],{"class":343},"store",[333,1080,523],{"class":522},[333,1082,1083],{"class":518},"])\n",[333,1085,1086,1089,1092,1095,1097,1100,1102,1105],{"class":335,"line":381},[333,1087,1088],{"class":574},"    ->",[333,1090,1091],{"class":567},"middleware",[333,1093,1094],{"class":518},"([",[333,1096,523],{"class":522},[333,1098,1099],{"class":343},"csrf:3600,false,false",[333,1101,523],{"class":522},[333,1103,1104],{"class":518},"]);",[333,1106,1107],{"class":377},"        // lifetime, double-submit, validate-origin — now honoured\n",[333,1109,1110],{"class":335,"line":556},[333,1111,1113],{"emptyLinePlaceholder":1112},true,"\n",[333,1115,1116,1118,1120,1122,1125,1127,1129,1132,1134,1136,1138,1141,1143,1145,1147,1149,1152,1154],{"class":335,"line":826},[333,1117,1036],{"class":518},[333,1119,1039],{"class":510},[333,1121,1042],{"class":574},[333,1123,1124],{"class":567},"get",[333,1126,519],{"class":518},[333,1128,523],{"class":522},[333,1130,1131],{"class":343},"/reports",[333,1133,523],{"class":522},[333,1135,1057],{"class":518},[333,1137,1060],{"class":518},[333,1139,1140],{"class":578},"ReportController",[333,1142,1066],{"class":574},[333,1144,1070],{"class":1069},[333,1146,1057],{"class":518},[333,1148,1075],{"class":522},[333,1150,1151],{"class":343},"index",[333,1153,523],{"class":522},[333,1155,1083],{"class":518},[333,1157,1159,1161,1163,1165,1167,1170,1172,1174],{"class":335,"line":1158},5,[333,1160,1088],{"class":574},[333,1162,1091],{"class":567},[333,1164,1094],{"class":518},[333,1166,523],{"class":522},[333,1168,1169],{"class":343},"request_logging:both,true,true",[333,1171,523],{"class":522},[333,1173,1104],{"class":518},[333,1175,1176],{"class":377}," // now honoured\n",[393,1178],{},"Review routes that pass these params: the values you wrote now apply.",[318,1181,1182,1196,1197,1200],{},[293,1183,1184,1185,1188,1189,1192,1193,531],{},"The container-built ",[298,1186,1187],{},"tracing"," middleware follows ",[298,1190,1191],{},"TRACING_ENABLED"," (default ",[298,1194,1195],{},"false"," and passes\nrequests straight through when disabled. Set ",[298,1198,1199],{},"TRACING_ENABLED=true"," if you bind a real tracer.",[318,1202,1203,1212,1213,725],{},[293,1204,1205,468,1208,1211],{},[298,1206,1207],{},"API_RATE_LIMITING_ENABLED=false",[298,1209,1210],{},"api.field_selection.enabled = false"," switch those features\noff."," Neither key was read, so rate limits and field selection always applied. Check apps that set\nthem to ",[298,1214,1195],{},[318,1216,1217,1220,1221,1224,1225,1228,1229,725],{},[293,1218,1219],{},"OpenAPI tags skip the API prefix."," Under ",[298,1222,1223],{},"/api/v1"," every operation was tagged \"Api\"; tags now come\nfrom the first segment after the prefix (e.g. \"Users\"). Generated SDKs and docs grouping change —\nregenerate them with ",[298,1226,1227],{},"php glueful generate:openapi",". See ",[303,1230,1231],{"href":150},"OpenAPI",[474,1233,1235],{"id":1234},"validation-database","Validation & database",[479,1237,1238,1383,1407,1424,1440,1453,1466,1629],{},[318,1239,1240,1249,1250,1253,1254,1257,1258,1261,1262,1370,1372,1373,1376,1377,1380,1381,725],{},[293,1241,1242,468,1245,1248],{},[298,1243,1244],{},"unique:",[298,1246,1247],{},"exists:"," rules are enforced."," They silently passed before, so requests that used to\npass may now get ",[298,1251,1252],{},"422",". The rules fail closed: with no connection or context they throw\n",[298,1255,1256],{},"LogicException",". A ",[298,1259,1260],{},"Validator"," you build by hand needs a context or connection:",[324,1263,1265],{"className":502,"code":1264,"language":368,"meta":329,"style":329},"// Before — unique:/exists: silently passed\n$validator = new Validator($rules);\n\n// After\n$validator = new Validator($rules, $context);\n// or\n$validator = new Validator($rules, connection: $connection);\n",[298,1266,1267,1272,1297,1301,1306,1332,1338],{"__ignoreMap":329},[333,1268,1269],{"class":335,"line":336},[333,1270,1271],{"class":377},"// Before — unique:/exists: silently passed\n",[333,1273,1274,1276,1279,1282,1285,1288,1291,1294],{"class":335,"line":381},[333,1275,1036],{"class":518},[333,1277,1278],{"class":510},"validator ",[333,1280,1281],{"class":574},"=",[333,1283,1284],{"class":1069}," new",[333,1286,1287],{"class":578}," Validator",[333,1289,1290],{"class":518},"($",[333,1292,1293],{"class":510},"rules",[333,1295,1296],{"class":518},");\n",[333,1298,1299],{"class":335,"line":556},[333,1300,1113],{"emptyLinePlaceholder":1112},[333,1302,1303],{"class":335,"line":826},[333,1304,1305],{"class":377},"// After\n",[333,1307,1308,1310,1312,1314,1316,1318,1320,1322,1324,1327,1330],{"class":335,"line":1158},[333,1309,1036],{"class":518},[333,1311,1278],{"class":510},[333,1313,1281],{"class":574},[333,1315,1284],{"class":1069},[333,1317,1287],{"class":578},[333,1319,1290],{"class":518},[333,1321,1293],{"class":510},[333,1323,1057],{"class":518},[333,1325,1326],{"class":518}," $",[333,1328,1329],{"class":510},"context",[333,1331,1296],{"class":518},[333,1333,1335],{"class":335,"line":1334},6,[333,1336,1337],{"class":377},"// or\n",[333,1339,1341,1343,1345,1347,1349,1351,1353,1355,1357,1361,1363,1365,1368],{"class":335,"line":1340},7,[333,1342,1036],{"class":518},[333,1344,1278],{"class":510},[333,1346,1281],{"class":574},[333,1348,1284],{"class":1069},[333,1350,1287],{"class":578},[333,1352,1290],{"class":518},[333,1354,1293],{"class":510},[333,1356,1057],{"class":518},[333,1358,1360],{"class":1359},"sQqfL"," connection",[333,1362,575],{"class":518},[333,1364,1326],{"class":518},[333,1366,1367],{"class":510},"connection",[333,1369,1296],{"class":518},[393,1371],{},"(Or call ",[298,1374,1375],{},"setConnection()"," on the rule.) Database-backed custom rules can implement the new\n",[298,1378,1379],{},"Glueful\\Validation\\Contracts\\DatabaseAwareRule"," to receive the Validator's connection. See\n",[303,1382,57],{"href":58},[318,1384,1385,1393,1394,1396,1397,1399,1400,1403,1404,725],{},[293,1386,1387,468,1390,651],{},[298,1388,1389],{},"nullable",[298,1391,1392],{},"sometimes"," A null or empty value stops the rules after ",[298,1395,1389],{},";\nan absent ",[298,1398,1392],{}," field skips all its rules and no longer appears in ",[298,1401,1402],{},"filtered()"," /\n",[298,1405,1406],{},"validated()",[318,1408,1409,1419,1420,1423],{},[293,1410,1411,1414,1415,1418],{},[298,1412,1413],{},"useCurrentOnUpdate()"," throws ",[298,1416,1417],{},"UnsupportedSchemaOperationException"," on PostgreSQL and SQLite","\ninstead of being silently ignored (MySQL now emits ",[298,1421,1422],{},"ON UPDATE CURRENT_TIMESTAMP","). Use a trigger or\nset the column in application code, and gate the call by driver in portable migrations.",[318,1425,1426,1435,1436,1439],{},[293,1427,1428,1431,1432,725],{},[298,1429,1430],{},"ChunkedDatabaseProcessor","'s constructor is typed ",[298,1433,1434],{},"Connection|PDO|mysqli"," Pass a Glueful\n",[298,1437,1438],{},"Connection"," (now supported) or a raw driver handle.",[318,1441,1442,1452],{},[293,1443,1444,1445,1448,1449,725],{},"SQLite unique violations return ",[298,1446,1447],{},"409",", not ",[298,1450,1451],{},"500"," Adjust tests that expected a 500.",[318,1454,1455,1461,1462,1465],{},[293,1456,1457,1458],{},"Deprecated: ",[298,1459,1460],{},"QueryBuilder::optimize()"," — it never had an effect and is now a no-op returning the\nbuilder. Call ",[298,1463,1464],{},"QueryOptimizer::optimizeQuery()"," directly.",[318,1467,1468,1474,1475],{},[293,1469,1470,1471],{},"Removed: ",[298,1472,1473],{},"Utils::buildSearchConditions()"," (deprecated since 1.86.3). Use the query builder:",[324,1476,1478],{"className":502,"code":1477,"language":368,"meta":329,"style":329},"// Before\n$conditions = Utils::buildSearchConditions($search, ['name', 'email']);\n\n// After\n$query->where(function ($q) use ($search) {\n    $q->whereContains('name', $search)->orWhereContains('email', $search);\n});\n",[298,1479,1480,1485,1530,1534,1538,1575,1624],{"__ignoreMap":329},[333,1481,1482],{"class":335,"line":336},[333,1483,1484],{"class":377},"// Before\n",[333,1486,1487,1489,1492,1494,1497,1499,1502,1504,1507,1509,1511,1513,1516,1518,1520,1522,1525,1527],{"class":335,"line":381},[333,1488,1036],{"class":518},[333,1490,1491],{"class":510},"conditions ",[333,1493,1281],{"class":574},[333,1495,1496],{"class":578}," Utils",[333,1498,1066],{"class":574},[333,1500,1501],{"class":567},"buildSearchConditions",[333,1503,1290],{"class":518},[333,1505,1506],{"class":510},"search",[333,1508,1057],{"class":518},[333,1510,1060],{"class":518},[333,1512,523],{"class":522},[333,1514,1515],{"class":343},"name",[333,1517,523],{"class":522},[333,1519,1057],{"class":518},[333,1521,1075],{"class":522},[333,1523,1524],{"class":343},"email",[333,1526,523],{"class":522},[333,1528,1529],{"class":518},"]);\n",[333,1531,1532],{"class":335,"line":556},[333,1533,1113],{"emptyLinePlaceholder":1112},[333,1535,1536],{"class":335,"line":826},[333,1537,1305],{"class":377},[333,1539,1540,1542,1545,1547,1550,1552,1555,1558,1561,1563,1566,1568,1570,1572],{"class":335,"line":1158},[333,1541,1036],{"class":518},[333,1543,1544],{"class":510},"query",[333,1546,1042],{"class":574},[333,1548,1549],{"class":567},"where",[333,1551,519],{"class":518},[333,1553,1554],{"class":563},"function",[333,1556,1557],{"class":518}," ($",[333,1559,1560],{"class":510},"q",[333,1562,531],{"class":518},[333,1564,1565],{"class":1069}," use",[333,1567,1557],{"class":518},[333,1569,1506],{"class":510},[333,1571,531],{"class":518},[333,1573,1574],{"class":518}," {\n",[333,1576,1577,1580,1582,1584,1587,1589,1591,1593,1595,1597,1599,1601,1603,1605,1608,1610,1612,1614,1616,1618,1620,1622],{"class":335,"line":1334},[333,1578,1579],{"class":518},"    $",[333,1581,1560],{"class":510},[333,1583,1042],{"class":574},[333,1585,1586],{"class":567},"whereContains",[333,1588,519],{"class":518},[333,1590,523],{"class":522},[333,1592,1515],{"class":343},[333,1594,523],{"class":522},[333,1596,1057],{"class":518},[333,1598,1326],{"class":518},[333,1600,1506],{"class":510},[333,1602,531],{"class":518},[333,1604,1042],{"class":574},[333,1606,1607],{"class":567},"orWhereContains",[333,1609,519],{"class":518},[333,1611,523],{"class":522},[333,1613,1524],{"class":343},[333,1615,523],{"class":522},[333,1617,1057],{"class":518},[333,1619,1326],{"class":518},[333,1621,1506],{"class":510},[333,1623,1296],{"class":518},[333,1625,1626],{"class":335,"line":1340},[333,1627,1628],{"class":518},"});\n",[318,1630,1631,594,1634,1637,1638,1641,1642,629,1645,629,1648,1651,1652,1655,1656,1659],{},[293,1632,1633],{},"MySQL with SSL now connects over TLS with certificate verification on.",[298,1635,1636],{},"ssl.*"," connection\nconfig was never applied; when ",[298,1639,1640],{},"DB_SSL_ENABLED"," is true and at least one certificate path\n(",[298,1643,1644],{},"DB_SSL_CA_CERT",[298,1646,1647],{},"DB_SSL_CLIENT_CERT",[298,1649,1650],{},"DB_SSL_CLIENT_KEY",") is set, it now maps to\n",[298,1653,1654],{},"PDO::MYSQL_ATTR_SSL_*",". Set ",[298,1657,1658],{},"DB_SSL_VERIFY_CERT=false"," if your server's certificate can't be\nverified.",[474,1661,1663],{"id":1662},"orm","ORM",[479,1665,1666,1701,1762,1973,2104,2138],{},[318,1667,1668,1674,1675,1677,1678,1681,1682,1685,1686,1689,1690,1693,1694,1697,1698,1700],{},[293,1669,1670,1673],{},[298,1671,1672],{},"forceDelete()"," really deletes."," Instance and builder ",[298,1676,1672],{}," issue a real ",[298,1679,1680],{},"DELETE","\n(they used to soft-delete); builder ",[298,1683,1684],{},"restore()"," updates only trashed rows; after a soft ",[298,1687,1688],{},"delete()"," the\ninstance keeps ",[298,1691,1692],{},"exists = true"," (and reports ",[298,1695,1696],{},"trashed()","), so it can still be restored or\nforce-deleted. Check code that called ",[298,1699,1672],{}," expecting a soft delete.",[318,1702,1703,1709,1710,1712,1713,717,1716,717,1719,717,1722,717,1725,717,1728,1731,1732,717,1735,717,1737,717,1739,1741,1742,1744,1745,1747,1748,629,1751,629,1753,629,1756,468,1758,1761],{},[293,1704,1705,1706,1708],{},"Model event callbacks that return ",[298,1707,1195],{}," cancel the write."," A ",[298,1711,1195],{}," from\n",[298,1714,1715],{},"saving",[298,1717,1718],{},"creating",[298,1720,1721],{},"updating",[298,1723,1724],{},"deleting",[298,1726,1727],{},"restoring",[298,1729,1730],{},"forceDeleting"," now stops it, and\n",[298,1733,1734],{},"save()",[298,1736,1688],{},[298,1738,1684],{},[298,1740,1672],{}," return ",[298,1743,1195],{},". Make sure no callback returns\n",[298,1746,1195],{}," by accident. The ",[298,1749,1750],{},"trashed",[298,1752,1727],{},[298,1754,1755],{},"restored",[298,1757,1730],{},[298,1759,1760],{},"forceDeleted","\ncallbacks, which were dropped, now run.",[318,1763,1764,1775,1776,717,1779,1782,1783,1964,1966,717,1969,1972],{},[293,1765,1766,1767,1770,1771,1774],{},"ORM ",[298,1768,1769],{},"paginate()['data']"," is a ",[298,1772,1773],{},"Glueful\\Database\\ORM\\Collection"," of models",", not a list of arrays,\nand honours ",[298,1777,1778],{},"with()",[298,1780,1781],{},"withCount()",". The other keys are unchanged.",[324,1784,1786],{"className":502,"code":1785,"language":368,"meta":329,"style":329},"$result = User::query($context)->paginate(page: 1, perPage: 25);\n\n// Before\n$name = $result['data'][0]['name'];\n\n// After\n$name = $result['data'][0]->name;\n$rows = $result['data']->toArray(); // if you need arrays\n",[298,1787,1788,1838,1842,1846,1887,1891,1895,1929],{"__ignoreMap":329},[333,1789,1790,1792,1795,1797,1800,1802,1804,1806,1808,1810,1812,1815,1817,1820,1822,1826,1828,1831,1833,1836],{"class":335,"line":336},[333,1791,1036],{"class":518},[333,1793,1794],{"class":510},"result ",[333,1796,1281],{"class":574},[333,1798,1799],{"class":578}," User",[333,1801,1066],{"class":574},[333,1803,1544],{"class":567},[333,1805,1290],{"class":518},[333,1807,1329],{"class":510},[333,1809,531],{"class":518},[333,1811,1042],{"class":574},[333,1813,1814],{"class":567},"paginate",[333,1816,519],{"class":518},[333,1818,1819],{"class":1359},"page",[333,1821,575],{"class":518},[333,1823,1825],{"class":1824},"sYThS"," 1",[333,1827,1057],{"class":518},[333,1829,1830],{"class":1359}," perPage",[333,1832,575],{"class":518},[333,1834,1835],{"class":1824}," 25",[333,1837,1296],{"class":518},[333,1839,1840],{"class":335,"line":381},[333,1841,1113],{"emptyLinePlaceholder":1112},[333,1843,1844],{"class":335,"line":556},[333,1845,1484],{"class":377},[333,1847,1848,1850,1853,1855,1857,1860,1863,1865,1868,1870,1873,1876,1878,1880,1882,1884],{"class":335,"line":826},[333,1849,1036],{"class":518},[333,1851,1852],{"class":510},"name ",[333,1854,1281],{"class":574},[333,1856,1326],{"class":518},[333,1858,1859],{"class":510},"result",[333,1861,1862],{"class":518},"[",[333,1864,523],{"class":522},[333,1866,1867],{"class":343},"data",[333,1869,523],{"class":522},[333,1871,1872],{"class":518},"][",[333,1874,1875],{"class":1824},"0",[333,1877,1872],{"class":518},[333,1879,523],{"class":522},[333,1881,1515],{"class":343},[333,1883,523],{"class":522},[333,1885,1886],{"class":518},"];\n",[333,1888,1889],{"class":335,"line":1158},[333,1890,1113],{"emptyLinePlaceholder":1112},[333,1892,1893],{"class":335,"line":1334},[333,1894,1305],{"class":377},[333,1896,1897,1899,1901,1903,1905,1907,1909,1911,1913,1915,1917,1919,1922,1924,1926],{"class":335,"line":1340},[333,1898,1036],{"class":518},[333,1900,1852],{"class":510},[333,1902,1281],{"class":574},[333,1904,1326],{"class":518},[333,1906,1859],{"class":510},[333,1908,1862],{"class":518},[333,1910,523],{"class":522},[333,1912,1867],{"class":343},[333,1914,523],{"class":522},[333,1916,1872],{"class":518},[333,1918,1875],{"class":1824},[333,1920,1921],{"class":518},"]",[333,1923,1042],{"class":574},[333,1925,1515],{"class":510},[333,1927,1928],{"class":518},";\n",[333,1930,1932,1934,1937,1939,1941,1943,1945,1947,1949,1951,1953,1955,1958,1961],{"class":335,"line":1931},8,[333,1933,1036],{"class":518},[333,1935,1936],{"class":510},"rows ",[333,1938,1281],{"class":574},[333,1940,1326],{"class":518},[333,1942,1859],{"class":510},[333,1944,1862],{"class":518},[333,1946,523],{"class":522},[333,1948,1867],{"class":343},[333,1950,523],{"class":522},[333,1952,1921],{"class":518},[333,1954,1042],{"class":574},[333,1956,1957],{"class":567},"toArray",[333,1959,1960],{"class":518},"();",[333,1962,1963],{"class":377}," // if you need arrays\n",[393,1965],{},[298,1967,1968],{},"JsonResource::collection()",[298,1970,1971],{},"ModelResource::collection()"," accept the collection as-is.",[318,1974,1975,1987,1988,1990,1991,1993,1994],{},[293,1976,1977,717,1980,1983,1984],{},[298,1978,1979],{},"whereHas()",[298,1981,1982],{},"has()"," closures receive the related model's ORM ",[298,1985,1986],{},"Builder"," (unknown methods still\npass through to the QueryBuilder), and the closure is actually applied. ",[298,1989,1781],{}," closures\nreceive the relation, like ",[298,1992,1778],{},". Self-referencing relations and unsupported relation types now\nthrow instead of returning wrong rows. Type-hinted closures may need updating:",[324,1995,1997],{"className":502,"code":1996,"language":368,"meta":329,"style":329},"use Glueful\\Database\\ORM\\Builder;\n\nPost::query($context)->whereHas('comments', function (Builder $q) {\n    $q->where('approved', true);\n});\n",[298,1998,1999,2024,2028,2073,2100],{"__ignoreMap":329},[333,2000,2001,2004,2007,2011,2013,2015,2017,2019,2022],{"class":335,"line":336},[333,2002,2003],{"class":1069},"use",[333,2005,2006],{"class":514}," Glueful",[333,2008,2010],{"class":2009},"sv8o3","\\",[333,2012,53],{"class":514},[333,2014,2010],{"class":2009},[333,2016,1663],{"class":514},[333,2018,2010],{"class":2009},[333,2020,1986],{"class":2021},"seZir",[333,2023,1928],{"class":518},[333,2025,2026],{"class":335,"line":381},[333,2027,1113],{"emptyLinePlaceholder":1112},[333,2029,2030,2033,2035,2037,2039,2041,2043,2045,2048,2050,2052,2055,2057,2059,2061,2063,2065,2067,2069,2071],{"class":335,"line":556},[333,2031,2032],{"class":578},"Post",[333,2034,1066],{"class":574},[333,2036,1544],{"class":567},[333,2038,1290],{"class":518},[333,2040,1329],{"class":510},[333,2042,531],{"class":518},[333,2044,1042],{"class":574},[333,2046,2047],{"class":567},"whereHas",[333,2049,519],{"class":518},[333,2051,523],{"class":522},[333,2053,2054],{"class":343},"comments",[333,2056,523],{"class":522},[333,2058,1057],{"class":518},[333,2060,564],{"class":563},[333,2062,909],{"class":518},[333,2064,1986],{"class":578},[333,2066,1326],{"class":518},[333,2068,1560],{"class":510},[333,2070,531],{"class":518},[333,2072,1574],{"class":518},[333,2074,2075,2077,2079,2081,2083,2085,2087,2090,2092,2094,2098],{"class":335,"line":826},[333,2076,1579],{"class":518},[333,2078,1560],{"class":510},[333,2080,1042],{"class":574},[333,2082,1549],{"class":567},[333,2084,519],{"class":518},[333,2086,523],{"class":522},[333,2088,2089],{"class":343},"approved",[333,2091,523],{"class":522},[333,2093,1057],{"class":518},[333,2095,2097],{"class":2096},"sMTiH"," true",[333,2099,1296],{"class":518},[333,2101,2102],{"class":335,"line":1158},[333,2103,1628],{"class":518},[318,2105,2106,2115,2116,2119,2120,2123,2124,2126,2127,2130,2131,909,2134,2137],{},[293,2107,2108,2111,2112],{},[298,2109,2110],{},"AsEncryptedString"," writes with ",[298,2113,2114],{},"EncryptionService"," (AES-256-GCM under ",[298,2117,2118],{},"APP_KEY",", AAD\n",[298,2121,2122],{},"\u003Ctable>.\u003Ccolumn>","). Set ",[298,2125,2118],{},", and keep ",[298,2128,2129],{},"ENCRYPTION_KEY"," until every legacy AES-256-CBC value has\nbeen re-saved (legacy values still decrypt and are re-encrypted on their next save). Models using the\ncast must be constructed with an ",[298,2132,2133],{},"ApplicationContext",[298,2135,2136],{},"new User($attributes, $context)",", or the\ncontext-first static helpers).",[318,2139,2140,2149,2150],{},[293,2141,2142,2145,2146,725],{},[298,2143,2144],{},"HasTimestamps::fromDateTime()"," takes ",[298,2147,2148],{},"mixed"," A subclass override with a narrower parameter\ntype must widen it:",[324,2151,2153],{"className":502,"code":2152,"language":368,"meta":329,"style":329},"public function fromDateTime(mixed $date): string { … }\n",[298,2154,2155],{"__ignoreMap":329},[333,2156,2157,2159,2161,2164,2166,2168,2170,2173,2175,2177,2180,2182,2186],{"class":335,"line":336},[333,2158,560],{"class":559},[333,2160,564],{"class":563},[333,2162,2163],{"class":567}," fromDateTime",[333,2165,519],{"class":518},[333,2167,2148],{"class":1069},[333,2169,1326],{"class":518},[333,2171,2172],{"class":510},"date",[333,2174,531],{"class":518},[333,2176,575],{"class":574},[333,2178,2179],{"class":1069}," string",[333,2181,582],{"class":518},[333,2183,2185],{"class":2184},"sQeA1"," …",[333,2187,2188],{"class":518}," }\n",[474,2190,2192],{"id":2191},"logging-errors-events","Logging, errors & events",[479,2194,2195,2216,2226,2350,2376,2387],{},[318,2196,2197,417,2203,2206,2207,940,2209,2211,2212,2215],{},[293,2198,2199,2202],{},[298,2200,2201],{},"LOG_LEVEL"," filters.",[298,2204,2205],{},"LogManager"," used to write every level; the environment profile's default\n(for example ",[298,2208,429],{},[298,2210,451],{},") now drops lower-level messages. Set ",[298,2213,2214],{},"LOG_LEVEL=debug"," to keep\neverything.",[318,2217,2218,2225],{},[293,2219,2220,2221,2224],{},"App ",[298,2222,2223],{},"Handler::mapChannel()"," mappings win"," over the framework-origin heuristic and the built-in\ndefaults, so mapped exceptions move to your app's channel (the most specific class wins).",[318,2227,2228,2242,2243,2246,2247],{},[293,2229,2230,2231,717,2234,2237,2238,2241],{},"The container's ",[298,2232,2233],{},"CacheStore",[298,2235,2236],{},"cache.store"," is an ",[298,2239,2240],{},"EventDispatchingCacheStore"," wrapper"," that\nfires cache hit/miss events. ",[298,2244,2245],{},"instanceof"," checks against a driver class fail on it; unwrap first.\nDriver-only methods are forwarded.",[324,2248,2250],{"className":502,"code":2249,"language":368,"meta":329,"style":329},"// Before\nif ($cache instanceof RedisCacheDriver) { … }\n\n// After\n$store = $cache instanceof EventDispatchingCacheStore ? $cache->getInnerStore() : $cache;\nif ($store instanceof RedisCacheDriver) { … }\n",[298,2251,2252,2256,2280,2284,2288,2330],{"__ignoreMap":329},[333,2253,2254],{"class":335,"line":336},[333,2255,1484],{"class":377},[333,2257,2258,2262,2264,2267,2269,2272,2274,2276,2278],{"class":335,"line":381},[333,2259,2261],{"class":2260},"sRxSC","if",[333,2263,1557],{"class":518},[333,2265,2266],{"class":510},"cache ",[333,2268,2245],{"class":574},[333,2270,2271],{"class":578}," RedisCacheDriver",[333,2273,531],{"class":518},[333,2275,582],{"class":518},[333,2277,2185],{"class":2184},[333,2279,2188],{"class":518},[333,2281,2282],{"class":335,"line":556},[333,2283,1113],{"emptyLinePlaceholder":1112},[333,2285,2286],{"class":335,"line":826},[333,2287,1305],{"class":377},[333,2289,2290,2292,2295,2297,2299,2301,2303,2306,2309,2311,2314,2316,2319,2321,2324,2326,2328],{"class":335,"line":1158},[333,2291,1036],{"class":518},[333,2293,2294],{"class":510},"store ",[333,2296,1281],{"class":574},[333,2298,1326],{"class":518},[333,2300,2266],{"class":510},[333,2302,2245],{"class":574},[333,2304,2305],{"class":578}," EventDispatchingCacheStore",[333,2307,2308],{"class":574}," ?",[333,2310,1326],{"class":518},[333,2312,2313],{"class":510},"cache",[333,2315,1042],{"class":574},[333,2317,2318],{"class":567},"getInnerStore",[333,2320,571],{"class":518},[333,2322,2323],{"class":574}," :",[333,2325,1326],{"class":518},[333,2327,2313],{"class":510},[333,2329,1928],{"class":518},[333,2331,2332,2334,2336,2338,2340,2342,2344,2346,2348],{"class":335,"line":1334},[333,2333,2261],{"class":2260},[333,2335,1557],{"class":518},[333,2337,2294],{"class":510},[333,2339,2245],{"class":574},[333,2341,2271],{"class":578},[333,2343,531],{"class":518},[333,2345,582],{"class":518},[333,2347,2185],{"class":2184},[333,2349,2188],{"class":518},[318,2351,2352,2358,2359,629,2362,629,2365,2368,2369,2372,2373,725],{},[293,2353,2354,2357],{},[298,2355,2356],{},"#[AsListener]"," registers listeners",", and ",[298,2360,2361],{},"RequestEvent",[298,2363,2364],{},"ResponseEvent",[298,2366,2367],{},"CacheHitEvent"," and\n",[298,2370,2371],{},"CacheMissEvent"," are now dispatched. Listeners you annotated (or subscribed to these events) start\nrunning — check them. See ",[303,2374,2375],{"href":98},"Events",[318,2377,2378,2384,2386],{},[293,2379,2380,2381,725],{},"Listener classes that aren't container services run, and unbuildable ones fail at ",[298,2382,2383],{},"subscribe()",[298,2385,2383],{}," now autowires unregistered listener classes (they used to get a lazy reference that\nfailed at dispatch and was skipped silently). A listener whose dependencies can't be resolved now\nthrows when it is subscribed, at boot, instead of quietly never running.",[318,2388,2389,2395,2396,2399],{},[293,2390,2391,2394],{},[298,2392,2393],{},"QueryExecutedEvent"," listeners run outside debug mode too."," The event now fires whenever a\nlistener exists (and ",[298,2397,2398],{},"withPurpose()"," tags reach it). Make sure such listeners are cheap enough for\nproduction traffic.",[474,2401,2403],{"id":2402},"queue-scheduler-notifications","Queue, scheduler & notifications",[479,2405,2406,2619,2766,2813,2825,2844,2858,2870],{},[318,2407,2408,2418,2419,2422,2423,2426,2427,2613,2615,2616,725],{},[293,2409,2410,2411,2413,2414,2417],{},"Apps with their own ",[298,2412,416],{}," must add the ",[298,2415,2416],{},"scheduled_notifications"," job."," Your\n",[298,2420,2421],{},"schedule.jobs"," list replaces the framework's, so without it notifications sent with a ",[298,2424,2425],{},"schedule","\nare still never delivered:",[324,2428,2430],{"className":502,"code":2429,"language":368,"meta":329,"style":329},"// config/schedule.php → 'jobs'\n[\n    'name' => 'scheduled_notifications',\n    'schedule' => '* * * * *',\n    'handler_class' => 'Glueful\\\\Queue\\\\Jobs\\\\ScheduledNotificationsJob',\n    'parameters' => ['options' => ['limit' => 50]],\n    'description' => 'Deliver scheduled notifications that are due',\n    'enabled' => env('SCHEDULED_NOTIFICATIONS_ENABLED', true),\n],\n",[298,2431,2432,2437,2442,2463,2482,2518,2557,2577,2607],{"__ignoreMap":329},[333,2433,2434],{"class":335,"line":336},[333,2435,2436],{"class":377},"// config/schedule.php → 'jobs'\n",[333,2438,2439],{"class":335,"line":381},[333,2440,2441],{"class":518},"[\n",[333,2443,2444,2447,2449,2451,2454,2456,2458,2460],{"class":335,"line":556},[333,2445,2446],{"class":522},"    '",[333,2448,1515],{"class":343},[333,2450,523],{"class":522},[333,2452,2453],{"class":574}," =>",[333,2455,1075],{"class":522},[333,2457,2416],{"class":343},[333,2459,523],{"class":522},[333,2461,2462],{"class":518},",\n",[333,2464,2465,2467,2469,2471,2473,2475,2478,2480],{"class":335,"line":826},[333,2466,2446],{"class":522},[333,2468,2425],{"class":343},[333,2470,523],{"class":522},[333,2472,2453],{"class":574},[333,2474,1075],{"class":522},[333,2476,2477],{"class":343},"* * * * *",[333,2479,523],{"class":522},[333,2481,2462],{"class":518},[333,2483,2484,2486,2489,2491,2493,2495,2498,2501,2504,2506,2509,2511,2514,2516],{"class":335,"line":1158},[333,2485,2446],{"class":522},[333,2487,2488],{"class":343},"handler_class",[333,2490,523],{"class":522},[333,2492,2453],{"class":574},[333,2494,1075],{"class":522},[333,2496,2497],{"class":343},"Glueful",[333,2499,2500],{"class":2184},"\\\\",[333,2502,2503],{"class":343},"Queue",[333,2505,2500],{"class":2184},[333,2507,2508],{"class":343},"Jobs",[333,2510,2500],{"class":2184},[333,2512,2513],{"class":343},"ScheduledNotificationsJob",[333,2515,523],{"class":522},[333,2517,2462],{"class":518},[333,2519,2520,2522,2525,2527,2529,2531,2533,2536,2538,2540,2542,2544,2547,2549,2551,2554],{"class":335,"line":1334},[333,2521,2446],{"class":522},[333,2523,2524],{"class":343},"parameters",[333,2526,523],{"class":522},[333,2528,2453],{"class":574},[333,2530,1060],{"class":518},[333,2532,523],{"class":522},[333,2534,2535],{"class":343},"options",[333,2537,523],{"class":522},[333,2539,2453],{"class":574},[333,2541,1060],{"class":518},[333,2543,523],{"class":522},[333,2545,2546],{"class":343},"limit",[333,2548,523],{"class":522},[333,2550,2453],{"class":574},[333,2552,2553],{"class":1824}," 50",[333,2555,2556],{"class":518},"]],\n",[333,2558,2559,2561,2564,2566,2568,2570,2573,2575],{"class":335,"line":1340},[333,2560,2446],{"class":522},[333,2562,2563],{"class":343},"description",[333,2565,523],{"class":522},[333,2567,2453],{"class":574},[333,2569,1075],{"class":522},[333,2571,2572],{"class":343},"Deliver scheduled notifications that are due",[333,2574,523],{"class":522},[333,2576,2462],{"class":518},[333,2578,2579,2581,2584,2586,2588,2591,2593,2595,2598,2600,2602,2604],{"class":335,"line":1931},[333,2580,2446],{"class":522},[333,2582,2583],{"class":343},"enabled",[333,2585,523],{"class":522},[333,2587,2453],{"class":574},[333,2589,2590],{"class":567}," env",[333,2592,519],{"class":518},[333,2594,523],{"class":522},[333,2596,2597],{"class":343},"SCHEDULED_NOTIFICATIONS_ENABLED",[333,2599,523],{"class":522},[333,2601,1057],{"class":518},[333,2603,2097],{"class":2096},[333,2605,2606],{"class":518},"),\n",[333,2608,2610],{"class":335,"line":2609},9,[333,2611,2612],{"class":518},"],\n",[393,2614],{},"It is a no-op when ",[298,2617,2618],{},"NOTIFICATIONS_DATABASE_STORE=false",[318,2620,2621,2630,2631,2634,2635],{},[293,2622,2410,2623,2625,2626,2629],{},[298,2624,416],{}," must also add the ",[298,2627,2628],{},"queue_maintenance"," job"," to get\nautomatic failed-job pruning (past ",[298,2632,2633],{},"queue.failed.retention_days","):",[324,2636,2638],{"className":502,"code":2637,"language":368,"meta":329,"style":329},"// config/schedule.php → 'jobs'\n[\n    'name' => 'queue_maintenance',\n    'schedule' => '15 4 * * *',\n    'handler_class' => 'Glueful\\\\Queue\\\\Jobs\\\\QueueMaintenanceJob',\n    'description' => 'Prune failed jobs past queue.failed.retention_days and stale worker records',\n    'enabled' => env('QUEUE_MAINTENANCE_ENABLED', true),\n],\n",[298,2639,2640,2644,2648,2666,2685,2716,2735,2762],{"__ignoreMap":329},[333,2641,2642],{"class":335,"line":336},[333,2643,2436],{"class":377},[333,2645,2646],{"class":335,"line":381},[333,2647,2441],{"class":518},[333,2649,2650,2652,2654,2656,2658,2660,2662,2664],{"class":335,"line":556},[333,2651,2446],{"class":522},[333,2653,1515],{"class":343},[333,2655,523],{"class":522},[333,2657,2453],{"class":574},[333,2659,1075],{"class":522},[333,2661,2628],{"class":343},[333,2663,523],{"class":522},[333,2665,2462],{"class":518},[333,2667,2668,2670,2672,2674,2676,2678,2681,2683],{"class":335,"line":826},[333,2669,2446],{"class":522},[333,2671,2425],{"class":343},[333,2673,523],{"class":522},[333,2675,2453],{"class":574},[333,2677,1075],{"class":522},[333,2679,2680],{"class":343},"15 4 * * *",[333,2682,523],{"class":522},[333,2684,2462],{"class":518},[333,2686,2687,2689,2691,2693,2695,2697,2699,2701,2703,2705,2707,2709,2712,2714],{"class":335,"line":1158},[333,2688,2446],{"class":522},[333,2690,2488],{"class":343},[333,2692,523],{"class":522},[333,2694,2453],{"class":574},[333,2696,1075],{"class":522},[333,2698,2497],{"class":343},[333,2700,2500],{"class":2184},[333,2702,2503],{"class":343},[333,2704,2500],{"class":2184},[333,2706,2508],{"class":343},[333,2708,2500],{"class":2184},[333,2710,2711],{"class":343},"QueueMaintenanceJob",[333,2713,523],{"class":522},[333,2715,2462],{"class":518},[333,2717,2718,2720,2722,2724,2726,2728,2731,2733],{"class":335,"line":1334},[333,2719,2446],{"class":522},[333,2721,2563],{"class":343},[333,2723,523],{"class":522},[333,2725,2453],{"class":574},[333,2727,1075],{"class":522},[333,2729,2730],{"class":343},"Prune failed jobs past queue.failed.retention_days and stale worker records",[333,2732,523],{"class":522},[333,2734,2462],{"class":518},[333,2736,2737,2739,2741,2743,2745,2747,2749,2751,2754,2756,2758,2760],{"class":335,"line":1340},[333,2738,2446],{"class":522},[333,2740,2583],{"class":343},[333,2742,523],{"class":522},[333,2744,2453],{"class":574},[333,2746,2590],{"class":567},[333,2748,519],{"class":518},[333,2750,523],{"class":522},[333,2752,2753],{"class":343},"QUEUE_MAINTENANCE_ENABLED",[333,2755,523],{"class":522},[333,2757,1057],{"class":518},[333,2759,2097],{"class":2096},[333,2761,2606],{"class":518},[333,2763,2764],{"class":335,"line":1931},[333,2765,2612],{"class":518},[318,2767,2768,2774,2775,2462,2778,629,2781,629,2784,629,2787,629,2790,2793,2794,2796,2797,629,2800,629,2803,2462,2806,468,2809,2812],{},[293,2769,2770,2773],{},[298,2771,2772],{},"queue:scheduler add|remove|enable|disable|history"," are gone",", with their options (",[298,2776,2777],{},"--job-name",[298,2779,2780],{},"--cron",[298,2782,2783],{},"--command",[298,2785,2786],{},"--description",[298,2788,2789],{},"--timeout",[298,2791,2792],{},"--history","). They only printed \"not yet\nimplemented\". Manage jobs in ",[298,2795,416],{},"; the remaining actions are ",[298,2798,2799],{},"run",[298,2801,2802],{},"work",[298,2804,2805],{},"list",[298,2807,2808],{},"status",[298,2810,2811],{},"health",". Update any scripts that called the removed actions — they now fail.",[318,2814,2815,2824],{},[293,2816,2817,909,2820,2823],{},[298,2818,2819],{},"queue:scheduler --parallel",[298,2821,2822],{},"-p",") is gone."," It was accepted and ignored; remove it from scripts\nand supervisor configs.",[318,2826,2827,2835,2836,2839,2840,2843],{},[293,2828,1470,2829,717,2832],{},[298,2830,2831],{},"FailedJobProvider::setMaxRetries()",[298,2833,2834],{},"getMaxRetries()"," (deprecated since 1.86, never\nenforced). Delete the calls. The container's ",[298,2837,2838],{},"FailedJobProvider"," now honours ",[298,2841,2842],{},"queue.failed.*","\nconfig.",[318,2845,2846,2849,2850,2853,2854,2857],{},[293,2847,2848],{},"Persistent scheduled jobs no longer duplicate."," If a ",[298,2851,2852],{},"persistence => true"," job left duplicate\n",[298,2855,2856],{},"scheduled_jobs"," rows, the next scheduler load removes them.",[318,2859,2860,2865,2866,2869],{},[293,2861,2862,2864],{},[298,2863,2633],{}," is read"," for failed-job pruning (it was hardcoded to 30) unless\n",[298,2867,2868],{},"queue.maintenance.failed_jobs_cleanup_days"," is set.",[318,2871,2872,2882,2883,725],{},[293,2873,2874,2875,909,2878,2881],{},"Locks created without a TTL expire after ",[298,2876,2877],{},"lock.ttl",[298,2879,2880],{},"LOCK_TTL",", 300s)"," instead of never. Pass\nan explicit TTL for anything that must be held longer. See\n",[303,2884,93],{"href":94},[474,2886,2888],{"id":2887},"config-removals","Config removals",[289,2890,2891,2892,2894],{},"Delete these from your own config copies and ",[298,2893,876],{}," files — no code read them:",[2896,2897,2898,2911],"table",{},[2899,2900,2901],"thead",{},[2902,2903,2904,2908],"tr",{},[2905,2906,2907],"th",{},"Removed",[2905,2909,2910],{},"Notes",[2912,2913,2914,2934,2950,2963,2976,2994],"tbody",{},[2902,2915,2916,2931],{},[2917,2918,2919,629,2922,629,2925,629,2928],"td",{},[298,2920,2921],{},"cache.lock_ttl",[298,2923,2924],{},"cache.enable_tags",[298,2926,2927],{},"cache.distributed",[298,2929,2930],{},"cache.tags_store",[2917,2932,2933],{},"The file driver now supports tags natively.",[2902,2935,2936,2944],{},[2917,2937,2938,909,2941,531],{},[298,2939,2940],{},"lock.retry.times",[298,2942,2943],{},"LOCK_RETRY_TIMES",[2917,2945,2946,2949],{},[298,2947,2948],{},"lock.retry.max_wait"," bounds the wait.",[2902,2951,2952,2960],{},[2917,2953,2954,909,2957,531],{},[298,2955,2956],{},"capabilities.metrics",[298,2958,2959],{},"METRICS_DATABASE_STORE",[2917,2961,2962],{},"Core migrations install unconditionally since 1.79.",[2902,2964,2965,2973],{},[2917,2966,2967,909,2970,531],{},[298,2968,2969],{},"capabilities.scheduler",[298,2971,2972],{},"SCHEDULE_DATABASE_STORE",[2917,2974,2975],{},"Same.",[2902,2977,2978,2991],{},[2917,2979,2980,909,2983,2986,2987,2990],{},[298,2981,2982],{},"observability.tracing.driver",[298,2984,2985],{},"TRACING_DRIVER",") and the empty ",[298,2988,2989],{},"drivers"," block",[2917,2992,2993],{},"Only the no-op tracer exists.",[2902,2995,2996,3010],{},[2917,2997,2998,717,3001,909,3004,717,3007,531],{},[298,2999,3000],{},"observability.tracing.service_name",[298,3002,3003],{},"service_version",[298,3005,3006],{},"TRACING_SERVICE_NAME",[298,3008,3009],{},"TRACING_SERVICE_VERSION",[2917,3011,3012],{},"Nothing read them.",[474,3014,125],{"id":3015},"webhooks",[479,3017,3018,3044,3073,3116,3140,3223],{},[318,3019,3020,3025,3026,3028,3029,3032,3033,717,3036,3039,3040,3043],{},[293,3021,459,3022],{},[298,3023,3024],{},"php glueful migrate:run"," for the new ",[298,3027,3015],{}," migration leaf (",[298,3030,3031],{},"glueful/framework:webhooks","),\nwhich owns ",[298,3034,3035],{},"webhook_subscriptions",[298,3037,3038],{},"webhook_deliveries",". The runtime DDL in ",[298,3041,3042],{},"WebhookDispatcher"," is\ngone; databases whose tables the old lazy path created migrate cleanly.",[318,3045,3046,3052,3060,3062,3063,3066,3067,717,3070,725],{},[293,3047,3048,3051],{},[298,3049,3050],{},"WebhookDeliveryService"," uses the subscription signing scheme.",[324,3053,3058],{"className":3054,"code":3056,"language":3057,"meta":329},[3055],"language-text","# Before\nX-Glueful-Signature: sha256=\u003Chmac of body>\nX-Glueful-Timestamp: \u003Cts>\n\n# After\nX-Webhook-Signature: t=\u003Cts>,v1=\u003Chmac of \"\u003Cts>.\u003Cbody>\">\nX-Webhook-Timestamp: \u003Cts>\n","text",[298,3059,3056],{"__ignoreMap":329},[393,3061],{},"Update receivers to verify with ",[298,3064,3065],{},"Webhook::verify($payload, $signature, $secret)",". The header name and\nalgorithm follow ",[298,3068,3069],{},"api.webhooks.signature_header",[298,3071,3072],{},"signature_algorithm",[318,3074,3075,909,3080,629,3083,3086,3087,629,3090,629,3093,468,3096,3099,3100,3103,3104,3107,3108,3111,3112,3115],{},[293,3076,3077,3078],{},"New management routes under ",[298,3079,694],{},[298,3081,3082],{},"subscriptions",[298,3084,3085],{},"deliveries",", and their\n",[298,3088,3089],{},"rotate-secret",[298,3091,3092],{},"test",[298,3094,3095],{},"stats",[298,3097,3098],{},"retry"," actions) require ",[298,3101,3102],{},"auth"," plus ",[298,3105,3106],{},"system.config.view"," for\nreads or ",[298,3109,3110],{},"system.config.edit"," for writes. Remove any app routes that mounted ",[298,3113,3114],{},"WebhookController"," on\nthe same paths.",[318,3117,3118,3129,3132,3133,468,3136,3139],{},[293,3119,3120,3121,3124,3125,3128],{},"Events marked ",[298,3122,3123],{},"#[Webhookable]"," or using ",[298,3126,3127],{},"DispatchesWebhooks"," now send webhooks.",[298,3130,3131],{},"WebhookEventListener"," is registered at boot (when ",[298,3134,3135],{},"events.enabled",[298,3137,3138],{},"api.webhooks.enabled"," are\non). If you registered it yourself, remove that registration or each event dispatches twice.",[318,3141,3142,2358,3151,2462,3154,468,3157,3160,3161],{},[293,3143,3144,3146,3147,3150],{},[298,3145,3042],{},"'s constructor drops its unused ",[298,3148,3149],{},"$connection"," argument",[298,3152,3153],{},"getSchema()",[298,3155,3156],{},"areTablesEnsured()",[298,3158,3159],{},"resetTablesEnsured()"," are removed:",[324,3162,3164],{"className":502,"code":3163,"language":368,"meta":329,"style":329},"// Before\nnew WebhookDispatcher($connection, $payloadBuilder, $context);\n\n// After\nnew WebhookDispatcher($payloadBuilder, $context);\n",[298,3165,3166,3170,3197,3201,3205],{"__ignoreMap":329},[333,3167,3168],{"class":335,"line":336},[333,3169,1484],{"class":377},[333,3171,3172,3175,3178,3180,3182,3184,3186,3189,3191,3193,3195],{"class":335,"line":381},[333,3173,3174],{"class":1069},"new",[333,3176,3177],{"class":578}," WebhookDispatcher",[333,3179,1290],{"class":518},[333,3181,1367],{"class":510},[333,3183,1057],{"class":518},[333,3185,1326],{"class":518},[333,3187,3188],{"class":510},"payloadBuilder",[333,3190,1057],{"class":518},[333,3192,1326],{"class":518},[333,3194,1329],{"class":510},[333,3196,1296],{"class":518},[333,3198,3199],{"class":335,"line":556},[333,3200,1113],{"emptyLinePlaceholder":1112},[333,3202,3203],{"class":335,"line":826},[333,3204,1305],{"class":377},[333,3206,3207,3209,3211,3213,3215,3217,3219,3221],{"class":335,"line":1158},[333,3208,3174],{"class":1069},[333,3210,3177],{"class":578},[333,3212,1290],{"class":518},[333,3214,3188],{"class":510},[333,3216,1057],{"class":518},[333,3218,1326],{"class":518},[333,3220,1329],{"class":510},[333,3222,1296],{"class":518},[318,3224,3225,3236],{},[293,3226,1457,3227,602,3229,717,3232,3235],{},[298,3228,3123],{},[298,3230,3231],{},"only",[298,3233,3234],{},"except"," arguments"," — they never had an effect.",[289,3238,3239,3240,725],{},"See ",[303,3241,125],{"href":126},[474,3243,3245],{"id":3244},"extensions-scaffolding","Extensions & scaffolding",[479,3247,3248,3457,3477,3493],{},[318,3249,3250,3256,3257,3260,3261,3264,3265,3268,3269,3272,3273,3276,3277,3280,3281,3438,3440,3441,3444,3445,3448,3449,3451,3452,3454,3455,725],{},[293,3251,3252,3255],{},[298,3253,3254],{},"create:extension"," scaffolds an enable-able package."," It now writes an ",[298,3258,3259],{},"extra.glueful.migrations","\ndeclaration (a default ",[298,3262,3263],{},"on_enable"," lane at the flat ",[298,3266,3267],{},"migrations/"," folder) and no longer creates\n",[298,3270,3271],{},"database/migrations"," or calls ",[298,3274,3275],{},"loadMigrationsFrom()",". An extension scaffolded earlier needs the\ndeclaration added by hand before ",[298,3278,3279],{},"extensions:enable"," will run its migrations:",[324,3282,3286],{"className":3283,"code":3284,"language":3285,"meta":329,"style":329},"language-json shiki shiki-themes material-theme-lighter github-light github-dark monokai","{\n  \"extra\": {\n    \"glueful\": {\n      \"migrations\": [\n        { \"id\": \"default\", \"path\": \"migrations\", \"priority\": \"default\", \"mode\": \"on_enable\" }\n      ]\n    }\n  }\n}\n","json",[298,3287,3288,3293,3310,3324,3340,3418,3423,3428,3433],{"__ignoreMap":329},[333,3289,3290],{"class":335,"line":336},[333,3291,3292],{"class":518},"{\n",[333,3294,3295,3299,3303,3306,3308],{"class":335,"line":381},[333,3296,3298],{"class":3297},"saDeg","  \"",[333,3300,3302],{"class":3301},"sEff5","extra",[333,3304,3305],{"class":3297},"\"",[333,3307,575],{"class":518},[333,3309,1574],{"class":518},[333,3311,3312,3315,3318,3320,3322],{"class":335,"line":556},[333,3313,3314],{"class":3297},"    \"",[333,3316,3317],{"class":578},"glueful",[333,3319,3305],{"class":3297},[333,3321,575],{"class":518},[333,3323,1574],{"class":518},[333,3325,3326,3329,3333,3335,3337],{"class":335,"line":826},[333,3327,3328],{"class":3297},"      \"",[333,3330,3332],{"class":3331},"sTC9v","migrations",[333,3334,3305],{"class":3297},[333,3336,575],{"class":518},[333,3338,3339],{"class":518}," [\n",[333,3341,3342,3345,3348,3352,3354,3356,3359,3363,3365,3367,3369,3372,3374,3376,3378,3380,3382,3384,3386,3389,3391,3393,3395,3397,3399,3401,3403,3406,3408,3410,3412,3414,3416],{"class":335,"line":1158},[333,3343,3344],{"class":518},"        {",[333,3346,3347],{"class":3297}," \"",[333,3349,3351],{"class":3350},"sogGa","id",[333,3353,3305],{"class":3297},[333,3355,575],{"class":518},[333,3357,3347],{"class":3358},"sh1VR",[333,3360,3362],{"class":3361},"sINAO","default",[333,3364,3305],{"class":3358},[333,3366,1057],{"class":518},[333,3368,3347],{"class":3297},[333,3370,3371],{"class":3350},"path",[333,3373,3305],{"class":3297},[333,3375,575],{"class":518},[333,3377,3347],{"class":3358},[333,3379,3332],{"class":3361},[333,3381,3305],{"class":3358},[333,3383,1057],{"class":518},[333,3385,3347],{"class":3297},[333,3387,3388],{"class":3350},"priority",[333,3390,3305],{"class":3297},[333,3392,575],{"class":518},[333,3394,3347],{"class":3358},[333,3396,3362],{"class":3361},[333,3398,3305],{"class":3358},[333,3400,1057],{"class":518},[333,3402,3347],{"class":3297},[333,3404,3405],{"class":3350},"mode",[333,3407,3305],{"class":3297},[333,3409,575],{"class":518},[333,3411,3347],{"class":3358},[333,3413,3263],{"class":3361},[333,3415,3305],{"class":3358},[333,3417,2188],{"class":518},[333,3419,3420],{"class":335,"line":1334},[333,3421,3422],{"class":518},"      ]\n",[333,3424,3425],{"class":335,"line":1340},[333,3426,3427],{"class":518},"    }\n",[333,3429,3430],{"class":335,"line":1931},[333,3431,3432],{"class":518},"  }\n",[333,3434,3435],{"class":335,"line":2609},[333,3436,3437],{"class":518},"}\n",[393,3439],{},"A package with no schema declares ",[298,3442,3443],{},"\"migrations\": \"none\""," instead. Move files from\n",[298,3446,3447],{},"database/migrations/"," to ",[298,3450,3267],{}," and drop the provider's ",[298,3453,3275],{}," call. See\n",[303,3456,268],{"href":241},[318,3458,3459,3472,3473,3476],{},[293,3460,3461,3464,3465,909,3468,3471],{},[298,3462,3463],{},"scaffold:controller"," writes to ",[298,3466,3467],{},"app/Controllers",[298,3469,3470],{},"App\\Controllers",")."," It used to write\n",[298,3474,3475],{},"api/Controllers","; controllers generated there need moving if your autoloader doesn't cover that\nfolder.",[318,3478,3479,3488,3489,3492],{},[293,3480,3481,3484,3485],{},[298,3482,3483],{},"system:check --production"," validates ",[298,3486,3487],{},"JWT_KEY"," (it checked ",[298,3490,3491],{},"JWT_SECRET",", which nothing reads).\nA missing signing key is now reported.",[318,3494,3495,3501,3502,3505],{},[293,3496,3497,3500],{},[298,3498,3499],{},"DB_SQLITE_DATABASE=:memory:"," is a real in-memory database."," It used to create a file named\n",[298,3503,3504],{},":memory:","; delete that file if one exists.",[310,3507,3509],{"id":3508},"_180-190","1.80 – 1.90",[474,3511,3513],{"id":3512},"_190","1.90",[479,3515,3516],{},[318,3517,3518,3524,3525,3528,3529,3532,3533,3536,3537,3540,3541,3544],{},[293,3519,3520,3521,725],{},"The file cache defaults to ",[298,3522,3523],{},"\u003Capp>/storage/cache"," An app whose ",[298,3526,3527],{},"config/cache.php"," sets\n",[298,3530,3531],{},"cache.stores.file.path"," (the api-skeleton does) is unaffected. Otherwise make sure the web server\ncan write ",[298,3534,3535],{},"storage/cache",", or set ",[298,3538,3539],{},"CACHE_FILE_PATH",". Old entries under\n",[298,3542,3543],{},"vendor/glueful/framework/storage"," can be deleted.",[474,3546,3548],{"id":3547},"_187","1.87",[479,3550,3551,3571,3587,3597,3609],{},[318,3552,3553,3563,3564,717,3567,3570],{},[293,3554,3555,3556,468,3559,3562],{},"Set ",[298,3557,3558],{},"MAIL_HOST",[298,3560,3561],{},"MAIL_FROM"," if your app sends mail."," They no longer fall back to\n",[298,3565,3566],{},"smtp.mailtrap.io",[298,3568,3569],{},"noreply@glueful.com","; with either unset, the email channel reports itself\nunavailable.",[318,3572,3573,2358,3576,717,3579,3582,3583,3586],{},[293,3574,3575],{},"PostgreSQL searches are case-insensitive",[298,3577,3578],{},"%",[298,3580,3581],{},"_"," sent by a client match literally. A\ncustom ",[298,3584,3585],{},"WhereClauseInterface"," implementation must add the four text-match methods.",[318,3588,3589,3592,3593,3596],{},[293,3590,3591],{},"Uploaded images are stripped of metadata"," by default. Set ",[298,3594,3595],{},"UPLOADS_STRIP_EXIF=false"," to keep it.",[318,3598,3599,3608],{},[293,3600,3601,3602,3605,3606],{},"Add ",[298,3603,3604],{},"blob_purge"," to your ",[298,3607,416],{}," to purge deleted uploads.",[318,3610,3611,417,3614,3616,3617,725],{},[293,3612,3613],{},"Deprecated:",[298,3615,1473],{}," (writes the term into raw SQL unescaped) — use\n",[298,3618,3619],{},"QueryBuilder::whereContains()",[474,3621,3623],{"id":3622},"_186","1.86",[479,3625,3626,3645,3698,3711,3726,3739,3745],{},[318,3627,3628,3631,3632,3635,3636,3639,3640,3642,3643,3471],{},[293,3629,3630],{},"Config lists are replaced, not merged by position"," (see ",[303,3633,313],{"href":3634},"#how-to-upgrade","). If you\nrelied on framework entries past the end of your own list, add them. Add ",[298,3637,3638],{},"webhook_cleanup"," to your\n",[298,3641,420],{}," to prune webhook delivery records (see ",[303,3644,125],{"href":126},[318,3646,3647,3650,3651,468,3654,3657,3658,3661,3662,3665,3666,629,3669,468,3672,3675,3676,3679,3680,3683,3684,629,3687,629,3690,629,3693,1712,3696,725],{},[293,3648,3649],{},"Delete config nothing read:"," the ",[298,3652,3653],{},"sync",[298,3655,3656],{},"null"," queue connections; the schedule's ",[298,3659,3660],{},"settings","\nblock, ",[298,3663,3664],{},"queue_mapping",", and each job's ",[298,3667,3668],{},"queue",[298,3670,3671],{},"timeout",[298,3673,3674],{},"retry_attempts","; ",[298,3677,3678],{},"app.force_https",";\nthe ",[298,3681,3682],{},"security.headers"," block; and ",[298,3685,3686],{},"HSTS_HEADER",[298,3688,3689],{},"FORCE_HTTPS",[298,3691,3692],{},"SCHEDULE_QUEUE_*",[298,3694,3695],{},"MAIL_BCC",[298,3697,876],{},[318,3699,3700,3706,3707,3710],{},[293,3701,3702,3703],{},"List your permission middleware in ",[298,3704,3705],{},"permissions.enforcing_middleware"," if you enforce permissions\nin route middleware, so ",[298,3708,3709],{},"permissions:diff"," counts them.",[318,3712,3713,3719,3720,3722,3723,3725],{},[293,3714,3715,3718],{},[298,3716,3717],{},"security:check"," can now fail."," Fix what it reports (world-readable ",[298,3721,876],{},", short signing secret,\nCORS ",[298,3724,779],{}," with credentials …).",[318,3727,3728,3731,3732,1403,3735,3738],{},[293,3729,3730],{},"Scheduled backups that make no dump fail their job."," PostgreSQL/MySQL sites need ",[298,3733,3734],{},"pg_dump",[298,3736,3737],{},"mysqldump"," on the scheduler host.",[318,3740,3741,3744],{},[293,3742,3743],{},"Webhook \"Send test event\" refuses local and private addresses."," Test against a public endpoint\nor a tunnel.",[318,3746,3747,417,3749,717,3751,3753],{},[293,3748,3613],{},[298,3750,2831],{},[298,3752,2834],{}," — nothing enforces the\nvalue.",[474,3755,3757],{"id":3756},"_185","1.85",[479,3759,3760],{},[318,3761,3762,3765,3766,3769,3770,3773,3774,3777],{},[293,3763,3764],{},"Bootstrap environment:"," if your skeleton bootstrap passes ",[298,3767,3768],{},"$_ENV['APP_ENV'] ?? 'development'"," to\n",[298,3771,3772],{},"withEnvironment()",", pass ",[298,3775,3776],{},"env('APP_ENV', 'development')"," instead (1.85.3).",[474,3779,3781],{"id":3780},"_184","1.84",[479,3783,3784],{},[318,3785,3786,3795,3796,3799,3800,3803,3804,575,3807,3825,3239,3827,725],{},[293,3787,3788,3789,3448,3792,725],{},"The API reference moved from ",[298,3790,3791],{},"/docs",[298,3793,3794],{},"/api-docs"," Update bookmarks and tooling, or set\n",[298,3797,3798],{},"API_DOCS_PATH=/docs"," to keep the old path. Regenerate the UI page so it loads the spec from the\nconfigured path, and mirror ",[298,3801,3802],{},"API_DOCS_PATH"," into ",[298,3805,3806],{},".env.example",[324,3808,3810],{"className":326,"code":3809,"language":328,"meta":329,"style":329},"php glueful generate:openapi --ui\n",[298,3811,3812],{"__ignoreMap":329},[333,3813,3814,3816,3818,3821],{"class":335,"line":336},[333,3815,368],{"class":339},[333,3817,371],{"class":343},[333,3819,3820],{"class":343}," generate:openapi",[333,3822,3824],{"class":3823},"sFhLe"," --ui\n",[393,3826],{},[303,3828,1231],{"href":150},[474,3830,3832],{"id":3831},"_183","1.83",[479,3834,3835],{},[318,3836,3837,3844,3845,3848,3849,3852],{},[293,3838,3839,3840,3843],{},"A non-empty ",[298,3841,3842],{},"CSP_HEADER"," is now sent"," as ",[298,3846,3847],{},"Content-Security-Policy"," on every response without a\npolicy of its own. Review it first, or set ",[298,3850,3851],{},"CSP_REPORT_ONLY=true"," and watch the browser console.",[474,3854,3856],{"id":3855},"_182","1.82",[479,3858,3859],{},[318,3860,3861,3864,3865,3868,3869,3872,3873,3887,3889,3890,3893],{},[293,3862,3863],{},"Production runs the compiled container."," Its artifact lives in\n",[298,3866,3867],{},"\u003Capp>/storage/cache/container/"," (delete the directory to force a fresh compile);\n",[298,3870,3871],{},"/tmp/glueful_compiled_container.php"," is no longer read. Recompile a precompiled container built\nbefore 1.82 once:",[324,3874,3876],{"className":326,"code":3875,"language":328,"meta":329,"style":329},"php glueful di:container:compile\n",[298,3877,3878],{"__ignoreMap":329},[333,3879,3880,3882,3884],{"class":335,"line":336},[333,3881,368],{"class":339},[333,3883,371],{"class":343},[333,3885,3886],{"class":343}," di:container:compile\n",[393,3888],{},"If a service misbehaves only in production, ",[298,3891,3892],{},"APP_DEBUG=true"," restores the runtime container for\ncomparison.",[474,3895,3897],{"id":3896},"_181","1.81",[479,3899,3900],{},[318,3901,3902,3908,3909,3912,3913,3916],{},[293,3903,3904,3907],{},[298,3905,3906],{},"/tmp/boot_profile.log"," is no longer written."," If tooling tailed it, set\n",[298,3910,3911],{},"BOOT_PROFILE_LOG=/tmp/boot_profile.log"," (or ",[298,3914,3915],{},"BOOT_PROFILE_LOG=true"," for a per-user temp file).",[474,3918,3920],{"id":3919},"_180","1.80",[479,3922,3923,3941],{},[318,3924,3925,3928,3929,3931,3932,3934,3935,1414,3937,3940],{},[293,3926,3927],{},"Every installed Glueful package must declare its schema."," A package without an\n",[298,3930,3259],{}," manifest (descriptors, or ",[298,3933,3443],{},") can no longer register\nmigration paths — ",[298,3936,3275],{},[298,3938,3939],{},"UndeclaredSchemaException",". App-local providers\nwithout a package keep the legacy append.",[318,3942,3943,3949,3950,3953],{},[293,3944,3945,3948],{},[298,3946,3947],{},"migrate:normalize-receipts"," and legacy-alias receipts are removed."," Upgrades from beta-era\nledgers are not supported: re-provision, or rewrite the ledger ",[298,3951,3952],{},"source"," values by hand first.",[310,3955,3957],{"id":3956},"_170-179","1.70 – 1.79",[474,3959,3961],{"id":3960},"_179","1.79",[479,3963,3964,3982,4010,4017],{},[318,3965,3966,3973,3974,3977,3978,3981],{},[293,3967,459,3968,3970,3971,725],{},[298,3969,3024],{}," once after upgrading, before any ",[298,3972,3279],{}," The\noperation ledger (",[298,3975,3976],{},"extension_operations",") is a new core migration; the executor refuses\n(",[298,3979,3980],{},"SchemaNotBootstrappedException",") until it exists.",[318,3983,3984,909,3987,629,3989,629,3992,629,3995,2462,3998,629,4001,629,4003,629,4006,4009],{},[293,3985,3986],{},"Core migrations install unconditionally",[298,3988,3102],{},[298,3990,3991],{},"extensions",[298,3993,3994],{},"locks",[298,3996,3997],{},"metrics",[298,3999,4000],{},"notifications",[298,4002,3668],{},[298,4004,4005],{},"scheduler",[298,4007,4008],{},"uploads","); capability config no longer gates schema.",[318,4011,4012],{},[293,4013,4014,4016],{},[298,4015,3275],{}," on a declared package must match its manifest exactly.",[318,4018,4019,4024],{},[293,4020,4021,4023],{},[298,4022,3279],{}," refuses packages without a manifest declaration"," (see 1.80).",[474,4026,4028],{"id":4027},"_176","1.76",[479,4030,4031,4040,4070,4083,4096,4116],{},[318,4032,4033,4036,4037,4039],{},[293,4034,4035],{},"SQLite schema changes apply for real."," Migrations that silently did nothing on SQLite now take\neffect or throw ",[298,4038,1417],{}," — audit SQLite migrations that modify or drop\ncolumns or foreign keys.",[318,4041,4042,417,4045,4048,4049,717,4052,1403,4055,717,4058,4061,4062,4065,4066,4069],{},[293,4043,4044],{},"No more false successes:",[298,4046,4047],{},"dropPrimary()",", table-comment changes, and ",[298,4050,4051],{},"engine()",[298,4053,4054],{},"charset()",[298,4056,4057],{},"collation()",[298,4059,4060],{},"->primary()"," inside an ",[298,4063,4064],{},"alterTable()"," callback now fail explicitly. A modified\ncolumn's ",[298,4067,4068],{},"$collation"," fails on SQLite — gate it by driver.",[318,4071,4072,4075,4076,629,4079,4082],{},[293,4073,4074],{},"SQLite rebuilds"," can't run on tables with bare SQL-keyword column names (",[298,4077,4078],{},"key",[298,4080,4081],{},"order",", …) or\nwhile you hold an unconsumed read cursor on the same connection — close cursors before altering.",[318,4084,4085,4091,4092,4095],{},[293,4086,4087,4088],{},"Commit-phase connection loss throws ",[298,4089,4090],{},"CommitOutcomeUnknownException"," (was\n",[298,4093,4094],{},"ConnectionLostException","). Audit handlers that retried on the old type.",[318,4097,4098,4101,4102,717,4105,4108,4109,3675,4112,4115],{},[293,4099,4100],{},"Transaction retries:"," tune ",[298,4103,4104],{},"Connection::transaction()",[298,4106,4107],{},"db($context)->transaction()"," with\n",[298,4110,4111],{},"DB_RETRY_MAX_ATTEMPTS",[298,4113,4114],{},"TransactionManager::setMaxRetries()"," no longer affects that path. Replay\ncallbacks must build queries inside the callback from the supplied connection.",[318,4117,4118,417,4121,4124,4125,2368,4128,3675,4131,4124,4134,725],{},[293,4119,4120],{},"Interface implementors:",[298,4122,4123],{},"SchemaBuilderInterface"," gained ",[298,4126,4127],{},"executeSqliteRebuild()",[298,4129,4130],{},"executeSqliteNativeAlteration()",[298,4132,4133],{},"TableBuilderInterface",[298,4135,4136],{},"rename()",[474,4138,4140],{"id":4139},"_175","1.75",[479,4142,4143,4152,4170],{},[318,4144,4145,417,4147,4124,4149,725],{},[293,4146,4120],{},[298,4148,3585],{},[298,4150,4151],{},"orWhereRaw()",[318,4153,4154,717,4157,4160,4161,3675,4163,1414,4166,4169],{},[298,4155,4156],{},"Model::toJson()",[298,4158,4159],{},"Collection::toJson()"," throw on an unencodable payload instead of returning\n",[298,4162,1195],{},[298,4164,4165],{},"FileUploader::calculateChecksum()",[298,4167,4168],{},"UploadException"," on an unreadable file.",[318,4171,4172,4173,4176,4177,4180],{},"An empty ",[298,4174,4175],{},"keyPrefix"," now gets ",[298,4178,4179],{},"QueryCacheService","'s default prefix — expect one round of query-cache\nmisses.",[474,4182,4184],{"id":4183},"_174","1.74",[479,4186,4187],{},[318,4188,4189,4192],{},[293,4190,4191],{},"Usernames up to 255 characters validate"," (was 30). If you depend on the old limit, enforce it at\nyour own input boundary.",[474,4194,4196],{"id":4195},"_173","1.73",[479,4198,4199],{},[318,4200,4201,4204,4205,4208],{},[293,4202,4203],{},"CSRF tokens issued to authenticated callers before 1.73 stop validating."," Affected clients get\n",[298,4206,4207],{},"403"," and must fetch a new token or reload. No action for unauthenticated forms.",[474,4210,4212],{"id":4211},"_171","1.71",[479,4214,4215],{},[318,4216,4217,4220,4221,725],{},[293,4218,4219],{},"API-key rotation no longer extends a predecessor's expiry"," — it takes the earlier of the two. To\nlengthen a key's life, issue a new key. See ",[303,4222,4223],{"href":78},"API Keys",[474,4225,4227],{"id":4226},"_170","1.70",[479,4229,4230],{},[318,4231,4232,4238,4239,4242],{},[293,4233,4234,4235,725],{},"SQLite/PostgreSQL databases migrated before 1.70 are missing plain indexes declared inline in\n",[298,4236,4237],{},"createTable()"," Re-run the relevant ",[298,4240,4241],{},"CREATE INDEX"," statements, or re-migrate dev databases.",[310,4244,4246],{"id":4245},"_152-169","1.52 – 1.69",[474,4248,4250],{"id":4249},"_166","1.66",[479,4252,4253,4284],{},[318,4254,4255,417,4258,4261,4262,4265,4266,4269,4270,4273,4274,1403,4277,4280,4281,725],{},[293,4256,4257],{},"Extension installer API (1.66.1):",[298,4259,4260],{},"POST /extensions/install"," returns the final result\n(",[298,4263,4264],{},"{ status: 'installed' | 'failed', … }","); the ",[298,4267,4268],{},"GET /extensions/install/{jobId}"," poll endpoint and\n",[298,4271,4272],{},"EXTENSIONS_INSTALL_AUTO_ENABLE"," are removed. Set ",[298,4275,4276],{},"EXTENSIONS_INSTALL_PHP_BINARY",[298,4278,4279],{},"COMPOSER_BINARY"," if the web SAPI can't find a CLI php or composer, then run\n",[298,4282,4283],{},"php glueful cache:clear",[318,4285,4286,4289,4290,468,4293,4296],{},[293,4287,4288],{},"The installer is off in production"," unless ",[298,4291,4292],{},"EXTENSIONS_INSTALL_ENABLED=true",[298,4294,4295],{},"vendor/"," is\nwritable by the web user.",[474,4298,4300],{"id":4299},"_165","1.65",[479,4302,4303],{},[318,4304,4305,4311,4312,725],{},[293,4306,4307,4310],{},[298,4308,4309],{},"->middleware('require_scope:…')"," is enforced fail-closed."," Requests lacking the scope now get\n",[298,4313,4207],{},[474,4315,4317],{"id":4316},"_161","1.61",[479,4319,4320],{},[318,4321,4322],{},[293,4323,4324,4325,468,4328,725],{},"Remove ",[298,4326,4327],{},"documentation.paths.route_definitions",[298,4329,4330],{},"documentation.paths.extension_definitions",[474,4332,4334],{"id":4333},"_160","1.60",[479,4336,4337],{},[318,4338,4339,4345,4346,4349,4350,4353,4354,4356,4357,725],{},[293,4340,4341,4344],{},[298,4342,4343],{},"php glueful install"," is interactive."," Non-interactive callers (CI, ",[298,4347,4348],{},"post-create-project-cmd",")\nmust pass ",[298,4351,4352],{},"--quiet"," (use the existing ",[298,4355,876],{},") or ",[298,4358,4359],{},"--skip-database",[474,4361,4363],{"id":4362},"_159","1.59",[479,4365,4366],{},[318,4367,4368,4374,4375,629,4378,2462,4381,4384,4385,575,4388],{},[293,4369,4370,4373],{},[298,4371,4372],{},"ServiceProvider::mountStatic()"," is removed"," (with ",[298,4376,4377],{},"SpaManager",[298,4379,4380],{},"StaticFileDetector",[298,4382,4383],{},"SpaProvider","). Use ",[298,4386,4387],{},"serveFrontend()",[324,4389,4391],{"className":502,"code":4390,"language":368,"meta":329,"style":329},"// Before — served at /extensions/foo\n$this->mountStatic('foo', __DIR__ . '/dist');\n\n// After — any literal path, with an index.html deep-link fallback\n$this->serveFrontend('/foo', __DIR__ . '/dist');\n// plain static bundle that should 404 on a miss:\n$this->serveFrontend('/foo', __DIR__ . '/dist', ['spaFallback' => false]);\n",[298,4392,4393,4398,4435,4439,4444,4476,4481],{"__ignoreMap":329},[333,4394,4395],{"class":335,"line":336},[333,4396,4397],{"class":377},"// Before — served at /extensions/foo\n",[333,4399,4400,4404,4406,4409,4411,4413,4416,4418,4420,4423,4426,4428,4431,4433],{"class":335,"line":381},[333,4401,4403],{"class":4402},"sSBr1","$this",[333,4405,1042],{"class":574},[333,4407,4408],{"class":567},"mountStatic",[333,4410,519],{"class":518},[333,4412,523],{"class":522},[333,4414,4415],{"class":343},"foo",[333,4417,523],{"class":522},[333,4419,1057],{"class":518},[333,4421,4422],{"class":2096}," __DIR__",[333,4424,4425],{"class":574}," .",[333,4427,1075],{"class":522},[333,4429,4430],{"class":343},"/dist",[333,4432,523],{"class":522},[333,4434,1296],{"class":518},[333,4436,4437],{"class":335,"line":556},[333,4438,1113],{"emptyLinePlaceholder":1112},[333,4440,4441],{"class":335,"line":826},[333,4442,4443],{"class":377},"// After — any literal path, with an index.html deep-link fallback\n",[333,4445,4446,4448,4450,4453,4455,4457,4460,4462,4464,4466,4468,4470,4472,4474],{"class":335,"line":1158},[333,4447,4403],{"class":4402},[333,4449,1042],{"class":574},[333,4451,4452],{"class":567},"serveFrontend",[333,4454,519],{"class":518},[333,4456,523],{"class":522},[333,4458,4459],{"class":343},"/foo",[333,4461,523],{"class":522},[333,4463,1057],{"class":518},[333,4465,4422],{"class":2096},[333,4467,4425],{"class":574},[333,4469,1075],{"class":522},[333,4471,4430],{"class":343},[333,4473,523],{"class":522},[333,4475,1296],{"class":518},[333,4477,4478],{"class":335,"line":1334},[333,4479,4480],{"class":377},"// plain static bundle that should 404 on a miss:\n",[333,4482,4483,4485,4487,4489,4491,4493,4495,4497,4499,4501,4503,4505,4507,4509,4511,4513,4515,4518,4520,4522,4525],{"class":335,"line":1340},[333,4484,4403],{"class":4402},[333,4486,1042],{"class":574},[333,4488,4452],{"class":567},[333,4490,519],{"class":518},[333,4492,523],{"class":522},[333,4494,4459],{"class":343},[333,4496,523],{"class":522},[333,4498,1057],{"class":518},[333,4500,4422],{"class":2096},[333,4502,4425],{"class":574},[333,4504,1075],{"class":522},[333,4506,4430],{"class":343},[333,4508,523],{"class":522},[333,4510,1057],{"class":518},[333,4512,1060],{"class":518},[333,4514,523],{"class":522},[333,4516,4517],{"class":343},"spaFallback",[333,4519,523],{"class":522},[333,4521,2453],{"class":574},[333,4523,4524],{"class":2096}," false",[333,4526,1529],{"class":518},[474,4528,4530],{"id":4529},"_157","1.57",[479,4532,4533],{},[318,4534,4535,4545,4546,717,4549,4552,4553,629,4556,629,4559,629,4562,4565,4566,4569,4570,629,4573,629,4576,468,4579,4582,4583,468,4585,725],{},[293,4536,4537,4538,4540,4541,4544],{},"The docblock (",[298,4539,2054],{},") OpenAPI generator is removed; ",[298,4542,4543],{},"reflect"," is the only generator."," Remove\n",[298,4547,4548],{},"API_DOCS_GENERATOR",[298,4550,4551],{},"documentation.generator",". ",[298,4554,4555],{},"@route",[298,4557,4558],{},"@summary",[298,4560,4561],{},"@requestBody",[298,4563,4564],{},"@response","\nand ",[298,4567,4568],{},"@queryParam"," docblocks are no longer read — document endpoints with typed DTOs,\n",[298,4571,4572],{},"#[ApiOperation]",[298,4574,4575],{},"#[QueryParam]",[298,4577,4578],{},"#[ApiRequestBody]",[298,4580,4581],{},"#[ApiResponse]",". See\n",[303,4584,1231],{"href":150},[303,4586,4587],{"href":34},"Typed I/O",[474,4589,4591],{"id":4590},"_156","1.56",[479,4593,4594,4609,4621,4647,4653,4662],{},[318,4595,4596,4599,4600,2358,4603,4606,4607,3471],{},[293,4597,4598],{},"CORS fails closed."," Set ",[298,4601,4602],{},"CORS_ALLOWED_ORIGINS",[298,4604,4605],{},"CORS_SUPPORTS_CREDENTIALS=true"," if you need\ncredentials (now defaults to ",[298,4608,1195],{},[318,4610,4611,4614,4615,4374,4618,3471],{},[293,4612,4613],{},"Remote image fetching is opt-in:"," configure ",[298,4616,4617],{},"image.security.allowed_domains",[298,4619,4620],{},"glueful/media",[318,4622,4623,909,4626,2462,4629,4632,4633,4636,4637,4639,4640,4643,4644,725],{},[293,4624,4625],{},"Queue and scheduler payloads are HMAC-signed",[298,4627,4628],{},"QUEUE_PAYLOAD_SIGNING",[298,4630,4631],{},"QUEUE_REQUIRE_SIGNED_PAYLOADS",", both default ",[298,4634,4635],{},"true","; needs ",[298,4638,2118],{},"). Set\n",[298,4641,4642],{},"QUEUE_REQUIRE_SIGNED_PAYLOADS=false"," temporarily to drain unsigned rows. Stored handler classes must\nimplement ",[298,4645,4646],{},"JobInterface",[318,4648,4649,4652],{},[293,4650,4651],{},"Memcached:"," flush the cache on upgrade (stored format changed).",[318,4654,4655,4661],{},[293,4656,4657,4658],{},"JWTs must carry a valid ",[298,4659,4660],{},"exp","; tokens without one are rejected.",[318,4663,4664,4669],{},[293,4665,3555,4666],{},[298,4667,4668],{},"TRUSTED_PROXIES"," behind a load balancer, or client IPs resolve to the proxy.",[474,4671,4673],{"id":4672},"_155","1.55",[479,4675,4676,4692,4709,4724],{},[318,4677,4678,4684,4685,4687,4688,4691],{},[293,4679,4680,717,4682,930],{},[298,4681,498],{},[298,4683,488],{}," Without a permission provider bound,\nannotated routes return ",[298,4686,4207],{}," — bind one (e.g. ",[298,4689,4690],{},"glueful/aegis","), grant the permissions, or remove the\nattribute.",[318,4693,4694,4701,4702,4705,4706,725],{},[293,4695,4696,4697,4700],{},"API keys in ",[298,4698,4699],{},"?api_key="," are off by default."," Move clients to the ",[298,4703,4704],{},"X-API-Key"," header or set\n",[298,4707,4708],{},"security.api_keys.allow_query_param = true",[318,4710,4711,909,4714,629,4717,4720,4721,4723],{},[293,4712,4713],{},"Signed URLs need a secret",[298,4715,4716],{},"SIGNED_URL_SECRET",[298,4718,4719],{},"uploads.signed_urls.secret",", or ",[298,4722,2118],{},") — use a\ndistinct value per environment.",[318,4725,4726,4729,4730,4733,4734,4737],{},[293,4727,4728],{},"Broken extension bindings fail loud outside production."," Fix any provider whose ",[298,4731,4732],{},"services()","\nthrows or binds a bare interface (give it ",[298,4735,4736],{},"['class' => Concrete::class]"," or a factory).",[474,4739,4741],{"id":4740},"_154","1.54",[479,4743,4744,4802],{},[318,4745,4746,4749,4750,468,4753,4756,4757,725,4760],{},[293,4747,4748],{},"Cloud storage drivers moved to packages."," Core keeps only ",[298,4751,4752],{},"local",[298,4754,4755],{},"memory","; a disk using\nanother driver throws ",[298,4758,4759],{},"UnsupportedStorageDriverException",[324,4761,4763],{"className":326,"code":4762,"language":328,"meta":329,"style":329},"composer require glueful/storage-s3     # S3, R2, MinIO, Spaces, Wasabi\ncomposer require glueful/storage-gcs    # Google Cloud Storage\ncomposer require glueful/storage-azure  # Azure Blob Storage\n",[298,4764,4765,4778,4790],{"__ignoreMap":329},[333,4766,4767,4769,4772,4775],{"class":335,"line":336},[333,4768,340],{"class":339},[333,4770,4771],{"class":343}," require",[333,4773,4774],{"class":343}," glueful/storage-s3",[333,4776,4777],{"class":377},"     # S3, R2, MinIO, Spaces, Wasabi\n",[333,4779,4780,4782,4784,4787],{"class":335,"line":381},[333,4781,340],{"class":339},[333,4783,4771],{"class":343},[333,4785,4786],{"class":343}," glueful/storage-gcs",[333,4788,4789],{"class":377},"    # Google Cloud Storage\n",[333,4791,4792,4794,4796,4799],{"class":335,"line":556},[333,4793,340],{"class":339},[333,4795,4771],{"class":343},[333,4797,4798],{"class":343}," glueful/storage-azure",[333,4800,4801],{"class":377},"  # Azure Blob Storage\n",[318,4803,4804,4807,4808,725],{},[293,4805,4806],{},"Extension container definitions override core"," (last layer wins). Audit extensions that reuse a\ncore service id, and recompile any precompiled container: ",[298,4809,4810],{},"php glueful di:container:compile --force",[474,4812,4814],{"id":4813},"_152","1.52",[289,4816,4817,4818,4821,4822,4825],{},"Four subsystems left core. Install the package for each one you use (they auto-discover via\n",[298,4819,4820],{},"extra.glueful","; enable them in ",[298,4823,4824],{},"config/extensions.php"," if your app gates extensions there):",[2896,4827,4828,4841],{},[2899,4829,4830],{},[2902,4831,4832,4835,4838],{},[2905,4833,4834],{},"Subsystem",[2905,4836,4837],{},"Restore with",[2905,4839,4840],{},"Also do",[2912,4842,4843,4868,4892,4928],{},[2902,4844,4845,4851,4856],{},[2917,4846,4847,4848],{},"Archiving, ",[298,4849,4850],{},"archive:manage",[2917,4852,4853],{},[298,4854,4855],{},"composer require glueful/archive",[2917,4857,4858,4860,4861,4864,4865],{},[298,4859,3024],{},"; imports ",[298,4862,4863],{},"Glueful\\Services\\Archive\\*"," → ",[298,4866,4867],{},"Glueful\\Extensions\\Archive\\*",[2902,4869,4870,4876,4881],{},[2917,4871,4872,4873],{},"Edge cache / CDN purge, ",[298,4874,4875],{},"cache:purge",[2917,4877,4878],{},[298,4879,4880],{},"composer require glueful/cdn",[2917,4882,4883,4884,4887,4888,4891],{},"Move ",[298,4885,4886],{},"cache.edge"," overrides to the extension's ",[298,4889,4890],{},"cdn"," config",[2902,4893,4894,4903,4908],{},[2917,4895,4896,4897,629,4900],{},"Supervised workers, ",[298,4898,4899],{},"queue:autoscale",[298,4901,4902],{},"queue:supervise",[2917,4904,4905],{},[298,4906,4907],{},"composer require glueful/queue-ops",[2917,4909,4910,4913,4914,4917,4918,4917,4921,4864,4924,4927],{},[298,4911,4912],{},"queue.workers.{process,auto_scaling,resource_limits,resource_thresholds,supervisor}"," and per-queue ",[298,4915,4916],{},"workers","/",[298,4919,4920],{},"max_workers",[298,4922,4923],{},"auto_scale",[298,4925,4926],{},"queue_ops.*"," (same env vars)",[2902,4929,4930,4936,4941],{},[2917,4931,4932,4933],{},"Image processing, thumbnails, metadata, ",[298,4934,4935],{},"image()",[2917,4937,4938],{},[298,4939,4940],{},"composer require glueful/media",[2917,4942,4943,4864,4946,3675,4949,717,4952,4955],{},[298,4944,4945],{},"Glueful\\Services\\ImageProcessor",[298,4947,4948],{},"Glueful\\Extensions\\Media\\ImageProcessor",[298,4950,4951],{},"IMAGE_*",[298,4953,4954],{},"config/image.php"," are extension-owned",[289,4957,4958,4959,4962,4963,4966,4967,4970,4971,629,4973,2462,4975,4977,4978,4980,4981,629,4984,629,4987,629,4989,629,4992,2462,4995,4997],{},"Without them, behaviour degrades gracefully: ",[298,4960,4961],{},"queue:work"," runs one lean worker, ",[298,4964,4965],{},"uploadMedia()","\nreturns ",[298,4968,4969],{},"thumb_url: null",", and edge purges are no-ops — but ",[298,4972,4935],{},[298,4974,4875],{},[298,4976,4850],{}," and the old ",[298,4979,4961],{}," sub-actions (",[298,4982,4983],{},"spawn",[298,4985,4986],{},"scale",[298,4988,2808],{},[298,4990,4991],{},"stop",[298,4993,4994],{},"restart",[298,4996,2811],{},") simply don't exist.",[310,4999,5001],{"id":5000},"_150-151","1.50 – 1.51",[289,5003,5004,5005,5007],{},"These predate the main range of this guide; see the ",[303,5006,280],{"href":281}," for detail.",[479,5009,5010,5029],{},[318,5011,5012,417,5015,4864,5018,5021,5022,5025,5026,725],{},[293,5013,5014],{},"1.51:",[298,5016,5017],{},"ChannelManager::getAvailableChannels()",[298,5019,5020],{},"getRegisteredChannelNames()"," (no alias).\nNotification retry options move from the ",[298,5023,5024],{},"emailnotification"," config key to ",[298,5027,5028],{},"notifications.retry",[318,5030,5031,5034,5035,2634,5038,5052,5054,468,5057,5060,5061,4552,5064,5067,5068,4582,5071,725],{},[293,5032,5033],{},"1.50:"," the user store left core. Install and enable a user store, or authentication stays\ndisabled (core binds a fail-closed ",[298,5036,5037],{},"NullUserProvider",[324,5039,5041],{"className":326,"code":5040,"language":328,"meta":329,"style":329},"composer require glueful/users\n",[298,5042,5043],{"__ignoreMap":329},[333,5044,5045,5047,5049],{"class":335,"line":336},[333,5046,340],{"class":339},[333,5048,4771],{"class":343},[333,5050,5051],{"class":343}," glueful/users\n",[393,5053],{},[298,5055,5056],{},"Glueful\\Models\\User",[298,5058,5059],{},"Glueful\\Repository\\UserRepository"," are gone from core — read users\nthrough ",[298,5062,5063],{},"UserProviderInterface",[298,5065,5066],{},"api_keys.user_id"," is renamed ",[298,5069,5070],{},"user_uuid",[303,5072,5073],{"href":42},"Identity",[5075,5076,5077],"style",{},"html pre.shiki code .ss--_, html code.shiki .ss--_{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .s91G_, html code.shiki .s91G_{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#F8F8F2}html pre.shiki code .swvn1, html code.shiki .swvn1{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .siCPE, html code.shiki .siCPE{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sLACW, html code.shiki .sLACW{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sTNss, html code.shiki .sTNss{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .srJo8, html code.shiki .srJo8{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sD0ED, html code.shiki .sD0ED{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .sGXK2, html code.shiki .sGXK2{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .s_MOj, html code.shiki .s_MOj{--shiki-light:#E2931D;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .ss7Ak, html code.shiki .ss7Ak{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit;--shiki-sepia:#88846F;--shiki-sepia-font-style:inherit}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .shWJe, html code.shiki .shWJe{--shiki-light:#F76D47;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .sQqfL, html code.shiki .sQqfL{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#F8F8F2}html pre.shiki code .sYThS, html code.shiki .sYThS{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html pre.shiki code .sv8o3, html code.shiki .sv8o3{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#F8F8F2}html pre.shiki code .seZir, html code.shiki .seZir{--shiki-light:#90A4AE;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sMTiH, html code.shiki .sMTiH{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html pre.shiki code .sQeA1, html code.shiki .sQeA1{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html pre.shiki code .sRxSC, html code.shiki .sRxSC{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#F92672;--shiki-sepia-font-style:inherit}html pre.shiki code .saDeg, html code.shiki .saDeg{--shiki-light:#39ADB5;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sEff5, html code.shiki .sEff5{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sTC9v, html code.shiki .sTC9v{--shiki-light:#F76D47;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sogGa, html code.shiki .sogGa{--shiki-light:#E53935;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sh1VR, html code.shiki .sh1VR{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#CFCFC2}html pre.shiki code .sINAO, html code.shiki .sINAO{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#CFCFC2}html pre.shiki code .sSBr1, html code.shiki .sSBr1{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#FD971F}html pre.shiki code .sR7ES, html code.shiki .sR7ES{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .sFhLe, html code.shiki .sFhLe{--shiki-light:#91B859;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}",{"title":329,"searchDepth":336,"depth":381,"links":5079},[5080,5081,5092,5103,5112,5124],{"id":312,"depth":381,"text":313},{"id":440,"depth":381,"text":441,"children":5082},[5083,5084,5085,5086,5087,5088,5089,5090,5091],{"id":476,"depth":556,"text":477},{"id":916,"depth":556,"text":917},{"id":1234,"depth":556,"text":1235},{"id":1662,"depth":556,"text":1663},{"id":2191,"depth":556,"text":2192},{"id":2402,"depth":556,"text":2403},{"id":2887,"depth":556,"text":2888},{"id":3015,"depth":556,"text":125},{"id":3244,"depth":556,"text":3245},{"id":3508,"depth":381,"text":3509,"children":5093},[5094,5095,5096,5097,5098,5099,5100,5101,5102],{"id":3512,"depth":556,"text":3513},{"id":3547,"depth":556,"text":3548},{"id":3622,"depth":556,"text":3623},{"id":3756,"depth":556,"text":3757},{"id":3780,"depth":556,"text":3781},{"id":3831,"depth":556,"text":3832},{"id":3855,"depth":556,"text":3856},{"id":3896,"depth":556,"text":3897},{"id":3919,"depth":556,"text":3920},{"id":3956,"depth":381,"text":3957,"children":5104},[5105,5106,5107,5108,5109,5110,5111],{"id":3960,"depth":556,"text":3961},{"id":4027,"depth":556,"text":4028},{"id":4139,"depth":556,"text":4140},{"id":4183,"depth":556,"text":4184},{"id":4195,"depth":556,"text":4196},{"id":4211,"depth":556,"text":4212},{"id":4226,"depth":556,"text":4227},{"id":4245,"depth":381,"text":4246,"children":5113},[5114,5115,5116,5117,5118,5119,5120,5121,5122,5123],{"id":4249,"depth":556,"text":4250},{"id":4299,"depth":556,"text":4300},{"id":4316,"depth":556,"text":4317},{"id":4333,"depth":556,"text":4334},{"id":4362,"depth":556,"text":4363},{"id":4529,"depth":556,"text":4530},{"id":4590,"depth":556,"text":4591},{"id":4672,"depth":556,"text":4673},{"id":4740,"depth":556,"text":4741},{"id":4813,"depth":556,"text":4814},{"id":5000,"depth":381,"text":5001},"Step-by-step upgrade guide for the Glueful framework — the actions each release requires, from 1.91 back to 1.50.","md",null,{},{"title":17,"description":5125},"_JzxOhiCVhZLsf21R_4ug8Rknpghyb9OZS8TgmH0nBI",[5132,5134],{"title":13,"path":14,"stem":15,"description":5133,"children":-1},"Install Glueful with the API skeleton (or as a framework package), run the dev server, and add your first route.",{"title":26,"path":22,"stem":27,"description":5135,"children":-1},"Core concepts you'll use every day",1793709777401]