π§© Schemas & Records β
@tellia-solutions/schemas is the single source of truth for types, validation, and UI metadata shared between frontend, backend, and AI agents.
Location: packages/schemas/Build: pnpm --filter @tellia-solutions/schemas build (outputs CJS + ESM + .d.ts)
Structure β
packages/schemas/src/
properties/ β field type system (value schemas, property schemas, registry)
records/ β parse result schemas + field validation
common/ β cross-cutting infra (MongoId, auth, permissions, language)
workspace.ts β workspace + tableHeader schemas
company.ts
workspace-assistant/properties/ is the core of the type system. Everything else consumes it.
How property types work β
Each type is a file in properties/ that exports two things:
- A value schema β what the raw value looks like (
z.string(),z.number(), etc.) - A property schema β the full field definition (
type,name,displayName, config options)
Value schemas carry metadata via Zod's .meta():
export const StringSchema = z.string().meta({
id: 'string',
label: 'Text',
iconName: 'TextFields',
description: 'Single line or multi-line text',
category: 'primitive',
needsConfiguration: false,
active: true,
suggestFor: ['name', 'title', 'description'],
} satisfies PropertyTypeMetadata);active: true means the type appears in the property picker UI. Set it to false for types that exist but shouldn't be user-selectable (e.g. email, address).
All value schemas are collected in ALL_PROPERTY_SCHEMAS in properties/meta.ts. All property schemas are collected in ALL_PROPERTY_TYPE_SCHEMAS in properties/all.ts. These two lists are the only place you register a new type β everything else (the UI picker, the discriminated union, the validation map) derives from them automatically.
Adding a new property type β
- Create
properties/<type-name>.tsβ follow any existing type file as the pattern - Add the value schema to
ALL_PROPERTY_SCHEMASinproperties/meta.ts - Add the property schema to
ALL_PROPERTY_TYPE_SCHEMASinproperties/all.ts - Export from
properties/index.ts
That's it. The UI picker, TableHeaderFieldSchema, CanonicalFieldValueSchema, and the field validator all update automatically.
name vs displayName β
| Field | Purpose | Format |
|---|---|---|
name | JSON key, internal | camelCase |
displayName | UI label, any format | free text |
createPropertySchema auto-derives displayName from name if not provided. Use normalizePropertyName when you need to convert user input to a valid name.
TableHeaderFieldSchema β
A workspace's tableHeader is an array of field definitions. TableHeaderFieldSchema is a discriminated union over ALL_PROPERTY_TYPE_SCHEMAS β TypeScript narrows to the exact type when you check field.type:
if (field.type === 'array') {
field.arrayType; // available, type-safe
}Adding a new property type extends this union automatically.
Multi-Tenancy Base β
All company-scoped documents extend CompanyAwareBase (not a Zod schema β a Mongoose abstract class):
abstract class CompanyAwareBase {
companyId: Types.ObjectId; // Tenant isolation
visibility: 'private' | 'company' | 'public';
sharedWithCompanies: Types.ObjectId[];
sharedWithUsers: Types.ObjectId[];
}π Related Files β
Records & parse results β
A parse result is a MongoDB document that mixes system fields (workspaceId, userId, β¦) with dynamic workspace fields β whatever the AI extracted. The dynamic fields use .catchall(CanonicalFieldValueSchema), a union of all value schemas plus null and a generic object fallback.
This means any canonical value type is accepted at the DB layer without explicit field declaration.
Validating AI-extracted fields β
Before storing an AI parse result, validate the dynamic fields against the workspace tableHeader:
import { validateParseResultFields } from '@tellia-solutions/schemas';
const result = validateParseResultFields(aiFields, workspace.tableHeader);
if (!result.valid) {
// Feed result.errors back to the agent for self-correction
// Each error has: field, declaredType, value, message
}This runs the type-appropriate Zod schema for each declared field. null/undefined values are always valid (cleared field). Unknown fields in the record are ignored β they pass through the .catchall() at the DB layer.
Related β
properties/meta.tsβ registry,ALL_PROPERTY_SCHEMAS, helpersproperties/all.tsβTableHeaderFieldSchema,ALL_PROPERTY_TYPE_SCHEMASrecords/validate-parse-result.tsβ field validator- Frontend icon map:
apps/agri-frontend/src/libs/propertyTypes.icons.tsβ mapsiconNameto MUI components