apprun 3.37.1 → 3.37.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -2
- package/README.md +5 -0
- package/ai/apprun-html.prompt.md +290 -0
- package/ai/apprun.prompt.md +166 -0
- package/apprun.d.ts +7 -4
- package/dist/apprun-code.js +1 -1
- package/dist/apprun-code.js.map +1 -1
- package/dist/apprun-dev-tools.js +1 -1
- package/dist/apprun-dev-tools.js.map +1 -1
- package/dist/apprun-html.esm.js +1 -1
- package/dist/apprun-html.esm.js.map +1 -1
- package/dist/apprun-html.js +1 -1
- package/dist/apprun-html.js.map +1 -1
- package/dist/apprun-play-html.esm.js +1 -1
- package/dist/apprun-play-html.esm.js.map +1 -1
- package/dist/apprun-play.js +1 -1
- package/dist/apprun-play.js.map +1 -1
- package/dist/apprun.esm.js +1 -1
- package/dist/apprun.esm.js.map +1 -1
- package/dist/apprun.js +1 -1
- package/dist/apprun.js.map +1 -1
- package/esm/apprun.js +2 -7
- package/esm/apprun.js.map +1 -1
- package/esm/component.js.map +1 -1
- package/esm/types.js +9 -3
- package/esm/types.js.map +1 -1
- package/esm/vdom-my-prop-attr.js +2 -2
- package/esm/vdom-my-prop-attr.js.map +1 -1
- package/esm/version.js +1 -1
- package/jsx-runtime.js +1 -1
- package/jsx-runtime.js.map +1 -1
- package/package.json +1 -1
- package/src/apprun.ts +3 -8
- package/src/component.ts +4 -6
- package/src/types.ts +11 -4
- package/src/vdom-my-prop-attr.ts +2 -2
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## V3.37.3
|
|
4
|
+
|
|
5
|
+
* Updated skip logic for - removed `value` property
|
|
6
|
+
|
|
7
|
+
## V3.37.2
|
|
8
|
+
|
|
9
|
+
* Include rule / prompt files for AI Coding Agent
|
|
10
|
+
* Improved type definitions for better TypeScript support
|
|
11
|
+
- Support async function and async generator in `Action<T>` that is used in `Update<T, E>`
|
|
12
|
+
- Added `State` type to allow the initial state to be a function or an async function
|
|
13
|
+
* Fixed routing basePath initialization issue - not to set basePath to location.pathname by default
|
|
14
|
+
|
|
3
15
|
## V3.37.1
|
|
4
16
|
|
|
5
17
|
* Make `immer` and `property-information` as dependencies
|
|
@@ -8,9 +20,8 @@
|
|
|
8
20
|
|
|
9
21
|
* Introduced `createState` helper for simplified immutable state updates using Immer [create-state-immer](#docs/done/blog-create-state-immer.md)
|
|
10
22
|
* Added `addComponents` function for adding components to routes [add-components](#docs/done/blog-addcomponents-feature.md)
|
|
11
|
-
* Improved type definitions for better TypeScript support
|
|
12
23
|
* Moved the component to dev-tools
|
|
13
|
-
|
|
24
|
+
|
|
14
25
|
|
|
15
26
|
## 3.36.1
|
|
16
27
|
|
package/README.md
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
[](https://apprun.js.org/#play)
|
|
5
5
|
[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][downloads-url] [![License][license-image]][license-url] [![twitter][twitter-badge]][twitter] [![Discord Chat][discord-image]][discord-invite]
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
🕵️ We now have rule / prompt files that you can use with your AI Coding Agent:
|
|
9
|
+
- [For Components using JSX](ai/apprun.prompt.md)
|
|
10
|
+
- [For Components using HTML](ai/apprun-html.prompt.md)
|
|
11
|
+
|
|
7
12
|
🚀 July 2025, We have started to improve the codebase using AI. See [whats new](#new) for details.
|
|
8
13
|
|
|
9
14
|
## Introduction
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
---
|
|
2
|
+
mode: agent
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# AppRun Component Creation Rules
|
|
6
|
+
|
|
7
|
+
When creating AppRun components, follow these guidelines for consistent, maintainable code.
|
|
8
|
+
|
|
9
|
+
## Core Component Architecture
|
|
10
|
+
- **HTML templates**: Always use `html` tagged literals for UI rendering
|
|
11
|
+
- **State management**: Handle state through event handlers that return new state objects
|
|
12
|
+
- **Event handling**: Use `event-name` syntax in templates with handlers `(state, param) => newState`
|
|
13
|
+
- **Local events**: Use `run()` for component-local events (no registration needed)
|
|
14
|
+
- **Immutable updates**: Always return new state objects, never mutate existing state
|
|
15
|
+
|
|
16
|
+
## Component Creation Patterns
|
|
17
|
+
|
|
18
|
+
### 1. Functional Components (Pure UI Components)
|
|
19
|
+
**When to use**: For reusable UI pieces that don't manage their own state.
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
// Create pure function components that take props and return HTML
|
|
23
|
+
export const AgentModal = (agent, onClose) => {
|
|
24
|
+
return html`
|
|
25
|
+
<div class="modal-overlay" @click=${run(onClose, false)}>
|
|
26
|
+
<div class="modal-content" @click=${(e) => e.stopPropagation()}>
|
|
27
|
+
<!-- Use conditional rendering for dynamic content -->
|
|
28
|
+
${agent.status ? html`<h2>${agent.name}</h2>` : html`
|
|
29
|
+
<input value="${agent.name || ''}" @input=${(e) => agent.name = e.target.value}>
|
|
30
|
+
`}
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
`;
|
|
34
|
+
};
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Rules for functional components**:
|
|
38
|
+
- Export as pure functions
|
|
39
|
+
- Accept props as parameters
|
|
40
|
+
- Return HTML template literals
|
|
41
|
+
- Handle events through callback props
|
|
42
|
+
- No internal state management
|
|
43
|
+
|
|
44
|
+
### 2. Stateful Page Components (Full Components)
|
|
45
|
+
**When to use**: For main application views that manage state and handle complex interactions.
|
|
46
|
+
|
|
47
|
+
**Step 1: Create async state initialization**
|
|
48
|
+
```js
|
|
49
|
+
// Always use async state function for API calls and data loading
|
|
50
|
+
const state = async () => {
|
|
51
|
+
const data = await api.getData();
|
|
52
|
+
return {
|
|
53
|
+
...initialState,
|
|
54
|
+
data,
|
|
55
|
+
loading: false
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Step 2: Define event handlers as separate functions**
|
|
61
|
+
```js
|
|
62
|
+
// Create specific handlers for each user interaction
|
|
63
|
+
const selectWorld = async (state, worldName) => {
|
|
64
|
+
if (worldName === state.worldName) return state;
|
|
65
|
+
const agents = await api.getAgents(worldName);
|
|
66
|
+
return ({ ...state, worldName, agents });
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const openModal = (state, item = null) => {
|
|
70
|
+
return ({
|
|
71
|
+
...state,
|
|
72
|
+
editingItem: item || { name: 'New Item' },
|
|
73
|
+
showModal: true
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Step 3: Create view function with proper rendering patterns**
|
|
79
|
+
```js
|
|
80
|
+
// View function should be pure - only render, never mutate
|
|
81
|
+
const view = (state) => {
|
|
82
|
+
return html`
|
|
83
|
+
<div class="container">
|
|
84
|
+
${state.items.map(item => html`
|
|
85
|
+
<div class="item" @click=${run(openModal, item)}>
|
|
86
|
+
${item.name}
|
|
87
|
+
</div>
|
|
88
|
+
`)}
|
|
89
|
+
${state.showModal ? ModalComponent(state.editingItem, closeModal) : ''}
|
|
90
|
+
</div>
|
|
91
|
+
`;
|
|
92
|
+
};
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Step 4: Define minimal update object for routing**
|
|
96
|
+
```js
|
|
97
|
+
// Keep update object minimal - most events handled by run()
|
|
98
|
+
const update = {
|
|
99
|
+
'/,#': state => state,
|
|
100
|
+
// Only add global events or routing here
|
|
101
|
+
};
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Step 5: Export Component instance**
|
|
105
|
+
```js
|
|
106
|
+
// Create and export component instance without class syntax
|
|
107
|
+
// enable global events with {global_event: true}
|
|
108
|
+
export default new Component(state, view, update, {global_event: true});
|
|
109
|
+
|
|
110
|
+
// Mount to DOM element
|
|
111
|
+
component.start('#main'); // For visible components
|
|
112
|
+
component.mount('#modal'); // For modal/hidden components
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Event Handling Best Practices
|
|
116
|
+
|
|
117
|
+
### Using run() for Local Events
|
|
118
|
+
**✅ Correct patterns**:
|
|
119
|
+
```js
|
|
120
|
+
// Simple event with parameter
|
|
121
|
+
@click=${run(selectWorld, world.name)}
|
|
122
|
+
|
|
123
|
+
// Event with callback function
|
|
124
|
+
@click=${run(closeModal, false)}
|
|
125
|
+
|
|
126
|
+
// Global event
|
|
127
|
+
@input=${run('updateModalAgentName')}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**❌ Common mistakes to avoid**:
|
|
131
|
+
```js
|
|
132
|
+
// DON'T wrap run() in arrow function - prevents re-rendering
|
|
133
|
+
@click=${() => run(selectWorld, world.name)}
|
|
134
|
+
|
|
135
|
+
// DON'T manually pass event in arrow function
|
|
136
|
+
@click=${(e) => run(updateName, e)}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Event Handler Function Rules
|
|
140
|
+
```js
|
|
141
|
+
// Event handlers automatically receive event as last parameter
|
|
142
|
+
const updateModalAgentName = (state, eventParam) => {
|
|
143
|
+
const name = eventParam.target.value;
|
|
144
|
+
return ({...state, agentName: name });
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// Handle event.stopPropagation() in handler when needed
|
|
148
|
+
const openAgentModal = (state, agent, e) => {
|
|
149
|
+
e.stopPropagation();
|
|
150
|
+
return ({...state, editingAgent: agent, showModal: true });
|
|
151
|
+
};
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
## Template Rendering Guidelines
|
|
156
|
+
|
|
157
|
+
### Conditional Rendering Patterns
|
|
158
|
+
```js
|
|
159
|
+
// Simple boolean conditions
|
|
160
|
+
${state.loading ? html`<div>Loading...</div>` : ''}
|
|
161
|
+
|
|
162
|
+
// Complex conditional with nested HTML blocks
|
|
163
|
+
${agent.status ? html`
|
|
164
|
+
<h2>${agent.name}</h2>
|
|
165
|
+
<p>Status: Active</p>
|
|
166
|
+
` : html`
|
|
167
|
+
<input value="${agent.name || ''}" @input=${run(updateName)}>
|
|
168
|
+
<button @click=${run(saveAgent)}>Save</button>
|
|
169
|
+
`}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### List Rendering with map()
|
|
173
|
+
```js
|
|
174
|
+
// Basic list rendering
|
|
175
|
+
${state.items.map(item => html`
|
|
176
|
+
<div class="${item.active ? 'active' : ''}">${item.name}</div>
|
|
177
|
+
`)}
|
|
178
|
+
|
|
179
|
+
// Complex list items with events
|
|
180
|
+
${state.agents.map(agent => html`
|
|
181
|
+
<div class="agent-card" @click=${run(selectAgent, agent.id)}>
|
|
182
|
+
<h3>${agent.name}</h3>
|
|
183
|
+
<button @click=${run(editAgent, agent)}
|
|
184
|
+
@click=${(e) => e.stopPropagation()}>
|
|
185
|
+
Edit
|
|
186
|
+
</button>
|
|
187
|
+
</div>
|
|
188
|
+
`)}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Component Composition
|
|
192
|
+
```js
|
|
193
|
+
// Pass data and callbacks to child components
|
|
194
|
+
${state.showModal ? AgentModal(state.editingAgent, closeModal) : ''}
|
|
195
|
+
|
|
196
|
+
// Conditional component rendering with fallbacks
|
|
197
|
+
${state.currentView === 'list'
|
|
198
|
+
? AgentList(state.agents, selectAgent)
|
|
199
|
+
: AgentDetail(state.selectedAgent, goBack)
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## State Management Rules
|
|
204
|
+
|
|
205
|
+
### Immutable State Updates
|
|
206
|
+
```js
|
|
207
|
+
// ✅ Always spread existing state for updates
|
|
208
|
+
return ({ ...state, newProperty: value });
|
|
209
|
+
|
|
210
|
+
// ✅ Update nested objects immutably
|
|
211
|
+
return ({
|
|
212
|
+
...state,
|
|
213
|
+
user: { ...state.user, name: newName },
|
|
214
|
+
items: [...state.items, newItem]
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// ❌ Never mutate state directly
|
|
218
|
+
state.newProperty = value; // Wrong!
|
|
219
|
+
state.items.push(newItem); // Wrong!
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Async State Updates
|
|
223
|
+
```js
|
|
224
|
+
// Standard async handler
|
|
225
|
+
const handler = async (state, param) => {
|
|
226
|
+
try {
|
|
227
|
+
const data = await api.call(param);
|
|
228
|
+
return ({ ...state, data, loading: false });
|
|
229
|
+
} catch (error) {
|
|
230
|
+
return ({ ...state, error: error.message, loading: false });
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// Generator for progressive updates
|
|
235
|
+
const handler = async function* (state, param) {
|
|
236
|
+
yield ({ ...state, loading: true });
|
|
237
|
+
try {
|
|
238
|
+
const data = await api.call(param);
|
|
239
|
+
yield ({ ...state, data, loading: false });
|
|
240
|
+
} catch (error) {
|
|
241
|
+
yield ({ ...state, error: error.message, loading: false });
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Error Handling in Components
|
|
247
|
+
```js
|
|
248
|
+
// Always handle errors in async operations
|
|
249
|
+
const saveData = async (state, data) => {
|
|
250
|
+
try {
|
|
251
|
+
await api.saveData(data);
|
|
252
|
+
return ({ ...state, showModal: false, saved: true });
|
|
253
|
+
} catch (error) {
|
|
254
|
+
return ({ ...state, error: `Save failed: ${error.message}` });
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Component Creation Checklist
|
|
260
|
+
|
|
261
|
+
When creating any AppRun component, ensure you follow these rules:
|
|
262
|
+
|
|
263
|
+
### Essential Requirements
|
|
264
|
+
- ✅ **Event handlers must return new state** - No return = no re-render
|
|
265
|
+
- ✅ **View functions are pure** - Only render, never mutate state
|
|
266
|
+
- ✅ **Use `run()` for local events** - No registration needed in update object
|
|
267
|
+
- ✅ **Use `app.run()` for global events** - Cross-component communication
|
|
268
|
+
- ✅ **Immutable state updates** - Always spread existing state `{...state, newProp}`
|
|
269
|
+
- ✅ **HTML template literals** - Use `html` tagged templates for all UI
|
|
270
|
+
- ✅ **Conditional rendering** - Use ternary operators and template conditionals
|
|
271
|
+
|
|
272
|
+
### Component Structure Guidelines
|
|
273
|
+
- ✅ **Functional components**: Export pure functions that take props and return HTML
|
|
274
|
+
- ✅ **Stateful components**: Use async state, separate handlers, Component instance
|
|
275
|
+
- ✅ **Error boundaries**: Always handle async errors in try/catch blocks
|
|
276
|
+
- ✅ **Component composition**: Pass data and callbacks to child components
|
|
277
|
+
- ✅ **Event delegation**: Use event.stopPropagation() when needed in handlers
|
|
278
|
+
|
|
279
|
+
### File Organization
|
|
280
|
+
- ✅ **One component per file** - Keep components focused and reusable
|
|
281
|
+
- ✅ **Named exports for functions** - `export const ComponentName = (...) => html`
|
|
282
|
+
- ✅ **Default export for pages** - `export default new Component(...)`
|
|
283
|
+
- ✅ **Import dependencies** - Use static imports, omit `.js`/`.ts` extensions
|
|
284
|
+
|
|
285
|
+
### Performance Considerations
|
|
286
|
+
- ✅ **Minimal update object** - Only include routing and global events
|
|
287
|
+
- ✅ **Efficient rendering** - Use conditional rendering to avoid unnecessary DOM updates
|
|
288
|
+
- ✅ **State normalization** - Keep state flat when possible for easier updates
|
|
289
|
+
- ✅ **Event handler optimization** - Define handlers outside render when possible
|
|
290
|
+
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
---
|
|
2
|
+
mode: agent
|
|
3
|
+
---
|
|
4
|
+
# AppRun Component Creation Guide
|
|
5
|
+
|
|
6
|
+
## Core Concepts
|
|
7
|
+
|
|
8
|
+
AppRun follows the State-View-Update architecture pattern with TypeScript support for type-safe component development.
|
|
9
|
+
|
|
10
|
+
## Step 1: Define State Interface
|
|
11
|
+
|
|
12
|
+
### **State Management**
|
|
13
|
+
1. Define typed state interface with all required properties
|
|
14
|
+
2. Initialize with sensible defaults including loading/error states
|
|
15
|
+
3. Plan for immutable updates using spread operator
|
|
16
|
+
4. Include states for: loading, error, data, UI state
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
interface ComponentState {
|
|
20
|
+
data: DataType[];
|
|
21
|
+
currentIndex: number;
|
|
22
|
+
loading: boolean;
|
|
23
|
+
error: string | null;
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Step 2: Create Component Structure
|
|
28
|
+
|
|
29
|
+
### **Component-Based Architecture**
|
|
30
|
+
- Extend `Component<StateType>` class
|
|
31
|
+
- Initialize state with async function for API data loading
|
|
32
|
+
- Plan MVU (Model-View-Update) separation
|
|
33
|
+
- Use functional design principles
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
export default class MyComponent extends Component<ComponentState> {
|
|
37
|
+
state = async () => {
|
|
38
|
+
const data = await getData();
|
|
39
|
+
return {
|
|
40
|
+
data,
|
|
41
|
+
currentIndex: 0,
|
|
42
|
+
loading: false,
|
|
43
|
+
error: null
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Step 3: Design View Function
|
|
50
|
+
|
|
51
|
+
### **View Structure**
|
|
52
|
+
```typescript
|
|
53
|
+
view = (state: State) => {
|
|
54
|
+
// Guard clauses first (early returns)
|
|
55
|
+
if (state.loading) return loadingView;
|
|
56
|
+
if (state.error) return errorView;
|
|
57
|
+
if (state.empty) return emptyView;
|
|
58
|
+
|
|
59
|
+
// Main content last
|
|
60
|
+
return mainView;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### **Multi-State Rendering**
|
|
65
|
+
- State-driven conditional rendering with guard clauses
|
|
66
|
+
- Early returns for cleaner code flow
|
|
67
|
+
- Dedicated views for each state (loading, error, empty, main)
|
|
68
|
+
|
|
69
|
+
### **Accessibility & Responsive Design**
|
|
70
|
+
1. Use semantic HTML elements
|
|
71
|
+
2. Add `title` attributes for tooltips
|
|
72
|
+
3. Proper button usage over div+click
|
|
73
|
+
4. Use conditional rendering based on data size
|
|
74
|
+
5. Implement adaptive visibility logic
|
|
75
|
+
6. Mobile-first button sizing
|
|
76
|
+
|
|
77
|
+
## Step 4: Implement Event Handling
|
|
78
|
+
|
|
79
|
+
### **$onclick Usage Rules**
|
|
80
|
+
|
|
81
|
+
#### Action Pattern
|
|
82
|
+
```typescript
|
|
83
|
+
// String actions for simple state updates
|
|
84
|
+
$onclick='action-name'
|
|
85
|
+
|
|
86
|
+
// Tuple actions for data passing
|
|
87
|
+
$onclick={['action-name', data]}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### Event Handling Best Practices
|
|
91
|
+
1. Use `$onclick` for all click events
|
|
92
|
+
2. Simple actions: string literals
|
|
93
|
+
3. Data passing: tuple format `[action, data]`
|
|
94
|
+
4. Conditional actions: ternary in JSX
|
|
95
|
+
|
|
96
|
+
#### Action Naming Convention
|
|
97
|
+
- Use kebab-case: `'prev-item'`, `'next-item'`
|
|
98
|
+
- Be descriptive: `'select-item'`, `'enter-item'`
|
|
99
|
+
- Match business logic, not UI elements
|
|
100
|
+
|
|
101
|
+
## Step 5: Create Update Functions
|
|
102
|
+
|
|
103
|
+
### **Update Function Signatures**
|
|
104
|
+
```typescript
|
|
105
|
+
update = {
|
|
106
|
+
// State update (returns new state)
|
|
107
|
+
'action-name': (state: State, payload?: any): State => ({ ...state, changes }),
|
|
108
|
+
|
|
109
|
+
// Side effect (no return, no re-render)
|
|
110
|
+
'action-name': (state: State, payload?: any): void => { /* side effects */ },
|
|
111
|
+
|
|
112
|
+
// Async operations (generators for progressive updates)
|
|
113
|
+
'action-name': async function* (state: State): AsyncGenerator<State> { /* async logic */ }
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### **State Management Pattern**
|
|
118
|
+
- Use immutable state updates with spread operator
|
|
119
|
+
- Single source of truth for component state
|
|
120
|
+
- Handle all state transitions explicitly
|
|
121
|
+
|
|
122
|
+
## Step 6: Handle Data Loading
|
|
123
|
+
|
|
124
|
+
### **Data Loading Patterns**
|
|
125
|
+
- Use async state function for initial data loading
|
|
126
|
+
- Async generator pattern for progressive updates during user interactions
|
|
127
|
+
- Graceful handling of loading transitions
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// Progressive updates for user interactions
|
|
131
|
+
update = {
|
|
132
|
+
'refresh-data': async function* (state: State): AsyncGenerator<State> {
|
|
133
|
+
try {
|
|
134
|
+
yield { ...state, loading: true, error: null };
|
|
135
|
+
const data = await fetchData();
|
|
136
|
+
yield { ...state, data, loading: false, error: null };
|
|
137
|
+
} catch (error: any) {
|
|
138
|
+
yield { ...state, loading: false, error: error.message };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Step 7: Error Handling
|
|
145
|
+
|
|
146
|
+
### **Error Handling Strategy**
|
|
147
|
+
1. Wrap async operations in try-catch
|
|
148
|
+
2. Yield error states in generators
|
|
149
|
+
3. Provide user-friendly error messages
|
|
150
|
+
4. Include retry mechanisms
|
|
151
|
+
5. Defensive programming with null checks
|
|
152
|
+
|
|
153
|
+
## Step 8: Navigation & Side Effects
|
|
154
|
+
|
|
155
|
+
### **Navigation & Side-Effect Actions**
|
|
156
|
+
1. Use void actions for navigation (no re-render)
|
|
157
|
+
2. Direct `window.location.href` for page changes
|
|
158
|
+
3. Use anchor tags `<a href>` for static links
|
|
159
|
+
4. Actions with void return indicate no re-render needed
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
'navigate-action': (state: State, data: any): void => {
|
|
163
|
+
window.location.href = '/path/' + data.id;
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
package/apprun.d.ts
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
* - Comprehensive options typing matching implementation
|
|
32
32
|
* - Added proper deprecation warnings
|
|
33
33
|
* - Enhanced error handling and validation
|
|
34
|
+
* - Added support for async generator and generator functions in Action types
|
|
34
35
|
*/
|
|
35
36
|
|
|
36
37
|
import { TemplateResult } from 'lit-html';
|
|
@@ -39,6 +40,8 @@ declare module 'apprun' {
|
|
|
39
40
|
|
|
40
41
|
export type Element = HTMLElement | string;
|
|
41
42
|
|
|
43
|
+
export type State<T> = T | Promise<T> | (() => T) | (() => Promise<T>);
|
|
44
|
+
|
|
42
45
|
export type VNode = {
|
|
43
46
|
tag: string | Function,
|
|
44
47
|
props: {},
|
|
@@ -47,7 +50,7 @@ declare module 'apprun' {
|
|
|
47
50
|
|
|
48
51
|
export type VDOM = false | string | VNode | Array<VNode | string> | TemplateResult;
|
|
49
52
|
export type View<T> = (state: T) => VDOM | void;
|
|
50
|
-
export type Action<T> = (state: T, ...p: any[]) => T | Promise<T> | void
|
|
53
|
+
export type Action<T> = (state: T, ...p: any[]) => T | Promise<T> | void | AsyncGenerator<T> | Generator<T>;
|
|
51
54
|
export type ActionDef<T, E> = (readonly [E, Action<T>, {}?]);
|
|
52
55
|
export type Update<T, E = any> = ActionDef<T, E>[] | { [name: string]: Action<T> | {}[] } | (E | Action<T> | {})[];
|
|
53
56
|
export type Router = (url: string, ...args: any[]) => any;
|
|
@@ -106,7 +109,7 @@ declare module 'apprun' {
|
|
|
106
109
|
/** @deprecated Use runAsync() instead. query() will be removed in a future version. */
|
|
107
110
|
query(name: string, ...args: any[]): Promise<any[]>;
|
|
108
111
|
|
|
109
|
-
start<T, E = any>(element?: Element | string,
|
|
112
|
+
start<T, E = any>(element?: Element | string, state?: State<T>, view?: View<T>, update?: Update<T, E>,
|
|
110
113
|
options?: AppStartOptions<T>): Component<T, E>;
|
|
111
114
|
|
|
112
115
|
h(tag: string | Function, props?: any, ...children: any[]): VNode | VNode[];
|
|
@@ -126,9 +129,9 @@ declare module 'apprun' {
|
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
export class Component<T = any, E = any> {
|
|
129
|
-
constructor(state?: T
|
|
132
|
+
constructor(state?: State<T>, view?: View<T>, update?: Update<T, E>, options?: any);
|
|
130
133
|
readonly element: Element;
|
|
131
|
-
|
|
134
|
+
protected state: State<T>;
|
|
132
135
|
view?: View<T>;
|
|
133
136
|
update?: Update<T, E>;
|
|
134
137
|
|