@vcp-dev/site-edit-bridge 0.10.23 → 0.10.25
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.
- package/dist/auto-bootstrap.js +4 -4
- package/dist/client.d.ts +8 -0
- package/dist/client.js +1 -1
- package/dist/document-codec.d.ts +12 -0
- package/dist/document-codec.js +1 -1
- package/dist/engine-worker-client.js +20 -1
- package/dist/engine-worker.js +2 -2
- package/dist/index.js +7 -6
- package/dist/next-dev-server.js +4 -3
- package/dist/server.js +2 -2
- package/package.json +3 -3
package/dist/client.d.ts
CHANGED
|
@@ -86,6 +86,14 @@ export declare class SiteEditWebSocketClient implements SiteEditBridgeClient {
|
|
|
86
86
|
};
|
|
87
87
|
parentKey?: string | null | undefined;
|
|
88
88
|
pageCompositionUnitKey?: string | undefined;
|
|
89
|
+
pageCompositionActionScope?: {
|
|
90
|
+
kind: "route-unit";
|
|
91
|
+
} | {
|
|
92
|
+
kind: "source-siblings";
|
|
93
|
+
siblingKeys: string[];
|
|
94
|
+
} | {
|
|
95
|
+
kind: "unavailable";
|
|
96
|
+
} | undefined;
|
|
89
97
|
dynamicDataSummary?: {
|
|
90
98
|
hasDynamicData: boolean;
|
|
91
99
|
sourceKinds: ("vcp-cms-collection" | "vcp-cms-form" | "vcp-cms-settings" | "vcp-cms-navigation" | "site-api-route" | "external-api" | "unknown-api")[];
|
package/dist/client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function T(n){if(!N(n)){if(n.encoding==="segment-trie-v1")throw new Error("Invalid compact render document");return n}let e=new Map,t=n.pageComposition.status==="supported"?n.pageComposition.placementUnitKeys:[],o=new Map(n.dynamicDataSummaries??[]),s=i=>{let r=e.get(i);if(r)return r;let c=n.nodes[i];if(!c)throw new Error(`Missing compact document node ${i}`);let[a,u]=c,d=n.tokens[u];if(typeof d!="string")throw new Error(`Missing compact document token ${u}`);let p=`${a===null?"":s(a)}${d}`;return e.set(i,p),p};return{siteId:n.siteId,snapshotId:n.snapshotId,composition:{...n.composition},routeId:n.composition.routeId,version:n.version,pageComposition:n.pageComposition,entries:n.entries.map((i,r)=>{let[c,a,u]=i,d=u===void 0?void 0:t[u];if(u!==void 0&&!d)throw new Error(`Missing page composition unit ${u}`);let p=n.entryParentNodeIds?.[r];return{key:s(c),...n.entryParentNodeIds?{parentKey:p===null?null:s(p)}:{},...d?{pageCompositionUnitKey:d}:{},operationSummary:v(a),...o.get(r)?{dynamicDataSummary:o.get(r)}:{}}})}}function v(n){return{canUpdateText:(n&1)!==0,canInsertChild:(n&2)!==0,canMove:(n&4)!==0,canRemove:(n&8)!==0}}function N(n){if(typeof n!="object"||n===null)return!1;let e=n;if(e.encoding!=="segment-trie-v1"||typeof e.siteId!="string"||typeof e.snapshotId!="string"||!K(e.composition)||typeof e.routeId!="string"||e.routeId!==e.composition.routeId||!Number.isInteger(e.version)||e.version<0||!x(e.pageComposition)||!Array.isArray(e.tokens)||!e.tokens.every(i=>typeof i=="string")||!Array.isArray(e.nodes)||!Array.isArray(e.entries))return!1;let t=e.nodes;if(!t.every((i,r)=>Array.isArray(i)&&i.length===2&&(i[0]===null||g(i[0])&&i[0]<r)&&g(i[1])&&i[1]<e.tokens.length)||!e.entries.every(i=>Array.isArray(i)&&(i.length===2||i.length===3)&&g(i[0])&&i[0]<t.length&&g(i[1])&&i[1]<=15&&(i.length===2||g(i[2]))))return!1;let o=e.entryParentNodeIds;if(o!==void 0&&(!Array.isArray(o)||o.length!==e.entries.length||!o.every(i=>i===null||g(i)&&i<t.length)))return!1;let s=e.dynamicDataSummaries;return s===void 0||Array.isArray(s)&&s.every(i=>Array.isArray(i)&&i.length===2&&g(i[0])&&i[0]<e.entries.length&&x(i[1]))}function x(n){return typeof n=="object"&&n!==null&&!Array.isArray(n)}function K(n){return!x(n)||typeof n.routeId!="string"?!1:n.effectiveTemplateId===void 0||typeof n.effectiveTemplateId=="string"}function g(n){return typeof n=="number"&&Number.isInteger(n)&&n>=0}var l=class n{constructor(){}static parseMessage(e,t={}){let o=JSON.parse(e);if(!n.isMessage(o,t))throw new Error("Invalid site edit bridge message");return o}static stringifyMessage(e){return JSON.stringify(e)}static extractRequestInfo(e){if(h(e)&&h(e.payload)){let t=typeof e.payload.id=="string"?e.payload.id:"unknown",o=this.isRequestKind(e.payload.kind)?e.payload.kind:"get-document";return{id:t,kind:o}}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,t={}){return!h(e)||typeof e.type!="string"?!1:e.type==="request"?B(e.payload,t):e.type==="response"||e.type==="error"?h(e.payload)&&typeof e.payload.id=="string":e.type==="event"?h(e.payload)&&h(e.payload.event):e.type==="auth"?h(e.payload)&&typeof e.payload.key=="string":e.type==="auth-ok"&&h(e.payload)&&e.payload.ok===!0}};function B(n,e){if(!h(n)||typeof n.id!="string"||!l.isRequestKind(n.kind))return!1;let t=w(n.composition);switch(n.kind){case"initialize-session":return t&&(n.mutationContext===void 0||b(n.mutationContext));case"get-document":case"get-style-context":return t;case"get-object-edit-context":return typeof n.key=="string"&&t&&j(n.selectionRef);case"get-class-name-source":case"resolve-source-location":return typeof n.key=="string"&&t;case"get-history-status":return t;case"get-source-tree":return t;case"get-source-file":return typeof n.path=="string"&&t;case"execute":return t&&F(n.request)&&(n.request.mutationContext===void 0||b(n.request.mutationContext))&&(e.validateOperationRequest?.(n.request)??!0);case"undo":case"redo":return t&&n.mutationContext===void 0||t&&b(n.mutationContext)}}function j(n){if(n===void 0)return!0;if(!h(n)||typeof n.key!="string"||n.key.length===0)return!1;let e=n.instanceIndex,t=n.instanceCount;return e!==void 0&&(typeof e!="number"||!Number.isInteger(e)||e<0)||t!==void 0&&(typeof t!="number"||!Number.isInteger(t)||t<=0)||typeof e=="number"&&typeof t=="number"&&e>=t?!1:Object.keys(n).every(o=>o==="key"||o==="instanceIndex"||o==="instanceCount")}function F(n){return h(n)&&typeof n.id=="string"&&typeof n.siteId=="string"&&typeof n.baseSnapshotId=="string"&&w(n.composition)&&typeof n.kind=="string"&&h(n.target)&&(n.target.kind!=="route"||w(n.target.composition))&&typeof n.documentVersion=="number"&&Number.isInteger(n.documentVersion)&&n.documentVersion>=0&&h(n.params)}function w(n){return h(n)&&typeof n.routeId=="string"&&n.routeId.length>0&&(n.templateId===void 0||typeof n.templateId=="string"&&n.templateId.length>0)&&Object.keys(n).every(e=>e==="routeId"||e==="templateId")}function h(n){return typeof n=="object"&&n!==null}function b(n){return h(n)&&typeof n.editingSessionId=="string"&&n.editingSessionId.length>0&&typeof n.lockId=="string"&&n.lockId.length>0&&typeof n.ownerId=="string"&&n.ownerId.length>0&&typeof n.leaseAssertion=="string"&&n.leaseAssertion.length>0}var P="vcp:designer:trace",U="designer_trace",R="__VCP_DESIGNER_TRACE__";var W=/(authorization|cookie|token|secret|providerTrace|headers?|sourceCode|rawSource|fullSource)/i;function $(n=globalThis){let e=n;try{if(e.localStorage?.getItem(P)==="1")return!0}catch{}try{let t=e.location?.search??"";return new URLSearchParams(t).get(U)==="1"}catch{return!1}}function m(n,e={},t=globalThis){if(!$(t))return;let o={type:n,timestamp:Date.now(),payload:z(e)},s=t,i=s[R]??[];i.push(o),i.length>1e3&&i.splice(0,i.length-1e3),s[R]=i;let r=t.console?.debug;o.payload.diagnosticOnly!==!0&&typeof r=="function"&&r.call(t.console,"[designer-trace]",o)}function z(n){return E(n,0)}function E(n,e){if(e>4)return"[truncated]";if(Array.isArray(n))return n.map(o=>E(o,e+1));if(!n||typeof n!="object")return n;let t={};for(let[o,s]of Object.entries(n)){if(W.test(o)){t[o]="[redacted]";continue}t[o]=E(s,e+1)}return t}var D=class{constructor(e){this.options=e,this.socket=null,this.connectPromise=null,this.connectAttempt=null,this.connectionState="idle",this.intentionalClose=!1,this.sequence=0,this.pending=new Map,this.listeners=new Set,this.connectionStatusListeners=new Set}connect(){return this.socket?.readyState===k&&this.connectionState==="open"?(this.setConnectionState("open"),Promise.resolve()):this.connectPromise?this.connectPromise:(this.intentionalClose=!1,this.setConnectionState("connecting"),this.connectPromise=new Promise((e,t)=>{let o;try{o=this.createSocket(this.options.url)}catch(c){let a=y(c,"WebSocket failed");this.socket=null,this.connectAttempt=null,this.connectPromise=null,this.setConnectionState("failed",a.message),t(a);return}let s={socket:o,settled:!1,reject:t};this.connectAttempt=s,this.socket=o;let i=()=>{this.socket!==o||this.connectAttempt!==s||(s.settled=!0,this.connectAttempt=null,this.connectPromise=null,this.setConnectionState("open"),e())},r=(c,a)=>{this.cleanupSocket(o,c,a)};o.onopen=()=>{if(!(this.socket!==o||this.connectAttempt!==s)){if(this.options.authKey){try{o.send(l.stringifyMessage({type:"auth",payload:{key:this.options.authKey}}))}catch(c){r(y(c,"WebSocket auth failed"),"failed")}return}i()}},o.onerror=c=>{if(this.socket!==o)return;let a=y(c,"WebSocket failed");r(a,"failed")},o.onmessage=c=>{if(this.socket===o){if(this.options.authKey&&this.connectAttempt===s&&!s.settled){this.handleAuthHandshakeMessage(c.data,o,s,()=>{i()});return}this.handleMessage(c.data)}},o.onclose=c=>{if(this.socket!==o)return;let a=this.intentionalClose,u=new Error(V(c,{intentional:a}));r(u,a?"closed":"failed")}}),this.connectPromise)}disconnect(){this.intentionalClose=!0,this.setConnectionState("closing");let e=this.socket;if(!e){this.rejectAll(new Error("WebSocket disconnected")),this.connectAttempt=null,this.connectPromise=null,this.setConnectionState("closed");return}this.cleanupSocket(e,new Error("WebSocket disconnected"),"closed")}getConnectionState(){return this.connectionState}subscribeConnectionStatus(e){return this.connectionStatusListeners.add(e),e({state:this.connectionState}),()=>{this.connectionStatusListeners.delete(e)}}getDocument(e){let t=this.selector(e);return this.request("get-document",{composition:t})}async initializeSession(e){let t=this.selector(e.composition);return await this.request("initialize-session",{composition:t,...e.mutationContext?{mutationContext:e.mutationContext}:{}})}getHistoryStatus(e){return this.request("get-history-status",{composition:this.selector(e)})}getStyleContext(e){let t=this.selector(e);return this.request("get-style-context",{composition:t})}getObjectEditContext(e,t,o){let s=this.selector(t);return this.request("get-object-edit-context",{key:e,composition:s,...o?{selectionRef:o}:{}})}getClassNameSource(e,t){let o=this.selector(t);return this.request("get-class-name-source",{key:e,composition:o})}getSourceTree(e){return this.request("get-source-tree",{composition:this.selector(e)})}getSourceFile(e){return this.request("get-source-file",{path:e.path,composition:this.selector(e.composition)})}resolveSourceLocation(e){return this.request("resolve-source-location",{key:e.key,composition:this.selector(e.composition)})}execute(e,t){let o=S(e),s={...e,composition:f(o)};return this.request("execute",{request:s})}undo(e,t){return this.request("undo",{composition:this.selector(e)})}redo(e,t){return this.request("redo",{composition:this.selector(e)})}subscribe(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}selector(e){return f(e)}request(e,t){let o=this.socket;if(!o||o.readyState!==k||this.connectionState!=="open")return Promise.reject(new Error(`WebSocket is not open (state: ${this.connectionState}, readyState: ${o?.readyState??"none"})`));let s=`${e}-${this.sequence+=1}`,i=Z(s,e,t);return m("bridge.request",{id:s,kind:e,routeId:typeof t.routeId=="string"?t.routeId:null,key:typeof t.key=="string"?t.key:null,operationKind:_(t.request,"kind"),documentVersion:_(t.request,"documentVersion")}),new Promise((r,c)=>{let a=this.resolveRequestTimeoutMs(e),u=a>0?setTimeout(()=>{let d=this.pending.get(s);d&&(this.pending.delete(s),m("bridge.response",{id:s,kind:e,ok:!1,timedOut:!0}),d.reject(new Error(`Bridge request timed out after ${a}ms: ${e}`)))},a):null;u?.unref?.(),this.pending.set(s,{kind:e,timeout:u,resolve:d=>r(d),reject:c});try{o.send(l.stringifyMessage(i))}catch(d){let p=y(d,"WebSocket send failed");this.clearPendingRequest(s),c(p),this.cleanupSocket(o,p,"failed")}})}handleAuthHandshakeMessage(e,t,o,s){try{if(l.parseMessage(e).type==="auth-ok"){s();return}throw new Error("Bridge authentication failed")}catch(i){let r=y(i,"Bridge authentication failed");this.connectAttempt===o&&this.cleanupSocket(t,r,"failed")}}handleMessage(e){let t=l.parseMessage(e);if(t.type==="event"){for(let s of this.listeners)s(t.payload.event);return}if(t.type!=="response"&&t.type!=="error")return;let o=this.pending.get(t.payload.id);if(o){if(this.clearPendingRequest(t.payload.id),t.type==="error"){m("bridge.response",{id:t.payload.id,kind:o.kind,ok:!1});let s=new Error(t.payload.message);t.payload.code&&(s.code=t.payload.code),o.reject(s);return}m("bridge.response",{id:t.payload.id,kind:o.kind,ok:!0,resultVersion:Q(t.payload.result)}),o.resolve(o.kind==="get-document"?{...t.payload.result,data:M(t.payload.result.data,t.payload.result.composition)}:o.kind==="initialize-session"?H(t.payload.result):t.payload.result)}}rejectAll(e){for(let[t,o]of[...this.pending])this.clearPendingRequest(t),o.reject(e)}clearPendingRequest(e){let t=this.pending.get(e);t&&(t.timeout&&clearTimeout(t.timeout),this.pending.delete(e))}resolveRequestTimeoutMs(e){let t=this.options.requestTimeoutMsByKind?.[e];return typeof t=="number"?Math.max(0,t):typeof this.options.requestTimeoutMs=="number"?Math.max(0,this.options.requestTimeoutMs):G[e]??J}cleanupSocket(e,t,o){if(this.socket===e&&(this.socket=null),this.connectAttempt?.socket===e){let s=this.connectAttempt;this.connectAttempt=null,s.settled||(s.settled=!0,s.reject(t))}this.connectPromise=null,this.rejectAll(t),this.setConnectionState(o,t.message),e.onopen=null,e.onmessage=null,e.onerror=null,e.onclose=null;try{(e.readyState===L||e.readyState===k)&&e.close()}catch{}}setConnectionState(e,t){if(this.connectionState===e&&!t)return;this.connectionState=e;let o={state:e,...t?{message:t}:{}};this.options.onConnectionStatus?.(o);for(let s of this.connectionStatusListeners)s(o)}createSocket(e){if(this.options.createSocket)return this.options.createSocket(e);if(typeof WebSocket>"u")throw new Error("Site edit preview bridge requires WebSocket");return new WebSocket(e)}},L=0,k=1,J=15e3,G={"get-source-tree":1e4,"get-source-file":1e4,"resolve-source-location":1e4,execute:6e4,undo:2e4,redo:2e4};function y(n,e){return n instanceof Error?n:new Error(e)}function V(n,e){return e.intentional?"WebSocket disconnected":n?.code===1008?n.reason?`Bridge session authentication failed: ${n.reason}`:"Bridge session authentication failed":n?.reason?`WebSocket closed: ${n.reason}`:typeof n?.code=="number"&&n.code!==1005?`WebSocket closed with code ${n.code}`:"WebSocket closed"}function _(n,e){if(!n||typeof n!="object")return null;let o=n[e];return typeof o=="string"||typeof o=="number"?o:null}function Q(n){if(!n||typeof n!="object")return null;let e=n,t=e.data?.resultVersion??e.data?.version??null;return typeof t=="number"?t:null}function ce(n){let e="composition"in n?f(n.composition):f({routeId:n.routeId});return new q({composition:e,initialDocument:n.initialDocument,client:n.client})}var q=class{constructor(e){this.params=e,this.serverConfirmedComposition=null,this.refreshInFlight=null,this.objectGeneration=0,this.styleGeneration=0,this.editContextCache=new Map,this.editContextInFlight=new Map,this.classNameSourceCache=new Map,this.classNameSourceInFlight=new Map,this.styleContextCache=new Map,this.styleContextInFlight=new Map,this.snapshotIdentityStale=!1,this.composition=f(e.composition);let t=e.initialDocument.composition.effectiveTemplateId;if(t!==void 0&&(this.composition.templateId===void 0||this.composition.templateId!==t))throw new Error(`Site edit bridge requires a matching templateId for initial document composition ${JSON.stringify(e.initialDocument.composition)}`);this.currentDocument=e.initialDocument,this.observedDocumentVersion=e.initialDocument.version,this.sessionInitialized=!O(e.initialDocument),this.activeCompositionContext=this.sessionInitialized?{...e.initialDocument.composition}:null}async getDocument(e){this.requireBoundComposition(e);let t=O(this.currentDocument)||this.snapshotIdentityStale;return(t||this.currentDocument.version<this.observedDocumentVersion)&&(this.currentDocument=await this.refreshSnapshot(t?void 0:this.observedDocumentVersion,"get-document"),this.snapshotIdentityStale=!1,this.sessionInitialized=!0),{ok:!0,data:this.currentDocument}}async initializeSession(e){let t=this.requireBoundComposition(e.composition);if(!this.params.client.initializeSession)throw new Error("Site edit bridge client does not support initialize-session");let o=await this.params.client.initializeSession({...e,composition:t});return this.currentDocument=o.data.document,this.observedDocumentVersion=o.data.document.version,this.activeCompositionContext={...o.composition},this.rememberServerConfirmedComposition(o.composition),this.sessionInitialized=!0,this.clearObjectCaches(),o}async getHistoryStatus(e){return this.params.client.getHistoryStatus(this.requireBoundComposition(e))}async getStyleContext(e){let t=this.requireBoundComposition(e),o=I(t),s=this.styleContextCache.get(o);if(s?.generation===this.styleGeneration)return{ok:!0,data:s.value};let i=this.styleContextInFlight.get(o);if(i?.generation===this.styleGeneration)return i.request;let r=this.styleGeneration,c=this.params.client.getStyleContext(t).then(a=>(this.styleGeneration===r&&this.styleContextCache.set(o,{generation:r,value:a.data}),a)).finally(()=>{this.styleContextInFlight.get(o)?.request===c&&this.styleContextInFlight.delete(o)});return this.styleContextInFlight.set(o,{generation:r,request:c}),c}async ensureDocumentVersion(e,t){this.requireBoundComposition(e);let o=await this.refreshSnapshot(t,"ensure-version");if(o.version<t)throw new Error(`Document refresh returned version ${o.version}, expected at least ${t}`);return{ok:!0,data:o}}async acknowledgeDocumentVersion(e,t){if(this.requireBoundComposition(e),t>this.currentDocument.version){if(this.currentDocument.pageComposition.status==="supported"){let o=await this.refreshSnapshot(t,"ensure-version");if(o.version<t)throw new Error(`Document refresh returned version ${o.version}, expected at least ${t}`);return{ok:!0,data:o}}this.currentDocument={...this.currentDocument,version:t},this.clearObjectCaches({preserveStyleContext:!0})}return this.observedDocumentVersion=Math.max(this.observedDocumentVersion,t),{ok:!0,data:this.currentDocument}}async getObjectEditContext(e,t,o){let s=this.requireBoundComposition(t),i=this.objectCacheKey(e,s,o),r=this.editContextCache.get(i);if(r)return{ok:!0,data:r};let c=this.editContextInFlight.get(i);if(c)return c;let a=this.objectGeneration,u=this.params.client.getObjectEditContext(e,s,...o?[o]:[]).then(d=>(this.objectGeneration===a&&this.editContextCache.set(i,d.data),d)).finally(()=>{this.editContextInFlight.get(i)===u&&this.editContextInFlight.delete(i)});return this.editContextInFlight.set(i,u),u}async getClassNameSource(e,t){let o=this.requireBoundComposition(t),s=this.objectCacheKey(e,o),i=this.classNameSourceCache.get(s);if(i)return i;let r=this.classNameSourceInFlight.get(s);if(r)return r;let c=this.objectGeneration,a=this.params.client.getClassNameSource(e,o).then(u=>(this.objectGeneration===c&&this.classNameSourceCache.set(s,u),u)).finally(()=>{this.classNameSourceInFlight.get(s)===a&&this.classNameSourceInFlight.delete(s)});return this.classNameSourceInFlight.set(s,a),a}async getSourceTree(e){return this.params.client.getSourceTree(this.requireBoundComposition(e))}async getSourceFile(e){return this.params.client.getSourceFile({path:e.path,composition:this.requireBoundComposition(e.composition)})}async resolveSourceLocation(e){return this.params.client.resolveSourceLocation({key:e.key,composition:this.requireBoundComposition(e.composition)})}async execute(e,t){let o=S(e);o&&this.composition.templateId!==void 0&&this.requireBoundComposition(o);let s={...e,composition:o??this.composition};return this.params.client.execute(s)}async undo(e,t){return this.params.client.undo(this.requireBoundComposition(e))}async redo(e,t){return this.params.client.redo(this.requireBoundComposition(e))}subscribe(e){return this.params.client.subscribe(t=>{if(t.type==="site-edit.snapshot.changed"&&this.sessionInitialized&&this.isActiveComposition(t.composition)){this.snapshotIdentityStale=!0,this.clearObjectCaches(),e(t);return}if(t.type==="site-edit.document.changed"&&this.sessionInitialized&&this.isActiveComposition(t.composition)){this.observedDocumentVersion=Math.max(this.observedDocumentVersion,t.version),t.version>this.currentDocument.version&&this.clearObjectCaches(),e(t);return}e(t)})}async refreshSnapshot(e,t="ensure-version"){if(typeof e=="number"&&this.currentDocument.version>=e)return m("bridge.document-refresh",{composition:this.composition,routeId:this.composition.routeId,minVersion:e,currentVersion:this.currentDocument.version,deduped:!0,skipped:!0,source:t}),this.currentDocument;let o=I(this.composition),s=this.refreshInFlight;if(s?.compositionKey===o){m("bridge.document-refresh",{composition:this.composition,routeId:this.composition.routeId,minVersion:e,currentVersion:this.currentDocument.version,deduped:!0,source:t});let r=await s.request;if(typeof e!="number"||r.version>=e||this.currentDocument.version>=e)return this.currentDocument}m("bridge.document-refresh",{composition:this.composition,routeId:this.composition.routeId,minVersion:e,currentVersion:this.currentDocument.version,deduped:!1,source:t});let i=this.params.client.getDocument(this.composition).then(r=>(this.currentDocument=r.data,this.activeCompositionContext={...r.data.composition},this.rememberServerConfirmedComposition(r.data.composition),this.observedDocumentVersion=Math.max(this.observedDocumentVersion,r.data.version),this.clearObjectCaches(),this.currentDocument));this.refreshInFlight={compositionKey:o,request:i};try{return await i}finally{this.refreshInFlight?.request===i&&(this.refreshInFlight=null)}}clearObjectCaches(e){this.objectGeneration+=1,this.editContextCache.clear(),this.editContextInFlight.clear(),this.classNameSourceCache.clear(),this.classNameSourceInFlight.clear(),!e?.preserveStyleContext&&(this.styleGeneration+=1,this.styleContextCache.clear(),this.styleContextInFlight.clear())}objectCacheKey(e,t,o){return`${I(t)}\0${e}\0${Y(o)}`}rememberServerConfirmedComposition(e){!e||typeof e.routeId!="string"||(this.serverConfirmedComposition={...e})}isAcceptedComposition(e){if(I(e)===I(this.composition))return!0;let t=this.serverConfirmedComposition;return t!==null&&C(t)===C(A(e))}requireBoundComposition(e){let t=e===void 0?this.composition:f(e);if(!this.isAcceptedComposition(t))throw new Error(`Site edit bridge is bound to composition ${JSON.stringify(this.composition)}, received ${JSON.stringify(t)}`);return f(t)}isActiveComposition(e){let t=this.activeCompositionContext??A(this.composition);return C(e)===C(t)}};function O(n){return n.version===0&&n.entries.length===0}function H(n){let e=n;return{...e,data:{...e.data,document:M(e.data.document,e.composition)}}}function M(n,e){let t=n,o=T(t.routeId?t:{...t,routeId:e?.routeId??t.composition?.routeId});return e?{...o,composition:e}:o}function f(n){if(n&&typeof n=="object"&&typeof n.routeId=="string"){let e="effectiveTemplateId"in n?n.effectiveTemplateId:"templateId"in n?n.templateId:void 0;return{routeId:n.routeId,...typeof e=="string"?{templateId:e}:{}}}throw new Error("SITE_EDIT_COMPOSITION_SESSION_REQUIRED: a composition selector is required")}function I(n){return JSON.stringify([n.routeId,n.templateId??null])}function Y(n){return n?JSON.stringify([n.key,n.instanceIndex??null,n.instanceCount??null]):""}function C(n){return JSON.stringify([n.routeId,n.effectiveTemplateId??null])}function A(n){return{routeId:n.routeId,...n.templateId?{effectiveTemplateId:n.templateId}:{}}}function S(n){if(n&&typeof n=="object"){let e=n;if(e.composition&&typeof e.composition=="object"){let t=e.composition;if(typeof t.routeId=="string")return{routeId:t.routeId,...typeof t.templateId=="string"?{templateId:t.templateId}:{}}}}}function Z(n,e,t){let o=f(e==="execute"?S(t.request):t.composition),s={id:n,kind:e,composition:o};switch(e){case"initialize-session":case"get-document":case"get-history-status":case"get-style-context":case"get-source-tree":case"undo":case"redo":return{type:"request",payload:{...s,...t.mutationContext?{mutationContext:t.mutationContext}:{}}};case"get-object-edit-context":return{type:"request",payload:{...s,key:String(t.key),...t.selectionRef?{selectionRef:t.selectionRef}:{}}};case"get-class-name-source":case"resolve-source-location":return{type:"request",payload:{...s,key:String(t.key)}};case"get-source-file":return{type:"request",payload:{...s,path:String(t.path)}};case"execute":return{type:"request",payload:{...s,request:{...t.request,composition:S(t.request)??o}}}}}export{D as SiteEditWebSocketClient,ce as createSiteEditBridge};
|
|
1
|
+
function O(t){if(!P(t)){if(t.encoding==="segment-trie-v1")throw new Error("Invalid compact render document");return t}let e=new Map,n=t.pageComposition.status==="supported"?t.pageComposition.placementUnitKeys:[],o=new Map(t.dynamicDataSummaries??[]),i=new Map(t.pageCompositionActionScopes??[]),r=c=>{let s=e.get(c);if(s)return s;let a=t.nodes[c];if(!a)throw new Error(`Missing compact document node ${c}`);let[u,d]=a,p=t.tokens[d];if(typeof p!="string")throw new Error(`Missing compact document token ${d}`);let y=`${u===null?"":r(u)}${p}`;return e.set(c,y),y};return{siteId:t.siteId,snapshotId:t.snapshotId,composition:{...t.composition},routeId:t.composition.routeId,version:t.version,pageComposition:t.pageComposition,entries:t.entries.map((c,s)=>{let[a,u,d]=c,p=d===void 0?void 0:n[d];if(d!==void 0&&!p)throw new Error(`Missing page composition unit ${d}`);let y=t.entryParentNodeIds?.[s],w=i.get(s),D=w?.kind==="source-siblings"?{kind:"source-siblings",siblingKeys:w.siblingEntryIndexes.map(A=>{let _=t.entries[A];if(!_)throw new Error(`Missing source sibling entry ${A}`);return r(_[0])})}:w;return{key:r(a),...t.entryParentNodeIds?{parentKey:y===null?null:r(y)}:{},...p?{pageCompositionUnitKey:p}:{},...D?{pageCompositionActionScope:D}:{},operationSummary:F(u),...o.get(s)?{dynamicDataSummary:o.get(s)}:{}}})}}function F(t){return{canUpdateText:(t&1)!==0,canInsertChild:(t&2)!==0,canMove:(t&4)!==0,canRemove:(t&8)!==0}}function P(t){if(typeof t!="object"||t===null)return!1;let e=t;if(e.encoding!=="segment-trie-v1"||typeof e.siteId!="string"||typeof e.snapshotId!="string"||!U(e.composition)||typeof e.routeId!="string"||e.routeId!==e.composition.routeId||!Number.isInteger(e.version)||e.version<0||!S(e.pageComposition)||!Array.isArray(e.tokens)||!e.tokens.every(s=>typeof s=="string")||!Array.isArray(e.nodes)||!Array.isArray(e.entries))return!1;let n=e.nodes;if(!n.every((s,a)=>Array.isArray(s)&&s.length===2&&(s[0]===null||l(s[0])&&s[0]<a)&&l(s[1])&&s[1]<e.tokens.length)||!e.entries.every(s=>Array.isArray(s)&&(s.length===2||s.length===3)&&l(s[0])&&s[0]<n.length&&l(s[1])&&s[1]<=15&&(s.length===2||l(s[2]))))return!1;let o=e.entryParentNodeIds;if(o!==void 0&&(!Array.isArray(o)||o.length!==e.entries.length||!o.every(s=>s===null||l(s)&&s<n.length)))return!1;let i=e.dynamicDataSummaries,r=e.pageCompositionActionScopes;return(r===void 0||Array.isArray(r)&&r.every(s=>{if(!Array.isArray(s)||s.length!==2||!l(s[0])||s[0]>=e.entries.length||!S(s[1])||!["route-unit","source-siblings","unavailable"].includes(String(s[1].kind)))return!1;let a=s[1];return a.kind!=="source-siblings"||Array.isArray(a.siblingEntryIndexes)&&a.siblingEntryIndexes.length>=2&&a.siblingEntryIndexes.every(u=>l(u)&&u<e.entries.length)&&a.siblingEntryIndexes.includes(s[0])}))&&(i===void 0||Array.isArray(i)&&i.every(s=>Array.isArray(s)&&s.length===2&&l(s[0])&&s[0]<e.entries.length&&S(s[1])))}function S(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function U(t){return!S(t)||typeof t.routeId!="string"?!1:t.effectiveTemplateId===void 0||typeof t.effectiveTemplateId=="string"}function l(t){return typeof t=="number"&&Number.isInteger(t)&&t>=0}var m=class t{constructor(){}static parseMessage(e,n={}){let o=JSON.parse(e);if(!t.isMessage(o,n))throw new Error("Invalid site edit bridge message");return o}static stringifyMessage(e){return JSON.stringify(e)}static extractRequestInfo(e){if(h(e)&&h(e.payload)){let n=typeof e.payload.id=="string"?e.payload.id:"unknown",o=this.isRequestKind(e.payload.kind)?e.payload.kind:"get-document";return{id:n,kind:o}}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!h(e)||typeof e.type!="string"?!1:e.type==="request"?W(e.payload,n):e.type==="response"||e.type==="error"?h(e.payload)&&typeof e.payload.id=="string":e.type==="event"?h(e.payload)&&h(e.payload.event):e.type==="auth"?h(e.payload)&&typeof e.payload.key=="string":e.type==="auth-ok"&&h(e.payload)&&e.payload.ok===!0}};function W(t,e){if(!h(t)||typeof t.id!="string"||!m.isRequestKind(t.kind))return!1;let n=k(t.composition);switch(t.kind){case"initialize-session":return n&&(t.mutationContext===void 0||E(t.mutationContext));case"get-document":case"get-style-context":return n;case"get-object-edit-context":return typeof t.key=="string"&&n&&$(t.selectionRef);case"get-class-name-source":case"resolve-source-location":return typeof t.key=="string"&&n;case"get-history-status":return n;case"get-source-tree":return n;case"get-source-file":return typeof t.path=="string"&&n;case"execute":return n&&z(t.request)&&(t.request.mutationContext===void 0||E(t.request.mutationContext))&&(e.validateOperationRequest?.(t.request)??!0);case"undo":case"redo":return n&&t.mutationContext===void 0||n&&E(t.mutationContext)}}function $(t){if(t===void 0)return!0;if(!h(t)||typeof t.key!="string"||t.key.length===0)return!1;let e=t.instanceIndex,n=t.instanceCount;return e!==void 0&&(typeof e!="number"||!Number.isInteger(e)||e<0)||n!==void 0&&(typeof n!="number"||!Number.isInteger(n)||n<=0)||typeof e=="number"&&typeof n=="number"&&e>=n?!1:Object.keys(t).every(o=>o==="key"||o==="instanceIndex"||o==="instanceCount")}function z(t){return h(t)&&typeof t.id=="string"&&typeof t.siteId=="string"&&typeof t.baseSnapshotId=="string"&&k(t.composition)&&typeof t.kind=="string"&&h(t.target)&&(t.target.kind!=="route"||k(t.target.composition))&&typeof t.documentVersion=="number"&&Number.isInteger(t.documentVersion)&&t.documentVersion>=0&&h(t.params)}function k(t){return h(t)&&typeof t.routeId=="string"&&t.routeId.length>0&&(t.templateId===void 0||typeof t.templateId=="string"&&t.templateId.length>0)&&Object.keys(t).every(e=>e==="routeId"||e==="templateId")}function h(t){return typeof t=="object"&&t!==null}function E(t){return h(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}var L="vcp:designer:trace",V="designer_trace",M="__VCP_DESIGNER_TRACE__";var J=/(authorization|cookie|token|secret|providerTrace|headers?|sourceCode|rawSource|fullSource)/i;function G(t=globalThis){let e=t;try{if(e.localStorage?.getItem(L)==="1")return!0}catch{}try{let n=e.location?.search??"";return new URLSearchParams(n).get(V)==="1"}catch{return!1}}function f(t,e={},n=globalThis){if(!G(n))return;let o={type:t,timestamp:Date.now(),payload:Q(e)},i=n,r=i[M]??[];r.push(o),r.length>1e3&&r.splice(0,r.length-1e3),i[M]=r;let c=n.console?.debug;o.payload.diagnosticOnly!==!0&&typeof c=="function"&&c.call(n.console,"[designer-trace]",o)}function Q(t){return q(t,0)}function q(t,e){if(e>4)return"[truncated]";if(Array.isArray(t))return t.map(o=>q(o,e+1));if(!t||typeof t!="object")return t;let n={};for(let[o,i]of Object.entries(t)){if(J.test(o)){n[o]="[redacted]";continue}n[o]=q(i,e+1)}return n}var v=class{constructor(e){this.options=e,this.socket=null,this.connectPromise=null,this.connectAttempt=null,this.connectionState="idle",this.intentionalClose=!1,this.sequence=0,this.pending=new Map,this.listeners=new Set,this.connectionStatusListeners=new Set}connect(){return this.socket?.readyState===T&&this.connectionState==="open"?(this.setConnectionState("open"),Promise.resolve()):this.connectPromise?this.connectPromise:(this.intentionalClose=!1,this.setConnectionState("connecting"),this.connectPromise=new Promise((e,n)=>{let o;try{o=this.createSocket(this.options.url)}catch(s){let a=I(s,"WebSocket failed");this.socket=null,this.connectAttempt=null,this.connectPromise=null,this.setConnectionState("failed",a.message),n(a);return}let i={socket:o,settled:!1,reject:n};this.connectAttempt=i,this.socket=o;let r=()=>{this.socket!==o||this.connectAttempt!==i||(i.settled=!0,this.connectAttempt=null,this.connectPromise=null,this.setConnectionState("open"),e())},c=(s,a)=>{this.cleanupSocket(o,s,a)};o.onopen=()=>{if(!(this.socket!==o||this.connectAttempt!==i)){if(this.options.authKey){try{o.send(m.stringifyMessage({type:"auth",payload:{key:this.options.authKey}}))}catch(s){c(I(s,"WebSocket auth failed"),"failed")}return}r()}},o.onerror=s=>{if(this.socket!==o)return;let a=I(s,"WebSocket failed");c(a,"failed")},o.onmessage=s=>{if(this.socket===o){if(this.options.authKey&&this.connectAttempt===i&&!i.settled){this.handleAuthHandshakeMessage(s.data,o,i,()=>{r()});return}this.handleMessage(s.data)}},o.onclose=s=>{if(this.socket!==o)return;let a=this.intentionalClose,u=new Error(X(s,{intentional:a}));c(u,a?"closed":"failed")}}),this.connectPromise)}disconnect(){this.intentionalClose=!0,this.setConnectionState("closing");let e=this.socket;if(!e){this.rejectAll(new Error("WebSocket disconnected")),this.connectAttempt=null,this.connectPromise=null,this.setConnectionState("closed");return}this.cleanupSocket(e,new Error("WebSocket disconnected"),"closed")}getConnectionState(){return this.connectionState}subscribeConnectionStatus(e){return this.connectionStatusListeners.add(e),e({state:this.connectionState}),()=>{this.connectionStatusListeners.delete(e)}}getDocument(e){let n=this.selector(e);return this.request("get-document",{composition:n})}async initializeSession(e){let n=this.selector(e.composition);return await this.request("initialize-session",{composition:n,...e.mutationContext?{mutationContext:e.mutationContext}:{}})}getHistoryStatus(e){return this.request("get-history-status",{composition:this.selector(e)})}getStyleContext(e){let n=this.selector(e);return this.request("get-style-context",{composition:n})}getObjectEditContext(e,n,o){let i=this.selector(n);return this.request("get-object-edit-context",{key:e,composition:i,...o?{selectionRef:o}:{}})}getClassNameSource(e,n){let o=this.selector(n);return this.request("get-class-name-source",{key:e,composition:o})}getSourceTree(e){return this.request("get-source-tree",{composition:this.selector(e)})}getSourceFile(e){return this.request("get-source-file",{path:e.path,composition:this.selector(e.composition)})}resolveSourceLocation(e){return this.request("resolve-source-location",{key:e.key,composition:this.selector(e.composition)})}execute(e,n){let o=x(e),i={...e,composition:g(o)};return this.request("execute",{request:i})}undo(e,n){return this.request("undo",{composition:this.selector(e)})}redo(e,n){return this.request("redo",{composition:this.selector(e)})}subscribe(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}selector(e){return g(e)}request(e,n){let o=this.socket;if(!o||o.readyState!==T||this.connectionState!=="open")return Promise.reject(new Error(`WebSocket is not open (state: ${this.connectionState}, readyState: ${o?.readyState??"none"})`));let i=`${e}-${this.sequence+=1}`,r=oe(i,e,n);return f("bridge.request",{id:i,kind:e,routeId:typeof n.routeId=="string"?n.routeId:null,key:typeof n.key=="string"?n.key:null,operationKind:N(n.request,"kind"),documentVersion:N(n.request,"documentVersion")}),new Promise((c,s)=>{let a=this.resolveRequestTimeoutMs(e),u=a>0?setTimeout(()=>{let d=this.pending.get(i);d&&(this.pending.delete(i),f("bridge.response",{id:i,kind:e,ok:!1,timedOut:!0}),d.reject(new Error(`Bridge request timed out after ${a}ms: ${e}`)))},a):null;u?.unref?.(),this.pending.set(i,{kind:e,timeout:u,resolve:d=>c(d),reject:s});try{o.send(m.stringifyMessage(r))}catch(d){let p=I(d,"WebSocket send failed");this.clearPendingRequest(i),s(p),this.cleanupSocket(o,p,"failed")}})}handleAuthHandshakeMessage(e,n,o,i){try{if(m.parseMessage(e).type==="auth-ok"){i();return}throw new Error("Bridge authentication failed")}catch(r){let c=I(r,"Bridge authentication failed");this.connectAttempt===o&&this.cleanupSocket(n,c,"failed")}}handleMessage(e){let n=m.parseMessage(e);if(n.type==="event"){for(let i of this.listeners)i(n.payload.event);return}if(n.type!=="response"&&n.type!=="error")return;let o=this.pending.get(n.payload.id);if(o){if(this.clearPendingRequest(n.payload.id),n.type==="error"){f("bridge.response",{id:n.payload.id,kind:o.kind,ok:!1});let i=new Error(n.payload.message);n.payload.code&&(i.code=n.payload.code),o.reject(i);return}f("bridge.response",{id:n.payload.id,kind:o.kind,ok:!0,resultVersion:ee(n.payload.result)}),o.resolve(o.kind==="get-document"?{...n.payload.result,data:j(n.payload.result.data,n.payload.result.composition)}:o.kind==="initialize-session"?te(n.payload.result):n.payload.result)}}rejectAll(e){for(let[n,o]of[...this.pending])this.clearPendingRequest(n),o.reject(e)}clearPendingRequest(e){let n=this.pending.get(e);n&&(n.timeout&&clearTimeout(n.timeout),this.pending.delete(e))}resolveRequestTimeoutMs(e){let n=this.options.requestTimeoutMsByKind?.[e];return typeof n=="number"?Math.max(0,n):typeof this.options.requestTimeoutMs=="number"?Math.max(0,this.options.requestTimeoutMs):Z[e]??Y}cleanupSocket(e,n,o){if(this.socket===e&&(this.socket=null),this.connectAttempt?.socket===e){let i=this.connectAttempt;this.connectAttempt=null,i.settled||(i.settled=!0,i.reject(n))}this.connectPromise=null,this.rejectAll(n),this.setConnectionState(o,n.message),e.onopen=null,e.onmessage=null,e.onerror=null,e.onclose=null;try{(e.readyState===H||e.readyState===T)&&e.close()}catch{}}setConnectionState(e,n){if(this.connectionState===e&&!n)return;this.connectionState=e;let o={state:e,...n?{message:n}:{}};this.options.onConnectionStatus?.(o);for(let i of this.connectionStatusListeners)i(o)}createSocket(e){if(this.options.createSocket)return this.options.createSocket(e);if(typeof WebSocket>"u")throw new Error("Site edit preview bridge requires WebSocket");return new WebSocket(e)}},H=0,T=1,Y=15e3,Z={"get-source-tree":1e4,"get-source-file":1e4,"resolve-source-location":1e4,execute:6e4,undo:2e4,redo:2e4};function I(t,e){return t instanceof Error?t:new Error(e)}function X(t,e){return e.intentional?"WebSocket disconnected":t?.code===1008?t.reason?`Bridge session authentication failed: ${t.reason}`:"Bridge session authentication failed":t?.reason?`WebSocket closed: ${t.reason}`:typeof t?.code=="number"&&t.code!==1005?`WebSocket closed with code ${t.code}`:"WebSocket closed"}function N(t,e){if(!t||typeof t!="object")return null;let o=t[e];return typeof o=="string"||typeof o=="number"?o:null}function ee(t){if(!t||typeof t!="object")return null;let e=t,n=e.data?.resultVersion??e.data?.version??null;return typeof n=="number"?n:null}function pe(t){let e="composition"in t?g(t.composition):g({routeId:t.routeId});return new R({composition:e,initialDocument:t.initialDocument,client:t.client})}var R=class{constructor(e){this.params=e,this.serverConfirmedComposition=null,this.refreshInFlight=null,this.objectGeneration=0,this.styleGeneration=0,this.editContextCache=new Map,this.editContextInFlight=new Map,this.classNameSourceCache=new Map,this.classNameSourceInFlight=new Map,this.styleContextCache=new Map,this.styleContextInFlight=new Map,this.snapshotIdentityStale=!1,this.composition=g(e.composition);let n=e.initialDocument.composition.effectiveTemplateId;if(n!==void 0&&(this.composition.templateId===void 0||this.composition.templateId!==n))throw new Error(`Site edit bridge requires a matching templateId for initial document composition ${JSON.stringify(e.initialDocument.composition)}`);this.currentDocument=e.initialDocument,this.observedDocumentVersion=e.initialDocument.version,this.sessionInitialized=!K(e.initialDocument),this.activeCompositionContext=this.sessionInitialized?{...e.initialDocument.composition}:null}async getDocument(e){this.requireBoundComposition(e);let n=K(this.currentDocument)||this.snapshotIdentityStale;return(n||this.currentDocument.version<this.observedDocumentVersion)&&(this.currentDocument=await this.refreshSnapshot(n?void 0:this.observedDocumentVersion,"get-document"),this.snapshotIdentityStale=!1,this.sessionInitialized=!0),{ok:!0,data:this.currentDocument}}async initializeSession(e){let n=this.requireBoundComposition(e.composition);if(!this.params.client.initializeSession)throw new Error("Site edit bridge client does not support initialize-session");let o=await this.params.client.initializeSession({...e,composition:n});return this.currentDocument=o.data.document,this.observedDocumentVersion=o.data.document.version,this.activeCompositionContext={...o.composition},this.rememberServerConfirmedComposition(o.composition),this.sessionInitialized=!0,this.clearObjectCaches(),o}async getHistoryStatus(e){return this.params.client.getHistoryStatus(this.requireBoundComposition(e))}async getStyleContext(e){let n=this.requireBoundComposition(e),o=C(n),i=this.styleContextCache.get(o);if(i?.generation===this.styleGeneration)return{ok:!0,data:i.value};let r=this.styleContextInFlight.get(o);if(r?.generation===this.styleGeneration)return r.request;let c=this.styleGeneration,s=this.params.client.getStyleContext(n).then(a=>(this.styleGeneration===c&&this.styleContextCache.set(o,{generation:c,value:a.data}),a)).finally(()=>{this.styleContextInFlight.get(o)?.request===s&&this.styleContextInFlight.delete(o)});return this.styleContextInFlight.set(o,{generation:c,request:s}),s}async ensureDocumentVersion(e,n){this.requireBoundComposition(e);let o=await this.refreshSnapshot(n,"ensure-version");if(o.version<n)throw new Error(`Document refresh returned version ${o.version}, expected at least ${n}`);return{ok:!0,data:o}}async acknowledgeDocumentVersion(e,n){if(this.requireBoundComposition(e),n>this.currentDocument.version){if(this.currentDocument.pageComposition.status==="supported"){let o=await this.refreshSnapshot(n,"ensure-version");if(o.version<n)throw new Error(`Document refresh returned version ${o.version}, expected at least ${n}`);return{ok:!0,data:o}}this.currentDocument={...this.currentDocument,version:n},this.clearObjectCaches({preserveStyleContext:!0})}return this.observedDocumentVersion=Math.max(this.observedDocumentVersion,n),{ok:!0,data:this.currentDocument}}async getObjectEditContext(e,n,o){let i=this.requireBoundComposition(n),r=this.objectCacheKey(e,i,o),c=this.editContextCache.get(r);if(c)return{ok:!0,data:c};let s=this.editContextInFlight.get(r);if(s)return s;let a=this.objectGeneration,u=this.params.client.getObjectEditContext(e,i,...o?[o]:[]).then(d=>(this.objectGeneration===a&&this.editContextCache.set(r,d.data),d)).finally(()=>{this.editContextInFlight.get(r)===u&&this.editContextInFlight.delete(r)});return this.editContextInFlight.set(r,u),u}async getClassNameSource(e,n){let o=this.requireBoundComposition(n),i=this.objectCacheKey(e,o),r=this.classNameSourceCache.get(i);if(r)return r;let c=this.classNameSourceInFlight.get(i);if(c)return c;let s=this.objectGeneration,a=this.params.client.getClassNameSource(e,o).then(u=>(this.objectGeneration===s&&this.classNameSourceCache.set(i,u),u)).finally(()=>{this.classNameSourceInFlight.get(i)===a&&this.classNameSourceInFlight.delete(i)});return this.classNameSourceInFlight.set(i,a),a}async getSourceTree(e){return this.params.client.getSourceTree(this.requireBoundComposition(e))}async getSourceFile(e){return this.params.client.getSourceFile({path:e.path,composition:this.requireBoundComposition(e.composition)})}async resolveSourceLocation(e){return this.params.client.resolveSourceLocation({key:e.key,composition:this.requireBoundComposition(e.composition)})}async execute(e,n){let o=x(e);o&&this.composition.templateId!==void 0&&this.requireBoundComposition(o);let i={...e,composition:o??this.composition};return this.params.client.execute(i)}async undo(e,n){return this.params.client.undo(this.requireBoundComposition(e))}async redo(e,n){return this.params.client.redo(this.requireBoundComposition(e))}subscribe(e){return this.params.client.subscribe(n=>{if(n.type==="site-edit.snapshot.changed"&&this.sessionInitialized&&this.isActiveComposition(n.composition)){this.snapshotIdentityStale=!0,this.clearObjectCaches(),e(n);return}if(n.type==="site-edit.document.changed"&&this.sessionInitialized&&this.isActiveComposition(n.composition)){this.observedDocumentVersion=Math.max(this.observedDocumentVersion,n.version),n.version>this.currentDocument.version&&this.clearObjectCaches(),e(n);return}e(n)})}async refreshSnapshot(e,n="ensure-version"){if(typeof e=="number"&&this.currentDocument.version>=e)return f("bridge.document-refresh",{composition:this.composition,routeId:this.composition.routeId,minVersion:e,currentVersion:this.currentDocument.version,deduped:!0,skipped:!0,source:n}),this.currentDocument;let o=C(this.composition),i=this.refreshInFlight;if(i?.compositionKey===o){f("bridge.document-refresh",{composition:this.composition,routeId:this.composition.routeId,minVersion:e,currentVersion:this.currentDocument.version,deduped:!0,source:n});let c=await i.request;if(typeof e!="number"||c.version>=e||this.currentDocument.version>=e)return this.currentDocument}f("bridge.document-refresh",{composition:this.composition,routeId:this.composition.routeId,minVersion:e,currentVersion:this.currentDocument.version,deduped:!1,source:n});let r=this.params.client.getDocument(this.composition).then(c=>(this.currentDocument=c.data,this.activeCompositionContext={...c.data.composition},this.rememberServerConfirmedComposition(c.data.composition),this.observedDocumentVersion=Math.max(this.observedDocumentVersion,c.data.version),this.clearObjectCaches(),this.currentDocument));this.refreshInFlight={compositionKey:o,request:r};try{return await r}finally{this.refreshInFlight?.request===r&&(this.refreshInFlight=null)}}clearObjectCaches(e){this.objectGeneration+=1,this.editContextCache.clear(),this.editContextInFlight.clear(),this.classNameSourceCache.clear(),this.classNameSourceInFlight.clear(),!e?.preserveStyleContext&&(this.styleGeneration+=1,this.styleContextCache.clear(),this.styleContextInFlight.clear())}objectCacheKey(e,n,o){return`${C(n)}\0${e}\0${ne(o)}`}rememberServerConfirmedComposition(e){!e||typeof e.routeId!="string"||(this.serverConfirmedComposition={...e})}isAcceptedComposition(e){if(C(e)===C(this.composition))return!0;let n=this.serverConfirmedComposition;return n!==null&&b(n)===b(B(e))}requireBoundComposition(e){let n=e===void 0?this.composition:g(e);if(!this.isAcceptedComposition(n))throw new Error(`Site edit bridge is bound to composition ${JSON.stringify(this.composition)}, received ${JSON.stringify(n)}`);return g(n)}isActiveComposition(e){let n=this.activeCompositionContext??B(this.composition);return b(e)===b(n)}};function K(t){return t.version===0&&t.entries.length===0}function te(t){let e=t;return{...e,data:{...e.data,document:j(e.data.document,e.composition)}}}function j(t,e){let n=t,o=O(n.routeId?n:{...n,routeId:e?.routeId??n.composition?.routeId});return e?{...o,composition:e}:o}function g(t){if(t&&typeof t=="object"&&typeof t.routeId=="string"){let e="effectiveTemplateId"in t?t.effectiveTemplateId:"templateId"in t?t.templateId:void 0;return{routeId:t.routeId,...typeof e=="string"?{templateId:e}:{}}}throw new Error("SITE_EDIT_COMPOSITION_SESSION_REQUIRED: a composition selector is required")}function C(t){return JSON.stringify([t.routeId,t.templateId??null])}function ne(t){return t?JSON.stringify([t.key,t.instanceIndex??null,t.instanceCount??null]):""}function b(t){return JSON.stringify([t.routeId,t.effectiveTemplateId??null])}function B(t){return{routeId:t.routeId,...t.templateId?{effectiveTemplateId:t.templateId}:{}}}function x(t){if(t&&typeof t=="object"){let e=t;if(e.composition&&typeof e.composition=="object"){let n=e.composition;if(typeof n.routeId=="string")return{routeId:n.routeId,...typeof n.templateId=="string"?{templateId:n.templateId}:{}}}}}function oe(t,e,n){let o=g(e==="execute"?x(n.request):n.composition),i={id:t,kind:e,composition:o};switch(e){case"initialize-session":case"get-document":case"get-history-status":case"get-style-context":case"get-source-tree":case"undo":case"redo":return{type:"request",payload:{...i,...n.mutationContext?{mutationContext:n.mutationContext}:{}}};case"get-object-edit-context":return{type:"request",payload:{...i,key:String(n.key),...n.selectionRef?{selectionRef:n.selectionRef}:{}}};case"get-class-name-source":case"resolve-source-location":return{type:"request",payload:{...i,key:String(n.key)}};case"get-source-file":return{type:"request",payload:{...i,path:String(n.path)}};case"execute":return{type:"request",payload:{...i,request:{...n.request,composition:x(n.request)??o}}}}}export{v as SiteEditWebSocketClient,pe as createSiteEditBridge};
|
package/dist/document-codec.d.ts
CHANGED
|
@@ -12,6 +12,10 @@ export type CompactRenderDocument = {
|
|
|
12
12
|
nodes: CompactRenderDocumentNode[];
|
|
13
13
|
entries: CompactRenderDocumentEntry[];
|
|
14
14
|
entryParentNodeIds?: Array<number | null>;
|
|
15
|
+
pageCompositionActionScopes?: Array<[
|
|
16
|
+
entryIndex: number,
|
|
17
|
+
scope: CompactPageCompositionActionScope
|
|
18
|
+
]>;
|
|
15
19
|
dynamicDataSummaries?: Array<[
|
|
16
20
|
entryIndex: number,
|
|
17
21
|
summary: SiteEditDynamicDataSummary
|
|
@@ -28,6 +32,14 @@ export type CompactRenderDocumentEntry = [
|
|
|
28
32
|
operationFlags: number,
|
|
29
33
|
pageCompositionUnitIndex?: number
|
|
30
34
|
];
|
|
35
|
+
export type CompactPageCompositionActionScope = {
|
|
36
|
+
kind: "route-unit";
|
|
37
|
+
} | {
|
|
38
|
+
kind: "source-siblings";
|
|
39
|
+
siblingEntryIndexes: number[];
|
|
40
|
+
} | {
|
|
41
|
+
kind: "unavailable";
|
|
42
|
+
};
|
|
31
43
|
export declare function encodeRenderDocument(document: SiteEditRenderDocument): CompactRenderDocument;
|
|
32
44
|
export declare function decodeRenderDocument(document: CompactRenderDocument | SiteEditRenderDocument): SiteEditRenderDocument;
|
|
33
45
|
export declare function isCompactRenderDocument(value: unknown): value is CompactRenderDocument;
|
package/dist/document-codec.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
function U(e){let t=new Map,o=[],r=new Map,u=[],g=e.pageComposition.status==="supported"?e.pageComposition.placementUnitKeys:[],l=new Map(g.map((i,s)=>[i,s])),n=e.pageComposition.status==="supported"?{status:"supported",containerKey:e.pageComposition.containerKey,hasBusinessMembers:e.pageComposition.hasBusinessMembers,placementUnitKeys:e.pageComposition.placementUnitKeys,placements:e.pageComposition.placements.map(i=>({placementKey:i.placementKey,placementUnitKey:i.placementUnitKey,relation:i.relation}))}:{status:"unsupported",reason:e.pageComposition.reason,diagnostics:e.pageComposition.diagnostics},a=e.entries.flatMap((i,s)=>i.dynamicDataSummary?[[s,i.dynamicDataSummary]]:[]),d=new Map(e.entries.map((i,s)=>[i.key,s])),c=[];for(let[i,s]of e.entries.entries()){let p=s.pageCompositionActionScope;if(!p)continue;if(p.kind!=="source-siblings"){c.push([i,{kind:p.kind}]);continue}let h=p.siblingKeys.map(I=>{let C=d.get(I);if(C===void 0)throw new Error("Render entry "+s.key+" references missing source sibling "+I);return C});c.push([i,{kind:"source-siblings",siblingEntryIndexes:h}])}let y=i=>{let s=t.get(i);if(typeof s=="number")return s;let p=o.length;return o.push(i),t.set(i,p),p},f=i=>{let s=null;for(let p of v(i)){let h=`${s??"root"}\0${p}`,I=r.get(h);if(typeof I=="number"){s=I;continue}let C=u.length;u.push([s,y(p)]),r.set(h,C),s=C}if(s===null)throw new Error("Document key must not be empty");return s};return{encoding:"segment-trie-v1",siteId:e.siteId,snapshotId:e.snapshotId,composition:{...e.composition},routeId:e.composition.routeId,version:e.version,pageComposition:n,tokens:o,nodes:u,entries:e.entries.map(i=>{let s=i.pageCompositionUnitKey?l.get(i.pageCompositionUnitKey)??-1:-1;if(i.pageCompositionUnitKey&&s<0)throw new Error(`Render entry ${i.key} references unknown page composition unit ${i.pageCompositionUnitKey}`);return[f(i.key),K(i.operationSummary),...s>=0?[s]:[]]}),...c.length>0?{pageCompositionActionScopes:c}:{},...e.entries.every(i=>i.parentKey!==void 0)?{entryParentNodeIds:e.entries.map(i=>i.parentKey===null?null:f(i.parentKey))}:{},...a.length>0?{dynamicDataSummaries:a}:{}}}function k(e){if(!x(e)){if(e.encoding==="segment-trie-v1")throw new Error("Invalid compact render document");return e}let t=new Map,o=e.pageComposition.status==="supported"?e.pageComposition.placementUnitKeys:[],r=new Map(e.dynamicDataSummaries??[]),u=new Map(e.pageCompositionActionScopes??[]),g=l=>{let n=t.get(l);if(n)return n;let a=e.nodes[l];if(!a)throw new Error(`Missing compact document node ${l}`);let[d,c]=a,y=e.tokens[c];if(typeof y!="string")throw new Error(`Missing compact document token ${c}`);let f=`${d===null?"":g(d)}${y}`;return t.set(l,f),f};return{siteId:e.siteId,snapshotId:e.snapshotId,composition:{...e.composition},routeId:e.composition.routeId,version:e.version,pageComposition:e.pageComposition,entries:e.entries.map((l,n)=>{let[a,d,c]=l,y=c===void 0?void 0:o[c];if(c!==void 0&&!y)throw new Error(`Missing page composition unit ${c}`);let f=e.entryParentNodeIds?.[n],i=u.get(n),s=i?.kind==="source-siblings"?{kind:"source-siblings",siblingKeys:i.siblingEntryIndexes.map(p=>{let h=e.entries[p];if(!h)throw new Error(`Missing source sibling entry ${p}`);return g(h[0])})}:i;return{key:g(a),...e.entryParentNodeIds?{parentKey:f===null?null:g(f)}:{},...y?{pageCompositionUnitKey:y}:{},...s?{pageCompositionActionScope:s}:{},operationSummary:w(d),...r.get(n)?{dynamicDataSummary:r.get(n)}:{}}})}}function K(e){return(e.canUpdateText?1:0)|(e.canInsertChild?2:0)|(e.canMove?4:0)|(e.canRemove?8:0)}function w(e){return{canUpdateText:(e&1)!==0,canInsertChild:(e&2)!==0,canMove:(e&4)!==0,canRemove:(e&8)!==0}}function x(e){if(typeof e!="object"||e===null)return!1;let t=e;if(t.encoding!=="segment-trie-v1"||typeof t.siteId!="string"||typeof t.snapshotId!="string"||!S(t.composition)||typeof t.routeId!="string"||t.routeId!==t.composition.routeId||!Number.isInteger(t.version)||t.version<0||!A(t.pageComposition)||!Array.isArray(t.tokens)||!t.tokens.every(n=>typeof n=="string")||!Array.isArray(t.nodes)||!Array.isArray(t.entries))return!1;let o=t.nodes;if(!o.every((n,a)=>Array.isArray(n)&&n.length===2&&(n[0]===null||m(n[0])&&n[0]<a)&&m(n[1])&&n[1]<t.tokens.length)||!t.entries.every(n=>Array.isArray(n)&&(n.length===2||n.length===3)&&m(n[0])&&n[0]<o.length&&m(n[1])&&n[1]<=15&&(n.length===2||m(n[2]))))return!1;let r=t.entryParentNodeIds;if(r!==void 0&&(!Array.isArray(r)||r.length!==t.entries.length||!r.every(n=>n===null||m(n)&&n<o.length)))return!1;let u=t.dynamicDataSummaries,g=t.pageCompositionActionScopes;return(g===void 0||Array.isArray(g)&&g.every(n=>{if(!Array.isArray(n)||n.length!==2||!m(n[0])||n[0]>=t.entries.length||!A(n[1])||!["route-unit","source-siblings","unavailable"].includes(String(n[1].kind)))return!1;let a=n[1];return a.kind!=="source-siblings"||Array.isArray(a.siblingEntryIndexes)&&a.siblingEntryIndexes.length>=2&&a.siblingEntryIndexes.every(d=>m(d)&&d<t.entries.length)&&a.siblingEntryIndexes.includes(n[0])}))&&(u===void 0||Array.isArray(u)&&u.every(n=>Array.isArray(n)&&n.length===2&&m(n[0])&&n[0]<t.entries.length&&A(n[1])))}function A(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function S(e){return!A(e)||typeof e.routeId!="string"?!1:e.effectiveTemplateId===void 0||typeof e.effectiveTemplateId=="string"}function m(e){return typeof e=="number"&&Number.isInteger(e)&&e>=0}function v(e){if(!e)return[];let t=[],o=0,r=b(e,o);if(r<0)return t.push(e),t;for(t.push(e.slice(0,r)),o=r;o<e.length;){if(r=b(e,o+(e.startsWith("::",o)?2:1)),r<0){t.push(e.slice(o));break}t.push(e.slice(o,r)),o=r}return t}function b(e,t){let o=e.indexOf("/",t),r=e.indexOf("::",t);return o<0?r:r<0?o:Math.min(o,r)}export{k as decodeRenderDocument,U as encodeRenderDocument,x as isCompactRenderDocument};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
1
3
|
import { Worker } from "node:worker_threads";
|
|
2
4
|
import { SITE_EDIT_ENGINE_WORKER_METHODS, deserializeSiteEditEngineError, } from "./engine-worker-messages.js";
|
|
3
5
|
import { attachSiteEditWebSocketServer, normalizeSiteEditBridgeServerOptions, } from "./server.js";
|
|
@@ -114,7 +116,8 @@ export function attachSiteEditBridgeServerInWorker(server, options, dependencies
|
|
|
114
116
|
};
|
|
115
117
|
const spawnWorker = () => {
|
|
116
118
|
const workerGeneration = (generation += 1);
|
|
117
|
-
const
|
|
119
|
+
const workerUrl = dependencies.workerUrl ?? new URL("./engine-worker.js", import.meta.url);
|
|
120
|
+
const next = new Worker(resolveWorkerEntrypoint(workerUrl), {
|
|
118
121
|
workerData,
|
|
119
122
|
resourceLimits: { maxOldGenerationSizeMb: maxHeapMb },
|
|
120
123
|
...(dependencies.execArgv ? { execArgv: dependencies.execArgv } : {}),
|
|
@@ -248,6 +251,22 @@ export function attachSiteEditBridgeServerInWorker(server, options, dependencies
|
|
|
248
251
|
eventListeners: listeners,
|
|
249
252
|
};
|
|
250
253
|
}
|
|
254
|
+
function resolveWorkerEntrypoint(workerUrl) {
|
|
255
|
+
if (!workerUrl.pathname.endsWith(".ts")) {
|
|
256
|
+
return workerUrl;
|
|
257
|
+
}
|
|
258
|
+
// Node 24 chooses its built-in strip-only loader before worker execArgv
|
|
259
|
+
// preloads run. A .ts worker entry can therefore bypass `--import tsx` and
|
|
260
|
+
// fail on non-erasable syntax in its dependency graph. Start from a JS data
|
|
261
|
+
// module and let tsx's scoped API import the requested source entry after
|
|
262
|
+
// the worker is online. Built .js and custom .mjs workers remain direct.
|
|
263
|
+
const tsxApiUrl = pathToFileURL(createRequire(import.meta.url).resolve("tsx/esm/api")).href;
|
|
264
|
+
const launcherSource = [
|
|
265
|
+
`import { tsImport } from ${JSON.stringify(tsxApiUrl)};`,
|
|
266
|
+
`await tsImport(${JSON.stringify(workerUrl.href)}, ${JSON.stringify(workerUrl.href)});`,
|
|
267
|
+
].join("\n");
|
|
268
|
+
return new URL(`data:text/javascript,${encodeURIComponent(launcherSource)}`);
|
|
269
|
+
}
|
|
251
270
|
function readPositiveInteger(value) {
|
|
252
271
|
if (!value || !/^\d+$/.test(value.trim())) {
|
|
253
272
|
return undefined;
|
package/dist/engine-worker.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{parentPort as Ye,workerData as Ze}from"node:worker_threads";function T(t){let e=t&&typeof t=="object"?t:null,n=typeof e?.message=="string"&&e.message.length>0?e.message:String(t);return{name:typeof e?.name=="string"?e.name:"Error",message:n,...typeof e?.code=="string"&&e.code.length>0?{code:e.code}:{}}}import ne from"node:http";import v from"node:fs";import{createHash as se,randomBytes as Se,timingSafeEqual as we}from"node:crypto";import f from"node:path";import{WebSocket as ie,WebSocketServer as Ce}from"ws";import{createPreviewSiteEditEngineRuntime as Ee,openFileHistoryStore as Re,resolveManagedPageTemplateComposition as xe,scanSiteEditCompositionsFromFiles as ve}from"@vcp-dev/site-edit-engine";import{resolveNextAppRouteId as be,scanNextAppRouterRoutesFromFiles as ke}from"@vcp-dev/site-edit-engine/next-app-router";import{SiteEditSourceWatcher as _e}from"@vcp-dev/site-edit-engine/source-watcher";function J(t){let e=new Map,n=[],s=new Map,o=[],i=t.pageComposition.status==="supported"?t.pageComposition.placementUnitKeys:[],r=new Map(i.map((a,h)=>[a,h])),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},p=t.entries.flatMap((a,h)=>a.dynamicDataSummary?[[h,a.dynamicDataSummary]]:[]),u=a=>{let h=e.get(a);if(typeof h=="number")return h;let R=n.length;return n.push(a),e.set(a,R),R},m=a=>{let h=null;for(let R of de(a)){let j=`${h??"root"}\0${R}`,$=s.get(j);if(typeof $=="number"){h=$;continue}let z=o.length;o.push([h,u(R)]),s.set(j,z),h=z}if(h===null)throw new Error("Document key must not be empty");return h};return{encoding:"segment-trie-v1",siteId:t.siteId,snapshotId:t.snapshotId,composition:{...t.composition},routeId:t.composition.routeId,version:t.version,pageComposition:c,tokens:n,nodes:o,entries:t.entries.map(a=>{let h=a.pageCompositionUnitKey?r.get(a.pageCompositionUnitKey)??-1:-1;if(a.pageCompositionUnitKey&&h<0)throw new Error(`Render entry ${a.key} references unknown page composition unit ${a.pageCompositionUnitKey}`);return[m(a.key),ue(a.operationSummary),...h>=0?[h]:[]]}),...t.entries.every(a=>a.parentKey!==void 0)?{entryParentNodeIds:t.entries.map(a=>a.parentKey===null?null:m(a.parentKey))}:{},...p.length>0?{dynamicDataSummaries:p}:{}}}function ue(t){return(t.canUpdateText?1:0)|(t.canInsertChild?2:0)|(t.canMove?4:0)|(t.canRemove?8:0)}function de(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 Q="SITE_EDIT_COMPOSITION_SESSION_MISMATCH",F="SITE_EDIT_COMPOSITION_SESSION_REQUIRED";var l=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(d(e)&&d(e.payload)){let n=typeof e.payload.id=="string"?e.payload.id:"unknown",s=this.isRequestKind(e.payload.kind)?e.payload.kind:"get-document";return{id:n,kind:s}}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!d(e)||typeof e.type!="string"?!1:e.type==="request"?he(e.payload,n):e.type==="response"||e.type==="error"?d(e.payload)&&typeof e.payload.id=="string":e.type==="event"?d(e.payload)&&d(e.payload.event):e.type==="auth"?d(e.payload)&&typeof e.payload.key=="string":e.type==="auth-ok"&&d(e.payload)&&e.payload.ok===!0}},k=class{constructor(e){this.options=e,this.session={subscribedCompositionKeys:new Set},this.messageQueue=new N,this.eventDispatcher=new A(n=>{this.sendJson(n)}),this.unsubscribe=e.engine.subscribe(n=>{this.handleEngineEvent(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(),this.session.activeComposition=void 0,this.session.subscribedCompositionKeys.clear()}}async handleMessage(e){try{let n=e;if(n.type!=="request")return;let s=n.payload,{id:o}=s;switch(s.kind){case"initialize-session":{let i=O(s),r=await this.resolve(i),c=await this.options.engine.initializeSession({composition:r.context,...s.mutationContext?{mutationContext:s.mutationContext}:{}});this.activateSession(r.context),this.sendJson({type:"response",payload:{id:o,kind:s.kind,result:y({...c,data:{...c.data,document:Y(c.data.document)}},r.context)}});return}case"get-document":{let i=await this.resolveWireContext(s),r=await this.options.engine.getDocument(i);this.sendResult(o,s.kind,ye(r,i));return}case"get-history-status":{let i=await this.resolveWireContext(s),r=await this.options.engine.getHistoryStatus(i);this.sendResult(o,s.kind,y(r,i));return}case"get-style-context":{let i=await this.resolveWireContext(s);this.sendResult(o,s.kind,y(await this.options.engine.getStyleContext(i),i));return}case"get-object-edit-context":{let i=await this.resolveWireContext(s);this.sendResult(o,s.kind,y(await this.options.engine.getObjectEditContext(s.key,i,s.selectionRef),i));return}case"get-class-name-source":{let i=await this.resolveWireContext(s);this.sendResult(o,s.kind,y(await this.options.engine.getClassNameSource(s.key,i),i));return}case"get-source-tree":{let i=await this.resolveWireContext(s);this.sendResult(o,s.kind,y(await this.options.engine.getSourceTree(i),i));return}case"get-source-file":{let i=await this.resolveWireContext(s);this.sendResult(o,s.kind,y(await this.options.engine.getSourceFile({path:s.path,composition:i}),i));return}case"resolve-source-location":{let i=await this.resolveWireContext(s),r=y(await this.options.engine.resolveSourceLocation({key:s.key,composition:i}),i);this.sendResult(o,s.kind,r);return}case"execute":{let i=await this.resolveOperationWireContext(s.request),r=await this.resolveWireContext(s);if(I(i)!==I(r))throw new S(Q,"The execute request composition does not match its operation composition.");let c=await this.normalizeOperationRequest(s.request,i);await this.handleBufferedOperation(o,s.kind,()=>this.options.engine.execute(c));return}case"undo":case"redo":{let i=await this.resolveWireContext(s);await this.handleBufferedOperation(o,s.kind,()=>s.kind==="undo"?this.options.engine.undo(i):this.options.engine.redo(i));return}}}catch(n){let s=l.extractRequestInfo(e);this.sendJson({type:"error",payload:{...s,message:M(n),code:D(n)}})}}async resolve(e){return this.options.engine.resolveComposition(e)}async resolveAndAssert(e){let n=await this.resolve(e),s=this.session.activeComposition;if(!s)throw new S(F,"Initialize a composition session before issuing this request.");if(I(s)!==I(n.context))throw new S(Q,"The request composition does not match the active socket session.");return n.context}async resolveWireContext(e){let n=O(e);return this.resolveAndAssert(n)}async resolveOperationWireContext(e){let n=me(e);return this.resolveAndAssert(n)}activateSession(e){this.session.activeComposition=e,this.session.subscribedCompositionKeys.clear(),this.session.subscribedCompositionKeys.add(I(e))}async normalizeOperationRequest(e,n){let s={...e,composition:G(n)};if(s.target.kind==="route"){let o=s.target,i=O(o),r=await this.resolveAndAssert(i);s.target={...o,composition:G(r)}}return s}handleEngineEvent(e){let n=e?.composition;if(!fe(n))return;let s=I(n);this.session.subscribedCompositionKeys.has(s)&&(this.eventDispatcher.push(e),e.type==="site-edit.graph.invalidated"&&s===ge(this.session)&&(this.session.activeComposition=void 0,this.session.subscribedCompositionKeys.delete(s)))}sendResult(e,n,s){this.sendJson({type:"response",payload:{id:e,kind:n,result:s}})}async handleRawMessage(e){try{await this.handleMessage(l.parseMessage(e,{validateOperationRequest:this.options.validateOperationRequest}))}catch(n){let s=Ie(e);this.sendJson({type:"error",payload:{...s,message:M(n),code:D(n)}})}}sendJson(e){this.options.socket.send(l.stringifyMessage(e))}async handleBufferedOperation(e,n,s){this.eventDispatcher.beginBuffering();try{let o=await s();this.sendResult(e,n,o)}catch(o){this.sendJson({type:"error",payload:{id:e,kind:n,message:M(o),code:D(o)}})}finally{this.eventDispatcher.flush()}}},S=class extends Error{constructor(e,n){super(n),this.code=e,this.name="BridgeProtocolError"}},A=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))}},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()},s=this.queue.then(n,n);return this.queue=s.then(()=>{},()=>{}),s}};function he(t,e){if(!d(t)||typeof t.id!="string"||!l.isRequestKind(t.kind))return!1;let n=x(t.composition);switch(t.kind){case"initialize-session":return n&&(t.mutationContext===void 0||H(t.mutationContext));case"get-document":case"get-style-context":return n;case"get-object-edit-context":return typeof t.key=="string"&&n&&pe(t.selectionRef);case"get-class-name-source":case"resolve-source-location":return typeof t.key=="string"&&n;case"get-history-status":return n;case"get-source-tree":return n;case"get-source-file":return typeof t.path=="string"&&n;case"execute":return n&&le(t.request)&&(t.request.mutationContext===void 0||H(t.request.mutationContext))&&(e.validateOperationRequest?.(t.request)??!0);case"undo":case"redo":return n&&t.mutationContext===void 0||n&&H(t.mutationContext)}}function pe(t){if(t===void 0)return!0;if(!d(t)||typeof t.key!="string"||t.key.length===0)return!1;let e=t.instanceIndex,n=t.instanceCount;return e!==void 0&&(typeof e!="number"||!Number.isInteger(e)||e<0)||n!==void 0&&(typeof n!="number"||!Number.isInteger(n)||n<=0)||typeof e=="number"&&typeof n=="number"&&e>=n?!1:Object.keys(t).every(s=>s==="key"||s==="instanceIndex"||s==="instanceCount")}function le(t){return d(t)&&typeof t.id=="string"&&typeof t.siteId=="string"&&typeof t.baseSnapshotId=="string"&&x(t.composition)&&typeof t.kind=="string"&&d(t.target)&&(t.target.kind!=="route"||x(t.target.composition))&&typeof t.documentVersion=="number"&&Number.isInteger(t.documentVersion)&&t.documentVersion>=0&&d(t.params)}function x(t){return d(t)&&typeof t.routeId=="string"&&t.routeId.length>0&&(t.templateId===void 0||typeof t.templateId=="string"&&t.templateId.length>0)&&Object.keys(t).every(e=>e==="routeId"||e==="templateId")}function fe(t){return d(t)&&typeof t.routeId=="string"&&(t.effectiveTemplateId===void 0||typeof t.effectiveTemplateId=="string")}function O(t){if(x(t.composition))return t.composition;throw new S(F,"A composition selector is required for this request.")}function me(t){if(d(t)&&x(t.composition))return t.composition;throw new S(F,"A composition selector is required for execute.")}function G(t){return{routeId:t.routeId,...t.effectiveTemplateId?{templateId:t.effectiveTemplateId}:{}}}function ge(t){return t.activeComposition?I(t.activeComposition):null}function I(t){return JSON.stringify([t.routeId,t.effectiveTemplateId??null])}function y(t,e){return d(t)&&t.ok===!0?{...t,composition:e}:t}function ye(t,e){return!d(t)||t.ok!==!0?t:{...t,...e?{composition:e}:{},data:Y(t.data)}}function Y(t){let e=t;return J(e.routeId?e:{...e,routeId:e.composition.routeId})}function d(t){return typeof t=="object"&&t!==null}function H(t){return d(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}function D(t){if(!(!d(t)||typeof t.code!="string"||t.code.length===0))return t.code}function M(t){return t instanceof Error||d(t)&&typeof t.message=="string"&&t.message.length>0?t.message:"Unknown websocket operation failure"}function Ie(t){try{return l.extractRequestInfo(JSON.parse(t))}catch{return{id:"unknown",kind:"get-document"}}}var _=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,computeSiteEditCompositionKey as w}from"@vcp-dev/contracts";var Oe="127.0.0.1",He=30218,De="/__vcp/site-edit/bridge",oe=6*1024*1024,Me=oe*2,Ae=3e3,Z=50,P=".vcp/current-snapshot.json",X="__VCP_SITE_EDIT_BRIDGE_SERVERS__";var V=class{constructor(e,n=ne.createServer((i,r)=>{r.writeHead(404),r.end("VCP site edit bridge server")}),s=!0,o={}){this.options=e,this.server=n,this.closeHttpServerOnStop=s,this.dependencies=o,this.runtime=null,this.activeHistorySessionId=null,this.historySessionTransition=Promise.resolve(),this.mutationQueue=new _,this.eventListeners=new Set,this.routes=[],this.compositions=[],this.compositionFiles=[],this.compositionResolutionError=null,this.launchCandidateEntries=null,this.stopWatching=null,this.stopWebSocketServer=null,this.pendingChangedFiles=new Set,this.fileChangeTimer=null,this.isStarted=!1,this.stopping=!1,this.runtimeVersionFloors=new Map,this.activeHistoryStore=null,this.versionPersistence=Promise.resolve(),this.bridgeEngine=null,this.lastNotifiedHealth=null}start(e={}){if(!this.isStarted){this.stopping=!1;try{v.mkdirSync(f.join(this.options.projectRoot,".vcp"),{recursive:!0})}catch(n){console.warn(`[vcp-site-edit] failed to ensure .vcp directory: ${n instanceof Error?n.message:String(n)}`)}if(this.refreshCompositions(!0),this.isStarted=!0,this.createRuntime(),this.bridgeEngine=this.createBridgeEngine(),e.webSocket!==!1){let n=Ue({server:this.server,path:this.options.path,sessionKey:this.options.sessionKey,host:this.options.host,allowedOrigins:this.options.allowedOrigins,engine:this.bridgeEngine});this.stopWebSocketServer=n.stop}this.stopWatching=new _e({projectRoot:this.options.projectRoot,watch_dirs:Fe(this.options.runtime.watch_dirs),interval_ms:this.options.runtime.watch_interval_ms,debounce_ms:this.options.runtime.watch_debounce_ms??Z,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.notifyHealthChanged()}}getEngine(){if(!this.bridgeEngine)throw new Error("VCP site edit preview bridge runtime is not ready");return this.bridgeEngine}getHealth(){let e=!!this.runtime&&!this.compositionResolutionError;return{ok:this.isStarted&&e,bridgePath:this.options.path,runtimeReady:e,routesIndexed:this.routes.length,sessionRequired:!!this.options.sessionKey}}stop(){this.stopping=!0,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.compositions=[],this.compositionFiles=[],this.compositionResolutionError=null,this.closeHttpServerOnStop&&this.server.close();let e=Ve(),n=qe(this.options);e.get(n)===this&&e.delete(n),this.isStarted=!1,this.notifyHealthChanged()}notifyHealthChanged(){let e=this.options.onHealthChanged;if(!e)return;let n=this.getHealth(),s=JSON.stringify(n);if(s!==this.lastNotifiedHealth){this.lastNotifiedHealth=s;try{e(n)}catch(o){console.warn(`[vcp-site-edit] onHealthChanged callback failed: ${o instanceof Error?o.message:String(o)}`)}}}createRuntime(e,n=null){let s=this.runtime;this.activeHistoryStore=e??null;let o=(this.dependencies.createRuntime??Ee)({...this.options.tenantId?{tenantId:this.options.tenantId}:{},siteId:this.options.siteId,snapshotId:this.options.snapshotId,projectRoot:this.options.projectRoot,compositions:this.compositions,onEvent:i=>{"version"in i&&typeof i.version=="number"&&this.recordRuntimeVersion(i.version,"composition"in i?i.composition:void 0);for(let r of this.eventListeners)r(i)},...e?{historyStore:e}:{},...this.getInitialDocumentVersions().length>0?{initialDocumentVersions:this.getInitialDocumentVersions()}:{}});this.runtime=o,this.activeHistorySessionId=n,s?.dispose(),this.notifyHealthChanged()}createBridgeEngine(){return{resolveComposition:e=>this.resolveComposition(e),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 s=Ke(e.composition),o=this.getRuntime(),i=g(s);await o.initializeComposition(i);let c=o.getDocument(i).data;await this.recordRuntimeVersion(c.version,s);let p=(await o.getHistoryStatus()).data;return{ok:!0,composition:s,data:{document:c,historyStatus:p}}}),getDocument:async e=>{let n=this.getRuntime(),s=g(e);await n.initializeComposition(s);let o=n.getDocument(s);return await this.recordRuntimeVersion(o.data.version,e),{...o,composition:e}},getHistoryStatus:async e=>({...await this.getRuntime().getHistoryStatus(),composition:e}),getStyleContext:async e=>this.getRuntime().getStyleContext(g(e)),getObjectEditContext:async(e,n,s)=>this.getRuntime().getObjectEditContext(e,g(n),s),getClassNameSource:async(e,n)=>this.getRuntime().getClassNameSource(e,g(n)),getSourceTree:async e=>this.getRuntime().getSourceTree(g(e)),getSourceFile:async e=>this.getRuntime().getSourceFile({path:e.path,composition:g(e.composition)}),resolveSourceLocation:async e=>this.getRuntime().resolveSourceLocation({key:e.key,composition:g(e.composition)}),execute:(e,n)=>this.mutationQueue.run(async()=>{let s=await this.bindHistorySession(this.options.snapshotId);if(!s.ok)return B(s,e);let o=await this.getRuntime().executeOperation(e);return o.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(o.data.resultVersion,o.composition),o}),undo:e=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)return B(n,void 0,e);let s=await this.getRuntime().undo();return s.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(s.data.resultVersion,s.composition??e),s.composition?s:{...s,composition:e}}),redo:e=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)return B(n,void 0,e);let s=await this.getRuntime().redo();return s.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(s.data.resultVersion,s.composition??e),s.composition?s:{...s,composition:e}}),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??Re)(Pe({root:this.options.historyRoot,siteId:this.options.siteId,snapshotId:this.options.snapshotId,editingSessionId:e}));if(!this.isStarted)return;let o=s.readDocumentVersion?await s.readDocumentVersion():0,i=this.compositions[0]?.context;if(i){let r=w(i);this.runtimeVersionFloors.set(r,Math.max(this.runtimeVersionFloors.get(r)??0,o))}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,n){let s=n?w(n):null;s&&this.runtimeVersionFloors.set(s,Math.max(this.runtimeVersionFloors.get(s)??0,e));let o=this.activeHistoryStore;if(!o?.writeDocumentVersion)return Promise.resolve();let i=s?this.runtimeVersionFloors.get(s)??e:e;return this.versionPersistence=this.versionPersistence.then(()=>o.writeDocumentVersion(i)).catch(()=>{}),this.versionPersistence}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??Z;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));if(this.pendingChangedFiles.clear(),e.some(Ge)&&this.scheduleSnapshotIdentityAdvance(),!!this.refreshCompositions(!1))for(let n of e)this.runtime?.notifyFileChanged(n)}scheduleSnapshotIdentityAdvance(){if(this.stopping||!this.isStarted)return;let e=this.readCurrentSnapshotMarker();!e||e===this.options.snapshotId||this.mutationQueue.run(()=>this.advanceSnapshotId(e)).catch(n=>{console.warn(`[vcp-site-edit] snapshot identity advance failed: ${n instanceof Error?n.message:String(n)}`)})}readCurrentSnapshotMarker(){try{let e=v.readFileSync(f.join(this.options.projectRoot,P),"utf8"),n=JSON.parse(e),s=typeof n.snapshot_id=="string"?n.snapshot_id:"";return!s||q(s)!==s?(console.warn("[vcp-site-edit] current-snapshot marker has no valid snapshot_id"),null):s}catch(e){return console.warn(`[vcp-site-edit] current-snapshot marker unreadable: ${e instanceof Error?e.message:String(e)}`),null}}async advanceSnapshotId(e){if(this.stopping||!this.isStarted||e===this.options.snapshotId)return;let n=this.options.snapshotId;this.options.snapshotId=e;try{if(this.options.historyRoot){let o=await this.bindHistorySession(e);if(!o.ok)throw new Error(`${o.code}: ${o.message}`)}else this.createRuntime()}catch(o){this.options.snapshotId=n,console.warn(`[vcp-site-edit] snapshot identity advance rolled back to ${n}: ${o instanceof Error?o.message:String(o)}`);return}let s=new Date().toISOString();for(let o of this.compositions){let i={type:"site-edit.snapshot.changed",siteId:this.options.siteId,composition:o.context,snapshotId:e,previousSnapshotId:n,at:s};for(let r of this.eventListeners)try{r(i)}catch(c){console.warn(`[vcp-site-edit] snapshot.changed listener failed: ${c instanceof Error?c.message:String(c)}`)}}try{this.options.onSnapshotIdChanged?.(e,n)}catch(o){console.warn(`[vcp-site-edit] onSnapshotIdChanged callback failed: ${o instanceof Error?o.message:String(o)}`)}}refreshCompositions(e=!1){try{let n=Be(this.options.projectRoot),s=ke(n),o=s.length>0?s:(this.options.runtime.routes??[]).map(u=>({...u})),i=ve({files:n});if(i.length===0)throw new Error("No canonical App Router composition is indexed by the preview bridge.");let r=JSON.stringify(i.filter(u=>u.context.effectiveTemplateId!==void 0).map(u=>u.entryFile).sort());if(e&&(this.launchCandidateEntries=r),this.launchCandidateEntries!==r)throw new Error("SITE_EDIT_RUNTIME_RELOAD_REQUIRED: Candidate route collection changed since preview launch");let c=new Map(this.compositions.map(u=>[w(u.context),u])),p=new Map(i.map(u=>[w(u.context),u]));if(this.runtime){for(let[u,m]of c){let a=p.get(u);(!a||a.entryFile!==m.entryFile)&&this.runtime.unregisterComposition(g(m.context))}for(let[u,m]of p){let a=c.get(u);(!a||a.entryFile!==m.entryFile)&&this.runtime.registerComposition(m.context,m.entryFile)}}return this.routes=o,this.compositions=i,this.compositionFiles=n,this.compositionResolutionError=null,this.notifyHealthChanged(),!0}catch(n){let s=n instanceof Error?n:new Error(String(n));if(this.compositionResolutionError=s,e)throw s;return this.notifyHealthChanged(),!1}}resolveComposition(e){if(this.compositionResolutionError)throw this.compositionResolutionError;let n=be(e.routeId,this.routes,this.options.basePath);if(!n)throw new Error(`Route is not indexed by the preview bridge: ${e.routeId}`);let s=this.routes.find(r=>r.routeId===n);if(!s)throw new Error(`Route is not indexed by the preview bridge: ${e.routeId}`);let o=xe({selector:{routeId:n,...e.templateId!==void 0?{templateId:e.templateId}:{}},files:this.compositionFiles,canonicalEntryFile:s.entryFile}),i=w(o.context);if(!this.compositions.some(r=>w(r.context)===i))throw new Error(`Composition is not registered by the preview bridge: ${i}`);return o}getInitialDocumentVersions(){return this.compositions.flatMap(e=>{let n=this.runtimeVersionFloors.get(w(e.context));return n===void 0?[]:[{composition:e.context,version:n}]})}getRuntime(){if(!this.runtime)throw new Error("VCP site edit preview bridge runtime is not ready");return this.runtime}};function re(t){let e=new V(Ne(t),ne.createServer(),!1);return e.start({listen:!1,webSocket:!1}),{engine:e.getEngine(),getHealth:()=>e.getHealth(),stop:()=>e.stop()}}function Ne(t){return{tenantId:t.tenantId,siteId:t.siteId,snapshotId:t.snapshotId,projectRoot:f.resolve(t.projectRoot??process.cwd()),basePath:t.basePath??"",host:t.host??Oe,port:t.port??He,path:t.path??De,sessionKey:t.sessionKey??Qe(),historyRoot:t.historyRoot,allowedOrigins:t.allowedOrigins??[],runtime:t.runtime??{},onSnapshotIdChanged:t.onSnapshotIdChanged,onHealthChanged:t.onHealthChanged}}function Fe(t){let e=["app","src/app","components/site","src/components/site","lib/generated","src/lib/generated","public"];return[...new Set([...t?.length?t:e,".vcp"])]}function Be(t){let e=[],n=["app","src/app",".vcp"],s=new Set([".git",".next",".turbo","coverage","dist","node_modules","output","snapshot-store"]),o=i=>{if(!v.existsSync(i))return;let r;try{r=v.readdirSync(i,{withFileTypes:!0})}catch{return}for(let c of r){if(c.name.startsWith(".")&&c.name!==".vcp")continue;if(c.isDirectory()){if(s.has(c.name))continue;o(f.join(i,c.name));continue}if(!c.isFile())continue;let p=f.join(i,c.name),u="";try{u=v.readFileSync(p,"utf8")}catch{}e.push({path:f.relative(t,p).replaceAll(f.sep,"/"),content:u})}};for(let i of n)o(f.join(t,i));return e.sort((i,r)=>i.path.localeCompare(r.path))}function Ke(t){if(!t||typeof t!="object"||typeof t.routeId!="string")throw new Error("Normalized composition context is required.");if("effectiveTemplateId"in t)return t;let e=t;return{routeId:e.routeId,...e.templateId?{effectiveTemplateId:e.templateId}:{}}}function g(t){return{routeId:t.routeId,...t.effectiveTemplateId?{templateId:t.effectiveTemplateId}:{}}}function Pe(t){return f.join(f.resolve(t.root),q(t.siteId),q(t.snapshotId),se("sha256").update(t.editingSessionId).digest("hex").slice(0,32))}function q(t){return/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(t)&&t!=="."&&t!==".."?t:se("sha256").update(t).digest("hex").slice(0,32)}function Ve(){let t=globalThis;return t[X]??=new Map,t[X]}function qe(t){return`${t.projectRoot}:${t.host}:${t.port}:${t.path}:${t.historyRoot??""}`}function B(t,e,n){let s=n??(e?.composition?{routeId:e.composition.routeId,...e.composition.templateId?{effectiveTemplateId:e.composition.templateId}:{}}:void 0);if(!s)throw new Error("A normalized composition is required to construct a mutation result.");return{ok:!1,composition:s,error:{code:t.code,message:t.message},data:{requestId:e?.id??"mutation-context",kind:e?.kind??"update-text",composition:s,target:e?.target??{kind:"node",key:""},resultVersion:e?.documentVersion??0}}}function Ue(t){let e=new Ce({maxPayload:oe,noServer:!0}),n=new WeakSet,s=i=>{new k({socket:new U(i),engine:t.engine,validateOperationRequest:r=>Te.safeParse(r).success})},o=(i,r,c)=>{if(!We(i,t))return;let p=je(i,t);if(!p){r.write(`HTTP/1.1 401 Unauthorized\r
|
|
1
|
+
import{parentPort as Xe,workerData as et}from"node:worker_threads";function M(t){let e=t&&typeof t=="object"?t:null,n=typeof e?.message=="string"&&e.message.length>0?e.message:String(t);return{name:typeof e?.name=="string"?e.name:"Error",message:n,...typeof e?.code=="string"&&e.code.length>0?{code:e.code}:{}}}import se from"node:http";import _ from"node:fs";import{createHash as oe,randomBytes as Ce,timingSafeEqual as Ee}from"node:crypto";import m from"node:path";import{WebSocket as re,WebSocketServer as xe}from"ws";import{createPreviewSiteEditEngineRuntime as Re,openFileHistoryStore as be,resolveManagedPageTemplateComposition as ve,scanSiteEditCompositionsFromFiles as ke}from"@vcp-dev/site-edit-engine";import{resolveNextAppRouteId as _e,scanNextAppRouterRoutesFromFiles as Te}from"@vcp-dev/site-edit-engine/next-app-router";import{SiteEditSourceWatcher as Oe}from"@vcp-dev/site-edit-engine/source-watcher";function G(t){let e=new Map,n=[],i=new Map,o=[],s=t.pageComposition.status==="supported"?t.pageComposition.placementUnitKeys:[],r=new Map(s.map((a,d)=>[a,d])),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},p=t.entries.flatMap((a,d)=>a.dynamicDataSummary?[[d,a.dynamicDataSummary]]:[]),u=new Map(t.entries.map((a,d)=>[a.key,d])),l=[];for(let[a,d]of t.entries.entries()){let f=d.pageCompositionActionScope;if(!f)continue;if(f.kind!=="source-siblings"){l.push([a,{kind:f.kind}]);continue}let O=f.siblingKeys.map(b=>{let v=u.get(b);if(v===void 0)throw new Error("Render entry "+d.key+" references missing source sibling "+b);return v});l.push([a,{kind:"source-siblings",siblingEntryIndexes:O}])}let S=a=>{let d=e.get(a);if(typeof d=="number")return d;let f=n.length;return n.push(a),e.set(a,f),f},J=a=>{let d=null;for(let f of pe(a)){let O=`${d??"root"}\0${f}`,b=i.get(O);if(typeof b=="number"){d=b;continue}let v=o.length;o.push([d,S(f)]),i.set(O,v),d=v}if(d===null)throw new Error("Document key must not be empty");return d};return{encoding:"segment-trie-v1",siteId:t.siteId,snapshotId:t.snapshotId,composition:{...t.composition},routeId:t.composition.routeId,version:t.version,pageComposition:c,tokens:n,nodes:o,entries:t.entries.map(a=>{let d=a.pageCompositionUnitKey?r.get(a.pageCompositionUnitKey)??-1:-1;if(a.pageCompositionUnitKey&&d<0)throw new Error(`Render entry ${a.key} references unknown page composition unit ${a.pageCompositionUnitKey}`);return[J(a.key),he(a.operationSummary),...d>=0?[d]:[]]}),...l.length>0?{pageCompositionActionScopes:l}:{},...t.entries.every(a=>a.parentKey!==void 0)?{entryParentNodeIds:t.entries.map(a=>a.parentKey===null?null:J(a.parentKey))}:{},...p.length>0?{dynamicDataSummaries:p}:{}}}function he(t){return(t.canUpdateText?1:0)|(t.canInsertChild?2:0)|(t.canMove?4:0)|(t.canRemove?8:0)}function pe(t){if(!t)return[];let e=[],n=0,i=Q(t,n);if(i<0)return e.push(t),e;for(e.push(t.slice(0,i)),n=i;n<t.length;){if(i=Q(t,n+(t.startsWith("::",n)?2:1)),i<0){e.push(t.slice(n));break}e.push(t.slice(n,i)),n=i}return e}function Q(t,e){let n=t.indexOf("/",e),i=t.indexOf("::",e);return n<0?i:i<0?n:Math.min(n,i)}var Y="SITE_EDIT_COMPOSITION_SESSION_MISMATCH",V="SITE_EDIT_COMPOSITION_SESSION_REQUIRED";var g=class t{constructor(){}static parseMessage(e,n={}){let i=JSON.parse(e);if(!t.isMessage(i,n))throw new Error("Invalid site edit bridge message");return i}static stringifyMessage(e){return JSON.stringify(e)}static extractRequestInfo(e){if(h(e)&&h(e.payload)){let n=typeof e.payload.id=="string"?e.payload.id:"unknown",i=this.isRequestKind(e.payload.kind)?e.payload.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,n={}){return!h(e)||typeof e.type!="string"?!1:e.type==="request"?le(e.payload,n):e.type==="response"||e.type==="error"?h(e.payload)&&typeof e.payload.id=="string":e.type==="event"?h(e.payload)&&h(e.payload.event):e.type==="auth"?h(e.payload)&&typeof e.payload.key=="string":e.type==="auth-ok"&&h(e.payload)&&e.payload.ok===!0}},A=class{constructor(e){this.options=e,this.session={subscribedCompositionKeys:new Set},this.messageQueue=new P,this.eventDispatcher=new B(n=>{this.sendJson(n)}),this.unsubscribe=e.engine.subscribe(n=>{this.handleEngineEvent(n)}),e.socket.onmessage=n=>{try{this.messageQueue.enqueue(()=>this.handleRawMessage(n.data))}catch(i){this.sendJson({type:"error",payload:{id:"unknown",kind:"get-document",message:i instanceof Error?i.message:"Bridge queue overflow"}}),e.socket.close(1013,"bridge busy")}},e.socket.onclose=()=>{this.unsubscribe(),this.session.activeComposition=void 0,this.session.subscribedCompositionKeys.clear()}}async handleMessage(e){try{let n=e;if(n.type!=="request")return;let i=n.payload,{id:o}=i;switch(i.kind){case"initialize-session":{let s=D(i),r=await this.resolve(s),c=await this.options.engine.initializeSession({composition:r.context,...i.mutationContext?{mutationContext:i.mutationContext}:{}});this.activateSession(r.context),this.sendJson({type:"response",payload:{id:o,kind:i.kind,result:I({...c,data:{...c.data,document:X(c.data.document)}},r.context)}});return}case"get-document":{let s=await this.resolveWireContext(i),r=await this.options.engine.getDocument(s);this.sendResult(o,i.kind,Se(r,s));return}case"get-history-status":{let s=await this.resolveWireContext(i),r=await this.options.engine.getHistoryStatus(s);this.sendResult(o,i.kind,I(r,s));return}case"get-style-context":{let s=await this.resolveWireContext(i);this.sendResult(o,i.kind,I(await this.options.engine.getStyleContext(s),s));return}case"get-object-edit-context":{let s=await this.resolveWireContext(i);this.sendResult(o,i.kind,I(await this.options.engine.getObjectEditContext(i.key,s,i.selectionRef),s));return}case"get-class-name-source":{let s=await this.resolveWireContext(i);this.sendResult(o,i.kind,I(await this.options.engine.getClassNameSource(i.key,s),s));return}case"get-source-tree":{let s=await this.resolveWireContext(i);this.sendResult(o,i.kind,I(await this.options.engine.getSourceTree(s),s));return}case"get-source-file":{let s=await this.resolveWireContext(i);this.sendResult(o,i.kind,I(await this.options.engine.getSourceFile({path:i.path,composition:s}),s));return}case"resolve-source-location":{let s=await this.resolveWireContext(i),r=I(await this.options.engine.resolveSourceLocation({key:i.key,composition:s}),s);this.sendResult(o,i.kind,r);return}case"execute":{let s=await this.resolveOperationWireContext(i.request),r=await this.resolveWireContext(i);if(w(s)!==w(r))throw new C(Y,"The execute request composition does not match its operation composition.");let c=await this.normalizeOperationRequest(i.request,s);await this.handleBufferedOperation(o,i.kind,()=>this.options.engine.execute(c));return}case"undo":case"redo":{let s=await this.resolveWireContext(i);await this.handleBufferedOperation(o,i.kind,()=>i.kind==="undo"?this.options.engine.undo(s):this.options.engine.redo(s));return}}}catch(n){let i=g.extractRequestInfo(e);this.sendJson({type:"error",payload:{...i,message:F(n),code:N(n)}})}}async resolve(e){return this.options.engine.resolveComposition(e)}async resolveAndAssert(e){let n=await this.resolve(e),i=this.session.activeComposition;if(!i)throw new C(V,"Initialize a composition session before issuing this request.");if(w(i)!==w(n.context))throw new C(Y,"The request composition does not match the active socket session.");return n.context}async resolveWireContext(e){let n=D(e);return this.resolveAndAssert(n)}async resolveOperationWireContext(e){let n=ye(e);return this.resolveAndAssert(n)}activateSession(e){this.session.activeComposition=e,this.session.subscribedCompositionKeys.clear(),this.session.subscribedCompositionKeys.add(w(e))}async normalizeOperationRequest(e,n){let i={...e,composition:Z(n)};if(i.target.kind==="route"){let o=i.target,s=D(o),r=await this.resolveAndAssert(s);i.target={...o,composition:Z(r)}}return i}handleEngineEvent(e){let n=e?.composition;if(!me(n))return;let i=w(n);this.session.subscribedCompositionKeys.has(i)&&(this.eventDispatcher.push(e),e.type==="site-edit.graph.invalidated"&&i===Ie(this.session)&&(this.session.activeComposition=void 0,this.session.subscribedCompositionKeys.delete(i)))}sendResult(e,n,i){this.sendJson({type:"response",payload:{id:e,kind:n,result:i}})}async handleRawMessage(e){try{await this.handleMessage(g.parseMessage(e,{validateOperationRequest:this.options.validateOperationRequest}))}catch(n){let i=we(e);this.sendJson({type:"error",payload:{...i,message:F(n),code:N(n)}})}}sendJson(e){this.options.socket.send(g.stringifyMessage(e))}async handleBufferedOperation(e,n,i){this.eventDispatcher.beginBuffering();try{let o=await i();this.sendResult(e,n,o)}catch(o){this.sendJson({type:"error",payload:{id:e,kind:n,message:F(o),code:N(o)}})}finally{this.eventDispatcher.flush()}}},C=class extends Error{constructor(e,n){super(n),this.code=e,this.name="BridgeProtocolError"}},B=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))}},P=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()},i=this.queue.then(n,n);return this.queue=i.then(()=>{},()=>{}),i}};function le(t,e){if(!h(t)||typeof t.id!="string"||!g.isRequestKind(t.kind))return!1;let n=k(t.composition);switch(t.kind){case"initialize-session":return n&&(t.mutationContext===void 0||K(t.mutationContext));case"get-document":case"get-style-context":return n;case"get-object-edit-context":return typeof t.key=="string"&&n&&fe(t.selectionRef);case"get-class-name-source":case"resolve-source-location":return typeof t.key=="string"&&n;case"get-history-status":return n;case"get-source-tree":return n;case"get-source-file":return typeof t.path=="string"&&n;case"execute":return n&&ge(t.request)&&(t.request.mutationContext===void 0||K(t.request.mutationContext))&&(e.validateOperationRequest?.(t.request)??!0);case"undo":case"redo":return n&&t.mutationContext===void 0||n&&K(t.mutationContext)}}function fe(t){if(t===void 0)return!0;if(!h(t)||typeof t.key!="string"||t.key.length===0)return!1;let e=t.instanceIndex,n=t.instanceCount;return e!==void 0&&(typeof e!="number"||!Number.isInteger(e)||e<0)||n!==void 0&&(typeof n!="number"||!Number.isInteger(n)||n<=0)||typeof e=="number"&&typeof n=="number"&&e>=n?!1:Object.keys(t).every(i=>i==="key"||i==="instanceIndex"||i==="instanceCount")}function ge(t){return h(t)&&typeof t.id=="string"&&typeof t.siteId=="string"&&typeof t.baseSnapshotId=="string"&&k(t.composition)&&typeof t.kind=="string"&&h(t.target)&&(t.target.kind!=="route"||k(t.target.composition))&&typeof t.documentVersion=="number"&&Number.isInteger(t.documentVersion)&&t.documentVersion>=0&&h(t.params)}function k(t){return h(t)&&typeof t.routeId=="string"&&t.routeId.length>0&&(t.templateId===void 0||typeof t.templateId=="string"&&t.templateId.length>0)&&Object.keys(t).every(e=>e==="routeId"||e==="templateId")}function me(t){return h(t)&&typeof t.routeId=="string"&&(t.effectiveTemplateId===void 0||typeof t.effectiveTemplateId=="string")}function D(t){if(k(t.composition))return t.composition;throw new C(V,"A composition selector is required for this request.")}function ye(t){if(h(t)&&k(t.composition))return t.composition;throw new C(V,"A composition selector is required for execute.")}function Z(t){return{routeId:t.routeId,...t.effectiveTemplateId?{templateId:t.effectiveTemplateId}:{}}}function Ie(t){return t.activeComposition?w(t.activeComposition):null}function w(t){return JSON.stringify([t.routeId,t.effectiveTemplateId??null])}function I(t,e){return h(t)&&t.ok===!0?{...t,composition:e}:t}function Se(t,e){return!h(t)||t.ok!==!0?t:{...t,...e?{composition:e}:{},data:X(t.data)}}function X(t){let e=t;return G(e.routeId?e:{...e,routeId:e.composition.routeId})}function h(t){return typeof t=="object"&&t!==null}function K(t){return h(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}function N(t){if(!(!h(t)||typeof t.code!="string"||t.code.length===0))return t.code}function F(t){return t instanceof Error||h(t)&&typeof t.message=="string"&&t.message.length>0?t.message:"Unknown websocket operation failure"}function we(t){try{return g.extractRequestInfo(JSON.parse(t))}catch{return{id:"unknown",kind:"get-document"}}}var H=class{constructor(){this.tail=Promise.resolve()}run(e){let n=this.tail.then(e,e);return this.tail=n.then(()=>{},()=>{}),n}};import{SiteEditOperationRequestSchema as Ae,computeSiteEditCompositionKey as E}from"@vcp-dev/contracts";var He="127.0.0.1",Me=30218,De="/__vcp/site-edit/bridge",ae=6*1024*1024,Ke=ae*2,Ne=3e3,ee=50,W=".vcp/current-snapshot.json",te="__VCP_SITE_EDIT_BRIDGE_SERVERS__";var $=class{constructor(e,n=se.createServer((s,r)=>{r.writeHead(404),r.end("VCP site edit bridge server")}),i=!0,o={}){this.options=e,this.server=n,this.closeHttpServerOnStop=i,this.dependencies=o,this.runtime=null,this.activeHistorySessionId=null,this.historySessionTransition=Promise.resolve(),this.mutationQueue=new H,this.eventListeners=new Set,this.routes=[],this.compositions=[],this.compositionFiles=[],this.compositionResolutionError=null,this.launchCandidateEntries=null,this.stopWatching=null,this.stopWebSocketServer=null,this.pendingChangedFiles=new Set,this.fileChangeTimer=null,this.isStarted=!1,this.stopping=!1,this.runtimeVersionFloors=new Map,this.activeHistoryStore=null,this.versionPersistence=Promise.resolve(),this.bridgeEngine=null,this.lastNotifiedHealth=null}start(e={}){if(!this.isStarted){this.stopping=!1;try{_.mkdirSync(m.join(this.options.projectRoot,".vcp"),{recursive:!0})}catch(n){console.warn(`[vcp-site-edit] failed to ensure .vcp directory: ${n instanceof Error?n.message:String(n)}`)}if(this.refreshCompositions(!0),this.isStarted=!0,this.createRuntime(),this.bridgeEngine=this.createBridgeEngine(),e.webSocket!==!1){let n=$e({server:this.server,path:this.options.path,sessionKey:this.options.sessionKey,host:this.options.host,allowedOrigins:this.options.allowedOrigins,engine:this.bridgeEngine});this.stopWebSocketServer=n.stop}this.stopWatching=new Oe({projectRoot:this.options.projectRoot,watch_dirs:Be(this.options.runtime.watch_dirs),interval_ms:this.options.runtime.watch_interval_ms,debounce_ms:this.options.runtime.watch_debounce_ms??ee,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.notifyHealthChanged()}}getEngine(){if(!this.bridgeEngine)throw new Error("VCP site edit preview bridge runtime is not ready");return this.bridgeEngine}getHealth(){let e=!!this.runtime&&!this.compositionResolutionError;return{ok:this.isStarted&&e,bridgePath:this.options.path,runtimeReady:e,routesIndexed:this.routes.length,sessionRequired:!!this.options.sessionKey}}stop(){this.stopping=!0,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.compositions=[],this.compositionFiles=[],this.compositionResolutionError=null,this.closeHttpServerOnStop&&this.server.close();let e=Ue(),n=We(this.options);e.get(n)===this&&e.delete(n),this.isStarted=!1,this.notifyHealthChanged()}notifyHealthChanged(){let e=this.options.onHealthChanged;if(!e)return;let n=this.getHealth(),i=JSON.stringify(n);if(i!==this.lastNotifiedHealth){this.lastNotifiedHealth=i;try{e(n)}catch(o){console.warn(`[vcp-site-edit] onHealthChanged callback failed: ${o instanceof Error?o.message:String(o)}`)}}}createRuntime(e,n=null){let i=this.runtime;this.activeHistoryStore=e??null;let o=(this.dependencies.createRuntime??Re)({...this.options.tenantId?{tenantId:this.options.tenantId}:{},siteId:this.options.siteId,snapshotId:this.options.snapshotId,projectRoot:this.options.projectRoot,compositions:this.compositions,onEvent:s=>{"version"in s&&typeof s.version=="number"&&this.recordRuntimeVersion(s.version,"composition"in s?s.composition:void 0);for(let r of this.eventListeners)r(s)},...e?{historyStore:e}:{},...this.getInitialDocumentVersions().length>0?{initialDocumentVersions:this.getInitialDocumentVersions()}:{}});this.runtime=o,this.activeHistorySessionId=n,i?.dispose(),this.notifyHealthChanged()}createBridgeEngine(){return{resolveComposition:e=>this.resolveComposition(e),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 i=Ve(e.composition),o=this.getRuntime(),s=y(i);await o.initializeComposition(s);let c=o.getDocument(s).data;await this.recordRuntimeVersion(c.version,i);let p=(await o.getHistoryStatus()).data;return{ok:!0,composition:i,data:{document:c,historyStatus:p}}}),getDocument:async e=>{let n=this.getRuntime(),i=y(e);await n.initializeComposition(i);let o=n.getDocument(i);return await this.recordRuntimeVersion(o.data.version,e),{...o,composition:e}},getHistoryStatus:async e=>({...await this.getRuntime().getHistoryStatus(),composition:e}),getStyleContext:async e=>this.getRuntime().getStyleContext(y(e)),getObjectEditContext:async(e,n,i)=>this.getRuntime().getObjectEditContext(e,y(n),i),getClassNameSource:async(e,n)=>this.getRuntime().getClassNameSource(e,y(n)),getSourceTree:async e=>this.getRuntime().getSourceTree(y(e)),getSourceFile:async e=>this.getRuntime().getSourceFile({path:e.path,composition:y(e.composition)}),resolveSourceLocation:async e=>this.getRuntime().resolveSourceLocation({key:e.key,composition:y(e.composition)}),execute:(e,n)=>this.mutationQueue.run(async()=>{let i=await this.bindHistorySession(this.options.snapshotId);if(!i.ok)return q(i,e);let o=await this.getRuntime().executeOperation(e);return o.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(o.data.resultVersion,o.composition),o}),undo:e=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)return q(n,void 0,e);let i=await this.getRuntime().undo();return i.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(i.data.resultVersion,i.composition??e),i.composition?i:{...i,composition:e}}),redo:e=>this.mutationQueue.run(async()=>{let n=await this.bindHistorySession(this.options.snapshotId);if(!n.ok)return q(n,void 0,e);let i=await this.getRuntime().redo();return i.data?.resultVersion!==void 0&&await this.recordRuntimeVersion(i.data.resultVersion,i.composition??e),i.composition?i:{...i,composition:e}}),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 i=await(this.dependencies.openHistoryStore??be)(qe({root:this.options.historyRoot,siteId:this.options.siteId,snapshotId:this.options.snapshotId,editingSessionId:e}));if(!this.isStarted)return;let o=i.readDocumentVersion?await i.readDocumentVersion():0,s=this.compositions[0]?.context;if(s){let r=E(s);this.runtimeVersionFloors.set(r,Math.max(this.runtimeVersionFloors.get(r)??0,o))}this.createRuntime(i,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,n){let i=n?E(n):null;i&&this.runtimeVersionFloors.set(i,Math.max(this.runtimeVersionFloors.get(i)??0,e));let o=this.activeHistoryStore;if(!o?.writeDocumentVersion)return Promise.resolve();let s=i?this.runtimeVersionFloors.get(i)??e:e;return this.versionPersistence=this.versionPersistence.then(()=>o.writeDocumentVersion(s)).catch(()=>{}),this.versionPersistence}handleFileChanged(e){this.handleFilesChanged([e])}handleFilesChanged(e){for(let i of e)this.pendingChangedFiles.add(i);this.fileChangeTimer&&clearTimeout(this.fileChangeTimer);let n=this.options.runtime.watch_debounce_ms??ee;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,i)=>n.localeCompare(i));if(this.pendingChangedFiles.clear(),e.some(Ze)&&this.scheduleSnapshotIdentityAdvance(),!!this.refreshCompositions(!1))for(let n of e)this.runtime?.notifyFileChanged(n)}scheduleSnapshotIdentityAdvance(){if(this.stopping||!this.isStarted)return;let e=this.readCurrentSnapshotMarker();!e||e===this.options.snapshotId||this.mutationQueue.run(()=>this.advanceSnapshotId(e)).catch(n=>{console.warn(`[vcp-site-edit] snapshot identity advance failed: ${n instanceof Error?n.message:String(n)}`)})}readCurrentSnapshotMarker(){try{let e=_.readFileSync(m.join(this.options.projectRoot,W),"utf8"),n=JSON.parse(e),i=typeof n.snapshot_id=="string"?n.snapshot_id:"";return!i||j(i)!==i?(console.warn("[vcp-site-edit] current-snapshot marker has no valid snapshot_id"),null):i}catch(e){return console.warn(`[vcp-site-edit] current-snapshot marker unreadable: ${e instanceof Error?e.message:String(e)}`),null}}async advanceSnapshotId(e){if(this.stopping||!this.isStarted||e===this.options.snapshotId)return;let n=this.options.snapshotId;this.options.snapshotId=e;try{if(this.options.historyRoot){let o=await this.bindHistorySession(e);if(!o.ok)throw new Error(`${o.code}: ${o.message}`)}else this.createRuntime()}catch(o){this.options.snapshotId=n,console.warn(`[vcp-site-edit] snapshot identity advance rolled back to ${n}: ${o instanceof Error?o.message:String(o)}`);return}let i=new Date().toISOString();for(let o of this.compositions){let s={type:"site-edit.snapshot.changed",siteId:this.options.siteId,composition:o.context,snapshotId:e,previousSnapshotId:n,at:i};for(let r of this.eventListeners)try{r(s)}catch(c){console.warn(`[vcp-site-edit] snapshot.changed listener failed: ${c instanceof Error?c.message:String(c)}`)}}try{this.options.onSnapshotIdChanged?.(e,n)}catch(o){console.warn(`[vcp-site-edit] onSnapshotIdChanged callback failed: ${o instanceof Error?o.message:String(o)}`)}}refreshCompositions(e=!1){try{let n=Pe(this.options.projectRoot),i=Te(n),o=i.length>0?i:(this.options.runtime.routes??[]).map(u=>({...u})),s=ke({files:n});if(s.length===0)throw new Error("No canonical App Router composition is indexed by the preview bridge.");let r=JSON.stringify(s.filter(u=>u.context.effectiveTemplateId!==void 0).map(u=>u.entryFile).sort());if(e&&(this.launchCandidateEntries=r),this.launchCandidateEntries!==r)throw new Error("SITE_EDIT_RUNTIME_RELOAD_REQUIRED: Candidate route collection changed since preview launch");let c=new Map(this.compositions.map(u=>[E(u.context),u])),p=new Map(s.map(u=>[E(u.context),u]));if(this.runtime){for(let[u,l]of c){let S=p.get(u);(!S||S.entryFile!==l.entryFile)&&this.runtime.unregisterComposition(y(l.context))}for(let[u,l]of p){let S=c.get(u);(!S||S.entryFile!==l.entryFile)&&this.runtime.registerComposition(l.context,l.entryFile)}}return this.routes=o,this.compositions=s,this.compositionFiles=n,this.compositionResolutionError=null,this.notifyHealthChanged(),!0}catch(n){let i=n instanceof Error?n:new Error(String(n));if(this.compositionResolutionError=i,e)throw i;return this.notifyHealthChanged(),!1}}resolveComposition(e){if(this.compositionResolutionError)throw this.compositionResolutionError;let n=_e(e.routeId,this.routes,this.options.basePath);if(!n)throw new Error(`Route is not indexed by the preview bridge: ${e.routeId}`);let i=this.routes.find(r=>r.routeId===n);if(!i)throw new Error(`Route is not indexed by the preview bridge: ${e.routeId}`);let o=ve({selector:{routeId:n,...e.templateId!==void 0?{templateId:e.templateId}:{}},files:this.compositionFiles,canonicalEntryFile:i.entryFile}),s=E(o.context);if(!this.compositions.some(r=>E(r.context)===s))throw new Error(`Composition is not registered by the preview bridge: ${s}`);return o}getInitialDocumentVersions(){return this.compositions.flatMap(e=>{let n=this.runtimeVersionFloors.get(E(e.context));return n===void 0?[]:[{composition:e.context,version:n}]})}getRuntime(){if(!this.runtime)throw new Error("VCP site edit preview bridge runtime is not ready");return this.runtime}};function ce(t){let e=new $(Fe(t),se.createServer(),!1);return e.start({listen:!1,webSocket:!1}),{engine:e.getEngine(),getHealth:()=>e.getHealth(),stop:()=>e.stop()}}function Fe(t){return{tenantId:t.tenantId,siteId:t.siteId,snapshotId:t.snapshotId,projectRoot:m.resolve(t.projectRoot??process.cwd()),basePath:t.basePath??"",host:t.host??He,port:t.port??Me,path:t.path??De,sessionKey:t.sessionKey??Ye(),historyRoot:t.historyRoot,allowedOrigins:t.allowedOrigins??[],runtime:t.runtime??{},onSnapshotIdChanged:t.onSnapshotIdChanged,onHealthChanged:t.onHealthChanged}}function Be(t){let e=["app","src/app","components/site","src/components/site","lib/generated","src/lib/generated","public"];return[...new Set([...t?.length?t:e,".vcp"])]}function Pe(t){let e=[],n=["app","src/app",".vcp"],i=new Set([".git",".next",".turbo","coverage","dist","node_modules","output","snapshot-store"]),o=s=>{if(!_.existsSync(s))return;let r;try{r=_.readdirSync(s,{withFileTypes:!0})}catch{return}for(let c of r){if(c.name.startsWith(".")&&c.name!==".vcp")continue;if(c.isDirectory()){if(i.has(c.name))continue;o(m.join(s,c.name));continue}if(!c.isFile())continue;let p=m.join(s,c.name),u="";try{u=_.readFileSync(p,"utf8")}catch{}e.push({path:m.relative(t,p).replaceAll(m.sep,"/"),content:u})}};for(let s of n)o(m.join(t,s));return e.sort((s,r)=>s.path.localeCompare(r.path))}function Ve(t){if(!t||typeof t!="object"||typeof t.routeId!="string")throw new Error("Normalized composition context is required.");if("effectiveTemplateId"in t)return t;let e=t;return{routeId:e.routeId,...e.templateId?{effectiveTemplateId:e.templateId}:{}}}function y(t){return{routeId:t.routeId,...t.effectiveTemplateId?{templateId:t.effectiveTemplateId}:{}}}function qe(t){return m.join(m.resolve(t.root),j(t.siteId),j(t.snapshotId),oe("sha256").update(t.editingSessionId).digest("hex").slice(0,32))}function j(t){return/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(t)&&t!=="."&&t!==".."?t:oe("sha256").update(t).digest("hex").slice(0,32)}function Ue(){let t=globalThis;return t[te]??=new Map,t[te]}function We(t){return`${t.projectRoot}:${t.host}:${t.port}:${t.path}:${t.historyRoot??""}`}function q(t,e,n){let i=n??(e?.composition?{routeId:e.composition.routeId,...e.composition.templateId?{effectiveTemplateId:e.composition.templateId}:{}}:void 0);if(!i)throw new Error("A normalized composition is required to construct a mutation result.");return{ok:!1,composition:i,error:{code:t.code,message:t.message},data:{requestId:e?.id??"mutation-context",kind:e?.kind??"update-text",composition:i,target:e?.target??{kind:"node",key:""},resultVersion:e?.documentVersion??0}}}function $e(t){let e=new xe({maxPayload:ae,noServer:!0}),n=new WeakSet,i=s=>{new A({socket:new z(s),engine:t.engine,validateOperationRequest:r=>Ae.safeParse(r).success})},o=(s,r,c)=>{if(!je(s,t))return;let p=ze(s,t);if(!p){r.write(`HTTP/1.1 401 Unauthorized\r
|
|
2
2
|
Connection: close\r
|
|
3
3
|
\r
|
|
4
|
-
`),r.destroy();return}p.authenticatedByHeader&&n.add(
|
|
4
|
+
`),r.destroy();return}p.authenticatedByHeader&&n.add(s),e.handleUpgrade(s,r,c,u=>{e.emit("connection",u,s)})};return t.server.on("upgrade",o),e.on("connection",(s,r)=>{if(n.has(r)){i(s);return}Le(s,t.sessionKey,()=>{i(s)})}),e.stop=()=>{t.server.off("upgrade",o);for(let s of e.clients)s.close(1001,"bridge stopped");e.close()},e}var z=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===re.OPEN){if(this.socket.bufferedAmount+Buffer.byteLength(e,"utf8")>Ke){this.socket.close(1013,"bridge backpressure");return}this.socket.send(e)}}close(e,n){this.socket.close(e,n)}};function je(t,e){return new URL(t.url??"/","http://localhost").pathname===e.path}function ze(t,e){if(new URL(t.url??"/","http://localhost").searchParams.has("vcp_bridge_key"))return null;let i=U(t.headers["x-vcp-site-edit-key"]),o=ue(i,e.sessionKey),s=U(t.headers.host),r=U(t.headers.origin);return s&&!Je(s,e.host,r)?null:r?Ge(r,s,e.allowedOrigins)?{authenticatedByHeader:o}:null:o?{authenticatedByHeader:!0}:null}function Le(t,e,n){let i=setTimeout(()=>{t.close(1008,"bridge authentication timeout")},Ne),o,s=()=>{clearTimeout(i),t.off("message",o),t.off("close",s)},r=()=>{s(),t.close(1008,"bridge authentication failed")};o=c=>{try{let p=g.parseMessage(c.toString());if(p.type!=="auth"||!ue(p.payload.key,e)){r();return}s(),t.readyState===re.OPEN&&t.send(g.stringifyMessage({type:"auth-ok",payload:{ok:!0}})),n()}catch{r()}},t.on("message",o),t.on("close",s)}function ue(t,e){if(!t)return!1;let n=Buffer.from(t),i=Buffer.from(e);return n.length===i.length&&Ee(n,i)}function Je(t,e,n){let i=ie(t),o=ie(e);return i===o||ne(o)&&ne(i)&&(!n||L(n,t))||Qe(o)&&(!n||L(n,t))}function Qe(t){return t==="0.0.0.0"||t==="::"}function ne(t){return t==="localhost"||t==="127.0.0.1"||t==="::1"}function Ge(t,e,n){return n.includes(t)||(e?L(t,e):!1)}function L(t,e){try{return new URL(t).host===de(e)}catch{return!1}}function ie(t){let e=de(t);return e.startsWith("[")?e.slice(1,e.indexOf("]")):e.split(":").length-1===1?e.split(":",1)[0]??"":e}function de(t){return t.trim().toLowerCase()}function U(t){return Array.isArray(t)?t[0]:t}function Ye(){return Ce(32).toString("base64url")}function Ze(t){let e=t.replaceAll("\\","/");return e===W||e.endsWith(`/${W}`)}var R=Xe;if(!R)throw new Error("site edit engine worker must run inside worker_threads");var x=t=>{R.postMessage(t)},tt=(t,e)=>{try{R.postMessage({type:"result",id:t,ok:!0,value:e})}catch(n){if(n instanceof Error&&n.name==="DataCloneError"){R.postMessage({type:"result",id:t,ok:!0,value:e===void 0?void 0:JSON.parse(JSON.stringify(e))});return}throw n}},{options:nt}=et,T;try{T=ce({...nt,onSnapshotIdChanged:(t,e)=>{x({type:"snapshot-id-changed",next:t,previous:e})},onHealthChanged:t=>{x({type:"health",health:t})}})}catch(t){x({type:"start-failed",error:M(t)}),process.exit(1)}T.engine.subscribe(t=>{x({type:"event",event:t})});var it=async t=>{try{let e=T.engine[t.method],n=await e(...t.args);tt(t.id,n)}catch(e){x({type:"result",id:t.id,ok:!1,error:M(e)})}};R.on("message",t=>{if(t.type==="call"){it(t);return}t.type==="stop"&&(T.stop(),R.close())});x({type:"ready",health:T.getHealth()});
|