[{"data":1,"prerenderedAt":3955},["ShallowReactive",2],{"blog-post-\u002Fblog\u002Fsyncing-marten-with-search":3,"blog-surround-\u002Fblog\u002Fsyncing-marten-with-search":3953},{"id":4,"title":5,"body":6,"date":3939,"description":3940,"extension":3941,"meta":3942,"navigation":179,"path":3943,"readingTime":170,"seo":3944,"sitemap":3945,"stem":3946,"tags":3947,"__hash__":3952},"blog\u002Fblog\u002Fsyncing-marten-with-search.md","Keeping a search index in sync with Marten",{"type":7,"value":8,"toc":3931},"minimark",[9,13,25,403,408,411,734,769,773,784,1319,1322,1473,1507,1511,1514,2220,2330,2361,2365,2376,2569,2842,2845,3035,3066,3070,3073,3486,3762,3765,3800,3804,3916,3919,3927],[10,11,12],"p",{},"Marten is great at storing documents, but sooner or later someone wants full-text search with typo tolerance or faceting. You'll likely reach for a dedicated engine like Meilisearch, Elasticsearch or OpenSearch. Querying the index is easy, but ensuring your index is kept up to date is not.",[10,14,15,16,20,21,24],{},"This post walks through five ways to do that, with the pros and cons laid out. Every example indexes the same ",[17,18,19],"code",{},"Order"," document into a Meilisearch ",[17,22,23],{},"orders"," index, enriched with the customer's name:",[26,27,33],"pre",{"className":28,"code":29,"filename":30,"language":31,"meta":32,"style":32},"language-csharp shiki shiki-themes github-light-high-contrast github-light-high-contrast catppuccin-mocha","public class Order\n{\n    public Guid Id { get; set; }\n    public string Number { get; set; } = \"\";\n    public Guid CustomerId { get; set; }\n    public decimal Total { get; set; }\n}\n\npublic class Customer\n{\n    public Guid Id { get; set; }\n    public string Name { get; set; } = \"\";\n}\n\npublic record OrderSearchDocument(string Id, string Number, decimal Total, string? Customer)\n{\n    public static OrderSearchDocument From(Order order, Customer? customer) =>\n        new(order.Id.ToString(), order.Number, order.Total, customer?.Name);\n}\n","Documents.cs","csharp","",[17,34,35,51,58,88,123,145,168,174,181,191,196,217,245,250,255,300,305,341,398],{"__ignoreMap":32},[36,37,40,44,47],"span",{"class":38,"line":39},"line",1,[36,41,43],{"class":42},"szs56","public",[36,45,46],{"class":42}," class",[36,48,50],{"class":49},"s4IXt"," Order\n",[36,52,54],{"class":38,"line":53},2,[36,55,57],{"class":56},"sSUUE","{\n",[36,59,61,64,67,71,74,77,80,83,85],{"class":38,"line":60},3,[36,62,63],{"class":42},"    public",[36,65,66],{"class":49}," Guid",[36,68,70],{"class":69},"stEof"," Id",[36,72,73],{"class":56}," {",[36,75,76],{"class":42}," get",[36,78,79],{"class":56},";",[36,81,82],{"class":42}," set",[36,84,79],{"class":56},[36,86,87],{"class":56}," }\n",[36,89,91,93,96,99,101,103,105,107,109,112,116,120],{"class":38,"line":90},4,[36,92,63],{"class":42},[36,94,95],{"class":42}," string",[36,97,98],{"class":69}," Number",[36,100,73],{"class":56},[36,102,76],{"class":42},[36,104,79],{"class":56},[36,106,82],{"class":42},[36,108,79],{"class":56},[36,110,111],{"class":56}," }",[36,113,115],{"class":114},"sOrU-"," =",[36,117,119],{"class":118},"s0foE"," \"\"",[36,121,122],{"class":56},";\n",[36,124,126,128,130,133,135,137,139,141,143],{"class":38,"line":125},5,[36,127,63],{"class":42},[36,129,66],{"class":49},[36,131,132],{"class":69}," CustomerId",[36,134,73],{"class":56},[36,136,76],{"class":42},[36,138,79],{"class":56},[36,140,82],{"class":42},[36,142,79],{"class":56},[36,144,87],{"class":56},[36,146,148,150,153,156,158,160,162,164,166],{"class":38,"line":147},6,[36,149,63],{"class":42},[36,151,152],{"class":42}," decimal",[36,154,155],{"class":69}," Total",[36,157,73],{"class":56},[36,159,76],{"class":42},[36,161,79],{"class":56},[36,163,82],{"class":42},[36,165,79],{"class":56},[36,167,87],{"class":56},[36,169,171],{"class":38,"line":170},7,[36,172,173],{"class":56},"}\n",[36,175,177],{"class":38,"line":176},8,[36,178,180],{"emptyLinePlaceholder":179},true,"\n",[36,182,184,186,188],{"class":38,"line":183},9,[36,185,43],{"class":42},[36,187,46],{"class":42},[36,189,190],{"class":49}," Customer\n",[36,192,194],{"class":38,"line":193},10,[36,195,57],{"class":56},[36,197,199,201,203,205,207,209,211,213,215],{"class":38,"line":198},11,[36,200,63],{"class":42},[36,202,66],{"class":49},[36,204,70],{"class":69},[36,206,73],{"class":56},[36,208,76],{"class":42},[36,210,79],{"class":56},[36,212,82],{"class":42},[36,214,79],{"class":56},[36,216,87],{"class":56},[36,218,220,222,224,227,229,231,233,235,237,239,241,243],{"class":38,"line":219},12,[36,221,63],{"class":42},[36,223,95],{"class":42},[36,225,226],{"class":69}," Name",[36,228,73],{"class":56},[36,230,76],{"class":42},[36,232,79],{"class":56},[36,234,82],{"class":42},[36,236,79],{"class":56},[36,238,111],{"class":56},[36,240,115],{"class":114},[36,242,119],{"class":118},[36,244,122],{"class":56},[36,246,248],{"class":38,"line":247},13,[36,249,173],{"class":56},[36,251,253],{"class":38,"line":252},14,[36,254,180],{"emptyLinePlaceholder":179},[36,256,258,260,263,266,269,272,274,277,279,281,283,285,287,289,291,294,297],{"class":38,"line":257},15,[36,259,43],{"class":42},[36,261,262],{"class":42}," record",[36,264,265],{"class":49}," OrderSearchDocument",[36,267,268],{"class":56},"(",[36,270,271],{"class":42},"string",[36,273,70],{"class":69},[36,275,276],{"class":56},",",[36,278,95],{"class":42},[36,280,98],{"class":69},[36,282,276],{"class":56},[36,284,152],{"class":42},[36,286,155],{"class":69},[36,288,276],{"class":56},[36,290,95],{"class":42},[36,292,293],{"class":56},"?",[36,295,296],{"class":69}," Customer",[36,298,299],{"class":56},")\n",[36,301,303],{"class":38,"line":302},16,[36,304,57],{"class":56},[36,306,308,310,313,315,319,321,323,326,328,330,332,335,338],{"class":38,"line":307},17,[36,309,63],{"class":42},[36,311,312],{"class":42}," static",[36,314,265],{"class":49},[36,316,318],{"class":317},"sk8lX"," From",[36,320,268],{"class":56},[36,322,19],{"class":49},[36,324,325],{"class":69}," order",[36,327,276],{"class":56},[36,329,296],{"class":49},[36,331,293],{"class":56},[36,333,334],{"class":69}," customer",[36,336,337],{"class":56},")",[36,339,340],{"class":114}," =>\n",[36,342,344,347,349,353,357,360,362,365,368,370,372,375,377,379,381,384,386,388,390,392,395],{"class":38,"line":343},18,[36,345,346],{"class":114},"        new",[36,348,268],{"class":56},[36,350,352],{"class":351},"saRu8","order",[36,354,356],{"class":355},"si4eh",".",[36,358,359],{"class":351},"Id",[36,361,356],{"class":355},[36,363,364],{"class":317},"ToString",[36,366,367],{"class":56},"(),",[36,369,325],{"class":351},[36,371,356],{"class":355},[36,373,374],{"class":351},"Number",[36,376,276],{"class":56},[36,378,325],{"class":351},[36,380,356],{"class":355},[36,382,383],{"class":351},"Total",[36,385,276],{"class":56},[36,387,334],{"class":351},[36,389,293],{"class":114},[36,391,356],{"class":355},[36,393,394],{"class":351},"Name",[36,396,397],{"class":56},");\n",[36,399,401],{"class":38,"line":400},19,[36,402,173],{"class":56},[404,405,407],"h2",{"id":406},"inline-calls","Inline Calls",[10,409,410],{},"The first thing everyone considers: save the document, then call the search engine straight after. Incredibly simple, but far from an ideal option.",[26,412,415],{"className":28,"code":413,"filename":414,"language":31,"meta":32,"style":32},"public class PlaceOrderHandler(IDocumentSession session, MeilisearchClient meili)\n{\n    public async Task Handle(PlaceOrder command, CancellationToken ct)\n    {\n        var order = new Order\n        {\n            Id = Guid.CreateVersion7(),\n            Number = command.Number,\n            CustomerId = command.CustomerId,\n            Total = command.Total\n        };\n\n        session.Store(order);\n        await session.SaveChangesAsync(ct);\n\n        \u002F\u002F Committed. If anything below throws, the index is stale.\n        var customer = await session.LoadAsync\u003CCustomer>(order.CustomerId, ct);\n        await meili.Index(\"orders\").AddDocumentsAsync(\n            [OrderSearchDocument.From(order, customer)], cancellationToken: ct);\n    }\n}\n","PlaceOrderHandler.cs",[17,416,417,444,448,479,484,498,503,521,537,553,567,572,576,592,611,615,621,663,689,723,729],{"__ignoreMap":32},[36,418,419,421,423,426,428,431,434,436,439,442],{"class":38,"line":39},[36,420,43],{"class":42},[36,422,46],{"class":42},[36,424,425],{"class":49}," PlaceOrderHandler",[36,427,268],{"class":56},[36,429,430],{"class":49},"IDocumentSession",[36,432,433],{"class":69}," session",[36,435,276],{"class":56},[36,437,438],{"class":49}," MeilisearchClient",[36,440,441],{"class":69}," meili",[36,443,299],{"class":56},[36,445,446],{"class":38,"line":53},[36,447,57],{"class":56},[36,449,450,452,455,458,461,463,466,469,471,474,477],{"class":38,"line":60},[36,451,63],{"class":42},[36,453,454],{"class":42}," async",[36,456,457],{"class":49}," Task",[36,459,460],{"class":317}," Handle",[36,462,268],{"class":56},[36,464,465],{"class":49},"PlaceOrder",[36,467,468],{"class":69}," command",[36,470,276],{"class":56},[36,472,473],{"class":49}," CancellationToken",[36,475,476],{"class":69}," ct",[36,478,299],{"class":56},[36,480,481],{"class":38,"line":90},[36,482,483],{"class":56},"    {\n",[36,485,486,489,491,493,496],{"class":38,"line":125},[36,487,488],{"class":42},"        var",[36,490,325],{"class":69},[36,492,115],{"class":114},[36,494,495],{"class":114}," new",[36,497,50],{"class":49},[36,499,500],{"class":38,"line":147},[36,501,502],{"class":56},"        {\n",[36,504,505,508,511,513,515,518],{"class":38,"line":170},[36,506,507],{"class":351},"            Id ",[36,509,510],{"class":114},"=",[36,512,66],{"class":351},[36,514,356],{"class":355},[36,516,517],{"class":317},"CreateVersion7",[36,519,520],{"class":56},"(),\n",[36,522,523,526,528,530,532,534],{"class":38,"line":176},[36,524,525],{"class":351},"            Number ",[36,527,510],{"class":114},[36,529,468],{"class":351},[36,531,356],{"class":355},[36,533,374],{"class":351},[36,535,536],{"class":56},",\n",[36,538,539,542,544,546,548,551],{"class":38,"line":183},[36,540,541],{"class":351},"            CustomerId ",[36,543,510],{"class":114},[36,545,468],{"class":351},[36,547,356],{"class":355},[36,549,550],{"class":351},"CustomerId",[36,552,536],{"class":56},[36,554,555,558,560,562,564],{"class":38,"line":193},[36,556,557],{"class":351},"            Total ",[36,559,510],{"class":114},[36,561,468],{"class":351},[36,563,356],{"class":355},[36,565,566],{"class":351},"Total\n",[36,568,569],{"class":38,"line":198},[36,570,571],{"class":56},"        };\n",[36,573,574],{"class":38,"line":219},[36,575,180],{"emptyLinePlaceholder":179},[36,577,578,581,583,586,588,590],{"class":38,"line":247},[36,579,580],{"class":351},"        session",[36,582,356],{"class":355},[36,584,585],{"class":317},"Store",[36,587,268],{"class":56},[36,589,352],{"class":351},[36,591,397],{"class":56},[36,593,594,597,599,601,604,606,609],{"class":38,"line":252},[36,595,596],{"class":114},"        await",[36,598,433],{"class":351},[36,600,356],{"class":355},[36,602,603],{"class":317},"SaveChangesAsync",[36,605,268],{"class":56},[36,607,608],{"class":351},"ct",[36,610,397],{"class":56},[36,612,613],{"class":38,"line":257},[36,614,180],{"emptyLinePlaceholder":179},[36,616,617],{"class":38,"line":302},[36,618,620],{"class":619},"sa31r","        \u002F\u002F Committed. If anything below throws, the index is stale.\n",[36,622,623,625,627,629,632,634,636,639,643,646,649,651,653,655,657,659,661],{"class":38,"line":307},[36,624,488],{"class":42},[36,626,334],{"class":69},[36,628,115],{"class":114},[36,630,631],{"class":114}," await",[36,633,433],{"class":351},[36,635,356],{"class":355},[36,637,638],{"class":317},"LoadAsync",[36,640,642],{"class":641},"sLONz","\u003C",[36,644,645],{"class":49},"Customer",[36,647,648],{"class":641},">",[36,650,268],{"class":56},[36,652,352],{"class":351},[36,654,356],{"class":355},[36,656,550],{"class":351},[36,658,276],{"class":56},[36,660,476],{"class":351},[36,662,397],{"class":56},[36,664,665,667,669,671,674,676,679,681,683,686],{"class":38,"line":343},[36,666,596],{"class":114},[36,668,441],{"class":351},[36,670,356],{"class":355},[36,672,673],{"class":317},"Index",[36,675,268],{"class":56},[36,677,678],{"class":118},"\"orders\"",[36,680,337],{"class":56},[36,682,356],{"class":355},[36,684,685],{"class":317},"AddDocumentsAsync",[36,687,688],{"class":56},"(\n",[36,690,691,694,697,699,702,704,706,708,710,713,716,719,721],{"class":38,"line":400},[36,692,693],{"class":56},"            [",[36,695,696],{"class":351},"OrderSearchDocument",[36,698,356],{"class":355},[36,700,701],{"class":317},"From",[36,703,268],{"class":56},[36,705,352],{"class":351},[36,707,276],{"class":56},[36,709,334],{"class":351},[36,711,712],{"class":56},")],",[36,714,715],{"class":69}," cancellationToken",[36,717,718],{"class":56},":",[36,720,476],{"class":351},[36,722,397],{"class":56},[36,724,726],{"class":38,"line":725},20,[36,727,728],{"class":56},"    }\n",[36,730,732],{"class":38,"line":731},21,[36,733,173],{"class":56},[735,736,737,753],"pros-cons",{},[738,739,740],"template",{"v-slot:pros":32},[741,742,743,747,750],"ul",{},[744,745,746],"li",{},"No new infrastructure or moving parts",[744,748,749],{},"Easy to read and debug, the indexing sits right next to the write",[744,751,752],{},"The index is updated before the response returns",[738,754,755],{"v-slot:cons":32},[741,756,757,760,763,766],{},[744,758,759],{},"A dual write: the commit and the index can succeed or fail, so the index silently drifts",[744,761,762],{},"Every write path has to follow this pattern, including updates, deletes and admin scripts",[744,764,765],{},"Search latency and availability are now part of your request path",[744,767,768],{},"No way to rebuild or backfill the index without writing a separate tool",[404,770,772],{"id":771},"marten-session-listeners","Marten Session Listeners",[10,774,775,776,779,780,783],{},"Marten lets you hook into every session's unit of work with an ",[17,777,778],{},"IDocumentSessionListener",". ",[17,781,782],{},"AfterCommitAsync"," receives the change set, so indexing moves out of the handlers and into one place.",[26,785,788],{"className":28,"code":786,"filename":787,"language":31,"meta":32,"style":32},"public class OrderSearchListener(MeilisearchClient meili) : DocumentSessionListenerBase\n{\n    public override async Task AfterCommitAsync(\n        IDocumentSession session, IChangeSet commit, CancellationToken token)\n    {\n        var index = meili.Index(\"orders\");\n\n        var orders = commit.Inserted.Concat(commit.Updated).OfType\u003COrder>().ToList();\n        if (orders.Count > 0)\n        {\n            var customerIds = orders.Select(o => o.CustomerId).Distinct();\n            var customers = (await session.LoadManyAsync\u003CCustomer>(token, customerIds))\n                .ToDictionary(c => c.Id);\n\n            await index.AddDocumentsAsync(\n                orders.Select(o => OrderSearchDocument.From(o, customers.GetValueOrDefault(o.CustomerId))),\n                cancellationToken: token);\n        }\n\n        var deleted = commit.Deleted\n            .Where(d => d.DocumentType == typeof(Order) && d.Id is not null)\n            .Select(d => d.Id.ToString()!)\n            .ToList();\n        if (deleted.Count > 0)\n            await index.DeleteDocumentsAsync(deleted, token);\n    }\n}\n","OrderSearchListener.cs",[17,789,790,814,818,834,858,862,883,887,942,965,969,1010,1049,1073,1077,1090,1135,1146,1151,1155,1171,1228,1258,1267,1287,1309,1314],{"__ignoreMap":32},[36,791,792,794,796,799,801,804,806,808,811],{"class":38,"line":39},[36,793,43],{"class":42},[36,795,46],{"class":42},[36,797,798],{"class":49}," OrderSearchListener",[36,800,268],{"class":56},[36,802,803],{"class":49},"MeilisearchClient",[36,805,441],{"class":69},[36,807,337],{"class":56},[36,809,810],{"class":56}," :",[36,812,813],{"class":49}," DocumentSessionListenerBase\n",[36,815,816],{"class":38,"line":53},[36,817,57],{"class":56},[36,819,820,822,825,827,829,832],{"class":38,"line":60},[36,821,63],{"class":42},[36,823,824],{"class":42}," override",[36,826,454],{"class":42},[36,828,457],{"class":49},[36,830,831],{"class":317}," AfterCommitAsync",[36,833,688],{"class":56},[36,835,836,839,841,843,846,849,851,853,856],{"class":38,"line":90},[36,837,838],{"class":49},"        IDocumentSession",[36,840,433],{"class":69},[36,842,276],{"class":56},[36,844,845],{"class":49}," IChangeSet",[36,847,848],{"class":69}," commit",[36,850,276],{"class":56},[36,852,473],{"class":49},[36,854,855],{"class":69}," token",[36,857,299],{"class":56},[36,859,860],{"class":38,"line":125},[36,861,483],{"class":56},[36,863,864,866,869,871,873,875,877,879,881],{"class":38,"line":147},[36,865,488],{"class":42},[36,867,868],{"class":69}," index",[36,870,115],{"class":114},[36,872,441],{"class":351},[36,874,356],{"class":355},[36,876,673],{"class":317},[36,878,268],{"class":56},[36,880,678],{"class":118},[36,882,397],{"class":56},[36,884,885],{"class":38,"line":170},[36,886,180],{"emptyLinePlaceholder":179},[36,888,889,891,894,896,898,900,903,905,908,910,913,915,918,920,922,925,927,929,931,934,936,939],{"class":38,"line":176},[36,890,488],{"class":42},[36,892,893],{"class":69}," orders",[36,895,115],{"class":114},[36,897,848],{"class":351},[36,899,356],{"class":355},[36,901,902],{"class":351},"Inserted",[36,904,356],{"class":355},[36,906,907],{"class":317},"Concat",[36,909,268],{"class":56},[36,911,912],{"class":351},"commit",[36,914,356],{"class":355},[36,916,917],{"class":351},"Updated",[36,919,337],{"class":56},[36,921,356],{"class":355},[36,923,924],{"class":317},"OfType",[36,926,642],{"class":641},[36,928,19],{"class":49},[36,930,648],{"class":641},[36,932,933],{"class":56},"()",[36,935,356],{"class":355},[36,937,938],{"class":317},"ToList",[36,940,941],{"class":56},"();\n",[36,943,944,947,950,952,954,957,959,963],{"class":38,"line":183},[36,945,946],{"class":42},"        if",[36,948,949],{"class":56}," (",[36,951,23],{"class":351},[36,953,356],{"class":355},[36,955,956],{"class":351},"Count ",[36,958,648],{"class":114},[36,960,962],{"class":961},"s4M-N"," 0",[36,964,299],{"class":56},[36,966,967],{"class":38,"line":193},[36,968,502],{"class":56},[36,970,971,974,977,979,981,983,986,988,991,994,997,999,1001,1003,1005,1008],{"class":38,"line":198},[36,972,973],{"class":42},"            var",[36,975,976],{"class":69}," customerIds",[36,978,115],{"class":114},[36,980,893],{"class":351},[36,982,356],{"class":355},[36,984,985],{"class":317},"Select",[36,987,268],{"class":56},[36,989,990],{"class":69},"o",[36,992,993],{"class":114}," =>",[36,995,996],{"class":351}," o",[36,998,356],{"class":355},[36,1000,550],{"class":351},[36,1002,337],{"class":56},[36,1004,356],{"class":355},[36,1006,1007],{"class":317},"Distinct",[36,1009,941],{"class":56},[36,1011,1012,1014,1017,1019,1021,1024,1026,1028,1031,1033,1035,1037,1039,1042,1044,1046],{"class":38,"line":219},[36,1013,973],{"class":42},[36,1015,1016],{"class":69}," customers",[36,1018,115],{"class":114},[36,1020,949],{"class":56},[36,1022,1023],{"class":114},"await",[36,1025,433],{"class":351},[36,1027,356],{"class":355},[36,1029,1030],{"class":317},"LoadManyAsync",[36,1032,642],{"class":641},[36,1034,645],{"class":49},[36,1036,648],{"class":641},[36,1038,268],{"class":56},[36,1040,1041],{"class":351},"token",[36,1043,276],{"class":56},[36,1045,976],{"class":351},[36,1047,1048],{"class":56},"))\n",[36,1050,1051,1054,1057,1059,1062,1064,1067,1069,1071],{"class":38,"line":247},[36,1052,1053],{"class":355},"                .",[36,1055,1056],{"class":317},"ToDictionary",[36,1058,268],{"class":56},[36,1060,1061],{"class":69},"c",[36,1063,993],{"class":114},[36,1065,1066],{"class":351}," c",[36,1068,356],{"class":355},[36,1070,359],{"class":351},[36,1072,397],{"class":56},[36,1074,1075],{"class":38,"line":252},[36,1076,180],{"emptyLinePlaceholder":179},[36,1078,1079,1082,1084,1086,1088],{"class":38,"line":257},[36,1080,1081],{"class":114},"            await",[36,1083,868],{"class":351},[36,1085,356],{"class":355},[36,1087,685],{"class":317},[36,1089,688],{"class":56},[36,1091,1092,1095,1097,1099,1101,1103,1105,1107,1109,1111,1113,1115,1117,1119,1121,1124,1126,1128,1130,1132],{"class":38,"line":302},[36,1093,1094],{"class":351},"                orders",[36,1096,356],{"class":355},[36,1098,985],{"class":317},[36,1100,268],{"class":56},[36,1102,990],{"class":69},[36,1104,993],{"class":114},[36,1106,265],{"class":351},[36,1108,356],{"class":355},[36,1110,701],{"class":317},[36,1112,268],{"class":56},[36,1114,990],{"class":351},[36,1116,276],{"class":56},[36,1118,1016],{"class":351},[36,1120,356],{"class":355},[36,1122,1123],{"class":317},"GetValueOrDefault",[36,1125,268],{"class":56},[36,1127,990],{"class":351},[36,1129,356],{"class":355},[36,1131,550],{"class":351},[36,1133,1134],{"class":56},"))),\n",[36,1136,1137,1140,1142,1144],{"class":38,"line":307},[36,1138,1139],{"class":69},"                cancellationToken",[36,1141,718],{"class":56},[36,1143,855],{"class":351},[36,1145,397],{"class":56},[36,1147,1148],{"class":38,"line":343},[36,1149,1150],{"class":56},"        }\n",[36,1152,1153],{"class":38,"line":400},[36,1154,180],{"emptyLinePlaceholder":179},[36,1156,1157,1159,1162,1164,1166,1168],{"class":38,"line":725},[36,1158,488],{"class":42},[36,1160,1161],{"class":69}," deleted",[36,1163,115],{"class":114},[36,1165,848],{"class":351},[36,1167,356],{"class":355},[36,1169,1170],{"class":351},"Deleted\n",[36,1172,1173,1176,1179,1181,1184,1186,1189,1191,1194,1197,1200,1202,1204,1206,1209,1211,1213,1216,1219,1222,1226],{"class":38,"line":731},[36,1174,1175],{"class":355},"            .",[36,1177,1178],{"class":317},"Where",[36,1180,268],{"class":56},[36,1182,1183],{"class":69},"d",[36,1185,993],{"class":114},[36,1187,1188],{"class":351}," d",[36,1190,356],{"class":355},[36,1192,1193],{"class":351},"DocumentType ",[36,1195,1196],{"class":114},"==",[36,1198,1199],{"class":114}," typeof",[36,1201,268],{"class":56},[36,1203,19],{"class":49},[36,1205,337],{"class":56},[36,1207,1208],{"class":114}," &&",[36,1210,1188],{"class":351},[36,1212,356],{"class":355},[36,1214,1215],{"class":351},"Id ",[36,1217,1218],{"class":114},"is",[36,1220,1221],{"class":114}," not",[36,1223,1225],{"class":1224},"s_jeQ"," null",[36,1227,299],{"class":56},[36,1229,1231,1233,1235,1237,1239,1241,1243,1245,1247,1249,1251,1253,1256],{"class":38,"line":1230},22,[36,1232,1175],{"class":355},[36,1234,985],{"class":317},[36,1236,268],{"class":56},[36,1238,1183],{"class":69},[36,1240,993],{"class":114},[36,1242,1188],{"class":351},[36,1244,356],{"class":355},[36,1246,359],{"class":351},[36,1248,356],{"class":355},[36,1250,364],{"class":317},[36,1252,933],{"class":56},[36,1254,1255],{"class":114},"!",[36,1257,299],{"class":56},[36,1259,1261,1263,1265],{"class":38,"line":1260},23,[36,1262,1175],{"class":355},[36,1264,938],{"class":317},[36,1266,941],{"class":56},[36,1268,1270,1272,1274,1277,1279,1281,1283,1285],{"class":38,"line":1269},24,[36,1271,946],{"class":42},[36,1273,949],{"class":56},[36,1275,1276],{"class":351},"deleted",[36,1278,356],{"class":355},[36,1280,956],{"class":351},[36,1282,648],{"class":114},[36,1284,962],{"class":961},[36,1286,299],{"class":56},[36,1288,1290,1292,1294,1296,1299,1301,1303,1305,1307],{"class":38,"line":1289},25,[36,1291,1081],{"class":114},[36,1293,868],{"class":351},[36,1295,356],{"class":355},[36,1297,1298],{"class":317},"DeleteDocumentsAsync",[36,1300,268],{"class":56},[36,1302,1276],{"class":351},[36,1304,276],{"class":56},[36,1306,855],{"class":351},[36,1308,397],{"class":56},[36,1310,1312],{"class":38,"line":1311},26,[36,1313,728],{"class":56},[36,1315,1317],{"class":38,"line":1316},27,[36,1318,173],{"class":56},[10,1320,1321],{},"The listener is registered on the store options, so it's built once alongside the store:",[26,1323,1326],{"className":28,"code":1324,"filename":1325,"language":31,"meta":32,"style":32},"builder.Services.AddSingleton(new MeilisearchClient(meiliUrl, meiliKey));\nbuilder.Services.AddMarten(sp =>\n{\n    var options = new StoreOptions();\n    options.Connection(connectionString);\n    options.Listeners.Add(new OrderSearchListener(sp.GetRequiredService\u003CMeilisearchClient>()));\n    return options;\n});\n","Program.cs",[17,1327,1328,1363,1383,1387,1404,1421,1459,1468],{"__ignoreMap":32},[36,1329,1330,1333,1335,1338,1340,1343,1345,1348,1350,1352,1355,1357,1360],{"class":38,"line":39},[36,1331,1332],{"class":351},"builder",[36,1334,356],{"class":355},[36,1336,1337],{"class":351},"Services",[36,1339,356],{"class":355},[36,1341,1342],{"class":317},"AddSingleton",[36,1344,268],{"class":56},[36,1346,1347],{"class":114},"new",[36,1349,438],{"class":49},[36,1351,268],{"class":56},[36,1353,1354],{"class":351},"meiliUrl",[36,1356,276],{"class":56},[36,1358,1359],{"class":351}," meiliKey",[36,1361,1362],{"class":56},"));\n",[36,1364,1365,1367,1369,1371,1373,1376,1378,1381],{"class":38,"line":53},[36,1366,1332],{"class":351},[36,1368,356],{"class":355},[36,1370,1337],{"class":351},[36,1372,356],{"class":355},[36,1374,1375],{"class":317},"AddMarten",[36,1377,268],{"class":56},[36,1379,1380],{"class":69},"sp",[36,1382,340],{"class":114},[36,1384,1385],{"class":38,"line":60},[36,1386,57],{"class":56},[36,1388,1389,1392,1395,1397,1399,1402],{"class":38,"line":90},[36,1390,1391],{"class":42},"    var",[36,1393,1394],{"class":69}," options",[36,1396,115],{"class":114},[36,1398,495],{"class":114},[36,1400,1401],{"class":49}," StoreOptions",[36,1403,941],{"class":56},[36,1405,1406,1409,1411,1414,1416,1419],{"class":38,"line":125},[36,1407,1408],{"class":351},"    options",[36,1410,356],{"class":355},[36,1412,1413],{"class":317},"Connection",[36,1415,268],{"class":56},[36,1417,1418],{"class":351},"connectionString",[36,1420,397],{"class":56},[36,1422,1423,1425,1427,1430,1432,1435,1437,1439,1441,1443,1445,1447,1450,1452,1454,1456],{"class":38,"line":147},[36,1424,1408],{"class":351},[36,1426,356],{"class":355},[36,1428,1429],{"class":351},"Listeners",[36,1431,356],{"class":355},[36,1433,1434],{"class":317},"Add",[36,1436,268],{"class":56},[36,1438,1347],{"class":114},[36,1440,798],{"class":49},[36,1442,268],{"class":56},[36,1444,1380],{"class":351},[36,1446,356],{"class":355},[36,1448,1449],{"class":317},"GetRequiredService",[36,1451,642],{"class":641},[36,1453,803],{"class":49},[36,1455,648],{"class":641},[36,1457,1458],{"class":56},"()));\n",[36,1460,1461,1464,1466],{"class":38,"line":170},[36,1462,1463],{"class":42},"    return",[36,1465,1394],{"class":351},[36,1467,122],{"class":56},[36,1469,1470],{"class":38,"line":176},[36,1471,1472],{"class":56},"});\n",[735,1474,1475,1488],{},[738,1476,1477],{"v-slot:pros":32},[741,1478,1479,1482,1485],{},[744,1480,1481],{},"One place to maintain, every session is covered automatically",[744,1483,1484],{},"Handlers go back to only caring about the domain",[744,1486,1487],{},"Still no new infrastructure",[738,1489,1490],{"v-slot:cons":32},[741,1491,1492,1495,1498,1504],{},[744,1493,1494],{},"Still a dual write: the listener runs after the commit, so a failed index call is lost",[744,1496,1497],{},"Only sees work done through a session's unit of work, so bulk inserts, patches and raw SQL slip past",[744,1499,1500,1501,1503],{},"Runs inline with ",[17,1502,603],{},", adding latency to every write",[744,1505,1506],{},"Still no backfill story",[404,1508,1510],{"id":1509},"event-sourcing-with-subscriptions","Event Sourcing with Subscriptions",[10,1512,1513],{},"If the data is event sourced, Marten's async daemon can feed events to a subscription. Events and the subscription's progress both live in Postgres, so the daemon can retry failed batches and resumes from its last checkpoint.",[26,1515,1518],{"className":28,"code":1516,"filename":1517,"language":31,"meta":32,"style":32},"public record OrderPlaced(Guid OrderId, string Number, Guid CustomerId, decimal Total);\npublic record OrderCancelled(Guid OrderId);\n\npublic class OrderSearchSubscription : SubscriptionBase\n{\n    private readonly MeilisearchClient _meili;\n\n    public OrderSearchSubscription(MeilisearchClient meili)\n    {\n        _meili = meili;\n        Name = \"order-search\";\n        IncludeType\u003COrderPlaced>();\n        IncludeType\u003COrderCancelled>();\n    }\n\n    public override async Task\u003CIChangeListener> ProcessEventsAsync(\n        EventRange page, ISubscriptionController controller,\n        IDocumentOperations operations, CancellationToken cancellationToken)\n    {\n        var index = _meili.Index(\"orders\");\n\n        var placed = page.Events.Select(e => e.Data).OfType\u003COrderPlaced>().ToList();\n        if (placed.Count > 0)\n        {\n            var customerIds = placed.Select(p => p.CustomerId).Distinct();\n            var customers = (await operations.LoadManyAsync\u003CCustomer>(cancellationToken, customerIds))\n                .ToDictionary(c => c.Id);\n\n            await index.AddDocumentsAsync(\n                placed.Select(p => new OrderSearchDocument(\n                    p.OrderId.ToString(), p.Number, p.Total,\n                    customers.GetValueOrDefault(p.CustomerId)?.Name)),\n                cancellationToken: cancellationToken);\n        }\n\n        var cancelled = page.Events.Select(e => e.Data).OfType\u003COrderCancelled>()\n            .Select(c => c.OrderId.ToString())\n            .ToList();\n        if (cancelled.Count > 0)\n            await index.DeleteDocumentsAsync(cancelled, cancellationToken);\n\n        return NullChangeListener.Instance;\n    }\n}\n","OrderSearchSubscription.cs",[17,1519,1520,1557,1574,1578,1592,1596,1611,1615,1629,1633,1644,1656,1670,1683,1687,1691,1713,1731,1747,1751,1771,1775,1830,1849,1853,1888,1923,1943,1948,1961,1983,2016,2045,2056,2061,2066,2113,2139,2148,2168,2189,2194,2210,2215],{"__ignoreMap":32},[36,1521,1522,1524,1526,1529,1531,1534,1537,1539,1541,1543,1545,1547,1549,1551,1553,1555],{"class":38,"line":39},[36,1523,43],{"class":42},[36,1525,262],{"class":42},[36,1527,1528],{"class":49}," OrderPlaced",[36,1530,268],{"class":56},[36,1532,1533],{"class":49},"Guid",[36,1535,1536],{"class":69}," OrderId",[36,1538,276],{"class":56},[36,1540,95],{"class":42},[36,1542,98],{"class":69},[36,1544,276],{"class":56},[36,1546,66],{"class":49},[36,1548,132],{"class":69},[36,1550,276],{"class":56},[36,1552,152],{"class":42},[36,1554,155],{"class":69},[36,1556,397],{"class":56},[36,1558,1559,1561,1563,1566,1568,1570,1572],{"class":38,"line":53},[36,1560,43],{"class":42},[36,1562,262],{"class":42},[36,1564,1565],{"class":49}," OrderCancelled",[36,1567,268],{"class":56},[36,1569,1533],{"class":49},[36,1571,1536],{"class":69},[36,1573,397],{"class":56},[36,1575,1576],{"class":38,"line":60},[36,1577,180],{"emptyLinePlaceholder":179},[36,1579,1580,1582,1584,1587,1589],{"class":38,"line":90},[36,1581,43],{"class":42},[36,1583,46],{"class":42},[36,1585,1586],{"class":49}," OrderSearchSubscription",[36,1588,810],{"class":56},[36,1590,1591],{"class":49}," SubscriptionBase\n",[36,1593,1594],{"class":38,"line":125},[36,1595,57],{"class":56},[36,1597,1598,1601,1604,1606,1609],{"class":38,"line":147},[36,1599,1600],{"class":42},"    private",[36,1602,1603],{"class":42}," readonly",[36,1605,438],{"class":49},[36,1607,1608],{"class":69}," _meili",[36,1610,122],{"class":56},[36,1612,1613],{"class":38,"line":170},[36,1614,180],{"emptyLinePlaceholder":179},[36,1616,1617,1619,1621,1623,1625,1627],{"class":38,"line":176},[36,1618,63],{"class":42},[36,1620,1586],{"class":317},[36,1622,268],{"class":56},[36,1624,803],{"class":49},[36,1626,441],{"class":69},[36,1628,299],{"class":56},[36,1630,1631],{"class":38,"line":183},[36,1632,483],{"class":56},[36,1634,1635,1638,1640,1642],{"class":38,"line":193},[36,1636,1637],{"class":351},"        _meili ",[36,1639,510],{"class":114},[36,1641,441],{"class":351},[36,1643,122],{"class":56},[36,1645,1646,1649,1651,1654],{"class":38,"line":198},[36,1647,1648],{"class":351},"        Name ",[36,1650,510],{"class":114},[36,1652,1653],{"class":118}," \"order-search\"",[36,1655,122],{"class":56},[36,1657,1658,1661,1663,1666,1668],{"class":38,"line":219},[36,1659,1660],{"class":317},"        IncludeType",[36,1662,642],{"class":641},[36,1664,1665],{"class":49},"OrderPlaced",[36,1667,648],{"class":641},[36,1669,941],{"class":56},[36,1671,1672,1674,1676,1679,1681],{"class":38,"line":247},[36,1673,1660],{"class":317},[36,1675,642],{"class":641},[36,1677,1678],{"class":49},"OrderCancelled",[36,1680,648],{"class":641},[36,1682,941],{"class":56},[36,1684,1685],{"class":38,"line":252},[36,1686,728],{"class":56},[36,1688,1689],{"class":38,"line":257},[36,1690,180],{"emptyLinePlaceholder":179},[36,1692,1693,1695,1697,1699,1701,1703,1706,1708,1711],{"class":38,"line":302},[36,1694,63],{"class":42},[36,1696,824],{"class":42},[36,1698,454],{"class":42},[36,1700,457],{"class":49},[36,1702,642],{"class":641},[36,1704,1705],{"class":49},"IChangeListener",[36,1707,648],{"class":641},[36,1709,1710],{"class":317}," ProcessEventsAsync",[36,1712,688],{"class":56},[36,1714,1715,1718,1721,1723,1726,1729],{"class":38,"line":307},[36,1716,1717],{"class":49},"        EventRange",[36,1719,1720],{"class":69}," page",[36,1722,276],{"class":56},[36,1724,1725],{"class":49}," ISubscriptionController",[36,1727,1728],{"class":69}," controller",[36,1730,536],{"class":56},[36,1732,1733,1736,1739,1741,1743,1745],{"class":38,"line":343},[36,1734,1735],{"class":49},"        IDocumentOperations",[36,1737,1738],{"class":69}," operations",[36,1740,276],{"class":56},[36,1742,473],{"class":49},[36,1744,715],{"class":69},[36,1746,299],{"class":56},[36,1748,1749],{"class":38,"line":400},[36,1750,483],{"class":56},[36,1752,1753,1755,1757,1759,1761,1763,1765,1767,1769],{"class":38,"line":725},[36,1754,488],{"class":42},[36,1756,868],{"class":69},[36,1758,115],{"class":114},[36,1760,1608],{"class":351},[36,1762,356],{"class":355},[36,1764,673],{"class":317},[36,1766,268],{"class":56},[36,1768,678],{"class":118},[36,1770,397],{"class":56},[36,1772,1773],{"class":38,"line":731},[36,1774,180],{"emptyLinePlaceholder":179},[36,1776,1777,1779,1782,1784,1786,1788,1791,1793,1795,1797,1800,1802,1805,1807,1810,1812,1814,1816,1818,1820,1822,1824,1826,1828],{"class":38,"line":1230},[36,1778,488],{"class":42},[36,1780,1781],{"class":69}," placed",[36,1783,115],{"class":114},[36,1785,1720],{"class":351},[36,1787,356],{"class":355},[36,1789,1790],{"class":351},"Events",[36,1792,356],{"class":355},[36,1794,985],{"class":317},[36,1796,268],{"class":56},[36,1798,1799],{"class":69},"e",[36,1801,993],{"class":114},[36,1803,1804],{"class":351}," e",[36,1806,356],{"class":355},[36,1808,1809],{"class":351},"Data",[36,1811,337],{"class":56},[36,1813,356],{"class":355},[36,1815,924],{"class":317},[36,1817,642],{"class":641},[36,1819,1665],{"class":49},[36,1821,648],{"class":641},[36,1823,933],{"class":56},[36,1825,356],{"class":355},[36,1827,938],{"class":317},[36,1829,941],{"class":56},[36,1831,1832,1834,1836,1839,1841,1843,1845,1847],{"class":38,"line":1260},[36,1833,946],{"class":42},[36,1835,949],{"class":56},[36,1837,1838],{"class":351},"placed",[36,1840,356],{"class":355},[36,1842,956],{"class":351},[36,1844,648],{"class":114},[36,1846,962],{"class":961},[36,1848,299],{"class":56},[36,1850,1851],{"class":38,"line":1269},[36,1852,502],{"class":56},[36,1854,1855,1857,1859,1861,1863,1865,1867,1869,1871,1873,1876,1878,1880,1882,1884,1886],{"class":38,"line":1289},[36,1856,973],{"class":42},[36,1858,976],{"class":69},[36,1860,115],{"class":114},[36,1862,1781],{"class":351},[36,1864,356],{"class":355},[36,1866,985],{"class":317},[36,1868,268],{"class":56},[36,1870,10],{"class":69},[36,1872,993],{"class":114},[36,1874,1875],{"class":351}," p",[36,1877,356],{"class":355},[36,1879,550],{"class":351},[36,1881,337],{"class":56},[36,1883,356],{"class":355},[36,1885,1007],{"class":317},[36,1887,941],{"class":56},[36,1889,1890,1892,1894,1896,1898,1900,1902,1904,1906,1908,1910,1912,1914,1917,1919,1921],{"class":38,"line":1311},[36,1891,973],{"class":42},[36,1893,1016],{"class":69},[36,1895,115],{"class":114},[36,1897,949],{"class":56},[36,1899,1023],{"class":114},[36,1901,1738],{"class":351},[36,1903,356],{"class":355},[36,1905,1030],{"class":317},[36,1907,642],{"class":641},[36,1909,645],{"class":49},[36,1911,648],{"class":641},[36,1913,268],{"class":56},[36,1915,1916],{"class":351},"cancellationToken",[36,1918,276],{"class":56},[36,1920,976],{"class":351},[36,1922,1048],{"class":56},[36,1924,1925,1927,1929,1931,1933,1935,1937,1939,1941],{"class":38,"line":1316},[36,1926,1053],{"class":355},[36,1928,1056],{"class":317},[36,1930,268],{"class":56},[36,1932,1061],{"class":69},[36,1934,993],{"class":114},[36,1936,1066],{"class":351},[36,1938,356],{"class":355},[36,1940,359],{"class":351},[36,1942,397],{"class":56},[36,1944,1946],{"class":38,"line":1945},28,[36,1947,180],{"emptyLinePlaceholder":179},[36,1949,1951,1953,1955,1957,1959],{"class":38,"line":1950},29,[36,1952,1081],{"class":114},[36,1954,868],{"class":351},[36,1956,356],{"class":355},[36,1958,685],{"class":317},[36,1960,688],{"class":56},[36,1962,1964,1967,1969,1971,1973,1975,1977,1979,1981],{"class":38,"line":1963},30,[36,1965,1966],{"class":351},"                placed",[36,1968,356],{"class":355},[36,1970,985],{"class":317},[36,1972,268],{"class":56},[36,1974,10],{"class":69},[36,1976,993],{"class":114},[36,1978,495],{"class":114},[36,1980,265],{"class":49},[36,1982,688],{"class":56},[36,1984,1986,1989,1991,1994,1996,1998,2000,2002,2004,2006,2008,2010,2012,2014],{"class":38,"line":1985},31,[36,1987,1988],{"class":351},"                    p",[36,1990,356],{"class":355},[36,1992,1993],{"class":351},"OrderId",[36,1995,356],{"class":355},[36,1997,364],{"class":317},[36,1999,367],{"class":56},[36,2001,1875],{"class":351},[36,2003,356],{"class":355},[36,2005,374],{"class":351},[36,2007,276],{"class":56},[36,2009,1875],{"class":351},[36,2011,356],{"class":355},[36,2013,383],{"class":351},[36,2015,536],{"class":56},[36,2017,2019,2022,2024,2026,2028,2030,2032,2034,2036,2038,2040,2042],{"class":38,"line":2018},32,[36,2020,2021],{"class":351},"                    customers",[36,2023,356],{"class":355},[36,2025,1123],{"class":317},[36,2027,268],{"class":56},[36,2029,10],{"class":351},[36,2031,356],{"class":355},[36,2033,550],{"class":351},[36,2035,337],{"class":56},[36,2037,293],{"class":114},[36,2039,356],{"class":355},[36,2041,394],{"class":351},[36,2043,2044],{"class":56},")),\n",[36,2046,2048,2050,2052,2054],{"class":38,"line":2047},33,[36,2049,1139],{"class":69},[36,2051,718],{"class":56},[36,2053,715],{"class":351},[36,2055,397],{"class":56},[36,2057,2059],{"class":38,"line":2058},34,[36,2060,1150],{"class":56},[36,2062,2064],{"class":38,"line":2063},35,[36,2065,180],{"emptyLinePlaceholder":179},[36,2067,2069,2071,2074,2076,2078,2080,2082,2084,2086,2088,2090,2092,2094,2096,2098,2100,2102,2104,2106,2108,2110],{"class":38,"line":2068},36,[36,2070,488],{"class":42},[36,2072,2073],{"class":69}," cancelled",[36,2075,115],{"class":114},[36,2077,1720],{"class":351},[36,2079,356],{"class":355},[36,2081,1790],{"class":351},[36,2083,356],{"class":355},[36,2085,985],{"class":317},[36,2087,268],{"class":56},[36,2089,1799],{"class":69},[36,2091,993],{"class":114},[36,2093,1804],{"class":351},[36,2095,356],{"class":355},[36,2097,1809],{"class":351},[36,2099,337],{"class":56},[36,2101,356],{"class":355},[36,2103,924],{"class":317},[36,2105,642],{"class":641},[36,2107,1678],{"class":49},[36,2109,648],{"class":641},[36,2111,2112],{"class":56},"()\n",[36,2114,2116,2118,2120,2122,2124,2126,2128,2130,2132,2134,2136],{"class":38,"line":2115},37,[36,2117,1175],{"class":355},[36,2119,985],{"class":317},[36,2121,268],{"class":56},[36,2123,1061],{"class":69},[36,2125,993],{"class":114},[36,2127,1066],{"class":351},[36,2129,356],{"class":355},[36,2131,1993],{"class":351},[36,2133,356],{"class":355},[36,2135,364],{"class":317},[36,2137,2138],{"class":56},"())\n",[36,2140,2142,2144,2146],{"class":38,"line":2141},38,[36,2143,1175],{"class":355},[36,2145,938],{"class":317},[36,2147,941],{"class":56},[36,2149,2151,2153,2155,2158,2160,2162,2164,2166],{"class":38,"line":2150},39,[36,2152,946],{"class":42},[36,2154,949],{"class":56},[36,2156,2157],{"class":351},"cancelled",[36,2159,356],{"class":355},[36,2161,956],{"class":351},[36,2163,648],{"class":114},[36,2165,962],{"class":961},[36,2167,299],{"class":56},[36,2169,2171,2173,2175,2177,2179,2181,2183,2185,2187],{"class":38,"line":2170},40,[36,2172,1081],{"class":114},[36,2174,868],{"class":351},[36,2176,356],{"class":355},[36,2178,1298],{"class":317},[36,2180,268],{"class":56},[36,2182,2157],{"class":351},[36,2184,276],{"class":56},[36,2186,715],{"class":351},[36,2188,397],{"class":56},[36,2190,2192],{"class":38,"line":2191},41,[36,2193,180],{"emptyLinePlaceholder":179},[36,2195,2197,2200,2203,2205,2208],{"class":38,"line":2196},42,[36,2198,2199],{"class":42},"        return",[36,2201,2202],{"class":351}," NullChangeListener",[36,2204,356],{"class":355},[36,2206,2207],{"class":351},"Instance",[36,2209,122],{"class":56},[36,2211,2213],{"class":38,"line":2212},43,[36,2214,728],{"class":56},[36,2216,2218],{"class":38,"line":2217},44,[36,2219,173],{"class":56},[26,2221,2223],{"className":28,"code":2222,"filename":1325,"language":31,"meta":32,"style":32},"builder.Services.AddSingleton(new MeilisearchClient(meiliUrl, meiliKey));\nbuilder.Services.AddMarten(options => options.Connection(connectionString))\n    .AddSubscriptionWithServices\u003COrderSearchSubscription>(ServiceLifetime.Singleton)\n    .AddAsyncDaemon(DaemonMode.HotCold);\n",[17,2224,2225,2253,2284,2311],{"__ignoreMap":32},[36,2226,2227,2229,2231,2233,2235,2237,2239,2241,2243,2245,2247,2249,2251],{"class":38,"line":39},[36,2228,1332],{"class":351},[36,2230,356],{"class":355},[36,2232,1337],{"class":351},[36,2234,356],{"class":355},[36,2236,1342],{"class":317},[36,2238,268],{"class":56},[36,2240,1347],{"class":114},[36,2242,438],{"class":49},[36,2244,268],{"class":56},[36,2246,1354],{"class":351},[36,2248,276],{"class":56},[36,2250,1359],{"class":351},[36,2252,1362],{"class":56},[36,2254,2255,2257,2259,2261,2263,2265,2267,2270,2272,2274,2276,2278,2280,2282],{"class":38,"line":53},[36,2256,1332],{"class":351},[36,2258,356],{"class":355},[36,2260,1337],{"class":351},[36,2262,356],{"class":355},[36,2264,1375],{"class":317},[36,2266,268],{"class":56},[36,2268,2269],{"class":69},"options",[36,2271,993],{"class":114},[36,2273,1394],{"class":351},[36,2275,356],{"class":355},[36,2277,1413],{"class":317},[36,2279,268],{"class":56},[36,2281,1418],{"class":351},[36,2283,1048],{"class":56},[36,2285,2286,2289,2292,2294,2297,2299,2301,2304,2306,2309],{"class":38,"line":60},[36,2287,2288],{"class":355},"    .",[36,2290,2291],{"class":317},"AddSubscriptionWithServices",[36,2293,642],{"class":641},[36,2295,2296],{"class":49},"OrderSearchSubscription",[36,2298,648],{"class":641},[36,2300,268],{"class":56},[36,2302,2303],{"class":351},"ServiceLifetime",[36,2305,356],{"class":355},[36,2307,2308],{"class":351},"Singleton",[36,2310,299],{"class":56},[36,2312,2313,2315,2318,2320,2323,2325,2328],{"class":38,"line":90},[36,2314,2288],{"class":355},[36,2316,2317],{"class":317},"AddAsyncDaemon",[36,2319,268],{"class":56},[36,2321,2322],{"class":351},"DaemonMode",[36,2324,356],{"class":355},[36,2326,2327],{"class":351},"HotCold",[36,2329,397],{"class":56},[735,2331,2332,2348],{},[738,2333,2334],{"v-slot:pros":32},[741,2335,2336,2339,2342,2345],{},[744,2337,2338],{},"No dual write: events are the source of truth and delivery is at-least-once",[744,2340,2341],{},"Rewinding the subscription replays history, which doubles as a backfill",[744,2343,2344],{},"Indexing happens in batches, off the request path",[744,2346,2347],{},"Events carry intent, so the same stream can drive notifications and integrations too",[738,2349,2350],{"v-slot:cons":32},[741,2351,2352,2355,2358],{},[744,2353,2354],{},"Only an option if the data is already event sourced",[744,2356,2357],{},"Every event that affects the index needs to be accounted for",[744,2359,2360],{},"Enrichment across stream boundries might be complex or non-performant",[404,2362,2364],{"id":2363},"bonus-wolverine","Bonus: Wolverine",[10,2366,2367,2368,2375],{},"If you're already deep in the Critter stack, you might be using Wolverine which allows for indexing to be driven by handler events. ",[2369,2370,2374],"a",{"href":2371,"rel":2372},"https:\u002F\u002Fwolverinefx.net",[2373],"nofollow","Wolverine","'s Marten integration includes a transactional outbox to take care of persistence. A handler returns a message, and Wolverine stores it in the same Postgres transaction as the document.",[26,2377,2379],{"className":28,"code":2378,"filename":414,"language":31,"meta":32,"style":32},"public record SyncOrderToSearch(Guid OrderId);\n\npublic static class PlaceOrderHandler\n{\n    \u002F\u002F Returned messages go into the outbox, in the same transaction as the order\n    public static SyncOrderToSearch Handle(PlaceOrder command, IDocumentSession session)\n    {\n        var order = new Order\n        {\n            Id = Guid.CreateVersion7(),\n            Number = command.Number,\n            CustomerId = command.CustomerId,\n            Total = command.Total\n        };\n\n        session.Store(order);\n        return new SyncOrderToSearch(order.Id);\n    }\n}\n",[17,2380,2381,2398,2402,2413,2417,2422,2447,2451,2463,2467,2481,2495,2509,2521,2525,2529,2543,2561,2565],{"__ignoreMap":32},[36,2382,2383,2385,2387,2390,2392,2394,2396],{"class":38,"line":39},[36,2384,43],{"class":42},[36,2386,262],{"class":42},[36,2388,2389],{"class":49}," SyncOrderToSearch",[36,2391,268],{"class":56},[36,2393,1533],{"class":49},[36,2395,1536],{"class":69},[36,2397,397],{"class":56},[36,2399,2400],{"class":38,"line":53},[36,2401,180],{"emptyLinePlaceholder":179},[36,2403,2404,2406,2408,2410],{"class":38,"line":60},[36,2405,43],{"class":42},[36,2407,312],{"class":42},[36,2409,46],{"class":42},[36,2411,2412],{"class":49}," PlaceOrderHandler\n",[36,2414,2415],{"class":38,"line":90},[36,2416,57],{"class":56},[36,2418,2419],{"class":38,"line":125},[36,2420,2421],{"class":619},"    \u002F\u002F Returned messages go into the outbox, in the same transaction as the order\n",[36,2423,2424,2426,2428,2430,2432,2434,2436,2438,2440,2443,2445],{"class":38,"line":147},[36,2425,63],{"class":42},[36,2427,312],{"class":42},[36,2429,2389],{"class":49},[36,2431,460],{"class":317},[36,2433,268],{"class":56},[36,2435,465],{"class":49},[36,2437,468],{"class":69},[36,2439,276],{"class":56},[36,2441,2442],{"class":49}," IDocumentSession",[36,2444,433],{"class":69},[36,2446,299],{"class":56},[36,2448,2449],{"class":38,"line":170},[36,2450,483],{"class":56},[36,2452,2453,2455,2457,2459,2461],{"class":38,"line":176},[36,2454,488],{"class":42},[36,2456,325],{"class":69},[36,2458,115],{"class":114},[36,2460,495],{"class":114},[36,2462,50],{"class":49},[36,2464,2465],{"class":38,"line":183},[36,2466,502],{"class":56},[36,2468,2469,2471,2473,2475,2477,2479],{"class":38,"line":193},[36,2470,507],{"class":351},[36,2472,510],{"class":114},[36,2474,66],{"class":351},[36,2476,356],{"class":355},[36,2478,517],{"class":317},[36,2480,520],{"class":56},[36,2482,2483,2485,2487,2489,2491,2493],{"class":38,"line":198},[36,2484,525],{"class":351},[36,2486,510],{"class":114},[36,2488,468],{"class":351},[36,2490,356],{"class":355},[36,2492,374],{"class":351},[36,2494,536],{"class":56},[36,2496,2497,2499,2501,2503,2505,2507],{"class":38,"line":219},[36,2498,541],{"class":351},[36,2500,510],{"class":114},[36,2502,468],{"class":351},[36,2504,356],{"class":355},[36,2506,550],{"class":351},[36,2508,536],{"class":56},[36,2510,2511,2513,2515,2517,2519],{"class":38,"line":247},[36,2512,557],{"class":351},[36,2514,510],{"class":114},[36,2516,468],{"class":351},[36,2518,356],{"class":355},[36,2520,566],{"class":351},[36,2522,2523],{"class":38,"line":252},[36,2524,571],{"class":56},[36,2526,2527],{"class":38,"line":257},[36,2528,180],{"emptyLinePlaceholder":179},[36,2530,2531,2533,2535,2537,2539,2541],{"class":38,"line":302},[36,2532,580],{"class":351},[36,2534,356],{"class":355},[36,2536,585],{"class":317},[36,2538,268],{"class":56},[36,2540,352],{"class":351},[36,2542,397],{"class":56},[36,2544,2545,2547,2549,2551,2553,2555,2557,2559],{"class":38,"line":307},[36,2546,2199],{"class":42},[36,2548,495],{"class":114},[36,2550,2389],{"class":49},[36,2552,268],{"class":56},[36,2554,352],{"class":351},[36,2556,356],{"class":355},[36,2558,359],{"class":351},[36,2560,397],{"class":56},[36,2562,2563],{"class":38,"line":343},[36,2564,728],{"class":56},[36,2566,2567],{"class":38,"line":400},[36,2568,173],{"class":56},[26,2570,2573],{"className":28,"code":2571,"filename":2572,"language":31,"meta":32,"style":32},"public static class SyncOrderToSearchHandler\n{\n    public static async Task Handle(\n        SyncOrderToSearch message, IQuerySession session, MeilisearchClient meili, CancellationToken ct)\n    {\n        var index = meili.Index(\"orders\");\n\n        var order = await session.LoadAsync\u003COrder>(message.OrderId, ct);\n        if (order is null)\n        {\n            await index.DeleteOneDocumentAsync(message.OrderId.ToString(), ct);\n            return;\n        }\n\n        var customer = await session.LoadAsync\u003CCustomer>(order.CustomerId, ct);\n        await index.AddDocumentsAsync([OrderSearchDocument.From(order, customer)], cancellationToken: ct);\n    }\n}\n","SyncOrderToSearchHandler.cs",[17,2574,2575,2586,2590,2604,2633,2637,2657,2661,2698,2713,2717,2746,2753,2757,2761,2797,2834,2838],{"__ignoreMap":32},[36,2576,2577,2579,2581,2583],{"class":38,"line":39},[36,2578,43],{"class":42},[36,2580,312],{"class":42},[36,2582,46],{"class":42},[36,2584,2585],{"class":49}," SyncOrderToSearchHandler\n",[36,2587,2588],{"class":38,"line":53},[36,2589,57],{"class":56},[36,2591,2592,2594,2596,2598,2600,2602],{"class":38,"line":60},[36,2593,63],{"class":42},[36,2595,312],{"class":42},[36,2597,454],{"class":42},[36,2599,457],{"class":49},[36,2601,460],{"class":317},[36,2603,688],{"class":56},[36,2605,2606,2609,2612,2614,2617,2619,2621,2623,2625,2627,2629,2631],{"class":38,"line":90},[36,2607,2608],{"class":49},"        SyncOrderToSearch",[36,2610,2611],{"class":69}," message",[36,2613,276],{"class":56},[36,2615,2616],{"class":49}," IQuerySession",[36,2618,433],{"class":69},[36,2620,276],{"class":56},[36,2622,438],{"class":49},[36,2624,441],{"class":69},[36,2626,276],{"class":56},[36,2628,473],{"class":49},[36,2630,476],{"class":69},[36,2632,299],{"class":56},[36,2634,2635],{"class":38,"line":125},[36,2636,483],{"class":56},[36,2638,2639,2641,2643,2645,2647,2649,2651,2653,2655],{"class":38,"line":147},[36,2640,488],{"class":42},[36,2642,868],{"class":69},[36,2644,115],{"class":114},[36,2646,441],{"class":351},[36,2648,356],{"class":355},[36,2650,673],{"class":317},[36,2652,268],{"class":56},[36,2654,678],{"class":118},[36,2656,397],{"class":56},[36,2658,2659],{"class":38,"line":170},[36,2660,180],{"emptyLinePlaceholder":179},[36,2662,2663,2665,2667,2669,2671,2673,2675,2677,2679,2681,2683,2685,2688,2690,2692,2694,2696],{"class":38,"line":176},[36,2664,488],{"class":42},[36,2666,325],{"class":69},[36,2668,115],{"class":114},[36,2670,631],{"class":114},[36,2672,433],{"class":351},[36,2674,356],{"class":355},[36,2676,638],{"class":317},[36,2678,642],{"class":641},[36,2680,19],{"class":49},[36,2682,648],{"class":641},[36,2684,268],{"class":56},[36,2686,2687],{"class":351},"message",[36,2689,356],{"class":355},[36,2691,1993],{"class":351},[36,2693,276],{"class":56},[36,2695,476],{"class":351},[36,2697,397],{"class":56},[36,2699,2700,2702,2704,2707,2709,2711],{"class":38,"line":183},[36,2701,946],{"class":42},[36,2703,949],{"class":56},[36,2705,2706],{"class":351},"order ",[36,2708,1218],{"class":114},[36,2710,1225],{"class":1224},[36,2712,299],{"class":56},[36,2714,2715],{"class":38,"line":193},[36,2716,502],{"class":56},[36,2718,2719,2721,2723,2725,2728,2730,2732,2734,2736,2738,2740,2742,2744],{"class":38,"line":198},[36,2720,1081],{"class":114},[36,2722,868],{"class":351},[36,2724,356],{"class":355},[36,2726,2727],{"class":317},"DeleteOneDocumentAsync",[36,2729,268],{"class":56},[36,2731,2687],{"class":351},[36,2733,356],{"class":355},[36,2735,1993],{"class":351},[36,2737,356],{"class":355},[36,2739,364],{"class":317},[36,2741,367],{"class":56},[36,2743,476],{"class":351},[36,2745,397],{"class":56},[36,2747,2748,2751],{"class":38,"line":219},[36,2749,2750],{"class":42},"            return",[36,2752,122],{"class":56},[36,2754,2755],{"class":38,"line":247},[36,2756,1150],{"class":56},[36,2758,2759],{"class":38,"line":252},[36,2760,180],{"emptyLinePlaceholder":179},[36,2762,2763,2765,2767,2769,2771,2773,2775,2777,2779,2781,2783,2785,2787,2789,2791,2793,2795],{"class":38,"line":257},[36,2764,488],{"class":42},[36,2766,334],{"class":69},[36,2768,115],{"class":114},[36,2770,631],{"class":114},[36,2772,433],{"class":351},[36,2774,356],{"class":355},[36,2776,638],{"class":317},[36,2778,642],{"class":641},[36,2780,645],{"class":49},[36,2782,648],{"class":641},[36,2784,268],{"class":56},[36,2786,352],{"class":351},[36,2788,356],{"class":355},[36,2790,550],{"class":351},[36,2792,276],{"class":56},[36,2794,476],{"class":351},[36,2796,397],{"class":56},[36,2798,2799,2801,2803,2805,2807,2810,2812,2814,2816,2818,2820,2822,2824,2826,2828,2830,2832],{"class":38,"line":302},[36,2800,596],{"class":114},[36,2802,868],{"class":351},[36,2804,356],{"class":355},[36,2806,685],{"class":317},[36,2808,2809],{"class":56},"([",[36,2811,696],{"class":351},[36,2813,356],{"class":355},[36,2815,701],{"class":317},[36,2817,268],{"class":56},[36,2819,352],{"class":351},[36,2821,276],{"class":56},[36,2823,334],{"class":351},[36,2825,712],{"class":56},[36,2827,715],{"class":69},[36,2829,718],{"class":56},[36,2831,476],{"class":351},[36,2833,397],{"class":56},[36,2835,2836],{"class":38,"line":307},[36,2837,728],{"class":56},[36,2839,2840],{"class":38,"line":343},[36,2841,173],{"class":56},[10,2843,2844],{},"Durable local queues keep the message in Postgres until the handler succeeds, and an error policy retries if the search engine is unavailable:",[26,2846,2848],{"className":28,"code":2847,"filename":1325,"language":31,"meta":32,"style":32},"builder.Services.AddSingleton(new MeilisearchClient(meiliUrl, meiliKey));\nbuilder.Services.AddMarten(options => options.Connection(connectionString))\n    .IntegrateWithWolverine();\n\nbuilder.UseWolverine(opts =>\n{\n    opts.Policies.AutoApplyTransactions();\n    opts.Policies.UseDurableLocalQueues();\n    opts.OnException\u003CMeilisearchCommunicationError>()\n        .RetryWithCooldown(100.Milliseconds(), 1.Seconds(), 5.Seconds());\n});\n",[17,2849,2850,2878,2908,2917,2921,2937,2941,2958,2973,2991,3031],{"__ignoreMap":32},[36,2851,2852,2854,2856,2858,2860,2862,2864,2866,2868,2870,2872,2874,2876],{"class":38,"line":39},[36,2853,1332],{"class":351},[36,2855,356],{"class":355},[36,2857,1337],{"class":351},[36,2859,356],{"class":355},[36,2861,1342],{"class":317},[36,2863,268],{"class":56},[36,2865,1347],{"class":114},[36,2867,438],{"class":49},[36,2869,268],{"class":56},[36,2871,1354],{"class":351},[36,2873,276],{"class":56},[36,2875,1359],{"class":351},[36,2877,1362],{"class":56},[36,2879,2880,2882,2884,2886,2888,2890,2892,2894,2896,2898,2900,2902,2904,2906],{"class":38,"line":53},[36,2881,1332],{"class":351},[36,2883,356],{"class":355},[36,2885,1337],{"class":351},[36,2887,356],{"class":355},[36,2889,1375],{"class":317},[36,2891,268],{"class":56},[36,2893,2269],{"class":69},[36,2895,993],{"class":114},[36,2897,1394],{"class":351},[36,2899,356],{"class":355},[36,2901,1413],{"class":317},[36,2903,268],{"class":56},[36,2905,1418],{"class":351},[36,2907,1048],{"class":56},[36,2909,2910,2912,2915],{"class":38,"line":60},[36,2911,2288],{"class":355},[36,2913,2914],{"class":317},"IntegrateWithWolverine",[36,2916,941],{"class":56},[36,2918,2919],{"class":38,"line":90},[36,2920,180],{"emptyLinePlaceholder":179},[36,2922,2923,2925,2927,2930,2932,2935],{"class":38,"line":125},[36,2924,1332],{"class":351},[36,2926,356],{"class":355},[36,2928,2929],{"class":317},"UseWolverine",[36,2931,268],{"class":56},[36,2933,2934],{"class":69},"opts",[36,2936,340],{"class":114},[36,2938,2939],{"class":38,"line":147},[36,2940,57],{"class":56},[36,2942,2943,2946,2948,2951,2953,2956],{"class":38,"line":170},[36,2944,2945],{"class":351},"    opts",[36,2947,356],{"class":355},[36,2949,2950],{"class":351},"Policies",[36,2952,356],{"class":355},[36,2954,2955],{"class":317},"AutoApplyTransactions",[36,2957,941],{"class":56},[36,2959,2960,2962,2964,2966,2968,2971],{"class":38,"line":176},[36,2961,2945],{"class":351},[36,2963,356],{"class":355},[36,2965,2950],{"class":351},[36,2967,356],{"class":355},[36,2969,2970],{"class":317},"UseDurableLocalQueues",[36,2972,941],{"class":56},[36,2974,2975,2977,2979,2982,2984,2987,2989],{"class":38,"line":183},[36,2976,2945],{"class":351},[36,2978,356],{"class":355},[36,2980,2981],{"class":317},"OnException",[36,2983,642],{"class":641},[36,2985,2986],{"class":49},"MeilisearchCommunicationError",[36,2988,648],{"class":641},[36,2990,2112],{"class":56},[36,2992,2993,2996,2999,3001,3004,3006,3009,3011,3014,3016,3019,3021,3024,3026,3028],{"class":38,"line":193},[36,2994,2995],{"class":355},"        .",[36,2997,2998],{"class":317},"RetryWithCooldown",[36,3000,268],{"class":56},[36,3002,3003],{"class":961},"100",[36,3005,356],{"class":355},[36,3007,3008],{"class":317},"Milliseconds",[36,3010,367],{"class":56},[36,3012,3013],{"class":961}," 1",[36,3015,356],{"class":355},[36,3017,3018],{"class":317},"Seconds",[36,3020,367],{"class":56},[36,3022,3023],{"class":961}," 5",[36,3025,356],{"class":355},[36,3027,3018],{"class":317},[36,3029,3030],{"class":56},"());\n",[36,3032,3033],{"class":38,"line":198},[36,3034,1472],{"class":56},[735,3036,3037,3053],{},[738,3038,3039],{"v-slot:pros":32},[741,3040,3041,3044,3047,3050],{},[744,3042,3043],{},"No dual write: the message commits or rolls back with the document",[744,3045,3046],{},"Retries and error policies come built in, and indexing moves off the request path",[744,3048,3049],{},"Loading the latest document makes every message idempotent",[744,3051,3052],{},"The same outbox can drive notifications and integrations too",[738,3054,3055],{"v-slot:cons":32},[741,3056,3057,3060,3063],{},[744,3058,3059],{},"Every write path has to ensure it sends the message",[744,3061,3062],{},"Bulk inserts, patches and raw SQL bypass it, just like the listener",[744,3064,3065],{},"No built-in backfill, so reindexing means sending a message per document yourself",[404,3067,3069],{"id":3068},"change-data-capture-with-wallaby","Change Data Capture with Wallaby",[10,3071,3072],{},"The last option skips the application entirely. Postgres already records every committed change in its write-ahead log, and logical replication streams it to anyone listening. Wallaby reads that stream, rehydrates Marten's JSONB back into your document type and hands batches of changes to a transform.",[26,3074,3077],{"className":28,"code":3075,"filename":3076,"language":31,"meta":32,"style":32},"public sealed class OrderSearchTransform : IWallabyMartenTransform\u003COrder>\n{\n    public async Task\u003CIReadOnlyDictionary\u003CDocumentKey, WallabyDocument?>> TransformAsync(\n        IQuerySession session, IReadOnlyList\u003CChangeEvent\u003COrder>> changes, CancellationToken ct)\n    {\n        var customerIds = changes.Select(c => c.Entity!.CustomerId).Distinct();\n        var customers = (await session.LoadManyAsync\u003CCustomer>(ct, customerIds))\n            .ToDictionary(c => c.Id);\n\n        var documents = new Dictionary\u003CDocumentKey, WallabyDocument?>(changes.Count);\n        foreach (var change in changes)\n        {\n            var order = change.Entity!;\n            documents[change.Key] = new WallabyDocument\n            {\n                [\"number\"] = order.Number,\n                [\"total\"] = order.Total,\n                [\"customer\"] = customers.GetValueOrDefault(order.CustomerId)?.Name\n            };\n        }\n        return documents;\n    }\n}\n","OrderSearchTransform.cs",[17,3078,3079,3103,3107,3140,3174,3178,3219,3253,3273,3277,3315,3335,3339,3357,3383,3388,3408,3427,3461,3466,3470,3478,3482],{"__ignoreMap":32},[36,3080,3081,3083,3086,3088,3091,3093,3096,3098,3100],{"class":38,"line":39},[36,3082,43],{"class":42},[36,3084,3085],{"class":42}," sealed",[36,3087,46],{"class":42},[36,3089,3090],{"class":49}," OrderSearchTransform",[36,3092,810],{"class":56},[36,3094,3095],{"class":49}," IWallabyMartenTransform",[36,3097,642],{"class":641},[36,3099,19],{"class":49},[36,3101,3102],{"class":641},">\n",[36,3104,3105],{"class":38,"line":53},[36,3106,57],{"class":56},[36,3108,3109,3111,3113,3115,3117,3120,3122,3125,3127,3130,3132,3135,3138],{"class":38,"line":60},[36,3110,63],{"class":42},[36,3112,454],{"class":42},[36,3114,457],{"class":49},[36,3116,642],{"class":641},[36,3118,3119],{"class":49},"IReadOnlyDictionary",[36,3121,642],{"class":641},[36,3123,3124],{"class":49},"DocumentKey",[36,3126,276],{"class":56},[36,3128,3129],{"class":49}," WallabyDocument",[36,3131,293],{"class":56},[36,3133,3134],{"class":641},">>",[36,3136,3137],{"class":317}," TransformAsync",[36,3139,688],{"class":56},[36,3141,3142,3145,3147,3149,3152,3154,3157,3159,3161,3163,3166,3168,3170,3172],{"class":38,"line":90},[36,3143,3144],{"class":49},"        IQuerySession",[36,3146,433],{"class":69},[36,3148,276],{"class":56},[36,3150,3151],{"class":49}," IReadOnlyList",[36,3153,642],{"class":641},[36,3155,3156],{"class":49},"ChangeEvent",[36,3158,642],{"class":641},[36,3160,19],{"class":49},[36,3162,3134],{"class":641},[36,3164,3165],{"class":69}," changes",[36,3167,276],{"class":56},[36,3169,473],{"class":49},[36,3171,476],{"class":69},[36,3173,299],{"class":56},[36,3175,3176],{"class":38,"line":125},[36,3177,483],{"class":56},[36,3179,3180,3182,3184,3186,3188,3190,3192,3194,3196,3198,3200,3202,3205,3207,3209,3211,3213,3215,3217],{"class":38,"line":147},[36,3181,488],{"class":42},[36,3183,976],{"class":69},[36,3185,115],{"class":114},[36,3187,3165],{"class":351},[36,3189,356],{"class":355},[36,3191,985],{"class":317},[36,3193,268],{"class":56},[36,3195,1061],{"class":69},[36,3197,993],{"class":114},[36,3199,1066],{"class":351},[36,3201,356],{"class":355},[36,3203,3204],{"class":351},"Entity",[36,3206,1255],{"class":114},[36,3208,356],{"class":355},[36,3210,550],{"class":351},[36,3212,337],{"class":56},[36,3214,356],{"class":355},[36,3216,1007],{"class":317},[36,3218,941],{"class":56},[36,3220,3221,3223,3225,3227,3229,3231,3233,3235,3237,3239,3241,3243,3245,3247,3249,3251],{"class":38,"line":170},[36,3222,488],{"class":42},[36,3224,1016],{"class":69},[36,3226,115],{"class":114},[36,3228,949],{"class":56},[36,3230,1023],{"class":114},[36,3232,433],{"class":351},[36,3234,356],{"class":355},[36,3236,1030],{"class":317},[36,3238,642],{"class":641},[36,3240,645],{"class":49},[36,3242,648],{"class":641},[36,3244,268],{"class":56},[36,3246,608],{"class":351},[36,3248,276],{"class":56},[36,3250,976],{"class":351},[36,3252,1048],{"class":56},[36,3254,3255,3257,3259,3261,3263,3265,3267,3269,3271],{"class":38,"line":176},[36,3256,1175],{"class":355},[36,3258,1056],{"class":317},[36,3260,268],{"class":56},[36,3262,1061],{"class":69},[36,3264,993],{"class":114},[36,3266,1066],{"class":351},[36,3268,356],{"class":355},[36,3270,359],{"class":351},[36,3272,397],{"class":56},[36,3274,3275],{"class":38,"line":183},[36,3276,180],{"emptyLinePlaceholder":179},[36,3278,3279,3281,3284,3286,3288,3291,3293,3295,3297,3299,3301,3303,3305,3308,3310,3313],{"class":38,"line":193},[36,3280,488],{"class":42},[36,3282,3283],{"class":69}," documents",[36,3285,115],{"class":114},[36,3287,495],{"class":114},[36,3289,3290],{"class":49}," Dictionary",[36,3292,642],{"class":641},[36,3294,3124],{"class":49},[36,3296,276],{"class":56},[36,3298,3129],{"class":49},[36,3300,293],{"class":56},[36,3302,648],{"class":641},[36,3304,268],{"class":56},[36,3306,3307],{"class":351},"changes",[36,3309,356],{"class":355},[36,3311,3312],{"class":351},"Count",[36,3314,397],{"class":56},[36,3316,3317,3320,3322,3325,3328,3331,3333],{"class":38,"line":198},[36,3318,3319],{"class":42},"        foreach",[36,3321,949],{"class":56},[36,3323,3324],{"class":42},"var",[36,3326,3327],{"class":69}," change",[36,3329,3330],{"class":42}," in",[36,3332,3165],{"class":351},[36,3334,299],{"class":56},[36,3336,3337],{"class":38,"line":219},[36,3338,502],{"class":56},[36,3340,3341,3343,3345,3347,3349,3351,3353,3355],{"class":38,"line":247},[36,3342,973],{"class":42},[36,3344,325],{"class":69},[36,3346,115],{"class":114},[36,3348,3327],{"class":351},[36,3350,356],{"class":355},[36,3352,3204],{"class":351},[36,3354,1255],{"class":114},[36,3356,122],{"class":56},[36,3358,3359,3362,3365,3368,3370,3373,3376,3378,3380],{"class":38,"line":252},[36,3360,3361],{"class":351},"            documents",[36,3363,3364],{"class":56},"[",[36,3366,3367],{"class":351},"change",[36,3369,356],{"class":355},[36,3371,3372],{"class":351},"Key",[36,3374,3375],{"class":56},"]",[36,3377,115],{"class":114},[36,3379,495],{"class":114},[36,3381,3382],{"class":49}," WallabyDocument\n",[36,3384,3385],{"class":38,"line":257},[36,3386,3387],{"class":56},"            {\n",[36,3389,3390,3393,3396,3398,3400,3402,3404,3406],{"class":38,"line":302},[36,3391,3392],{"class":56},"                [",[36,3394,3395],{"class":118},"\"number\"",[36,3397,3375],{"class":56},[36,3399,115],{"class":114},[36,3401,325],{"class":351},[36,3403,356],{"class":355},[36,3405,374],{"class":351},[36,3407,536],{"class":56},[36,3409,3410,3412,3415,3417,3419,3421,3423,3425],{"class":38,"line":307},[36,3411,3392],{"class":56},[36,3413,3414],{"class":118},"\"total\"",[36,3416,3375],{"class":56},[36,3418,115],{"class":114},[36,3420,325],{"class":351},[36,3422,356],{"class":355},[36,3424,383],{"class":351},[36,3426,536],{"class":56},[36,3428,3429,3431,3434,3436,3438,3440,3442,3444,3446,3448,3450,3452,3454,3456,3458],{"class":38,"line":343},[36,3430,3392],{"class":56},[36,3432,3433],{"class":118},"\"customer\"",[36,3435,3375],{"class":56},[36,3437,115],{"class":114},[36,3439,1016],{"class":351},[36,3441,356],{"class":355},[36,3443,1123],{"class":317},[36,3445,268],{"class":56},[36,3447,352],{"class":351},[36,3449,356],{"class":355},[36,3451,550],{"class":351},[36,3453,337],{"class":56},[36,3455,293],{"class":114},[36,3457,356],{"class":355},[36,3459,3460],{"class":351},"Name\n",[36,3462,3463],{"class":38,"line":400},[36,3464,3465],{"class":56},"            };\n",[36,3467,3468],{"class":38,"line":725},[36,3469,1150],{"class":56},[36,3471,3472,3474,3476],{"class":38,"line":731},[36,3473,2199],{"class":42},[36,3475,3283],{"class":351},[36,3477,122],{"class":56},[36,3479,3480],{"class":38,"line":1230},[36,3481,728],{"class":56},[36,3483,3484],{"class":38,"line":1260},[36,3485,173],{"class":56},[26,3487,3489],{"className":28,"code":3488,"filename":1325,"language":31,"meta":32,"style":32},"builder.Services.AddMarten(options =>\n{\n    options.Connection(connectionString);\n    options.RegisterDocumentType\u003COrder>();\n    options.RegisterDocumentType\u003CCustomer>();\n});\n\nbuilder.Services.AddWallaby(cdc =>\n{\n    cdc.UseMarten()\n       .UseConnectionString(connectionString)\n       .AddMeilisearchSink(\"meili\", m =>\n       {\n           m.Endpoint = meiliUrl;\n           m.ApiKey = meiliKey;\n       })\n       .WithMappings(sink => sink\n           .Map\u003COrder>()\n           .ToDestination(\"orders\")\n           .WithBackfillVersion(\"v1\")\n           .UsingTransform\u003COrder, OrderSearchTransform>());\n});\n",[17,3490,3491,3509,3513,3527,3544,3560,3564,3568,3588,3592,3604,3618,3637,3642,3659,3674,3679,3696,3712,3725,3739,3758],{"__ignoreMap":32},[36,3492,3493,3495,3497,3499,3501,3503,3505,3507],{"class":38,"line":39},[36,3494,1332],{"class":351},[36,3496,356],{"class":355},[36,3498,1337],{"class":351},[36,3500,356],{"class":355},[36,3502,1375],{"class":317},[36,3504,268],{"class":56},[36,3506,2269],{"class":69},[36,3508,340],{"class":114},[36,3510,3511],{"class":38,"line":53},[36,3512,57],{"class":56},[36,3514,3515,3517,3519,3521,3523,3525],{"class":38,"line":60},[36,3516,1408],{"class":351},[36,3518,356],{"class":355},[36,3520,1413],{"class":317},[36,3522,268],{"class":56},[36,3524,1418],{"class":351},[36,3526,397],{"class":56},[36,3528,3529,3531,3533,3536,3538,3540,3542],{"class":38,"line":90},[36,3530,1408],{"class":351},[36,3532,356],{"class":355},[36,3534,3535],{"class":317},"RegisterDocumentType",[36,3537,642],{"class":641},[36,3539,19],{"class":49},[36,3541,648],{"class":641},[36,3543,941],{"class":56},[36,3545,3546,3548,3550,3552,3554,3556,3558],{"class":38,"line":125},[36,3547,1408],{"class":351},[36,3549,356],{"class":355},[36,3551,3535],{"class":317},[36,3553,642],{"class":641},[36,3555,645],{"class":49},[36,3557,648],{"class":641},[36,3559,941],{"class":56},[36,3561,3562],{"class":38,"line":147},[36,3563,1472],{"class":56},[36,3565,3566],{"class":38,"line":170},[36,3567,180],{"emptyLinePlaceholder":179},[36,3569,3570,3572,3574,3576,3578,3581,3583,3586],{"class":38,"line":176},[36,3571,1332],{"class":351},[36,3573,356],{"class":355},[36,3575,1337],{"class":351},[36,3577,356],{"class":355},[36,3579,3580],{"class":317},"AddWallaby",[36,3582,268],{"class":56},[36,3584,3585],{"class":69},"cdc",[36,3587,340],{"class":114},[36,3589,3590],{"class":38,"line":183},[36,3591,57],{"class":56},[36,3593,3594,3597,3599,3602],{"class":38,"line":193},[36,3595,3596],{"class":351},"    cdc",[36,3598,356],{"class":355},[36,3600,3601],{"class":317},"UseMarten",[36,3603,2112],{"class":56},[36,3605,3606,3609,3612,3614,3616],{"class":38,"line":198},[36,3607,3608],{"class":355},"       .",[36,3610,3611],{"class":317},"UseConnectionString",[36,3613,268],{"class":56},[36,3615,1418],{"class":351},[36,3617,299],{"class":56},[36,3619,3620,3622,3625,3627,3630,3632,3635],{"class":38,"line":219},[36,3621,3608],{"class":355},[36,3623,3624],{"class":317},"AddMeilisearchSink",[36,3626,268],{"class":56},[36,3628,3629],{"class":118},"\"meili\"",[36,3631,276],{"class":56},[36,3633,3634],{"class":69}," m",[36,3636,340],{"class":114},[36,3638,3639],{"class":38,"line":247},[36,3640,3641],{"class":56},"       {\n",[36,3643,3644,3647,3649,3652,3654,3657],{"class":38,"line":252},[36,3645,3646],{"class":351},"           m",[36,3648,356],{"class":355},[36,3650,3651],{"class":351},"Endpoint ",[36,3653,510],{"class":114},[36,3655,3656],{"class":351}," meiliUrl",[36,3658,122],{"class":56},[36,3660,3661,3663,3665,3668,3670,3672],{"class":38,"line":257},[36,3662,3646],{"class":351},[36,3664,356],{"class":355},[36,3666,3667],{"class":351},"ApiKey ",[36,3669,510],{"class":114},[36,3671,1359],{"class":351},[36,3673,122],{"class":56},[36,3675,3676],{"class":38,"line":302},[36,3677,3678],{"class":56},"       })\n",[36,3680,3681,3683,3686,3688,3691,3693],{"class":38,"line":307},[36,3682,3608],{"class":355},[36,3684,3685],{"class":317},"WithMappings",[36,3687,268],{"class":56},[36,3689,3690],{"class":69},"sink",[36,3692,993],{"class":114},[36,3694,3695],{"class":351}," sink\n",[36,3697,3698,3701,3704,3706,3708,3710],{"class":38,"line":343},[36,3699,3700],{"class":355},"           .",[36,3702,3703],{"class":317},"Map",[36,3705,642],{"class":641},[36,3707,19],{"class":49},[36,3709,648],{"class":641},[36,3711,2112],{"class":56},[36,3713,3714,3716,3719,3721,3723],{"class":38,"line":400},[36,3715,3700],{"class":355},[36,3717,3718],{"class":317},"ToDestination",[36,3720,268],{"class":56},[36,3722,678],{"class":118},[36,3724,299],{"class":56},[36,3726,3727,3729,3732,3734,3737],{"class":38,"line":725},[36,3728,3700],{"class":355},[36,3730,3731],{"class":317},"WithBackfillVersion",[36,3733,268],{"class":56},[36,3735,3736],{"class":118},"\"v1\"",[36,3738,299],{"class":56},[36,3740,3741,3743,3746,3748,3750,3752,3754,3756],{"class":38,"line":731},[36,3742,3700],{"class":355},[36,3744,3745],{"class":317},"UsingTransform",[36,3747,642],{"class":641},[36,3749,19],{"class":49},[36,3751,276],{"class":56},[36,3753,3090],{"class":49},[36,3755,648],{"class":641},[36,3757,3030],{"class":56},[36,3759,3760],{"class":38,"line":1230},[36,3761,1472],{"class":56},[10,3763,3764],{},"Deletes come through as delete events, so there's no separate code path for them. Bumping the backfill version reindexes every existing order through the same transform.",[735,3766,3767,3783],{},[738,3768,3769],{"v-slot:pros":32},[741,3770,3771,3774,3777,3780],{},[744,3772,3773],{},"Captures every committed write, including bulk inserts, patches, raw SQL and other services",[744,3775,3776],{},"No dual write: changes arrive in commit order with at-least-once delivery",[744,3778,3779],{},"Backfills run through the same pipeline as live changes",[744,3781,3782],{},"Works on plain documents, no event sourcing required",[738,3784,3785],{"v-slot:cons":32},[741,3786,3787,3794,3797],{},[744,3788,3789,3790,3793],{},"Needs logical replication enabled (",[17,3791,3792],{},"wal_level = logical",").",[744,3795,3796],{},"WAL consumes resources and requires monitoring",[744,3798,3799],{},"Additional worker service deployment",[404,3801,3803],{"id":3802},"which-one-should-you-use","Which one should you use?",[3805,3806,3807,3830],"table",{},[3808,3809,3810],"thead",{},[3811,3812,3813,3816,3819,3822,3825,3827],"tr",{},[3814,3815],"th",{},[3814,3817,3818],{},"Inline",[3814,3820,3821],{},"Listener",[3814,3823,3824],{},"Subscription",[3814,3826,2374],{},[3814,3828,3829],{},"Wallaby",[3831,3832,3833,3851,3867,3883,3898],"tbody",{},[3811,3834,3835,3839,3842,3844,3847,3849],{},[3836,3837,3838],"td",{},"Survives a failed index call",[3836,3840,3841],{},"No",[3836,3843,3841],{},[3836,3845,3846],{},"Yes",[3836,3848,3846],{},[3836,3850,3846],{},[3811,3852,3853,3856,3858,3860,3863,3865],{},[3836,3854,3855],{},"Catches every write path",[3836,3857,3841],{},[3836,3859,3841],{},[3836,3861,3862],{},"Events only",[3836,3864,3841],{},[3836,3866,3846],{},[3811,3868,3869,3872,3874,3876,3879,3881],{},[3836,3870,3871],{},"Built-in backfill",[3836,3873,3841],{},[3836,3875,3841],{},[3836,3877,3878],{},"Replay",[3836,3880,3841],{},[3836,3882,3846],{},[3811,3884,3885,3888,3890,3892,3894,3896],{},[3836,3886,3887],{},"Requires event sourcing",[3836,3889,3841],{},[3836,3891,3841],{},[3836,3893,3846],{},[3836,3895,3841],{},[3836,3897,3841],{},[3811,3899,3900,3903,3906,3908,3910,3913],{},[3836,3901,3902],{},"Extra infrastructure",[3836,3904,3905],{},"None",[3836,3907,3905],{},[3836,3909,3905],{},[3836,3911,3912],{},"Dependency",[3836,3914,3915],{},"Worker",[10,3917,3918],{},"If your data is already event sourced, then a subscription within the async daemon for one or two indexes makes a lot of sense. I'd recommend starting here if you can.",[10,3920,3921,3922,3926],{},"For Marten documents or more complex scenarios ",[2369,3923,3829],{"href":3924,"rel":3925},"https:\u002F\u002Fwallabycdc.net",[2373]," is likely the most robust solution for your requirements.",[3928,3929,3930],"style",{},"html pre.shiki code .szs56, html code.shiki .szs56{--shiki-light:#A0111F;--shiki-default:#A0111F;--shiki-dark:#CBA6F7}html pre.shiki code .s4IXt, html code.shiki .s4IXt{--shiki-light:#702C00;--shiki-light-font-style:inherit;--shiki-default:#702C00;--shiki-default-font-style:inherit;--shiki-dark:#F9E2AF;--shiki-dark-font-style:italic}html pre.shiki code .sSUUE, html code.shiki .sSUUE{--shiki-light:#0E1116;--shiki-default:#0E1116;--shiki-dark:#9399B2}html pre.shiki code .stEof, html code.shiki .stEof{--shiki-light:#702C00;--shiki-default:#702C00;--shiki-dark:#CDD6F4}html pre.shiki code .sOrU-, html code.shiki .sOrU-{--shiki-light:#A0111F;--shiki-default:#A0111F;--shiki-dark:#94E2D5}html pre.shiki code .s0foE, html code.shiki .s0foE{--shiki-light:#032563;--shiki-default:#032563;--shiki-dark:#A6E3A1}html pre.shiki code .sk8lX, html code.shiki .sk8lX{--shiki-light:#622CBC;--shiki-light-font-style:inherit;--shiki-default:#622CBC;--shiki-default-font-style:inherit;--shiki-dark:#89B4FA;--shiki-dark-font-style:italic}html pre.shiki code .saRu8, html code.shiki .saRu8{--shiki-light:#0E1116;--shiki-default:#0E1116;--shiki-dark:#CDD6F4}html pre.shiki code .si4eh, html code.shiki .si4eh{--shiki-light:#0E1116;--shiki-default:#0E1116;--shiki-dark:#94E2D5}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 .sa31r, html code.shiki .sa31r{--shiki-light:#66707B;--shiki-light-font-style:inherit;--shiki-default:#66707B;--shiki-default-font-style:inherit;--shiki-dark:#9399B2;--shiki-dark-font-style:italic}html pre.shiki code .sLONz, html code.shiki .sLONz{--shiki-light:#0E1116;--shiki-default:#0E1116;--shiki-dark:#89DCEB}html pre.shiki code .s4M-N, html code.shiki .s4M-N{--shiki-light:#023B95;--shiki-default:#023B95;--shiki-dark:#FAB387}html pre.shiki code .s_jeQ, html code.shiki .s_jeQ{--shiki-light:#023B95;--shiki-default:#023B95;--shiki-dark:#F38BA8}",{"title":32,"searchDepth":53,"depth":53,"links":3932},[3933,3934,3935,3936,3937,3938],{"id":406,"depth":53,"text":407},{"id":771,"depth":53,"text":772},{"id":1509,"depth":53,"text":1510},{"id":2363,"depth":53,"text":2364},{"id":3068,"depth":53,"text":3069},{"id":3802,"depth":53,"text":3803},"2026-09-29","Five ways to push Marten documents into your search engine.","md",{},"\u002Fblog\u002Fsyncing-marten-with-search",{"title":5,"description":3940},{"loc":3943},"blog\u002Fsyncing-marten-with-search",[3948,3949,3950,3951],"marten","postgres","search","dotnet","6cS3RPoCDbRagL1YhglNNwWYKptTkpi3d7lA0GHHHIY",[3954,3954],null,1790779459700]