diff --git a/clients/typescript/.eslintignore b/clients/typescript/.eslintignore deleted file mode 100644 index 214af6f..0000000 --- a/clients/typescript/.eslintignore +++ /dev/null @@ -1,17 +0,0 @@ -# Build outputs -dist/ -coverage/ - -# Dependencies -node_modules/ - -# Scripts (JavaScript files that don't need TypeScript parsing) -scripts/ - -# Test outputs -*.test.js -*.spec.js - -# ESLint config files -.eslintrc.js -jest.config.js diff --git a/clients/typescript/.eslintrc.js b/clients/typescript/.eslintrc.js deleted file mode 100644 index 3231c82..0000000 --- a/clients/typescript/.eslintrc.js +++ /dev/null @@ -1,45 +0,0 @@ -module.exports = { - parser: '@typescript-eslint/parser', - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - ], - plugins: ['@typescript-eslint'], - parserOptions: { - ecmaVersion: 2020, - sourceType: 'module', - project: './tsconfig.json', - }, - rules: { - '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], - '@typescript-eslint/explicit-function-return-type': ['error', { - allowExpressions: true, - allowTypedFunctionExpressions: true - }], - // Enforce explicit return types for exported/public APIs - '@typescript-eslint/explicit-module-boundary-types': 'error', - '@typescript-eslint/no-explicit-any': 'error', - '@typescript-eslint/no-inferrable-types': 'off', - '@typescript-eslint/strict-boolean-expressions': ['error', { - allowNullableObject: true - }], - 'prefer-const': 'error', - 'no-var': 'error', - 'no-unused-vars': 'off', // Turn off base rule as it can conflict with @typescript-eslint/no-unused-vars - }, - env: { - node: true, - es6: true, - }, - overrides: [ - { - files: ['tests/**/*.ts', '**/*.test.ts', '**/*.spec.ts'], - parserOptions: { - project: './tsconfig.test.json', - }, - env: { - jest: true, - }, - }, - ], -}; diff --git a/clients/typescript/eslint.config.js b/clients/typescript/eslint.config.js new file mode 100644 index 0000000..00c6dcf --- /dev/null +++ b/clients/typescript/eslint.config.js @@ -0,0 +1,103 @@ +import js from '@eslint/js'; +import typescript from '@typescript-eslint/eslint-plugin'; +import typescriptParser from '@typescript-eslint/parser'; +import globals from 'globals'; + +export default [ + js.configs.recommended, + { + files: ['**/*.ts', '**/*.tsx'], + languageOptions: { + parser: typescriptParser, + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: import.meta.dirname, +... + project: './tsconfig.test.json', + tsconfigRootDir: import.meta.dirname, + globals: { + ...globals.node, + ...globals.browser, + URL: 'readonly', + Response: 'readonly', + RequestInit: 'readonly', + AbortController: 'readonly', + fetch: 'readonly', + setTimeout: 'readonly', + clearTimeout: 'readonly', + }, + }, + plugins: { + '@typescript-eslint': typescript, + }, + rules: { + // Optionally merge in typed presets for stronger checks: + // ...typescript.configs['recommended-type-checked']?.rules, + // ...typescript.configs['strict-type-checked']?.rules, + '@typescript-eslint/explicit-module-boundary-types': 'error', + '@typescript-eslint/no-explicit-any': 'error', + '@typescript-eslint/no-inferrable-types': 'off', + '@typescript-eslint/strict-boolean-expressions': ['error', { + allowNullableObject: true + }], + 'prefer-const': 'error', + 'no-var': 'error', + 'no-unused-vars': 'off', // Turn off base rule as it can conflict with @typescript-eslint/no-unused-vars + }, + }, + { + files: ['tests/**/*.ts', '**/*.test.ts', '**/*.spec.ts'], + languageOptions: { + parser: typescriptParser, + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module', + project: './tsconfig.test.json', + }, + globals: { + ...globals.node, + ...globals.browser, + ...globals.jest, + global: 'readonly', + URL: 'readonly', + Response: 'readonly', + RequestInit: 'readonly', + AbortController: 'readonly', + fetch: 'readonly', + setTimeout: 'readonly', + clearTimeout: 'readonly', + Headers: 'readonly', + }, + }, + plugins: { + '@typescript-eslint': typescript, + }, + rules: { + ...typescript.configs.recommended.rules, + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + '@typescript-eslint/explicit-function-return-type': ['error', { + allowExpressions: true, + allowTypedFunctionExpressions: true + }], + '@typescript-eslint/explicit-module-boundary-types': 'error', + '@typescript-eslint/no-explicit-any': 'error', + '@typescript-eslint/no-inferrable-types': 'off', + '@typescript-eslint/strict-boolean-expressions': ['error', { + allowNullableObject: true + }], + 'prefer-const': 'error', + 'no-var': 'error', + 'no-unused-vars': 'off', + }, + }, + { + ignores: [ + 'dist/**', + 'node_modules/**', + 'coverage/**', + // Keep TS focused; optionally lint JS configs by removing these: + // '*.js', + // '*.mjs', + ], + }, +]; diff --git a/clients/typescript/jest.config.js b/clients/typescript/jest.config.js index e6c1531..8fa7048 100644 --- a/clients/typescript/jest.config.js +++ b/clients/typescript/jest.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { preset: 'ts-jest/presets/default-esm', globals: { 'ts-jest': { diff --git a/clients/typescript/package-lock.json b/clients/typescript/package-lock.json index 9a5b47d..c28ed04 100644 --- a/clients/typescript/package-lock.json +++ b/clients/typescript/package-lock.json @@ -19,6 +19,7 @@ "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", "eslint": "^9.0.0", + "globals": "^16.4.0", "husky": "^9.0.0", "jest": "^30.0.0", "prettier": "^3.1.0", @@ -61,7 +62,6 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -710,6 +710,19 @@ "concat-map": "0.0.1" } }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2138,7 +2151,6 @@ "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/types": "8.46.2", @@ -2620,7 +2632,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2903,7 +2914,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -3261,7 +3271,6 @@ "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3808,9 +3817,9 @@ } }, "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", "dev": true, "license": "MIT", "engines": { @@ -4184,7 +4193,6 @@ "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "30.2.0", "@jest/types": "30.2.0", @@ -5596,7 +5604,6 @@ "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -6140,8 +6147,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/type-check": { "version": "0.4.0", @@ -6172,7 +6178,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/clients/typescript/package.json b/clients/typescript/package.json index ee6d39b..2aac580 100644 --- a/clients/typescript/package.json +++ b/clients/typescript/package.json @@ -1,15 +1,16 @@ { "name": "@contextforge/memory-client", "version": "0.1.0", + "type": "module", "description": "TypeScript client for ContextForge Memory API with v0 and v1 support", - "main": "dist/index.js", + "main": "dist/index.cjs", "module": "dist/index.esm.js", "types": "dist/types/index.d.ts", "sideEffects": false, "exports": { ".": { "import": "./dist/index.esm.js", - "require": "./dist/index.js", + "require": "./dist/index.cjs", "types": "./dist/types/index.d.ts" }, "./package.json": "./package.json" @@ -60,7 +61,6 @@ "engines": { "node": ">=18.0.0" }, - "peerDependencies": {}, "devDependencies": { "@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-terser": "^0.4.4", @@ -69,6 +69,7 @@ "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", "eslint": "^9.0.0", + "globals": "^16.4.0", "husky": "^9.0.0", "jest": "^30.0.0", "prettier": "^3.1.0", diff --git a/clients/typescript/rollup.config.js b/clients/typescript/rollup.config.js index 932be03..63b4844 100644 --- a/clients/typescript/rollup.config.js +++ b/clients/typescript/rollup.config.js @@ -41,7 +41,7 @@ export default defineConfig([ tsconfig: './tsconfig.json', declaration: true, declarationMap: true, - outDir: 'dist/types' + declarationDir: 'dist/types' }), terser() ], diff --git a/clients/typescript/scripts/dev.js b/clients/typescript/scripts/dev.js index 83b8481..d4dfd5b 100755 --- a/clients/typescript/scripts/dev.js +++ b/clients/typescript/scripts/dev.js @@ -16,9 +16,13 @@ * node scripts/dev.js pre-publish - Run pre-publish checks/tasks */ -const { execSync } = require('child_process'); -const fs = require('fs'); -const path = require('path'); +import { execSync } from 'child_process'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); // Validate we're in the package root const packageJsonPath = path.join(__dirname, '..', 'package.json'); diff --git a/clients/typescript/scripts/prepare.js b/clients/typescript/scripts/prepare.js index 81b73a8..3aae314 100644 --- a/clients/typescript/scripts/prepare.js +++ b/clients/typescript/scripts/prepare.js @@ -8,8 +8,12 @@ console.log('Preparing TypeScript client...'); // Ensure dist directory exists -const fs = require('fs'); -const path = require('path'); +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); const distDir = path.join(__dirname, '..', 'dist'); if (!fs.existsSync(distDir)) { diff --git a/clients/typescript/src/index.ts b/clients/typescript/src/index.ts index cb04103..a327ad4 100644 --- a/clients/typescript/src/index.ts +++ b/clients/typescript/src/index.ts @@ -117,8 +117,9 @@ export class ContextForgeClient { this.baseUrl = baseUrl.replace(/\/$/, ''); // Normalize apiKey: treat undefined or empty/whitespace-only strings as undefined - this.apiKey = (apiKey === undefined || apiKey.trim() === '') ? undefined : apiKey; - + const sanitized = + typeof apiKey === 'string' && apiKey.trim() !== '' ? apiKey : undefined; // pragma: allowlist secret + this.apiKey = sanitized; // Validate timeoutMs const defaultTimeout = 30000; // Keep current default if (timeoutMs !== undefined) { @@ -258,7 +259,7 @@ export class ContextForgeClient { this.shouldRetry(lastError, lastResponse) ) { const delay = this.calculateDelay(attempt); - await new Promise(resolve => setTimeout(resolve, delay)); + await new Promise((resolve) => setTimeout(resolve, delay)); continue; } @@ -313,10 +314,10 @@ export class ContextForgeClient { async store(items: MemoryItem[]): Promise<{ stored: number }> { // Validate input if (!Array.isArray(items) || items.length === 0) { - throw new Error("items must be a non-empty array"); + throw new Error('items must be a non-empty array'); } if (items.length > 100) { - throw new Error("items array must not exceed 100 items"); + throw new Error('items array must not exceed 100 items'); } // Validate each item @@ -338,16 +339,16 @@ export class ContextForgeClient { ): Promise<{ results: MemoryItem[] }> { // Validate input if (!namespace || typeof namespace !== 'string' || namespace.trim() === '') { - throw new Error("namespace must be a non-empty string"); + throw new Error('namespace must be a non-empty string'); } if (!project_id || typeof project_id !== 'string' || project_id.trim() === '') { - throw new Error("project_id must be a non-empty string"); + throw new Error('project_id must be a non-empty string'); } if (!query || typeof query !== 'string' || query.trim() === '') { - throw new Error("query must be a non-empty string"); + throw new Error('query must be a non-empty string'); } if (!Number.isInteger(top_k) || top_k < 1 || top_k > 100) { - throw new Error("top_k must be an integer between 1 and 100"); + throw new Error('top_k must be an integer between 1 and 100'); } const r = await this.fetchWithRetry(`${this.baseUrl}/v0/search`, { @@ -361,10 +362,10 @@ export class ContextForgeClient { async embed(texts: string[]): Promise<{ vectors: number[][] }> { // Validate input if (!Array.isArray(texts) || texts.length === 0) { - throw new Error("texts must be a non-empty array"); + throw new Error('texts must be a non-empty array'); } if (texts.length > 100) { - throw new Error("texts array must not exceed 100 items"); + throw new Error('texts array must not exceed 100 items'); } texts.forEach((text, index) => { if (!text || typeof text !== 'string' || text.trim() === '') { @@ -439,16 +440,16 @@ export class ContextForgeClient { ): Promise<{ results: MemoryItem[] }> { // Validate input if (!namespace || typeof namespace !== 'string' || namespace.trim() === '') { - throw new Error("namespace must be a non-empty string"); + throw new Error('namespace must be a non-empty string'); } if (!project_id || typeof project_id !== 'string' || project_id.trim() === '') { - throw new Error("project_id must be a non-empty string"); + throw new Error('project_id must be a non-empty string'); } if (!query || typeof query !== 'string' || query.trim() === '') { - throw new Error("query must be a non-empty string"); + throw new Error('query must be a non-empty string'); } if (!Number.isInteger(top_k) || top_k < 1 || top_k > 100) { - throw new Error("top_k must be an integer between 1 and 100"); + throw new Error('top_k must be an integer between 1 and 100'); } const r = await this.fetchWithRetry(`${this.baseUrl}/v1/search`, { @@ -469,10 +470,10 @@ export class ContextForgeClient { async v1Embed(texts: string[]): Promise<{ vectors: number[][] }> { // Validate input if (!Array.isArray(texts) || texts.length === 0) { - throw new Error("texts must be a non-empty array"); + throw new Error('texts must be a non-empty array'); } if (texts.length > 100) { - throw new Error("texts array must not exceed 100 items"); + throw new Error('texts array must not exceed 100 items'); } texts.forEach((text, index) => { if (!text || typeof text !== 'string' || text.trim() === '') { @@ -496,7 +497,7 @@ export class ContextForgeClient { ): Promise<{ ok: boolean }> { // Validate input if (!session_id || typeof session_id !== 'string' || session_id.trim() === '') { - throw new Error("session_id must be a non-empty string"); + throw new Error('session_id must be a non-empty string'); } // Client-side validation for phase parameter @@ -535,13 +536,13 @@ export class ContextForgeClient { async v1Restore(session_id: string, task: string, top_k = 5): Promise<{ context: string }> { // Validate input if (!session_id || typeof session_id !== 'string' || session_id.trim() === '') { - throw new Error("session_id must be a non-empty string"); + throw new Error('session_id must be a non-empty string'); } if (!task || typeof task !== 'string' || task.trim() === '') { - throw new Error("task must be a non-empty string"); + throw new Error('task must be a non-empty string'); } if (!Number.isInteger(top_k) || top_k < 1 || top_k > 100) { - throw new Error("top_k must be an integer between 1 and 100"); + throw new Error('top_k must be an integer between 1 and 100'); } const response = await this.fetchWithRetry(`${this.baseUrl}/v1/restore`, { diff --git a/clients/typescript/tests/contextforgeClient.test.ts b/clients/typescript/tests/contextforgeClient.test.ts index bdbdaca..7725d4a 100644 --- a/clients/typescript/tests/contextforgeClient.test.ts +++ b/clients/typescript/tests/contextforgeClient.test.ts @@ -1,4 +1,10 @@ -import { ContextForgeClient, MemoryItem, RequestTimeoutError, HTTPError, NetworkError } from '../src/index'; +import { + ContextForgeClient, + MemoryItem, + RequestTimeoutError, + HTTPError, + NetworkError, +} from '../src/index'; // Import Jest types explicitly import { jest, describe, it, expect, beforeEach } from '@jest/globals'; @@ -34,10 +40,14 @@ describe('ContextForgeClient', () => { it('should handle non-numeric timeoutMs inputs', () => { // String input should throw RangeError (Number.isFinite returns false for strings) - expect(() => new ContextForgeClient(baseUrl, apiKey, 'invalid' as unknown as number)).toThrow(RangeError); + expect(() => new ContextForgeClient(baseUrl, apiKey, 'invalid' as unknown as number)).toThrow( + RangeError, + ); // Null input should throw RangeError (Number.isFinite returns false for null) - expect(() => new ContextForgeClient(baseUrl, apiKey, null as unknown as number)).toThrow(RangeError); + expect(() => new ContextForgeClient(baseUrl, apiKey, null as unknown as number)).toThrow( + RangeError, + ); // Undefined input should not throw and use default timeout expect(() => new ContextForgeClient(baseUrl, apiKey, undefined)).not.toThrow(); @@ -55,8 +65,8 @@ describe('ContextForgeClient', () => { expect(mockFetch).toHaveBeenCalledWith( `${baseUrl}/v0/health`, expect.objectContaining({ - headers: expect.not.objectContaining({ 'x-api-key': expect.anything() }) - }) + headers: expect.not.objectContaining({ 'x-api-key': expect.anything() }), + }), ); // Test with whitespace-only string const clientWhitespace = new ContextForgeClient(baseUrl, ' '); @@ -70,8 +80,8 @@ describe('ContextForgeClient', () => { expect(mockFetch).toHaveBeenCalledWith( `${baseUrl}/v0/health`, expect.objectContaining({ - headers: { 'Content-Type': 'application/json' } // No x-api-key header - }) + headers: { 'Content-Type': 'application/json' }, // No x-api-key header + }), ); // Test with undefined (should work the same) @@ -86,8 +96,8 @@ describe('ContextForgeClient', () => { expect(mockFetch).toHaveBeenCalledWith( `${baseUrl}/v0/health`, expect.objectContaining({ - headers: { 'Content-Type': 'application/json' } // No x-api-key header - }) + headers: { 'Content-Type': 'application/json' }, // No x-api-key header + }), ); }); }); @@ -102,10 +112,7 @@ describe('ContextForgeClient', () => { const result = await client.health(); expect(result).toEqual(mockResponse); - expect(mockFetch).toHaveBeenCalledWith( - `${baseUrl}/v0/health`, - expect.objectContaining({}) - ); + expect(mockFetch).toHaveBeenCalledWith(`${baseUrl}/v0/health`, expect.objectContaining({})); }); }); @@ -116,8 +123,8 @@ describe('ContextForgeClient', () => { namespace: 'org:project:env', project_id: 'project1', kind: 'test', - text: 'test text' - } + text: 'test text', + }, ]; it('should store items successfully', async () => { @@ -134,8 +141,8 @@ describe('ContextForgeClient', () => { expect.objectContaining({ method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey }, - body: JSON.stringify({ items: mockItems }) - }) + body: JSON.stringify({ items: mockItems }), + }), ); }); }); @@ -159,9 +166,9 @@ describe('ContextForgeClient', () => { namespace: 'org:project:env', project_id: 'project1', query: 'query', - top_k: 5 - }) - }) + top_k: 5, + }), + }), ); }); }); @@ -173,8 +180,8 @@ describe('ContextForgeClient', () => { namespace: 'org:project:env', project_id: 'project1', kind: 'test', - text: 'test text' - } + text: 'test text', + }, ]; it('should store items with v1 API', async () => { @@ -192,10 +199,10 @@ describe('ContextForgeClient', () => { method: 'POST', headers: expect.objectContaining({ 'Content-Type': 'application/json', - 'x-api-key': apiKey + 'x-api-key': apiKey, }), - body: JSON.stringify({ items: mockItems }) - }) + body: JSON.stringify({ items: mockItems }), + }), ); }); @@ -206,19 +213,22 @@ describe('ContextForgeClient', () => { namespace: 'org:project:env', project_id: 'project1', kind: 'test', - text: 'test text' - } + text: 'test text', + }, ]; await expect(client.v1Store(invalidItems)).rejects.toThrow(); }); it('should validate vectors length matches items length', async () => { - const vectors = [[0.1, 0.2], [0.3, 0.4]]; // 2 vectors + const vectors = [ + [0.1, 0.2], + [0.3, 0.4], + ]; // 2 vectors const items = [mockItems[0]]; // 1 item await expect(client.v1Store(items, vectors)).rejects.toThrow( - 'Vectors length (2) must match items length (1)' + 'Vectors length (2) must match items length (1)', ); }); }); @@ -226,7 +236,11 @@ describe('ContextForgeClient', () => { describe('Error Handling', () => { it('should handle network errors', async () => { // Create client with minimal retries to avoid timeout - const clientMinRetry = new ContextForgeClient(baseUrl, apiKey, 1000, { maxRetries: 1, baseDelay: 1, maxDelay: 1 }); + const clientMinRetry = new ContextForgeClient(baseUrl, apiKey, 1000, { + maxRetries: 1, + baseDelay: 1, + maxDelay: 1, + }); const networkError = new Error('Network error'); mockFetch.mockRejectedValueOnce(networkError); mockFetch.mockRejectedValueOnce(networkError); // Second call for retry @@ -240,7 +254,7 @@ describe('ContextForgeClient', () => { status: 400, statusText: 'Bad Request', headers: new Headers({ 'content-type': 'application/json' }), - json: async () => ({ error: 'Invalid request' }) + json: async () => ({ error: 'Invalid request' }), } as Response; mockFetch.mockResolvedValueOnce(mockResponse); @@ -250,7 +264,11 @@ describe('ContextForgeClient', () => { it('should handle timeout errors', async () => { // Create client with minimal retries to avoid timeout - const clientMinRetry = new ContextForgeClient(baseUrl, apiKey, 1000, { maxRetries: 1, baseDelay: 1, maxDelay: 1 }); + const clientMinRetry = new ContextForgeClient(baseUrl, apiKey, 1000, { + maxRetries: 1, + baseDelay: 1, + maxDelay: 1, + }); const timeoutError = new Error('Request timeout'); timeoutError.name = 'AbortError'; mockFetch.mockRejectedValueOnce(timeoutError); diff --git a/clients/typescript/tests/setup.ts b/clients/typescript/tests/setup.ts index c65bedf..99cd10d 100644 --- a/clients/typescript/tests/setup.ts +++ b/clients/typescript/tests/setup.ts @@ -8,7 +8,7 @@ global.fetch = jest.fn() as jest.MockedFunction; // Default mock that fails - forces tests to provide explicit mocks (global.fetch as jest.MockedFunction).mockRejectedValue( - new Error('fetch must be explicitly mocked in each test') + new Error('fetch must be explicitly mocked in each test'), ); // Reset all mocks after each test (resets mock implementations for deterministic isolation)