diff --git a/packages/workflow-executor/src/types/validated/collection.ts b/packages/workflow-executor/src/types/validated/collection.ts index c6c49414b6..801423d384 100644 --- a/packages/workflow-executor/src/types/validated/collection.ts +++ b/packages/workflow-executor/src/types/validated/collection.ts @@ -52,7 +52,10 @@ export const FieldSchemaSchema = z.object({ // Polymorphic relations: discriminator column + candidate target collections, resolved per record. polymorphicTypeField: z.string().optional(), polymorphicReferencedModels: z.array(z.string()).optional(), - type: ColumnTypeSchema.nullable().optional(), + // forest-rails apimaps carry field types this contract can't model — hand-authored smart-field + // type strings, and `[null]` from array columns the liana fails to map. They must not fail the + // whole collection schema; an unparseable type normalizes to null (consumers already guard on it). + type: ColumnTypeSchema.nullable().optional().catch(null), enumValues: z.array(z.string()).min(1).optional(), }); export type FieldSchema = z.infer; diff --git a/packages/workflow-executor/test/types/collection.test.ts b/packages/workflow-executor/test/types/collection.test.ts new file mode 100644 index 0000000000..b2ca376252 --- /dev/null +++ b/packages/workflow-executor/test/types/collection.test.ts @@ -0,0 +1,49 @@ +import { CollectionSchemaSchema, FieldSchemaSchema } from '../../src/types/validated/collection'; + +describe('FieldSchemaSchema type tolerance', () => { + const base = { + fieldName: 'col', + displayName: 'Col', + isRelationship: false, + }; + + it('parses a primitive type', () => { + expect(FieldSchemaSchema.parse({ ...base, type: 'String' }).type).toBe('String'); + }); + + it('normalizes a non-primitive smart-field type string to null', () => { + expect(FieldSchemaSchema.parse({ ...base, type: 'DateTime' }).type).toBeNull(); + }); + + it('normalizes a [null] array-column type to null', () => { + expect(FieldSchemaSchema.parse({ ...base, type: [null] }).type).toBeNull(); + }); + + it('keeps an explicit null type as null', () => { + expect(FieldSchemaSchema.parse({ ...base, type: null }).type).toBeNull(); + }); + + it('keeps a missing type as undefined', () => { + expect(FieldSchemaSchema.parse(base).type).toBeUndefined(); + }); +}); + +describe('CollectionSchemaSchema field type tolerance', () => { + const collection = { + collectionName: 'orders', + collectionId: 'orders', + collectionDisplayName: 'Orders', + primaryKeyFields: ['id'], + fields: [ + { fieldName: 'id', displayName: 'Id', isRelationship: false, type: 'Number' }, + { fieldName: 'computed', displayName: 'Computed', isRelationship: false, type: 'DateTime' }, + { fieldName: 'tags', displayName: 'Tags', isRelationship: false, type: [null] }, + ], + }; + + it('parses the collection instead of rejecting it, dropping unparseable types to null', () => { + const parsed = CollectionSchemaSchema.parse(collection); + + expect(parsed.fields.map(f => f.type)).toEqual(['Number', null, null]); + }); +});