@omen.foundation/node-microservice-runtime 0.1.101 → 0.1.103

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.
@@ -71,29 +71,42 @@ function loadDeveloperEnvVars(beamEnvPath) {
71
71
  }
72
72
  async function fetchBeamableConfig(env, timeoutMs = 2000) {
73
73
  const configVars = {};
74
- if (!env.secret || !env.cid || !env.pid) {
75
- console.log('[EnvLoader] Skipping Beamable Config fetch - missing SECRET, CID, or PID');
74
+ if (!env.cid || !env.pid) {
75
+ console.log('[EnvLoader] Skipping Beamable Config fetch - missing CID or PID');
76
+ return configVars;
77
+ }
78
+ const accessToken = process.env.ACCESS_TOKEN || process.env.BEAMABLE_TOKEN;
79
+ const hasSecret = !!env.secret;
80
+ if (!accessToken && !hasSecret) {
81
+ console.log('[EnvLoader] Skipping Beamable Config fetch - missing ACCESS_TOKEN/BEAMABLE_TOKEN or SECRET');
76
82
  return configVars;
77
83
  }
78
84
  try {
79
85
  const apiUrl = (0, urls_js_1.hostToHttpUrl)(env.host);
80
86
  const uriPath = process.env.BEAM_CONFIG_API_PATH || '/basic/realms/config';
81
87
  const configUrl = new node_url_1.URL(uriPath, apiUrl).toString();
82
- const pathAndQuery = uriPath;
83
- const signature = calculateSignature(env.pid, env.secret, pathAndQuery, null, '1');
88
+ const headers = {
89
+ 'Content-Type': 'application/json',
90
+ Accept: 'application/json',
91
+ 'X-BEAM-SCOPE': `${env.cid}.${env.pid}`,
92
+ };
93
+ if (accessToken) {
94
+ headers['Authorization'] = `Bearer ${accessToken}`;
95
+ }
96
+ else if (hasSecret && env.secret) {
97
+ const pathAndQuery = uriPath;
98
+ const signature = calculateSignature(env.pid, env.secret, pathAndQuery, null, '1');
99
+ headers['X-BEAM-SIGNATURE'] = signature;
100
+ }
84
101
  console.log(`[EnvLoader] Fetching Beamable Config from ${configUrl}...`);
102
+ console.log(`[EnvLoader] Using ${accessToken ? 'token-based' : 'signature-based'} authentication`);
85
103
  const controller = new AbortController();
86
104
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
87
105
  let response;
88
106
  try {
89
107
  response = await fetch(configUrl, {
90
108
  method: 'GET',
91
- headers: {
92
- 'Content-Type': 'application/json',
93
- Accept: 'application/json',
94
- 'X-BEAM-SCOPE': `${env.cid}.${env.pid}`,
95
- 'X-BEAM-SIGNATURE': signature,
96
- },
109
+ headers,
97
110
  signal: controller.signal,
98
111
  });
99
112
  clearTimeout(timeoutId);
@@ -101,13 +114,25 @@ async function fetchBeamableConfig(env, timeoutMs = 2000) {
101
114
  catch (error) {
102
115
  clearTimeout(timeoutId);
103
116
  if (error instanceof Error && error.name === 'AbortError') {
117
+ console.warn(`[EnvLoader] Beamable Config fetch timed out after ${timeoutMs}ms`);
118
+ console.warn(`[EnvLoader] URL: ${configUrl}`);
119
+ console.warn(`[EnvLoader] Auth method: ${accessToken ? 'token-based' : 'signature-based'}`);
104
120
  throw new Error(`Beamable Config fetch timed out after ${timeoutMs}ms`);
105
121
  }
106
122
  throw error;
107
123
  }
108
124
  if (!response.ok) {
109
125
  const errorText = await response.text().catch(() => 'Unknown error');
110
- console.warn(`[EnvLoader] Failed to fetch Beamable Config: ${response.status} ${response.statusText} - ${errorText.substring(0, 200)}`);
126
+ console.warn(`[EnvLoader] Failed to fetch Beamable Config: ${response.status} ${response.statusText}`);
127
+ console.warn(`[EnvLoader] URL: ${configUrl}`);
128
+ console.warn(`[EnvLoader] Auth method: ${accessToken ? 'token-based' : 'signature-based'}`);
129
+ if (response.status === 401 || response.status === 403) {
130
+ console.warn(`[EnvLoader] Authentication failed. This endpoint requires token-based auth (ACCESS_TOKEN or BEAMABLE_TOKEN).`);
131
+ if (!accessToken) {
132
+ console.warn(`[EnvLoader] Tip: Set ACCESS_TOKEN or BEAMABLE_TOKEN in your environment to enable Beamable Config fetching.`);
133
+ }
134
+ }
135
+ console.warn(`[EnvLoader] Response: ${errorText.substring(0, 500)}`);
111
136
  return configVars;
112
137
  }
113
138
  const data = (await response.json());
@@ -1 +1 @@
1
- {"version":3,"file":"env-loader.d.ts","sourceRoot":"","sources":["../src/env-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAsBpD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAenE;AA4LD;;;;;;;;;;;;GAYG;AACH,wBAAsB,iCAAiC,CACrD,GAAG,EAAE,iBAAiB,EACtB,WAAW,CAAC,EAAE,MAAM,EACpB,aAAa,GAAE,OAAc,EAC7B,SAAS,GAAE,MAAa,GACvB,OAAO,CAAC,IAAI,CAAC,CAoEf"}
1
+ {"version":3,"file":"env-loader.d.ts","sourceRoot":"","sources":["../src/env-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAsBpD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAenE;AA0ND;;;;;;;;;;;;GAYG;AACH,wBAAsB,iCAAiC,CACrD,GAAG,EAAE,iBAAiB,EACtB,WAAW,CAAC,EAAE,MAAM,EACpB,aAAa,GAAE,OAAc,EAC7B,SAAS,GAAE,MAAa,GACvB,OAAO,CAAC,IAAI,CAAC,CAoEf"}
@@ -113,8 +113,15 @@ function loadDeveloperEnvVars(beamEnvPath) {
113
113
  async function fetchBeamableConfig(env, timeoutMs = 2000) {
114
114
  const configVars = {};
115
115
  // Skip if we don't have required credentials
116
- if (!env.secret || !env.cid || !env.pid) {
117
- console.log('[EnvLoader] Skipping Beamable Config fetch - missing SECRET, CID, or PID');
116
+ if (!env.cid || !env.pid) {
117
+ console.log('[EnvLoader] Skipping Beamable Config fetch - missing CID or PID');
118
+ return configVars;
119
+ }
120
+ // Check if we have an access token (preferred) or secret (fallback)
121
+ const accessToken = process.env.ACCESS_TOKEN || process.env.BEAMABLE_TOKEN;
122
+ const hasSecret = !!env.secret;
123
+ if (!accessToken && !hasSecret) {
124
+ console.log('[EnvLoader] Skipping Beamable Config fetch - missing ACCESS_TOKEN/BEAMABLE_TOKEN or SECRET');
118
125
  return configVars;
119
126
  }
120
127
  try {
@@ -123,10 +130,24 @@ async function fetchBeamableConfig(env, timeoutMs = 2000) {
123
130
  // Default endpoint matches C# SDK and Portal: /basic/realms/config (not /api/basic/realms/config)
124
131
  const uriPath = process.env.BEAM_CONFIG_API_PATH || '/basic/realms/config';
125
132
  const configUrl = new URL(uriPath, apiUrl).toString();
126
- // Calculate signature for signed request
127
- const pathAndQuery = uriPath;
128
- const signature = calculateSignature(env.pid, env.secret, pathAndQuery, null, '1');
133
+ // Build headers - prefer token-based auth if available, fallback to signature-based
134
+ const headers = {
135
+ 'Content-Type': 'application/json',
136
+ Accept: 'application/json',
137
+ 'X-BEAM-SCOPE': `${env.cid}.${env.pid}`,
138
+ };
139
+ if (accessToken) {
140
+ // Use token-based authentication (matches Portal's $beamable.get())
141
+ headers['Authorization'] = `Bearer ${accessToken}`;
142
+ }
143
+ else if (hasSecret && env.secret) {
144
+ // Fallback to signature-based authentication
145
+ const pathAndQuery = uriPath;
146
+ const signature = calculateSignature(env.pid, env.secret, pathAndQuery, null, '1');
147
+ headers['X-BEAM-SIGNATURE'] = signature;
148
+ }
129
149
  console.log(`[EnvLoader] Fetching Beamable Config from ${configUrl}...`);
150
+ console.log(`[EnvLoader] Using ${accessToken ? 'token-based' : 'signature-based'} authentication`);
130
151
  // Add timeout to fetch to prevent hanging requests
131
152
  const controller = new AbortController();
132
153
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
@@ -134,12 +155,7 @@ async function fetchBeamableConfig(env, timeoutMs = 2000) {
134
155
  try {
135
156
  response = await fetch(configUrl, {
136
157
  method: 'GET',
137
- headers: {
138
- 'Content-Type': 'application/json',
139
- Accept: 'application/json',
140
- 'X-BEAM-SCOPE': `${env.cid}.${env.pid}`,
141
- 'X-BEAM-SIGNATURE': signature,
142
- },
158
+ headers,
143
159
  signal: controller.signal,
144
160
  });
145
161
  clearTimeout(timeoutId);
@@ -147,13 +163,25 @@ async function fetchBeamableConfig(env, timeoutMs = 2000) {
147
163
  catch (error) {
148
164
  clearTimeout(timeoutId);
149
165
  if (error instanceof Error && error.name === 'AbortError') {
166
+ console.warn(`[EnvLoader] Beamable Config fetch timed out after ${timeoutMs}ms`);
167
+ console.warn(`[EnvLoader] URL: ${configUrl}`);
168
+ console.warn(`[EnvLoader] Auth method: ${accessToken ? 'token-based' : 'signature-based'}`);
150
169
  throw new Error(`Beamable Config fetch timed out after ${timeoutMs}ms`);
151
170
  }
152
171
  throw error;
153
172
  }
154
173
  if (!response.ok) {
155
174
  const errorText = await response.text().catch(() => 'Unknown error');
156
- console.warn(`[EnvLoader] Failed to fetch Beamable Config: ${response.status} ${response.statusText} - ${errorText.substring(0, 200)}`);
175
+ console.warn(`[EnvLoader] Failed to fetch Beamable Config: ${response.status} ${response.statusText}`);
176
+ console.warn(`[EnvLoader] URL: ${configUrl}`);
177
+ console.warn(`[EnvLoader] Auth method: ${accessToken ? 'token-based' : 'signature-based'}`);
178
+ if (response.status === 401 || response.status === 403) {
179
+ console.warn(`[EnvLoader] Authentication failed. This endpoint requires token-based auth (ACCESS_TOKEN or BEAMABLE_TOKEN).`);
180
+ if (!accessToken) {
181
+ console.warn(`[EnvLoader] Tip: Set ACCESS_TOKEN or BEAMABLE_TOKEN in your environment to enable Beamable Config fetching.`);
182
+ }
183
+ }
184
+ console.warn(`[EnvLoader] Response: ${errorText.substring(0, 500)}`);
157
185
  return configVars;
158
186
  }
159
187
  const data = (await response.json());
@@ -1 +1 @@
1
- {"version":3,"file":"env-loader.js","sourceRoot":"","sources":["../src/env-loader.ts"],"names":[],"mappings":"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","sourcesContent":["/**\r\n * Environment Variable Loader\r\n * \r\n * This module handles loading environment variables from two sources:\r\n * 1. Developer-defined variables (from beam.env file)\r\n * 2. Beamable Config values (from API: namespace -> environment -> key-value pairs)\r\n * \r\n * All variables are injected into process.env before service initialization.\r\n */\r\n\r\nimport { existsSync, readFileSync } from 'node:fs';\r\nimport { join } from 'node:path';\r\nimport { createHash } from 'node:crypto';\r\nimport { URL } from 'node:url';\r\nimport type { EnvironmentConfig } from './types.js';\r\nimport { hostToHttpUrl } from './utils/urls.js';\r\n\r\n/**\r\n * Calculates Beamable signature for signed requests\r\n * Signature format: MD5(secret + pid + version + uriPathAndQuery + body) as Base64\r\n */\r\nfunction calculateSignature(\r\n pid: string,\r\n secret: string,\r\n uriPathAndQuery: string,\r\n body: string | null = null,\r\n version: string = '1'\r\n): string {\r\n let dataToSign = `${secret}${pid}${version}${uriPathAndQuery}`;\r\n if (body) {\r\n dataToSign += body;\r\n }\r\n const hash = createHash('md5').update(dataToSign, 'utf8').digest('base64');\r\n return hash;\r\n}\r\n\r\n/**\r\n * Loads developer-defined environment variables from beam.env file\r\n * Supports standard .env format: KEY=value\r\n * \r\n * This is exported for synchronous loading before logger initialization\r\n */\r\nexport function loadDeveloperEnvVarsSync(beamEnvPath?: string): void {\r\n const envVars = loadDeveloperEnvVars(beamEnvPath);\r\n \r\n // Inject immediately into process.env\r\n let injectedCount = 0;\r\n for (const [key, value] of Object.entries(envVars)) {\r\n if (!(key in process.env)) {\r\n process.env[key] = value;\r\n injectedCount++;\r\n }\r\n }\r\n \r\n if (injectedCount > 0) {\r\n console.log(`[EnvLoader] Synchronously injected ${injectedCount} developer-defined variables from beam.env`);\r\n }\r\n}\r\n\r\n/**\r\n * Loads developer-defined environment variables from beam.env file\r\n * Supports standard .env format: KEY=value\r\n */\r\nfunction loadDeveloperEnvVars(beamEnvPath?: string): Record<string, string> {\r\n const envVars: Record<string, string> = {};\r\n \r\n // Try to find beam.env file\r\n const possiblePaths = [\r\n beamEnvPath,\r\n join(process.cwd(), 'beam.env'),\r\n join(process.cwd(), '.beam.env'),\r\n '/beam/service/beam.env', // Container path\r\n '/beam/service/.beam.env',\r\n ].filter((path): path is string => !!path && existsSync(path));\r\n\r\n if (possiblePaths.length === 0) {\r\n return envVars;\r\n }\r\n\r\n const envFilePath = possiblePaths[0];\r\n try {\r\n const content = readFileSync(envFilePath, 'utf-8');\r\n const lines = content.split('\\n');\r\n \r\n for (const line of lines) {\r\n const trimmed = line.trim();\r\n // Skip empty lines and comments\r\n if (!trimmed || trimmed.startsWith('#')) {\r\n continue;\r\n }\r\n \r\n // Parse KEY=value format\r\n const match = trimmed.match(/^([^=#]+)=(.*)$/);\r\n if (match) {\r\n const key = match[1].trim();\r\n let value = match[2].trim();\r\n \r\n // Remove quotes if present\r\n if ((value.startsWith('\"') && value.endsWith('\"')) || \r\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\r\n value = value.slice(1, -1);\r\n }\r\n \r\n if (key) {\r\n envVars[key] = value;\r\n }\r\n }\r\n }\r\n \r\n console.log(`[EnvLoader] Loaded ${Object.keys(envVars).length} variables from ${envFilePath}`);\r\n } catch (error) {\r\n console.warn(`[EnvLoader] Failed to load beam.env from ${envFilePath}:`, error instanceof Error ? error.message : String(error));\r\n }\r\n \r\n return envVars;\r\n}\r\n\r\n/**\r\n * Beamable Config API response structure\r\n */\r\ninterface BeamableConfigResponse {\r\n config?: {\r\n [key: string]: {\r\n [environment: string]: string | number | boolean | null;\r\n };\r\n };\r\n [key: string]: unknown;\r\n}\r\n\r\n/**\r\n * Fetches Beamable Config values from the API\r\n * \r\n * The endpoint can be configured via BEAM_CONFIG_API_PATH environment variable.\r\n * Default: /api/basic/realms/config\r\n * \r\n * Note: The exact endpoint format may vary. The API should return a structure like:\r\n * {\r\n * \"config\": {\r\n * \"key1\": {\r\n * \"environment\": \"value1\"\r\n * }\r\n * }\r\n * }\r\n * \r\n * Returns key-value pairs from the current realm's config namespace/environment\r\n */\r\nasync function fetchBeamableConfig(\r\n env: EnvironmentConfig,\r\n timeoutMs: number = 2000\r\n): Promise<Record<string, string>> {\r\n const configVars: Record<string, string> = {};\r\n \r\n // Skip if we don't have required credentials\r\n if (!env.secret || !env.cid || !env.pid) {\r\n console.log('[EnvLoader] Skipping Beamable Config fetch - missing SECRET, CID, or PID');\r\n return configVars;\r\n }\r\n\r\n try {\r\n const apiUrl = hostToHttpUrl(env.host);\r\n // Allow endpoint to be configured via environment variable (for flexibility)\r\n // Default endpoint matches C# SDK and Portal: /basic/realms/config (not /api/basic/realms/config)\r\n const uriPath = process.env.BEAM_CONFIG_API_PATH || '/basic/realms/config';\r\n const configUrl = new URL(uriPath, apiUrl).toString();\r\n \r\n // Calculate signature for signed request\r\n const pathAndQuery = uriPath;\r\n const signature = calculateSignature(env.pid, env.secret, pathAndQuery, null, '1');\r\n \r\n console.log(`[EnvLoader] Fetching Beamable Config from ${configUrl}...`);\r\n \r\n // Add timeout to fetch to prevent hanging requests\r\n const controller = new AbortController();\r\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\r\n \r\n let response: Response;\r\n try {\r\n response = await fetch(configUrl, {\r\n method: 'GET',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n Accept: 'application/json',\r\n 'X-BEAM-SCOPE': `${env.cid}.${env.pid}`,\r\n 'X-BEAM-SIGNATURE': signature,\r\n },\r\n signal: controller.signal,\r\n });\r\n clearTimeout(timeoutId);\r\n } catch (error) {\r\n clearTimeout(timeoutId);\r\n if (error instanceof Error && error.name === 'AbortError') {\r\n throw new Error(`Beamable Config fetch timed out after ${timeoutMs}ms`);\r\n }\r\n throw error;\r\n }\r\n\r\n if (!response.ok) {\r\n const errorText = await response.text().catch(() => 'Unknown error');\r\n console.warn(`[EnvLoader] Failed to fetch Beamable Config: ${response.status} ${response.statusText} - ${errorText.substring(0, 200)}`);\r\n return configVars;\r\n }\r\n\r\n const data = (await response.json()) as BeamableConfigResponse;\r\n \r\n // Parse config structure: config[key][environment] = value\r\n // We need to determine the current environment - typically it's the PID or a specific env var\r\n const currentEnvironment = process.env.BEAM_ENVIRONMENT || process.env.ENVIRONMENT || env.pid;\r\n \r\n if (data.config) {\r\n for (const [key, environments] of Object.entries(data.config)) {\r\n // Try to get value for current environment, fallback to first available\r\n let value: string | number | boolean | null | undefined;\r\n \r\n if (environments && typeof environments === 'object') {\r\n value = environments[currentEnvironment];\r\n \r\n // If no value for current environment, try common environment names\r\n if (value === undefined) {\r\n value = environments['production'] || \r\n environments['staging'] || \r\n environments['development'] ||\r\n environments['dev'] ||\r\n Object.values(environments)[0];\r\n }\r\n }\r\n \r\n // Convert value to string and add to env vars\r\n if (value !== undefined && value !== null) {\r\n // Use BEAM_CONFIG_ prefix to avoid conflicts\r\n const envKey = `BEAM_CONFIG_${key.toUpperCase()}`;\r\n configVars[envKey] = String(value);\r\n }\r\n }\r\n \r\n console.log(`[EnvLoader] Loaded ${Object.keys(configVars).length} variables from Beamable Config`);\r\n } else {\r\n console.log('[EnvLoader] Beamable Config response contains no config object');\r\n }\r\n } catch (error) {\r\n console.warn('[EnvLoader] Failed to fetch Beamable Config:', error instanceof Error ? error.message : String(error));\r\n }\r\n \r\n return configVars;\r\n}\r\n\r\n/**\r\n * Loads all environment variables from both sources and injects them into process.env\r\n * \r\n * Priority (highest to lowest):\r\n * 1. Existing process.env values (never overwrite)\r\n * 2. Developer-defined variables (beam.env)\r\n * 3. Beamable Config values (API)\r\n * \r\n * @param env - Environment configuration (CID, PID, HOST, SECRET)\r\n * @param beamEnvPath - Optional path to beam.env file\r\n * @param waitForConfig - If true, waits up to timeoutMs for Beamable Config. If false, returns immediately after developer vars.\r\n * @param timeoutMs - Maximum time to wait for Beamable Config (default: 2000ms)\r\n */\r\nexport async function loadAndInjectEnvironmentVariables(\r\n env: EnvironmentConfig,\r\n beamEnvPath?: string,\r\n waitForConfig: boolean = true,\r\n timeoutMs: number = 2000\r\n): Promise<void> {\r\n console.log('[EnvLoader] Starting environment variable loading...');\r\n \r\n // Load developer-defined variables from beam.env (synchronous, immediate)\r\n const developerVars = loadDeveloperEnvVars(beamEnvPath);\r\n \r\n // Inject developer vars immediately\r\n let injectedCount = 0;\r\n for (const [key, value] of Object.entries(developerVars)) {\r\n if (!(key in process.env)) {\r\n process.env[key] = value;\r\n injectedCount++;\r\n }\r\n }\r\n \r\n if (injectedCount > 0) {\r\n console.log(`[EnvLoader] Injected ${injectedCount} developer-defined variables from beam.env`);\r\n }\r\n \r\n // Fetch Beamable Config values (async, with optional timeout)\r\n if (waitForConfig) {\r\n try {\r\n const configPromise = fetchBeamableConfig(env, timeoutMs);\r\n const timeoutPromise = new Promise<Record<string, string>>((resolve) => {\r\n setTimeout(() => {\r\n console.warn(`[EnvLoader] Beamable Config fetch timed out after ${timeoutMs}ms, continuing without it`);\r\n resolve({});\r\n }, timeoutMs);\r\n });\r\n \r\n const beamableConfigVars = await Promise.race([configPromise, timeoutPromise]);\r\n \r\n // Inject Beamable Config vars\r\n let configInjectedCount = 0;\r\n for (const [key, value] of Object.entries(beamableConfigVars)) {\r\n if (!(key in process.env)) {\r\n process.env[key] = value;\r\n configInjectedCount++;\r\n }\r\n }\r\n \r\n if (configInjectedCount > 0) {\r\n console.log(`[EnvLoader] Injected ${configInjectedCount} variables from Beamable Config`);\r\n }\r\n } catch (error) {\r\n console.warn('[EnvLoader] Error loading Beamable Config (continuing anyway):', error instanceof Error ? error.message : String(error));\r\n }\r\n } else {\r\n // Start fetch in background (non-blocking)\r\n fetchBeamableConfig(env)\r\n .then((beamableConfigVars) => {\r\n let configInjectedCount = 0;\r\n for (const [key, value] of Object.entries(beamableConfigVars)) {\r\n if (!(key in process.env)) {\r\n process.env[key] = value;\r\n configInjectedCount++;\r\n }\r\n }\r\n if (configInjectedCount > 0) {\r\n console.log(`[EnvLoader] Injected ${configInjectedCount} variables from Beamable Config (loaded asynchronously)`);\r\n }\r\n })\r\n .catch((error) => {\r\n console.warn('[EnvLoader] Failed to load Beamable Config in background:', error instanceof Error ? error.message : String(error));\r\n });\r\n }\r\n \r\n console.log('[EnvLoader] Environment variable loading completed');\r\n}\r\n\r\n"]}
1
+ {"version":3,"file":"env-loader.js","sourceRoot":"","sources":["../src/env-loader.ts"],"names":[],"mappings":"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","sourcesContent":["/**\r\n * Environment Variable Loader\r\n * \r\n * This module handles loading environment variables from two sources:\r\n * 1. Developer-defined variables (from beam.env file)\r\n * 2. Beamable Config values (from API: namespace -> environment -> key-value pairs)\r\n * \r\n * All variables are injected into process.env before service initialization.\r\n */\r\n\r\nimport { existsSync, readFileSync } from 'node:fs';\r\nimport { join } from 'node:path';\r\nimport { createHash } from 'node:crypto';\r\nimport { URL } from 'node:url';\r\nimport type { EnvironmentConfig } from './types.js';\r\nimport { hostToHttpUrl } from './utils/urls.js';\r\n\r\n/**\r\n * Calculates Beamable signature for signed requests\r\n * Signature format: MD5(secret + pid + version + uriPathAndQuery + body) as Base64\r\n */\r\nfunction calculateSignature(\r\n pid: string,\r\n secret: string,\r\n uriPathAndQuery: string,\r\n body: string | null = null,\r\n version: string = '1'\r\n): string {\r\n let dataToSign = `${secret}${pid}${version}${uriPathAndQuery}`;\r\n if (body) {\r\n dataToSign += body;\r\n }\r\n const hash = createHash('md5').update(dataToSign, 'utf8').digest('base64');\r\n return hash;\r\n}\r\n\r\n/**\r\n * Loads developer-defined environment variables from beam.env file\r\n * Supports standard .env format: KEY=value\r\n * \r\n * This is exported for synchronous loading before logger initialization\r\n */\r\nexport function loadDeveloperEnvVarsSync(beamEnvPath?: string): void {\r\n const envVars = loadDeveloperEnvVars(beamEnvPath);\r\n \r\n // Inject immediately into process.env\r\n let injectedCount = 0;\r\n for (const [key, value] of Object.entries(envVars)) {\r\n if (!(key in process.env)) {\r\n process.env[key] = value;\r\n injectedCount++;\r\n }\r\n }\r\n \r\n if (injectedCount > 0) {\r\n console.log(`[EnvLoader] Synchronously injected ${injectedCount} developer-defined variables from beam.env`);\r\n }\r\n}\r\n\r\n/**\r\n * Loads developer-defined environment variables from beam.env file\r\n * Supports standard .env format: KEY=value\r\n */\r\nfunction loadDeveloperEnvVars(beamEnvPath?: string): Record<string, string> {\r\n const envVars: Record<string, string> = {};\r\n \r\n // Try to find beam.env file\r\n const possiblePaths = [\r\n beamEnvPath,\r\n join(process.cwd(), 'beam.env'),\r\n join(process.cwd(), '.beam.env'),\r\n '/beam/service/beam.env', // Container path\r\n '/beam/service/.beam.env',\r\n ].filter((path): path is string => !!path && existsSync(path));\r\n\r\n if (possiblePaths.length === 0) {\r\n return envVars;\r\n }\r\n\r\n const envFilePath = possiblePaths[0];\r\n try {\r\n const content = readFileSync(envFilePath, 'utf-8');\r\n const lines = content.split('\\n');\r\n \r\n for (const line of lines) {\r\n const trimmed = line.trim();\r\n // Skip empty lines and comments\r\n if (!trimmed || trimmed.startsWith('#')) {\r\n continue;\r\n }\r\n \r\n // Parse KEY=value format\r\n const match = trimmed.match(/^([^=#]+)=(.*)$/);\r\n if (match) {\r\n const key = match[1].trim();\r\n let value = match[2].trim();\r\n \r\n // Remove quotes if present\r\n if ((value.startsWith('\"') && value.endsWith('\"')) || \r\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\r\n value = value.slice(1, -1);\r\n }\r\n \r\n if (key) {\r\n envVars[key] = value;\r\n }\r\n }\r\n }\r\n \r\n console.log(`[EnvLoader] Loaded ${Object.keys(envVars).length} variables from ${envFilePath}`);\r\n } catch (error) {\r\n console.warn(`[EnvLoader] Failed to load beam.env from ${envFilePath}:`, error instanceof Error ? error.message : String(error));\r\n }\r\n \r\n return envVars;\r\n}\r\n\r\n/**\r\n * Beamable Config API response structure\r\n */\r\ninterface BeamableConfigResponse {\r\n config?: {\r\n [key: string]: {\r\n [environment: string]: string | number | boolean | null;\r\n };\r\n };\r\n [key: string]: unknown;\r\n}\r\n\r\n/**\r\n * Fetches Beamable Config values from the API\r\n * \r\n * The endpoint can be configured via BEAM_CONFIG_API_PATH environment variable.\r\n * Default: /api/basic/realms/config\r\n * \r\n * Note: The exact endpoint format may vary. The API should return a structure like:\r\n * {\r\n * \"config\": {\r\n * \"key1\": {\r\n * \"environment\": \"value1\"\r\n * }\r\n * }\r\n * }\r\n * \r\n * Returns key-value pairs from the current realm's config namespace/environment\r\n */\r\nasync function fetchBeamableConfig(\r\n env: EnvironmentConfig,\r\n timeoutMs: number = 2000\r\n): Promise<Record<string, string>> {\r\n const configVars: Record<string, string> = {};\r\n \r\n // Skip if we don't have required credentials\r\n if (!env.cid || !env.pid) {\r\n console.log('[EnvLoader] Skipping Beamable Config fetch - missing CID or PID');\r\n return configVars;\r\n }\r\n\r\n // Check if we have an access token (preferred) or secret (fallback)\r\n const accessToken = process.env.ACCESS_TOKEN || process.env.BEAMABLE_TOKEN;\r\n const hasSecret = !!env.secret;\r\n \r\n if (!accessToken && !hasSecret) {\r\n console.log('[EnvLoader] Skipping Beamable Config fetch - missing ACCESS_TOKEN/BEAMABLE_TOKEN or SECRET');\r\n return configVars;\r\n }\r\n\r\n try {\r\n const apiUrl = hostToHttpUrl(env.host);\r\n // Allow endpoint to be configured via environment variable (for flexibility)\r\n // Default endpoint matches C# SDK and Portal: /basic/realms/config (not /api/basic/realms/config)\r\n const uriPath = process.env.BEAM_CONFIG_API_PATH || '/basic/realms/config';\r\n const configUrl = new URL(uriPath, apiUrl).toString();\r\n \r\n // Build headers - prefer token-based auth if available, fallback to signature-based\r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n Accept: 'application/json',\r\n 'X-BEAM-SCOPE': `${env.cid}.${env.pid}`,\r\n };\r\n \r\n if (accessToken) {\r\n // Use token-based authentication (matches Portal's $beamable.get())\r\n headers['Authorization'] = `Bearer ${accessToken}`;\r\n } else if (hasSecret && env.secret) {\r\n // Fallback to signature-based authentication\r\n const pathAndQuery = uriPath;\r\n const signature = calculateSignature(env.pid, env.secret, pathAndQuery, null, '1');\r\n headers['X-BEAM-SIGNATURE'] = signature;\r\n }\r\n \r\n console.log(`[EnvLoader] Fetching Beamable Config from ${configUrl}...`);\r\n console.log(`[EnvLoader] Using ${accessToken ? 'token-based' : 'signature-based'} authentication`);\r\n \r\n // Add timeout to fetch to prevent hanging requests\r\n const controller = new AbortController();\r\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\r\n \r\n let response: Response;\r\n try {\r\n response = await fetch(configUrl, {\r\n method: 'GET',\r\n headers,\r\n signal: controller.signal,\r\n });\r\n clearTimeout(timeoutId);\r\n } catch (error) {\r\n clearTimeout(timeoutId);\r\n if (error instanceof Error && error.name === 'AbortError') {\r\n console.warn(`[EnvLoader] Beamable Config fetch timed out after ${timeoutMs}ms`);\r\n console.warn(`[EnvLoader] URL: ${configUrl}`);\r\n console.warn(`[EnvLoader] Auth method: ${accessToken ? 'token-based' : 'signature-based'}`);\r\n throw new Error(`Beamable Config fetch timed out after ${timeoutMs}ms`);\r\n }\r\n throw error;\r\n }\r\n\r\n if (!response.ok) {\r\n const errorText = await response.text().catch(() => 'Unknown error');\r\n console.warn(`[EnvLoader] Failed to fetch Beamable Config: ${response.status} ${response.statusText}`);\r\n console.warn(`[EnvLoader] URL: ${configUrl}`);\r\n console.warn(`[EnvLoader] Auth method: ${accessToken ? 'token-based' : 'signature-based'}`);\r\n if (response.status === 401 || response.status === 403) {\r\n console.warn(`[EnvLoader] Authentication failed. This endpoint requires token-based auth (ACCESS_TOKEN or BEAMABLE_TOKEN).`);\r\n if (!accessToken) {\r\n console.warn(`[EnvLoader] Tip: Set ACCESS_TOKEN or BEAMABLE_TOKEN in your environment to enable Beamable Config fetching.`);\r\n }\r\n }\r\n console.warn(`[EnvLoader] Response: ${errorText.substring(0, 500)}`);\r\n return configVars;\r\n }\r\n\r\n const data = (await response.json()) as BeamableConfigResponse;\r\n \r\n // Parse config structure: config[key][environment] = value\r\n // We need to determine the current environment - typically it's the PID or a specific env var\r\n const currentEnvironment = process.env.BEAM_ENVIRONMENT || process.env.ENVIRONMENT || env.pid;\r\n \r\n if (data.config) {\r\n for (const [key, environments] of Object.entries(data.config)) {\r\n // Try to get value for current environment, fallback to first available\r\n let value: string | number | boolean | null | undefined;\r\n \r\n if (environments && typeof environments === 'object') {\r\n value = environments[currentEnvironment];\r\n \r\n // If no value for current environment, try common environment names\r\n if (value === undefined) {\r\n value = environments['production'] || \r\n environments['staging'] || \r\n environments['development'] ||\r\n environments['dev'] ||\r\n Object.values(environments)[0];\r\n }\r\n }\r\n \r\n // Convert value to string and add to env vars\r\n if (value !== undefined && value !== null) {\r\n // Use BEAM_CONFIG_ prefix to avoid conflicts\r\n const envKey = `BEAM_CONFIG_${key.toUpperCase()}`;\r\n configVars[envKey] = String(value);\r\n }\r\n }\r\n \r\n console.log(`[EnvLoader] Loaded ${Object.keys(configVars).length} variables from Beamable Config`);\r\n } else {\r\n console.log('[EnvLoader] Beamable Config response contains no config object');\r\n }\r\n } catch (error) {\r\n console.warn('[EnvLoader] Failed to fetch Beamable Config:', error instanceof Error ? error.message : String(error));\r\n }\r\n \r\n return configVars;\r\n}\r\n\r\n/**\r\n * Loads all environment variables from both sources and injects them into process.env\r\n * \r\n * Priority (highest to lowest):\r\n * 1. Existing process.env values (never overwrite)\r\n * 2. Developer-defined variables (beam.env)\r\n * 3. Beamable Config values (API)\r\n * \r\n * @param env - Environment configuration (CID, PID, HOST, SECRET)\r\n * @param beamEnvPath - Optional path to beam.env file\r\n * @param waitForConfig - If true, waits up to timeoutMs for Beamable Config. If false, returns immediately after developer vars.\r\n * @param timeoutMs - Maximum time to wait for Beamable Config (default: 2000ms)\r\n */\r\nexport async function loadAndInjectEnvironmentVariables(\r\n env: EnvironmentConfig,\r\n beamEnvPath?: string,\r\n waitForConfig: boolean = true,\r\n timeoutMs: number = 2000\r\n): Promise<void> {\r\n console.log('[EnvLoader] Starting environment variable loading...');\r\n \r\n // Load developer-defined variables from beam.env (synchronous, immediate)\r\n const developerVars = loadDeveloperEnvVars(beamEnvPath);\r\n \r\n // Inject developer vars immediately\r\n let injectedCount = 0;\r\n for (const [key, value] of Object.entries(developerVars)) {\r\n if (!(key in process.env)) {\r\n process.env[key] = value;\r\n injectedCount++;\r\n }\r\n }\r\n \r\n if (injectedCount > 0) {\r\n console.log(`[EnvLoader] Injected ${injectedCount} developer-defined variables from beam.env`);\r\n }\r\n \r\n // Fetch Beamable Config values (async, with optional timeout)\r\n if (waitForConfig) {\r\n try {\r\n const configPromise = fetchBeamableConfig(env, timeoutMs);\r\n const timeoutPromise = new Promise<Record<string, string>>((resolve) => {\r\n setTimeout(() => {\r\n console.warn(`[EnvLoader] Beamable Config fetch timed out after ${timeoutMs}ms, continuing without it`);\r\n resolve({});\r\n }, timeoutMs);\r\n });\r\n \r\n const beamableConfigVars = await Promise.race([configPromise, timeoutPromise]);\r\n \r\n // Inject Beamable Config vars\r\n let configInjectedCount = 0;\r\n for (const [key, value] of Object.entries(beamableConfigVars)) {\r\n if (!(key in process.env)) {\r\n process.env[key] = value;\r\n configInjectedCount++;\r\n }\r\n }\r\n \r\n if (configInjectedCount > 0) {\r\n console.log(`[EnvLoader] Injected ${configInjectedCount} variables from Beamable Config`);\r\n }\r\n } catch (error) {\r\n console.warn('[EnvLoader] Error loading Beamable Config (continuing anyway):', error instanceof Error ? error.message : String(error));\r\n }\r\n } else {\r\n // Start fetch in background (non-blocking)\r\n fetchBeamableConfig(env)\r\n .then((beamableConfigVars) => {\r\n let configInjectedCount = 0;\r\n for (const [key, value] of Object.entries(beamableConfigVars)) {\r\n if (!(key in process.env)) {\r\n process.env[key] = value;\r\n configInjectedCount++;\r\n }\r\n }\r\n if (configInjectedCount > 0) {\r\n console.log(`[EnvLoader] Injected ${configInjectedCount} variables from Beamable Config (loaded asynchronously)`);\r\n }\r\n })\r\n .catch((error) => {\r\n console.warn('[EnvLoader] Failed to load Beamable Config in background:', error instanceof Error ? error.message : String(error));\r\n });\r\n }\r\n \r\n console.log('[EnvLoader] Environment variable loading completed');\r\n}\r\n\r\n"]}
package/dist/storage.cjs CHANGED
@@ -122,15 +122,28 @@ class StorageService {
122
122
  }
123
123
  async getMongoClient(connectionString) {
124
124
  const normalizedConnectionString = this.normalizeConnectionString(connectionString);
125
+ if (normalizedConnectionString !== connectionString) {
126
+ this.logger.debug('MongoDB connection string was normalized (password encoding applied)');
127
+ }
125
128
  if (this.clientCache.has(normalizedConnectionString)) {
126
129
  return this.clientCache.get(normalizedConnectionString);
127
130
  }
128
- const client = new mongodb_1.MongoClient(normalizedConnectionString, {
129
- maxPoolSize: 20,
130
- });
131
- await client.connect();
132
- this.clientCache.set(normalizedConnectionString, client);
133
- return client;
131
+ try {
132
+ const client = new mongodb_1.MongoClient(normalizedConnectionString, {
133
+ maxPoolSize: 20,
134
+ });
135
+ await client.connect();
136
+ this.clientCache.set(normalizedConnectionString, client);
137
+ return client;
138
+ }
139
+ catch (error) {
140
+ const sanitizedConnectionString = normalizedConnectionString.replace(/:([^:@]+)@/, ':****@');
141
+ this.logger.error({
142
+ error: error instanceof Error ? error.message : String(error),
143
+ connectionString: sanitizedConnectionString
144
+ }, 'Failed to connect to MongoDB');
145
+ throw error;
146
+ }
134
147
  }
135
148
  async getConnectionString(storageName) {
136
149
  const variableName = `${CONNECTION_STRING_ENV_PREFIX}${storageName}`;
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,EAAe,KAAK,EAAE,EAAE,KAAK,UAAU,EAAE,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,wBAAyB,SAAQ,wBAAwB;IACxE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;CACrB;AAID,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAOjE;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,QAAQ,GAAG,eAAe,GAAG,SAAS,CAEhF;AAED,wBAAgB,4BAA4B,IAAI,eAAe,EAAE,CAEhE;AAED,UAAU,0BAA0B;IAClC,SAAS,EAAE,aAAa,CAAC;IACzB,GAAG,EAAE,YAAY,CAAC;IAClB,GAAG,EAAE,iBAAiB,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAQD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgB;IAC1C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAoB;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAyB;IACvD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAkC;IAC9D,OAAO,CAAC,sBAAsB,CAAC,CAAS;gBAE5B,YAAY,EAAE,0BAA0B;IAO9C,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,EAAE,CAAC;IAkBrF,cAAc,CAAC,CAAC,EAAE,WAAW,EAAE,UAAU,CAAC,EAAE,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,EAAE,CAAC;IAUhG,aAAa,CAAC,SAAS,SAAS,QAAQ,EAC5C,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAS3B,gBAAgB,CAAC,QAAQ,EAAE,SAAS,SAAS,QAAQ,EACzD,WAAW,EAAE,UAAU,QAAQ,EAC/B,cAAc,EAAE,UAAU,SAAS,EACnC,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAYjC;;;;;;OAMG;IACH,OAAO,CAAC,yBAAyB;YAqEnB,cAAc;YAed,mBAAmB;YAmBnB,qBAAqB;IAwBnC,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,oBAAoB;IAQtB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAQ/B"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,EAAe,KAAK,EAAE,EAAE,KAAK,UAAU,EAAE,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,wBAAyB,SAAQ,wBAAwB;IACxE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;CACrB;AAID,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAOjE;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,QAAQ,GAAG,eAAe,GAAG,SAAS,CAEhF;AAED,wBAAgB,4BAA4B,IAAI,eAAe,EAAE,CAEhE;AAED,UAAU,0BAA0B;IAClC,SAAS,EAAE,aAAa,CAAC;IACzB,GAAG,EAAE,YAAY,CAAC;IAClB,GAAG,EAAE,iBAAiB,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAQD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgB;IAC1C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAoB;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAyB;IACvD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAkC;IAC9D,OAAO,CAAC,sBAAsB,CAAC,CAAS;gBAE5B,YAAY,EAAE,0BAA0B;IAO9C,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,EAAE,CAAC;IAkBrF,cAAc,CAAC,CAAC,EAAE,WAAW,EAAE,UAAU,CAAC,EAAE,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,EAAE,CAAC;IAUhG,aAAa,CAAC,SAAS,SAAS,QAAQ,EAC5C,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAS3B,gBAAgB,CAAC,QAAQ,EAAE,SAAS,SAAS,QAAQ,EACzD,WAAW,EAAE,UAAU,QAAQ,EAC/B,cAAc,EAAE,UAAU,SAAS,EACnC,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAYjC;;;;;;OAMG;IACH,OAAO,CAAC,yBAAyB;YAqEnB,cAAc;YAkCd,mBAAmB;YAmBnB,qBAAqB;IAwBnC,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,oBAAoB;IAQtB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAQ/B"}
package/dist/storage.js CHANGED
@@ -138,15 +138,30 @@ export class StorageService {
138
138
  async getMongoClient(connectionString) {
139
139
  // Normalize the connection string to ensure username/password are properly encoded
140
140
  const normalizedConnectionString = this.normalizeConnectionString(connectionString);
141
+ // Log the normalization for debugging (only log if different to avoid spam)
142
+ if (normalizedConnectionString !== connectionString) {
143
+ this.logger.debug('MongoDB connection string was normalized (password encoding applied)');
144
+ }
141
145
  if (this.clientCache.has(normalizedConnectionString)) {
142
146
  return this.clientCache.get(normalizedConnectionString);
143
147
  }
144
- const client = new MongoClient(normalizedConnectionString, {
145
- maxPoolSize: 20,
146
- });
147
- await client.connect();
148
- this.clientCache.set(normalizedConnectionString, client);
149
- return client;
148
+ try {
149
+ const client = new MongoClient(normalizedConnectionString, {
150
+ maxPoolSize: 20,
151
+ });
152
+ await client.connect();
153
+ this.clientCache.set(normalizedConnectionString, client);
154
+ return client;
155
+ }
156
+ catch (error) {
157
+ // If connection fails, log the error with connection string details (without password)
158
+ const sanitizedConnectionString = normalizedConnectionString.replace(/:([^:@]+)@/, ':****@');
159
+ this.logger.error({
160
+ error: error instanceof Error ? error.message : String(error),
161
+ connectionString: sanitizedConnectionString
162
+ }, 'Failed to connect to MongoDB');
163
+ throw error;
164
+ }
150
165
  }
151
166
  async getConnectionString(storageName) {
152
167
  const variableName = `${CONNECTION_STRING_ENV_PREFIX}${storageName}`;
@@ -1 +1 @@
1
- {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Logger } from 'pino';\r\nimport { MongoClient, type Db, type Collection, type Document } from 'mongodb';\r\nimport type { BoundBeamApi } from './services.js';\r\nimport type { EnvironmentConfig } from './types.js';\r\nimport type { HttpRequester } from 'beamable-sdk';\r\n\r\nexport interface StorageConnectionOptions {\r\n useCache?: boolean;\r\n}\r\n\r\nexport interface StorageCollectionOptions extends StorageConnectionOptions {\r\n collectionName?: string;\r\n}\r\n\r\nexport interface StorageMetadata {\r\n storageName: string;\r\n}\r\n\r\nconst STORAGE_OBJECT_METADATA = new Map<Function, StorageMetadata>();\r\n\r\nexport function StorageObject(storageName: string): ClassDecorator {\r\n if (!storageName || !storageName.trim()) {\r\n throw new Error('@StorageObject requires a non-empty storage name.');\r\n }\r\n return (target) => {\r\n STORAGE_OBJECT_METADATA.set(target, { storageName: storageName.trim() });\r\n };\r\n}\r\n\r\nexport function getStorageMetadata(target: Function): StorageMetadata | undefined {\r\n return STORAGE_OBJECT_METADATA.get(target);\r\n}\r\n\r\nexport function listRegisteredStorageObjects(): StorageMetadata[] {\r\n return Array.from(STORAGE_OBJECT_METADATA.values());\r\n}\r\n\r\ninterface StorageServiceDependencies {\r\n requester: HttpRequester;\r\n api: BoundBeamApi;\r\n env: EnvironmentConfig;\r\n logger: Logger;\r\n}\r\n\r\ninterface ConnectionStringResponse {\r\n connectionString: string;\r\n}\r\n\r\nconst CONNECTION_STRING_ENV_PREFIX = 'STORAGE_CONNSTR_';\r\n\r\nexport class StorageService {\r\n private readonly requester: HttpRequester;\r\n private readonly api: BoundBeamApi;\r\n private readonly env: EnvironmentConfig;\r\n private readonly logger: Logger;\r\n private readonly databaseCache = new Map<string, Db>();\r\n private readonly clientCache = new Map<string, MongoClient>();\r\n private cachedConnectionString?: string;\r\n\r\n constructor(dependencies: StorageServiceDependencies) {\r\n this.requester = dependencies.requester;\r\n this.api = dependencies.api;\r\n this.env = dependencies.env;\r\n this.logger = dependencies.logger.child({ component: 'StorageService' });\r\n }\r\n\r\n async getDatabase(storageName: string, options: StorageConnectionOptions = {}): Promise<Db> {\r\n const normalized = this.normalizeStorageName(storageName);\r\n if (!options.useCache) {\r\n this.databaseCache.delete(normalized);\r\n }\r\n const cached = this.databaseCache.get(normalized);\r\n if (cached) {\r\n return cached;\r\n }\r\n\r\n const connectionString = await this.getConnectionString(normalized);\r\n const client = await this.getMongoClient(connectionString);\r\n const databaseName = this.buildDatabaseName(normalized);\r\n const database = client.db(databaseName);\r\n this.databaseCache.set(normalized, database);\r\n return database;\r\n }\r\n\r\n async getDatabaseFor<T>(storageCtor: new () => T, options: StorageConnectionOptions = {}): Promise<Db> {\r\n const metadata = getStorageMetadata(storageCtor);\r\n if (!metadata) {\r\n throw new Error(\r\n `Storage metadata for ${storageCtor.name} not found. Did you decorate the class with @StorageObject('Name')?`,\r\n );\r\n }\r\n return this.getDatabase(metadata.storageName, options);\r\n }\r\n\r\n async getCollection<TDocument extends Document>(\r\n storageName: string,\r\n options: StorageCollectionOptions = {},\r\n ): Promise<Collection<TDocument>> {\r\n const database = await this.getDatabase(storageName, options);\r\n const collectionName = options.collectionName?.trim();\r\n if (!collectionName) {\r\n throw new Error('Collection name must be provided when using getCollection with raw storage name.');\r\n }\r\n return database.collection<TDocument>(collectionName);\r\n }\r\n\r\n async getCollectionFor<TStorage, TDocument extends Document>(\r\n storageCtor: new () => TStorage,\r\n collectionCtor: new () => TDocument,\r\n options: StorageCollectionOptions = {},\r\n ): Promise<Collection<TDocument>> {\r\n const metadata = getStorageMetadata(storageCtor);\r\n if (!metadata) {\r\n throw new Error(\r\n `Storage metadata for ${storageCtor.name} not found. Did you decorate the class with @StorageObject('Name')?`,\r\n );\r\n }\r\n const collectionName = options.collectionName?.trim() ?? collectionCtor.name;\r\n const database = await this.getDatabase(metadata.storageName, options);\r\n return database.collection<TDocument>(collectionName);\r\n }\r\n\r\n /**\r\n * Normalizes a MongoDB connection string by ensuring username and password are properly URL-encoded.\r\n * This prevents \"Password contains unescaped characters\" errors when passwords contain special characters.\r\n * \r\n * Handles both mongodb:// and mongodb+srv:// formats.\r\n * Safely handles already-encoded credentials by decoding first, then re-encoding.\r\n */\r\n private normalizeConnectionString(connectionString: string): string {\r\n try {\r\n // Match MongoDB connection string format: mongodb:// or mongodb+srv://\r\n // Format: mongodb[+srv]://[username:password@]host[:port][/database][?options]\r\n const mongoUriRegex = /^(mongodb(?:\\+srv)?:\\/\\/)(?:([^:@]+)(?::([^@]+))?@)?([^\\/?]+)(?:\\/([^?]*))?(?:\\?(.*))?$/;\r\n const match = connectionString.match(mongoUriRegex);\r\n \r\n if (!match) {\r\n // If it doesn't match the expected format, return as-is (might be a different format)\r\n this.logger.warn('Connection string does not match expected MongoDB URI format, using as-is');\r\n return connectionString;\r\n }\r\n\r\n const [, protocol, username, password, host, database, options] = match;\r\n \r\n // If no username/password, return as-is\r\n if (!username) {\r\n return connectionString;\r\n }\r\n\r\n // Decode username and password first (in case they're already encoded)\r\n // Then re-encode them properly to ensure special characters are handled\r\n let decodedUsername: string;\r\n let decodedPassword: string | undefined;\r\n \r\n try {\r\n decodedUsername = decodeURIComponent(username);\r\n } catch {\r\n // If decoding fails, assume it's not encoded and use as-is\r\n decodedUsername = username;\r\n }\r\n \r\n if (password) {\r\n try {\r\n decodedPassword = decodeURIComponent(password);\r\n } catch {\r\n // If decoding fails, assume it's not encoded and use as-is\r\n decodedPassword = password;\r\n }\r\n }\r\n\r\n // Now encode them properly (encodeURIComponent handles all special characters)\r\n const encodedUsername = encodeURIComponent(decodedUsername);\r\n const encodedPassword = decodedPassword ? encodeURIComponent(decodedPassword) : undefined;\r\n\r\n // Reconstruct the connection string\r\n let normalized = `${protocol}${encodedUsername}`;\r\n if (encodedPassword) {\r\n normalized += `:${encodedPassword}`;\r\n }\r\n normalized += `@${host}`;\r\n if (database) {\r\n normalized += `/${database}`;\r\n }\r\n if (options) {\r\n normalized += `?${options}`;\r\n }\r\n\r\n return normalized;\r\n } catch (error) {\r\n // If parsing fails, log warning and return original\r\n this.logger.warn(\r\n { error: error instanceof Error ? error.message : String(error) },\r\n 'Failed to normalize MongoDB connection string, using as-is'\r\n );\r\n return connectionString;\r\n }\r\n }\r\n\r\n private async getMongoClient(connectionString: string): Promise<MongoClient> {\r\n // Normalize the connection string to ensure username/password are properly encoded\r\n const normalizedConnectionString = this.normalizeConnectionString(connectionString);\r\n \r\n if (this.clientCache.has(normalizedConnectionString)) {\r\n return this.clientCache.get(normalizedConnectionString) as MongoClient;\r\n }\r\n const client = new MongoClient(normalizedConnectionString, {\r\n maxPoolSize: 20,\r\n });\r\n await client.connect();\r\n this.clientCache.set(normalizedConnectionString, client);\r\n return client;\r\n }\r\n\r\n private async getConnectionString(storageName: string): Promise<string> {\r\n const variableName = `${CONNECTION_STRING_ENV_PREFIX}${storageName}`;\r\n const envValue = process.env[variableName];\r\n if (envValue && envValue.trim()) {\r\n return envValue.trim();\r\n }\r\n\r\n if (this.cachedConnectionString) {\r\n return this.cachedConnectionString;\r\n }\r\n\r\n const response = await this.fetchConnectionString();\r\n if (!response.connectionString || !response.connectionString.trim()) {\r\n throw new Error(`Connection string for storage \"${storageName}\" is empty.`);\r\n }\r\n this.cachedConnectionString = response.connectionString.trim();\r\n return this.cachedConnectionString;\r\n }\r\n\r\n private async fetchConnectionString(): Promise<ConnectionStringResponse> {\r\n if (typeof this.api.beamoGetStorageConnectionBasic === 'function') {\r\n const result = await this.api.beamoGetStorageConnectionBasic();\r\n if (result && typeof result === 'object' && 'body' in result) {\r\n return (result as { body: ConnectionStringResponse }).body;\r\n }\r\n return result as ConnectionStringResponse;\r\n }\r\n\r\n this.logger.warn(\r\n 'beamable-sdk does not expose beamoGetStorageConnectionBasic; falling back to manual requester call.',\r\n );\r\n const response = await this.requester.request({\r\n method: 'GET',\r\n url: '/basic/beamo/storage/connection',\r\n withAuth: true,\r\n });\r\n const body = response.body as ConnectionStringResponse | undefined;\r\n if (!body || typeof body.connectionString !== 'string') {\r\n throw new Error('Failed to retrieve Beamable storage connection string.');\r\n }\r\n return body;\r\n }\r\n\r\n private buildDatabaseName(storageName: string): string {\r\n const cid = this.sanitize(this.env.cid);\r\n const pid = this.sanitize(this.env.pid);\r\n const storage = this.sanitize(storageName);\r\n return `${cid}${pid}_${storage}`;\r\n }\r\n\r\n private sanitize(value: string): string {\r\n return value.replace(/[^A-Za-z0-9_]/g, '_');\r\n }\r\n\r\n private normalizeStorageName(storageName: string): string {\r\n const normalized = storageName.trim();\r\n if (!normalized) {\r\n throw new Error('Storage name cannot be empty.');\r\n }\r\n return normalized;\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n for (const client of this.clientCache.values()) {\r\n await client.close();\r\n }\r\n this.clientCache.clear();\r\n this.databaseCache.clear();\r\n this.cachedConnectionString = undefined;\r\n }\r\n}\r\n\r\n"]}
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Logger } from 'pino';\r\nimport { MongoClient, type Db, type Collection, type Document } from 'mongodb';\r\nimport type { BoundBeamApi } from './services.js';\r\nimport type { EnvironmentConfig } from './types.js';\r\nimport type { HttpRequester } from 'beamable-sdk';\r\n\r\nexport interface StorageConnectionOptions {\r\n useCache?: boolean;\r\n}\r\n\r\nexport interface StorageCollectionOptions extends StorageConnectionOptions {\r\n collectionName?: string;\r\n}\r\n\r\nexport interface StorageMetadata {\r\n storageName: string;\r\n}\r\n\r\nconst STORAGE_OBJECT_METADATA = new Map<Function, StorageMetadata>();\r\n\r\nexport function StorageObject(storageName: string): ClassDecorator {\r\n if (!storageName || !storageName.trim()) {\r\n throw new Error('@StorageObject requires a non-empty storage name.');\r\n }\r\n return (target) => {\r\n STORAGE_OBJECT_METADATA.set(target, { storageName: storageName.trim() });\r\n };\r\n}\r\n\r\nexport function getStorageMetadata(target: Function): StorageMetadata | undefined {\r\n return STORAGE_OBJECT_METADATA.get(target);\r\n}\r\n\r\nexport function listRegisteredStorageObjects(): StorageMetadata[] {\r\n return Array.from(STORAGE_OBJECT_METADATA.values());\r\n}\r\n\r\ninterface StorageServiceDependencies {\r\n requester: HttpRequester;\r\n api: BoundBeamApi;\r\n env: EnvironmentConfig;\r\n logger: Logger;\r\n}\r\n\r\ninterface ConnectionStringResponse {\r\n connectionString: string;\r\n}\r\n\r\nconst CONNECTION_STRING_ENV_PREFIX = 'STORAGE_CONNSTR_';\r\n\r\nexport class StorageService {\r\n private readonly requester: HttpRequester;\r\n private readonly api: BoundBeamApi;\r\n private readonly env: EnvironmentConfig;\r\n private readonly logger: Logger;\r\n private readonly databaseCache = new Map<string, Db>();\r\n private readonly clientCache = new Map<string, MongoClient>();\r\n private cachedConnectionString?: string;\r\n\r\n constructor(dependencies: StorageServiceDependencies) {\r\n this.requester = dependencies.requester;\r\n this.api = dependencies.api;\r\n this.env = dependencies.env;\r\n this.logger = dependencies.logger.child({ component: 'StorageService' });\r\n }\r\n\r\n async getDatabase(storageName: string, options: StorageConnectionOptions = {}): Promise<Db> {\r\n const normalized = this.normalizeStorageName(storageName);\r\n if (!options.useCache) {\r\n this.databaseCache.delete(normalized);\r\n }\r\n const cached = this.databaseCache.get(normalized);\r\n if (cached) {\r\n return cached;\r\n }\r\n\r\n const connectionString = await this.getConnectionString(normalized);\r\n const client = await this.getMongoClient(connectionString);\r\n const databaseName = this.buildDatabaseName(normalized);\r\n const database = client.db(databaseName);\r\n this.databaseCache.set(normalized, database);\r\n return database;\r\n }\r\n\r\n async getDatabaseFor<T>(storageCtor: new () => T, options: StorageConnectionOptions = {}): Promise<Db> {\r\n const metadata = getStorageMetadata(storageCtor);\r\n if (!metadata) {\r\n throw new Error(\r\n `Storage metadata for ${storageCtor.name} not found. Did you decorate the class with @StorageObject('Name')?`,\r\n );\r\n }\r\n return this.getDatabase(metadata.storageName, options);\r\n }\r\n\r\n async getCollection<TDocument extends Document>(\r\n storageName: string,\r\n options: StorageCollectionOptions = {},\r\n ): Promise<Collection<TDocument>> {\r\n const database = await this.getDatabase(storageName, options);\r\n const collectionName = options.collectionName?.trim();\r\n if (!collectionName) {\r\n throw new Error('Collection name must be provided when using getCollection with raw storage name.');\r\n }\r\n return database.collection<TDocument>(collectionName);\r\n }\r\n\r\n async getCollectionFor<TStorage, TDocument extends Document>(\r\n storageCtor: new () => TStorage,\r\n collectionCtor: new () => TDocument,\r\n options: StorageCollectionOptions = {},\r\n ): Promise<Collection<TDocument>> {\r\n const metadata = getStorageMetadata(storageCtor);\r\n if (!metadata) {\r\n throw new Error(\r\n `Storage metadata for ${storageCtor.name} not found. Did you decorate the class with @StorageObject('Name')?`,\r\n );\r\n }\r\n const collectionName = options.collectionName?.trim() ?? collectionCtor.name;\r\n const database = await this.getDatabase(metadata.storageName, options);\r\n return database.collection<TDocument>(collectionName);\r\n }\r\n\r\n /**\r\n * Normalizes a MongoDB connection string by ensuring username and password are properly URL-encoded.\r\n * This prevents \"Password contains unescaped characters\" errors when passwords contain special characters.\r\n * \r\n * Handles both mongodb:// and mongodb+srv:// formats.\r\n * Safely handles already-encoded credentials by decoding first, then re-encoding.\r\n */\r\n private normalizeConnectionString(connectionString: string): string {\r\n try {\r\n // Match MongoDB connection string format: mongodb:// or mongodb+srv://\r\n // Format: mongodb[+srv]://[username:password@]host[:port][/database][?options]\r\n const mongoUriRegex = /^(mongodb(?:\\+srv)?:\\/\\/)(?:([^:@]+)(?::([^@]+))?@)?([^\\/?]+)(?:\\/([^?]*))?(?:\\?(.*))?$/;\r\n const match = connectionString.match(mongoUriRegex);\r\n \r\n if (!match) {\r\n // If it doesn't match the expected format, return as-is (might be a different format)\r\n this.logger.warn('Connection string does not match expected MongoDB URI format, using as-is');\r\n return connectionString;\r\n }\r\n\r\n const [, protocol, username, password, host, database, options] = match;\r\n \r\n // If no username/password, return as-is\r\n if (!username) {\r\n return connectionString;\r\n }\r\n\r\n // Decode username and password first (in case they're already encoded)\r\n // Then re-encode them properly to ensure special characters are handled\r\n let decodedUsername: string;\r\n let decodedPassword: string | undefined;\r\n \r\n try {\r\n decodedUsername = decodeURIComponent(username);\r\n } catch {\r\n // If decoding fails, assume it's not encoded and use as-is\r\n decodedUsername = username;\r\n }\r\n \r\n if (password) {\r\n try {\r\n decodedPassword = decodeURIComponent(password);\r\n } catch {\r\n // If decoding fails, assume it's not encoded and use as-is\r\n decodedPassword = password;\r\n }\r\n }\r\n\r\n // Now encode them properly (encodeURIComponent handles all special characters)\r\n const encodedUsername = encodeURIComponent(decodedUsername);\r\n const encodedPassword = decodedPassword ? encodeURIComponent(decodedPassword) : undefined;\r\n\r\n // Reconstruct the connection string\r\n let normalized = `${protocol}${encodedUsername}`;\r\n if (encodedPassword) {\r\n normalized += `:${encodedPassword}`;\r\n }\r\n normalized += `@${host}`;\r\n if (database) {\r\n normalized += `/${database}`;\r\n }\r\n if (options) {\r\n normalized += `?${options}`;\r\n }\r\n\r\n return normalized;\r\n } catch (error) {\r\n // If parsing fails, log warning and return original\r\n this.logger.warn(\r\n { error: error instanceof Error ? error.message : String(error) },\r\n 'Failed to normalize MongoDB connection string, using as-is'\r\n );\r\n return connectionString;\r\n }\r\n }\r\n\r\n private async getMongoClient(connectionString: string): Promise<MongoClient> {\r\n // Normalize the connection string to ensure username/password are properly encoded\r\n const normalizedConnectionString = this.normalizeConnectionString(connectionString);\r\n \r\n // Log the normalization for debugging (only log if different to avoid spam)\r\n if (normalizedConnectionString !== connectionString) {\r\n this.logger.debug('MongoDB connection string was normalized (password encoding applied)');\r\n }\r\n \r\n if (this.clientCache.has(normalizedConnectionString)) {\r\n return this.clientCache.get(normalizedConnectionString) as MongoClient;\r\n }\r\n \r\n try {\r\n const client = new MongoClient(normalizedConnectionString, {\r\n maxPoolSize: 20,\r\n });\r\n await client.connect();\r\n this.clientCache.set(normalizedConnectionString, client);\r\n return client;\r\n } catch (error) {\r\n // If connection fails, log the error with connection string details (without password)\r\n const sanitizedConnectionString = normalizedConnectionString.replace(/:([^:@]+)@/, ':****@');\r\n this.logger.error(\r\n { \r\n error: error instanceof Error ? error.message : String(error),\r\n connectionString: sanitizedConnectionString \r\n },\r\n 'Failed to connect to MongoDB'\r\n );\r\n throw error;\r\n }\r\n }\r\n\r\n private async getConnectionString(storageName: string): Promise<string> {\r\n const variableName = `${CONNECTION_STRING_ENV_PREFIX}${storageName}`;\r\n const envValue = process.env[variableName];\r\n if (envValue && envValue.trim()) {\r\n return envValue.trim();\r\n }\r\n\r\n if (this.cachedConnectionString) {\r\n return this.cachedConnectionString;\r\n }\r\n\r\n const response = await this.fetchConnectionString();\r\n if (!response.connectionString || !response.connectionString.trim()) {\r\n throw new Error(`Connection string for storage \"${storageName}\" is empty.`);\r\n }\r\n this.cachedConnectionString = response.connectionString.trim();\r\n return this.cachedConnectionString;\r\n }\r\n\r\n private async fetchConnectionString(): Promise<ConnectionStringResponse> {\r\n if (typeof this.api.beamoGetStorageConnectionBasic === 'function') {\r\n const result = await this.api.beamoGetStorageConnectionBasic();\r\n if (result && typeof result === 'object' && 'body' in result) {\r\n return (result as { body: ConnectionStringResponse }).body;\r\n }\r\n return result as ConnectionStringResponse;\r\n }\r\n\r\n this.logger.warn(\r\n 'beamable-sdk does not expose beamoGetStorageConnectionBasic; falling back to manual requester call.',\r\n );\r\n const response = await this.requester.request({\r\n method: 'GET',\r\n url: '/basic/beamo/storage/connection',\r\n withAuth: true,\r\n });\r\n const body = response.body as ConnectionStringResponse | undefined;\r\n if (!body || typeof body.connectionString !== 'string') {\r\n throw new Error('Failed to retrieve Beamable storage connection string.');\r\n }\r\n return body;\r\n }\r\n\r\n private buildDatabaseName(storageName: string): string {\r\n const cid = this.sanitize(this.env.cid);\r\n const pid = this.sanitize(this.env.pid);\r\n const storage = this.sanitize(storageName);\r\n return `${cid}${pid}_${storage}`;\r\n }\r\n\r\n private sanitize(value: string): string {\r\n return value.replace(/[^A-Za-z0-9_]/g, '_');\r\n }\r\n\r\n private normalizeStorageName(storageName: string): string {\r\n const normalized = storageName.trim();\r\n if (!normalized) {\r\n throw new Error('Storage name cannot be empty.');\r\n }\r\n return normalized;\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n for (const client of this.clientCache.values()) {\r\n await client.close();\r\n }\r\n this.clientCache.clear();\r\n this.databaseCache.clear();\r\n this.cachedConnectionString = undefined;\r\n }\r\n}\r\n\r\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omen.foundation/node-microservice-runtime",
3
- "version": "0.1.101",
3
+ "version": "0.1.103",
4
4
  "description": "Beamable microservice runtime for Node.js/TypeScript services.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -753,6 +753,10 @@ async function discoverStorageObjects(srcDir) {
753
753
  const srcPath = path.resolve(srcDir || 'src');
754
754
  const files = await getAllTypeScriptFiles(srcPath);
755
755
 
756
+ if (files.length === 0) {
757
+ console.warn(`[beamo-node] Warning: No TypeScript files found in ${srcPath}`);
758
+ }
759
+
756
760
  for (const file of files) {
757
761
  const content = await fs.readFile(file, 'utf-8');
758
762
  // Match @StorageObject('StorageName') pattern
@@ -767,12 +771,17 @@ async function discoverStorageObjects(srcDir) {
767
771
  checksum: null,
768
772
  archived: false,
769
773
  });
774
+ console.log(`[beamo-node] Discovered storage object: ${storageName}`);
770
775
  }
771
776
  }
772
777
  }
778
+
779
+ if (storageObjects.length === 0) {
780
+ console.log(`[beamo-node] No @StorageObject decorators found in ${srcPath}`);
781
+ }
773
782
  } catch (error) {
783
+ console.warn(`[beamo-node] Error discovering storage objects: ${error instanceof Error ? error.message : String(error)}`);
774
784
  // If we can't discover storage, that's okay - we'll just use existing ones
775
- // Debug logging only
776
785
  }
777
786
  return storageObjects;
778
787
  }
@@ -1013,6 +1022,16 @@ async function updateManifest({
1013
1022
  manifest: mappedServices,
1014
1023
  storageReferences,
1015
1024
  };
1025
+
1026
+ // Log storage references being sent to backend
1027
+ if (storageReferences.length > 0) {
1028
+ console.log(`[beamo-node] Publishing ${storageReferences.length} storage reference(s) in manifest:`);
1029
+ storageReferences.forEach(s => {
1030
+ console.log(`[beamo-node] - ${s.id} (type: ${s.storageType || 'mongov1'}, enabled: ${s.enabled !== false})`);
1031
+ });
1032
+ } else {
1033
+ console.warn(`[beamo-node] Warning: No storage references in manifest. Database will not be created automatically.`);
1034
+ }
1016
1035
 
1017
1036
  const publishUrl = new URL('/basic/beamo/manifest', apiHost);
1018
1037
  const publishHeaders = {
@@ -1447,6 +1466,11 @@ async function main() {
1447
1466
  id: s.id,
1448
1467
  storageType: 'mongov1', // MongoDB storage type (matches ServiceStorageReference in backend)
1449
1468
  }));
1469
+
1470
+ if (discoveredStorage.length > 0) {
1471
+ console.log(`[beamo-node] Discovered ${discoveredStorage.length} storage object(s): ${discoveredStorage.map(s => s.id).join(', ')}`);
1472
+ }
1473
+
1450
1474
  progress.complete('Storage and components discovered');
1451
1475
 
1452
1476
  // Step 8: Publish manifest