From e50b685c7c740f352fa91bb52c078be3a2ef99b5 Mon Sep 17 00:00:00 2001 From: SachaMorard <2254275+SachaMorard@users.noreply.github.com> Date: Fri, 2 Jan 2026 06:42:17 +0100 Subject: [PATCH] feat: base url configuration --- example/package-lock.json | 12 ++- example/tsconfig.json | 11 ++ package.json | 2 +- src/index.ts | 23 ++++- tests/index.test.ts | 206 +++++++++++++++++++++++++++++++++++--- 5 files changed, 236 insertions(+), 18 deletions(-) create mode 100644 example/tsconfig.json diff --git a/example/package-lock.json b/example/package-lock.json index b75c6d9..ec7ae6c 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -16,9 +16,17 @@ }, "..": { "name": "edgee", - "version": "0.1.0", + "version": "0.1.1", "devDependencies": { - "typescript": "^5.7.2" + "@eslint/js": "^9.39.2", + "@types/node": "^25.0.3", + "@typescript-eslint/eslint-plugin": "^8.51.0", + "@typescript-eslint/parser": "^8.51.0", + "@vitest/ui": "^4.0.16", + "eslint": "^9.39.2", + "typescript": "^5.7.2", + "typescript-eslint": "^8.51.0", + "vitest": "^4.0.16" } }, "node_modules/@esbuild/aix-ppc64": { diff --git a/example/tsconfig.json b/example/tsconfig.json new file mode 100644 index 0000000..2cfd8cc --- /dev/null +++ b/example/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "moduleResolution": "bundler", + "module": "ESNext", + "target": "ES2022", + "types": ["node"] + }, + "include": ["test.ts"] +} + diff --git a/package.json b/package.json index 106b2c6..026679c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "edgee", - "version": "0.1.0", + "version": "0.1.1", "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/src/index.ts b/src/index.ts index 0f5306b..6b71533 100644 --- a/src/index.ts +++ b/src/index.ts @@ -68,15 +68,34 @@ export interface SendResponse { }; } +export interface EdgeeConfig { + apiKey?: string; + baseUrl?: string; +} + export default class Edgee { private apiKey: string; - private baseUrl = "https://api.edgee.ai"; + private baseUrl: string; + + constructor(config?: string | EdgeeConfig) { + let apiKey: string | undefined; + let baseUrl: string | undefined; + + if (typeof config === "string") { + // Backward compatibility: accept apiKey as string + apiKey = config; + } else if (config) { + // New format: accept config object + apiKey = config.apiKey; + baseUrl = config.baseUrl; + } - constructor(apiKey?: string) { this.apiKey = apiKey || process.env.EDGEE_API_KEY || ""; if (!this.apiKey) { throw new Error("EDGEE_API_KEY is not set"); } + + this.baseUrl = baseUrl || process.env.EDGEE_BASE_URL || "https://api.edgee.ai"; } async send(options: SendOptions): Promise { diff --git a/tests/index.test.ts b/tests/index.test.ts index 2ff7b0f..af8fc34 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -8,28 +8,75 @@ describe('Edgee', () => { beforeEach(() => { vi.clearAllMocks(); global.fetch = mockFetch; - // Clear environment variable + // Clear environment variables delete process.env.EDGEE_API_KEY; + delete process.env.EDGEE_BASE_URL; }); describe('constructor', () => { - it('should use provided API key', () => { - const client = new Edgee(mockApiKey); - expect(client).toBeInstanceOf(Edgee); - }); + describe('with string API key (backward compatibility)', () => { + it('should use provided API key', () => { + const client = new Edgee(mockApiKey); + expect(client).toBeInstanceOf(Edgee); + }); - it('should use EDGEE_API_KEY environment variable when no API key provided', () => { - process.env.EDGEE_API_KEY = 'env-api-key'; - const client = new Edgee(); - expect(client).toBeInstanceOf(Edgee); + it('should throw error when empty string is provided as API key', () => { + expect(() => new Edgee('')).toThrow('EDGEE_API_KEY is not set'); + }); }); - it('should throw error when no API key is provided and EDGEE_API_KEY is not set', () => { - expect(() => new Edgee()).toThrow('EDGEE_API_KEY is not set'); + describe('with config object', () => { + it('should use provided API key and baseUrl', () => { + const customBaseUrl = 'https://custom-api.example.com'; + const client = new Edgee({ apiKey: mockApiKey, baseUrl: customBaseUrl }); + expect(client).toBeInstanceOf(Edgee); + }); + + it('should use provided API key with default baseUrl when baseUrl not provided', () => { + const client = new Edgee({ apiKey: mockApiKey }); + expect(client).toBeInstanceOf(Edgee); + }); + + it('should use EDGEE_API_KEY environment variable when apiKey not provided in config', () => { + process.env.EDGEE_API_KEY = 'env-api-key'; + const customBaseUrl = 'https://custom-api.example.com'; + const client = new Edgee({ baseUrl: customBaseUrl }); + expect(client).toBeInstanceOf(Edgee); + }); + + it('should use EDGEE_BASE_URL environment variable when baseUrl not provided in config', () => { + process.env.EDGEE_API_KEY = 'env-api-key'; + process.env.EDGEE_BASE_URL = 'https://env-base-url.example.com'; + const client = new Edgee({ apiKey: mockApiKey }); + expect(client).toBeInstanceOf(Edgee); + }); + + it('should use both environment variables when config object is empty', () => { + process.env.EDGEE_API_KEY = 'env-api-key'; + process.env.EDGEE_BASE_URL = 'https://env-base-url.example.com'; + const client = new Edgee({}); + expect(client).toBeInstanceOf(Edgee); + }); + + it('should throw error when no API key is provided in config and EDGEE_API_KEY is not set', () => { + expect(() => new Edgee({})).toThrow('EDGEE_API_KEY is not set'); + }); + + it('should throw error when empty string is provided as apiKey in config', () => { + expect(() => new Edgee({ apiKey: '' })).toThrow('EDGEE_API_KEY is not set'); + }); }); - it('should throw error when empty string is provided as API key', () => { - expect(() => new Edgee('')).toThrow('EDGEE_API_KEY is not set'); + describe('without arguments', () => { + it('should use EDGEE_API_KEY environment variable when no API key provided', () => { + process.env.EDGEE_API_KEY = 'env-api-key'; + const client = new Edgee(); + expect(client).toBeInstanceOf(Edgee); + }); + + it('should throw error when no API key is provided and EDGEE_API_KEY is not set', () => { + expect(() => new Edgee()).toThrow('EDGEE_API_KEY is not set'); + }); }); }); @@ -310,6 +357,139 @@ describe('Edgee', () => { expect(result.choices[0].message.content).toBe('First response'); expect(result.choices[1].message.content).toBe('Second response'); }); + + it('should use custom baseUrl when provided in constructor', async () => { + const customBaseUrl = 'https://custom-api.example.com'; + const customClient = new Edgee({ apiKey: mockApiKey, baseUrl: customBaseUrl }); + + const mockResponse: SendResponse = { + choices: [ + { + index: 0, + message: { + role: 'assistant', + content: 'Response', + }, + finish_reason: 'stop', + }, + ], + }; + + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => mockResponse, + }); + + await customClient.send({ + model: 'gpt-4', + input: 'Test', + }); + + expect(mockFetch).toHaveBeenCalledWith( + `${customBaseUrl}/v1/chat/completions`, + expect.any(Object) + ); + }); + + it('should use EDGEE_BASE_URL environment variable when baseUrl not provided', async () => { + const envBaseUrl = 'https://env-base-url.example.com'; + process.env.EDGEE_BASE_URL = envBaseUrl; + const envClient = new Edgee(mockApiKey); + + const mockResponse: SendResponse = { + choices: [ + { + index: 0, + message: { + role: 'assistant', + content: 'Response', + }, + finish_reason: 'stop', + }, + ], + }; + + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => mockResponse, + }); + + await envClient.send({ + model: 'gpt-4', + input: 'Test', + }); + + expect(mockFetch).toHaveBeenCalledWith( + `${envBaseUrl}/v1/chat/completions`, + expect.any(Object) + ); + }); + + it('should use default baseUrl when neither custom baseUrl nor EDGEE_BASE_URL is provided', async () => { + const defaultClient = new Edgee(mockApiKey); + + const mockResponse: SendResponse = { + choices: [ + { + index: 0, + message: { + role: 'assistant', + content: 'Response', + }, + finish_reason: 'stop', + }, + ], + }; + + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => mockResponse, + }); + + await defaultClient.send({ + model: 'gpt-4', + input: 'Test', + }); + + expect(mockFetch).toHaveBeenCalledWith( + 'https://api.edgee.ai/v1/chat/completions', + expect.any(Object) + ); + }); + + it('should prioritize config baseUrl over EDGEE_BASE_URL environment variable', async () => { + const configBaseUrl = 'https://config-base-url.example.com'; + process.env.EDGEE_BASE_URL = 'https://env-base-url.example.com'; + const configClient = new Edgee({ apiKey: mockApiKey, baseUrl: configBaseUrl }); + + const mockResponse: SendResponse = { + choices: [ + { + index: 0, + message: { + role: 'assistant', + content: 'Response', + }, + finish_reason: 'stop', + }, + ], + }; + + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => mockResponse, + }); + + await configClient.send({ + model: 'gpt-4', + input: 'Test', + }); + + expect(mockFetch).toHaveBeenCalledWith( + `${configBaseUrl}/v1/chat/completions`, + expect.any(Object) + ); + }); }); });