@vcp-dev/site-edit-bridge 0.10.20 → 0.10.22

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.
@@ -0,0 +1,26 @@
1
+ import type { Server as HttpServer } from "node:http";
2
+ import { Worker } from "node:worker_threads";
3
+ import type { SiteEditEvent } from "@vcp-dev/contracts";
4
+ import { type SiteEditBridgeHealth, type SiteEditBridgeServerOptions } from "./server.js";
5
+ /** @internal 测试与运维调参:从源码起线程时换入口、加载 TS 运行器,或改重起预算。 */
6
+ export type SiteEditBridgeWorkerDependencies = {
7
+ workerUrl?: URL;
8
+ execArgv?: string[];
9
+ onWorker?: (worker: Worker) => void;
10
+ maxHeapMb?: number;
11
+ maxRestarts?: number;
12
+ restartWindowMs?: number;
13
+ };
14
+ export type SiteEditBridgeWorkerAttachment = {
15
+ getHealth(): SiteEditBridgeHealth;
16
+ stop(): void;
17
+ /** 引擎线程首次完成启动(路由索引、运行时创建)后兑现;启动失败时拒绝。 */
18
+ whenReady(): Promise<void>;
19
+ /** @internal 与同线程桥同名,供测试核对连接关闭后监听已注销。 */
20
+ readonly eventListeners: ReadonlySet<(event: SiteEditEvent) => void>;
21
+ };
22
+ /**
23
+ * 与 attachSiteEditBridgeServer 对外行为一致,但编辑引擎跑在独立线程:
24
+ * 主线程只负责 WebSocket 连接、鉴权和协议,引擎调用经消息转发。
25
+ */
26
+ export declare function attachSiteEditBridgeServerInWorker(server: HttpServer, options: SiteEditBridgeServerOptions, dependencies?: SiteEditBridgeWorkerDependencies): SiteEditBridgeWorkerAttachment;
@@ -0,0 +1,257 @@
1
+ import { Worker } from "node:worker_threads";
2
+ import { SITE_EDIT_ENGINE_WORKER_METHODS, deserializeSiteEditEngineError, } from "./engine-worker-messages.js";
3
+ import { attachSiteEditWebSocketServer, normalizeSiteEditBridgeServerOptions, } from "./server.js";
4
+ const ENGINE_UNAVAILABLE_CODE = "SITE_EDIT_ENGINE_UNAVAILABLE";
5
+ const WORKER_STOP_GRACE_MS = 5_000;
6
+ // 引擎线程的堆上限。超限时 Node 只结束该线程(ERR_WORKER_OUT_OF_MEMORY),
7
+ // Next 预览照常服务;上限要明显低于沙盒内存,保证线程先于整个容器出局。
8
+ const DEFAULT_ENGINE_MAX_HEAP_MB = 1024;
9
+ const ENGINE_MAX_HEAP_ENV_KEY = "VCP_SITE_EDIT_ENGINE_MAX_HEAP_MB";
10
+ // 运行中意外退出后自动重起;短时间内反复崩溃说明重起解决不了,交还给
11
+ // health 不可用,由 agent 走整台预览重启。
12
+ const DEFAULT_MAX_RESTARTS = 3;
13
+ const DEFAULT_RESTART_WINDOW_MS = 10 * 60_000;
14
+ // 1012:服务端重启。设计器 bridge 客户端把连接断开当作可重连,会重新连接并
15
+ // 初始化会话,与整台预览重启后的恢复路径相同。
16
+ const ENGINE_RESTART_CLOSE_CODE = 1012;
17
+ /**
18
+ * 与 attachSiteEditBridgeServer 对外行为一致,但编辑引擎跑在独立线程:
19
+ * 主线程只负责 WebSocket 连接、鉴权和协议,引擎调用经消息转发。
20
+ */
21
+ export function attachSiteEditBridgeServerInWorker(server, options, dependencies = {}) {
22
+ const normalized = normalizeSiteEditBridgeServerOptions(options);
23
+ const { onSnapshotIdChanged, onHealthChanged: _onHealthChanged, ...workerOptions } = normalized;
24
+ const workerData = { options: workerOptions };
25
+ const maxHeapMb = dependencies.maxHeapMb ??
26
+ readPositiveInteger(process.env[ENGINE_MAX_HEAP_ENV_KEY]) ??
27
+ DEFAULT_ENGINE_MAX_HEAP_MB;
28
+ const maxRestarts = dependencies.maxRestarts ?? DEFAULT_MAX_RESTARTS;
29
+ const restartWindowMs = dependencies.restartWindowMs ?? DEFAULT_RESTART_WINDOW_MS;
30
+ // starting:首次启动中;restarting:曾就绪、崩溃后重起中。
31
+ let state = "starting";
32
+ let worker;
33
+ let generation = 0;
34
+ const restartTimes = [];
35
+ let workerHealth = null;
36
+ let nextCallId = 0;
37
+ const pendingCalls = new Map();
38
+ const listeners = new Set();
39
+ let resolveReady;
40
+ let rejectReady;
41
+ const ready = new Promise((resolve, reject) => {
42
+ resolveReady = resolve;
43
+ rejectReady = reject;
44
+ });
45
+ // 调用方可能只看 health 不等 ready,避免启动失败变成未处理的拒绝。
46
+ ready.catch(() => undefined);
47
+ const unavailableError = (reason) => {
48
+ const error = new Error(`VCP site edit preview bridge runtime is not ready: ${reason}`);
49
+ error.code = ENGINE_UNAVAILABLE_CODE;
50
+ return error;
51
+ };
52
+ const rejectPendingCalls = (error) => {
53
+ for (const pending of pendingCalls.values()) {
54
+ pending.reject(error);
55
+ }
56
+ pendingCalls.clear();
57
+ };
58
+ const handleMessage = (message) => {
59
+ switch (message.type) {
60
+ case "ready":
61
+ workerHealth = message.health;
62
+ if (state === "starting") {
63
+ state = "ready";
64
+ resolveReady();
65
+ }
66
+ else if (state === "restarting") {
67
+ state = "ready";
68
+ console.warn("[vcp-site-edit] engine worker restarted");
69
+ }
70
+ return;
71
+ case "start-failed":
72
+ // 线程随后以非零码退出,重起与否统一在退出处理里判断。
73
+ if (state === "starting") {
74
+ handleWorkerDown(deserializeSiteEditEngineError(message.error));
75
+ }
76
+ return;
77
+ case "result": {
78
+ const pending = pendingCalls.get(message.id);
79
+ if (!pending)
80
+ return;
81
+ pendingCalls.delete(message.id);
82
+ if (message.ok) {
83
+ pending.resolve(message.value);
84
+ }
85
+ else {
86
+ pending.reject(deserializeSiteEditEngineError(message.error));
87
+ }
88
+ return;
89
+ }
90
+ case "event":
91
+ for (const listener of listeners) {
92
+ try {
93
+ listener(message.event);
94
+ }
95
+ catch (error) {
96
+ console.warn(`[vcp-site-edit] engine event listener failed: ${error instanceof Error ? error.message : String(error)}`);
97
+ }
98
+ }
99
+ return;
100
+ case "health":
101
+ workerHealth = message.health;
102
+ return;
103
+ case "snapshot-id-changed":
104
+ // 保存引擎已确认的身份,意外退出后的新线程不能退回首次启动的快照。
105
+ workerData.options.snapshotId = message.next;
106
+ try {
107
+ onSnapshotIdChanged?.(message.next, message.previous);
108
+ }
109
+ catch (error) {
110
+ console.warn(`[vcp-site-edit] onSnapshotIdChanged callback failed: ${error instanceof Error ? error.message : String(error)}`);
111
+ }
112
+ return;
113
+ }
114
+ };
115
+ const spawnWorker = () => {
116
+ const workerGeneration = (generation += 1);
117
+ const next = new Worker(dependencies.workerUrl ?? new URL("./engine-worker.js", import.meta.url), {
118
+ workerData,
119
+ resourceLimits: { maxOldGenerationSizeMb: maxHeapMb },
120
+ ...(dependencies.execArgv ? { execArgv: dependencies.execArgv } : {}),
121
+ });
122
+ // 线程不单独拖住进程退出:进程生命周期由 HTTP 服务器决定。
123
+ next.unref();
124
+ // 旧线程的迟到消息(例如 error 之后的 exit)不得影响新线程的状态。
125
+ const isCurrent = () => workerGeneration === generation;
126
+ next.on("message", (message) => {
127
+ if (isCurrent())
128
+ handleMessage(message);
129
+ });
130
+ next.on("error", (error) => {
131
+ if (isCurrent()) {
132
+ handleWorkerDown(error instanceof Error ? error : new Error(String(error)));
133
+ }
134
+ });
135
+ next.on("exit", (exitCode) => {
136
+ if (isCurrent()) {
137
+ handleWorkerDown(new Error(`engine worker exited with code ${exitCode}`));
138
+ }
139
+ });
140
+ worker = next;
141
+ dependencies.onWorker?.(next);
142
+ };
143
+ const handleWorkerDown = (error) => {
144
+ if (state === "stopped" || state === "failed") {
145
+ return;
146
+ }
147
+ rejectPendingCalls(unavailableError(error.message));
148
+ workerHealth = null;
149
+ // 首次启动失败(例如没有可索引的路由)是确定性的,重起无益。
150
+ if (state === "starting") {
151
+ state = "failed";
152
+ console.error(`[vcp-site-edit] engine worker failed to start: ${error.message}`);
153
+ rejectReady(error);
154
+ return;
155
+ }
156
+ const now = Date.now();
157
+ while (restartTimes.length > 0 &&
158
+ now - restartTimes[0] > restartWindowMs) {
159
+ restartTimes.shift();
160
+ }
161
+ if (restartTimes.length >= maxRestarts) {
162
+ state = "failed";
163
+ console.error(`[vcp-site-edit] engine worker stopped unexpectedly and exhausted ${maxRestarts} restarts: ${error.message}`);
164
+ return;
165
+ }
166
+ restartTimes.push(now);
167
+ state = "restarting";
168
+ // 让设计器重新连接并初始化会话,不沿用旧线程里的会话与版本状态。
169
+ for (const client of webSocketServer.clients) {
170
+ client.close(ENGINE_RESTART_CLOSE_CODE, "site edit engine restarting");
171
+ }
172
+ console.error(`[vcp-site-edit] engine worker stopped unexpectedly, restarting (${restartTimes.length}/${maxRestarts}): ${error.message}`);
173
+ spawnWorker();
174
+ };
175
+ const call = (method, args) => {
176
+ if (state === "failed" || state === "stopped" || state === "restarting") {
177
+ return Promise.reject(unavailableError(state));
178
+ }
179
+ const id = (nextCallId += 1);
180
+ return new Promise((resolve, reject) => {
181
+ pendingCalls.set(id, { resolve, reject });
182
+ const request = {
183
+ type: "call",
184
+ id,
185
+ method,
186
+ args,
187
+ };
188
+ worker.postMessage(request);
189
+ });
190
+ };
191
+ const engine = Object.fromEntries(SITE_EDIT_ENGINE_WORKER_METHODS.map((method) => [
192
+ method,
193
+ (...args) => call(method, args),
194
+ ]));
195
+ const webSocketServer = attachSiteEditWebSocketServer({
196
+ server,
197
+ path: normalized.path,
198
+ sessionKey: normalized.sessionKey,
199
+ host: normalized.host,
200
+ allowedOrigins: normalized.allowedOrigins,
201
+ engine: {
202
+ ...engine,
203
+ subscribe(listener) {
204
+ listeners.add(listener);
205
+ return () => listeners.delete(listener);
206
+ },
207
+ },
208
+ });
209
+ spawnWorker();
210
+ return {
211
+ getHealth() {
212
+ if (state === "ready" && workerHealth) {
213
+ return workerHealth;
214
+ }
215
+ return {
216
+ ok: false,
217
+ bridgePath: normalized.path,
218
+ runtimeReady: false,
219
+ routesIndexed: workerHealth?.routesIndexed ?? 0,
220
+ sessionRequired: Boolean(normalized.sessionKey),
221
+ };
222
+ },
223
+ stop() {
224
+ if (state === "stopped") {
225
+ return;
226
+ }
227
+ const wasStarting = state === "starting";
228
+ state = "stopped";
229
+ webSocketServer.stop();
230
+ if (wasStarting) {
231
+ rejectReady(unavailableError("bridge stopped"));
232
+ }
233
+ rejectPendingCalls(unavailableError("bridge stopped"));
234
+ listeners.clear();
235
+ const current = worker;
236
+ const request = { type: "stop" };
237
+ current.postMessage(request);
238
+ // 正常情况下引擎线程收到 stop 后自行退出;卡住时兜底结束。
239
+ const forceStop = setTimeout(() => {
240
+ void current.terminate();
241
+ }, WORKER_STOP_GRACE_MS);
242
+ forceStop.unref();
243
+ current.once("exit", () => clearTimeout(forceStop));
244
+ },
245
+ whenReady() {
246
+ return ready;
247
+ },
248
+ eventListeners: listeners,
249
+ };
250
+ }
251
+ function readPositiveInteger(value) {
252
+ if (!value || !/^\d+$/.test(value.trim())) {
253
+ return undefined;
254
+ }
255
+ const parsed = Number(value.trim());
256
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
257
+ }
@@ -0,0 +1,44 @@
1
+ // 主线程与引擎线程之间的消息。两端只经这一个通道收发,postMessage 保证先后
2
+ // 顺序:引擎在一次操作里发出的事件一定先于该操作的结果到达主线程,协议层
3
+ // “先回结果、再冲刷缓冲事件”的顺序因此与同线程时一致。
4
+ export const SITE_EDIT_ENGINE_WORKER_METHODS = [
5
+ "resolveComposition",
6
+ "initializeSession",
7
+ "getDocument",
8
+ "getHistoryStatus",
9
+ "getStyleContext",
10
+ "getObjectEditContext",
11
+ "getClassNameSource",
12
+ "getSourceTree",
13
+ "getSourceFile",
14
+ "resolveSourceLocation",
15
+ "execute",
16
+ "undo",
17
+ "redo",
18
+ ];
19
+ export const siteEditEngineWorkerMethodsAreExhaustive = true;
20
+ // 协议层只读错误的 message 和字符串 code(见 protocol.ts 的
21
+ // getBridgeErrorMessage / getBridgeErrorCode),跨线程只保留这几个字段。
22
+ export function serializeSiteEditEngineError(error) {
23
+ const record = error && typeof error === "object"
24
+ ? error
25
+ : null;
26
+ const message = typeof record?.message === "string" && record.message.length > 0
27
+ ? record.message
28
+ : String(error);
29
+ return {
30
+ name: typeof record?.name === "string" ? record.name : "Error",
31
+ message,
32
+ ...(typeof record?.code === "string" && record.code.length > 0
33
+ ? { code: record.code }
34
+ : {}),
35
+ };
36
+ }
37
+ export function deserializeSiteEditEngineError(error) {
38
+ const restored = new Error(error.message);
39
+ restored.name = error.name;
40
+ if (error.code !== undefined) {
41
+ restored.code = error.code;
42
+ }
43
+ return restored;
44
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +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
2
+ Connection: close\r
3
+ \r
4
+ `),r.destroy();return}p.authenticatedByHeader&&n.add(i),e.handleUpgrade(i,r,c,u=>{e.emit("connection",u,i)})};return t.server.on("upgrade",o),e.on("connection",(i,r)=>{if(n.has(r)){s(i);return}$e(i,t.sessionKey,()=>{s(i)})}),e.stop=()=>{t.server.off("upgrade",o);for(let i of e.clients)i.close(1001,"bridge stopped");e.close()},e}var U=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===ie.OPEN){if(this.socket.bufferedAmount+Buffer.byteLength(e,"utf8")>Me){this.socket.close(1013,"bridge backpressure");return}this.socket.send(e)}}close(e,n){this.socket.close(e,n)}};function We(t,e){return new URL(t.url??"/","http://localhost").pathname===e.path}function je(t,e){if(new URL(t.url??"/","http://localhost").searchParams.has("vcp_bridge_key"))return null;let s=K(t.headers["x-vcp-site-edit-key"]),o=ae(s,e.sessionKey),i=K(t.headers.host),r=K(t.headers.origin);return i&&!ze(i,e.host,r)?null:r?Je(r,i,e.allowedOrigins)?{authenticatedByHeader:o}:null:o?{authenticatedByHeader:!0}:null}function $e(t,e,n){let s=setTimeout(()=>{t.close(1008,"bridge authentication timeout")},Ae),o,i=()=>{clearTimeout(s),t.off("message",o),t.off("close",i)},r=()=>{i(),t.close(1008,"bridge authentication failed")};o=c=>{try{let p=l.parseMessage(c.toString());if(p.type!=="auth"||!ae(p.payload.key,e)){r();return}i(),t.readyState===ie.OPEN&&t.send(l.stringifyMessage({type:"auth-ok",payload:{ok:!0}})),n()}catch{r()}},t.on("message",o),t.on("close",i)}function ae(t,e){if(!t)return!1;let n=Buffer.from(t),s=Buffer.from(e);return n.length===s.length&&we(n,s)}function ze(t,e,n){let s=te(t),o=te(e);return s===o||ee(o)&&ee(s)&&(!n||W(n,t))||Le(o)&&(!n||W(n,t))}function Le(t){return t==="0.0.0.0"||t==="::"}function ee(t){return t==="localhost"||t==="127.0.0.1"||t==="::1"}function Je(t,e,n){return n.includes(t)||(e?W(t,e):!1)}function W(t,e){try{return new URL(t).host===ce(e)}catch{return!1}}function te(t){let e=ce(t);return e.startsWith("[")?e.slice(1,e.indexOf("]")):e.split(":").length-1===1?e.split(":",1)[0]??"":e}function ce(t){return t.trim().toLowerCase()}function K(t){return Array.isArray(t)?t[0]:t}function Qe(){return Se(32).toString("base64url")}function Ge(t){let e=t.replaceAll("\\","/");return e===P||e.endsWith(`/${P}`)}var E=Ye;if(!E)throw new Error("site edit engine worker must run inside worker_threads");var C=t=>{E.postMessage(t)},Xe=(t,e)=>{try{E.postMessage({type:"result",id:t,ok:!0,value:e})}catch(n){if(n instanceof Error&&n.name==="DataCloneError"){E.postMessage({type:"result",id:t,ok:!0,value:e===void 0?void 0:JSON.parse(JSON.stringify(e))});return}throw n}},{options:et}=Ze,b;try{b=re({...et,onSnapshotIdChanged:(t,e)=>{C({type:"snapshot-id-changed",next:t,previous:e})},onHealthChanged:t=>{C({type:"health",health:t})}})}catch(t){C({type:"start-failed",error:T(t)}),process.exit(1)}b.engine.subscribe(t=>{C({type:"event",event:t})});var tt=async t=>{try{let e=b.engine[t.method],n=await e(...t.args);Xe(t.id,n)}catch(e){C({type:"result",id:t.id,ok:!1,error:T(e)})}};E.on("message",t=>{if(t.type==="call"){tt(t);return}t.type==="stop"&&(b.stop(),E.close())});C({type:"ready",health:b.getHealth()});