@aluvia/sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +423 -0
  3. package/dist/cjs/api/AluviaApi.js +51 -0
  4. package/dist/cjs/api/account.js +155 -0
  5. package/dist/cjs/api/geos.js +76 -0
  6. package/dist/cjs/api/request.js +84 -0
  7. package/dist/cjs/api/types.js +2 -0
  8. package/dist/cjs/client/AluviaClient.js +325 -0
  9. package/dist/cjs/client/ConfigManager.js +303 -0
  10. package/dist/cjs/client/ProxyServer.js +182 -0
  11. package/dist/cjs/client/adapters.js +49 -0
  12. package/dist/cjs/client/logger.js +52 -0
  13. package/dist/cjs/client/rules.js +128 -0
  14. package/dist/cjs/client/types.js +3 -0
  15. package/dist/cjs/errors.js +49 -0
  16. package/dist/cjs/index.js +16 -0
  17. package/dist/cjs/package.json +1 -0
  18. package/dist/esm/api/AluviaApi.js +47 -0
  19. package/dist/esm/api/account.js +152 -0
  20. package/dist/esm/api/geos.js +73 -0
  21. package/dist/esm/api/request.js +81 -0
  22. package/dist/esm/api/types.js +1 -0
  23. package/dist/esm/client/AluviaClient.js +321 -0
  24. package/dist/esm/client/ConfigManager.js +299 -0
  25. package/dist/esm/client/ProxyServer.js +178 -0
  26. package/dist/esm/client/adapters.js +39 -0
  27. package/dist/esm/client/logger.js +48 -0
  28. package/dist/esm/client/rules.js +124 -0
  29. package/dist/esm/client/types.js +2 -0
  30. package/dist/esm/errors.js +42 -0
  31. package/dist/esm/index.js +7 -0
  32. package/dist/types/api/AluviaApi.d.ts +29 -0
  33. package/dist/types/api/account.d.ts +41 -0
  34. package/dist/types/api/geos.d.ts +5 -0
  35. package/dist/types/api/request.d.ts +20 -0
  36. package/dist/types/api/types.d.ts +30 -0
  37. package/dist/types/client/AluviaClient.d.ts +50 -0
  38. package/dist/types/client/ConfigManager.d.ts +100 -0
  39. package/dist/types/client/ProxyServer.d.ts +47 -0
  40. package/dist/types/client/adapters.d.ts +26 -0
  41. package/dist/types/client/logger.d.ts +33 -0
  42. package/dist/types/client/rules.d.ts +34 -0
  43. package/dist/types/client/types.d.ts +194 -0
  44. package/dist/types/errors.d.ts +25 -0
  45. package/dist/types/index.d.ts +5 -0
  46. package/package.json +65 -0
@@ -0,0 +1,303 @@
1
+ "use strict";
2
+ // ConfigManager - Control plane for connection configuration
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.ConfigManager = void 0;
5
+ const logger_js_1 = require("./logger.js");
6
+ const errors_js_1 = require("../errors.js");
7
+ const request_js_1 = require("../api/request.js");
8
+ function isRecord(value) {
9
+ return typeof value === 'object' && value !== null;
10
+ }
11
+ function toAccountConnectionApiResponse(value) {
12
+ if (!isRecord(value))
13
+ return {};
14
+ const data = value['data'];
15
+ if (!isRecord(data))
16
+ return {};
17
+ return { data: data };
18
+ }
19
+ function toValidationErrors(value) {
20
+ if (!isRecord(value))
21
+ return null;
22
+ const apiError = value['error'];
23
+ if (!isRecord(apiError))
24
+ return null;
25
+ if (apiError['code'] !== 'validation_error')
26
+ return null;
27
+ const details = apiError['details'];
28
+ const errors = [];
29
+ if (isRecord(details)) {
30
+ for (const fieldMessages of Object.values(details)) {
31
+ if (Array.isArray(fieldMessages)) {
32
+ for (const message of fieldMessages) {
33
+ if (typeof message === 'string') {
34
+ errors.push(message);
35
+ }
36
+ }
37
+ }
38
+ }
39
+ }
40
+ return errors.length ? errors : null;
41
+ }
42
+ /**
43
+ * ConfigManager handles fetching and maintaining connection configuration from the Aluvia API.
44
+ *
45
+ * Responsibilities:
46
+ * - Initial fetch of account connection config
47
+ * - Polling for updates using ETag
48
+ * - Providing current config to ProxyServer
49
+ */
50
+ class ConfigManager {
51
+ constructor(options) {
52
+ this.config = null;
53
+ this.timer = null;
54
+ this.pollInFlight = false;
55
+ this.options = options;
56
+ this.logger = new logger_js_1.Logger(options.logLevel);
57
+ this.strict = options.strict ?? true;
58
+ }
59
+ /**
60
+ * Fetch initial configuration from the account connections API.
61
+ * Must be called before starting the proxy.
62
+ *
63
+ * @throws InvalidApiKeyError if apiKey is invalid (401/403)
64
+ * @throws ApiError for other API errors
65
+ */
66
+ async init() {
67
+ if (this.options.connectionId) {
68
+ this.accountConnectionId = this.options.connectionId ?? null;
69
+ this.logger.info(`Using account connection API (connection id: ${this.accountConnectionId})`);
70
+ let result;
71
+ try {
72
+ result = await (0, request_js_1.requestCore)({
73
+ apiBaseUrl: this.options.apiBaseUrl,
74
+ apiKey: this.options.apiKey,
75
+ method: 'GET',
76
+ path: `/account/connections/${this.accountConnectionId}`,
77
+ });
78
+ }
79
+ catch (err) {
80
+ if (err instanceof errors_js_1.ApiError)
81
+ throw err;
82
+ const msg = err instanceof Error ? err.message : String(err);
83
+ throw new errors_js_1.ApiError(`Failed to fetch account connection config: ${msg}`);
84
+ }
85
+ if (result.status === 401 || result.status === 403) {
86
+ throw new errors_js_1.InvalidApiKeyError(`Authentication failed with status ${result.status}`);
87
+ }
88
+ if (result.status === 200 && result.body) {
89
+ this.config = this.buildConfigFromAny(result.body, result.etag);
90
+ this.logger.info('Configuration loaded successfully');
91
+ this.logger.debug('Config summary:', this.redactConfig(this.config));
92
+ return;
93
+ }
94
+ throw new errors_js_1.ApiError(`Failed to fetch account connection config: HTTP ${result.status}`, result.status);
95
+ }
96
+ // No connectionId: create an account connection (preferred)
97
+ this.logger.info('No connectionId provided; creating account connection...');
98
+ try {
99
+ const created = await (0, request_js_1.requestCore)({
100
+ apiBaseUrl: this.options.apiBaseUrl,
101
+ apiKey: this.options.apiKey,
102
+ method: 'POST',
103
+ path: '/account/connections',
104
+ body: {},
105
+ });
106
+ if (created.status === 401 || created.status === 403) {
107
+ throw new errors_js_1.InvalidApiKeyError(`Authentication failed with status ${created.status}`);
108
+ }
109
+ if ((created.status === 200 || created.status === 201) && created.body) {
110
+ const createdResponse = toAccountConnectionApiResponse(created.body);
111
+ this.accountConnectionId = Number(createdResponse.data?.connection_id);
112
+ if (this.accountConnectionId != null) {
113
+ this.logger.info(`Account connection created (connection id: ${this.accountConnectionId})`);
114
+ }
115
+ else {
116
+ this.logger.info('Account connection created (connection id unavailable in response)');
117
+ }
118
+ this.config = this.buildConfigFromAny(created.body, created.etag);
119
+ this.logger.info('Configuration loaded successfully');
120
+ this.logger.debug('Config summary:', this.redactConfig(this.config));
121
+ return;
122
+ }
123
+ const msg = `Failed to create account connection config: HTTP ${created.status}`;
124
+ if (this.strict) {
125
+ throw new errors_js_1.ApiError(msg, created.status);
126
+ }
127
+ this.logger.warn(`${msg}; continuing without config (strict=false)`);
128
+ return;
129
+ }
130
+ catch (err) {
131
+ if (err instanceof errors_js_1.InvalidApiKeyError)
132
+ throw err;
133
+ if (err instanceof errors_js_1.ApiError) {
134
+ if (this.strict)
135
+ throw err;
136
+ this.logger.warn('Create account connection failed; continuing without config (strict=false)', err);
137
+ return;
138
+ }
139
+ const msg = err instanceof Error ? err.message : String(err);
140
+ if (this.strict) {
141
+ throw new errors_js_1.ApiError(`Failed to create account connection config: ${msg}`);
142
+ }
143
+ this.logger.warn('Create account connection failed; continuing without config (strict=false)', err);
144
+ return;
145
+ }
146
+ }
147
+ /**
148
+ * Start polling for configuration updates.
149
+ * Uses ETag for efficient conditional requests.
150
+ */
151
+ startPolling() {
152
+ // Don't start if already polling
153
+ if (this.timer) {
154
+ this.logger.debug('Polling already active, skipping startPolling()');
155
+ return;
156
+ }
157
+ this.logger.info(`Starting config polling every ${this.options.pollIntervalMs}ms`);
158
+ this.timer = setInterval(async () => {
159
+ if (this.pollInFlight) {
160
+ this.logger.debug('Previous poll still running, skipping this poll tick');
161
+ return;
162
+ }
163
+ this.pollInFlight = true;
164
+ try {
165
+ await this.pollOnce();
166
+ }
167
+ finally {
168
+ this.pollInFlight = false;
169
+ }
170
+ }, this.options.pollIntervalMs);
171
+ }
172
+ /**
173
+ * Stop polling for configuration updates.
174
+ */
175
+ stopPolling() {
176
+ if (this.timer) {
177
+ clearInterval(this.timer);
178
+ this.timer = null;
179
+ this.logger.info('Config polling stopped');
180
+ }
181
+ }
182
+ /**
183
+ * Get the current configuration.
184
+ * Returns null if init() hasn't been called or failed.
185
+ */
186
+ getConfig() {
187
+ return this.config;
188
+ }
189
+ async setConfig(body) {
190
+ this.logger.debug(`Setting config: ${JSON.stringify(body)}`);
191
+ let result;
192
+ try {
193
+ result = await (0, request_js_1.requestCore)({
194
+ apiBaseUrl: this.options.apiBaseUrl,
195
+ apiKey: this.options.apiKey,
196
+ method: 'PATCH',
197
+ path: `/account/connections/${this.accountConnectionId}`,
198
+ body,
199
+ });
200
+ }
201
+ catch (err) {
202
+ if (err instanceof errors_js_1.ApiError)
203
+ throw err;
204
+ const msg = err instanceof Error ? err.message : String(err);
205
+ throw new errors_js_1.ApiError(`Failed to update account connection config: ${msg}`);
206
+ }
207
+ if (result.status === 401 || result.status === 403) {
208
+ throw new errors_js_1.InvalidApiKeyError(`Authentication failed with status ${result.status}`);
209
+ }
210
+ if (result.status === 200 && result.body) {
211
+ this.config = this.buildConfigFromAny(result.body, result.etag);
212
+ this.logger.debug('Configuration updated from API');
213
+ this.logger.debug('New config summary:', this.redactConfig(this.config));
214
+ return this.config;
215
+ }
216
+ if (result.status === 422 && result.body) {
217
+ const validationErrors = toValidationErrors(result.body);
218
+ if (validationErrors) {
219
+ throw new errors_js_1.ApiError(`Failed to update account connection config: ${validationErrors[0]}`, result.status);
220
+ }
221
+ }
222
+ throw new errors_js_1.ApiError(`Failed to update account connection config: HTTP ${result.status}`, result.status);
223
+ }
224
+ /**
225
+ * Perform a single poll iteration.
226
+ * Called by the polling timer.
227
+ */
228
+ async pollOnce() {
229
+ if (!this.config) {
230
+ this.logger.warn('No config available, skipping poll');
231
+ return;
232
+ }
233
+ try {
234
+ const result = await (0, request_js_1.requestCore)({
235
+ apiBaseUrl: this.options.apiBaseUrl,
236
+ apiKey: this.options.apiKey,
237
+ method: 'GET',
238
+ path: `/account/connections/${this.accountConnectionId}`,
239
+ ifNoneMatch: this.config.etag,
240
+ });
241
+ if (result.status === 304) {
242
+ this.logger.debug('Config unchanged (304 Not Modified)');
243
+ return;
244
+ }
245
+ if (result.status === 200 && result.body) {
246
+ this.config = this.buildConfigFromAny(result.body, result.etag);
247
+ this.logger.debug('Configuration updated from API');
248
+ this.logger.debug('New config summary:', this.redactConfig(this.config));
249
+ return;
250
+ }
251
+ this.logger.warn(`Poll returned unexpected status ${result.status}`);
252
+ }
253
+ catch (error) {
254
+ this.logger.warn('Poll failed, keeping existing config:', error);
255
+ }
256
+ }
257
+ /**
258
+ * Build ConnectionNetworkConfig from API response.
259
+ */
260
+ buildConfigFromAny(body, etag) {
261
+ const response = toAccountConnectionApiResponse(body);
262
+ const data = response.data;
263
+ const rules = Array.isArray(data?.rules) ? data.rules : [];
264
+ const sessionId = data?.session_id ?? null;
265
+ const targetGeo = data?.target_geo ?? null;
266
+ const username = data?.proxy_username ?? null;
267
+ const password = data?.proxy_password ?? null;
268
+ if (!username || !password) {
269
+ throw new errors_js_1.ApiError('Account connection response missing proxy credentials (data.proxy_username and data.proxy_password are required)', 500);
270
+ }
271
+ return {
272
+ rawProxy: {
273
+ protocol: this.options.gatewayProtocol,
274
+ host: 'gateway.aluvia.io',
275
+ port: this.options.gatewayPort,
276
+ username,
277
+ password,
278
+ },
279
+ rules,
280
+ sessionId,
281
+ targetGeo,
282
+ etag,
283
+ };
284
+ }
285
+ redactConfig(config) {
286
+ if (!config)
287
+ return null;
288
+ return {
289
+ rulesCount: config.rules?.length ?? 0,
290
+ sessionId: config.sessionId,
291
+ targetGeo: config.targetGeo,
292
+ etag: config.etag,
293
+ rawProxy: {
294
+ protocol: config.rawProxy.protocol,
295
+ host: config.rawProxy.host,
296
+ port: config.rawProxy.port,
297
+ username: config.rawProxy.username ? '[set]' : '[missing]',
298
+ password: config.rawProxy.password ? '[set]' : '[missing]',
299
+ },
300
+ };
301
+ }
302
+ }
303
+ exports.ConfigManager = ConfigManager;
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ // ProxyServer - Local HTTP proxy using proxy-chain
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.ProxyServer = void 0;
5
+ const proxy_chain_1 = require("proxy-chain");
6
+ const logger_js_1 = require("./logger.js");
7
+ const errors_js_1 = require("../errors.js");
8
+ const rules_js_1 = require("./rules.js");
9
+ /**
10
+ * ProxyServer manages the local HTTP(S) proxy that routes traffic
11
+ * through Aluvia or directly based on rules.
12
+ */
13
+ class ProxyServer {
14
+ constructor(configManager, options) {
15
+ this.server = null;
16
+ this.bindHost = '127.0.0.1';
17
+ this.lastNoConfigWarnAt = 0;
18
+ this.suppressedNoConfigWarnCount = 0;
19
+ this.configManager = configManager;
20
+ this.logger = new logger_js_1.Logger(options?.logLevel ?? 'info');
21
+ }
22
+ /**
23
+ * Start the local proxy server.
24
+ *
25
+ * @param port - Optional port to listen on. If not provided, OS assigns a free port.
26
+ * @returns ProxyServerInfo with host, port, and url
27
+ * @throws ProxyStartError if server fails to start
28
+ */
29
+ async start(port) {
30
+ const listenPort = port ?? 0;
31
+ try {
32
+ this.server = new proxy_chain_1.Server({
33
+ // Security: bind to loopback only (proxy-chain defaults to 0.0.0.0 if host is omitted)
34
+ host: this.bindHost,
35
+ port: listenPort,
36
+ prepareRequestFunction: this.handleRequest.bind(this),
37
+ });
38
+ await this.server.listen();
39
+ // Get the actual port (especially important when port was 0)
40
+ const address = this.server.server.address();
41
+ const actualPort = address.port;
42
+ const info = {
43
+ host: this.bindHost,
44
+ port: actualPort,
45
+ url: `http://${this.bindHost}:${actualPort}`,
46
+ };
47
+ this.logger.info(`Proxy server listening on ${info.url}`);
48
+ return info;
49
+ }
50
+ catch (error) {
51
+ const message = error instanceof Error ? error.message : 'Unknown error';
52
+ throw new errors_js_1.ProxyStartError(`Failed to start proxy server: ${message}`);
53
+ }
54
+ }
55
+ /**
56
+ * Stop the local proxy server.
57
+ */
58
+ async stop() {
59
+ if (!this.server) {
60
+ return;
61
+ }
62
+ try {
63
+ await this.server.close(true);
64
+ this.logger.info('Proxy server stopped');
65
+ }
66
+ finally {
67
+ this.server = null;
68
+ }
69
+ }
70
+ /**
71
+ * Handle incoming proxy requests.
72
+ * Decides whether to route through Aluvia or direct.
73
+ */
74
+ handleRequest(params) {
75
+ // Get current config
76
+ const config = this.configManager.getConfig();
77
+ if (!config) {
78
+ const now = Date.now();
79
+ const shouldWarn = this.lastNoConfigWarnAt === 0 ||
80
+ now - this.lastNoConfigWarnAt >= ProxyServer.NO_CONFIG_WARN_INTERVAL_MS;
81
+ if (shouldWarn) {
82
+ const suppressed = this.suppressedNoConfigWarnCount;
83
+ this.suppressedNoConfigWarnCount = 0;
84
+ this.lastNoConfigWarnAt = now;
85
+ const suffix = suppressed > 0 ? ` (suppressed ${suppressed} similar warnings)` : '';
86
+ this.logger.warn(`No config available, bypassing proxy (direct)${suffix}`);
87
+ }
88
+ else {
89
+ this.suppressedNoConfigWarnCount += 1;
90
+ this.logger.debug('No config available, bypassing proxy (direct)');
91
+ }
92
+ return undefined;
93
+ }
94
+ // Extract hostname
95
+ const hostname = this.extractHostname(params);
96
+ if (!hostname) {
97
+ this.logger.debug('Could not extract hostname, going direct');
98
+ return undefined;
99
+ }
100
+ // Check if we should proxy this hostname
101
+ const useProxy = (0, rules_js_1.shouldProxy)(hostname, config.rules);
102
+ if (!useProxy) {
103
+ this.logger.debug(`Hostname ${hostname} bypassing proxy (direct)`);
104
+ return undefined;
105
+ }
106
+ // Build upstream proxy URL
107
+ const { protocol, host, port, username, password } = config.rawProxy;
108
+ const upstreamProxyUrl = `${protocol}://${encodeURIComponent(username)}:${encodeURIComponent(password)}@${host}:${port}`;
109
+ this.logger.debug(`Hostname ${hostname} routing through Aluvia`);
110
+ return { upstreamProxyUrl };
111
+ }
112
+ /**
113
+ * Extract hostname from request parameters.
114
+ */
115
+ extractHostname(params) {
116
+ // For CONNECT requests (HTTPS), hostname is provided directly
117
+ if (typeof params.hostname === 'string') {
118
+ const trimmed = params.hostname.trim();
119
+ if (trimmed.length > 0)
120
+ return trimmed;
121
+ }
122
+ const urlLikeRaw = params.request?.url;
123
+ if (typeof urlLikeRaw === 'string') {
124
+ const urlLike = urlLikeRaw.trim();
125
+ if (urlLike.length > 0) {
126
+ const fromUrlLike = (() => {
127
+ try {
128
+ return new URL(urlLike).hostname;
129
+ }
130
+ catch {
131
+ // continue
132
+ }
133
+ if (urlLike.startsWith('//')) {
134
+ try {
135
+ return new URL(`http:${urlLike}`).hostname;
136
+ }
137
+ catch {
138
+ // continue
139
+ }
140
+ }
141
+ if (urlLike.startsWith('/')) {
142
+ return null;
143
+ }
144
+ try {
145
+ return new URL(`http://${urlLike}`).hostname;
146
+ }
147
+ catch {
148
+ return null;
149
+ }
150
+ })();
151
+ if (fromUrlLike)
152
+ return fromUrlLike;
153
+ }
154
+ }
155
+ // For origin-form URLs, fall back to Host header if available.
156
+ const hostHeader = (() => {
157
+ const headers = params.request?.headers;
158
+ if (!headers)
159
+ return null;
160
+ const host = headers['host'];
161
+ if (Array.isArray(host))
162
+ return typeof host[0] === 'string' ? host[0] : null;
163
+ return typeof host === 'string' ? host : null;
164
+ })();
165
+ if (hostHeader) {
166
+ const value = hostHeader.trim();
167
+ if (!value)
168
+ return null;
169
+ if (value.startsWith('[')) {
170
+ const end = value.indexOf(']');
171
+ if (end > 1)
172
+ return value.slice(1, end);
173
+ return null;
174
+ }
175
+ const hostOnly = value.split(':')[0]?.trim();
176
+ return hostOnly && hostOnly.length > 0 ? hostOnly : null;
177
+ }
178
+ return null;
179
+ }
180
+ }
181
+ exports.ProxyServer = ProxyServer;
182
+ ProxyServer.NO_CONFIG_WARN_INTERVAL_MS = 30000;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toPlaywrightProxySettings = toPlaywrightProxySettings;
4
+ exports.toPuppeteerArgs = toPuppeteerArgs;
5
+ exports.toSeleniumArgs = toSeleniumArgs;
6
+ exports.createNodeProxyAgents = createNodeProxyAgents;
7
+ exports.toAxiosConfig = toAxiosConfig;
8
+ exports.toGotOptions = toGotOptions;
9
+ exports.createUndiciDispatcher = createUndiciDispatcher;
10
+ exports.createUndiciFetch = createUndiciFetch;
11
+ const http_proxy_agent_1 = require("http-proxy-agent");
12
+ const https_proxy_agent_1 = require("https-proxy-agent");
13
+ const undici_1 = require("undici");
14
+ function toPlaywrightProxySettings(serverUrl) {
15
+ return { server: serverUrl };
16
+ }
17
+ function toPuppeteerArgs(serverUrl) {
18
+ // Puppeteer wants --proxy-server=<server> without embedding creds.
19
+ return [`--proxy-server=${serverUrl}`];
20
+ }
21
+ function toSeleniumArgs(serverUrl) {
22
+ // Selenium/Chromium wants --proxy-server=<server> without embedding creds.
23
+ return `--proxy-server=${serverUrl}`;
24
+ }
25
+ function createNodeProxyAgents(serverUrl) {
26
+ return {
27
+ http: new http_proxy_agent_1.HttpProxyAgent(serverUrl),
28
+ https: new https_proxy_agent_1.HttpsProxyAgent(serverUrl),
29
+ };
30
+ }
31
+ function toAxiosConfig(agents) {
32
+ return {
33
+ proxy: false,
34
+ httpAgent: agents.http,
35
+ httpsAgent: agents.https,
36
+ };
37
+ }
38
+ function toGotOptions(agents) {
39
+ return { agent: { http: agents.http, https: agents.https } };
40
+ }
41
+ function createUndiciDispatcher(serverUrl) {
42
+ return new undici_1.ProxyAgent(serverUrl);
43
+ }
44
+ function createUndiciFetch(dispatcher) {
45
+ return ((input, init) => {
46
+ const nextInit = init ? { ...init, dispatcher } : { dispatcher };
47
+ return (0, undici_1.fetch)(input, nextInit);
48
+ });
49
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ // Logger utility for Aluvia Client
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.Logger = void 0;
5
+ /**
6
+ * Simple logger that respects log levels.
7
+ *
8
+ * Log level hierarchy:
9
+ * - 'silent': only error() logs
10
+ * - 'info': info(), warn(), error() log
11
+ * - 'debug': all methods log
12
+ */
13
+ class Logger {
14
+ constructor(level) {
15
+ this.level = level;
16
+ }
17
+ /**
18
+ * Log informational messages.
19
+ * Logs when level is 'info' or 'debug'.
20
+ */
21
+ info(...args) {
22
+ if (this.level === 'info' || this.level === 'debug') {
23
+ console.log('[aluvia][info]', ...args);
24
+ }
25
+ }
26
+ /**
27
+ * Log debug messages.
28
+ * Logs only when level is 'debug'.
29
+ */
30
+ debug(...args) {
31
+ if (this.level === 'debug') {
32
+ console.debug('[aluvia][debug]', ...args);
33
+ }
34
+ }
35
+ /**
36
+ * Log warning messages.
37
+ * Logs when level is not 'silent'.
38
+ */
39
+ warn(...args) {
40
+ if (this.level !== 'silent') {
41
+ console.warn('[aluvia][warn]', ...args);
42
+ }
43
+ }
44
+ /**
45
+ * Log error messages.
46
+ * Always logs regardless of level.
47
+ */
48
+ error(...args) {
49
+ console.error('[aluvia][error]', ...args);
50
+ }
51
+ }
52
+ exports.Logger = Logger;