Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/workflow-executor/src/types/validated/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof FieldSchemaSchema>;
Expand Down
49 changes: 49 additions & 0 deletions packages/workflow-executor/test/types/collection.test.ts
Original file line number Diff line number Diff line change
@@ -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]);
});
});
Loading