[{"data":1,"prerenderedAt":973},["ShallowReactive",2],{"search-de":3,"content-de-developer\u002Fdecisions\u002F010-listing-engine":4,"surround-de-\u002Fdeveloper\u002Fdecisions\u002F010-listing-engine":964},[],{"id":5,"title":6,"body":7,"description":956,"extension":957,"meta":958,"navigation":959,"path":960,"seo":961,"stem":962,"__hash__":963},"content_de\u002F3.developer\u002Fdecisions\u002F11.010-listing-engine.md","ADR-010: Listing-Engine",{"type":8,"value":9,"toc":946},"minimark",[10,27,32,50,53,169,172,244,247,251,270,273,422,433,488,493,601,605,612,688,713,717,777,781,803,825,834,838,843,879,884,925,942],[11,12,13,21],"ul",{},[14,15,16,20],"li",{},[17,18,19],"strong",{},"Status:"," Angenommen",[14,22,23,26],{},[17,24,25],{},"Datum:"," 2026-05-26",[28,29,31],"h2",{"id":30},"kontext","Kontext",[33,34,35,36,40,41,45,46,49],"p",{},"Das Convex-Backend hatte bereits tiefgehende Schreibmodule — Lifecycles, Intake, Dispatch, Find-or-Create —, von denen jedes besitzt, wie sich eine Entität ",[37,38,39],"em",{},"ändert",". Die Leseseite hat diese Behandlung nie erfahren. „Eine Seite ",[42,43,44],"code",{},"\u003Centity>"," auflisten“ war über rund 80 ",[42,47,48],{},"list*","-Query-Endpunkte hinweg ausgeschrieben, und die Duplikation war noch nicht einmal das Schlimmste: Keine zwei von ihnen waren sich über ihren eigenen Vertrag einig.",[33,51,52],{},"Es existierten vier zueinander inkompatible Formen von „gib mir eine gefilterte, paginierte Seite“:",[54,55,56,75],"table",{},[57,58,59],"thead",{},[60,61,62,66,69,72],"tr",{},[63,64,65],"th",{},"Entität",[63,67,68],{},"Rückgabeform",[63,70,71],{},"Cursor",[63,73,74],{},"Zugriffspfad",[76,77,78,103,129,146],"tbody",{},[60,79,80,84,89,96],{},[81,82,83],"td",{},"Kontakte",[81,85,86],{},[42,87,88],{},"{ page, isDone, continueCursor }",[81,90,91,92,95],{},"Echter Convex-Cursor beim Browsen; der Literal-String ",[42,93,94],{},"'search'"," bei der Suche",[81,97,98,99,102],{},"Index ",[42,100,101],{},"search_contacts"," + Erstellungsindex",[60,104,105,108,112,115],{},[81,106,107],{},"Kampagnen",[81,109,110],{},[42,111,88],{},[81,113,114],{},"Als String serialisierter ganzzahliger Offset",[81,116,117,120,121,124,125,128],{},[42,118,119],{},"by_status","\u002F",[42,122,123],{},"by_updated_at",", danach ",[42,126,127],{},".filter()"," im Speicher",[60,130,131,134,137,140],{},[81,132,133],{},"E-Mail-Vorlagen",[81,135,136],{},"Reines Array, überhaupt keine Paginierung",[81,138,139],{},"—",[81,141,142,145],{},[42,143,144],{},".collect()"," über die gesamte Tabelle, danach Filtern und Sortieren im Speicher",[60,147,148,151,157,160],{},[81,149,150],{},"Themen",[81,152,153,156],{},[42,154,155],{},"{ page, … }"," + Anreicherung pro Zeile",[81,158,159],{},"Echter Convex-Cursor",[81,161,162,165,166],{},[42,163,164],{},".paginate()",", kein Filter, N+1 bei ",[42,167,168],{},"contactCount",[33,170,171],{},"Vier Listenabfragen, vier Rückgabeverträge, keiner davon kanonisch. Eine einzelne Paginierungs-UI konnte sie schlicht physisch nicht alle bedienen. Vier konkrete Probleme verstärkten die Divergenz:",[173,174,175,189,206,228],"ol",{},[14,176,177,180,181,184,185,188],{},[17,178,179],{},"Der Cursor war auf dem Suchpfad eine Lüge."," Die Kontaktsuche gab ",[42,182,183],{},"continueCursor: 'search'"," zurück (gecastet als ",[42,186,187],{},"as unknown as string",") und las stets von vorn, sodass die Anfrage nach Seite 2 einer Suche erneut Seite 1 auslieferte. Suchergebnisse waren stillschweigend einseitig.",[14,190,191,194,195,198,199,201,202,205],{},[17,192,193],{},"Die Entscheidung zwischen Index und Collect wurde pro Datei getroffen — oft falsch."," Kontakte nutzten einen echten ",[42,196,197],{},"searchIndex","; Kampagnen sammelten einen ganzen Status-Bucket ein und filterten die Suche im Speicher; E-Mail-Vorlagen führten ",[42,200,144],{}," über die ",[37,203,204],{},"gesamte"," Tabelle aus, für jede Auflistung, jeden Filter und jede Sortierung — eine Skalierungsklippe ohne einen Index weit und breit.",[14,207,208,211,212,215,216,219,220,223,224,227],{},[17,209,210],{},"Zählungen waren ein eigener Zoo."," ",[42,213,214],{},"countByStatus",", ",[42,217,218],{},"countByType"," und ",[42,221,222],{},"count"," verwendeten jeweils eine andere Strategie (einsammeln und gruppieren, erneut einsammeln und gruppieren, ein denormalisierter zwischengespeicherter Zähler), ohne gemeinsame Oberfläche — obwohl die Dashboards, die eine Liste rendern, ihre Facettenzählungen ",[37,225,226],{},"zusammen mit"," der Seite benötigen.",[14,229,230,233,234,236,237,219,240,243],{},[17,231,232],{},"Anreicherung war dupliziert und N+1."," Die ",[42,235,168],{},"-Anreicherung der Themen war in ",[42,238,239],{},"list",[42,241,242],{},"get"," jeweils separat inline ausgeschrieben, und die Zählung pro Zeile war ein vollständiger Paginierungs-Scan, sobald das zwischengespeicherte Feld fehlte.",[33,245,246],{},"Dies ist das leseseitige Gegenstück zu den schreibseitigen Lifecycle-Modulen und folgt dem bereits in der Codebase etablierten Präzedenzfall: ein dünner generischer Dispatcher (der Walker) über Daten pro Typ (die Block-Module).",[28,248,250],{"id":249},"entscheidung","Entscheidung",[33,252,253,254,257,258,261,262,265,266,269],{},"Das Auflisten von Ressourcen zu einer einzigen Naht machen: eine generische ",[17,255,256],{},"Listing-Engine",", die über ",[17,259,260],{},"Listing-Deskriptoren"," pro Entität dispatcht und einen einzigen Convex-nativen Vertrag zurückgibt. Die Engine liegt in ",[42,263,264],{},"apps\u002Fapi\u002Fconvex\u002Flib\u002Flisting.ts","; jede Entität besitzt einen Deskriptor unter ",[42,267,268],{},"apps\u002Fapi\u002Fconvex\u002F\u003Centity>\u002Flisting.ts",".",[33,271,272],{},"Ein Deskriptor deklariert die Leseoberfläche der Entität als Daten — ihren Suchindex, ihren Browse-Index, zulässige Sortierungen und Filter, die Soft-Delete-Policy, die Anreicherung pro Zeile und benannte Facettenzählungen:",[274,275,280],"pre",{"className":276,"code":277,"language":278,"meta":279,"style":279},"language-ts shiki shiki-themes github-light github-dark-dimmed","\u002F\u002F apps\u002Fapi\u002Fconvex\u002Fcontacts\u002Flisting.ts — the cleanest case\nexport const contactListing: ListingDescriptor\u003C'contacts'> = {\n  table: 'contacts',\n  search: { index: 'search_contacts', field: 'searchableText', filterFields: ['deletedAt'] },\n  browse: { index: 'by_deleted_at_and_created_at', order: 'desc' },\n  softDelete: true,\n  facets: { total: { kind: 'cachedCounter', table: 'instanceSettings', field: 'contactCount' } },\n};\n","ts","",[42,281,282,291,329,340,364,382,393,416],{"__ignoreMap":279},[283,284,287],"span",{"class":285,"line":286},"line",1,[283,288,290],{"class":289},"sDN9O","\u002F\u002F apps\u002Fapi\u002Fconvex\u002Fcontacts\u002Flisting.ts — the cleanest case\n",[283,292,294,298,301,305,308,312,316,320,323,326],{"class":285,"line":293},2,[283,295,297],{"class":296},"s7YZ4","export",[283,299,300],{"class":296}," const",[283,302,304],{"class":303},"sviXB"," contactListing",[283,306,307],{"class":296},":",[283,309,311],{"class":310},"sOLd2"," ListingDescriptor",[283,313,315],{"class":314},"sYgZi","\u003C",[283,317,319],{"class":318},"s-HuK","'contacts'",[283,321,322],{"class":314},"> ",[283,324,325],{"class":296},"=",[283,327,328],{"class":314}," {\n",[283,330,332,335,337],{"class":285,"line":331},3,[283,333,334],{"class":314},"  table: ",[283,336,319],{"class":318},[283,338,339],{"class":314},",\n",[283,341,343,346,349,352,355,358,361],{"class":285,"line":342},4,[283,344,345],{"class":314},"  search: { index: ",[283,347,348],{"class":318},"'search_contacts'",[283,350,351],{"class":314},", field: ",[283,353,354],{"class":318},"'searchableText'",[283,356,357],{"class":314},", filterFields: [",[283,359,360],{"class":318},"'deletedAt'",[283,362,363],{"class":314},"] },\n",[283,365,367,370,373,376,379],{"class":285,"line":366},5,[283,368,369],{"class":314},"  browse: { index: ",[283,371,372],{"class":318},"'by_deleted_at_and_created_at'",[283,374,375],{"class":314},", order: ",[283,377,378],{"class":318},"'desc'",[283,380,381],{"class":314}," },\n",[283,383,385,388,391],{"class":285,"line":384},6,[283,386,387],{"class":314},"  softDelete: ",[283,389,390],{"class":303},"true",[283,392,339],{"class":314},[283,394,396,399,402,405,408,410,413],{"class":285,"line":395},7,[283,397,398],{"class":314},"  facets: { total: { kind: ",[283,400,401],{"class":318},"'cachedCounter'",[283,403,404],{"class":314},", table: ",[283,406,407],{"class":318},"'instanceSettings'",[283,409,351],{"class":314},[283,411,412],{"class":318},"'contactCount'",[283,414,415],{"class":314}," } },\n",[283,417,419],{"class":285,"line":418},8,[283,420,421],{"class":314},"};\n",[33,423,424,425,428,429,432],{},"Die Session-Auth-Hülle (",[42,426,427],{},"contacts\u002Fcontacts.ts:list",") und die API-Key-Hülle (",[42,430,431],{},"\u003Centity>\u002Forganization.ts",") behalten ihre eigene Auth-Haltung und rufen in die Engine hinein:",[274,434,436],{"className":276,"code":435,"language":278,"meta":279,"style":279},"\u002F\u002F apps\u002Fapi\u002Fconvex\u002Fcontacts\u002Fcontacts.ts (shape)\nconst page  = await listResources(ctx.db, contactListing, args);\nconst total = await countFacet(ctx.db, contactListing, 'total');\n",[42,437,438,443,464],{"__ignoreMap":279},[283,439,440],{"class":285,"line":286},[283,441,442],{"class":289},"\u002F\u002F apps\u002Fapi\u002Fconvex\u002Fcontacts\u002Fcontacts.ts (shape)\n",[283,444,445,448,451,454,457,461],{"class":285,"line":293},[283,446,447],{"class":296},"const",[283,449,450],{"class":303}," page",[283,452,453],{"class":296},"  =",[283,455,456],{"class":296}," await",[283,458,460],{"class":459},"sPO5f"," listResources",[283,462,463],{"class":314},"(ctx.db, contactListing, args);\n",[283,465,466,468,471,474,476,479,482,485],{"class":285,"line":331},[283,467,447],{"class":296},[283,469,470],{"class":303}," total",[283,472,473],{"class":296}," =",[283,475,456],{"class":296},[283,477,478],{"class":459}," countFacet",[283,480,481],{"class":314},"(ctx.db, contactListing, ",[283,483,484],{"class":318},"'total'",[283,486,487],{"class":314},");\n",[489,490,492],"h3",{"id":491},"was-die-engine-garantiert","Was die Engine garantiert",[11,494,495,512,539,564,581],{},[14,496,497,500,501,504,505,507,508,511],{},[17,498,499],{},"Einen echten Convex-Cursor auf beiden Pfaden."," Die Suche verwendet ",[42,502,503],{},".withSearchIndex(...).paginate()"," (einen echten, opaken Cursor — das ",[42,506,94],{},"-Sentinel ist verschwunden); das Browsen verwendet ",[42,509,510],{},".withIndex(...).order().paginate()",". Die Suche ist echt mehrseitig, sobald eine Abfrage durch die Engine geleitet wird.",[14,513,514,517,518,211,521,524,525,528,529,532,533,536,537,269],{},[17,515,516],{},"Suche bedeutet Relevanzreihenfolge."," Suchergebnisse sind nach Relevanz sortiert, daher gelten ",[42,519,520],{},"sortKeys",[17,522,523],{},"nur für den Browse-Pfad",". Wird ",[42,526,527],{},"search"," übergeben, wird ",[42,530,531],{},"sort"," ignoriert. Das ist Teil der Schnittstelle, kein verstecktes Detail — siehe ",[42,534,535],{},"listResources"," in ",[42,538,264],{},[14,540,541,544,545,548,549,552,553,556,557,560,561,563],{},[17,542,543],{},"Soft-Delete reitet auf dem Index und dünnt nie die Seite aus."," Ist ",[42,546,547],{},"softDelete"," gesetzt, wird ",[42,550,551],{},"deletedAt === undefined"," auf beiden Pfaden innerhalb des Indexbereichs fixiert. Der Browse-Index muss deshalb mit ",[42,554,555],{},"deletedAt"," beginnen (Kontakte verwenden ",[42,558,559],{},"by_deleted_at_and_created_at","). Gewöhnliche Gleichheitsfilter dürfen eine Seite ausdünnen; nur ",[42,562,555],{}," ist von diesem Schicksal ausgenommen.",[14,565,566,569,570,573,574,577,578,580],{},[17,567,568],{},"Index-native Filter, wo ein zusammengesetzter Index existiert."," Ein einzelner Gleichheitsfilter mit einem dedizierten zusammengesetzten Index (z. B. ",[42,571,572],{},"status"," → ",[42,575,576],{},"by_status_and_updated_at",") wird index-nativ und geordnet bedient. Ein Filter ohne einen solchen Index fällt auf ein ",[42,579,127],{}," nach dem Index zurück.",[14,582,583,586,587,590,591,593,594,596,597,600],{},[17,584,585],{},"Der Deskriptor besitzt die Anreicherungskosten."," Die Engine führt ",[42,588,589],{},"enrich"," über die Seite aus (und das ",[42,592,242],{}," der Entität verwendet es wieder) und verbirgt nicht, ob jeder Aufruf O(1)-zwischengespeichert oder ein Scan ist — die Kosten stehen im Deskriptor. Themen reichern ",[42,595,168],{}," aus dem denormalisierten ",[42,598,599],{},"cachedMemberCount"," an (mit einem begrenzten Mitgliedschafts-Scan als Fallback); der Automations-Deskriptor deklariert keine Anreicherung pro Zeile.",[489,602,604],{"id":603},"facetten-sind-auf-drei-strategien-beschränkt","Facetten sind auf drei Strategien beschränkt",[33,606,607,608,611],{},"Der Zählungs-Zoo kollabiert auf exakt drei ",[42,609,610],{},"Facet","-Arten — alles Reichhaltigere wird an der Schnittstelle abgelehnt, und eine einmalige Zählung wird als schlichte Query außerhalb der Naht geschrieben:",[54,613,614,627],{},[57,615,616],{},[60,617,618,621,624],{},[63,619,620],{},"Facettenart",[63,622,623],{},"Zählt über",[63,625,626],{},"Beispiel",[76,628,629,648,669],{},[60,630,631,636,639],{},[81,632,633],{},[42,634,635],{},"indexCount",[81,637,638],{},"Eine begrenzte paginierte Zählung über einen Index",[81,640,641,642,645,646],{},"Kampagnen-",[42,643,644],{},"total"," über ",[42,647,123],{},[60,649,650,655,660],{},[81,651,652],{},[42,653,654],{},"groupBy",[81,656,657,658],{},"Eine begrenzte Indexzählung pro Bucket, summiert zu ",[42,659,644],{},[81,661,641,662,665,666],{},[42,663,664],{},"byStatus",", Vorlagen-",[42,667,668],{},"byType",[60,670,671,676,679],{},[81,672,673],{},[42,674,675],{},"cachedCounter",[81,677,678],{},"Ein denormalisierter Zähler auf einer Singleton-Zeile (mit begrenztem Scan als Fallback)",[81,680,681,682,684,685],{},"Kontakt-",[42,683,644],{}," aus ",[42,686,687],{},"instanceSettings.contactCount",[33,689,690,693,694,696,697,699,700,536,703,705,706,536,709,712],{},[42,691,692],{},"countFacet(db, descriptor, name)"," löst die Strategie auf; ",[42,695,654],{}," liefert Zählungen pro Bucket, deren ",[42,698,644],{}," deren Summe ist. Die Implementierung liegt in ",[42,701,702],{},"countFacet",[42,704,264],{}," und verwendet das Primitiv ",[42,707,708],{},"countWithPagination",[42,710,711],{},"apps\u002Fapi\u002Fconvex\u002Flib\u002Fpagination.ts"," wieder.",[489,714,716],{"id":715},"während-des-entwurfs-getroffene-entscheidungen","Während des Entwurfs getroffene Entscheidungen",[173,718,719,725,750,756,762,767],{},[14,720,721,724],{},[17,722,723],{},"Hybrid aus Walker + Deskriptoren",", weder ein generischer Config-Bag noch eine Modulfamilie pro Entität — eine einzige generische Engine über dünnen Daten pro Typ, also die in der Codebase bereits akzeptierte Form „Dispatcher über Daten“.",[14,726,727,730,731,734,735,738,739,742,743,745,746,749],{},[17,728,729],{},"Ausschließlich Convex-nativer Cursor",", unter Inkaufnahme der Schema-Rechnung: Kampagnen und E-Mail-Vorlagen erhielten die Suchindizes ",[42,732,733],{},"search_campaigns"," \u002F ",[42,736,737],{},"search_templates"," mit ",[42,740,741],{},"filterFields",", und ",[42,744,555],{}," kam in den betreffenden Browse-Index hinzu. Der In-Memory-Offset-Fallback ",[42,747,748],{},"paginateArray"," wurde verworfen, da er sowohl den Offset-Cursor als auch den Table-Scan bei der Suche verursacht hat.",[14,751,752,755],{},[17,753,754],{},"Der Umfang ist Seite + Anreicherung + Facettenzählungen."," Der Deskriptor besitzt die gesamte Leseoberfläche der Entität, sodass die Zähl-Queries und die Themen-Anreicherung ebenfalls hineinfallen. Eine Naht nur für Seiten hätte den Zählungs-Zoo und das N+1 stehen lassen.",[14,757,758,761],{},[17,759,760],{},"Der Deskriptor besitzt die Anreicherungskosten",", statt dass die Engine nicht-O(1)-Anreicherung verweigert, was zu vorzeitiger Denormalisierung gezwungen hätte.",[14,763,764,766],{},[17,765,604],{}," — genau auf das, was in freier Wildbahn existierte.",[14,768,769,772,773,776],{},[17,770,771],{},"Auth bleibt in den Hüllen."," Die Engine nimmt einen ",[42,774,775],{},"DatabaseReader"," entgegen, nie eine Session — sie liest, sie authentifiziert oder scopet nicht.",[489,778,780],{"id":779},"durchsetzung","Durchsetzung",[33,782,783,784,787,788,791,792,795,796,219,799,802],{},"Ein ",[42,785,786],{},"lint:listing","-Guard (",[42,789,790],{},"apps\u002Fapi\u002Fscripts\u002Fcheck-listing.sh",", ausgeführt als Teil von ",[42,793,794],{},"bun run lint",", Geschwister von ",[42,797,798],{},"lint:env",[42,800,801],{},"lint:errors",") verhindert, dass die Naht erodiert. Er verbietet in Query-Dateien außerhalb der Engine und der Deskriptoren:",[11,804,805,811],{},[14,806,807,810],{},[42,808,809],{},"paginateArray("," — den entfernten Helfer für den als String serialisierten ganzzahligen Offset.",[14,812,813,814,817,818,821,822,824],{},"Manuelle ",[42,815,816],{},"numItems + 1","-Paginierung nach dem Muster „nimm n+1, um ",[42,819,820],{},"hasMore"," zu berechnen“ — das handgestrickte Muster, das den gefälschten ",[42,823,94],{},"-Sentinel-Cursor hervorgebracht hat.",[33,826,827,828,830,831,833],{},"Die dritte ausgeschriebene Form (",[42,829,144],{},", dann filtern, dann paginieren) wird durch die bestehende ",[42,832,144],{},"-Baseline-Prüfung in Schach gehalten; die Portierung einer Listenabfrage auf die Engine senkt sie immer nur.",[28,835,837],{"id":836},"konsequenzen","Konsequenzen",[33,839,840],{},[17,841,842],{},"Ermöglicht:",[11,844,845,851,854,860],{},[14,846,847,848,850],{},"Einen einzigen Lesevertrag — jeder Listenendpunkt liefert dasselbe ",[42,849,88],{}," mit einem echten Convex-Cursor samt seinen Facetten, sodass eine einzige Paginierungs-UI gegen alle funktioniert.",[14,852,853],{},"Der Cursor-Bug der Kontaktsuche stirbt beim Umschalten; die Suche wird echt mehrseitig.",[14,855,856,857,859],{},"Die Entscheidung zwischen Index und Collect wird einmal getroffen, an einer getesteten Stelle. Das vollständige ",[42,858,144],{},", das die E-Mail-Vorlagen-Auflistung falsch gemacht hat, ist verschwunden; eine neue listbare Entität ist ein Deskriptor von rund 6 Zeilen statt 60 Zeilen neu hergeleiteter Query. Sechs Entitäten nutzen die Engine inzwischen — Kontakte, Kampagnen, Themen, Segmente, Automationen und E-Mail-Vorlagen.",[14,861,862,863,734,865,734,867,869,870,873,874,219,876,878],{},"Der Zählungs-Zoo und das N+1 kollabieren: ",[42,864,214],{},[42,866,218],{},[42,868,222],{}," werden zu deklarierten ",[42,871,872],{},"facets",", und die Anreicherung pro Zeile wird einmal deklariert und von ",[42,875,239],{},[42,877,242],{}," gemeinsam genutzt.",[33,880,881],{},[17,882,883],{},"Trade-offs:",[11,885,886,902,908],{},[14,887,888,889,734,891,893,894,734,896,219,899,901],{},"Eine additive Schema-Rechnung — die Suchindizes ",[42,890,733],{},[42,892,737],{},", die zusammengesetzten Indizes ",[42,895,576],{},[42,897,898],{},"by_type_and_updated_at",[42,900,555],{},", eingefaltet in die soft-delete-fähigen Browse-Indizes.",[14,903,904,905,907],{},"Ein bewusster, atomarer Bruch: Die HTTP-Liste der E-Mail-Vorlagen wechselte vom reinen Array zu einem paginierten ",[42,906,155],{},", im selben Geist des sauberen Schnitts wie die SDK-Änderungen (keine zweiphasige Migration).",[14,909,910,911,734,914,917,918,921,922,924],{},"Convex' ",[42,912,913],{},"withIndex",[42,915,916],{},"withSearchIndex"," verlangen pro Tabelle String-Literal-Indexnamen, die eine über ",[42,919,920],{},"TableNames"," generische Funktion nicht liefern kann, weshalb die Engine ihre Query-Builder intern castet — derselbe Handel, den ",[42,923,708],{}," bereits eingeht, beschränkt auf ein Modul.",[33,926,927,928,931,932,937,938,269],{},"Diese Entscheidung ist als ADR-0037 im Engineering-ADR-Satz festgehalten (",[42,929,930],{},"docs\u002Fadr\u002F0037-resource-listing-engine.md","). Sie baut auf ",[933,934,936],"a",{"href":935},"\u002Fdeveloper\u002Fdecisions\u002F002-convex-backend","ADR-002: Convex als Backend"," auf und spiegelt das Muster „Dispatcher über Daten“ wider; zum umfassenderen Backend-Aufbau siehe die Referenz ",[933,939,941],{"href":940},"\u002Fdeveloper\u002Fconvex","Convex Backend",[943,944,945],"style",{},"html pre.shiki code .sDN9O, html code.shiki .sDN9O{--shiki-default:#6A737D;--shiki-dark:#768390}html pre.shiki code .s7YZ4, html code.shiki .s7YZ4{--shiki-default:#D73A49;--shiki-dark:#F47067}html pre.shiki code .sviXB, html code.shiki .sviXB{--shiki-default:#005CC5;--shiki-dark:#6CB6FF}html pre.shiki code .sOLd2, html code.shiki .sOLd2{--shiki-default:#6F42C1;--shiki-dark:#F69D50}html pre.shiki code .sYgZi, html code.shiki .sYgZi{--shiki-default:#24292E;--shiki-dark:#ADBAC7}html pre.shiki code .s-HuK, html code.shiki .s-HuK{--shiki-default:#032F62;--shiki-dark:#96D0FF}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 .sPO5f, html code.shiki .sPO5f{--shiki-default:#6F42C1;--shiki-dark:#DCBDFB}",{"title":279,"searchDepth":293,"depth":293,"links":947},[948,949,955],{"id":30,"depth":293,"text":31},{"id":249,"depth":293,"text":250,"children":950},[951,952,953,954],{"id":491,"depth":331,"text":492},{"id":603,"depth":331,"text":604},{"id":715,"depth":331,"text":716},{"id":779,"depth":331,"text":780},{"id":836,"depth":293,"text":837},"Warum Owlat vier inkompatible Vertragsformen für Listenabfragen durch eine generische Listing-Engine ersetzt hat, die von Deskriptoren pro Entität gesteuert wird.","md",{},true,"\u002Fdeveloper\u002Fdecisions\u002F010-listing-engine",{"title":6,"description":956},"3.developer\u002Fdecisions\u002F11.010-listing-engine","SE_6IV6yck9Uv0I1ofHrUaxFIH0oKI2bHfH3a3fctMo",[965,969],{"title":966,"path":967,"stem":968,"children":-1},"ADR-009: Aufgabenbasiertes Modell-Routing","\u002Fdeveloper\u002Fdecisions\u002F009-model-routing","3.developer\u002Fdecisions\u002F10.009-model-routing",{"title":970,"path":971,"stem":972,"children":-1},"ADR-049: Plugin-Plattformvertrag","\u002Fdeveloper\u002Fdecisions\u002F049-plugin-platform-contract","3.developer\u002Fdecisions\u002F12.049-plugin-platform-contract",1786915110906]