@intelligems/sst 2.49.3 → 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,80 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ import fs from "fs";
3
+ /**
4
+ * Configuration for creating a debug file logger
5
+ */
6
+ export interface DebugFileLoggerConfig {
7
+ /** Log file path (relative paths are relative to CWD) */
8
+ filePath: string;
9
+ /** Optional environment variable to enable/disable logging (if not set, logging is always enabled) */
10
+ envVar?: string;
11
+ /** Name for session markers (e.g., "POOL", "TRACE") */
12
+ sessionName: string;
13
+ /** Optional header line content for session start */
14
+ sessionHeader?: string;
15
+ /** Column width for padding (default: 80) */
16
+ width?: number;
17
+ }
18
+ /**
19
+ * Debug file logger instance
20
+ */
21
+ export interface DebugFileLogger {
22
+ /** Log a message with timestamp and formatted details */
23
+ log: (action: string, details?: Record<string, any>) => void;
24
+ /** Log a raw line (no formatting) */
25
+ logRaw: (line: string) => void;
26
+ /** Check if logging is enabled */
27
+ isEnabled: () => boolean;
28
+ /** Write session end marker and close the stream */
29
+ close: (summary?: string) => void;
30
+ /** Get the write stream (for advanced use cases) */
31
+ getStream: () => fs.WriteStream | null;
32
+ }
33
+ /**
34
+ * Create a debug file logger with consistent formatting
35
+ *
36
+ * Features:
37
+ * - File-based logging to configurable path
38
+ * - Optional enable/disable via environment variable
39
+ * - Session start/end markers
40
+ * - Consistent timestamp formatting (HH:MM:SS.mmm)
41
+ * - Automatic directory creation
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * const logger = createDebugFileLogger({
46
+ * filePath: ".sst/debug-workers.log",
47
+ * envVar: "SST_DEBUG_WORKERS",
48
+ * sessionName: "WORKERS",
49
+ * });
50
+ *
51
+ * logger.log("REQUEST", { path: "/api/test", elapsed: 50 });
52
+ * // Output: [14:30:45.123] REQUEST path=/api/test elapsed=50
53
+ * ```
54
+ */
55
+ export declare function createDebugFileLogger(config: DebugFileLoggerConfig): DebugFileLogger;
56
+ /**
57
+ * Create a simple console+file logger for debug output
58
+ * Logs to both console and a file with consistent formatting
59
+ */
60
+ export interface ConsoleFileLoggerConfig {
61
+ /** Log file path */
62
+ filePath: string;
63
+ /** Prefix for console output (e.g., "[WORKERS]") */
64
+ prefix: string;
65
+ /** Session name for file markers */
66
+ sessionName: string;
67
+ }
68
+ export interface ConsoleFileLogger {
69
+ /** Log a message to both console and file */
70
+ log: (message: string) => void;
71
+ /** Check if file logging is enabled */
72
+ isEnabled: () => boolean;
73
+ /** Close the file stream */
74
+ close: () => void;
75
+ }
76
+ /**
77
+ * Create a logger that writes to both console and a file
78
+ * Always writes to console, writes to file when SST_DEBUG_BRIDGE is set
79
+ */
80
+ export declare function createConsoleFileLogger(config: ConsoleFileLoggerConfig): ConsoleFileLogger;
@@ -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 {