[{"data":1,"prerenderedAt":2731},["ShallowReactive",2],{"highlight:import \"github.com/zoobz-io/slush\"\n\n// Initialize and register services\nk := slush.Start()\nhandle := slush.Register[UserService](k, &userServiceImpl{})\n\n// Chain guards — each validates context before access\nhandle.Guard(func(ctx context.Context) error {\n    caller := sctx.FromContext(ctx)\n    if !caller.HasScope(\"users:read\") {\n        return errors.New(\"insufficient permissions\")\n    }\n    return nil\n})\n\n// Lock registry — no more registration allowed\nslush.Freeze(k)\n\n// Retrieve with guard validation\nsvc, err := slush.Use[UserService](ctx)\nif err != nil {\n    // Guard denied access — not \"service not found\"\n    log.Fatal(err)\n}\n\n// Introspect registered services\nfor _, info := range slush.Services(k) {\n    fmt.Println(info.Name, info.Version)\n}":3,"footer-resources":4,"search-sections-slush":1793,"nav-slush":2634,"content-tree-slush":2685,"content-table-nav-slush":2708},"\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-keyword)\">import\u003C/span>\u003Cspan style=\"color:var(--shiki-string)\"> \"github.com/zoobz-io/slush\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-comment)\">// Initialize and register services\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-text)\">k\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\"> :=\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\"> slush\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">.\u003C/span>\u003Cspan style=\"color:var(--shiki-function)\">Start\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">()\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-text)\">handle\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\"> :=\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\"> slush\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">.\u003C/span>\u003Cspan style=\"color:var(--shiki-function)\">Register\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">[\u003C/span>\u003Cspan style=\"color:var(--shiki-type)\">UserService\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">](\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\">k\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">,\u003C/span>\u003Cspan style=\"color:var(--shiki-operator)\"> &#x26;\u003C/span>\u003Cspan style=\"color:var(--shiki-type)\">userServiceImpl\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">{})\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-comment)\">// Chain guards — each validates context before access\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-text)\">handle\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">.\u003C/span>\u003Cspan style=\"color:var(--shiki-function)\">Guard\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">(\u003C/span>\u003Cspan style=\"color:var(--shiki-keyword)\">func\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">(\u003C/span>\u003Cspan style=\"color:var(--shiki-parameter)\">ctx\u003C/span>\u003Cspan style=\"color:var(--shiki-type)\"> context\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">.\u003C/span>\u003Cspan style=\"color:var(--shiki-type)\">Context\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">)\u003C/span>\u003Cspan style=\"color:var(--shiki-type)\"> error\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-text)\">    caller\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\"> :=\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\"> sctx\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">.\u003C/span>\u003Cspan style=\"color:var(--shiki-function)\">FromContext\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">(\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\">ctx\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-operator)\">    if\u003C/span>\u003Cspan style=\"color:var(--shiki-operator)\"> !\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\">caller\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">.\u003C/span>\u003Cspan style=\"color:var(--shiki-function)\">HasScope\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">(\u003C/span>\u003Cspan style=\"color:var(--shiki-string)\">\"users:read\"\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">)\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-operator)\">        return\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\"> errors\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">.\u003C/span>\u003Cspan style=\"color:var(--shiki-function)\">New\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">(\u003C/span>\u003Cspan style=\"color:var(--shiki-string)\">\"insufficient permissions\"\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-punctuation)\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-operator)\">    return\u003C/span>\u003Cspan style=\"color:var(--shiki-keyword)\"> nil\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-punctuation)\">})\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-comment)\">// Lock registry — no more registration allowed\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-text)\">slush\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">.\u003C/span>\u003Cspan style=\"color:var(--shiki-function)\">Freeze\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">(\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\">k\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-comment)\">// Retrieve with guard validation\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-text)\">svc\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">,\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\"> err\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\"> :=\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\"> slush\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">.\u003C/span>\u003Cspan style=\"color:var(--shiki-function)\">Use\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">[\u003C/span>\u003Cspan style=\"color:var(--shiki-type)\">UserService\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">](\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\">ctx\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-operator)\">if\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\"> err\u003C/span>\u003Cspan style=\"color:var(--shiki-operator)\"> !=\u003C/span>\u003Cspan style=\"color:var(--shiki-keyword)\"> nil\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-comment)\">    // Guard denied access — not \"service not found\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-text)\">    log\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">.\u003C/span>\u003Cspan style=\"color:var(--shiki-function)\">Fatal\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">(\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\">err\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-punctuation)\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-comment)\">// Introspect registered services\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-operator)\">for\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\"> _\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">,\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\"> info\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\"> :=\u003C/span>\u003Cspan style=\"color:var(--shiki-operator)\"> range\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\"> slush\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">.\u003C/span>\u003Cspan style=\"color:var(--shiki-function)\">Services\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">(\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\">k\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">)\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-text)\">    fmt\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">.\u003C/span>\u003Cspan style=\"color:var(--shiki-function)\">Println\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">(\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\">info\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">.\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\">Name\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">,\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\"> info\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">.\u003C/span>\u003Cspan style=\"color:var(--shiki-text)\">Version\u003C/span>\u003Cspan style=\"color:var(--shiki-punctuation)\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:var(--shiki-punctuation)\">}\u003C/span>\u003C/span>",[5,1459,1562],{"id":6,"title":7,"body":8,"description":102,"extension":1452,"icon":1453,"meta":1454,"navigation":171,"path":1455,"seo":1456,"stem":1457,"__hash__":1458},"resources/readme.md","README",{"type":9,"value":10,"toc":1441},"minimark",[11,15,85,88,91,96,260,267,271,288,291,295,976,980,1048,1052,1097,1101,1110,1345,1348,1359,1363,1366,1411,1415,1427,1431,1437],[12,13,14],"h1",{"id":14},"slush",[16,17,18,29,37,45,53,61,69,77],"p",{},[19,20,24],"a",{"href":21,"rel":22},"https://github.com/zoobz-io/slush/actions/workflows/ci.yml",[23],"nofollow",[25,26],"img",{"alt":27,"src":28},"CI","https://github.com/zoobz-io/slush/actions/workflows/ci.yml/badge.svg",[19,30,33],{"href":31,"rel":32},"https://codecov.io/gh/zoobz-io/slush",[23],[25,34],{"alt":35,"src":36},"codecov","https://codecov.io/gh/zoobz-io/slush/branch/main/graph/badge.svg",[19,38,41],{"href":39,"rel":40},"https://goreportcard.com/report/github.com/zoobz-io/slush",[23],[25,42],{"alt":43,"src":44},"Go Report Card","https://goreportcard.com/badge/github.com/zoobz-io/slush",[19,46,49],{"href":47,"rel":48},"https://github.com/zoobz-io/slush/actions/workflows/codeql.yml",[23],[25,50],{"alt":51,"src":52},"CodeQL","https://github.com/zoobz-io/slush/actions/workflows/codeql.yml/badge.svg",[19,54,57],{"href":55,"rel":56},"https://pkg.go.dev/github.com/zoobz-io/slush",[23],[25,58],{"alt":59,"src":60},"Go Reference","https://pkg.go.dev/badge/github.com/zoobz-io/slush.svg",[19,62,65],{"href":63,"rel":64},"https://opensource.org/licenses/MIT",[23],[25,66],{"alt":67,"src":68},"License: MIT","https://img.shields.io/badge/License-MIT-yellow.svg",[19,70,73],{"href":71,"rel":72},"https://github.com/zoobz-io/slush",[23],[25,74],{"alt":75,"src":76},"Go Version","https://img.shields.io/github/go-mod/go-version/zoobz-io/slush",[19,78,81],{"href":79,"rel":80},"https://github.com/zoobz-io/slush/releases",[23],[25,82],{"alt":83,"src":84},"Release","https://img.shields.io/github/v/release/zoobz-io/slush",[16,86,87],{},"Services behind gates.",[16,89,90],{},"A type-safe service locator where every lookup passes through guards—composable access checks that validate context before returning the service.",[92,93,95],"h2",{"id":94},"guarded-lookup","Guarded Lookup",[97,98,103],"pre",{"className":99,"code":100,"language":101,"meta":102,"style":102},"language-go shiki shiki-themes","// Register with guards\nslush.Register[Database](db).\n    Guard(requirePermission(\"db:read\"))\n\n// Lookup validates context\ndb, err := slush.Use[Database](ctx)\nif errors.Is(err, slush.ErrAccessDenied) {\n    // Guard rejected the request\n}\n","go","",[104,105,106,115,145,166,173,179,213,248,254],"code",{"__ignoreMap":102},[107,108,111],"span",{"class":109,"line":110},"line",1,[107,112,114],{"class":113},"sLkEo","// Register with guards\n",[107,116,118,121,125,129,132,136,139,142],{"class":109,"line":117},2,[107,119,14],{"class":120},"sh8_p",[107,122,124],{"class":123},"sq5bi",".",[107,126,128],{"class":127},"s5klm","Register",[107,130,131],{"class":123},"[",[107,133,135],{"class":134},"sYBwO","Database",[107,137,138],{"class":123},"](",[107,140,141],{"class":120},"db",[107,143,144],{"class":123},").\n",[107,146,148,151,154,157,159,163],{"class":109,"line":147},3,[107,149,150],{"class":127},"    Guard",[107,152,153],{"class":123},"(",[107,155,156],{"class":127},"requirePermission",[107,158,153],{"class":123},[107,160,162],{"class":161},"sxAnc","\"db:read\"",[107,164,165],{"class":123},"))\n",[107,167,169],{"class":109,"line":168},4,[107,170,172],{"emptyLinePlaceholder":171},true,"\n",[107,174,176],{"class":109,"line":175},5,[107,177,178],{"class":113},"// Lookup validates context\n",[107,180,182,184,187,190,193,196,198,201,203,205,207,210],{"class":109,"line":181},6,[107,183,141],{"class":120},[107,185,186],{"class":123},",",[107,188,189],{"class":120}," err",[107,191,192],{"class":120}," :=",[107,194,195],{"class":120}," slush",[107,197,124],{"class":123},[107,199,200],{"class":127},"Use",[107,202,131],{"class":123},[107,204,135],{"class":134},[107,206,138],{"class":123},[107,208,209],{"class":120},"ctx",[107,211,212],{"class":123},")\n",[107,214,216,220,223,225,228,230,233,235,237,239,242,245],{"class":109,"line":215},7,[107,217,219],{"class":218},"sW3Qg","if",[107,221,222],{"class":120}," errors",[107,224,124],{"class":123},[107,226,227],{"class":127},"Is",[107,229,153],{"class":123},[107,231,232],{"class":120},"err",[107,234,186],{"class":123},[107,236,195],{"class":120},[107,238,124],{"class":123},[107,240,241],{"class":120},"ErrAccessDenied",[107,243,244],{"class":123},")",[107,246,247],{"class":123}," {\n",[107,249,251],{"class":109,"line":250},8,[107,252,253],{"class":113},"    // Guard rejected the request\n",[107,255,257],{"class":109,"line":256},9,[107,258,259],{"class":123},"}\n",[16,261,262,263,266],{},"Guards are just functions: ",[104,264,265],{},"func(context.Context) error",". Chain them, compose them, bring your own validation.",[92,268,270],{"id":269},"install","Install",[97,272,276],{"className":273,"code":274,"language":275,"meta":102,"style":102},"language-bash shiki shiki-themes","go get github.com/zoobz-io/slush\n","bash",[104,277,278],{"__ignoreMap":102},[107,279,280,282,285],{"class":109,"line":110},[107,281,101],{"class":127},[107,283,284],{"class":161}," get",[107,286,287],{"class":161}," github.com/zoobz-io/slush\n",[16,289,290],{},"Requires Go 1.24+.",[92,292,294],{"id":293},"quick-start","Quick Start",[97,296,298],{"className":99,"code":297,"language":101,"meta":102,"style":102},"package main\n\nimport (\n    \"context\"\n    \"errors\"\n    \"log\"\n\n    \"github.com/zoobz-io/slush\"\n)\n\n// Define service contracts as interfaces\ntype Database interface {\n    Query(ctx context.Context, sql string) ([]Row, error)\n}\n\ntype Mailer interface {\n    Send(ctx context.Context, to, subject, body string) error\n}\n\nfunc main() {\n    // Create services (you own lifecycle)\n    db := postgres.New(cfg.DatabaseURL)\n    defer db.Close()\n\n    mailer := sendgrid.New(cfg.APIKey)\n\n    // Register by interface type, configure guards\n    slush.Register[Database](db).\n        Guard(requireRole(\"service\"))\n\n    slush.Register[Mailer](mailer).\n        Guard(requireRole(\"service\")).\n        Guard(requirePermission(\"mailer:send\"))\n\n    // Start your server...\n}\n\nfunc handleRequest(ctx context.Context) error {\n    // Context carries caller identity\n    db, err := slush.Use[Database](ctx)\n    if err != nil {\n        return err // ErrNotFound or ErrAccessDenied\n    }\n\n    rows, err := db.Query(ctx, \"SELECT ...\")\n    // ...\n}\n\nfunc requireRole(role string) slush.Guard {\n    return func(ctx context.Context) error {\n        if !hasRole(ctx, role) {\n            return errors.New(\"missing role: \" + role)\n        }\n        return nil\n    }\n}\n",[104,299,300,309,313,322,327,332,337,341,346,350,355,361,375,417,422,427,439,477,482,487,501,507,535,552,557,583,588,594,614,632,637,658,674,690,695,701,706,711,735,741,768,784,795,801,806,836,842,847,852,878,903,928,952,958,966,971],{"__ignoreMap":102},[107,301,302,306],{"class":109,"line":110},[107,303,305],{"class":304},"sUt3r","package",[107,307,308],{"class":134}," main\n",[107,310,311],{"class":109,"line":117},[107,312,172],{"emptyLinePlaceholder":171},[107,314,315,318],{"class":109,"line":147},[107,316,317],{"class":304},"import",[107,319,321],{"class":320},"soy-K"," (\n",[107,323,324],{"class":109,"line":168},[107,325,326],{"class":161},"    \"context\"\n",[107,328,329],{"class":109,"line":175},[107,330,331],{"class":161},"    \"errors\"\n",[107,333,334],{"class":109,"line":181},[107,335,336],{"class":161},"    \"log\"\n",[107,338,339],{"class":109,"line":215},[107,340,172],{"emptyLinePlaceholder":171},[107,342,343],{"class":109,"line":250},[107,344,345],{"class":161},"    \"github.com/zoobz-io/slush\"\n",[107,347,348],{"class":109,"line":256},[107,349,212],{"class":320},[107,351,353],{"class":109,"line":352},10,[107,354,172],{"emptyLinePlaceholder":171},[107,356,358],{"class":109,"line":357},11,[107,359,360],{"class":113},"// Define service contracts as interfaces\n",[107,362,364,367,370,373],{"class":109,"line":363},12,[107,365,366],{"class":304},"type",[107,368,369],{"class":134}," Database",[107,371,372],{"class":304}," interface",[107,374,247],{"class":123},[107,376,378,381,383,386,389,391,394,396,399,402,404,407,410,412,415],{"class":109,"line":377},13,[107,379,380],{"class":127},"    Query",[107,382,153],{"class":123},[107,384,209],{"class":385},"sSYET",[107,387,388],{"class":134}," context",[107,390,124],{"class":123},[107,392,393],{"class":134},"Context",[107,395,186],{"class":123},[107,397,398],{"class":385}," sql",[107,400,401],{"class":134}," string",[107,403,244],{"class":123},[107,405,406],{"class":123}," ([]",[107,408,409],{"class":134},"Row",[107,411,186],{"class":123},[107,413,414],{"class":134}," error",[107,416,212],{"class":123},[107,418,420],{"class":109,"line":419},14,[107,421,259],{"class":123},[107,423,425],{"class":109,"line":424},15,[107,426,172],{"emptyLinePlaceholder":171},[107,428,430,432,435,437],{"class":109,"line":429},16,[107,431,366],{"class":304},[107,433,434],{"class":134}," Mailer",[107,436,372],{"class":304},[107,438,247],{"class":123},[107,440,442,445,447,449,451,453,455,457,460,462,465,467,470,472,474],{"class":109,"line":441},17,[107,443,444],{"class":127},"    Send",[107,446,153],{"class":123},[107,448,209],{"class":385},[107,450,388],{"class":134},[107,452,124],{"class":123},[107,454,393],{"class":134},[107,456,186],{"class":123},[107,458,459],{"class":385}," to",[107,461,186],{"class":123},[107,463,464],{"class":385}," subject",[107,466,186],{"class":123},[107,468,469],{"class":385}," body",[107,471,401],{"class":134},[107,473,244],{"class":123},[107,475,476],{"class":134}," error\n",[107,478,480],{"class":109,"line":479},18,[107,481,259],{"class":123},[107,483,485],{"class":109,"line":484},19,[107,486,172],{"emptyLinePlaceholder":171},[107,488,490,493,496,499],{"class":109,"line":489},20,[107,491,492],{"class":304},"func",[107,494,495],{"class":127}," main",[107,497,498],{"class":123},"()",[107,500,247],{"class":123},[107,502,504],{"class":109,"line":503},21,[107,505,506],{"class":113},"    // Create services (you own lifecycle)\n",[107,508,510,513,515,518,520,523,525,528,530,533],{"class":109,"line":509},22,[107,511,512],{"class":120},"    db",[107,514,192],{"class":120},[107,516,517],{"class":120}," postgres",[107,519,124],{"class":123},[107,521,522],{"class":127},"New",[107,524,153],{"class":123},[107,526,527],{"class":120},"cfg",[107,529,124],{"class":123},[107,531,532],{"class":120},"DatabaseURL",[107,534,212],{"class":123},[107,536,538,541,544,546,549],{"class":109,"line":537},23,[107,539,540],{"class":218},"    defer",[107,542,543],{"class":120}," db",[107,545,124],{"class":123},[107,547,548],{"class":127},"Close",[107,550,551],{"class":123},"()\n",[107,553,555],{"class":109,"line":554},24,[107,556,172],{"emptyLinePlaceholder":171},[107,558,560,563,565,568,570,572,574,576,578,581],{"class":109,"line":559},25,[107,561,562],{"class":120},"    mailer",[107,564,192],{"class":120},[107,566,567],{"class":120}," sendgrid",[107,569,124],{"class":123},[107,571,522],{"class":127},[107,573,153],{"class":123},[107,575,527],{"class":120},[107,577,124],{"class":123},[107,579,580],{"class":120},"APIKey",[107,582,212],{"class":123},[107,584,586],{"class":109,"line":585},26,[107,587,172],{"emptyLinePlaceholder":171},[107,589,591],{"class":109,"line":590},27,[107,592,593],{"class":113},"    // Register by interface type, configure guards\n",[107,595,597,600,602,604,606,608,610,612],{"class":109,"line":596},28,[107,598,599],{"class":120},"    slush",[107,601,124],{"class":123},[107,603,128],{"class":127},[107,605,131],{"class":123},[107,607,135],{"class":134},[107,609,138],{"class":123},[107,611,141],{"class":120},[107,613,144],{"class":123},[107,615,617,620,622,625,627,630],{"class":109,"line":616},29,[107,618,619],{"class":127},"        Guard",[107,621,153],{"class":123},[107,623,624],{"class":127},"requireRole",[107,626,153],{"class":123},[107,628,629],{"class":161},"\"service\"",[107,631,165],{"class":123},[107,633,635],{"class":109,"line":634},30,[107,636,172],{"emptyLinePlaceholder":171},[107,638,640,642,644,646,648,651,653,656],{"class":109,"line":639},31,[107,641,599],{"class":120},[107,643,124],{"class":123},[107,645,128],{"class":127},[107,647,131],{"class":123},[107,649,650],{"class":134},"Mailer",[107,652,138],{"class":123},[107,654,655],{"class":120},"mailer",[107,657,144],{"class":123},[107,659,661,663,665,667,669,671],{"class":109,"line":660},32,[107,662,619],{"class":127},[107,664,153],{"class":123},[107,666,624],{"class":127},[107,668,153],{"class":123},[107,670,629],{"class":161},[107,672,673],{"class":123},")).\n",[107,675,677,679,681,683,685,688],{"class":109,"line":676},33,[107,678,619],{"class":127},[107,680,153],{"class":123},[107,682,156],{"class":127},[107,684,153],{"class":123},[107,686,687],{"class":161},"\"mailer:send\"",[107,689,165],{"class":123},[107,691,693],{"class":109,"line":692},34,[107,694,172],{"emptyLinePlaceholder":171},[107,696,698],{"class":109,"line":697},35,[107,699,700],{"class":113},"    // Start your server...\n",[107,702,704],{"class":109,"line":703},36,[107,705,259],{"class":123},[107,707,709],{"class":109,"line":708},37,[107,710,172],{"emptyLinePlaceholder":171},[107,712,714,716,719,721,723,725,727,729,731,733],{"class":109,"line":713},38,[107,715,492],{"class":304},[107,717,718],{"class":127}," handleRequest",[107,720,153],{"class":123},[107,722,209],{"class":385},[107,724,388],{"class":134},[107,726,124],{"class":123},[107,728,393],{"class":134},[107,730,244],{"class":123},[107,732,414],{"class":134},[107,734,247],{"class":123},[107,736,738],{"class":109,"line":737},39,[107,739,740],{"class":113},"    // Context carries caller identity\n",[107,742,744,746,748,750,752,754,756,758,760,762,764,766],{"class":109,"line":743},40,[107,745,512],{"class":120},[107,747,186],{"class":123},[107,749,189],{"class":120},[107,751,192],{"class":120},[107,753,195],{"class":120},[107,755,124],{"class":123},[107,757,200],{"class":127},[107,759,131],{"class":123},[107,761,135],{"class":134},[107,763,138],{"class":123},[107,765,209],{"class":120},[107,767,212],{"class":123},[107,769,771,774,776,779,782],{"class":109,"line":770},41,[107,772,773],{"class":218},"    if",[107,775,189],{"class":120},[107,777,778],{"class":218}," !=",[107,780,781],{"class":304}," nil",[107,783,247],{"class":123},[107,785,787,790,792],{"class":109,"line":786},42,[107,788,789],{"class":218},"        return",[107,791,189],{"class":120},[107,793,794],{"class":113}," // ErrNotFound or ErrAccessDenied\n",[107,796,798],{"class":109,"line":797},43,[107,799,800],{"class":123},"    }\n",[107,802,804],{"class":109,"line":803},44,[107,805,172],{"emptyLinePlaceholder":171},[107,807,809,812,814,816,818,820,822,825,827,829,831,834],{"class":109,"line":808},45,[107,810,811],{"class":120},"    rows",[107,813,186],{"class":123},[107,815,189],{"class":120},[107,817,192],{"class":120},[107,819,543],{"class":120},[107,821,124],{"class":123},[107,823,824],{"class":127},"Query",[107,826,153],{"class":123},[107,828,209],{"class":120},[107,830,186],{"class":123},[107,832,833],{"class":161}," \"SELECT ...\"",[107,835,212],{"class":123},[107,837,839],{"class":109,"line":838},46,[107,840,841],{"class":113},"    // ...\n",[107,843,845],{"class":109,"line":844},47,[107,846,259],{"class":123},[107,848,850],{"class":109,"line":849},48,[107,851,172],{"emptyLinePlaceholder":171},[107,853,855,857,860,862,865,867,869,871,873,876],{"class":109,"line":854},49,[107,856,492],{"class":304},[107,858,859],{"class":127}," requireRole",[107,861,153],{"class":123},[107,863,864],{"class":385},"role",[107,866,401],{"class":134},[107,868,244],{"class":123},[107,870,195],{"class":134},[107,872,124],{"class":123},[107,874,875],{"class":134},"Guard",[107,877,247],{"class":123},[107,879,881,884,887,889,891,893,895,897,899,901],{"class":109,"line":880},50,[107,882,883],{"class":218},"    return",[107,885,886],{"class":304}," func",[107,888,153],{"class":123},[107,890,209],{"class":385},[107,892,388],{"class":134},[107,894,124],{"class":123},[107,896,393],{"class":134},[107,898,244],{"class":123},[107,900,414],{"class":134},[107,902,247],{"class":123},[107,904,906,909,912,915,917,919,921,924,926],{"class":109,"line":905},51,[107,907,908],{"class":218},"        if",[107,910,911],{"class":218}," !",[107,913,914],{"class":127},"hasRole",[107,916,153],{"class":123},[107,918,209],{"class":120},[107,920,186],{"class":123},[107,922,923],{"class":120}," role",[107,925,244],{"class":123},[107,927,247],{"class":123},[107,929,931,934,936,938,940,942,945,948,950],{"class":109,"line":930},52,[107,932,933],{"class":218},"            return",[107,935,222],{"class":120},[107,937,124],{"class":123},[107,939,522],{"class":127},[107,941,153],{"class":123},[107,943,944],{"class":161},"\"missing role: \"",[107,946,947],{"class":120}," +",[107,949,923],{"class":120},[107,951,212],{"class":123},[107,953,955],{"class":109,"line":954},53,[107,956,957],{"class":123},"        }\n",[107,959,961,963],{"class":109,"line":960},54,[107,962,789],{"class":218},[107,964,965],{"class":304}," nil\n",[107,967,969],{"class":109,"line":968},55,[107,970,800],{"class":123},[107,972,974],{"class":109,"line":973},56,[107,975,259],{"class":123},[92,977,979],{"id":978},"capabilities","Capabilities",[981,982,983,996],"table",{},[984,985,986],"thead",{},[987,988,989,993],"tr",{},[990,991,992],"th",{},"Feature",[990,994,995],{},"Description",[997,998,999,1010,1020,1030,1040],"tbody",{},[987,1000,1001,1007],{},[1002,1003,1004],"td",{},[104,1005,1006],{},"Register[T]",[1002,1008,1009],{},"Store service by interface type, returns handle for configuration",[987,1011,1012,1017],{},[1002,1013,1014],{},[104,1015,1016],{},"Handle.Guard",[1002,1018,1019],{},"Add access checks, chainable",[987,1021,1022,1027],{},[1002,1023,1024],{},[104,1025,1026],{},"Use[T]",[1002,1028,1029],{},"Retrieve service, runs all guards first",[987,1031,1032,1037],{},[1002,1033,1034],{},[104,1035,1036],{},"Services()",[1002,1038,1039],{},"Enumerate registered services with sentinel metadata",[987,1041,1042,1045],{},[1002,1043,1044],{},"capitan events",[1002,1046,1047],{},"Emits on register, access, denied, not-found",[92,1049,1051],{"id":1050},"why-slush","Why slush?",[1053,1054,1055,1067,1076,1082,1088],"ul",{},[1056,1057,1058,1062,1063,1066],"li",{},[1059,1060,1061],"strong",{},"Type-safe generics"," — No ",[104,1064,1065],{},"interface{}"," casting at call sites",[1056,1068,1069,1072,1073],{},[1059,1070,1071],{},"Guards are composable"," — Chain checks fluently: ",[104,1074,1075],{},".Guard(a).Guard(b).Guard(c)",[1056,1077,1078,1081],{},[1059,1079,1080],{},"Context flows through"," — Guards receive request context for validation",[1056,1083,1084,1087],{},[1059,1085,1086],{},"You own lifecycle"," — Register initialized services; slush just finds them",[1056,1089,1090,1093,1094,1096],{},[1059,1091,1092],{},"Enumerable registry"," — ",[104,1095,1036],{}," exposes FQDNs and sentinel metadata",[92,1098,1100],{"id":1099},"cryptographic-guards-with-sctx","Cryptographic Guards with sctx",[16,1102,1103,1104,1109],{},"Combine slush with ",[19,1105,1108],{"href":1106,"rel":1107},"https://github.com/zoobz-io/sctx",[23],"sctx"," for cryptographically-verified service access:",[97,1111,1113],{"className":99,"code":1112,"language":101,"meta":102,"style":102},"// Bootstrap: create sctx admin with your PKI\nadmin, _ := sctx.NewAdminService[AppMeta](privateKey, trustedCAs)\n\n// Create a guard that requires specific permissions\ndbGuard, _ := admin.CreateGuard(ctx, adminToken, \"db:read\", \"db:write\")\n\n// Bridge sctx guard to slush guard\nslush.Register[Database](db).\n    Guard(func(ctx context.Context) error {\n        token := sctx.TokenFromContext(ctx)\n        return dbGuard.Validate(ctx, token)\n    })\n\n// In handlers: context carries the cryptographic token\n// Guard validates signature + permissions before service access\ndb, err := slush.Use[Database](ctx)\n",[104,1114,1115,1120,1157,1161,1166,1206,1210,1215,1233,1257,1277,1300,1305,1309,1314,1319],{"__ignoreMap":102},[107,1116,1117],{"class":109,"line":110},[107,1118,1119],{"class":113},"// Bootstrap: create sctx admin with your PKI\n",[107,1121,1122,1125,1127,1130,1132,1135,1137,1140,1142,1145,1147,1150,1152,1155],{"class":109,"line":117},[107,1123,1124],{"class":120},"admin",[107,1126,186],{"class":123},[107,1128,1129],{"class":120}," _",[107,1131,192],{"class":120},[107,1133,1134],{"class":120}," sctx",[107,1136,124],{"class":123},[107,1138,1139],{"class":127},"NewAdminService",[107,1141,131],{"class":123},[107,1143,1144],{"class":134},"AppMeta",[107,1146,138],{"class":123},[107,1148,1149],{"class":120},"privateKey",[107,1151,186],{"class":123},[107,1153,1154],{"class":120}," trustedCAs",[107,1156,212],{"class":123},[107,1158,1159],{"class":109,"line":147},[107,1160,172],{"emptyLinePlaceholder":171},[107,1162,1163],{"class":109,"line":168},[107,1164,1165],{"class":113},"// Create a guard that requires specific permissions\n",[107,1167,1168,1171,1173,1175,1177,1180,1182,1185,1187,1189,1191,1194,1196,1199,1201,1204],{"class":109,"line":175},[107,1169,1170],{"class":120},"dbGuard",[107,1172,186],{"class":123},[107,1174,1129],{"class":120},[107,1176,192],{"class":120},[107,1178,1179],{"class":120}," admin",[107,1181,124],{"class":123},[107,1183,1184],{"class":127},"CreateGuard",[107,1186,153],{"class":123},[107,1188,209],{"class":120},[107,1190,186],{"class":123},[107,1192,1193],{"class":120}," adminToken",[107,1195,186],{"class":123},[107,1197,1198],{"class":161}," \"db:read\"",[107,1200,186],{"class":123},[107,1202,1203],{"class":161}," \"db:write\"",[107,1205,212],{"class":123},[107,1207,1208],{"class":109,"line":181},[107,1209,172],{"emptyLinePlaceholder":171},[107,1211,1212],{"class":109,"line":215},[107,1213,1214],{"class":113},"// Bridge sctx guard to slush guard\n",[107,1216,1217,1219,1221,1223,1225,1227,1229,1231],{"class":109,"line":250},[107,1218,14],{"class":120},[107,1220,124],{"class":123},[107,1222,128],{"class":127},[107,1224,131],{"class":123},[107,1226,135],{"class":134},[107,1228,138],{"class":123},[107,1230,141],{"class":120},[107,1232,144],{"class":123},[107,1234,1235,1237,1239,1241,1243,1245,1247,1249,1251,1253,1255],{"class":109,"line":256},[107,1236,150],{"class":127},[107,1238,153],{"class":123},[107,1240,492],{"class":304},[107,1242,153],{"class":123},[107,1244,209],{"class":385},[107,1246,388],{"class":134},[107,1248,124],{"class":123},[107,1250,393],{"class":134},[107,1252,244],{"class":123},[107,1254,414],{"class":134},[107,1256,247],{"class":123},[107,1258,1259,1262,1264,1266,1268,1271,1273,1275],{"class":109,"line":352},[107,1260,1261],{"class":120},"        token",[107,1263,192],{"class":120},[107,1265,1134],{"class":120},[107,1267,124],{"class":123},[107,1269,1270],{"class":127},"TokenFromContext",[107,1272,153],{"class":123},[107,1274,209],{"class":120},[107,1276,212],{"class":123},[107,1278,1279,1281,1284,1286,1289,1291,1293,1295,1298],{"class":109,"line":357},[107,1280,789],{"class":218},[107,1282,1283],{"class":120}," dbGuard",[107,1285,124],{"class":123},[107,1287,1288],{"class":127},"Validate",[107,1290,153],{"class":123},[107,1292,209],{"class":120},[107,1294,186],{"class":123},[107,1296,1297],{"class":120}," token",[107,1299,212],{"class":123},[107,1301,1302],{"class":109,"line":363},[107,1303,1304],{"class":123},"    })\n",[107,1306,1307],{"class":109,"line":377},[107,1308,172],{"emptyLinePlaceholder":171},[107,1310,1311],{"class":109,"line":419},[107,1312,1313],{"class":113},"// In handlers: context carries the cryptographic token\n",[107,1315,1316],{"class":109,"line":424},[107,1317,1318],{"class":113},"// Guard validates signature + permissions before service access\n",[107,1320,1321,1323,1325,1327,1329,1331,1333,1335,1337,1339,1341,1343],{"class":109,"line":429},[107,1322,141],{"class":120},[107,1324,186],{"class":123},[107,1326,189],{"class":120},[107,1328,192],{"class":120},[107,1330,195],{"class":120},[107,1332,124],{"class":123},[107,1334,200],{"class":127},[107,1336,131],{"class":123},[107,1338,135],{"class":134},[107,1340,138],{"class":123},[107,1342,209],{"class":120},[107,1344,212],{"class":123},[16,1346,1347],{},"The sctx guard validates:",[1053,1349,1350,1353,1356],{},[1056,1351,1352],{},"Token signature (proves possession of private key)",[1056,1354,1355],{},"Token expiry and replay protection",[1056,1357,1358],{},"Required permissions present in token's context",[92,1360,1362],{"id":1361},"ecosystem","Ecosystem",[16,1364,1365],{},"slush integrates with the zoobz-io toolkit:",[1053,1367,1368,1380,1403],{},[1056,1369,1370,1093,1377,1379],{},[1059,1371,1372],{},[19,1373,1376],{"href":1374,"rel":1375},"https://github.com/zoobz-io/sentinel",[23],"sentinel",[104,1378,1036],{}," returns sentinel metadata for each registered implementation",[1056,1381,1382,1389,1390,1393,1394,1393,1397,1393,1400],{},[1059,1383,1384],{},[19,1385,1388],{"href":1386,"rel":1387},"https://github.com/zoobz-io/capitan",[23],"capitan"," — Events emitted on ",[104,1391,1392],{},"slush.registered",", ",[104,1395,1396],{},"slush.accessed",[104,1398,1399],{},"slush.denied",[104,1401,1402],{},"slush.not_found",[1056,1404,1405,1410],{},[1059,1406,1407],{},[19,1408,1108],{"href":1106,"rel":1409},[23]," — Cryptographic context for guards that verify identity and permissions",[92,1412,1414],{"id":1413},"contributing","Contributing",[16,1416,1417,1418,1422,1423,1426],{},"See ",[19,1419,1421],{"href":1420},"CONTRIBUTING","CONTRIBUTING.md",". Run ",[104,1424,1425],{},"make help"," for available commands.",[92,1428,1430],{"id":1429},"license","License",[16,1432,1433,1434,124],{},"MIT — see ",[19,1435,1436],{"href":1436},"LICENSE",[1438,1439,1440],"style",{},"html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}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 pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}",{"title":102,"searchDepth":117,"depth":117,"links":1442},[1443,1444,1445,1446,1447,1448,1449,1450,1451],{"id":94,"depth":117,"text":95},{"id":269,"depth":117,"text":270},{"id":293,"depth":117,"text":294},{"id":978,"depth":117,"text":979},{"id":1050,"depth":117,"text":1051},{"id":1099,"depth":117,"text":1100},{"id":1361,"depth":117,"text":1362},{"id":1413,"depth":117,"text":1414},{"id":1429,"depth":117,"text":1430},"md","book-open",{},"/readme",{"title":7,"description":102},"readme","WpQhslc2Dbzp1eOGIYAISUjeoxoWdj6IZeIOATpXrTQ",{"id":1460,"title":1461,"body":1462,"description":102,"extension":1452,"icon":1556,"meta":1557,"navigation":171,"path":1558,"seo":1559,"stem":1560,"__hash__":1561},"resources/security.md","Security",{"type":9,"value":1463,"toc":1551},[1464,1468,1472,1475,1480,1483,1487,1490,1520,1524,1527],[12,1465,1467],{"id":1466},"security-policy","Security Policy",[92,1469,1471],{"id":1470},"reporting-a-vulnerability","Reporting a Vulnerability",[16,1473,1474],{},"If you discover a security vulnerability in slush, please report it responsibly.",[16,1476,1477],{},[1059,1478,1479],{},"Do not open a public issue.",[16,1481,1482],{},"Instead, please email security concerns to the maintainers or use GitHub's private vulnerability reporting feature.",[92,1484,1486],{"id":1485},"supported-versions","Supported Versions",[16,1488,1489],{},"Only the latest minor version receives security updates.",[981,1491,1492,1502],{},[984,1493,1494],{},[987,1495,1496,1499],{},[990,1497,1498],{},"Version",[990,1500,1501],{},"Supported",[997,1503,1504,1512],{},[987,1505,1506,1509],{},[1002,1507,1508],{},"latest",[1002,1510,1511],{},"✅",[987,1513,1514,1517],{},[1002,1515,1516],{},"\u003C latest",[1002,1518,1519],{},"❌",[92,1521,1523],{"id":1522},"security-considerations","Security Considerations",[16,1525,1526],{},"slush is a service locator with cryptographic access control. Security-relevant aspects:",[1053,1528,1529,1535,1545],{},[1056,1530,1531,1534],{},[1059,1532,1533],{},"Guard functions",": User-provided guards control service access. Ensure guards validate context appropriately.",[1056,1536,1537,1540,1541,1544],{},[1059,1538,1539],{},"Global registry",": The registry is global by design (Go generics constraint). Test isolation requires explicit ",[104,1542,1543],{},"Reset()"," calls.",[1056,1546,1547,1550],{},[1059,1548,1549],{},"No credential storage",": slush does not store credentials; it delegates validation to guard functions.",{"title":102,"searchDepth":117,"depth":117,"links":1552},[1553,1554,1555],{"id":1470,"depth":117,"text":1471},{"id":1485,"depth":117,"text":1486},{"id":1522,"depth":117,"text":1523},"shield",{},"/security",{"title":1461,"description":102},"security","QD69mO5ShTYh3vDSgMcLvPsNchp8-kXvZGF21C2Pb8w",{"id":1563,"title":1414,"body":1564,"description":1572,"extension":1452,"icon":104,"meta":1789,"navigation":171,"path":1790,"seo":1791,"stem":1413,"__hash__":1792},"resources/contributing.md",{"type":9,"value":1565,"toc":1781},[1566,1570,1573,1577,1655,1659,1665,1677,1681,1708,1712,1732,1736,1771,1775,1778],[12,1567,1569],{"id":1568},"contributing-to-slush","Contributing to slush",[16,1571,1572],{},"Thank you for your interest in contributing to slush.",[92,1574,1576],{"id":1575},"development-setup","Development Setup",[97,1578,1580],{"className":273,"code":1579,"language":275,"meta":102,"style":102},"# Clone the repository\ngit clone https://github.com/zoobzio/slush.git\ncd slush\n\n# Install development tools\nmake install-tools\n\n# Install git hooks\nmake install-hooks\n\n# Run checks\nmake check\n",[104,1581,1582,1587,1598,1606,1610,1615,1623,1627,1632,1639,1643,1648],{"__ignoreMap":102},[107,1583,1584],{"class":109,"line":110},[107,1585,1586],{"class":113},"# Clone the repository\n",[107,1588,1589,1592,1595],{"class":109,"line":117},[107,1590,1591],{"class":127},"git",[107,1593,1594],{"class":161}," clone",[107,1596,1597],{"class":161}," https://github.com/zoobzio/slush.git\n",[107,1599,1600,1603],{"class":109,"line":147},[107,1601,1602],{"class":127},"cd",[107,1604,1605],{"class":161}," slush\n",[107,1607,1608],{"class":109,"line":168},[107,1609,172],{"emptyLinePlaceholder":171},[107,1611,1612],{"class":109,"line":175},[107,1613,1614],{"class":113},"# Install development tools\n",[107,1616,1617,1620],{"class":109,"line":181},[107,1618,1619],{"class":127},"make",[107,1621,1622],{"class":161}," install-tools\n",[107,1624,1625],{"class":109,"line":215},[107,1626,172],{"emptyLinePlaceholder":171},[107,1628,1629],{"class":109,"line":250},[107,1630,1631],{"class":113},"# Install git hooks\n",[107,1633,1634,1636],{"class":109,"line":256},[107,1635,1619],{"class":127},[107,1637,1638],{"class":161}," install-hooks\n",[107,1640,1641],{"class":109,"line":352},[107,1642,172],{"emptyLinePlaceholder":171},[107,1644,1645],{"class":109,"line":357},[107,1646,1647],{"class":113},"# Run checks\n",[107,1649,1650,1652],{"class":109,"line":363},[107,1651,1619],{"class":127},[107,1653,1654],{"class":161}," check\n",[92,1656,1658],{"id":1657},"available-commands","Available Commands",[16,1660,1661,1662,1664],{},"Run ",[104,1663,1425],{}," to see all available commands:",[97,1666,1668],{"className":273,"code":1667,"language":275,"meta":102,"style":102},"make help\n",[104,1669,1670],{"__ignoreMap":102},[107,1671,1672,1674],{"class":109,"line":110},[107,1673,1619],{"class":127},[107,1675,1676],{"class":161}," help\n",[92,1678,1680],{"id":1679},"workflow","Workflow",[1682,1683,1684,1687,1693,1696,1702,1705],"ol",{},[1056,1685,1686],{},"Fork the repository",[1056,1688,1689,1690,244],{},"Create a feature branch (",[104,1691,1692],{},"git checkout -b feature/my-feature",[1056,1694,1695],{},"Make your changes",[1056,1697,1661,1698,1701],{},[104,1699,1700],{},"make check"," to ensure tests and linting pass",[1056,1703,1704],{},"Commit your changes with a descriptive message",[1056,1706,1707],{},"Push to your fork and submit a pull request",[92,1709,1711],{"id":1710},"code-standards","Code Standards",[1053,1713,1714,1720,1726,1729],{},[1056,1715,1716,1717],{},"All code must pass ",[104,1718,1719],{},"make lint",[1056,1721,1722,1723,244],{},"All code must have tests (",[104,1724,1725],{},"make test",[1056,1727,1728],{},"Coverage should not decrease significantly",[1056,1730,1731],{},"Follow existing code style and patterns",[92,1733,1735],{"id":1734},"testing","Testing",[97,1737,1739],{"className":273,"code":1738,"language":275,"meta":102,"style":102},"make test          # Run all tests\nmake test-unit     # Run unit tests only\nmake coverage      # Generate coverage report\n",[104,1740,1741,1751,1761],{"__ignoreMap":102},[107,1742,1743,1745,1748],{"class":109,"line":110},[107,1744,1619],{"class":127},[107,1746,1747],{"class":161}," test",[107,1749,1750],{"class":113},"          # Run all tests\n",[107,1752,1753,1755,1758],{"class":109,"line":117},[107,1754,1619],{"class":127},[107,1756,1757],{"class":161}," test-unit",[107,1759,1760],{"class":113},"     # Run unit tests only\n",[107,1762,1763,1765,1768],{"class":109,"line":147},[107,1764,1619],{"class":127},[107,1766,1767],{"class":161}," coverage",[107,1769,1770],{"class":113},"      # Generate coverage report\n",[92,1772,1774],{"id":1773},"questions","Questions?",[16,1776,1777],{},"Open an issue for discussion before making large changes.",[1438,1779,1780],{},"html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}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);}",{"title":102,"searchDepth":117,"depth":117,"links":1782},[1783,1784,1785,1786,1787,1788],{"id":1575,"depth":117,"text":1576},{"id":1657,"depth":117,"text":1658},{"id":1679,"depth":117,"text":1680},{"id":1710,"depth":117,"text":1711},{"id":1734,"depth":117,"text":1735},{"id":1773,"depth":117,"text":1774},{},"/contributing",{"title":1414,"description":1572},"RGNqSSFkmKwYfIcxqj-kdxZMZBV4FAzyQvRylebsLug",[1794,1799,1803,1808,1813,1818,1823,1828,1832,1837,1842,1847,1852,1857,1861,1866,1870,1875,1880,1885,1890,1894,1899,1903,1908,1913,1918,1923,1928,1933,1937,1941,1945,1950,1955,1959,1964,1969,1974,1979,1984,1989,1993,1998,2002,2007,2012,2017,2022,2027,2032,2037,2042,2047,2052,2057,2062,2067,2072,2077,2082,2087,2092,2097,2101,2105,2109,2114,2118,2123,2128,2133,2138,2142,2147,2152,2157,2162,2167,2172,2177,2182,2187,2191,2195,2200,2204,2209,2214,2219,2223,2228,2233,2238,2243,2248,2253,2257,2262,2266,2271,2276,2281,2285,2290,2295,2300,2304,2309,2314,2319,2324,2329,2334,2339,2343,2348,2352,2357,2362,2365,2370,2375,2380,2385,2390,2395,2400,2405,2409,2413,2418,2422,2426,2431,2436,2440,2444,2448,2452,2457,2461,2465,2470,2475,2480,2484,2488,2492,2497,2502,2507,2512,2516,2521,2526,2531,2535,2540,2544,2548,2553,2557,2562,2567,2571,2576,2580,2585,2590,2595,2600,2605,2610,2615,2620,2625,2630],{"id":1795,"title":1796,"titles":1797,"content":1798,"level":110},"/v0.0.3/learn/overview","Overview",[],"A type-safe service locator with guarded access control",{"id":1800,"title":1796,"titles":1801,"content":1802,"level":110},"/v0.0.3/learn/overview#overview",[],"slush is a guarded service locator for Go. Register services by interface type, configure access guards, and retrieve them with context-aware validation.",{"id":1804,"title":1805,"titles":1806,"content":1807,"level":117},"/v0.0.3/learn/overview#the-idea","The Idea",[1796],"Service locators have a reputation problem. They hide dependencies and make code harder to reason about. But sometimes you need runtime service discovery—plugins, HTTP handlers, or frameworks where the consumer doesn't control instantiation. The question: Can we make service lookup explicit about access control? slush answers by putting guards at the center. Every lookup passes through composable guard functions that validate the request context before returning the service. The registry isn't a bag of services—it's a set of guarded gates.",{"id":1809,"title":1810,"titles":1811,"content":1812,"level":117},"/v0.0.3/learn/overview#the-implementation","The Implementation",[1796],"slush provides: Generic registration — Register[T](impl) stores services by interface type with full type safetyComposable guards — Handle.Guard(fn) chains access checks that validate contextContext-aware lookup — Use[T](ctx) runs all guards before returning the serviceService enumeration — Services() returns metadata for all registered servicesEvent emission — Lifecycle events via capitan for observability",{"id":1814,"title":1815,"titles":1816,"content":1817,"level":117},"/v0.0.3/learn/overview#what-it-enables","What It Enables",[1796],"Cryptographic access control: Combine with sctx for guards that verify token signatures and permissions. Service introspection: Services() returns sentinel metadata for each implementation, enabling documentation endpoints and ERD generation. Observability: capitan events on register, access, denied, and not-found for tracing and alerting.",{"id":1819,"title":1820,"titles":1821,"content":1822,"level":117},"/v0.0.3/learn/overview#next-steps","Next Steps",[1796],"Quickstart — Install and basic usageConcepts — Mental models for services, guards, and handlesAPI Reference — Complete function documentation",{"id":1824,"title":1825,"titles":1826,"content":1827,"level":110},"/v0.0.3/learn/quickstart","Quickstart",[],"Get started with slush in five minutes",{"id":1829,"title":1825,"titles":1830,"content":1831,"level":110},"/v0.0.3/learn/quickstart#quickstart",[],"Install slush and register your first guarded service.",{"id":1833,"title":1834,"titles":1835,"content":1836,"level":117},"/v0.0.3/learn/quickstart#installation","Installation",[1825],"go get github.com/zoobzio/slush Requires Go 1.24 or later.",{"id":1838,"title":1839,"titles":1840,"content":1841,"level":117},"/v0.0.3/learn/quickstart#basic-usage","Basic Usage",[1825],"package main\n\nimport (\n    \"context\"\n    \"errors\"\n    \"fmt\"\n\n    \"github.com/zoobzio/slush\"\n)\n\n// Define a service contract\ntype Greeter interface {\n    Greet(name string) string\n}\n\n// Implement it\ntype englishGreeter struct{}\n\nfunc (g englishGreeter) Greet(name string) string {\n    return fmt.Sprintf(\"Hello, %s!\", name)\n}\n\nfunc main() {\n    // Initialize the registry (once per process)\n    key := slush.Start()\n\n    // Register the service with a guard\n    slush.Register[Greeter](key, englishGreeter{}).\n        Guard(func(ctx context.Context) error {\n            // Your access logic here\n            return nil\n        })\n\n    // Retrieve it\n    ctx := context.Background()\n    greeter, err := slush.Use[Greeter](ctx)\n    if err != nil {\n        panic(err)\n    }\n\n    fmt.Println(greeter.Greet(\"World\"))\n    // Output: Hello, World!\n}",{"id":1843,"title":1844,"titles":1845,"content":1846,"level":117},"/v0.0.3/learn/quickstart#registering-services","Registering Services",[1825],"First, initialize the registry with Start(). This returns a Key that authorizes registration. key := slush.Start()  // Call once at startup Register services by interface type. The implementation is stored; the interface type is the lookup key. slush.Register[Database](key, postgresDB)\nslush.Register[Mailer](key, sendgridMailer)\nslush.Register[Logger](key, zapLogger) You own the service lifecycle. Create and initialize services before registering; slush just stores and retrieves them. Optionally, freeze the registry to prevent further registration: slush.Freeze(key)  // No more registrations allowed",{"id":1848,"title":1849,"titles":1850,"content":1851,"level":117},"/v0.0.3/learn/quickstart#adding-guards","Adding Guards",[1825],"Guards are functions that validate context before service access. Chain them with .Guard(): slush.Register[Database](key, db).\n    Guard(requireAuth).\n    Guard(requireRole(\"admin\")).\n    Guard(rateLimit(100)) Guards run in order. The first error stops the chain and returns ErrAccessDenied. See Guards Guide for composition patterns.",{"id":1853,"title":1854,"titles":1855,"content":1856,"level":117},"/v0.0.3/learn/quickstart#retrieving-services","Retrieving Services",[1825],"Use Use[T](ctx) to retrieve a service: db, err := slush.Use[Database](ctx)\nif errors.Is(err, slush.ErrNotFound) {\n    // Service not registered\n}\nif errors.Is(err, slush.ErrAccessDenied) {\n    // Guard rejected the request\n} For bootstrap code where errors should panic, use MustUse: logger := slush.MustUse[Logger](ctx)",{"id":1858,"title":1820,"titles":1859,"content":1860,"level":117},"/v0.0.3/learn/quickstart#next-steps",[1825],"Concepts — Understand services, guards, and handlesArchitecture — How slush works internallyTesting Guide — Test code that uses slush html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}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 pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"id":1862,"title":1863,"titles":1864,"content":1865,"level":110},"/v0.0.3/learn/concepts","Concepts",[],"Mental models for services, guards, and handles",{"id":1867,"title":1863,"titles":1868,"content":1869,"level":110},"/v0.0.3/learn/concepts#concepts",[],"slush has four core concepts: services, guards, handles, and the registry. Understanding how they relate helps you use slush effectively.",{"id":1871,"title":1872,"titles":1873,"content":1874,"level":117},"/v0.0.3/learn/concepts#services","Services",[1863],"A service is any value registered with slush. Typically an interface implementation: type Database interface {\n    Query(ctx context.Context, sql string) ([]Row, error)\n}\n\n// The service is this implementation\ndb := postgres.New(connectionString)\nslush.Register[Database](db) Key points: The type parameter (Database) is the lookup key—an interface typeThe value (db) is the implementation stored in the registryYou create and own the service; slush just stores a referenceOne service per interface type (subsequent registrations overwrite)",{"id":1876,"title":1877,"titles":1878,"content":1879,"level":117},"/v0.0.3/learn/concepts#guards","Guards",[1863],"A guard is a function that decides whether a lookup should succeed: type Guard func(ctx context.Context) error Return nil to allow access; return an error to deny it. The error is wrapped with ErrAccessDenied and returned to the caller. Why guards exist: Traditional service locators return services unconditionally. Anyone with registry access gets any service. Guards make access control explicit—every lookup must pass through your validation logic. Guard patterns: // Check a context value\nfunc requireAuth(ctx context.Context) error {\n    if user := auth.UserFromContext(ctx); user == nil {\n        return errors.New(\"unauthenticated\")\n    }\n    return nil\n}\n\n// Check permissions\nfunc requirePermission(perm string) slush.Guard {\n    return func(ctx context.Context) error {\n        if !hasPermission(ctx, perm) {\n            return fmt.Errorf(\"missing permission: %s\", perm)\n        }\n        return nil\n    }\n}\n\n// Rate limiting\nfunc rateLimit(perSecond int) slush.Guard {\n    limiter := rate.NewLimiter(rate.Limit(perSecond), perSecond)\n    return func(ctx context.Context) error {\n        if !limiter.Allow() {\n            return errors.New(\"rate limited\")\n        }\n        return nil\n    }\n} See Guards Guide for more patterns.",{"id":1881,"title":1882,"titles":1883,"content":1884,"level":117},"/v0.0.3/learn/concepts#handles","Handles",[1863],"A handle is returned from Register and lets you configure the service: handle := slush.Register[Database](db)\nhandle.Guard(requireAuth)\nhandle.Guard(requirePermission(\"db:read\")) The handle is chainable: slush.Register[Database](db).\n    Guard(requireAuth).\n    Guard(requirePermission(\"db:read\")) What handles provide: .Guard(fn) — Add access control checksAccess to the underlying service for the type parameter Internally, the handle wraps a generic handle[T] that stores the implementation with full type safety.",{"id":1886,"title":1887,"titles":1888,"content":1889,"level":117},"/v0.0.3/learn/concepts#the-registry","The Registry",[1863],"The registry is slush's global state—a map from interface types to services. It's protected by a read-write mutex for concurrent access. // Conceptually:\nvar services = map[reflect.Type]service Registry operations: FunctionDescriptionRegister[T]Add or replace a serviceUse[T]Retrieve a service (runs guards)Unregister[T]Remove a serviceResetClear all servicesServicesList all registered services Why global state? Go generics require package-level functions for generic type parameters. Methods can't introduce new type parameters. The global registry is a consequence of this constraint, not a design preference. For testing, Reset() clears the registry between tests. See Testing Guide.",{"id":1891,"title":1820,"titles":1892,"content":1893,"level":117},"/v0.0.3/learn/concepts#next-steps",[1863],"Architecture — How slush implements type-safe storageGuards Guide — Writing and composing guardsTypes Reference — Complete type documentation html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}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 pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}",{"id":1895,"title":1896,"titles":1897,"content":1898,"level":110},"/v0.0.3/learn/architecture","Architecture",[],"How slush implements type-safe guarded service lookup",{"id":1900,"title":1896,"titles":1901,"content":1902,"level":110},"/v0.0.3/learn/architecture#architecture",[],"This document explains how slush implements type-safe service storage and retrieval. It's intended for contributors and users who want to understand the internals.",{"id":1904,"title":1905,"titles":1906,"content":1907,"level":117},"/v0.0.3/learn/architecture#component-overview","Component Overview",[1896],"┌─────────────────────────────────────────────────────────────┐\n│                        Public API                           │\n│  Register[T]()  Use[T]()  Services()  Reset()  Unregister[] │\n└─────────────────────────────────────────────────────────────┘\n                              │\n                              ▼\n┌─────────────────────────────────────────────────────────────┐\n│                        Registry                             │\n│            map[reflect.Type]service (interface)             │\n└─────────────────────────────────────────────────────────────┘\n                              │\n                              ▼\n┌─────────────────────────────────────────────────────────────┐\n│                      handle[T] (generic)                    │\n│    impl T │ guards []Guard │ interfaceFQDN │ implFQDN       │\n└─────────────────────────────────────────────────────────────┘\n                              │\n                              ▼\n┌─────────────────────────────────────────────────────────────┐\n│                    service (interface)                      │\n│         fqdns() │ guards() │ guardCount()                   │\n└─────────────────────────────────────────────────────────────┘",{"id":1909,"title":1910,"titles":1911,"content":1912,"level":117},"/v0.0.3/learn/architecture#type-erasure-pattern","Type Erasure Pattern",[1896],"The core challenge: Go maps require a uniform value type, but we want to store handle[Database], handle[Mailer], etc. in the same map. Solution: Interface-based type erasure // Non-generic interface for storage\ntype service interface {\n    fqdns() (iface, impl string)\n    guards() []Guard\n    guardCount() int\n}\n\n// Generic implementation\ntype handle[T any] struct {\n    impl          T\n    g             []Guard\n    interfaceFQDN string\n    implFQDN      string\n}\n\n// handle[T] satisfies service\nfunc (h *handle[T]) fqdns() (string, string) { ... }\nfunc (h *handle[T]) guards() []Guard         { ... }\nfunc (h *handle[T]) guardCount() int         { ... }\n\n// Registry stores the interface\nvar services = map[reflect.Type]service On registration, we create a *handle[T] and store it as service. On retrieval, we type-assert back to *handle[T]: func Use[T any](ctx context.Context) (T, error) {\n    svc := services[reflect.TypeFor[T]()]\n    h := svc.(*handle[T])  // Safe: keyed by same type\n    return h.impl, nil\n} This preserves type safety—impl is stored as T, not any.",{"id":1914,"title":1915,"titles":1916,"content":1917,"level":117},"/v0.0.3/learn/architecture#registration-flow","Registration Flow",[1896],"Register[Database](db)\n        │\n        ▼\n┌───────────────────────────────────┐\n│ 1. Compute FQDNs                  │\n│    - interfaceFQDN from TypeFor[T]│\n│    - implFQDN from TypeOf(impl)   │\n└───────────────────────────────────┘\n        │\n        ▼\n┌───────────────────────────────────┐\n│ 2. Create handle[T]               │\n│    - Store impl T                 │\n│    - Initialize empty guards      │\n└───────────────────────────────────┘\n        │\n        ▼\n┌───────────────────────────────────┐\n│ 3. Store in registry              │\n│    - Lock mutex                   │\n│    - services[key] = handle       │\n│    - Unlock mutex                 │\n└───────────────────────────────────┘\n        │\n        ▼\n┌───────────────────────────────────┐\n│ 4. Emit capitan event             │\n│    - SignalRegistered             │\n│    - interface + impl FQDNs       │\n└───────────────────────────────────┘\n        │\n        ▼\n┌───────────────────────────────────┐\n│ 5. Return Handle[T]               │\n│    - Wraps *handle[T]             │\n│    - Exposes .Guard() method      │\n└───────────────────────────────────┘",{"id":1919,"title":1920,"titles":1921,"content":1922,"level":117},"/v0.0.3/learn/architecture#retrieval-flow","Retrieval Flow",[1896],"Use[Database](ctx)\n        │\n        ▼\n┌───────────────────────────────────┐\n│ 1. Lookup in registry             │\n│    - RLock mutex                  │\n│    - Get services[TypeFor[T]()]   │\n│    - RUnlock mutex                │\n└───────────────────────────────────┘\n        │\n        ▼\n┌───────────────────────────────────┐\n│ 2. Check existence                │\n│    - If not found: emit event,    │\n│      return ErrNotFound           │\n└───────────────────────────────────┘\n        │\n        ▼\n┌───────────────────────────────────┐\n│ 3. Type-assert to *handle[T]      │\n│    - Safe because key matches     │\n└───────────────────────────────────┘\n        │\n        ▼\n┌───────────────────────────────────┐\n│ 4. Run guards                     │\n│    - For each guard in order:     │\n│      - Call guard(ctx)            │\n│      - If error: emit event,      │\n│        return ErrAccessDenied     │\n└───────────────────────────────────┘\n        │\n        ▼\n┌───────────────────────────────────┐\n│ 5. Emit success event             │\n│    - SignalAccessed               │\n└───────────────────────────────────┘\n        │\n        ▼\n┌───────────────────────────────────┐\n│ 6. Return h.impl                  │\n│    - Type T, no casting needed    │\n└───────────────────────────────────┘",{"id":1924,"title":1925,"titles":1926,"content":1927,"level":117},"/v0.0.3/learn/architecture#design-qa","Design Q&A",[1896],"Why a global registry instead of explicit dependency injection? Go generics can't have type parameters on methods. Register[T] must be a package-level function. A method like registry.Register[T]() isn't valid Go. Why reflect.Type as the map key? It's the only way to differentiate handle[Database] from handle[Mailer] at runtime. reflect.TypeFor[T]() is computed at compile time, so there's no runtime reflection cost for the key. Why pre-compute FQDNs at registration? To avoid reflection in the hot path (Use). FQDNs are computed once and stored as strings. Use only calls pre-computed string accessors. Why the service interface? To enable Services() enumeration without knowing T. The interface provides accessors for metadata that don't require the type parameter.",{"id":1929,"title":1930,"titles":1931,"content":1932,"level":117},"/v0.0.3/learn/architecture#performance-characteristics","Performance Characteristics",[1896],"OperationComplexityNotesRegisterO(1)Map insert + one reflection callUse (no guards)O(1)Map lookup + type assertionUse (with guards)O(n)n = number of guardsServicesO(n)n = number of registered servicesResetO(1)Map reallocation Concurrency: All operations are protected by sync.RWMutex. Multiple Use calls can run concurrently; Register and Reset acquire exclusive locks.",{"id":1934,"title":1820,"titles":1935,"content":1936,"level":117},"/v0.0.3/learn/architecture#next-steps",[1896],"Guards Guide — Writing efficient guardsAPI Reference — Complete function documentationcapitan Integration — Event emission details html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}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);}",{"id":1938,"title":1735,"titles":1939,"content":1940,"level":110},"/v0.0.3/guides/testing",[],"How to test code that uses slush",{"id":1942,"title":1735,"titles":1943,"content":1944,"level":110},"/v0.0.3/guides/testing#testing",[],"This guide covers testing code that uses slush for service lookup.",{"id":1946,"title":1947,"titles":1948,"content":1949,"level":117},"/v0.0.3/guides/testing#test-isolation","Test Isolation",[1735],"slush uses a global registry. Tests must reset state to avoid interference: func TestMyHandler(t *testing.T) {\n    slush.Reset()\n    defer slush.Reset()\n\n    key := slush.Start()\n    slush.Register[Database](key, mockDB{})\n\n    // Run test...\n} Using the testing helper: slush provides a helper that resets, starts, and registers cleanup: import slushtesting \"github.com/zoobzio/slush/testing\"\n\nfunc TestMyHandler(t *testing.T) {\n    key := slushtesting.StartRegistry(t)  // Resets, starts, and registers cleanup\n\n    slush.Register[Database](key, mockDB{})\n    // ...\n}",{"id":1951,"title":1952,"titles":1953,"content":1954,"level":117},"/v0.0.3/guides/testing#mocking-services","Mocking Services",[1735],"Create mock implementations of your service interfaces: type mockDatabase struct {\n    queryFunc func(ctx context.Context, sql string) ([]Row, error)\n}\n\nfunc (m mockDatabase) Query(ctx context.Context, sql string) ([]Row, error) {\n    if m.queryFunc != nil {\n        return m.queryFunc(ctx, sql)\n    }\n    return nil, nil\n}\n\nfunc TestQueryHandler(t *testing.T) {\n    key := slushtesting.StartRegistry(t)\n\n    // Configure mock behavior\n    slush.Register[Database](key, mockDatabase{\n        queryFunc: func(ctx context.Context, sql string) ([]Row, error) {\n            if sql == \"SELECT * FROM users\" {\n                return []Row{{ID: 1, Name: \"Alice\"}}, nil\n            }\n            return nil, errors.New(\"unexpected query\")\n        },\n    })\n\n    // Test the handler\n    result := handleQuery(context.Background(), \"SELECT * FROM users\")\n    // Assert...\n}",{"id":1956,"title":1957,"titles":1958,"content":102,"level":117},"/v0.0.3/guides/testing#testing-guards","Testing Guards",[1735],{"id":1960,"title":1961,"titles":1962,"content":1963,"level":147},"/v0.0.3/guides/testing#test-that-guards-are-invoked","Test that guards are invoked",[1735,1957],"func TestGuardIsInvoked(t *testing.T) {\n    key := slushtesting.StartRegistry(t)\n\n    var guardCalled bool\n    slush.Register[Database](key, mockDB{}).\n        Guard(func(ctx context.Context) error {\n            guardCalled = true\n            return nil\n        })\n\n    _, _ = slush.Use[Database](context.Background())\n\n    if !guardCalled {\n        t.Error(\"guard was not invoked\")\n    }\n}",{"id":1965,"title":1966,"titles":1967,"content":1968,"level":147},"/v0.0.3/guides/testing#test-guard-rejection","Test guard rejection",[1735,1957],"func TestGuardRejectsUnauthorized(t *testing.T) {\n    key := slushtesting.StartRegistry(t)\n\n    slush.Register[Database](key, mockDB{}).\n        Guard(requireAuth)\n\n    // Context without auth\n    ctx := context.Background()\n    _, err := slush.Use[Database](ctx)\n\n    if !errors.Is(err, slush.ErrAccessDenied) {\n        t.Errorf(\"expected ErrAccessDenied, got %v\", err)\n    }\n}",{"id":1970,"title":1971,"titles":1972,"content":1973,"level":147},"/v0.0.3/guides/testing#test-guard-passes-with-valid-context","Test guard passes with valid context",[1735,1957],"func TestGuardAllowsAuthorized(t *testing.T) {\n    key := slushtesting.StartRegistry(t)\n\n    slush.Register[Database](key, mockDB{}).\n        Guard(requireAuth)\n\n    // Context with auth\n    ctx := auth.WithUser(context.Background(), &User{ID: 1})\n    db, err := slush.Use[Database](ctx)\n\n    if err != nil {\n        t.Errorf(\"expected access, got %v\", err)\n    }\n    if db == nil {\n        t.Error(\"expected service, got nil\")\n    }\n}",{"id":1975,"title":1976,"titles":1977,"content":1978,"level":117},"/v0.0.3/guides/testing#test-helpers","Test Helpers",[1735],"slush's testing package provides common guard helpers: import slushtesting \"github.com/zoobzio/slush/testing\"\n\nfunc TestWithAllowAllGuard(t *testing.T) {\n    key := slushtesting.StartRegistry(t)\n\n    slush.Register[Database](key, mockDB{}).\n        Guard(slushtesting.AllowAllGuard())\n\n    // Access always succeeds\n    _, err := slush.Use[Database](context.Background())\n    if err != nil {\n        t.Error(\"AllowAllGuard should permit access\")\n    }\n}\n\nfunc TestWithDenyAllGuard(t *testing.T) {\n    key := slushtesting.StartRegistry(t)\n\n    slush.Register[Database](key, mockDB{}).\n        Guard(slushtesting.DenyAllGuard())\n\n    // Access always fails\n    _, err := slush.Use[Database](context.Background())\n    if !errors.Is(err, slush.ErrAccessDenied) {\n        t.Error(\"DenyAllGuard should deny access\")\n    }\n}\n\nfunc TestGuardInvocationCount(t *testing.T) {\n    key := slushtesting.StartRegistry(t)\n\n    var count int\n    slush.Register[Database](key, mockDB{}).\n        Guard(slushtesting.CountingGuard(&count))\n\n    _, _ = slush.Use[Database](context.Background())\n    _, _ = slush.Use[Database](context.Background())\n\n    if count != 2 {\n        t.Errorf(\"expected 2 invocations, got %d\", count)\n    }\n}",{"id":1980,"title":1981,"titles":1982,"content":1983,"level":117},"/v0.0.3/guides/testing#testing-service-enumeration","Testing Service Enumeration",[1735],"func TestServicesReturnsRegistered(t *testing.T) {\n    key := slushtesting.StartRegistry(t)\n\n    slush.Register[Database](key, mockDB{})\n    slush.Register[Mailer](key, mockMailer{})\n\n    svcs, err := slush.Services(key)\n    if err != nil {\n        t.Fatalf(\"Services error: %v\", err)\n    }\n\n    if len(svcs) != 2 {\n        t.Errorf(\"expected 2 services, got %d\", len(svcs))\n    }\n}",{"id":1985,"title":1986,"titles":1987,"content":1988,"level":117},"/v0.0.3/guides/testing#table-driven-tests","Table-Driven Tests",[1735],"For comprehensive guard testing: func TestAccessControl(t *testing.T) {\n    tests := []struct {\n        name      string\n        ctx       context.Context\n        wantErr   error\n    }{\n        {\n            name:    \"anonymous user denied\",\n            ctx:     context.Background(),\n            wantErr: slush.ErrAccessDenied,\n        },\n        {\n            name:    \"regular user denied\",\n            ctx:     auth.WithUser(context.Background(), &User{Role: \"user\"}),\n            wantErr: slush.ErrAccessDenied,\n        },\n        {\n            name:    \"admin user allowed\",\n            ctx:     auth.WithUser(context.Background(), &User{Role: \"admin\"}),\n            wantErr: nil,\n        },\n    }\n\n    for _, tt := range tests {\n        t.Run(tt.name, func(t *testing.T) {\n            key := slushtesting.StartRegistry(t)\n            slush.Register[AdminService](key, mockAdminService{}).\n                Guard(requireRole(\"admin\"))\n\n            _, err := slush.Use[AdminService](tt.ctx)\n\n            if tt.wantErr == nil && err != nil {\n                t.Errorf(\"expected no error, got %v\", err)\n            }\n            if tt.wantErr != nil && !errors.Is(err, tt.wantErr) {\n                t.Errorf(\"expected %v, got %v\", tt.wantErr, err)\n            }\n        })\n    }\n}",{"id":1990,"title":1820,"titles":1991,"content":1992,"level":117},"/v0.0.3/guides/testing#next-steps",[1735],"Troubleshooting — Debug common issuesGuards Guide — Write testable guards html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}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 pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}",{"id":1994,"title":1995,"titles":1996,"content":1997,"level":110},"/v0.0.3/guides/troubleshooting","Troubleshooting",[],"Debug common slush issues",{"id":1999,"title":1995,"titles":2000,"content":2001,"level":110},"/v0.0.3/guides/troubleshooting#troubleshooting",[],"Common issues and how to resolve them.",{"id":2003,"title":2004,"titles":2005,"content":2006,"level":117},"/v0.0.3/guides/troubleshooting#errnotfound-service-not-registered","ErrNotFound: Service Not Registered",[1995],"Symptom: db, err := slush.Use[Database](ctx)\n// err is slush.ErrNotFound Causes and solutions:",{"id":2008,"title":2009,"titles":2010,"content":2011,"level":147},"/v0.0.3/guides/troubleshooting#_1-service-was-never-registered","1. Service was never registered",[1995,2004],"Ensure Register is called before Use: // At startup\nslush.Register[Database](db)\n\n// Later\ndb, _ := slush.Use[Database](ctx)  // Works",{"id":2013,"title":2014,"titles":2015,"content":2016,"level":147},"/v0.0.3/guides/troubleshooting#_2-registered-with-different-type","2. Registered with different type",[1995,2004],"The type parameter must match exactly: type Database interface { Query() }\ntype DB interface { Query() }  // Different type!\n\nslush.Register[Database](impl)\nslush.Use[DB](ctx)  // ErrNotFound - DB != Database",{"id":2018,"title":2019,"titles":2020,"content":2021,"level":147},"/v0.0.3/guides/troubleshooting#_3-test-didnt-register-the-service","3. Test didn't register the service",[1995,2004],"Tests reset the registry. Each test must register needed services: func TestHandler(t *testing.T) {\n    slush.Reset()\n    // Forgot to register!\n\n    _, err := slush.Use[Database](ctx)  // ErrNotFound\n}",{"id":2023,"title":2024,"titles":2025,"content":2026,"level":147},"/v0.0.3/guides/troubleshooting#_4-race-condition-at-startup","4. Race condition at startup",[1995,2004],"If Use is called before registration completes: // Wrong: concurrent startup\ngo startServer()      // Calls Use[Database]\ngo initServices()     // Calls Register[Database]\n\n// Right: sequential initialization\ninitServices()\nstartServer()",{"id":2028,"title":2029,"titles":2030,"content":2031,"level":117},"/v0.0.3/guides/troubleshooting#erraccessdenied-guard-rejected-request","ErrAccessDenied: Guard Rejected Request",[1995],"Symptom: db, err := slush.Use[Database](ctx)\n// err wraps slush.ErrAccessDenied Debugging steps:",{"id":2033,"title":2034,"titles":2035,"content":2036,"level":147},"/v0.0.3/guides/troubleshooting#_1-check-the-wrapped-error","1. Check the wrapped error",[1995,2029],"ErrAccessDenied wraps the guard's error: if errors.Is(err, slush.ErrAccessDenied) {\n    // Unwrap to see guard's reason\n    fmt.Println(err)  // \"slush: access denied: missing permission: db:read\"\n}",{"id":2038,"title":2039,"titles":2040,"content":2041,"level":147},"/v0.0.3/guides/troubleshooting#_2-verify-context-has-required-values","2. Verify context has required values",[1995,2029],"Guards typically check context values: func requireAuth(ctx context.Context) error {\n    if auth.UserFromContext(ctx) == nil {\n        return errors.New(\"unauthenticated\")\n    }\n    return nil\n} Ensure middleware sets these values before the handler runs.",{"id":2043,"title":2044,"titles":2045,"content":2046,"level":147},"/v0.0.3/guides/troubleshooting#_3-check-guard-order","3. Check guard order",[1995,2029],"Guards run in registration order. An early guard may reject before a later guard that would pass: slush.Register[Database](db).\n    Guard(requireAdmin).   // Fails first\n    Guard(requireAuth)     // Never reached",{"id":2048,"title":2049,"titles":2050,"content":2051,"level":147},"/v0.0.3/guides/troubleshooting#_4-add-debug-logging-to-guards","4. Add debug logging to guards",[1995,2029],"Temporarily log guard execution: func requireAuth(ctx context.Context) error {\n    user := auth.UserFromContext(ctx)\n    log.Printf(\"requireAuth: user=%v\", user)\n    if user == nil {\n        return errors.New(\"unauthenticated\")\n    }\n    return nil\n}",{"id":2053,"title":2054,"titles":2055,"content":2056,"level":147},"/v0.0.3/guides/troubleshooting#_5-listen-to-capitan-events","5. Listen to capitan events",[1995,2029],"slush emits SignalDenied with error details: capitan.Hook(slush.SignalDenied, func(e *capitan.Event) {\n    iface, _ := e.String(slush.KeyInterface)\n    errMsg, _ := e.String(slush.KeyError)\n    log.Printf(\"Access denied: %s - %s\", iface, errMsg)\n})",{"id":2058,"title":2059,"titles":2060,"content":2061,"level":117},"/v0.0.3/guides/troubleshooting#panic-in-mustuse","Panic in MustUse",[1995],"Symptom: db := slush.MustUse[Database](ctx)\n// panic: slush: service not registered Solution: MustUse is for bootstrap code where failure is unrecoverable. Use Use with error handling for runtime lookups: // Bootstrap (panic is appropriate)\nfunc main() {\n    initServices()\n    logger := slush.MustUse[Logger](ctx)  // OK to panic\n}\n\n// Runtime (handle errors)\nfunc handleRequest(ctx context.Context) error {\n    db, err := slush.Use[Database](ctx)\n    if err != nil {\n        return err  // Don't panic\n    }\n}",{"id":2063,"title":2064,"titles":2065,"content":2066,"level":117},"/v0.0.3/guides/troubleshooting#tests-interfering-with-each-other","Tests Interfering with Each Other",[1995],"Symptom: Tests pass individually but fail when run together. Cause: Global registry state leaking between tests. Solution: Reset at start and end of each test: func TestA(t *testing.T) {\n    slush.Reset()\n    defer slush.Reset()\n    // ...\n}\n\nfunc TestB(t *testing.T) {\n    slush.Reset()\n    defer slush.Reset()\n    // ...\n} Or use the testing helper: import slushtesting \"github.com/zoobzio/slush/testing\"\n\nfunc TestA(t *testing.T) {\n    slushtesting.ResetRegistry(t)  // Handles cleanup\n    // ...\n}",{"id":2068,"title":2069,"titles":2070,"content":2071,"level":117},"/v0.0.3/guides/troubleshooting#service-returns-wrong-implementation","Service Returns Wrong Implementation",[1995],"Symptom: Use returns an unexpected implementation. Cause: Later registration overwrote earlier one: slush.Register[Database](postgresDB)\nslush.Register[Database](mockDB)  // Overwrites!\n\ndb, _ := slush.Use[Database](ctx)  // Returns mockDB Solution: Register each interface type once. If you need multiple implementations, use different interface types or a factory pattern.",{"id":2073,"title":2074,"titles":2075,"content":2076,"level":117},"/v0.0.3/guides/troubleshooting#guards-not-being-called","Guards Not Being Called",[1995],"Symptom: Guard function never executes. Possible causes:",{"id":2078,"title":2079,"titles":2080,"content":2081,"level":147},"/v0.0.3/guides/troubleshooting#_1-guard-added-after-use","1. Guard added after Use",[1995,2074],"handle := slush.Register[Database](db)\nslush.Use[Database](ctx)  // No guards yet!\nhandle.Guard(myGuard)     // Too late",{"id":2083,"title":2084,"titles":2085,"content":2086,"level":147},"/v0.0.3/guides/troubleshooting#_2-service-retrieved-from-cache","2. Service retrieved from cache",[1995,2074],"slush doesn't cache—every Use runs guards. If guards aren't running, check that you're calling slush.Use, not using a cached reference.",{"id":2088,"title":2089,"titles":2090,"content":2091,"level":147},"/v0.0.3/guides/troubleshooting#_3-using-wrong-service-type","3. Using wrong service type",[1995,2074],"slush.Register[Database](db).Guard(myGuard)\nslush.Use[OtherDatabase](ctx)  // Different type, no guards",{"id":2093,"title":2094,"titles":2095,"content":2096,"level":117},"/v0.0.3/guides/troubleshooting#debugging-with-services","Debugging with Services()",[1995],"List all registered services: for _, svc := range slush.Services() {\n    fmt.Printf(\"Interface: %s\\n\", svc.Interface)\n    fmt.Printf(\"Impl: %s\\n\", svc.Impl)\n    fmt.Printf(\"Guards: %d\\n\", svc.GuardCount)\n    fmt.Println(\"---\")\n}",{"id":2098,"title":1820,"titles":2099,"content":2100,"level":117},"/v0.0.3/guides/troubleshooting#next-steps",[1995],"Testing Guide — Write reliable testsGuards Guide — Build correct guardscapitan Integration — Event-based debugging html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}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 pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .suWN2, html code.shiki .suWN2{--shiki-default:var(--shiki-tag)}",{"id":2102,"title":1877,"titles":2103,"content":2104,"level":110},"/v0.0.3/guides/guards",[],"Writing and composing access control guards",{"id":2106,"title":1877,"titles":2107,"content":2108,"level":110},"/v0.0.3/guides/guards#guards",[],"Guards are the core access control mechanism in slush. This guide covers writing, composing, and testing guards.",{"id":2110,"title":2111,"titles":2112,"content":2113,"level":117},"/v0.0.3/guides/guards#guard-signature","Guard Signature",[1877],"A guard is a function that validates context: type Guard func(ctx context.Context) error Return nil to allow accessReturn an error to deny access (wrapped with ErrAccessDenied)",{"id":2115,"title":2116,"titles":2117,"content":102,"level":117},"/v0.0.3/guides/guards#basic-guards","Basic Guards",[1877],{"id":2119,"title":2120,"titles":2121,"content":2122,"level":147},"/v0.0.3/guides/guards#static-guard","Static guard",[1877,2116],"Always allows or denies: func allowAll(ctx context.Context) error {\n    return nil\n}\n\nfunc denyAll(ctx context.Context) error {\n    return errors.New(\"access denied\")\n}",{"id":2124,"title":2125,"titles":2126,"content":2127,"level":147},"/v0.0.3/guides/guards#context-checking-guard","Context-checking guard",[1877,2116],"Validates values in context: func requireAuth(ctx context.Context) error {\n    if auth.UserFromContext(ctx) == nil {\n        return errors.New(\"unauthenticated\")\n    }\n    return nil\n}",{"id":2129,"title":2130,"titles":2131,"content":2132,"level":117},"/v0.0.3/guides/guards#parameterized-guards","Parameterized Guards",[1877],"Guards that take configuration return a Guard: func requireRole(role string) slush.Guard {\n    return func(ctx context.Context) error {\n        user := auth.UserFromContext(ctx)\n        if user == nil {\n            return errors.New(\"unauthenticated\")\n        }\n        if user.Role != role {\n            return fmt.Errorf(\"requires role: %s\", role)\n        }\n        return nil\n    }\n}\n\n// Usage\nslush.Register[AdminAPI](key, api).\n    Guard(requireRole(\"admin\"))",{"id":2134,"title":2135,"titles":2136,"content":2137,"level":147},"/v0.0.3/guides/guards#multiple-parameters","Multiple parameters",[1877,2130],"func requirePermissions(perms ...string) slush.Guard {\n    return func(ctx context.Context) error {\n        user := auth.UserFromContext(ctx)\n        if user == nil {\n            return errors.New(\"unauthenticated\")\n        }\n        for _, perm := range perms {\n            if !user.HasPermission(perm) {\n                return fmt.Errorf(\"missing permission: %s\", perm)\n            }\n        }\n        return nil\n    }\n}\n\nslush.Register[Database](key, db).\n    Guard(requirePermissions(\"db:read\", \"db:write\"))",{"id":2139,"title":2140,"titles":2141,"content":102,"level":117},"/v0.0.3/guides/guards#composing-guards","Composing Guards",[1877],{"id":2143,"title":2144,"titles":2145,"content":2146,"level":147},"/v0.0.3/guides/guards#chaining","Chaining",[1877,2140],"Guards chain via .Guard(): slush.Register[Database](key, db).\n    Guard(requireAuth).\n    Guard(requireRole(\"service\")).\n    Guard(requirePermission(\"db:access\")) Guards run in order; first error stops the chain.",{"id":2148,"title":2149,"titles":2150,"content":2151,"level":147},"/v0.0.3/guides/guards#and-composition","AND composition",[1877,2140],"All guards must pass: func and(guards ...slush.Guard) slush.Guard {\n    return func(ctx context.Context) error {\n        for _, g := range guards {\n            if err := g(ctx); err != nil {\n                return err\n            }\n        }\n        return nil\n    }\n}\n\nslush.Register[Database](key, db).\n    Guard(and(requireAuth, requireRole(\"admin\")))",{"id":2153,"title":2154,"titles":2155,"content":2156,"level":147},"/v0.0.3/guides/guards#or-composition","OR composition",[1877,2140],"At least one guard must pass: func or(guards ...slush.Guard) slush.Guard {\n    return func(ctx context.Context) error {\n        var lastErr error\n        for _, g := range guards {\n            if err := g(ctx); err == nil {\n                return nil\n            } else {\n                lastErr = err\n            }\n        }\n        return lastErr\n    }\n}\n\n// Allow admins OR the resource owner\nslush.Register[Resource](key, r).\n    Guard(or(requireRole(\"admin\"), requireOwner))",{"id":2158,"title":2159,"titles":2160,"content":2161,"level":147},"/v0.0.3/guides/guards#not-composition","NOT composition",[1877,2140],"Invert a guard: func not(g slush.Guard) slush.Guard {\n    return func(ctx context.Context) error {\n        if err := g(ctx); err != nil {\n            return nil  // Original failed, so NOT passes\n        }\n        return errors.New(\"guard passed when it should fail\")\n    }\n}",{"id":2163,"title":2164,"titles":2165,"content":2166,"level":117},"/v0.0.3/guides/guards#stateful-guards","Stateful Guards",[1877],"Guards can maintain state across invocations:",{"id":2168,"title":2169,"titles":2170,"content":2171,"level":147},"/v0.0.3/guides/guards#rate-limiting","Rate limiting",[1877,2164],"func rateLimit(perSecond int) slush.Guard {\n    limiter := rate.NewLimiter(rate.Limit(perSecond), perSecond)\n    return func(ctx context.Context) error {\n        if !limiter.Allow() {\n            return errors.New(\"rate limited\")\n        }\n        return nil\n    }\n}\n\nslush.Register[ExternalAPI](key, api).\n    Guard(rateLimit(100))",{"id":2173,"title":2174,"titles":2175,"content":2176,"level":147},"/v0.0.3/guides/guards#circuit-breaker","Circuit breaker",[1877,2164],"func circuitBreaker(threshold int, resetAfter time.Duration) slush.Guard {\n    var (\n        mu       sync.Mutex\n        failures int\n        openedAt time.Time\n    )\n\n    return func(ctx context.Context) error {\n        mu.Lock()\n        defer mu.Unlock()\n\n        // Check if circuit is open\n        if failures >= threshold {\n            if time.Since(openedAt) \u003C resetAfter {\n                return errors.New(\"circuit open\")\n            }\n            // Reset after cooldown\n            failures = 0\n        }\n        return nil\n    }\n}",{"id":2178,"title":2179,"titles":2180,"content":2181,"level":117},"/v0.0.3/guides/guards#async-safe-guards","Async-Safe Guards",[1877],"Guards may be called concurrently. Ensure thread safety: func countingGuard() (slush.Guard, func() int) {\n    var count atomic.Int64\n\n    guard := func(ctx context.Context) error {\n        count.Add(1)\n        return nil\n    }\n\n    getCount := func() int {\n        return int(count.Load())\n    }\n\n    return guard, getCount\n}",{"id":2183,"title":2184,"titles":2185,"content":2186,"level":117},"/v0.0.3/guides/guards#error-messages","Error Messages",[1877],"Provide clear, actionable error messages: // Bad\nreturn errors.New(\"denied\")\n\n// Good\nreturn errors.New(\"missing permission: db:write\")\n\n// Better (with context)\nreturn fmt.Errorf(\"user %s lacks permission: db:write\", user.ID) Error messages appear in: The wrapped error returned by Usecapitan events (KeyError field)Logs if you're logging access denials",{"id":2188,"title":1957,"titles":2189,"content":2190,"level":117},"/v0.0.3/guides/guards#testing-guards",[1877],"Guards are plain functions—test them directly: func TestRequireAuth(t *testing.T) {\n    // Without user\n    err := requireAuth(context.Background())\n    if err == nil {\n        t.Error(\"expected error without user\")\n    }\n\n    // With user\n    ctx := auth.WithUser(context.Background(), &User{ID: 1})\n    err = requireAuth(ctx)\n    if err != nil {\n        t.Errorf(\"unexpected error: %v\", err)\n    }\n}\n\nfunc TestRequireRole(t *testing.T) {\n    guard := requireRole(\"admin\")\n\n    tests := []struct {\n        name    string\n        user    *User\n        wantErr bool\n    }{\n        {\"no user\", nil, true},\n        {\"wrong role\", &User{Role: \"user\"}, true},\n        {\"correct role\", &User{Role: \"admin\"}, false},\n    }\n\n    for _, tt := range tests {\n        t.Run(tt.name, func(t *testing.T) {\n            ctx := context.Background()\n            if tt.user != nil {\n                ctx = auth.WithUser(ctx, tt.user)\n            }\n            err := guard(ctx)\n            if (err != nil) != tt.wantErr {\n                t.Errorf(\"got err=%v, wantErr=%v\", err, tt.wantErr)\n            }\n        })\n    }\n}",{"id":2192,"title":1820,"titles":2193,"content":2194,"level":117},"/v0.0.3/guides/guards#next-steps",[1877],"sctx Integration — Cryptographic guardsTesting Guide — Test services with guardsTypes Reference — Guard type documentation html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}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 pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}",{"id":2196,"title":2197,"titles":2198,"content":2199,"level":110},"/v0.0.3/integrations/sentinel","sentinel Integration",[],"Service metadata extraction with sentinel",{"id":2201,"title":2197,"titles":2202,"content":2203,"level":110},"/v0.0.3/integrations/sentinel#sentinel-integration",[],"slush integrates with sentinel to provide type metadata for registered services.",{"id":2205,"title":2206,"titles":2207,"content":2208,"level":117},"/v0.0.3/integrations/sentinel#what-sentinel-provides","What sentinel Provides",[2197],"sentinel is a struct introspection library that extracts metadata from Go types: Field names, types, and tagsType relationships (references, collections, embeddings)Fully qualified domain names (FQDNs) This metadata enables documentation generation, schema export, and ERD visualization.",{"id":2210,"title":2211,"titles":2212,"content":2213,"level":117},"/v0.0.3/integrations/sentinel#how-slush-uses-sentinel","How slush Uses sentinel",[2197],"When you call Services(), slush looks up each implementation's metadata from sentinel's cache: svcs := slush.Services()\nfor _, svc := range svcs {\n    fmt.Printf(\"Interface: %s\\n\", svc.Interface)\n    fmt.Printf(\"Impl: %s\\n\", svc.Impl)\n    fmt.Printf(\"Fields: %d\\n\", len(svc.Metadata.Fields))\n}",{"id":2215,"title":2216,"titles":2217,"content":2218,"level":117},"/v0.0.3/integrations/sentinel#the-pipeline","The Pipeline",[2197],"Your App                    sentinel                 slush\n────────                    ────────                 ─────\n\n1. Define types\n   type UserService struct {\n       db Database\n   }\n\n2. Scan at startup  ──────► sentinel.Scan[UserService]()\n                            (caches metadata)\n\n3. Register service ──────────────────────────────────► Register[UserAPI](svc)\n                                                        (stores impl FQDN)\n\n4. Enumerate        ◄─────────────────────────────────  Services()\n                            sentinel.Lookup(fqdn)       (returns ServiceInfo\n                            (retrieves metadata)         with Metadata)",{"id":2220,"title":2221,"titles":2222,"content":102,"level":117},"/v0.0.3/integrations/sentinel#setup","Setup",[2197],{"id":2224,"title":2225,"titles":2226,"content":2227,"level":147},"/v0.0.3/integrations/sentinel#_1-scan-your-types","1. Scan your types",[2197,2221],"Before registering services, scan your domain types with sentinel: func main() {\n    // Scan root type - discovers related types\n    sentinel.Scan[UserService]()\n    sentinel.Scan[OrderService]()\n\n    // Now register services\n    slush.Register[UserAPI](userService)\n    slush.Register[OrderAPI](orderService)\n}",{"id":2229,"title":2230,"titles":2231,"content":2232,"level":147},"/v0.0.3/integrations/sentinel#_2-query-metadata","2. Query metadata",[2197,2221],"Use Services() to get metadata for all registered services: func handleSchemaEndpoint(w http.ResponseWriter, r *http.Request) {\n    svcs := slush.Services()\n\n    schema := make(map[string]any)\n    for _, svc := range svcs {\n        schema[svc.Interface] = map[string]any{\n            \"impl\":       svc.Impl,\n            \"fields\":     svc.Metadata.Fields,\n            \"guards\":     svc.GuardCount,\n        }\n    }\n\n    json.NewEncoder(w).Encode(schema)\n}",{"id":2234,"title":2235,"titles":2236,"content":2237,"level":117},"/v0.0.3/integrations/sentinel#what-slush-provides-to-sentinel","What slush Provides to sentinel",[2197],"sentinel Needsslush ProvidesType FQDN for lookupServiceInfo.Impl (implementation FQDN)Registration eventcapitan SignalRegistered with FQDN",{"id":2239,"title":2240,"titles":2241,"content":2242,"level":117},"/v0.0.3/integrations/sentinel#serviceinfo-fields","ServiceInfo Fields",[2197],"type ServiceInfo struct {\n    Interface  string            // Interface FQDN (lookup key)\n    Impl       string            // Implementation FQDN\n    Metadata   sentinel.Metadata // From sentinel.Lookup()\n    GuardCount int               // Number of guards\n} The Metadata field contains sentinel's extracted information: type Metadata struct {\n    FQDN          string\n    TypeName      string\n    PackageName   string\n    Fields        []FieldMetadata\n    Relationships []TypeRelationship\n}",{"id":2244,"title":2245,"titles":2246,"content":2247,"level":117},"/v0.0.3/integrations/sentinel#example-erd-generation","Example: ERD Generation",[2197],"Combine slush and sentinel for service relationship visualization: func generateERD() string {\n    var buf strings.Builder\n    buf.WriteString(\"graph LR\\n\")\n\n    for _, svc := range slush.Services() {\n        // Node for service\n        buf.WriteString(fmt.Sprintf(\"  %s[%s]\\n\",\n            svc.Metadata.TypeName,\n            svc.Interface))\n\n        // Edges for relationships\n        for _, rel := range svc.Metadata.Relationships {\n            buf.WriteString(fmt.Sprintf(\"  %s --> %s\\n\",\n                svc.Metadata.TypeName,\n                rel.To))\n        }\n    }\n\n    return buf.String()\n}",{"id":2249,"title":2250,"titles":2251,"content":2252,"level":117},"/v0.0.3/integrations/sentinel#when-metadata-is-empty","When Metadata Is Empty",[2197],"ServiceInfo.Metadata will be empty if: Type wasn't scanned: Call sentinel.Scan[T]() before registrationFQDN mismatch: sentinel and slush compute FQDNs the same way, but verify with sentinel.Browse()Interface type: sentinel scans structs, not interfaces. The impl FQDN must be a struct. Debug with: // What sentinel has cached\nfor _, fqdn := range sentinel.Browse() {\n    fmt.Println(fqdn)\n}\n\n// What slush is looking for\nfor _, svc := range slush.Services() {\n    fmt.Printf(\"Looking for: %s\\n\", svc.Impl)\n}",{"id":2254,"title":1820,"titles":2255,"content":2256,"level":117},"/v0.0.3/integrations/sentinel#next-steps",[2197],"capitan Integration — Event emissionAPI Reference — Services() documentationsentinel Documentation — Full sentinel guide html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .suWN2, html code.shiki .suWN2{--shiki-default:var(--shiki-tag)}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}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 pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}",{"id":2258,"title":2259,"titles":2260,"content":2261,"level":110},"/v0.0.3/integrations/capitan","capitan Integration",[],"Service lifecycle events with capitan",{"id":2263,"title":2259,"titles":2264,"content":2265,"level":110},"/v0.0.3/integrations/capitan#capitan-integration",[],"slush emits lifecycle events through capitan for observability and debugging.",{"id":2267,"title":2268,"titles":2269,"content":2270,"level":117},"/v0.0.3/integrations/capitan#what-capitan-provides","What capitan Provides",[2259],"capitan is a type-safe event coordination library: Async event emission with per-signal workersTyped fields for structured event dataListeners and observers for event handlingNo-op if no listeners are registered",{"id":2272,"title":2273,"titles":2274,"content":2275,"level":117},"/v0.0.3/integrations/capitan#signals-emitted-by-slush","Signals Emitted by slush",[2259],"slush emits four signals: SignalSeverityWhenSignalRegisteredInfoService registeredSignalAccessedDebugService retrieved successfullySignalDeniedWarnGuard rejected accessSignalNotFoundWarnService not in registry",{"id":2277,"title":2278,"titles":2279,"content":2280,"level":117},"/v0.0.3/integrations/capitan#event-fields","Event Fields",[2259],"Events include typed fields: KeyTypeDescriptionKeyInterfacestringInterface FQDN (lookup key)KeyImplstringImplementation FQDNKeyErrorstringError message (denied/not-found only)",{"id":2282,"title":2283,"titles":2284,"content":102,"level":117},"/v0.0.3/integrations/capitan#listening-to-events","Listening to Events",[2259],{"id":2286,"title":2287,"titles":2288,"content":2289,"level":147},"/v0.0.3/integrations/capitan#single-signal","Single signal",[2259,2283],"import \"github.com/zoobzio/capitan\"\n\ncapitan.Hook(slush.SignalDenied, func(e *capitan.Event) {\n    iface, _ := e.String(slush.KeyInterface)\n    errMsg, _ := e.String(slush.KeyError)\n    log.Printf(\"Access denied: %s - %s\", iface, errMsg)\n})",{"id":2291,"title":2292,"titles":2293,"content":2294,"level":147},"/v0.0.3/integrations/capitan#multiple-signals","Multiple signals",[2259,2283],"capitan.Observe(func(e *capitan.Event) {\n    log.Printf(\"[%s] %s\", e.Signal().Name(), e.String(slush.KeyInterface))\n}, slush.SignalRegistered, slush.SignalAccessed, slush.SignalDenied)",{"id":2296,"title":2297,"titles":2298,"content":2299,"level":147},"/v0.0.3/integrations/capitan#all-slush-signals","All slush signals",[2259,2283],"slushSignals := []capitan.Signal{\n    slush.SignalRegistered,\n    slush.SignalAccessed,\n    slush.SignalDenied,\n    slush.SignalNotFound,\n}\n\ncapitan.Observe(handleSlushEvent, slushSignals...)",{"id":2301,"title":2302,"titles":2303,"content":102,"level":117},"/v0.0.3/integrations/capitan#use-cases","Use Cases",[2259],{"id":2305,"title":2306,"titles":2307,"content":2308,"level":147},"/v0.0.3/integrations/capitan#access-logging","Access logging",[2259,2302],"capitan.Hook(slush.SignalAccessed, func(e *capitan.Event) {\n    iface, _ := e.String(slush.KeyInterface)\n    impl, _ := e.String(slush.KeyImpl)\n\n    accessLog.Info(\"service accessed\",\n        \"interface\", iface,\n        \"impl\", impl,\n        \"time\", e.Timestamp())\n})",{"id":2310,"title":2311,"titles":2312,"content":2313,"level":147},"/v0.0.3/integrations/capitan#security-alerting","Security alerting",[2259,2302],"capitan.Hook(slush.SignalDenied, func(e *capitan.Event) {\n    iface, _ := e.String(slush.KeyInterface)\n    errMsg, _ := e.String(slush.KeyError)\n\n    // Alert on repeated denials\n    securityMonitor.RecordDenial(iface, errMsg)\n})",{"id":2315,"title":2316,"titles":2317,"content":2318,"level":147},"/v0.0.3/integrations/capitan#metrics","Metrics",[2259,2302],"var (\n    accessCounter = prometheus.NewCounterVec(\n        prometheus.CounterOpts{Name: \"slush_access_total\"},\n        []string{\"interface\", \"result\"},\n    )\n)\n\ncapitan.Hook(slush.SignalAccessed, func(e *capitan.Event) {\n    iface, _ := e.String(slush.KeyInterface)\n    accessCounter.WithLabelValues(iface, \"success\").Inc()\n})\n\ncapitan.Hook(slush.SignalDenied, func(e *capitan.Event) {\n    iface, _ := e.String(slush.KeyInterface)\n    accessCounter.WithLabelValues(iface, \"denied\").Inc()\n})",{"id":2320,"title":2321,"titles":2322,"content":2323,"level":147},"/v0.0.3/integrations/capitan#service-discovery-tracking","Service discovery tracking",[2259,2302],"var registeredServices sync.Map\n\ncapitan.Hook(slush.SignalRegistered, func(e *capitan.Event) {\n    iface, _ := e.String(slush.KeyInterface)\n    impl, _ := e.String(slush.KeyImpl)\n    registeredServices.Store(iface, impl)\n})",{"id":2325,"title":2326,"titles":2327,"content":2328,"level":117},"/v0.0.3/integrations/capitan#no-op-when-unused","No-Op When Unused",[2259],"If you don't register listeners, capitan events are no-ops: // No listeners registered\nslush.Register[Database](db)  // Event emitted but discarded\nslush.Use[Database](ctx)      // Event emitted but discarded This means slush has no capitan overhead unless you opt in.",{"id":2330,"title":2331,"titles":2332,"content":2333,"level":117},"/v0.0.3/integrations/capitan#event-flow","Event Flow",[2259],"Register[Database](db)\n        │\n        ▼\ncapitan.Info(ctx, SignalRegistered,\n    KeyInterface.Field(\"...Database\"),\n    KeyImpl.Field(\"...postgresDB\"))\n        │\n        ▼\n┌───────────────────────────────────┐\n│ capitan worker for SignalRegistered│\n│                                   │\n│  → Listener 1: log event          │\n│  → Listener 2: update metrics     │\n│  → Listener 3: notify discovery   │\n└───────────────────────────────────┘",{"id":2335,"title":2336,"titles":2337,"content":2338,"level":117},"/v0.0.3/integrations/capitan#testing-with-events","Testing with Events",[2259],"Mock or capture events in tests: func TestEmitsRegisteredEvent(t *testing.T) {\n    var captured *capitan.Event\n\n    listener := capitan.Hook(slush.SignalRegistered, func(e *capitan.Event) {\n        captured = e\n    })\n    defer listener.Close()\n\n    slush.Reset()\n    slush.Register[Database](mockDB{})\n\n    // Wait for async event\n    time.Sleep(10 * time.Millisecond)\n\n    if captured == nil {\n        t.Fatal(\"expected event\")\n    }\n\n    iface, _ := captured.String(slush.KeyInterface)\n    if !strings.Contains(iface, \"Database\") {\n        t.Errorf(\"unexpected interface: %s\", iface)\n    }\n}",{"id":2340,"title":1820,"titles":2341,"content":2342,"level":117},"/v0.0.3/integrations/capitan#next-steps",[2259],"sctx Integration — Cryptographic guardsTroubleshooting — Debug with eventscapitan Documentation — Full capitan guide html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}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 pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"id":2344,"title":2345,"titles":2346,"content":2347,"level":110},"/v0.0.3/integrations/sctx","sctx Integration",[],"Cryptographic access control with sctx",{"id":2349,"title":2345,"titles":2350,"content":2351,"level":110},"/v0.0.3/integrations/sctx#sctx-integration",[],"slush integrates with sctx to provide cryptographically-verified service access control.",{"id":2353,"title":2354,"titles":2355,"content":2356,"level":117},"/v0.0.3/integrations/sctx#what-sctx-provides","What sctx Provides",[2345],"sctx is a cryptographic context library that: Issues signed tokens proving identity (certificate-based)Creates guards that verify token signatures and permissionsValidates tokens haven't expired or been replayedMaps certificates to permissions via configurable policies",{"id":2358,"title":2359,"titles":2360,"content":2361,"level":117},"/v0.0.3/integrations/sctx#the-value-proposition","The Value Proposition",[2345],"Traditional guards check context values set by middleware. An attacker who compromises middleware can set arbitrary values. sctx guards verify cryptographic proofs: // Traditional guard - trusts middleware\nfunc requireAdmin(ctx context.Context) error {\n    if ctx.Value(\"role\") != \"admin\" {  // Attacker can forge this\n        return errors.New(\"not admin\")\n    }\n    return nil\n}\n\n// sctx guard - verifies cryptographic proof\nfunc sctxGuard(ctx context.Context) error {\n    token := sctx.TokenFromContext(ctx)\n    return dbGuard.Validate(ctx, token)  // Signature verified\n}",{"id":2363,"title":2221,"titles":2364,"content":102,"level":117},"/v0.0.3/integrations/sctx#setup",[2345],{"id":2366,"title":2367,"titles":2368,"content":2369,"level":147},"/v0.0.3/integrations/sctx#_1-initialize-sctx-admin","1. Initialize sctx admin",[2345,2221],"import \"github.com/zoobzio/sctx\"\n\n// Your PKI setup\nprivateKey, _ := loadPrivateKey(\"server.key\")\ntrustedCAs, _ := loadCACertPool(\"ca.crt\")\n\n// Create admin service\nadmin, err := sctx.NewAdminService[AppMeta](privateKey, trustedCAs)\nif err != nil {\n    log.Fatal(err)\n}\n\n// Configure permission policy\nadmin.SetPolicy(func(cert *x509.Certificate) (*sctx.Context[AppMeta], error) {\n    // Map certificate to permissions\n    return &sctx.Context[AppMeta]{\n        Permissions: permissionsForCert(cert),\n        Metadata:    AppMeta{...},\n    }, nil\n})",{"id":2371,"title":2372,"titles":2373,"content":2374,"level":147},"/v0.0.3/integrations/sctx#_2-create-sctx-guards","2. Create sctx guards",[2345,2221],"// Admin token with guard-creation permission\nadminToken := getAdminToken()\n\n// Create a guard requiring specific permissions\ndbGuard, err := admin.CreateGuard(ctx, adminToken, \"db:read\", \"db:write\")\nif err != nil {\n    log.Fatal(err)\n}\n\nmailerGuard, err := admin.CreateGuard(ctx, adminToken, \"mailer:send\")\nif err != nil {\n    log.Fatal(err)\n}",{"id":2376,"title":2377,"titles":2378,"content":2379,"level":147},"/v0.0.3/integrations/sctx#_3-bridge-to-slush-guards","3. Bridge to slush guards",[2345,2221],"// Wrap sctx guard as slush guard\nfunc sctxGuard(guard sctx.Guard) slush.Guard {\n    return func(ctx context.Context) error {\n        token := sctx.TokenFromContext(ctx)\n        if token == \"\" {\n            return errors.New(\"no token in context\")\n        }\n        return guard.Validate(ctx, token)\n    }\n}\n\n// Register services with cryptographic guards\nslush.Register[Database](db).\n    Guard(sctxGuard(dbGuard))\n\nslush.Register[Mailer](mailer).\n    Guard(sctxGuard(mailerGuard))",{"id":2381,"title":2382,"titles":2383,"content":2384,"level":147},"/v0.0.3/integrations/sctx#_4-set-token-in-request-context","4. Set token in request context",[2345,2221],"In your HTTP middleware: func tokenMiddleware(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        // Extract token from header\n        token := sctx.SignedToken(r.Header.Get(\"X-Auth-Token\"))\n\n        // Add to context\n        ctx := sctx.WithToken(r.Context(), token)\n\n        next.ServeHTTP(w, r.WithContext(ctx))\n    })\n}",{"id":2386,"title":2387,"titles":2388,"content":2389,"level":117},"/v0.0.3/integrations/sctx#complete-example","Complete Example",[2345],"package main\n\nimport (\n    \"context\"\n    \"crypto/x509\"\n    \"errors\"\n    \"log\"\n    \"net/http\"\n\n    \"github.com/zoobzio/sctx\"\n    \"github.com/zoobzio/slush\"\n)\n\ntype AppMeta struct {\n    UserID string\n}\n\nfunc main() {\n    // === sctx setup ===\n    privateKey, _ := loadPrivateKey(\"server.key\")\n    trustedCAs, _ := loadCACertPool(\"ca.crt\")\n\n    admin, _ := sctx.NewAdminService[AppMeta](privateKey, trustedCAs)\n    admin.SetPolicy(policyFromCert)\n\n    // Create guards for different services\n    adminToken := bootstrapAdminToken(admin)\n    dbGuard, _ := admin.CreateGuard(context.Background(), adminToken, \"db:access\")\n    mailerGuard, _ := admin.CreateGuard(context.Background(), adminToken, \"mailer:send\")\n\n    // === slush setup ===\n    db := initDatabase()\n    mailer := initMailer()\n\n    slush.Register[Database](db).\n        Guard(wrapSctxGuard(dbGuard))\n\n    slush.Register[Mailer](mailer).\n        Guard(wrapSctxGuard(mailerGuard))\n\n    // === HTTP server ===\n    mux := http.NewServeMux()\n    mux.HandleFunc(\"/send-email\", handleSendEmail)\n\n    handler := tokenMiddleware(mux)\n    log.Fatal(http.ListenAndServe(\":8080\", handler))\n}\n\nfunc wrapSctxGuard(guard sctx.Guard) slush.Guard {\n    return func(ctx context.Context) error {\n        token := sctx.TokenFromContext(ctx)\n        if token == \"\" {\n            return errors.New(\"missing authentication token\")\n        }\n        return guard.Validate(ctx, token)\n    }\n}\n\nfunc handleSendEmail(w http.ResponseWriter, r *http.Request) {\n    ctx := r.Context()\n\n    // This will verify the token has \"mailer:send\" permission\n    mailer, err := slush.Use[Mailer](ctx)\n    if err != nil {\n        http.Error(w, err.Error(), http.StatusForbidden)\n        return\n    }\n\n    // Use the mailer...\n}",{"id":2391,"title":2392,"titles":2393,"content":2394,"level":117},"/v0.0.3/integrations/sctx#what-gets-verified","What Gets Verified",[2345],"When guard.Validate(ctx, token) runs: Signature verification — Token was signed by trusted keyExpiry check — Token hasn't expiredReplay protection — Nonce hasn't been seen beforePermission check — Token's context has required permissions",{"id":2396,"title":2397,"titles":2398,"content":2399,"level":117},"/v0.0.3/integrations/sctx#error-handling","Error Handling",[2345],"sctx errors are descriptive: db, err := slush.Use[Database](ctx)\nif errors.Is(err, slush.ErrAccessDenied) {\n    // Unwrap to see sctx reason\n    // \"token expired\"\n    // \"missing permission: db:write\"\n    // \"invalid signature\"\n    log.Println(err)\n}",{"id":2401,"title":2402,"titles":2403,"content":2404,"level":117},"/v0.0.3/integrations/sctx#combining-with-other-guards","Combining with Other Guards",[2345],"sctx guards compose with regular guards: slush.Register[Database](db).\n    Guard(wrapSctxGuard(dbGuard)).  // Cryptographic verification\n    Guard(rateLimit(100)).          // Rate limiting\n    Guard(circuitBreaker(5))        // Circuit breaker",{"id":2406,"title":1735,"titles":2407,"content":2408,"level":117},"/v0.0.3/integrations/sctx#testing",[2345],"In tests, you can bypass sctx or use test tokens: func TestWithMockGuard(t *testing.T) {\n    slushtesting.ResetRegistry(t)\n\n    // Use AllowAllGuard instead of sctx in tests\n    slush.Register[Database](mockDB{}).\n        Guard(slushtesting.AllowAllGuard())\n\n    // Test proceeds without PKI setup\n} Or create test tokens with a test CA: func TestWithTestToken(t *testing.T) {\n    admin, _ := sctx.NewAdminService[AppMeta](testKey, testCAs)\n    testToken := createTestToken(admin)\n\n    ctx := sctx.WithToken(context.Background(), testToken)\n    db, err := slush.Use[Database](ctx)\n    // ...\n}",{"id":2410,"title":1820,"titles":2411,"content":2412,"level":117},"/v0.0.3/integrations/sctx#next-steps",[2345],"Guards Guide — Guard composition patternsArchitecture — How slush processes guardssctx Documentation — Full sctx guide html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}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 pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"id":2414,"title":2415,"titles":2416,"content":2417,"level":110},"/v0.0.3/reference/api","API Reference",[],"Complete function documentation for slush",{"id":2419,"title":2415,"titles":2420,"content":2421,"level":110},"/v0.0.3/reference/api#api-reference",[],"Complete documentation for slush's public functions.",{"id":2423,"title":2424,"titles":2425,"content":102,"level":117},"/v0.0.3/reference/api#initialization","Initialization",[2415],{"id":2427,"title":2428,"titles":2429,"content":2430,"level":147},"/v0.0.3/reference/api#start","Start",[2415,2424],"func Start() Key Initializes the service registry and returns a capability key for registration. Returns: Key — Capability token required for Register, Freeze, and Services Panics: If called more than once Behavior: Must be called before any registrationReturns a Key that authorizes registration operationsCan only be called once per process Example: func main() {\n    key := slush.Start()\n\n    slush.Register[Database](key, db)\n    slush.Register[Logger](key, logger)\n\n    slush.Freeze(key)  // Optional: prevent further registration\n\n    startServer()\n} See also: Key, Freeze, Register",{"id":2432,"title":2433,"titles":2434,"content":2435,"level":147},"/v0.0.3/reference/api#freeze","Freeze",[2415,2424],"func Freeze(key Key) Prevents further service registration. Parameters: key — The key returned by Start() Panics: If key is invalid Behavior: After calling, any attempt to Register will panicUse and Services continue to work normallyCannot be undone (except via Reset in tests) Example: key := slush.Start()\nslush.Register[Database](key, db)\nslush.Freeze(key)\n\n// This would panic:\n// slush.Register[Logger](key, logger) See also: Start, Register",{"id":2437,"title":2438,"titles":2439,"content":102,"level":117},"/v0.0.3/reference/api#registration","Registration",[2415],{"id":2441,"title":128,"titles":2442,"content":2443,"level":147},"/v0.0.3/reference/api#register",[2415,2438],"func Register[T any](key Key, impl T) *Handle[T] Stores a service implementation keyed by interface type T. Parameters: key — The key returned by Start()impl — The service implementation to store Returns: *Handle[T] — Configuration handle for adding guards Panics: If Start() has not been calledIf key is invalidIf registry is frozen Behavior: Creates a new registry entry for type TSubsequent calls with the same T overwrite the previous registrationEmits SignalRegistered via capitanPre-computes FQDNs for interface and implementation Example: key := slush.Start()\nslush.Register[Database](key, postgresDB)\nslush.Register[Mailer](key, sendgridMailer).\n    Guard(requireAuth) See also: Start, Handle.Guard, Types Reference",{"id":2445,"title":2446,"titles":2447,"content":102,"level":117},"/v0.0.3/reference/api#retrieval","Retrieval",[2415],{"id":2449,"title":200,"titles":2450,"content":2451,"level":147},"/v0.0.3/reference/api#use",[2415,2446],"func Use[T any](ctx context.Context) (T, error) Retrieves a service by interface type, running all guards first. Parameters: ctx — Context passed to guard functions Returns: T — The service implementation (zero value on error)error — nil on success, ErrNotFound or ErrAccessDenied on failure Errors: ErrNotFound — No service registered for type TErrAccessDenied — A guard returned an error (wrapped with guard's error) Behavior: Looks up service by reflect.TypeFor[T]()Runs guards in registration order; stops on first errorEmits SignalAccessed, SignalDenied, or SignalNotFound via capitan Example: db, err := slush.Use[Database](ctx)\nif errors.Is(err, slush.ErrNotFound) {\n    // Service not registered\n}\nif errors.Is(err, slush.ErrAccessDenied) {\n    // Guard rejected - unwrap for details\n    log.Println(err)  // \"slush: access denied: missing permission\"\n} See also: MustUse, Guard",{"id":2453,"title":2454,"titles":2455,"content":2456,"level":147},"/v0.0.3/reference/api#mustuse","MustUse",[2415,2446],"func MustUse[T any](ctx context.Context) T Retrieves a service, panicking on error. For bootstrap code only. Parameters: ctx — Context passed to guard functions Returns: T — The service implementation Panics: If service not foundIf any guard returns an error Example: func main() {\n    initServices()\n\n    // OK to panic during bootstrap\n    logger := slush.MustUse[Logger](context.Background())\n    db := slush.MustUse[Database](context.Background())\n\n    startServer(logger, db)\n} See also: Use",{"id":2458,"title":2459,"titles":2460,"content":102,"level":117},"/v0.0.3/reference/api#enumeration","Enumeration",[2415],{"id":2462,"title":1872,"titles":2463,"content":2464,"level":147},"/v0.0.3/reference/api#services",[2415,2459],"func Services(key Key) ([]ServiceInfo, error) Returns information about all registered services. Parameters: key — The key returned by Start() Returns: []ServiceInfo — Slice of service metadata (see ServiceInfo)error — nil on success, ErrInvalidKey if key is invalid Errors: ErrInvalidKey — The provided key is invalid Behavior: Iterates all registry entriesLooks up sentinel metadata for each implementation FQDNThread-safe (holds read lock during iteration) Example: svcs, err := slush.Services(key)\nif err != nil {\n    log.Fatal(err)\n}\nfor _, svc := range svcs {\n    fmt.Printf(\"Interface: %s\\n\", svc.Interface)\n    fmt.Printf(\"Impl: %s\\n\", svc.Impl)\n    fmt.Printf(\"Guards: %d\\n\", svc.GuardCount)\n    fmt.Printf(\"Fields: %d\\n\", len(svc.Metadata.Fields))\n} See also: ServiceInfo, sentinel Integration",{"id":2466,"title":2467,"titles":2468,"content":2469,"level":117},"/v0.0.3/reference/api#testing-utilities","Testing Utilities",[2415],"Note: These functions are only available in test builds (-tags=testing or -tags=integration).\nThey do not exist in production binaries.",{"id":2471,"title":2472,"titles":2473,"content":2474,"level":147},"/v0.0.3/reference/api#unregister","Unregister",[2415,2467],"func Unregister[T any]() Removes a service by type. For testing only. Build tag: testing or integration Behavior: Deletes registry entry for type TNo-op if type not registeredThread-safe Example: func TestServiceReplacement(t *testing.T) {\n    slush.Reset()\n    key := slush.Start()\n    slush.Register[Database](key, mockDB{})\n\n    // Replace with different mock\n    slush.Unregister[Database]()\n    slush.Register[Database](key, otherMockDB{})\n} See also: Reset",{"id":2476,"title":2477,"titles":2478,"content":2479,"level":147},"/v0.0.3/reference/api#reset","Reset",[2415,2467],"func Reset() Clears all registered services and resets initialization state. For testing only. Build tag: testing or integration Behavior: Clears all registered servicesResets started and frozen stateInvalidates the current keyAllows Start() to be called again Example: func TestIsolated(t *testing.T) {\n    slush.Reset()\n    defer slush.Reset()\n\n    key := slush.Start()\n    slush.Register[Database](key, mockDB{})\n    // Test...\n} See also: Unregister, Testing Guide",{"id":2481,"title":2482,"titles":2483,"content":102,"level":117},"/v0.0.3/reference/api#handle-methods","Handle Methods",[2415],{"id":2485,"title":1016,"titles":2486,"content":2487,"level":147},"/v0.0.3/reference/api#handleguard",[2415,2482],"func (h *Handle[T]) Guard(g Guard) *Handle[T] Adds an access control check to the service. Chainable. Parameters: g — Guard function to add Returns: *Handle[T] — Same handle for chaining Behavior: Appends guard to service's guard listGuards run in order during Use[T]Thread-safe (acquires write lock) Example: slush.Register[Database](key, db).\n    Guard(requireAuth).\n    Guard(requireRole(\"admin\")).\n    Guard(rateLimit(100)) See also: Guard, Guards Guide",{"id":2489,"title":2490,"titles":2491,"content":102,"level":117},"/v0.0.3/reference/api#signals","Signals",[2415],{"id":2493,"title":2494,"titles":2495,"content":2496,"level":147},"/v0.0.3/reference/api#signalregistered","SignalRegistered",[2415,2490],"var SignalRegistered = capitan.NewSignal(\"slush.registered\", \"Service registered\") Emitted when a service is registered. Severity: Info. Fields: KeyInterface — Interface FQDNKeyImpl — Implementation FQDN",{"id":2498,"title":2499,"titles":2500,"content":2501,"level":147},"/v0.0.3/reference/api#signalaccessed","SignalAccessed",[2415,2490],"var SignalAccessed = capitan.NewSignal(\"slush.accessed\", \"Service accessed\") Emitted when a service is successfully retrieved. Severity: Debug. Fields: KeyInterface — Interface FQDNKeyImpl — Implementation FQDN",{"id":2503,"title":2504,"titles":2505,"content":2506,"level":147},"/v0.0.3/reference/api#signaldenied","SignalDenied",[2415,2490],"var SignalDenied = capitan.NewSignal(\"slush.denied\", \"Service access denied\") Emitted when a guard rejects access. Severity: Warn. Fields: KeyInterface — Interface FQDNKeyImpl — Implementation FQDNKeyError — Guard error message",{"id":2508,"title":2509,"titles":2510,"content":2511,"level":147},"/v0.0.3/reference/api#signalnotfound","SignalNotFound",[2415,2490],"var SignalNotFound = capitan.NewSignal(\"slush.not_found\", \"Service not found\") Emitted when a requested service isn't registered. Severity: Warn. Fields: KeyInterface — Interface FQDN",{"id":2513,"title":2514,"titles":2515,"content":102,"level":117},"/v0.0.3/reference/api#field-keys","Field Keys",[2415],{"id":2517,"title":2518,"titles":2519,"content":2520,"level":147},"/v0.0.3/reference/api#keyinterface","KeyInterface",[2415,2514],"var KeyInterface = capitan.NewStringKey(\"interface\") Event field for interface FQDN.",{"id":2522,"title":2523,"titles":2524,"content":2525,"level":147},"/v0.0.3/reference/api#keyimpl","KeyImpl",[2415,2514],"var KeyImpl = capitan.NewStringKey(\"impl\") Event field for implementation FQDN.",{"id":2527,"title":2528,"titles":2529,"content":2530,"level":147},"/v0.0.3/reference/api#keyerror","KeyError",[2415,2514],"var KeyError = capitan.NewStringKey(\"error\") Event field for error messages.",{"id":2532,"title":1820,"titles":2533,"content":2534,"level":117},"/v0.0.3/reference/api#next-steps",[2415],"Types Reference — Type documentationGuards Guide — Writing guardscapitan Integration — Event handling html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}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 pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .suWN2, html code.shiki .suWN2{--shiki-default:var(--shiki-tag)}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"id":2536,"title":2537,"titles":2538,"content":2539,"level":110},"/v0.0.3/reference/types","Types Reference",[],"Complete type documentation for slush",{"id":2541,"title":2537,"titles":2542,"content":2543,"level":110},"/v0.0.3/reference/types#types-reference",[],"Complete documentation for slush's public types.",{"id":2545,"title":2546,"titles":2547,"content":102,"level":117},"/v0.0.3/reference/types#core-types","Core Types",[2537],{"id":2549,"title":2550,"titles":2551,"content":2552,"level":147},"/v0.0.3/reference/types#key","Key",[2537,2546],"type Key struct {\n    // contains filtered or unexported fields\n} Capability token that authorizes service registration and enumeration. Returned by Start(). Obtaining a Key: Call Start() at application startupStart() can only be called once Operations requiring a Key: Register[T](key, impl) — Register a serviceFreeze(key) — Lock the registryServices(key) — Enumerate services Security: The inner pointer is unexported, preventing forgery via struct literalOnly code with access to the key can modify the registryKeys are invalidated by Reset() (testing only) Example: func main() {\n    key := slush.Start()\n\n    // Pass key to initialization code\n    initDatabase(key)\n    initServices(key)\n\n    // Optionally lock the registry\n    slush.Freeze(key)\n\n    // Key not needed for Use\n    db, _ := slush.Use[Database](ctx)\n}\n\nfunc initDatabase(key slush.Key) {\n    slush.Register[Database](key, newPostgresDB())\n} See also: Start, Freeze",{"id":2554,"title":875,"titles":2555,"content":2556,"level":147},"/v0.0.3/reference/types#guard",[2537,2546],"type Guard func(ctx context.Context) error A function that validates context before service access. Contract: Return nil to allow accessReturn an error to deny access Behavior when returned error is non-nil: Use[T] returns ErrAccessDenied wrapping your errorGuard chain stops (subsequent guards don't run)SignalDenied event emitted with error message Example: // Simple guard\nfunc requireAuth(ctx context.Context) error {\n    if auth.UserFromContext(ctx) == nil {\n        return errors.New(\"unauthenticated\")\n    }\n    return nil\n}\n\n// Parameterized guard\nfunc requirePermission(perm string) slush.Guard {\n    return func(ctx context.Context) error {\n        if !hasPermission(ctx, perm) {\n            return fmt.Errorf(\"missing permission: %s\", perm)\n        }\n        return nil\n    }\n} See also: Guards Guide, Handle.Guard",{"id":2558,"title":2559,"titles":2560,"content":2561,"level":147},"/v0.0.3/reference/types#handle","Handle",[2537,2546],"type Handle[T any] struct {\n    // contains filtered or unexported fields\n} Configuration handle for a registered service. Returned by Register[T]. Methods: MethodDescriptionGuard(g Guard) *Handle[T]Add access control check (chainable) Example: handle := slush.Register[Database](key, db)\nhandle.Guard(requireAuth)\nhandle.Guard(requireRole(\"admin\"))\n\n// Or chained\nslush.Register[Database](key, db).\n    Guard(requireAuth).\n    Guard(requireRole(\"admin\")) Internals: Handle wraps a generic *handle[T] that stores: The service implementation (impl T)Guard slice ([]Guard)Pre-computed FQDNs for interface and implementation See also: Register, Architecture",{"id":2563,"title":2564,"titles":2565,"content":2566,"level":147},"/v0.0.3/reference/types#serviceinfo","ServiceInfo",[2537,2546],"type ServiceInfo struct {\n    Interface  string            // Interface FQDN (the lookup key)\n    Impl       string            // Implementation FQDN\n    Metadata   sentinel.Metadata // Sentinel metadata from cache\n    GuardCount int               // Number of guards configured\n} Describes a registered service. Returned by Services(). Fields: FieldTypeDescriptionInterfacestringFully qualified name of the interface type (e.g., \"github.com/app.Database\")ImplstringFully qualified name of the implementation type (e.g., \"github.com/app.postgresDB\")Metadatasentinel.MetadataType metadata from sentinel's cache (may be empty if not scanned)GuardCountintNumber of guards attached to this service Example: svcs, err := slush.Services(key)\nif err != nil {\n    log.Fatal(err)\n}\nfor _, svc := range svcs {\n    fmt.Printf(\"Service: %s\\n\", svc.Interface)\n    fmt.Printf(\"  Impl: %s\\n\", svc.Impl)\n    fmt.Printf(\"  Guards: %d\\n\", svc.GuardCount)\n\n    if len(svc.Metadata.Fields) > 0 {\n        fmt.Printf(\"  Fields:\\n\")\n        for _, f := range svc.Metadata.Fields {\n            fmt.Printf(\"    - %s: %s\\n\", f.Name, f.Type)\n        }\n    }\n} See also: Services, sentinel Integration",{"id":2568,"title":2569,"titles":2570,"content":102,"level":117},"/v0.0.3/reference/types#errors","Errors",[2537],{"id":2572,"title":2573,"titles":2574,"content":2575,"level":147},"/v0.0.3/reference/types#errnotfound","ErrNotFound",[2537,2569],"var ErrNotFound = errors.New(\"slush: service not registered\") Returned by Use[T] when no service is registered for type T. Example: db, err := slush.Use[Database](ctx)\nif errors.Is(err, slush.ErrNotFound) {\n    // Handle missing service\n    log.Println(\"Database service not configured\")\n}",{"id":2577,"title":241,"titles":2578,"content":2579,"level":147},"/v0.0.3/reference/types#erraccessdenied",[2537,2569],"var ErrAccessDenied = errors.New(\"slush: access denied\") Returned by Use[T] when a guard returns an error. The guard's error is joined. Example: db, err := slush.Use[Database](ctx)\nif errors.Is(err, slush.ErrAccessDenied) {\n    // Guard rejected - full error includes reason\n    log.Println(err)  // \"slush: access denied\\nmissing permission: db:write\"\n\n    // Can also check the guard's specific error\n    var permErr *PermissionError\n    if errors.As(err, &permErr) {\n        log.Printf(\"Missing: %s\", permErr.Permission)\n    }\n}",{"id":2581,"title":2582,"titles":2583,"content":2584,"level":147},"/v0.0.3/reference/types#errinvalidkey","ErrInvalidKey",[2537,2569],"var ErrInvalidKey = errors.New(\"slush: invalid key\") Returned by Services() when an invalid key is provided. Example: svcs, err := slush.Services(key)\nif errors.Is(err, slush.ErrInvalidKey) {\n    log.Println(\"Invalid registry key\")\n} See also: Key, Services",{"id":2586,"title":2587,"titles":2588,"content":2589,"level":117},"/v0.0.3/reference/types#internal-types","Internal Types",[2537],"These types are not exported but understanding them helps with debugging.",{"id":2591,"title":2592,"titles":2593,"content":2594,"level":147},"/v0.0.3/reference/types#service-interface","service (interface)",[2537,2587],"type service interface {\n    fqdns() (iface, impl string)\n    guards() []Guard\n    guardCount() int\n} Internal interface that handle[T] satisfies. Enables heterogeneous storage in the registry map.",{"id":2596,"title":2597,"titles":2598,"content":2599,"level":147},"/v0.0.3/reference/types#handlet-generic-struct","handleT (generic struct)",[2537,2587],"type handle[T any] struct {\n    g             []Guard\n    interfaceFQDN string\n    implFQDN      string\n    impl          T\n} Internal generic struct that stores a service with full type safety. Satisfies the service interface for storage while preserving T for type-safe retrieval.",{"id":2601,"title":2602,"titles":2603,"content":2604,"level":117},"/v0.0.3/reference/types#sentinel-types","sentinel Types",[2537],"These types come from the sentinel package and appear in ServiceInfo.Metadata.",{"id":2606,"title":2607,"titles":2608,"content":2609,"level":147},"/v0.0.3/reference/types#sentinelmetadata","sentinel.Metadata",[2537,2602],"type Metadata struct {\n    FQDN          string\n    TypeName      string\n    PackageName   string\n    Fields        []FieldMetadata\n    Relationships []TypeRelationship\n} Type metadata extracted by sentinel. May be empty if the implementation wasn't scanned.",{"id":2611,"title":2612,"titles":2613,"content":2614,"level":147},"/v0.0.3/reference/types#sentinelfieldmetadata","sentinel.FieldMetadata",[2537,2602],"type FieldMetadata struct {\n    Name  string\n    Type  string\n    Kind  FieldKind\n    Tags  map[string]string\n    Index []int\n} Field-level metadata including struct tags. See also: sentinel Integration, sentinel Documentation",{"id":2616,"title":2617,"titles":2618,"content":2619,"level":117},"/v0.0.3/reference/types#capitan-types","capitan Types",[2537],"These types come from the capitan package and are used for event handling.",{"id":2621,"title":2622,"titles":2623,"content":2624,"level":147},"/v0.0.3/reference/types#capitansignal","capitan.Signal",[2537,2617],"Represents an event type. slush exports four signals: SignalRegisteredSignalAccessedSignalDeniedSignalNotFound",{"id":2626,"title":2627,"titles":2628,"content":2629,"level":147},"/v0.0.3/reference/types#capitanstringkey","capitan.StringKey",[2537,2617],"Typed key for string event fields. slush exports: KeyInterfaceKeyImplKeyError See also: capitan Integration, capitan Documentation",{"id":2631,"title":1820,"titles":2632,"content":2633,"level":117},"/v0.0.3/reference/types#next-steps",[2537],"API Reference — Function documentationConcepts — Mental modelsArchitecture — Internal design html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}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 pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .suWN2, html code.shiki .suWN2{--shiki-default:var(--shiki-tag)}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",[2635],{"title":2636,"path":2637,"stem":2638,"children":2639,"page":2653},"V003","/v0.0.3","v0.0.3",[2640,2654,2665,2676],{"title":2641,"path":2642,"stem":2643,"children":2644,"page":2653},"Learn","/v0.0.3/learn","v0.0.3/1.learn",[2645,2647,2649,2651],{"title":1796,"path":1795,"stem":2646,"description":1798},"v0.0.3/1.learn/1.overview",{"title":1825,"path":1824,"stem":2648,"description":1827},"v0.0.3/1.learn/2.quickstart",{"title":1863,"path":1862,"stem":2650,"description":1865},"v0.0.3/1.learn/3.concepts",{"title":1896,"path":1895,"stem":2652,"description":1898},"v0.0.3/1.learn/4.architecture",false,{"title":2655,"path":2656,"stem":2657,"children":2658,"page":2653},"Guides","/v0.0.3/guides","v0.0.3/2.guides",[2659,2661,2663],{"title":1735,"path":1938,"stem":2660,"description":1940},"v0.0.3/2.guides/1.testing",{"title":1995,"path":1994,"stem":2662,"description":1997},"v0.0.3/2.guides/2.troubleshooting",{"title":1877,"path":2102,"stem":2664,"description":2104},"v0.0.3/2.guides/3.guards",{"title":2666,"path":2667,"stem":2668,"children":2669,"page":2653},"Integrations","/v0.0.3/integrations","v0.0.3/3.integrations",[2670,2672,2674],{"title":2197,"path":2196,"stem":2671,"description":2199},"v0.0.3/3.integrations/1.sentinel",{"title":2259,"path":2258,"stem":2673,"description":2261},"v0.0.3/3.integrations/2.capitan",{"title":2345,"path":2344,"stem":2675,"description":2347},"v0.0.3/3.integrations/3.sctx",{"title":2677,"path":2678,"stem":2679,"children":2680,"page":2653},"Reference","/v0.0.3/reference","v0.0.3/4.reference",[2681,2683],{"title":2415,"path":2414,"stem":2682,"description":2417},"v0.0.3/4.reference/1.api",{"title":2537,"path":2536,"stem":2684,"description":2539},"v0.0.3/4.reference/2.types",[2686],{"title":2636,"path":2637,"stem":2638,"children":2687,"page":2653},[2688,2694,2699,2704],{"title":2641,"path":2642,"stem":2643,"children":2689,"page":2653},[2690,2691,2692,2693],{"title":1796,"path":1795,"stem":2646},{"title":1825,"path":1824,"stem":2648},{"title":1863,"path":1862,"stem":2650},{"title":1896,"path":1895,"stem":2652},{"title":2655,"path":2656,"stem":2657,"children":2695,"page":2653},[2696,2697,2698],{"title":1735,"path":1938,"stem":2660},{"title":1995,"path":1994,"stem":2662},{"title":1877,"path":2102,"stem":2664},{"title":2666,"path":2667,"stem":2668,"children":2700,"page":2653},[2701,2702,2703],{"title":2197,"path":2196,"stem":2671},{"title":2259,"path":2258,"stem":2673},{"title":2345,"path":2344,"stem":2675},{"title":2677,"path":2678,"stem":2679,"children":2705,"page":2653},[2706,2707],{"title":2415,"path":2414,"stem":2682},{"title":2537,"path":2536,"stem":2684},[2709],{"title":2636,"path":2637,"stem":2638,"children":2710,"page":2653},[2711,2717,2722,2727],{"title":2641,"path":2642,"stem":2643,"children":2712,"page":2653},[2713,2714,2715,2716],{"title":1796,"path":1795,"stem":2646,"description":1798},{"title":1825,"path":1824,"stem":2648,"description":1827},{"title":1863,"path":1862,"stem":2650,"description":1865},{"title":1896,"path":1895,"stem":2652,"description":1898},{"title":2655,"path":2656,"stem":2657,"children":2718,"page":2653},[2719,2720,2721],{"title":1735,"path":1938,"stem":2660,"description":1940},{"title":1995,"path":1994,"stem":2662,"description":1997},{"title":1877,"path":2102,"stem":2664,"description":2104},{"title":2666,"path":2667,"stem":2668,"children":2723,"page":2653},[2724,2725,2726],{"title":2197,"path":2196,"stem":2671,"description":2199},{"title":2259,"path":2258,"stem":2673,"description":2261},{"title":2345,"path":2344,"stem":2675,"description":2347},{"title":2677,"path":2678,"stem":2679,"children":2728,"page":2653},[2729,2730],{"title":2415,"path":2414,"stem":2682,"description":2417},{"title":2537,"path":2536,"stem":2684,"description":2539},1776114144201]