diff --git a/ROADMAP.md b/ROADMAP.md index b1570faa0..e2e96fc27 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -395,6 +395,10 @@ Each plugin view must work seamlessly from 320px (small phone) to 2560px (ultraw - [x] Increase touch targets for all form controls (min 44×44px) - [x] Optimize select/dropdown fields for mobile (bottom sheet pattern on phones) - [x] Ensure date pickers and multi-select fields are mobile-friendly +- [x] Auto-Layout: infer optimal columns from field count (≤3 → 1 col, ≥4 → 2 cols) +- [x] Auto-Layout: smart colSpan for wide fields (textarea/markdown/html/grid → full row) +- [x] Auto-Layout: filter auto-generated fields (formula/summary/auto_number) in create mode +- [x] Auto-Layout: user configuration always takes priority over inferred defaults ##### ObjectDashboard (`plugin-dashboard`) - [x] Implement responsive grid: `grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4` diff --git a/packages/plugin-form/src/ObjectForm.tsx b/packages/plugin-form/src/ObjectForm.tsx index f061aa80d..267ec0dc2 100644 --- a/packages/plugin-form/src/ObjectForm.tsx +++ b/packages/plugin-form/src/ObjectForm.tsx @@ -23,6 +23,7 @@ import { SplitForm } from './SplitForm'; import { DrawerForm } from './DrawerForm'; import { ModalForm } from './ModalForm'; import { FormSection } from './FormSection'; +import { applyAutoLayout } from './autoLayout'; export interface ObjectFormProps { /** @@ -575,12 +576,18 @@ const SimpleObjectForm: React.FC = ({ ); } + // Apply auto-layout: infer columns and colSpan when not explicitly configured + const hasSections = schema.sections?.length; + const autoLayoutResult = !hasSections + ? applyAutoLayout(formFields, objectSchema, schema.columns, schema.mode) + : { fields: formFields, columns: schema.columns }; + // Default flat form (no sections) const formSchema: FormSchema = { type: 'form', - fields: formFields, + fields: autoLayoutResult.fields, layout: formLayout, - columns: schema.columns, + columns: autoLayoutResult.columns, submitLabel: schema.submitText || (schema.mode === 'create' ? 'Create' : 'Update'), cancelLabel: schema.cancelText, showSubmit: schema.showSubmit !== false && schema.mode !== 'view', diff --git a/packages/plugin-form/src/__tests__/autoLayout.test.ts b/packages/plugin-form/src/__tests__/autoLayout.test.ts new file mode 100644 index 000000000..c59891a59 --- /dev/null +++ b/packages/plugin-form/src/__tests__/autoLayout.test.ts @@ -0,0 +1,321 @@ +import { describe, it, expect } from 'vitest'; +import { + isWideFieldType, + isAutoGeneratedFieldType, + inferColumns, + applyAutoColSpan, + filterCreateModeFields, + applyAutoLayout, +} from '../autoLayout'; +import type { FormField } from '@object-ui/types'; + +describe('autoLayout', () => { + describe('isWideFieldType', () => { + it('returns true for wide form field types', () => { + expect(isWideFieldType('field:textarea')).toBe(true); + expect(isWideFieldType('field:markdown')).toBe(true); + expect(isWideFieldType('field:html')).toBe(true); + expect(isWideFieldType('field:grid')).toBe(true); + expect(isWideFieldType('field:rich-text')).toBe(true); + }); + + it('returns true for raw wide field types', () => { + expect(isWideFieldType('textarea')).toBe(true); + expect(isWideFieldType('markdown')).toBe(true); + expect(isWideFieldType('html')).toBe(true); + expect(isWideFieldType('grid')).toBe(true); + expect(isWideFieldType('rich-text')).toBe(true); + }); + + it('returns false for narrow field types', () => { + expect(isWideFieldType('field:text')).toBe(false); + expect(isWideFieldType('field:number')).toBe(false); + expect(isWideFieldType('field:select')).toBe(false); + expect(isWideFieldType('text')).toBe(false); + expect(isWideFieldType('boolean')).toBe(false); + }); + }); + + describe('isAutoGeneratedFieldType', () => { + it('returns true for auto-generated types', () => { + expect(isAutoGeneratedFieldType('formula')).toBe(true); + expect(isAutoGeneratedFieldType('summary')).toBe(true); + expect(isAutoGeneratedFieldType('auto_number')).toBe(true); + expect(isAutoGeneratedFieldType('autonumber')).toBe(true); + }); + + it('returns false for user-editable types', () => { + expect(isAutoGeneratedFieldType('text')).toBe(false); + expect(isAutoGeneratedFieldType('number')).toBe(false); + expect(isAutoGeneratedFieldType('select')).toBe(false); + }); + }); + + describe('inferColumns', () => { + it('returns 1 column for 0 fields', () => { + expect(inferColumns(0)).toBe(1); + }); + + it('returns 1 column for 1-3 fields', () => { + expect(inferColumns(1)).toBe(1); + expect(inferColumns(2)).toBe(1); + expect(inferColumns(3)).toBe(1); + }); + + it('returns 2 columns for 4+ fields', () => { + expect(inferColumns(4)).toBe(2); + expect(inferColumns(8)).toBe(2); + expect(inferColumns(20)).toBe(2); + }); + }); + + describe('applyAutoColSpan', () => { + it('returns fields unchanged when columns is 1', () => { + const fields: FormField[] = [ + { name: 'a', label: 'A', type: 'field:textarea' }, + ]; + const result = applyAutoColSpan(fields, 1); + expect(result).toEqual(fields); + }); + + it('sets colSpan for wide fields in multi-column layout', () => { + const fields: FormField[] = [ + { name: 'name', label: 'Name', type: 'field:text' }, + { name: 'desc', label: 'Description', type: 'field:textarea' }, + { name: 'notes', label: 'Notes', type: 'field:markdown' }, + ]; + const result = applyAutoColSpan(fields, 2); + + expect(result[0].colSpan).toBeUndefined(); + expect(result[1].colSpan).toBe(2); + expect(result[2].colSpan).toBe(2); + }); + + it('does not override user-defined colSpan', () => { + const fields: FormField[] = [ + { name: 'desc', label: 'Description', type: 'field:textarea', colSpan: 1 }, + ]; + const result = applyAutoColSpan(fields, 2); + expect(result[0].colSpan).toBe(1); + }); + + it('does not mutate original fields', () => { + const fields: FormField[] = [ + { name: 'desc', label: 'Description', type: 'field:textarea' }, + ]; + const result = applyAutoColSpan(fields, 2); + expect(fields[0].colSpan).toBeUndefined(); + expect(result[0].colSpan).toBe(2); + }); + }); + + describe('filterCreateModeFields', () => { + const objectSchema = { + name: 'test', + fields: { + name: { type: 'text', label: 'Name' }, + total: { type: 'formula', label: 'Total' }, + count: { type: 'summary', label: 'Count' }, + record_no: { type: 'auto_number', label: 'Record #' }, + email: { type: 'email', label: 'Email' }, + }, + }; + + it('filters out formula, summary, and auto_number fields', () => { + const fields: FormField[] = [ + { name: 'name', label: 'Name', type: 'field:text' }, + { name: 'total', label: 'Total', type: 'field:text' }, + { name: 'count', label: 'Count', type: 'field:text' }, + { name: 'record_no', label: 'Record #', type: 'field:text' }, + { name: 'email', label: 'Email', type: 'field:text' }, + ]; + + const result = filterCreateModeFields(fields, objectSchema); + + expect(result).toHaveLength(2); + expect(result.map(f => f.name)).toEqual(['name', 'email']); + }); + + it('keeps all fields when objectSchema has no fields metadata', () => { + const fields: FormField[] = [ + { name: 'name', label: 'Name', type: 'field:text' }, + { name: 'total', label: 'Total', type: 'field:text' }, + ]; + + const result = filterCreateModeFields(fields, { name: 'test' }); + expect(result).toHaveLength(2); + }); + + it('keeps custom fields not in object schema', () => { + const fields: FormField[] = [ + { name: 'custom_field', label: 'Custom', type: 'field:text' }, + ]; + + const result = filterCreateModeFields(fields, objectSchema); + expect(result).toHaveLength(1); + }); + }); + + describe('applyAutoLayout', () => { + it('infers 1 column for 3 fields', () => { + const fields: FormField[] = [ + { name: 'a', label: 'A', type: 'field:text' }, + { name: 'b', label: 'B', type: 'field:number' }, + { name: 'c', label: 'C', type: 'field:select' }, + ]; + + const result = applyAutoLayout(fields, null, undefined, 'create'); + expect(result.columns).toBe(1); + expect(result.fields).toHaveLength(3); + }); + + it('infers 2 columns for 5 fields', () => { + const fields: FormField[] = [ + { name: 'a', label: 'A', type: 'field:text' }, + { name: 'b', label: 'B', type: 'field:text' }, + { name: 'c', label: 'C', type: 'field:text' }, + { name: 'd', label: 'D', type: 'field:text' }, + { name: 'e', label: 'E', type: 'field:text' }, + ]; + + const result = applyAutoLayout(fields, null, undefined, 'edit'); + expect(result.columns).toBe(2); + }); + + it('applies colSpan to wide fields when columns > 1', () => { + const fields: FormField[] = [ + { name: 'a', label: 'A', type: 'field:text' }, + { name: 'b', label: 'B', type: 'field:text' }, + { name: 'c', label: 'C', type: 'field:text' }, + { name: 'd', label: 'D', type: 'field:textarea' }, + ]; + + const result = applyAutoLayout(fields, null, undefined, 'edit'); + expect(result.columns).toBe(2); + expect(result.fields[3].colSpan).toBe(2); + // Regular fields should not have colSpan + expect(result.fields[0].colSpan).toBeUndefined(); + }); + + it('filters auto-generated fields in create mode', () => { + const fields: FormField[] = [ + { name: 'name', label: 'Name', type: 'field:text' }, + { name: 'total', label: 'Total', type: 'field:text' }, + { name: 'email', label: 'Email', type: 'field:text' }, + { name: 'count', label: 'Count', type: 'field:text' }, + ]; + + const objectSchema = { + name: 'test', + fields: { + name: { type: 'text' }, + total: { type: 'formula' }, + email: { type: 'email' }, + count: { type: 'summary' }, + }, + }; + + const result = applyAutoLayout(fields, objectSchema, undefined, 'create'); + expect(result.fields.map(f => f.name)).toEqual(['name', 'email']); + expect(result.columns).toBe(1); // Only 2 fields after filtering → 1 column + }); + + it('does not filter auto-generated fields in edit mode', () => { + const fields: FormField[] = [ + { name: 'name', label: 'Name', type: 'field:text' }, + { name: 'total', label: 'Total', type: 'field:text' }, + ]; + + const objectSchema = { + name: 'test', + fields: { + name: { type: 'text' }, + total: { type: 'formula' }, + }, + }; + + const result = applyAutoLayout(fields, objectSchema, undefined, 'edit'); + expect(result.fields).toHaveLength(2); + }); + + it('respects user-provided columns', () => { + const fields: FormField[] = [ + { name: 'a', label: 'A', type: 'field:text' }, + { name: 'b', label: 'B', type: 'field:text' }, + ]; + + const result = applyAutoLayout(fields, null, 3, 'edit'); + expect(result.columns).toBe(3); + }); + + it('applies auto colSpan even with user-provided columns', () => { + const fields: FormField[] = [ + { name: 'a', label: 'A', type: 'field:text' }, + { name: 'b', label: 'B', type: 'field:textarea' }, + ]; + + const result = applyAutoLayout(fields, null, 3, 'edit'); + expect(result.columns).toBe(3); + expect(result.fields[1].colSpan).toBe(3); + }); + + it('does not override user-defined colSpan on fields', () => { + const fields: FormField[] = [ + { name: 'a', label: 'A', type: 'field:text' }, + { name: 'b', label: 'B', type: 'field:textarea', colSpan: 1 }, + { name: 'c', label: 'C', type: 'field:text' }, + { name: 'd', label: 'D', type: 'field:text' }, + ]; + + const result = applyAutoLayout(fields, null, undefined, 'edit'); + expect(result.columns).toBe(2); + expect(result.fields[1].colSpan).toBe(1); // User override preserved + }); + + it('does not mutate original fields array', () => { + const fields: FormField[] = [ + { name: 'a', label: 'A', type: 'field:text' }, + { name: 'b', label: 'B', type: 'field:textarea' }, + { name: 'c', label: 'C', type: 'field:text' }, + { name: 'd', label: 'D', type: 'field:text' }, + ]; + + const result = applyAutoLayout(fields, null, undefined, 'edit'); + expect(fields[1].colSpan).toBeUndefined(); + expect(result.fields[1].colSpan).toBe(2); + }); + + it('handles empty fields array', () => { + const result = applyAutoLayout([], null, undefined, 'create'); + expect(result.fields).toEqual([]); + expect(result.columns).toBe(1); + }); + + it('infers columns based on field count after create-mode filtering', () => { + // Start with 5 fields, but 3 are auto-generated → 2 remain → 1 column + const fields: FormField[] = [ + { name: 'name', label: 'Name', type: 'field:text' }, + { name: 'f1', label: 'F1', type: 'field:text' }, + { name: 'f2', label: 'F2', type: 'field:text' }, + { name: 'f3', label: 'F3', type: 'field:text' }, + { name: 'f4', label: 'F4', type: 'field:text' }, + ]; + + const objectSchema = { + name: 'test', + fields: { + name: { type: 'text' }, + f1: { type: 'formula' }, + f2: { type: 'summary' }, + f3: { type: 'auto_number' }, + f4: { type: 'text' }, + }, + }; + + const result = applyAutoLayout(fields, objectSchema, undefined, 'create'); + // Only 'name' and 'f4' remain after filtering + expect(result.fields).toHaveLength(2); + expect(result.columns).toBe(1); + }); + }); +}); diff --git a/packages/plugin-form/src/autoLayout.ts b/packages/plugin-form/src/autoLayout.ts new file mode 100644 index 000000000..cf4d4ab38 --- /dev/null +++ b/packages/plugin-form/src/autoLayout.ts @@ -0,0 +1,151 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * Auto-Layout for ObjectForm + * + * Provides intelligent, zero-configuration default layout for metadata-driven forms. + * When the user has not explicitly set columns/colSpan/sections, this module + * infers optimal layout based on field count and field types. + * + * Priority: User configuration > Auto-layout inference + */ + +import type { FormField } from '@object-ui/types'; + +/** Field types that should span full width in multi-column layouts */ +const WIDE_FIELD_TYPES = new Set([ + 'field:textarea', + 'field:markdown', + 'field:html', + 'field:grid', + 'field:rich-text', + 'textarea', + 'markdown', + 'html', + 'grid', + 'rich-text', +]); + +/** Object field types that are auto-generated and should be hidden in create mode */ +const AUTO_GENERATED_FIELD_TYPES = new Set([ + 'formula', + 'summary', + 'auto_number', + 'autonumber', +]); + +/** + * Check if a field type is "wide" (should span full row in multi-column layout). + */ +export function isWideFieldType(type: string): boolean { + return WIDE_FIELD_TYPES.has(type); +} + +/** + * Check if an object field type is auto-generated (formula, summary, auto_number). + */ +export function isAutoGeneratedFieldType(type: string): boolean { + return AUTO_GENERATED_FIELD_TYPES.has(type); +} + +/** + * Infer optimal number of columns based on the number of visible fields + * and whether any wide fields are present. + * + * Rules: + * - 0-3 fields → 1 column + * - 4+ fields → 2 columns + */ +export function inferColumns(fieldCount: number): number { + if (fieldCount <= 3) return 1; + return 2; +} + +/** + * Apply colSpan to wide fields so they span the full row. + * Only sets colSpan if the field does not already have one explicitly set. + * + * @returns A new array of fields with colSpan applied where needed. + */ +export function applyAutoColSpan(fields: FormField[], columns: number): FormField[] { + if (columns <= 1) return fields; + + return fields.map(field => { + // User-defined colSpan takes priority + if (field.colSpan !== undefined) return field; + + // Wide field types should span full row + if (isWideFieldType(field.type)) { + return { ...field, colSpan: columns }; + } + + return field; + }); +} + +/** + * Filter out auto-generated/readonly fields for create mode. + * These fields (formula, summary, auto_number) are computed server-side + * and should not appear in create forms. + * + * @param fields - The form fields array + * @param objectSchema - The object schema with original field metadata + * @returns Filtered fields array + */ +export function filterCreateModeFields( + fields: FormField[], + objectSchema: any +): FormField[] { + if (!objectSchema?.fields) return fields; + + return fields.filter(field => { + const objField = objectSchema.fields[field.name]; + if (!objField) return true; // keep fields not in schema (custom fields) + + return !isAutoGeneratedFieldType(objField.type); + }); +} + +/** + * Main auto-layout orchestrator. + * Applies intelligent defaults only when the user has not explicitly configured layout. + * + * @param formFields - Generated form fields + * @param objectSchema - The original object schema (with field types) + * @param schemaColumns - User-provided columns (from ObjectFormSchema) + * @param mode - Form mode ('create' | 'edit' | 'view') + * @returns Object with processed fields and inferred columns + */ +export function applyAutoLayout( + formFields: FormField[], + objectSchema: any, + schemaColumns: number | undefined, + mode: string | undefined +): { fields: FormField[]; columns: number | undefined } { + let fields = [...formFields]; + + // Step 1: Filter auto-generated fields in create mode + if (mode === 'create') { + fields = filterCreateModeFields(fields, objectSchema); + } + + // Step 2: If user explicitly set columns, respect it but still apply auto colSpan + if (schemaColumns !== undefined) { + fields = applyAutoColSpan(fields, schemaColumns); + return { fields, columns: schemaColumns }; + } + + // Step 3: Infer columns from field count + const columns = inferColumns(fields.length); + + // Step 4: Apply auto colSpan for wide fields + fields = applyAutoColSpan(fields, columns); + + return { fields, columns }; +} diff --git a/packages/plugin-form/src/index.tsx b/packages/plugin-form/src/index.tsx index fe9e8d306..efa1d03c2 100644 --- a/packages/plugin-form/src/index.tsx +++ b/packages/plugin-form/src/index.tsx @@ -14,6 +14,14 @@ export { ObjectForm }; export type { ObjectFormProps } from './ObjectForm'; export { FormSection } from './FormSection'; export type { FormSectionProps } from './FormSection'; +export { + applyAutoLayout, + inferColumns, + isWideFieldType, + isAutoGeneratedFieldType, + applyAutoColSpan, + filterCreateModeFields, +} from './autoLayout'; export { TabbedForm } from './TabbedForm'; export type { TabbedFormProps, TabbedFormSchema, FormSectionConfig } from './TabbedForm'; export { WizardForm } from './WizardForm'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c6c0762e..96305f421 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -573,6 +573,273 @@ importers: specifier: 19.2.4 version: 19.2.4(react@19.2.4) + examples/hotcrm/packages/ai: + dependencies: + '@objectstack/spec': + specifier: ^3.0.3 + version: 3.0.7 + axios: + specifier: ^1.13.5 + version: 1.13.5 + zod: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + '@objectstack/cli': + specifier: ^3.0.3 + version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + + examples/hotcrm/packages/analytics: + dependencies: + '@hotcrm/ai': + specifier: workspace:* + version: link:../ai + '@objectstack/spec': + specifier: ^3.0.3 + version: 3.0.7 + zod: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + '@objectstack/cli': + specifier: ^3.0.3 + version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + + examples/hotcrm/packages/community: + dependencies: + '@objectstack/spec': + specifier: ^3.0.3 + version: 3.0.7 + zod: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + '@objectstack/cli': + specifier: ^3.0.3 + version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + + examples/hotcrm/packages/core: + dependencies: + '@objectstack/spec': + specifier: ^3.0.2 + version: 3.0.7 + zod: + specifier: ^4.3.6 + version: 4.3.6 + + examples/hotcrm/packages/crm: + dependencies: + '@hotcrm/ai': + specifier: workspace:* + version: link:../ai + '@objectstack/spec': + specifier: ^3.0.3 + version: 3.0.7 + zod: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + '@objectstack/cli': + specifier: ^3.0.3 + version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + + examples/hotcrm/packages/education: + dependencies: + '@objectstack/spec': + specifier: ^3.0.3 + version: 3.0.7 + zod: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + '@objectstack/cli': + specifier: ^3.0.3 + version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + + examples/hotcrm/packages/finance: + dependencies: + '@objectstack/spec': + specifier: ^3.0.3 + version: 3.0.7 + devDependencies: + '@objectstack/cli': + specifier: ^3.0.3 + version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + + examples/hotcrm/packages/financial-services: + dependencies: + '@objectstack/spec': + specifier: ^3.0.3 + version: 3.0.7 + zod: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + '@objectstack/cli': + specifier: ^3.0.3 + version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + + examples/hotcrm/packages/healthcare: + dependencies: + '@objectstack/spec': + specifier: ^3.0.3 + version: 3.0.7 + zod: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + '@objectstack/cli': + specifier: ^3.0.3 + version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + + examples/hotcrm/packages/hr: + dependencies: + '@objectstack/spec': + specifier: ^3.0.3 + version: 3.0.7 + devDependencies: + '@objectstack/cli': + specifier: ^3.0.3 + version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + + examples/hotcrm/packages/integration: + dependencies: + '@hotcrm/ai': + specifier: workspace:* + version: link:../ai + '@objectstack/spec': + specifier: ^3.0.3 + version: 3.0.7 + zod: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + '@objectstack/cli': + specifier: ^3.0.3 + version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + + examples/hotcrm/packages/marketing: + dependencies: + '@objectstack/spec': + specifier: ^3.0.3 + version: 3.0.7 + devDependencies: + '@objectstack/cli': + specifier: ^3.0.3 + version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + + examples/hotcrm/packages/products: + dependencies: + '@objectstack/spec': + specifier: ^3.0.3 + version: 3.0.7 + devDependencies: + '@objectstack/cli': + specifier: ^3.0.3 + version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + + examples/hotcrm/packages/real-estate: + dependencies: + '@objectstack/spec': + specifier: ^3.0.3 + version: 3.0.7 + zod: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + '@objectstack/cli': + specifier: ^3.0.3 + version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + + examples/hotcrm/packages/server: + dependencies: + '@objectstack/cli': + specifier: ^3.0.2 + version: 3.0.7(@objectstack/core@3.0.7(pino@10.3.1))(esbuild@0.27.3)(pino@10.3.1) + '@objectstack/core': + specifier: ^3.0.2 + version: 3.0.7(pino@10.3.1) + '@objectstack/metadata': + specifier: ^3.0.2 + version: 3.0.7(pino@10.3.1) + '@objectstack/objectql': + specifier: ^3.0.2 + version: 3.0.7(pino@10.3.1) + '@objectstack/plugin-hono-server': + specifier: ^3.0.2 + version: 3.0.7(pino@10.3.1) + '@objectstack/runtime': + specifier: ^3.0.2 + version: 3.0.7(pino@10.3.1) + '@objectstack/spec': + specifier: ^3.0.2 + version: 3.0.7 + pino: + specifier: ^10.3.1 + version: 10.3.1 + devDependencies: + '@types/js-yaml': + specifier: ^4.0.9 + version: 4.0.9 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@swc/core@1.15.11)(@types/node@25.2.3)(typescript@5.9.3) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + + examples/hotcrm/packages/support: + dependencies: + '@objectstack/spec': + specifier: ^3.0.3 + version: 3.0.7 + devDependencies: + '@objectstack/cli': + specifier: ^3.0.3 + version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + examples/kitchen-sink: dependencies: '@objectstack/spec': @@ -4020,6 +4287,9 @@ packages: resolution: {integrity: sha512-a61ljmRVVyG5MC/698C8/FfFDw5a8LOIvyOLW5fztgUXqUpc1jOfQzOitSCbge657OgXXThmY3Tk8fpiDb4UcA==} engines: {node: '>= 20.0.0'} + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -9650,6 +9920,13 @@ packages: pino-std-serializers@6.2.2: resolution: {integrity: sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==} + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@10.3.1: + resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} + hasBin: true + pino@8.21.0: resolution: {integrity: sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==} hasBin: true @@ -9783,6 +10060,9 @@ packages: process-warning@3.0.0: resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==} + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} @@ -10742,6 +11022,10 @@ packages: thread-stream@2.7.0: resolution: {integrity: sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==} + thread-stream@4.0.0: + resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==} + engines: {node: '>=20'} + tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -12215,7 +12499,6 @@ snapshots: '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 - optional: true '@csstools/color-helpers@6.0.1': {} @@ -13028,7 +13311,6 @@ snapshots: dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - optional: true '@manypkg/find-root@1.1.0': dependencies: @@ -13308,6 +13590,24 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 + '@objectstack/cli@3.0.7(@objectstack/core@3.0.7(pino@10.3.1))(esbuild@0.27.3)(pino@10.3.1)': + dependencies: + '@objectstack/core': 3.0.7(pino@10.3.1) + '@objectstack/driver-memory': 3.0.7(pino@10.3.1) + '@objectstack/objectql': 3.0.7(pino@10.3.1) + '@objectstack/plugin-hono-server': 3.0.7(pino@10.3.1) + '@objectstack/rest': 3.0.7(pino@10.3.1) + '@objectstack/runtime': 3.0.7(pino@10.3.1) + '@objectstack/spec': 3.0.7 + '@oclif/core': 4.8.0 + bundle-require: 5.1.0(esbuild@0.27.3) + chalk: 5.6.2 + tsx: 4.21.0 + zod: 4.3.6 + transitivePeerDependencies: + - esbuild + - pino + '@objectstack/cli@3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0)': dependencies: '@objectstack/core': 3.0.7(pino@8.21.0) @@ -13342,6 +13642,14 @@ snapshots: transitivePeerDependencies: - pino + '@objectstack/core@3.0.7(pino@10.3.1)': + dependencies: + '@objectstack/spec': 3.0.7 + pino-pretty: 13.1.3 + zod: 4.3.6 + optionalDependencies: + pino: 10.3.1 + '@objectstack/core@3.0.7(pino@8.21.0)': dependencies: '@objectstack/spec': 3.0.7 @@ -13350,6 +13658,14 @@ snapshots: optionalDependencies: pino: 8.21.0 + '@objectstack/driver-memory@3.0.7(pino@10.3.1)': + dependencies: + '@objectstack/core': 3.0.7(pino@10.3.1) + '@objectstack/spec': 3.0.7 + mingo: 7.2.0 + transitivePeerDependencies: + - pino + '@objectstack/driver-memory@3.0.7(pino@8.21.0)': dependencies: '@objectstack/core': 3.0.7(pino@8.21.0) @@ -13358,6 +13674,18 @@ snapshots: transitivePeerDependencies: - pino + '@objectstack/metadata@3.0.7(pino@10.3.1)': + dependencies: + '@objectstack/core': 3.0.7(pino@10.3.1) + '@objectstack/spec': 3.0.7 + '@objectstack/types': 3.0.7 + chokidar: 5.0.0 + glob: 13.0.5 + js-yaml: 4.1.1 + zod: 4.3.6 + transitivePeerDependencies: + - pino + '@objectstack/metadata@3.0.7(pino@8.21.0)': dependencies: '@objectstack/core': 3.0.7(pino@8.21.0) @@ -13370,6 +13698,14 @@ snapshots: transitivePeerDependencies: - pino + '@objectstack/objectql@3.0.7(pino@10.3.1)': + dependencies: + '@objectstack/core': 3.0.7(pino@10.3.1) + '@objectstack/spec': 3.0.7 + '@objectstack/types': 3.0.7 + transitivePeerDependencies: + - pino + '@objectstack/objectql@3.0.7(pino@8.21.0)': dependencies: '@objectstack/core': 3.0.7(pino@8.21.0) @@ -13405,6 +13741,15 @@ snapshots: - vitest - vue + '@objectstack/plugin-hono-server@3.0.7(pino@10.3.1)': + dependencies: + '@hono/node-server': 1.19.9(hono@4.11.9) + '@objectstack/core': 3.0.7(pino@10.3.1) + '@objectstack/spec': 3.0.7 + hono: 4.11.9 + transitivePeerDependencies: + - pino + '@objectstack/plugin-hono-server@3.0.7(pino@8.21.0)': dependencies: '@hono/node-server': 1.19.9(hono@4.11.9) @@ -13427,6 +13772,14 @@ snapshots: - pino - typescript + '@objectstack/rest@3.0.7(pino@10.3.1)': + dependencies: + '@objectstack/core': 3.0.7(pino@10.3.1) + '@objectstack/spec': 3.0.7 + zod: 4.3.6 + transitivePeerDependencies: + - pino + '@objectstack/rest@3.0.7(pino@8.21.0)': dependencies: '@objectstack/core': 3.0.7(pino@8.21.0) @@ -13435,6 +13788,16 @@ snapshots: transitivePeerDependencies: - pino + '@objectstack/runtime@3.0.7(pino@10.3.1)': + dependencies: + '@objectstack/core': 3.0.7(pino@10.3.1) + '@objectstack/rest': 3.0.7(pino@10.3.1) + '@objectstack/spec': 3.0.7 + '@objectstack/types': 3.0.7 + zod: 4.3.6 + transitivePeerDependencies: + - pino + '@objectstack/runtime@3.0.7(pino@8.21.0)': dependencies: '@objectstack/core': 3.0.7(pino@8.21.0) @@ -13524,6 +13887,8 @@ snapshots: '@orama/orama@3.1.18': {} + '@pinojs/redact@0.4.0': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -15046,17 +15411,13 @@ snapshots: minimatch: 10.2.1 path-browserify: 1.0.1 - '@tsconfig/node10@1.0.12': - optional: true + '@tsconfig/node10@1.0.12': {} - '@tsconfig/node12@1.0.11': - optional: true + '@tsconfig/node12@1.0.11': {} - '@tsconfig/node14@1.0.3': - optional: true + '@tsconfig/node14@1.0.3': {} - '@tsconfig/node16@1.0.4': - optional: true + '@tsconfig/node16@1.0.4': {} '@tybys/wasm-util@0.10.1': dependencies: @@ -15918,8 +16279,7 @@ snapshots: archy@1.0.0: {} - arg@4.1.3: - optional: true + arg@4.1.3: {} argparse@1.0.10: dependencies: @@ -16498,8 +16858,7 @@ snapshots: coverage-v8@0.0.1-security: {} - create-require@1.1.1: - optional: true + create-require@1.1.1: {} cross-spawn@7.0.6: dependencies: @@ -16680,8 +17039,7 @@ snapshots: diff-sequences@29.6.3: {} - diff@4.0.4: - optional: true + diff@4.0.4: {} diff@8.0.3: {} @@ -18963,8 +19321,7 @@ snapshots: dependencies: semver: 7.7.4 - make-error@1.3.6: - optional: true + make-error@1.3.6: {} makeerror@1.0.12: dependencies: @@ -20016,6 +20373,22 @@ snapshots: pino-std-serializers@6.2.2: {} + pino-std-serializers@7.1.0: {} + + pino@10.3.1: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.1 + thread-stream: 4.0.0 + pino@8.21.0: dependencies: atomic-sleep: 1.0.0 @@ -20152,6 +20525,8 @@ snapshots: process-warning@3.0.0: {} + process-warning@5.0.0: {} + process@0.11.10: {} prompts@2.4.2: @@ -21361,6 +21736,10 @@ snapshots: dependencies: real-require: 0.2.0 + thread-stream@4.0.0: + dependencies: + real-require: 0.2.0 + tiny-invariant@1.3.3: {} tinybench@2.9.0: {} @@ -21450,7 +21829,6 @@ snapshots: yn: 3.1.1 optionalDependencies: '@swc/core': 1.15.11 - optional: true tsconfig-paths@4.2.0: dependencies: @@ -21753,8 +22131,7 @@ snapshots: uuid@9.0.1: {} - v8-compile-cache-lib@3.0.1: - optional: true + v8-compile-cache-lib@3.0.1: {} v8-to-istanbul@9.3.0: dependencies: @@ -22244,8 +22621,7 @@ snapshots: dependencies: buffer-crc32: 0.2.13 - yn@3.1.1: - optional: true + yn@3.1.1: {} yocto-queue@0.1.0: {}