@intelligems/sst 2.49.6-ig.1 → 2.49.6-ig.10

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,148 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ // Active loggers registry (for cleanup)
4
+ const activeLoggers = new Map();
5
+ // Cleanup on process exit
6
+ process.on("exit", () => {
7
+ for (const stream of activeLoggers.values()) {
8
+ try {
9
+ stream.end();
10
+ }
11
+ catch { }
12
+ }
13
+ });
14
+ /**
15
+ * Create a debug file logger with consistent formatting
16
+ *
17
+ * Features:
18
+ * - File-based logging to configurable path
19
+ * - Optional enable/disable via environment variable
20
+ * - Session start/end markers
21
+ * - Consistent timestamp formatting (HH:MM:SS.mmm)
22
+ * - Automatic directory creation
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const logger = createDebugFileLogger({
27
+ * filePath: ".sst/debug-workers.log",
28
+ * envVar: "SST_DEBUG_WORKERS",
29
+ * sessionName: "WORKERS",
30
+ * });
31
+ *
32
+ * logger.log("REQUEST", { path: "/api/test", elapsed: 50 });
33
+ * // Output: [14:30:45.123] REQUEST path=/api/test elapsed=50
34
+ * ```
35
+ */
36
+ export function createDebugFileLogger(config) {
37
+ const { filePath, envVar, sessionName, sessionHeader, width = 80 } = config;
38
+ let stream = null;
39
+ let initialized = false;
40
+ function isEnabled() {
41
+ // If no envVar specified, always enabled
42
+ if (!envVar)
43
+ return true;
44
+ return process.env[envVar] === "true";
45
+ }
46
+ function initLogFile() {
47
+ if (initialized)
48
+ return stream !== null;
49
+ initialized = true;
50
+ if (!isEnabled())
51
+ return false;
52
+ try {
53
+ const logDir = path.dirname(filePath);
54
+ if (!fs.existsSync(logDir)) {
55
+ fs.mkdirSync(logDir, { recursive: true });
56
+ }
57
+ stream = fs.createWriteStream(filePath, { flags: "w" });
58
+ activeLoggers.set(filePath, stream);
59
+ // Write session start marker
60
+ const header = sessionHeader ? ` | ${sessionHeader}` : "";
61
+ stream.write("\n" +
62
+ "=".repeat(width) +
63
+ "\n" +
64
+ `[${sessionName} SESSION START] ${new Date().toISOString()}${header}\n` +
65
+ "=".repeat(width) +
66
+ "\n");
67
+ return true;
68
+ }
69
+ catch {
70
+ return false;
71
+ }
72
+ }
73
+ function formatTimestamp() {
74
+ return new Date().toISOString().slice(11, 23); // HH:MM:SS.mmm
75
+ }
76
+ function formatDetails(details) {
77
+ return Object.entries(details)
78
+ .filter(([_, v]) => v !== undefined)
79
+ .map(([k, v]) => {
80
+ if (typeof v === "object") {
81
+ return `${k}=${JSON.stringify(v)}`;
82
+ }
83
+ return `${k}=${v}`;
84
+ })
85
+ .join(" ");
86
+ }
87
+ return {
88
+ log(action, details = {}) {
89
+ if (!initLogFile() || !stream)
90
+ return;
91
+ const timestamp = formatTimestamp();
92
+ const detailStr = formatDetails(details);
93
+ const line = `[${timestamp}] ${action.padEnd(20)} ${detailStr}\n`;
94
+ stream.write(line);
95
+ },
96
+ logRaw(line) {
97
+ if (!initLogFile() || !stream)
98
+ return;
99
+ stream.write(line.endsWith("\n") ? line : line + "\n");
100
+ },
101
+ isEnabled,
102
+ close(summary) {
103
+ if (!stream)
104
+ return;
105
+ const endMarker = "\n" +
106
+ "-".repeat(width) +
107
+ "\n" +
108
+ `[${sessionName} SESSION END] ${new Date().toISOString()}\n` +
109
+ (summary ? summary + "\n" : "") +
110
+ "-".repeat(width) +
111
+ "\n";
112
+ stream.write(endMarker);
113
+ stream.end();
114
+ activeLoggers.delete(filePath);
115
+ stream = null;
116
+ },
117
+ getStream() {
118
+ initLogFile();
119
+ return stream;
120
+ },
121
+ };
122
+ }
123
+ /**
124
+ * Create a logger that writes to both console and a file
125
+ * Always writes to console, writes to file when SST_DEBUG_BRIDGE is set
126
+ */
127
+ export function createConsoleFileLogger(config) {
128
+ const { filePath, prefix, sessionName } = config;
129
+ const fileLogger = createDebugFileLogger({
130
+ filePath,
131
+ envVar: "SST_DEBUG_BRIDGE",
132
+ sessionName,
133
+ width: 100,
134
+ });
135
+ return {
136
+ log(message) {
137
+ // Always log to console
138
+ console.log(`${prefix} ${message}`);
139
+ // Also log to file if enabled
140
+ if (fileLogger.isEnabled()) {
141
+ const timestamp = new Date().toISOString().slice(11, 23);
142
+ fileLogger.logRaw(`[${timestamp}] ${prefix} ${message}`);
143
+ }
144
+ },
145
+ isEnabled: fileLogger.isEnabled,
146
+ close: () => fileLogger.close(),
147
+ };
148
+ }
@@ -0,0 +1,27 @@
1
+ type EventType = "IOT_RECEIVED" | "IOT_ACK" | "WORKER_START" | "WORKER_LOG" | "WORKER_END";
2
+ interface EventTraceDetails {
3
+ requestID: string;
4
+ functionID?: string;
5
+ workerID?: string;
6
+ message?: string;
7
+ status?: "success" | "error";
8
+ elapsed?: number;
9
+ [key: string]: any;
10
+ }
11
+ /**
12
+ * Log an event trace entry with requestID as the common identifier
13
+ *
14
+ * @param event - The event type
15
+ * @param details - Event details including requestID (required)
16
+ *
17
+ * @example
18
+ * logEventTrace("IOT_RECEIVED", { requestID: "abc123", functionID: "MyFunc" });
19
+ * logEventTrace("WORKER_LOG", { requestID: "abc123", message: "[LOG] User action" });
20
+ * logEventTrace("WORKER_END", { requestID: "abc123", status: "success", elapsed: 150 });
21
+ */
22
+ export declare function logEventTrace(event: EventType, details: EventTraceDetails): void;
23
+ /**
24
+ * Check if event trace logging is enabled
25
+ */
26
+ export declare function isEventTraceEnabled(): boolean;
27
+ export {};
@@ -0,0 +1,69 @@
1
+ import { createDebugFileLogger } from "./debug-file-logger.js";
2
+ /**
3
+ * Event Trace Logging for SST Dev Bridge
4
+ *
5
+ * Tracks the complete lifecycle of a request from IOT reception to worker completion.
6
+ * All events share a common `requestID` for end-to-end tracing.
7
+ *
8
+ * Enable with: SST_EVENT_TRACE=true
9
+ *
10
+ * Log file: .sst/event-trace.log
11
+ *
12
+ * Event Types:
13
+ * - IOT_RECEIVED : IOT event received from Lambda
14
+ * - IOT_ACK : IOT acknowledgment published
15
+ * - WORKER_START : Worker function started
16
+ * - WORKER_LOG : Worker function log with [LOG] prefix
17
+ * - WORKER_END : Worker function completed (success/error)
18
+ *
19
+ * Tracing from Frontend:
20
+ * - Send X-Correlation-ID header from frontend for easy correlation
21
+ * - Or use the path to filter by endpoint
22
+ * - Or use apiGwReqId (visible in browser as x-amzn-requestid response header)
23
+ */
24
+ const EVENT_TRACE_ENABLED = process.env.SST_EVENT_TRACE === "true";
25
+ let eventLogger = null;
26
+ function getEventLogger() {
27
+ if (!EVENT_TRACE_ENABLED)
28
+ return null;
29
+ if (!eventLogger) {
30
+ eventLogger = createDebugFileLogger({
31
+ filePath: ".sst/event-trace.log",
32
+ sessionName: "EVENT_TRACE",
33
+ width: 140,
34
+ });
35
+ }
36
+ return eventLogger;
37
+ }
38
+ /**
39
+ * Log an event trace entry with requestID as the common identifier
40
+ *
41
+ * @param event - The event type
42
+ * @param details - Event details including requestID (required)
43
+ *
44
+ * @example
45
+ * logEventTrace("IOT_RECEIVED", { requestID: "abc123", functionID: "MyFunc" });
46
+ * logEventTrace("WORKER_LOG", { requestID: "abc123", message: "[LOG] User action" });
47
+ * logEventTrace("WORKER_END", { requestID: "abc123", status: "success", elapsed: 150 });
48
+ */
49
+ export function logEventTrace(event, details) {
50
+ const logger = getEventLogger();
51
+ if (!logger)
52
+ return;
53
+ // Format requestID for consistent display (first 8 chars)
54
+ const reqId = details.requestID?.slice(0, 8) || "N/A";
55
+ // Build the log message with requestID prominently displayed
56
+ const logDetails = {
57
+ reqId,
58
+ ...details,
59
+ };
60
+ // Remove the full requestID since we're using the shortened version
61
+ delete logDetails.requestID;
62
+ logger.log(event, logDetails);
63
+ }
64
+ /**
65
+ * Check if event trace logging is enabled
66
+ */
67
+ export function isEventTraceEnabled() {
68
+ return EVENT_TRACE_ENABLED;
69
+ }
@@ -11,6 +11,7 @@ import { useRuntimeWorkers } from "../workers.js";
11
11
  import { Colors } from "../../cli/colors.js";
12
12
  import { Logger } from "../../logger.js";
13
13
  import { findAbove } from "../../util/fs.js";
14
+ import { useMonoBuildConfig } from "../mono-build-config.js";
14
15
  export const useNodeHandler = () => {
15
16
  const rebuildCache = {};
16
17
  process.on("exit", () => {
@@ -34,33 +35,72 @@ export const useNodeHandler = () => {
34
35
  canHandle: (input) => input.startsWith("nodejs"),
35
36
  startWorker: async (input) => {
36
37
  const workers = await useRuntimeWorkers();
37
- new Promise(async () => {
38
- const worker = new Worker(url.fileURLToPath(new URL("../../support/nodejs-runtime/index.mjs", import.meta.url)), {
39
- env: {
40
- ...input.environment,
41
- IS_LOCAL: "true",
42
- },
43
- execArgv: ["--enable-source-maps"],
44
- workerData: input,
45
- stderr: true,
46
- stdin: true,
47
- stdout: true,
48
- });
49
- worker.stdout.on("data", (data) => {
50
- workers.stdout(input.workerID, data.toString());
51
- });
52
- worker.stderr.on("data", (data) => {
53
- workers.stdout(input.workerID, data.toString());
54
- });
55
- worker.on("exit", () => workers.exited(input.workerID));
56
- threads.set(input.workerID, worker);
38
+ const worker = new Worker(url.fileURLToPath(new URL("../../support/nodejs-runtime/index.mjs", import.meta.url)), {
39
+ env: {
40
+ ...input.environment,
41
+ IS_LOCAL: "true",
42
+ },
43
+ execArgv: ["--enable-source-maps"],
44
+ workerData: input,
45
+ stderr: true,
46
+ stdin: true,
47
+ stdout: true,
57
48
  });
49
+ worker.stdout.on("data", (data) => {
50
+ workers.stdout(input.workerID, data.toString());
51
+ });
52
+ worker.stderr.on("data", (data) => {
53
+ workers.stdout(input.workerID, data.toString());
54
+ });
55
+ worker.on("exit", () => workers.exited(input.workerID));
56
+ threads.set(input.workerID, worker);
58
57
  },
59
58
  stopWorker: async (workerID) => {
60
59
  const worker = threads.get(workerID);
61
60
  await worker?.terminate();
62
61
  },
63
62
  build: async (input) => {
63
+ // Check for dev mode mono-bundle using global config
64
+ const monoBuildConfig = useMonoBuildConfig();
65
+ if (input.mode === "start" && monoBuildConfig.enabled) {
66
+ Colors.line(Colors.prefix, Colors.dim.bold("MonoBundle"), Colors.dim(`mode=${input.mode}, handler=${input.props.handler}`));
67
+ // Symlink node_modules to mono-bundle dir for external dependencies
68
+ // Only create if symlink doesn't exist or points to wrong location (avoid redundant I/O)
69
+ const parsed = path.parse(input.props.handler);
70
+ const handlerFunctionsDir = path.join(project.paths.root, parsed.dir);
71
+ const root = await findAbove(handlerFunctionsDir, "package.json");
72
+ if (root) {
73
+ const sourceNodeModules = path.resolve(root, "node_modules");
74
+ const monoBundleNodeModules = path.join(monoBuildConfig.dir, "node_modules");
75
+ try {
76
+ const existingTarget = await fs.readlink(monoBundleNodeModules);
77
+ if (existingTarget === sourceNodeModules) {
78
+ // Symlink already correct, skip
79
+ }
80
+ else {
81
+ // Symlink points to wrong location, recreate
82
+ await fs.rm(monoBundleNodeModules, { recursive: true, force: true });
83
+ await fs.symlink(sourceNodeModules, monoBundleNodeModules, "dir");
84
+ Logger.debug("Symlinked node_modules for mono-bundle from:", sourceNodeModules);
85
+ }
86
+ }
87
+ catch {
88
+ // Symlink doesn't exist, create it
89
+ try {
90
+ await fs.symlink(sourceNodeModules, monoBundleNodeModules, "dir");
91
+ Logger.debug("Symlinked node_modules for mono-bundle from:", sourceNodeModules);
92
+ }
93
+ catch (err) {
94
+ Logger.debug("Failed to symlink node_modules for mono-bundle:", err);
95
+ }
96
+ }
97
+ }
98
+ return {
99
+ type: "success",
100
+ handler: monoBuildConfig.handler,
101
+ out: monoBuildConfig.dir,
102
+ };
103
+ }
64
104
  const parsed = path.parse(input.props.handler);
65
105
  const file = [
66
106
  ".ts",
@@ -108,7 +148,8 @@ export const useNodeHandler = () => {
108
148
  try {
109
149
  await fs.symlink(path.resolve(dir), path.resolve(path.join(input.out, "node_modules")), "dir");
110
150
  }
111
- catch { }
151
+ catch {
152
+ }
112
153
  }
113
154
  // Rebuilt using existing esbuild context
114
155
  let ctx = rebuildCache[input.functionID]?.ctx;
@@ -6,6 +6,7 @@ declare module "../bus.js" {
6
6
  };
7
7
  "function.build.success": {
8
8
  functionID: string;
9
+ monoBundle?: boolean;
9
10
  };
10
11
  "function.build.failed": {
11
12
  functionID: string;
@@ -27,6 +28,8 @@ export interface StartWorkerInput {
27
28
  out: string;
28
29
  handler: string;
29
30
  runtime: string;
31
+ /** Whether this worker is using mono build mode */
32
+ isMonoBuild?: boolean;
30
33
  }
31
34
  interface ShouldBuildInput {
32
35
  file: string;
@@ -41,6 +44,7 @@ export interface RuntimeHandler {
41
44
  type: "success";
42
45
  handler: string;
43
46
  sourcemap?: string;
47
+ out?: string;
44
48
  } | {
45
49
  type: "error";
46
50
  errors: string[];
@@ -57,10 +61,10 @@ export declare const useRuntimeHandlers: () => {
57
61
  type: "error";
58
62
  errors: string[];
59
63
  } | {
60
- out: string;
61
- sourcemap: string | undefined;
62
64
  type: "success";
63
65
  handler: string;
66
+ out: string;
67
+ sourcemap: string | undefined;
64
68
  }>;
65
69
  };
66
70
  interface Artifact {
@@ -14,6 +14,9 @@ import { usePythonHandler } from "./handlers/python.js";
14
14
  import { useRustHandler } from "./handlers/rust.js";
15
15
  import { lazy } from "../util/lazy.js";
16
16
  import { Semaphore } from "../util/semaphore.js";
17
+ import { isMonoBuildPath } from "./mono-build-config.js";
18
+ // Build concurrency semaphore - only used for actual builds, not mono-build lookups
19
+ const buildSemaphore = new Semaphore(parseInt(process.env.SST_BUILD_CONCURRENCY || "4", 10));
17
20
  export const useRuntimeHandlers = lazy(() => {
18
21
  const handlers = [
19
22
  useNodeHandler(),
@@ -39,64 +42,110 @@ export const useRuntimeHandlers = lazy(() => {
39
42
  return result;
40
43
  },
41
44
  async build(functionID, mode) {
42
- async function task() {
45
+ // Fast path for mono-bundle: check without semaphore since no actual build work
46
+ async function tryMonoBundleFastPath() {
43
47
  const func = useFunctions().fromID(functionID);
44
48
  if (!func)
45
- return {
46
- type: "error",
47
- errors: [`Function with ID "${functionID}" not found`],
48
- };
49
+ return null;
49
50
  const handler = result.for(func.runtime);
50
51
  const out = path.join(project.paths.artifacts, functionID);
51
- await fs.rm(out, { recursive: true, force: true });
52
- await fs.mkdir(out, { recursive: true });
53
- bus.publish("function.build.started", { functionID });
54
- if (func.hooks?.beforeBuild)
55
- await func.hooks.beforeBuild(func, out);
56
- const built = await handler.build({
52
+ // Check for mono-bundle mode by doing a preliminary build call
53
+ // In mono-bundle mode, handler returns its own out path immediately without building
54
+ const monoBundleCheck = await handler.build({
57
55
  functionID,
58
56
  out,
59
57
  mode,
60
58
  props: func,
61
59
  });
62
- if (built.type === "error") {
63
- bus.publish("function.build.failed", {
60
+ // If mono-bundle detected (handler returned custom out), skip all artifact work
61
+ // Don't fire build events - mono-bundle is built externally by esbuild watch
62
+ // Worker pool invalidation is handled separately when bundle file actually changes
63
+ if (monoBundleCheck.type === "success" && monoBundleCheck.out) {
64
+ return {
65
+ type: "success",
66
+ handler: monoBundleCheck.handler,
67
+ out: monoBundleCheck.out,
68
+ sourcemap: monoBundleCheck.sourcemap,
69
+ };
70
+ }
71
+ return null; // Not mono-bundle, need full build
72
+ }
73
+ // Full build with semaphore protection for actual compilation work
74
+ async function fullBuild() {
75
+ const func = useFunctions().fromID(functionID);
76
+ if (!func)
77
+ return {
78
+ type: "error",
79
+ errors: [`Function with ID "${functionID}" not found`],
80
+ };
81
+ const handler = result.for(func.runtime);
82
+ const out = path.join(project.paths.artifacts, functionID);
83
+ // Acquire semaphore only for actual build work
84
+ const unlock = await buildSemaphore.lock();
85
+ try {
86
+ // Non-mono-bundle: follow original flow
87
+ await fs.rm(out, { recursive: true, force: true });
88
+ await fs.mkdir(out, { recursive: true });
89
+ bus.publish("function.build.started", { functionID });
90
+ if (func.hooks?.beforeBuild)
91
+ await func.hooks.beforeBuild(func, out);
92
+ const built = await handler.build({
64
93
  functionID,
65
- errors: built.errors,
94
+ out,
95
+ mode,
96
+ props: func,
66
97
  });
67
- return built;
68
- }
69
- if (func.copyFiles) {
70
- await Promise.all(func.copyFiles.map(async (entry) => {
71
- const fromPath = path.join(project.paths.root, entry.from);
72
- const to = entry.to || entry.from;
73
- if (path.isAbsolute(to))
74
- throw new Error(`Copy destination path "${to}" must be relative`);
75
- const toPath = path.join(out, to);
76
- if (mode === "deploy")
77
- await fs.cp(fromPath, toPath, {
78
- recursive: true,
79
- });
80
- if (mode === "start") {
81
- try {
82
- const dir = path.dirname(toPath);
83
- await fs.mkdir(dir, { recursive: true });
84
- await fs.symlink(fromPath, toPath);
98
+ if (built.type === "error") {
99
+ bus.publish("function.build.failed", {
100
+ functionID,
101
+ errors: built.errors,
102
+ });
103
+ return built;
104
+ }
105
+ if (func.copyFiles) {
106
+ await Promise.all(func.copyFiles.map(async (entry) => {
107
+ const fromPath = path.join(project.paths.root, entry.from);
108
+ const to = entry.to || entry.from;
109
+ if (path.isAbsolute(to))
110
+ throw new Error(`Copy destination path "${to}" must be relative`);
111
+ const toPath = path.join(out, to);
112
+ if (mode === "deploy")
113
+ await fs.cp(fromPath, toPath, {
114
+ recursive: true,
115
+ });
116
+ if (mode === "start") {
117
+ try {
118
+ const dir = path.dirname(toPath);
119
+ await fs.mkdir(dir, { recursive: true });
120
+ await fs.symlink(fromPath, toPath);
121
+ }
122
+ catch (ex) {
123
+ Logger.debug("Failed to symlink", fromPath, toPath, ex);
124
+ }
85
125
  }
86
- catch (ex) {
87
- Logger.debug("Failed to symlink", fromPath, toPath, ex);
88
- }
89
- }
90
- }));
126
+ }));
127
+ }
128
+ if (func.hooks?.afterBuild)
129
+ await func.hooks.afterBuild(func, out);
130
+ bus.publish("function.build.success", { functionID });
131
+ return {
132
+ ...built,
133
+ out,
134
+ sourcemap: built.sourcemap,
135
+ };
136
+ }
137
+ finally {
138
+ unlock();
91
139
  }
92
- if (func.hooks?.afterBuild)
93
- await func.hooks.afterBuild(func, out);
94
- bus.publish("function.build.success", { functionID });
95
- return {
96
- ...built,
97
- out,
98
- sourcemap: built.sourcemap,
99
- };
140
+ }
141
+ async function task() {
142
+ // Try mono-bundle fast path first (no semaphore needed)
143
+ const monoBundleResult = await tryMonoBundleFastPath();
144
+ if (monoBundleResult) {
145
+ return monoBundleResult;
146
+ }
147
+ // Fall back to full build with semaphore protection
148
+ return fullBuild();
100
149
  }
101
150
  if (pendingBuilds.has(functionID)) {
102
151
  Logger.debug("Waiting on pending build", functionID);
@@ -118,27 +167,43 @@ export const useRuntimeHandlers = lazy(() => {
118
167
  export const useFunctionBuilder = lazy(() => {
119
168
  const artifacts = new Map();
120
169
  const handlers = useRuntimeHandlers();
121
- const semaphore = new Semaphore(4);
170
+ // Track pending builds to prevent duplicate concurrent builds for same function
171
+ const pendingArtifactBuilds = new Map();
122
172
  const result = {
123
173
  artifact: (functionID) => {
174
+ // Fast path: already cached - return immediately without any async work
124
175
  if (artifacts.has(functionID))
125
176
  return artifacts.get(functionID);
126
177
  return result.build(functionID);
127
178
  },
128
179
  build: async (functionID) => {
129
- const unlock = await semaphore.lock();
130
- try {
131
- const result = await handlers.build(functionID, "start");
132
- if (!result)
133
- return;
134
- if (result.type === "error")
135
- return;
136
- artifacts.set(functionID, result);
180
+ // Fast path: already cached (check again in case of concurrent calls)
181
+ if (artifacts.has(functionID))
137
182
  return artifacts.get(functionID);
138
- }
139
- finally {
140
- unlock();
141
- }
183
+ // Deduplication: if build already in progress for this function, wait for it
184
+ const pending = pendingArtifactBuilds.get(functionID);
185
+ if (pending)
186
+ return pending;
187
+ const buildTask = async () => {
188
+ try {
189
+ // handlers.build() handles semaphore internally:
190
+ // - mono-build: no semaphore (fast path)
191
+ // - non-mono-build: semaphore protected
192
+ const buildResult = await handlers.build(functionID, "start");
193
+ if (!buildResult)
194
+ return;
195
+ if (buildResult.type === "error")
196
+ return;
197
+ artifacts.set(functionID, buildResult);
198
+ return artifacts.get(functionID);
199
+ }
200
+ finally {
201
+ pendingArtifactBuilds.delete(functionID);
202
+ }
203
+ };
204
+ const promise = buildTask();
205
+ pendingArtifactBuilds.set(functionID, promise);
206
+ return promise;
142
207
  },
143
208
  };
144
209
  const watcher = useWatcher();
@@ -146,6 +211,11 @@ export const useFunctionBuilder = lazy(() => {
146
211
  try {
147
212
  const functions = useFunctions();
148
213
  for (const [functionID, info] of Object.entries(functions.all)) {
214
+ // Optimization: For mono-build, the artifact path is stable and build is handled externally.
215
+ // We can skip the potentially expensive shouldBuild check and rebuild call.
216
+ const existing = artifacts.get(functionID);
217
+ if (existing && isMonoBuildPath(existing.out))
218
+ continue;
149
219
  const handler = handlers.for(info.runtime);
150
220
  if (!handler?.shouldBuild({
151
221
  functionID,
@@ -156,7 +226,8 @@ export const useFunctionBuilder = lazy(() => {
156
226
  Logger.debug("Rebuilt function", functionID);
157
227
  }
158
228
  }
159
- catch { }
229
+ catch {
230
+ }
160
231
  });
161
232
  return result;
162
233
  });