UNPKG

@firefliesai/schema-forge

Version:

Transform TypeScript classes into JSON Schema definitions with automatic support for OpenAI, Anthropic, and Google Gemini function calling (tool) formats

574 lines (573 loc) 30.5 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); require("reflect-metadata"); const lodash_1 = require("lodash"); const complex_class_tool_dto_1 = require("./fixture/complex-class.tool.dto"); const simple_class_tool_dto_1 = require("./fixture/simple-class.tool.dto"); const schema_forge_1 = require("./schema-forge"); describe('schema-forge test', () => { it('1 simple classes: classToJsonSchema, inheritance, classToJsonSchema with temp updated property, updateSchemaProperty (permanently)', async () => { const user2JsonSchemaTempChangeID2 = (0, schema_forge_1.classToJsonSchema)(simple_class_tool_dto_1.User2, { propertyOverrides: { id2: { description: 'temp updated id2 description' }, }, }); expect(user2JsonSchemaTempChangeID2).toMatchSnapshot('1-1 inheritance class: classToJsonSchema with temp updated property'); const user2JsonSchema = (0, schema_forge_1.classToJsonSchema)(simple_class_tool_dto_1.User2); expect(user2JsonSchema).toMatchSnapshot('1-2 inheritance class: classToJsonSchema and should not affected by temp updated property'); (0, schema_forge_1.updateSchemaProperty)(simple_class_tool_dto_1.User2, 'id2', { description: 'permanently updated id2 description', }); const user2JsonSchemaPersistChangeID2 = (0, schema_forge_1.classToJsonSchema)(simple_class_tool_dto_1.User2); expect(user2JsonSchemaPersistChangeID2).toMatchSnapshot('1-3 inheritance class: updateSchemaProperty desc (permanently) and classToJsonSchema'); const userJsonSchema = (0, schema_forge_1.classToJsonSchema)(simple_class_tool_dto_1.User); expect(userJsonSchema).toMatchSnapshot('1-4 parent class: classToJsonSchema (should not be affected by child class update)'); }); it('2 complex: array and enum class: classToJsonSchema, classToOpenAITool, updateSchemaProperty (permanently) w/ enum, ', async () => { const gameCharSchema = (0, schema_forge_1.classToJsonSchema)(complex_class_tool_dto_1.GameCharacter); expect(gameCharSchema).toMatchSnapshot('2-1 complex classToJsonSchema'); const gameCharTool = (0, schema_forge_1.classToOpenAITool)(complex_class_tool_dto_1.GameCharacter); expect(gameCharTool.type).toBe('function'); expect(gameCharTool.function.name).toBe(complex_class_tool_dto_1.GameCharacterToolName); expect(gameCharTool.function.description).toBe(complex_class_tool_dto_1.GameCharacterToolDesc); expect((0, lodash_1.isEqual)(gameCharTool.function.parameters, gameCharSchema)).toBe(true); /** updateSchemaProperty: enum, enum array */ (0, schema_forge_1.updateSchemaProperty)(complex_class_tool_dto_1.GameCharacter, 'roles', { enum: ['hero'], }); (0, schema_forge_1.updateSchemaProperty)(complex_class_tool_dto_1.GameCharacter, 'status', { enum: ['unknown'], }); (0, schema_forge_1.updateSchemaProperty)(complex_class_tool_dto_1.GameCharacter, 'level', { description: 'Updated level description', }); const gameCharUpdatedSchema = (0, schema_forge_1.classToJsonSchema)(complex_class_tool_dto_1.GameCharacter); gameCharSchema.properties.status.enum = ['unknown']; gameCharSchema.properties.roles.items.enum = ['hero']; gameCharSchema.properties.level.description = 'Updated level description'; expect((0, lodash_1.isEqual)(gameCharUpdatedSchema, gameCharSchema)).toBe(true); }); it('3 complex nested_object class: classToOpenAITool, updateSchemaProperty (permanently) w/ enum, ', async () => { const gameCharV2Tool = (0, schema_forge_1.classToOpenAITool)(complex_class_tool_dto_1.GameCharacterV2); expect(gameCharV2Tool).toMatchSnapshot('3-1 complex nested_object: classToOpenAITool'); (0, schema_forge_1.updateSchemaProperty)(complex_class_tool_dto_1.GameCharacterV2, 'banks.bankName', { description: 'New bankname description', }); const gameCharV2Tool2 = (0, schema_forge_1.classToOpenAITool)(complex_class_tool_dto_1.GameCharacterV2, { propertyOverrides: { location: { description: 'New location description', }, 'location.country': { description: 'New country description', }, }, }); gameCharV2Tool.function.parameters.properties.location.description = 'New location description'; gameCharV2Tool.function.parameters.properties.banks.items.properties.bankName.description = 'New bankname description'; gameCharV2Tool.function.parameters.properties.location.properties.country.description = 'New country description'; expect((0, lodash_1.isEqual)(gameCharV2Tool2, gameCharV2Tool)).toBe(true); }); it('4 complex nested nested three layer class: classToOpenAITool,updateSchemaProperty (permanently) w/ enum ', async () => { (0, schema_forge_1.updateSchemaProperty)(complex_class_tool_dto_1.FirstLevelDto, 'secondLevelObj.thirdLevelObjs.name', { enum: ['E', 'F', 'G', 'H'], }); const firstLevelDto = (0, schema_forge_1.classToOpenAITool)(complex_class_tool_dto_1.FirstLevelDto); expect(firstLevelDto).toMatchSnapshot('4-1 complex nested nested three layer class: updateSchemaProperty and classToOpenAITool'); }); it('5 addSchemaProperty case', async () => { class TicketLLMAnswer { } (0, schema_forge_1.addSchemaProperty)(TicketLLMAnswer, 'ticketTitle1', { type: 'string', description: 'answer', }); (0, schema_forge_1.addSchemaProperty)(TicketLLMAnswer, 'ticketTitle2', { enum: ['optionName1', 'optionName2'], isOptional: true, }); const ticketLLMAnswerSchema = (0, schema_forge_1.classToJsonSchema)(TicketLLMAnswer); expect(ticketLLMAnswerSchema).toMatchSnapshot('5-1 addSchemaProperty case'); }); it('6 class with ToolProp() case', async () => { class SimpleAnswer { } __decorate([ (0, schema_forge_1.ToolProp)(), __metadata("design:type", String) ], SimpleAnswer.prototype, "answer", void 0); const schema = (0, schema_forge_1.classToJsonSchema)(SimpleAnswer); expect(schema).toMatchSnapshot('6-1 class with ToolProp()'); }); it('7 structured output enhancement', async () => { // Test enhanced JSON Schema const userSchemaEnhanced = (0, schema_forge_1.classToJsonSchema)(simple_class_tool_dto_1.User, { forStructuredOutput: true }); expect(userSchemaEnhanced).toMatchSnapshot('7-1 enhanced JSON Schema'); // Test OpenAI function calling format const userToolEnhanced = (0, schema_forge_1.classToOpenAITool)(simple_class_tool_dto_1.User, { forStructuredOutput: true }); expect(userToolEnhanced).toMatchSnapshot('7-2 enhanced OpenAI function calling format'); // Test OpenAI response_format const userJsonSchemaFormat = (0, schema_forge_1.classToOpenAIResponseFormatJsonSchema)(simple_class_tool_dto_1.User, { forStructuredOutput: true, }); expect(userJsonSchemaFormat).toMatchSnapshot('7-3 OpenAI JSON Schema format for response_format'); }); it('8 different LLM formats', async () => { // Test Gemini tool format const geminiTool = (0, schema_forge_1.classToGeminiTool)(simple_class_tool_dto_1.User); expect(geminiTool).toMatchSnapshot('8-1 Gemini tool format'); // Test Anthropic tool format const anthropicTool = (0, schema_forge_1.classToAnthropicTool)(simple_class_tool_dto_1.User); expect(anthropicTool).toMatchSnapshot('8-2 Anthropic tool format'); // Test Gemini response schema const geminiResponseSchema = (0, schema_forge_1.classToGeminiResponseSchema)(simple_class_tool_dto_1.User); expect(geminiResponseSchema).toMatchSnapshot('8-3 Gemini response schema'); }); it('9 optional properties handling for different LLM providers', async () => { // Define nested DTOs first class AddressDto { } __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Street address' }), __metadata("design:type", String) ], AddressDto.prototype, "street", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'City name', isOptional: true }), __metadata("design:type", String) ], AddressDto.prototype, "city", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Postal code' }), __metadata("design:type", String) ], AddressDto.prototype, "postalCode", void 0); class ContactDto { } __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Contact name' }), __metadata("design:type", String) ], ContactDto.prototype, "name", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Contact email', isOptional: true }), __metadata("design:type", String) ], ContactDto.prototype, "email", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Phone number' }), __metadata("design:type", String) ], ContactDto.prototype, "phone", void 0); // Create a test class with optional properties including nested objects class TestWithOptionals { } __decorate([ (0, schema_forge_1.ToolProp)({ description: 'User ID' }), __metadata("design:type", String) ], TestWithOptionals.prototype, "id", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Username', isOptional: true }), __metadata("design:type", String) ], TestWithOptionals.prototype, "username", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Age of the user', isOptional: true }), __metadata("design:type", Number) ], TestWithOptionals.prototype, "age", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Tags for the user', items: { type: 'string' }, isOptional: true, }), __metadata("design:type", Array) ], TestWithOptionals.prototype, "tags", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Primary address information', isOptional: true }), __metadata("design:type", AddressDto) ], TestWithOptionals.prototype, "address", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'List of contact methods', items: { type: ContactDto }, isOptional: true, }), __metadata("design:type", Array) ], TestWithOptionals.prototype, "contacts", void 0); // Test OpenAI format handles optional properties with ["type", "null"] const openaiTool = (0, schema_forge_1.classToOpenAITool)(TestWithOptionals, { forStructuredOutput: true }); expect(openaiTool.function.parameters.properties.username.type).toEqual(['string', 'null']); expect(openaiTool.function.parameters.properties.age.type).toEqual(['number', 'null']); expect(openaiTool.function.parameters.properties.tags.type).toEqual(['array', 'null']); expect(openaiTool.function.parameters.properties.address.type).toEqual(['object', 'null']); expect(openaiTool.function.parameters.properties.contacts.type).toEqual(['array', 'null']); expect(openaiTool).toMatchSnapshot('9-1 OpenAI optional properties with nested objects'); // Test Gemini format properly marks properties as optional (not in required array) const geminiTool = (0, schema_forge_1.classToGeminiTool)(TestWithOptionals); // Check that optional properties are not in the required array expect(geminiTool.parameters.required).not.toContain('username'); expect(geminiTool.parameters.required).not.toContain('age'); expect(geminiTool.parameters.required).not.toContain('tags'); expect(geminiTool.parameters.required).not.toContain('address'); expect(geminiTool.parameters.required).not.toContain('contacts'); // Required properties should be in the required array expect(geminiTool.parameters.required).toContain('id'); expect(geminiTool).toMatchSnapshot('9-2 Gemini optional properties handling'); // Test Gemini response schema also properly marks properties as optional const geminiResponseSchema = (0, schema_forge_1.classToGeminiResponseSchema)(TestWithOptionals); // Check that optional properties are not in the required array expect(geminiResponseSchema.required).not.toContain('username'); expect(geminiResponseSchema.required).not.toContain('age'); expect(geminiResponseSchema.required).not.toContain('tags'); expect(geminiResponseSchema.required).not.toContain('address'); expect(geminiResponseSchema.required).not.toContain('contacts'); // Required properties should be in the required array expect(geminiResponseSchema.required).toContain('id'); expect(geminiResponseSchema).toMatchSnapshot('9-3 Gemini response schema optional properties handling'); // Verify that optionals are properly handled in nested objects expect(openaiTool.function.parameters.properties.address.properties.city.type).toEqual([ 'string', 'null', ]); // For Gemini, verify city is not in address's required array const addressProps = geminiTool.parameters.properties.address; expect(addressProps.properties.city).toBeDefined(); const addressRequired = addressProps.required || []; expect(addressRequired).not.toContain('city'); }); it('10 Date type support with format date-time', () => { // Test class with Date property class EventDto { } __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Event name' }), __metadata("design:type", String) ], EventDto.prototype, "name", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Event start date and time' }), __metadata("design:type", Date) ], EventDto.prototype, "startDate", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Event end date and time', isOptional: true }), __metadata("design:type", Date) ], EventDto.prototype, "endDate", void 0); // Test basic JSON Schema generation const schema = (0, schema_forge_1.classToJsonSchema)(EventDto); expect(schema.properties.startDate.type).toBe('string'); expect(schema.properties.startDate.format).toBe('date-time'); expect(schema.properties.endDate.type).toBe('string'); expect(schema.properties.endDate.format).toBe('date-time'); expect(schema).toMatchSnapshot('10-1 Date type JSON Schema'); // Test OpenAI tool format const openaiTool = (0, schema_forge_1.classToOpenAITool)(EventDto); expect(openaiTool.function.parameters.properties.startDate.type).toBe('string'); expect(openaiTool.function.parameters.properties.startDate.format).toBe('date-time'); expect(openaiTool).toMatchSnapshot('10-2 Date type OpenAI tool format'); // Test Gemini tool format const geminiTool = (0, schema_forge_1.classToGeminiTool)(EventDto); expect(geminiTool.parameters.properties.startDate.type).toBe('string'); expect(geminiTool.parameters.properties.startDate.format).toBe('date-time'); expect(geminiTool).toMatchSnapshot('10-3 Date type Gemini tool format'); // Test Anthropic tool format const anthropicTool = (0, schema_forge_1.classToAnthropicTool)(EventDto); expect(anthropicTool.input_schema.properties.startDate.type).toBe('string'); expect(anthropicTool.input_schema.properties.startDate.format).toBe('date-time'); expect(anthropicTool).toMatchSnapshot('10-4 Date type Anthropic tool format'); // Test structured output format (OpenAI) const structuredOutput = (0, schema_forge_1.classToOpenAITool)(EventDto, { forStructuredOutput: true }); // Note: format is stripped for OpenAI structured output as it's not supported expect(structuredOutput.function.parameters.properties.startDate.type).toBe('string'); expect(structuredOutput.function.parameters.properties.startDate.format).toBeUndefined(); expect(structuredOutput).toMatchSnapshot('10-5 Date type OpenAI structured output'); }); it('11 ToolProp validation constraint options', () => { // Note: This test verifies that ToolProp correctly passes through // validation constraint options (minimum, maximum, minLength, etc.) to the schema. // These options can be set manually or inferred from class-validator decorators. // Test class with manual schema properties class ValidationTestDto { } __decorate([ (0, schema_forge_1.ToolProp)({ description: 'User age', minimum: 0, maximum: 120, }), __metadata("design:type", Number) ], ValidationTestDto.prototype, "age", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'User name', minLength: 2, maxLength: 50, }), __metadata("design:type", String) ], ValidationTestDto.prototype, "name", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'User website', format: 'uri', }), __metadata("design:type", String) ], ValidationTestDto.prototype, "website", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'User tags', items: { type: 'string' }, minItems: 1, maxItems: 10, }), __metadata("design:type", Array) ], ValidationTestDto.prototype, "tags", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'User score', type: 'integer', minimum: 1, }), __metadata("design:type", Number) ], ValidationTestDto.prototype, "score", void 0); // Test basic JSON Schema generation with validation constraints const schema = (0, schema_forge_1.classToJsonSchema)(ValidationTestDto); // Verify number constraints expect(schema.properties.age.minimum).toBe(0); expect(schema.properties.age.maximum).toBe(120); // Verify string constraints expect(schema.properties.name.minLength).toBe(2); expect(schema.properties.name.maxLength).toBe(50); // Verify format expect(schema.properties.website.format).toBe('uri'); // Verify array constraints expect(schema.properties.tags.minItems).toBe(1); expect(schema.properties.tags.maxItems).toBe(10); // Verify integer type and positive constraint expect(schema.properties.score.type).toBe('integer'); expect(schema.properties.score.minimum).toBe(1); expect(schema).toMatchSnapshot('11-1 Validation constraints JSON Schema'); // Test OpenAI tool format const openaiTool = (0, schema_forge_1.classToOpenAITool)(ValidationTestDto); expect(openaiTool.function.parameters.properties.age.minimum).toBe(0); expect(openaiTool.function.parameters.properties.age.maximum).toBe(120); expect(openaiTool).toMatchSnapshot('11-2 Validation constraints OpenAI tool format'); // Test Gemini tool format const geminiTool = (0, schema_forge_1.classToGeminiTool)(ValidationTestDto); expect(geminiTool.parameters.properties.age.minimum).toBe(0); expect(geminiTool.parameters.properties.age.maximum).toBe(120); expect(geminiTool).toMatchSnapshot('11-3 Validation constraints Gemini tool format'); // Test Anthropic tool format const anthropicTool = (0, schema_forge_1.classToAnthropicTool)(ValidationTestDto); expect(anthropicTool.input_schema.properties.age.minimum).toBe(0); expect(anthropicTool.input_schema.properties.age.maximum).toBe(120); expect(anthropicTool).toMatchSnapshot('11-4 Validation constraints Anthropic tool format'); }); it('12 class-validator array integration: array constraints and each:true merge', () => { // Dynamic import to avoid test failure when class-validator is not installed let ArrayMinSize; let ArrayMaxSize; let Min; let Max; try { // eslint-disable-next-line @typescript-eslint/no-require-imports const cv = require('class-validator'); ArrayMinSize = cv.ArrayMinSize; ArrayMaxSize = cv.ArrayMaxSize; Min = cv.Min; Max = cv.Max; } catch { return; // Skip if class-validator not available } class ArrayIntegrationDto { } __decorate([ ArrayMinSize(1), ArrayMaxSize(5), (0, schema_forge_1.ToolProp)({ description: 'Tags with class-validator constraints', items: { type: 'string' } }), __metadata("design:type", Array) ], ArrayIntegrationDto.prototype, "tags", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Scores with Min/Max each', items: { type: 'number' } }), Min(0, { each: true }), Max(100, { each: true }), __metadata("design:type", Array) ], ArrayIntegrationDto.prototype, "scores", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'URLs with format', items: { type: 'string', format: 'uri' } }), __metadata("design:type", Array) ], ArrayIntegrationDto.prototype, "urls", void 0); const schema = (0, schema_forge_1.classToJsonSchema)(ArrayIntegrationDto); // ArrayMinSize/ArrayMaxSize should be inferred and applied expect(schema.properties.tags.type).toBe('array'); expect(schema.properties.tags.minItems).toBe(1); expect(schema.properties.tags.maxItems).toBe(5); expect(schema.properties.tags.items).toEqual({ type: 'string' }); // Min/Max with each:true should merge into items expect(schema.properties.scores.type).toBe('array'); expect(schema.properties.scores.items).toMatchObject({ type: 'number', minimum: 0, maximum: 100, }); // Format should be preserved in array items expect(schema.properties.urls.type).toBe('array'); expect(schema.properties.urls.items).toMatchObject({ type: 'string', format: 'uri', }); }); it('13 array items with format support', () => { class ArrayFormatDto { } __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Array of dates', items: { type: 'string', format: 'date-time' }, }), __metadata("design:type", Array) ], ArrayFormatDto.prototype, "dates", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Array of URLs', items: { type: 'string', format: 'uri' }, }), __metadata("design:type", Array) ], ArrayFormatDto.prototype, "urls", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Array of emails', items: { type: 'string', format: 'email' }, }), __metadata("design:type", Array) ], ArrayFormatDto.prototype, "emails", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Array of numbers with constraints', items: { type: 'number', minimum: 0, maximum: 100 }, }), __metadata("design:type", Array) ], ArrayFormatDto.prototype, "numbers", void 0); const schema = (0, schema_forge_1.classToJsonSchema)(ArrayFormatDto); // Verify format is preserved in array items expect(schema.properties.dates.type).toBe('array'); expect(schema.properties.dates.items).toEqual({ type: 'string', format: 'date-time', }); expect(schema.properties.urls.type).toBe('array'); expect(schema.properties.urls.items).toEqual({ type: 'string', format: 'uri', }); expect(schema.properties.emails.type).toBe('array'); expect(schema.properties.emails.items).toEqual({ type: 'string', format: 'email', }); expect(schema.properties.numbers.type).toBe('array'); expect(schema.properties.numbers.items).toEqual({ type: 'number', minimum: 0, maximum: 100, }); }); it('14 array items with constructor types auto-transformation', () => { class ConstructorTypesDto { } __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Array of dates using Date constructor', items: { type: Date }, }), __metadata("design:type", Array) ], ConstructorTypesDto.prototype, "dates", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Array of strings using String constructor', items: { type: String }, }), __metadata("design:type", Array) ], ConstructorTypesDto.prototype, "strings", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Array of numbers using Number constructor', items: { type: Number }, }), __metadata("design:type", Array) ], ConstructorTypesDto.prototype, "numbers", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Array of booleans using Boolean constructor', items: { type: Boolean }, }), __metadata("design:type", Array) ], ConstructorTypesDto.prototype, "booleans", void 0); __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Array of dates with format preserved', items: { type: Date, format: 'date-time' }, }), __metadata("design:type", Array) ], ConstructorTypesDto.prototype, "datesWithFormat", void 0); const schema = (0, schema_forge_1.classToJsonSchema)(ConstructorTypesDto); // Date constructor should be transformed to { type: 'string', format: 'date-time' } expect(schema.properties.dates.type).toBe('array'); expect(schema.properties.dates.items).toEqual({ type: 'string', format: 'date-time', }); // String constructor should be transformed to { type: 'string' } expect(schema.properties.strings.type).toBe('array'); expect(schema.properties.strings.items).toEqual({ type: 'string', }); // Number constructor should be transformed to { type: 'number' } expect(schema.properties.numbers.type).toBe('array'); expect(schema.properties.numbers.items).toEqual({ type: 'number', }); // Boolean constructor should be transformed to { type: 'boolean' } expect(schema.properties.booleans.type).toBe('array'); expect(schema.properties.booleans.items).toEqual({ type: 'boolean', }); // Date with explicit format should preserve format expect(schema.properties.datesWithFormat.type).toBe('array'); expect(schema.properties.datesWithFormat.items).toEqual({ type: 'string', format: 'date-time', }); }); it('15 updateSchemaProperty with constructor types in items', () => { class UpdateConstructorDto { } __decorate([ (0, schema_forge_1.ToolProp)({ description: 'Array property', items: { type: 'string' }, }), __metadata("design:type", Array) ], UpdateConstructorDto.prototype, "items", void 0); // Update with Date constructor (0, schema_forge_1.updateSchemaProperty)(UpdateConstructorDto, 'items', { items: { type: Date }, }); const schema = (0, schema_forge_1.classToJsonSchema)(UpdateConstructorDto); expect(schema.properties.items.items).toEqual({ type: 'string', format: 'date-time', }); // Update with Number constructor (0, schema_forge_1.updateSchemaProperty)(UpdateConstructorDto, 'items', { items: { type: Number }, }); const schema2 = (0, schema_forge_1.classToJsonSchema)(UpdateConstructorDto); expect(schema2.properties.items.items).toEqual({ type: 'number', }); }); });