[{"data":1,"prerenderedAt":1653},["ShallowReactive",2],{"docs:/docs/concepts/qos":3},{"id":4,"title":5,"body":6,"description":1646,"extension":1647,"meta":1648,"navigation":227,"path":1649,"seo":1650,"stem":1651,"__hash__":1652},"docs/docs/concepts/qos.md","Quality of Service (QoS)",{"type":7,"value":8,"toc":1632},"minimark",[9,13,17,22,50,53,74,82,86,157,161,164,328,334,348,352,355,425,429,443,447,450,526,530,541,545,554,754,758,763,789,793,796,1349,1353,1356,1602,1606,1610,1628],[10,11,5],"h1",{"id":12},"quality-of-service-qos",[14,15,16],"p",{},"QoS is the MQTT delivery level NoLag applies on the broker hop of a message's journey. Choose it per message or per connection, and know exactly what it does and does not cover.",[18,19,21],"h2",{"id":20},"what-qos-governs","What QoS Governs",[14,23,24,25,29,30,33,34,37,38,40,41,43,44,46,47,49],{},"A message travels two legs: from your client to the NoLag gateway over WebSocket, and from the gateway across the internal MQTT broker to the gateways of every subscriber. The QoS level you set (",[26,27,28],"code",{},"0",", ",[26,31,32],{},"1"," or ",[26,35,36],{},"2",", default ",[26,39,32],{},") is validated and passed to that internal broker hop. Anything outside ",[26,42,28],{}," to ",[26,45,36],{}," is treated as ",[26,48,32],{},".",[14,51,52],{},"The WebSocket leg has its own, simpler contract:",[54,55,56,68,71],"ul",{},[57,58,59,60,63,64,67],"li",{},"A publish can be acknowledged. On protocol v2 (js-sdk 1.6+, python-sdk 2.5+, go-sdk v0.5.0) the SDK attaches a ",[26,61,62],{},"msgRef"," when you pass a callback, and the broker answers with a ",[26,65,66],{},"published"," frame once it has accepted the message. That acknowledgement is what the emit callback resolves on.",[57,69,70],{},"Nothing is resent. If the acknowledgement does not arrive within 10 seconds the callback receives an error, and it is up to you to publish again.",[57,72,73],{},"There is no exactly-once guarantee end to end. Publishers never receive their own messages, and a subscriber may see a duplicate if a message is published twice, so use an idempotency key in your payload for anything that must not be applied twice.",[14,75,76,77,49],{},"If your design needs durable catch-up for a worker group, that is a separate feature; see ",[78,79,81],"a",{"href":80},"/docs/concepts/replay","Replay and Durable Delivery",[18,83,85],{"id":84},"qos-levels","QoS Levels",[87,88,89,108],"table",{},[90,91,92],"thead",{},[93,94,95,99,102,105],"tr",{},[96,97,98],"th",{},"Level",[96,100,101],{},"Name",[96,103,104],{},"Broker hop behaviour",[96,106,107],{},"Use Case",[109,110,111,127,142],"tbody",{},[93,112,113,118,121,124],{},[114,115,116],"td",{},[26,117,28],{},[114,119,120],{},"At-most-once",[114,122,123],{},"Forwarded once, no broker acknowledgement; may be dropped under pressure",[114,125,126],{},"Telemetry, cursors, typing",[93,128,129,133,136,139],{},[114,130,131],{},[26,132,32],{},[114,134,135],{},"At-least-once",[114,137,138],{},"Acknowledged between broker nodes; may be forwarded more than once",[114,140,141],{},"Notifications, chat messages (default)",[93,143,144,148,151,154],{},[114,145,146],{},[26,147,36],{},[114,149,150],{},"Deduplicated on the broker hop",[114,152,153],{},"The broker hop runs the MQTT level 2 handshake; the WebSocket leg does not, so this is not an end-to-end guarantee",[114,155,156],{},"Where a duplicate is costly and you also dedupe on the client",[18,158,160],{"id":159},"qos-0-at-most-once","QoS 0: At-most-once",[14,162,163],{},"The internal hop forwards the message once with no acknowledgement. The cheapest option, and the right one when the next update supersedes this one anyway.",[165,166,167,264,302],"code-tabs",{},[168,169,175],"pre",{"className":170,"code":171,"filename":172,"language":173,"meta":174,"style":174},"language-typescript shiki shiki-themes github-light github-dark","const room = client.setApp(APP_SLUG).setRoom('factory-floor')\n\n// Fire and forget on the broker hop\nroom.emit('temperature', { temperature: 72.5 }, { qos: 0 })\n","TypeScript","typescript","",[26,176,177,222,229,236],{"__ignoreMap":174},[178,179,182,186,190,193,197,201,204,207,210,213,215,219],"span",{"class":180,"line":181},"line",1,[178,183,185],{"class":184},"szBVR","const",[178,187,189],{"class":188},"sj4cs"," room",[178,191,192],{"class":184}," =",[178,194,196],{"class":195},"sVt8B"," client.",[178,198,200],{"class":199},"sScJk","setApp",[178,202,203],{"class":195},"(",[178,205,206],{"class":188},"APP_SLUG",[178,208,209],{"class":195},").",[178,211,212],{"class":199},"setRoom",[178,214,203],{"class":195},[178,216,218],{"class":217},"sZZnC","'factory-floor'",[178,220,221],{"class":195},")\n",[178,223,225],{"class":180,"line":224},2,[178,226,228],{"emptyLinePlaceholder":227},true,"\n",[178,230,232],{"class":180,"line":231},3,[178,233,235],{"class":234},"sJ8bj","// Fire and forget on the broker hop\n",[178,237,239,242,245,247,250,253,256,259,261],{"class":180,"line":238},4,[178,240,241],{"class":195},"room.",[178,243,244],{"class":199},"emit",[178,246,203],{"class":195},[178,248,249],{"class":217},"'temperature'",[178,251,252],{"class":195},", { temperature: ",[178,254,255],{"class":188},"72.5",[178,257,258],{"class":195}," }, { qos: ",[178,260,28],{"class":188},[178,262,263],{"class":195}," })\n",[168,265,270],{"className":266,"code":267,"filename":268,"language":269,"meta":174,"style":174},"language-python shiki shiki-themes github-light github-dark","from nolag import EmitOptions, QoS\n\nroom = client.set_app(APP_SLUG).set_room('factory-floor')\n\n# Fire and forget on the broker hop\nawait room.emit('temperature', {'temperature': 72.5}, EmitOptions(qos=QoS.AT_MOST_ONCE))\n","Python","python",[26,271,272,277,281,286,290,296],{"__ignoreMap":174},[178,273,274],{"class":180,"line":181},[178,275,276],{},"from nolag import EmitOptions, QoS\n",[178,278,279],{"class":180,"line":224},[178,280,228],{"emptyLinePlaceholder":227},[178,282,283],{"class":180,"line":231},[178,284,285],{},"room = client.set_app(APP_SLUG).set_room('factory-floor')\n",[178,287,288],{"class":180,"line":238},[178,289,228],{"emptyLinePlaceholder":227},[178,291,293],{"class":180,"line":292},5,[178,294,295],{},"# Fire and forget on the broker hop\n",[178,297,299],{"class":180,"line":298},6,[178,300,301],{},"await room.emit('temperature', {'temperature': 72.5}, EmitOptions(qos=QoS.AT_MOST_ONCE))\n",[168,303,308],{"className":304,"code":305,"filename":306,"language":307,"meta":174,"style":174},"language-go shiki shiki-themes github-light github-dark","room := client.SetApp(appSlug).SetRoom(\"factory-floor\")\n\n// Fire and forget on the broker hop\nroom.Emit(\"temperature\", map[string]any{\"temperature\": 72.5}, nolag.EmitOptions{QoS: nolag.QoSLevel(nolag.QoSAtMostOnce)})\n","Go","go",[26,309,310,315,319,323],{"__ignoreMap":174},[178,311,312],{"class":180,"line":181},[178,313,314],{},"room := client.SetApp(appSlug).SetRoom(\"factory-floor\")\n",[178,316,317],{"class":180,"line":224},[178,318,228],{"emptyLinePlaceholder":227},[178,320,321],{"class":180,"line":231},[178,322,235],{},[178,324,325],{"class":180,"line":238},[178,326,327],{},"room.Emit(\"temperature\", map[string]any{\"temperature\": 72.5}, nolag.EmitOptions{QoS: nolag.QoSLevel(nolag.QoSAtMostOnce)})\n",[14,329,330],{},[331,332,333],"strong",{},"Best for:",[54,335,336,339,342,345],{},[57,337,338],{},"Sensor data that updates frequently",[57,340,341],{},"Live location updates",[57,343,344],{},"Typing indicators",[57,346,347],{},"Mouse cursor positions",[18,349,351],{"id":350},"qos-1-at-least-once","QoS 1: At-least-once",[14,353,354],{},"The internal hop acknowledges the message between broker nodes and may forward it more than once. This is the default, and the right choice for most application traffic.",[165,356,357,396,411],{},[168,358,360],{"className":170,"code":359,"filename":172,"language":173,"meta":174,"style":174},"// Acknowledged on the broker hop (the default, shown explicitly)\nroom.emit('notifications', { type: 'alert', message: 'You have a new message' }, { qos: 1 })\n",[26,361,362,367],{"__ignoreMap":174},[178,363,364],{"class":180,"line":181},[178,365,366],{"class":234},"// Acknowledged on the broker hop (the default, shown explicitly)\n",[178,368,369,371,373,375,378,381,384,387,390,392,394],{"class":180,"line":224},[178,370,241],{"class":195},[178,372,244],{"class":199},[178,374,203],{"class":195},[178,376,377],{"class":217},"'notifications'",[178,379,380],{"class":195},", { type: ",[178,382,383],{"class":217},"'alert'",[178,385,386],{"class":195},", message: ",[178,388,389],{"class":217},"'You have a new message'",[178,391,258],{"class":195},[178,393,32],{"class":188},[178,395,263],{"class":195},[168,397,399],{"className":266,"code":398,"filename":268,"language":269,"meta":174,"style":174},"# Acknowledged on the broker hop (the default, shown explicitly)\nawait room.emit('notifications', {'type': 'alert', 'message': 'You have a new message'}, EmitOptions(qos=QoS.AT_LEAST_ONCE))\n",[26,400,401,406],{"__ignoreMap":174},[178,402,403],{"class":180,"line":181},[178,404,405],{},"# Acknowledged on the broker hop (the default, shown explicitly)\n",[178,407,408],{"class":180,"line":224},[178,409,410],{},"await room.emit('notifications', {'type': 'alert', 'message': 'You have a new message'}, EmitOptions(qos=QoS.AT_LEAST_ONCE))\n",[168,412,414],{"className":304,"code":413,"filename":306,"language":307,"meta":174,"style":174},"// Acknowledged on the broker hop (the default, shown explicitly)\nroom.Emit(\"notifications\", map[string]any{\"type\": \"alert\", \"message\": \"You have a new message\"}, nolag.EmitOptions{QoS: nolag.QoSLevel(nolag.QoSAtLeastOnce)})\n",[26,415,416,420],{"__ignoreMap":174},[178,417,418],{"class":180,"line":181},[178,419,366],{},[178,421,422],{"class":180,"line":224},[178,423,424],{},"room.Emit(\"notifications\", map[string]any{\"type\": \"alert\", \"message\": \"You have a new message\"}, nolag.EmitOptions{QoS: nolag.QoSLevel(nolag.QoSAtLeastOnce)})\n",[14,426,427],{},[331,428,333],{},[54,430,431,434,437,440],{},[57,432,433],{},"Chat messages",[57,435,436],{},"Notifications",[57,438,439],{},"Event broadcasts",[57,441,442],{},"Most real-time applications",[18,444,446],{"id":445},"qos-2-deduplicated-on-the-broker-hop","QoS 2: Deduplicated on the broker hop",[14,448,449],{},"The internal hop runs the MQTT level 2 four-step handshake, so the broker does not forward a duplicate between its own nodes. It is the slowest level, and it does not extend to the WebSocket leg: the gateway still delivers what the broker hands it, and a client that publishes twice still produces two messages. Treat QoS 2 as \"fewer duplicates\", not \"no duplicates\", and keep the idempotency check below.",[165,451,452,497,512],{},[168,453,455],{"className":170,"code":454,"filename":172,"language":173,"meta":174,"style":174},"// Deduplicated on the broker hop only\nroom.emit('orders', { orderId: '12345', status: 'completed', amount: 99.99 }, { qos: 2 })\n",[26,456,457,462],{"__ignoreMap":174},[178,458,459],{"class":180,"line":181},[178,460,461],{"class":234},"// Deduplicated on the broker hop only\n",[178,463,464,466,468,470,473,476,479,482,485,488,491,493,495],{"class":180,"line":224},[178,465,241],{"class":195},[178,467,244],{"class":199},[178,469,203],{"class":195},[178,471,472],{"class":217},"'orders'",[178,474,475],{"class":195},", { orderId: ",[178,477,478],{"class":217},"'12345'",[178,480,481],{"class":195},", status: ",[178,483,484],{"class":217},"'completed'",[178,486,487],{"class":195},", amount: ",[178,489,490],{"class":188},"99.99",[178,492,258],{"class":195},[178,494,36],{"class":188},[178,496,263],{"class":195},[168,498,500],{"className":266,"code":499,"filename":268,"language":269,"meta":174,"style":174},"# Deduplicated on the broker hop only\nawait room.emit('orders', {'orderId': '12345', 'status': 'completed', 'amount': 99.99}, EmitOptions(qos=QoS.EXACTLY_ONCE))\n",[26,501,502,507],{"__ignoreMap":174},[178,503,504],{"class":180,"line":181},[178,505,506],{},"# Deduplicated on the broker hop only\n",[178,508,509],{"class":180,"line":224},[178,510,511],{},"await room.emit('orders', {'orderId': '12345', 'status': 'completed', 'amount': 99.99}, EmitOptions(qos=QoS.EXACTLY_ONCE))\n",[168,513,515],{"className":304,"code":514,"filename":306,"language":307,"meta":174,"style":174},"// Deduplicated on the broker hop only\nroom.Emit(\"orders\", map[string]any{\"orderId\": \"12345\", \"status\": \"completed\", \"amount\": 99.99}, nolag.EmitOptions{QoS: nolag.QoSLevel(nolag.QoSExactlyOnce)})\n",[26,516,517,521],{"__ignoreMap":174},[178,518,519],{"class":180,"line":181},[178,520,461],{},[178,522,523],{"class":180,"line":224},[178,524,525],{},"room.Emit(\"orders\", map[string]any{\"orderId\": \"12345\", \"status\": \"completed\", \"amount\": 99.99}, nolag.EmitOptions{QoS: nolag.QoSLevel(nolag.QoSExactlyOnce)})\n",[14,527,528],{},[331,529,333],{},[54,531,532,535,538],{},[57,533,534],{},"Order and payment status events, together with an idempotency key",[57,536,537],{},"Critical state changes that are also deduplicated by the consumer",[57,539,540],{},"Inventory updates where a duplicate would be expensive to reverse",[18,542,544],{"id":543},"confirming-a-publish","Confirming a Publish",[14,546,547,548,550,551,553],{},"Pass a callback to ",[26,549,244],{}," to learn whether the broker accepted the message. On protocol v2 the callback resolves on the broker's ",[26,552,66],{}," acknowledgement; if none arrives within 10 seconds it receives an error. The SDK never resends on its own.",[165,555,556,666,710],{},[168,557,559],{"className":170,"code":558,"filename":172,"language":173,"meta":174,"style":174},"room.emit('orders', { orderId: '12345', status: 'completed' }, { qos: 1 }, (err) => {\n  if (err) {\n    // Not acknowledged: unknown room, no publish permission, or a timeout.\n    // Decide whether to publish again; the SDK will not.\n    console.error('publish failed:', err.message)\n    return\n  }\n  console.log('accepted by the broker')\n})\n",[26,560,561,599,607,612,617,633,638,644,660],{"__ignoreMap":174},[178,562,563,565,567,569,571,573,575,577,579,581,583,586,590,593,596],{"class":180,"line":181},[178,564,241],{"class":195},[178,566,244],{"class":199},[178,568,203],{"class":195},[178,570,472],{"class":217},[178,572,475],{"class":195},[178,574,478],{"class":217},[178,576,481],{"class":195},[178,578,484],{"class":217},[178,580,258],{"class":195},[178,582,32],{"class":188},[178,584,585],{"class":195}," }, (",[178,587,589],{"class":588},"s4XuR","err",[178,591,592],{"class":195},") ",[178,594,595],{"class":184},"=>",[178,597,598],{"class":195}," {\n",[178,600,601,604],{"class":180,"line":224},[178,602,603],{"class":184},"  if",[178,605,606],{"class":195}," (err) {\n",[178,608,609],{"class":180,"line":231},[178,610,611],{"class":234},"    // Not acknowledged: unknown room, no publish permission, or a timeout.\n",[178,613,614],{"class":180,"line":238},[178,615,616],{"class":234},"    // Decide whether to publish again; the SDK will not.\n",[178,618,619,622,625,627,630],{"class":180,"line":292},[178,620,621],{"class":195},"    console.",[178,623,624],{"class":199},"error",[178,626,203],{"class":195},[178,628,629],{"class":217},"'publish failed:'",[178,631,632],{"class":195},", err.message)\n",[178,634,635],{"class":180,"line":298},[178,636,637],{"class":184},"    return\n",[178,639,641],{"class":180,"line":640},7,[178,642,643],{"class":195},"  }\n",[178,645,647,650,653,655,658],{"class":180,"line":646},8,[178,648,649],{"class":195},"  console.",[178,651,652],{"class":199},"log",[178,654,203],{"class":195},[178,656,657],{"class":217},"'accepted by the broker'",[178,659,221],{"class":195},[178,661,663],{"class":180,"line":662},9,[178,664,665],{"class":195},"})\n",[168,667,669],{"className":266,"code":668,"filename":268,"language":269,"meta":174,"style":174},"def on_published(err):\n    if err:\n        # Not acknowledged: unknown room, no publish permission, or a timeout.\n        print('publish failed:', err)\n    else:\n        print('accepted by the broker')\n\nawait room.emit('orders', {'orderId': '12345', 'status': 'completed'}, EmitOptions(qos=QoS.AT_LEAST_ONCE), on_published)\n",[26,670,671,676,681,686,691,696,701,705],{"__ignoreMap":174},[178,672,673],{"class":180,"line":181},[178,674,675],{},"def on_published(err):\n",[178,677,678],{"class":180,"line":224},[178,679,680],{},"    if err:\n",[178,682,683],{"class":180,"line":231},[178,684,685],{},"        # Not acknowledged: unknown room, no publish permission, or a timeout.\n",[178,687,688],{"class":180,"line":238},[178,689,690],{},"        print('publish failed:', err)\n",[178,692,693],{"class":180,"line":292},[178,694,695],{},"    else:\n",[178,697,698],{"class":180,"line":298},[178,699,700],{},"        print('accepted by the broker')\n",[178,702,703],{"class":180,"line":640},[178,704,228],{"emptyLinePlaceholder":227},[178,706,707],{"class":180,"line":646},[178,708,709],{},"await room.emit('orders', {'orderId': '12345', 'status': 'completed'}, EmitOptions(qos=QoS.AT_LEAST_ONCE), on_published)\n",[168,711,713],{"className":304,"code":712,"filename":306,"language":307,"meta":174,"style":174},"// Emit returns an error only when the frame could not be sent locally.\n// Broker refusals (unknown room, no permission) arrive on OnError.\nclient.OnError(func(err *nolag.ServerError) {\n    fmt.Println(\"refused:\", err.Name, err.Topic, err.Hint)\n})\nif err := room.Emit(\"orders\", map[string]any{\"orderId\": \"12345\", \"status\": \"completed\"}); err != nil {\n    fmt.Println(\"not sent:\", err)\n}\n",[26,714,715,720,725,730,735,739,744,749],{"__ignoreMap":174},[178,716,717],{"class":180,"line":181},[178,718,719],{},"// Emit returns an error only when the frame could not be sent locally.\n",[178,721,722],{"class":180,"line":224},[178,723,724],{},"// Broker refusals (unknown room, no permission) arrive on OnError.\n",[178,726,727],{"class":180,"line":231},[178,728,729],{},"client.OnError(func(err *nolag.ServerError) {\n",[178,731,732],{"class":180,"line":238},[178,733,734],{},"    fmt.Println(\"refused:\", err.Name, err.Topic, err.Hint)\n",[178,736,737],{"class":180,"line":292},[178,738,665],{},[178,740,741],{"class":180,"line":298},[178,742,743],{},"if err := room.Emit(\"orders\", map[string]any{\"orderId\": \"12345\", \"status\": \"completed\"}); err != nil {\n",[178,745,746],{"class":180,"line":640},[178,747,748],{},"    fmt.Println(\"not sent:\", err)\n",[178,750,751],{"class":180,"line":646},[178,752,753],{},"}\n",[18,755,757],{"id":756},"choosing-the-right-qos","Choosing the Right QoS",[759,760,762],"h3",{"id":761},"decision-guide","Decision Guide",[54,764,765,771,777,783],{},[57,766,767,770],{},[331,768,769],{},"Can you tolerate lost messages?"," → Use QoS 0",[57,772,773,776],{},[331,774,775],{},"Need the broker hop acknowledged?"," → Use QoS 1",[57,778,779,782],{},[331,780,781],{},"Want the broker hop to deduplicate as well?"," → Use QoS 2, and still dedupe on the client",[57,784,785,788],{},[331,786,787],{},"Not sure?"," → Start with QoS 1 (the default)",[18,790,792],{"id":791},"handling-duplicates","Handling Duplicates",[14,794,795],{},"Whatever the level, implement idempotency on the consumer. Key it on an identifier from your own payload, which survives a publisher retry as well as a broker duplicate:",[165,797,798,1064,1178],{},[168,799,801],{"className":170,"code":800,"filename":172,"language":173,"meta":174,"style":174},"const processedMessages = new Set\u003Cstring>()\n\nroom.on('orders', (data: { orderId: string }, meta) => {\n  // Use a unique identifier from your data\n  const messageId = data.orderId\n\n  // Skip if already processed\n  if (processedMessages.has(messageId)) {\n    return\n  }\n\n  // Process the message\n  processOrder(data)\n\n  // Mark as processed\n  processedMessages.add(messageId)\n\n  // Clean up old IDs periodically\n  if (processedMessages.size > 10000) {\n    const entries = Array.from(processedMessages)\n    entries.slice(0, 5000).forEach(id => processedMessages.delete(id))\n  }\n})\n",[26,802,803,827,831,874,879,892,896,901,914,918,923,928,934,943,948,954,966,971,977,994,1014,1054,1059],{"__ignoreMap":174},[178,804,805,807,810,812,815,818,821,824],{"class":180,"line":181},[178,806,185],{"class":184},[178,808,809],{"class":188}," processedMessages",[178,811,192],{"class":184},[178,813,814],{"class":184}," new",[178,816,817],{"class":199}," Set",[178,819,820],{"class":195},"\u003C",[178,822,823],{"class":188},"string",[178,825,826],{"class":195},">()\n",[178,828,829],{"class":180,"line":224},[178,830,228],{"emptyLinePlaceholder":227},[178,832,833,835,838,840,842,845,848,851,854,857,859,862,865,868,870,872],{"class":180,"line":231},[178,834,241],{"class":195},[178,836,837],{"class":199},"on",[178,839,203],{"class":195},[178,841,472],{"class":217},[178,843,844],{"class":195},", (",[178,846,847],{"class":588},"data",[178,849,850],{"class":184},":",[178,852,853],{"class":195}," { ",[178,855,856],{"class":588},"orderId",[178,858,850],{"class":184},[178,860,861],{"class":188}," string",[178,863,864],{"class":195}," }, ",[178,866,867],{"class":588},"meta",[178,869,592],{"class":195},[178,871,595],{"class":184},[178,873,598],{"class":195},[178,875,876],{"class":180,"line":238},[178,877,878],{"class":234},"  // Use a unique identifier from your data\n",[178,880,881,884,887,889],{"class":180,"line":292},[178,882,883],{"class":184},"  const",[178,885,886],{"class":188}," messageId",[178,888,192],{"class":184},[178,890,891],{"class":195}," data.orderId\n",[178,893,894],{"class":180,"line":298},[178,895,228],{"emptyLinePlaceholder":227},[178,897,898],{"class":180,"line":640},[178,899,900],{"class":234},"  // Skip if already processed\n",[178,902,903,905,908,911],{"class":180,"line":646},[178,904,603],{"class":184},[178,906,907],{"class":195}," (processedMessages.",[178,909,910],{"class":199},"has",[178,912,913],{"class":195},"(messageId)) {\n",[178,915,916],{"class":180,"line":662},[178,917,637],{"class":184},[178,919,921],{"class":180,"line":920},10,[178,922,643],{"class":195},[178,924,926],{"class":180,"line":925},11,[178,927,228],{"emptyLinePlaceholder":227},[178,929,931],{"class":180,"line":930},12,[178,932,933],{"class":234},"  // Process the message\n",[178,935,937,940],{"class":180,"line":936},13,[178,938,939],{"class":199},"  processOrder",[178,941,942],{"class":195},"(data)\n",[178,944,946],{"class":180,"line":945},14,[178,947,228],{"emptyLinePlaceholder":227},[178,949,951],{"class":180,"line":950},15,[178,952,953],{"class":234},"  // Mark as processed\n",[178,955,957,960,963],{"class":180,"line":956},16,[178,958,959],{"class":195},"  processedMessages.",[178,961,962],{"class":199},"add",[178,964,965],{"class":195},"(messageId)\n",[178,967,969],{"class":180,"line":968},17,[178,970,228],{"emptyLinePlaceholder":227},[178,972,974],{"class":180,"line":973},18,[178,975,976],{"class":234},"  // Clean up old IDs periodically\n",[178,978,980,982,985,988,991],{"class":180,"line":979},19,[178,981,603],{"class":184},[178,983,984],{"class":195}," (processedMessages.size ",[178,986,987],{"class":184},">",[178,989,990],{"class":188}," 10000",[178,992,993],{"class":195},") {\n",[178,995,997,1000,1003,1005,1008,1011],{"class":180,"line":996},20,[178,998,999],{"class":184},"    const",[178,1001,1002],{"class":188}," entries",[178,1004,192],{"class":184},[178,1006,1007],{"class":195}," Array.",[178,1009,1010],{"class":199},"from",[178,1012,1013],{"class":195},"(processedMessages)\n",[178,1015,1017,1020,1023,1025,1027,1029,1032,1034,1037,1039,1042,1045,1048,1051],{"class":180,"line":1016},21,[178,1018,1019],{"class":195},"    entries.",[178,1021,1022],{"class":199},"slice",[178,1024,203],{"class":195},[178,1026,28],{"class":188},[178,1028,29],{"class":195},[178,1030,1031],{"class":188},"5000",[178,1033,209],{"class":195},[178,1035,1036],{"class":199},"forEach",[178,1038,203],{"class":195},[178,1040,1041],{"class":588},"id",[178,1043,1044],{"class":184}," =>",[178,1046,1047],{"class":195}," processedMessages.",[178,1049,1050],{"class":199},"delete",[178,1052,1053],{"class":195},"(id))\n",[178,1055,1057],{"class":180,"line":1056},22,[178,1058,643],{"class":195},[178,1060,1062],{"class":180,"line":1061},23,[178,1063,665],{"class":195},[168,1065,1067],{"className":266,"code":1066,"filename":268,"language":269,"meta":174,"style":174},"processed_messages = set()\n\ndef handle_order(data, meta):\n    # Use a unique identifier from your data\n    message_id = data['orderId']\n\n    # Skip if already processed\n    if message_id in processed_messages:\n        return\n\n    # Process the message\n    process_order(data)\n\n    # Mark as processed\n    processed_messages.add(message_id)\n\n    # Clean up old IDs periodically\n    if len(processed_messages) > 10000:\n        to_remove = list(processed_messages)[:5000]\n        for id in to_remove:\n            processed_messages.discard(id)\n\nroom.on('orders', handle_order)\n",[26,1068,1069,1074,1078,1083,1088,1093,1097,1102,1107,1112,1116,1121,1126,1130,1135,1140,1144,1149,1154,1159,1164,1169,1173],{"__ignoreMap":174},[178,1070,1071],{"class":180,"line":181},[178,1072,1073],{},"processed_messages = set()\n",[178,1075,1076],{"class":180,"line":224},[178,1077,228],{"emptyLinePlaceholder":227},[178,1079,1080],{"class":180,"line":231},[178,1081,1082],{},"def handle_order(data, meta):\n",[178,1084,1085],{"class":180,"line":238},[178,1086,1087],{},"    # Use a unique identifier from your data\n",[178,1089,1090],{"class":180,"line":292},[178,1091,1092],{},"    message_id = data['orderId']\n",[178,1094,1095],{"class":180,"line":298},[178,1096,228],{"emptyLinePlaceholder":227},[178,1098,1099],{"class":180,"line":640},[178,1100,1101],{},"    # Skip if already processed\n",[178,1103,1104],{"class":180,"line":646},[178,1105,1106],{},"    if message_id in processed_messages:\n",[178,1108,1109],{"class":180,"line":662},[178,1110,1111],{},"        return\n",[178,1113,1114],{"class":180,"line":920},[178,1115,228],{"emptyLinePlaceholder":227},[178,1117,1118],{"class":180,"line":925},[178,1119,1120],{},"    # Process the message\n",[178,1122,1123],{"class":180,"line":930},[178,1124,1125],{},"    process_order(data)\n",[178,1127,1128],{"class":180,"line":936},[178,1129,228],{"emptyLinePlaceholder":227},[178,1131,1132],{"class":180,"line":945},[178,1133,1134],{},"    # Mark as processed\n",[178,1136,1137],{"class":180,"line":950},[178,1138,1139],{},"    processed_messages.add(message_id)\n",[178,1141,1142],{"class":180,"line":956},[178,1143,228],{"emptyLinePlaceholder":227},[178,1145,1146],{"class":180,"line":968},[178,1147,1148],{},"    # Clean up old IDs periodically\n",[178,1150,1151],{"class":180,"line":973},[178,1152,1153],{},"    if len(processed_messages) > 10000:\n",[178,1155,1156],{"class":180,"line":979},[178,1157,1158],{},"        to_remove = list(processed_messages)[:5000]\n",[178,1160,1161],{"class":180,"line":996},[178,1162,1163],{},"        for id in to_remove:\n",[178,1165,1166],{"class":180,"line":1016},[178,1167,1168],{},"            processed_messages.discard(id)\n",[178,1170,1171],{"class":180,"line":1056},[178,1172,228],{"emptyLinePlaceholder":227},[178,1174,1175],{"class":180,"line":1061},[178,1176,1177],{},"room.on('orders', handle_order)\n",[168,1179,1181],{"className":304,"code":1180,"filename":306,"language":307,"meta":174,"style":174},"processedMessages := make(map[string]bool)\nmu := &sync.Mutex{}\n\nroom.Subscribe(\"orders\", func(data any, meta nolag.MessageMeta) {\n    msg, _ := data.(map[string]any)\n    messageId, _ := msg[\"orderId\"].(string)\n\n    mu.Lock()\n    defer mu.Unlock()\n\n    // Skip if already processed\n    if processedMessages[messageId] {\n        return\n    }\n\n    // Process the message\n    processOrder(msg)\n\n    // Mark as processed\n    processedMessages[messageId] = true\n\n    // Clean up old IDs periodically\n    if len(processedMessages) > 10000 {\n        i := 0\n        for id := range processedMessages {\n            if i >= 5000 {\n                break\n            }\n            delete(processedMessages, id)\n            i++\n        }\n    }\n})\n",[26,1182,1183,1188,1193,1197,1202,1207,1212,1216,1221,1226,1230,1235,1240,1244,1249,1253,1258,1263,1267,1272,1277,1281,1286,1291,1297,1303,1309,1315,1321,1327,1333,1339,1344],{"__ignoreMap":174},[178,1184,1185],{"class":180,"line":181},[178,1186,1187],{},"processedMessages := make(map[string]bool)\n",[178,1189,1190],{"class":180,"line":224},[178,1191,1192],{},"mu := &sync.Mutex{}\n",[178,1194,1195],{"class":180,"line":231},[178,1196,228],{"emptyLinePlaceholder":227},[178,1198,1199],{"class":180,"line":238},[178,1200,1201],{},"room.Subscribe(\"orders\", func(data any, meta nolag.MessageMeta) {\n",[178,1203,1204],{"class":180,"line":292},[178,1205,1206],{},"    msg, _ := data.(map[string]any)\n",[178,1208,1209],{"class":180,"line":298},[178,1210,1211],{},"    messageId, _ := msg[\"orderId\"].(string)\n",[178,1213,1214],{"class":180,"line":640},[178,1215,228],{"emptyLinePlaceholder":227},[178,1217,1218],{"class":180,"line":646},[178,1219,1220],{},"    mu.Lock()\n",[178,1222,1223],{"class":180,"line":662},[178,1224,1225],{},"    defer mu.Unlock()\n",[178,1227,1228],{"class":180,"line":920},[178,1229,228],{"emptyLinePlaceholder":227},[178,1231,1232],{"class":180,"line":925},[178,1233,1234],{},"    // Skip if already processed\n",[178,1236,1237],{"class":180,"line":930},[178,1238,1239],{},"    if processedMessages[messageId] {\n",[178,1241,1242],{"class":180,"line":936},[178,1243,1111],{},[178,1245,1246],{"class":180,"line":945},[178,1247,1248],{},"    }\n",[178,1250,1251],{"class":180,"line":950},[178,1252,228],{"emptyLinePlaceholder":227},[178,1254,1255],{"class":180,"line":956},[178,1256,1257],{},"    // Process the message\n",[178,1259,1260],{"class":180,"line":968},[178,1261,1262],{},"    processOrder(msg)\n",[178,1264,1265],{"class":180,"line":973},[178,1266,228],{"emptyLinePlaceholder":227},[178,1268,1269],{"class":180,"line":979},[178,1270,1271],{},"    // Mark as processed\n",[178,1273,1274],{"class":180,"line":996},[178,1275,1276],{},"    processedMessages[messageId] = true\n",[178,1278,1279],{"class":180,"line":1016},[178,1280,228],{"emptyLinePlaceholder":227},[178,1282,1283],{"class":180,"line":1056},[178,1284,1285],{},"    // Clean up old IDs periodically\n",[178,1287,1288],{"class":180,"line":1061},[178,1289,1290],{},"    if len(processedMessages) > 10000 {\n",[178,1292,1294],{"class":180,"line":1293},24,[178,1295,1296],{},"        i := 0\n",[178,1298,1300],{"class":180,"line":1299},25,[178,1301,1302],{},"        for id := range processedMessages {\n",[178,1304,1306],{"class":180,"line":1305},26,[178,1307,1308],{},"            if i >= 5000 {\n",[178,1310,1312],{"class":180,"line":1311},27,[178,1313,1314],{},"                break\n",[178,1316,1318],{"class":180,"line":1317},28,[178,1319,1320],{},"            }\n",[178,1322,1324],{"class":180,"line":1323},29,[178,1325,1326],{},"            delete(processedMessages, id)\n",[178,1328,1330],{"class":180,"line":1329},30,[178,1331,1332],{},"            i++\n",[178,1334,1336],{"class":180,"line":1335},31,[178,1337,1338],{},"        }\n",[178,1340,1342],{"class":180,"line":1341},32,[178,1343,1248],{},[178,1345,1347],{"class":180,"line":1346},33,[178,1348,665],{},[18,1350,1352],{"id":1351},"default-qos","Default QoS",[14,1354,1355],{},"You can set a default QoS level for the connection and override it per message:",[165,1357,1358,1486,1547],{},[168,1359,1361],{"className":170,"code":1360,"filename":172,"language":173,"meta":174,"style":174},"import { NoLag } from '@nolag/js-sdk'\n\n// Default QoS for every publish and subscription on this connection\nconst client = NoLag('your_access_token', {\n  qos: 1\n})\n\nawait client.connect()\nconst room = client.setApp(APP_SLUG).setRoom('factory-floor')\n\n// Override per message\nroom.emit('orders', data, { qos: 2 })\n",[26,1362,1363,1376,1380,1385,1405,1413,1417,1421,1434,1460,1464,1469],{"__ignoreMap":174},[178,1364,1365,1368,1371,1373],{"class":180,"line":181},[178,1366,1367],{"class":184},"import",[178,1369,1370],{"class":195}," { NoLag } ",[178,1372,1010],{"class":184},[178,1374,1375],{"class":217}," '@nolag/js-sdk'\n",[178,1377,1378],{"class":180,"line":224},[178,1379,228],{"emptyLinePlaceholder":227},[178,1381,1382],{"class":180,"line":231},[178,1383,1384],{"class":234},"// Default QoS for every publish and subscription on this connection\n",[178,1386,1387,1389,1392,1394,1397,1399,1402],{"class":180,"line":238},[178,1388,185],{"class":184},[178,1390,1391],{"class":188}," client",[178,1393,192],{"class":184},[178,1395,1396],{"class":199}," NoLag",[178,1398,203],{"class":195},[178,1400,1401],{"class":217},"'your_access_token'",[178,1403,1404],{"class":195},", {\n",[178,1406,1407,1410],{"class":180,"line":292},[178,1408,1409],{"class":195},"  qos: ",[178,1411,1412],{"class":188},"1\n",[178,1414,1415],{"class":180,"line":298},[178,1416,665],{"class":195},[178,1418,1419],{"class":180,"line":640},[178,1420,228],{"emptyLinePlaceholder":227},[178,1422,1423,1426,1428,1431],{"class":180,"line":646},[178,1424,1425],{"class":184},"await",[178,1427,196],{"class":195},[178,1429,1430],{"class":199},"connect",[178,1432,1433],{"class":195},"()\n",[178,1435,1436,1438,1440,1442,1444,1446,1448,1450,1452,1454,1456,1458],{"class":180,"line":662},[178,1437,185],{"class":184},[178,1439,189],{"class":188},[178,1441,192],{"class":184},[178,1443,196],{"class":195},[178,1445,200],{"class":199},[178,1447,203],{"class":195},[178,1449,206],{"class":188},[178,1451,209],{"class":195},[178,1453,212],{"class":199},[178,1455,203],{"class":195},[178,1457,218],{"class":217},[178,1459,221],{"class":195},[178,1461,1462],{"class":180,"line":920},[178,1463,228],{"emptyLinePlaceholder":227},[178,1465,1466],{"class":180,"line":925},[178,1467,1468],{"class":234},"// Override per message\n",[178,1470,1471,1473,1475,1477,1479,1482,1484],{"class":180,"line":930},[178,1472,241],{"class":195},[178,1474,244],{"class":199},[178,1476,203],{"class":195},[178,1478,472],{"class":217},[178,1480,1481],{"class":195},", data, { qos: ",[178,1483,36],{"class":188},[178,1485,263],{"class":195},[168,1487,1489],{"className":266,"code":1488,"filename":268,"language":269,"meta":174,"style":174},"from nolag import NoLag, NoLagOptions, EmitOptions, QoS\n\n# Default QoS for every publish and subscription on this connection\nclient = NoLag('your_access_token', NoLagOptions(\n    qos=QoS.AT_LEAST_ONCE\n))\n\nawait client.connect()\nroom = client.set_app(APP_SLUG).set_room('factory-floor')\n\n# Override per message\nawait room.emit('orders', data, EmitOptions(qos=QoS.EXACTLY_ONCE))\n",[26,1490,1491,1496,1500,1505,1510,1515,1520,1524,1529,1533,1537,1542],{"__ignoreMap":174},[178,1492,1493],{"class":180,"line":181},[178,1494,1495],{},"from nolag import NoLag, NoLagOptions, EmitOptions, QoS\n",[178,1497,1498],{"class":180,"line":224},[178,1499,228],{"emptyLinePlaceholder":227},[178,1501,1502],{"class":180,"line":231},[178,1503,1504],{},"# Default QoS for every publish and subscription on this connection\n",[178,1506,1507],{"class":180,"line":238},[178,1508,1509],{},"client = NoLag('your_access_token', NoLagOptions(\n",[178,1511,1512],{"class":180,"line":292},[178,1513,1514],{},"    qos=QoS.AT_LEAST_ONCE\n",[178,1516,1517],{"class":180,"line":298},[178,1518,1519],{},"))\n",[178,1521,1522],{"class":180,"line":640},[178,1523,228],{"emptyLinePlaceholder":227},[178,1525,1526],{"class":180,"line":646},[178,1527,1528],{},"await client.connect()\n",[178,1530,1531],{"class":180,"line":662},[178,1532,285],{},[178,1534,1535],{"class":180,"line":920},[178,1536,228],{"emptyLinePlaceholder":227},[178,1538,1539],{"class":180,"line":925},[178,1540,1541],{},"# Override per message\n",[178,1543,1544],{"class":180,"line":930},[178,1545,1546],{},"await room.emit('orders', data, EmitOptions(qos=QoS.EXACTLY_ONCE))\n",[168,1548,1550],{"className":304,"code":1549,"filename":306,"language":307,"meta":174,"style":174},"// Declared on the client options and per publish, but see the note below\nclient := nolag.New(\"your_access_token\", nolag.Options{\n    QoS: nolag.QoSLevel(nolag.QoSAtLeastOnce), // omit to keep the default\n})\n\nif err := client.Connect(); err != nil {\n    log.Fatal(err)\n}\nroom := client.SetApp(appSlug).SetRoom(\"factory-floor\")\n\nroom.Emit(\"orders\", data, nolag.EmitOptions{QoS: nolag.QoSLevel(nolag.QoSExactlyOnce)})\n",[26,1551,1552,1557,1562,1567,1571,1575,1580,1585,1589,1593,1597],{"__ignoreMap":174},[178,1553,1554],{"class":180,"line":181},[178,1555,1556],{},"// Declared on the client options and per publish, but see the note below\n",[178,1558,1559],{"class":180,"line":224},[178,1560,1561],{},"client := nolag.New(\"your_access_token\", nolag.Options{\n",[178,1563,1564],{"class":180,"line":231},[178,1565,1566],{},"    QoS: nolag.QoSLevel(nolag.QoSAtLeastOnce), // omit to keep the default\n",[178,1568,1569],{"class":180,"line":238},[178,1570,665],{},[178,1572,1573],{"class":180,"line":292},[178,1574,228],{"emptyLinePlaceholder":227},[178,1576,1577],{"class":180,"line":298},[178,1578,1579],{},"if err := client.Connect(); err != nil {\n",[178,1581,1582],{"class":180,"line":640},[178,1583,1584],{},"    log.Fatal(err)\n",[178,1586,1587],{"class":180,"line":646},[178,1588,753],{},[178,1590,1591],{"class":180,"line":662},[178,1592,314],{},[178,1594,1595],{"class":180,"line":920},[178,1596,228],{"emptyLinePlaceholder":227},[178,1598,1599],{"class":180,"line":925},[178,1600,1601],{},"room.Emit(\"orders\", data, nolag.EmitOptions{QoS: nolag.QoSLevel(nolag.QoSExactlyOnce)})\n",[1603,1604],"callout",{"type":1605},"warning",[18,1607,1609],{"id":1608},"next-steps","Next Steps",[54,1611,1612,1616,1622],{},[57,1613,1614],{},[78,1615,81],{"href":80},[57,1617,1618],{},[78,1619,1621],{"href":1620},"/docs/concepts/acl","Learn about Access Control",[57,1623,1624],{},[78,1625,1627],{"href":1626},"/docs/api-reference","API Reference",[1629,1630,1631],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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 .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":174,"searchDepth":224,"depth":224,"links":1633},[1634,1635,1636,1637,1638,1639,1640,1643,1644,1645],{"id":20,"depth":224,"text":21},{"id":84,"depth":224,"text":85},{"id":159,"depth":224,"text":160},{"id":350,"depth":224,"text":351},{"id":445,"depth":224,"text":446},{"id":543,"depth":224,"text":544},{"id":756,"depth":224,"text":757,"children":1641},[1642],{"id":761,"depth":231,"text":762},{"id":791,"depth":224,"text":792},{"id":1351,"depth":224,"text":1352},{"id":1608,"depth":224,"text":1609},"What the QoS 0, 1 and 2 levels govern in NoLag, what the WebSocket connection acknowledges, and which delivery guarantees the platform does not make.","md",{},"/docs/concepts/qos",{"title":5,"description":1646},"docs/concepts/qos","pcOArJijoK5Y7GbBTgeREu1ALeebMmk7duTCRIHB-s8",1789869419444]