veryfront 0.1.73 → 0.1.75
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/esm/cli/commands/knowledge/command-help.d.ts.map +1 -1
- package/esm/cli/commands/knowledge/command-help.js +3 -1
- package/esm/cli/commands/knowledge/command.d.ts +34 -5
- package/esm/cli/commands/knowledge/command.d.ts.map +1 -1
- package/esm/cli/commands/knowledge/command.js +151 -22
- package/esm/cli/commands/knowledge/parser-source.d.ts.map +1 -1
- package/esm/cli/commands/knowledge/parser-source.js +110 -5
- package/esm/deno.d.ts +2 -0
- package/esm/deno.js +3 -1
- package/esm/src/data/data-fetcher.d.ts +11 -1
- package/esm/src/data/data-fetcher.d.ts.map +1 -1
- package/esm/src/data/data-fetcher.js +5 -2
- package/esm/src/data/index.d.ts +1 -1
- package/esm/src/data/index.d.ts.map +1 -1
- package/esm/src/data/server-data-fetcher.d.ts +14 -1
- package/esm/src/data/server-data-fetcher.d.ts.map +1 -1
- package/esm/src/data/server-data-fetcher.js +49 -3
- package/esm/src/rendering/orchestrator/lifecycle.d.ts +4 -0
- package/esm/src/rendering/orchestrator/lifecycle.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/lifecycle.js +8 -0
- package/esm/src/rendering/orchestrator/pipeline.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/pipeline.js +6 -1
- package/esm/src/rendering/orchestrator/ssr-orchestrator.d.ts +26 -1
- package/esm/src/rendering/orchestrator/ssr-orchestrator.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/ssr-orchestrator.js +77 -1
- package/esm/src/routing/api/handler.d.ts.map +1 -1
- package/esm/src/routing/api/handler.js +6 -2
- package/esm/src/routing/api/route-executor.d.ts +8 -2
- package/esm/src/routing/api/route-executor.d.ts.map +1 -1
- package/esm/src/routing/api/route-executor.js +131 -3
- package/esm/src/security/deno-permissions.d.ts +6 -0
- package/esm/src/security/deno-permissions.d.ts.map +1 -1
- package/esm/src/security/deno-permissions.js +10 -0
- package/esm/src/security/sandbox/project-worker.d.ts +61 -0
- package/esm/src/security/sandbox/project-worker.d.ts.map +1 -0
- package/esm/src/security/sandbox/project-worker.js +318 -0
- package/esm/src/security/sandbox/worker-permissions.d.ts +30 -0
- package/esm/src/security/sandbox/worker-permissions.d.ts.map +1 -0
- package/esm/src/security/sandbox/worker-permissions.js +60 -0
- package/esm/src/security/sandbox/worker-pool.d.ts +87 -0
- package/esm/src/security/sandbox/worker-pool.d.ts.map +1 -0
- package/esm/src/security/sandbox/worker-pool.js +356 -0
- package/esm/src/security/sandbox/worker-types.d.ts +165 -0
- package/esm/src/security/sandbox/worker-types.d.ts.map +1 -0
- package/esm/src/security/sandbox/worker-types.js +17 -0
- package/esm/src/server/handlers/request/ssr/ssr.handler.d.ts +2 -0
- package/esm/src/server/handlers/request/ssr/ssr.handler.d.ts.map +1 -1
- package/esm/src/server/handlers/request/ssr/ssr.handler.js +6 -2
- package/esm/src/server/project-env/storage.d.ts +6 -0
- package/esm/src/server/project-env/storage.d.ts.map +1 -1
- package/esm/src/server/project-env/storage.js +8 -0
- package/esm/src/server/runtime-handler/adapter-factory.d.ts +3 -0
- package/esm/src/server/runtime-handler/adapter-factory.d.ts.map +1 -1
- package/esm/src/server/runtime-handler/adapter-factory.js +6 -5
- package/esm/src/server/runtime-handler/index.d.ts +33 -0
- package/esm/src/server/runtime-handler/index.d.ts.map +1 -1
- package/esm/src/server/runtime-handler/index.js +103 -37
- package/esm/src/server/runtime-handler/local-project-discovery.d.ts +32 -4
- package/esm/src/server/runtime-handler/local-project-discovery.d.ts.map +1 -1
- package/esm/src/server/runtime-handler/local-project-discovery.js +46 -16
- package/esm/src/server/runtime-handler/project-isolation.d.ts +5 -0
- package/esm/src/server/runtime-handler/project-isolation.d.ts.map +1 -1
- package/esm/src/server/runtime-handler/project-isolation.js +44 -0
- package/esm/src/server/services/rendering/ssr.service.d.ts +19 -1
- package/esm/src/server/services/rendering/ssr.service.d.ts.map +1 -1
- package/esm/src/server/services/rendering/ssr.service.js +9 -1
- package/esm/src/server/shared/renderer/adapter.d.ts +25 -0
- package/esm/src/server/shared/renderer/adapter.d.ts.map +1 -1
- package/esm/src/server/shared/renderer/adapter.js +83 -10
- package/esm/src/server/shared/renderer/index.d.ts +1 -1
- package/esm/src/server/shared/renderer/index.d.ts.map +1 -1
- package/esm/src/server/shared/renderer/index.js +1 -1
- package/esm/src/server/shared/renderer/memory/pressure.d.ts +7 -0
- package/esm/src/server/shared/renderer/memory/pressure.d.ts.map +1 -1
- package/esm/src/server/shared/renderer/memory/pressure.js +7 -0
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.d.ts +4 -4
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.js +15 -15
- package/esm/src/utils/index.d.ts +10 -1
- package/esm/src/utils/index.d.ts.map +1 -1
- package/esm/src/utils/index.js +9 -1
- package/esm/src/utils/logger/index.d.ts +1 -1
- package/esm/src/utils/logger/index.d.ts.map +1 -1
- package/esm/src/utils/logger/index.js +1 -1
- package/esm/src/utils/logger/logger.d.ts +14 -0
- package/esm/src/utils/logger/logger.d.ts.map +1 -1
- package/esm/src/utils/logger/logger.js +17 -0
- package/esm/src/workflow/claude-code/tool.d.ts +5 -5
- package/package.json +4 -1
- package/src/cli/commands/knowledge/command-help.ts +3 -1
- package/src/cli/commands/knowledge/command.ts +180 -22
- package/src/cli/commands/knowledge/parser-source.ts +110 -5
- package/src/deno.js +3 -1
- package/src/src/data/data-fetcher.ts +18 -2
- package/src/src/data/index.ts +1 -1
- package/src/src/data/server-data-fetcher.ts +78 -3
- package/src/src/rendering/orchestrator/lifecycle.ts +11 -0
- package/src/src/rendering/orchestrator/pipeline.ts +7 -2
- package/src/src/rendering/orchestrator/ssr-orchestrator.ts +119 -0
- package/src/src/routing/api/handler.ts +16 -3
- package/src/src/routing/api/route-executor.ts +222 -1
- package/src/src/security/deno-permissions.ts +11 -0
- package/src/src/security/sandbox/project-worker.ts +416 -0
- package/src/src/security/sandbox/worker-permissions.ts +74 -0
- package/src/src/security/sandbox/worker-pool.ts +451 -0
- package/src/src/security/sandbox/worker-types.ts +209 -0
- package/src/src/server/handlers/request/ssr/ssr.handler.ts +11 -2
- package/src/src/server/project-env/storage.ts +9 -0
- package/src/src/server/runtime-handler/adapter-factory.ts +13 -5
- package/src/src/server/runtime-handler/index.ts +132 -39
- package/src/src/server/runtime-handler/local-project-discovery.ts +51 -17
- package/src/src/server/runtime-handler/project-isolation.ts +53 -0
- package/src/src/server/services/rendering/ssr.service.ts +34 -3
- package/src/src/server/shared/renderer/adapter.ts +107 -8
- package/src/src/server/shared/renderer/index.ts +7 -1
- package/src/src/server/shared/renderer/memory/pressure.ts +8 -0
- package/src/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.ts +18 -12
- package/src/src/utils/index.ts +11 -0
- package/src/src/utils/logger/index.ts +1 -0
- package/src/src/utils/logger/logger.ts +34 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { WorkerPermissions } from "./worker-permissions.js";
|
|
2
|
+
import type { WorkerRequest, WorkerResponse } from "./worker-types.js";
|
|
3
|
+
export interface ProjectWorkerOptions {
|
|
4
|
+
projectId: string;
|
|
5
|
+
permissions: WorkerPermissions;
|
|
6
|
+
requestTimeoutMs: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Status of a project worker.
|
|
10
|
+
*/
|
|
11
|
+
export type WorkerStatus = "idle" | "busy" | "crashed" | "terminated";
|
|
12
|
+
export declare class ProjectWorker {
|
|
13
|
+
readonly projectId: string;
|
|
14
|
+
private worker;
|
|
15
|
+
private pending;
|
|
16
|
+
private streamHandlers;
|
|
17
|
+
private requestTimeoutMs;
|
|
18
|
+
private permissions;
|
|
19
|
+
private _requestCount;
|
|
20
|
+
private _lastActivityAt;
|
|
21
|
+
private _status;
|
|
22
|
+
constructor(options: ProjectWorkerOptions);
|
|
23
|
+
get status(): WorkerStatus;
|
|
24
|
+
get requestCount(): number;
|
|
25
|
+
get lastActivityAt(): number;
|
|
26
|
+
get hasPendingRequests(): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Start the worker. Idempotent — safe to call if already running.
|
|
29
|
+
*/
|
|
30
|
+
start(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Execute a request in this worker. Returns a typed response.
|
|
33
|
+
*/
|
|
34
|
+
execute(request: WorkerRequest): Promise<WorkerResponse>;
|
|
35
|
+
/**
|
|
36
|
+
* Execute a streaming request. Returns a ReadableStream that yields
|
|
37
|
+
* chunks as they arrive from the Worker via postMessage.
|
|
38
|
+
*
|
|
39
|
+
* Used for streaming SSR where the Worker sends chunks progressively.
|
|
40
|
+
* Falls back to a single-chunk stream if the Worker returns a non-streaming
|
|
41
|
+
* response (ssr-result with full HTML).
|
|
42
|
+
*/
|
|
43
|
+
executeStream(request: WorkerRequest): ReadableStream<Uint8Array>;
|
|
44
|
+
/**
|
|
45
|
+
* Health check — send a ping and wait for pong.
|
|
46
|
+
*/
|
|
47
|
+
isHealthy(timeoutMs?: number): Promise<boolean>;
|
|
48
|
+
/**
|
|
49
|
+
* Clear the worker's module cache. Used for dev mode hot reload.
|
|
50
|
+
*/
|
|
51
|
+
clearModuleCache(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Terminate the worker. Rejects all pending requests.
|
|
54
|
+
*/
|
|
55
|
+
terminate(): void;
|
|
56
|
+
private getWorkerScriptUrl;
|
|
57
|
+
private handleMessage;
|
|
58
|
+
private updateIdleStatus;
|
|
59
|
+
private rejectAllPending;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=project-worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-worker.d.ts","sourceRoot":"","sources":["../../../../src/src/security/sandbox/project-worker.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EAGf,MAAM,mBAAmB,CAAC;AAU3B,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,iBAAiB,CAAC;IAC/B,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAcD;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,YAAY,CAAC;AAEtE,qBAAa,aAAa;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,cAAc,CAAoC;IAC1D,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,eAAe,CAAc;IACrC,OAAO,CAAC,OAAO,CAAwB;gBAE3B,OAAO,EAAE,oBAAoB;IAMzC,IAAI,MAAM,IAAI,YAAY,CAEzB;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED,IAAI,kBAAkB,IAAI,OAAO,CAEhC;IAED;;OAEG;IACH,KAAK,IAAI,IAAI;IA+Bb;;OAEG;IACH,OAAO,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;IAqCxD;;;;;;;OAOG;IACH,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,cAAc,CAAC,UAAU,CAAC;IAgGjE;;OAEG;IACG,SAAS,CAAC,SAAS,SAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IA+BpD;;OAEG;IACH,gBAAgB,IAAI,IAAI;IAKxB;;OAEG;IACH,SAAS,IAAI,IAAI;IAuBjB,OAAO,CAAC,kBAAkB;IAc1B,OAAO,CAAC,aAAa;IA+CrB,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,gBAAgB;CAazB"}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Worker
|
|
3
|
+
*
|
|
4
|
+
* Wraps a single Deno Worker for one project. Manages the Worker lifecycle,
|
|
5
|
+
* sends/receives structured messages, enforces per-request timeouts,
|
|
6
|
+
* and serializes errors across the Worker boundary.
|
|
7
|
+
*
|
|
8
|
+
* @module security/sandbox/project-worker
|
|
9
|
+
*/
|
|
10
|
+
import * as dntShim from "../../../_dnt.shims.js";
|
|
11
|
+
import { serverLogger } from "../../utils/index.js";
|
|
12
|
+
import { isCompiledBinary } from "../../utils/index.js";
|
|
13
|
+
import { withSpan } from "../../observability/tracing/otlp-setup.js";
|
|
14
|
+
import { TIMEOUT_ERROR, UNKNOWN_ERROR } from "../../errors/index.js";
|
|
15
|
+
const logger = serverLogger.component("project-worker");
|
|
16
|
+
export class ProjectWorker {
|
|
17
|
+
projectId;
|
|
18
|
+
worker = null;
|
|
19
|
+
pending = new Map();
|
|
20
|
+
streamHandlers = new Map();
|
|
21
|
+
requestTimeoutMs;
|
|
22
|
+
permissions;
|
|
23
|
+
_requestCount = 0;
|
|
24
|
+
_lastActivityAt = Date.now();
|
|
25
|
+
_status = "idle";
|
|
26
|
+
constructor(options) {
|
|
27
|
+
this.projectId = options.projectId;
|
|
28
|
+
this.permissions = options.permissions;
|
|
29
|
+
this.requestTimeoutMs = options.requestTimeoutMs;
|
|
30
|
+
}
|
|
31
|
+
get status() {
|
|
32
|
+
return this._status;
|
|
33
|
+
}
|
|
34
|
+
get requestCount() {
|
|
35
|
+
return this._requestCount;
|
|
36
|
+
}
|
|
37
|
+
get lastActivityAt() {
|
|
38
|
+
return this._lastActivityAt;
|
|
39
|
+
}
|
|
40
|
+
get hasPendingRequests() {
|
|
41
|
+
return this.pending.size > 0;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Start the worker. Idempotent — safe to call if already running.
|
|
45
|
+
*/
|
|
46
|
+
start() {
|
|
47
|
+
if (this.worker)
|
|
48
|
+
return;
|
|
49
|
+
const workerUrl = this.getWorkerScriptUrl();
|
|
50
|
+
const workerOptions = {
|
|
51
|
+
type: "module",
|
|
52
|
+
name: `project-worker-${this.projectId}`,
|
|
53
|
+
deno: { permissions: this.permissions },
|
|
54
|
+
};
|
|
55
|
+
// @ts-ignore - Deno Worker accepts extended options
|
|
56
|
+
this.worker = new Worker(workerUrl, workerOptions);
|
|
57
|
+
this._status = "idle";
|
|
58
|
+
this.worker.onmessage = (event) => {
|
|
59
|
+
this.handleMessage(event.data);
|
|
60
|
+
};
|
|
61
|
+
this.worker.onerror = (event) => {
|
|
62
|
+
logger.error("Worker error", {
|
|
63
|
+
projectId: this.projectId,
|
|
64
|
+
error: event.message ?? String(event),
|
|
65
|
+
});
|
|
66
|
+
this._status = "crashed";
|
|
67
|
+
this.rejectAllPending("Worker crashed");
|
|
68
|
+
};
|
|
69
|
+
logger.debug("Worker started", { projectId: this.projectId });
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Execute a request in this worker. Returns a typed response.
|
|
73
|
+
*/
|
|
74
|
+
execute(request) {
|
|
75
|
+
return withSpan("worker.execute", () => {
|
|
76
|
+
if (!this.worker || this._status === "crashed" || this._status === "terminated") {
|
|
77
|
+
return Promise.reject(UNKNOWN_ERROR.create({ detail: `Worker not available (status: ${this._status})` }));
|
|
78
|
+
}
|
|
79
|
+
this._requestCount++;
|
|
80
|
+
this._lastActivityAt = Date.now();
|
|
81
|
+
this._status = "busy";
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
const timer = dntShim.setTimeout(() => {
|
|
84
|
+
this.pending.delete(request.id);
|
|
85
|
+
this.updateIdleStatus();
|
|
86
|
+
reject(TIMEOUT_ERROR.create({
|
|
87
|
+
detail: `Worker request timed out after ${this.requestTimeoutMs}ms`,
|
|
88
|
+
}));
|
|
89
|
+
}, this.requestTimeoutMs);
|
|
90
|
+
this.pending.set(request.id, { resolve, reject, timer });
|
|
91
|
+
this.worker.postMessage(request);
|
|
92
|
+
});
|
|
93
|
+
}, {
|
|
94
|
+
"worker.projectId": this.projectId,
|
|
95
|
+
"worker.requestType": request.type,
|
|
96
|
+
"worker.requestId": request.id,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Execute a streaming request. Returns a ReadableStream that yields
|
|
101
|
+
* chunks as they arrive from the Worker via postMessage.
|
|
102
|
+
*
|
|
103
|
+
* Used for streaming SSR where the Worker sends chunks progressively.
|
|
104
|
+
* Falls back to a single-chunk stream if the Worker returns a non-streaming
|
|
105
|
+
* response (ssr-result with full HTML).
|
|
106
|
+
*/
|
|
107
|
+
executeStream(request) {
|
|
108
|
+
if (!this.worker || this._status === "crashed" || this._status === "terminated") {
|
|
109
|
+
throw UNKNOWN_ERROR.create({ detail: `Worker not available (status: ${this._status})` });
|
|
110
|
+
}
|
|
111
|
+
this._requestCount++;
|
|
112
|
+
this._lastActivityAt = Date.now();
|
|
113
|
+
this._status = "busy";
|
|
114
|
+
const requestId = request.id;
|
|
115
|
+
const encoder = new TextEncoder();
|
|
116
|
+
return new ReadableStream({
|
|
117
|
+
start: (controller) => {
|
|
118
|
+
let timer = dntShim.setTimeout(() => {
|
|
119
|
+
this.streamHandlers.delete(requestId);
|
|
120
|
+
this.pending.delete(requestId);
|
|
121
|
+
this.updateIdleStatus();
|
|
122
|
+
controller.error(TIMEOUT_ERROR.create({
|
|
123
|
+
detail: `Worker stream timed out after ${this.requestTimeoutMs}ms`,
|
|
124
|
+
}));
|
|
125
|
+
}, this.requestTimeoutMs);
|
|
126
|
+
const resetTimer = () => {
|
|
127
|
+
clearTimeout(timer);
|
|
128
|
+
timer = dntShim.setTimeout(() => {
|
|
129
|
+
this.streamHandlers.delete(requestId);
|
|
130
|
+
this.pending.delete(requestId);
|
|
131
|
+
this.updateIdleStatus();
|
|
132
|
+
controller.error(TIMEOUT_ERROR.create({
|
|
133
|
+
detail: `Worker stream timed out after ${this.requestTimeoutMs}ms`,
|
|
134
|
+
}));
|
|
135
|
+
}, this.requestTimeoutMs);
|
|
136
|
+
};
|
|
137
|
+
// Register a stream handler for this request
|
|
138
|
+
this.streamHandlers.set(requestId, {
|
|
139
|
+
onChunk: (chunk) => {
|
|
140
|
+
resetTimer();
|
|
141
|
+
controller.enqueue(chunk);
|
|
142
|
+
},
|
|
143
|
+
onEnd: () => {
|
|
144
|
+
clearTimeout(timer);
|
|
145
|
+
this.streamHandlers.delete(requestId);
|
|
146
|
+
this.pending.delete(requestId);
|
|
147
|
+
this.updateIdleStatus();
|
|
148
|
+
controller.close();
|
|
149
|
+
},
|
|
150
|
+
onError: (error) => {
|
|
151
|
+
clearTimeout(timer);
|
|
152
|
+
this.streamHandlers.delete(requestId);
|
|
153
|
+
this.pending.delete(requestId);
|
|
154
|
+
this.updateIdleStatus();
|
|
155
|
+
controller.error(error);
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
// Also register in pending for non-streaming responses (fallback)
|
|
159
|
+
this.pending.set(requestId, {
|
|
160
|
+
resolve: (response) => {
|
|
161
|
+
clearTimeout(timer);
|
|
162
|
+
this.streamHandlers.delete(requestId);
|
|
163
|
+
this.pending.delete(requestId);
|
|
164
|
+
this.updateIdleStatus();
|
|
165
|
+
// If we get an ssr-result, emit it as a single chunk
|
|
166
|
+
if (response.type === "ssr-result") {
|
|
167
|
+
controller.enqueue(encoder.encode(response.html));
|
|
168
|
+
controller.close();
|
|
169
|
+
}
|
|
170
|
+
else if (response.type === "error") {
|
|
171
|
+
const err = new Error(response.error.message);
|
|
172
|
+
err.name = response.error.name;
|
|
173
|
+
controller.error(err);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
controller.close();
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
reject: (error) => {
|
|
180
|
+
clearTimeout(timer);
|
|
181
|
+
this.streamHandlers.delete(requestId);
|
|
182
|
+
this.pending.delete(requestId);
|
|
183
|
+
this.updateIdleStatus();
|
|
184
|
+
controller.error(error);
|
|
185
|
+
},
|
|
186
|
+
timer,
|
|
187
|
+
});
|
|
188
|
+
this.worker.postMessage(request);
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Health check — send a ping and wait for pong.
|
|
194
|
+
*/
|
|
195
|
+
async isHealthy(timeoutMs = 5_000) {
|
|
196
|
+
if (!this.worker || this._status === "crashed" || this._status === "terminated") {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
const id = dntShim.crypto.randomUUID();
|
|
200
|
+
return new Promise((resolve) => {
|
|
201
|
+
const timer = dntShim.setTimeout(() => {
|
|
202
|
+
this.pending.delete(id);
|
|
203
|
+
resolve(false);
|
|
204
|
+
}, timeoutMs);
|
|
205
|
+
this.pending.set(id, {
|
|
206
|
+
resolve: () => {
|
|
207
|
+
clearTimeout(timer);
|
|
208
|
+
this.pending.delete(id);
|
|
209
|
+
resolve(true);
|
|
210
|
+
},
|
|
211
|
+
reject: () => {
|
|
212
|
+
clearTimeout(timer);
|
|
213
|
+
this.pending.delete(id);
|
|
214
|
+
resolve(false);
|
|
215
|
+
},
|
|
216
|
+
timer,
|
|
217
|
+
});
|
|
218
|
+
this.worker.postMessage({ type: "ping", id });
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Clear the worker's module cache. Used for dev mode hot reload.
|
|
223
|
+
*/
|
|
224
|
+
clearModuleCache() {
|
|
225
|
+
if (!this.worker || this._status === "crashed" || this._status === "terminated")
|
|
226
|
+
return;
|
|
227
|
+
this.worker.postMessage({ type: "clear-cache" });
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Terminate the worker. Rejects all pending requests.
|
|
231
|
+
*/
|
|
232
|
+
terminate() {
|
|
233
|
+
if (!this.worker)
|
|
234
|
+
return;
|
|
235
|
+
this._status = "terminated";
|
|
236
|
+
this.rejectAllPending("Worker terminated");
|
|
237
|
+
try {
|
|
238
|
+
this.worker.terminate();
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
logger.debug("Worker terminate failed", {
|
|
242
|
+
projectId: this.projectId,
|
|
243
|
+
error,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
this.worker = null;
|
|
247
|
+
logger.debug("Worker terminated", { projectId: this.projectId });
|
|
248
|
+
}
|
|
249
|
+
// -----------------------------------------------------------------------
|
|
250
|
+
// Private
|
|
251
|
+
// -----------------------------------------------------------------------
|
|
252
|
+
getWorkerScriptUrl() {
|
|
253
|
+
// In compiled binary mode, use a data URL because blob URLs don't work
|
|
254
|
+
// See: deno-sandbox.ts for the same pattern
|
|
255
|
+
if (isCompiledBinary()) {
|
|
256
|
+
// For compiled binaries, we'd need to inline the worker script.
|
|
257
|
+
// For now, fall through to the import.meta.resolve path which works
|
|
258
|
+
// in development and standard Deno execution.
|
|
259
|
+
}
|
|
260
|
+
// Use import.meta.resolve to get the absolute URL of the worker script.
|
|
261
|
+
// This works in both `deno run` and `deno compile` contexts.
|
|
262
|
+
return globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).resolve("./worker-script.ts");
|
|
263
|
+
}
|
|
264
|
+
handleMessage(data) {
|
|
265
|
+
if (data.type === "pong") {
|
|
266
|
+
const pending = this.pending.get(data.id);
|
|
267
|
+
if (pending) {
|
|
268
|
+
clearTimeout(pending.timer);
|
|
269
|
+
pending.resolve(data);
|
|
270
|
+
this.pending.delete(data.id);
|
|
271
|
+
}
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
// Handle streaming SSR chunks
|
|
275
|
+
if (data.type === "stream-chunk") {
|
|
276
|
+
const handler = this.streamHandlers.get(data.id);
|
|
277
|
+
if (handler)
|
|
278
|
+
handler.onChunk(data.chunk);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (data.type === "stream-end") {
|
|
282
|
+
const handler = this.streamHandlers.get(data.id);
|
|
283
|
+
if (handler)
|
|
284
|
+
handler.onEnd();
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const response = data;
|
|
288
|
+
const pending = this.pending.get(response.id);
|
|
289
|
+
if (!pending) {
|
|
290
|
+
logger.warn("Received response for unknown request", {
|
|
291
|
+
projectId: this.projectId,
|
|
292
|
+
id: response.id,
|
|
293
|
+
});
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
clearTimeout(pending.timer);
|
|
297
|
+
this.pending.delete(response.id);
|
|
298
|
+
this.updateIdleStatus();
|
|
299
|
+
pending.resolve(response);
|
|
300
|
+
}
|
|
301
|
+
updateIdleStatus() {
|
|
302
|
+
if (this.pending.size === 0 && this._status === "busy") {
|
|
303
|
+
this._status = "idle";
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
rejectAllPending(reason) {
|
|
307
|
+
for (const [id, pending] of this.pending) {
|
|
308
|
+
clearTimeout(pending.timer);
|
|
309
|
+
pending.reject(UNKNOWN_ERROR.create({ detail: reason }));
|
|
310
|
+
this.pending.delete(id);
|
|
311
|
+
}
|
|
312
|
+
// Clean up stream handlers
|
|
313
|
+
for (const [id, handler] of this.streamHandlers) {
|
|
314
|
+
handler.onError(UNKNOWN_ERROR.create({ detail: reason }));
|
|
315
|
+
this.streamHandlers.delete(id);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker Permission Builder
|
|
3
|
+
*
|
|
4
|
+
* Builds scoped Deno Worker permissions for per-project isolation.
|
|
5
|
+
* Each project worker gets the minimum required permissions.
|
|
6
|
+
*
|
|
7
|
+
* @module security/sandbox/worker-permissions
|
|
8
|
+
*/
|
|
9
|
+
export interface WorkerPermissions {
|
|
10
|
+
read: string[] | boolean;
|
|
11
|
+
write: boolean;
|
|
12
|
+
net: boolean;
|
|
13
|
+
env: boolean;
|
|
14
|
+
run: boolean;
|
|
15
|
+
ffi: boolean;
|
|
16
|
+
sys: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Build scoped permissions for a project worker.
|
|
20
|
+
*
|
|
21
|
+
* - read: restricted to the project temp dir (transformed modules) and cache dirs
|
|
22
|
+
* - write: denied (workers produce output via postMessage, not filesystem)
|
|
23
|
+
* - net: allowed (data fetchers and API routes may call external APIs)
|
|
24
|
+
* - env: allowed (user code reads API keys and config from environment)
|
|
25
|
+
* - run: denied (no subprocess spawning from user code)
|
|
26
|
+
* - ffi: denied (no native code from user code)
|
|
27
|
+
* - sys: denied (no system info access from user code)
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildWorkerPermissions(readPaths: string[]): WorkerPermissions;
|
|
30
|
+
//# sourceMappingURL=worker-permissions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-permissions.d.ts","sourceRoot":"","sources":["../../../../src/src/security/sandbox/worker-permissions.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IACzB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;CACd;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,EAAE,GAClB,iBAAiB,CAmCnB"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker Permission Builder
|
|
3
|
+
*
|
|
4
|
+
* Builds scoped Deno Worker permissions for per-project isolation.
|
|
5
|
+
* Each project worker gets the minimum required permissions.
|
|
6
|
+
*
|
|
7
|
+
* @module security/sandbox/worker-permissions
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Deno Worker permission object.
|
|
11
|
+
* See: https://docs.deno.com/runtime/fundamentals/permissions/
|
|
12
|
+
*/
|
|
13
|
+
import * as dntShim from "../../../_dnt.shims.js";
|
|
14
|
+
/**
|
|
15
|
+
* Build scoped permissions for a project worker.
|
|
16
|
+
*
|
|
17
|
+
* - read: restricted to the project temp dir (transformed modules) and cache dirs
|
|
18
|
+
* - write: denied (workers produce output via postMessage, not filesystem)
|
|
19
|
+
* - net: allowed (data fetchers and API routes may call external APIs)
|
|
20
|
+
* - env: allowed (user code reads API keys and config from environment)
|
|
21
|
+
* - run: denied (no subprocess spawning from user code)
|
|
22
|
+
* - ffi: denied (no native code from user code)
|
|
23
|
+
* - sys: denied (no system info access from user code)
|
|
24
|
+
*/
|
|
25
|
+
export function buildWorkerPermissions(readPaths) {
|
|
26
|
+
// In compiled binaries, user modules import from the VFS temp dir which
|
|
27
|
+
// is outside the project directory. Rather than trying to enumerate all
|
|
28
|
+
// read paths, grant full read access — the security boundary is enforced
|
|
29
|
+
// by denying write/run/ffi/sys, not by restricting reads.
|
|
30
|
+
// Check for compiled binary by testing if execPath is NOT "deno"/"deno.exe"
|
|
31
|
+
try {
|
|
32
|
+
const exec = typeof dntShim.Deno !== "undefined" ? dntShim.Deno.execPath?.() : undefined;
|
|
33
|
+
if (exec) {
|
|
34
|
+
const name = exec.split(/[/\\]/).pop()?.toLowerCase() ?? "";
|
|
35
|
+
if (name !== "deno" && name !== "deno.exe") {
|
|
36
|
+
return {
|
|
37
|
+
read: true,
|
|
38
|
+
write: false,
|
|
39
|
+
net: true,
|
|
40
|
+
env: true,
|
|
41
|
+
run: false,
|
|
42
|
+
ffi: false,
|
|
43
|
+
sys: false,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// execPath may not be available
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
read: readPaths.length > 0 ? readPaths : false,
|
|
53
|
+
write: false,
|
|
54
|
+
net: true,
|
|
55
|
+
env: true,
|
|
56
|
+
run: false,
|
|
57
|
+
ffi: false,
|
|
58
|
+
sys: false,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { ProjectWorker } from "./project-worker.js";
|
|
2
|
+
import type { WorkerPoolConfig, WorkerRequest, WorkerResponse } from "./worker-types.js";
|
|
3
|
+
export declare class WorkerPool {
|
|
4
|
+
private pool;
|
|
5
|
+
private recycling;
|
|
6
|
+
private config;
|
|
7
|
+
private cleanupInterval;
|
|
8
|
+
private healthCheckInterval;
|
|
9
|
+
constructor(config?: Partial<WorkerPoolConfig>);
|
|
10
|
+
/**
|
|
11
|
+
* Get or create a worker for the given project.
|
|
12
|
+
*/
|
|
13
|
+
getOrCreateWorker(projectId: string, readPaths: string[]): ProjectWorker;
|
|
14
|
+
/**
|
|
15
|
+
* Execute a request in a project worker. Convenience method that
|
|
16
|
+
* combines getOrCreateWorker + execute.
|
|
17
|
+
*/
|
|
18
|
+
execute(projectId: string, readPaths: string[], request: WorkerRequest): Promise<WorkerResponse>;
|
|
19
|
+
/**
|
|
20
|
+
* Evict a specific project's worker.
|
|
21
|
+
*/
|
|
22
|
+
evictWorker(projectId: string): void;
|
|
23
|
+
/**
|
|
24
|
+
* Get pool statistics for monitoring.
|
|
25
|
+
*/
|
|
26
|
+
getStats(): {
|
|
27
|
+
poolSize: number;
|
|
28
|
+
maxPoolSize: number;
|
|
29
|
+
memoryBudgetMb: number;
|
|
30
|
+
workers: Record<string, {
|
|
31
|
+
status: string;
|
|
32
|
+
requestCount: number;
|
|
33
|
+
hasPending: boolean;
|
|
34
|
+
idleMs: number;
|
|
35
|
+
ageMs: number;
|
|
36
|
+
}>;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Get aggregate metrics suitable for Prometheus exposition.
|
|
40
|
+
*/
|
|
41
|
+
getMetrics(): {
|
|
42
|
+
/** Current number of active workers */
|
|
43
|
+
workerPoolSize: number;
|
|
44
|
+
/** Number of workers at capacity (max pool size) */
|
|
45
|
+
workerPoolCapacity: number;
|
|
46
|
+
/** Total requests processed across all workers */
|
|
47
|
+
totalRequestsProcessed: number;
|
|
48
|
+
/** Number of workers with pending requests (busy) */
|
|
49
|
+
busyWorkers: number;
|
|
50
|
+
/** Number of crashed workers (cleaned up at next health check) */
|
|
51
|
+
crashedWorkers: number;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Shutdown the pool. Terminates all workers and stops timers.
|
|
55
|
+
*/
|
|
56
|
+
shutdown(): void;
|
|
57
|
+
private startCleanup;
|
|
58
|
+
private startHealthChecks;
|
|
59
|
+
private evictIdleWorkers;
|
|
60
|
+
private evictIfNeeded;
|
|
61
|
+
private checkHealth;
|
|
62
|
+
/**
|
|
63
|
+
* Evict workers when the process is under memory pressure.
|
|
64
|
+
* Uses the global heap stats — if heap usage is above a threshold,
|
|
65
|
+
* evict idle workers starting with the oldest to free memory.
|
|
66
|
+
*/
|
|
67
|
+
private evictUnderMemoryPressure;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Whether worker isolation is enabled for API routes.
|
|
71
|
+
* Controlled by WORKER_ISOLATION_API=1 (or WORKER_ISOLATION_ENABLED=1 as master switch).
|
|
72
|
+
*/
|
|
73
|
+
export declare function isWorkerIsolationEnabled(): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Whether worker isolation is enabled for data fetchers (getServerData).
|
|
76
|
+
* Controlled by WORKER_ISOLATION_DATA=1 (requires WORKER_ISOLATION_ENABLED=1).
|
|
77
|
+
*/
|
|
78
|
+
export declare function isDataIsolationEnabled(): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Whether worker isolation is enabled for SSR rendering.
|
|
81
|
+
* Controlled by WORKER_ISOLATION_SSR=1 (requires WORKER_ISOLATION_ENABLED=1).
|
|
82
|
+
*/
|
|
83
|
+
export declare function isSSRIsolationEnabled(): boolean;
|
|
84
|
+
export declare function getWorkerPool(): WorkerPool;
|
|
85
|
+
/** Reset the singleton and cached flags — for testing only */
|
|
86
|
+
export declare function __resetPoolForTests(): void;
|
|
87
|
+
//# sourceMappingURL=worker-pool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-pool.d.ts","sourceRoot":"","sources":["../../../../src/src/security/sandbox/worker-pool.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAWzF,qBAAa,UAAU;IACrB,OAAO,CAAC,IAAI,CAAgC;IAC5C,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,MAAM,CAAmB;IAEjC,OAAO,CAAC,eAAe,CAAqD;IAC5E,OAAO,CAAC,mBAAmB,CAAqD;gBAEpE,MAAM,GAAE,OAAO,CAAC,gBAAgB,CAAM;IAMlD;;OAEG;IACH,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,aAAa;IA0CxE;;;OAGG;IACH,OAAO,CACL,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EAAE,EACnB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,cAAc,CAAC;IAkD1B;;OAEG;IACH,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAUpC;;OAEG;IACH,QAAQ,IAAI;QACV,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,cAAc,EAAE,MAAM,CAAC;QACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;YACtB,MAAM,EAAE,MAAM,CAAC;YACf,YAAY,EAAE,MAAM,CAAC;YACrB,UAAU,EAAE,OAAO,CAAC;YACpB,MAAM,EAAE,MAAM,CAAC;YACf,KAAK,EAAE,MAAM,CAAC;SACf,CAAC,CAAC;KACJ;IA4BD;;OAEG;IACH,UAAU,IAAI;QACZ,uCAAuC;QACvC,cAAc,EAAE,MAAM,CAAC;QACvB,oDAAoD;QACpD,kBAAkB,EAAE,MAAM,CAAC;QAC3B,kDAAkD;QAClD,sBAAsB,EAAE,MAAM,CAAC;QAC/B,qDAAqD;QACrD,WAAW,EAAE,MAAM,CAAC;QACpB,kEAAkE;QAClE,cAAc,EAAE,MAAM,CAAC;KACxB;IAoBD;;OAEG;IACH,QAAQ,IAAI,IAAI;IAgBhB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,gBAAgB;IAmBxB,OAAO,CAAC,aAAa;YA4BP,WAAW;IAmBzB;;;;OAIG;IACH,OAAO,CAAC,wBAAwB;CA8BjC;AAqBD;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,OAAO,CAGlD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAGhD;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAG/C;AAKD,wBAAgB,aAAa,IAAI,UAAU,CAiB1C;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,IAAI,IAAI,CAO1C"}
|