Skip to content

Commit 157636d

Browse files
committed
refactor(food)
1 parent 96f9dfb commit 157636d

File tree

26 files changed

+1727
-892
lines changed

26 files changed

+1727
-892
lines changed

apps/models-research/src/food/IMPL.md

Lines changed: 0 additions & 111 deletions
This file was deleted.
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# Architectural Plan: The Unified Reactive List
2+
3+
**Status:** Draft
4+
**Date:** January 17, 2026
5+
**Context:** Merging the "Smart List" capabilities of legacy `createListApi` with the "Thermodynamic Model" architecture of `core-experimental`.
6+
7+
---
8+
9+
## 1. Executive Summary
10+
11+
Our research has identified a gap in the current `core-experimental` architecture. While `keyval` excels at managing the lifecycle and topology of polymorphic **Models** (Entities), it lacks the sophisticated list management capabilities (Filtering, Mapping, Path-based Updates) found in our legacy `createListApi` implementation.
12+
13+
This plan proposes a unified architecture that layers a **Query Engine** (ListApi) on top of the **Storage Engine** (Keyval), providing the best of both worlds: highly efficient entity management with ergonomic list operations.
14+
15+
## 2. The Architecture: Storage vs. View
16+
17+
We propose strictly separating the **Data Plane** (Storage) from the **Presentation Plane** (View).
18+
19+
### 2.1. Layer 1: The Storage Engine (`keyval`)
20+
21+
_Responsibility: Lifecycle, Persistence, Topology._
22+
23+
The current `keyval` implementation remains the foundation. It manages:
24+
25+
- **`$instances`**: A Record of active Model instances (Scopes).
26+
- **`$state`**: A serialized snapshot of the data.
27+
- **`lifecycle`**: Creating and destroying scopes based on ID presence.
28+
29+
**Improvements needed:**
30+
31+
- **`sync(Store<T[]>)`**: Ability to synchronize the order and existence of items from an external source (e.g., Server Response), replacing the manual `add/remove` logic.
32+
- **`update(id, path, value)`**: A generic update method that uses path string/array to modify deep state, reducing boilerplate.
33+
34+
### 2.2. Layer 2: The Query Engine (`ListApi`)
35+
36+
_Responsibility: Sorting, Filtering, Projection._
37+
38+
This is the new layer inspired by `createListApi`. It consumes a `keyval` and produces a derived **View**.
39+
40+
```typescript
41+
// Definition
42+
const allUsers = keyval({ model: UserModel });
43+
44+
// Derived View (Reactive)
45+
const admins = allUsers.view()
46+
.filter((user) => user.input.role === 'admin')
47+
.sort((a, b) => a.input.name.localeCompare(b.input.name));
48+
49+
// Consumption
50+
useList(admins, (user) => <UserCard model={user} />);
51+
```
52+
53+
**Key Features:**
54+
55+
1. **`$visibleKeys`**: A store containing only the IDs that match the filter.
56+
2. **Virtualization Support**: The View only tracks IDs, preventing render churn for items that are filtered out.
57+
3. **Chainable API**: `filter().sort().map()` creates a pipeline of derived stores.
58+
59+
## 3. Proposed API Specification
60+
61+
### 3.1. Enhanced `Keyval`
62+
63+
```typescript
64+
type Keyval<M> = {
65+
// ... existing fields ...
66+
67+
// New: Path-based update (inspired by legacy set)
68+
set: (id: string, path: string, value: any) => void;
69+
70+
// New: Create a derived View
71+
view: () => ListApi<M>;
72+
73+
// New: Synchronization (inspired by createStoreMap)
74+
sync: (source: Store<any[]>, getKey: (item: any) => string) => void;
75+
};
76+
```
77+
78+
### 3.2. `ListApi` (The View)
79+
80+
```typescript
81+
type ListApi<M> = {
82+
$items: Store<string[]>; // Filtered & Sorted IDs
83+
84+
// Refines the view
85+
filter: (fn: (instance: LensProxy<M>) => boolean | Store<boolean>) => ListApi<M>;
86+
sort: (fn: (a: LensProxy<M>, b: LensProxy<M>) => number) => ListApi<M>;
87+
88+
// Returns the subset of instances
89+
use: () => LensProxy<M>[];
90+
};
91+
```
92+
93+
## 4. Implementation Strategy
94+
95+
### Phase 1: Storage Improvements
96+
97+
1. **Implement `keyval.set`**: modify `updateInstanceFx` to accept a path array (e.g., `['facets', 'product', '$price']`) and traverse the instance to find the store to `rehydrate`.
98+
2. **Implement `keyval.sync`**: Create logic that watches an external array store.
99+
- **Diffing**: Calculate added/removed IDs.
100+
- **Reordering**: Update `$items` order to match source.
101+
- **Garbage Collection**: Call `destroy()` on removed IDs.
102+
103+
### Phase 2: Query Engine
104+
105+
1. **Implement `createListView(keyval)`**:
106+
- Create `$filter` store.
107+
- Derive `$filteredIds` from `keyval.$items` + `$filter` + `keyval.$instances`.
108+
- **Optimization**: Use `shouldNotify` logic (from legacy code) to avoid re-calculating filter if only unrelated data changed.
109+
110+
### Phase 3: Developer Experience
111+
112+
1. **Typed Paths**: Use TypeScript Template Literal Types to auto-complete paths in `.set()`.
113+
- `cart.set('id', 'facets.product.$quantity', 5)`
114+
115+
## 5. Comparison with Legacy Code
116+
117+
| Feature | Legacy `createStoreMap` | Legacy `createListApi` | New `keyval` + `ListApi` |
118+
| :------------------ | :---------------------- | :----------------------- | :-------------------------- |
119+
| **Source of Truth** | Map (Derived) | List + Map (Stand-alone) | Keyval (Storage) |
120+
| **Order** | Manual Sync | Managed Array | Managed Array |
121+
| **Updates** | `setState` (Manual) | `set(path)` (Smart) | `set(path)` (Smart) |
122+
| **Filtering** | N/A | Native `$filter` | Native `.view().filter()` |
123+
| **Typing** | Manual | Manual | **Fully Inferred (Models)** |
124+
125+
## 6. Conclusion
126+
127+
By integrating the "Smart List" features into the "Thermodynamic" architecture, we create a system that is not only performant (memory efficient) but also ergonomic for complex UI requirements (filtering/sorting). The distinction between **Storage** (Backend state) and **View** (UI state) is the critical architectural leap.

apps/models-research/src/food/impl_report.md

Lines changed: 0 additions & 54 deletions
This file was deleted.

apps/models-research/src/food/models/cart.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
1+
import { createEvent } from 'effector';
12
import { keyval, union } from '@effector-model/core-experimental';
23
import { pizzaModel } from './products/pizza';
34
import { drinkModel } from './products/drink';
45
import { coffeeModel } from './products/coffee';
56
import { cocktailModel } from './products/cocktail';
67
import { sauceModel } from './products/sauce';
78

9+
export const productUnion = union({
10+
pizza: pizzaModel,
11+
drink: drinkModel,
12+
coffee: coffeeModel,
13+
cocktail: cocktailModel,
14+
sauce: sauceModel,
15+
});
16+
817
export const cartModel = keyval({
9-
model: union({
10-
pizza: pizzaModel,
11-
drink: drinkModel,
12-
coffee: coffeeModel,
13-
cocktail: cocktailModel,
14-
sauce: sauceModel,
15-
}),
18+
model: productUnion,
1619
});
1720

21+
export const cartApi = cartModel.getItem(createEvent<{ id: string }>());
22+
1823
export const $totalPrice = cartModel.$state.map((state) => {
1924
return Object.values(state).reduce((sum: number, item: any) => {
2025
const price = item?.facets?.product?.$price || 0;

0 commit comments

Comments
 (0)