[{"data":1,"prerenderedAt":2553},["ShallowReactive",2],{"docs-sections":3,"docs-page-\u002Fdocs\u002Fserver":123},[4,9,13,17,22,26,30,34,38,42,46,50,55,59,63,67,72,76,80,85,89,93,97,102,106,110,114,119],{"path":5,"title":6,"description":7,"group":8},"\u002Fdocs","Introduction","What tunables is, the problem it solves, and the principles it holds to.","Get started",{"path":10,"title":11,"description":12,"group":8},"\u002Fdocs\u002Fquickstart","Quickstart","Take one group of settings from constants to a screen with a history, in a Nuxt app on Postgres.",{"path":14,"title":15,"description":16,"group":8},"\u002Fdocs\u002Fpatterns","What it is for","The kinds of value tunables keeps — live limits, plans, per-workspace and per-person values, model choices, values that bound each other, costly switches — and the feature that does each.",{"path":18,"title":19,"description":20,"group":21},"\u002Fdocs\u002Fsettings-and-groups","Settings and groups","Declare each setting once, in a group edited beside what it affects, and read it with types.","Concepts",{"path":23,"title":24,"description":25,"group":21},"\u002Fdocs\u002Fkinds-and-units","Kinds and units","The eight kinds of setting, the built-in units, your own units, and how values are checked and read.",{"path":27,"title":28,"description":29,"group":21},"\u002Fdocs\u002Fchecks-and-guards","Checks and guards","Rules across settings, guards for what your app keeps, within for presets, confirmations for costly changes, and notes.",{"path":31,"title":32,"description":33,"group":21},"\u002Fdocs\u002Fhistory-and-restore","History and restore","Every change is a revision with who, when, why and each value before and after; restoring one runs the same checks as any save.",{"path":35,"title":36,"description":37,"group":21},"\u002Fdocs\u002Fpresets","Presets","Named, reusable configurations with one default per slot, assigned by reference and bounded by the settings.",{"path":39,"title":40,"description":41,"group":21},"\u002Fdocs\u002Fscopes","Scopes","Values per workspace and per person, through layers — the system, a plan, the workspace, the person — each bounded by what it inherits.",{"path":43,"title":44,"description":45,"group":21},"\u002Fdocs\u002Fcatalogs","Catalogs","Lists of allowed choices, such as permitted models, whose entries are retired rather than deleted and cannot be retired while in use.",{"path":47,"title":48,"description":49,"group":21},"\u002Fdocs\u002Fdeployment-values","Deployment values","Values a deployment sets in its environment and nobody tunes while the app runs, declared once and listed in .env.example.",{"path":51,"title":52,"description":53,"group":54},"\u002Fdocs\u002Fstorage","Storage","Postgres through your own client, the four tables and how to create them, joining your transactions, and the storage contract behind it.","Integrate",{"path":56,"title":57,"description":58,"group":54},"\u002Fdocs\u002Fserver","Server and access","Mount tunables' API as a fetch handler in Nitro, Hono, Bun or Deno, as Express middleware or as an oRPC router, with access decided by your app.",{"path":60,"title":61,"description":62,"group":54},"\u002Fdocs\u002Faccess","Who can do what","End users bound by values they never see, staff who tune groups, owners and members who change only their own scope, and the terminal.",{"path":64,"title":65,"description":66,"group":54},"\u002Fdocs\u002Ftesting","Testing","Your app's tunables in memory for its own tests, with values set where a test needs them, and how to test rules, guards and confirmations.",{"path":68,"title":69,"description":70,"group":71},"\u002Fdocs\u002Fnuxt-layer","The Nuxt layer","Ready screens for a Nuxt UI app — a group's form, All settings, history, presets, assignments and catalogs — placed beside what they affect.","Screens",{"path":73,"title":74,"description":75,"group":71},"\u002Fdocs\u002Fvue-composables","Vue composables","Headless composables for building tunables' screens in any Vue app, on any design system.",{"path":77,"title":78,"description":79,"group":71},"\u002Fdocs\u002Ftheming","Theming","The screens bring no colors, fonts or CSS of their own; they take your Nuxt UI theme.",{"path":81,"title":82,"description":83,"group":84},"\u002Fdocs\u002Fcli","The CLI","The tunables command, tunables.config.ts, and which commands need a database.","CLI",{"path":86,"title":87,"description":88,"group":84},"\u002Fdocs\u002Fcli-check","Check and generate","The commands that need no database — checking definitions, writing .env.example, and keeping explanations and defaults in step with a pdd-kit Product Model.",{"path":90,"title":91,"description":92,"group":84},"\u002Fdocs\u002Fcli-values","Read and change values","The commands that read and change stored values, through the same checks and history as the screens.",{"path":94,"title":95,"description":96,"group":84},"\u002Fdocs\u002Fcli-skills","Install skills","Install tunables' agent skills for Claude Code, Codex CLI, Cursor, Gemini CLI and GitHub Copilot, and keep them current.",{"path":98,"title":99,"description":100,"group":101},"\u002Fdocs\u002Fskills","Agent skills","Three skills that let a coding agent adopt tunables, define settings the right way and verify that every value has one home.","Skills",{"path":103,"title":104,"description":105,"group":101},"\u002Fdocs\u002Fskill-adopt","tunables-adopt","Bring tunables into an app that has none yet, from an inventory of every value to a wired, synced app with the old homes removed.",{"path":107,"title":108,"description":109,"group":101},"\u002Fdocs\u002Fskill-define","tunables-define","Add, change, rename or remove one value in an app that uses tunables, with its home, its checks, its reads and what happens to stored values.",{"path":111,"title":112,"description":113,"group":101},"\u002Fdocs\u002Fskill-verify","tunables-verify","Audit an app's tunables against their rules, report every finding with where it is and the rule it breaks, and fix only the findings you choose.",{"path":115,"title":116,"description":117,"group":118},"\u002Fdocs\u002Fapi","API reference","Every export of the tunables package, by the subpath it is imported from.","Reference",{"path":120,"title":121,"description":122,"group":118},"\u002Fdocs\u002Ftasklane-example","Tasklane, the example app","A todo app with workspaces and people as tunables — two groups, plans as a layer, a catalog of models, scopes, a rule, a confirmation, and the playground that runs it.",{"id":124,"title":57,"body":125,"description":58,"extension":2548,"group":54,"meta":2549,"navigation":593,"order":1459,"path":56,"seo":2550,"stem":2551,"__hash__":2552},"docs\u002Fdocs\u002Fserver.md",{"type":126,"value":127,"toc":2535},"minimark",[128,132,136,205,210,213,456,559,881,892,908,912,938,943,1227,1231,1534,1538,1692,1698,1712,1716,1955,1981,1985,2032,2035,2274,2300,2304,2309,2479,2519,2523,2531],[129,130,57],"h1",{"id":131},"server-and-access",[133,134,135],"p",{},"The screens, and any other client, reach tunables through one set of\noperations: describe the definitions, read and save groups, read history and\nrestore, and manage presets and catalogs. tunables serves them three ways,\nfrom one service, so every adapter asks the same access questions and answers\nthe same way.",[137,138,139,152],"table",{},[140,141,142],"thead",{},[143,144,145,149],"tr",{},[146,147,148],"th",{},"Your server",[146,150,151],{},"Use",[153,154,155,179,192],"tbody",{},[143,156,157,170],{},[158,159,160,161,165,166,169],"td",{},"Anything that speaks web ",[162,163,164],"code",{},"Request"," and ",[162,167,168],{},"Response",": Nitro and Nuxt, h3, Hono, Bun, Deno, Cloudflare Workers",[158,171,172,175,176],{},[162,173,174],{},"createHandler"," from ",[162,177,178],{},"tunables\u002Fserver",[143,180,181,184],{},[158,182,183],{},"Express",[158,185,186,175,189],{},[162,187,188],{},"tunablesMiddleware",[162,190,191],{},"tunables\u002Fexpress",[143,193,194,197],{},[158,195,196],{},"An oRPC router",[158,198,199,175,202],{},[162,200,201],{},"tunablesRouter",[162,203,204],{},"tunables\u002Forpc",[206,207,209],"h2",{"id":208},"access","Access",[133,211,212],{},"tunables has no users, roles or sessions of its own. Every adapter takes the\nsame options, and every request is answered only after they allow it.",[214,215,220],"pre",{"className":216,"code":217,"language":218,"meta":219,"style":219},"language-ts shiki shiki-themes tunables-panel-light tunables-panel-light tunables-panel-dark","interface ServiceOptions\u003CContext> {\n  tunables: AnyTunables\n  actor: (context: Context) => Actor | undefined | Promise\u003CActor | undefined>\n  authorize?: (context: Context, request: { action: 'read' | 'change', target: AccessTarget, actor: Actor }) => boolean | Promise\u003Cboolean>\n  refusal?: 'not-found' | 'forbidden'\n  host?: (context: Context) => unknown\n}\n","ts","",[162,221,222,248,261,312,402,427,450],{"__ignoreMap":219},[223,224,227,231,235,239,242,245],"span",{"class":225,"line":226},"line",1,[223,228,230],{"class":229},"sZ4mS","interface",[223,232,234],{"class":233},"sOThN"," ServiceOptions",[223,236,238],{"class":237},"sp9ci","\u003C",[223,240,241],{"class":233},"Context",[223,243,244],{"class":237},">",[223,246,247],{"class":237}," {\n",[223,249,251,255,258],{"class":225,"line":250},2,[223,252,254],{"class":253},"sVz71","  tunables",[223,256,257],{"class":237},":",[223,259,260],{"class":233}," AnyTunables\n",[223,262,264,267,269,272,275,277,280,283,286,289,292,295,297,300,302,305,307,309],{"class":225,"line":263},3,[223,265,266],{"class":233},"  actor",[223,268,257],{"class":237},[223,270,271],{"class":237}," (",[223,273,274],{"class":253},"context",[223,276,257],{"class":237},[223,278,279],{"class":233}," Context",[223,281,282],{"class":237},")",[223,284,285],{"class":229}," =>",[223,287,288],{"class":233}," Actor",[223,290,291],{"class":237}," |",[223,293,294],{"class":233}," undefined",[223,296,291],{"class":237},[223,298,299],{"class":233}," Promise",[223,301,238],{"class":237},[223,303,304],{"class":233},"Actor",[223,306,291],{"class":237},[223,308,294],{"class":233},[223,310,311],{"class":237},">\n",[223,313,315,318,321,323,325,327,329,332,335,337,340,343,345,348,352,355,357,359,362,364,366,369,371,374,376,379,381,383,386,388,391,393,395,397,400],{"class":225,"line":314},4,[223,316,317],{"class":233},"  authorize",[223,319,320],{"class":237},"?:",[223,322,271],{"class":237},[223,324,274],{"class":253},[223,326,257],{"class":237},[223,328,279],{"class":233},[223,330,331],{"class":237},",",[223,333,334],{"class":253}," request",[223,336,257],{"class":237},[223,338,339],{"class":237}," {",[223,341,342],{"class":253}," action",[223,344,257],{"class":237},[223,346,347],{"class":237}," '",[223,349,351],{"class":350},"sjCzm","read",[223,353,354],{"class":237},"'",[223,356,291],{"class":237},[223,358,347],{"class":237},[223,360,361],{"class":350},"change",[223,363,354],{"class":237},[223,365,331],{"class":237},[223,367,368],{"class":253}," target",[223,370,257],{"class":237},[223,372,373],{"class":233}," AccessTarget",[223,375,331],{"class":237},[223,377,378],{"class":253}," actor",[223,380,257],{"class":237},[223,382,288],{"class":233},[223,384,385],{"class":237}," })",[223,387,285],{"class":229},[223,389,390],{"class":233}," boolean",[223,392,291],{"class":237},[223,394,299],{"class":233},[223,396,238],{"class":237},[223,398,399],{"class":233},"boolean",[223,401,311],{"class":237},[223,403,405,408,410,412,415,417,419,421,424],{"class":225,"line":404},5,[223,406,407],{"class":253},"  refusal",[223,409,320],{"class":237},[223,411,347],{"class":237},[223,413,414],{"class":350},"not-found",[223,416,354],{"class":237},[223,418,291],{"class":237},[223,420,347],{"class":237},[223,422,423],{"class":350},"forbidden",[223,425,426],{"class":237},"'\n",[223,428,430,433,435,437,439,441,443,445,447],{"class":225,"line":429},6,[223,431,432],{"class":233},"  host",[223,434,320],{"class":237},[223,436,271],{"class":237},[223,438,274],{"class":253},[223,440,257],{"class":237},[223,442,279],{"class":233},[223,444,282],{"class":237},[223,446,285],{"class":229},[223,448,449],{"class":233}," unknown\n",[223,451,453],{"class":225,"line":452},7,[223,454,455],{"class":237},"}\n",[457,458,459,477,537,551],"ul",{},[460,461,462,468,469,472,473,476],"li",{},[463,464,465],"strong",{},[162,466,467],{},"actor"," says who is asking, from your own sign-in: a name, or\n",[162,470,471],{},"{ id, name }",". Return ",[162,474,475],{},"undefined"," when nobody is signed in. Revisions record\nthe actor's name.",[460,478,479,484,485,488,489,491,492,494,495,488,498,501,502,505,506,491,509,512,513,516,517,520,521,526,527,530,531],{},[463,480,481],{},[162,482,483],{},"authorize"," says whether this actor may do this. It is asked on every\nrequest. ",[162,486,487],{},"action"," is ",[162,490,351],{}," or ",[162,493,361],{},"; ",[162,496,497],{},"target.kind",[162,499,500],{},"description",",\n",[162,503,504],{},"group",", ",[162,507,508],{},"presets",[162,510,511],{},"catalog",", and ",[162,514,515],{},"target.id"," names the group, preset set\nor catalog when there is one. For a group, ",[162,518,519],{},"target.scope"," is the\n",[522,523,525],"a",{"href":524},".\u002Fscopes","scope"," path the request reads or changes, such as\n",[162,528,529],{},"{ workspace: 'acme' }",", and is absent for the whole system. ",[463,532,533,534,536],{},"Without\n",[162,535,483],{},", nothing is allowed.",[460,538,539,544,545,547,548,550],{},[463,540,541],{},[162,542,543],{},"refusal"," decides how a refused request answers. The default,\n",[162,546,414],{},", answers 404, so an outsider cannot tell the administration\nexists. ",[162,549,423],{}," answers 401 to nobody signed in and 403 to someone who\nmay not.",[460,552,553,558],{},[463,554,555],{},[162,556,557],{},"host"," hands your guards, notes and uses something from the request,\nsuch as its database transaction.",[214,560,562],{"className":216,"code":561,"language":218,"meta":219,"style":219},"import type { ServiceOptions } from 'tunables\u002Fserver'\n\nexport const access: Pick\u003CServiceOptions\u003CAppContext>, 'actor' | 'authorize'> = {\n  actor: context => context.user ? { id: context.user.id, name: context.user.name } : undefined,\n  authorize: (context, { action, target }) => {\n    if (!context.user) return false\n    if (action === 'read') return context.user.staff\n    return context.user.role === 'admin' || (target.kind === 'catalog' && context.user.role === 'operator')\n  }\n}\n",[162,563,564,589,595,645,715,743,765,798,870,876],{"__ignoreMap":219},[223,565,566,569,572,574,577,580,583,585,587],{"class":225,"line":226},[223,567,568],{"class":229},"import",[223,570,571],{"class":229}," type",[223,573,339],{"class":237},[223,575,576],{"class":253}," ServiceOptions ",[223,578,579],{"class":237},"}",[223,581,582],{"class":229}," from",[223,584,347],{"class":237},[223,586,178],{"class":350},[223,588,426],{"class":237},[223,590,591],{"class":225,"line":250},[223,592,594],{"emptyLinePlaceholder":593},true,"\n",[223,596,597,600,603,606,608,611,613,616,618,621,624,626,628,630,632,634,636,638,640,643],{"class":225,"line":263},[223,598,599],{"class":229},"export",[223,601,602],{"class":229}," const",[223,604,605],{"class":253}," access",[223,607,257],{"class":237},[223,609,610],{"class":233}," Pick",[223,612,238],{"class":237},[223,614,615],{"class":233},"ServiceOptions",[223,617,238],{"class":237},[223,619,620],{"class":233},"AppContext",[223,622,623],{"class":237},">,",[223,625,347],{"class":237},[223,627,467],{"class":350},[223,629,354],{"class":237},[223,631,291],{"class":237},[223,633,347],{"class":237},[223,635,483],{"class":350},[223,637,354],{"class":237},[223,639,244],{"class":237},[223,641,642],{"class":237}," =",[223,644,247],{"class":237},[223,646,647,649,651,654,657,660,663,666,669,671,674,676,678,680,683,685,688,690,693,695,697,699,701,703,706,708,711,713],{"class":225,"line":314},[223,648,266],{"class":233},[223,650,257],{"class":237},[223,652,653],{"class":253}," context ",[223,655,656],{"class":229},"=>",[223,658,659],{"class":253}," context",[223,661,662],{"class":237},".",[223,664,665],{"class":253},"user ",[223,667,668],{"class":237},"?",[223,670,339],{"class":237},[223,672,673],{"class":253}," id",[223,675,257],{"class":237},[223,677,659],{"class":253},[223,679,662],{"class":237},[223,681,682],{"class":253},"user",[223,684,662],{"class":237},[223,686,687],{"class":253},"id",[223,689,331],{"class":237},[223,691,692],{"class":253}," name",[223,694,257],{"class":237},[223,696,659],{"class":253},[223,698,662],{"class":237},[223,700,682],{"class":253},[223,702,662],{"class":237},[223,704,705],{"class":253},"name ",[223,707,579],{"class":237},[223,709,710],{"class":237}," :",[223,712,294],{"class":229},[223,714,501],{"class":237},[223,716,717,719,721,723,725,727,729,731,733,736,739,741],{"class":225,"line":404},[223,718,317],{"class":233},[223,720,257],{"class":237},[223,722,271],{"class":237},[223,724,274],{"class":253},[223,726,331],{"class":237},[223,728,339],{"class":237},[223,730,342],{"class":253},[223,732,331],{"class":237},[223,734,735],{"class":253}," target ",[223,737,738],{"class":237},"})",[223,740,285],{"class":229},[223,742,247],{"class":237},[223,744,745,748,751,753,755,757,759,762],{"class":225,"line":429},[223,746,747],{"class":229},"    if",[223,749,750],{"class":237}," (!",[223,752,274],{"class":253},[223,754,662],{"class":237},[223,756,682],{"class":253},[223,758,282],{"class":237},[223,760,761],{"class":229}," return",[223,763,764],{"class":229}," false\n",[223,766,767,769,771,774,777,779,781,783,785,787,789,791,793,795],{"class":225,"line":452},[223,768,747],{"class":229},[223,770,271],{"class":237},[223,772,773],{"class":253},"action ",[223,775,776],{"class":237},"===",[223,778,347],{"class":237},[223,780,351],{"class":350},[223,782,354],{"class":237},[223,784,282],{"class":237},[223,786,761],{"class":229},[223,788,659],{"class":253},[223,790,662],{"class":237},[223,792,682],{"class":253},[223,794,662],{"class":237},[223,796,797],{"class":253},"staff\n",[223,799,801,804,806,808,810,812,815,817,819,822,824,827,829,832,834,837,839,841,843,845,848,850,852,854,856,858,860,862,865,867],{"class":225,"line":800},8,[223,802,803],{"class":229},"    return",[223,805,659],{"class":253},[223,807,662],{"class":237},[223,809,682],{"class":253},[223,811,662],{"class":237},[223,813,814],{"class":253},"role ",[223,816,776],{"class":237},[223,818,347],{"class":237},[223,820,821],{"class":350},"admin",[223,823,354],{"class":237},[223,825,826],{"class":237}," ||",[223,828,271],{"class":237},[223,830,831],{"class":253},"target",[223,833,662],{"class":237},[223,835,836],{"class":253},"kind ",[223,838,776],{"class":237},[223,840,347],{"class":237},[223,842,511],{"class":350},[223,844,354],{"class":237},[223,846,847],{"class":237}," &&",[223,849,659],{"class":253},[223,851,662],{"class":237},[223,853,682],{"class":253},[223,855,662],{"class":237},[223,857,814],{"class":253},[223,859,776],{"class":237},[223,861,347],{"class":237},[223,863,864],{"class":350},"operator",[223,866,354],{"class":237},[223,868,869],{"class":237},")\n",[223,871,873],{"class":225,"line":872},9,[223,874,875],{"class":237},"  }\n",[223,877,879],{"class":225,"line":878},10,[223,880,455],{"class":237},[133,882,883,884,887,888,891],{},"Spread it into whichever adapter you use: ",[162,885,886],{},"createHandler({ tunables, ...access })",".\n",[522,889,61],{"href":890},".\u002Faccess"," walks through the usual cases, including\nletting a workspace's owner and each person change only their own scope.",[133,893,894,895,505,898,505,901,904,905,662],{},"When your own code calls ",[162,896,897],{},"save",[162,899,900],{},"reset",[162,902,903],{},"restore"," or a preset or catalog\nchange directly, it decides access itself first, then passes ",[162,906,907],{},"{ actor, reason }",[206,909,911],{"id":910},"the-fetch-handler","The fetch handler",[133,913,914,916,917,919,920,922,923,505,926,929,930,932,933,165,935,937],{},[162,915,174],{}," returns one function from a ",[162,918,164],{}," to a ",[162,921,168],{},". It\nanswers only paths under its ",[162,924,925],{},"basePath",[162,927,928],{},"\u002Fapi\u002Ftunables"," by default, and\nreturns ",[162,931,475],{}," for everything else, so your own routes go on as before.\nCall it with the request and the context your ",[162,934,467],{},[162,936,483],{}," read; the\ncontext defaults to the request itself.",[939,940,942],"h3",{"id":941},"nuxt-and-nitro","Nuxt and Nitro",[214,944,947],{"className":216,"code":945,"filename":946,"language":218,"meta":219,"style":219},"import type { H3Event } from 'h3'\nimport { createHandler } from 'tunables\u002Fserver'\nimport { tunables } from '..\u002F..\u002F..\u002Ftunables'\n\nconst handle = createHandler\u003CH3Event>({\n  tunables,\n  actor: event => event.context.user ? { id: event.context.user.id, name: event.context.user.name } : undefined,\n  authorize: event => event.context.user?.role === 'admin'\n})\n\nexport default defineEventHandler(async event =>\n  (await handle(toWebRequest(event), event)) ?? createError({ statusCode: 404 }))\n","server\u002Froutes\u002Fapi\u002Ftunables\u002F[...].ts",[162,948,949,970,989,1009,1013,1035,1041,1113,1146,1151,1155,1177],{"__ignoreMap":219},[223,950,951,953,955,957,960,962,964,966,968],{"class":225,"line":226},[223,952,568],{"class":229},[223,954,571],{"class":229},[223,956,339],{"class":237},[223,958,959],{"class":253}," H3Event ",[223,961,579],{"class":237},[223,963,582],{"class":229},[223,965,347],{"class":237},[223,967,939],{"class":350},[223,969,426],{"class":237},[223,971,972,974,976,979,981,983,985,987],{"class":225,"line":250},[223,973,568],{"class":229},[223,975,339],{"class":237},[223,977,978],{"class":253}," createHandler ",[223,980,579],{"class":237},[223,982,582],{"class":229},[223,984,347],{"class":237},[223,986,178],{"class":350},[223,988,426],{"class":237},[223,990,991,993,995,998,1000,1002,1004,1007],{"class":225,"line":263},[223,992,568],{"class":229},[223,994,339],{"class":237},[223,996,997],{"class":253}," tunables ",[223,999,579],{"class":237},[223,1001,582],{"class":229},[223,1003,347],{"class":237},[223,1005,1006],{"class":350},"..\u002F..\u002F..\u002Ftunables",[223,1008,426],{"class":237},[223,1010,1011],{"class":225,"line":314},[223,1012,594],{"emptyLinePlaceholder":593},[223,1014,1015,1018,1021,1024,1027,1029,1032],{"class":225,"line":404},[223,1016,1017],{"class":229},"const",[223,1019,1020],{"class":253}," handle ",[223,1022,1023],{"class":237},"=",[223,1025,1026],{"class":233}," createHandler",[223,1028,238],{"class":237},[223,1030,1031],{"class":233},"H3Event",[223,1033,1034],{"class":237},">({\n",[223,1036,1037,1039],{"class":225,"line":429},[223,1038,254],{"class":253},[223,1040,501],{"class":237},[223,1042,1043,1045,1047,1050,1052,1055,1057,1059,1061,1063,1065,1067,1069,1071,1073,1075,1077,1079,1081,1083,1085,1087,1089,1091,1093,1095,1097,1099,1101,1103,1105,1107,1109,1111],{"class":225,"line":452},[223,1044,266],{"class":233},[223,1046,257],{"class":237},[223,1048,1049],{"class":253}," event ",[223,1051,656],{"class":229},[223,1053,1054],{"class":253}," event",[223,1056,662],{"class":237},[223,1058,274],{"class":253},[223,1060,662],{"class":237},[223,1062,665],{"class":253},[223,1064,668],{"class":237},[223,1066,339],{"class":237},[223,1068,673],{"class":253},[223,1070,257],{"class":237},[223,1072,1054],{"class":253},[223,1074,662],{"class":237},[223,1076,274],{"class":253},[223,1078,662],{"class":237},[223,1080,682],{"class":253},[223,1082,662],{"class":237},[223,1084,687],{"class":253},[223,1086,331],{"class":237},[223,1088,692],{"class":253},[223,1090,257],{"class":237},[223,1092,1054],{"class":253},[223,1094,662],{"class":237},[223,1096,274],{"class":253},[223,1098,662],{"class":237},[223,1100,682],{"class":253},[223,1102,662],{"class":237},[223,1104,705],{"class":253},[223,1106,579],{"class":237},[223,1108,710],{"class":237},[223,1110,294],{"class":229},[223,1112,501],{"class":237},[223,1114,1115,1117,1119,1121,1123,1125,1127,1129,1131,1133,1136,1138,1140,1142,1144],{"class":225,"line":800},[223,1116,317],{"class":233},[223,1118,257],{"class":237},[223,1120,1049],{"class":253},[223,1122,656],{"class":229},[223,1124,1054],{"class":253},[223,1126,662],{"class":237},[223,1128,274],{"class":253},[223,1130,662],{"class":237},[223,1132,682],{"class":253},[223,1134,1135],{"class":237},"?.",[223,1137,814],{"class":253},[223,1139,776],{"class":237},[223,1141,347],{"class":237},[223,1143,821],{"class":350},[223,1145,426],{"class":237},[223,1147,1148],{"class":225,"line":872},[223,1149,1150],{"class":237},"})\n",[223,1152,1153],{"class":225,"line":878},[223,1154,594],{"emptyLinePlaceholder":593},[223,1156,1158,1160,1163,1166,1169,1172,1174],{"class":225,"line":1157},11,[223,1159,599],{"class":229},[223,1161,1162],{"class":229}," default",[223,1164,1165],{"class":233}," defineEventHandler",[223,1167,1168],{"class":237},"(",[223,1170,1171],{"class":229},"async",[223,1173,1049],{"class":253},[223,1175,1176],{"class":229},"=>\n",[223,1178,1180,1183,1186,1189,1191,1194,1196,1199,1202,1204,1207,1210,1213,1216,1219,1221,1224],{"class":225,"line":1179},12,[223,1181,1182],{"class":237},"  (",[223,1184,1185],{"class":229},"await",[223,1187,1188],{"class":233}," handle",[223,1190,1168],{"class":237},[223,1192,1193],{"class":233},"toWebRequest",[223,1195,1168],{"class":237},[223,1197,1198],{"class":253},"event",[223,1200,1201],{"class":237},"),",[223,1203,1054],{"class":253},[223,1205,1206],{"class":237},"))",[223,1208,1209],{"class":237}," ??",[223,1211,1212],{"class":233}," createError",[223,1214,1215],{"class":237},"({",[223,1217,1218],{"class":253}," statusCode",[223,1220,257],{"class":237},[223,1222,1223],{"class":350}," 404",[223,1225,1226],{"class":237}," }))\n",[939,1228,1230],{"id":1229},"hono","Hono",[214,1232,1234],{"className":216,"code":1233,"language":218,"meta":219,"style":219},"import { Hono, type Context } from 'hono'\nimport { createHandler } from 'tunables\u002Fserver'\n\nconst app = new Hono()\n\nconst handle = createHandler\u003CContext>({\n  tunables,\n  actor: (c) => {\n    const user = c.get('user')\n    return user ? { id: user.id, name: user.name } : undefined\n  },\n  authorize: c => c.get('user')?.role === 'admin'\n})\n\napp.all('\u002Fapi\u002Ftunables\u002F*', async c => (await handle(c.req.raw, c)) ?? c.notFound())\n",[162,1235,1236,1262,1280,1284,1301,1305,1321,1327,1344,1374,1414,1419,1457,1462,1467],{"__ignoreMap":219},[223,1237,1238,1240,1242,1245,1247,1249,1252,1254,1256,1258,1260],{"class":225,"line":226},[223,1239,568],{"class":229},[223,1241,339],{"class":237},[223,1243,1244],{"class":253}," Hono",[223,1246,331],{"class":237},[223,1248,571],{"class":229},[223,1250,1251],{"class":253}," Context ",[223,1253,579],{"class":237},[223,1255,582],{"class":229},[223,1257,347],{"class":237},[223,1259,1229],{"class":350},[223,1261,426],{"class":237},[223,1263,1264,1266,1268,1270,1272,1274,1276,1278],{"class":225,"line":250},[223,1265,568],{"class":229},[223,1267,339],{"class":237},[223,1269,978],{"class":253},[223,1271,579],{"class":237},[223,1273,582],{"class":229},[223,1275,347],{"class":237},[223,1277,178],{"class":350},[223,1279,426],{"class":237},[223,1281,1282],{"class":225,"line":263},[223,1283,594],{"emptyLinePlaceholder":593},[223,1285,1286,1288,1291,1293,1296,1298],{"class":225,"line":314},[223,1287,1017],{"class":229},[223,1289,1290],{"class":253}," app ",[223,1292,1023],{"class":237},[223,1294,1295],{"class":229}," new",[223,1297,1244],{"class":233},[223,1299,1300],{"class":237},"()\n",[223,1302,1303],{"class":225,"line":404},[223,1304,594],{"emptyLinePlaceholder":593},[223,1306,1307,1309,1311,1313,1315,1317,1319],{"class":225,"line":429},[223,1308,1017],{"class":229},[223,1310,1020],{"class":253},[223,1312,1023],{"class":237},[223,1314,1026],{"class":233},[223,1316,238],{"class":237},[223,1318,241],{"class":233},[223,1320,1034],{"class":237},[223,1322,1323,1325],{"class":225,"line":452},[223,1324,254],{"class":253},[223,1326,501],{"class":237},[223,1328,1329,1331,1333,1335,1338,1340,1342],{"class":225,"line":800},[223,1330,266],{"class":233},[223,1332,257],{"class":237},[223,1334,271],{"class":237},[223,1336,1337],{"class":253},"c",[223,1339,282],{"class":237},[223,1341,285],{"class":229},[223,1343,247],{"class":237},[223,1345,1346,1349,1352,1354,1358,1361,1364,1366,1368,1370,1372],{"class":225,"line":872},[223,1347,1348],{"class":229},"    const",[223,1350,1351],{"class":253}," user ",[223,1353,1023],{"class":237},[223,1355,1357],{"class":1356},"sq0K3"," c",[223,1359,662],{"class":1360},"sBd0f",[223,1362,1363],{"class":233},"get",[223,1365,1168],{"class":237},[223,1367,354],{"class":237},[223,1369,682],{"class":350},[223,1371,354],{"class":237},[223,1373,869],{"class":237},[223,1375,1376,1378,1380,1382,1384,1386,1388,1391,1393,1395,1397,1399,1401,1403,1405,1407,1409,1411],{"class":225,"line":878},[223,1377,803],{"class":229},[223,1379,1351],{"class":253},[223,1381,668],{"class":237},[223,1383,339],{"class":237},[223,1385,673],{"class":253},[223,1387,257],{"class":237},[223,1389,1390],{"class":253}," user",[223,1392,662],{"class":237},[223,1394,687],{"class":253},[223,1396,331],{"class":237},[223,1398,692],{"class":253},[223,1400,257],{"class":237},[223,1402,1390],{"class":253},[223,1404,662],{"class":237},[223,1406,705],{"class":253},[223,1408,579],{"class":237},[223,1410,710],{"class":237},[223,1412,1413],{"class":229}," undefined\n",[223,1415,1416],{"class":225,"line":1157},[223,1417,1418],{"class":237},"  },\n",[223,1420,1421,1423,1425,1428,1430,1432,1434,1436,1438,1440,1442,1444,1447,1449,1451,1453,1455],{"class":225,"line":1179},[223,1422,317],{"class":233},[223,1424,257],{"class":237},[223,1426,1427],{"class":253}," c ",[223,1429,656],{"class":229},[223,1431,1357],{"class":1356},[223,1433,662],{"class":1360},[223,1435,1363],{"class":233},[223,1437,1168],{"class":237},[223,1439,354],{"class":237},[223,1441,682],{"class":350},[223,1443,354],{"class":237},[223,1445,1446],{"class":237},")?.",[223,1448,814],{"class":253},[223,1450,776],{"class":237},[223,1452,347],{"class":237},[223,1454,821],{"class":350},[223,1456,426],{"class":237},[223,1458,1460],{"class":225,"line":1459},13,[223,1461,1150],{"class":237},[223,1463,1465],{"class":225,"line":1464},14,[223,1466,594],{"emptyLinePlaceholder":593},[223,1468,1470,1473,1475,1478,1480,1482,1485,1487,1489,1492,1494,1496,1498,1500,1502,1504,1506,1508,1511,1513,1516,1518,1520,1522,1524,1526,1528,1531],{"class":225,"line":1469},15,[223,1471,1472],{"class":1356},"app",[223,1474,662],{"class":1360},[223,1476,1477],{"class":233},"all",[223,1479,1168],{"class":237},[223,1481,354],{"class":237},[223,1483,1484],{"class":350},"\u002Fapi\u002Ftunables\u002F*",[223,1486,354],{"class":237},[223,1488,331],{"class":237},[223,1490,1491],{"class":229}," async",[223,1493,1427],{"class":253},[223,1495,656],{"class":229},[223,1497,271],{"class":237},[223,1499,1185],{"class":229},[223,1501,1188],{"class":233},[223,1503,1168],{"class":237},[223,1505,1337],{"class":253},[223,1507,662],{"class":237},[223,1509,1510],{"class":253},"req",[223,1512,662],{"class":237},[223,1514,1515],{"class":253},"raw",[223,1517,331],{"class":237},[223,1519,1357],{"class":253},[223,1521,1206],{"class":237},[223,1523,1209],{"class":237},[223,1525,1357],{"class":1356},[223,1527,662],{"class":1360},[223,1529,1530],{"class":233},"notFound",[223,1532,1533],{"class":237},"())\n",[939,1535,1537],{"id":1536},"bun-and-deno","Bun and Deno",[214,1539,1541],{"className":216,"code":1540,"language":218,"meta":219,"style":219},"const handle = createHandler\u003CRequest>({\n  tunables,\n  actor: request => sessionUser(request), \u002F\u002F your own sign-in\n  authorize: (request, { actor }) => isAdministrator(actor)\n})\n\nBun.serve({\n  async fetch(request) {\n    return (await handle(request)) ?? yourRoutes(request)\n  }\n})\n",[162,1542,1543,1559,1565,1590,1620,1624,1628,1641,1657,1684,1688],{"__ignoreMap":219},[223,1544,1545,1547,1549,1551,1553,1555,1557],{"class":225,"line":226},[223,1546,1017],{"class":229},[223,1548,1020],{"class":253},[223,1550,1023],{"class":237},[223,1552,1026],{"class":233},[223,1554,238],{"class":237},[223,1556,164],{"class":233},[223,1558,1034],{"class":237},[223,1560,1561,1563],{"class":225,"line":250},[223,1562,254],{"class":253},[223,1564,501],{"class":237},[223,1566,1567,1569,1571,1574,1576,1579,1581,1584,1586],{"class":225,"line":263},[223,1568,266],{"class":233},[223,1570,257],{"class":237},[223,1572,1573],{"class":253}," request ",[223,1575,656],{"class":229},[223,1577,1578],{"class":233}," sessionUser",[223,1580,1168],{"class":237},[223,1582,1583],{"class":253},"request",[223,1585,1201],{"class":237},[223,1587,1589],{"class":1588},"sKllz"," \u002F\u002F your own sign-in\n",[223,1591,1592,1594,1596,1598,1600,1602,1604,1607,1609,1611,1614,1616,1618],{"class":225,"line":314},[223,1593,317],{"class":233},[223,1595,257],{"class":237},[223,1597,271],{"class":237},[223,1599,1583],{"class":253},[223,1601,331],{"class":237},[223,1603,339],{"class":237},[223,1605,1606],{"class":253}," actor ",[223,1608,738],{"class":237},[223,1610,285],{"class":229},[223,1612,1613],{"class":233}," isAdministrator",[223,1615,1168],{"class":237},[223,1617,467],{"class":253},[223,1619,869],{"class":237},[223,1621,1622],{"class":225,"line":404},[223,1623,1150],{"class":237},[223,1625,1626],{"class":225,"line":429},[223,1627,594],{"emptyLinePlaceholder":593},[223,1629,1630,1633,1635,1638],{"class":225,"line":452},[223,1631,1632],{"class":1356},"Bun",[223,1634,662],{"class":1360},[223,1636,1637],{"class":233},"serve",[223,1639,1640],{"class":237},"({\n",[223,1642,1643,1646,1649,1651,1653,1655],{"class":225,"line":800},[223,1644,1645],{"class":229},"  async",[223,1647,1648],{"class":233}," fetch",[223,1650,1168],{"class":237},[223,1652,1583],{"class":253},[223,1654,282],{"class":237},[223,1656,247],{"class":237},[223,1658,1659,1661,1663,1665,1667,1669,1671,1673,1675,1678,1680,1682],{"class":225,"line":872},[223,1660,803],{"class":229},[223,1662,271],{"class":237},[223,1664,1185],{"class":229},[223,1666,1188],{"class":233},[223,1668,1168],{"class":237},[223,1670,1583],{"class":253},[223,1672,1206],{"class":237},[223,1674,1209],{"class":237},[223,1676,1677],{"class":233}," yourRoutes",[223,1679,1168],{"class":237},[223,1681,1583],{"class":253},[223,1683,869],{"class":237},[223,1685,1686],{"class":225,"line":878},[223,1687,875],{"class":237},[223,1689,1690],{"class":225,"line":1157},[223,1691,1150],{"class":237},[133,1693,1694,1697],{},[162,1695,1696],{},"Deno.serve"," takes the same function.",[133,1699,1700,1708,1709,662],{},[522,1701,1705],{"href":1702,"rel":1703},"https:\u002F\u002Fgithub.com\u002Fitai-gendler\u002Ftunables\u002Ftree\u002Fmain\u002Fexamples\u002Fhono",[1704],"nofollow",[162,1706,1707],{},"examples\u002Fhono","\nruns Tasklane this way, with its own route beside the handler:\n",[162,1710,1711],{},"npx jiti examples\u002Fhono\u002Fserver.ts",[939,1713,1715],{"id":1714},"routes","Routes",[137,1717,1718,1731],{},[140,1719,1720],{},[143,1721,1722,1725,1728],{},[146,1723,1724],{},"Method",[146,1726,1727],{},"Path",[146,1729,1730],{},"Does",[153,1732,1733,1748,1762,1784,1799,1816,1829,1845,1862,1876,1890,1908,1924,1938],{},[143,1734,1735,1740,1745],{},[158,1736,1737],{},[162,1738,1739],{},"GET",[158,1741,1742],{},[162,1743,1744],{},"describe",[158,1746,1747],{},"The definitions, as the screens need them",[143,1749,1750,1754,1759],{},[158,1751,1752],{},[162,1753,1739],{},[158,1755,1756],{},[162,1757,1758],{},"groups",[158,1760,1761],{},"Every group's entry",[143,1763,1764,1772,1777],{},[158,1765,1766,1768,1769],{},[162,1767,1739],{}," \u002F ",[162,1770,1771],{},"PUT",[158,1773,1774],{},[162,1775,1776],{},"groups\u002F:id",[158,1778,1779,1780,1783],{},"Read a group's entry, at ",[162,1781,1782],{},"?scope="," when given; save a change",[143,1785,1786,1791,1796],{},[158,1787,1788],{},[162,1789,1790],{},"POST",[158,1792,1793],{},[162,1794,1795],{},"groups\u002F:id\u002Freset",[158,1797,1798],{},"Reset fields to their defaults, or at a scope to what it inherits",[143,1800,1801,1805,1810],{},[158,1802,1803],{},[162,1804,1739],{},[158,1806,1807],{},[162,1808,1809],{},"history?kind=&name=&id=&limit=&before=",[158,1811,1812,1813,1815],{},"A target's revisions; for a scope's, ",[162,1814,687],{}," is its key",[143,1817,1818,1822,1826],{},[158,1819,1820],{},[162,1821,1790],{},[158,1823,1824],{},[162,1825,903],{},[158,1827,1828],{},"Restore a group's revision",[143,1830,1831,1837,1842],{},[158,1832,1833,1768,1835],{},[162,1834,1739],{},[162,1836,1790],{},[158,1838,1839],{},[162,1840,1841],{},"presets\u002F:set",[158,1843,1844],{},"List a set's presets; create one",[143,1846,1847,1854,1859],{},[158,1848,1849,1768,1851],{},[162,1850,1771],{},[162,1852,1853],{},"DELETE",[158,1855,1856],{},[162,1857,1858],{},"presets\u002F:set\u002F:key",[158,1860,1861],{},"Update a preset; remove it",[143,1863,1864,1868,1873],{},[158,1865,1866],{},[162,1867,1790],{},[158,1869,1870],{},[162,1871,1872],{},"presets\u002F:set\u002F:key\u002Fdefault",[158,1874,1875],{},"Make a preset its slot's default",[143,1877,1878,1882,1887],{},[158,1879,1880],{},[162,1881,1739],{},[158,1883,1884],{},[162,1885,1886],{},"presets\u002F:set\u002Fassignments?subject=",[158,1888,1889],{},"Assignments",[143,1891,1892,1896,1901],{},[158,1893,1894],{},[162,1895,1771],{},[158,1897,1898],{},[162,1899,1900],{},"presets\u002F:set\u002Fassignments\u002F:subject\u002F:slot",[158,1902,1903,1904,1907],{},"Assign a preset, or ",[162,1905,1906],{},"{ key: null }"," to follow the default",[143,1909,1910,1916,1921],{},[158,1911,1912,1768,1914],{},[162,1913,1739],{},[162,1915,1790],{},[158,1917,1918],{},[162,1919,1920],{},"catalogs\u002F:id",[158,1922,1923],{},"List entries with what uses each; add one",[143,1925,1926,1930,1935],{},[158,1927,1928],{},[162,1929,1771],{},[158,1931,1932],{},[162,1933,1934],{},"catalogs\u002F:id\u002F:entry",[158,1936,1937],{},"Edit an entry",[143,1939,1940,1944,1952],{},[158,1941,1942],{},[162,1943,1790],{},[158,1945,1946,505,1949],{},[162,1947,1948],{},"catalogs\u002F:id\u002F:entry\u002Fretire",[162,1950,1951],{},"…\u002Freinstate",[158,1953,1954],{},"Retire or reinstate an entry",[133,1956,1957,1958,1961,1962,1965,1966,1969,1970,1973,1974,1976,1977,1980],{},"Bodies are JSON. A save sends ",[162,1959,1960],{},"{ values, reason?, confirmed?, scope? }",", and a\nreset ",[162,1963,1964],{},"{ keys?, reason?, scope? }",". A scope is an object,\n",[162,1967,1968],{},"{ \"workspace\": \"acme\" }",", or its key, ",[162,1971,1972],{},"workspace=acme;user=dana",", which is\nalso how ",[162,1975,1782],{}," writes it. A value of ",[162,1978,1979],{},"null"," at a scope uses the inherited\nvalue again.",[939,1982,1984],{"id":1983},"answers","Answers",[457,1986,1987,2011,2014,2021,2024],{},[460,1988,1989,1990,1994,1995,1998,1999,2002,2003,2006,2007,2010],{},"A change answers 200 with its ",[522,1991,1993],{"href":1992},".\u002Fsettings-and-groups#saving-values","outcome",",\nwhether it was saved or refused. ",[463,1996,1997],{},"A refused change is an answer, not an\nerror",": it carries ",[162,2000,2001],{},"saved: false"," and its ",[162,2004,2005],{},"problems",", or a ",[162,2008,2009],{},"confirm","\nquestion.",[460,2012,2013],{},"A malformed request answers 400, naming what is wrong, but only to someone\nallowed to read the description; anyone else is refused as for any other\nrequest.",[460,2015,2016,2017,2020],{},"A refused request answers 404, or 401 and 403 with ",[162,2018,2019],{},"refusal: 'forbidden'",".\nAn unknown group, preset set or catalog answers 404.",[460,2022,2023],{},"Stored values that no longer pass, or a group not yet synced, answer 409\nwith the error's message.",[460,2025,2026,2027,2031],{},"Anything else answers 500 with ",[2028,2029,2030],"em",{},"\"Something went wrong on the server.\"",",\nlogs the error, and names nothing internal.",[206,2033,183],{"id":2034},"express",[214,2036,2038],{"className":216,"code":2037,"language":218,"meta":219,"style":219},"import express from 'express'\nimport { tunablesMiddleware } from 'tunables\u002Fexpress'\n\nconst app = express()\napp.use(express.json())\napp.use(tunablesMiddleware({\n  tunables,\n  \u002F\u002F req.user as your sign-in middleware sets it\n  actor: req => req.user ? { id: req.user.id, name: req.user.name } : undefined,\n  authorize: (req, { action }) => action === 'read' ? Boolean(req.user?.staff) : req.user?.role === 'admin'\n}))\n",[162,2039,2040,2056,2075,2079,2092,2112,2126,2132,2137,2197,2269],{"__ignoreMap":219},[223,2041,2042,2044,2047,2050,2052,2054],{"class":225,"line":226},[223,2043,568],{"class":229},[223,2045,2046],{"class":253}," express ",[223,2048,2049],{"class":229},"from",[223,2051,347],{"class":237},[223,2053,2034],{"class":350},[223,2055,426],{"class":237},[223,2057,2058,2060,2062,2065,2067,2069,2071,2073],{"class":225,"line":250},[223,2059,568],{"class":229},[223,2061,339],{"class":237},[223,2063,2064],{"class":253}," tunablesMiddleware ",[223,2066,579],{"class":237},[223,2068,582],{"class":229},[223,2070,347],{"class":237},[223,2072,191],{"class":350},[223,2074,426],{"class":237},[223,2076,2077],{"class":225,"line":263},[223,2078,594],{"emptyLinePlaceholder":593},[223,2080,2081,2083,2085,2087,2090],{"class":225,"line":314},[223,2082,1017],{"class":229},[223,2084,1290],{"class":253},[223,2086,1023],{"class":237},[223,2088,2089],{"class":233}," express",[223,2091,1300],{"class":237},[223,2093,2094,2096,2098,2101,2103,2105,2107,2110],{"class":225,"line":404},[223,2095,1472],{"class":1356},[223,2097,662],{"class":1360},[223,2099,2100],{"class":233},"use",[223,2102,1168],{"class":237},[223,2104,2034],{"class":1356},[223,2106,662],{"class":1360},[223,2108,2109],{"class":233},"json",[223,2111,1533],{"class":237},[223,2113,2114,2116,2118,2120,2122,2124],{"class":225,"line":429},[223,2115,1472],{"class":1356},[223,2117,662],{"class":1360},[223,2119,2100],{"class":233},[223,2121,1168],{"class":237},[223,2123,188],{"class":233},[223,2125,1640],{"class":237},[223,2127,2128,2130],{"class":225,"line":452},[223,2129,254],{"class":253},[223,2131,501],{"class":237},[223,2133,2134],{"class":225,"line":800},[223,2135,2136],{"class":1588},"  \u002F\u002F req.user as your sign-in middleware sets it\n",[223,2138,2139,2141,2143,2146,2148,2151,2153,2155,2157,2159,2161,2163,2165,2167,2169,2171,2173,2175,2177,2179,2181,2183,2185,2187,2189,2191,2193,2195],{"class":225,"line":872},[223,2140,266],{"class":233},[223,2142,257],{"class":237},[223,2144,2145],{"class":253}," req ",[223,2147,656],{"class":229},[223,2149,2150],{"class":253}," req",[223,2152,662],{"class":237},[223,2154,665],{"class":253},[223,2156,668],{"class":237},[223,2158,339],{"class":237},[223,2160,673],{"class":253},[223,2162,257],{"class":237},[223,2164,2150],{"class":253},[223,2166,662],{"class":237},[223,2168,682],{"class":253},[223,2170,662],{"class":237},[223,2172,687],{"class":253},[223,2174,331],{"class":237},[223,2176,692],{"class":253},[223,2178,257],{"class":237},[223,2180,2150],{"class":253},[223,2182,662],{"class":237},[223,2184,682],{"class":253},[223,2186,662],{"class":237},[223,2188,705],{"class":253},[223,2190,579],{"class":237},[223,2192,710],{"class":237},[223,2194,294],{"class":229},[223,2196,501],{"class":237},[223,2198,2199,2201,2203,2205,2207,2209,2211,2214,2216,2218,2220,2222,2224,2226,2228,2231,2234,2236,2238,2240,2242,2244,2247,2249,2251,2253,2255,2257,2259,2261,2263,2265,2267],{"class":225,"line":878},[223,2200,317],{"class":233},[223,2202,257],{"class":237},[223,2204,271],{"class":237},[223,2206,1510],{"class":253},[223,2208,331],{"class":237},[223,2210,339],{"class":237},[223,2212,2213],{"class":253}," action ",[223,2215,738],{"class":237},[223,2217,285],{"class":229},[223,2219,2213],{"class":253},[223,2221,776],{"class":237},[223,2223,347],{"class":237},[223,2225,351],{"class":350},[223,2227,354],{"class":237},[223,2229,2230],{"class":237}," ?",[223,2232,2233],{"class":233}," Boolean",[223,2235,1168],{"class":237},[223,2237,1510],{"class":253},[223,2239,662],{"class":237},[223,2241,682],{"class":253},[223,2243,1135],{"class":237},[223,2245,2246],{"class":253},"staff",[223,2248,282],{"class":237},[223,2250,710],{"class":237},[223,2252,2150],{"class":253},[223,2254,662],{"class":237},[223,2256,682],{"class":253},[223,2258,1135],{"class":237},[223,2260,814],{"class":253},[223,2262,776],{"class":237},[223,2264,347],{"class":237},[223,2266,821],{"class":350},[223,2268,426],{"class":237},[223,2270,2271],{"class":225,"line":1157},[223,2272,2273],{"class":237},"}))\n",[133,2275,2276,2277,2279,2280,2283,2284,165,2286,2288,2289,2296,2297,662],{},"The middleware answers under its ",[162,2278,925],{}," and calls ",[162,2281,2282],{},"next()"," for everything\nelse. Its context is the Express request, so ",[162,2285,467],{},[162,2287,483],{}," read your\nsession from it. It works with or without a body parser in front of it.\n",[522,2290,2293],{"href":2291,"rel":2292},"https:\u002F\u002Fgithub.com\u002Fitai-gendler\u002Ftunables\u002Ftree\u002Fmain\u002Fexamples\u002Fexpress",[1704],[162,2294,2295],{},"examples\u002Fexpress","\nruns Tasklane this way: ",[162,2298,2299],{},"npx jiti examples\u002Fexpress\u002Fserver.ts",[206,2301,2303],{"id":2302},"orpc","oRPC",[133,2305,2306,2308],{},[162,2307,201],{}," returns tunables' operations as oRPC procedures, to nest in\nyour own router. They receive your router's context, so access reads the same\nsession the rest of your app does.",[214,2310,2312],{"className":216,"code":2311,"language":218,"meta":219,"style":219},"import { tunablesRouter } from 'tunables\u002Forpc'\n\nexport const router = {\n  ...appRouter,\n  tunables: tunablesRouter\u003CAppContext>({\n    tunables,\n    actor: context => context.user ? { id: context.user.id, name: context.user.name } : undefined,\n    authorize: context => context.user?.role === 'admin'\n  })\n}\n",[162,2313,2314,2333,2337,2350,2360,2375,2382,2441,2470,2475],{"__ignoreMap":219},[223,2315,2316,2318,2320,2323,2325,2327,2329,2331],{"class":225,"line":226},[223,2317,568],{"class":229},[223,2319,339],{"class":237},[223,2321,2322],{"class":253}," tunablesRouter ",[223,2324,579],{"class":237},[223,2326,582],{"class":229},[223,2328,347],{"class":237},[223,2330,204],{"class":350},[223,2332,426],{"class":237},[223,2334,2335],{"class":225,"line":250},[223,2336,594],{"emptyLinePlaceholder":593},[223,2338,2339,2341,2343,2346,2348],{"class":225,"line":263},[223,2340,599],{"class":229},[223,2342,602],{"class":229},[223,2344,2345],{"class":253}," router ",[223,2347,1023],{"class":237},[223,2349,247],{"class":237},[223,2351,2352,2355,2358],{"class":225,"line":314},[223,2353,2354],{"class":237},"  ...",[223,2356,2357],{"class":253},"appRouter",[223,2359,501],{"class":237},[223,2361,2362,2364,2366,2369,2371,2373],{"class":225,"line":404},[223,2363,254],{"class":253},[223,2365,257],{"class":237},[223,2367,2368],{"class":233}," tunablesRouter",[223,2370,238],{"class":237},[223,2372,620],{"class":233},[223,2374,1034],{"class":237},[223,2376,2377,2380],{"class":225,"line":429},[223,2378,2379],{"class":253},"    tunables",[223,2381,501],{"class":237},[223,2383,2384,2387,2389,2391,2393,2395,2397,2399,2401,2403,2405,2407,2409,2411,2413,2415,2417,2419,2421,2423,2425,2427,2429,2431,2433,2435,2437,2439],{"class":225,"line":452},[223,2385,2386],{"class":233},"    actor",[223,2388,257],{"class":237},[223,2390,653],{"class":253},[223,2392,656],{"class":229},[223,2394,659],{"class":253},[223,2396,662],{"class":237},[223,2398,665],{"class":253},[223,2400,668],{"class":237},[223,2402,339],{"class":237},[223,2404,673],{"class":253},[223,2406,257],{"class":237},[223,2408,659],{"class":253},[223,2410,662],{"class":237},[223,2412,682],{"class":253},[223,2414,662],{"class":237},[223,2416,687],{"class":253},[223,2418,331],{"class":237},[223,2420,692],{"class":253},[223,2422,257],{"class":237},[223,2424,659],{"class":253},[223,2426,662],{"class":237},[223,2428,682],{"class":253},[223,2430,662],{"class":237},[223,2432,705],{"class":253},[223,2434,579],{"class":237},[223,2436,710],{"class":237},[223,2438,294],{"class":229},[223,2440,501],{"class":237},[223,2442,2443,2446,2448,2450,2452,2454,2456,2458,2460,2462,2464,2466,2468],{"class":225,"line":800},[223,2444,2445],{"class":233},"    authorize",[223,2447,257],{"class":237},[223,2449,653],{"class":253},[223,2451,656],{"class":229},[223,2453,659],{"class":253},[223,2455,662],{"class":237},[223,2457,682],{"class":253},[223,2459,1135],{"class":237},[223,2461,814],{"class":253},[223,2463,776],{"class":237},[223,2465,347],{"class":237},[223,2467,821],{"class":350},[223,2469,426],{"class":237},[223,2471,2472],{"class":225,"line":872},[223,2473,2474],{"class":237},"  })\n",[223,2476,2477],{"class":225,"line":878},[223,2478,455],{"class":237},[133,2480,2481,2482,2484,2485,505,2488,505,2491,501,2494,505,2497,491,2500,2503,2504,2510,2511,2514,2515,662],{},"A refused change is an answer with ",[162,2483,2005],{},", as over HTTP. Errors become\noRPC errors with the matching code: ",[162,2486,2487],{},"BAD_REQUEST",[162,2489,2490],{},"UNAUTHORIZED",[162,2492,2493],{},"FORBIDDEN",[162,2495,2496],{},"NOT_FOUND",[162,2498,2499],{},"CONFLICT",[162,2501,2502],{},"INTERNAL_SERVER_ERROR",". The screens call the router\nthrough ",[522,2505,2507],{"href":2506},".\u002Fnuxt-layer#another-client",[162,2508,2509],{},"orpcClient",". The repository's\n",[162,2512,2513],{},"playground\u002F"," nests the router this way; see\n",[522,2516,2518],{"href":2517},".\u002Ftasklane-example#the-playground","Tasklane",[206,2520,2522],{"id":2521},"the-service-itself","The service itself",[133,2524,2525,175,2528,2530],{},[162,2526,2527],{},"createService(options)",[162,2529,178],{}," is the service every adapter\nwraps: one method per operation, each taking your context first. Use it to\nserve tunables over a transport none of the adapters cover.",[2532,2533,2534],"style",{},"html pre.shiki code .sZ4mS, html code.shiki .sZ4mS{--shiki-light:#115E59;--shiki-default:#115E59;--shiki-dark:#5EEAD4}html pre.shiki code .sOThN, html code.shiki .sOThN{--shiki-light:#2A3439;--shiki-light-font-weight:bold;--shiki-default:#2A3439;--shiki-default-font-weight:bold;--shiki-dark:#F6F8F8;--shiki-dark-font-weight:bold}html pre.shiki code .sp9ci, html code.shiki .sp9ci{--shiki-light:#4F5D63;--shiki-default:#4F5D63;--shiki-dark:#A3AFB4}html pre.shiki code .sVz71, html code.shiki .sVz71{--shiki-light:#182025;--shiki-default:#182025;--shiki-dark:#E6EBEC}html pre.shiki code .sjCzm, html code.shiki .sjCzm{--shiki-light:#92400E;--shiki-default:#92400E;--shiki-dark:#FCD34D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sq0K3, html code.shiki .sq0K3{--shiki-light:#182025;--shiki-light-font-weight:bold;--shiki-default:#182025;--shiki-default-font-weight:bold;--shiki-dark:#E6EBEC;--shiki-dark-font-weight:bold}html pre.shiki code .sBd0f, html code.shiki .sBd0f{--shiki-light:#4F5D63;--shiki-light-font-weight:bold;--shiki-default:#4F5D63;--shiki-default-font-weight:bold;--shiki-dark:#A3AFB4;--shiki-dark-font-weight:bold}html pre.shiki code .sKllz, html code.shiki .sKllz{--shiki-light:#4F5D63;--shiki-light-font-style:italic;--shiki-default:#4F5D63;--shiki-default-font-style:italic;--shiki-dark:#A3AFB4;--shiki-dark-font-style:italic}",{"title":219,"searchDepth":250,"depth":250,"links":2536},[2537,2538,2545,2546,2547],{"id":208,"depth":250,"text":209},{"id":910,"depth":250,"text":911,"children":2539},[2540,2541,2542,2543,2544],{"id":941,"depth":263,"text":942},{"id":1229,"depth":263,"text":1230},{"id":1536,"depth":263,"text":1537},{"id":1714,"depth":263,"text":1715},{"id":1983,"depth":263,"text":1984},{"id":2034,"depth":250,"text":183},{"id":2302,"depth":250,"text":2303},{"id":2521,"depth":250,"text":2522},"md",{},{"title":57,"description":58},"docs\u002Fserver","_pPY6fTF0NkBVyoCwKB3YTgbPhQvamIuiH6uM5z-iE8",1792845117973]