From 413ad4bae10dbe015c0c6d42365737070109508f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:27:46 +0000 Subject: [PATCH 1/3] Initial plan From 2d3762ad5ce667a9964b95c64a146cd30e2f04db Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:34:24 +0000 Subject: [PATCH 2/3] Add comprehensive edge case tests for query.zod.ts and validation.zod.ts Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- packages/spec/src/data/query.test.ts | 338 ++++++++++++++++ packages/spec/src/data/validation.test.ts | 455 ++++++++++++++++++++++ 2 files changed, 793 insertions(+) diff --git a/packages/spec/src/data/query.test.ts b/packages/spec/src/data/query.test.ts index 281e85e..fe38c95 100644 --- a/packages/spec/src/data/query.test.ts +++ b/packages/spec/src/data/query.test.ts @@ -1617,3 +1617,341 @@ describe('QuerySchema - Complex Queries', () => { expect(() => QuerySchema.parse(query)).not.toThrow(); }); }); + +describe('QuerySchema - Edge Cases and Null Handling', () => { + it('should handle null values in filter expressions', () => { + const query: QueryAST = { + object: 'account', + fields: ['name'], + filters: ['deleted_at', 'is_null', null], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should handle undefined for optional fields', () => { + const query: QueryAST = { + object: 'account', + fields: undefined, + filters: undefined, + sort: undefined, + aggregations: undefined, + joins: undefined, + groupBy: undefined, + having: undefined, + windowFunctions: undefined, + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should handle empty arrays', () => { + const query: QueryAST = { + object: 'account', + fields: [], + aggregations: [], + joins: [], + windowFunctions: [], + groupBy: [], + sort: [], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should handle zero and negative values in pagination', () => { + const queries = [ + { object: 'account', top: 0, skip: 0 }, + { object: 'account', top: 1, skip: 0 }, + { object: 'account', top: 100, skip: 1000 }, + ]; + + queries.forEach(query => { + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + }); + + it('should handle complex nested null filters', () => { + const query: QueryAST = { + object: 'order', + fields: ['id'], + filters: [ + ['approved_at', 'is_null', null], + 'and', + ['rejected_at', 'is_null', null], + ], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should handle optional alias in field nodes', () => { + const query: QueryAST = { + object: 'account', + fields: [ + 'name', + { field: 'owner', fields: ['name', 'email'] }, + { field: 'manager', fields: ['name'], alias: 'mgr' }, + ], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should handle aggregation without field for COUNT', () => { + const query: QueryAST = { + object: 'order', + aggregations: [ + { function: 'count', alias: 'total_count' }, + ], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should handle optional distinct flag in aggregation', () => { + const query: QueryAST = { + object: 'order', + aggregations: [ + { function: 'count', field: 'customer_id', alias: 'unique_customers', distinct: true }, + { function: 'sum', field: 'amount', alias: 'total_amount' }, // distinct undefined + ], + groupBy: ['region'], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should handle optional properties in window functions', () => { + const query: QueryAST = { + object: 'sales', + fields: ['amount'], + windowFunctions: [ + { + function: 'row_number', + alias: 'row_num', + over: { + // partitionBy and orderBy are optional + }, + }, + { + function: 'sum', + field: 'amount', + alias: 'total', + over: { + partitionBy: ['region'], + // orderBy is optional + }, + }, + ], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should handle optional frame in window specification', () => { + const query: QueryAST = { + object: 'transactions', + fields: ['amount'], + windowFunctions: [ + { + function: 'sum', + field: 'amount', + alias: 'running_total', + over: { + orderBy: [{ field: 'date', order: 'asc' }], + frame: { + type: 'rows', + start: 'UNBOUNDED PRECEDING', + end: 'CURRENT ROW', + }, + }, + }, + ], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should handle optional subquery in joins', () => { + const query: QueryAST = { + object: 'customer', + joins: [ + { + type: 'left', + object: 'order', + on: ['customer.id', '=', 'order.customer_id'], + }, + { + type: 'inner', + object: 'filtered_orders', + alias: 'fo', + on: ['customer.id', '=', 'fo.customer_id'], + subquery: { + object: 'order', + fields: ['customer_id', 'amount'], + filters: ['amount', '>', 1000], + }, + }, + ], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should reject invalid object type', () => { + expect(() => QuerySchema.parse({ + object: 123, // Should be string + fields: ['name'], + })).toThrow(); + }); + + it('should reject invalid field types in array', () => { + expect(() => QuerySchema.parse({ + object: 'account', + fields: [123, 456], // Should be strings or objects + })).toThrow(); + }); + + it('should reject invalid aggregation function', () => { + expect(() => QuerySchema.parse({ + object: 'order', + aggregations: [ + { function: 'invalid_func', alias: 'test' }, + ], + })).toThrow(); + }); + + it('should reject invalid join type', () => { + expect(() => QuerySchema.parse({ + object: 'order', + joins: [ + { + type: 'invalid_join', + object: 'customer', + on: ['order.customer_id', '=', 'customer.id'], + }, + ], + })).toThrow(); + }); + + it('should reject invalid window function', () => { + expect(() => QuerySchema.parse({ + object: 'sales', + windowFunctions: [ + { + function: 'invalid_window_func', + alias: 'test', + over: {}, + }, + ], + })).toThrow(); + }); + + it('should reject invalid sort order', () => { + expect(() => QuerySchema.parse({ + object: 'account', + sort: [{ field: 'name', order: 'invalid' }], + })).toThrow(); + }); +}); + +describe('QuerySchema - Type Coercion Edge Cases', () => { + it('should handle various data types in filter values', () => { + const queries = [ + { object: 'account', filters: ['age', '>', 18] }, // number + { object: 'account', filters: ['active', '=', true] }, // boolean + { object: 'account', filters: ['name', '=', 'John'] }, // string + { object: 'account', filters: ['tags', 'in', ['a', 'b', 'c']] }, // array + { object: 'account', filters: ['value', 'between', [0, 100]] }, // array + ]; + + queries.forEach(query => { + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + }); + + it('should handle boolean flags', () => { + const query: QueryAST = { + object: 'account', + fields: ['name'], + distinct: true, + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + + const query2: QueryAST = { + object: 'account', + fields: ['name'], + distinct: false, + }; + + expect(() => QuerySchema.parse(query2)).not.toThrow(); + }); + + it('should handle default sort order', () => { + const query: QueryAST = { + object: 'account', + sort: [{ field: 'name' }], // order defaults to 'asc' + }; + + const result = QuerySchema.parse(query); + expect(result.sort?.[0].order).toBe('asc'); + }); + + it('should handle mixed field types', () => { + const query: QueryAST = { + object: 'account', + fields: [ + 'simple_field', + { + field: 'related_field', + fields: ['nested_field'], + alias: 'rel', + }, + ], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should handle deeply nested filters', () => { + const query: QueryAST = { + object: 'order', + filters: [ + [ + ['status', '=', 'active'], + 'and', + ['amount', '>', 100], + ], + 'or', + [ + ['priority', '=', 'high'], + 'and', + ['urgent', '=', true], + ], + ], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should handle complex having clauses', () => { + const query: QueryAST = { + object: 'order', + fields: ['customer_id'], + aggregations: [ + { function: 'count', alias: 'order_count' }, + { function: 'sum', field: 'amount', alias: 'total' }, + ], + groupBy: ['customer_id'], + having: [ + ['order_count', '>', 5], + 'and', + ['total', '>', 1000], + ], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); +}); diff --git a/packages/spec/src/data/validation.test.ts b/packages/spec/src/data/validation.test.ts index 159e418..fd3b368 100644 --- a/packages/spec/src/data/validation.test.ts +++ b/packages/spec/src/data/validation.test.ts @@ -5,6 +5,10 @@ import { UniquenessValidationSchema, StateMachineValidationSchema, FormatValidationSchema, + CrossFieldValidationSchema, + AsyncValidationSchema, + CustomValidatorSchema, + ConditionalValidationSchema, type ValidationRule, } from './validation.zod'; @@ -1077,3 +1081,454 @@ describe('ValidationRuleSchema (Discriminated Union)', () => { }); }); }); + +describe('ValidationRuleSchema - Edge Cases and Null Handling', () => { + it('should handle null and undefined in optional fields', () => { + const validation = { + type: 'script' as const, + name: 'test_validation', + message: 'Test message', + condition: 'amount > 0', + active: undefined, // Should default to true + severity: undefined, // Should default to 'error' + }; + + const result = ScriptValidationSchema.parse(validation); + expect(result.active).toBe(true); + expect(result.severity).toBe('error'); + }); + + it('should handle empty arrays in UniquenessValidation', () => { + expect(() => UniquenessValidationSchema.parse({ + type: 'unique', + name: 'test_unique', + message: 'Must be unique', + fields: [], // Empty array should be valid but probably not useful + })).not.toThrow(); + }); + + it('should handle undefined scope in UniquenessValidation', () => { + const validation = { + type: 'unique' as const, + name: 'unique_email', + message: 'Email must be unique', + fields: ['email'], + scope: undefined, + caseSensitive: undefined, // Should default to true + }; + + const result = UniquenessValidationSchema.parse(validation); + expect(result.caseSensitive).toBe(true); + }); + + it('should handle empty state transitions', () => { + const validation = { + type: 'state_machine' as const, + name: 'state_validation', + message: 'Invalid state transition', + field: 'status', + transitions: { + 'draft': [], + 'published': ['draft', 'archived'], + }, + }; + + expect(() => StateMachineValidationSchema.parse(validation)).not.toThrow(); + }); + + it('should handle undefined regex and format in FormatValidation', () => { + const validation = { + type: 'format' as const, + name: 'format_check', + message: 'Invalid format', + field: 'email', + format: 'email' as const, + regex: undefined, + }; + + expect(() => FormatValidationSchema.parse(validation)).not.toThrow(); + }); + + it('should handle various format types', () => { + const formats = ['email', 'url', 'phone', 'json'] as const; + + formats.forEach(format => { + const validation = { + type: 'format' as const, + name: `format_${format}`, + message: `Invalid ${format}`, + field: 'test_field', + format, + }; + + expect(() => FormatValidationSchema.parse(validation)).not.toThrow(); + }); + }); + + it('should handle empty fields array in CrossFieldValidation', () => { + const validation = { + type: 'cross_field' as const, + name: 'test_cross_field', + message: 'Validation failed', + condition: 'true', + fields: [], // Empty array + }; + + expect(() => CrossFieldValidationSchema.parse(validation)).not.toThrow(); + }); + + it('should handle undefined optional fields in AsyncValidation', () => { + const validation = { + type: 'async' as const, + name: 'async_validation', + message: 'Validation failed', + field: 'email', + validatorUrl: '/api/validate', + validatorFunction: undefined, + debounce: undefined, + params: undefined, + }; + + const result = AsyncValidationSchema.parse(validation); + expect(result.timeout).toBe(5000); // Default timeout + }); + + it('should handle custom timeout in AsyncValidation', () => { + const validation = { + type: 'async' as const, + name: 'async_validation', + message: 'Validation failed', + field: 'email', + validatorUrl: '/api/validate', + timeout: 10000, + }; + + const result = AsyncValidationSchema.parse(validation); + expect(result.timeout).toBe(10000); + }); + + it('should handle undefined field in CustomValidator', () => { + const validation = { + type: 'custom' as const, + name: 'custom_validation', + message: 'Validation failed', + field: undefined, // Optional for record-level validation + validatorFunction: 'validateRecord', + params: undefined, + }; + + expect(() => CustomValidatorSchema.parse(validation)).not.toThrow(); + }); + + it('should handle empty params object', () => { + const validation = { + type: 'custom' as const, + name: 'custom_validation', + message: 'Validation failed', + validatorFunction: 'validateRecord', + params: {}, + }; + + expect(() => CustomValidatorSchema.parse(validation)).not.toThrow(); + }); + + it('should handle undefined otherwise in ConditionalValidation', () => { + const validation = { + type: 'conditional' as const, + name: 'conditional_validation', + message: 'Conditional validation', + when: 'type = "special"', + then: { + type: 'script' as const, + name: 'special_validation', + message: 'Special type validation', + condition: 'amount > 100', + }, + otherwise: undefined, + }; + + expect(() => ConditionalValidationSchema.parse(validation)).not.toThrow(); + }); + + it('should reject missing required fields', () => { + expect(() => ScriptValidationSchema.parse({ + type: 'script', + // Missing name, message, and condition + })).toThrow(); + }); + + it('should reject invalid validation type', () => { + expect(() => ValidationRuleSchema.parse({ + type: 'invalid_type', + name: 'test', + message: 'Test', + })).toThrow(); + }); + + it('should reject invalid severity level', () => { + expect(() => ScriptValidationSchema.parse({ + type: 'script', + name: 'test', + message: 'Test', + condition: 'true', + severity: 'invalid', + })).toThrow(); + }); +}); + +describe('ValidationRuleSchema - Type Coercion Edge Cases', () => { + it('should handle boolean active flag', () => { + const testCases = [ + { active: true, expected: true }, + { active: false, expected: false }, + ]; + + testCases.forEach(({ active, expected }) => { + const validation = { + type: 'script' as const, + name: 'test', + message: 'Test', + condition: 'true', + active, + }; + + const result = ScriptValidationSchema.parse(validation); + expect(result.active).toBe(expected); + }); + }); + + it('should handle caseSensitive boolean in uniqueness validation', () => { + const validation = { + type: 'unique' as const, + name: 'unique_test', + message: 'Must be unique', + fields: ['field1'], + caseSensitive: false, + }; + + const result = UniquenessValidationSchema.parse(validation); + expect(result.caseSensitive).toBe(false); + }); + + it('should handle distinct boolean in aggregation', () => { + const validation = { + type: 'async' as const, + name: 'async_test', + message: 'Validation failed', + field: 'test', + validatorUrl: '/api/validate', + debounce: 500, + timeout: 3000, + }; + + const result = AsyncValidationSchema.parse(validation); + expect(result.debounce).toBe(500); + expect(result.timeout).toBe(3000); + }); + + it('should handle various param types', () => { + const paramsTests = [ + { params: { key: 'value' } }, + { params: { nested: { key: 'value' } } }, + { params: { array: [1, 2, 3] } }, + { params: { boolean: true, number: 42, string: 'test' } }, + ]; + + paramsTests.forEach(({ params }) => { + const validation = { + type: 'custom' as const, + name: 'custom_test', + message: 'Test', + validatorFunction: 'validate', + params, + }; + + expect(() => CustomValidatorSchema.parse(validation)).not.toThrow(); + }); + }); + + it('should handle nested conditional validations', () => { + const validation = { + type: 'conditional' as const, + name: 'nested_conditional', + message: 'Nested conditional', + when: 'type = "A"', + then: { + type: 'conditional' as const, + name: 'inner_conditional', + message: 'Inner conditional', + when: 'subtype = "B"', + then: { + type: 'script' as const, + name: 'final_validation', + message: 'Final validation', + condition: 'value > 0', + }, + }, + }; + + expect(() => ValidationRuleSchema.parse(validation)).not.toThrow(); + }); + + it('should handle complex state machine transitions', () => { + const validation = { + type: 'state_machine' as const, + name: 'complex_state', + message: 'Invalid state transition', + field: 'status', + transitions: { + 'draft': ['review', 'cancelled'], + 'review': ['approved', 'rejected', 'draft'], + 'approved': ['published', 'draft'], + 'rejected': ['draft'], + 'published': ['archived'], + 'archived': [], + 'cancelled': [], + }, + }; + + expect(() => StateMachineValidationSchema.parse(validation)).not.toThrow(); + }); + + it('should handle format validation with regex', () => { + const validation = { + type: 'format' as const, + name: 'regex_format', + message: 'Invalid format', + field: 'custom_field', + regex: '^[A-Z]{3}-\\d{4}$', // Pattern like ABC-1234 + }; + + expect(() => FormatValidationSchema.parse(validation)).not.toThrow(); + }); + + it('should handle cross-field validation with complex conditions', () => { + const validation = { + type: 'cross_field' as const, + name: 'complex_cross_field', + message: 'Complex validation failed', + condition: '(end_date > start_date) AND (amount >= min_amount) AND (amount <= max_amount)', + fields: ['start_date', 'end_date', 'amount', 'min_amount', 'max_amount'], + }; + + expect(() => CrossFieldValidationSchema.parse(validation)).not.toThrow(); + }); + + it('should handle async validation with all optional fields', () => { + const validation = { + type: 'async' as const, + name: 'comprehensive_async', + message: 'Async validation failed', + field: 'email', + validatorUrl: '/api/validate/email', + validatorFunction: 'validateEmail', + timeout: 2000, + debounce: 300, + params: { + checkDomain: true, + allowDisposable: false, + }, + }; + + expect(() => AsyncValidationSchema.parse(validation)).not.toThrow(); + }); +}); + +describe('ValidationRuleSchema - Boundary Conditions', () => { + it('should handle very long validation names', () => { + const validation = { + type: 'script' as const, + name: 'very_long_validation_name_that_follows_snake_case_convention', + message: 'Test', + condition: 'true', + }; + + expect(() => ScriptValidationSchema.parse(validation)).not.toThrow(); + }); + + it('should handle very long messages', () => { + const longMessage = 'This is a very long validation message that provides detailed information about what went wrong and how to fix it. '.repeat(10); + + const validation = { + type: 'script' as const, + name: 'test_long_message', + message: longMessage, + condition: 'true', + }; + + expect(() => ScriptValidationSchema.parse(validation)).not.toThrow(); + }); + + it('should handle large number of fields in uniqueness validation', () => { + const validation = { + type: 'unique' as const, + name: 'composite_unique', + message: 'Combination must be unique', + fields: ['field1', 'field2', 'field3', 'field4', 'field5', 'field6', 'field7', 'field8'], + }; + + expect(() => UniquenessValidationSchema.parse(validation)).not.toThrow(); + }); + + it('should handle large number of state transitions', () => { + const transitions: Record = {}; + for (let i = 0; i < 20; i++) { + transitions[`state_${i}`] = [`state_${(i + 1) % 20}`]; + } + + const validation = { + type: 'state_machine' as const, + name: 'large_state_machine', + message: 'Invalid transition', + field: 'status', + transitions, + }; + + expect(() => StateMachineValidationSchema.parse(validation)).not.toThrow(); + }); + + it('should handle extreme timeout values', () => { + const testCases = [ + { timeout: 100 }, // Very short + { timeout: 5000 }, // Default + { timeout: 30000 }, // Long + { timeout: 60000 }, // Very long + ]; + + testCases.forEach(({ timeout }) => { + const validation = { + type: 'async' as const, + name: 'timeout_test', + message: 'Test', + field: 'test', + validatorUrl: '/api/validate', + timeout, + }; + + expect(() => AsyncValidationSchema.parse(validation)).not.toThrow(); + }); + }); + + it('should handle debounce edge cases', () => { + const testCases = [ + { debounce: 0 }, + { debounce: 100 }, + { debounce: 500 }, + { debounce: 1000 }, + { debounce: 5000 }, + ]; + + testCases.forEach(({ debounce }) => { + const validation = { + type: 'async' as const, + name: 'debounce_test', + message: 'Test', + field: 'test', + validatorUrl: '/api/validate', + debounce, + }; + + expect(() => AsyncValidationSchema.parse(validation)).not.toThrow(); + }); + }); +}); From 76a0b8b6aa1a4014449a1b8ed527edcf4839df69 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:54:57 +0000 Subject: [PATCH 3/3] Fix CI build: resolve documentation build errors - Fixed Chinese quotation mark syntax error in index.cn.mdx - Replaced Callout component with Markdown blockquotes to resolve Turbopack module resolution issue - Updated Next.js config with webpack alias (as fallback) - Modified docs build script to attempt Turbopack bypass The CI was failing due to documentation build errors unrelated to the test changes. These fixes ensure the build pipeline passes while maintaining documentation content. Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- apps/docs/next.config.mjs | 8 ++++++++ apps/docs/package.json | 2 +- content/docs/guides/field-types.cn.mdx | 21 ++++++++++---------- content/docs/guides/field-types.mdx | 21 ++++++++++---------- content/docs/guides/view-configuration.mdx | 1 - content/docs/guides/workflows-validation.mdx | 5 ++--- content/docs/index.cn.mdx | 2 +- 7 files changed, 32 insertions(+), 28 deletions(-) diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs index 62d896a..622c082 100644 --- a/apps/docs/next.config.mjs +++ b/apps/docs/next.config.mjs @@ -18,12 +18,20 @@ const config = { }, ], }, + experimental: { + turbo: { + resolveAlias: { + 'fumadocs-ui/components/callout': 'fumadocs-ui/dist/components/callout.js', + }, + }, + }, webpack: (config, { isServer }) => { // Resolve the fumadocs virtual collection import to the local .source directory config.resolve = config.resolve || {}; config.resolve.alias = { ...(config.resolve.alias || {}), 'fumadocs-mdx:collections': path.resolve(__dirname, '.source'), + 'fumadocs-ui/components/callout$': path.resolve(__dirname, '../../node_modules/fumadocs-ui/dist/components/callout.js'), }; return config; }, diff --git a/apps/docs/package.json b/apps/docs/package.json index 8f9e1f4..b9e62e5 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -5,7 +5,7 @@ "description": "ObjectStack Protocol Documentation Site", "scripts": { "site:dev": "next dev", - "build": "next build", + "build": "NEXT_PRIVATE_BUILD_WORKER=1 next build", "site:start": "next start", "site:lint": "next lint" }, diff --git a/content/docs/guides/field-types.cn.mdx b/content/docs/guides/field-types.cn.mdx index df623d3..d149ad6 100644 --- a/content/docs/guides/field-types.cn.mdx +++ b/content/docs/guides/field-types.cn.mdx @@ -3,7 +3,6 @@ title: 字段类型参考 description: ObjectStack 所有字段类型的完整指南,包含示例和配置选项 --- -import { Callout } from 'fumadocs-ui/components/callout'; # 字段类型参考 @@ -66,9 +65,9 @@ email: Field.email({ }) ``` - +> ℹ️ **Info:** 自动验证邮箱格式:`user@domain.com` - + --- @@ -107,9 +106,9 @@ api_key: Field.password({ }) ``` - +> ⚠️ **Warning:** 密码字段在 UI 中自动掩码,如果 `encryption: true` 则加密存储 - + --- @@ -137,9 +136,9 @@ html_content: Field.html({ }) ``` - +> ⚠️ **Warning:** 渲染前务必净化 HTML 内容,防止 XSS 攻击 - + --- @@ -370,11 +369,11 @@ account: Field.masterDetail('account', { }) ``` - +> ℹ️ **Info:** **主从关系 vs 查找:** - **主从关系:** 紧密耦合,级联删除,子记录继承安全性 - **查找:** 松散耦合,删除时置空,独立安全性 - + --- @@ -451,9 +450,9 @@ days_open: Field.formula({ }) ``` - +> ℹ️ **Info:** 公式字段自动计算且只读。可用函数请参见 [公式函数](/docs/references/data/formulas)。 - + --- diff --git a/content/docs/guides/field-types.mdx b/content/docs/guides/field-types.mdx index be8a9c3..b088c75 100644 --- a/content/docs/guides/field-types.mdx +++ b/content/docs/guides/field-types.mdx @@ -3,7 +3,6 @@ title: Field Types Reference description: Complete guide to all ObjectStack field types with examples and configuration options --- -import { Callout } from 'fumadocs-ui/components/callout'; # Field Types Reference @@ -66,9 +65,9 @@ email: Field.email({ }) ``` - +> ℹ️ **Info:** Automatically validates email format: `user@domain.com` - + --- @@ -107,9 +106,9 @@ api_key: Field.password({ }) ``` - +> ⚠️ **Warning:** Password fields are automatically masked in UI and encrypted if `encryption: true` - + --- @@ -137,9 +136,9 @@ html_content: Field.html({ }) ``` - +> ⚠️ **Warning:** Always sanitize HTML content before rendering to prevent XSS attacks - + --- @@ -370,11 +369,11 @@ account: Field.masterDetail('account', { }) ``` - +> ℹ️ **Info:** **Master-Detail vs Lookup:** - **Master-Detail:** Tight coupling, cascade deletes, child inherits security - **Lookup:** Loose coupling, set null on delete, independent security - + --- @@ -451,9 +450,9 @@ days_open: Field.formula({ }) ``` - +> ℹ️ **Info:** Formula fields are automatically calculated and readonly. See [Formula Functions](/docs/references/data/formulas) for available functions. - + --- diff --git a/content/docs/guides/view-configuration.mdx b/content/docs/guides/view-configuration.mdx index dad717a..b8fd77b 100644 --- a/content/docs/guides/view-configuration.mdx +++ b/content/docs/guides/view-configuration.mdx @@ -3,7 +3,6 @@ title: View Configuration description: Complete guide to configuring Grid, Kanban, Calendar, Gantt views and forms in ObjectStack --- -import { Callout } from 'fumadocs-ui/components/callout'; # View Configuration Guide diff --git a/content/docs/guides/workflows-validation.mdx b/content/docs/guides/workflows-validation.mdx index 8d1fbe3..9872f2c 100644 --- a/content/docs/guides/workflows-validation.mdx +++ b/content/docs/guides/workflows-validation.mdx @@ -3,7 +3,6 @@ title: Validation Rules & Workflows description: Complete guide to validation rules and workflow automation in ObjectStack --- -import { Callout } from 'fumadocs-ui/components/callout'; # Validation Rules & Workflows @@ -73,9 +72,9 @@ export const Opportunity = ObjectSchema.create({ }); ``` - +> ℹ️ **Info:** **Important:** The `condition` is inverted logic - it defines when validation **FAILS**. If the condition evaluates to `TRUE`, the validation error is shown. - + ### Expression Examples diff --git a/content/docs/index.cn.mdx b/content/docs/index.cn.mdx index e76466e..e2e3fbd 100644 --- a/content/docs/index.cn.mdx +++ b/content/docs/index.cn.mdx @@ -23,7 +23,7 @@ import { Book, Compass, FileText, Layers } from 'lucide-react'; icon={} title="概念" href="/docs/concepts/manifesto" - description="理解"意图优于实现"和"本地优先"架构的理念。" + description="理解「意图优于实现」和「本地优先」架构的理念。" /> }