@vcp-dev/site-edit-bridge 0.4.9 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
- import je from"node:http";import{existsSync as y,readFileSync as m}from"node:fs";import{createRequire as V}from"node:module";import k from"node:path";import{basename as z,dirname as S,join as b}from"node:path";import{fileURLToPath as G}from"node:url";import ie from"node:http";import{randomBytes as oe,timingSafeEqual as ae}from"node:crypto";import ue from"node:path";import{WebSocket as q,WebSocketServer as ce}from"ws";import{createPreviewSiteEditEngineRuntime as de}from"@vcp-dev/site-edit-engine";import{resolveNextAppRouteId as he,scanNextAppRouterRoutes as le}from"@vcp-dev/site-edit-engine/next-app-router";import{SiteEditSourceWatcher as fe}from"@vcp-dev/site-edit-engine/source-watcher";function O(t){let e=new Map,n=[],r=new Map,s=[],i=a=>{let u=e.get(a);if(typeof u=="number")return u;let c=n.length;return n.push(a),e.set(a,c),c},o=a=>{let u=null;for(let c of ne(a)){let d=`${u??"root"}\0${c}`,f=r.get(d);if(typeof f=="number"){u=f;continue}let h=s.length;s.push({parent:u,token:i(c)}),r.set(d,h),u=h}if(u===null)throw new Error("Document key must not be empty");return u};return{encoding:"segment-trie-v1",siteId:t.siteId,snapshotId:t.snapshotId,routeId:t.routeId,version:t.version,tokens:n,nodes:s,rootKeyNodeIds:t.rootKeys.map(o),entries:t.entries.map(a=>({keyNodeId:o(a.key),tag:a.tag,label:a.label,parentKeyNodeId:a.parentKey?o(a.parentKey):null,childKeyNodeIds:a.childKeys.map(o),operationSummary:a.operationSummary}))}}function ne(t){if(!t)return[];let e=[],n=0,r=N(t,n);if(r<0)return e.push(t),e;for(e.push(t.slice(0,r)),n=r;n<t.length;){if(r=N(t,n+(t.startsWith("::",n)?2:1)),r<0){e.push(t.slice(n));break}e.push(t.slice(n,r)),n=r}return e}function N(t,e){let n=t.indexOf("/",e),r=t.indexOf("::",e);return n<0?r:r<0?n:Math.min(n,r)}var p=class t{constructor(){}static parseMessage(e,n={}){let r=JSON.parse(e);if(!t.isMessage(r,n))throw new Error("Invalid site edit bridge message");return r}static stringifyMessage(e){return JSON.stringify(e)}static extractRequestInfo(e){if(typeof e=="object"&&e!==null&&"payload"in e&&typeof e.payload=="object"&&e.payload!==null){let n=e.payload,r=typeof n.id=="string"?n.id:"unknown",s=t.isRequestKind(n.kind)?n.kind:"get-document";return{id:r,kind:s}}return{id:"unknown",kind:"get-document"}}static isRequestKind(e){return e==="get-document"||e==="get-object-summary"||e==="get-object-edit-context"||e==="get-class-name-source"||e==="get-source-tree"||e==="get-source-file"||e==="resolve-source-location"||e==="execute"||e==="undo"||e==="redo"}static isMessage(e,n={}){return!l(e)||typeof e.type!="string"?!1:e.type==="request"?t.isRequestPayload(e.payload,n):e.type==="response"||e.type==="error"?l(e.payload)&&typeof e.payload.id=="string":e.type==="event"?l(e.payload)&&l(e.payload.event):e.type==="auth"?l(e.payload)&&typeof e.payload.key=="string":e.type==="auth-ok"?l(e.payload)&&e.payload.ok===!0:!1}static isRequestPayload(e,n={}){if(!l(e)||typeof e.id!="string"||!t.isRequestKind(e.kind))return!1;switch(e.kind){case"get-document":return typeof e.routeId=="string";case"get-object-summary":case"get-object-edit-context":case"get-class-name-source":case"resolve-source-location":return typeof e.key=="string"&&(e.routeId===void 0||typeof e.routeId=="string");case"get-source-tree":return!0;case"get-source-file":return typeof e.path=="string";case"execute":return re(e.request)&&(n.validateOperationRequest?.(e.request)??!0);case"undo":case"redo":return!0}}},R=class{constructor(e){this.options=e,this.messageQueue=new _,this.eventDispatcher=new E(n=>{this.sendJson(n)}),this.unsubscribe=e.engine.subscribe(n=>{this.eventDispatcher.push(n)}),e.socket.onmessage=n=>{try{this.messageQueue.enqueue(()=>this.handleRawMessage(n.data))}catch(r){this.sendJson({type:"error",payload:{id:"unknown",kind:"get-document",message:r instanceof Error?r.message:"Bridge queue overflow"}}),e.socket.close(1013,"bridge busy")}},e.socket.onclose=()=>{this.unsubscribe()}}async handleMessage(e){try{let n=e;if(n.type!=="request")return;let r=n.payload,{id:s}=r;switch(r.kind){case"get-document":this.sendJson({type:"response",payload:{id:s,kind:r.kind,result:{ok:!0,data:O((await this.options.engine.getDocument(r.routeId)).data)}}});return;case"get-object-summary":this.sendJson({type:"response",payload:{id:s,kind:r.kind,result:await this.options.engine.getObjectSummary(r.key,r.routeId)}});return;case"get-object-edit-context":this.sendJson({type:"response",payload:{id:s,kind:r.kind,result:await this.options.engine.getObjectEditContext(r.key,r.routeId)}});return;case"get-class-name-source":this.sendJson({type:"response",payload:{id:s,kind:r.kind,result:await this.options.engine.getClassNameSource(r.key,r.routeId)}});return;case"get-source-tree":this.sendJson({type:"response",payload:{id:s,kind:r.kind,result:await this.options.engine.getSourceTree()}});return;case"get-source-file":this.sendJson({type:"response",payload:{id:s,kind:r.kind,result:await this.options.engine.getSourceFile({path:r.path})}});return;case"resolve-source-location":this.sendJson({type:"response",payload:{id:s,kind:r.kind,result:await this.options.engine.resolveSourceLocation({key:r.key,routeId:r.routeId})}});return;case"execute":await this.handleBufferedOperation(s,r.kind,()=>this.options.engine.execute(r.request));return;case"undo":await this.handleBufferedOperation(s,r.kind,()=>this.options.engine.undo());return;case"redo":await this.handleBufferedOperation(s,r.kind,()=>this.options.engine.redo());return}}catch(n){let r=p.extractRequestInfo(e);this.sendJson({type:"error",payload:{...r,message:n instanceof Error?n.message:"Unknown websocket operation failure"}})}}async handleRawMessage(e){try{await this.handleMessage(p.parseMessage(e,{validateOperationRequest:this.options.validateOperationRequest}))}catch(n){let r=n instanceof Error?n.message:"Invalid websocket message",s=se(e);this.sendJson({type:"error",payload:{...s,message:r}})}}sendJson(e){this.options.socket.send(p.stringifyMessage(e))}async handleBufferedOperation(e,n,r){this.eventDispatcher.beginBuffering();try{let s=await r();this.sendJson({type:"response",payload:{id:e,kind:n,result:s}}),this.eventDispatcher.flush()}catch(s){this.sendJson({type:"error",payload:{id:e,kind:n,message:s instanceof Error?s.message:"Unknown websocket operation failure"}}),this.eventDispatcher.flush()}}},E=class{constructor(e){this.send=e,this.bufferedEvents=null}push(e){let n={type:"event",payload:{event:e}};if(this.bufferedEvents){this.bufferedEvents.push(n);return}this.send(n)}beginBuffering(){this.bufferedEvents=[]}flush(){let e=this.bufferedEvents??[];this.bufferedEvents=null,e.forEach(n=>{this.send(n)})}},_=class{constructor(e=64){this.maxQueued=e,this.queue=Promise.resolve(),this.queued=0}enqueue(e){if(this.queued>=this.maxQueued)throw new Error("Bridge request queue is full");this.queued+=1;let n=async()=>{this.queued-=1,await e()},r=this.queue.then(n,n);return this.queue=r.then(()=>{},()=>{}),r}};function l(t){return typeof t=="object"&&t!==null}function re(t){return l(t)&&typeof t.id=="string"&&typeof t.siteId=="string"&&typeof t.baseSnapshotId=="string"&&(t.routeId===void 0||typeof t.routeId=="string")&&typeof t.kind=="string"&&l(t.target)&&typeof t.documentVersion=="number"&&Number.isInteger(t.documentVersion)&&t.documentVersion>=0&&l(t.params)}function se(t){try{return p.extractRequestInfo(JSON.parse(t))}catch{return{id:"unknown",kind:"get-document"}}}import{SiteEditOperationRequestSchema as pe}from"@vcp-dev/contracts";var ge="127.0.0.1",me=30218,ye="/__vcp/site-edit/bridge",Se=6*1024*1024,be=3e3,K=50,M="__VCP_SITE_EDIT_BRIDGE_SERVERS__";function F(t,e){let n=new P(we(e),t,!1);return n.start({listen:!1}),n}var P=class{constructor(e,n=ie.createServer((s,i)=>{i.writeHead(404),i.end("VCP site edit bridge server")}),r=!0){this.options=e,this.server=n,this.closeHttpServerOnStop=r,this.runtime=null,this.routes=[],this.stopWatching=null,this.stopWebSocketServer=null,this.pendingChangedFiles=new Set,this.fileChangeTimer=null,this.isStarted=!1}start(e={}){if(this.isStarted)return;this.refreshRoutes(),this.runtime=de({siteId:this.options.siteId,snapshotId:this.options.snapshotId,projectRoot:this.options.projectRoot,routes:this.routes});let n=Ie({server:this.server,path:this.options.path,sessionKey:this.options.sessionKey,host:this.options.host,allowedOrigins:this.options.allowedOrigins,engine:this.createBridgeEngine()});this.stopWebSocketServer=n.stop,this.stopWatching=new fe({projectRoot:this.options.projectRoot,watch_dirs:this.options.runtime.watch_dirs,debounce_ms:this.options.runtime.watch_debounce_ms??K,max_dirs:this.options.runtime.watch_max_dirs,max_files:this.options.runtime.watch_max_files,max_depth:this.options.runtime.watch_max_depth,max_startup_ms:this.options.runtime.watch_max_startup_ms,on_change:r=>this.handleFileChanged(r),on_batch_change:r=>this.handleFilesChanged(r)}).start(),e.listen!==!1&&this.server.listen(this.options.port,this.options.host),this.isStarted=!0}getHealth(){let e=!!this.runtime;return{ok:this.isStarted&&e,bridgePath:this.options.path,runtimeReady:e,routesIndexed:this.routes.length,sessionRequired:!!this.options.sessionKey}}stop(){this.stopWatching?.(),this.stopWatching=null,this.flushChangedFiles(),this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.stopWebSocketServer?.(),this.stopWebSocketServer=null,this.runtime?.dispose(),this.runtime=null,this.closeHttpServerOnStop&&this.server.close();let e=Re(),n=xe(this.options);e.get(n)===this&&e.delete(n),this.isStarted=!1}createBridgeEngine(){return{getDocument:async e=>{let{runtime:n,resolvedRouteId:r}=await this.activateRoute(e);return n.getDocument(r)},getObjectSummary:async(e,n)=>this.getRuntime().getObjectSummary(e,n?this.resolveRouteId(n):void 0),getObjectEditContext:async(e,n)=>this.getRuntime().getObjectEditContext(e,n?this.resolveRouteId(n):void 0),getClassNameSource:async(e,n)=>{let r=this.getRuntime(),s=n?this.resolveRouteId(n):void 0;return await r.refreshSourceForKey(e,s),r.getClassNameSource(e,s)},getSourceTree:async()=>this.getRuntime().getSourceTree(),getSourceFile:async e=>this.getRuntime().getSourceFile(e),resolveSourceLocation:async e=>this.getRuntime().resolveSourceLocation({key:e.key,routeId:e.routeId?this.resolveRouteId(e.routeId):void 0}),execute:async e=>this.getRuntime().executeOperation(this.normalizeRequest(e)),undo:async()=>this.getRuntime().undo(),redo:async()=>this.getRuntime().redo(),subscribe:e=>this.getRuntime().subscribe(e)}}async activateRoute(e){let n=this.getRuntime(),r=this.resolveRouteId(e);return await n.setActiveRoute(r),{runtime:n,resolvedRouteId:r}}normalizeRequest(e){return e.target.kind!=="route"?e.routeId?{...e,routeId:this.resolveRouteId(e.routeId)}:e:{...e,target:{...e.target,routeId:this.resolveRouteId(e.target.routeId)}}}handleFileChanged(e){this.handleFilesChanged([e])}handleFilesChanged(e){for(let r of e)this.pendingChangedFiles.add(r);this.fileChangeTimer&&clearTimeout(this.fileChangeTimer);let n=this.options.runtime.watch_debounce_ms??K;this.fileChangeTimer=setTimeout(()=>{this.fileChangeTimer=null,this.flushChangedFiles()},n),this.fileChangeTimer?.unref?.()}flushChangedFiles(){if(this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.pendingChangedFiles.size===0)return;let e=[...this.pendingChangedFiles].sort((n,r)=>n.localeCompare(r));this.pendingChangedFiles.clear(),this.refreshRoutes();for(let n of e)this.runtime?.notifyFileChanged(n)}refreshRoutes(){let e=this.options.runtime.routes?.length?this.options.runtime.routes:le(this.options.projectRoot),n=new Map(this.routes.map(s=>[s.routeId,s.entryFile])),r=new Set(e.map(s=>s.routeId));this.routes=e;for(let s of e)n.get(s.routeId)!==s.entryFile&&this.runtime?.registerRoute(s.routeId,s.entryFile);for(let s of n.keys())r.has(s)||this.runtime?.unregisterRoute(s)}resolveRouteId(e){let n=he(e,this.routes,this.options.basePath);if(!n)throw new Error(`Route is not indexed by the preview bridge: ${e}`);return n}getRuntime(){if(!this.runtime)throw new Error("VCP site edit preview bridge runtime is not ready");return this.runtime}};function we(t){return{siteId:t.siteId,snapshotId:t.snapshotId,projectRoot:ue.resolve(t.projectRoot??process.cwd()),basePath:t.basePath??"",host:t.host??ge,port:t.port??me,path:t.path??ye,sessionKey:t.sessionKey??Te(),allowedOrigins:t.allowedOrigins??[],runtime:t.runtime??{}}}function Re(){let t=globalThis;return t[M]??=new Map,t[M]}function xe(t){return`${t.projectRoot}:${t.host}:${t.port}:${t.path}`}function Ie(t){let e=new ce({maxPayload:Se,noServer:!0}),n=new WeakSet,r=i=>{new R({socket:new T(i),engine:t.engine,validateOperationRequest:o=>pe.safeParse(o).success})},s=(i,o,a)=>{if(!ke(i,t))return;let u=ve(i,t);if(!u){o.write(`HTTP/1.1 401 Unauthorized\r
1
+ import ze from"node:http";import{existsSync as y,readFileSync as m}from"node:fs";import{createRequire as ae}from"node:module";import R from"node:path";import{basename as ue,dirname as I,join as x}from"node:path";import{fileURLToPath as ce}from"node:url";import Ie from"node:http";import{createHash as Z,randomBytes as xe,timingSafeEqual as be}from"node:crypto";import O from"node:path";import{WebSocket as X,WebSocketServer as ke}from"ws";import{createPreviewSiteEditEngineRuntime as Ce,openFileHistoryStore as ve}from"@vcp-dev/site-edit-engine";import{resolveNextAppRouteId as Ee,scanNextAppRouterRoutes as _e}from"@vcp-dev/site-edit-engine/next-app-router";import{SiteEditSourceWatcher as Pe}from"@vcp-dev/site-edit-engine/source-watcher";function H(t){let e=new Map,n=[],s=new Map,r=[],i=t.pageComposition.status==="supported"?t.pageComposition.placementUnitKeys:[],o=new Map(i.map((a,u)=>[a,u])),c=t.pageComposition.status==="supported"?{status:"supported",containerKey:t.pageComposition.containerKey,hasBusinessMembers:t.pageComposition.hasBusinessMembers,placementUnitKeys:t.pageComposition.placementUnitKeys,placements:t.pageComposition.placements.map(a=>({placementKey:a.placementKey,placementUnitKey:a.placementUnitKey,relation:a.relation}))}:{status:"unsupported",reason:t.pageComposition.reason,diagnostics:t.pageComposition.diagnostics},d=a=>{let u=e.get(a);if(typeof u=="number")return u;let l=n.length;return n.push(a),e.set(a,l),l},h=a=>{let u=null;for(let l of ye(a)){let g=`${u??"root"}\0${l}`,b=s.get(g);if(typeof b=="number"){u=b;continue}let S=r.length;r.push([u,d(l)]),s.set(g,S),u=S}if(u===null)throw new Error("Document key must not be empty");return u};return{encoding:"segment-trie-v1",siteId:t.siteId,snapshotId:t.snapshotId,routeId:t.routeId,version:t.version,pageComposition:c,tokens:n,nodes:r,entries:t.entries.map(a=>{let u=a.pageCompositionUnitKey?o.get(a.pageCompositionUnitKey)??-1:-1;if(a.pageCompositionUnitKey&&u<0)throw new Error(`Render entry ${a.key} references unknown page composition unit ${a.pageCompositionUnitKey}`);return[h(a.key),me(a.operationSummary),...u>=0?[u]:[]]})}}function me(t){return(t.canUpdateText?1:0)|(t.canInsertChild?2:0)|(t.canMove?4:0)|(t.canRemove?8:0)}function ye(t){if(!t)return[];let e=[],n=0,s=L(t,n);if(s<0)return e.push(t),e;for(e.push(t.slice(0,s)),n=s;n<t.length;){if(s=L(t,n+(t.startsWith("::",n)?2:1)),s<0){e.push(t.slice(n));break}e.push(t.slice(n,s)),n=s}return e}function L(t,e){let n=t.indexOf("/",e),s=t.indexOf("::",e);return n<0?s:s<0?n:Math.min(n,s)}var f=class t{constructor(){}static parseMessage(e,n={}){let s=JSON.parse(e);if(!t.isMessage(s,n))throw new Error("Invalid site edit bridge message");return s}static stringifyMessage(e){return JSON.stringify(e)}static extractRequestInfo(e){if(typeof e=="object"&&e!==null&&"payload"in e&&typeof e.payload=="object"&&e.payload!==null){let n=e.payload,s=typeof n.id=="string"?n.id:"unknown",r=t.isRequestKind(n.kind)?n.kind:"get-document";return{id:s,kind:r}}return{id:"unknown",kind:"get-document"}}static isRequestKind(e){return e==="initialize-session"||e==="get-document"||e==="get-history-status"||e==="get-style-context"||e==="get-object-edit-context"||e==="get-class-name-source"||e==="get-source-tree"||e==="get-source-file"||e==="resolve-source-location"||e==="execute"||e==="undo"||e==="redo"}static isMessage(e,n={}){return!p(e)||typeof e.type!="string"?!1:e.type==="request"?t.isRequestPayload(e.payload,n):e.type==="response"||e.type==="error"?p(e.payload)&&typeof e.payload.id=="string":e.type==="event"?p(e.payload)&&p(e.payload.event):e.type==="auth"?p(e.payload)&&typeof e.payload.key=="string":e.type==="auth-ok"?p(e.payload)&&e.payload.ok===!0:!1}static isRequestPayload(e,n={}){if(!p(e)||typeof e.id!="string"||!t.isRequestKind(e.kind))return!1;switch(e.kind){case"initialize-session":return typeof e.routeId=="string"&&(e.mutationContext===void 0||B(e.mutationContext));case"get-document":case"get-style-context":return typeof e.routeId=="string";case"get-object-edit-context":case"get-class-name-source":case"resolve-source-location":return typeof e.key=="string"&&(e.routeId===void 0||typeof e.routeId=="string");case"get-history-status":return e.mutationContext===void 0;case"get-source-tree":return!0;case"get-source-file":return typeof e.path=="string";case"execute":return Se(e.request)&&(e.request.mutationContext===void 0||B(e.request.mutationContext))&&(n.validateOperationRequest?.(e.request)??!0);case"undo":case"redo":return e.mutationContext===void 0||B(e.mutationContext)}}},C=class{constructor(e){this.options=e,this.messageQueue=new D,this.eventDispatcher=new j(n=>{this.sendJson(n)}),this.unsubscribe=e.engine.subscribe(n=>{this.eventDispatcher.push(n)}),e.socket.onmessage=n=>{try{this.messageQueue.enqueue(()=>this.handleRawMessage(n.data))}catch(s){this.sendJson({type:"error",payload:{id:"unknown",kind:"get-document",message:s instanceof Error?s.message:"Bridge queue overflow"}}),e.socket.close(1013,"bridge busy")}},e.socket.onclose=()=>{this.unsubscribe()}}async handleMessage(e){try{let n=e;if(n.type!=="request")return;let s=n.payload,{id:r}=s;switch(s.kind){case"initialize-session":{let i=await this.options.engine.initializeSession({routeId:s.routeId,...s.mutationContext?{mutationContext:s.mutationContext}:{}});this.sendJson({type:"response",payload:{id:r,kind:s.kind,result:{ok:!0,data:{document:H(i.data.document),historyStatus:i.data.historyStatus}}}});return}case"get-document":this.sendJson({type:"response",payload:{id:r,kind:s.kind,result:{ok:!0,data:H((await this.options.engine.getDocument(s.routeId)).data)}}});return;case"get-history-status":this.sendJson({type:"response",payload:{id:r,kind:s.kind,result:await this.options.engine.getHistoryStatus()}});return;case"get-style-context":this.sendJson({type:"response",payload:{id:r,kind:s.kind,result:await this.options.engine.getStyleContext(s.routeId)}});return;case"get-object-edit-context":this.sendJson({type:"response",payload:{id:r,kind:s.kind,result:await this.options.engine.getObjectEditContext(s.key,s.routeId)}});return;case"get-class-name-source":this.sendJson({type:"response",payload:{id:r,kind:s.kind,result:await this.options.engine.getClassNameSource(s.key,s.routeId)}});return;case"get-source-tree":this.sendJson({type:"response",payload:{id:r,kind:s.kind,result:await this.options.engine.getSourceTree()}});return;case"get-source-file":this.sendJson({type:"response",payload:{id:r,kind:s.kind,result:await this.options.engine.getSourceFile({path:s.path})}});return;case"resolve-source-location":this.sendJson({type:"response",payload:{id:r,kind:s.kind,result:await this.options.engine.resolveSourceLocation({key:s.key,routeId:s.routeId})}});return;case"execute":{await this.handleBufferedOperation(r,s.kind,()=>this.options.engine.execute(s.request));return}case"undo":await this.handleBufferedOperation(r,s.kind,()=>this.options.engine.undo());return;case"redo":await this.handleBufferedOperation(r,s.kind,()=>this.options.engine.redo());return}}catch(n){let s=f.extractRequestInfo(e);this.sendJson({type:"error",payload:{...s,message:J(n),code:$(n)}})}}async handleRawMessage(e){try{await this.handleMessage(f.parseMessage(e,{validateOperationRequest:this.options.validateOperationRequest}))}catch(n){let s=n instanceof Error?n.message:"Invalid websocket message",r=Re(e);this.sendJson({type:"error",payload:{...r,message:s}})}}sendJson(e){this.options.socket.send(f.stringifyMessage(e))}async handleBufferedOperation(e,n,s){this.eventDispatcher.beginBuffering();try{let r=await s();this.sendJson({type:"response",payload:{id:e,kind:n,result:r}}),this.eventDispatcher.flush()}catch(r){this.sendJson({type:"error",payload:{id:e,kind:n,message:J(r),code:$(r)}}),this.eventDispatcher.flush()}}},j=class{constructor(e){this.send=e,this.bufferedEvents=null}push(e){let n={type:"event",payload:{event:e}};if(this.bufferedEvents){this.bufferedEvents.push(n);return}this.send(n)}beginBuffering(){this.bufferedEvents=[]}flush(){let e=this.bufferedEvents??[];this.bufferedEvents=null,e.forEach(n=>{this.send(n)})}},D=class{constructor(e=64){this.maxQueued=e,this.queue=Promise.resolve(),this.queued=0}enqueue(e){if(this.queued>=this.maxQueued)throw new Error("Bridge request queue is full");this.queued+=1;let n=async()=>{this.queued-=1,await e()},s=this.queue.then(n,n);return this.queue=s.then(()=>{},()=>{}),s}};function p(t){return typeof t=="object"&&t!==null}function Se(t){return p(t)&&typeof t.id=="string"&&typeof t.siteId=="string"&&typeof t.baseSnapshotId=="string"&&(t.routeId===void 0||typeof t.routeId=="string")&&typeof t.kind=="string"&&p(t.target)&&typeof t.documentVersion=="number"&&Number.isInteger(t.documentVersion)&&t.documentVersion>=0&&p(t.params)}function B(t){return we(t)}function $(t){if(!t||typeof t!="object")return;let e=t.code;return typeof e=="string"&&e.length>0?e:void 0}function J(t){if(t instanceof Error)return t.message;if(!t||typeof t!="object")return"Unknown websocket operation failure";let e=t.message;return typeof e=="string"&&e.length>0?e:"Unknown websocket operation failure"}function we(t){return p(t)?typeof t.editingSessionId=="string"&&t.editingSessionId.length>0&&typeof t.lockId=="string"&&t.lockId.length>0&&typeof t.ownerId=="string"&&t.ownerId.length>0&&typeof t.leaseAssertion=="string"&&t.leaseAssertion.length>0:!1}function Re(t){try{return f.extractRequestInfo(JSON.parse(t))}catch{return{id:"unknown",kind:"get-document"}}}var v=class{constructor(){this.tail=Promise.resolve()}run(e){let n=this.tail.then(e,e);return this.tail=n.then(()=>{},()=>{}),n}};import{SiteEditOperationRequestSchema as Te}from"@vcp-dev/contracts";var He="127.0.0.1",Be=30218,je="/__vcp/site-edit/bridge",ee=6*1024*1024,De=ee*2,Me=3e3,W=50,z="__VCP_SITE_EDIT_BRIDGE_SERVERS__";function te(t,e,n={}){let s=new V(Ue(e),t,!1,n);return s.start({listen:!1}),s}var V=class{constructor(e,n=Ie.createServer((i,o)=>{o.writeHead(404),o.end("VCP site edit bridge server")}),s=!0,r={}){this.options=e,this.server=n,this.closeHttpServerOnStop=s,this.dependencies=r,this.runtime=null,this.activeHistorySessionId=null,this.historySessionTransition=Promise.resolve(),this.mutationQueue=new v,this.eventListeners=new Set,this.routes=[],this.stopWatching=null,this.stopWebSocketServer=null,this.pendingChangedFiles=new Set,this.fileChangeTimer=null,this.isStarted=!1,this.runtimeVersionFloor=0,this.activeHistoryStore=null,this.versionPersistence=Promise.resolve()}start(e={}){if(this.isStarted)return;this.refreshRoutes(),this.isStarted=!0,this.createRuntime();let n=Fe({server:this.server,path:this.options.path,sessionKey:this.options.sessionKey,host:this.options.host,allowedOrigins:this.options.allowedOrigins,engine:this.createBridgeEngine()});this.stopWebSocketServer=n.stop,this.stopWatching=new Pe({projectRoot:this.options.projectRoot,watch_dirs:this.options.runtime.watch_dirs,debounce_ms:this.options.runtime.watch_debounce_ms??W,max_dirs:this.options.runtime.watch_max_dirs,max_files:this.options.runtime.watch_max_files,max_depth:this.options.runtime.watch_max_depth,max_startup_ms:this.options.runtime.watch_max_startup_ms,on_change:s=>this.handleFileChanged(s),on_batch_change:s=>this.handleFilesChanged(s)}).start(),e.listen!==!1&&this.server.listen(this.options.port,this.options.host)}getHealth(){let e=!!this.runtime;return{ok:this.isStarted&&e,bridgePath:this.options.path,runtimeReady:e,routesIndexed:this.routes.length,sessionRequired:!!this.options.sessionKey}}stop(){this.stopWatching?.(),this.stopWatching=null,this.flushChangedFiles(),this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.stopWebSocketServer?.(),this.stopWebSocketServer=null,this.runtime?.dispose(),this.runtime=null,this.closeHttpServerOnStop&&this.server.close();let e=Ve(),n=Ae(this.options);e.get(n)===this&&e.delete(n),this.isStarted=!1}createRuntime(e,n=null){let s=this.runtime;this.activeHistoryStore=e??null;let r=(this.dependencies.createRuntime??Ce)({...this.options.tenantId?{tenantId:this.options.tenantId}:{},siteId:this.options.siteId,snapshotId:this.options.snapshotId,projectRoot:this.options.projectRoot,routes:this.routes,onEvent:i=>{"version"in i&&typeof i.version=="number"&&this.recordRuntimeVersion(i.version);for(let o of this.eventListeners)o(i)},...e?{historyStore:e}:{},initialDocumentVersion:this.runtimeVersionFloor});this.runtime=r,this.activeHistorySessionId=n,s?.dispose()}createBridgeEngine(){return{initializeSession:e=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)throw new Error(`${n.code}: ${n.message}`);let{runtime:s,resolvedRouteId:r}=await this.activateRoute(e.routeId),i=s.getDocument(r).data;await this.recordRuntimeVersion(i.version);let o=(await s.getHistoryStatus()).data;return{ok:!0,data:{document:i,historyStatus:o}}}),getDocument:async e=>{let{runtime:n,resolvedRouteId:s}=await this.activateRoute(e),r=n.getDocument(s);return await this.recordRuntimeVersion(r.data.version),r},getHistoryStatus:async()=>this.getRuntime().getHistoryStatus(),getStyleContext:async e=>{let{runtime:n,resolvedRouteId:s}=await this.activateRoute(e);return n.getStyleContext(s)},getObjectEditContext:async(e,n)=>this.getRuntime().getObjectEditContext(e,n?this.resolveRouteId(n):void 0),getClassNameSource:async(e,n)=>{let s=this.getRuntime(),r=n?this.resolveRouteId(n):void 0;return s.getClassNameSource(e,r)},getSourceTree:async()=>this.getRuntime().getSourceTree(),getSourceFile:async e=>this.getRuntime().getSourceFile(e),resolveSourceLocation:async e=>this.getRuntime().resolveSourceLocation({key:e.key,routeId:e.routeId?this.resolveRouteId(e.routeId):void 0}),execute:(e,n)=>this.mutationQueue.run(async()=>{let s=await this.bindHistorySession(this.options.snapshotId);if(!s.ok)return M(s,e);let r=await this.getRuntime().executeOperation(this.normalizeRequest(e));return r.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(r.data.resultVersion),r}),undo:()=>this.mutationQueue.run(async()=>{let e=await this.bindHistorySession(this.options.snapshotId);if(!e.ok)return M(e);let n=await this.getRuntime().undo();return n.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(n.data.resultVersion),n}),redo:()=>this.mutationQueue.run(async()=>{let e=await this.bindHistorySession(this.options.snapshotId);if(!e.ok)return M(e);let n=await this.getRuntime().redo();return n.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(n.data.resultVersion),n}),subscribe:e=>(this.eventListeners.add(e),()=>this.eventListeners.delete(e))}}async bindHistorySession(e){if(!this.options.historyRoot)return{ok:!1,code:"RECOVERY_PERSIST_FAILED",message:"Durable edit history is not configured."};if(this.activeHistorySessionId===e)return{ok:!0};let n=this.historySessionTransition.then(async()=>{if(this.activeHistorySessionId===e)return;let s=await(this.dependencies.openHistoryStore??ve)(Oe({root:this.options.historyRoot,siteId:this.options.siteId,snapshotId:this.options.snapshotId,editingSessionId:e}));if(!this.isStarted)return;let r=s.readDocumentVersion?await s.readDocumentVersion():0;this.runtimeVersionFloor=Math.max(this.runtimeVersionFloor,r),this.createRuntime(s,e)});this.historySessionTransition=n.catch(()=>{});try{return await n,{ok:!0}}catch{return{ok:!1,code:"RECOVERY_PERSIST_FAILED",message:"Durable edit history could not be opened."}}}recordRuntimeVersion(e){this.runtimeVersionFloor=Math.max(this.runtimeVersionFloor,e);let n=this.activeHistoryStore;return n?.writeDocumentVersion?(this.versionPersistence=this.versionPersistence.then(()=>n.writeDocumentVersion(this.runtimeVersionFloor)).catch(()=>{}),this.versionPersistence):Promise.resolve()}async activateRoute(e){let n=this.getRuntime(),s=this.resolveRouteId(e);return await n.setActiveRoute(s),{runtime:n,resolvedRouteId:s}}normalizeRequest(e){return e.target.kind!=="route"?e.routeId?{...e,routeId:this.resolveRouteId(e.routeId)}:e:{...e,target:{...e.target,routeId:this.resolveRouteId(e.target.routeId)}}}handleFileChanged(e){this.handleFilesChanged([e])}handleFilesChanged(e){for(let s of e)this.pendingChangedFiles.add(s);this.fileChangeTimer&&clearTimeout(this.fileChangeTimer);let n=this.options.runtime.watch_debounce_ms??W;this.fileChangeTimer=setTimeout(()=>{this.fileChangeTimer=null,this.flushChangedFiles()},n),this.fileChangeTimer?.unref?.()}flushChangedFiles(){if(this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.pendingChangedFiles.size===0)return;let e=[...this.pendingChangedFiles].sort((n,s)=>n.localeCompare(s));this.pendingChangedFiles.clear(),this.refreshRoutes();for(let n of e)this.runtime?.notifyFileChanged(n)}refreshRoutes(){let e=this.options.runtime.routes?.length?this.options.runtime.routes:_e(this.options.projectRoot),n=new Map(this.routes.map(r=>[r.routeId,r.entryFile])),s=new Set(e.map(r=>r.routeId));this.routes=e;for(let r of e)n.get(r.routeId)!==r.entryFile&&this.runtime?.registerRoute(r.routeId,r.entryFile);for(let r of n.keys())s.has(r)||this.runtime?.unregisterRoute(r)}resolveRouteId(e){let n=Ee(e,this.routes,this.options.basePath);if(!n)throw new Error(`Route is not indexed by the preview bridge: ${e}`);return n}getRuntime(){if(!this.runtime)throw new Error("VCP site edit preview bridge runtime is not ready");return this.runtime}};function Ue(t){return{tenantId:t.tenantId,siteId:t.siteId,snapshotId:t.snapshotId,projectRoot:O.resolve(t.projectRoot??process.cwd()),basePath:t.basePath??"",host:t.host??He,port:t.port??Be,path:t.path??je,sessionKey:t.sessionKey??We(),historyRoot:t.historyRoot,allowedOrigins:t.allowedOrigins??[],runtime:t.runtime??{}}}function Oe(t){return O.join(O.resolve(t.root),G(t.siteId),G(t.snapshotId),Z("sha256").update(t.editingSessionId).digest("hex").slice(0,32))}function G(t){return/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(t)&&t!=="."&&t!==".."?t:Z("sha256").update(t).digest("hex").slice(0,32)}function Ve(){let t=globalThis;return t[z]??=new Map,t[z]}function Ae(t){return`${t.projectRoot}:${t.host}:${t.port}:${t.path}:${t.historyRoot??""}`}function M(t,e){return{ok:!1,error:{code:t.code,message:t.message},data:{requestId:e?.id??"mutation-context",kind:e?.kind??"update-text",target:e?.target??{kind:"node",key:""},resultVersion:e?.documentVersion??0}}}function Fe(t){let e=new ke({maxPayload:ee,noServer:!0}),n=new WeakSet,s=i=>{new C({socket:new A(i),engine:t.engine,validateOperationRequest:o=>Te.safeParse(o).success})},r=(i,o,c)=>{if(!Ne(i,t))return;let d=Ke(i,t);if(!d){o.write(`HTTP/1.1 401 Unauthorized\r
2
2
  Connection: close\r
3
3
  \r
4
- `),o.destroy();return}u.authenticatedByHeader&&n.add(i),e.handleUpgrade(i,o,a,c=>{e.emit("connection",c,i)})};return t.server.on("upgrade",s),e.on("connection",(i,o)=>{if(n.has(o)){r(i);return}Ee(i,t.sessionKey,()=>{r(i)})}),e.stop=()=>{t.server.off("upgrade",s);for(let i of e.clients)i.close(1001,"bridge stopped");e.close()},e}var T=class{constructor(e){this.socket=e,this.onmessage=null,this.onclose=null,e.on("message",n=>{this.onmessage?.({data:n.toString()})}),e.on("close",()=>{this.onclose?.()})}send(e){this.socket.readyState===q.OPEN&&this.socket.send(e)}close(e,n){this.socket.close(e,n)}};function ke(t,e){return new URL(t.url??"/","http://localhost").pathname===e.path}function ve(t,e){if(new URL(t.url??"/","http://localhost").searchParams.has("vcp_bridge_key"))return null;let r=C(t.headers["x-vcp-site-edit-key"]),s=U(r,e.sessionKey),i=C(t.headers.host),o=C(t.headers.origin);return i&&!_e(i,e.host,o)?null:o?Pe(o,i,e.allowedOrigins)?{authenticatedByHeader:s}:null:s?{authenticatedByHeader:!0}:null}function Ee(t,e,n){let r=setTimeout(()=>{t.close(1008,"bridge authentication timeout")},be),s,i=()=>{clearTimeout(r),t.off("message",s),t.off("close",i)},o=()=>{i(),t.close(1008,"bridge authentication failed")};s=a=>{try{let u=p.parseMessage(a.toString());if(u.type!=="auth"||!U(u.payload.key,e)){o();return}i(),t.readyState===q.OPEN&&t.send(p.stringifyMessage({type:"auth-ok",payload:{ok:!0}})),n()}catch{o()}},t.on("message",s),t.on("close",i)}function U(t,e){if(!t)return!1;let n=Buffer.from(t),r=Buffer.from(e);return n.length===r.length&&ae(n,r)}function _e(t,e,n){let r=D(t),s=D(e);return r===s||A(s)&&A(r)&&(!n||j(n,t))||Ce(s)&&(!n||j(n,t))}function Ce(t){return t==="0.0.0.0"||t==="::"}function A(t){return t==="localhost"||t==="127.0.0.1"||t==="::1"}function Pe(t,e,n){return n.includes(t)||(e?j(t,e):!1)}function j(t,e){try{return new URL(t).host===J(e)}catch{return!1}}function D(t){let e=J(t);return e.startsWith("[")?e.slice(1,e.indexOf("]")):e.split(":").length-1===1?e.split(":",1)[0]??"":e}function J(t){return t.trim().toLowerCase()}function C(t){return Array.isArray(t)?t[0]:t}function Te(){return oe(32).toString("base64url")}var Be="next",He="127.0.0.1",Ne=3e3,x="/__vcp/site-edit/bridge",Q="/__vcp/site-edit/health",I="/__vcp/site-edit/client/",Oe=`${I}auto-bootstrap.js`,Y='data-vcp-site-edit-bootstrap="true"',Ke=["next.config.ts","next.config.mts","next.config.cts","next.config.js","next.config.mjs","next.config.cjs"],Me="@vcp-dev/site-edit-engine/webpack-loader",Ae=/@vcp\/site-edit-engine\/(?:webpack-loader|site-edit-instrumentation-loader)/,De=[/\bconfig\.webpack\s*=/,/\bwebpack\s*\([^)]*\)\s*\{/,/\bwebpack\s*:\s*(?:async\s*)?(?:\([^)]*\)|[A-Za-z_$][\w$]*)?\s*=>/,/\bwebpack\s*:\s*(?:async\s+)?function\b/],qe=new Set(["auto-bootstrap.js","client.js","designer-trace.js","document-codec.js","protocol.js","window-message.js"]);async function qt(t={}){let e=k.resolve(t.projectRoot??process.cwd()),n=t.host??He,r=t.port??St(process.env.PORT)??Ne,s=t.bridgePath??x,i=Ve(s),o=ct(),a=t.basePath??process.env.VCP_SITE_PREVIEW_BASE_PATH??"/",u=await pt(e),c=Fe(e),d=u({dev:!0,dir:e,hostname:n,port:r,...Ue(c)}),f=d.getRequestHandler();await d.prepare();let h=null,g=je.createServer((ee,w)=>{et({basePath:a,bridgePath:s,bridgeHealthPath:i,bridgeSessionKey:o,getBridgeHealth:()=>h?.getHealth()??nt(s),handle:f,request:ee,response:w}).catch(te=>{console.error(te),w.headersSent||(w.statusCode=500),w.end("site edit preview request failed")})});h=F(g,{siteId:t.siteId??process.env.VCP_SITE_ID??"site_preview",snapshotId:t.snapshotId??process.env.VCP_SNAPSHOT_ID??yt(e),projectRoot:e,basePath:a,host:n,port:r,path:s,sessionKey:o}),$e({server:g,bridgePath:s,handleUpgrade:d.getUpgradeHandler()});let v=()=>{h?.stop(),d.close().catch(()=>{}),g.close()};return process.on("SIGINT",v),process.on("SIGTERM",v),await bt(g,r,n),{bundler:c,close:v,server:g}}function Fe(t){let e=Je(t);return e&&We(e)?"webpack":"turbopack"}function Ue(t){return t==="webpack"?{webpack:!0}:{turbopack:!0}}function Je(t){for(let e of Ke){let n=b(t,e);if(y(n))return m(n,"utf8")}return null}function We(t){return t.includes(Me)?!0:Ae.test(t)&&De.some(e=>e.test(t))}function $e(t){let e=(n,r,s)=>{Le(n.url,t.bridgePath)||t.handleUpgrade(n,r,s).catch(i=>{console.error(i),r.destroyed||r.destroy()})};return t.server.on("upgrade",e),()=>t.server.off("upgrade",e)}function Le(t,e){return new URL(t??"/","http://localhost").pathname===e}function Ve(t=x){if(t===x)return Q;let e=(t.startsWith("/")?t:`/${t}`).replace(/\/+$/,"");return e.endsWith("/bridge")?`${e.slice(0,-7)}/health`:e?`${e}/health`:"/health"}function ze(t){let e=JSON.stringify({basePath:t.basePath,wsPath:t.bridgePath,bridge_session_key:t.bridgeSessionKey,defer_initial_sync:!0,runtime_versions:t.runtimeVersions??Ge()});return`<script type="module" ${Y}>import { startSiteEditAutoBootstrap } from ${JSON.stringify(Oe)};void startSiteEditAutoBootstrap(window, ${e}).catch(() => undefined);</script>`}function Ge(){return{bridge:B("@vcp-dev/site-edit-bridge"),contracts:B("@vcp-dev/contracts"),engine:B("@vcp-dev/site-edit-engine")}}function B(t){let e=V(import.meta.url);try{let n=e.resolve(t),r=Qe(n,t);if(!r)return;let s=JSON.parse(m(r,"utf8"));return typeof s.version=="string"?s.version:void 0}catch{return}}function Qe(t,e){let n=k.parse(t).root,r=S(t);for(;r&&r!==n;){let s=b(r,"package.json");if(y(s))try{if(JSON.parse(m(s,"utf8")).name===e)return s}catch{return}r=S(r)}}function Ye(t,e){if(t.includes(Y))return t;let n=ze(e),r=/<\/body\s*>/i.exec(t);return r?`${t.slice(0,r.index)}${n}${t.slice(r.index)}`:`${t}${n}`}function Ft(t,e=S(G(import.meta.url)),n=y){return X(t,e,n)?.path??null}function X(t,e=S(G(import.meta.url)),n=y){let r=new URL(t??"/","http://localhost").pathname;if(!r.startsWith(I))return null;let s=r.slice(I.length);if(!qe.has(s))return null;let i=H(gt(e),s);if(z(e)!=="src")return{path:i,requiresTranspile:!1};let o=H(e,s.replace(/\.js$/,".ts"));return n(o)?{path:o,requiresTranspile:!0}:{path:i,requiresTranspile:!1}}async function Xe(t,e){let n=X(t.url);if(!n)return!1;if(!y(n.path))return e.writeHead(404),e.end("site edit client asset not found"),!0;let r=n.requiresTranspile?await Ze(m(n.path,"utf8")):m(n.path);return e.writeHead(200,{"cache-control":"no-store","content-type":"text/javascript; charset=utf-8"}),e.end(r),!0}async function Ze(t){let e=await import("typescript");return e.transpileModule(t,{compilerOptions:{module:e.ModuleKind.ES2022,target:e.ScriptTarget.ES2022}}).outputText}async function et({basePath:t,bridgePath:e,bridgeHealthPath:n,bridgeSessionKey:r,getBridgeHealth:s,handle:i,request:o,response:a}){if(tt(o,a,n,s)||await Xe(o,a))return;let u=rt(o),c=st(o),d=u||c?at(a,{basePath:t,bridgePath:e,bridgeSessionKey:r,patchNextDevChunk:c}):()=>{};try{await i(o,a)}catch(f){throw d(),f}}function tt(t,e,n,r){if(t.method!=="GET"||new URL(t.url??"/","http://localhost").pathname!==n)return!1;let i=r();return e.writeHead(i.ok?200:503,{"cache-control":"no-store","content-type":"application/json; charset=utf-8"}),e.end(JSON.stringify(i)),!0}function nt(t){return{ok:!1,bridgePath:t,runtimeReady:!1,routesIndexed:0,sessionRequired:!0}}function rt(t){return it(t)?(t.headers["accept-encoding"]="identity",!0):!1}function st(t){return ot(t)?(t.headers["accept-encoding"]="identity",!0):!1}function it(t){if(t.method!=="GET")return!1;let e=new URL(t.url??"/","http://localhost").pathname;if(e.startsWith("/_next/")||e.startsWith(I)||e===x||e===Q)return!1;let n=t.headers.accept??"";return n===""||n.includes("text/html")}function ot(t){if(t.method!=="GET"||!String(t.headers["user-agent"]??"").includes("Firefox"))return!1;let n=new URL(t.url??"/","http://localhost").pathname;return n.startsWith("/_next/static/chunks/")&&n.endsWith(".js")}function at(t,e){let n=[],r=t,s=r.flushHeaders.bind(t),i=r.write.bind(t),o=r.end.bind(t);return r.flushHeaders=()=>{},r.write=(a,u,c)=>(a!==void 0&&n.push(W(a,$(u))),L(u),L(c),!0),r.end=(a,u,c)=>{let d=typeof a=="function"?void 0:a;d!==void 0&&n.push(W(d,$(u))),r.write=i,r.end=o,r.flushHeaders=s;let f=Buffer.concat(n),h=ut(t,f,e);h!==f&&!t.headersSent&&t.removeHeader("content-length");let g=typeof a=="function"?a:typeof u=="function"?u:typeof c=="function"?c:void 0;return g?o(h,g):o(h)},()=>{r.write=i,r.end=o,r.flushHeaders=s}}function ut(t,e,n){if(dt(t,e))return Buffer.from(Ye(e.toString("utf8"),n));if(n.patchNextDevChunk&&ht(t)){let r=e.toString("utf8"),s=lt(r);if(s!==r)return Buffer.from(s)}return e}function ct(){let t=new Uint8Array(32);return globalThis.crypto.getRandomValues(t),Buffer.from(t).toString("base64url")}function dt(t,e){if(t.statusCode<200||t.statusCode>=300)return!1;let n=t.getHeader("content-type"),r=Array.isArray(n)?n.join(";"):String(n??"");return e.toString("utf8",0,256).includes("<html")&&(r===""||r.includes("text/html"))}function ht(t){if(t.statusCode<200||t.statusCode>=300)return!1;let e=t.getHeader("content-type"),n=Array.isArray(e)?e.join(";"):String(e??"");return n===""||n.includes("javascript")||n.includes("ecmascript")}function lt(t){return[["debugChannel = createDebugChannel(undefined);","debugChannel = undefined;"],["if (isStreamErrorOrUnfinished(ctr)) {","if (false) {"],["return ctr.desiredSize === null || ctr.desiredSize < 0;","return false;"],[["findSourceMapURL: _appfindsourcemapurl.findSourceMapURL,"," debugChannel,"," startTime: 0"].join(`
4
+ `),o.destroy();return}d.authenticatedByHeader&&n.add(i),e.handleUpgrade(i,o,c,h=>{e.emit("connection",h,i)})};return t.server.on("upgrade",r),e.on("connection",(i,o)=>{if(n.has(o)){s(i);return}qe(i,t.sessionKey,()=>{s(i)})}),e.stop=()=>{t.server.off("upgrade",r);for(let i of e.clients)i.close(1001,"bridge stopped");e.close()},e}var A=class{constructor(e){this.socket=e,this.onmessage=null,this.onclose=null,e.on("error",()=>{}),e.on("message",n=>{this.onmessage?.({data:n.toString()})}),e.on("close",()=>{this.onclose?.()})}send(e){if(this.socket.readyState===X.OPEN){if(this.socket.bufferedAmount+Buffer.byteLength(e,"utf8")>De){this.socket.close(1013,"bridge backpressure");return}this.socket.send(e)}}close(e,n){this.socket.close(e,n)}};function Ne(t,e){return new URL(t.url??"/","http://localhost").pathname===e.path}function Ke(t,e){if(new URL(t.url??"/","http://localhost").searchParams.has("vcp_bridge_key"))return null;let s=U(t.headers["x-vcp-site-edit-key"]),r=ne(s,e.sessionKey),i=U(t.headers.host),o=U(t.headers.origin);return i&&!Le(i,e.host,o)?null:o?Je(o,i,e.allowedOrigins)?{authenticatedByHeader:r}:null:r?{authenticatedByHeader:!0}:null}function qe(t,e,n){let s=setTimeout(()=>{t.close(1008,"bridge authentication timeout")},Me),r,i=()=>{clearTimeout(s),t.off("message",r),t.off("close",i)},o=()=>{i(),t.close(1008,"bridge authentication failed")};r=c=>{try{let d=f.parseMessage(c.toString());if(d.type!=="auth"||!ne(d.payload.key,e)){o();return}i(),t.readyState===X.OPEN&&t.send(f.stringifyMessage({type:"auth-ok",payload:{ok:!0}})),n()}catch{o()}},t.on("message",r),t.on("close",i)}function ne(t,e){if(!t)return!1;let n=Buffer.from(t),s=Buffer.from(e);return n.length===s.length&&be(n,s)}function Le(t,e,n){let s=Y(t),r=Y(e);return s===r||Q(r)&&Q(s)&&(!n||F(n,t))||$e(r)&&(!n||F(n,t))}function $e(t){return t==="0.0.0.0"||t==="::"}function Q(t){return t==="localhost"||t==="127.0.0.1"||t==="::1"}function Je(t,e,n){return n.includes(t)||(e?F(t,e):!1)}function F(t,e){try{return new URL(t).host===se(e)}catch{return!1}}function Y(t){let e=se(t);return e.startsWith("[")?e.slice(1,e.indexOf("]")):e.split(":").length-1===1?e.split(":",1)[0]??"":e}function se(t){return t.trim().toLowerCase()}function U(t){return Array.isArray(t)?t[0]:t}function We(){return xe(32).toString("base64url")}var Ge="next",Qe="127.0.0.1",Ye=3e3,E="/__vcp/site-edit/bridge",de="/__vcp/site-edit/health",_="/__vcp/site-edit/client/",Ze=`${_}auto-bootstrap.js`,he='data-vcp-site-edit-bootstrap="true"',Xe=["next.config.ts","next.config.mts","next.config.cts","next.config.js","next.config.mjs","next.config.cjs"],et="@vcp-dev/site-edit-engine/webpack-loader",tt=/@vcp\/site-edit-engine\/(?:webpack-loader|site-edit-instrumentation-loader)/,nt=[/\bconfig\.webpack\s*=/,/\bwebpack\s*\([^)]*\)\s*\{/,/\bwebpack\s*:\s*(?:async\s*)?(?:\([^)]*\)|[A-Za-z_$][\w$]*)?\s*=>/,/\bwebpack\s*:\s*(?:async\s+)?function\b/],st=new Set(["auto-bootstrap.js","client.js","designer-trace.js","document-codec.js","protocol.js","window-message.js"]);async function on(t={}){let e=R.resolve(t.projectRoot??process.cwd()),n=t.host??Qe,s=t.port??Mt(process.env.PORT)??Ye,r=t.bridgePath??E,i=dt(r),o=vt(),c=t.basePath??process.env.VCP_SITE_PREVIEW_BASE_PATH??"/",d=(t.tenantId??process.env.VCP_TENANT_ID)?.trim()||void 0,h=await Ht(e),a=rt(e),u=h({dev:!0,dir:e,hostname:n,port:s,...it(a)}),l=u.getRequestHandler(),g=t.siteId??process.env.VCP_SITE_ID??"site_preview",b=t.snapshotId??process.env.VCP_SNAPSHOT_ID??Dt(e),S=t.historyRoot??process.env.VCP_SITE_EDIT_HISTORY_ROOT,q=S?.trim()?R.resolve(S):void 0;await u.prepare();let P=null,w=ze.createServer((fe,k)=>{yt({basePath:c,bridgePath:r,bridgeHealthPath:i,bridgeSessionKey:o,getBridgeHealth:()=>P?.getHealth()??wt(r),handle:l,request:fe,response:k}).catch(ge=>{console.error(ge),k.headersSent||(k.statusCode=500),k.end("site edit preview request failed")})});P=te(w,{...d?{tenantId:d}:{},siteId:g,snapshotId:b,projectRoot:e,basePath:c,host:n,port:s,path:r,sessionKey:o,...q?{historyRoot:q}:{}}),ut({server:w,bridgePath:r,handleUpgrade:u.getUpgradeHandler()});let T=()=>{P?.stop(),u.close().catch(()=>{}),w.close()};return process.on("SIGINT",T),process.on("SIGTERM",T),await Ut(w,s,n),{bundler:a,close:T,server:w}}function rt(t){let e=ot(t);return e&&at(e)?"webpack":"turbopack"}function it(t){return t==="webpack"?{webpack:!0}:{turbopack:!0}}function ot(t){for(let e of Xe){let n=x(t,e);if(y(n))return m(n,"utf8")}return null}function at(t){return t.includes(et)?!0:tt.test(t)&&nt.some(e=>e.test(t))}function ut(t){let e=(n,s,r)=>{ct(n.url,t.bridgePath)||t.handleUpgrade(n,s,r).catch(i=>{console.error(i),s.destroyed||s.destroy()})};return t.server.on("upgrade",e),()=>t.server.off("upgrade",e)}function ct(t,e){return new URL(t??"/","http://localhost").pathname===e}function dt(t=E){if(t===E)return de;let e=(t.startsWith("/")?t:`/${t}`).replace(/\/+$/,"");return e.endsWith("/bridge")?`${e.slice(0,-7)}/health`:e?`${e}/health`:"/health"}function ht(t){let e=JSON.stringify({basePath:t.basePath,wsPath:t.bridgePath,bridge_session_key:t.bridgeSessionKey,defer_initial_sync:!0,runtime_versions:t.runtimeVersions??lt()});return`<script type="module" ${he}>import { startSiteEditAutoBootstrap } from ${JSON.stringify(Ze)};void startSiteEditAutoBootstrap(window, ${e}).catch(() => undefined);</script>`}function lt(){return{bridge:N("@vcp-dev/site-edit-bridge"),contracts:N("@vcp-dev/contracts"),engine:N("@vcp-dev/site-edit-engine")}}function N(t){let e=ae(import.meta.url);try{let n=e.resolve(t),s=pt(n,t);if(!s)return;let r=JSON.parse(m(s,"utf8"));return typeof r.version=="string"?r.version:void 0}catch{return}}function pt(t,e){let n=R.parse(t).root,s=I(t);for(;s&&s!==n;){let r=x(s,"package.json");if(y(r))try{if(JSON.parse(m(r,"utf8")).name===e)return r}catch{return}s=I(s)}}function ft(t,e){if(t.includes(he))return t;let n=ht(e),s=/<\/body\s*>/i.exec(t);return s?`${t.slice(0,s.index)}${n}${t.slice(s.index)}`:`${t}${n}`}function an(t,e=I(ce(import.meta.url)),n=y){return le(t,e,n)?.path??null}function le(t,e=I(ce(import.meta.url)),n=y){let s=new URL(t??"/","http://localhost").pathname;if(!s.startsWith(_))return null;let r=s.slice(_.length);if(!st.has(r))return null;let i=K(Bt(e),r);if(ue(e)!=="src")return{path:i,requiresTranspile:!1};let o=K(e,r.replace(/\.js$/,".ts"));return n(o)?{path:o,requiresTranspile:!0}:{path:i,requiresTranspile:!1}}async function gt(t,e){let n=le(t.url);if(!n)return!1;if(!y(n.path))return e.writeHead(404),e.end("site edit client asset not found"),!0;let s=n.requiresTranspile?await mt(m(n.path,"utf8")):m(n.path);return e.writeHead(200,{"cache-control":"no-store","content-type":"text/javascript; charset=utf-8"}),e.end(s),!0}async function mt(t){let e=await import("typescript");return e.transpileModule(t,{compilerOptions:{module:e.ModuleKind.ES2022,target:e.ScriptTarget.ES2022}}).outputText}async function yt({basePath:t,bridgePath:e,bridgeHealthPath:n,bridgeSessionKey:s,getBridgeHealth:r,handle:i,request:o,response:c}){if(St(o,c,n,r)||await gt(o,c))return;let d=Rt(o),h=It(o),a=d||h?kt(c,{basePath:t,bridgePath:e,bridgeSessionKey:s,patchNextDevChunk:h}):()=>{};try{await i(o,c)}catch(u){throw a(),u}}function St(t,e,n,s){if(t.method!=="GET"||new URL(t.url??"/","http://localhost").pathname!==n)return!1;let i=s();return e.writeHead(i.ok?200:503,{"cache-control":"no-store","content-type":"application/json; charset=utf-8"}),e.end(JSON.stringify(i)),!0}function wt(t){return{ok:!1,bridgePath:t,runtimeReady:!1,routesIndexed:0,sessionRequired:!0}}function Rt(t){return xt(t)?(t.headers["accept-encoding"]="identity",!0):!1}function It(t){return bt(t)?(t.headers["accept-encoding"]="identity",!0):!1}function xt(t){if(t.method!=="GET")return!1;let e=new URL(t.url??"/","http://localhost").pathname;if(e.startsWith("/_next/")||e.startsWith(_)||e===E||e===de)return!1;let n=t.headers.accept??"";return n===""||n.includes("text/html")}function bt(t){if(t.method!=="GET"||!String(t.headers["user-agent"]??"").includes("Firefox"))return!1;let n=new URL(t.url??"/","http://localhost").pathname;return n.startsWith("/_next/static/chunks/")&&n.endsWith(".js")}function kt(t,e){let n=[],s=t,r=s.flushHeaders.bind(t),i=s.write.bind(t),o=s.end.bind(t);return s.flushHeaders=()=>{},s.write=(c,d,h)=>(c!==void 0&&n.push(re(c,ie(d))),oe(d),oe(h),!0),s.end=(c,d,h)=>{let a=typeof c=="function"?void 0:c;a!==void 0&&n.push(re(a,ie(d))),s.write=i,s.end=o,s.flushHeaders=r;let u=Buffer.concat(n),l=Ct(t,u,e);l!==u&&!t.headersSent&&t.removeHeader("content-length");let g=typeof c=="function"?c:typeof d=="function"?d:typeof h=="function"?h:void 0;return g?o(l,g):o(l)},()=>{s.write=i,s.end=o,s.flushHeaders=r}}function Ct(t,e,n){if(Et(t,e))return Buffer.from(ft(e.toString("utf8"),n));if(n.patchNextDevChunk&&_t(t)){let s=e.toString("utf8"),r=Pt(s);if(r!==s)return Buffer.from(r)}return e}function vt(){let t=new Uint8Array(32);return globalThis.crypto.getRandomValues(t),Buffer.from(t).toString("base64url")}function Et(t,e){if(t.statusCode<200||t.statusCode>=300)return!1;let n=t.getHeader("content-type"),s=Array.isArray(n)?n.join(";"):String(n??"");return e.toString("utf8",0,256).includes("<html")&&(s===""||s.includes("text/html"))}function _t(t){if(t.statusCode<200||t.statusCode>=300)return!1;let e=t.getHeader("content-type"),n=Array.isArray(e)?e.join(";"):String(e??"");return n===""||n.includes("javascript")||n.includes("ecmascript")}function Pt(t){return[["debugChannel = createDebugChannel(undefined);","debugChannel = undefined;"],["if (isStreamErrorOrUnfinished(ctr)) {","if (false) {"],["return ctr.desiredSize === null || ctr.desiredSize < 0;","return false;"],[["findSourceMapURL: _appfindsourcemapurl.findSourceMapURL,"," debugChannel,"," startTime: 0"].join(`
5
5
  `),["unstable_allowPartialStream: true,"," debugChannel,"," startTime: 0"].join(`
6
- `)],[["findSourceMapURL: _appfindsourcemapurl.findSourceMapURL,"," debugChannel,"," startTime: 0"].join("\\n"),["unstable_allowPartialStream: true,"," debugChannel,"," startTime: 0"].join("\\n")],["debugChannel: createDebugChannel && createDebugChannel(requestHeaders)","debugChannel: undefined"],["debugChannel: createDebugChannel && createDebugChannel(headers)","debugChannel: undefined"]].reduce((n,[r,s])=>ft(n,r,s),t)}function ft(t,e,n){if(n.length>e.length)throw new Error("Replacement must not be longer than the search string.");return t.replaceAll(e,n.padEnd(e.length," "))}function W(t,e){return Buffer.isBuffer(t)?t:t instanceof Uint8Array?Buffer.from(t):Buffer.from(String(t),e)}function $(t){return typeof t=="string"?t:void 0}function L(t){typeof t=="function"&&t()}async function pt(t){let n=V(b(t,"package.json"))(Be);return n.default??n}function gt(t){return z(t)!=="src"?t:H(mt(t),"dist")}function mt(t){return Z(t)?k.posix.dirname(t):S(t)}function H(t,e){return Z(t)?k.posix.join(t,e):b(t,e)}function Z(t){return t.includes("/")&&!t.includes("\\")}function yt(t){let e=b(t,".vcp/current-snapshot.json");if(!y(e))return"sandbox_worktree";try{let n=JSON.parse(m(e,"utf8"));return typeof n.snapshotId=="string"?n.snapshotId:"sandbox_worktree"}catch{return"sandbox_worktree"}}function St(t){if(!t)return null;let e=Number.parseInt(t,10);return Number.isInteger(e)&&e>0&&e<=65535?e:null}function bt(t,e,n){return new Promise((r,s)=>{t.once("error",s),t.listen(e,n,()=>{t.off("error",s),r()})})}export{$e as attachNextDevUpgradeHandler,ze as createSiteEditAutoBootstrapHtmlSnippet,Ye as injectSiteEditAutoBootstrap,lt as patchNextFirefoxDevChunk,st as prepareFirefoxNextDevChunkPatchRequest,rt as prepareSiteEditHtmlInjectionRequest,Ve as resolveSiteEditBridgeHealthPath,X as resolveSiteEditClientAsset,Ft as resolveSiteEditClientAssetPath,Fe as resolveSiteEditNextDevBundler,qt as startSiteEditNextDevServer,Ze as transpileSiteEditClientSource};
6
+ `)],[["findSourceMapURL: _appfindsourcemapurl.findSourceMapURL,"," debugChannel,"," startTime: 0"].join("\\n"),["unstable_allowPartialStream: true,"," debugChannel,"," startTime: 0"].join("\\n")],["debugChannel: createDebugChannel && createDebugChannel(requestHeaders)","debugChannel: undefined"],["debugChannel: createDebugChannel && createDebugChannel(headers)","debugChannel: undefined"]].reduce((n,[s,r])=>Tt(n,s,r),t)}function Tt(t,e,n){if(n.length>e.length)throw new Error("Replacement must not be longer than the search string.");return t.replaceAll(e,n.padEnd(e.length," "))}function re(t,e){return Buffer.isBuffer(t)?t:t instanceof Uint8Array?Buffer.from(t):Buffer.from(String(t),e)}function ie(t){return typeof t=="string"?t:void 0}function oe(t){typeof t=="function"&&t()}async function Ht(t){let n=ae(x(t,"package.json"))(Ge);return n.default??n}function Bt(t){return ue(t)!=="src"?t:K(jt(t),"dist")}function jt(t){return pe(t)?R.posix.dirname(t):I(t)}function K(t,e){return pe(t)?R.posix.join(t,e):x(t,e)}function pe(t){return t.includes("/")&&!t.includes("\\")}function Dt(t){let e=x(t,".vcp/current-snapshot.json");if(!y(e))return"sandbox_worktree";try{let n=JSON.parse(m(e,"utf8"));return typeof n.snapshotId=="string"?n.snapshotId:"sandbox_worktree"}catch{return"sandbox_worktree"}}function Mt(t){if(!t)return null;let e=Number.parseInt(t,10);return Number.isInteger(e)&&e>0&&e<=65535?e:null}function Ut(t,e,n){return new Promise((s,r)=>{t.once("error",r),t.listen(e,n,()=>{t.off("error",r),s()})})}export{ut as attachNextDevUpgradeHandler,ht as createSiteEditAutoBootstrapHtmlSnippet,ft as injectSiteEditAutoBootstrap,Pt as patchNextFirefoxDevChunk,It as prepareFirefoxNextDevChunkPatchRequest,Rt as prepareSiteEditHtmlInjectionRequest,dt as resolveSiteEditBridgeHealthPath,le as resolveSiteEditClientAsset,an as resolveSiteEditClientAssetPath,rt as resolveSiteEditNextDevBundler,on as startSiteEditNextDevServer,mt as transpileSiteEditClientSource};
@@ -1,8 +1,23 @@
1
- import type { SiteEditCommandSuccess, SiteEditEvent, SiteEditGetClassNameSourceCommandResult, SiteEditObjectEditContext, SiteEditObjectSummary, SiteEditOperationCommandResult, SiteEditOperationRequest, SiteEditRenderDocument, SiteEditResolveSourceLocationInput, SiteEditSourceFileResponse, SiteEditSourceLocation, SiteEditSourceTreeResponse } from "@vcp-dev/contracts";
1
+ import type { SiteEditCommandSuccess, SiteEditClassNameSourceStyleContext, SiteEditEvent, SiteEditGetClassNameSourceCommandResult, SiteEditObjectEditContext, SiteEditOperationCommandResult, SiteEditMutationContext, SiteEditInitializeSessionInput, SiteEditAuthenticatedOperationRequest, SiteEditOperationRequest, SiteEditRenderDocument, SiteEditResolveSourceLocationInput, SiteEditSourceFileResponse, SiteEditSourceLocation, SiteEditSourceTreeResponse } from "@vcp-dev/contracts";
2
2
  import { type CompactRenderDocument } from "./document-codec.js";
3
3
  type Awaitable<T> = T | Promise<T>;
4
- export type SiteEditBridgeRequestKind = "get-document" | "get-object-summary" | "get-object-edit-context" | "get-class-name-source" | "get-source-tree" | "get-source-file" | "resolve-source-location" | "execute" | "undo" | "redo";
4
+ export type SiteEditHistoryStatus = {
5
+ canUndo: boolean;
6
+ canRedo: boolean;
7
+ undoDepth: number;
8
+ redoDepth: number;
9
+ recoveryRequired: boolean;
10
+ };
11
+ export type SiteEditBridgeRequestKind = "initialize-session" | "get-document" | "get-history-status" | "get-style-context" | "get-object-edit-context" | "get-class-name-source" | "get-source-tree" | "get-source-file" | "resolve-source-location" | "execute" | "undo" | "redo";
5
12
  export type SiteEditBridgeRequest = {
13
+ type: "request";
14
+ payload: {
15
+ id: string;
16
+ kind: "initialize-session";
17
+ routeId: string;
18
+ mutationContext?: SiteEditMutationContext;
19
+ };
20
+ } | {
6
21
  type: "request";
7
22
  payload: {
8
23
  id: string;
@@ -13,9 +28,14 @@ export type SiteEditBridgeRequest = {
13
28
  type: "request";
14
29
  payload: {
15
30
  id: string;
16
- kind: "get-object-summary";
17
- key: string;
18
- routeId?: string;
31
+ kind: "get-history-status";
32
+ };
33
+ } | {
34
+ type: "request";
35
+ payload: {
36
+ id: string;
37
+ kind: "get-style-context";
38
+ routeId: string;
19
39
  };
20
40
  } | {
21
41
  type: "request";
@@ -59,22 +79,48 @@ export type SiteEditBridgeRequest = {
59
79
  payload: {
60
80
  id: string;
61
81
  kind: "execute";
62
- request: SiteEditOperationRequest;
82
+ request: SiteEditAuthenticatedOperationRequest;
63
83
  };
64
84
  } | {
65
85
  type: "request";
66
86
  payload: {
67
87
  id: string;
68
88
  kind: "undo" | "redo";
89
+ /** @deprecated Ignored by the runtime. */
90
+ mutationContext?: SiteEditMutationContext;
69
91
  };
70
92
  };
71
93
  export type SiteEditBridgeResponse = {
94
+ type: "response";
95
+ payload: {
96
+ id: string;
97
+ kind: "initialize-session";
98
+ result: SiteEditCommandSuccess<{
99
+ document: SiteEditRenderDocument | CompactRenderDocument;
100
+ historyStatus: SiteEditHistoryStatus;
101
+ }>;
102
+ };
103
+ } | {
72
104
  type: "response";
73
105
  payload: {
74
106
  id: string;
75
107
  kind: "get-document";
76
108
  result: SiteEditCommandSuccess<SiteEditRenderDocument> | SiteEditCommandSuccess<CompactRenderDocument>;
77
109
  };
110
+ } | {
111
+ type: "response";
112
+ payload: {
113
+ id: string;
114
+ kind: "get-history-status";
115
+ result: SiteEditCommandSuccess<SiteEditHistoryStatus>;
116
+ };
117
+ } | {
118
+ type: "response";
119
+ payload: {
120
+ id: string;
121
+ kind: "get-style-context";
122
+ result: SiteEditCommandSuccess<SiteEditClassNameSourceStyleContext | null>;
123
+ };
78
124
  } | {
79
125
  type: "response";
80
126
  payload: {
@@ -96,13 +142,6 @@ export type SiteEditBridgeResponse = {
96
142
  kind: "resolve-source-location";
97
143
  result: SiteEditCommandSuccess<SiteEditSourceLocation>;
98
144
  };
99
- } | {
100
- type: "response";
101
- payload: {
102
- id: string;
103
- kind: "get-object-summary";
104
- result: SiteEditCommandSuccess<SiteEditObjectSummary>;
105
- };
106
145
  } | {
107
146
  type: "response";
108
147
  payload: {
@@ -131,6 +170,7 @@ export type SiteEditBridgeError = {
131
170
  id: string;
132
171
  kind: SiteEditBridgeRequestKind;
133
172
  message: string;
173
+ code?: string;
134
174
  };
135
175
  };
136
176
  export type SiteEditBridgeEventMessage = {
@@ -153,8 +193,13 @@ export type SiteEditBridgeAuthOkMessage = {
153
193
  };
154
194
  export type SiteEditBridgeMessage = SiteEditBridgeAuthMessage | SiteEditBridgeAuthOkMessage | SiteEditBridgeRequest | SiteEditBridgeResponse | SiteEditBridgeError | SiteEditBridgeEventMessage;
155
195
  export type SiteEditBridgeEngine = {
196
+ initializeSession(input: SiteEditInitializeSessionInput): Awaitable<SiteEditCommandSuccess<{
197
+ document: SiteEditRenderDocument;
198
+ historyStatus: SiteEditHistoryStatus;
199
+ }>>;
156
200
  getDocument(routeId: string): Awaitable<SiteEditCommandSuccess<SiteEditRenderDocument>>;
157
- getObjectSummary(key: string, routeId?: string): Awaitable<SiteEditCommandSuccess<SiteEditObjectSummary>>;
201
+ getHistoryStatus(): Awaitable<SiteEditCommandSuccess<SiteEditHistoryStatus>>;
202
+ getStyleContext(routeId: string): Awaitable<SiteEditCommandSuccess<SiteEditClassNameSourceStyleContext | null>>;
158
203
  getObjectEditContext(key: string, routeId?: string): Awaitable<SiteEditCommandSuccess<SiteEditObjectEditContext>>;
159
204
  getClassNameSource(key: string, routeId?: string): Awaitable<SiteEditGetClassNameSourceCommandResult>;
160
205
  getSourceTree(): Awaitable<SiteEditCommandSuccess<SiteEditSourceTreeResponse>>;
@@ -162,7 +207,7 @@ export type SiteEditBridgeEngine = {
162
207
  path: string;
163
208
  }): Awaitable<SiteEditCommandSuccess<SiteEditSourceFileResponse>>;
164
209
  resolveSourceLocation(input: SiteEditResolveSourceLocationInput): Awaitable<SiteEditCommandSuccess<SiteEditSourceLocation>>;
165
- execute(request: SiteEditOperationRequest): Awaitable<SiteEditOperationCommandResult>;
210
+ execute(request: SiteEditAuthenticatedOperationRequest, mutationContext?: SiteEditMutationContext): Awaitable<SiteEditOperationCommandResult>;
166
211
  undo(): Awaitable<SiteEditOperationCommandResult>;
167
212
  redo(): Awaitable<SiteEditOperationCommandResult>;
168
213
  subscribe(listener: (event: SiteEditEvent) => void): () => void;
package/dist/server.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import type { Server as HttpServer } from "node:http";
2
2
  import { WebSocketServer } from "ws";
3
+ import { type PreviewSiteEditEngineRuntime, type PreviewSiteEditEngineRuntimeOptions } from "@vcp-dev/site-edit-engine";
4
+ import type { FileHistoryStore } from "@vcp-dev/site-edit-engine";
3
5
  import { type SiteEditNextRouteConfig } from "@vcp-dev/site-edit-engine/next-app-router";
4
6
  import { type SiteEditBridgeEngine } from "./protocol.js";
5
7
  export type SiteEditBridgeRuntimeOptions = {
@@ -12,6 +14,7 @@ export type SiteEditBridgeRuntimeOptions = {
12
14
  watch_max_startup_ms?: number;
13
15
  };
14
16
  export type SiteEditBridgeServerOptions = {
17
+ tenantId?: string;
15
18
  siteId: string;
16
19
  snapshotId: string;
17
20
  projectRoot?: string;
@@ -20,9 +23,16 @@ export type SiteEditBridgeServerOptions = {
20
23
  port?: number;
21
24
  path?: string;
22
25
  sessionKey?: string;
26
+ /** Private runtime directory for the durable, server-side undo/redo store. */
27
+ historyRoot?: string;
23
28
  allowedOrigins?: string[];
24
29
  runtime?: SiteEditBridgeRuntimeOptions;
25
30
  };
31
+ /** @internal Dependency seam for deterministic runtime and durable-store tests. */
32
+ export type SiteEditBridgeServerDependencies = {
33
+ createRuntime?: (options: PreviewSiteEditEngineRuntimeOptions) => PreviewSiteEditEngineRuntime;
34
+ openHistoryStore?: (root: string) => Promise<FileHistoryStore>;
35
+ };
26
36
  export type SiteEditBridgeHealth = {
27
37
  ok: boolean;
28
38
  bridgePath: string;
@@ -31,26 +41,38 @@ export type SiteEditBridgeHealth = {
31
41
  sessionRequired: boolean;
32
42
  };
33
43
  export declare const SITE_EDIT_BRIDGE_MAX_PAYLOAD_BYTES: number;
44
+ export declare const SITE_EDIT_BRIDGE_MAX_BUFFERED_BYTES: number;
34
45
  export declare function ensureSiteEditBridgeServerStarted(options: SiteEditBridgeServerOptions): SiteEditBridgeServer;
35
- export declare function attachSiteEditBridgeServer(server: HttpServer, options: SiteEditBridgeServerOptions): SiteEditBridgeServer;
46
+ export declare function attachSiteEditBridgeServer(server: HttpServer, options: SiteEditBridgeServerOptions, dependencies?: SiteEditBridgeServerDependencies): SiteEditBridgeServer;
36
47
  export declare class SiteEditBridgeServer {
37
48
  private readonly options;
38
49
  private readonly server;
39
50
  private readonly closeHttpServerOnStop;
51
+ private readonly dependencies;
40
52
  private runtime;
53
+ private activeHistorySessionId;
54
+ private historySessionTransition;
55
+ private readonly mutationQueue;
56
+ private readonly eventListeners;
41
57
  private routes;
42
58
  private stopWatching;
43
59
  private stopWebSocketServer;
44
60
  private readonly pendingChangedFiles;
45
61
  private fileChangeTimer;
46
62
  private isStarted;
47
- constructor(options: Required<SiteEditBridgeServerOptions>, server?: HttpServer, closeHttpServerOnStop?: boolean);
63
+ private runtimeVersionFloor;
64
+ private activeHistoryStore;
65
+ private versionPersistence;
66
+ constructor(options: ReturnType<typeof normalizeOptions>, server?: HttpServer, closeHttpServerOnStop?: boolean, dependencies?: SiteEditBridgeServerDependencies);
48
67
  start(options?: {
49
68
  listen?: boolean;
50
69
  }): void;
51
70
  getHealth(): SiteEditBridgeHealth;
52
71
  stop(): void;
72
+ private createRuntime;
53
73
  private createBridgeEngine;
74
+ private bindHistorySession;
75
+ private recordRuntimeVersion;
54
76
  private activateRoute;
55
77
  private normalizeRequest;
56
78
  private handleFileChanged;
@@ -60,6 +82,20 @@ export declare class SiteEditBridgeServer {
60
82
  private resolveRouteId;
61
83
  private getRuntime;
62
84
  }
85
+ declare function normalizeOptions(options: SiteEditBridgeServerOptions): {
86
+ tenantId: string | undefined;
87
+ siteId: string;
88
+ snapshotId: string;
89
+ projectRoot: string;
90
+ basePath: string;
91
+ host: string;
92
+ port: number;
93
+ path: string;
94
+ sessionKey: string;
95
+ historyRoot: string | undefined;
96
+ allowedOrigins: string[];
97
+ runtime: SiteEditBridgeRuntimeOptions;
98
+ };
63
99
  export declare function attachSiteEditWebSocketServer(options: {
64
100
  server: HttpServer;
65
101
  path: string;
@@ -70,3 +106,4 @@ export declare function attachSiteEditWebSocketServer(options: {
70
106
  }): WebSocketServer & {
71
107
  stop(): void;
72
108
  };
109
+ export {};
package/dist/server.js CHANGED
@@ -1,4 +1,4 @@
1
- import D from"node:http";import{randomBytes as N,timingSafeEqual as H}from"node:crypto";import P from"node:path";import{WebSocket as C,WebSocketServer as J}from"ws";import{createPreviewSiteEditEngineRuntime as W}from"@vcp-dev/site-edit-engine";import{resolveNextAppRouteId as A,scanNextAppRouterRoutes as U}from"@vcp-dev/site-edit-engine/next-app-router";import{SiteEditSourceWatcher as L}from"@vcp-dev/site-edit-engine/source-watcher";function k(t){let e=new Map,s=[],n=new Map,r=[],o=a=>{let u=e.get(a);if(typeof u=="number")return u;let h=s.length;return s.push(a),e.set(a,h),h},i=a=>{let u=null;for(let h of j(a)){let R=`${u??"root"}\0${h}`,w=n.get(R);if(typeof w=="number"){u=w;continue}let S=r.length;r.push({parent:u,token:o(h)}),n.set(R,S),u=S}if(u===null)throw new Error("Document key must not be empty");return u};return{encoding:"segment-trie-v1",siteId:t.siteId,snapshotId:t.snapshotId,routeId:t.routeId,version:t.version,tokens:s,nodes:r,rootKeyNodeIds:t.rootKeys.map(i),entries:t.entries.map(a=>({keyNodeId:i(a.key),tag:a.tag,label:a.label,parentKeyNodeId:a.parentKey?i(a.parentKey):null,childKeyNodeIds:a.childKeys.map(i),operationSummary:a.operationSummary}))}}function j(t){if(!t)return[];let e=[],s=0,n=b(t,s);if(n<0)return e.push(t),e;for(e.push(t.slice(0,n)),s=n;s<t.length;){if(n=b(t,s+(t.startsWith("::",s)?2:1)),n<0){e.push(t.slice(s));break}e.push(t.slice(s,n)),s=n}return e}function b(t,e){let s=t.indexOf("/",e),n=t.indexOf("::",e);return s<0?n:n<0?s:Math.min(s,n)}var d=class t{constructor(){}static parseMessage(e,s={}){let n=JSON.parse(e);if(!t.isMessage(n,s))throw new Error("Invalid site edit bridge message");return n}static stringifyMessage(e){return JSON.stringify(e)}static extractRequestInfo(e){if(typeof e=="object"&&e!==null&&"payload"in e&&typeof e.payload=="object"&&e.payload!==null){let s=e.payload,n=typeof s.id=="string"?s.id:"unknown",r=t.isRequestKind(s.kind)?s.kind:"get-document";return{id:n,kind:r}}return{id:"unknown",kind:"get-document"}}static isRequestKind(e){return e==="get-document"||e==="get-object-summary"||e==="get-object-edit-context"||e==="get-class-name-source"||e==="get-source-tree"||e==="get-source-file"||e==="resolve-source-location"||e==="execute"||e==="undo"||e==="redo"}static isMessage(e,s={}){return!c(e)||typeof e.type!="string"?!1:e.type==="request"?t.isRequestPayload(e.payload,s):e.type==="response"||e.type==="error"?c(e.payload)&&typeof e.payload.id=="string":e.type==="event"?c(e.payload)&&c(e.payload.event):e.type==="auth"?c(e.payload)&&typeof e.payload.key=="string":e.type==="auth-ok"?c(e.payload)&&e.payload.ok===!0:!1}static isRequestPayload(e,s={}){if(!c(e)||typeof e.id!="string"||!t.isRequestKind(e.kind))return!1;switch(e.kind){case"get-document":return typeof e.routeId=="string";case"get-object-summary":case"get-object-edit-context":case"get-class-name-source":case"resolve-source-location":return typeof e.key=="string"&&(e.routeId===void 0||typeof e.routeId=="string");case"get-source-tree":return!0;case"get-source-file":return typeof e.path=="string";case"execute":return M(e.request)&&(s.validateOperationRequest?.(e.request)??!0);case"undo":case"redo":return!0}}},l=class{constructor(e){this.options=e,this.messageQueue=new g,this.eventDispatcher=new f(s=>{this.sendJson(s)}),this.unsubscribe=e.engine.subscribe(s=>{this.eventDispatcher.push(s)}),e.socket.onmessage=s=>{try{this.messageQueue.enqueue(()=>this.handleRawMessage(s.data))}catch(n){this.sendJson({type:"error",payload:{id:"unknown",kind:"get-document",message:n instanceof Error?n.message:"Bridge queue overflow"}}),e.socket.close(1013,"bridge busy")}},e.socket.onclose=()=>{this.unsubscribe()}}async handleMessage(e){try{let s=e;if(s.type!=="request")return;let n=s.payload,{id:r}=n;switch(n.kind){case"get-document":this.sendJson({type:"response",payload:{id:r,kind:n.kind,result:{ok:!0,data:k((await this.options.engine.getDocument(n.routeId)).data)}}});return;case"get-object-summary":this.sendJson({type:"response",payload:{id:r,kind:n.kind,result:await this.options.engine.getObjectSummary(n.key,n.routeId)}});return;case"get-object-edit-context":this.sendJson({type:"response",payload:{id:r,kind:n.kind,result:await this.options.engine.getObjectEditContext(n.key,n.routeId)}});return;case"get-class-name-source":this.sendJson({type:"response",payload:{id:r,kind:n.kind,result:await this.options.engine.getClassNameSource(n.key,n.routeId)}});return;case"get-source-tree":this.sendJson({type:"response",payload:{id:r,kind:n.kind,result:await this.options.engine.getSourceTree()}});return;case"get-source-file":this.sendJson({type:"response",payload:{id:r,kind:n.kind,result:await this.options.engine.getSourceFile({path:n.path})}});return;case"resolve-source-location":this.sendJson({type:"response",payload:{id:r,kind:n.kind,result:await this.options.engine.resolveSourceLocation({key:n.key,routeId:n.routeId})}});return;case"execute":await this.handleBufferedOperation(r,n.kind,()=>this.options.engine.execute(n.request));return;case"undo":await this.handleBufferedOperation(r,n.kind,()=>this.options.engine.undo());return;case"redo":await this.handleBufferedOperation(r,n.kind,()=>this.options.engine.redo());return}}catch(s){let n=d.extractRequestInfo(e);this.sendJson({type:"error",payload:{...n,message:s instanceof Error?s.message:"Unknown websocket operation failure"}})}}async handleRawMessage(e){try{await this.handleMessage(d.parseMessage(e,{validateOperationRequest:this.options.validateOperationRequest}))}catch(s){let n=s instanceof Error?s.message:"Invalid websocket message",r=F(e);this.sendJson({type:"error",payload:{...r,message:n}})}}sendJson(e){this.options.socket.send(d.stringifyMessage(e))}async handleBufferedOperation(e,s,n){this.eventDispatcher.beginBuffering();try{let r=await n();this.sendJson({type:"response",payload:{id:e,kind:s,result:r}}),this.eventDispatcher.flush()}catch(r){this.sendJson({type:"error",payload:{id:e,kind:s,message:r instanceof Error?r.message:"Unknown websocket operation failure"}}),this.eventDispatcher.flush()}}},f=class{constructor(e){this.send=e,this.bufferedEvents=null}push(e){let s={type:"event",payload:{event:e}};if(this.bufferedEvents){this.bufferedEvents.push(s);return}this.send(s)}beginBuffering(){this.bufferedEvents=[]}flush(){let e=this.bufferedEvents??[];this.bufferedEvents=null,e.forEach(s=>{this.send(s)})}},g=class{constructor(e=64){this.maxQueued=e,this.queue=Promise.resolve(),this.queued=0}enqueue(e){if(this.queued>=this.maxQueued)throw new Error("Bridge request queue is full");this.queued+=1;let s=async()=>{this.queued-=1,await e()},n=this.queue.then(s,s);return this.queue=n.then(()=>{},()=>{}),n}};function c(t){return typeof t=="object"&&t!==null}function M(t){return c(t)&&typeof t.id=="string"&&typeof t.siteId=="string"&&typeof t.baseSnapshotId=="string"&&(t.routeId===void 0||typeof t.routeId=="string")&&typeof t.kind=="string"&&c(t.target)&&typeof t.documentVersion=="number"&&Number.isInteger(t.documentVersion)&&t.documentVersion>=0&&c(t.params)}function F(t){try{return d.extractRequestInfo(JSON.parse(t))}catch{return{id:"unknown",kind:"get-document"}}}import{SiteEditOperationRequestSchema as $}from"@vcp-dev/contracts";var z="127.0.0.1",V=30218,G="/__vcp/site-edit/bridge",Q=6*1024*1024,Y=3e3,x=50,_="__VCP_SITE_EDIT_BRIDGE_SERVERS__";function Ie(t){let e=O(t),s=K(e),n=T(),r=n.get(s);if(r)return r;let o=new p(e);n.set(s,o);try{o.start()}catch(i){throw n.delete(s),o.stop(),i}return o}function Re(t,e){let s=new p(O(e),t,!1);return s.start({listen:!1}),s}var p=class{constructor(e,s=D.createServer((r,o)=>{o.writeHead(404),o.end("VCP site edit bridge server")}),n=!0){this.options=e,this.server=s,this.closeHttpServerOnStop=n,this.runtime=null,this.routes=[],this.stopWatching=null,this.stopWebSocketServer=null,this.pendingChangedFiles=new Set,this.fileChangeTimer=null,this.isStarted=!1}start(e={}){if(this.isStarted)return;this.refreshRoutes(),this.runtime=W({siteId:this.options.siteId,snapshotId:this.options.snapshotId,projectRoot:this.options.projectRoot,routes:this.routes});let s=X({server:this.server,path:this.options.path,sessionKey:this.options.sessionKey,host:this.options.host,allowedOrigins:this.options.allowedOrigins,engine:this.createBridgeEngine()});this.stopWebSocketServer=s.stop,this.stopWatching=new L({projectRoot:this.options.projectRoot,watch_dirs:this.options.runtime.watch_dirs,debounce_ms:this.options.runtime.watch_debounce_ms??x,max_dirs:this.options.runtime.watch_max_dirs,max_files:this.options.runtime.watch_max_files,max_depth:this.options.runtime.watch_max_depth,max_startup_ms:this.options.runtime.watch_max_startup_ms,on_change:n=>this.handleFileChanged(n),on_batch_change:n=>this.handleFilesChanged(n)}).start(),e.listen!==!1&&this.server.listen(this.options.port,this.options.host),this.isStarted=!0}getHealth(){let e=!!this.runtime;return{ok:this.isStarted&&e,bridgePath:this.options.path,runtimeReady:e,routesIndexed:this.routes.length,sessionRequired:!!this.options.sessionKey}}stop(){this.stopWatching?.(),this.stopWatching=null,this.flushChangedFiles(),this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.stopWebSocketServer?.(),this.stopWebSocketServer=null,this.runtime?.dispose(),this.runtime=null,this.closeHttpServerOnStop&&this.server.close();let e=T(),s=K(this.options);e.get(s)===this&&e.delete(s),this.isStarted=!1}createBridgeEngine(){return{getDocument:async e=>{let{runtime:s,resolvedRouteId:n}=await this.activateRoute(e);return s.getDocument(n)},getObjectSummary:async(e,s)=>this.getRuntime().getObjectSummary(e,s?this.resolveRouteId(s):void 0),getObjectEditContext:async(e,s)=>this.getRuntime().getObjectEditContext(e,s?this.resolveRouteId(s):void 0),getClassNameSource:async(e,s)=>{let n=this.getRuntime(),r=s?this.resolveRouteId(s):void 0;return await n.refreshSourceForKey(e,r),n.getClassNameSource(e,r)},getSourceTree:async()=>this.getRuntime().getSourceTree(),getSourceFile:async e=>this.getRuntime().getSourceFile(e),resolveSourceLocation:async e=>this.getRuntime().resolveSourceLocation({key:e.key,routeId:e.routeId?this.resolveRouteId(e.routeId):void 0}),execute:async e=>this.getRuntime().executeOperation(this.normalizeRequest(e)),undo:async()=>this.getRuntime().undo(),redo:async()=>this.getRuntime().redo(),subscribe:e=>this.getRuntime().subscribe(e)}}async activateRoute(e){let s=this.getRuntime(),n=this.resolveRouteId(e);return await s.setActiveRoute(n),{runtime:s,resolvedRouteId:n}}normalizeRequest(e){return e.target.kind!=="route"?e.routeId?{...e,routeId:this.resolveRouteId(e.routeId)}:e:{...e,target:{...e.target,routeId:this.resolveRouteId(e.target.routeId)}}}handleFileChanged(e){this.handleFilesChanged([e])}handleFilesChanged(e){for(let n of e)this.pendingChangedFiles.add(n);this.fileChangeTimer&&clearTimeout(this.fileChangeTimer);let s=this.options.runtime.watch_debounce_ms??x;this.fileChangeTimer=setTimeout(()=>{this.fileChangeTimer=null,this.flushChangedFiles()},s),this.fileChangeTimer?.unref?.()}flushChangedFiles(){if(this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.pendingChangedFiles.size===0)return;let e=[...this.pendingChangedFiles].sort((s,n)=>s.localeCompare(n));this.pendingChangedFiles.clear(),this.refreshRoutes();for(let s of e)this.runtime?.notifyFileChanged(s)}refreshRoutes(){let e=this.options.runtime.routes?.length?this.options.runtime.routes:U(this.options.projectRoot),s=new Map(this.routes.map(r=>[r.routeId,r.entryFile])),n=new Set(e.map(r=>r.routeId));this.routes=e;for(let r of e)s.get(r.routeId)!==r.entryFile&&this.runtime?.registerRoute(r.routeId,r.entryFile);for(let r of s.keys())n.has(r)||this.runtime?.unregisterRoute(r)}resolveRouteId(e){let s=A(e,this.routes,this.options.basePath);if(!s)throw new Error(`Route is not indexed by the preview bridge: ${e}`);return s}getRuntime(){if(!this.runtime)throw new Error("VCP site edit preview bridge runtime is not ready");return this.runtime}};function O(t){return{siteId:t.siteId,snapshotId:t.snapshotId,projectRoot:P.resolve(t.projectRoot??process.cwd()),basePath:t.basePath??"",host:t.host??z,port:t.port??V,path:t.path??G,sessionKey:t.sessionKey??oe(),allowedOrigins:t.allowedOrigins??[],runtime:t.runtime??{}}}function T(){let t=globalThis;return t[_]??=new Map,t[_]}function K(t){return`${t.projectRoot}:${t.host}:${t.port}:${t.path}`}function X(t){let e=new J({maxPayload:Q,noServer:!0}),s=new WeakSet,n=o=>{new l({socket:new y(o),engine:t.engine,validateOperationRequest:i=>$.safeParse(i).success})},r=(o,i,a)=>{if(!Z(o,t))return;let u=ee(o,t);if(!u){i.write(`HTTP/1.1 401 Unauthorized\r
1
+ import Y from"node:http";import{createHash as U,randomBytes as X,timingSafeEqual as Z}from"node:crypto";import C from"node:path";import{WebSocket as P,WebSocketServer as ee}from"ws";import{createPreviewSiteEditEngineRuntime as te,openFileHistoryStore as se}from"@vcp-dev/site-edit-engine";import{resolveNextAppRouteId as ne,scanNextAppRouterRoutes as ie}from"@vcp-dev/site-edit-engine/next-app-router";import{SiteEditSourceWatcher as oe}from"@vcp-dev/site-edit-engine/source-watcher";function I(t){let e=new Map,s=[],n=new Map,i=[],o=t.pageComposition.status==="supported"?t.pageComposition.placementUnitKeys:[],r=new Map(o.map((a,u)=>[a,u])),p=t.pageComposition.status==="supported"?{status:"supported",containerKey:t.pageComposition.containerKey,hasBusinessMembers:t.pageComposition.hasBusinessMembers,placementUnitKeys:t.pageComposition.placementUnitKeys,placements:t.pageComposition.placements.map(a=>({placementKey:a.placementKey,placementUnitKey:a.placementUnitKey,relation:a.relation}))}:{status:"unsupported",reason:t.pageComposition.reason,diagnostics:t.pageComposition.diagnostics},h=a=>{let u=e.get(a);if(typeof u=="number")return u;let l=s.length;return s.push(a),e.set(a,l),l},y=a=>{let u=null;for(let l of z(a)){let v=`${u??"root"}\0${l}`,_=n.get(v);if(typeof _=="number"){u=_;continue}let T=i.length;i.push([u,h(l)]),n.set(v,T),u=T}if(u===null)throw new Error("Document key must not be empty");return u};return{encoding:"segment-trie-v1",siteId:t.siteId,snapshotId:t.snapshotId,routeId:t.routeId,version:t.version,pageComposition:p,tokens:s,nodes:i,entries:t.entries.map(a=>{let u=a.pageCompositionUnitKey?r.get(a.pageCompositionUnitKey)??-1:-1;if(a.pageCompositionUnitKey&&u<0)throw new Error(`Render entry ${a.key} references unknown page composition unit ${a.pageCompositionUnitKey}`);return[y(a.key),W(a.operationSummary),...u>=0?[u]:[]]})}}function W(t){return(t.canUpdateText?1:0)|(t.canInsertChild?2:0)|(t.canMove?4:0)|(t.canRemove?8:0)}function z(t){if(!t)return[];let e=[],s=0,n=B(t,s);if(n<0)return e.push(t),e;for(e.push(t.slice(0,n)),s=n;s<t.length;){if(n=B(t,s+(t.startsWith("::",s)?2:1)),n<0){e.push(t.slice(s));break}e.push(t.slice(s,n)),s=n}return e}function B(t,e){let s=t.indexOf("/",e),n=t.indexOf("::",e);return s<0?n:n<0?s:Math.min(s,n)}var d=class t{constructor(){}static parseMessage(e,s={}){let n=JSON.parse(e);if(!t.isMessage(n,s))throw new Error("Invalid site edit bridge message");return n}static stringifyMessage(e){return JSON.stringify(e)}static extractRequestInfo(e){if(typeof e=="object"&&e!==null&&"payload"in e&&typeof e.payload=="object"&&e.payload!==null){let s=e.payload,n=typeof s.id=="string"?s.id:"unknown",i=t.isRequestKind(s.kind)?s.kind:"get-document";return{id:n,kind:i}}return{id:"unknown",kind:"get-document"}}static isRequestKind(e){return e==="initialize-session"||e==="get-document"||e==="get-history-status"||e==="get-style-context"||e==="get-object-edit-context"||e==="get-class-name-source"||e==="get-source-tree"||e==="get-source-file"||e==="resolve-source-location"||e==="execute"||e==="undo"||e==="redo"}static isMessage(e,s={}){return!c(e)||typeof e.type!="string"?!1:e.type==="request"?t.isRequestPayload(e.payload,s):e.type==="response"||e.type==="error"?c(e.payload)&&typeof e.payload.id=="string":e.type==="event"?c(e.payload)&&c(e.payload.event):e.type==="auth"?c(e.payload)&&typeof e.payload.key=="string":e.type==="auth-ok"?c(e.payload)&&e.payload.ok===!0:!1}static isRequestPayload(e,s={}){if(!c(e)||typeof e.id!="string"||!t.isRequestKind(e.kind))return!1;switch(e.kind){case"initialize-session":return typeof e.routeId=="string"&&(e.mutationContext===void 0||R(e.mutationContext));case"get-document":case"get-style-context":return typeof e.routeId=="string";case"get-object-edit-context":case"get-class-name-source":case"resolve-source-location":return typeof e.key=="string"&&(e.routeId===void 0||typeof e.routeId=="string");case"get-history-status":return e.mutationContext===void 0;case"get-source-tree":return!0;case"get-source-file":return typeof e.path=="string";case"execute":return N(e.request)&&(e.request.mutationContext===void 0||R(e.request.mutationContext))&&(s.validateOperationRequest?.(e.request)??!0);case"undo":case"redo":return e.mutationContext===void 0||R(e.mutationContext)}}},g=class{constructor(e){this.options=e,this.messageQueue=new S,this.eventDispatcher=new w(s=>{this.sendJson(s)}),this.unsubscribe=e.engine.subscribe(s=>{this.eventDispatcher.push(s)}),e.socket.onmessage=s=>{try{this.messageQueue.enqueue(()=>this.handleRawMessage(s.data))}catch(n){this.sendJson({type:"error",payload:{id:"unknown",kind:"get-document",message:n instanceof Error?n.message:"Bridge queue overflow"}}),e.socket.close(1013,"bridge busy")}},e.socket.onclose=()=>{this.unsubscribe()}}async handleMessage(e){try{let s=e;if(s.type!=="request")return;let n=s.payload,{id:i}=n;switch(n.kind){case"initialize-session":{let o=await this.options.engine.initializeSession({routeId:n.routeId,...n.mutationContext?{mutationContext:n.mutationContext}:{}});this.sendJson({type:"response",payload:{id:i,kind:n.kind,result:{ok:!0,data:{document:I(o.data.document),historyStatus:o.data.historyStatus}}}});return}case"get-document":this.sendJson({type:"response",payload:{id:i,kind:n.kind,result:{ok:!0,data:I((await this.options.engine.getDocument(n.routeId)).data)}}});return;case"get-history-status":this.sendJson({type:"response",payload:{id:i,kind:n.kind,result:await this.options.engine.getHistoryStatus()}});return;case"get-style-context":this.sendJson({type:"response",payload:{id:i,kind:n.kind,result:await this.options.engine.getStyleContext(n.routeId)}});return;case"get-object-edit-context":this.sendJson({type:"response",payload:{id:i,kind:n.kind,result:await this.options.engine.getObjectEditContext(n.key,n.routeId)}});return;case"get-class-name-source":this.sendJson({type:"response",payload:{id:i,kind:n.kind,result:await this.options.engine.getClassNameSource(n.key,n.routeId)}});return;case"get-source-tree":this.sendJson({type:"response",payload:{id:i,kind:n.kind,result:await this.options.engine.getSourceTree()}});return;case"get-source-file":this.sendJson({type:"response",payload:{id:i,kind:n.kind,result:await this.options.engine.getSourceFile({path:n.path})}});return;case"resolve-source-location":this.sendJson({type:"response",payload:{id:i,kind:n.kind,result:await this.options.engine.resolveSourceLocation({key:n.key,routeId:n.routeId})}});return;case"execute":{await this.handleBufferedOperation(i,n.kind,()=>this.options.engine.execute(n.request));return}case"undo":await this.handleBufferedOperation(i,n.kind,()=>this.options.engine.undo());return;case"redo":await this.handleBufferedOperation(i,n.kind,()=>this.options.engine.redo());return}}catch(s){let n=d.extractRequestInfo(e);this.sendJson({type:"error",payload:{...n,message:O(s),code:H(s)}})}}async handleRawMessage(e){try{await this.handleMessage(d.parseMessage(e,{validateOperationRequest:this.options.validateOperationRequest}))}catch(s){let n=s instanceof Error?s.message:"Invalid websocket message",i=G(e);this.sendJson({type:"error",payload:{...i,message:n}})}}sendJson(e){this.options.socket.send(d.stringifyMessage(e))}async handleBufferedOperation(e,s,n){this.eventDispatcher.beginBuffering();try{let i=await n();this.sendJson({type:"response",payload:{id:e,kind:s,result:i}}),this.eventDispatcher.flush()}catch(i){this.sendJson({type:"error",payload:{id:e,kind:s,message:O(i),code:H(i)}}),this.eventDispatcher.flush()}}},w=class{constructor(e){this.send=e,this.bufferedEvents=null}push(e){let s={type:"event",payload:{event:e}};if(this.bufferedEvents){this.bufferedEvents.push(s);return}this.send(s)}beginBuffering(){this.bufferedEvents=[]}flush(){let e=this.bufferedEvents??[];this.bufferedEvents=null,e.forEach(s=>{this.send(s)})}},S=class{constructor(e=64){this.maxQueued=e,this.queue=Promise.resolve(),this.queued=0}enqueue(e){if(this.queued>=this.maxQueued)throw new Error("Bridge request queue is full");this.queued+=1;let s=async()=>{this.queued-=1,await e()},n=this.queue.then(s,s);return this.queue=n.then(()=>{},()=>{}),n}};function c(t){return typeof t=="object"&&t!==null}function N(t){return c(t)&&typeof t.id=="string"&&typeof t.siteId=="string"&&typeof t.baseSnapshotId=="string"&&(t.routeId===void 0||typeof t.routeId=="string")&&typeof t.kind=="string"&&c(t.target)&&typeof t.documentVersion=="number"&&Number.isInteger(t.documentVersion)&&t.documentVersion>=0&&c(t.params)}function R(t){return Q(t)}function H(t){if(!t||typeof t!="object")return;let e=t.code;return typeof e=="string"&&e.length>0?e:void 0}function O(t){if(t instanceof Error)return t.message;if(!t||typeof t!="object")return"Unknown websocket operation failure";let e=t.message;return typeof e=="string"&&e.length>0?e:"Unknown websocket operation failure"}function Q(t){return c(t)?typeof t.editingSessionId=="string"&&t.editingSessionId.length>0&&typeof t.lockId=="string"&&t.lockId.length>0&&typeof t.ownerId=="string"&&t.ownerId.length>0&&typeof t.leaseAssertion=="string"&&t.leaseAssertion.length>0:!1}function G(t){try{return d.extractRequestInfo(JSON.parse(t))}catch{return{id:"unknown",kind:"get-document"}}}var f=class{constructor(){this.tail=Promise.resolve()}run(e){let s=this.tail.then(e,e);return this.tail=s.then(()=>{},()=>{}),s}};import{SiteEditOperationRequestSchema as re}from"@vcp-dev/contracts";var ae="127.0.0.1",ue=30218,ce="/__vcp/site-edit/bridge",q=6*1024*1024,de=q*2,he=3e3,M=50,V="__VCP_SITE_EDIT_BRIDGE_SERVERS__";function Ke(t){let e=j(t),s=J(e),n=A(),i=n.get(s);if(i)return i;let o=new m(e);n.set(s,o);try{o.start()}catch(r){throw n.delete(s),o.stop(),r}return o}function De(t,e,s={}){let n=new m(j(e),t,!1,s);return n.start({listen:!1}),n}var m=class{constructor(e,s=Y.createServer((o,r)=>{r.writeHead(404),r.end("VCP site edit bridge server")}),n=!0,i={}){this.options=e,this.server=s,this.closeHttpServerOnStop=n,this.dependencies=i,this.runtime=null,this.activeHistorySessionId=null,this.historySessionTransition=Promise.resolve(),this.mutationQueue=new f,this.eventListeners=new Set,this.routes=[],this.stopWatching=null,this.stopWebSocketServer=null,this.pendingChangedFiles=new Set,this.fileChangeTimer=null,this.isStarted=!1,this.runtimeVersionFloor=0,this.activeHistoryStore=null,this.versionPersistence=Promise.resolve()}start(e={}){if(this.isStarted)return;this.refreshRoutes(),this.isStarted=!0,this.createRuntime();let s=le({server:this.server,path:this.options.path,sessionKey:this.options.sessionKey,host:this.options.host,allowedOrigins:this.options.allowedOrigins,engine:this.createBridgeEngine()});this.stopWebSocketServer=s.stop,this.stopWatching=new oe({projectRoot:this.options.projectRoot,watch_dirs:this.options.runtime.watch_dirs,debounce_ms:this.options.runtime.watch_debounce_ms??M,max_dirs:this.options.runtime.watch_max_dirs,max_files:this.options.runtime.watch_max_files,max_depth:this.options.runtime.watch_max_depth,max_startup_ms:this.options.runtime.watch_max_startup_ms,on_change:n=>this.handleFileChanged(n),on_batch_change:n=>this.handleFilesChanged(n)}).start(),e.listen!==!1&&this.server.listen(this.options.port,this.options.host)}getHealth(){let e=!!this.runtime;return{ok:this.isStarted&&e,bridgePath:this.options.path,runtimeReady:e,routesIndexed:this.routes.length,sessionRequired:!!this.options.sessionKey}}stop(){this.stopWatching?.(),this.stopWatching=null,this.flushChangedFiles(),this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.stopWebSocketServer?.(),this.stopWebSocketServer=null,this.runtime?.dispose(),this.runtime=null,this.closeHttpServerOnStop&&this.server.close();let e=A(),s=J(this.options);e.get(s)===this&&e.delete(s),this.isStarted=!1}createRuntime(e,s=null){let n=this.runtime;this.activeHistoryStore=e??null;let i=(this.dependencies.createRuntime??te)({...this.options.tenantId?{tenantId:this.options.tenantId}:{},siteId:this.options.siteId,snapshotId:this.options.snapshotId,projectRoot:this.options.projectRoot,routes:this.routes,onEvent:o=>{"version"in o&&typeof o.version=="number"&&this.recordRuntimeVersion(o.version);for(let r of this.eventListeners)r(o)},...e?{historyStore:e}:{},initialDocumentVersion:this.runtimeVersionFloor});this.runtime=i,this.activeHistorySessionId=s,n?.dispose()}createBridgeEngine(){return{initializeSession:e=>this.mutationQueue.run(async()=>{let s=await this.bindHistorySession(this.options.snapshotId);if(!s.ok)throw new Error(`${s.code}: ${s.message}`);let{runtime:n,resolvedRouteId:i}=await this.activateRoute(e.routeId),o=n.getDocument(i).data;await this.recordRuntimeVersion(o.version);let r=(await n.getHistoryStatus()).data;return{ok:!0,data:{document:o,historyStatus:r}}}),getDocument:async e=>{let{runtime:s,resolvedRouteId:n}=await this.activateRoute(e),i=s.getDocument(n);return await this.recordRuntimeVersion(i.data.version),i},getHistoryStatus:async()=>this.getRuntime().getHistoryStatus(),getStyleContext:async e=>{let{runtime:s,resolvedRouteId:n}=await this.activateRoute(e);return s.getStyleContext(n)},getObjectEditContext:async(e,s)=>this.getRuntime().getObjectEditContext(e,s?this.resolveRouteId(s):void 0),getClassNameSource:async(e,s)=>{let n=this.getRuntime(),i=s?this.resolveRouteId(s):void 0;return n.getClassNameSource(e,i)},getSourceTree:async()=>this.getRuntime().getSourceTree(),getSourceFile:async e=>this.getRuntime().getSourceFile(e),resolveSourceLocation:async e=>this.getRuntime().resolveSourceLocation({key:e.key,routeId:e.routeId?this.resolveRouteId(e.routeId):void 0}),execute:(e,s)=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)return k(n,e);let i=await this.getRuntime().executeOperation(this.normalizeRequest(e));return i.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(i.data.resultVersion),i}),undo:()=>this.mutationQueue.run(async()=>{let e=await this.bindHistorySession(this.options.snapshotId);if(!e.ok)return k(e);let s=await this.getRuntime().undo();return s.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(s.data.resultVersion),s}),redo:()=>this.mutationQueue.run(async()=>{let e=await this.bindHistorySession(this.options.snapshotId);if(!e.ok)return k(e);let s=await this.getRuntime().redo();return s.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(s.data.resultVersion),s}),subscribe:e=>(this.eventListeners.add(e),()=>this.eventListeners.delete(e))}}async bindHistorySession(e){if(!this.options.historyRoot)return{ok:!1,code:"RECOVERY_PERSIST_FAILED",message:"Durable edit history is not configured."};if(this.activeHistorySessionId===e)return{ok:!0};let s=this.historySessionTransition.then(async()=>{if(this.activeHistorySessionId===e)return;let n=await(this.dependencies.openHistoryStore??se)(pe({root:this.options.historyRoot,siteId:this.options.siteId,snapshotId:this.options.snapshotId,editingSessionId:e}));if(!this.isStarted)return;let i=n.readDocumentVersion?await n.readDocumentVersion():0;this.runtimeVersionFloor=Math.max(this.runtimeVersionFloor,i),this.createRuntime(n,e)});this.historySessionTransition=s.catch(()=>{});try{return await s,{ok:!0}}catch{return{ok:!1,code:"RECOVERY_PERSIST_FAILED",message:"Durable edit history could not be opened."}}}recordRuntimeVersion(e){this.runtimeVersionFloor=Math.max(this.runtimeVersionFloor,e);let s=this.activeHistoryStore;return s?.writeDocumentVersion?(this.versionPersistence=this.versionPersistence.then(()=>s.writeDocumentVersion(this.runtimeVersionFloor)).catch(()=>{}),this.versionPersistence):Promise.resolve()}async activateRoute(e){let s=this.getRuntime(),n=this.resolveRouteId(e);return await s.setActiveRoute(n),{runtime:s,resolvedRouteId:n}}normalizeRequest(e){return e.target.kind!=="route"?e.routeId?{...e,routeId:this.resolveRouteId(e.routeId)}:e:{...e,target:{...e.target,routeId:this.resolveRouteId(e.target.routeId)}}}handleFileChanged(e){this.handleFilesChanged([e])}handleFilesChanged(e){for(let n of e)this.pendingChangedFiles.add(n);this.fileChangeTimer&&clearTimeout(this.fileChangeTimer);let s=this.options.runtime.watch_debounce_ms??M;this.fileChangeTimer=setTimeout(()=>{this.fileChangeTimer=null,this.flushChangedFiles()},s),this.fileChangeTimer?.unref?.()}flushChangedFiles(){if(this.fileChangeTimer&&(clearTimeout(this.fileChangeTimer),this.fileChangeTimer=null),this.pendingChangedFiles.size===0)return;let e=[...this.pendingChangedFiles].sort((s,n)=>s.localeCompare(n));this.pendingChangedFiles.clear(),this.refreshRoutes();for(let s of e)this.runtime?.notifyFileChanged(s)}refreshRoutes(){let e=this.options.runtime.routes?.length?this.options.runtime.routes:ie(this.options.projectRoot),s=new Map(this.routes.map(i=>[i.routeId,i.entryFile])),n=new Set(e.map(i=>i.routeId));this.routes=e;for(let i of e)s.get(i.routeId)!==i.entryFile&&this.runtime?.registerRoute(i.routeId,i.entryFile);for(let i of s.keys())n.has(i)||this.runtime?.unregisterRoute(i)}resolveRouteId(e){let s=ne(e,this.routes,this.options.basePath);if(!s)throw new Error(`Route is not indexed by the preview bridge: ${e}`);return s}getRuntime(){if(!this.runtime)throw new Error("VCP site edit preview bridge runtime is not ready");return this.runtime}};function j(t){return{tenantId:t.tenantId,siteId:t.siteId,snapshotId:t.snapshotId,projectRoot:C.resolve(t.projectRoot??process.cwd()),basePath:t.basePath??"",host:t.host??ae,port:t.port??ue,path:t.path??ce,sessionKey:t.sessionKey??we(),historyRoot:t.historyRoot,allowedOrigins:t.allowedOrigins??[],runtime:t.runtime??{}}}function pe(t){return C.join(C.resolve(t.root),K(t.siteId),K(t.snapshotId),U("sha256").update(t.editingSessionId).digest("hex").slice(0,32))}function K(t){return/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(t)&&t!=="."&&t!==".."?t:U("sha256").update(t).digest("hex").slice(0,32)}function A(){let t=globalThis;return t[V]??=new Map,t[V]}function J(t){return`${t.projectRoot}:${t.host}:${t.port}:${t.path}:${t.historyRoot??""}`}function k(t,e){return{ok:!1,error:{code:t.code,message:t.message},data:{requestId:e?.id??"mutation-context",kind:e?.kind??"update-text",target:e?.target??{kind:"node",key:""},resultVersion:e?.documentVersion??0}}}function le(t){let e=new ee({maxPayload:q,noServer:!0}),s=new WeakSet,n=o=>{new g({socket:new b(o),engine:t.engine,validateOperationRequest:r=>re.safeParse(r).success})},i=(o,r,p)=>{if(!ge(o,t))return;let h=fe(o,t);if(!h){r.write(`HTTP/1.1 401 Unauthorized\r
2
2
  Connection: close\r
3
3
  \r
4
- `),i.destroy();return}u.authenticatedByHeader&&s.add(o),e.handleUpgrade(o,i,a,h=>{e.emit("connection",h,o)})};return t.server.on("upgrade",r),e.on("connection",(o,i)=>{if(s.has(i)){n(o);return}te(o,t.sessionKey,()=>{n(o)})}),e.stop=()=>{t.server.off("upgrade",r);for(let o of e.clients)o.close(1001,"bridge stopped");e.close()},e}var y=class{constructor(e){this.socket=e,this.onmessage=null,this.onclose=null,e.on("message",s=>{this.onmessage?.({data:s.toString()})}),e.on("close",()=>{this.onclose?.()})}send(e){this.socket.readyState===C.OPEN&&this.socket.send(e)}close(e,s){this.socket.close(e,s)}};function Z(t,e){return new URL(t.url??"/","http://localhost").pathname===e.path}function ee(t,e){if(new URL(t.url??"/","http://localhost").searchParams.has("vcp_bridge_key"))return null;let n=m(t.headers["x-vcp-site-edit-key"]),r=B(n,e.sessionKey),o=m(t.headers.host),i=m(t.headers.origin);return o&&!se(o,e.host,i)?null:i?re(i,o,e.allowedOrigins)?{authenticatedByHeader:r}:null:r?{authenticatedByHeader:!0}:null}function te(t,e,s){let n=setTimeout(()=>{t.close(1008,"bridge authentication timeout")},Y),r,o=()=>{clearTimeout(n),t.off("message",r),t.off("close",o)},i=()=>{o(),t.close(1008,"bridge authentication failed")};r=a=>{try{let u=d.parseMessage(a.toString());if(u.type!=="auth"||!B(u.payload.key,e)){i();return}o(),t.readyState===C.OPEN&&t.send(d.stringifyMessage({type:"auth-ok",payload:{ok:!0}})),s()}catch{i()}},t.on("message",r),t.on("close",o)}function B(t,e){if(!t)return!1;let s=Buffer.from(t),n=Buffer.from(e);return s.length===n.length&&H(s,n)}function se(t,e,s){let n=v(t),r=v(e);return n===r||E(r)&&E(n)&&(!s||I(s,t))||ne(r)&&(!s||I(s,t))}function ne(t){return t==="0.0.0.0"||t==="::"}function E(t){return t==="localhost"||t==="127.0.0.1"||t==="::1"}function re(t,e,s){return s.includes(t)||(e?I(t,e):!1)}function I(t,e){try{return new URL(t).host===q(e)}catch{return!1}}function v(t){let e=q(t);return e.startsWith("[")?e.slice(1,e.indexOf("]")):e.split(":").length-1===1?e.split(":",1)[0]??"":e}function q(t){return t.trim().toLowerCase()}function m(t){return Array.isArray(t)?t[0]:t}function oe(){return N(32).toString("base64url")}export{Q as SITE_EDIT_BRIDGE_MAX_PAYLOAD_BYTES,p as SiteEditBridgeServer,Re as attachSiteEditBridgeServer,X as attachSiteEditWebSocketServer,Ie as ensureSiteEditBridgeServerStarted};
4
+ `),r.destroy();return}h.authenticatedByHeader&&s.add(o),e.handleUpgrade(o,r,p,y=>{e.emit("connection",y,o)})};return t.server.on("upgrade",i),e.on("connection",(o,r)=>{if(s.has(r)){n(o);return}me(o,t.sessionKey,()=>{n(o)})}),e.stop=()=>{t.server.off("upgrade",i);for(let o of e.clients)o.close(1001,"bridge stopped");e.close()},e}var b=class{constructor(e){this.socket=e,this.onmessage=null,this.onclose=null,e.on("error",()=>{}),e.on("message",s=>{this.onmessage?.({data:s.toString()})}),e.on("close",()=>{this.onclose?.()})}send(e){if(this.socket.readyState===P.OPEN){if(this.socket.bufferedAmount+Buffer.byteLength(e,"utf8")>de){this.socket.close(1013,"bridge backpressure");return}this.socket.send(e)}}close(e,s){this.socket.close(e,s)}};function ge(t,e){return new URL(t.url??"/","http://localhost").pathname===e.path}function fe(t,e){if(new URL(t.url??"/","http://localhost").searchParams.has("vcp_bridge_key"))return null;let n=x(t.headers["x-vcp-site-edit-key"]),i=L(n,e.sessionKey),o=x(t.headers.host),r=x(t.headers.origin);return o&&!ye(o,e.host,r)?null:r?Re(r,o,e.allowedOrigins)?{authenticatedByHeader:i}:null:i?{authenticatedByHeader:!0}:null}function me(t,e,s){let n=setTimeout(()=>{t.close(1008,"bridge authentication timeout")},he),i,o=()=>{clearTimeout(n),t.off("message",i),t.off("close",o)},r=()=>{o(),t.close(1008,"bridge authentication failed")};i=p=>{try{let h=d.parseMessage(p.toString());if(h.type!=="auth"||!L(h.payload.key,e)){r();return}o(),t.readyState===P.OPEN&&t.send(d.stringifyMessage({type:"auth-ok",payload:{ok:!0}})),s()}catch{r()}},t.on("message",i),t.on("close",o)}function L(t,e){if(!t)return!1;let s=Buffer.from(t),n=Buffer.from(e);return s.length===n.length&&Z(s,n)}function ye(t,e,s){let n=F(t),i=F(e);return n===i||D(i)&&D(n)&&(!s||E(s,t))||Ie(i)&&(!s||E(s,t))}function Ie(t){return t==="0.0.0.0"||t==="::"}function D(t){return t==="localhost"||t==="127.0.0.1"||t==="::1"}function Re(t,e,s){return s.includes(t)||(e?E(t,e):!1)}function E(t,e){try{return new URL(t).host===$(e)}catch{return!1}}function F(t){let e=$(t);return e.startsWith("[")?e.slice(1,e.indexOf("]")):e.split(":").length-1===1?e.split(":",1)[0]??"":e}function $(t){return t.trim().toLowerCase()}function x(t){return Array.isArray(t)?t[0]:t}function we(){return X(32).toString("base64url")}export{de as SITE_EDIT_BRIDGE_MAX_BUFFERED_BYTES,q as SITE_EDIT_BRIDGE_MAX_PAYLOAD_BYTES,m as SiteEditBridgeServer,De as attachSiteEditBridgeServer,le as attachSiteEditWebSocketServer,Ke as ensureSiteEditBridgeServerStarted};
@@ -1,4 +1,5 @@
1
- import type { SiteEditCommandSuccess, SiteEditEvent, SiteEditObjectEditContext, SiteEditObjectSummary, SiteEditGetClassNameSourceCommandResult, SiteEditOperationCommandResult, SiteEditOperationRequest, SiteEditRenderDocument, SiteEditSourceFileResponse, SiteEditSourceLocation, SiteEditSourceTreeResponse } from "@vcp-dev/contracts";
1
+ import type { SiteEditCommandSuccess, SiteEditClassNameSourceStyleContext, SiteEditEvent, SiteEditObjectEditContext, SiteEditGetClassNameSourceCommandResult, SiteEditOperationCommandResult, SiteEditOperationRequest, SiteEditRenderDocument, SiteEditSourceFileResponse, SiteEditSourceLocation, SiteEditSourceTreeResponse } from "@vcp-dev/contracts";
2
+ import type { SiteEditHistoryStatus } from "./protocol.js";
2
3
  export declare const VCP_WINDOW_MESSAGE_SOURCE = "vcp-window-message";
3
4
  export declare const VCP_WINDOW_MESSAGE_VERSION = 1;
4
5
  export type VcpWindowMessageEnvelope<TNamespace extends string = string, TChannel extends string = string, TBody = unknown> = {
@@ -210,15 +211,35 @@ export type SiteEditFrameRuntimeRequestBody = {
210
211
  } | {
211
212
  type: "request";
212
213
  id: string;
213
- kind: "bridge-get-document" | "bridge-ensure-document-version";
214
+ kind: "bridge-initialize-session";
215
+ payload: {
216
+ routeId: string;
217
+ };
218
+ } | {
219
+ type: "request";
220
+ id: string;
221
+ kind: "bridge-get-document" | "bridge-ensure-document-version" | "bridge-acknowledge-document-version";
214
222
  payload: {
215
223
  routeId: string;
216
224
  minVersion?: number;
225
+ version?: number;
226
+ };
227
+ } | {
228
+ type: "request";
229
+ id: string;
230
+ kind: "bridge-get-history-status";
231
+ payload: Record<string, never>;
232
+ } | {
233
+ type: "request";
234
+ id: string;
235
+ kind: "bridge-get-style-context";
236
+ payload: {
237
+ routeId: string;
217
238
  };
218
239
  } | {
219
240
  type: "request";
220
241
  id: string;
221
- kind: "bridge-get-object-summary" | "bridge-get-object-edit-context" | "bridge-get-class-name-source";
242
+ kind: "bridge-get-object-edit-context" | "bridge-get-class-name-source";
222
243
  payload: {
223
244
  key: string;
224
245
  routeId?: string;
@@ -274,6 +295,7 @@ export type SiteEditFrameRuntimeHit = {
274
295
  componentName?: string;
275
296
  sourceFile?: string;
276
297
  parentKey?: string;
298
+ pageCompositionUnitKey?: string;
277
299
  textPreview?: string;
278
300
  dataset?: Record<string, string>;
279
301
  attributes?: Record<string, string>;
@@ -298,7 +320,10 @@ export type SiteEditFrameRuntimeResponseBody = {
298
320
  type: "response";
299
321
  id: string;
300
322
  ok: boolean;
301
- result?: SiteEditFrameRuntimeStarted | SiteEditFrameRuntimeHit | SiteEditFrameRuntimeNodePatchAck | SiteEditFrameRuntimeHit[] | SiteEditFrameRuntimeDropTarget | SiteEditFrameRuntimeScrollMetrics | SiteEditCommandSuccess<SiteEditRenderDocument> | SiteEditCommandSuccess<SiteEditObjectSummary> | SiteEditCommandSuccess<SiteEditObjectEditContext> | SiteEditGetClassNameSourceCommandResult | SiteEditCommandSuccess<SiteEditSourceTreeResponse> | SiteEditCommandSuccess<SiteEditSourceFileResponse> | SiteEditCommandSuccess<SiteEditSourceLocation> | SiteEditOperationCommandResult | (SiteEditFrameRuntimeScrollMetrics & {
323
+ result?: SiteEditFrameRuntimeStarted | SiteEditFrameRuntimeHit | SiteEditFrameRuntimeNodePatchAck | SiteEditFrameRuntimeHit[] | SiteEditFrameRuntimeDropTarget | SiteEditFrameRuntimeScrollMetrics | SiteEditCommandSuccess<SiteEditRenderDocument> | SiteEditCommandSuccess<{
324
+ document: SiteEditRenderDocument;
325
+ historyStatus: SiteEditHistoryStatus;
326
+ }> | SiteEditCommandSuccess<SiteEditHistoryStatus> | SiteEditCommandSuccess<SiteEditClassNameSourceStyleContext | null> | SiteEditCommandSuccess<SiteEditObjectEditContext> | SiteEditGetClassNameSourceCommandResult | SiteEditCommandSuccess<SiteEditSourceTreeResponse> | SiteEditCommandSuccess<SiteEditSourceFileResponse> | SiteEditCommandSuccess<SiteEditSourceLocation> | SiteEditOperationCommandResult | (SiteEditFrameRuntimeScrollMetrics & {
302
327
  scrolled: true;
303
328
  }) | {
304
329
  interactionEnabled: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vcp-dev/site-edit-bridge",
3
- "version": "0.4.9",
3
+ "version": "0.6.1",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -70,12 +70,12 @@
70
70
  },
71
71
  "scripts": {
72
72
  "build": "pnpm --dir ../contracts build && pnpm --dir ../site-edit-engine build && node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.build.json",
73
- "test": "vitest run --root ../.. packages/site-edit-bridge/src",
73
+ "test": "vitest run --root ../.. packages/site-edit-bridge/src --maxWorkers=2",
74
74
  "typecheck": "tsc -p tsconfig.json --noEmit"
75
75
  },
76
76
  "dependencies": {
77
- "@vcp-dev/contracts": "0.4.9",
78
- "@vcp-dev/site-edit-engine": "0.4.9",
77
+ "@vcp-dev/contracts": "0.6.1",
78
+ "@vcp-dev/site-edit-engine": "0.6.1",
79
79
  "@types/ws": "^8.18.1",
80
80
  "ws": "^8.20.1"
81
81
  },