diff --git a/packages/foundation/core/src/util.ts b/packages/foundation/core/src/util.ts index 2060a74c..0fd33e8a 100644 --- a/packages/foundation/core/src/util.ts +++ b/packages/foundation/core/src/util.ts @@ -110,7 +110,10 @@ export function convertIntrospectedSchemaToObjects( if (foreignKey) { // This is a lookup field + // Note: name must be set explicitly here since we're creating the config programmatically. + // When defined in YAML (ObjectConfig.fields Record), the name is auto-populated from the key. fieldConfig = { + name: column.name, type: 'lookup', reference_to: foreignKey.referencedTable, label: toTitleCase(column.name), @@ -120,7 +123,10 @@ export function convertIntrospectedSchemaToObjects( // Regular field const fieldType = mapDatabaseTypeToFieldType(column.type); + // Note: name must be set explicitly here since we're creating the config programmatically. + // When defined in YAML (ObjectConfig.fields Record), the name is auto-populated from the key. fieldConfig = { + name: column.name, type: fieldType, label: toTitleCase(column.name), required: !column.nullable @@ -133,7 +139,7 @@ export function convertIntrospectedSchemaToObjects( // Add max length for text fields if (column.maxLength && (fieldType === 'text' || fieldType === 'textarea')) { - fieldConfig.max_length = column.maxLength; + fieldConfig.maxLength = column.maxLength; } // Add default value diff --git a/packages/foundation/core/src/validator.ts b/packages/foundation/core/src/validator.ts index 0a70b858..bd66a351 100644 --- a/packages/foundation/core/src/validator.ts +++ b/packages/foundation/core/src/validator.ts @@ -190,11 +190,10 @@ export class Validator { } } - // Pattern validation (supports both pattern and deprecated regex) - const patternValue = validation.pattern ?? validation.regex; - if (patternValue) { + // Pattern validation + if (validation.pattern) { try { - const pattern = new RegExp(patternValue); + const pattern = new RegExp(validation.pattern); if (!pattern.test(String(value))) { results.push({ rule: `${fieldName}_pattern`, @@ -208,7 +207,7 @@ export class Validator { results.push({ rule: `${fieldName}_pattern`, valid: false, - message: `Invalid regex pattern: ${patternValue}`, + message: `Invalid regex pattern: ${validation.pattern}`, severity: 'error', fields: [fieldName], }); diff --git a/packages/foundation/core/test/util.test.ts b/packages/foundation/core/test/util.test.ts index 85677ec7..d3aba3a5 100644 --- a/packages/foundation/core/test/util.test.ts +++ b/packages/foundation/core/test/util.test.ts @@ -327,7 +327,7 @@ describe('Utility Functions', () => { const objects = convertIntrospectedSchemaToObjects(schema); const fields = objects[0].fields!; - expect(fields.short_text.max_length).toBe(100); + expect(fields.short_text.maxLength).toBe(100); }); it('should add default value when present', () => { diff --git a/packages/foundation/types/package.json b/packages/foundation/types/package.json index 43073492..1ff6f460 100644 --- a/packages/foundation/types/package.json +++ b/packages/foundation/types/package.json @@ -22,10 +22,13 @@ "schemas" ], "scripts": { - "build": "tsc && npm run generate:schemas", + "build": "tsc", "generate:schemas": "node scripts/generate-schemas.js", "test": "jest --passWithNoTests" }, + "dependencies": { + "@objectstack/spec": "^0.1.1" + }, "devDependencies": { "ts-json-schema-generator": "^2.4.0" } diff --git a/packages/foundation/types/src/action.ts b/packages/foundation/types/src/action.ts index efa908fd..aead8890 100644 --- a/packages/foundation/types/src/action.ts +++ b/packages/foundation/types/src/action.ts @@ -6,9 +6,21 @@ * LICENSE file in the root directory of this source tree. */ +// Import and re-export types from the Protocol Constitution (@objectstack/spec) +import type { Action } from '@objectstack/spec'; import { FieldConfig } from "./field"; import { HookAPI } from "./hook"; // Reuse the restricted API interface +/** + * Re-export Protocol Types from the Constitution + */ +export type { Action as SpecAction }; + +/** + * RUNTIME-SPECIFIC TYPES + * The following types extend the Protocol Action definition with runtime execution capabilities + */ + /** * Defines the scope of the action. * - `record`: Acts on a specific record instance (e.g. "Approve Order"). @@ -24,6 +36,8 @@ export type ActionInputDefinition = Record; /** * Context passed to the action handler execution. + * + * RUNTIME TYPE: Used during action execution. */ export interface ActionContext { /** The object this action belongs to. */ @@ -58,11 +72,19 @@ export interface ActionContext { } /** + * Runtime Action Configuration + * * The configuration of an Action visible to the Metadata engine (YAML/JSON side). + * Compatible with Protocol Action but adds runtime-specific options. */ export interface ActionConfig { + /** Display label */ label?: string; + + /** Description */ description?: string; + + /** Icon name */ icon?: string; /** @@ -93,6 +115,8 @@ export interface ActionConfig { /** * The full implementation definition (Code side). + * + * RUNTIME TYPE: Includes the handler function for execution. */ export interface ActionDefinition extends ActionConfig { /** diff --git a/packages/foundation/types/src/field.ts b/packages/foundation/types/src/field.ts index ee3f5d80..1ed0b8be 100644 --- a/packages/foundation/types/src/field.ts +++ b/packages/foundation/types/src/field.ts @@ -6,11 +6,26 @@ * LICENSE file in the root directory of this source tree. */ -import { FieldValidation, ValidationAiContext } from './validation'; +// Import types from the Protocol Constitution (@objectstack/spec) +import type { FieldType as ProtocolFieldType, Field, SelectOption as SpecSelectOption } from '@objectstack/spec'; + +/** + * Re-export Protocol Types from the Constitution + * These are the wire-protocol standard types defined in @objectstack/spec + */ +export type { Field as SpecField, SpecSelectOption, ProtocolFieldType }; + +/** + * RUNTIME-SPECIFIC TYPES + * The following types extend or complement the Protocol Constitution + * with runtime-specific properties that don't belong in the wire protocol. + */ /** * Attachment field data structure for file and image types. * Stores metadata about uploaded files, with actual file content stored separately. + * + * This is a RUNTIME type - not part of the wire protocol. */ export interface AttachmentData { /** Unique identifier for this file */ @@ -41,6 +56,8 @@ export interface AttachmentData { /** * Image-specific attachment data with additional metadata. * Extends AttachmentData with image-specific properties. + * + * This is a RUNTIME type - not part of the wire protocol. */ export interface ImageAttachmentData extends AttachmentData { /** Image width in pixels */ @@ -61,111 +78,129 @@ export interface ImageAttachmentData extends AttachmentData { } /** - * Represents the supported field data types in the ObjectQL schema. - * These types determine how data is stored, validated, and rendered. + * Runtime Field Type * - * - `text`: Simple string. - * - `textarea`: Long string. - * - `select`: Choice from a list. - * - `lookup`: Relationship to another object. - * - `file`: File attachment. Value stored as AttachmentData (single) or AttachmentData[] (multiple). - * - `image`: Image attachment. Value stored as ImageAttachmentData (single) or ImageAttachmentData[] (multiple). + * Extends the Protocol FieldType with runtime-specific types. + * The Protocol Constitution defines the core field types. + * We add runtime-specific types like 'vector', 'grid', 'location', 'object' here. */ export type FieldType = - | 'text' - | 'textarea' - | 'markdown' - | 'html' - | 'select' - | 'date' - | 'datetime' - | 'time' - | 'number' - | 'currency' - | 'percent' - | 'boolean' - | 'email' - | 'phone' - | 'url' - | 'image' - | 'file' - | 'location' - | 'lookup' - | 'master_detail' - | 'password' - | 'formula' - | 'summary' - | 'auto_number' - | 'object' - | 'vector' - | 'grid'; + | ProtocolFieldType + | 'location' // Runtime: Geographic location + | 'object' // Runtime: Nested object/JSON + | 'vector' // Runtime: Vector embeddings for AI + | 'grid'; // Runtime: Inline grid/table /** - * Defines a single option for select/multiselect fields. + * Runtime Field Option + * + * Extends the Protocol SelectOption to allow number values (for backwards compatibility). */ export interface FieldOption { /** The display label for the option. */ label: string; /** The actual value stored in the database. */ value: string | number; + /** Optional color for visual representation */ + color?: string; + /** Whether this is the default option */ + default?: boolean; } /** - * Configuration for a single field on an object. - * This defines the schema, validation rules, and UI hints for the attribute. + * Runtime Field Configuration + * + * Extends the Protocol Field definition with runtime-specific properties. + * The Protocol Constitution (SpecField) defines the core field schema. + * This adds runtime conveniences and extensions. + * + * Properties from the protocol Field interface that are re-declared here: + * - `name`, `label`: Made optional for runtime convenience (auto-populated from Record key) + * - `type`: Extended union type to include runtime-specific types (vector, grid, location, object) + * - `options`: Extended to allow numeric values for backwards compatibility + * - Boolean flags (`required`, `multiple`, `unique`, `hidden`, `readonly`, `encryption`, `index`, `externalId`): + * Made optional since Zod applies defaults at parse time, but runtime usage typically specifies these explicitly + * - `deleteBehavior`: Made optional with protocol-compatible enum values + * + * All other protocol properties (description, defaultValue, maxLength, minLength, precision, scale, min, max, + * reference, referenceFilters, writeRequiresMasterRead, expression, formula, summaryOperations) are inherited as-is. */ -export interface FieldConfig { - /** - * The unique API name of the field. - * If defined within an object map, this is often automatically populated from the key. - */ +export interface FieldConfig extends Omit { + /** Field name (inferred from Record key when used in ObjectConfig.fields) */ name?: string; - /** The human-readable label used in UIs. */ + /** Display label (derived from name if not provided) */ label?: string; - - /** Description of the field for documentation or tooltip. */ - description?: string; - /** The data type of the field. */ + /** The data type of the field (extended with runtime types) */ type: FieldType; + /** Options for select fields (extended to allow number values) */ + options?: FieldOption[]; + /** Whether the field is mandatory. Defaults to false. */ required?: boolean; - + + /** Whether the field allows multiple values. */ + multiple?: boolean; + /** Whether the field is unique in the table. */ unique?: boolean; - - /** Whether to create a database index for this field. */ - index?: boolean; - - /** Whether the field is read-only in UI. */ - readonly?: boolean; - + + /** Delete behavior for relationships */ + deleteBehavior?: 'set_null' | 'cascade' | 'restrict'; + /** Whether the field is hidden from default UI/API response. */ hidden?: boolean; - /** The default value if not provided during creation. */ - defaultValue?: any; - - /** Tooltip or help text for the user. */ - help_text?: string; + /** Whether the field is read-only in UI. */ + readonly?: boolean; - /** - * Whether the field allows multiple values. - * Supported by 'select', 'lookup', 'file', 'image'. - */ - multiple?: boolean; + /** Whether the field is encrypted */ + encryption?: boolean; - /** - * Options for select fields. - * List of available choices for select/multiselect fields. + /** Whether to create a database index for this field. */ + index?: boolean; + + /** Whether this is an external ID field */ + externalId?: boolean; + + /** + * RUNTIME EXTENSIONS BELOW + * These properties are NOT in the wire protocol but are useful for the runtime. */ - options?: FieldOption[]; + + /** Tooltip or help text for the user. */ + help_text?: string; /** * Reference to another object for lookup/master_detail fields. - * Specifies the target object name for relationship fields. + * + * @deprecated Legacy alias for the protocol-level `reference` field (inherited from {@link Field}). + * + * **Migration Guidance:** + * - **New schemas MUST** use the `reference` field (defined in the protocol Field interface) instead of `reference_to`. + * - **During migration**, engines and tooling SHOULD: + * - Treat `reference_to` as a fallback alias for `reference` (i.e., if `reference` is undefined and `reference_to` + * is defined, behave as though `reference` were set to the same value). + * - Prefer the protocol `reference` value when both are present. + * - This property is retained only for backward compatibility with older *.object.yml definitions and will be + * removed in a future major version once all callers have migrated to `reference`. + * + * @example + * ```yaml + * # Old (deprecated): + * fields: + * owner: + * type: lookup + * reference_to: users + * + * # New (protocol-compliant): + * fields: + * owner: + * type: lookup + * reference: users + * ``` */ reference_to?: string; @@ -206,37 +241,47 @@ export interface FieldConfig { */ min_height?: number; - // Validation properties - /** Minimum for number/currency/percent. */ - min?: number; - /** Maximum for number/currency/percent. */ - max?: number; - /** Minimum length for text based fields. */ - min_length?: number; - /** Maximum length for text based fields. */ - max_length?: number; - /** Regular expression pattern for validation. */ - regex?: string; - /** * Field validation configuration. * Defines validation rules applied at the field level. */ - validation?: FieldValidation; + validation?: { + /** Format validation (email, url, etc.) */ + format?: 'email' | 'url' | 'phone' | 'date' | 'datetime'; + /** Allowed protocols for URL validation */ + protocols?: string[]; + /** Minimum value for numbers */ + min?: number; + /** Maximum value for numbers */ + max?: number; + /** Minimum length for strings */ + min_length?: number; + /** Maximum length for strings */ + max_length?: number; + /** + * Regular expression pattern for validation. + * Use this for custom pattern matching (e.g., /^[A-Z]{2}\d{4}$/ for codes). + */ + pattern?: string; + /** Custom validation message */ + message?: string; + }; /** * AI context for the field. * Provides semantic information for AI tools. */ - ai_context?: ValidationAiContext; + ai_context?: { + intent?: string; + validation_strategy?: string; + [key: string]: unknown; + }; - // Vector properties + // Vector properties (runtime-specific) /** Dimension of the vector for 'vector' type fields. */ dimension?: number; - // Formula properties - /** Formula expression (for 'formula' type fields). */ - formula?: string; + // Formula properties (extended from protocol) /** Expected return data type for formula fields. */ data_type?: 'number' | 'text' | 'date' | 'datetime' | 'boolean' | 'currency' | 'percent'; /** Display format for formula results (e.g., "0.00", "YYYY-MM-DD"). */ @@ -248,12 +293,13 @@ export interface FieldConfig { /** Default value for null/undefined referenced fields in formulas. */ treat_blank_as?: string | number | boolean | Date | null; - // Summary properties + // Summary properties (extended from protocol) /** Object to summarize. */ summary_object?: string; /** Field on the summary object. */ summary_field?: string; /** Type of summary (count, sum, min, max, avg). */ summary_type?: string; + /** Filters for summary */ filters?: unknown[]; } diff --git a/packages/foundation/types/src/index.ts b/packages/foundation/types/src/index.ts index 7cb938a2..e2c1712b 100644 --- a/packages/foundation/types/src/index.ts +++ b/packages/foundation/types/src/index.ts @@ -6,6 +6,13 @@ * LICENSE file in the root directory of this source tree. */ +/** + * Export our runtime types. + * + * These modules import and extend types from @objectstack/spec where needed. + * Users of @objectql/types should import from here to get both protocol types + * and runtime extensions. + */ export * from './field'; export * from './object'; export * from './driver'; diff --git a/packages/foundation/types/src/object.ts b/packages/foundation/types/src/object.ts index 7756be42..c53d99e9 100644 --- a/packages/foundation/types/src/object.ts +++ b/packages/foundation/types/src/object.ts @@ -6,17 +6,42 @@ * LICENSE file in the root directory of this source tree. */ +// Import and re-export types from the Protocol Constitution (@objectstack/spec) +import type { ServiceObject, IndexSchema } from '@objectstack/spec'; import { FieldConfig } from './field'; import { ActionConfig } from './action'; import { AnyValidationRule } from './validation'; +/** + * Re-export Protocol Types from the Constitution + */ +export type { ServiceObject as SpecObject, IndexSchema }; + +/** + * RUNTIME-SPECIFIC TYPES + * The following types extend or complement the Protocol Constitution + */ + +/** + * Index Configuration (compatible with Protocol) + * + * The Protocol defines IndexSchema. We provide a simpler alias for runtime convenience. + */ export interface IndexConfig { + /** Index name (optional, auto-generated if not provided) */ + name?: string; /** List of fields involved in the index */ fields: string[]; /** Whether the index enforces uniqueness */ unique?: boolean; } +/** + * AI Search Configuration + * + * RUNTIME EXTENSION: Not part of the wire protocol. + * Defines semantic search capabilities for the runtime engine. + */ export interface AiSearchConfig { /** Enable semantic search for this object */ enabled: boolean; @@ -28,24 +53,90 @@ export interface AiSearchConfig { target_field?: string; } +/** + * Object AI Configuration + * + * RUNTIME EXTENSION: Not part of the wire protocol. + */ export interface ObjectAiConfig { /** Configuration for semantic search / RAG */ search?: AiSearchConfig; } +/** + * Runtime Object Configuration + * + * Extends the Protocol ServiceObject with runtime-specific properties. + * The Protocol Constitution (SpecObject) defines the core object schema. + * This adds runtime conveniences like actions, AI config, and validation rules. + */ export interface ObjectConfig { + /** Object name (required by Protocol) */ name: string; - datasource?: string; // The name of the datasource to use + + /** Display label */ label?: string; - icon?: string; + + /** Plural label */ + pluralLabel?: string; + + /** Description */ description?: string; + /** Icon identifier */ + icon?: string; + + /** Datasource name (defaults to 'default') */ + datasource?: string; + + /** Custom table name (defaults to object name) */ + tableName?: string; + + /** Whether this is a system object */ + isSystem?: boolean; + + /** + * Field definitions + * Maps field names to their configurations + */ fields: Record; + + /** + * Index definitions + * Maps index names to their configurations + */ indexes?: Record; - /** AI capabilities configuration */ + + /** Primary display field */ + nameField?: string; + + /** Object capabilities (from Protocol) */ + enable?: { + /** Enable history tracking (Audit Trail) */ + trackHistory?: boolean; + /** Enable global search indexing */ + searchable?: boolean; + /** Enable REST/GraphQL API access */ + apiEnabled?: boolean; + /** Enable attachments/files */ + files?: boolean; + /** Enable discussions/chatter */ + feedEnabled?: boolean; + /** Enable Recycle Bin mechanics */ + trash?: boolean; + }; + + /** + * RUNTIME EXTENSIONS BELOW + */ + + /** AI capabilities configuration (RUNTIME ONLY) */ ai?: ObjectAiConfig; + + /** Custom actions (RUNTIME ONLY) */ actions?: Record; - /** Validation rules for this object */ + + /** Validation rules for this object (RUNTIME ONLY) */ validation?: { /** Validation rules */ rules?: AnyValidationRule[]; @@ -59,6 +150,8 @@ export interface ObjectConfig { /** * Base interface for all ObjectQL documents. + * + * RUNTIME TYPE: Represents a document instance in the database. */ export interface ObjectDoc { _id?: string | number; diff --git a/packages/runtime/server/src/adapters/graphql.ts b/packages/runtime/server/src/adapters/graphql.ts index d79521be..160c2b41 100644 --- a/packages/runtime/server/src/adapters/graphql.ts +++ b/packages/runtime/server/src/adapters/graphql.ts @@ -76,7 +76,7 @@ function mapFieldTypeToGraphQL(field: FieldConfig, isInput: boolean = false): Gr case 'currency': case 'percent': return GraphQLFloat; - case 'auto_number': + case 'autonumber': return GraphQLInt; case 'boolean': return GraphQLBoolean; diff --git a/packages/runtime/server/src/metadata.ts b/packages/runtime/server/src/metadata.ts index 66e07834..ae655f6e 100644 --- a/packages/runtime/server/src/metadata.ts +++ b/packages/runtime/server/src/metadata.ts @@ -197,9 +197,9 @@ export function createMetadataHandler(app: IObjectQL, options?: MetadataHandlerO options: field.options, min: field.min, max: field.max, - min_length: field.min_length, - max_length: field.max_length, - regex: field.regex + minLength: field.minLength, + maxLength: field.maxLength, + validation: field.validation }); } diff --git a/packages/tools/cli/src/commands/generate.ts b/packages/tools/cli/src/commands/generate.ts index e40d7787..6d1975e9 100644 --- a/packages/tools/cli/src/commands/generate.ts +++ b/packages/tools/cli/src/commands/generate.ts @@ -107,7 +107,7 @@ function mapFieldTypeToTs(field: FieldConfig): string { case 'number': case 'currency': case 'percent': - case 'auto_number': + case 'autonumber': return 'number'; case 'boolean': diff --git a/packages/tools/cli/src/commands/sync.ts b/packages/tools/cli/src/commands/sync.ts index 0bf0de1e..fa17e9f9 100644 --- a/packages/tools/cli/src/commands/sync.ts +++ b/packages/tools/cli/src/commands/sync.ts @@ -189,9 +189,9 @@ function generateObjectDefinition(table: IntrospectedTable, schema: Introspected field.unique = true; } - // Add max_length for text-based fields + // Add maxLength for text-based fields if (column.maxLength && (fieldType === 'text' || fieldType === 'textarea')) { - field.max_length = column.maxLength; + field.maxLength = column.maxLength; } if (column.defaultValue !== undefined && column.defaultValue !== null) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5eb67951..62b9b99e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -428,6 +428,10 @@ importers: version: 5.9.3 packages/foundation/types: + dependencies: + '@objectstack/spec': + specifier: ^0.1.1 + version: 0.1.1 devDependencies: ts-json-schema-generator: specifier: ^2.4.0