[{"data":1,"prerenderedAt":3252},["ShallowReactive",2],{"navigation_docs":3,"-guides-storage-adapters":412,"-guides-storage-adapters-surround":3247},[4,38,84,326,386],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":37},"Getting started","i-lucide-rocket","\u002Fgetting-started","1.getting-started",[10,15,20,24,28,32],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fgetting-started\u002Fintroduction","1.getting-started\u002F1.introduction","i-lucide-book-open",{"title":16,"path":17,"stem":18,"icon":19},"Installation","\u002Fgetting-started\u002Finstallation","1.getting-started\u002F2.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":6},"Quick start: Nuxt","\u002Fgetting-started\u002Fquick-start-nuxt","1.getting-started\u002F3.quick-start-nuxt",{"title":25,"path":26,"stem":27,"icon":6},"Quick start: plain Vue","\u002Fgetting-started\u002Fquick-start-vue","1.getting-started\u002F4.quick-start-vue",{"title":29,"path":30,"stem":31,"icon":6},"Quick start: React","\u002Fgetting-started\u002Fquick-start-react","1.getting-started\u002F5.quick-start-react",{"title":33,"path":34,"stem":35,"icon":36},"Glossary","\u002Fgetting-started\u002Fglossary","1.getting-started\u002F6.glossary","i-lucide-book-a",false,{"title":39,"icon":40,"path":41,"stem":42,"children":43,"page":37},"Tutorial","i-lucide-graduation-cap","\u002Ftutorial","2.tutorial",[44,49,54,59,64,69,74,79],{"title":45,"path":46,"stem":47,"icon":48},"Tutorial: overview","\u002Ftutorial\u002Foverview","2.tutorial\u002F1.overview","i-lucide-map",{"title":50,"path":51,"stem":52,"icon":53},"Chapter 2: A simple editor","\u002Ftutorial\u002Fsimple-editor","2.tutorial\u002F2.simple-editor","i-lucide-pencil",{"title":55,"path":56,"stem":57,"icon":58},"Chapter 3: Compose features","\u002Ftutorial\u002Fcompose-features","2.tutorial\u002F3.compose-features","i-lucide-blocks",{"title":60,"path":61,"stem":62,"icon":63},"Chapter 4: Your own block, path A","\u002Ftutorial\u002Fown-block-path-a","2.tutorial\u002F4.own-block-path-a","i-lucide-square-plus",{"title":65,"path":66,"stem":67,"icon":68},"Chapter 5: Your own feature, path B","\u002Ftutorial\u002Fown-feature-path-b","2.tutorial\u002F5.own-feature-path-b","i-lucide-puzzle",{"title":70,"path":71,"stem":72,"icon":73},"Chapter 6: Backend and collaboration","\u002Ftutorial\u002Fbackend-and-collaboration","2.tutorial\u002F6.backend-and-collaboration","i-lucide-users",{"title":75,"path":76,"stem":77,"icon":78},"Chapter 7: AI","\u002Ftutorial\u002Fai","2.tutorial\u002F7.ai","i-lucide-sparkles",{"title":80,"path":81,"stem":82,"icon":83},"Chapter 8: The React surface","\u002Ftutorial\u002Freact-surface","2.tutorial\u002F8.react-surface","i-lucide-atom",{"title":85,"icon":14,"path":86,"stem":87,"children":88,"page":37},"Guides","\u002Fguides","3.guides",[89,93,98,103,108,112,116,121,126,130,134,139,144,149,154,159,164,169,174,179,184,189,194,199,204,209,213,218,223,228,233,237,242,247,251,256,261,266,271,276,281,286,291,295,302,307,311,316,321],{"title":90,"path":91,"stem":92,"icon":58},"Feature composition and entries","\u002Fguides\u002Fcomposition","3.guides\u002F1.composition",{"title":94,"path":95,"stem":96,"icon":97},"Media, mentions and transcript features","\u002Fguides\u002Fmedia-mentions-transcript","3.guides\u002F10.media-mentions-transcript","i-lucide-image",{"title":99,"path":100,"stem":101,"icon":102},"Authors and comments","\u002Fguides\u002Fauthors-and-comments","3.guides\u002F11.authors-and-comments","i-lucide-message-square-text",{"title":104,"path":105,"stem":106,"icon":107},"Themes (Obsidian)","\u002Fguides\u002Fthemes","3.guides\u002F12.themes","i-lucide-palette",{"title":109,"path":110,"stem":111,"icon":58},"Composites and the settings UI","\u002Fguides\u002Fcomposites","3.guides\u002F13.composites",{"title":113,"path":114,"stem":115,"icon":78},"AI","\u002Fguides\u002Fai","3.guides\u002F14.ai",{"title":117,"path":118,"stem":119,"icon":120},"Document tree","\u002Fguides\u002Fdocument-tree","3.guides\u002F15.document-tree","i-lucide-folder-tree",{"title":122,"path":123,"stem":124,"icon":125},"Markdown and code","\u002Fguides\u002Fmarkdown-and-code","3.guides\u002F16.markdown-and-code","i-lucide-file-code",{"title":127,"path":128,"stem":129,"icon":83},"Advanced editor in React","\u002Fguides\u002Fadvanced-react","3.guides\u002F17.advanced-react",{"title":131,"path":132,"stem":133,"icon":6},"Kitchen sink: learning path","\u002Fguides\u002Fadvanced-nuxt-example","3.guides\u002F18.advanced-nuxt-example",{"title":135,"path":136,"stem":137,"icon":138},"Production","\u002Fguides\u002Fproduction","3.guides\u002F19.production","i-lucide-server",{"title":140,"path":141,"stem":142,"icon":143},"Register the Nuxt module","\u002Fguides\u002Fnuxt-module","3.guides\u002F2.nuxt-module","i-lucide-package",{"title":145,"path":146,"stem":147,"icon":148},"Storage adapters, comment stores, Nitro host","\u002Fguides\u002Fstorage-adapters","3.guides\u002F20.storage-adapters","i-lucide-database",{"title":150,"path":151,"stem":152,"icon":153},"Migrating from #tiptap-editor","\u002Fguides\u002Fmigration","3.guides\u002F21.migration","i-lucide-arrow-right-left",{"title":155,"path":156,"stem":157,"icon":158},"Content tree mechanics","\u002Fguides\u002Fcontent-tree","3.guides\u002F22.content-tree","i-lucide-git-fork",{"title":160,"path":161,"stem":162,"icon":163},"Block capabilities, abilities and actions","\u002Fguides\u002Fblock-capabilities","3.guides\u002F23.block-capabilities","i-lucide-shield-check",{"title":165,"path":166,"stem":167,"icon":168},"Block types in code and JSON","\u002Fguides\u002Fblock-types","3.guides\u002F24.block-types","i-lucide-shapes",{"title":170,"path":171,"stem":172,"icon":173},"Derive methods and the Derivation index","\u002Fguides\u002Fderive-methods","3.guides\u002F25.derive-methods","i-lucide-git-branch-plus",{"title":175,"path":176,"stem":177,"icon":178},"Block properties and Property definitions","\u002Fguides\u002Fblock-properties","3.guides\u002F26.block-properties","i-lucide-sliders-horizontal",{"title":180,"path":181,"stem":182,"icon":183},"Image studio","\u002Fguides\u002Fimage-studio","3.guides\u002F27.image-studio","i-lucide-image-plus",{"title":185,"path":186,"stem":187,"icon":188},"Prüfung and Freigabestatus","\u002Fguides\u002Fpruefung","3.guides\u002F28.pruefung","i-lucide-stamp",{"title":190,"path":191,"stem":192,"icon":193},"Workspace: find, read, reference and embed Blocks across Documents","\u002Fguides\u002Fworkspace","3.guides\u002F29.workspace","i-lucide-network",{"title":195,"path":196,"stem":197,"icon":198},"EditorPorts: auth and storage","\u002Fguides\u002Feditor-ports","3.guides\u002F3.editor-ports","i-lucide-plug",{"title":200,"path":201,"stem":202,"icon":203},"Collections: an ordered list of Blocks","\u002Fguides\u002Fcollections","3.guides\u002F30.collections","i-lucide-list-checks",{"title":205,"path":206,"stem":207,"icon":208},"Data views: a query shown as table, list or board","\u002Fguides\u002Fdata-views","3.guides\u002F31.data-views","i-lucide-table-properties",{"title":210,"path":211,"stem":212,"icon":193},"Explore: backlinks, related Blocks and the trail","\u002Fguides\u002Fexplore","3.guides\u002F32.explore",{"title":214,"path":215,"stem":216,"icon":217},"Render: export a Block or a Document to a file","\u002Fguides\u002Frender","3.guides\u002F33.render","i-lucide-file-output",{"title":219,"path":220,"stem":221,"icon":222},"Importers: what a dropped file becomes","\u002Fguides\u002Fimporters","3.guides\u002F34.importers","i-lucide-file-input",{"title":224,"path":225,"stem":226,"icon":227},"Templates: start a Document from a Dokumentvorlage","\u002Fguides\u002Ftemplates","3.guides\u002F35.templates","i-lucide-file-stack",{"title":229,"path":230,"stem":231,"icon":232},"Citations","\u002Fguides\u002Fcitations","3.guides\u002F36.citations","i-lucide-quote",{"title":234,"path":235,"stem":236,"icon":163},"AI proposals and the export check","\u002Fguides\u002Fexport-check","3.guides\u002F37.export-check",{"title":238,"path":239,"stem":240,"icon":241},"Query: filter, sort, group and total Blocks","\u002Fguides\u002Fquery","3.guides\u002F38.query","i-lucide-list-filter",{"title":243,"path":244,"stem":245,"icon":246},"Edges: backlinks and authored connections","\u002Fguides\u002Fedges","3.guides\u002F39.edges","i-lucide-link-2",{"title":248,"path":249,"stem":250,"icon":163},"Server routes and authentication","\u002Fguides\u002Froutes-and-auth","3.guides\u002F4.routes-and-auth",{"title":252,"path":253,"stem":254,"icon":255},"Speech: text to audio, and subtitle files","\u002Fguides\u002Fspeech","3.guides\u002F41.speech","i-lucide-audio-lines",{"title":257,"path":258,"stem":259,"icon":260},"Graph: the Edges around a Block","\u002Fguides\u002Fgraph","3.guides\u002F42.graph","i-lucide-waypoints",{"title":262,"path":263,"stem":264,"icon":265},"PDF viewer: passages, anchors and quotes","\u002Fguides\u002Fpdf-viewer","3.guides\u002F43.pdf-viewer","i-lucide-file-search",{"title":267,"path":268,"stem":269,"icon":270},"Obsidian: import and export a vault","\u002Fguides\u002Fobsidian","3.guides\u002F44.obsidian","i-lucide-folder-input",{"title":272,"path":273,"stem":274,"icon":275},"Typst: render a Document as a PDF with citations","\u002Fguides\u002Ftypst","3.guides\u002F45.typst","i-lucide-file-text",{"title":277,"path":278,"stem":279,"icon":280},"Audio: read aloud, mixdown and ElevenLabs","\u002Fguides\u002Faudio","3.guides\u002F46.audio","i-lucide-audio-waveform",{"title":282,"path":283,"stem":284,"icon":285},"Slides: decks, slide layouts and exports","\u002Fguides\u002Fslides","3.guides\u002F47.slides","i-lucide-presentation",{"title":287,"path":288,"stem":289,"icon":290},"Knowledge graph: sources and concepts beside the Documents","\u002Fguides\u002Fknowledge-graph","3.guides\u002F49.knowledge-graph","i-lucide-share-2",{"title":292,"path":293,"stem":294,"icon":78},"AI configuration and secrets","\u002Fguides\u002Fai-configuration","3.guides\u002F5.ai-configuration",{"title":296,"path":297,"stem":298,"children":299,"icon":301},"Index on Postgres: typos, meaning and SQL pushdown","\u002Fguides\u002Findex-pg","3.guides\u002F50.index-pg",[300],{"title":296,"path":297,"stem":298,"icon":301},"i-lucide-database-zap",{"title":303,"path":304,"stem":305,"icon":306},"Charts: bar, line, pie and scatter over a query","\u002Fguides\u002Fcharts","3.guides\u002F51.charts","i-lucide-chart-column",{"title":308,"path":309,"stem":310,"icon":73},"Collaboration on the client","\u002Fguides\u002Fcollaboration","3.guides\u002F6.collaboration",{"title":312,"path":313,"stem":314,"icon":315},"Block UI (drag handle, hover actions)","\u002Fguides\u002Fblock-ui","3.guides\u002F7.block-ui","i-lucide-grip-vertical",{"title":317,"path":318,"stem":319,"icon":320},"Settings and app lifetime","\u002Fguides\u002Fsettings","3.guides\u002F8.settings","i-lucide-settings",{"title":322,"path":323,"stem":324,"icon":325},"Language","\u002Fguides\u002Flanguage","3.guides\u002F9.language","i-lucide-languages",{"title":327,"icon":14,"path":328,"stem":329,"children":330,"page":37},"Reference","\u002Freference","4.reference",[331,335,339,344,348,352,356,360,364,368,373,378,382],{"title":332,"path":333,"stem":334,"icon":143},"Public exports","\u002Freference\u002Fexports","4.reference\u002F1.exports",{"title":336,"path":337,"stem":338,"icon":173},"Derive method contract","\u002Freference\u002Fderive-methods","4.reference\u002F10.derive-methods",{"title":340,"path":341,"stem":342,"icon":343},"Changelog","\u002Freference\u002Fchangelog","4.reference\u002F11.changelog","i-lucide-history",{"title":345,"path":346,"stem":347,"icon":217},"Renderer port","\u002Freference\u002Frenderer-port","4.reference\u002F12.renderer-port",{"title":349,"path":350,"stem":351,"icon":255},"Speech port","\u002Freference\u002Fspeech-port","4.reference\u002F13.speech-port",{"title":353,"path":354,"stem":355,"icon":58},"Presets and features","\u002Freference\u002Fpresets-and-features","4.reference\u002F2.presets-and-features",{"title":357,"path":358,"stem":359,"icon":68},"Feature contract","\u002Freference\u002Ffeature-contract","4.reference\u002F3.feature-contract",{"title":361,"path":362,"stem":363,"icon":107},"CSS and UI-provider requirements","\u002Freference\u002Fcss-and-ui","4.reference\u002F4.css-and-ui",{"title":365,"path":366,"stem":367,"icon":148},"Vue-free advanced schema","\u002Freference\u002Fadvanced-schema","4.reference\u002F5.advanced-schema",{"title":369,"path":370,"stem":371,"icon":372},"Dependency rules","\u002Freference\u002Fdependency-rules","4.reference\u002F6.dependency-rules","i-lucide-git-branch",{"title":374,"path":375,"stem":376,"icon":377},"Commands","\u002Freference\u002Fcommands","4.reference\u002F7.commands","i-lucide-terminal",{"title":379,"path":380,"stem":381,"icon":163},"Block capability contract","\u002Freference\u002Fblock-capabilities","4.reference\u002F8.block-capabilities",{"title":383,"path":384,"stem":385,"icon":168},"Block type contract","\u002Freference\u002Fblock-types","4.reference\u002F9.block-types",{"title":387,"path":388,"stem":389,"children":390,"icon":393},"AI agents","\u002Fai-agents","5.ai-agents\u002F1.index",[391,394,398,402,407],{"title":392,"path":388,"stem":389,"icon":393},"Overview","i-lucide-bot",{"title":395,"path":396,"stem":397,"icon":198},"Integrate the editor","\u002Fai-agents\u002Fintegrate","5.ai-agents\u002F2.integrate",{"title":399,"path":400,"stem":401,"icon":68},"Add a block or feature","\u002Fai-agents\u002Fextend","5.ai-agents\u002F3.extend",{"title":403,"path":404,"stem":405,"icon":406},"Pitfalls and invariants","\u002Fai-agents\u002Finvariants","5.ai-agents\u002F4.invariants","i-lucide-shield-alert",{"title":408,"path":409,"stem":410,"icon":411},"Verify","\u002Fai-agents\u002Fverify","5.ai-agents\u002F5.verify","i-lucide-check-check",{"id":413,"title":145,"body":414,"description":3240,"extension":3241,"links":3242,"meta":3243,"navigation":3244,"path":146,"seo":3245,"stem":147,"__hash__":3246},"docs\u002F3.guides\u002F20.storage-adapters.md",{"type":415,"value":416,"toc":3233},"minimark",[417,443,459,934,1047,1052,1055,1102,1106,1261,1268,1272,1312,1614,1638,1641,1697,1704,1708,1754,2154,2170,2174,2411,2521,2985,3144,3207,3229],[418,419,420,421,425,426,430,431,434,435,438,439,442],"p",{},"Ready-made ",[422,423,424],"code",{},"EditorPorts"," bundles (",[427,428,429],"a",{"href":196},"the ports each storage serves","), comment stores, a Nuxt-free Nitro host and a Vue composable ship inside this package as subpath entries (formerly the separate package ",[422,432,433],{},"@skaile\u002Fforge-editor-adapters","; from 2.1.0 ",[422,436,437],{},"@skaile\u002Fforge-editor"," is the only dependency). A host picks where documents live without touching the editor. All adapters satisfy the same contract test (",[422,440,441],{},"test\u002Fadapters\u002Fadapter-contract.test.ts",").",[444,445,450],"pre",{"className":446,"code":447,"language":448,"meta":449,"style":449},"language-sh shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","bun add @skaile\u002Fforge-editor   # plus drizzle-orm only for the three drizzle entries (PGlite or a Postgres driver for the Block index)\n","sh","",[422,451,452],{"__ignoreMap":449},[453,454,457],"span",{"class":455,"line":456},"line",1,[453,458,447],{},[460,461,462,481],"table",{},[463,464,465],"thead",{},[466,467,468,472,475,478],"tr",{},[469,470,471],"th",{},"Entry",[469,473,474],{},"Adapter",[469,476,477],{},"Storage",[469,479,480],{},"Needs",[482,483,484,506,530,551,572,619,648,669,688,705,728,781,830,876,920],"tbody",{},[466,485,486,492,497,503],{},[487,488,489],"td",{},[422,490,491],{},"@skaile\u002Fforge-editor\u002Fadapters\u002Fjson",[487,493,494],{},[422,495,496],{},"createJsonAdapter({ dir, users? })",[487,498,499,500],{},"files under ",[422,501,502],{},"dir",[487,504,505],{},"nothing",[466,507,508,513,518,525],{},[487,509,510],{},[422,511,512],{},"@skaile\u002Fforge-editor\u002Fadapters\u002Fdrizzle",[487,514,515],{},[422,516,517],{},"createDrizzleAdapter({ db, tables: editorTables() })",[487,519,520,521,524],{},"SQLite via drizzle (",[422,522,523],{},"bun:sqlite",", better-sqlite3, libsql, D1)",[487,526,527],{},[422,528,529],{},"drizzle-orm",[466,531,532,537,542,549],{},[487,533,534],{},[422,535,536],{},"@skaile\u002Fforge-editor\u002Fadapters\u002Fforge",[487,538,539],{},[422,540,541],{},"createForgeAdapter({ content, state, schema, authenticate, … })",[487,543,544,545,548],{},"the host's Markdown files; state, assets and settings go to a ",[422,546,547],{},"state"," adapter",[487,550,505],{},[466,552,553,558,563,570],{},[487,554,555],{},[422,556,557],{},"@skaile\u002Fforge-editor\u002Fadapters\u002Fblock-index",[487,559,560],{},[422,561,562],{},"createMemoryBlockIndex()",[487,564,565,566,569],{},"the Block index (",[422,567,568],{},"ports.index",") in the process; rebuilt from the documents on start",[487,571,505],{},[466,573,574,579,588,607],{},[487,575,576],{},[422,577,578],{},"@skaile\u002Fforge-editor\u002Fadapters\u002Fblock-index\u002Fdrizzle",[487,580,581,584,585],{},[422,582,583],{},"createDrizzleBlockIndex({ db, tables: blockIndexTables() })",", ",[422,586,587],{},"bootstrapBlockIndexTables(db)",[487,589,590,591,594,595,598,599,602,603,606],{},"the Block index in Postgres or PGlite (",[422,592,593],{},"editor_block_index",", one ",[422,596,597],{},"tsvector"," per title and text, and ",[422,600,601],{},"editor_block_edges",", one row per Edge; search runs in the database, scope as a SQL filter); same answers as the memory adapter (",[422,604,605],{},"test\u002Fadapters\u002Fblock-index-contract.test.ts",")",[487,608,609,611,612,584,615,618],{},[422,610,529],{},", a Postgres driver (",[422,613,614],{},"@electric-sql\u002Fpglite",[422,616,617],{},"pg",", …)",[466,620,621,626,640,643],{},[487,622,623],{},[422,624,625],{},"@skaile\u002Fforge-editor\u002Fanchors",[487,627,628,584,631,584,634,584,637],{},[422,629,630],{},"captureAnchor",[422,632,633],{},"capturePoint",[422,635,636],{},"resolveAnchor",[422,638,639],{},"mapAnchor",[487,641,642],{},"pure functions over ProseMirror documents",[487,644,645],{},[422,646,647],{},"@tiptap\u002Fpm",[466,649,650,655,660,667],{},[487,651,652],{},[422,653,654],{},"@skaile\u002Fforge-editor\u002Fcomments\u002Fjson",[487,656,657],{},[422,658,659],{},"createJsonCommentStore({ file })",[487,661,662,663,666],{},"one JSON file (forge's ",[422,664,665],{},"concept-discussions.json"," layout)",[487,668,505],{},[466,670,671,676,681,684],{},[487,672,673],{},[422,674,675],{},"@skaile\u002Fforge-editor\u002Fcomments\u002Fdrizzle",[487,677,678],{},[422,679,680],{},"createDrizzleCommentStore({ db, tables: commentTables() })",[487,682,683],{},"SQLite via drizzle",[487,685,686],{},[422,687,529],{},[466,689,690,695,700,703],{},[487,691,692],{},[422,693,694],{},"@skaile\u002Fforge-editor\u002Fcomments\u002Fserver",[487,696,697],{},[422,698,699],{},"handleCommentsRequest(store, { method, documentId, body })",[487,701,702],{},"the REST handler a host routes its comments endpoint to",[487,704,505],{},[466,706,707,712,717,723],{},[487,708,709],{},[422,710,711],{},"@skaile\u002Fforge-editor\u002Fserver\u002Fnitro",[487,713,714],{},[422,715,716],{},"installEditorServer(nitroApp, { ports, collab, ai?, rateLimit?, schema? })",[487,718,719,720,606],{},"mounts the editor's routes, the collaboration websocket and optional AI and comments routes on a Nitro app (comments: ",[422,721,722],{},"ports.comments",[487,724,725],{},[422,726,727],{},"h3",[466,729,730,735,740,770],{},[487,731,732],{},[422,733,734],{},"@skaile\u002Fforge-editor\u002Fadapters\u002Fvue",[487,736,737],{},[422,738,739],{},"useEditorDocument(id, { backend?, user?, canComment?, collaboration? })",[487,741,742,743,746,747,584,750,584,753,756,757,584,760,584,763,584,766,584,768],{},"the document a Vue editor surface binds to, through ",[422,744,745],{},"EditorBackend"," (",[422,748,749],{},"documents",[422,751,752],{},"collaboration",[422,754,755],{},"comments","): ",[422,758,759],{},"modelValue",[422,761,762],{},"dirty",[422,764,765],{},"save()",[422,767,752],{},[422,769,755],{},[487,771,772,584,775,584,778],{},[422,773,774],{},"vue",[422,776,777],{},"yjs",[422,779,780],{},"@hocuspocus\u002Fprovider",[466,782,783,788,792,821],{},[487,784,785],{},[422,786,787],{},"@skaile\u002Fforge-editor\u002Fadapters\u002Freact",[487,789,790],{},[422,791,739],{},[487,793,794,795,584,797,584,800,584,803,584,805,584,808,584,811,584,814,584,816,584,818],{},"the same for a React surface: ",[422,796,759],{},[422,798,799],{},"onChange",[422,801,802],{},"ready",[422,804,762],{},[422,806,807],{},"save",[422,809,810],{},"reload",[422,812,813],{},"error",[422,815,752],{},[422,817,755],{},[422,819,820],{},"bind",[487,822,823,584,826,584,828],{},[422,824,825],{},"react",[422,827,777],{},[422,829,780],{},[466,831,832,837,842,867],{},[487,833,834],{},[422,835,836],{},"@skaile\u002Fforge-editor\u002Fadapters\u002Fhttp",[487,838,839],{},[422,840,841],{},"createHttpBackend({ fetch?, base?, probe? })",[487,843,844,845,746,847,850,851,854,855,858,859,862,863,866],{},"the client port ",[422,846,745],{},[422,848,849],{},"createEditorContext({ backend })",") over the editor's ",[422,852,853],{},"\u002Fapi\u002Feditor"," and ",[422,856,857],{},"\u002Fapi\u002Fai"," routes, the collaboration websocket and the comments endpoint; the default backend of every editor context. ",[422,860,861],{},"probe: true"," asks ",[422,864,865],{},"GET \u002Fapi\u002Feditor\u002Fcapabilities"," once and drops the capabilities the server does not serve (see below)",[487,868,869,584,871,584,873],{},[422,870,780],{},[422,872,777],{},[422,874,875],{},"ofetch",[466,877,878,883,888,918],{},[487,879,880],{},[422,881,882],{},"@skaile\u002Fforge-editor\u002Fadapters\u002Fdirect-ai",[487,884,885],{},[422,886,887],{},"createDirectAi({ presets, roles?, users?, fetch? })",[487,889,890,891,894,895,897,898,584,901,584,904,854,907,910,911,854,914,917],{},"the ",[422,892,893],{},"ai"," capability of ",[422,896,745],{}," without a server: ",[422,899,900],{},"models",[422,902,903],{},"complete",[422,905,906],{},"generate",[422,908,909],{},"transcriptCleanup"," call OpenAI-compatible chat-completion endpoints straight from the page (see below); ",[422,912,913],{},"chat",[422,915,916],{},"extract"," answer 501",[487,919,505],{},[466,921,922,927,930,932],{},[487,923,924],{},[422,925,926],{},"@skaile\u002Fforge-editor\u002Fadapters",[487,928,929],{},"types only",[487,931],{},[487,933],{},[418,935,936,937,746,939,942,943,584,946,854,949,952,953,955,956,959,960,962,963,584,965,967,968,971,972,975,976,979,980,584,983,584,986,584,989,584,992,584,995,959,998,1001,1002,979,1005,959,1007,979,1010,959,1012,979,1015,1018,1019,979,1022,1025,1026,1028,1029,1031,1032,1035,1036,854,1039,1042,1043,1046],{},"Peers: ",[422,938,529],{},[422,940,941],{},"^0.45.0",", optional) is needed by ",[422,944,945],{},".\u002Fadapters\u002Fdrizzle",[422,947,948],{},".\u002Fcomments\u002Fdrizzle",[422,950,951],{},".\u002Fadapters\u002Fblock-index\u002Fdrizzle"," only; ",[422,954,727],{}," (optional) by ",[422,957,958],{},".\u002Fserver\u002Fnitro","; ",[422,961,774],{}," (optional), ",[422,964,777],{},[422,966,780],{}," by ",[422,969,970],{},".\u002Fadapters\u002Fvue",". ",[422,973,974],{},"bun run check:editor-entries"," builds the adapter entries through its fixtures (",[422,977,978],{},"adapters-node",": ",[422,981,982],{},".\u002Fadapters",[422,984,985],{},".\u002Fadapters\u002Fjson",[422,987,988],{},".\u002Fadapters\u002Fforge",[422,990,991],{},".\u002Fanchors",[422,993,994],{},".\u002Fcomments\u002Fserver",[422,996,997],{},".\u002Fcomments\u002Fjson",[422,999,1000],{},"adapters-drizzle",": the three drizzle entries; ",[422,1003,1004],{},"adapters-nitro",[422,1006,958],{},[422,1008,1009],{},"adapters-vue",[422,1011,970],{},[422,1013,1014],{},"adapters-http",[422,1016,1017],{},".\u002Fadapters\u002Fhttp",", which holds the REST comments model; ",[422,1020,1021],{},"adapters-direct-ai",[422,1023,1024],{},".\u002Fadapters\u002Fdirect-ai",", which reaches no AI SDK, server or HTTP client) and proves on the bundles that Vue is only reachable from ",[422,1027,970],{},", Nitro only from ",[422,1030,958],{},", drizzle only from the two drizzle entries, and that no adapter entry reaches React (except ",[422,1033,1034],{},".\u002Fadapters\u002Freact","). Entries named ",[422,1037,1038],{},".\u002Fserver\u002Fnest",[422,1040,1041],{},".\u002Fadapters\u002Fprisma"," are reserved for a later platform adoption and are not exported; the check fails if one of them appears in ",[422,1044,1045],{},"exports"," before it is designed.",[1048,1049,1051],"h2",{"id":1050},"the-tier-rule","The tier rule",[418,1053,1054],{},"Three tiers, one direction of dependency:",[1056,1057,1058,1079,1088],"ol",{},[1059,1060,1061,746,1065,1067,1068,1071,1072,1074,1075,1078],"li",{},[1062,1063,1064],"strong",{},"The editor",[422,1066,437],{},", everything except ",[422,1069,1070],{},"adapters\u002F",") and the ",[422,1073,424],{}," contract (",[422,1076,1077],{},"@skaile\u002Fforge-editor\u002Fserver","). It knows no storage and no host.",[1059,1080,1081,746,1084,1087],{},[1062,1082,1083],{},"Adapters",[422,1085,1086],{},"src\u002Fadapters\u002F",", the entries above): implementations of that contract and the wiring around it. They depend on the editor; they import no application code, and no entry imports another UI or server framework than the one it is named for.",[1059,1089,1090,1093,1094,1097,1098,1101],{},[1062,1091,1092],{},"Hosts",": an app (Nuxt, a bare Nitro server, Vite, a Fastify or tRPC server) that supplies identity (",[422,1095,1096],{},"authenticate","), a store and its own chrome. A host calls these entries; it never reaches into ",[422,1099,1100],{},"dist\u002F"," internals or table names.",[1048,1103,1105],{"id":1104},"adapter-matrix","Adapter matrix",[460,1107,1108,1129],{},[463,1109,1110],{},[466,1111,1112,1114,1119,1124],{},[469,1113],{},[469,1115,1116],{},[422,1117,1118],{},"json",[469,1120,1121],{},[422,1122,1123],{},"drizzle",[469,1125,1126],{},[422,1127,1128],{},"forge",[482,1130,1131,1149,1169,1194,1210,1226,1248],{},[466,1132,1133,1136,1141,1146],{},[487,1134,1135],{},"Documents",[487,1137,1138],{},[422,1139,1140],{},"\u003Cid>.json",[487,1142,1143],{},[422,1144,1145],{},"editor_documents",[487,1147,1148],{},"the host's Markdown files",[466,1150,1151,1154,1159,1164],{},[487,1152,1153],{},"Collaboration state",[487,1155,1156],{},[422,1157,1158],{},"\u003Cid>.ystate",[487,1160,1161],{},[422,1162,1163],{},"editor_states",[487,1165,890,1166,1168],{},[422,1167,547],{}," adapter you pass in",[466,1170,1171,1174,1182,1190],{},[487,1172,1173],{},"Settings, assets",[487,1175,499,1176,584,1179],{},[422,1177,1178],{},"settings\u002F",[422,1180,1181],{},"assets\u002F",[487,1183,1184,584,1187],{},[422,1185,1186],{},"editor_settings",[422,1188,1189],{},"editor_assets",[487,1191,890,1192,548],{},[422,1193,547],{},[466,1195,1196,1202,1205,1207],{},[487,1197,1198,1199,606],{},"Creates documents (",[422,1200,1201],{},"PUT \u002Fapi\u002Feditor\u002Fdocuments\u002F:id",[487,1203,1204],{},"yes",[487,1206,1204],{},[487,1208,1209],{},"no (the host owns its files)",[466,1211,1212,1215,1218,1221],{},[487,1213,1214],{},"Default identity",[487,1216,1217],{},"anonymous dev user outside production",[487,1219,1220],{},"same",[487,1222,1223,1224],{},"none: the host supplies ",[422,1225,1096],{},[466,1227,1228,1231,1236,1241],{},[487,1229,1230],{},"Comment store",[487,1232,1233],{},[422,1234,1235],{},"comments\u002Fjson",[487,1237,1238],{},[422,1239,1240],{},"comments\u002Fdrizzle",[487,1242,1243,1245,1246],{},[422,1244,1235],{}," or ",[422,1247,1240],{},[466,1249,1250,1253,1256,1259],{},[487,1251,1252],{},"Nodes",[487,1254,1255],{},"one process",[487,1257,1258],{},"SQLite writers of the driver",[487,1260,1255],{},[418,1262,1263,1264,1267],{},"All three pass the adapter contract test; both comment stores pass ",[422,1265,1266],{},"test\u002Fadapters\u002Fcomment-store.test.ts",". The JSON and forge adapters and the JSON comment store are single-node: no cross-process locking.",[1048,1269,1271],{"id":1270},"browser-direct-ai","Browser-direct AI",[418,1273,1274,1275,1277,1278,1281,1282,1285,1286,1289,1290,1293,1294,584,1297,584,1300,1303,1304,1307,1308,1311],{},"By default the ",[422,1276,893],{}," capability is a relay: the browser calls ",[422,1279,1280],{},"\u002Fapi\u002Fai\u002F*",", and the server holds the model key, applies the per-user rate limit (",[422,1283,1284],{},"config.ai.rateLimit",", enforced in the server handlers only) and calls the model. A host without a server, or one that points the editor at a local model, drops the relay: ",[422,1287,1288],{},"createDirectAi"," is an ",[422,1291,1292],{},"EditorBackend['ai']"," that runs the same protocol modules as the handlers (",[422,1295,1296],{},"runGenerate",[422,1298,1299],{},"runCompletion",[422,1301,1302],{},"runTranscriptCleanup",", so prompts, limits and results are identical) and calls ",[422,1305,1306],{},"\u003Cendpoint>\u002Fchat\u002Fcompletions"," itself, over a plain ",[422,1309,1310],{},"fetch"," with a hand-written SSE reader (no AI SDK in the bundle). It goes beside the memory backend:",[444,1313,1317],{"className":1314,"code":1315,"language":1316,"meta":449,"style":449},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { createDirectAi } from '@skaile\u002Fforge-editor\u002Fadapters\u002Fdirect-ai'\nimport { createMemoryBackend } from '@skaile\u002Fforge-editor\u002Fadapters\u002Fmemory'\n\nconst backend = {\n  ...createMemoryBackend({ users }),\n  ai: createDirectAi({\n    presets: { local: { label: 'Lokales Modell', endpoint: 'http:\u002F\u002Flocalhost:11434\u002Fv1', model: 'llama3.1' } },\n    roles: { complete: 'local' }, \u002F\u002F Model roles without an entry use the first preset\n    users: () => ['anna', 'ben'], \u002F\u002F offered to tasks that write `@` mentions\n  }),\n}\napp.use(createEditor({ backend }))\n","ts",[422,1318,1319,1348,1369,1376,1392,1419,1436,1500,1529,1570,1580,1586],{"__ignoreMap":449},[453,1320,1321,1325,1329,1333,1336,1339,1342,1345],{"class":455,"line":456},[453,1322,1324],{"class":1323},"s7zQu","import",[453,1326,1328],{"class":1327},"sMK4o"," {",[453,1330,1332],{"class":1331},"sTEyZ"," createDirectAi",[453,1334,1335],{"class":1327}," }",[453,1337,1338],{"class":1323}," from",[453,1340,1341],{"class":1327}," '",[453,1343,882],{"class":1344},"sfazB",[453,1346,1347],{"class":1327},"'\n",[453,1349,1351,1353,1355,1358,1360,1362,1364,1367],{"class":455,"line":1350},2,[453,1352,1324],{"class":1323},[453,1354,1328],{"class":1327},[453,1356,1357],{"class":1331}," createMemoryBackend",[453,1359,1335],{"class":1327},[453,1361,1338],{"class":1323},[453,1363,1341],{"class":1327},[453,1365,1366],{"class":1344},"@skaile\u002Fforge-editor\u002Fadapters\u002Fmemory",[453,1368,1347],{"class":1327},[453,1370,1372],{"class":455,"line":1371},3,[453,1373,1375],{"emptyLinePlaceholder":1374},true,"\n",[453,1377,1379,1383,1386,1389],{"class":455,"line":1378},4,[453,1380,1382],{"class":1381},"spNyl","const",[453,1384,1385],{"class":1331}," backend ",[453,1387,1388],{"class":1327},"=",[453,1390,1391],{"class":1327}," {\n",[453,1393,1395,1398,1402,1405,1408,1411,1414,1416],{"class":455,"line":1394},5,[453,1396,1397],{"class":1327},"  ...",[453,1399,1401],{"class":1400},"s2Zo4","createMemoryBackend",[453,1403,1404],{"class":1331},"(",[453,1406,1407],{"class":1327},"{",[453,1409,1410],{"class":1331}," users ",[453,1412,1413],{"class":1327},"}",[453,1415,606],{"class":1331},[453,1417,1418],{"class":1327},",\n",[453,1420,1422,1426,1429,1431,1433],{"class":455,"line":1421},6,[453,1423,1425],{"class":1424},"swJcz","  ai",[453,1427,1428],{"class":1327},":",[453,1430,1332],{"class":1400},[453,1432,1404],{"class":1331},[453,1434,1435],{"class":1327},"{\n",[453,1437,1439,1442,1444,1446,1449,1451,1453,1456,1458,1460,1463,1466,1469,1472,1474,1476,1479,1481,1483,1486,1488,1490,1493,1495,1497],{"class":455,"line":1438},7,[453,1440,1441],{"class":1424},"    presets",[453,1443,1428],{"class":1327},[453,1445,1328],{"class":1327},[453,1447,1448],{"class":1424}," local",[453,1450,1428],{"class":1327},[453,1452,1328],{"class":1327},[453,1454,1455],{"class":1424}," label",[453,1457,1428],{"class":1327},[453,1459,1341],{"class":1327},[453,1461,1462],{"class":1344},"Lokales Modell",[453,1464,1465],{"class":1327},"'",[453,1467,1468],{"class":1327},",",[453,1470,1471],{"class":1424}," endpoint",[453,1473,1428],{"class":1327},[453,1475,1341],{"class":1327},[453,1477,1478],{"class":1344},"http:\u002F\u002Flocalhost:11434\u002Fv1",[453,1480,1465],{"class":1327},[453,1482,1468],{"class":1327},[453,1484,1485],{"class":1424}," model",[453,1487,1428],{"class":1327},[453,1489,1341],{"class":1327},[453,1491,1492],{"class":1344},"llama3.1",[453,1494,1465],{"class":1327},[453,1496,1335],{"class":1327},[453,1498,1499],{"class":1327}," },\n",[453,1501,1503,1506,1508,1510,1513,1515,1517,1520,1522,1525],{"class":455,"line":1502},8,[453,1504,1505],{"class":1424},"    roles",[453,1507,1428],{"class":1327},[453,1509,1328],{"class":1327},[453,1511,1512],{"class":1424}," complete",[453,1514,1428],{"class":1327},[453,1516,1341],{"class":1327},[453,1518,1519],{"class":1344},"local",[453,1521,1465],{"class":1327},[453,1523,1524],{"class":1327}," },",[453,1526,1528],{"class":1527},"sHwdD"," \u002F\u002F Model roles without an entry use the first preset\n",[453,1530,1532,1535,1537,1540,1543,1546,1548,1551,1553,1555,1557,1560,1562,1565,1567],{"class":455,"line":1531},9,[453,1533,1534],{"class":1400},"    users",[453,1536,1428],{"class":1327},[453,1538,1539],{"class":1327}," ()",[453,1541,1542],{"class":1381}," =>",[453,1544,1545],{"class":1331}," [",[453,1547,1465],{"class":1327},[453,1549,1550],{"class":1344},"anna",[453,1552,1465],{"class":1327},[453,1554,1468],{"class":1327},[453,1556,1341],{"class":1327},[453,1558,1559],{"class":1344},"ben",[453,1561,1465],{"class":1327},[453,1563,1564],{"class":1331},"]",[453,1566,1468],{"class":1327},[453,1568,1569],{"class":1527}," \u002F\u002F offered to tasks that write `@` mentions\n",[453,1571,1573,1576,1578],{"class":455,"line":1572},10,[453,1574,1575],{"class":1327},"  }",[453,1577,606],{"class":1331},[453,1579,1418],{"class":1327},[453,1581,1583],{"class":455,"line":1582},11,[453,1584,1585],{"class":1327},"}\n",[453,1587,1589,1592,1595,1598,1600,1603,1605,1607,1609,1611],{"class":455,"line":1588},12,[453,1590,1591],{"class":1331},"app",[453,1593,1594],{"class":1327},".",[453,1596,1597],{"class":1400},"use",[453,1599,1404],{"class":1331},[453,1601,1602],{"class":1400},"createEditor",[453,1604,1404],{"class":1331},[453,1606,1407],{"class":1327},[453,1608,1385],{"class":1331},[453,1610,1413],{"class":1327},[453,1612,1613],{"class":1331},"))\n",[418,1615,1616,1619,1620,959,1623,1626,1627,1630,1631,1634,1635,1637],{},[422,1617,1618],{},"endpoint"," is the OpenAI-compatible base URL, as for the server's ",[422,1621,1622],{},"ModelPresetConfig.endpoint",[422,1624,1625],{},"models()"," lists the Model presets without their keys, and a request may also carry its own ",[422,1628,1629],{},"{ endpoint, model, apiKey }"," (the Model profiles users add in the settings). A refused model request (HTTP error, unreachable endpoint) rejects with a ",[422,1632,1633],{},"BackendError"," carrying the upstream status before anything streams, like the server's non-2xx answers; a failure inside the stream is an ",[422,1636,813],{}," event.",[418,1639,1640],{},"Limits to know before choosing it:",[1642,1643,1644,1654,1660,1691],"ul",{},[1059,1645,1646,1649,1650,1653],{},[1062,1647,1648],{},"The key is in the page."," A ",[422,1651,1652],{},"presets[].apiKey"," is shipped to every visitor and readable in the network panel. Use it for local models, endpoints without a key, or keys the user brings; keep a key that must stay secret behind the server routes.",[1059,1655,1656,1659],{},[1062,1657,1658],{},"No rate limit."," The per-user limit lives in the server handlers, so nothing throttles direct calls except the model provider itself.",[1059,1661,1662,1669,1670,1672,1673,1675,1676,1678,1679,1682,1683,756,1685,1688,1689,1594],{},[1062,1663,1664,854,1666,1668],{},[422,1665,913],{},[422,1667,916],{}," need a server."," The document chat (tool calls) and ",[422,1671,916],{}," (transcription, OCR, Crossref) answer ",[422,1674,1633],{}," 501. A host that needs them takes ",[422,1677,893],{}," from the HTTP backend instead (",[422,1680,1681],{},"createHttpBackend"," of ",[422,1684,836],{},[422,1686,1687],{},"{ ...createMemoryBackend(), ai: createHttpBackend().ai }",", which sends every AI request to ",[422,1690,857],{},[1059,1692,1693,1696],{},[1062,1694,1695],{},"CORS."," The endpoint must allow the page's origin.",[418,1698,1699,1700,1594],{},"Which capabilities work in the page and which need a server is summarised in ",[427,1701,1703],{"href":1702},"\u002Fguides\u002Feditor-ports#capability-locality","the locality table",[1048,1705,1707],{"id":1706},"your-own-store","Your own store",[418,1709,1710,1711,746,1714,1716,1717,584,1720,584,1723,584,1726,584,1729,584,1732,854,1735,1738,1739,1742,1743,746,1746,1749,1750,1753],{},"A host that keeps comments in its own database implements ",[422,1712,1713],{},"CommentStore",[422,1715,694],{},") and does not have to rewrite the rules. ",[422,1718,1719],{},"addComment",[422,1721,1722],{},"createThread",[422,1724,1725],{},"editComment",[422,1727,1728],{},"removeComment",[422,1730,1731],{},"setThreadStatus",[422,1733,1734],{},"updateAnchor",[422,1736,1737],{},"removeThread"," are the pure operations the JSON store is built from, and ",[422,1740,1741],{},"emptyDiscussion()"," is a document without discussion. Each takes the document's ",[422,1744,1745],{},"Discussion",[422,1747,1748],{},"{ annotations, threads }","), changes it in place and throws a ",[422,1751,1752],{},"CommentStoreError"," (unknown or duplicate ids, inconsistent input) before it changes anything. A store loads the record, applies the operation and writes the record back:",[444,1755,1757],{"className":1314,"code":1756,"language":1316,"meta":449,"style":449},"import { addComment, createThread, emptyDiscussion, type CommentStore, type Discussion } from '@skaile\u002Fforge-editor\u002Fcomments\u002Fserver'\n\n\u002F\u002F `db`: the host's own storage, one `Discussion` per document id\nconst load = async (documentId: string): Promise\u003CDiscussion> => (await db.get(documentId)) ?? emptyDiscussion()\n\nexport const store: Pick\u003CCommentStore, 'getDiscussion' | 'createThread' | 'addComment'> = {\n  getDiscussion: load,\n  async createThread(documentId, input) {\n    const record = await load(documentId)\n    const created = createThread(record, input)\n    await db.put(documentId, record)\n    return created\n  },\n  async addComment(documentId, threadId, comment) {\n    const record = await load(documentId)\n    const thread = addComment(record, threadId, comment)\n    await db.put(documentId, record)\n    return thread\n  },\n}\n",[422,1758,1759,1803,1807,1812,1876,1880,1933,1945,1965,1987,2009,2031,2039,2045,2070,2089,2115,2136,2144,2149],{"__ignoreMap":449},[453,1760,1761,1763,1765,1768,1770,1773,1775,1778,1780,1783,1786,1788,1790,1793,1795,1797,1799,1801],{"class":455,"line":456},[453,1762,1324],{"class":1323},[453,1764,1328],{"class":1327},[453,1766,1767],{"class":1331}," addComment",[453,1769,1468],{"class":1327},[453,1771,1772],{"class":1331}," createThread",[453,1774,1468],{"class":1327},[453,1776,1777],{"class":1331}," emptyDiscussion",[453,1779,1468],{"class":1327},[453,1781,1782],{"class":1323}," type",[453,1784,1785],{"class":1331}," CommentStore",[453,1787,1468],{"class":1327},[453,1789,1782],{"class":1323},[453,1791,1792],{"class":1331}," Discussion",[453,1794,1335],{"class":1327},[453,1796,1338],{"class":1323},[453,1798,1341],{"class":1327},[453,1800,694],{"class":1344},[453,1802,1347],{"class":1327},[453,1804,1805],{"class":455,"line":1350},[453,1806,1375],{"emptyLinePlaceholder":1374},[453,1808,1809],{"class":455,"line":1371},[453,1810,1811],{"class":1527},"\u002F\u002F `db`: the host's own storage, one `Discussion` per document id\n",[453,1813,1814,1816,1819,1821,1824,1826,1830,1832,1836,1839,1842,1845,1847,1850,1852,1854,1857,1860,1862,1865,1868,1871,1873],{"class":455,"line":1378},[453,1815,1382],{"class":1381},[453,1817,1818],{"class":1331}," load ",[453,1820,1388],{"class":1327},[453,1822,1823],{"class":1381}," async",[453,1825,746],{"class":1327},[453,1827,1829],{"class":1828},"sHdIc","documentId",[453,1831,1428],{"class":1327},[453,1833,1835],{"class":1834},"sBMFI"," string",[453,1837,1838],{"class":1327},"):",[453,1840,1841],{"class":1834}," Promise",[453,1843,1844],{"class":1327},"\u003C",[453,1846,1745],{"class":1834},[453,1848,1849],{"class":1327},">",[453,1851,1542],{"class":1381},[453,1853,746],{"class":1331},[453,1855,1856],{"class":1323},"await",[453,1858,1859],{"class":1331}," db",[453,1861,1594],{"class":1327},[453,1863,1864],{"class":1400},"get",[453,1866,1867],{"class":1331},"(documentId)) ",[453,1869,1870],{"class":1327},"??",[453,1872,1777],{"class":1400},[453,1874,1875],{"class":1331},"()\n",[453,1877,1878],{"class":455,"line":1394},[453,1879,1375],{"emptyLinePlaceholder":1374},[453,1881,1882,1885,1888,1891,1893,1896,1898,1900,1902,1904,1907,1909,1912,1914,1916,1918,1920,1922,1924,1926,1928,1931],{"class":455,"line":1421},[453,1883,1884],{"class":1323},"export",[453,1886,1887],{"class":1381}," const",[453,1889,1890],{"class":1331}," store",[453,1892,1428],{"class":1327},[453,1894,1895],{"class":1834}," Pick",[453,1897,1844],{"class":1327},[453,1899,1713],{"class":1834},[453,1901,1468],{"class":1327},[453,1903,1341],{"class":1327},[453,1905,1906],{"class":1344},"getDiscussion",[453,1908,1465],{"class":1327},[453,1910,1911],{"class":1327}," |",[453,1913,1341],{"class":1327},[453,1915,1722],{"class":1344},[453,1917,1465],{"class":1327},[453,1919,1911],{"class":1327},[453,1921,1341],{"class":1327},[453,1923,1719],{"class":1344},[453,1925,1465],{"class":1327},[453,1927,1849],{"class":1327},[453,1929,1930],{"class":1327}," =",[453,1932,1391],{"class":1327},[453,1934,1935,1938,1940,1943],{"class":455,"line":1438},[453,1936,1937],{"class":1424},"  getDiscussion",[453,1939,1428],{"class":1327},[453,1941,1942],{"class":1331}," load",[453,1944,1418],{"class":1327},[453,1946,1947,1950,1952,1954,1956,1958,1961,1963],{"class":455,"line":1502},[453,1948,1949],{"class":1381},"  async",[453,1951,1772],{"class":1424},[453,1953,1404],{"class":1327},[453,1955,1829],{"class":1828},[453,1957,1468],{"class":1327},[453,1959,1960],{"class":1828}," input",[453,1962,606],{"class":1327},[453,1964,1391],{"class":1327},[453,1966,1967,1970,1973,1975,1978,1980,1982,1984],{"class":455,"line":1531},[453,1968,1969],{"class":1381},"    const",[453,1971,1972],{"class":1331}," record",[453,1974,1930],{"class":1327},[453,1976,1977],{"class":1323}," await",[453,1979,1942],{"class":1400},[453,1981,1404],{"class":1424},[453,1983,1829],{"class":1331},[453,1985,1986],{"class":1424},")\n",[453,1988,1989,1991,1994,1996,1998,2000,2003,2005,2007],{"class":455,"line":1572},[453,1990,1969],{"class":1381},[453,1992,1993],{"class":1331}," created",[453,1995,1930],{"class":1327},[453,1997,1772],{"class":1400},[453,1999,1404],{"class":1424},[453,2001,2002],{"class":1331},"record",[453,2004,1468],{"class":1327},[453,2006,1960],{"class":1331},[453,2008,1986],{"class":1424},[453,2010,2011,2014,2016,2018,2021,2023,2025,2027,2029],{"class":455,"line":1582},[453,2012,2013],{"class":1323},"    await",[453,2015,1859],{"class":1331},[453,2017,1594],{"class":1327},[453,2019,2020],{"class":1400},"put",[453,2022,1404],{"class":1424},[453,2024,1829],{"class":1331},[453,2026,1468],{"class":1327},[453,2028,1972],{"class":1331},[453,2030,1986],{"class":1424},[453,2032,2033,2036],{"class":455,"line":1588},[453,2034,2035],{"class":1323},"    return",[453,2037,2038],{"class":1331}," created\n",[453,2040,2042],{"class":455,"line":2041},13,[453,2043,2044],{"class":1327},"  },\n",[453,2046,2048,2050,2052,2054,2056,2058,2061,2063,2066,2068],{"class":455,"line":2047},14,[453,2049,1949],{"class":1381},[453,2051,1767],{"class":1424},[453,2053,1404],{"class":1327},[453,2055,1829],{"class":1828},[453,2057,1468],{"class":1327},[453,2059,2060],{"class":1828}," threadId",[453,2062,1468],{"class":1327},[453,2064,2065],{"class":1828}," comment",[453,2067,606],{"class":1327},[453,2069,1391],{"class":1327},[453,2071,2073,2075,2077,2079,2081,2083,2085,2087],{"class":455,"line":2072},15,[453,2074,1969],{"class":1381},[453,2076,1972],{"class":1331},[453,2078,1930],{"class":1327},[453,2080,1977],{"class":1323},[453,2082,1942],{"class":1400},[453,2084,1404],{"class":1424},[453,2086,1829],{"class":1331},[453,2088,1986],{"class":1424},[453,2090,2092,2094,2097,2099,2101,2103,2105,2107,2109,2111,2113],{"class":455,"line":2091},16,[453,2093,1969],{"class":1381},[453,2095,2096],{"class":1331}," thread",[453,2098,1930],{"class":1327},[453,2100,1767],{"class":1400},[453,2102,1404],{"class":1424},[453,2104,2002],{"class":1331},[453,2106,1468],{"class":1327},[453,2108,2060],{"class":1331},[453,2110,1468],{"class":1327},[453,2112,2065],{"class":1331},[453,2114,1986],{"class":1424},[453,2116,2118,2120,2122,2124,2126,2128,2130,2132,2134],{"class":455,"line":2117},17,[453,2119,2013],{"class":1323},[453,2121,1859],{"class":1331},[453,2123,1594],{"class":1327},[453,2125,2020],{"class":1400},[453,2127,1404],{"class":1424},[453,2129,1829],{"class":1331},[453,2131,1468],{"class":1327},[453,2133,1972],{"class":1331},[453,2135,1986],{"class":1424},[453,2137,2139,2141],{"class":455,"line":2138},18,[453,2140,2035],{"class":1323},[453,2142,2143],{"class":1331}," thread\n",[453,2145,2147],{"class":455,"line":2146},19,[453,2148,2044],{"class":1327},[453,2150,2152],{"class":455,"line":2151},20,[453,2153,1585],{"class":1327},[418,2155,2156,2157,2159,2160,584,2163,854,2166,2169],{},"The other ",[422,2158,1713],{}," methods follow the same three steps; ",[422,2161,2162],{},"getThread",[422,2164,2165],{},"listThreads",[422,2167,2168],{},"removeDocuments"," need no operation. Serialise the read-modify-write per document (a revision column, a transaction) when several writers share the database.",[1048,2171,2173],{"id":2172},"nitro-host-and-composable","Nitro host and composable",[444,2175,2177],{"className":1314,"code":2176,"language":1316,"meta":449,"style":449},"\u002F\u002F server\u002Fplugins\u002Feditor.ts (bare Nitro, no Nuxt)\nimport { createJsonAdapter } from '@skaile\u002Fforge-editor\u002Fadapters\u002Fjson'\nimport { createJsonCommentStore } from '@skaile\u002Fforge-editor\u002Fcomments\u002Fjson'\nimport { installEditorServer } from '@skaile\u002Fforge-editor\u002Fserver\u002Fnitro'\nimport { defineNitroPlugin } from 'nitropack\u002Fruntime'\n\nexport default defineNitroPlugin((nitroApp) => {\n  installEditorServer(nitroApp, {\n    ports: {\n      ...createJsonAdapter({ dir: '.data\u002Fdocuments' }),\n      comments: createJsonCommentStore({ file: '.data\u002Fcomments.json' }), \u002F\u002F optional: mounts \u002Fapi\u002Feditor\u002Fcomments\u002F:id\n    },\n    collab: {},                                                   \u002F\u002F `false`: no websocket, documents still load and save over HTTP; `{ authenticateUpgrade: true }`: 401 at the upgrade\n    \u002F\u002F ...serverConfig(defineEditorConfig({ ai: { apiKey: process.env.AI_KEY } })),   \u002F\u002F optional: mounts \u002Fapi\u002Fai\u002F* with the defaults of the AI configuration guide, replaces `collab`\u002F`ai`\u002F`rateLimit`\n  })\n})\n",[422,2178,2179,2184,2203,2222,2241,2261,2265,2287,2300,2309,2339,2373,2378,2391,2399,2405],{"__ignoreMap":449},[453,2180,2181],{"class":455,"line":456},[453,2182,2183],{"class":1527},"\u002F\u002F server\u002Fplugins\u002Feditor.ts (bare Nitro, no Nuxt)\n",[453,2185,2186,2188,2190,2193,2195,2197,2199,2201],{"class":455,"line":1350},[453,2187,1324],{"class":1323},[453,2189,1328],{"class":1327},[453,2191,2192],{"class":1331}," createJsonAdapter",[453,2194,1335],{"class":1327},[453,2196,1338],{"class":1323},[453,2198,1341],{"class":1327},[453,2200,491],{"class":1344},[453,2202,1347],{"class":1327},[453,2204,2205,2207,2209,2212,2214,2216,2218,2220],{"class":455,"line":1371},[453,2206,1324],{"class":1323},[453,2208,1328],{"class":1327},[453,2210,2211],{"class":1331}," createJsonCommentStore",[453,2213,1335],{"class":1327},[453,2215,1338],{"class":1323},[453,2217,1341],{"class":1327},[453,2219,654],{"class":1344},[453,2221,1347],{"class":1327},[453,2223,2224,2226,2228,2231,2233,2235,2237,2239],{"class":455,"line":1378},[453,2225,1324],{"class":1323},[453,2227,1328],{"class":1327},[453,2229,2230],{"class":1331}," installEditorServer",[453,2232,1335],{"class":1327},[453,2234,1338],{"class":1323},[453,2236,1341],{"class":1327},[453,2238,711],{"class":1344},[453,2240,1347],{"class":1327},[453,2242,2243,2245,2247,2250,2252,2254,2256,2259],{"class":455,"line":1394},[453,2244,1324],{"class":1323},[453,2246,1328],{"class":1327},[453,2248,2249],{"class":1331}," defineNitroPlugin",[453,2251,1335],{"class":1327},[453,2253,1338],{"class":1323},[453,2255,1341],{"class":1327},[453,2257,2258],{"class":1344},"nitropack\u002Fruntime",[453,2260,1347],{"class":1327},[453,2262,2263],{"class":455,"line":1421},[453,2264,1375],{"emptyLinePlaceholder":1374},[453,2266,2267,2269,2272,2274,2276,2278,2281,2283,2285],{"class":455,"line":1438},[453,2268,1884],{"class":1323},[453,2270,2271],{"class":1323}," default",[453,2273,2249],{"class":1400},[453,2275,1404],{"class":1331},[453,2277,1404],{"class":1327},[453,2279,2280],{"class":1828},"nitroApp",[453,2282,606],{"class":1327},[453,2284,1542],{"class":1381},[453,2286,1391],{"class":1327},[453,2288,2289,2292,2294,2296,2298],{"class":455,"line":1502},[453,2290,2291],{"class":1400},"  installEditorServer",[453,2293,1404],{"class":1424},[453,2295,2280],{"class":1331},[453,2297,1468],{"class":1327},[453,2299,1391],{"class":1327},[453,2301,2302,2305,2307],{"class":455,"line":1531},[453,2303,2304],{"class":1424},"    ports",[453,2306,1428],{"class":1327},[453,2308,1391],{"class":1327},[453,2310,2311,2314,2317,2319,2321,2324,2326,2328,2331,2333,2335,2337],{"class":455,"line":1572},[453,2312,2313],{"class":1327},"      ...",[453,2315,2316],{"class":1400},"createJsonAdapter",[453,2318,1404],{"class":1424},[453,2320,1407],{"class":1327},[453,2322,2323],{"class":1424}," dir",[453,2325,1428],{"class":1327},[453,2327,1341],{"class":1327},[453,2329,2330],{"class":1344},".data\u002Fdocuments",[453,2332,1465],{"class":1327},[453,2334,1335],{"class":1327},[453,2336,606],{"class":1424},[453,2338,1418],{"class":1327},[453,2340,2341,2344,2346,2348,2350,2352,2355,2357,2359,2362,2364,2366,2368,2370],{"class":455,"line":1582},[453,2342,2343],{"class":1424},"      comments",[453,2345,1428],{"class":1327},[453,2347,2211],{"class":1400},[453,2349,1404],{"class":1424},[453,2351,1407],{"class":1327},[453,2353,2354],{"class":1424}," file",[453,2356,1428],{"class":1327},[453,2358,1341],{"class":1327},[453,2360,2361],{"class":1344},".data\u002Fcomments.json",[453,2363,1465],{"class":1327},[453,2365,1335],{"class":1327},[453,2367,606],{"class":1424},[453,2369,1468],{"class":1327},[453,2371,2372],{"class":1527}," \u002F\u002F optional: mounts \u002Fapi\u002Feditor\u002Fcomments\u002F:id\n",[453,2374,2375],{"class":455,"line":1588},[453,2376,2377],{"class":1327},"    },\n",[453,2379,2380,2383,2385,2388],{"class":455,"line":2041},[453,2381,2382],{"class":1424},"    collab",[453,2384,1428],{"class":1327},[453,2386,2387],{"class":1327}," {},",[453,2389,2390],{"class":1527},"                                                   \u002F\u002F `false`: no websocket, documents still load and save over HTTP; `{ authenticateUpgrade: true }`: 401 at the upgrade\n",[453,2392,2393,2396],{"class":455,"line":2047},[453,2394,2395],{"class":1527},"    \u002F\u002F ...serverConfig(defineEditorConfig({ ai: { apiKey: process.env.AI_KEY } })),",[453,2397,2398],{"class":1527},"   \u002F\u002F optional: mounts \u002Fapi\u002Fai\u002F* with the defaults of the AI configuration guide, replaces `collab`\u002F`ai`\u002F`rateLimit`\n",[453,2400,2401,2403],{"class":455,"line":2072},[453,2402,1575],{"class":1327},[453,2404,1986],{"class":1424},[453,2406,2407,2409],{"class":455,"line":2091},[453,2408,1413],{"class":1327},[453,2410,1986],{"class":1331},[418,2412,2413,2416,2417,2420,2421,2424,2425,746,2428,2431,2432,2435,2436,2439,2440,2443,2444,2446,2447,2450,2451,2453,2454,2457,2458,2443,2460,746,2463,2466,2467,2469,2470,2472,2473,2475,2476,2479,2480,2483,2484,2487,2488,584,2491,584,2494,854,2497,2500,2501,2504,2505,584,2508,584,2511,584,2513,2516,2517,2520],{},[422,2414,2415],{},"installEditorServer"," is the one installer of both hosts: the ",[427,2418,2419],{"href":141},"Nuxt module"," calls it from its server plugin, a host without Nuxt calls it from its own; set ",[422,2422,2423],{},"experimental: { websocket: true }"," in the Nitro config for the collaboration websocket. It mounts ",[422,2426,2427],{},"serverRoutesOf(ports, { ai })",[422,2429,2430],{},"src\u002Fserver\u002Froutes.ts","), all behind ",[422,2433,2434],{},"ports.identity.authenticate",": the editor's client endpoints (",[422,2437,2438],{},"\u002Fapi\u002Feditor\u002F{settings,users,tags,assets\u002F…,themes\u002F…}",", each served only when its port is present), ",[422,2441,2442],{},"GET","\u002F",[422,2445,1201],{}," (a document's TipTap JSON; ",[422,2448,2449],{},"PUT"," needs a ",[422,2452,749],{}," port that also manages documents: ",[422,2455,2456],{},"putDocument","), ",[422,2459,2442],{},[422,2461,2462],{},"POST \u002Fapi\u002Feditor\u002Fcomments\u002F:id",[422,2464,2465],{},"handleCommentsRequest"," over ",[422,2468,722],{},", mounted only when that port is present), ",[422,2471,1280],{}," with ",[422,2474,893],{},", and ",[422,2477,2478],{},"\u002Fcollab\u002F:id"," (the Hocuspocus websocket) unless ",[422,2481,2482],{},"collab: false",". It returns the ",[422,2485,2486],{},"EditorServer"," handle: ",[422,2489,2490],{},"forgetDocument(id)",[422,2492,2493],{},"liveContent(id)",[422,2495,2496],{},"enforceRateLimit(request)",[422,2498,2499],{},"resolveModel(ref, user)"," act on the collaboration server, AI models and rate limiter the routes use (a Nuxt host reaches it with ",[422,2502,2503],{},"useEditorServer()","). The returned object (",[422,2506,2507],{},"ports",[422,2509,2510],{},"collab",[422,2512,900],{},[422,2514,2515],{},"close()",") is for tests and shutdown; Nitro's ",[422,2518,2519],{},"close"," hook already calls it.",[444,2522,2525],{"className":2523,"code":2524,"language":774,"meta":449,"style":449},"language-vue shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003Cscript setup lang=\"ts\">\nimport { useEditorDocument } from '@skaile\u002Fforge-editor\u002Fadapters\u002Fvue'\nimport { commentsFeature } from '@skaile\u002Fforge-editor\u002Fcomments\u002Fvue'\nimport { coreContentFeature, coreUiFeature, defineEditor } from '@skaile\u002Fforge-editor\u002Fcore'\nimport { EditorSurface } from '@skaile\u002Fforge-editor\u002Fvue'\nimport { ref } from 'vue'\n\nconst id = ref('welcome')\nconst doc = useEditorDocument(id, { user: { id: 'u1', name: 'Anna', color: '#e11d48' } }) \u002F\u002F backend: the one `createEditor({ backend: createHttpBackend() })` installed\nconst preset = defineEditor({ features: [coreContentFeature(), coreUiFeature(), commentsFeature({ model: () => doc.comments.value! })] })\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cbutton :disabled=\"!doc.dirty.value\" @click=\"doc.save()\">Save\u003C\u002Fbutton>\n  \u003CEditorSurface v-if=\"doc.ready.value\" :key=\"id\" :preset=\"preset\" v-bind=\"doc.bind.value\" \u002F>\n\u003C\u002Ftemplate>\n",[422,2526,2527,2552,2571,2591,2621,2641,2660,2664,2686,2761,2835,2844,2848,2857,2900,2977],{"__ignoreMap":449},[453,2528,2529,2531,2534,2537,2540,2542,2545,2547,2549],{"class":455,"line":456},[453,2530,1844],{"class":1327},[453,2532,2533],{"class":1424},"script",[453,2535,2536],{"class":1381}," setup",[453,2538,2539],{"class":1381}," lang",[453,2541,1388],{"class":1327},[453,2543,2544],{"class":1327},"\"",[453,2546,1316],{"class":1344},[453,2548,2544],{"class":1327},[453,2550,2551],{"class":1327},">\n",[453,2553,2554,2556,2558,2561,2563,2565,2567,2569],{"class":455,"line":1350},[453,2555,1324],{"class":1323},[453,2557,1328],{"class":1327},[453,2559,2560],{"class":1331}," useEditorDocument",[453,2562,1335],{"class":1327},[453,2564,1338],{"class":1323},[453,2566,1341],{"class":1327},[453,2568,734],{"class":1344},[453,2570,1347],{"class":1327},[453,2572,2573,2575,2577,2580,2582,2584,2586,2589],{"class":455,"line":1371},[453,2574,1324],{"class":1323},[453,2576,1328],{"class":1327},[453,2578,2579],{"class":1331}," commentsFeature",[453,2581,1335],{"class":1327},[453,2583,1338],{"class":1323},[453,2585,1341],{"class":1327},[453,2587,2588],{"class":1344},"@skaile\u002Fforge-editor\u002Fcomments\u002Fvue",[453,2590,1347],{"class":1327},[453,2592,2593,2595,2597,2600,2602,2605,2607,2610,2612,2614,2616,2619],{"class":455,"line":1378},[453,2594,1324],{"class":1323},[453,2596,1328],{"class":1327},[453,2598,2599],{"class":1331}," coreContentFeature",[453,2601,1468],{"class":1327},[453,2603,2604],{"class":1331}," coreUiFeature",[453,2606,1468],{"class":1327},[453,2608,2609],{"class":1331}," defineEditor",[453,2611,1335],{"class":1327},[453,2613,1338],{"class":1323},[453,2615,1341],{"class":1327},[453,2617,2618],{"class":1344},"@skaile\u002Fforge-editor\u002Fcore",[453,2620,1347],{"class":1327},[453,2622,2623,2625,2627,2630,2632,2634,2636,2639],{"class":455,"line":1394},[453,2624,1324],{"class":1323},[453,2626,1328],{"class":1327},[453,2628,2629],{"class":1331}," EditorSurface",[453,2631,1335],{"class":1327},[453,2633,1338],{"class":1323},[453,2635,1341],{"class":1327},[453,2637,2638],{"class":1344},"@skaile\u002Fforge-editor\u002Fvue",[453,2640,1347],{"class":1327},[453,2642,2643,2645,2647,2650,2652,2654,2656,2658],{"class":455,"line":1421},[453,2644,1324],{"class":1323},[453,2646,1328],{"class":1327},[453,2648,2649],{"class":1331}," ref",[453,2651,1335],{"class":1327},[453,2653,1338],{"class":1323},[453,2655,1341],{"class":1327},[453,2657,774],{"class":1344},[453,2659,1347],{"class":1327},[453,2661,2662],{"class":455,"line":1438},[453,2663,1375],{"emptyLinePlaceholder":1374},[453,2665,2666,2668,2671,2673,2675,2677,2679,2682,2684],{"class":455,"line":1502},[453,2667,1382],{"class":1381},[453,2669,2670],{"class":1331}," id ",[453,2672,1388],{"class":1327},[453,2674,2649],{"class":1400},[453,2676,1404],{"class":1331},[453,2678,1465],{"class":1327},[453,2680,2681],{"class":1344},"welcome",[453,2683,1465],{"class":1327},[453,2685,1986],{"class":1331},[453,2687,2688,2690,2693,2695,2697,2700,2702,2704,2707,2709,2711,2714,2716,2718,2721,2723,2725,2728,2730,2732,2735,2737,2739,2742,2744,2746,2749,2751,2753,2755,2758],{"class":455,"line":1531},[453,2689,1382],{"class":1381},[453,2691,2692],{"class":1331}," doc ",[453,2694,1388],{"class":1327},[453,2696,2560],{"class":1400},[453,2698,2699],{"class":1331},"(id",[453,2701,1468],{"class":1327},[453,2703,1328],{"class":1327},[453,2705,2706],{"class":1424}," user",[453,2708,1428],{"class":1327},[453,2710,1328],{"class":1327},[453,2712,2713],{"class":1424}," id",[453,2715,1428],{"class":1327},[453,2717,1341],{"class":1327},[453,2719,2720],{"class":1344},"u1",[453,2722,1465],{"class":1327},[453,2724,1468],{"class":1327},[453,2726,2727],{"class":1424}," name",[453,2729,1428],{"class":1327},[453,2731,1341],{"class":1327},[453,2733,2734],{"class":1344},"Anna",[453,2736,1465],{"class":1327},[453,2738,1468],{"class":1327},[453,2740,2741],{"class":1424}," color",[453,2743,1428],{"class":1327},[453,2745,1341],{"class":1327},[453,2747,2748],{"class":1344},"#e11d48",[453,2750,1465],{"class":1327},[453,2752,1335],{"class":1327},[453,2754,1335],{"class":1327},[453,2756,2757],{"class":1331},") ",[453,2759,2760],{"class":1527},"\u002F\u002F backend: the one `createEditor({ backend: createHttpBackend() })` installed\n",[453,2762,2763,2765,2768,2770,2772,2774,2776,2779,2781,2783,2786,2789,2791,2793,2795,2797,2799,2801,2803,2805,2807,2809,2811,2814,2816,2818,2820,2823,2826,2828,2831,2833],{"class":455,"line":1572},[453,2764,1382],{"class":1381},[453,2766,2767],{"class":1331}," preset ",[453,2769,1388],{"class":1327},[453,2771,2609],{"class":1400},[453,2773,1404],{"class":1331},[453,2775,1407],{"class":1327},[453,2777,2778],{"class":1424}," features",[453,2780,1428],{"class":1327},[453,2782,1545],{"class":1331},[453,2784,2785],{"class":1400},"coreContentFeature",[453,2787,2788],{"class":1331},"()",[453,2790,1468],{"class":1327},[453,2792,2604],{"class":1400},[453,2794,2788],{"class":1331},[453,2796,1468],{"class":1327},[453,2798,2579],{"class":1400},[453,2800,1404],{"class":1331},[453,2802,1407],{"class":1327},[453,2804,1485],{"class":1400},[453,2806,1428],{"class":1327},[453,2808,1539],{"class":1327},[453,2810,1542],{"class":1381},[453,2812,2813],{"class":1331}," doc",[453,2815,1594],{"class":1327},[453,2817,755],{"class":1331},[453,2819,1594],{"class":1327},[453,2821,2822],{"class":1331},"value",[453,2824,2825],{"class":1327},"!",[453,2827,1335],{"class":1327},[453,2829,2830],{"class":1331},")] ",[453,2832,1413],{"class":1327},[453,2834,1986],{"class":1331},[453,2836,2837,2840,2842],{"class":455,"line":1582},[453,2838,2839],{"class":1327},"\u003C\u002F",[453,2841,2533],{"class":1424},[453,2843,2551],{"class":1327},[453,2845,2846],{"class":455,"line":1588},[453,2847,1375],{"emptyLinePlaceholder":1374},[453,2849,2850,2852,2855],{"class":455,"line":2041},[453,2851,1844],{"class":1327},[453,2853,2854],{"class":1424},"template",[453,2856,2551],{"class":1327},[453,2858,2859,2862,2865,2868,2870,2872,2875,2877,2880,2882,2884,2887,2889,2891,2894,2896,2898],{"class":455,"line":2047},[453,2860,2861],{"class":1327},"  \u003C",[453,2863,2864],{"class":1424},"button",[453,2866,2867],{"class":1381}," :disabled",[453,2869,1388],{"class":1327},[453,2871,2544],{"class":1327},[453,2873,2874],{"class":1344},"!doc.dirty.value",[453,2876,2544],{"class":1327},[453,2878,2879],{"class":1381}," @click",[453,2881,1388],{"class":1327},[453,2883,2544],{"class":1327},[453,2885,2886],{"class":1344},"doc.save()",[453,2888,2544],{"class":1327},[453,2890,1849],{"class":1327},[453,2892,2893],{"class":1331},"Save",[453,2895,2839],{"class":1327},[453,2897,2864],{"class":1424},[453,2899,2551],{"class":1327},[453,2901,2902,2904,2907,2910,2912,2914,2917,2919,2921,2923,2925,2927,2930,2933,2935,2937,2940,2942,2944,2947,2949,2951,2953,2955,2958,2960,2962,2964,2966,2968,2970,2972,2974],{"class":455,"line":2072},[453,2903,2861],{"class":1327},[453,2905,2906],{"class":1424},"EditorSurface",[453,2908,2909],{"class":1323}," v-if",[453,2911,1388],{"class":1327},[453,2913,2544],{"class":1327},[453,2915,2916],{"class":1331},"doc",[453,2918,1594],{"class":1327},[453,2920,802],{"class":1331},[453,2922,1594],{"class":1327},[453,2924,2822],{"class":1331},[453,2926,2544],{"class":1327},[453,2928,2929],{"class":1327}," :",[453,2931,2932],{"class":1381},"key",[453,2934,1388],{"class":1327},[453,2936,2544],{"class":1327},[453,2938,2939],{"class":1331},"id",[453,2941,2544],{"class":1327},[453,2943,2929],{"class":1327},[453,2945,2946],{"class":1381},"preset",[453,2948,1388],{"class":1327},[453,2950,2544],{"class":1327},[453,2952,2946],{"class":1331},[453,2954,2544],{"class":1327},[453,2956,2957],{"class":1381}," v-bind",[453,2959,1388],{"class":1327},[453,2961,2544],{"class":1327},[453,2963,2916],{"class":1331},[453,2965,1594],{"class":1327},[453,2967,820],{"class":1331},[453,2969,1594],{"class":1327},[453,2971,2822],{"class":1331},[453,2973,2544],{"class":1327},[453,2975,2976],{"class":1327}," \u002F>\n",[453,2978,2979,2981,2983],{"class":455,"line":2091},[453,2980,2839],{"class":1327},[453,2982,2854],{"class":1424},[453,2984,2551],{"class":1327},[418,2986,2987,2990,2991,2994,2995,2443,2997,2999,3000,3003,3004,3006,3007,584,3009,584,3011,584,3013,584,3015,584,3018,584,3020,584,3022,3024,3025,3028,3029,3032,3033,854,3036,3038,3039,3042,3043,3046,3047,3050,3051,971,3054,3056,3057,3060,3061,3064,3065,3068,3069,3072,3073,3075,3076,3079,3080,3082,3083,3086,3087,854,3090,3093,3094,3096,3097,3099,3100,584,3102,584,3104,584,3106,584,3108,584,3110,584,3112,584,3114,584,3116,584,3118,584,3120,3122,3123,959,3126,3128,3129,584,3131,584,3133,854,3136,3138,3139,1245,3141,3143],{},[422,2988,2989],{},"useEditorDocument(id, options)"," loads and saves through ",[422,2992,2993],{},"backend.documents"," (the HTTP backend: ",[422,2996,2442],{},[422,2998,1201],{},"; a missing document, ",[422,3001,3002],{},"null",", starts empty and is created by the first ",[422,3005,765],{},") and returns ",[422,3008,759],{},[422,3010,802],{},[422,3012,762],{},[422,3014,765],{},[422,3016,3017],{},"reload()",[422,3019,813],{},[422,3021,752],{},[422,3023,755],{}," (made by ",[422,3026,3027],{},"backend.comments",": the REST comments model of ",[422,3030,3031],{},"\u002Fapi\u002Feditor\u002Fcomments\u002F:id"," over HTTP), ",[422,3034,3035],{},"editor",[422,3037,820],{}," (the surface's props and events). The backend is ",[422,3040,3041],{},"options.backend",", else the one of the editor context (",[422,3044,3045],{},"createEditor({ backend })",", see the ",[422,3048,3049],{},"\u002Fadapters\u002Fhttp"," row above); the default context backend has neither collaboration nor comments, so a host that needs them installs ",[422,3052,3053],{},"createHttpBackend()",[422,3055,762],{}," ignores the block ids the editor assigns when it mounts content; they are saved with the next edit. With ",[422,3058,3059],{},"collaboration: true"," (or ",[422,3062,3063],{},"{ token? }",", the token the collaboration server's ",[422,3066,3067],{},"AccessControl.authenticate"," receives) the Y.Doc is the document: ",[422,3070,3071],{},"backend.collaboration.connect(id, token)"," opens it, the server loads and stores the content, nothing is fetched or saved by the composable, and ",[422,3074,759],{}," stays ",[422,3077,3078],{},"undefined","; a backend without the ",[422,3081,752],{}," capability makes the call throw ",[422,3084,3085],{},"@skaile\u002Fforge-editor: useEditorDocument needs backend capability \"collaboration\"…",". Mount the surface with ",[422,3088,3089],{},":key=\"id\"",[422,3091,3092],{},"v-if=\"doc.ready.value\"",". The React twin, ",[422,3095,2989],{}," from ",[422,3098,787],{},", takes the same options and returns plain values (",[422,3101,759],{},[422,3103,799],{},[422,3105,802],{},[422,3107,762],{},[422,3109,807],{},[422,3111,810],{},[422,3113,813],{},[422,3115,752],{},[422,3117,755],{},[422,3119,3035],{},[422,3121,820],{},") and reads its backend from ",[422,3124,3125],{},"EditorContextProvider",[422,3127,820],{}," spreads onto the surface as ",[422,3130,2822],{},[422,3132,799],{},[422,3134,3135],{},"onReady",[422,3137,752],{},", and a ",[422,3140,3002],{},[422,3142,3078],{}," id keeps it idle (no document open yet).",[418,3145,3146,3147,3149,3150,854,3152,3154,3155,2443,3157,2443,3160,3163,3164,3166,3167,3169,3170,3173,3174,3176,3177,3180,3181,3184,3185,3188,3189,3192,3193,3196,3197,3200,3201,3203,3204,1594],{},"Every adapter returns an ",[422,3148,424],{}," bundle with only the ports it serves (",[422,3151,1118],{},[422,3153,1123],{},": identity, documents with ",[422,3156,2456],{},[422,3158,3159],{},"deleteDocument",[422,3161,3162],{},"listDocumentIds",", collabState, directory, assets, settings; ",[422,3165,1128],{},": identity, documents and, when its ",[422,3168,547],{}," has them, collabState, assets, settings, themes). All take the same hooks: ",[422,3171,3172],{},"authenticate({ headers, token })"," (the user behind a request or collaboration socket, ",[422,3175,3002],{}," refuses; default: an anonymous development user outside ",[422,3178,3179],{},"NODE_ENV=production",", nobody in production; the forge adapter has no default), ",[422,3182,3183],{},"authorize(user, documentId)"," (per-document access for collaboration, ",[422,3186,3187],{},"{ readOnly } | null","; omitted = every authenticated user edits every document) and ",[422,3190,3191],{},"users()"," (who can be ",[422,3194,3195],{},"@","-mentioned). Host nodes for the collaboration schema are the server's ",[422,3198,3199],{},"schema"," option, not an adapter hook. Comment stores are the ",[422,3202,755],{}," port: ",[422,3205,3206],{},"{ ...createJsonAdapter({ dir }), comments: createJsonCommentStore({ file }) }",[418,3208,3209,3210,979,3218,3221,3222,3225,3226,1594],{},"The runnable app is ",[427,3211,3215],{"href":3212,"rel":3213},"https:\u002F\u002Fgithub.com\u002Fskaile-ai\u002Fforge-editor\u002Ftree\u002Fmain\u002Fexamples\u002Fvue-nitro-json",[3214],"nofollow",[422,3216,3217],{},"examples\u002Fvue-nitro-json",[422,3219,3220],{},"bun run proto:vue-nitro-json"," serves it on port 3046, documents land in ",[422,3223,3224],{},"examples\u002Fvue-nitro-json\u002F.data\u002Fdocuments\u002F\u003Cid>.json",", comments in ",[422,3227,3228],{},"examples\u002Fvue-nitro-json\u002F.data\u002Fcomments.json",[3230,3231,3232],"style",{},"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}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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 .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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":449,"searchDepth":1350,"depth":1350,"links":3234},[3235,3236,3237,3238,3239],{"id":1050,"depth":1350,"text":1051},{"id":1104,"depth":1350,"text":1105},{"id":1270,"depth":1350,"text":1271},{"id":1706,"depth":1350,"text":1707},{"id":2172,"depth":1350,"text":2173},"Pick where documents live with ready-made EditorPorts adapters, comment stores, a Nuxt-free Nitro host and the useEditorDocument composable.","md",null,{},{"icon":148},{"title":145,"description":3240},"1gIRyr_YJiHWVG_0D7IjeUkj7-VkU2JcYuHQL82uhAQ",[3248,3250],{"title":140,"path":141,"stem":142,"description":3249,"icon":143,"children":-1},"Register @skaile\u002Fforge-editor\u002Fnuxt in nuxt.config.ts, what the module wires into build, client and server, and which routes it mounts.",{"title":150,"path":151,"stem":152,"description":3251,"icon":153,"children":-1},"Move a host from the removed #tiptap-editor Nuxt module to explicit imports from @skaile\u002Fforge-editor entries.",1793213903169]