[{"data":1,"prerenderedAt":1066},["ShallowReactive",2],{"navigation_docs_en":3,"-en-cli-add-realtime":342,"-en-cli-add-realtime-surround":1061},[4,45,61,96,132,166,196,217,241,265],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":44},"Getting Started","i-lucide-rocket","\u002Fen\u002Fgetting-started","en\u002F1.getting-started",[10,15,20,24,29,34,39],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fen\u002Fgetting-started\u002Fintroduction","en\u002F1.getting-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Coming from Laravel","\u002Fen\u002Fgetting-started\u002Fcoming-from-laravel","en\u002F1.getting-started\u002F2.coming-from-laravel","i-lucide-arrow-right-left",{"title":21,"path":22,"stem":23,"icon":19},"Coming from Symfony","\u002Fen\u002Fgetting-started\u002Fcoming-from-symfony","en\u002F1.getting-started\u002F3.coming-from-symfony",{"title":25,"path":26,"stem":27,"icon":28},"Installation","\u002Fen\u002Fgetting-started\u002Finstallation","en\u002F1.getting-started\u002F4.installation","i-lucide-download",{"title":30,"path":31,"stem":32,"icon":33},"Your first module","\u002Fen\u002Fgetting-started\u002Ffirst-module","en\u002F1.getting-started\u002F5.first-module","i-lucide-package-plus",{"title":35,"path":36,"stem":37,"icon":38},"Project structure","\u002Fen\u002Fgetting-started\u002Fproject-structure","en\u002F1.getting-started\u002F6.project-structure","i-lucide-folder-tree",{"title":40,"path":41,"stem":42,"icon":43},"The shop sample application","\u002Fen\u002Fgetting-started\u002Fsample-app","en\u002F1.getting-started\u002F7.sample-app","i-lucide-shopping-cart",false,{"title":46,"icon":47,"path":48,"stem":49,"children":50,"page":44},"Reference","i-lucide-book-open","\u002Fen\u002Freference","en\u002F10.reference",[51,56],{"title":52,"path":53,"stem":54,"icon":55},"Configuration","\u002Fen\u002Freference\u002Fconfiguration","en\u002F10.reference\u002F1.configuration","i-lucide-settings",{"title":57,"path":58,"stem":59,"icon":60},"External dependencies","\u002Fen\u002Freference\u002Fdependencies","en\u002F10.reference\u002F2.dependencies","i-lucide-package",{"title":62,"icon":63,"path":64,"stem":65,"children":66,"page":44},"Concepts","i-lucide-lightbulb","\u002Fen\u002Fconcepts","en\u002F2.concepts",[67,72,77,82,86,91],{"title":68,"path":69,"stem":70,"icon":71},"Architecture","\u002Fen\u002Fconcepts\u002Farchitecture","en\u002F2.concepts\u002F1.architecture","i-lucide-layers",{"title":73,"path":74,"stem":75,"icon":76},"Application lifecycle","\u002Fen\u002Fconcepts\u002Fapplication-lifecycle","en\u002F2.concepts\u002F2.application-lifecycle","i-lucide-power",{"title":78,"path":79,"stem":80,"icon":81},"Error model","\u002Fen\u002Fconcepts\u002Ferror-model","en\u002F2.concepts\u002F3.error-model","i-lucide-shield-alert",{"title":52,"path":83,"stem":84,"icon":85},"\u002Fen\u002Fconcepts\u002Fconfiguration","en\u002F2.concepts\u002F4.configuration","i-lucide-settings-2",{"title":87,"path":88,"stem":89,"icon":90},"Codegen pipeline","\u002Fen\u002Fconcepts\u002Fcodegen-pipeline","en\u002F2.concepts\u002F5.codegen-pipeline","i-lucide-file-json",{"title":92,"path":93,"stem":94,"icon":95},"Design patterns","\u002Fen\u002Fconcepts\u002Fdesign-patterns","en\u002F2.concepts\u002F6.design-patterns","i-lucide-puzzle",{"title":97,"icon":98,"path":99,"stem":100,"children":101,"page":44},"HTTP & Routing","i-lucide-globe","\u002Fen\u002Fhttp","en\u002F3.http",[102,107,112,117,122,127],{"title":103,"path":104,"stem":105,"icon":106},"The server core","\u002Fen\u002Fhttp\u002Fserver","en\u002F3.http\u002F1.server","i-lucide-server",{"title":108,"path":109,"stem":110,"icon":111},"The Fiber engine","\u002Fen\u002Fhttp\u002Ffiber","en\u002F3.http\u002F2.fiber","i-lucide-zap",{"title":113,"path":114,"stem":115,"icon":116},"The chi engine","\u002Fen\u002Fhttp\u002Fchi","en\u002F3.http\u002F3.chi","i-lucide-route",{"title":118,"path":119,"stem":120,"icon":121},"Error responses","\u002Fen\u002Fhttp\u002Ferror-responses","en\u002F3.http\u002F4.error-responses","i-lucide-octagon-alert",{"title":123,"path":124,"stem":125,"icon":126},"Validation","\u002Fen\u002Fhttp\u002Fvalidation","en\u002F3.http\u002F5.validation","i-lucide-badge-check",{"title":128,"path":129,"stem":130,"icon":131},"Pagination","\u002Fen\u002Fhttp\u002Fpagination","en\u002F3.http\u002F6.pagination","i-lucide-list-ordered",{"title":133,"icon":134,"path":135,"stem":136,"children":137,"page":44},"Database","i-lucide-database","\u002Fen\u002Fdatabase","en\u002F4.database",[138,142,147,151,156,161],{"title":139,"path":140,"stem":141,"icon":134},"Overview","\u002Fen\u002Fdatabase\u002Foverview","en\u002F4.database\u002F1.overview",{"title":143,"path":144,"stem":145,"icon":146},"pgx","\u002Fen\u002Fdatabase\u002Fpgx","en\u002F4.database\u002F2.pgx","i-lucide-plug",{"title":148,"path":149,"stem":150,"icon":71},"bun","\u002Fen\u002Fdatabase\u002Fbun","en\u002F4.database\u002F3.bun",{"title":152,"path":153,"stem":154,"icon":155},"Transactions","\u002Fen\u002Fdatabase\u002Ftransactions","en\u002F4.database\u002F4.transactions","i-lucide-git-merge",{"title":157,"path":158,"stem":159,"icon":160},"Migrations","\u002Fen\u002Fdatabase\u002Fmigrations","en\u002F4.database\u002F5.migrations","i-lucide-file-stack",{"title":162,"path":163,"stem":164,"icon":165},"Seeders","\u002Fen\u002Fdatabase\u002Fseeding","en\u002F4.database\u002F6.seeding","i-lucide-sprout",{"title":167,"icon":168,"path":169,"stem":170,"children":171,"page":44},"Async & Background","i-lucide-workflow","\u002Fen\u002Fasync","en\u002F5.async",[172,176,181,186,191],{"title":173,"path":174,"stem":175,"icon":131},"Queue","\u002Fen\u002Fasync\u002Fqueue","en\u002F5.async\u002F1.queue",{"title":177,"path":178,"stem":179,"icon":180},"Events","\u002Fen\u002Fasync\u002Fevents","en\u002F5.async\u002F2.events","i-lucide-radio",{"title":182,"path":183,"stem":184,"icon":185},"Outbox","\u002Fen\u002Fasync\u002Foutbox","en\u002F5.async\u002F3.outbox","i-lucide-inbox",{"title":187,"path":188,"stem":189,"icon":190},"Scheduling","\u002Fen\u002Fasync\u002Fscheduler","en\u002F5.async\u002F4.scheduler","i-lucide-calendar-clock",{"title":192,"path":193,"stem":194,"icon":195},"Worker","\u002Fen\u002Fasync\u002Fworker","en\u002F5.async\u002F5.worker","i-lucide-cog",{"title":197,"icon":198,"path":199,"stem":200,"children":201,"page":44},"Security","i-lucide-shield-check","\u002Fen\u002Fsecurity","en\u002F6.security",[202,207,212],{"title":203,"path":204,"stem":205,"icon":206},"Authentication","\u002Fen\u002Fsecurity\u002Fauthentication","en\u002F6.security\u002F1.authentication","i-lucide-key-round",{"title":208,"path":209,"stem":210,"icon":211},"RBAC","\u002Fen\u002Fsecurity\u002Frbac","en\u002F6.security\u002F2.rbac","i-lucide-users",{"title":213,"path":214,"stem":215,"icon":216},"Policies","\u002Fen\u002Fsecurity\u002Fpolicies","en\u002F6.security\u002F3.policies","i-lucide-gavel",{"title":218,"icon":219,"path":220,"stem":221,"children":222,"page":44},"Observability","i-lucide-activity","\u002Fen\u002Fobservability","en\u002F7.observability",[223,226,231,236],{"title":139,"path":224,"stem":225,"icon":219},"\u002Fen\u002Fobservability\u002Foverview","en\u002F7.observability\u002F1.overview",{"title":227,"path":228,"stem":229,"icon":230},"Logging","\u002Fen\u002Fobservability\u002Flogging","en\u002F7.observability\u002F2.logging","i-lucide-scroll-text",{"title":232,"path":233,"stem":234,"icon":235},"OpenTelemetry","\u002Fen\u002Fobservability\u002Fopentelemetry","en\u002F7.observability\u002F3.opentelemetry","i-lucide-radar",{"title":237,"path":238,"stem":239,"icon":240},"Sentry","\u002Fen\u002Fobservability\u002Fsentry","en\u002F7.observability\u002F4.sentry","i-lucide-bug",{"title":242,"icon":243,"path":244,"stem":245,"children":246,"page":44},"Storage & Mail","i-lucide-hard-drive","\u002Fen\u002Fstorage-mail","en\u002F8.storage-mail",[247,251,256,261],{"title":248,"path":249,"stem":250,"icon":243},"Storage","\u002Fen\u002Fstorage-mail\u002Fstorage","en\u002F8.storage-mail\u002F1.storage",{"title":252,"path":253,"stem":254,"icon":255},"Email","\u002Fen\u002Fstorage-mail\u002Fmail","en\u002F8.storage-mail\u002F2.mail","i-lucide-mail",{"title":257,"path":258,"stem":259,"icon":260},"Notifications","\u002Fen\u002Fstorage-mail\u002Fnotifications","en\u002F8.storage-mail\u002F3.notifications","i-lucide-bell",{"title":262,"path":263,"stem":264,"icon":180},"Realtime","\u002Fen\u002Fstorage-mail\u002Frealtime","en\u002F8.storage-mail\u002F4.realtime",{"title":266,"icon":267,"path":268,"stem":269,"children":270,"page":44},"CLI Reference","i-lucide-terminal","\u002Fen\u002Fcli","en\u002F9.cli",[271,274,279,284,289,294,299,303,308,313,318,323,328,333,337],{"title":139,"path":272,"stem":273,"icon":267},"\u002Fen\u002Fcli\u002Foverview","en\u002F9.cli\u002F1.overview",{"title":275,"path":276,"stem":277,"icon":278},"add db","\u002Fen\u002Fcli\u002Fadd-db","en\u002F9.cli\u002F10.add-db","i-lucide-database-zap",{"title":280,"path":281,"stem":282,"icon":283},"add compose \u002F add docker","\u002Fen\u002Fcli\u002Fadd-compose","en\u002F9.cli\u002F11.add-compose","i-lucide-container",{"title":285,"path":286,"stem":287,"icon":288},"add mail","\u002Fen\u002Fcli\u002Fadd-mail","en\u002F9.cli\u002F12.add-mail","i-lucide-mail-plus",{"title":290,"path":291,"stem":292,"icon":293},"add notification","\u002Fen\u002Fcli\u002Fadd-notification","en\u002F9.cli\u002F13.add-notification","i-lucide-bell-plus",{"title":295,"path":296,"stem":297,"icon":298},"add realtime","\u002Fen\u002Fcli\u002Fadd-realtime","en\u002F9.cli\u002F14.add-realtime","i-lucide-radio-tower",{"title":300,"path":301,"stem":302,"icon":165},"add seeder","\u002Fen\u002Fcli\u002Fadd-seeder","en\u002F9.cli\u002F15.add-seeder",{"title":304,"path":305,"stem":306,"icon":307},"new project","\u002Fen\u002Fcli\u002Fnew-project","en\u002F9.cli\u002F2.new-project","i-lucide-folder-plus",{"title":309,"path":310,"stem":311,"icon":312},"new module","\u002Fen\u002Fcli\u002Fnew-module","en\u002F9.cli\u002F3.new-module","i-lucide-blocks",{"title":314,"path":315,"stem":316,"icon":317},"add surface","\u002Fen\u002Fcli\u002Fadd-surface","en\u002F9.cli\u002F4.add-surface","i-lucide-layers-2",{"title":319,"path":320,"stem":321,"icon":322},"add core","\u002Fen\u002Fcli\u002Fadd-core","en\u002F9.cli\u002F5.add-core","i-lucide-box",{"title":324,"path":325,"stem":326,"icon":327},"add handler","\u002Fen\u002Fcli\u002Fadd-handler","en\u002F9.cli\u002F6.add-handler","i-lucide-webhook",{"title":329,"path":330,"stem":331,"icon":332},"new migration","\u002Fen\u002Fcli\u002Fnew-migration","en\u002F9.cli\u002F7.new-migration","i-lucide-file-plus",{"title":334,"path":335,"stem":336,"icon":195},"worker generators","\u002Fen\u002Fcli\u002Fworker-generators","en\u002F9.cli\u002F8.worker-generators",{"title":338,"path":339,"stem":340,"icon":341},"upgrade templates","\u002Fen\u002Fcli\u002Fupgrade-templates","en\u002F9.cli\u002F9.upgrade-templates","i-lucide-refresh-cw",{"id":343,"title":295,"body":344,"description":1054,"extension":1055,"links":1056,"meta":1057,"navigation":1058,"path":296,"seo":1059,"stem":297,"__hash__":1060},"docs_en\u002Fen\u002F9.cli\u002F14.add-realtime.md",{"type":345,"value":346,"toc":1047},"minimark",[347,379,397,410,415,653,669,702,706,736,869,920,923,951,955,976,980,1008,1012,1043],[348,349,354],"pre",{"className":350,"code":351,"language":352,"meta":353,"style":353},"language-sh shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go tool gpsystem add realtime\n","sh","",[355,356,357],"code",{"__ignoreMap":353},[358,359,362,366,370,373,376],"span",{"class":360,"line":361},"line",1,[358,363,365],{"class":364},"sBMFI","go",[358,367,369],{"class":368},"sfazB"," tool",[358,371,372],{"class":368}," gpsystem",[358,374,375],{"class":368}," add",[358,377,378],{"class":368}," realtime\n",[380,381,382,383,386,387,393,394,396],"p",{},"Adds ",[355,384,385],{},"cmd\u002Frealtime",": a dedicated server that holds client streaming connections (WebSocket, with an HTTP-streaming\u002FSSE fallback) and delivers messages published through ",[388,389,390],"a",{"href":263},[355,391,392],{},"notify\u002Fbroadcast"," from any process. It carries no business dependencies, so it scales freely and independently of the API\u002Fworker processes; see ",[388,395,262],{"href":263}," for the architecture and scaling story.",[380,398,399,400,402,403,409],{},"Explicit, retrofit-style command, not a ",[355,401,304],{}," default: like ",[388,404,406],{"href":405},"\u002Fen\u002Fcli\u002Fworker-generators#add-worker",[355,407,408],{},"add worker",", it is opt-in and adds a Traefik-exposed service.",[411,412,414],"h2",{"id":413},"what-it-generates","What it generates",[348,416,420],{"className":417,"code":418,"filename":419,"language":365,"meta":353,"style":353},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","func main() {\n    cfg := envconf.MustLoad[config.Config]()\n    ctx := context.Background()\n\n    err := realtime.Run(ctx, cfg.Realtime, func(topics *realtime.TopicAuth) error {\n        \u002F\u002F Authorize shared topic channels here, e.g.:\n        \u002F\u002F topics.Allow(\"announcements\", func(id *rbac.Identity) bool { return true })\n        \u002F\u002F gpsystem:topics\n        return nil\n    },\n        \u002F\u002F gpsystem:closers\n    )\n    if err != nil {\n        log.Fatal(err)\n    }\n}\n","cmd\u002Frealtime\u002Fmain.go",[355,421,422,438,471,490,497,558,565,571,577,587,593,599,605,622,641,647],{"__ignoreMap":353},[358,423,424,428,432,435],{"class":360,"line":361},[358,425,427],{"class":426},"sMK4o","func",[358,429,431],{"class":430},"s2Zo4"," main",[358,433,434],{"class":426},"()",[358,436,437],{"class":426}," {\n",[358,439,441,445,448,451,454,457,460,463,465,468],{"class":360,"line":440},2,[358,442,444],{"class":443},"sTEyZ","    cfg ",[358,446,447],{"class":426},":=",[358,449,450],{"class":443}," envconf",[358,452,453],{"class":426},".",[358,455,456],{"class":430},"MustLoad",[358,458,459],{"class":426},"[",[358,461,462],{"class":364},"config",[358,464,453],{"class":426},[358,466,467],{"class":364},"Config",[358,469,470],{"class":426},"]()\n",[358,472,474,477,479,482,484,487],{"class":360,"line":473},3,[358,475,476],{"class":443},"    ctx ",[358,478,447],{"class":426},[358,480,481],{"class":443}," context",[358,483,453],{"class":426},[358,485,486],{"class":430},"Background",[358,488,489],{"class":426},"()\n",[358,491,493],{"class":360,"line":492},4,[358,494,496],{"emptyLinePlaceholder":495},true,"\n",[358,498,500,503,505,508,510,513,516,519,522,525,527,529,531,534,538,541,544,546,549,552,556],{"class":360,"line":499},5,[358,501,502],{"class":443},"    err ",[358,504,447],{"class":426},[358,506,507],{"class":443}," realtime",[358,509,453],{"class":426},[358,511,512],{"class":430},"Run",[358,514,515],{"class":426},"(",[358,517,518],{"class":443},"ctx",[358,520,521],{"class":426},",",[358,523,524],{"class":443}," cfg",[358,526,453],{"class":426},[358,528,262],{"class":443},[358,530,521],{"class":426},[358,532,533],{"class":426}," func(",[358,535,537],{"class":536},"sHdIc","topics",[358,539,540],{"class":426}," *",[358,542,543],{"class":364},"realtime",[358,545,453],{"class":426},[358,547,548],{"class":364},"TopicAuth",[358,550,551],{"class":426},")",[358,553,555],{"class":554},"spNyl"," error",[358,557,437],{"class":426},[358,559,561],{"class":360,"line":560},6,[358,562,564],{"class":563},"sHwdD","        \u002F\u002F Authorize shared topic channels here, e.g.:\n",[358,566,568],{"class":360,"line":567},7,[358,569,570],{"class":563},"        \u002F\u002F topics.Allow(\"announcements\", func(id *rbac.Identity) bool { return true })\n",[358,572,574],{"class":360,"line":573},8,[358,575,576],{"class":563},"        \u002F\u002F gpsystem:topics\n",[358,578,580,584],{"class":360,"line":579},9,[358,581,583],{"class":582},"s7zQu","        return",[358,585,586],{"class":426}," nil\n",[358,588,590],{"class":360,"line":589},10,[358,591,592],{"class":426},"    },\n",[358,594,596],{"class":360,"line":595},11,[358,597,598],{"class":563},"        \u002F\u002F gpsystem:closers\n",[358,600,602],{"class":360,"line":601},12,[358,603,604],{"class":426},"    )\n",[358,606,608,611,614,617,620],{"class":360,"line":607},13,[358,609,610],{"class":582},"    if",[358,612,613],{"class":443}," err ",[358,615,616],{"class":426},"!=",[358,618,619],{"class":426}," nil",[358,621,437],{"class":426},[358,623,625,628,630,633,635,638],{"class":360,"line":624},14,[358,626,627],{"class":443},"        log",[358,629,453],{"class":426},[358,631,632],{"class":430},"Fatal",[358,634,515],{"class":426},[358,636,637],{"class":443},"err",[358,639,640],{"class":426},")\n",[358,642,644],{"class":360,"line":643},15,[358,645,646],{"class":426},"    }\n",[358,648,650],{"class":360,"line":649},16,[358,651,652],{"class":426},"}\n",[348,654,657],{"className":417,"code":655,"filename":656,"language":365,"meta":353,"style":353},"Realtime realtime.Config\n","internal\u002Fplatform\u002Fconfig\u002Fconfig.go",[355,658,659],{"__ignoreMap":353},[358,660,661,664,666],{"class":360,"line":361},[358,662,663],{"class":443},"Realtime realtime",[358,665,453],{"class":426},[358,667,668],{"class":443},"Config\n",[380,670,671,674,675,678,679,684,685,678,688,693,694,697,698,701],{},[355,672,673],{},"realtime.Config"," reuses ",[355,676,677],{},"REDIS_*"," (",[388,680,681],{"href":53},[355,682,683],{},"queue.Config",") and ",[355,686,687],{},"JWT_*",[388,689,690],{"href":53},[355,691,692],{},"auth.Config","); no new secrets to manage. See the ",[388,695,262],{"href":696},"\u002Fen\u002Fstorage-mail\u002Frealtime#config"," page for the full ",[355,699,700],{},"REALTIME_*"," table.",[411,703,705],{"id":704},"composeyml-and-envexample","compose.yml and .env.example",[380,707,708,709,711,712,715,716,719,720,723,724,727,728,731,732,735],{},"Adds a ",[355,710,543],{}," service (path-routed under ",[355,713,714],{},"\u002Frealtime"," on the project's existing ",[355,717,718],{},"APP_HOST",", so no new DNS entry or CORS case): no ",[355,721,722],{},"container_name"," (so ",[355,725,726],{},"--scale"," works), a Traefik health-check label pointed at ",[355,729,730],{},"\u002Frealtime\u002Fhealthz"," (so a draining instance is evicted from the load-balancer pool), ",[355,733,734],{},"responseForwarding.flushInterval=-1"," (so streamed bytes flush immediately through the proxy).",[348,737,742],{"className":738,"code":739,"filename":740,"language":741,"meta":353,"style":353},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","realtime:\n  build: { context: ., dockerfile: Dockerfile, target: development, args: { CMD: realtime } }\n  depends_on:\n    redis:\n      condition: service_started\n  stop_grace_period: 30s\n  labels:\n    - traefik.http.routers.shopRealtime.rule=Host(`${APP_HOST}`) && PathPrefix(`\u002Frealtime`)\n    - traefik.http.services.shopRealtime.loadbalancer.healthCheck.path=\u002Frealtime\u002Fhealthz\n","compose.yml","yaml",[355,743,744,752,813,820,827,837,847,854,862],{"__ignoreMap":353},[358,745,746,749],{"class":360,"line":361},[358,747,543],{"class":748},"swJcz",[358,750,751],{"class":426},":\n",[358,753,754,757,760,763,765,767,771,773,776,778,781,783,786,788,791,793,796,798,800,803,805,807,810],{"class":360,"line":440},[358,755,756],{"class":748},"  build",[358,758,759],{"class":426},":",[358,761,762],{"class":426}," {",[358,764,481],{"class":748},[358,766,759],{"class":426},[358,768,770],{"class":769},"sbssI"," .",[358,772,521],{"class":426},[358,774,775],{"class":748}," dockerfile",[358,777,759],{"class":426},[358,779,780],{"class":368}," Dockerfile",[358,782,521],{"class":426},[358,784,785],{"class":748}," target",[358,787,759],{"class":426},[358,789,790],{"class":368}," development",[358,792,521],{"class":426},[358,794,795],{"class":748}," args",[358,797,759],{"class":426},[358,799,762],{"class":426},[358,801,802],{"class":748}," CMD",[358,804,759],{"class":426},[358,806,507],{"class":368},[358,808,809],{"class":426}," }",[358,811,812],{"class":426}," }\n",[358,814,815,818],{"class":360,"line":473},[358,816,817],{"class":748},"  depends_on",[358,819,751],{"class":426},[358,821,822,825],{"class":360,"line":492},[358,823,824],{"class":748},"    redis",[358,826,751],{"class":426},[358,828,829,832,834],{"class":360,"line":499},[358,830,831],{"class":748},"      condition",[358,833,759],{"class":426},[358,835,836],{"class":368}," service_started\n",[358,838,839,842,844],{"class":360,"line":560},[358,840,841],{"class":748},"  stop_grace_period",[358,843,759],{"class":426},[358,845,846],{"class":368}," 30s\n",[358,848,849,852],{"class":360,"line":567},[358,850,851],{"class":748},"  labels",[358,853,751],{"class":426},[358,855,856,859],{"class":360,"line":573},[358,857,858],{"class":426},"    -",[358,860,861],{"class":368}," traefik.http.routers.shopRealtime.rule=Host(`${APP_HOST}`) && PathPrefix(`\u002Frealtime`)\n",[358,863,864,866],{"class":360,"line":579},[358,865,858],{"class":426},[358,867,868],{"class":368}," traefik.http.services.shopRealtime.loadbalancer.healthCheck.path=\u002Frealtime\u002Fhealthz\n",[348,870,873],{"className":350,"code":871,"filename":872,"language":352,"meta":353,"style":353},"REALTIME_LISTEN_ADDR=:3000\nREALTIME_DRAIN_TIMEOUT=20s\nREALTIME_CHANNEL_PREFIX=\u003Cproject>\nREALTIME_MAX_CONNECTIONS=0\n",".env.example",[355,874,875,886,896,910],{"__ignoreMap":353},[358,876,877,880,883],{"class":360,"line":361},[358,878,879],{"class":443},"REALTIME_LISTEN_ADDR",[358,881,882],{"class":426},"=",[358,884,885],{"class":368},":3000\n",[358,887,888,891,893],{"class":360,"line":440},[358,889,890],{"class":443},"REALTIME_DRAIN_TIMEOUT",[358,892,882],{"class":426},[358,894,895],{"class":368},"20s\n",[358,897,898,901,904,907],{"class":360,"line":473},[358,899,900],{"class":443},"REALTIME_CHANNEL_PREFIX",[358,902,903],{"class":426},"=\u003C",[358,905,906],{"class":368},"project",[358,908,909],{"class":426},">\n",[358,911,912,915,917],{"class":360,"line":492},[358,913,914],{"class":443},"REALTIME_MAX_CONNECTIONS",[358,916,882],{"class":426},[358,918,919],{"class":368},"0\n",[380,921,922],{},"Scale it independently of every other service:",[348,924,926],{"className":350,"code":925,"language":352,"meta":353,"style":353},"docker compose up -d --scale realtime=3\n",[355,927,928],{"__ignoreMap":353},[358,929,930,933,936,939,942,945,948],{"class":360,"line":361},[358,931,932],{"class":364},"docker",[358,934,935],{"class":368}," compose",[358,937,938],{"class":368}," up",[358,940,941],{"class":368}," -d",[358,943,944],{"class":368}," --scale",[358,946,947],{"class":368}," realtime=",[358,949,950],{"class":769},"3\n",[411,952,954],{"id":953},"authorizing-shared-topics","Authorizing shared topics",[380,956,957,958,961,962,964,965,968,969,972,973,453],{},"Every connection is server-side subscribed to its own ",[355,959,960],{},"user:\u003Csubject>"," channel automatically; no code needed for that. Shared topics are opt-in: register a callback in ",[355,963,419],{}," at the ",[355,966,967],{},"\u002F\u002F gpsystem:topics"," anchor, and a client subscribes to it by name. A topic with no registered ",[355,970,971],{},"Allow"," is never subscribable; see ",[388,974,262],{"href":975},"\u002Fen\u002Fstorage-mail\u002Frealtime#the-gateway",[411,977,979],{"id":978},"wiring-a-notification-to-it","Wiring a notification to it",[380,981,982,984,985,989,990,993,994,997,998,1001,1002,1007],{},[355,983,295],{}," only wires the gateway itself. Run it before your first ",[388,986,987],{"href":291},[355,988,290],{}," to have the broadcast channel wired into the shared ",[355,991,992],{},"notify.Sender"," automatically; if notifications already exist, add ",[355,995,996],{},"notify.ChannelBroadcast"," to their ",[355,999,1000],{},"Via"," and the broadcast construction by hand (see the note on ",[388,1003,1005],{"href":1004},"\u002Fen\u002Fcli\u002Fadd-notification#what-it-generates",[355,1006,290],{},").",[411,1009,1011],{"id":1010},"older-projects","Older projects",[380,1013,1014,1015,1019,1020,1022,1023,1026,1027,1030,1031,1026,1034,1037,1038,1026,1040,1042],{},"Like ",[388,1016,1017],{"href":405},[355,1018,408],{},", ",[355,1021,295],{}," writes at the ",[355,1024,1025],{},"gpsystem:imports","\u002F",[355,1028,1029],{},"gpsystem:config"," anchors in platform config and the ",[355,1032,1033],{},"gpsystem:services",[355,1035,1036],{},"gpsystem:env"," anchors in ",[355,1039,740],{},[355,1041,872],{},"; on a project predating those files' anchors, it prints the block to paste instead of failing.",[1044,1045,1046],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":353,"searchDepth":440,"depth":440,"links":1048},[1049,1050,1051,1052,1053],{"id":413,"depth":440,"text":414},{"id":704,"depth":440,"text":705},{"id":953,"depth":440,"text":954},{"id":978,"depth":440,"text":979},{"id":1010,"depth":440,"text":1011},"Scaffold the realtime notification gateway: a dedicated, horizontally scalable cmd\u002Frealtime binary.","md",null,{},{"icon":298},{"title":295,"description":1054},"itoW4b2YR26SDh737BSrm_M-RbBoO4UwF2EmeGRMpkQ",[1062,1064],{"title":290,"path":291,"stem":292,"description":1063,"icon":293,"children":-1},"Generate a module-owned, multi-channel notification: payload, Via, per-channel content, and the shared Hub wiring.",{"title":300,"path":301,"stem":302,"description":1065,"icon":165,"children":-1},"Add a seeder to the project's central seeds\u002F package, registered automatically.",1784668713104]