[{"data":1,"prerenderedAt":1663},["ShallowReactive",2],{"navigation_docs_en":3,"-en-dbx-transactions":441,"-en-dbx-transactions-surround":1658},[4,45,60,81,112,129,146,173,197,213,242,279,356,372,384,403,429],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":44},"Getting Started","i-lucide-rocket","\u002Fen\u002Fgetting-started","en\u002F1.getting-started",[10,15,20,24,29,34,39],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fen\u002Fgetting-started\u002Fintroduction","en\u002F1.getting-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Coming from Laravel","\u002Fen\u002Fgetting-started\u002Fcoming-from-laravel","en\u002F1.getting-started\u002F2.coming-from-laravel","i-lucide-arrow-right-left",{"title":21,"path":22,"stem":23,"icon":19},"Coming from Symfony","\u002Fen\u002Fgetting-started\u002Fcoming-from-symfony","en\u002F1.getting-started\u002F3.coming-from-symfony",{"title":25,"path":26,"stem":27,"icon":28},"Installation","\u002Fen\u002Fgetting-started\u002Finstallation","en\u002F1.getting-started\u002F4.installation","i-lucide-download",{"title":30,"path":31,"stem":32,"icon":33},"Your first module","\u002Fen\u002Fgetting-started\u002Ffirst-module","en\u002F1.getting-started\u002F5.first-module","i-lucide-package-plus",{"title":35,"path":36,"stem":37,"icon":38},"Project structure","\u002Fen\u002Fgetting-started\u002Fproject-structure","en\u002F1.getting-started\u002F6.project-structure","i-lucide-folder-tree",{"title":40,"path":41,"stem":42,"icon":43},"The shop sample application","\u002Fen\u002Fgetting-started\u002Fsample-app","en\u002F1.getting-started\u002F7.sample-app","i-lucide-shopping-cart",false,{"title":46,"icon":47,"path":48,"stem":49,"children":50,"page":44},"queue","i-lucide-layers","\u002Fen\u002Fqueue","en\u002F10.queue",[51,56],{"title":52,"path":53,"stem":54,"icon":55},"Overview","\u002Fen\u002Fqueue\u002Foverview","en\u002F10.queue\u002F1.overview","i-lucide-list-ordered",{"title":57,"path":58,"stem":59,"icon":47},"Tasks","\u002Fen\u002Fqueue\u002Ftasks","en\u002F10.queue\u002F2.tasks",{"title":61,"icon":62,"path":63,"stem":64,"children":65,"page":44},"events","i-lucide-workflow","\u002Fen\u002Fevents","en\u002F11.events",[66,71,76],{"title":67,"path":68,"stem":69,"icon":70},"Events","\u002Fen\u002Fevents\u002Foverview","en\u002F11.events\u002F1.overview","i-lucide-radio",{"title":72,"path":73,"stem":74,"icon":75},"Outbox","\u002Fen\u002Fevents\u002Foutbox","en\u002F11.events\u002F2.outbox","i-lucide-inbox",{"title":77,"path":78,"stem":79,"icon":80},"Scheduling","\u002Fen\u002Fevents\u002Fscheduler","en\u002F11.events\u002F3.scheduler","i-lucide-calendar-clock",{"title":82,"icon":83,"path":84,"stem":85,"children":86,"page":44},"auth","i-lucide-shield-check","\u002Fen\u002Fauth","en\u002F12.auth",[87,92,97,102,107],{"title":88,"path":89,"stem":90,"icon":91},"Authentication","\u002Fen\u002Fauth\u002Foverview","en\u002F12.auth\u002F1.overview","i-lucide-key-round",{"title":93,"path":94,"stem":95,"icon":96},"RBAC","\u002Fen\u002Fauth\u002Frbac","en\u002F12.auth\u002F2.rbac","i-lucide-users",{"title":98,"path":99,"stem":100,"icon":101},"Policies","\u002Fen\u002Fauth\u002Fpolicy","en\u002F12.auth\u002F3.policy","i-lucide-gavel",{"title":103,"path":104,"stem":105,"icon":106},"Using auth standalone","\u002Fen\u002Fauth\u002Fstandalone","en\u002F12.auth\u002F4.standalone","i-lucide-plug",{"title":108,"path":109,"stem":110,"icon":111},"Social login with goth, standalone","\u002Fen\u002Fauth\u002Fsocial-standalone","en\u002F12.auth\u002F5.social-standalone","i-lucide-log-in",{"title":113,"icon":114,"path":115,"stem":116,"children":117,"page":44},"mail","i-lucide-mail","\u002Fen\u002Fmail","en\u002F13.mail",[118,121,125],{"title":52,"path":119,"stem":120,"icon":114},"\u002Fen\u002Fmail\u002Foverview","en\u002F13.mail\u002F1.overview",{"title":122,"path":123,"stem":124,"icon":114},"SMTP","\u002Fen\u002Fmail\u002Fsmtp","en\u002F13.mail\u002F2.smtp",{"title":126,"path":127,"stem":128,"icon":114},"MJML","\u002Fen\u002Fmail\u002Fmjml","en\u002F13.mail\u002F3.mjml",{"title":130,"icon":131,"path":132,"stem":133,"children":134,"page":44},"notify","i-lucide-bell","\u002Fen\u002Fnotify","en\u002F14.notify",[135,138,142],{"title":52,"path":136,"stem":137,"icon":131},"\u002Fen\u002Fnotify\u002Foverview","en\u002F14.notify\u002F1.overview",{"title":139,"path":140,"stem":141,"icon":131},"Database channel","\u002Fen\u002Fnotify\u002Fdatabase","en\u002F14.notify\u002F2.database",{"title":143,"path":144,"stem":145,"icon":131},"Broadcast channel","\u002Fen\u002Fnotify\u002Fbroadcast","en\u002F14.notify\u002F3.broadcast",{"title":147,"icon":148,"path":149,"stem":150,"children":151,"page":44},"storage","i-lucide-hard-drive","\u002Fen\u002Fstorage","en\u002F15.storage",[152,155,159,163,168],{"title":52,"path":153,"stem":154,"icon":148},"\u002Fen\u002Fstorage\u002Foverview","en\u002F15.storage\u002F1.overview",{"title":156,"path":157,"stem":158},"Disk API","\u002Fen\u002Fstorage\u002Fdisk-api","en\u002F15.storage\u002F2.disk-api",{"title":160,"path":161,"stem":162,"icon":148},"Memory and local disk","\u002Fen\u002Fstorage\u002Flocal","en\u002F15.storage\u002F3.local",{"title":164,"path":165,"stem":166,"icon":167},"S3","\u002Fen\u002Fstorage\u002Fs3","en\u002F15.storage\u002F4.s3","i-lucide-cloud",{"title":169,"path":170,"stem":171,"icon":172},"Testing","\u002Fen\u002Fstorage\u002Ftesting","en\u002F15.storage\u002F5.testing","i-lucide-flask-conical",{"title":174,"icon":175,"path":176,"stem":177,"children":178,"page":44},"telemetry","i-lucide-activity","\u002Fen\u002Ftelemetry","en\u002F16.telemetry",[179,182,187,192],{"title":52,"path":180,"stem":181,"icon":175},"\u002Fen\u002Ftelemetry\u002Foverview","en\u002F16.telemetry\u002F1.overview",{"title":183,"path":184,"stem":185,"icon":186},"Logging","\u002Fen\u002Ftelemetry\u002Flogging","en\u002F16.telemetry\u002F2.logging","i-lucide-scroll-text",{"title":188,"path":189,"stem":190,"icon":191},"OpenTelemetry","\u002Fen\u002Ftelemetry\u002Fopentelemetry","en\u002F16.telemetry\u002F3.opentelemetry","i-lucide-radar",{"title":193,"path":194,"stem":195,"icon":196},"Sentry","\u002Fen\u002Ftelemetry\u002Fsentry","en\u002F16.telemetry\u002F4.sentry","i-lucide-bug",{"title":198,"icon":199,"path":200,"stem":201,"children":202,"page":44},"Reference","i-lucide-book-open","\u002Fen\u002Freference","en\u002F17.reference",[203,208],{"title":204,"path":205,"stem":206,"icon":207},"Configuration","\u002Fen\u002Freference\u002Fconfiguration","en\u002F17.reference\u002F1.configuration","i-lucide-settings",{"title":209,"path":210,"stem":211,"icon":212},"External dependencies","\u002Fen\u002Freference\u002Fdependencies","en\u002F17.reference\u002F2.dependencies","i-lucide-package",{"title":214,"icon":215,"path":216,"stem":217,"children":218,"page":44},"Concepts","i-lucide-lightbulb","\u002Fen\u002Fconcepts","en\u002F2.concepts",[219,223,228,232,237],{"title":220,"path":221,"stem":222,"icon":47},"Architecture","\u002Fen\u002Fconcepts\u002Farchitecture","en\u002F2.concepts\u002F1.architecture",{"title":224,"path":225,"stem":226,"icon":227},"Error model","\u002Fen\u002Fconcepts\u002Ferror-model","en\u002F2.concepts\u002F2.error-model","i-lucide-shield-alert",{"title":204,"path":229,"stem":230,"icon":231},"\u002Fen\u002Fconcepts\u002Fconfiguration","en\u002F2.concepts\u002F3.configuration","i-lucide-settings-2",{"title":233,"path":234,"stem":235,"icon":236},"Codegen pipeline","\u002Fen\u002Fconcepts\u002Fcodegen-pipeline","en\u002F2.concepts\u002F4.codegen-pipeline","i-lucide-file-json",{"title":238,"path":239,"stem":240,"icon":241},"Design patterns","\u002Fen\u002Fconcepts\u002Fdesign-patterns","en\u002F2.concepts\u002F5.design-patterns","i-lucide-puzzle",{"title":243,"icon":244,"path":245,"stem":246,"children":247,"page":44},"Kit","i-lucide-box","\u002Fen\u002Fkit","en\u002F3.kit",[248,251,256,261,266,270,275],{"title":52,"path":249,"stem":250,"icon":244},"\u002Fen\u002Fkit\u002Foverview","en\u002F3.kit\u002F1.overview",{"title":252,"path":253,"stem":254,"icon":255},"Application lifecycle","\u002Fen\u002Fkit\u002Fapp","en\u002F3.kit\u002F2.app","i-lucide-power",{"title":257,"path":258,"stem":259,"icon":260},"Server","\u002Fen\u002Fkit\u002Fserver","en\u002F3.kit\u002F3.server","i-lucide-server",{"title":262,"path":263,"stem":264,"icon":265},"Worker","\u002Fen\u002Fkit\u002Fworker","en\u002F3.kit\u002F4.worker","i-lucide-cog",{"title":267,"path":268,"stem":269,"icon":70},"Realtime","\u002Fen\u002Fkit\u002Frealtime","en\u002F3.kit\u002F5.realtime",{"title":271,"path":272,"stem":273,"icon":274},"Migrations","\u002Fen\u002Fkit\u002Fmigrations","en\u002F3.kit\u002F6.migrations","i-lucide-file-stack",{"title":276,"path":277,"stem":278,"icon":111},"Social login","\u002Fen\u002Fkit\u002Fsocial-login","en\u002F3.kit\u002F7.social-login",{"title":280,"icon":281,"path":282,"stem":283,"children":284,"page":44},"CLI Reference","i-lucide-terminal","\u002Fen\u002Fcli","en\u002F4.cli",[285,288,293,298,303,308,313,318,323,328,333,337,342,347,351],{"title":52,"path":286,"stem":287,"icon":281},"\u002Fen\u002Fcli\u002Foverview","en\u002F4.cli\u002F1.overview",{"title":289,"path":290,"stem":291,"icon":292},"add db","\u002Fen\u002Fcli\u002Fadd-db","en\u002F4.cli\u002F10.add-db","i-lucide-database-zap",{"title":294,"path":295,"stem":296,"icon":297},"add compose \u002F add docker","\u002Fen\u002Fcli\u002Fadd-compose","en\u002F4.cli\u002F11.add-compose","i-lucide-container",{"title":299,"path":300,"stem":301,"icon":302},"add mail","\u002Fen\u002Fcli\u002Fadd-mail","en\u002F4.cli\u002F12.add-mail","i-lucide-mail-plus",{"title":304,"path":305,"stem":306,"icon":307},"add notification","\u002Fen\u002Fcli\u002Fadd-notification","en\u002F4.cli\u002F13.add-notification","i-lucide-bell-plus",{"title":309,"path":310,"stem":311,"icon":312},"add realtime","\u002Fen\u002Fcli\u002Fadd-realtime","en\u002F4.cli\u002F14.add-realtime","i-lucide-radio-tower",{"title":314,"path":315,"stem":316,"icon":317},"add seeder","\u002Fen\u002Fcli\u002Fadd-seeder","en\u002F4.cli\u002F15.add-seeder","i-lucide-sprout",{"title":319,"path":320,"stem":321,"icon":322},"new project","\u002Fen\u002Fcli\u002Fnew-project","en\u002F4.cli\u002F2.new-project","i-lucide-folder-plus",{"title":324,"path":325,"stem":326,"icon":327},"new module","\u002Fen\u002Fcli\u002Fnew-module","en\u002F4.cli\u002F3.new-module","i-lucide-blocks",{"title":329,"path":330,"stem":331,"icon":332},"add surface","\u002Fen\u002Fcli\u002Fadd-surface","en\u002F4.cli\u002F4.add-surface","i-lucide-layers-2",{"title":334,"path":335,"stem":336,"icon":244},"add core","\u002Fen\u002Fcli\u002Fadd-core","en\u002F4.cli\u002F5.add-core",{"title":338,"path":339,"stem":340,"icon":341},"add handler","\u002Fen\u002Fcli\u002Fadd-handler","en\u002F4.cli\u002F6.add-handler","i-lucide-webhook",{"title":343,"path":344,"stem":345,"icon":346},"new migration","\u002Fen\u002Fcli\u002Fnew-migration","en\u002F4.cli\u002F7.new-migration","i-lucide-file-plus",{"title":348,"path":349,"stem":350,"icon":265},"worker generators","\u002Fen\u002Fcli\u002Fworker-generators","en\u002F4.cli\u002F8.worker-generators",{"title":352,"path":353,"stem":354,"icon":355},"upgrade templates","\u002Fen\u002Fcli\u002Fupgrade-templates","en\u002F4.cli\u002F9.upgrade-templates","i-lucide-refresh-cw",{"title":357,"icon":227,"path":358,"stem":359,"children":360,"page":44},"errs","\u002Fen\u002Ferrs","en\u002F5.errs",[361,364,368],{"title":52,"path":362,"stem":363,"icon":227},"\u002Fen\u002Ferrs\u002Foverview","en\u002F5.errs\u002F1.overview",{"title":365,"path":366,"stem":367},"API","\u002Fen\u002Ferrs\u002Fapi","en\u002F5.errs\u002F2.api",{"title":369,"path":370,"stem":371,"icon":227},"Integration","\u002Fen\u002Ferrs\u002Fintegration","en\u002F5.errs\u002F3.integration",{"title":373,"icon":231,"path":374,"stem":375,"children":376,"page":44},"envconf","\u002Fen\u002Fenvconf","en\u002F6.envconf",[377,380],{"title":52,"path":378,"stem":379,"icon":231},"\u002Fen\u002Fenvconf\u002Foverview","en\u002F6.envconf\u002F1.overview",{"title":381,"path":382,"stem":383},"Recipes","\u002Fen\u002Fenvconf\u002Frecipes","en\u002F6.envconf\u002F2.recipes",{"title":385,"icon":386,"path":387,"stem":388,"children":389,"page":44},"httperr","i-lucide-globe","\u002Fen\u002Fhttperr","en\u002F7.httperr",[390,393,398],{"title":52,"path":391,"stem":392,"icon":386},"\u002Fen\u002Fhttperr\u002Foverview","en\u002F7.httperr\u002F1.overview",{"title":394,"path":395,"stem":396,"icon":397},"Error responses","\u002Fen\u002Fhttperr\u002Ferror-responses","en\u002F7.httperr\u002F2.error-responses","i-lucide-octagon-alert",{"title":399,"path":400,"stem":401,"icon":402},"Validation","\u002Fen\u002Fhttperr\u002Fvalidation","en\u002F7.httperr\u002F3.validation","i-lucide-badge-check",{"title":404,"icon":405,"path":406,"stem":407,"children":408,"page":44},"dbx","i-lucide-database","\u002Fen\u002Fdbx","en\u002F8.dbx",[409,412,416,420,425],{"title":52,"path":410,"stem":411,"icon":405},"\u002Fen\u002Fdbx\u002Foverview","en\u002F8.dbx\u002F1.overview",{"title":413,"path":414,"stem":415,"icon":106},"pgx","\u002Fen\u002Fdbx\u002Fpg","en\u002F8.dbx\u002F2.pg",{"title":417,"path":418,"stem":419,"icon":47},"bun","\u002Fen\u002Fdbx\u002Fbunx","en\u002F8.dbx\u002F3.bunx",{"title":421,"path":422,"stem":423,"icon":424},"Transactions","\u002Fen\u002Fdbx\u002Ftransactions","en\u002F8.dbx\u002F4.transactions","i-lucide-git-merge",{"title":426,"path":427,"stem":428,"icon":317},"Seeders","\u002Fen\u002Fdbx\u002Fseed","en\u002F8.dbx\u002F5.seed",{"title":430,"icon":55,"path":431,"stem":432,"children":433,"page":44},"paginate","\u002Fen\u002Fpaginate","en\u002F9.paginate",[434,437],{"title":52,"path":435,"stem":436,"icon":55},"\u002Fen\u002Fpaginate\u002Foverview","en\u002F9.paginate\u002F1.overview",{"title":438,"path":439,"stem":440,"icon":19},"Cursor pagination","\u002Fen\u002Fpaginate\u002Fcursor","en\u002F9.paginate\u002F2.cursor",{"id":442,"title":421,"body":443,"description":1651,"extension":1652,"links":1653,"meta":1654,"navigation":1655,"path":422,"seo":1656,"stem":423,"__hash__":1657},"docs_en\u002Fen\u002F8.dbx\u002F4.transactions.md",{"type":444,"value":445,"toc":1642},"minimark",[446,466,471,799,828,832,847,862,1331,1349,1392,1410,1414,1424,1463,1469,1479,1483,1496,1532,1539,1543,1567,1571,1620,1624,1638],[447,448,449,450,453,454,458,459,462,463,465],"p",{},"The hard requirement behind ",[451,452,404],"code",{},": ",[455,456,457],"strong",{},"a service must be able to call multiple repositories inside one database transaction, without any repository knowing about transactions."," This page covers the pattern in depth: the pgx-specific mechanics live on the ",[460,461,413],"a",{"href":414}," page, the bun variant on the ",[460,464,417],{"href":418}," page.",[467,468,470],"h2",{"id":469},"the-three-pieces","The three pieces",[472,473,478],"pre",{"className":474,"code":475,"language":476,"meta":477,"style":477},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F 1. The contract the service holds (dbx package, driver-independent):\ntype Transactor interface {\n    WithinTransaction(ctx context.Context, fn func(ctx context.Context) error) error\n}\n\n\u002F\u002F 2. The query surface the repositories hold (the pgx variant here):\ntype DBTX interface {\n    Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error)\n    Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)\n    QueryRow(ctx context.Context, sql string, args ...any) pgx.Row\n}\n\n\u002F\u002F 3. The executor connecting the two: every call first looks for a\n\u002F\u002F    transaction in the context; when there is none, it runs on the pool.\ndb := pg.NewDB(pool)\ntx := pg.NewTransactor(pool)\n","go","",[451,479,480,489,506,558,564,571,577,589,644,691,731,736,741,747,753,778],{"__ignoreMap":477},[481,482,485],"span",{"class":483,"line":484},"line",1,[481,486,488],{"class":487},"sHwdD","\u002F\u002F 1. The contract the service holds (dbx package, driver-independent):\n",[481,490,492,496,500,503],{"class":483,"line":491},2,[481,493,495],{"class":494},"sMK4o","type",[481,497,499],{"class":498},"sBMFI"," Transactor",[481,501,502],{"class":494}," interface",[481,504,505],{"class":494}," {\n",[481,507,509,513,516,520,523,526,529,532,535,538,540,542,544,546,549,553,555],{"class":483,"line":508},3,[481,510,512],{"class":511},"s2Zo4","    WithinTransaction",[481,514,515],{"class":494},"(",[481,517,519],{"class":518},"sHdIc","ctx",[481,521,522],{"class":498}," context",[481,524,525],{"class":494},".",[481,527,528],{"class":498},"Context",[481,530,531],{"class":494},",",[481,533,534],{"class":518}," fn",[481,536,537],{"class":494}," func(",[481,539,519],{"class":518},[481,541,522],{"class":498},[481,543,525],{"class":494},[481,545,528],{"class":498},[481,547,548],{"class":494},")",[481,550,552],{"class":551},"spNyl"," error",[481,554,548],{"class":494},[481,556,557],{"class":551}," error\n",[481,559,561],{"class":483,"line":560},4,[481,562,563],{"class":494},"}\n",[481,565,567],{"class":483,"line":566},5,[481,568,570],{"emptyLinePlaceholder":569},true,"\n",[481,572,574],{"class":483,"line":573},6,[481,575,576],{"class":487},"\u002F\u002F 2. The query surface the repositories hold (the pgx variant here):\n",[481,578,580,582,585,587],{"class":483,"line":579},7,[481,581,495],{"class":494},[481,583,584],{"class":498}," DBTX",[481,586,502],{"class":494},[481,588,505],{"class":494},[481,590,592,595,597,599,601,603,605,607,610,613,615,618,621,624,626,629,632,634,637,639,641],{"class":483,"line":591},8,[481,593,594],{"class":511},"    Exec",[481,596,515],{"class":494},[481,598,519],{"class":518},[481,600,522],{"class":498},[481,602,525],{"class":494},[481,604,528],{"class":498},[481,606,531],{"class":494},[481,608,609],{"class":518}," sql",[481,611,612],{"class":551}," string",[481,614,531],{"class":494},[481,616,617],{"class":518}," args",[481,619,620],{"class":494}," ...",[481,622,623],{"class":498},"any",[481,625,548],{"class":494},[481,627,628],{"class":494}," (",[481,630,631],{"class":498},"pgconn",[481,633,525],{"class":494},[481,635,636],{"class":498},"CommandTag",[481,638,531],{"class":494},[481,640,552],{"class":551},[481,642,643],{"class":494},")\n",[481,645,647,650,652,654,656,658,660,662,664,666,668,670,672,674,676,678,680,682,685,687,689],{"class":483,"line":646},9,[481,648,649],{"class":511},"    Query",[481,651,515],{"class":494},[481,653,519],{"class":518},[481,655,522],{"class":498},[481,657,525],{"class":494},[481,659,528],{"class":498},[481,661,531],{"class":494},[481,663,609],{"class":518},[481,665,612],{"class":551},[481,667,531],{"class":494},[481,669,617],{"class":518},[481,671,620],{"class":494},[481,673,623],{"class":498},[481,675,548],{"class":494},[481,677,628],{"class":494},[481,679,413],{"class":498},[481,681,525],{"class":494},[481,683,684],{"class":498},"Rows",[481,686,531],{"class":494},[481,688,552],{"class":551},[481,690,643],{"class":494},[481,692,694,697,699,701,703,705,707,709,711,713,715,717,719,721,723,726,728],{"class":483,"line":693},10,[481,695,696],{"class":511},"    QueryRow",[481,698,515],{"class":494},[481,700,519],{"class":518},[481,702,522],{"class":498},[481,704,525],{"class":494},[481,706,528],{"class":498},[481,708,531],{"class":494},[481,710,609],{"class":518},[481,712,612],{"class":551},[481,714,531],{"class":494},[481,716,617],{"class":518},[481,718,620],{"class":494},[481,720,623],{"class":498},[481,722,548],{"class":494},[481,724,725],{"class":498}," pgx",[481,727,525],{"class":494},[481,729,730],{"class":498},"Row\n",[481,732,734],{"class":483,"line":733},11,[481,735,563],{"class":494},[481,737,739],{"class":483,"line":738},12,[481,740,570],{"emptyLinePlaceholder":569},[481,742,744],{"class":483,"line":743},13,[481,745,746],{"class":487},"\u002F\u002F 3. The executor connecting the two: every call first looks for a\n",[481,748,750],{"class":483,"line":749},14,[481,751,752],{"class":487},"\u002F\u002F    transaction in the context; when there is none, it runs on the pool.\n",[481,754,756,760,763,766,768,771,773,776],{"class":483,"line":755},15,[481,757,759],{"class":758},"sTEyZ","db ",[481,761,762],{"class":494},":=",[481,764,765],{"class":758}," pg",[481,767,525],{"class":494},[481,769,770],{"class":511},"NewDB",[481,772,515],{"class":494},[481,774,775],{"class":758},"pool",[481,777,643],{"class":494},[481,779,781,784,786,788,790,793,795,797],{"class":483,"line":780},16,[481,782,783],{"class":758},"tx ",[481,785,762],{"class":494},[481,787,765],{"class":758},[481,789,525],{"class":494},[481,791,792],{"class":511},"NewTransactor",[481,794,515],{"class":494},[481,796,775],{"class":758},[481,798,643],{"class":494},[447,800,801,804,805,808,809,812,813,815,816,819,820,823,824,827],{},[451,802,803],{},"WithinTransaction"," opens a transaction, injects it into the context, and calls ",[451,806,807],{},"fn"," with the ",[455,810,811],{},"modified context",". Inside ",[451,814,807],{},", every repository call passes that same context along. ",[451,817,818],{},"pg.DB"," (or, on bun, the ",[451,821,822],{},"bunx.From","\u002F",[451,825,826],{},"bunx.Conn"," resolvers) fishes the transaction out of it and runs the query on it. Repository signatures do not change; only the executor knows the transaction exists.",[467,829,831],{"id":830},"the-placeorder-service","The PlaceOrder service",[447,833,834,835,838,839,842,843,846],{},"The best example of the pattern is the ",[460,836,837],{"href":41},"shop","'s order placement. Three writes must happen ",[455,840,841],{},"atomically",": inserting the order, decrementing the stock, and the outbox row for the ",[451,844,845],{},"orderPlaced"," event. If any of them fails, none may survive: an order must not exist without its stock deduction, and no confirmation email may go out for an order that was never recorded.",[447,848,849,850,853,854,857,858,861],{},"The full method lives in the module's shared ",[451,851,852],{},"core\u002F"," package (",[451,855,856],{},"internal\u002Fmodules\u002Fshop\u002Fcore\u002F","), called from the ",[451,859,860],{},"api"," surface's service seam:",[472,863,865],{"className":474,"code":864,"language":476,"meta":477,"style":477},"type OrderService struct {\n    orders     *repository.OrderRepository\n    products   *repository.ProductRepository\n    tx         dbx.Transactor\n    dispatcher events.Dispatcher\n}\n\nfunc (s *OrderService) PlaceOrder(ctx context.Context, in PlaceOrderInput) (*Order, error) {\n    var order *Order\n    err := s.tx.WithinTransaction(ctx, func(ctx context.Context) error {\n        var err error\n        order, err = s.orders.Insert(ctx, in)                     \u002F\u002F 1. order\n        if err != nil {\n            return err\n        }\n        if err := s.products.DecrementStock(ctx, in.ProductID, in.Qty); err != nil {\n            return err                                            \u002F\u002F 2. stock\n        }\n        return s.dispatcher.Dispatch(ctx, shopevents.OrderPlaced{ \u002F\u002F 3. outbox row\n            OrderID: order.ID,\n            Email:   in.Email,\n        })\n    })\n    if err != nil {\n        return nil, err\n    }\n    return order, nil\n}\n",[451,866,867,879,895,909,921,933,937,941,995,1008,1049,1060,1097,1113,1121,1126,1179,1190,1195,1233,1253,1271,1277,1283,1297,1307,1313,1326],{"__ignoreMap":477},[481,868,869,871,874,877],{"class":483,"line":484},[481,870,495],{"class":494},[481,872,873],{"class":498}," OrderService",[481,875,876],{"class":494}," struct",[481,878,505],{"class":494},[481,880,881,884,887,890,892],{"class":483,"line":491},[481,882,883],{"class":758},"    orders     ",[481,885,886],{"class":494},"*",[481,888,889],{"class":498},"repository",[481,891,525],{"class":494},[481,893,894],{"class":498},"OrderRepository\n",[481,896,897,900,902,904,906],{"class":483,"line":508},[481,898,899],{"class":758},"    products   ",[481,901,886],{"class":494},[481,903,889],{"class":498},[481,905,525],{"class":494},[481,907,908],{"class":498},"ProductRepository\n",[481,910,911,914,916,918],{"class":483,"line":560},[481,912,913],{"class":758},"    tx         ",[481,915,404],{"class":498},[481,917,525],{"class":494},[481,919,920],{"class":498},"Transactor\n",[481,922,923,926,928,930],{"class":483,"line":566},[481,924,925],{"class":758},"    dispatcher ",[481,927,61],{"class":498},[481,929,525],{"class":494},[481,931,932],{"class":498},"Dispatcher\n",[481,934,935],{"class":483,"line":573},[481,936,563],{"class":494},[481,938,939],{"class":483,"line":579},[481,940,570],{"emptyLinePlaceholder":569},[481,942,943,946,948,951,953,956,958,961,963,965,967,969,971,973,976,979,981,984,987,989,991,993],{"class":483,"line":591},[481,944,945],{"class":494},"func",[481,947,628],{"class":494},[481,949,950],{"class":518},"s ",[481,952,886],{"class":494},[481,954,955],{"class":498},"OrderService",[481,957,548],{"class":494},[481,959,960],{"class":511}," PlaceOrder",[481,962,515],{"class":494},[481,964,519],{"class":518},[481,966,522],{"class":498},[481,968,525],{"class":494},[481,970,528],{"class":498},[481,972,531],{"class":494},[481,974,975],{"class":518}," in",[481,977,978],{"class":498}," PlaceOrderInput",[481,980,548],{"class":494},[481,982,983],{"class":494}," (*",[481,985,986],{"class":498},"Order",[481,988,531],{"class":494},[481,990,552],{"class":551},[481,992,548],{"class":494},[481,994,505],{"class":494},[481,996,997,1000,1003,1005],{"class":483,"line":646},[481,998,999],{"class":494},"    var",[481,1001,1002],{"class":758}," order ",[481,1004,886],{"class":494},[481,1006,1007],{"class":498},"Order\n",[481,1009,1010,1013,1015,1018,1020,1023,1025,1027,1029,1031,1033,1035,1037,1039,1041,1043,1045,1047],{"class":483,"line":693},[481,1011,1012],{"class":758},"    err ",[481,1014,762],{"class":494},[481,1016,1017],{"class":758}," s",[481,1019,525],{"class":494},[481,1021,1022],{"class":758},"tx",[481,1024,525],{"class":494},[481,1026,803],{"class":511},[481,1028,515],{"class":494},[481,1030,519],{"class":758},[481,1032,531],{"class":494},[481,1034,537],{"class":494},[481,1036,519],{"class":518},[481,1038,522],{"class":498},[481,1040,525],{"class":494},[481,1042,528],{"class":498},[481,1044,548],{"class":494},[481,1046,552],{"class":551},[481,1048,505],{"class":494},[481,1050,1051,1054,1057],{"class":483,"line":733},[481,1052,1053],{"class":494},"        var",[481,1055,1056],{"class":758}," err ",[481,1058,1059],{"class":551},"error\n",[481,1061,1062,1065,1067,1069,1072,1074,1076,1079,1081,1084,1086,1088,1090,1092,1094],{"class":483,"line":738},[481,1063,1064],{"class":758},"        order",[481,1066,531],{"class":494},[481,1068,1056],{"class":758},[481,1070,1071],{"class":494},"=",[481,1073,1017],{"class":758},[481,1075,525],{"class":494},[481,1077,1078],{"class":758},"orders",[481,1080,525],{"class":494},[481,1082,1083],{"class":511},"Insert",[481,1085,515],{"class":494},[481,1087,519],{"class":758},[481,1089,531],{"class":494},[481,1091,975],{"class":758},[481,1093,548],{"class":494},[481,1095,1096],{"class":487},"                     \u002F\u002F 1. order\n",[481,1098,1099,1103,1105,1108,1111],{"class":483,"line":743},[481,1100,1102],{"class":1101},"s7zQu","        if",[481,1104,1056],{"class":758},[481,1106,1107],{"class":494},"!=",[481,1109,1110],{"class":494}," nil",[481,1112,505],{"class":494},[481,1114,1115,1118],{"class":483,"line":749},[481,1116,1117],{"class":1101},"            return",[481,1119,1120],{"class":758}," err\n",[481,1122,1123],{"class":483,"line":755},[481,1124,1125],{"class":494},"        }\n",[481,1127,1128,1130,1132,1134,1136,1138,1141,1143,1146,1148,1150,1152,1154,1156,1159,1161,1163,1165,1168,1171,1173,1175,1177],{"class":483,"line":780},[481,1129,1102],{"class":1101},[481,1131,1056],{"class":758},[481,1133,762],{"class":494},[481,1135,1017],{"class":758},[481,1137,525],{"class":494},[481,1139,1140],{"class":758},"products",[481,1142,525],{"class":494},[481,1144,1145],{"class":511},"DecrementStock",[481,1147,515],{"class":494},[481,1149,519],{"class":758},[481,1151,531],{"class":494},[481,1153,975],{"class":758},[481,1155,525],{"class":494},[481,1157,1158],{"class":758},"ProductID",[481,1160,531],{"class":494},[481,1162,975],{"class":758},[481,1164,525],{"class":494},[481,1166,1167],{"class":758},"Qty",[481,1169,1170],{"class":494},");",[481,1172,1056],{"class":758},[481,1174,1107],{"class":494},[481,1176,1110],{"class":494},[481,1178,505],{"class":494},[481,1180,1182,1184,1187],{"class":483,"line":1181},17,[481,1183,1117],{"class":1101},[481,1185,1186],{"class":758}," err                                            ",[481,1188,1189],{"class":487},"\u002F\u002F 2. stock\n",[481,1191,1193],{"class":483,"line":1192},18,[481,1194,1125],{"class":494},[481,1196,1198,1201,1203,1205,1208,1210,1213,1215,1217,1219,1222,1224,1227,1230],{"class":483,"line":1197},19,[481,1199,1200],{"class":1101},"        return",[481,1202,1017],{"class":758},[481,1204,525],{"class":494},[481,1206,1207],{"class":758},"dispatcher",[481,1209,525],{"class":494},[481,1211,1212],{"class":511},"Dispatch",[481,1214,515],{"class":494},[481,1216,519],{"class":758},[481,1218,531],{"class":494},[481,1220,1221],{"class":498}," shopevents",[481,1223,525],{"class":494},[481,1225,1226],{"class":498},"OrderPlaced",[481,1228,1229],{"class":494},"{",[481,1231,1232],{"class":487}," \u002F\u002F 3. outbox row\n",[481,1234,1236,1239,1242,1245,1247,1250],{"class":483,"line":1235},20,[481,1237,1238],{"class":758},"            OrderID",[481,1240,1241],{"class":494},":",[481,1243,1244],{"class":758}," order",[481,1246,525],{"class":494},[481,1248,1249],{"class":758},"ID",[481,1251,1252],{"class":494},",\n",[481,1254,1256,1259,1261,1264,1266,1269],{"class":483,"line":1255},21,[481,1257,1258],{"class":758},"            Email",[481,1260,1241],{"class":494},[481,1262,1263],{"class":758},"   in",[481,1265,525],{"class":494},[481,1267,1268],{"class":758},"Email",[481,1270,1252],{"class":494},[481,1272,1274],{"class":483,"line":1273},22,[481,1275,1276],{"class":494},"        })\n",[481,1278,1280],{"class":483,"line":1279},23,[481,1281,1282],{"class":494},"    })\n",[481,1284,1286,1289,1291,1293,1295],{"class":483,"line":1285},24,[481,1287,1288],{"class":1101},"    if",[481,1290,1056],{"class":758},[481,1292,1107],{"class":494},[481,1294,1110],{"class":494},[481,1296,505],{"class":494},[481,1298,1300,1302,1305],{"class":483,"line":1299},25,[481,1301,1200],{"class":1101},[481,1303,1304],{"class":494}," nil,",[481,1306,1120],{"class":758},[481,1308,1310],{"class":483,"line":1309},26,[481,1311,1312],{"class":494},"    }\n",[481,1314,1316,1319,1321,1323],{"class":483,"line":1315},27,[481,1317,1318],{"class":1101},"    return",[481,1320,1244],{"class":758},[481,1322,531],{"class":494},[481,1324,1325],{"class":494}," nil\n",[481,1327,1329],{"class":483,"line":1328},28,[481,1330,563],{"class":494},[447,1332,515,1333,1336,1337,1340,1341,1344,1345,1348],{},[451,1334,1335],{},"shopevents"," is the alias of the module's own events package: ",[451,1338,1339],{},"internal\u002Fmodules\u002Fshop\u002Fevents",", generated by ",[451,1342,1343],{},"add event",".) All three calls resolve the same ",[451,1346,1347],{},"pgx.Tx"," from the context:",[1350,1351,1352,1369],"ul",{},[1353,1354,1355,1358,1359,1362,1363,1368],"li",{},[451,1356,1357],{},"orders.Insert"," and ",[451,1360,1361],{},"products.DecrementStock"," through ",[460,1364,1366],{"href":1365},"\u002Fen\u002Fdbx\u002Fpg#db--the-executor-that-watches-the-context",[451,1367,818],{},";",[1353,1370,1371,1372,1374,1375,1380,1381,1383,1384,1387,1388,1391],{},"the ",[451,1373,1207],{}," is an ",[460,1376,1377],{"href":73},[451,1378,1379],{},"outbox.NewDispatcher"," whose store also writes through ",[451,1382,818],{},". The event lands in the ",[451,1385,1386],{},"outbox_events"," table ",[455,1389,1390],{},"in the same transaction"," as the business writes. On commit they become visible together; on rollback the event never existed. That is the essence of the transactional outbox.",[447,1393,1394,1395,1397,1398,1401,1402,1404,1405,1409],{},"If ",[451,1396,1145],{}," returns an ",[451,1399,1400],{},"insufficient_stock"," error, the already-executed ",[451,1403,1083],{}," rolls back with it, and the event is never delivered. The error maps to a problem+json response through the ",[460,1406,1407],{"href":225},[451,1408,357],{}," chain.",[467,1411,1413],{"id":1412},"semantics","Semantics",[447,1415,1416,1417,1358,1420,1423],{},"The guarantees, pinned down by dbx's own integration tests (",[451,1418,1419],{},"go test -tags=integration .\u002Fdbx\u002Fpg\u002F",[451,1421,1422],{},".\u002Fdbx\u002Fbunx\u002F",", against a real PostgreSQL):",[1350,1425,1426,1438,1444,1454],{},[1353,1427,1428,1437],{},[455,1429,1430,1431,1433,1434,525],{},"Commit only when ",[451,1432,807],{}," returns ",[451,1435,1436],{},"nil"," Any error rolls back, and propagates to the caller unchanged.",[1353,1439,1440,1443],{},[455,1441,1442],{},"A panic rolls back and is re-raised."," A handler panic cannot leave a transaction dangling.",[1353,1445,1446,1449,1450,1453],{},[455,1447,1448],{},"Rollback survives a cancelled context:"," the cleanup runs with ",[451,1451,1452],{},"context.WithoutCancel",", so a request timeout does not prevent an orderly rollback.",[1353,1455,1456,1462],{},[455,1457,1458,1459,1461],{},"A nested ",[451,1460,803],{}," joins"," the outer transaction: flat nesting, no savepoints. The outermost call always owns commit\u002Frollback: if the inner block succeeds but the outer one fails afterwards, the inner writes roll back too.",[447,1464,1465,1466,1468],{},"The practical consequence of flat nesting: a service method can freely call another service method that itself uses ",[451,1467,803],{},", the inner call becomes part of the outer transaction without noticing. There is no \"are we already in a transaction?\" logic to write.",[1470,1471,1472,1473,1476,1477,525],"warning",{},"There are no savepoints: an error in the inner block rolls back the ",[455,1474,1475],{},"entire"," transaction, not just the inner writes. If you need partial rollback, that is a separate transaction: run it outside the ",[451,1478,803],{},[467,1480,1482],{"id":1481},"why-the-context-and-not-a-parameter","Why the context, and not a parameter?",[447,1484,1485,1486,1488,1489,1491,1492,1495],{},"The classic alternative is passing the transaction by hand: every repository method takes a ",[451,1487,1022],{}," parameter, or exists in two variants (",[451,1490,1083],{}," \u002F ",[451,1493,1494],{},"InsertTx","). That hurts in three places:",[1497,1498,1499,1513,1522],"ol",{},[1353,1500,1501,1504,1505,1508,1509,1512],{},[455,1502,1503],{},"Every signature gets infected."," ",[451,1506,1507],{},"ListProducts(ctx, params)"," becomes ",[451,1510,1511],{},"ListProducts(ctx, tx, params)",", even for calls that will never have a transaction.",[1353,1514,1515,1518,1519,1521],{},[455,1516,1517],{},"The layers collapse into each other."," The service would have to import and pass around a driver type (",[451,1520,1347],{},"), even though the service layer's job is business logic, not connection management.",[1353,1523,1524,1527,1528,1531],{},[455,1525,1526],{},"Cross-cutting code is left out."," The ",[460,1529,1530],{"href":73},"outbox dispatcher"," is kit code: it cannot know about your repositories' transaction parameter. From the context, though, it reads the transaction exactly like your own repos do.",[447,1533,1534,1535,1538],{},"Context carrying is precisely the kind of implicit state ",[451,1536,1537],{},"context.Context"," is for: request-scoped, travelling down the call chain, cross-cutting. The Go standard library carries traces, deadlines and the cancel signal the same way.",[467,1540,1542],{"id":1541},"implementation-independence","Implementation independence",[447,1544,1545,1546,1549,1550,1553,1554,1557,1558,1561,1562,1566],{},"Not a single line of the service code above is pgx-specific: it depends on ",[451,1547,1548],{},"dbx.Transactor"," and your own repository interfaces. In a ",[451,1551,1552],{},"--db bun"," project the same method compiles and runs unchanged. ",[451,1555,1556],{},"main.go"," injects ",[451,1559,1560],{},"bunx.NewTransactor(bunDB)",", and the repositories' internals use the ",[460,1563,1565],{"href":1564},"\u002Fen\u002Fdbx\u002Fbunx#repositories","bun resolvers",". Drawing the transaction boundary belongs to the service layer; what carries the transaction underneath is a wiring detail.",[467,1568,1570],{"id":1569},"one-transactor-per-connection","One transactor per connection",[447,1572,1573,1574,1577,1578,1580,1581,1585,1586,628,1588,1591,1592,1595,1596,1599,1600,1603,1604,1606,1607,1610,1611,1613,1614,1617,1618,525],{},"A ",[451,1575,1576],{},"Transactor"," is scoped to a single connection: ",[451,1579,803],{}," begins a transaction on the pool it wraps and injects it into the context under that implementation's own key. Repositories bound to a ",[460,1582,1584],{"href":1583},"\u002Fen\u002Fdbx\u002Foverview#named-connections","named connection"," receive a different ",[451,1587,1576],{},[451,1589,1590],{},"\u003CName>Transactor"," on ",[451,1593,1594],{},"Dependencies",") than the default one: calling the default ",[451,1597,1598],{},"Transactor.WithinTransaction"," around a call to a named connection's repository does ",[455,1601,1602],{},"not"," put that call in the same transaction; it runs on its own connection, uncoordinated. This is also why a connection's connector (pgx or bun) cannot vary per call: a ",[451,1605,1347],{}," and a ",[451,1608,1609],{},"bun.Tx"," are two different types carried under two different context keys, so mixing them on one connection would defeat the whole point of ",[451,1612,1576],{},". If two repositories must commit or roll back together, they must sit behind the ",[455,1615,1616],{},"same"," connection's ",[451,1619,1576],{},[467,1621,1623],{"id":1622},"patterns-used","Patterns used",[447,1625,1626,1627,1630,1631,1634,1635,1637],{},"This page is the full write-up of the ",[460,1628,238],{"href":1629},"\u002Fen\u002Fconcepts\u002Fdesign-patterns#unit-of-work--transaction-in-context"," catalog's ",[455,1632,1633],{},"Unit of Work \u002F transaction in context"," entry: the ",[451,1636,1548],{}," interface and the panic-safe rollback logic, with code, live there.",[1639,1640,1641],"style",{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}",{"title":477,"searchDepth":491,"depth":491,"links":1643},[1644,1645,1646,1647,1648,1649,1650],{"id":469,"depth":491,"text":470},{"id":830,"depth":491,"text":831},{"id":1412,"depth":491,"text":1413},{"id":1481,"depth":491,"text":1482},{"id":1541,"depth":491,"text":1542},{"id":1569,"depth":491,"text":1570},{"id":1622,"depth":491,"text":1623},"Cross-repository transactions carried by the context, no plumbing.","md",null,{},{"icon":424},{"title":421,"description":1651},"n2XTCgxMXqSveyJtaHebOLPLD0FlAArXCvSWaASAKCc",[1659,1661],{"title":417,"path":418,"stem":419,"description":1660,"icon":47,"children":-1},"The optional bun adapter: query builder and struct models, with the Transactor contract unchanged.",{"title":426,"path":427,"stem":428,"description":1662,"icon":317,"children":-1},"Idempotent baseline data loading after migrations, built on first-or-create, with optional guards and multi-tenant support.",1785445881091]