UNPKG

@beparallel/langchain-ts

Version:

Extracts Langchain prompts and generates TypeScript types.

285 lines (284 loc) 14 kB
import fs from 'fs'; import { generateTypes } from './type-generator.js'; // Mock fs module jest.mock('fs'); const mockedFs = fs; describe('Type Generator', () => { beforeEach(() => { jest.clearAllMocks(); }); describe('generateTypes', () => { test('generates types for a simple prompt with basic schema', () => { const mockPrompt = { inputVariables: ['name', 'age'], metadata: { lc_hub_repo: 'simple-prompt' }, toJSON: () => ({ kwargs: { schema_: { title: 'SimpleOutput', type: 'object', properties: { greeting: { type: 'string', description: 'A greeting message' }, isValid: { type: 'boolean' }, }, required: ['greeting'], }, }, }), }; const outputPath = '/test/output.ts'; generateTypes([mockPrompt], outputPath); expect(mockedFs.writeFileSync).toHaveBeenCalledWith(outputPath, expect.stringContaining('export enum PromptName {'), 'utf-8'); const generatedContent = mockedFs.writeFileSync.mock.calls[0][1]; // Check enum generation expect(generatedContent).toContain('SIMPLE_PROMPT = "simple-prompt"'); // Check interface generation expect(generatedContent).toContain('export interface SimplePromptVariable {'); expect(generatedContent).toContain('name: any'); expect(generatedContent).toContain('age: any'); // Check output interface expect(generatedContent).toContain('export interface SimplePromptOutput'); expect(generatedContent).toContain('greeting: string'); expect(generatedContent).toContain('isValid?: boolean'); // Check wrapper interface expect(generatedContent).toContain('export interface SimplePromptOutputWrapper {'); expect(generatedContent).toContain('SimpleOutput: SimplePromptOutput'); // Check Zod schema expect(generatedContent).toContain('export const simplePromptOutputSchema = z.object({'); expect(generatedContent).toContain('greeting: z.string().describe(`A greeting message`)'); expect(generatedContent).toContain('isValid: z.boolean().optional()'); }); test('generates types for prompt with array schema', () => { const mockPrompt = { inputVariables: ['items'], metadata: { lc_hub_repo: 'array-prompt' }, toJSON: () => ({ kwargs: { schema_: { title: 'ArrayOutput', type: 'object', properties: { items: { type: 'array', items: { type: 'string' }, }, numbers: { type: 'array', items: { type: 'number' }, }, }, required: ['items'], }, }, }), }; generateTypes([mockPrompt], '/test/array-output.ts'); const generatedContent = mockedFs.writeFileSync.mock.calls[0][1]; expect(generatedContent).toContain('items: string[]'); expect(generatedContent).toContain('numbers?: number[]'); expect(generatedContent).toContain('items: z.array(z.string())'); expect(generatedContent).toContain('numbers: z.array(z.number()).optional()'); }); test('generates types for prompt with nested object schema', () => { const mockPrompt = { inputVariables: ['data'], metadata: { lc_hub_repo: 'nested-prompt' }, toJSON: () => ({ kwargs: { schema_: { title: 'NestedOutput', type: 'object', properties: { user: { type: 'object', properties: { name: { type: 'string' }, details: { type: 'object', properties: { age: { type: 'number' }, active: { type: 'boolean' }, }, required: ['age'], }, }, required: ['name', 'details'], }, }, required: ['user'], }, }, }), }; generateTypes([mockPrompt], '/test/nested-output.ts'); const generatedContent = mockedFs.writeFileSync.mock.calls[0][1]; expect(generatedContent).toContain('user: {'); expect(generatedContent).toContain('name: string'); expect(generatedContent).toContain('details: {'); expect(generatedContent).toContain('age: number'); expect(generatedContent).toContain('active?: boolean'); }); test('generates types for multiple prompts', () => { const mockPrompt1 = { inputVariables: ['input1'], metadata: { lc_hub_repo: 'first-prompt' }, toJSON: () => ({ kwargs: { schema_: { title: 'FirstOutput', type: 'object', properties: { result: { type: 'string' }, }, required: ['result'], }, }, }), }; const mockPrompt2 = { inputVariables: ['input2'], metadata: { lc_hub_repo: 'second-prompt' }, toJSON: () => ({ kwargs: { schema_: { title: 'SecondOutput', type: 'object', properties: { count: { type: 'number' }, }, required: ['count'], }, }, }), }; generateTypes([mockPrompt1, mockPrompt2], '/test/multiple-output.ts'); const generatedContent = mockedFs.writeFileSync.mock.calls[0][1]; // Check both prompts are included in enum expect(generatedContent).toContain('FIRST_PROMPT = "first-prompt"'); expect(generatedContent).toContain('SECOND_PROMPT = "second-prompt"'); // Check both interfaces are generated expect(generatedContent).toContain('export interface FirstPromptVariable'); expect(generatedContent).toContain('export interface SecondPromptVariable'); expect(generatedContent).toContain('export interface FirstPromptOutput'); expect(generatedContent).toContain('export interface SecondPromptOutput'); }); test('handles prompt with no schema gracefully', () => { const mockPrompt = { inputVariables: ['input'], metadata: { lc_hub_repo: 'no-schema-prompt' }, toJSON: () => ({ kwargs: { schema_: null, }, }), }; generateTypes([mockPrompt], '/test/no-schema-output.ts'); const generatedContent = mockedFs.writeFileSync.mock.calls[0][1]; expect(generatedContent).toContain('NO_SCHEMA_PROMPT = "no-schema-prompt"'); expect(generatedContent).toContain('export interface NoSchemaPromptVariable'); }); test('handles prompt with undefined schema gracefully', () => { const mockPrompt = { inputVariables: ['input'], metadata: { lc_hub_repo: 'undefined-schema-prompt' }, toJSON: () => ({ kwargs: { schema_: undefined, }, }), }; generateTypes([mockPrompt], '/test/undefined-schema-output.ts'); const generatedContent = mockedFs.writeFileSync.mock.calls[0][1]; expect(generatedContent).toContain('UNDEFINED_SCHEMA_PROMPT = "undefined-schema-prompt"'); expect(generatedContent).toContain('export interface UndefinedSchemaPromptVariable'); }); test('handles all supported data types', () => { const mockPrompt = { inputVariables: ['data'], metadata: { lc_hub_repo: 'all-types-prompt' }, toJSON: () => ({ kwargs: { schema_: { title: 'AllTypesOutput', type: 'object', properties: { stringField: { type: 'string' }, numberField: { type: 'number' }, booleanField: { type: 'boolean' }, nullField: { type: 'null' }, unknownField: { type: 'unknown' }, }, required: ['stringField', 'numberField'], }, }, }), }; generateTypes([mockPrompt], '/test/all-types-output.ts'); const generatedContent = mockedFs.writeFileSync.mock.calls[0][1]; expect(generatedContent).toContain('stringField: string'); expect(generatedContent).toContain('numberField: number'); expect(generatedContent).toContain('booleanField?: boolean'); expect(generatedContent).toContain('nullField?: null'); expect(generatedContent).toContain('unknownField?: any'); // Check Zod types expect(generatedContent).toContain('stringField: z.string()'); expect(generatedContent).toContain('numberField: z.number()'); expect(generatedContent).toContain('booleanField: z.boolean().optional()'); expect(generatedContent).toContain('nullField: z.null().optional()'); expect(generatedContent).toContain('unknownField: z.any().optional()'); }); test('includes file header and imports', () => { const mockPrompt = { inputVariables: ['test'], metadata: { lc_hub_repo: 'test-prompt' }, toJSON: () => ({ kwargs: { schema_: { title: 'TestOutput', type: 'object', properties: { value: { type: 'string' }, }, }, }, }), }; generateTypes([mockPrompt], '/test/header-test.ts'); const generatedContent = mockedFs.writeFileSync.mock.calls[0][1]; expect(generatedContent).toContain('// Auto-generated file by @beparallel/langchain-ts. Do not edit.'); expect(generatedContent).toContain('// For more information, visit https://github.com/beparallel/langchain-ts'); expect(generatedContent).toContain('import { z } from "zod";'); }); test('handles empty prompts array', () => { generateTypes([], '/test/empty-output.ts'); const generatedContent = mockedFs.writeFileSync.mock.calls[0][1]; expect(generatedContent).toContain('export enum PromptName {'); expect(generatedContent).toContain('// Auto-generated file by @beparallel/langchain-ts. Do not edit.'); expect(generatedContent).toContain('import { z } from "zod";'); // Should not contain any specific prompt interfaces expect(generatedContent).not.toContain('export interface'); }); test('handles prompt with empty input variables', () => { const mockPrompt = { inputVariables: [], metadata: { lc_hub_repo: 'empty-vars-prompt' }, toJSON: () => ({ kwargs: { schema_: { title: 'EmptyVarsOutput', type: 'object', properties: { result: { type: 'string' }, }, }, }, }), }; generateTypes([mockPrompt], '/test/empty-vars-output.ts'); const generatedContent = mockedFs.writeFileSync.mock.calls[0][1]; expect(generatedContent).toContain('export interface EmptyVarsPromptVariable {'); // Should have empty interface body expect(generatedContent).toMatch(/export interface EmptyVarsPromptVariable \{\s*\}/); }); }); });