1"use strict";(globalThis.webpackChunk_lumenize_website=globalThis.webpackChunk_lumenize_website||[]).push([[9585],{12801(e,n,t){t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>i,toc:()=>l});const i=JSON.parse('{"id":"mesh/continuations","title":"Continuations (this.ctn)","description":"How Lumenize\'s type-safe, serializable operation chains work under the hood","source":"@site/docs/mesh/continuations.mdx","sourceDirName":"mesh","slug":"/mesh/continuations","permalink":"/docs/mesh/continuations","draft":false,"unlisted":false,"editUrl":"https://github.com/lumenize/lumenize/tree/main/website/docs/mesh/continuations.mdx","tags":[],"version":"current","frontMatter":{"title":"Continuations (this.ctn)","description":"How Lumenize\'s type-safe, serializable operation chains work under the hood"},"sidebar":"docsSidebar","previous":{"title":"Creating Plugins (this.svc)","permalink":"/docs/mesh/creating-plugins"},"next":{"title":"Managing Context","permalink":"/docs/mesh/managing-context"}}');var s=t(62540),r=t(43023);const a={title:"Continuations (this.ctn)",description:"How Lumenize's type-safe, serializable operation chains work under the hood"},o="Continuations",c={},l=[{value:"Why Continuations?",id:"why-continuations",level:2},{value:"The <code>this.ctn()</code> Factory",id:"the-thisctn-factory",level:2},{value:"How It Works",id:"how-it-works",level:2},{value:"1. Proxy-Based Chain Building",id:"1-proxy-based-chain-building",level:3},{value:"2. Serialization",id:"2-serialization",level:3},{value:"3. Execution",id:"3-execution",level:3},{value:"OCAN: Operation Chaining And Nesting",id:"ocan-operation-chaining-and-nesting",level:2},{value:"Manual Serialization (Advanced)",id:"manual-serialization-advanced",level:2}];function d(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,r.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"continuations",children:"Continuations"})}),"\n",(0,s.jsxs)(n.p,{children:["A ",(0,s.jsx)(n.strong,{children:"Continuation"})," is a ",(0,s.jsx)(n.strong,{children:"description of work"})," that gets executed in some other place or time \u2014 usually during or after some native async operation like a call or alarm."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",metastring:"@skip-check-approved('conceptual')",children:"const remote = this.ctn<RemoteDO>().getUserData(userId);\nconst local = this.ctn().handleResult(this.ctn().$result);\nthis.lmz.call('REMOTE_DO', 'instance-id', remote, local);\n"})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"For practical patterns"})," \u2014 chaining, nesting, ",(0,s.jsx)(n.code,{children:"$result"})," placeholders, error handling, and more \u2014 see ",(0,s.jsx)(n.strong,{children:(0,s.jsx)(n.a,{href:"./calls",children:"Making Calls"})}),"."]}),"\n",(0,s.jsx)(n.h2,{id:"why-continuations",children:"Why Continuations?"}),"\n",(0,s.jsx)(n.p,{children:"Continuations solve several problems in Durable Objects:"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Serializable"})," \u2014 Can be sent over the wire or stored and then restored after hibernation/eviction"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Type-safe"})," \u2014 TypeScript checks method names and signatures at compile time"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsxs)(n.strong,{children:["No ",(0,s.jsx)(n.code,{children:"await"})," needed"]})," \u2014 Avoid ",(0,s.jsx)(n.code,{children:"async"}),"/",(0,s.jsx)(n.code,{children:"await"})," which can break DO consistency guarantees. While race conditions are still possible, the explicitness and the fact that handlers need not be declared ",(0,s.jsx)(n.code,{children:"async"})," reduces the likelihood you will do so accidentally."]}),"\n"]}),"\n",(0,s.jsxs)(n.h2,{id:"the-thisctn-factory",children:["The ",(0,s.jsx)(n.code,{children:"this.ctn()"})," Factory"]}),"\n",(0,s.jsxs)(n.p,{children:["Your classes that extend ",(0,s.jsx)(n.code,{children:"LumenizeDO"}),", ",(0,s.jsx)(n.code,{children:"LumenizeWorker"}),", or ",(0,s.jsx)(n.code,{children:"LumenizeClient"})," have access to ",(0,s.jsx)(n.code,{children:"this.ctn()"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",metastring:"@skip-check-approved('conceptual')",children:"// Typesafe: TypeScript knows RemoteDO's methods\nthis.ctn<RemoteDO>().someMethod(arg)\n\n// Default: uses local class type when no generic provided\nthis.ctn().myHandler(this.ctn().$result)\n\n// Chaining: multiple operations in sequence\nthis.ctn<RemoteDO>().validate().save().getRevision()\n\n// Nesting: output of one operation feeds another\nthis.ctn<RemoteDO>().multiply(this.ctn<RemoteDO>().add(1, 2), 10)\n"})}),"\n",(0,s.jsx)(n.h2,{id:"how-it-works",children:"How It Works"}),"\n",(0,s.jsx)(n.h3,{id:"1-proxy-based-chain-building",children:"1. Proxy-Based Chain Building"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"this.ctn()"})," returns a JavaScript ",(0,s.jsx)(n.code,{children:"Proxy"}),". Each property access or method call adds an operation to the continuation's operation chain."]}),"\n",(0,s.jsx)(n.h3,{id:"2-serialization",children:"2. Serialization"}),"\n",(0,s.jsxs)(n.p,{children:["When you pass a continuation to ",(0,s.jsx)(n.code,{children:"this.lmz.call()"}),", it's serialized using ",(0,s.jsx)(n.code,{children:"@lumenize/structured-clone"})," so rich types (Date, Set, Map, aliases, cycles, etc.) are supported for parameters and results :"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",metastring:"@skip-check-approved('conceptual json')",children:"// The chain becomes a serializable object\n{\n ops: [\n { type: 'call', name: 'getProfile', args: ['user-123'] },\n { type: 'call', name: 'formatName', args: [] }
1\n ],\n context: { /* captured callContext */ }\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"This can be:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Sent over the wire to another DO/Worker/Client"}),"\n",(0,s.jsx)(n.li,{children:"Stored for later execution"}),"\n",(0,s.jsxs)(n.li,{children:["Passed to ",(0,s.jsx)(n.code,{children:"this.svc.alarms"}),", which will automatically store it"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"3-execution",children:"3. Execution"}),"\n",(0,s.jsxs)(n.p,{children:["On the receiving end, ",(0,s.jsx)(n.code,{children:"executeOperationChain()"})," walks the chain:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",metastring:"@skip-check-approved('conceptual')",children:"// Conceptual execution logic\nlet result = target;\nfor (const op of chain.ops) {\n if (op.type === 'call') {\n result = result[op.name](...op.args);\n } else if (op.type === 'get') {\n result = result[op.name];\n }\n}\nreturn result;\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Before execution, ",(0,s.jsx)(n.code,{children:"callContext"})," is restored from the captured snapshot \u2014 ensuring your handler sees the correct auth and context even when the execution is on another node. Similarly, you can ",(0,s.jsx)(n.a,{href:"./managing-context#manual-persistence-power-users",children:"explicitly store/restore the serialized form"})," to add resiliency through hibernation/eviction."]}),"\n",(0,s.jsx)(n.h2,{id:"ocan-operation-chaining-and-nesting",children:"OCAN: Operation Chaining And Nesting"}),"\n",(0,s.jsxs)(n.p,{children:["Continuations use ",(0,s.jsx)(n.strong,{children:"OCAN"})," (Operation Chaining And Nesting) \u2014 a serialization format that supports:"]}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Feature"}),(0,s.jsx)(n.th,{children:"Example"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Chaining"})}),(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.code,{children:"a().b().c()"})," \u2014 sequential calls on returned values"]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Nesting"})}),(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.code,{children:"a(b(), c())"})," \u2014 results of inner calls as arguments"]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Mixed"})}),(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.code,{children:"a(b().c()).d()"})," \u2014 arbitrary combinations"]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"Placeholders"})}),(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.code,{children:"$result"})," \u2014 substitute async results"]})]})]})]}),"\n",(0,s.jsxs)(n.p,{children:["For the full specification, see ",(0,s.jsx)(n.a,{href:"/docs/rpc/operation-chaining-and-nesting",children:"@lumenize/rpc: Operation Chaining and Nesting"}),". Note, however, that batching is not automatically supported for Lumenize Mesh. After using the old Lumenize RPC for a while, we found that defining methods on the callee to do batch operations was a better approach for production code. It remains in the old Lumenize RPC for backward compatibility."]}),"\n",(0,s.jsx)(n.h2,{id:"manual-serialization-advanced",children:"Manual Serialization (Advanced)"}),"\n",(0,s.jsxs)(n.p,{children:["For power users who need direct access to the serialized form \u2014 extracting chains with ",(0,s.jsx)(n.code,{children:"getOperationChain()"})," and executing them with ",(0,s.jsx)(n.code,{children:"executeOperationChain()"})," \u2014 see ",(0,s.jsx)(n.a,{href:"./managing-context#manual-persistence-power-users",children:"Manual Persistence"})," for a complete example and ",(0,s.jsx)(n.a,{href:"./managing-context#api-reference",children:"API Reference"})," for function signatures."]})]})}function h(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},43023(e,n,t){t.d(n,{R:()=>a,x:()=>o});var i=t(63696);const s={},r=i.createContext(s);function a(e){const n=i.useContext(r);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),i.createElement(r.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.