[{"data":1,"prerenderedAt":2474},["ShallowReactive",2],{"navigation_docs":3,"-docs-capabilities-blob":493,"-docs-capabilities-blob-surround":2469},[4,46,135,218,257,292,355,377,420],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":45},"Getting Started","i-lucide-rocket","\u002Fdocs\u002Fgetting-started","docs\u002F01.getting-started",[10,15,20,25,30,35,40],{"title":11,"path":12,"stem":13,"icon":14},"What is Heximon?","\u002Fdocs\u002Fgetting-started\u002Fintroduction","docs\u002F01.getting-started\u002F01.introduction","i-lucide-info",{"title":16,"path":17,"stem":18,"icon":19},"Installation","\u002Fdocs\u002Fgetting-started\u002Finstallation","docs\u002F01.getting-started\u002F02.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":24},"Quick Start","\u002Fdocs\u002Fgetting-started\u002Fquick-start","docs\u002F01.getting-started\u002F03.quick-start","i-lucide-zap",{"title":26,"path":27,"stem":28,"icon":29},"Core Concepts","\u002Fdocs\u002Fgetting-started\u002Fcore-concepts","docs\u002F01.getting-started\u002F04.core-concepts","i-lucide-shapes",{"title":31,"path":32,"stem":33,"icon":34},"Examples","\u002Fdocs\u002Fgetting-started\u002Fexamples","docs\u002F01.getting-started\u002F05.examples","i-lucide-library",{"title":36,"path":37,"stem":38,"icon":39},"Where Next?","\u002Fdocs\u002Fgetting-started\u002Fwhere-next","docs\u002F01.getting-started\u002F06.where-next","i-lucide-signpost",{"title":41,"path":42,"stem":43,"icon":44},"Troubleshooting","\u002Fdocs\u002Fgetting-started\u002Ftroubleshooting","docs\u002F01.getting-started\u002F07.troubleshooting","i-lucide-life-buoy",false,{"title":47,"icon":48,"path":49,"stem":50,"children":51,"page":45},"Essentials","i-lucide-boxes","\u002Fdocs\u002Fessentials","docs\u002F02.essentials",[52,56,61,66,71,76,81,86,91,96,101,105,110,115,120,125,130],{"title":53,"path":54,"stem":55,"icon":48},"Modules & DI","\u002Fdocs\u002Fessentials\u002Fmodules-and-di","docs\u002F02.essentials\u002F01.modules-and-di",{"title":57,"path":58,"stem":59,"icon":60},"Controllers","\u002Fdocs\u002Fessentials\u002Fcontrollers","docs\u002F02.essentials\u002F02.controllers","i-lucide-route",{"title":62,"path":63,"stem":64,"icon":65},"Validation & DTOs","\u002Fdocs\u002Fessentials\u002Fvalidation","docs\u002F02.essentials\u002F03.validation","i-lucide-shield-check",{"title":67,"path":68,"stem":69,"icon":70},"Contracts","\u002Fdocs\u002Fessentials\u002Fcontracts","docs\u002F02.essentials\u002F04.contracts","i-lucide-file-signature",{"title":72,"path":73,"stem":74,"icon":75},"REST Client","\u002Fdocs\u002Fessentials\u002Fclient","docs\u002F02.essentials\u002F05.client","i-lucide-plug",{"title":77,"path":78,"stem":79,"icon":80},"Database","\u002Fdocs\u002Fessentials\u002Fdatabase","docs\u002F02.essentials\u002F06.database","i-lucide-database",{"title":82,"path":83,"stem":84,"icon":85},"Drizzle ORM","\u002Fdocs\u002Fessentials\u002Fdrizzle","docs\u002F02.essentials\u002F07.drizzle","i-lucide-database-zap",{"title":87,"path":88,"stem":89,"icon":90},"Persistence & Transactions","\u002Fdocs\u002Fessentials\u002Fpersistence","docs\u002F02.essentials\u002F08.persistence","i-lucide-database-backup",{"title":92,"path":93,"stem":94,"icon":95},"Migrations","\u002Fdocs\u002Fessentials\u002Fmigrations","docs\u002F02.essentials\u002F09.migrations","i-lucide-git-branch",{"title":97,"path":98,"stem":99,"icon":100},"Authentication","\u002Fdocs\u002Fessentials\u002Fauthentication","docs\u002F02.essentials\u002F10.authentication","i-lucide-key-round",{"title":102,"path":103,"stem":104,"icon":65},"Permissions & scopes","\u002Fdocs\u002Fessentials\u002Fpermissions","docs\u002F02.essentials\u002F11.permissions",{"title":106,"path":107,"stem":108,"icon":109},"Error Handling","\u002Fdocs\u002Fessentials\u002Ferrors","docs\u002F02.essentials\u002F12.errors","i-lucide-triangle-alert",{"title":111,"path":112,"stem":113,"icon":114},"Testing a Heximon App","\u002Fdocs\u002Fessentials\u002Ftesting","docs\u002F02.essentials\u002F13.testing","i-lucide-flask-conical",{"title":116,"path":117,"stem":118,"icon":119},"Test app harness","\u002Fdocs\u002Fessentials\u002Fcreatetestapp","docs\u002F02.essentials\u002F14.createtestapp","i-lucide-test-tube",{"title":121,"path":122,"stem":123,"icon":124},"Logging & Observability","\u002Fdocs\u002Fessentials\u002Flogging","docs\u002F02.essentials\u002F17.logging","i-lucide-scroll-text",{"title":126,"path":127,"stem":128,"icon":129},"Context & Lifecycle","\u002Fdocs\u002Fessentials\u002Fcontext-and-lifecycle","docs\u002F02.essentials\u002F18.context-and-lifecycle","i-lucide-radio",{"title":131,"path":132,"stem":133,"icon":134},"Project Structure","\u002Fdocs\u002Fessentials\u002Fproject-structure","docs\u002F02.essentials\u002F19.project-structure","i-lucide-folder-tree",{"title":136,"icon":137,"path":138,"stem":139,"children":140,"page":45},"Capabilities","i-lucide-sparkles","\u002Fdocs\u002Fcapabilities","docs\u002F03.capabilities",[141,145,150,155,160,164,169,174,179,184,188,193,198,203,208,213],{"title":142,"path":143,"stem":144,"icon":137},"Overview","\u002Fdocs\u002Fcapabilities\u002Foverview","docs\u002F03.capabilities\u002F01.overview",{"title":146,"path":147,"stem":148,"icon":149},"Events","\u002Fdocs\u002Fcapabilities\u002Fevents","docs\u002F03.capabilities\u002F02.events","i-lucide-radio-tower",{"title":151,"path":152,"stem":153,"icon":154},"Queue","\u002Fdocs\u002Fcapabilities\u002Fbackground-jobs","docs\u002F03.capabilities\u002F03.background-jobs","i-lucide-list-ordered",{"title":156,"path":157,"stem":158,"icon":159},"Scheduled Work","\u002Fdocs\u002Fcapabilities\u002Fscheduled-jobs","docs\u002F03.capabilities\u002F04.scheduled-jobs","i-lucide-clock",{"title":161,"path":162,"stem":163,"icon":85},"Caching","\u002Fdocs\u002Fcapabilities\u002Fcaching","docs\u002F03.capabilities\u002F05.caching",{"title":165,"path":166,"stem":167,"icon":168},"Key\u002FValue Storage","\u002Fdocs\u002Fcapabilities\u002Fkey-value","docs\u002F03.capabilities\u002F06.key-value","i-lucide-hard-drive",{"title":170,"path":171,"stem":172,"icon":173},"Blob Storage","\u002Fdocs\u002Fcapabilities\u002Fblob","docs\u002F03.capabilities\u002F07.blob","i-lucide-folder-open",{"title":175,"path":176,"stem":177,"icon":178},"Health & Readiness","\u002Fdocs\u002Fcapabilities\u002Fhealth","docs\u002F03.capabilities\u002F08.health","i-lucide-heart-pulse",{"title":180,"path":181,"stem":182,"icon":183},"Notifications","\u002Fdocs\u002Fcapabilities\u002Fnotifications","docs\u002F03.capabilities\u002F09.notifications","i-lucide-mail",{"title":185,"path":186,"stem":187,"icon":65},"Idempotency","\u002Fdocs\u002Fcapabilities\u002Fidempotency","docs\u002F03.capabilities\u002F10.idempotency",{"title":189,"path":190,"stem":191,"icon":192},"Security Hardening","\u002Fdocs\u002Fcapabilities\u002Fsecurity","docs\u002F03.capabilities\u002F11.security","i-lucide-shield",{"title":194,"path":195,"stem":196,"icon":197},"Observability & Tracing","\u002Fdocs\u002Fcapabilities\u002Fobservability","docs\u002F03.capabilities\u002F12.observability","i-lucide-activity",{"title":199,"path":200,"stem":201,"icon":202},"OpenAPI","\u002Fdocs\u002Fcapabilities\u002Fopenapi","docs\u002F03.capabilities\u002F13.openapi","i-lucide-book-marked",{"title":204,"path":205,"stem":206,"icon":207},"MCP Server","\u002Fdocs\u002Fcapabilities\u002Fmcp","docs\u002F03.capabilities\u002F14.mcp","i-lucide-bot",{"title":209,"path":210,"stem":211,"icon":212},"Schema DTOs","\u002Fdocs\u002Fcapabilities\u002Fschema","docs\u002F03.capabilities\u002F15.schema","i-lucide-file-json",{"title":214,"path":215,"stem":216,"icon":217},"Branded IDs","\u002Fdocs\u002Fcapabilities\u002Fbranded-ids","docs\u002F03.capabilities\u002F16.branded-ids","i-lucide-tag",{"title":219,"icon":220,"path":221,"stem":222,"children":223,"page":45},"Architecture","i-lucide-building","\u002Fdocs\u002Farchitecture","docs\u002F04.architecture",[224,227,232,237,242,247,252],{"title":142,"path":225,"stem":226,"icon":220},"\u002Fdocs\u002Farchitecture\u002Foverview","docs\u002F04.architecture\u002F01.overview",{"title":228,"path":229,"stem":230,"icon":231},"Domain-Driven Design","\u002Fdocs\u002Farchitecture\u002Fddd","docs\u002F04.architecture\u002F02.ddd","i-lucide-hexagon",{"title":233,"path":234,"stem":235,"icon":236},"CQRS","\u002Fdocs\u002Farchitecture\u002Fcqrs","docs\u002F04.architecture\u002F03.cqrs","i-lucide-split",{"title":238,"path":239,"stem":240,"icon":241},"Repository Pattern","\u002Fdocs\u002Farchitecture\u002Frepository-pattern","docs\u002F04.architecture\u002F04.repository-pattern","i-lucide-archive",{"title":243,"path":244,"stem":245,"icon":246},"Integration Events","\u002Fdocs\u002Farchitecture\u002Fintegration-events","docs\u002F04.architecture\u002F05.integration-events","i-lucide-send-horizontal",{"title":248,"path":249,"stem":250,"icon":251},"Saga Orchestration","\u002Fdocs\u002Farchitecture\u002Fsagas","docs\u002F04.architecture\u002F06.sagas","i-lucide-hourglass",{"title":253,"path":254,"stem":255,"icon":256},"Durable Workflows","\u002Fdocs\u002Farchitecture\u002Fworkflows","docs\u002F04.architecture\u002F07.workflows","i-lucide-workflow",{"title":258,"icon":259,"path":260,"stem":261,"children":262,"page":45},"Realtime","i-lucide-cable","\u002Fdocs\u002Frealtime","docs\u002F05.realtime",[263,266,270,274,279,284,288],{"title":142,"path":264,"stem":265,"icon":259},"\u002Fdocs\u002Frealtime\u002Foverview","docs\u002F05.realtime\u002F01.overview",{"title":267,"path":268,"stem":269,"icon":129},"WebSockets","\u002Fdocs\u002Frealtime\u002Fwebsockets","docs\u002F05.realtime\u002F02.websockets",{"title":271,"path":272,"stem":273,"icon":70},"WebSocket Contracts","\u002Fdocs\u002Frealtime\u002Fwebsocket-contracts","docs\u002F05.realtime\u002F03.websocket-contracts",{"title":275,"path":276,"stem":277,"icon":278},"Server-Sent Events","\u002Fdocs\u002Frealtime\u002Fserver-sent-events","docs\u002F05.realtime\u002F04.server-sent-events","i-lucide-rss",{"title":280,"path":281,"stem":282,"icon":283},"Typed Clients","\u002Fdocs\u002Frealtime\u002Ftyped-clients","docs\u002F05.realtime\u002F05.typed-clients","i-lucide-plug-zap",{"title":285,"path":286,"stem":287,"icon":259},"AsyncAPI","\u002Fdocs\u002Frealtime\u002Fasyncapi","docs\u002F05.realtime\u002F06.asyncapi",{"title":289,"path":290,"stem":291,"icon":80},"Durable Objects","\u002Fdocs\u002Frealtime\u002Fdurable-objects","docs\u002F05.realtime\u002F07.durable-objects",{"title":293,"icon":294,"path":295,"stem":296,"children":297,"page":45},"Deploy","i-lucide-cloud-upload","\u002Fdocs\u002Fdeploy","docs\u002F06.deploy",[298,301,306,311,316,321,326,331,336,341,345,350],{"title":142,"path":299,"stem":300,"icon":6},"\u002Fdocs\u002Fdeploy\u002Foverview","docs\u002F06.deploy\u002F01.overview",{"title":302,"path":303,"stem":304,"icon":305},"Node","\u002Fdocs\u002Fdeploy\u002Fnode","docs\u002F06.deploy\u002F02.node","la:node-js",{"title":307,"path":308,"stem":309,"icon":310},"Cloudflare","\u002Fdocs\u002Fdeploy\u002Fcloudflare","docs\u002F06.deploy\u002F03.cloudflare","simple-icons:cloudflare",{"title":312,"path":313,"stem":314,"icon":315},"Vercel","\u002Fdocs\u002Fdeploy\u002Fvercel","docs\u002F06.deploy\u002F04.vercel","ri:vercel-fill",{"title":317,"path":318,"stem":319,"icon":320},"Netlify","\u002Fdocs\u002Fdeploy\u002Fnetlify","docs\u002F06.deploy\u002F05.netlify","i-simple-icons-netlify",{"title":322,"path":323,"stem":324,"icon":325},"AWS Lambda","\u002Fdocs\u002Fdeploy\u002Faws-lambda","docs\u002F06.deploy\u002F06.aws-lambda","hugeicons:aws-lambda",{"title":327,"path":328,"stem":329,"icon":330},"Deno Deploy","\u002Fdocs\u002Fdeploy\u002Fdeno-deploy","docs\u002F06.deploy\u002F07.deno-deploy","simple-icons:denodeploy",{"title":332,"path":333,"stem":334,"icon":335},"Nitro","\u002Fdocs\u002Fdeploy\u002Fnitro","docs\u002F06.deploy\u002F08.nitro","i-unjs-nitro",{"title":337,"path":338,"stem":339,"icon":340},"Nuxt","\u002Fdocs\u002Fdeploy\u002Fnuxt","docs\u002F06.deploy\u002F09.nuxt","i-devicon-nuxt",{"title":342,"path":343,"stem":344,"icon":236},"Mixed-runtime split","\u002Fdocs\u002Fdeploy\u002Fmixed-runtime-split","docs\u002F06.deploy\u002F10.mixed-runtime-split",{"title":346,"path":347,"stem":348,"icon":349},"Hosts","\u002Fdocs\u002Fdeploy\u002Fhosts","docs\u002F06.deploy\u002F11.hosts","i-lucide-map",{"title":351,"path":352,"stem":353,"icon":354},"Cross-service events","\u002Fdocs\u002Fdeploy\u002Fcross-service-events","docs\u002F06.deploy\u002F12.cross-service-events","i-lucide-network",{"title":356,"icon":357,"path":358,"stem":359,"children":360,"page":45},"CLI","i-lucide-terminal","\u002Fdocs\u002Fcli","docs\u002F07.cli",[361,365,369,373],{"title":362,"path":363,"stem":364,"icon":357},"The heximon CLI","\u002Fdocs\u002Fcli\u002Foverview","docs\u002F07.cli\u002F01.overview",{"title":366,"path":367,"stem":368,"icon":137},"Create a New App","\u002Fdocs\u002Fcli\u002Fcreate","docs\u002F07.cli\u002F02.create",{"title":370,"path":371,"stem":372,"icon":95},"Migrations from the CLI","\u002Fdocs\u002Fcli\u002Fmigrations","docs\u002F07.cli\u002F03.migrations",{"title":374,"path":375,"stem":376,"icon":6},"Deploying a Heximon app","\u002Fdocs\u002Fcli\u002Fdeploy","docs\u002F07.cli\u002F04.deploy",{"title":378,"icon":379,"path":380,"stem":381,"children":382,"page":45},"Under the Hood","i-lucide-wrench","\u002Fdocs\u002Funder-the-hood","docs\u002F08.under-the-hood",[383,388,393,397,402,407,411,415],{"title":384,"path":385,"stem":386,"icon":387},"How the Compiler Works","\u002Fdocs\u002Funder-the-hood\u002Fhow-the-compiler-works","docs\u002F08.under-the-hood\u002F01.how-the-compiler-works","i-lucide-cog",{"title":389,"path":390,"stem":391,"icon":392},"Compiler","\u002Fdocs\u002Funder-the-hood\u002Fextending-the-compiler","docs\u002F08.under-the-hood\u002F02.extending-the-compiler","i-lucide-cpu",{"title":394,"path":395,"stem":396,"icon":357},"Compiler Diagnostics","\u002Fdocs\u002Funder-the-hood\u002Fcompiler-diagnostics","docs\u002F08.under-the-hood\u002F03.compiler-diagnostics",{"title":398,"path":399,"stem":400,"icon":401},"Library Mode","\u002Fdocs\u002Funder-the-hood\u002Flibrary-mode","docs\u002F08.under-the-hood\u002F04.library-mode","i-lucide-package",{"title":403,"path":404,"stem":405,"icon":406},"Other bundlers","\u002Fdocs\u002Funder-the-hood\u002Falternate-bundlers","docs\u002F08.under-the-hood\u002F05.alternate-bundlers","i-lucide-blocks",{"title":408,"path":409,"stem":410,"icon":401},"Packages","\u002Fdocs\u002Funder-the-hood\u002Fpackages","docs\u002F08.under-the-hood\u002F06.packages",{"title":412,"path":413,"stem":414,"icon":207},"AI Skills","\u002Fdocs\u002Funder-the-hood\u002Fai-and-agents","docs\u002F08.under-the-hood\u002F07.ai-and-agents",{"title":416,"path":417,"stem":418,"icon":419},"Advanced DI","\u002Fdocs\u002Funder-the-hood\u002Fadvanced-di","docs\u002F08.under-the-hood\u002F08.advanced-di","i-lucide-git-fork",{"title":421,"icon":422,"path":423,"stem":424,"children":425,"page":45},"Recipes","i-lucide-book-open-check","\u002Fdocs\u002Frecipes","docs\u002F09.recipes",[426,431,436,440,444,448,452,456,460,465,469,474,479,484,488],{"title":427,"path":428,"stem":429,"icon":430},"Send an Email","\u002Fdocs\u002Frecipes\u002Fsend-an-email","docs\u002F09.recipes\u002F01.send-an-email","i-lucide-send",{"title":432,"path":433,"stem":434,"icon":435},"Upload a File","\u002Fdocs\u002Frecipes\u002Fupload-a-file","docs\u002F09.recipes\u002F02.upload-a-file","i-lucide-upload",{"title":437,"path":438,"stem":439,"icon":24},"Cache an Expensive Call","\u002Fdocs\u002Frecipes\u002Fcache-an-expensive-call","docs\u002F09.recipes\u002F03.cache-an-expensive-call",{"title":441,"path":442,"stem":443,"icon":154},"Paginate a List","\u002Fdocs\u002Frecipes\u002Fpaginate-a-list","docs\u002F09.recipes\u002F04.paginate-a-list",{"title":445,"path":446,"stem":447,"icon":65},"Protect Admin Routes","\u002Fdocs\u002Frecipes\u002Fprotect-admin-routes","docs\u002F09.recipes\u002F05.protect-admin-routes",{"title":449,"path":450,"stem":451,"icon":178},"Health Probes and a Nightly Job","\u002Fdocs\u002Frecipes\u002Fhealth-probes-and-a-nightly-job","docs\u002F09.recipes\u002F06.health-probes-and-a-nightly-job",{"title":453,"path":454,"stem":455,"icon":80},"Build a CRUD API","\u002Fdocs\u002Frecipes\u002Fcrud-api","docs\u002F09.recipes\u002F07.crud-api",{"title":457,"path":458,"stem":459,"icon":100},"Add Authentication","\u002Fdocs\u002Frecipes\u002Fadd-authentication","docs\u002F09.recipes\u002F08.add-authentication",{"title":461,"path":462,"stem":463,"icon":464},"Background Jobs","\u002Fdocs\u002Frecipes\u002Fbackground-jobs","docs\u002F09.recipes\u002F09.background-jobs","i-lucide-timer",{"title":466,"path":467,"stem":468,"icon":149},"Decouple Features with Events","\u002Fdocs\u002Frecipes\u002Fevent-driven-module","docs\u002F09.recipes\u002F10.event-driven-module",{"title":470,"path":471,"stem":472,"icon":473},"Build a DDD Aggregate","\u002Fdocs\u002Frecipes\u002Fddd-aggregate","docs\u002F09.recipes\u002F11.ddd-aggregate","i-lucide-box",{"title":475,"path":476,"stem":477,"icon":478},"Publish a Feature Package","\u002Fdocs\u002Frecipes\u002Ffeature-package","docs\u002F09.recipes\u002F12.feature-package","i-lucide-package-open",{"title":480,"path":481,"stem":482,"icon":483},"Reliable Integration Events","\u002Fdocs\u002Frecipes\u002Freliable-integration-events","docs\u002F09.recipes\u002F13.reliable-integration-events","i-lucide-inbox",{"title":485,"path":486,"stem":487,"icon":95},"Reactive Choreography","\u002Fdocs\u002Frecipes\u002Freactive-choreography","docs\u002F09.recipes\u002F14.reactive-choreography",{"title":489,"path":490,"stem":491,"icon":492},"Build a Production CRUD API","\u002Fdocs\u002Frecipes\u002Fproduction-crud-api","docs\u002F09.recipes\u002F15.production-crud-api","i-lucide-layers",{"id":494,"title":170,"body":495,"description":2462,"extension":2463,"links":2464,"meta":2465,"navigation":2466,"path":171,"seo":2467,"stem":172,"__hash__":2468},"docs\u002Fdocs\u002F03.capabilities\u002F07.blob.md",{"type":496,"value":497,"toc":2454},"minimark",[498,511,516,519,573,576,580,587,637,641,644,913,970,1457,1461,1468,1561,1573,1753,1766,1796,1800,1818,1838,1933,1947,1994,2005,2281,2364,2391,2398,2402,2450],[499,500,501,502,506,507,510],"p",{},"Files and uploads need a different shape than key\u002Fvalue data — streamed bodies, byte ranges, and\nsometimes a signed URL so the bytes never transit your server. ",[503,504,505],"code",{},"BlobStore"," is the abstract DI port\nfor that: inject it, and the owning module decides whether it's an in-memory ",[503,508,509],{},"Map"," under tests or S3\nin production.",[512,513,515],"h2",{"id":514},"choose-storage-blobs-or-a-database","Choose storage, blobs, or a database",[499,517,518],{},"Pick by the shape of the data, not by habit.",[520,521,522,536],"table",{},[523,524,525],"thead",{},[526,527,528,533],"tr",{},[529,530,532],"th",{"align":531},"left","Reach for",[529,534,535],{"align":531},"When",[537,538,539,553,562],"tbody",{},[526,540,541,550],{},[542,543,544],"td",{"align":531},[545,546,547],"a",{"href":166},[503,548,549],{},"Storage",[542,551,552],{"align":531},"Session data, short-lived tokens, caches, feature flags — small JSON-shaped values you address by key",[526,554,555,559],{},[542,556,557],{"align":531},[503,558,505],{},[542,560,561],{"align":531},"Files, uploads, large or streamed binary payloads — addressed by key, but bytes instead of JSON",[526,563,564,570],{},[542,565,566,567],{"align":531},"A ",[545,568,569],{"href":83},"database",[542,571,572],{"align":531},"Domain entities, relationships, joins, transactions, schema evolution — anything you'd query, not just fetch",[499,574,575],{},"Neither port has a compiler plugin — they're ordinary DI providers, resolved by class identity like\nany other dependency, so adding one is just listing a binding in a module.",[512,577,579],{"id":578},"install-the-port","Install the port",[499,581,582,583,586],{},"The memory, filesystem, and Cloudflare subpaths pull in no SDK. Only ",[503,584,585],{},"@heximon\u002Fblob\u002Fs3"," needs the two\noptional AWS peers.",[588,589,590,616],"code-group",{},[591,592,598],"pre",{"className":593,"code":594,"filename":595,"language":596,"meta":597,"style":597},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","pnpm add @heximon\u002Fblob\n","memory \u002F filesystem \u002F R2","bash","",[503,599,600],{"__ignoreMap":597},[601,602,605,609,613],"span",{"class":603,"line":604},"line",1,[601,606,608],{"class":607},"sBMFI","pnpm",[601,610,612],{"class":611},"sfazB"," add",[601,614,615],{"class":611}," @heximon\u002Fblob\n",[591,617,620],{"className":593,"code":618,"filename":619,"language":596,"meta":597,"style":597},"pnpm add @heximon\u002Fblob @aws-sdk\u002Fclient-s3 @aws-sdk\u002Flib-storage\n","S3 driver",[503,621,622],{"__ignoreMap":597},[601,623,624,626,628,631,634],{"class":603,"line":604},[601,625,608],{"class":607},[601,627,612],{"class":611},[601,629,630],{"class":611}," @heximon\u002Fblob",[601,632,633],{"class":611}," @aws-sdk\u002Fclient-s3",[601,635,636],{"class":611}," @aws-sdk\u002Flib-storage\n",[512,638,640],{"id":639},"use-the-blobstore-port","Use the BlobStore port",[499,642,643],{},"The port adds streaming and ranges on top of the key\u002Fvalue idea:",[591,645,649],{"className":646,"code":647,"language":648,"meta":597,"style":597},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","abstract class BlobStore {\n  abstract put(key: string, body: BlobBody, metadata?: BlobMetadata): Promise\u003Cvoid>;\n  abstract get(key: string, range?: ByteRange): Promise\u003CStoredBlob | null>;\n  abstract getStream(key: string, range?: ByteRange): Promise\u003CReadableStream\u003CUint8Array> | null>;\n  abstract delete(key: string): Promise\u003Cvoid>;   \u002F\u002F deleting a missing key is a no-op\n  abstract exists(key: string): Promise\u003Cboolean>;\n  abstract list(prefix?: string): Promise\u003Cstring[]>;\n}\n","ts",[503,650,651,667,727,770,817,848,875,907],{"__ignoreMap":597},[601,652,653,657,660,663],{"class":603,"line":604},[601,654,656],{"class":655},"spNyl","abstract",[601,658,659],{"class":655}," class",[601,661,662],{"class":607}," BlobStore",[601,664,666],{"class":665},"sMK4o"," {\n",[601,668,670,673,677,680,684,687,690,693,696,698,701,703,706,709,712,715,718,721,724],{"class":603,"line":669},2,[601,671,672],{"class":655},"  abstract",[601,674,676],{"class":675},"swJcz"," put",[601,678,679],{"class":665},"(",[601,681,683],{"class":682},"sHdIc","key",[601,685,686],{"class":665},":",[601,688,689],{"class":607}," string",[601,691,692],{"class":665},",",[601,694,695],{"class":682}," body",[601,697,686],{"class":665},[601,699,700],{"class":607}," BlobBody",[601,702,692],{"class":665},[601,704,705],{"class":682}," metadata",[601,707,708],{"class":665},"?:",[601,710,711],{"class":607}," BlobMetadata",[601,713,714],{"class":665},"):",[601,716,717],{"class":607}," Promise",[601,719,720],{"class":665},"\u003C",[601,722,723],{"class":607},"void",[601,725,726],{"class":665},">;\n",[601,728,730,732,735,737,739,741,743,745,748,750,753,755,757,759,762,765,768],{"class":603,"line":729},3,[601,731,672],{"class":655},[601,733,734],{"class":675}," get",[601,736,679],{"class":665},[601,738,683],{"class":682},[601,740,686],{"class":665},[601,742,689],{"class":607},[601,744,692],{"class":665},[601,746,747],{"class":682}," range",[601,749,708],{"class":665},[601,751,752],{"class":607}," ByteRange",[601,754,714],{"class":665},[601,756,717],{"class":607},[601,758,720],{"class":665},[601,760,761],{"class":607},"StoredBlob",[601,763,764],{"class":665}," |",[601,766,767],{"class":607}," null",[601,769,726],{"class":665},[601,771,773,775,778,780,782,784,786,788,790,792,794,796,798,800,803,805,808,811,813,815],{"class":603,"line":772},4,[601,774,672],{"class":655},[601,776,777],{"class":675}," getStream",[601,779,679],{"class":665},[601,781,683],{"class":682},[601,783,686],{"class":665},[601,785,689],{"class":607},[601,787,692],{"class":665},[601,789,747],{"class":682},[601,791,708],{"class":665},[601,793,752],{"class":607},[601,795,714],{"class":665},[601,797,717],{"class":607},[601,799,720],{"class":665},[601,801,802],{"class":607},"ReadableStream",[601,804,720],{"class":665},[601,806,807],{"class":607},"Uint8Array",[601,809,810],{"class":665},">",[601,812,764],{"class":665},[601,814,767],{"class":607},[601,816,726],{"class":665},[601,818,820,822,825,827,829,831,833,835,837,839,841,844],{"class":603,"line":819},5,[601,821,672],{"class":655},[601,823,824],{"class":675}," delete",[601,826,679],{"class":665},[601,828,683],{"class":682},[601,830,686],{"class":665},[601,832,689],{"class":607},[601,834,714],{"class":665},[601,836,717],{"class":607},[601,838,720],{"class":665},[601,840,723],{"class":607},[601,842,843],{"class":665},">;",[601,845,847],{"class":846},"sHwdD","   \u002F\u002F deleting a missing key is a no-op\n",[601,849,851,853,856,858,860,862,864,866,868,870,873],{"class":603,"line":850},6,[601,852,672],{"class":655},[601,854,855],{"class":675}," exists",[601,857,679],{"class":665},[601,859,683],{"class":682},[601,861,686],{"class":665},[601,863,689],{"class":607},[601,865,714],{"class":665},[601,867,717],{"class":607},[601,869,720],{"class":665},[601,871,872],{"class":607},"boolean",[601,874,726],{"class":665},[601,876,878,880,883,885,888,890,892,894,896,898,901,905],{"class":603,"line":877},7,[601,879,672],{"class":655},[601,881,882],{"class":675}," list",[601,884,679],{"class":665},[601,886,887],{"class":682},"prefix",[601,889,708],{"class":665},[601,891,689],{"class":607},[601,893,714],{"class":665},[601,895,717],{"class":607},[601,897,720],{"class":665},[601,899,900],{"class":607},"string",[601,902,904],{"class":903},"sTEyZ","[]",[601,906,726],{"class":665},[601,908,910],{"class":603,"line":909},8,[601,911,912],{"class":665},"}\n",[914,915,916,927,953],"ul",{},[917,918,919,922,923,926],"li",{},[503,920,921],{},"BlobBody"," is ",[503,924,925],{},"Uint8Array | ReadableStream\u003CUint8Array>"," — a buffered payload, or a byte stream for\nlarge uploads. A stream is consumed once, so pass a fresh stream per write.",[917,928,929,922,932,935,936,940,941,944,945,948,949,952],{},[503,930,931],{},"ByteRange",[503,933,934],{},"{ start, end? }",", an ",[937,938,939],"strong",{},"inclusive"," ",[503,942,943],{},"[start, end]"," window (HTTP ",[503,946,947],{},"Range","-style). Omit\n",[503,950,951],{},"end"," to read to the end of the blob.",[917,954,566,955,957,958,961,962,965,966,969],{},[503,956,761],{}," exposes ",[503,959,960],{},"bytes",", ",[503,963,964],{},"metadata",", and ",[503,967,968],{},"stream()"," — which returns a fresh independent stream\neach call, so you can pipe the same blob more than once.",[591,971,973],{"className":646,"code":972,"language":648,"meta":597,"style":597},"import { MemoryBlobStore } from \"@heximon\u002Fblob\u002Fmemory\";\n\nconst store = new MemoryBlobStore();\nconst encode = (text: string) => new TextEncoder().encode(text);\n\nawait store.put(\"documents\u002Fone\", encode(\"first\"));\nawait store.put(\"documents\u002Ftwo\", encode(\"second\"), {\n  contentType: \"text\u002Fplain\",\n  custom: { checksum: \"abc123\" },\n});\n\nconst stored = await store.get(\"documents\u002Fone\");          \u002F\u002F StoredBlob | null\nnew TextDecoder().decode(stored!.bytes);                  \u002F\u002F \"first\"\nstored!.metadata.size;                                    \u002F\u002F 5 (defaulted to byte length)\n\nawait store.put(\"ranged\", encode(\"0123456789\"));\nconst middle = await store.get(\"ranged\", { start: 2, end: 5 });\nnew TextDecoder().decode(middle!.bytes);                  \u002F\u002F \"2345\" (inclusive)\n\nawait store.list(\"documents\u002F\");                           \u002F\u002F [\"documents\u002Fone\", \"documents\u002Ftwo\"]\n",[503,974,975,1005,1011,1033,1076,1080,1121,1159,1176,1201,1211,1216,1252,1282,1302,1307,1344,1399,1424,1429],{"__ignoreMap":597},[601,976,977,981,984,987,990,993,996,999,1002],{"class":603,"line":604},[601,978,980],{"class":979},"s7zQu","import",[601,982,983],{"class":665}," {",[601,985,986],{"class":903}," MemoryBlobStore",[601,988,989],{"class":665}," }",[601,991,992],{"class":979}," from",[601,994,995],{"class":665}," \"",[601,997,998],{"class":611},"@heximon\u002Fblob\u002Fmemory",[601,1000,1001],{"class":665},"\"",[601,1003,1004],{"class":665},";\n",[601,1006,1007],{"class":603,"line":669},[601,1008,1010],{"emptyLinePlaceholder":1009},true,"\n",[601,1012,1013,1016,1019,1022,1025,1028,1031],{"class":603,"line":729},[601,1014,1015],{"class":655},"const",[601,1017,1018],{"class":903}," store ",[601,1020,1021],{"class":665},"=",[601,1023,1024],{"class":665}," new",[601,1026,986],{"class":1027},"s2Zo4",[601,1029,1030],{"class":903},"()",[601,1032,1004],{"class":665},[601,1034,1035,1037,1040,1042,1045,1048,1050,1052,1055,1058,1060,1063,1065,1068,1071,1074],{"class":603,"line":772},[601,1036,1015],{"class":655},[601,1038,1039],{"class":903}," encode ",[601,1041,1021],{"class":665},[601,1043,1044],{"class":665}," (",[601,1046,1047],{"class":682},"text",[601,1049,686],{"class":665},[601,1051,689],{"class":607},[601,1053,1054],{"class":665},")",[601,1056,1057],{"class":655}," =>",[601,1059,1024],{"class":665},[601,1061,1062],{"class":1027}," TextEncoder",[601,1064,1030],{"class":903},[601,1066,1067],{"class":665},".",[601,1069,1070],{"class":1027},"encode",[601,1072,1073],{"class":903},"(text)",[601,1075,1004],{"class":665},[601,1077,1078],{"class":603,"line":819},[601,1079,1010],{"emptyLinePlaceholder":1009},[601,1081,1082,1085,1088,1090,1093,1095,1097,1100,1102,1104,1107,1109,1111,1114,1116,1119],{"class":603,"line":850},[601,1083,1084],{"class":979},"await",[601,1086,1087],{"class":903}," store",[601,1089,1067],{"class":665},[601,1091,1092],{"class":1027},"put",[601,1094,679],{"class":903},[601,1096,1001],{"class":665},[601,1098,1099],{"class":611},"documents\u002Fone",[601,1101,1001],{"class":665},[601,1103,692],{"class":665},[601,1105,1106],{"class":1027}," encode",[601,1108,679],{"class":903},[601,1110,1001],{"class":665},[601,1112,1113],{"class":611},"first",[601,1115,1001],{"class":665},[601,1117,1118],{"class":903},"))",[601,1120,1004],{"class":665},[601,1122,1123,1125,1127,1129,1131,1133,1135,1138,1140,1142,1144,1146,1148,1151,1153,1155,1157],{"class":603,"line":877},[601,1124,1084],{"class":979},[601,1126,1087],{"class":903},[601,1128,1067],{"class":665},[601,1130,1092],{"class":1027},[601,1132,679],{"class":903},[601,1134,1001],{"class":665},[601,1136,1137],{"class":611},"documents\u002Ftwo",[601,1139,1001],{"class":665},[601,1141,692],{"class":665},[601,1143,1106],{"class":1027},[601,1145,679],{"class":903},[601,1147,1001],{"class":665},[601,1149,1150],{"class":611},"second",[601,1152,1001],{"class":665},[601,1154,1054],{"class":903},[601,1156,692],{"class":665},[601,1158,666],{"class":665},[601,1160,1161,1164,1166,1168,1171,1173],{"class":603,"line":909},[601,1162,1163],{"class":675},"  contentType",[601,1165,686],{"class":665},[601,1167,995],{"class":665},[601,1169,1170],{"class":611},"text\u002Fplain",[601,1172,1001],{"class":665},[601,1174,1175],{"class":665},",\n",[601,1177,1179,1182,1184,1186,1189,1191,1193,1196,1198],{"class":603,"line":1178},9,[601,1180,1181],{"class":675},"  custom",[601,1183,686],{"class":665},[601,1185,983],{"class":665},[601,1187,1188],{"class":675}," checksum",[601,1190,686],{"class":665},[601,1192,995],{"class":665},[601,1194,1195],{"class":611},"abc123",[601,1197,1001],{"class":665},[601,1199,1200],{"class":665}," },\n",[601,1202,1204,1207,1209],{"class":603,"line":1203},10,[601,1205,1206],{"class":665},"}",[601,1208,1054],{"class":903},[601,1210,1004],{"class":665},[601,1212,1214],{"class":603,"line":1213},11,[601,1215,1010],{"emptyLinePlaceholder":1009},[601,1217,1219,1221,1224,1226,1229,1231,1233,1236,1238,1240,1242,1244,1246,1249],{"class":603,"line":1218},12,[601,1220,1015],{"class":655},[601,1222,1223],{"class":903}," stored ",[601,1225,1021],{"class":665},[601,1227,1228],{"class":979}," await",[601,1230,1087],{"class":903},[601,1232,1067],{"class":665},[601,1234,1235],{"class":1027},"get",[601,1237,679],{"class":903},[601,1239,1001],{"class":665},[601,1241,1099],{"class":611},[601,1243,1001],{"class":665},[601,1245,1054],{"class":903},[601,1247,1248],{"class":665},";",[601,1250,1251],{"class":846},"          \u002F\u002F StoredBlob | null\n",[601,1253,1255,1258,1261,1263,1265,1268,1271,1274,1277,1279],{"class":603,"line":1254},13,[601,1256,1257],{"class":665},"new",[601,1259,1260],{"class":1027}," TextDecoder",[601,1262,1030],{"class":903},[601,1264,1067],{"class":665},[601,1266,1267],{"class":1027},"decode",[601,1269,1270],{"class":903},"(stored",[601,1272,1273],{"class":665},"!.",[601,1275,1276],{"class":903},"bytes)",[601,1278,1248],{"class":665},[601,1280,1281],{"class":846},"                  \u002F\u002F \"first\"\n",[601,1283,1285,1288,1290,1292,1294,1297,1299],{"class":603,"line":1284},14,[601,1286,1287],{"class":903},"stored",[601,1289,1273],{"class":665},[601,1291,964],{"class":903},[601,1293,1067],{"class":665},[601,1295,1296],{"class":903},"size",[601,1298,1248],{"class":665},[601,1300,1301],{"class":846},"                                    \u002F\u002F 5 (defaulted to byte length)\n",[601,1303,1305],{"class":603,"line":1304},15,[601,1306,1010],{"emptyLinePlaceholder":1009},[601,1308,1310,1312,1314,1316,1318,1320,1322,1325,1327,1329,1331,1333,1335,1338,1340,1342],{"class":603,"line":1309},16,[601,1311,1084],{"class":979},[601,1313,1087],{"class":903},[601,1315,1067],{"class":665},[601,1317,1092],{"class":1027},[601,1319,679],{"class":903},[601,1321,1001],{"class":665},[601,1323,1324],{"class":611},"ranged",[601,1326,1001],{"class":665},[601,1328,692],{"class":665},[601,1330,1106],{"class":1027},[601,1332,679],{"class":903},[601,1334,1001],{"class":665},[601,1336,1337],{"class":611},"0123456789",[601,1339,1001],{"class":665},[601,1341,1118],{"class":903},[601,1343,1004],{"class":665},[601,1345,1347,1349,1352,1354,1356,1358,1360,1362,1364,1366,1368,1370,1372,1374,1377,1379,1383,1385,1388,1390,1393,1395,1397],{"class":603,"line":1346},17,[601,1348,1015],{"class":655},[601,1350,1351],{"class":903}," middle ",[601,1353,1021],{"class":665},[601,1355,1228],{"class":979},[601,1357,1087],{"class":903},[601,1359,1067],{"class":665},[601,1361,1235],{"class":1027},[601,1363,679],{"class":903},[601,1365,1001],{"class":665},[601,1367,1324],{"class":611},[601,1369,1001],{"class":665},[601,1371,692],{"class":665},[601,1373,983],{"class":665},[601,1375,1376],{"class":675}," start",[601,1378,686],{"class":665},[601,1380,1382],{"class":1381},"sbssI"," 2",[601,1384,692],{"class":665},[601,1386,1387],{"class":675}," end",[601,1389,686],{"class":665},[601,1391,1392],{"class":1381}," 5",[601,1394,989],{"class":665},[601,1396,1054],{"class":903},[601,1398,1004],{"class":665},[601,1400,1402,1404,1406,1408,1410,1412,1415,1417,1419,1421],{"class":603,"line":1401},18,[601,1403,1257],{"class":665},[601,1405,1260],{"class":1027},[601,1407,1030],{"class":903},[601,1409,1067],{"class":665},[601,1411,1267],{"class":1027},[601,1413,1414],{"class":903},"(middle",[601,1416,1273],{"class":665},[601,1418,1276],{"class":903},[601,1420,1248],{"class":665},[601,1422,1423],{"class":846},"                  \u002F\u002F \"2345\" (inclusive)\n",[601,1425,1427],{"class":603,"line":1426},19,[601,1428,1010],{"emptyLinePlaceholder":1009},[601,1430,1432,1434,1436,1438,1441,1443,1445,1448,1450,1452,1454],{"class":603,"line":1431},20,[601,1433,1084],{"class":979},[601,1435,1087],{"class":903},[601,1437,1067],{"class":665},[601,1439,1440],{"class":1027},"list",[601,1442,679],{"class":903},[601,1444,1001],{"class":665},[601,1446,1447],{"class":611},"documents\u002F",[601,1449,1001],{"class":665},[601,1451,1054],{"class":903},[601,1453,1248],{"class":665},[601,1455,1456],{"class":846},"                           \u002F\u002F [\"documents\u002Fone\", \"documents\u002Ftwo\"]\n",[512,1458,1460],{"id":1459},"pick-a-driver","Pick a driver",[499,1462,1463,1464,1467],{},"Four drivers ship, one per environment. Each ",[503,1465,1466],{},"extends BlobStore",", so each is a valid binding for the\nabstract token — you swap backends by changing only the module binding.",[520,1469,1470,1483],{},[523,1471,1472],{},[526,1473,1474,1477,1480],{},[529,1475,1476],{"align":531},"Driver",[529,1478,1479],{"align":531},"Subpath",[529,1481,1482],{"align":531},"Construct with",[537,1484,1485,1501,1518,1540],{},[526,1486,1487,1492,1496],{},[542,1488,1489],{"align":531},[503,1490,1491],{},"MemoryBlobStore",[542,1493,1494],{"align":531},[503,1495,998],{},[542,1497,1498],{"align":531},[503,1499,1500],{},"new MemoryBlobStore()",[526,1502,1503,1508,1513],{},[542,1504,1505],{"align":531},[503,1506,1507],{},"FileSystemBlobStore",[542,1509,1510],{"align":531},[503,1511,1512],{},"@heximon\u002Fblob\u002Ffilesystem",[542,1514,1515],{"align":531},[503,1516,1517],{},"new FileSystemBlobStore(rootDirectory)",[526,1519,1520,1525,1530],{},[542,1521,1522],{"align":531},[503,1523,1524],{},"R2BlobStore",[542,1526,1527],{"align":531},[503,1528,1529],{},"@heximon\u002Fblob\u002Fcloudflare",[542,1531,1532,1535,1536,1539],{"align":531},[503,1533,1534],{},"new R2BlobStore(bucket)"," (a Cloudflare ",[503,1537,1538],{},"R2Bucket"," binding)",[526,1541,1542,1547,1551],{},[542,1543,1544],{"align":531},[503,1545,1546],{},"S3BlobStore",[542,1548,1549],{"align":531},[503,1550,585],{},[542,1552,1553,1556,1557,1560],{"align":531},[503,1554,1555],{},"new S3BlobStore(client, bucket)"," (an AWS SDK v3 ",[503,1558,1559],{},"S3Client"," + bucket name)",[499,1562,1563,1565,1566,1569,1570,1572],{},[503,1564,1507],{}," keeps each blob's bytes in a file under the root and its metadata in a companion\n",[503,1567,1568],{},".meta.json"," file next to it — a real driver for dev or a single host, with streamed writes and positional\nreads so large blobs are never fully buffered. ",[503,1571,1546],{}," works against AWS S3, MinIO, or any\nS3-compatible endpoint (including R2's S3-compatible API).",[591,1574,1577],{"className":646,"code":1575,"filename":1576,"language":648,"meta":597,"style":597},"import { Module } from \"@heximon\u002Fruntime\";\nimport { BlobStore } from \"@heximon\u002Fblob\";\nimport { FileSystemBlobStore } from \"@heximon\u002Fblob\u002Ffilesystem\";\n\nexport class StorageModule extends Module({\n  providers: [\n    \u002F\u002F useFactory deps come from the factory's parameter types — no `inject` array.\n    { provide: BlobStore, useFactory: () => new FileSystemBlobStore(\"\u002Fvar\u002Fdata\u002Fblobs\") },\n  ],\n  exports: [BlobStore],\n}) {}\n","storage.module.ts",[503,1578,1579,1601,1622,1643,1647,1667,1677,1682,1725,1732,1744],{"__ignoreMap":597},[601,1580,1581,1583,1585,1588,1590,1592,1594,1597,1599],{"class":603,"line":604},[601,1582,980],{"class":979},[601,1584,983],{"class":665},[601,1586,1587],{"class":903}," Module",[601,1589,989],{"class":665},[601,1591,992],{"class":979},[601,1593,995],{"class":665},[601,1595,1596],{"class":611},"@heximon\u002Fruntime",[601,1598,1001],{"class":665},[601,1600,1004],{"class":665},[601,1602,1603,1605,1607,1609,1611,1613,1615,1618,1620],{"class":603,"line":669},[601,1604,980],{"class":979},[601,1606,983],{"class":665},[601,1608,662],{"class":903},[601,1610,989],{"class":665},[601,1612,992],{"class":979},[601,1614,995],{"class":665},[601,1616,1617],{"class":611},"@heximon\u002Fblob",[601,1619,1001],{"class":665},[601,1621,1004],{"class":665},[601,1623,1624,1626,1628,1631,1633,1635,1637,1639,1641],{"class":603,"line":729},[601,1625,980],{"class":979},[601,1627,983],{"class":665},[601,1629,1630],{"class":903}," FileSystemBlobStore",[601,1632,989],{"class":665},[601,1634,992],{"class":979},[601,1636,995],{"class":665},[601,1638,1512],{"class":611},[601,1640,1001],{"class":665},[601,1642,1004],{"class":665},[601,1644,1645],{"class":603,"line":772},[601,1646,1010],{"emptyLinePlaceholder":1009},[601,1648,1649,1652,1654,1657,1660,1662,1664],{"class":603,"line":819},[601,1650,1651],{"class":979},"export",[601,1653,659],{"class":655},[601,1655,1656],{"class":607}," StorageModule",[601,1658,1659],{"class":655}," extends",[601,1661,1587],{"class":1027},[601,1663,679],{"class":903},[601,1665,1666],{"class":665},"{\n",[601,1668,1669,1672,1674],{"class":603,"line":850},[601,1670,1671],{"class":675},"  providers",[601,1673,686],{"class":665},[601,1675,1676],{"class":903}," [\n",[601,1678,1679],{"class":603,"line":877},[601,1680,1681],{"class":846},"    \u002F\u002F useFactory deps come from the factory's parameter types — no `inject` array.\n",[601,1683,1684,1687,1690,1692,1694,1696,1699,1701,1704,1706,1708,1710,1712,1714,1717,1719,1722],{"class":603,"line":909},[601,1685,1686],{"class":665},"    {",[601,1688,1689],{"class":675}," provide",[601,1691,686],{"class":665},[601,1693,662],{"class":903},[601,1695,692],{"class":665},[601,1697,1698],{"class":1027}," useFactory",[601,1700,686],{"class":665},[601,1702,1703],{"class":665}," ()",[601,1705,1057],{"class":655},[601,1707,1024],{"class":665},[601,1709,1630],{"class":1027},[601,1711,679],{"class":903},[601,1713,1001],{"class":665},[601,1715,1716],{"class":611},"\u002Fvar\u002Fdata\u002Fblobs",[601,1718,1001],{"class":665},[601,1720,1721],{"class":903},") ",[601,1723,1724],{"class":665},"},\n",[601,1726,1727,1730],{"class":603,"line":1178},[601,1728,1729],{"class":903},"  ]",[601,1731,1175],{"class":665},[601,1733,1734,1737,1739,1742],{"class":603,"line":1203},[601,1735,1736],{"class":675},"  exports",[601,1738,686],{"class":665},[601,1740,1741],{"class":903}," [BlobStore]",[601,1743,1175],{"class":665},[601,1745,1746,1748,1750],{"class":603,"line":1213},[601,1747,1206],{"class":665},[601,1749,1721],{"class":903},[601,1751,1752],{"class":665},"{}\n",[499,1754,1755,1756,1762,1763,1765],{},"The ",[545,1757,1761],{"href":1758,"rel":1759},"https:\u002F\u002Fgithub.com\u002Fheximon\u002Fheximon\u002Ftree\u002Fmain\u002Fexamples\u002Fgap\u002Fstorage-blob-adapters",[1760],"nofollow","Storage & blob adapters","\nexample binds ",[503,1764,1491],{}," for its uploads feature and shows the controller\u002Fcontract\u002Froutes staying\nunchanged no matter which driver is behind the token.",[1767,1768,1770,1776,1777,1780,1781,1783,1784,1787,1788,1791,1792,1795],"callout",{"type":1769},"warning",[937,1771,1772,1773,1775],{},"R2 needs a declared ",[503,1774,1296],{}," to stream."," Cloudflare's ",[503,1778,1779],{},"R2Bucket.put"," rejects a streamed body of unknown\nlength, so ",[503,1782,1524],{}," collects an unsized stream into a buffer before writing — which puts the whole\nupload on the heap. Pass ",[503,1785,1786],{},"metadata.size"," to keep large uploads streaming:\n",[503,1789,1790],{},"store.put(key, uploadStream, { size: byteLength })",". S3 has no such limit — its multipart ",[503,1793,1794],{},"Upload","\nstreams unknown-length bodies natively, buffering one ~5 MB part at a time.",[512,1797,1799],{"id":1798},"hand-out-signed-urls","Hand out signed URLs",[499,1801,1802,1803,1806,1807,1810,1811,1721,1814,1817],{},"For large objects, you don't want every byte flowing through your app. Instead, mint a ",[937,1804,1805],{},"short-lived\nsigned URL"," and let the client upload (",[503,1808,1809],{},"PUT",") or download (",[503,1812,1813],{},"GET",[937,1815,1816],{},"directly"," against the storage\nbackend — the file never transits the server.",[499,1819,1820,1821,1823,1824,1827,1828,1831,1832,940,1836,686],{},"Not every backend can sign, so it isn't a method on ",[503,1822,505],{},". Signing is a ",[937,1825,1826],{},"capability",", modelled\nas its own DI token, ",[503,1829,1830],{},"SignedUrlBlobStore",", which ",[1833,1834,1835],"em",{},"extends",[503,1837,505],{},[591,1839,1841],{"className":646,"code":1840,"language":648,"meta":597,"style":597},"abstract class SignedUrlBlobStore extends BlobStore {\n  abstract createSignedUploadUrl(key: string, options?: SignedUploadOptions): Promise\u003CSignedUpload>;\n  abstract createSignedDownloadUrl(key: string, options?: SignedDownloadOptions): Promise\u003CSignedDownload>;\n}\n",[503,1842,1843,1858,1894,1929],{"__ignoreMap":597},[601,1844,1845,1847,1849,1852,1854,1856],{"class":603,"line":604},[601,1846,656],{"class":655},[601,1848,659],{"class":655},[601,1850,1851],{"class":607}," SignedUrlBlobStore",[601,1853,1659],{"class":655},[601,1855,662],{"class":607},[601,1857,666],{"class":665},[601,1859,1860,1862,1865,1867,1869,1871,1873,1875,1878,1880,1883,1885,1887,1889,1892],{"class":603,"line":669},[601,1861,672],{"class":655},[601,1863,1864],{"class":675}," createSignedUploadUrl",[601,1866,679],{"class":665},[601,1868,683],{"class":682},[601,1870,686],{"class":665},[601,1872,689],{"class":607},[601,1874,692],{"class":665},[601,1876,1877],{"class":682}," options",[601,1879,708],{"class":665},[601,1881,1882],{"class":607}," SignedUploadOptions",[601,1884,714],{"class":665},[601,1886,717],{"class":607},[601,1888,720],{"class":665},[601,1890,1891],{"class":607},"SignedUpload",[601,1893,726],{"class":665},[601,1895,1896,1898,1901,1903,1905,1907,1909,1911,1913,1915,1918,1920,1922,1924,1927],{"class":603,"line":729},[601,1897,672],{"class":655},[601,1899,1900],{"class":675}," createSignedDownloadUrl",[601,1902,679],{"class":665},[601,1904,683],{"class":682},[601,1906,686],{"class":665},[601,1908,689],{"class":607},[601,1910,692],{"class":665},[601,1912,1877],{"class":682},[601,1914,708],{"class":665},[601,1916,1917],{"class":607}," SignedDownloadOptions",[601,1919,714],{"class":665},[601,1921,717],{"class":607},[601,1923,720],{"class":665},[601,1925,1926],{"class":607},"SignedDownload",[601,1928,726],{"class":665},[601,1930,1931],{"class":603,"line":772},[601,1932,912],{"class":665},[499,1934,1935,1936,1939,1940,1943,1944,1946],{},"Only drivers that can sign natively ",[1833,1937,1938],{},"extend"," it — so the capability is ",[937,1941,1942],{},"enforced by the type system",".\nYou can only inject a ",[503,1945,1830],{}," where a signing-capable driver is bound:",[520,1948,1949,1958],{},[523,1950,1951],{},[526,1952,1953,1955],{},[529,1954,1476],{"align":531},[529,1956,1957],{"align":531},"Signs?",[537,1959,1960,1969,1982],{},[526,1961,1962,1966],{},[542,1963,1964],{"align":531},[503,1965,1546],{},[542,1967,1968],{"align":531},"Yes — AWS SigV4 presigning (also covers R2's S3-compatible endpoint)",[526,1970,1971,1976],{},[542,1972,1973,1975],{"align":531},[503,1974,1524],{}," (Worker binding)",[542,1977,1978,1979,1981],{"align":531},"No — the binding can't presign; point ",[503,1980,1546],{}," at R2's S3 endpoint instead",[526,1983,1984,1991],{},[542,1985,1986,1988,1989],{"align":531},[503,1987,1491],{}," \u002F ",[503,1990,1507],{},[542,1992,1993],{"align":531},"No — no URL concept",[499,1995,1996,1997,2000,2001,2004],{},"Bind the capability token to a supported driver, inject it, and forward the minted descriptor. The\nframework adds ",[937,1998,1999],{},"no"," upload\u002Fdownload route — issuing the URL is your code, exactly as the example's\n",[503,2002,2003],{},"SigningModule"," does:",[591,2006,2009],{"className":646,"code":2007,"filename":2008,"language":648,"meta":597,"style":597},"import { S3Client } from \"@aws-sdk\u002Fclient-s3\";\nimport { SignedUrlBlobStore } from \"@heximon\u002Fblob\";\nimport { S3BlobStore } from \"@heximon\u002Fblob\u002Fs3\";\nimport { Module } from \"@heximon\u002Fruntime\";\nimport { SigningController } from \".\u002Fsigning.controller\";\n\nexport class SigningModule extends Module({\n  providers: [\n    {\n      provide: SignedUrlBlobStore,\n      useFactory: () =>\n        new S3BlobStore(\n          new S3Client({ region: \"us-east-1\", forcePathStyle: true }),\n          \"uploads\",\n        ),\n    },\n  ],\n  http: { controllers: [SigningController] },\n}) {}\n","signing.module.ts",[503,2010,2011,2033,2053,2074,2094,2116,2120,2137,2145,2150,2161,2173,2183,2224,2236,2243,2248,2254,2273],{"__ignoreMap":597},[601,2012,2013,2015,2017,2020,2022,2024,2026,2029,2031],{"class":603,"line":604},[601,2014,980],{"class":979},[601,2016,983],{"class":665},[601,2018,2019],{"class":903}," S3Client",[601,2021,989],{"class":665},[601,2023,992],{"class":979},[601,2025,995],{"class":665},[601,2027,2028],{"class":611},"@aws-sdk\u002Fclient-s3",[601,2030,1001],{"class":665},[601,2032,1004],{"class":665},[601,2034,2035,2037,2039,2041,2043,2045,2047,2049,2051],{"class":603,"line":669},[601,2036,980],{"class":979},[601,2038,983],{"class":665},[601,2040,1851],{"class":903},[601,2042,989],{"class":665},[601,2044,992],{"class":979},[601,2046,995],{"class":665},[601,2048,1617],{"class":611},[601,2050,1001],{"class":665},[601,2052,1004],{"class":665},[601,2054,2055,2057,2059,2062,2064,2066,2068,2070,2072],{"class":603,"line":729},[601,2056,980],{"class":979},[601,2058,983],{"class":665},[601,2060,2061],{"class":903}," S3BlobStore",[601,2063,989],{"class":665},[601,2065,992],{"class":979},[601,2067,995],{"class":665},[601,2069,585],{"class":611},[601,2071,1001],{"class":665},[601,2073,1004],{"class":665},[601,2075,2076,2078,2080,2082,2084,2086,2088,2090,2092],{"class":603,"line":772},[601,2077,980],{"class":979},[601,2079,983],{"class":665},[601,2081,1587],{"class":903},[601,2083,989],{"class":665},[601,2085,992],{"class":979},[601,2087,995],{"class":665},[601,2089,1596],{"class":611},[601,2091,1001],{"class":665},[601,2093,1004],{"class":665},[601,2095,2096,2098,2100,2103,2105,2107,2109,2112,2114],{"class":603,"line":819},[601,2097,980],{"class":979},[601,2099,983],{"class":665},[601,2101,2102],{"class":903}," SigningController",[601,2104,989],{"class":665},[601,2106,992],{"class":979},[601,2108,995],{"class":665},[601,2110,2111],{"class":611},".\u002Fsigning.controller",[601,2113,1001],{"class":665},[601,2115,1004],{"class":665},[601,2117,2118],{"class":603,"line":850},[601,2119,1010],{"emptyLinePlaceholder":1009},[601,2121,2122,2124,2126,2129,2131,2133,2135],{"class":603,"line":877},[601,2123,1651],{"class":979},[601,2125,659],{"class":655},[601,2127,2128],{"class":607}," SigningModule",[601,2130,1659],{"class":655},[601,2132,1587],{"class":1027},[601,2134,679],{"class":903},[601,2136,1666],{"class":665},[601,2138,2139,2141,2143],{"class":603,"line":909},[601,2140,1671],{"class":675},[601,2142,686],{"class":665},[601,2144,1676],{"class":903},[601,2146,2147],{"class":603,"line":1178},[601,2148,2149],{"class":665},"    {\n",[601,2151,2152,2155,2157,2159],{"class":603,"line":1203},[601,2153,2154],{"class":675},"      provide",[601,2156,686],{"class":665},[601,2158,1851],{"class":903},[601,2160,1175],{"class":665},[601,2162,2163,2166,2168,2170],{"class":603,"line":1213},[601,2164,2165],{"class":1027},"      useFactory",[601,2167,686],{"class":665},[601,2169,1703],{"class":665},[601,2171,2172],{"class":655}," =>\n",[601,2174,2175,2178,2180],{"class":603,"line":1218},[601,2176,2177],{"class":665},"        new",[601,2179,2061],{"class":1027},[601,2181,2182],{"class":903},"(\n",[601,2184,2185,2188,2190,2192,2195,2198,2200,2202,2205,2207,2209,2212,2214,2218,2220,2222],{"class":603,"line":1254},[601,2186,2187],{"class":665},"          new",[601,2189,2019],{"class":1027},[601,2191,679],{"class":903},[601,2193,2194],{"class":665},"{",[601,2196,2197],{"class":675}," region",[601,2199,686],{"class":665},[601,2201,995],{"class":665},[601,2203,2204],{"class":611},"us-east-1",[601,2206,1001],{"class":665},[601,2208,692],{"class":665},[601,2210,2211],{"class":675}," forcePathStyle",[601,2213,686],{"class":665},[601,2215,2217],{"class":2216},"sfNiH"," true",[601,2219,989],{"class":665},[601,2221,1054],{"class":903},[601,2223,1175],{"class":665},[601,2225,2226,2229,2232,2234],{"class":603,"line":1284},[601,2227,2228],{"class":665},"          \"",[601,2230,2231],{"class":611},"uploads",[601,2233,1001],{"class":665},[601,2235,1175],{"class":665},[601,2237,2238,2241],{"class":603,"line":1304},[601,2239,2240],{"class":903},"        )",[601,2242,1175],{"class":665},[601,2244,2245],{"class":603,"line":1309},[601,2246,2247],{"class":665},"    },\n",[601,2249,2250,2252],{"class":603,"line":1346},[601,2251,1729],{"class":903},[601,2253,1175],{"class":665},[601,2255,2256,2259,2261,2263,2266,2268,2271],{"class":603,"line":1401},[601,2257,2258],{"class":675},"  http",[601,2260,686],{"class":665},[601,2262,983],{"class":665},[601,2264,2265],{"class":675}," controllers",[601,2267,686],{"class":665},[601,2269,2270],{"class":903}," [SigningController] ",[601,2272,1724],{"class":665},[601,2274,2275,2277,2279],{"class":603,"line":1426},[601,2276,1206],{"class":665},[601,2278,1721],{"class":903},[601,2280,1752],{"class":665},[591,2282,2285],{"className":646,"code":2283,"filename":2284,"language":648,"meta":597,"style":597},"const upload = await this.blobs.createSignedUploadUrl(key, { contentType });\n\u002F\u002F → { url, method: \"PUT\", headers: { \"Content-Type\": contentType }, expiresAt }\n\nconst download = await this.blobs.createSignedDownloadUrl(key);\n\u002F\u002F → { url, method: \"GET\", expiresAt }\n","signing.controller.ts",[503,2286,2287,2325,2330,2334,2359],{"__ignoreMap":597},[601,2288,2289,2291,2294,2296,2298,2301,2304,2306,2309,2312,2314,2316,2319,2321,2323],{"class":603,"line":604},[601,2290,1015],{"class":655},[601,2292,2293],{"class":903}," upload ",[601,2295,1021],{"class":665},[601,2297,1228],{"class":979},[601,2299,2300],{"class":665}," this.",[601,2302,2303],{"class":903},"blobs",[601,2305,1067],{"class":665},[601,2307,2308],{"class":1027},"createSignedUploadUrl",[601,2310,2311],{"class":903},"(key",[601,2313,692],{"class":665},[601,2315,983],{"class":665},[601,2317,2318],{"class":903}," contentType ",[601,2320,1206],{"class":665},[601,2322,1054],{"class":903},[601,2324,1004],{"class":665},[601,2326,2327],{"class":603,"line":669},[601,2328,2329],{"class":846},"\u002F\u002F → { url, method: \"PUT\", headers: { \"Content-Type\": contentType }, expiresAt }\n",[601,2331,2332],{"class":603,"line":729},[601,2333,1010],{"emptyLinePlaceholder":1009},[601,2335,2336,2338,2341,2343,2345,2347,2349,2351,2354,2357],{"class":603,"line":772},[601,2337,1015],{"class":655},[601,2339,2340],{"class":903}," download ",[601,2342,1021],{"class":665},[601,2344,1228],{"class":979},[601,2346,2300],{"class":665},[601,2348,2303],{"class":903},[601,2350,1067],{"class":665},[601,2352,2353],{"class":1027},"createSignedDownloadUrl",[601,2355,2356],{"class":903},"(key)",[601,2358,1004],{"class":665},[601,2360,2361],{"class":603,"line":819},[601,2362,2363],{"class":846},"\u002F\u002F → { url, method: \"GET\", expiresAt }\n",[499,2365,2366,2367,2370,2371,2374,2375,2378,2379,2382,2383,2386,2387,2390],{},"The result is a ",[937,2368,2369],{},"descriptor",", not a bare URL. When you bind a ",[503,2372,2373],{},"contentType",", the signature is valid\nonly for a request that sends that exact ",[503,2376,2377],{},"Content-Type"," header — so ",[503,2380,2381],{},"headers"," tells the client which\nheader to send. Pass ",[503,2384,2385],{},"expiresIn"," (a ",[503,2388,2389],{},"TimeSpan","; driver default 15 minutes) to change the lifetime.",[499,2392,2393,2394,2397],{},"The S3 driver needs one more optional peer for signing — ",[503,2395,2396],{},"@aws-sdk\u002Fs3-request-presigner"," — on top of\nthe two it already uses. Presigning is pure local crypto (no network call) and edge-safe on Workers.",[512,2399,2401],{"id":2400},"see-also","See also",[914,2403,2404,2417,2425,2430,2435],{},[917,2405,2406,2409,2410,2413,2414,2416],{},[545,2407,2408],{"href":433},"Upload a file"," — the shortest path: a multipart route validated by\n",[503,2411,2412],{},".files()",", streamed into a ",[503,2415,505],{},", with the production driver swap.",[917,2418,2419,2421,2422,2424],{},[545,2420,165],{"href":166}," — the ",[503,2423,549],{}," port for small JSON-shaped values instead of bytes.",[917,2426,2427,2429],{},[545,2428,82],{"href":83}," — when the data has relationships, transactions, or needs migrations.",[917,2431,2432,2434],{},[545,2433,214],{"href":215}," — type-safe identifiers to use as the keys flowing through your stores.",[917,2436,2437,2443,2444,2446,2447,2449],{},[545,2438,2440],{"href":1758,"rel":2439},[1760],[937,2441,2442],{},"Example: Storage & blob adapters","\n— swappable ",[503,2445,505],{}," bindings for uploads plus the ",[503,2448,2003],{},"'s signed-URL issuance, wired\nend-to-end with a test.",[2451,2452,2453],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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 .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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}",{"title":597,"searchDepth":669,"depth":669,"links":2455},[2456,2457,2458,2459,2460,2461],{"id":514,"depth":669,"text":515},{"id":578,"depth":669,"text":579},{"id":639,"depth":669,"text":640},{"id":1459,"depth":669,"text":1460},{"id":1798,"depth":669,"text":1799},{"id":2400,"depth":669,"text":2401},"The abstract BlobStore DI token for streamed bodies and byte ranges, the memory\u002Ffilesystem\u002FR2\u002FS3 drivers, and the SignedUrlBlobStore capability for direct-to-storage signed upload\u002Fdownload URLs.","md",null,{},{"icon":173},{"title":170,"description":2462},"hpNa78xO26-M8S3xi5GTSCvo2szPWluXyVzJbN5cpfE",[2470,2472],{"title":165,"path":166,"stem":167,"description":2471,"icon":168,"children":-1},"The abstract Storage DI token and its BatchStorage capability, MemoryStorage with TTL and LRU bounds, and the first-class UpstashStorage, CloudflareKvStorage, RedisStorage, and UnstorageStorage adapters.",{"title":175,"path":176,"stem":177,"description":2473,"icon":178,"children":-1},"Liveness and readiness probes with HealthIndicator, HealthRegistry, and an app-owned controller — DatabaseHealthIndicator, per-indicator timeout, id-keyed results.",1783300761903]