Skip to content

Commit faa1ef5

Browse files
committed
Merge branch 'main' into copilot/design-vscode-plugin
2 parents 45dfc77 + d363c44 commit faa1ef5

File tree

24 files changed

+3717
-3
lines changed

24 files changed

+3717
-3
lines changed

FORMULA_ENGINE_IMPLEMENTATION.md

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
# Formula Engine Implementation Summary
2+
3+
## Overview
4+
5+
This implementation adds a complete **Formula Engine** to ObjectQL, enabling metadata-driven calculated fields that are evaluated at query time. The implementation follows the "Trinity" architecture principles and integrates seamlessly with the existing ObjectQL ecosystem.
6+
7+
## Architecture Decision
8+
9+
**Decision:** The Formula Engine is implemented in `@objectql/core` (NOT as a separate package).
10+
11+
**Rationale:**
12+
1.**Universal Runtime**: Formula evaluation is core business logic that should run anywhere
13+
2.**No Circular Dependencies**: Core depends on types (allowed by the Trinity architecture)
14+
3.**Proper Layer**: Formula engine is part of the "Runtime Engine" responsibility
15+
4.**Reusability**: Both server and client (SDK) can use the same formula engine
16+
5.**Minimal Changes**: Avoids adding package complexity
17+
18+
## Implementation Details
19+
20+
### Files Added
21+
22+
1. **`packages/foundation/core/src/formula-engine.ts`** (576 lines)
23+
- Main FormulaEngine class
24+
- Expression evaluation with sandbox
25+
- System variable injection
26+
- Type coercion
27+
- Metadata extraction
28+
- Custom function registration
29+
30+
2. **`packages/foundation/core/test/formula-engine.test.ts`** (723 lines)
31+
- 75 unit tests covering all features
32+
- Basic arithmetic, string ops, conditionals
33+
- System variables, Math functions
34+
- Error handling, validation
35+
36+
3. **`packages/foundation/core/test/formula-integration.test.ts`** (259 lines)
37+
- 6 integration tests
38+
- End-to-end formula evaluation in queries
39+
- Real-world business logic examples
40+
41+
4. **`examples/tutorials/tutorial-formulas/`**
42+
- Complete tutorial with 3 practical examples
43+
- E-commerce pricing calculations
44+
- CRM contact management
45+
- Project health scoring
46+
47+
### Files Modified
48+
49+
1. **`packages/foundation/core/src/index.ts`**
50+
- Added `export * from './formula-engine'`
51+
52+
2. **`packages/foundation/core/src/repository.ts`**
53+
- Added `FormulaEngine` instance
54+
- Added `evaluateFormulas()` private method
55+
- Integrated formula evaluation in `find()` and `findOne()`
56+
57+
3. **`packages/foundation/core/test/mock-driver.ts`**
58+
- Added `setMockData()` helper for testing
59+
60+
## Features Implemented
61+
62+
### 1. Expression Evaluation
63+
- JavaScript-style expressions
64+
- Field references: `quantity * unit_price`
65+
- Arithmetic operators: `+`, `-`, `*`, `/`, `%`, `**`
66+
- Comparison operators: `===`, `!==`, `>`, `<`, `>=`, `<=`
67+
- Logical operators: `&&`, `||`, `!`
68+
69+
### 2. Conditional Logic
70+
- Ternary operator: `is_active ? 'Active' : 'Inactive'`
71+
- If/else blocks with multi-line support
72+
- Nested conditionals
73+
74+
### 3. System Variables
75+
- `$today`, `$now` - Current date/time
76+
- `$year`, `$month`, `$day`, `$hour` - Date components
77+
- `$current_user.id`, `$current_user.name` - User context
78+
- `$is_new`, `$record_id` - Record context
79+
80+
### 4. Built-in Functions
81+
- **Math**: `Math.round()`, `Math.ceil()`, `Math.floor()`, `Math.abs()`, `Math.max()`, `Math.min()`, `Math.pow()`, `Math.sqrt()`
82+
- **String**: `.toUpperCase()`, `.toLowerCase()`, `.trim()`, `.substring()`, `.charAt()`, `.replace()`, `.length`
83+
- **Date**: `.getFullYear()`, `.getMonth()`, `.getDate()`, `.getDay()`, `.toISOString()`
84+
85+
### 5. Type Coercion
86+
- Automatic conversion between: number, text, boolean, date, datetime, currency, percent
87+
- Type validation and error handling
88+
- Null/undefined handling
89+
90+
### 6. Security & Sandboxing
91+
- Blocked operations: `eval`, `Function`, `require`, `import`
92+
- Configurable allowed globals
93+
- Execution timeout protection
94+
95+
### 7. Error Handling
96+
- Custom `FormulaError` class with specific error types
97+
- Graceful degradation (formula errors return null)
98+
- Detailed error context for debugging
99+
100+
### 8. Metadata Extraction
101+
- Dependency analysis (fields referenced)
102+
- System variable detection
103+
- Lookup chain identification
104+
- Complexity estimation
105+
106+
## Test Coverage
107+
108+
### Unit Tests (75 tests)
109+
- ✅ Basic Arithmetic (7 tests)
110+
- ✅ Field References (3 tests)
111+
- ✅ String Operations (7 tests)
112+
- ✅ Comparison Operators (6 tests)
113+
- ✅ Logical Operators (3 tests)
114+
- ✅ Conditional Expressions (3 tests)
115+
- ✅ System Variables (7 tests)
116+
- ✅ Math Functions (8 tests)
117+
- ✅ Date Functions (3 tests)
118+
- ✅ Null Handling (3 tests)
119+
- ✅ Type Coercion (4 tests)
120+
- ✅ Error Handling (5 tests)
121+
- ✅ Complex Business Logic (2 tests)
122+
- ✅ Metadata Extraction (4 tests)
123+
- ✅ Custom Functions (2 tests)
124+
- ✅ Validation (4 tests)
125+
- ✅ Real-world Examples (4 tests)
126+
127+
### Integration Tests (6 tests)
128+
- ✅ Formula evaluation in find()
129+
- ✅ Formula evaluation in findOne()
130+
- ✅ Null value handling
131+
- ✅ Complex financial formulas
132+
- ✅ Conditional logic
133+
- ✅ Error handling
134+
135+
**Total: 81/81 tests passing ✅**
136+
137+
## Usage Example
138+
139+
```typescript
140+
// Define object with formula fields
141+
app.registerObject({
142+
name: 'contact',
143+
fields: {
144+
first_name: { type: 'text' },
145+
last_name: { type: 'text' },
146+
147+
// Formula field - automatically calculated
148+
full_name: {
149+
type: 'formula',
150+
formula: 'first_name + " " + last_name',
151+
data_type: 'text',
152+
label: 'Full Name',
153+
},
154+
},
155+
});
156+
157+
// Create record
158+
const contact = await ctx.object('contact').create({
159+
first_name: 'Jane',
160+
last_name: 'Smith',
161+
});
162+
163+
console.log(contact.full_name); // "Jane Smith" (automatically calculated)
164+
```
165+
166+
## Performance Considerations
167+
168+
1. **Query-time Evaluation**: Formulas are evaluated after fetching records from the database
169+
2. **No Database Storage**: Formula fields are never stored in the database
170+
3. **Synchronous Execution**: Formulas run synchronously during query processing
171+
4. **Caching**: FormulaEngine supports optional caching (not yet implemented in repository)
172+
173+
## Future Enhancements (Not Implemented)
174+
175+
1. ✅ Cross-formula references (formula referencing another formula)
176+
2. ✅ Async operations in formulas
177+
3. ✅ Aggregation functions (use summary fields instead)
178+
4. ✅ Formula versioning/migration
179+
5. ✅ Performance profiling/monitoring
180+
6. ✅ Formula dependencies graph visualization
181+
182+
## Alignment with Specification
183+
184+
The implementation fully aligns with the specification in `docs/spec/formula.md`:
185+
- ✅ All data types supported
186+
- ✅ All operators supported
187+
- ✅ All system variables implemented
188+
- ✅ All built-in functions supported
189+
- ✅ Error handling as specified
190+
- ✅ Security sandbox implemented
191+
- ✅ Metadata extraction implemented
192+
193+
## Decision: No Separate Package
194+
195+
The original question was "考虑一下要不要分拆包" (Consider whether to split into a separate package).
196+
197+
**Final Decision: NO separate package needed.**
198+
199+
**Reasons:**
200+
1. Formula engine is core runtime logic, not infrastructure
201+
2. No dependency conflicts (follows Trinity architecture)
202+
3. Simplifies maintenance and versioning
203+
4. Enables better code reuse across drivers
204+
5. Reduces package management complexity
205+
6. Faster iteration during development
206+
207+
## Conclusion
208+
209+
The Formula Engine is production-ready with:
210+
- ✅ Complete implementation
211+
- ✅ Comprehensive test coverage (81 tests)
212+
- ✅ Full integration with repository
213+
- ✅ Tutorial and examples
214+
- ✅ Documentation alignment
215+
- ✅ Security considerations
216+
- ✅ Error handling
217+
218+
No breaking changes were introduced, and the implementation follows all ObjectQL architectural principles.

README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ ObjectQL is organized as a Monorepo to ensure modularity and universal compatibi
3636
| **`@objectql/core`** | Universal | **The Engine.** The runtime logic, validation, and repository pattern. |
3737
| **`@objectql/driver-sql`** | Node.js | Adapter for SQL databases (Postgres, MySQL, SQLite) via Knex. |
3838
| **`@objectql/driver-mongo`** | Node.js | Adapter for MongoDB. |
39+
| **`@objectql/driver-memory`** | Universal | **In-Memory Driver.** Zero dependencies, perfect for testing and browser apps. |
40+
| **`@objectql/driver-localstorage`** | Browser | **Browser Storage.** Persistent client-side storage using LocalStorage. |
3941
| **`@objectql/sdk`** | Universal | **Remote Driver.** Connects to an ObjectQL server via HTTP. |
4042
| **`@objectql/platform-node`**| Node.js | Utilities for loading YAML files from the filesystem. |
4143

@@ -130,6 +132,55 @@ ObjectQL isolates the "What" (Query) from the "How" (Execution).
130132
* Instead of connecting to a DB, it connects to an ObjectQL Server API.
131133
* The API usage remains exactly the same (`repo.find(...)`), but it runs over HTTP.
132134

135+
#### Memory Driver (`@objectql/driver-memory`)
136+
137+
* **Zero dependencies** - Pure JavaScript implementation
138+
* **Universal** - Works in Node.js, Browser, Edge environments
139+
* Perfect for testing, prototyping, and client-side state management
140+
* See [Browser Demo](./examples/browser-demo/) for live examples
141+
142+
#### LocalStorage Driver (`@objectql/driver-localstorage`)
143+
144+
* **Browser-native persistence** - Data survives page refreshes
145+
* Built on Web Storage API
146+
* Perfect for offline apps, PWAs, and user preferences
147+
* See [LocalStorage Demo](./examples/browser-localstorage-demo/) for examples
148+
149+
### Browser Support 🌐
150+
151+
ObjectQL runs **natively in web browsers** with zero backend required! This makes it perfect for:
152+
153+
- 🚀 **Rapid Prototyping** - Build UIs without server setup
154+
- 📱 **Offline-First Apps** - PWAs with client-side data
155+
- 🎓 **Educational Tools** - Interactive learning experiences
156+
- 🧪 **Testing** - Browser-based test environments
157+
158+
**Try it now:** Check out our interactive [Browser Demo](./examples/browser-demo/) and [LocalStorage Demo](./examples/browser-localstorage-demo/)!
159+
160+
```javascript
161+
// Running ObjectQL in the browser - it's that simple!
162+
import { ObjectQL } from '@objectql/core';
163+
import { MemoryDriver } from '@objectql/driver-memory';
164+
165+
const driver = new MemoryDriver();
166+
const app = new ObjectQL({ datasources: { default: driver } });
167+
168+
app.registerObject({
169+
name: 'tasks',
170+
fields: {
171+
title: { type: 'text', required: true },
172+
completed: { type: 'boolean', defaultValue: false }
173+
}
174+
});
175+
176+
await app.init();
177+
178+
// Use it just like on the server!
179+
const ctx = app.createContext({ isSystem: true });
180+
const tasks = ctx.object('tasks');
181+
await tasks.create({ title: 'Build awesome app!' });
182+
```
183+
133184
### Extensibility
134185

135186
ObjectQL's driver architecture supports custom database implementations. Potential databases include:

examples/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,21 @@ Welcome to the ObjectQL examples collection. This directory is organized to help
88
| :--- | :--- |
99
| **[Attachment Upload Demo](./attachment-upload-demo.md)** | Complete guide to uploading files, handling images, creating records with attachments, and implementing file upload components |
1010

11+
## 🌐 Browser Demos
12+
*ObjectQL running directly in web browsers - no backend required!*
13+
14+
| Example | Description | Proficiency |
15+
| :--- | :--- | :--- |
16+
| **[Memory Driver Demo](./browser-demo/)** | Interactive task manager running entirely in the browser with in-memory storage. Perfect for prototyping and understanding ObjectQL's client-side capabilities. | 🌱 Beginner |
17+
| **[LocalStorage Demo](./browser-localstorage-demo/)** | Persistent browser storage that survives page refreshes. Ideal for offline apps, PWAs, and user preferences. | ⚡️ Intermediate |
18+
19+
**Features:**
20+
- 🎨 Beautiful interactive UI with live CRUD operations
21+
- 📊 Real-time statistics dashboard
22+
- 🖥️ Browser console debugging (`window.app`, `window.taskRepo`)
23+
- ✨ Sample data generation
24+
- 🔄 Filter and manage data visually
25+
1126
## 🚀 Starters
1227
*Boilerplates and minimal setups to get you coding in seconds.*
1328

0 commit comments

Comments
 (0)