[{"data":1,"prerenderedAt":964},["ShallowReactive",2],{"docs-nav-pkg":3,"docs-page-pkg-\u002Fpkg\u002Fchannels":133},[4,8,12,15,18,21,24,27,30,33,36,39,42,45,48,51,54,57,60,63,66,69,72,75,78,81,84,87,90,93,96,99,102,105,108,111,114,117,121,124,127,130],{"path":5,"title":6,"section":7},"\u002Fpkg","Overview","Start",{"path":9,"title":10,"section":11},"\u002Fpkg\u002Faccordion","Accordion","Components",{"path":13,"title":14,"section":11},"\u002Fpkg\u002Falert-dialog","Alert Dialog",{"path":16,"title":17,"section":11},"\u002Fpkg\u002Fautocomplete","Autocomplete",{"path":19,"title":20,"section":11},"\u002Fpkg\u002Favatar","Avatar",{"path":22,"title":23,"section":11},"\u002Fpkg\u002Fbutton","Button",{"path":25,"title":26,"section":11},"\u002Fpkg\u002Fcheckbox","Checkbox",{"path":28,"title":29,"section":11},"\u002Fpkg\u002Fcheckbox-group","Checkbox Group",{"path":31,"title":32,"section":11},"\u002Fpkg\u002Fcollapsible","Collapsible",{"path":34,"title":35,"section":11},"\u002Fpkg\u002Fcombobox","Combobox",{"path":37,"title":38,"section":11},"\u002Fpkg\u002Fcontext-menu","Context Menu",{"path":40,"title":41,"section":11},"\u002Fpkg\u002Fdialog","Dialog",{"path":43,"title":44,"section":11},"\u002Fpkg\u002Fdrawer","Drawer",{"path":46,"title":47,"section":11},"\u002Fpkg\u002Ffield","Field",{"path":49,"title":50,"section":11},"\u002Fpkg\u002Ffieldset","Fieldset",{"path":52,"title":53,"section":11},"\u002Fpkg\u002Fform","Form",{"path":55,"title":56,"section":11},"\u002Fpkg\u002Ficon-picker","Icon picker",{"path":58,"title":59,"section":11},"\u002Fpkg\u002Finput","Input",{"path":61,"title":62,"section":11},"\u002Fpkg\u002Fmenu","Menu",{"path":64,"title":65,"section":11},"\u002Fpkg\u002Fmenubar","Menubar",{"path":67,"title":68,"section":11},"\u002Fpkg\u002Fmeter","Meter",{"path":70,"title":71,"section":11},"\u002Fpkg\u002Fnavigation-menu","Navigation Menu",{"path":73,"title":74,"section":11},"\u002Fpkg\u002Fnumber-field","Number Field",{"path":76,"title":77,"section":11},"\u002Fpkg\u002Fotp-field","OTP Field",{"path":79,"title":80,"section":11},"\u002Fpkg\u002Fplot","Plot",{"path":82,"title":83,"section":11},"\u002Fpkg\u002Fpopover","Popover",{"path":85,"title":86,"section":11},"\u002Fpkg\u002Fpreview-card","Preview Card",{"path":88,"title":89,"section":11},"\u002Fpkg\u002Fprogress","Progress",{"path":91,"title":92,"section":11},"\u002Fpkg\u002Fradio-group","Radio Group",{"path":94,"title":95,"section":11},"\u002Fpkg\u002Fscroll-area","Scroll Area",{"path":97,"title":98,"section":11},"\u002Fpkg\u002Fselect","Select",{"path":100,"title":101,"section":11},"\u002Fpkg\u002Fseparator","Separator",{"path":103,"title":104,"section":11},"\u002Fpkg\u002Fsidebar","Sidebar",{"path":106,"title":107,"section":11},"\u002Fpkg\u002Fslider","Slider",{"path":109,"title":110,"section":11},"\u002Fpkg\u002Fswitch","Switch",{"path":112,"title":113,"section":11},"\u002Fpkg\u002Ftabs","Tabs",{"path":115,"title":116,"section":11},"\u002Fpkg\u002Ftoast","Toast",{"path":118,"title":119,"section":120},"\u002Fpkg\u002Fapi","Api","Runtime",{"path":122,"title":123,"section":120},"\u002Fpkg\u002Fauth","Auth",{"path":125,"title":126,"section":120},"\u002Fpkg\u002Fblob","Blob",{"path":128,"title":129,"section":120},"\u002Fpkg\u002Fchannels","Channels",{"path":131,"title":132,"section":120},"\u002Fpkg\u002Fdb","Db",{"id":134,"title":129,"body":135,"description":958,"extension":959,"meta":960,"navigation":857,"path":128,"section":120,"seo":961,"stem":962,"__hash__":963},"pkg\u002Fpkg\u002Fchannels.md",{"type":136,"value":137,"toc":949},"minimark",[138,172,177,282,286,394,406,416,419,435,450,462,482,523,526,672,676,731,744,748,772,775,779,782,810,814,831,939,945],[139,140,141,145,146,149,150,153,154,157,158,161,162,165,166,168,169,171],"p",{},[142,143,144],"code",{},"platform\u002Fchannels"," (",[142,147,148],{},"kalt-channels",") names a stream from the owner and model a service passes in. There is one bus on the mesh. ",[142,151,152],{},"lib\u002Fch"," is part of this package: ",[142,155,156],{},"ch.pub"," appends the stream, ",[142,159,160],{},"ch.sub"," reads one consumer group, and ",[142,163,164],{},"subscribe_to"," registers a reader. A service creates its owner group when it starts, and its reader group with ",[142,167,164],{},". The channels process reads group ",[142,170,148],{}," on every public stream and stores each entry in its own database.",[173,174,176],"h2",{"id":175},"principles","Principles",[178,179,180,204,222,239,268],"ol",{},[181,182,183,187,188,191,192,195,196,199,200,203],"li",{},[184,185,186],"strong",{},"One owner."," A channel names one service. That service is the only publisher. It passes its name and model: ",[142,189,190],{},"Channel::public(owner, model)"," is ",[142,193,194],{},"kalt.{owner}"," and group ",[142,197,198],{},"kalt-{owner}",", started at ",[142,201,202],{},"0",".",[181,205,206,209,210,213,214,217,218,221],{},[184,207,208],{},"The model is the message type."," The service chooses ",[142,211,212],{},"Model::Domain"," or ",[142,215,216],{},"Model::Source",". That is the only ",[142,219,220],{},"type"," on its stream.",[181,223,224,227,228,191,231,195,234,199,237,203],{},[184,225,226],{},"Public or private."," On a public channel, other services read with their own groups. On a private channel, the publisher and the reader are the same service. ",[142,229,230],{},"Channel::private(owner, model)",[142,232,233],{},"kalt.private.{owner}",[142,235,236],{},"kalt-private-{owner}",[142,238,202],{},[181,240,241,244,245,199,247,249,250,253,254,257,258,191,261,264,265,267],{},[184,242,243],{},"Each reader acks alone."," The owner group is ",[142,246,198],{},[142,248,202],{},". A service calls ",[142,251,252],{},"subscribe_to(owner, model, start)"," to read another public stream as ",[142,255,256],{},"kalt-{service}",". ",[142,259,260],{},"start",[142,262,263],{},"$"," for new entries, or ",[142,266,202],{}," when that service applies history.",[181,269,270,273,274,277,278,281],{},[184,271,272],{},"The publish is the stream entry."," The channels process stores it. ",[142,275,276],{},"POST \u002Fapi\u002Fchannels\u002Fresend"," appends those rows again with ",[142,279,280],{},"replay=1",". The archiver acks a replay and does not insert it.",[173,283,285],{"id":284},"streams","Streams",[287,288,289,310],"table",{},[290,291,292],"thead",{},[293,294,295,299,302,305,308],"tr",{},[296,297,298],"th",{},"Visibility",[296,300,301],{},"Model",[296,303,304],{},"Stream",[296,306,307],{},"Group",[296,309,7],{},[311,312,313,334,354,375],"tbody",{},[293,314,315,319,322,326,330],{},[316,317,318],"td",{},"Public",[316,320,321],{},"domain or source",[316,323,324],{},[142,325,194],{},[316,327,328],{},[142,329,198],{},[316,331,332],{},[142,333,202],{},[293,335,336,339,342,346,350],{},[316,337,338],{},"Private",[316,340,341],{},"same as the owner",[316,343,344],{},[142,345,233],{},[316,347,348],{},[142,349,236],{},[316,351,352],{},[142,353,202],{},[293,355,356,359,362,366,371],{},[316,357,358],{},"Subscriber",[316,360,361],{},"copied from the owner",[316,363,364],{},[142,365,194],{},[316,367,368],{},[142,369,370],{},"kalt-{subscriber}",[316,372,373],{},[142,374,263],{},[293,376,377,380,382,386,390],{},[316,378,379],{},"Archive",[316,381,361],{},[316,383,384],{},[142,385,194],{},[316,387,388],{},[142,389,148],{},[316,391,392],{},[142,393,202],{},[139,395,396,398,399,401,402,199,404,203],{},[142,397,230],{}," is one handle. The service that owns it publishes with it and reads the same group. The stream is ",[142,400,233],{},", the group is ",[142,403,236],{},[142,405,202],{},[139,407,408,410,411,413,414,203],{},[142,409,252],{}," is how a service reads someone else's public stream. The service names the owner and the model. The group is ",[142,412,256],{}," on ",[142,415,194],{},[173,417,156],{"id":418},"chpub",[139,420,421,424,425,428,429,431,432,203],{},[142,422,423],{},"AppState.ch"," binds ",[142,426,427],{},"Channel::public(service, model)",". The service passes both. HTTP handlers publish there. The owner's worker reads group ",[142,430,256],{}," and applies ",[142,433,434],{},"content",[139,436,437,438,440,441,413,443,445,446,449],{},"A service calls ",[142,439,252],{}," to read another public stream. That creates group ",[142,442,256],{},[142,444,194],{}," and returns a handle that reads with ",[142,447,448],{},"sub"," and acks. Publishing stays on the owner's handle.",[139,451,452,145,454,457,458,461],{},[142,453,156],{},[142,455,456],{},"r#pub"," in Rust, because ",[142,459,460],{},"pub"," is a keyword) accepts the write when three things hold, then appends the stream:",[463,464,465,468,471],"ul",{},[181,466,467],{},"The bound service is the stream's owner.",[181,469,470],{},"The handle is the owner's, public or private.",[181,472,473,475,476,213,479,203],{},[142,474,220],{}," matches the channel model: ",[142,477,478],{},"domain",[142,480,481],{},"source",[139,483,484,485,487,488,487,491,493,494,496,497,257,500,503,504,507,508,511,512,515,516,518,519,522],{},"The channels ledger row uses the Redis id, the stream name, ",[142,486,481],{},", ",[142,489,490],{},"trace_id",[142,492,220],{},", and ",[142,495,434],{},". The primary key is ",[142,498,499],{},"(stream, id)",[142,501,502],{},"pub_upsert(id)"," appends another entry. It does not replace an earlier one. ",[142,505,506],{},"pub_once(id)"," appends only when ",[142,509,510],{},"kalt.once:{stream}:{id}"," is absent, then sets that key. ",[142,513,514],{},"GET \u002Fapi\u002Fchannels\u002Fledger"," lists the rows. ",[142,517,276],{}," delivers each row again. Pass ",[142,520,521],{},"stream"," to limit either call to one queue. Private streams are not stored.",[139,524,525],{},"The envelope is already the promoted layer. Every entry is those fields, written as Redis stream fields:",[527,528,533],"pre",{"className":529,"code":530,"language":531,"meta":532,"style":532},"language-json shiki shiki-themes github-light github-dark","{\n  \"service\": \"publications\",\n  \"source\": \"app\u002Fcomposables\u002FusePublication.ts\",\n  \"traceId\": [\"nK7_abC1\"],\n  \"type\": \"domain\",\n  \"content\": { \"domain\": \"Publication\", \"body\": { \"kind\": \"title\", \"id\": \"AbC12~_x\", \"tz\": 1710000000000, \"value\": \"Title\" } }\n}\n","json","",[142,534,535,544,561,574,589,602,666],{"__ignoreMap":532},[536,537,540],"span",{"class":538,"line":539},"line",1,[536,541,543],{"class":542},"sVt8B","{\n",[536,545,547,551,554,558],{"class":538,"line":546},2,[536,548,550],{"class":549},"sj4cs","  \"service\"",[536,552,553],{"class":542},": ",[536,555,557],{"class":556},"sZZnC","\"publications\"",[536,559,560],{"class":542},",\n",[536,562,564,567,569,572],{"class":538,"line":563},3,[536,565,566],{"class":549},"  \"source\"",[536,568,553],{"class":542},[536,570,571],{"class":556},"\"app\u002Fcomposables\u002FusePublication.ts\"",[536,573,560],{"class":542},[536,575,577,580,583,586],{"class":538,"line":576},4,[536,578,579],{"class":549},"  \"traceId\"",[536,581,582],{"class":542},": [",[536,584,585],{"class":556},"\"nK7_abC1\"",[536,587,588],{"class":542},"],\n",[536,590,592,595,597,600],{"class":538,"line":591},5,[536,593,594],{"class":549},"  \"type\"",[536,596,553],{"class":542},[536,598,599],{"class":556},"\"domain\"",[536,601,560],{"class":542},[536,603,605,608,611,613,615,618,620,623,625,628,630,633,635,638,640,643,645,648,650,653,655,658,660,663],{"class":538,"line":604},6,[536,606,607],{"class":549},"  \"content\"",[536,609,610],{"class":542},": { ",[536,612,599],{"class":549},[536,614,553],{"class":542},[536,616,617],{"class":556},"\"Publication\"",[536,619,487],{"class":542},[536,621,622],{"class":549},"\"body\"",[536,624,610],{"class":542},[536,626,627],{"class":549},"\"kind\"",[536,629,553],{"class":542},[536,631,632],{"class":556},"\"title\"",[536,634,487],{"class":542},[536,636,637],{"class":549},"\"id\"",[536,639,553],{"class":542},[536,641,642],{"class":556},"\"AbC12~_x\"",[536,644,487],{"class":542},[536,646,647],{"class":549},"\"tz\"",[536,649,553],{"class":542},[536,651,652],{"class":549},"1710000000000",[536,654,487],{"class":542},[536,656,657],{"class":549},"\"value\"",[536,659,553],{"class":542},[536,661,662],{"class":556},"\"Title\"",[536,664,665],{"class":542}," } }\n",[536,667,669],{"class":538,"line":668},7,[536,670,671],{"class":542},"}\n",[173,673,675],{"id":674},"envelope","Envelope",[463,677,678,684,697,713,723],{},[181,679,680,683],{},[184,681,682],{},"service"," — the publishing binary. It is the channel owner.",[181,685,686,688,689,692,693,696],{},[184,687,481],{}," — the file that called the API. The browser sends ",[142,690,691],{},"X-Msg-Source",". If that header is missing, the publisher uses ",[142,694,695],{},"file!()",". This field is the call site. It is separate from a source-aligned channel.",[181,698,699,702,703,706,707,710,711,203],{},[184,700,701],{},"traceId"," — string array, set in the browser and passed through. Observe uses it to stitch a user action across hops. ",[142,704,705],{},"bindFetch"," sends it as ",[142,708,709],{},"X-Trace-Id",". On Redis the field name is ",[142,712,490],{},[181,714,715,717,718,213,720,722],{},[184,716,220],{}," — ",[142,719,478],{},[142,721,481],{},", the same word as the channel model.",[181,724,725,727,728,203],{},[184,726,434],{}," — JSON payload. A public domain message is a ",[142,729,730],{},"StreamEvent",[139,732,733,487,735,487,737,487,739,493,741,743],{},[142,734,682],{},[142,736,481],{},[142,738,490],{},[142,740,220],{},[142,742,434],{}," are reserved stream fields.",[173,745,747],{"id":746},"promoted-properties","Promoted properties",[139,749,750,751,753,754,145,757,487,759,487,762,765,766,769,770,203],{},"Promoted properties are scalars copied out of the message body so a consumer can drop a message without parsing JSON. They come from ",[142,752,434],{}," and ",[142,755,756],{},"content.body",[142,758,478],{},[142,760,761],{},"kind",[142,763,764],{},"id",", …). Nested objects and the write ",[142,767,768],{},"value"," stay inside ",[142,771,434],{},[139,773,774],{},"On Redis they sit beside the envelope as sibling fields. They are a flat set of stream fields, and they never repeat envelope keys.",[173,776,778],{"id":777},"types","Types",[139,780,781],{},"The type is the channel model. Publish rejects any other type on that stream.",[463,783,784,800],{},[181,785,786,788,789,792,793,796,797,799],{},[184,787,478],{}," — a fact on a domain channel (publication title, doctrine rels, blueprint ops). ",[142,790,791],{},"emit_domain"," in ",[794,795,119],"a",{"href":118}," calls ",[142,798,156],{}," with this type.",[181,801,802,804,805,807,808,203],{},[184,803,481],{}," — a fact on a source channel. The integration that owns the stream publishes there, including on its private stream, with the same type. It binds ",[142,806,230],{}," for both the write and the ack group ",[142,809,236],{},[173,811,813],{"id":812},"frontend","Frontend",[139,815,816,819,820,822,823,826,827,830],{},[142,817,818],{},"app\u002Fweb\u002Fapp\u002Flib\u002Fmsg.ts"," holds the session ",[142,821,701],{}," array. ",[142,824,825],{},"bindFetch(import.meta.url)"," attaches both headers on every ",[142,828,829],{},"okFetch",":",[527,832,836],{"className":833,"code":834,"language":835,"meta":532,"style":532},"language-ts shiki shiki-themes github-light github-dark","import { bindFetch } from '~\u002Flib\u002Fok-fetch'\n\nconst okFetch = bindFetch(import.meta.url)\n\nawait okFetch('\u002Fapi\u002Fpublications\u002Fid\u002Ftitle', {\n  method: 'PUT',\n  body: { tz: Date.now(), value: 'Title' },\n})\n","ts",[142,837,838,853,859,887,891,906,916,933],{"__ignoreMap":532},[536,839,840,844,847,850],{"class":538,"line":539},[536,841,843],{"class":842},"szBVR","import",[536,845,846],{"class":542}," { bindFetch } ",[536,848,849],{"class":842},"from",[536,851,852],{"class":556}," '~\u002Flib\u002Fok-fetch'\n",[536,854,855],{"class":538,"line":546},[536,856,858],{"emptyLinePlaceholder":857},true,"\n",[536,860,861,864,867,870,874,877,879,881,884],{"class":538,"line":563},[536,862,863],{"class":842},"const",[536,865,866],{"class":549}," okFetch",[536,868,869],{"class":842}," =",[536,871,873],{"class":872},"sScJk"," bindFetch",[536,875,876],{"class":542},"(",[536,878,843],{"class":842},[536,880,203],{"class":542},[536,882,883],{"class":549},"meta",[536,885,886],{"class":542},".url)\n",[536,888,889],{"class":538,"line":576},[536,890,858],{"emptyLinePlaceholder":857},[536,892,893,896,898,900,903],{"class":538,"line":591},[536,894,895],{"class":842},"await",[536,897,866],{"class":872},[536,899,876],{"class":542},[536,901,902],{"class":556},"'\u002Fapi\u002Fpublications\u002Fid\u002Ftitle'",[536,904,905],{"class":542},", {\n",[536,907,908,911,914],{"class":538,"line":604},[536,909,910],{"class":542},"  method: ",[536,912,913],{"class":556},"'PUT'",[536,915,560],{"class":542},[536,917,918,921,924,927,930],{"class":538,"line":668},[536,919,920],{"class":542},"  body: { tz: Date.",[536,922,923],{"class":872},"now",[536,925,926],{"class":542},"(), value: ",[536,928,929],{"class":556},"'Title'",[536,931,932],{"class":542}," },\n",[536,934,936],{"class":538,"line":935},8,[536,937,938],{"class":542},"})\n",[139,940,941,944],{},[142,942,943],{},"pushTraceId()"," appends another id when observe needs a nested span. The array is the whole trace, not a single string.",[946,947,948],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .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 .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}",{"title":532,"searchDepth":546,"depth":546,"links":950},[951,952,953,954,955,956,957],{"id":175,"depth":546,"text":176},{"id":284,"depth":546,"text":285},{"id":418,"depth":546,"text":156},{"id":674,"depth":546,"text":675},{"id":746,"depth":546,"text":747},{"id":777,"depth":546,"text":778},{"id":812,"depth":546,"text":813},"One Redis bus. ch.pub appends the stream. The channels process stores it. ch.sub reads one group.","md",{},{"title":129,"description":958},"pkg\u002Fchannels","EwKO5V6JOdlTLZselSWaSvbiGynMt8TiUM0_6YaJvsw",1791043754625]