veryfront 0.1.656 → 0.1.657

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.
Files changed (48) hide show
  1. package/esm/cli/sync/ignore.d.ts.map +1 -1
  2. package/esm/cli/sync/ignore.js +66 -18
  3. package/esm/cli/templates/manifest.js +2 -2
  4. package/esm/deno.js +3 -3
  5. package/esm/src/agent/ag-ui/request-shared.d.ts.map +1 -1
  6. package/esm/src/agent/ag-ui/request-shared.js +1 -2
  7. package/esm/src/agent/index.d.ts +6 -4
  8. package/esm/src/agent/index.d.ts.map +1 -1
  9. package/esm/src/agent/index.js +6 -4
  10. package/esm/src/agent/runtime/message-adapter.d.ts +1 -1
  11. package/esm/src/agent/runtime/message-adapter.d.ts.map +1 -1
  12. package/esm/src/chat/message-prep.js +2 -2
  13. package/esm/src/internal-agents/run-stream.d.ts.map +1 -1
  14. package/esm/src/internal-agents/run-stream.js +7 -1
  15. package/esm/src/mcp/http-transport.d.ts +1 -1
  16. package/esm/src/mcp/http-transport.d.ts.map +1 -1
  17. package/esm/src/mcp/http-transport.js +4 -3
  18. package/esm/src/mcp/index.d.ts +2 -2
  19. package/esm/src/mcp/index.js +2 -2
  20. package/esm/src/mcp/server.d.ts +4 -6
  21. package/esm/src/mcp/server.d.ts.map +1 -1
  22. package/esm/src/mcp/server.js +42 -14
  23. package/esm/src/resource/index.d.ts +2 -2
  24. package/esm/src/resource/index.js +2 -2
  25. package/esm/src/resource/registry.d.ts.map +1 -1
  26. package/esm/src/resource/registry.js +5 -1
  27. package/esm/src/routing/api/module-loader/external-import-rewriter.d.ts.map +1 -1
  28. package/esm/src/routing/api/module-loader/external-import-rewriter.js +59 -59
  29. package/esm/src/security/client/html-sanitizer.d.ts +1 -0
  30. package/esm/src/security/client/html-sanitizer.d.ts.map +1 -1
  31. package/esm/src/security/client/html-sanitizer.js +44 -6
  32. package/esm/src/server/services/rsc/endpoints/rsc-bundles.generated.d.ts.map +1 -1
  33. package/esm/src/server/services/rsc/endpoints/rsc-bundles.generated.js +2 -2
  34. package/esm/src/server/services/rsc/orchestrators/page-handler.d.ts.map +1 -1
  35. package/esm/src/server/services/rsc/orchestrators/page-handler.js +3 -21
  36. package/esm/src/tool/factory.d.ts.map +1 -1
  37. package/esm/src/tool/factory.js +3 -35
  38. package/esm/src/tool/host-tools.d.ts.map +1 -1
  39. package/esm/src/tool/host-tools.js +2 -11
  40. package/esm/src/tool/index.d.ts +12 -8
  41. package/esm/src/tool/index.d.ts.map +1 -1
  42. package/esm/src/tool/index.js +12 -8
  43. package/esm/src/tool/schema/zod-json-schema.d.ts +6 -11
  44. package/esm/src/tool/schema/zod-json-schema.d.ts.map +1 -1
  45. package/esm/src/tool/schema/zod-json-schema.js +7 -37
  46. package/esm/src/utils/version-constant.d.ts +1 -1
  47. package/esm/src/utils/version-constant.js +1 -1
  48. package/package.json +1 -1
@@ -34,6 +34,7 @@ export declare class MCPServer {
34
34
  private sessionManager;
35
35
  private taskStore;
36
36
  private pendingTasks;
37
+ private pendingRequestAbortControllers;
37
38
  private clientCapabilities;
38
39
  private sessionCapabilities;
39
40
  /** Callback for server-initiated notifications. Set by transport layer. */
@@ -60,15 +61,11 @@ export declare class MCPServer {
60
61
  notifyResourcesChanged(): void;
61
62
  notifyPromptsChanged(): void;
62
63
  /**
63
- * Configure integration tools to be loaded from the API.
64
- *
65
- * When API-side integration tools are available (apiBaseUrl + apiToken),
66
- * tools are loaded remotely via the API's /integrations/tools/list endpoint.
67
- * Otherwise falls back to the legacy local loading path.
64
+ * Configure API-backed integration tool loading.
68
65
  */
69
66
  setIntegrationLoader(config: IntegrationLoaderConfig): void;
70
67
  clientSupportsElicitation(mode: "form" | "url", sessionId?: string): boolean;
71
- handleRequest(request: JSONRPCRequest, context?: ToolExecutionContext): Promise<JSONRPCResponse>;
68
+ handleRequest(request: JSONRPCRequest, context?: ToolExecutionContext, sessionId?: string): Promise<JSONRPCResponse>;
72
69
  private dispatch;
73
70
  private initialize;
74
71
  private listTools;
@@ -80,6 +77,7 @@ export declare class MCPServer {
80
77
  private getPrompt;
81
78
  private complete;
82
79
  private setLogLevel;
80
+ private cancelRequest;
83
81
  private getTask;
84
82
  private getTaskResult;
85
83
  private cancelTask;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/src/mcp/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/src/mcp/server.ts"],"names":[],"mappings":"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"}
@@ -48,6 +48,9 @@ function readAllowedHeader(request, headerName, pattern) {
48
48
  }
49
49
  return value;
50
50
  }
51
+ function pendingRequestKey(requestId, sessionId) {
52
+ return JSON.stringify([sessionId ?? null, typeof requestId, requestId]);
53
+ }
51
54
  const MCP_SUPPORTED_VERSIONS = ["2025-11-25", "2024-11-05"];
52
55
  /** Implement mcpserver. */
53
56
  export class MCPServer {
@@ -68,6 +71,7 @@ export class MCPServer {
68
71
  sessionManager = new SessionManager();
69
72
  taskStore = new TaskStore();
70
73
  pendingTasks = new Map();
74
+ pendingRequestAbortControllers = new Map();
71
75
  clientCapabilities = {};
72
76
  sessionCapabilities = new Map();
73
77
  /** Callback for server-initiated notifications. Set by transport layer. */
@@ -127,11 +131,7 @@ export class MCPServer {
127
131
  this.onNotification?.({ jsonrpc: "2.0", method: "notifications/prompts/list_changed" });
128
132
  }
129
133
  /**
130
- * Configure integration tools to be loaded from the API.
131
- *
132
- * When API-side integration tools are available (apiBaseUrl + apiToken),
133
- * tools are loaded remotely via the API's /integrations/tools/list endpoint.
134
- * Otherwise falls back to the legacy local loading path.
134
+ * Configure API-backed integration tool loading.
135
135
  */
136
136
  setIntegrationLoader(config) {
137
137
  this.integrationLoader = config;
@@ -150,10 +150,10 @@ export class MCPServer {
150
150
  return true;
151
151
  return mode in elicitation;
152
152
  }
153
- handleRequest(request, context) {
153
+ handleRequest(request, context, sessionId) {
154
154
  return withSpan("mcp.handleRequest", async () => {
155
155
  try {
156
- const result = await this.dispatch(request.method, request.params, context);
156
+ const result = await this.dispatch(request.method, request.params, context, request.id, sessionId);
157
157
  return { jsonrpc: "2.0", id: request.id, result };
158
158
  }
159
159
  catch (error) {
@@ -165,12 +165,12 @@ export class MCPServer {
165
165
  }
166
166
  }, { "mcp.method": request.method });
167
167
  }
168
- dispatch(method, params, context) {
168
+ dispatch(method, params, context, requestId, sessionId) {
169
169
  switch (method) {
170
170
  case "tools/list":
171
171
  return this.listTools(params);
172
172
  case "tools/call":
173
- return this.callTool(params, context);
173
+ return this.callTool(params, context, requestId, sessionId);
174
174
  case "resources/list":
175
175
  return this.listResources(params);
176
176
  case "resources/read":
@@ -186,8 +186,7 @@ export class MCPServer {
186
186
  case "notifications/initialized":
187
187
  return Promise.resolve({});
188
188
  case "notifications/cancelled":
189
- // TODO(#841): propagate cancellation to in-flight tool executions via AbortController
190
- return Promise.resolve({});
189
+ return this.cancelRequest(params, sessionId);
191
190
  case "completion/complete":
192
191
  return this.complete(params);
193
192
  case "logging/setLevel":
@@ -265,7 +264,7 @@ export class MCPServer {
265
264
  }
266
265
  return { tools };
267
266
  }
268
- callTool(params, context) {
267
+ callTool(params, context, requestId, sessionId) {
269
268
  const p = toParamsRecord(params);
270
269
  const { name, arguments: args } = p;
271
270
  const meta = (p._meta ?? {});
@@ -345,8 +344,23 @@ export class MCPServer {
345
344
  return Promise.resolve({ task });
346
345
  }
347
346
  return withSpan("mcp.callTool", async () => {
347
+ const abortController = requestId === undefined ? undefined : new AbortController();
348
+ const outerAbortSignal = toolContext?.abortSignal;
349
+ const abortFromOuterSignal = () => abortController?.abort();
350
+ if (abortController) {
351
+ if (outerAbortSignal?.aborted) {
352
+ abortController.abort();
353
+ }
354
+ else {
355
+ outerAbortSignal?.addEventListener("abort", abortFromOuterSignal, { once: true });
356
+ }
357
+ this.pendingRequestAbortControllers.set(pendingRequestKey(requestId, sessionId), abortController);
358
+ }
359
+ const foregroundToolContext = abortController
360
+ ? { ...toolContext, abortSignal: abortController.signal }
361
+ : toolContext;
348
362
  try {
349
- const result = await executeTool(toolName, args, toolContext);
363
+ const result = await executeTool(toolName, args, foregroundToolContext);
350
364
  return {
351
365
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
352
366
  isError: false,
@@ -359,6 +373,12 @@ export class MCPServer {
359
373
  isError: true,
360
374
  };
361
375
  }
376
+ finally {
377
+ if (requestId !== undefined) {
378
+ this.pendingRequestAbortControllers.delete(pendingRequestKey(requestId, sessionId));
379
+ }
380
+ outerAbortSignal?.removeEventListener("abort", abortFromOuterSignal);
381
+ }
362
382
  }, { "mcp.tool.name": toolName });
363
383
  }
364
384
  listResourceTemplates(_params) {
@@ -481,6 +501,14 @@ export class MCPServer {
481
501
  this.logLevel = level;
482
502
  return Promise.resolve({});
483
503
  }
504
+ cancelRequest(params, sessionId) {
505
+ const { requestId } = toParamsRecord(params);
506
+ if (typeof requestId !== "string" && typeof requestId !== "number") {
507
+ return Promise.resolve({});
508
+ }
509
+ this.pendingRequestAbortControllers.get(pendingRequestKey(requestId, sessionId))?.abort();
510
+ return Promise.resolve({});
511
+ }
484
512
  getTask(params) {
485
513
  const { taskId } = toParamsRecord(params);
486
514
  if (!taskId) {
@@ -532,7 +560,7 @@ export class MCPServer {
532
560
  authEnabled: this.config.auth.type !== "none",
533
561
  getCORSHeaders: (requestOrigin) => this.getCORSHeaders(requestOrigin),
534
562
  validateAuth: (request) => this.validateAuth(request),
535
- handleRequest: (request, context) => this.handleRequest(request, context),
563
+ handleRequest: (request, context, sessionId) => this.handleRequest(request, context, sessionId),
536
564
  extractRequestContext: (request) => this.extractRequestContext(request),
537
565
  isOriginAllowed: (requestOrigin) => !requestOrigin ||
538
566
  !this.config.cors?.enabled ||
@@ -6,7 +6,7 @@
6
6
  * @example
7
7
  * ```ts
8
8
  * import { resource } from "veryfront/resource";
9
- * import { z } from "zod";
9
+ * import { defineSchema } from "veryfront/schemas";
10
10
  *
11
11
  * const docsBySection: Record<string, string> = {
12
12
  * agents: "Agents accept messages, tools, context, and runtime options.",
@@ -16,7 +16,7 @@
16
16
  * const docs = resource({
17
17
  * pattern: "docs/:section",
18
18
  * description: "API documentation",
19
- * paramsSchema: z.object({ section: z.string() }),
19
+ * paramsSchema: defineSchema((v) => v.object({ section: v.string() }))(),
20
20
  * load: ({ section }) => {
21
21
  * return { content: docsBySection[section] ?? "Section not found." };
22
22
  * },
@@ -6,7 +6,7 @@
6
6
  * @example
7
7
  * ```ts
8
8
  * import { resource } from "veryfront/resource";
9
- * import { z } from "zod";
9
+ * import { defineSchema } from "veryfront/schemas";
10
10
  *
11
11
  * const docsBySection: Record<string, string> = {
12
12
  * agents: "Agents accept messages, tools, context, and runtime options.",
@@ -16,7 +16,7 @@
16
16
  * const docs = resource({
17
17
  * pattern: "docs/:section",
18
18
  * description: "API documentation",
19
- * paramsSchema: z.object({ section: z.string() }),
19
+ * paramsSchema: defineSchema((v) => v.object({ section: v.string() }))(),
20
20
  * load: ({ section }) => {
21
21
  * return { content: docsBySection[section] ?? "Section not found." };
22
22
  * },
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/src/resource/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAK7E,cAAM,gBAAiB,SAAQ,oBAAoB,CAAC,QAAQ,CAAC;IAC3D,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;IAOhD,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,YAAY;IAIpB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAInE,IAAI,IAAI,MAAM,EAAE;CAGjB;AAED,sCAAsC;AACtC,eAAO,MAAM,gBAAgB,kBAAgD,CAAC"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/src/resource/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAS7E,cAAM,gBAAiB,SAAQ,oBAAoB,CAAC,QAAQ,CAAC;IAC3D,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;IAOhD,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,YAAY;IAIpB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAInE,IAAI,IAAI,MAAM,EAAE;CAGjB;AAED,sCAAsC;AACtC,eAAO,MAAM,gBAAgB,kBAAgD,CAAC"}
@@ -9,6 +9,9 @@
9
9
  import { ScopedRegistryFacade } from "../registry/scoped-registry-facade.js";
10
10
  import { ProjectScopedRegistryManager } from "../registry/project-scoped-registry-manager.js";
11
11
  const resourceRegistryManager = new ProjectScopedRegistryManager("resource");
12
+ function escapeRegExp(value) {
13
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14
+ }
12
15
  class ResourceRegistry extends ScopedRegistryFacade {
13
16
  findByPattern(uri) {
14
17
  for (const resource of this.getAll().values()) {
@@ -18,7 +21,8 @@ class ResourceRegistry extends ScopedRegistryFacade {
18
21
  return undefined;
19
22
  }
20
23
  patternToRegex(pattern) {
21
- return new RegExp(`^${pattern.replace(/:(\w+)/g, "(?<$1>[^/]+)")}$`);
24
+ const escapedPattern = escapeRegExp(pattern).replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, "(?<$1>[^/]+)");
25
+ return new RegExp(`^${escapedPattern}$`);
22
26
  }
23
27
  matchPattern(uri, pattern) {
24
28
  return uri.match(this.patternToRegex(pattern));
@@ -1 +1 @@
1
- {"version":3,"file":"external-import-rewriter.d.ts","sourceRoot":"","sources":["../../../../../src/src/routing/api/module-loader/external-import-rewriter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AASjE,wGAAwG;AACxG,eAAO,MAAM,aAAa,mVAkChB,CAAC;AAEX,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,UAAU,GACb,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAS9B;AAED;;;;;;;;;GASG;AACH,wBAAgB,iCAAiC,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CA+E5E;AAED,wBAAgB,gCAAgC,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,CAUxF;AAED,wBAAsB,2BAA2B,CAC/C,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,UAAU,EACd,aAAa,EAAE,cAAc,UAAU,EAAE,aAAa,GACrD,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoBxB;AAED,+FAA+F;AAC/F,MAAM,WAAW,qBAAqB;IACpC,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,uFAAuF;IACvF,UAAU,EAAE,MAAM,CAAC;CACpB;AAoCD;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAC9C,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,UAAU,EACd,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAmC7C;AAED,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,UAAU,GACb,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAW9C;AAED,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,UAAU,EACd,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5B,OAAO,CAAC,MAAM,CAAC,CA6EjB;AAED,wBAAgB,qCAAqC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAqB1E;AAED,wBAAgB,0CAA0C,CACxD,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAC7B,OAAO,GAAE,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAa,GACtD,MAAM,CAgGR;AAED,wBAAsB,+BAA+B,CACnD,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,UAAU,EACd,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5B,OAAO,CAAC,MAAM,CAAC,CA0BjB;AAED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgBlE;AAED,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,UAAU,EACd,QAAQ,GAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAa,GACxC,OAAO,CAAC,MAAM,CAAC,CAmCjB"}
1
+ {"version":3,"file":"external-import-rewriter.d.ts","sourceRoot":"","sources":["../../../../../src/src/routing/api/module-loader/external-import-rewriter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAUjE,wGAAwG;AACxG,eAAO,MAAM,aAAa,mVAkChB,CAAC;AAEX,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,UAAU,GACb,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAS9B;AAED;;;;;;;;;GASG;AACH,wBAAgB,iCAAiC,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CA+E5E;AAED,wBAAgB,gCAAgC,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,CAUxF;AAED,wBAAsB,2BAA2B,CAC/C,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,UAAU,EACd,aAAa,EAAE,cAAc,UAAU,EAAE,aAAa,GACrD,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoBxB;AAED,+FAA+F;AAC/F,MAAM,WAAW,qBAAqB;IACpC,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,uFAAuF;IACvF,UAAU,EAAE,MAAM,CAAC;CACpB;AAoCD;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAC9C,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,UAAU,EACd,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAmC7C;AAED,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,UAAU,GACb,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAW9C;AAED,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,UAAU,EACd,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5B,OAAO,CAAC,MAAM,CAAC,CA+DjB;AAED,wBAAgB,qCAAqC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAqB1E;AAED,wBAAgB,0CAA0C,CACxD,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAC7B,OAAO,GAAE,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAa,GACtD,MAAM,CAgGR;AAED,wBAAsB,+BAA+B,CACnD,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,UAAU,EACd,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5B,OAAO,CAAC,MAAM,CAAC,CAgCjB;AAED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgBlE;AAED,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,UAAU,EACd,QAAQ,GAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAa,GACxC,OAAO,CAAC,MAAM,CAAC,CAmCjB"}
@@ -2,6 +2,7 @@ import { isCompiledBinary, serverLogger } from "../../../utils/index.js";
2
2
  import * as pathHelper from "../../../platform/compat/path/index.js";
3
3
  import { isDeno, isNode } from "../../../platform/compat/runtime.js";
4
4
  import { rewriteNpmImports } from "../../../transforms/npm-import-rewrites.js";
5
+ import { parseImports, replaceSpecifiers } from "../../../transforms/esm/lexer.js";
5
6
  import { isWithinDirectory } from "../../../security/path-validation.js";
6
7
  import { resolveExportEntry, toCjsDestructureBindings } from "./loader-helpers.js";
7
8
  const logger = serverLogger.component("api");
@@ -256,66 +257,57 @@ export async function loadVeryfrontExportsMap(projectDir, fs) {
256
257
  }
257
258
  export async function rewriteNodeExternalImports(code, projectDir, fs, userDeps) {
258
259
  const { pathToFileURL } = await import("node:url");
259
- let transformed = code;
260
+ const replacements = new Map();
260
261
  logger.debug(`Rewriting external imports for Node.js, projectDir: ${projectDir}`);
261
- for (const pkg of getNodeExternalPackagesToResolve(userDeps)) {
262
- const escapedPkg = pkg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
263
- const staticImportRegex = new RegExp(`from\\s*["']${escapedPkg}(/[^"']*)?["']`, "g");
264
- const dynamicImportRegex = new RegExp(`import\\s*\\(\\s*["']${escapedPkg}(/[^"']*)?["']\\s*\\)`, "g");
265
- const needsStatic = staticImportRegex.test(transformed);
266
- staticImportRegex.lastIndex = 0;
267
- const needsDynamic = dynamicImportRegex.test(transformed);
268
- dynamicImportRegex.lastIndex = 0;
269
- if (!needsStatic && !needsDynamic)
262
+ const importedSpecifiers = new Set((await parseImports(code))
263
+ .map((imp) => imp.n)
264
+ .filter((specifier) => typeof specifier === "string"));
265
+ const packages = getNodeExternalPackagesToResolve(userDeps);
266
+ for (const specifier of importedSpecifiers) {
267
+ const pkg = packages.find((name) => specifier === name || specifier.startsWith(`${name}/`));
268
+ if (!pkg)
269
+ continue;
270
+ const subpath = specifier.slice(pkg.length);
271
+ if (subpath) {
272
+ const packageDir = pathToFileURL(pathHelper.join(projectDir, "node_modules", pkg)).href;
273
+ const resolvedSubpath = `${packageDir}${subpath}`;
274
+ logger.debug(`Resolved ${specifier} -> ${resolvedSubpath}`);
275
+ replacements.set(specifier, resolvedSubpath);
270
276
  continue;
271
- const packageDir = pathToFileURL(pathHelper.join(projectDir, "node_modules", pkg)).href;
272
- const resolvedUrl = await resolveNodePackageToFileUrl(projectDir, pkg, fs, pathToFileURL);
273
- if (needsStatic) {
274
- transformed = transformed.replace(staticImportRegex, (_, subpath) => {
275
- if (subpath) {
276
- const subUrl = `${packageDir}${subpath}`;
277
- logger.debug(`Resolved ${pkg}${subpath} -> ${subUrl}`);
278
- return `from "${subUrl}"`;
279
- }
280
- if (!resolvedUrl)
281
- return `from "${pkg}"`;
282
- logger.debug(`Resolved ${pkg} -> ${resolvedUrl}`);
283
- return `from "${resolvedUrl}"`;
284
- });
285
- }
286
- if (needsDynamic) {
287
- transformed = transformed.replace(dynamicImportRegex, (_, subpath) => {
288
- if (subpath) {
289
- return `import("${packageDir}${subpath}")`;
290
- }
291
- if (!resolvedUrl)
292
- return `import("${pkg}")`;
293
- return `import("${resolvedUrl}")`;
294
- });
295
277
  }
278
+ const resolvedUrl = await resolveNodePackageToFileUrl(projectDir, pkg, fs, pathToFileURL);
279
+ if (!resolvedUrl)
280
+ continue;
281
+ logger.debug(`Resolved ${pkg} -> ${resolvedUrl}`);
282
+ replacements.set(specifier, resolvedUrl);
296
283
  }
297
284
  const vfPackagePath = pathHelper.join(projectDir, "node_modules", "veryfront");
298
285
  const exportsMap = await loadVeryfrontExportsMap(projectDir, fs);
299
- transformed = transformed.replace(/from\s+["'](veryfront\/[^"']+)["']/g, (match, fullSpecifier) => {
300
- const subpath = "./" + fullSpecifier.replace("veryfront/", "");
301
- const exportEntry = exportsMap[subpath];
302
- if (!exportEntry?.import) {
303
- logger.warn(`No export found for ${subpath}`);
304
- return match;
286
+ for (const specifier of importedSpecifiers) {
287
+ if (specifier === "veryfront") {
288
+ const exportEntry = exportsMap["."];
289
+ if (!exportEntry?.import)
290
+ continue;
291
+ const resolvedPath = pathHelper.join(vfPackagePath, exportEntry.import);
292
+ logger.debug(`Resolved veryfront -> ${resolvedPath}`);
293
+ replacements.set(specifier, pathToFileURL(resolvedPath).href);
294
+ continue;
305
295
  }
306
- const resolvedPath = pathHelper.join(vfPackagePath, exportEntry.import);
307
- logger.debug(`Resolved ${fullSpecifier} -> ${resolvedPath}`);
308
- return `from "${pathToFileURL(resolvedPath).href}"`;
309
- });
310
- transformed = transformed.replace(/from\s+["']veryfront["']/g, () => {
311
- const exportEntry = exportsMap["."];
312
- if (!exportEntry?.import)
313
- return 'from "veryfront"';
314
- const resolvedPath = pathHelper.join(vfPackagePath, exportEntry.import);
315
- logger.debug(`Resolved veryfront -> ${resolvedPath}`);
316
- return `from "${pathToFileURL(resolvedPath).href}"`;
317
- });
318
- return transformed;
296
+ if (specifier.startsWith("veryfront/")) {
297
+ const subpath = "./" + specifier.replace("veryfront/", "");
298
+ const exportEntry = exportsMap[subpath];
299
+ if (!exportEntry?.import) {
300
+ logger.warn(`No export found for ${subpath}`);
301
+ continue;
302
+ }
303
+ const resolvedPath = pathHelper.join(vfPackagePath, exportEntry.import);
304
+ logger.debug(`Resolved ${specifier} -> ${resolvedPath}`);
305
+ replacements.set(specifier, pathToFileURL(resolvedPath).href);
306
+ }
307
+ }
308
+ if (replacements.size === 0)
309
+ return code;
310
+ return await replaceSpecifiers(code, (specifier) => replacements.get(specifier));
319
311
  }
320
312
  export function rewriteCompiledBinaryVeryfrontImports(code) {
321
313
  let transformed = code;
@@ -380,9 +372,15 @@ export function rewriteCompiledBinaryUserDependencyImports(code, userDeps, esmDe
380
372
  return transformed;
381
373
  }
382
374
  export async function rewriteDenoNpmDependencyImports(code, projectDir, fs, userDeps) {
383
- let transformed = code;
384
- for (const [name, version] of userDeps) {
385
- const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
375
+ const importedSpecifiers = new Set((await parseImports(code))
376
+ .map((imp) => imp.n)
377
+ .filter((specifier) => typeof specifier === "string"));
378
+ const replacements = new Map();
379
+ for (const specifier of importedSpecifiers) {
380
+ const entry = [...userDeps].find(([name]) => specifier === name || specifier.startsWith(`${name}/`));
381
+ if (!entry)
382
+ continue;
383
+ const [name, version] = entry;
386
384
  let resolvedVersion = version;
387
385
  try {
388
386
  const pkgPath = pathHelper.join(projectDir, "node_modules", name, "package.json");
@@ -394,10 +392,12 @@ export async function rewriteDenoNpmDependencyImports(code, projectDir, fs, user
394
392
  catch (_) {
395
393
  /* expected: installed package.json may not exist, fall back to declared range */
396
394
  }
397
- transformed = transformed.replace(new RegExp(`from\\s+["']${escaped}(/[^"']*)?["']`, "g"), (_, subpath) => `from "npm:${name}@${resolvedVersion}${subpath || ""}"`);
398
- transformed = transformed.replace(new RegExp(`import\\s*\\(\\s*["']${escaped}(/[^"']*)?["']\\s*\\)`, "g"), (_, subpath) => `import("npm:${name}@${resolvedVersion}${subpath || ""}")`);
395
+ const subpath = specifier.slice(name.length);
396
+ replacements.set(specifier, `npm:${name}@${resolvedVersion}${subpath}`);
399
397
  }
400
- return transformed;
398
+ if (replacements.size === 0)
399
+ return code;
400
+ return await replaceSpecifiers(code, (specifier) => replacements.get(specifier));
401
401
  }
402
402
  export function rewriteDenoNodeBuiltinImports(code) {
403
403
  let transformed = code;
@@ -1,5 +1,6 @@
1
1
  import { escapeHtml } from "../../html/html-escape.js";
2
2
  export { escapeHtml };
3
+ export declare function buildTrustedHtmlValidatorScript(): string;
3
4
  interface ValidateTrustedHtmlOptions {
4
5
  /** Throw on suspicious patterns even in dev mode */
5
6
  strict?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"html-sanitizer.d.ts","sourceRoot":"","sources":["../../../../src/src/security/client/html-sanitizer.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAGvD,OAAO,EAAE,UAAU,EAAE,CAAC;AA4BtB,UAAU,0BAA0B;IAClC,oDAAoD;IACpD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2CAA2C;IAC3C,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,0BAA+B,GACvC,MAAM,CAcR;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;CACtC,GAAG,cAAc,CA+BjB"}
1
+ {"version":3,"file":"html-sanitizer.d.ts","sourceRoot":"","sources":["../../../../src/src/security/client/html-sanitizer.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAGvD,OAAO,EAAE,UAAU,EAAE,CAAC;AA6BtB,wBAAgB,+BAA+B,IAAI,MAAM,CAwBxD;AAiBD,UAAU,0BAA0B;IAClC,oDAAoD;IACpD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2CAA2C;IAC3C,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,0BAA+B,GACvC,MAAM,CAcR;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;CACtC,GAAG,cAAc,CA+BjB"}
@@ -13,12 +13,50 @@ export { escapeHtml };
13
13
  * Patterns that RSC should never generate.
14
14
  * These indicate potential server compromise or misconfiguration.
15
15
  */
16
- const SUSPICIOUS_PATTERNS = [
17
- { pattern: /<script[^>]*>[\s\S]*?<\/script>/gi, name: "inline script" },
18
- { pattern: /javascript:/gi, name: "javascript: URL" },
19
- { pattern: /\bon\w+\s*=/gi, name: "event handler attribute" },
20
- { pattern: /data:\s*text\/html/gi, name: "data: HTML URL" },
16
+ const SUSPICIOUS_PATTERN_SPECS = [
17
+ { source: String.raw `<script[^>]*>[\s\S]*?<\/script>`, flags: "gi", name: "inline script" },
18
+ { source: String.raw `javascript:`, flags: "gi", name: "javascript: URL" },
19
+ { source: String.raw `\bon\w+\s*=`, flags: "gi", name: "event handler attribute" },
20
+ { source: String.raw `data:\s*text\/html`, flags: "gi", name: "data: HTML URL" },
21
21
  ];
22
+ function createSuspiciousPatterns() {
23
+ return SUSPICIOUS_PATTERN_SPECS.map(({ source, flags, name }) => ({
24
+ pattern: new RegExp(source, flags),
25
+ name,
26
+ }));
27
+ }
28
+ function jsonForInlineScript(value) {
29
+ return JSON.stringify(value)
30
+ .replace(/</g, "\\u003c")
31
+ .replace(/>/g, "\\u003e")
32
+ .replace(/&/g, "\\u0026")
33
+ .replace(/\u2028/g, "\\u2028")
34
+ .replace(/\u2029/g, "\\u2029");
35
+ }
36
+ export function buildTrustedHtmlValidatorScript() {
37
+ const specsJson = jsonForInlineScript(SUSPICIOUS_PATTERN_SPECS);
38
+ return `
39
+ function isDevMode() {
40
+ return window.__VERYFRONT_DEV__ === true;
41
+ }
42
+
43
+ const suspiciousHtmlPatterns = ${specsJson}.map(({ source, flags, name }) => ({
44
+ pattern: new RegExp(source, flags),
45
+ name,
46
+ }));
47
+
48
+ function validateTrustedHtml(html) {
49
+ for (const { pattern, name } of suspiciousHtmlPatterns) {
50
+ pattern.lastIndex = 0;
51
+ if (!pattern.test(html)) continue;
52
+ console.warn(\`[Security] Suspicious \${name} detected in server HTML\`);
53
+ if (!isDevMode()) throw new Error(\`Potentially unsafe HTML: \${name} detected\`);
54
+ }
55
+
56
+ return html;
57
+ }
58
+ `.trim();
59
+ }
22
60
  function isDevMode() {
23
61
  const g = globalThis;
24
62
  return g.__VERYFRONT_DEV__ === true || g.Deno?.env?.get?.("VERYFRONT_ENV") === "development";
@@ -36,7 +74,7 @@ function isDevMode() {
36
74
  */
37
75
  export function validateTrustedHtml(html, options = {}) {
38
76
  const { strict = false, warn = true } = options;
39
- for (const { pattern, name } of SUSPICIOUS_PATTERNS) {
77
+ for (const { pattern, name } of createSuspiciousPatterns()) {
40
78
  pattern.lastIndex = 0;
41
79
  if (!pattern.test(html))
42
80
  continue;
@@ -1 +1 @@
1
- {"version":3,"file":"rsc-bundles.generated.d.ts","sourceRoot":"","sources":["../../../../../../src/src/server/services/rsc/endpoints/rsc-bundles.generated.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,kBAAkB,EAAE,MACi11B,CAAC;AAEn31B,eAAO,MAAM,iBAAiB,EAAE,MAC40mB,CAAC"}
1
+ {"version":3,"file":"rsc-bundles.generated.d.ts","sourceRoot":"","sources":["../../../../../../src/src/server/services/rsc/endpoints/rsc-bundles.generated.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,kBAAkB,EAAE,MACw/1B,CAAC;AAE1h2B,eAAO,MAAM,iBAAiB,EAAE,MACm/mB,CAAC"}