@smythos/sre 1.5.13 → 1.5.16

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.
@@ -15,7 +15,9 @@ export declare class S3Storage extends StorageConnector {
15
15
  private client;
16
16
  private bucket;
17
17
  private isInitialized;
18
+ private initializationPromise;
18
19
  constructor(_settings: S3Config);
20
+ private ensureInitialized;
19
21
  private initialize;
20
22
  /**
21
23
  * Reads an object from the S3 bucket.
@@ -16,6 +16,13 @@ export declare class JSONFileVault extends VaultConnector {
16
16
  constructor(_settings: JSONFileVaultConfig);
17
17
  private findVaultFile;
18
18
  private getMasterKeyInteractive;
19
+ /**
20
+ * Resolves environment variable references in vault values.
21
+ * Supports syntax: $env(VARIABLE_NAME)
22
+ * @param value The value to process
23
+ * @returns The value with environment variables resolved
24
+ */
25
+ private resolveEnvironmentVariables;
19
26
  protected get(acRequest: AccessRequest, keyId: string): Promise<any>;
20
27
  protected exists(acRequest: AccessRequest, keyId: string): Promise<boolean>;
21
28
  protected listKeys(acRequest: AccessRequest): Promise<string[]>;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Configuration for lazy client error handling
3
+ */
4
+ export interface LazyClientConfig {
5
+ /** Custom package name if different from import path */
6
+ packageName?: string;
7
+ /** Custom display name for the package */
8
+ displayName?: string;
9
+ /** Additional installation notes */
10
+ installNotes?: string;
11
+ /** Documentation URL */
12
+ docsUrl?: string;
13
+ /** Disable auto-installation messages */
14
+ silent?: boolean;
15
+ }
16
+ /**
17
+ * Generic lazy loading utility for client libraries
18
+ * Preserves strong typing and requires minimal code changes
19
+ */
20
+ export declare class LazyClient<T = any> {
21
+ private clientFactory;
22
+ private config;
23
+ private _client;
24
+ private _clientPromise;
25
+ constructor(clientFactory: () => Promise<T>, config?: LazyClientConfig);
26
+ /**
27
+ * Creates a proxy that records method calls and executes them when needed
28
+ */
29
+ private createProxy;
30
+ /**
31
+ * Executes the recorded method chain on the actual client
32
+ */
33
+ private executeMethodChain;
34
+ /**
35
+ * Ensures the client is loaded and returns it
36
+ */
37
+ private ensureClient;
38
+ /**
39
+ * Returns the proxy that behaves like the actual client
40
+ */
41
+ get client(): T;
42
+ /**
43
+ * Get the actual client instance (async)
44
+ */
45
+ getClient(): Promise<T>;
46
+ /**
47
+ * Check if the client is already loaded
48
+ */
49
+ get isLoaded(): boolean;
50
+ }
51
+ /**
52
+ * Helper function to create a lazy client with dynamic import
53
+ */
54
+ export declare function createLazyClient<T>(importFn: () => Promise<any>, clientFactory: (module: any) => T, config?: LazyClientConfig): LazyClient<T>;
55
+ /**
56
+ * Specific helper for common client patterns
57
+ */
58
+ export declare function createLazyClientFromConstructor<T>(packageName: string, constructorName: string, ...args: any[]): LazyClient<T>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smythos/sre",
3
- "version": "1.5.13",
3
+ "version": "1.5.16",
4
4
  "description": "Smyth Runtime Environment",
5
5
  "author": "Alaa-eddine KADDOURI",
6
6
  "license": "MIT",
@@ -1,8 +1,9 @@
1
1
  import { GetBucketLifecycleConfigurationCommandOutput, PutBucketLifecycleConfigurationCommand } from '@aws-sdk/client-s3';
2
2
 
3
3
  import { GetBucketLifecycleConfigurationCommand } from '@aws-sdk/client-s3';
4
-
5
4
  import { S3Client } from '@aws-sdk/client-s3';
5
+ import { Logger } from '@sre/helpers/Log.helper';
6
+ const console = Logger('S3Cache');
6
7
 
7
8
  export function generateLifecycleRules() {
8
9
  const rules = [];
@@ -58,7 +59,6 @@ export function generateLifecycleRules() {
58
59
  return rules;
59
60
  }
60
61
 
61
-
62
62
  export function generateExpiryMetadata(expiryDays) {
63
63
  let metadataValue;
64
64
 
@@ -90,6 +90,17 @@ export function ttlToExpiryDays(ttl: number) {
90
90
  }
91
91
 
92
92
  export async function checkAndInstallLifecycleRules(bucketName: string, s3Client: S3Client) {
93
+ // Validate inputs
94
+ if (!bucketName || bucketName.trim() === '') {
95
+ throw new Error('Bucket name is required and cannot be empty');
96
+ }
97
+
98
+ if (!s3Client) {
99
+ throw new Error('S3Client is required');
100
+ }
101
+
102
+ console.log(`Checking lifecycle rules for bucket: ${bucketName}`);
103
+
93
104
  try {
94
105
  // Check existing lifecycle configuration
95
106
  const getLifecycleCommand = new GetBucketLifecycleConfigurationCommand({ Bucket: bucketName });
@@ -105,6 +116,7 @@ export async function checkAndInstallLifecycleRules(bucketName: string, s3Client
105
116
  const putLifecycleCommand = new PutBucketLifecycleConfigurationCommand(params);
106
117
  // Put the new lifecycle configuration
107
118
  await s3Client.send(putLifecycleCommand);
119
+ console.log(`Added ${nonExistingNewRules.length} new lifecycle rules to bucket: ${bucketName}`);
108
120
  } else {
109
121
  console.log('Lifecycle configuration already exists');
110
122
  }
@@ -124,6 +136,12 @@ export async function checkAndInstallLifecycleRules(bucketName: string, s3Client
124
136
  console.log('Lifecycle configuration created successfully.');
125
137
  } else {
126
138
  console.error('Error checking lifecycle configuration:', error);
139
+ console.error('Bucket name provided:', bucketName);
140
+ console.error('Error details:', {
141
+ name: error.name,
142
+ message: error.message,
143
+ code: error.code,
144
+ });
127
145
  }
128
146
  }
129
147
  }
@@ -58,10 +58,18 @@ export class S3Storage extends StorageConnector {
58
58
  private client: S3Client;
59
59
  private bucket: string;
60
60
  private isInitialized: boolean = false;
61
+ private initializationPromise: Promise<void> | null = null;
61
62
 
62
63
  constructor(protected _settings: S3Config) {
63
64
  super(_settings);
64
65
  //if (!SmythRuntime.Instance) throw new Error('SRE not initialized');
66
+
67
+ // Validate required configuration
68
+ if (!_settings.bucket || _settings.bucket.trim() === '') {
69
+ console.warn('S3 bucket name is required and cannot be empty, connector not initialized');
70
+ return;
71
+ }
72
+
65
73
  this.bucket = _settings.bucket;
66
74
  const clientConfig: any = {};
67
75
  if (_settings.region) clientConfig.region = _settings.region;
@@ -73,12 +81,41 @@ export class S3Storage extends StorageConnector {
73
81
  }
74
82
 
75
83
  this.client = new S3Client(clientConfig);
76
- this.initialize();
84
+ // Don't call initialize() synchronously in constructor
85
+ // It will be called when needed by methods that require initialization
77
86
  }
78
87
 
79
- private async initialize() {
80
- await checkAndInstallLifecycleRules(this.bucket, this.client);
81
- this.isInitialized = true;
88
+ private async ensureInitialized(): Promise<void> {
89
+ if (this.isInitialized) {
90
+ return;
91
+ }
92
+
93
+ if (this.initializationPromise) {
94
+ return this.initializationPromise;
95
+ }
96
+
97
+ this.initializationPromise = this.initialize();
98
+ return this.initializationPromise;
99
+ }
100
+
101
+ private async initialize(): Promise<void> {
102
+ if (!this.client) {
103
+ console.warn('S3 client not initialized');
104
+ return;
105
+ }
106
+ if (this.isInitialized) {
107
+ return;
108
+ }
109
+
110
+ try {
111
+ await checkAndInstallLifecycleRules(this.bucket, this.client);
112
+ this.isInitialized = true;
113
+ } catch (error) {
114
+ console.error('Failed to initialize S3Storage:', error);
115
+ // Reset the initialization promise so it can be retried
116
+ this.initializationPromise = null;
117
+ throw error;
118
+ }
82
119
  }
83
120
 
84
121
  /**
@@ -90,6 +127,8 @@ export class S3Storage extends StorageConnector {
90
127
 
91
128
  @SecureConnector.AccessControl
92
129
  public async read(acRequest: AccessRequest, resourceId: string) {
130
+ await this.ensureInitialized();
131
+
93
132
  // const accessTicket = await this.getAccessTicket(resourceId, acRequest);
94
133
  // if (accessTicket.access !== TAccessResult.Granted) throw new Error('Access Denied');
95
134
  const params = {
@@ -133,6 +172,8 @@ export class S3Storage extends StorageConnector {
133
172
 
134
173
  @SecureConnector.AccessControl
135
174
  async getMetadata(acRequest: AccessRequest, resourceId: string): Promise<StorageMetadata | undefined> {
175
+ await this.ensureInitialized();
176
+
136
177
  // const accessTicket = await this.getAccessTicket(resourceId, acRequest);
137
178
  // if (accessTicket.access !== TAccessResult.Granted) throw new Error('Access Denied');
138
179
 
@@ -147,6 +188,8 @@ export class S3Storage extends StorageConnector {
147
188
 
148
189
  @SecureConnector.AccessControl
149
190
  async setMetadata(acRequest: AccessRequest, resourceId: string, metadata: StorageMetadata) {
191
+ await this.ensureInitialized();
192
+
150
193
  // const accessTicket = await this.getAccessTicket(resourceId, acRequest);
151
194
  // if (accessTicket.access !== TAccessResult.Granted) throw new Error('Access Denied');
152
195
 
@@ -171,9 +214,10 @@ export class S3Storage extends StorageConnector {
171
214
  */
172
215
  @SecureConnector.AccessControl
173
216
  async write(acRequest: AccessRequest, resourceId: string, value: StorageData, acl?: IACL, metadata?: StorageMetadata): Promise<void> {
217
+ await this.ensureInitialized();
218
+
174
219
  // const accessTicket = await this.getAccessTicket(resourceId, acRequest);
175
220
  // if (accessTicket.access !== TAccessResult.Granted) throw new Error('Access Denied');
176
- if (!this.isInitialized) await this.initialize();
177
221
  const accessCandidate = acRequest.candidate;
178
222
 
179
223
  let amzACL = ACL.from(acl).addAccess(accessCandidate.role, accessCandidate.id, TAccessLevel.Owner).ACL;
@@ -207,6 +251,8 @@ export class S3Storage extends StorageConnector {
207
251
  */
208
252
  @SecureConnector.AccessControl
209
253
  async delete(acRequest: AccessRequest, resourceId: string): Promise<void> {
254
+ await this.ensureInitialized();
255
+
210
256
  // const accessTicket = await this.getAccessTicket(resourceId, acRequest);
211
257
  // if (accessTicket.access !== TAccessResult.Granted) throw new Error('Access Denied');
212
258
 
@@ -225,6 +271,8 @@ export class S3Storage extends StorageConnector {
225
271
 
226
272
  @SecureConnector.AccessControl
227
273
  async exists(acRequest: AccessRequest, resourceId: string): Promise<boolean> {
274
+ await this.ensureInitialized();
275
+
228
276
  // const accessTicket = await this.getAccessTicket(resourceId, acRequest);
229
277
  // if (accessTicket.access !== TAccessResult.Granted) throw new Error('Access Denied');
230
278
  const command = new HeadObjectCommand({
@@ -250,6 +298,8 @@ export class S3Storage extends StorageConnector {
250
298
  //if the resource exists we read it's ACL and return it
251
299
  //if the resource does not exist we return an write access ACL for the candidate
252
300
  public async getResourceACL(resourceId: string, candidate: IAccessCandidate) {
301
+ await this.ensureInitialized();
302
+
253
303
  const s3Metadata = await this.getS3Metadata(resourceId);
254
304
  const exists = s3Metadata !== undefined; //undefined metadata means the resource does not exist
255
305
  //let acl: ACL = ACL.from(s3Metadata?.['x-amz-meta-acl'] as IACL);
@@ -263,6 +313,8 @@ export class S3Storage extends StorageConnector {
263
313
 
264
314
  @SecureConnector.AccessControl
265
315
  async getACL(acRequest: AccessRequest, resourceId: string): Promise<ACL | undefined> {
316
+ await this.ensureInitialized();
317
+
266
318
  // const accessTicket = await this.getAccessTicket(resourceId, acRequest);
267
319
  // if (accessTicket.access !== TAccessResult.Granted) throw new Error('Access Denied');
268
320
 
@@ -277,6 +329,8 @@ export class S3Storage extends StorageConnector {
277
329
 
278
330
  @SecureConnector.AccessControl
279
331
  async setACL(acRequest: AccessRequest, resourceId: string, acl: IACL) {
332
+ await this.ensureInitialized();
333
+
280
334
  // const accessTicket = await this.getAccessTicket(resourceId, acRequest);
281
335
  // if (accessTicket.access !== TAccessResult.Granted) throw new Error('Access Denied');
282
336
 
@@ -294,6 +348,8 @@ export class S3Storage extends StorageConnector {
294
348
 
295
349
  @SecureConnector.AccessControl
296
350
  async expire(acRequest: AccessRequest, resourceId: string, ttl: number) {
351
+ await this.ensureInitialized();
352
+
297
353
  const expiryMetadata = generateExpiryMetadata(ttlToExpiryDays(ttl)); // seconds to days
298
354
  const s3PutObjectTaggingCommand = new PutObjectTaggingCommand({
299
355
  Bucket: this.bucket,
@@ -23,7 +23,7 @@ export type JSONFileVaultConfig = {
23
23
  shared?: string;
24
24
  };
25
25
 
26
- export class JSONFileVault extends VaultConnector {
26
+ export class JSONFileVault extends VaultConnector {
27
27
  public name: string = 'JSONFileVault';
28
28
  private vaultData: any;
29
29
  private index: any;
@@ -119,12 +119,39 @@ export class JSONFileVault extends VaultConnector {
119
119
  return masterKey;
120
120
  }
121
121
 
122
+ /**
123
+ * Resolves environment variable references in vault values.
124
+ * Supports syntax: $env(VARIABLE_NAME)
125
+ * @param value The value to process
126
+ * @returns The value with environment variables resolved
127
+ */
128
+ private resolveEnvironmentVariables(value: any): any {
129
+ if (typeof value !== 'string') {
130
+ return value;
131
+ }
132
+
133
+ // Match $env(VARIABLE_NAME) pattern
134
+ const envVarPattern = /\$env\(([^)]+)\)/g;
135
+
136
+ return value.replace(envVarPattern, (match, envVarName) => {
137
+ const envValue = process.env[envVarName];
138
+ if (envValue === undefined) {
139
+ console.warn(`Environment variable ${envVarName} not found, keeping original value: ${match}`);
140
+ return match;
141
+ }
142
+ return envValue;
143
+ });
144
+ }
145
+
122
146
  @SecureConnector.AccessControl
123
147
  protected async get(acRequest: AccessRequest, keyId: string) {
124
148
  const accountConnector = ConnectorService.getAccountConnector();
125
149
  const teamId = await accountConnector.getCandidateTeam(acRequest.candidate);
126
150
 
127
- return this.vaultData?.[teamId]?.[keyId] || this.vaultData?.[this.shared]?.[keyId];
151
+ const rawValue = this.vaultData?.[teamId]?.[keyId] || this.vaultData?.[this.shared]?.[keyId];
152
+
153
+ // Resolve environment variables if the value contains $env() references
154
+ return this.resolveEnvironmentVariables(rawValue);
128
155
  }
129
156
 
130
157
  @SecureConnector.AccessControl
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Configuration for lazy client error handling
3
+ */
4
+ export interface LazyClientConfig {
5
+ /** Custom package name if different from import path */
6
+ packageName?: string;
7
+ /** Custom display name for the package */
8
+ displayName?: string;
9
+ /** Additional installation notes */
10
+ installNotes?: string;
11
+ /** Documentation URL */
12
+ docsUrl?: string;
13
+ /** Disable auto-installation messages */
14
+ silent?: boolean;
15
+ }
16
+
17
+ /**
18
+ * Detects the package manager being used
19
+ */
20
+ function detectPackageManager(): string {
21
+ // Check npm_config_user_agent environment variable
22
+ const userAgent = process.env.npm_config_user_agent;
23
+ if (userAgent?.includes('pnpm')) return 'pnpm';
24
+ if (userAgent?.includes('yarn')) return 'yarn';
25
+
26
+ // Fallback: check for lock files in cwd (if accessible)
27
+ try {
28
+ const fs = require('fs');
29
+ if (fs.existsSync('pnpm-lock.yaml')) return 'pnpm';
30
+ if (fs.existsSync('yarn.lock')) return 'yarn';
31
+ } catch {
32
+ // Ignore file system errors
33
+ }
34
+
35
+ return 'npm';
36
+ }
37
+
38
+ /**
39
+ * Extracts package name from import path
40
+ */
41
+ function extractPackageName(importPath: string, config: LazyClientConfig): string {
42
+ if (config.packageName) {
43
+ return config.packageName;
44
+ }
45
+
46
+ // Handle scoped packages: @scope/package or @scope/package/subpath
47
+ if (importPath.startsWith('@')) {
48
+ const parts = importPath.split('/');
49
+ return `${parts[0]}/${parts[1]}`;
50
+ }
51
+
52
+ // Handle regular packages: package or package/subpath
53
+ return importPath.split('/')[0];
54
+ }
55
+
56
+ /**
57
+ * Creates a helpful error message for missing packages
58
+ */
59
+ function createInstallationMessage(importPath: string, originalError: Error, config: LazyClientConfig): Error {
60
+ if (config.silent) {
61
+ return originalError;
62
+ }
63
+
64
+ const packageName = extractPackageName(importPath, config);
65
+ const displayName = config.displayName || packageName;
66
+ const packageManager = detectPackageManager();
67
+
68
+ let message = `\n🔌 ${displayName} is required but not installed\n\n`;
69
+
70
+ message += `Quick install:\n`;
71
+ message += ` ${packageManager} add ${packageName}\n\n`;
72
+
73
+ // Add alternative package managers
74
+ if (packageManager !== 'npm') {
75
+ message += `Or with npm:\n npm install ${packageName}\n\n`;
76
+ }
77
+ if (packageManager !== 'pnpm') {
78
+ message += `Or with pnpm:\n pnpm add ${packageName}\n\n`;
79
+ }
80
+
81
+ if (config.installNotes) {
82
+ message += `📝 Note: ${config.installNotes}\n\n`;
83
+ }
84
+
85
+ if (config.docsUrl) {
86
+ message += `📚 Documentation: ${config.docsUrl}\n\n`;
87
+ }
88
+
89
+ message += `Original error: ${originalError.message}`;
90
+
91
+ const enhancedError = new Error(message);
92
+ enhancedError.name = 'LazyClientImportError';
93
+ enhancedError.stack = originalError.stack;
94
+ return enhancedError;
95
+ }
96
+
97
+ /**
98
+ * Generic lazy loading utility for client libraries
99
+ * Preserves strong typing and requires minimal code changes
100
+ */
101
+ export class LazyClient<T = any> {
102
+ private _client: T | null = null;
103
+ private _clientPromise: Promise<T> | null = null;
104
+
105
+ constructor(private clientFactory: () => Promise<T>, private config: LazyClientConfig = {}) {}
106
+
107
+ /**
108
+ * Creates a proxy that records method calls and executes them when needed
109
+ */
110
+ private createProxy(methodPath: Array<{ method: string; args: any[] }> = []): any {
111
+ const self = this;
112
+
113
+ // Create a proxy that can be both called and have properties accessed
114
+ return new Proxy(() => {}, {
115
+ get(target, prop, receiver) {
116
+ if (prop === 'then') {
117
+ // This is being awaited - execute the method chain and return a thenable
118
+ const promise = self.executeMethodChain(methodPath);
119
+ return promise.then.bind(promise);
120
+ }
121
+
122
+ if (prop === 'catch') {
123
+ // Handle .catch() calls
124
+ const promise = self.executeMethodChain(methodPath);
125
+ return promise.catch.bind(promise);
126
+ }
127
+
128
+ if (prop === 'finally') {
129
+ // Handle .finally() calls
130
+ const promise = self.executeMethodChain(methodPath);
131
+ return promise.finally.bind(promise);
132
+ }
133
+
134
+ if (prop === Symbol.toStringTag) {
135
+ return 'LazyClient';
136
+ }
137
+
138
+ if (typeof prop !== 'string') {
139
+ return undefined;
140
+ }
141
+
142
+ // Return a function that extends the method chain
143
+ return (...args: any[]) => {
144
+ const newPath = [...methodPath, { method: prop, args }];
145
+ return self.createProxy(newPath);
146
+ };
147
+ },
148
+
149
+ apply(target, thisArg, argumentsList) {
150
+ // If called as a function, extend the method chain
151
+ return self.createProxy(methodPath);
152
+ },
153
+
154
+ has(target, prop) {
155
+ return true;
156
+ },
157
+ });
158
+ }
159
+
160
+ /**
161
+ * Executes the recorded method chain on the actual client
162
+ */
163
+ private async executeMethodChain(methodPath: Array<{ method: string; args: any[] }>): Promise<any> {
164
+ const client = await this.ensureClient();
165
+
166
+ let current: any = client;
167
+
168
+ // Execute each method call in sequence
169
+ for (const { method, args } of methodPath) {
170
+ if (current && typeof current[method] === 'function') {
171
+ current = current[method].apply(current, args);
172
+ } else if (current && current[method] !== undefined) {
173
+ current = current[method];
174
+ } else {
175
+ throw new Error(`Method or property '${method}' not found`);
176
+ }
177
+ }
178
+
179
+ return current;
180
+ }
181
+
182
+ /**
183
+ * Ensures the client is loaded and returns it
184
+ */
185
+ private async ensureClient(): Promise<T> {
186
+ if (this._client) {
187
+ return this._client;
188
+ }
189
+
190
+ if (!this._clientPromise) {
191
+ this._clientPromise = this.clientFactory().catch((error) => {
192
+ // Reset promise so next attempt will retry
193
+ this._clientPromise = null;
194
+ throw error;
195
+ });
196
+ }
197
+
198
+ this._client = await this._clientPromise;
199
+ return this._client;
200
+ }
201
+
202
+ /**
203
+ * Returns the proxy that behaves like the actual client
204
+ */
205
+ get client(): T {
206
+ return this.createProxy() as T;
207
+ }
208
+
209
+ /**
210
+ * Get the actual client instance (async)
211
+ */
212
+ async getClient(): Promise<T> {
213
+ return this.ensureClient();
214
+ }
215
+
216
+ /**
217
+ * Check if the client is already loaded
218
+ */
219
+ get isLoaded(): boolean {
220
+ return this._client !== null;
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Helper function to create a lazy client with dynamic import
226
+ */
227
+ export function createLazyClient<T>(importFn: () => Promise<any>, clientFactory: (module: any) => T, config: LazyClientConfig = {}): LazyClient<T> {
228
+ return new LazyClient<T>(async () => {
229
+ try {
230
+ const module = await importFn();
231
+ return clientFactory(module);
232
+ } catch (error) {
233
+ // Try to extract import path from the error or use the configured package name
234
+ const importPath = config.packageName || 'unknown-package';
235
+ throw createInstallationMessage(importPath, error as Error, config);
236
+ }
237
+ }, config);
238
+ }
239
+
240
+ /**
241
+ * Specific helper for common client patterns
242
+ */
243
+ // export function createLazyClientFromConstructor<T>(
244
+ // importFn: () => Promise<any>,
245
+ // constructorName: string,
246
+ // config: LazyClientConfig = {},
247
+ // ...args: any[]
248
+ // ): LazyClient<T> {
249
+ // return createLazyClient<T>(importFn, (module) => new module[constructorName](...args), config);
250
+ // }
251
+ export function createLazyClientFromConstructor<T>(packageName: string, constructorName: string, ...args: any[]): LazyClient<T> {
252
+ const importFn = () => import(packageName);
253
+ const config = {
254
+ packageName,
255
+ displayName: constructorName,
256
+ //installNotes: `Install ${packageName} with your package manager`,
257
+ //docsUrl: `https://www.npmjs.com/package/${packageName}`,
258
+ };
259
+ console.log('LazyClient', packageName, constructorName);
260
+ return createLazyClient<T>(importFn, (module) => new module[constructorName](...args), config);
261
+ }