@alwatr/flux 9.15.0 → 9.16.0
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/README.md +989 -16
- package/dist/main.js +2 -2
- package/dist/main.js.map +1 -1
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -1,26 +1,999 @@
|
|
|
1
|
-
# Alwatr Flux
|
|
1
|
+
# 🌊 Alwatr Flux
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**The Ultimate Unidirectional Data Flow Architecture for Modern Web Applications**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@alwatr/flux)
|
|
6
|
+
[](https://github.com/Alwatr/alwatr/blob/next/LICENSE)
|
|
6
7
|
|
|
7
|
-
-
|
|
8
|
-
- [`@alwatr/action`](../nanolib/action) — Unidirectional Data Flow action layer with global event delegation
|
|
9
|
-
- [`@alwatr/directive`](../nanolib/directive) — attribute-based DOM directives with lifecycle hooks
|
|
10
|
-
- [`@alwatr/render-state`](../nanolib/render-state) — render state management utility
|
|
11
|
-
- [`@alwatr/local-storage`](../nanolib/local-storage) — versioned JSON in `localStorage`
|
|
12
|
-
- [`@alwatr/session-storage`](../nanolib/session-storage) — versioned JSON in `sessionStorage`
|
|
8
|
+
> A powerful, lightning-fast, zero-dependency reactive architecture bundle that brings together signals, actions, directives, and client-side storage into a cohesive, production-ready system for building scalable Progressive Web Applications.
|
|
13
9
|
|
|
14
|
-
|
|
10
|
+
---
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
## 🎯 What is Alwatr Flux?
|
|
13
|
+
|
|
14
|
+
`@alwatr/flux` is not just another state management library — it's a **complete architectural framework** that implements the **Unidirectional Data Flow (UDF)** pattern with unprecedented performance and developer experience.
|
|
15
|
+
|
|
16
|
+
Born from years of building production PWAs and inspired by the best ideas from React, Qwik, Solid.js, and Svelte, Flux combines:
|
|
17
|
+
|
|
18
|
+
- **Fine-grained reactivity** via Signals (no Virtual DOM overhead)
|
|
19
|
+
- **Global event delegation** for O(1) boot time (inspired by Qwik's Resumability)
|
|
20
|
+
- **Declarative DOM directives** for clean, maintainable UI code
|
|
21
|
+
- **Type-safe action bus** with zero runtime overhead
|
|
22
|
+
- **Persistent state management** with automatic localStorage/sessionStorage sync
|
|
23
|
+
|
|
24
|
+
All in a **tree-shakeable, ESM-only** package that adds **less than 15KB** to your production bundle.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 🧠 Core Philosophy
|
|
29
|
+
|
|
30
|
+
Alwatr Flux is built on three fundamental engineering principles:
|
|
31
|
+
|
|
32
|
+
### 1. **Strict Unidirectional Data Flow**
|
|
33
|
+
|
|
34
|
+
Data flows in **one direction only**: `View → Action → Controller → State → View`
|
|
35
|
+
|
|
36
|
+
- **Views** never manipulate state directly
|
|
37
|
+
- **Controllers** never touch the DOM
|
|
38
|
+
- **State** is the single source of truth
|
|
39
|
+
- **Actions** are the only way to request changes
|
|
40
|
+
|
|
41
|
+
This creates a **zero-coupling architecture** where every layer is independently testable and replaceable.
|
|
42
|
+
|
|
43
|
+
### 2. **Simplicity Over Cleverness (KISS & YAGNI)**
|
|
44
|
+
|
|
45
|
+
Instead of heavy Virtual DOM reconciliation, we use:
|
|
46
|
+
|
|
47
|
+
- **`lit-html`** for efficient, lazy template rendering
|
|
48
|
+
- **Signals** for surgical, fine-grained reactivity
|
|
49
|
+
- **Global delegation** for O(1) event listener registration
|
|
50
|
+
|
|
51
|
+
No magic. No hidden re-renders. No performance cliffs.
|
|
52
|
+
|
|
53
|
+
### 3. **Absolute Type Safety**
|
|
54
|
+
|
|
55
|
+
Through TypeScript's **Declaration Merging**, the entire action bus is fully typed:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// Define your actions once
|
|
59
|
+
declare module '@alwatr/flux' {
|
|
60
|
+
interface ActionRecord {
|
|
61
|
+
add_to_cart: {productId: number; qty: number};
|
|
62
|
+
open_drawer: 'menu' | 'settings';
|
|
63
|
+
logout: void;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Get compile-time safety everywhere
|
|
68
|
+
onAction('add_to_cart', (item) => {
|
|
69
|
+
// item is typed as {productId: number; qty: number}
|
|
70
|
+
cartService.add(item.productId, item.qty);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
dispatchAction('add_to_cart', {productId: 42, qty: 1}); // ✅
|
|
74
|
+
dispatchAction('add_to_cart', 'wrong'); // ❌ Compile error
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## ✨ Key Features
|
|
80
|
+
|
|
81
|
+
### ⚡ **O(1) Event Delegation**
|
|
82
|
+
|
|
83
|
+
Inspired by Qwik's Resumability, Flux uses **global event delegation** to eliminate per-element listeners:
|
|
84
|
+
|
|
85
|
+
- **One listener per event type** on `document.body` (not N listeners for N elements)
|
|
86
|
+
- **Zero boot-time cost** — works instantly with server-rendered HTML
|
|
87
|
+
- **Automatic support for dynamic content** — elements added after page load work immediately
|
|
88
|
+
- **Memory usage near zero** — no listener references to track
|
|
89
|
+
|
|
90
|
+
**Result:** 100 buttons = 1 listener. 10,000 buttons = still 1 listener.
|
|
91
|
+
|
|
92
|
+
### 🎯 **Fine-Grained Reactivity**
|
|
93
|
+
|
|
94
|
+
Signals provide **surgical updates** without Virtual DOM diffing:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import {createStateSignal, createComputedSignal, createEffect} from '@alwatr/flux';
|
|
98
|
+
|
|
99
|
+
// State
|
|
100
|
+
const firstName = createStateSignal({name: 'firstName', initialValue: 'Ali'});
|
|
101
|
+
const lastName = createStateSignal({name: 'lastName', initialValue: 'Mihandoost'});
|
|
102
|
+
|
|
103
|
+
// Computed (memoized, only recalculates when deps change)
|
|
104
|
+
const fullName = createComputedSignal({
|
|
105
|
+
name: 'fullName',
|
|
106
|
+
deps: [firstName, lastName],
|
|
107
|
+
get: () => `${firstName.get()} ${lastName.get()}`,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Effect (side-effect that runs when deps change)
|
|
111
|
+
createEffect({
|
|
112
|
+
name: 'log-name',
|
|
113
|
+
deps: [fullName],
|
|
114
|
+
run: () => console.log(`Name: ${fullName.get()}`),
|
|
115
|
+
runImmediately: true,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
lastName.set('Smith'); // Only fullName and the effect re-run — nothing else
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 🧩 **Declarative HTML Syntax**
|
|
122
|
+
|
|
123
|
+
Connect DOM events to typed actions without writing JavaScript:
|
|
124
|
+
|
|
125
|
+
```html
|
|
126
|
+
<!-- Simple action -->
|
|
127
|
+
<button on-click="open_drawer:menu">Menu</button>
|
|
128
|
+
|
|
129
|
+
<!-- Dynamic payload from input value -->
|
|
130
|
+
<input
|
|
131
|
+
on-input="search_query:$value"
|
|
132
|
+
placeholder="Search..."
|
|
133
|
+
/>
|
|
134
|
+
|
|
135
|
+
<!-- Form submission with validation -->
|
|
136
|
+
<form
|
|
137
|
+
on-submit="submit_form:$formdata; prevent,validate"
|
|
138
|
+
novalidate
|
|
139
|
+
>
|
|
140
|
+
<input
|
|
141
|
+
name="email"
|
|
142
|
+
type="email"
|
|
143
|
+
required
|
|
144
|
+
/>
|
|
145
|
+
<button type="submit">Submit</button>
|
|
146
|
+
</form>
|
|
147
|
+
|
|
148
|
+
<!-- Checkbox state -->
|
|
149
|
+
<input
|
|
150
|
+
type="checkbox"
|
|
151
|
+
on-change="toggle_feature:$checked"
|
|
152
|
+
/>
|
|
153
|
+
|
|
154
|
+
<!-- Fire once and remove -->
|
|
155
|
+
<button on-click="track_impression:hero_banner; once">Learn More</button>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Built-in modifiers:**
|
|
159
|
+
|
|
160
|
+
- `prevent` — calls `event.preventDefault()`
|
|
161
|
+
- `stop` — calls `event.stopPropagation()`
|
|
162
|
+
- `validate` — checks form validity before dispatch
|
|
163
|
+
- `once` — removes attribute after first fire
|
|
164
|
+
|
|
165
|
+
**Built-in payload resolvers:**
|
|
166
|
+
|
|
167
|
+
- `:$value` — reads `element.value`
|
|
168
|
+
- `:$formdata` — serializes nearest `<form>` to object
|
|
169
|
+
- `:$checked` — reads checkbox/radio state
|
|
170
|
+
|
|
171
|
+
### 🎨 **Attribute-Based Directives**
|
|
172
|
+
|
|
173
|
+
Attach TypeScript classes to DOM elements declaratively:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import {Directive, directive} from '@alwatr/flux';
|
|
177
|
+
|
|
178
|
+
@directive('tooltip')
|
|
179
|
+
export class TooltipDirective extends Directive {
|
|
180
|
+
protected init_(): void {
|
|
181
|
+
// this.element_ is the DOM element
|
|
182
|
+
// this.attributeValue is the attribute value
|
|
183
|
+
this.element_.title = this.attributeValue;
|
|
184
|
+
|
|
185
|
+
this.on_('mouseenter', this.show_);
|
|
186
|
+
this.on_('mouseleave', this.hide_);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private show_(): void {
|
|
190
|
+
console.log('Showing tooltip:', this.attributeValue);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private hide_(): void {
|
|
194
|
+
console.log('Hiding tooltip');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
```html
|
|
200
|
+
<button tooltip="Save your changes">Save</button>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Lifecycle hooks:**
|
|
204
|
+
|
|
205
|
+
- `init_()` — runs once after element is connected
|
|
206
|
+
- `lazyInit_()` — runs once when element enters viewport (lazy loading)
|
|
207
|
+
- `onVisible_()` — runs every time element enters viewport (impression tracking)
|
|
208
|
+
- `onHidden_()` — runs every time element leaves viewport (pause/cleanup)
|
|
209
|
+
|
|
210
|
+
### 💾 **Persistent State Management**
|
|
211
|
+
|
|
212
|
+
Signals that automatically sync with browser storage:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import {PersistentStateSignal, SessionStateSignal} from '@alwatr/flux';
|
|
216
|
+
|
|
217
|
+
// Persists across browser sessions
|
|
218
|
+
const userPrefs = new PersistentStateSignal({
|
|
219
|
+
name: 'user-preferences',
|
|
220
|
+
schemaVersion: 1,
|
|
221
|
+
initialValue: {theme: 'light', lang: 'en'},
|
|
222
|
+
saveDebounceDelay: 500, // Debounce writes to avoid thrashing
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Persists only for current tab session
|
|
226
|
+
const formDraft = new SessionStateSignal({
|
|
227
|
+
name: 'contact-form-draft',
|
|
228
|
+
schemaVersion: 1,
|
|
229
|
+
initialValue: {name: '', email: '', message: ''},
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Use like any other signal
|
|
233
|
+
userPrefs.set({theme: 'dark', lang: 'fa'});
|
|
234
|
+
console.log(userPrefs.get()); // {theme: 'dark', lang: 'fa'}
|
|
235
|
+
|
|
236
|
+
// Automatically saved to localStorage with debouncing
|
|
237
|
+
// Automatically loaded on next page load
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Features:**
|
|
241
|
+
|
|
242
|
+
- **Automatic versioning** — old schema versions are auto-cleared
|
|
243
|
+
- **Debounced writes** — prevents localStorage thrashing
|
|
244
|
+
- **Type-safe** — full TypeScript support
|
|
245
|
+
- **Migration-friendly** — bump `schemaVersion` to reset storage
|
|
246
|
+
|
|
247
|
+
### 📄 **Page-Ready Signal for MPA**
|
|
248
|
+
|
|
249
|
+
Lightweight page identity system for Multi-Page Applications:
|
|
250
|
+
|
|
251
|
+
```html
|
|
252
|
+
<body page-id="home">
|
|
253
|
+
<!-- Your content -->
|
|
254
|
+
</body>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
import {onPageReady, subscribePageReady, dispatchPageReady} from '@alwatr/flux';
|
|
259
|
+
|
|
260
|
+
// Subscribe to specific page
|
|
261
|
+
onPageReady('home', () => {
|
|
262
|
+
console.log('Home page is ready');
|
|
263
|
+
initHomePage();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Subscribe to all pages
|
|
267
|
+
subscribePageReady((pageId) => {
|
|
268
|
+
analytics.trackPageView(pageId);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Call once at bootstrap
|
|
272
|
+
dispatchPageReady(); // Reads [page-id] attribute and notifies subscribers
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### 🔄 **Signal Operators**
|
|
276
|
+
|
|
277
|
+
Transform signals with functional operators:
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import {createStateSignal, createDebouncedSignal, createFilteredSignal, createMappedSignal} from '@alwatr/flux';
|
|
281
|
+
|
|
282
|
+
const searchInput = createStateSignal({name: 'search', initialValue: ''});
|
|
283
|
+
|
|
284
|
+
// Debounce (wait 300ms after user stops typing)
|
|
285
|
+
const debouncedSearch = createDebouncedSignal(searchInput, {delay: 300});
|
|
286
|
+
|
|
287
|
+
// Filter (only emit non-empty values)
|
|
288
|
+
const validSearch = createFilteredSignal(debouncedSearch, {
|
|
289
|
+
filter: (value) => value.trim().length > 0,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Map (transform to API query)
|
|
293
|
+
const searchQuery = createMappedSignal(validSearch, {
|
|
294
|
+
map: (value) => ({q: value, limit: 10}),
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// React to final query
|
|
298
|
+
createEffect({
|
|
299
|
+
deps: [searchQuery],
|
|
300
|
+
run: () => fetchResults(searchQuery.get()),
|
|
301
|
+
});
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## 🏗️ Architecture Overview
|
|
307
|
+
|
|
308
|
+
Flux implements a **strict layered architecture** where each layer has a single responsibility:
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
312
|
+
│ VIEW LAYER │
|
|
313
|
+
│ (HTML templates, Directives, lit-html rendering) │
|
|
314
|
+
│ │
|
|
315
|
+
│ • Reads state from Signals │
|
|
316
|
+
│ • Dispatches Actions via on-<event> attributes │
|
|
317
|
+
│ • Never manipulates state directly │
|
|
318
|
+
└──────────────────┬──────────────────────────────────────────┘
|
|
319
|
+
│ on-click="add_to_cart:42"
|
|
320
|
+
▼
|
|
321
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
322
|
+
│ ACTION LAYER │
|
|
323
|
+
│ (@alwatr/action — Global Event Delegation) │
|
|
324
|
+
│ │
|
|
325
|
+
│ • Captures DOM events via document.body listener │
|
|
326
|
+
│ • Parses on-<event> attributes │
|
|
327
|
+
│ • Runs modifiers (prevent, validate, once) │
|
|
328
|
+
│ • Resolves payload ($value, $formdata) │
|
|
329
|
+
│ • Dispatches typed action to ChannelSignal │
|
|
330
|
+
└──────────────────┬──────────────────────────────────────────┘
|
|
331
|
+
│ dispatchAction('add_to_cart', 42)
|
|
332
|
+
▼
|
|
333
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
334
|
+
│ CONTROLLER LAYER │
|
|
335
|
+
│ (Business Logic, Services, Use Cases) │
|
|
336
|
+
│ │
|
|
337
|
+
│ • Subscribes to Actions via onAction() │
|
|
338
|
+
│ • Executes business logic │
|
|
339
|
+
│ • Updates State via Signal.set() │
|
|
340
|
+
│ • Never touches DOM directly │
|
|
341
|
+
└──────────────────┬──────────────────────────────────────────┘
|
|
342
|
+
│ cartSignal.set(newCart)
|
|
343
|
+
▼
|
|
344
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
345
|
+
│ STATE LAYER │
|
|
346
|
+
│ (@alwatr/signal — Reactive State Management) │
|
|
347
|
+
│ │
|
|
348
|
+
│ • StateSignal — mutable state │
|
|
349
|
+
│ • ComputedSignal — derived state (memoized) │
|
|
350
|
+
│ • EffectSignal — side effects │
|
|
351
|
+
│ • PersistentStateSignal — localStorage sync │
|
|
352
|
+
│ • SessionStateSignal — sessionStorage sync │
|
|
353
|
+
└──────────────────┬──────────────────────────────────────────┘
|
|
354
|
+
│ signal.subscribe(render)
|
|
355
|
+
▼
|
|
356
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
357
|
+
│ VIEW LAYER │
|
|
358
|
+
│ (Re-render only affected DOM nodes) │
|
|
359
|
+
└─────────────────────────────────────────────────────────────┘
|
|
18
360
|
```
|
|
19
361
|
|
|
20
|
-
|
|
362
|
+
**Key architectural benefits:**
|
|
363
|
+
|
|
364
|
+
- **Zero coupling** — layers communicate only through well-defined interfaces
|
|
365
|
+
- **Testability** — each layer can be tested in isolation
|
|
366
|
+
- **Scalability** — add features without touching existing code
|
|
367
|
+
- **Predictability** — data flows in one direction only
|
|
368
|
+
- **Performance** — fine-grained updates, no full-tree reconciliation
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## 📦 Installation
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
# npm
|
|
376
|
+
npm install @alwatr/flux
|
|
377
|
+
|
|
378
|
+
# yarn
|
|
379
|
+
yarn add @alwatr/flux
|
|
380
|
+
|
|
381
|
+
# pnpm
|
|
382
|
+
pnpm add @alwatr/flux
|
|
383
|
+
|
|
384
|
+
# bun
|
|
385
|
+
bun add @alwatr/flux
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**Zero dependencies.** Everything you need is included.
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## 🚀 Quick Start
|
|
393
|
+
|
|
394
|
+
### 1. Bootstrap the Application
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
import {setupActionDelegation, dispatchPageReady} from '@alwatr/flux';
|
|
398
|
+
|
|
399
|
+
// Activate global event delegation (call once at app start)
|
|
400
|
+
setupActionDelegation();
|
|
401
|
+
|
|
402
|
+
// Dispatch page-ready signal (for MPA routing)
|
|
403
|
+
dispatchPageReady();
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### 2. Define Your Actions (Type Safety)
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
// src/actions.ts
|
|
410
|
+
declare module '@alwatr/flux' {
|
|
411
|
+
interface ActionRecord {
|
|
412
|
+
increment: void;
|
|
413
|
+
decrement: void;
|
|
414
|
+
set_count: number;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### 3. Create State
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
// src/state.ts
|
|
423
|
+
import {createStateSignal} from '@alwatr/flux';
|
|
424
|
+
|
|
425
|
+
export const counterSignal = createStateSignal({
|
|
426
|
+
name: 'counter',
|
|
427
|
+
initialValue: 0,
|
|
428
|
+
});
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### 4. Wire Up Controllers
|
|
432
|
+
|
|
433
|
+
```typescript
|
|
434
|
+
// src/controllers.ts
|
|
435
|
+
import {onAction} from '@alwatr/flux';
|
|
436
|
+
import {counterSignal} from './state.js';
|
|
437
|
+
|
|
438
|
+
onAction('increment', () => {
|
|
439
|
+
counterSignal.update((count) => count + 1);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
onAction('decrement', () => {
|
|
443
|
+
counterSignal.update((count) => count - 1);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
onAction('set_count', (value) => {
|
|
447
|
+
counterSignal.set(value);
|
|
448
|
+
});
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### 5. Build the View
|
|
452
|
+
|
|
453
|
+
```html
|
|
454
|
+
<!DOCTYPE html>
|
|
455
|
+
<html>
|
|
456
|
+
<body>
|
|
457
|
+
<div id="app">
|
|
458
|
+
<h1>
|
|
459
|
+
Counter:
|
|
460
|
+
<span id="count">0</span>
|
|
461
|
+
</h1>
|
|
462
|
+
<button on-click="decrement">-</button>
|
|
463
|
+
<button on-click="increment">+</button>
|
|
464
|
+
<input
|
|
465
|
+
type="number"
|
|
466
|
+
on-input="set_count:$value"
|
|
467
|
+
value="0"
|
|
468
|
+
/>
|
|
469
|
+
</div>
|
|
470
|
+
|
|
471
|
+
<script
|
|
472
|
+
type="module"
|
|
473
|
+
src="./main.js"
|
|
474
|
+
></script>
|
|
475
|
+
</body>
|
|
476
|
+
</html>
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
// main.js
|
|
481
|
+
import {setupActionDelegation} from '@alwatr/flux';
|
|
482
|
+
import {counterSignal} from './state.js';
|
|
483
|
+
import './controllers.js'; // Register action handlers
|
|
484
|
+
|
|
485
|
+
setupActionDelegation();
|
|
486
|
+
|
|
487
|
+
// Subscribe to state changes and update DOM
|
|
488
|
+
counterSignal.subscribe((count) => {
|
|
489
|
+
document.getElementById('count').textContent = count;
|
|
490
|
+
});
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
**That's it!** You now have a fully reactive, type-safe counter with:
|
|
494
|
+
|
|
495
|
+
- ✅ Zero boilerplate
|
|
496
|
+
- ✅ Compile-time type safety
|
|
497
|
+
- ✅ O(1) event handling
|
|
498
|
+
- ✅ Fine-grained reactivity
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
## 📚 Complete API Reference
|
|
503
|
+
|
|
504
|
+
### Signals
|
|
505
|
+
|
|
506
|
+
#### `createStateSignal<T>(config)`
|
|
507
|
+
|
|
508
|
+
Creates a mutable state signal.
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
const count = createStateSignal({
|
|
512
|
+
name: 'count',
|
|
513
|
+
initialValue: 0,
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
count.get(); // 0
|
|
517
|
+
count.set(1);
|
|
518
|
+
count.update((n) => n + 1);
|
|
519
|
+
count.subscribe((value) => console.log(value));
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
#### `createEventSignal<T>(config)`
|
|
523
|
+
|
|
524
|
+
Creates a stateless event signal (no value, only notifications).
|
|
525
|
+
|
|
526
|
+
```typescript
|
|
527
|
+
const onClick = createEventSignal({name: 'click'});
|
|
528
|
+
|
|
529
|
+
onClick.subscribe(() => console.log('Clicked!'));
|
|
530
|
+
onClick.dispatch(); // Notify all subscribers
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
#### `createComputedSignal<T>(config)`
|
|
534
|
+
|
|
535
|
+
Creates a derived signal (memoized, recalculates only when deps change).
|
|
536
|
+
|
|
537
|
+
```typescript
|
|
538
|
+
const fullName = createComputedSignal({
|
|
539
|
+
name: 'fullName',
|
|
540
|
+
deps: [firstName, lastName],
|
|
541
|
+
get: () => `${firstName.get()} ${lastName.get()}`,
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// IMPORTANT: Must call destroy() when done
|
|
545
|
+
fullName.destroy();
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
#### `createEffect(config)`
|
|
549
|
+
|
|
550
|
+
Runs side effects when dependencies change.
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
const effect = createEffect({
|
|
554
|
+
name: 'logger',
|
|
555
|
+
deps: [count],
|
|
556
|
+
run: () => console.log('Count:', count.get()),
|
|
557
|
+
runImmediately: true,
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
// IMPORTANT: Must call destroy() when done
|
|
561
|
+
effect.destroy();
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
#### `PersistentStateSignal<T>` / `SessionStateSignal<T>`
|
|
565
|
+
|
|
566
|
+
State signals that sync with browser storage.
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
const prefs = new PersistentStateSignal({
|
|
570
|
+
name: 'user-prefs',
|
|
571
|
+
schemaVersion: 1,
|
|
572
|
+
initialValue: {theme: 'light'},
|
|
573
|
+
saveDebounceDelay: 500,
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
prefs.set({theme: 'dark'}); // Auto-saved to localStorage
|
|
577
|
+
prefs.remove(); // Clear from storage
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
#### Signal Operators
|
|
581
|
+
|
|
582
|
+
```typescript
|
|
583
|
+
// Debounce
|
|
584
|
+
const debounced = createDebouncedSignal(source, {delay: 300});
|
|
585
|
+
|
|
586
|
+
// Filter
|
|
587
|
+
const filtered = createFilteredSignal(source, {
|
|
588
|
+
filter: (value) => value > 0,
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
// Map
|
|
592
|
+
const mapped = createMappedSignal(source, {
|
|
593
|
+
map: (value) => value * 2,
|
|
594
|
+
});
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
---
|
|
598
|
+
|
|
599
|
+
### Actions
|
|
600
|
+
|
|
601
|
+
#### `setupActionDelegation(eventTypes?)`
|
|
602
|
+
|
|
603
|
+
Activates global event delegation. Call once at app bootstrap.
|
|
604
|
+
|
|
605
|
+
```typescript
|
|
606
|
+
import {setupActionDelegation, DEFAULT_DELEGATED_EVENTS} from '@alwatr/flux';
|
|
607
|
+
|
|
608
|
+
// Use defaults (click, submit, input, change)
|
|
609
|
+
setupActionDelegation();
|
|
610
|
+
|
|
611
|
+
// Or add custom events
|
|
612
|
+
setupActionDelegation([...DEFAULT_DELEGATED_EVENTS, 'keydown', 'focus']);
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
#### `onAction<K>(actionId, handler)`
|
|
616
|
+
|
|
617
|
+
Subscribes to a typed action.
|
|
618
|
+
|
|
619
|
+
```typescript
|
|
620
|
+
const sub = onAction('add_to_cart', (item) => {
|
|
621
|
+
cartService.add(item.productId, item.qty);
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
sub.unsubscribe(); // Clean up when done
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
#### `dispatchAction<K>(actionId, payload?)`
|
|
628
|
+
|
|
629
|
+
Dispatches a typed action programmatically.
|
|
630
|
+
|
|
631
|
+
```typescript
|
|
632
|
+
dispatchAction('navigate', '/home');
|
|
633
|
+
dispatchAction('logout'); // void payload
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
#### `registerModifier(name, handler)`
|
|
637
|
+
|
|
638
|
+
Adds a custom modifier for `on-<event>` attributes.
|
|
639
|
+
|
|
640
|
+
```typescript
|
|
641
|
+
registerModifier('confirm', () => {
|
|
642
|
+
return window.confirm('Are you sure?');
|
|
643
|
+
});
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
```html
|
|
647
|
+
<button on-click="delete_item:42; confirm">Delete</button>
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
#### `registerPayloadResolver(name, resolver)`
|
|
651
|
+
|
|
652
|
+
Adds a custom payload resolver.
|
|
653
|
+
|
|
654
|
+
```typescript
|
|
655
|
+
registerPayloadResolver('$data-id', (_event, element) => {
|
|
656
|
+
return element.dataset.id;
|
|
657
|
+
});
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
```html
|
|
661
|
+
<button
|
|
662
|
+
on-click="select:$data-id"
|
|
663
|
+
data-id="42"
|
|
664
|
+
>
|
|
665
|
+
Select
|
|
666
|
+
</button>
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
---
|
|
670
|
+
|
|
671
|
+
### Directives
|
|
672
|
+
|
|
673
|
+
#### `@directive(name)` / `lazyDirective(name, Class)`
|
|
674
|
+
|
|
675
|
+
Registers a directive class.
|
|
676
|
+
|
|
677
|
+
```typescript
|
|
678
|
+
import {Directive, directive} from '@alwatr/flux';
|
|
679
|
+
|
|
680
|
+
// Eager registration (side effect at import)
|
|
681
|
+
@directive('my-directive')
|
|
682
|
+
export class MyDirective extends Directive {
|
|
683
|
+
protected init_(): void {
|
|
684
|
+
console.log('Element:', this.element_);
|
|
685
|
+
console.log('Attribute value:', this.attributeValue);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Lazy registration (tree-shakeable)
|
|
690
|
+
export class MyDirective extends Directive {
|
|
691
|
+
/* ... */
|
|
692
|
+
}
|
|
693
|
+
export const registerMyDirective = lazyDirective('my-directive', MyDirective);
|
|
694
|
+
|
|
695
|
+
// In consumer code:
|
|
696
|
+
registerMyDirective();
|
|
697
|
+
bootstrapDirectives();
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
#### `bootstrapDirectives()`
|
|
701
|
+
|
|
702
|
+
Scans the DOM and instantiates all registered directives.
|
|
703
|
+
|
|
704
|
+
```typescript
|
|
705
|
+
import {bootstrapDirectives} from '@alwatr/flux';
|
|
706
|
+
|
|
707
|
+
bootstrapDirectives(); // Call after DOM is ready
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
#### Directive Lifecycle
|
|
711
|
+
|
|
712
|
+
```typescript
|
|
713
|
+
class MyDirective extends Directive {
|
|
714
|
+
// Runs once after element is connected
|
|
715
|
+
protected init_(): void {}
|
|
716
|
+
|
|
717
|
+
// Runs once when element enters viewport (lazy loading)
|
|
718
|
+
protected lazyInit_(): void {}
|
|
719
|
+
|
|
720
|
+
// Runs every time element enters viewport
|
|
721
|
+
protected onVisible_(): void {}
|
|
722
|
+
|
|
723
|
+
// Runs every time element leaves viewport
|
|
724
|
+
protected onHidden_(): void {}
|
|
725
|
+
}
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
#### Directive Utility Decorators
|
|
729
|
+
|
|
730
|
+
```typescript
|
|
731
|
+
import {Directive, directive, query, queryAll, attribute, on} from '@alwatr/flux';
|
|
732
|
+
|
|
733
|
+
@directive('my-form')
|
|
734
|
+
class FormDirective extends Directive {
|
|
735
|
+
@query('.submit-btn')
|
|
736
|
+
accessor submitBtn!: HTMLButtonElement | null;
|
|
737
|
+
|
|
738
|
+
@queryAll('input')
|
|
739
|
+
accessor inputs!: NodeListOf<HTMLInputElement>;
|
|
740
|
+
|
|
741
|
+
@attribute('data-form-id')
|
|
742
|
+
accessor formId!: string | null;
|
|
743
|
+
|
|
744
|
+
protected init_(): void {
|
|
745
|
+
this.on_('submit', this.handleSubmit_);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
private handleSubmit_(event: Event): void {
|
|
749
|
+
event.preventDefault();
|
|
750
|
+
console.log('Form submitted:', this.formId);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
---
|
|
756
|
+
|
|
757
|
+
### Page Ready
|
|
758
|
+
|
|
759
|
+
#### `onPageReady(pageId, handler)`
|
|
760
|
+
|
|
761
|
+
Subscribes to a specific page becoming ready.
|
|
762
|
+
|
|
763
|
+
```typescript
|
|
764
|
+
onPageReady('home', () => {
|
|
765
|
+
console.log('Home page ready');
|
|
766
|
+
});
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
#### `subscribePageReady(handler)`
|
|
770
|
+
|
|
771
|
+
Subscribes to all page-ready events.
|
|
772
|
+
|
|
773
|
+
```typescript
|
|
774
|
+
subscribePageReady((pageId) => {
|
|
775
|
+
analytics.trackPageView(pageId);
|
|
776
|
+
});
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
#### `dispatchPageReady()`
|
|
780
|
+
|
|
781
|
+
Reads `[page-id]` attribute and notifies subscribers.
|
|
782
|
+
|
|
783
|
+
```typescript
|
|
784
|
+
dispatchPageReady(); // Call once at bootstrap
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
---
|
|
788
|
+
|
|
789
|
+
### Storage
|
|
790
|
+
|
|
791
|
+
#### `createLocalStorageProvider<T>(config)`
|
|
792
|
+
|
|
793
|
+
Creates a versioned localStorage provider.
|
|
794
|
+
|
|
795
|
+
```typescript
|
|
796
|
+
import {createLocalStorageProvider} from '@alwatr/flux';
|
|
797
|
+
|
|
798
|
+
const storage = createLocalStorageProvider({
|
|
799
|
+
name: 'user-data',
|
|
800
|
+
schemaVersion: 1,
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
storage.write({name: 'Ali', age: 30});
|
|
804
|
+
const data = storage.read(); // {name: 'Ali', age: 30} | null
|
|
805
|
+
storage.has(); // true
|
|
806
|
+
storage.remove();
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
#### `createSessionStorageProvider<T>(config)`
|
|
810
|
+
|
|
811
|
+
Same as `createLocalStorageProvider` but uses `sessionStorage`.
|
|
812
|
+
|
|
813
|
+
---
|
|
814
|
+
|
|
815
|
+
### Render State
|
|
816
|
+
|
|
817
|
+
#### `renderState<R, T>(state, renderRecord, thisArg?)`
|
|
818
|
+
|
|
819
|
+
Utility for state-based rendering (useful with FSM).
|
|
820
|
+
|
|
821
|
+
```typescript
|
|
822
|
+
import {renderState} from '@alwatr/flux';
|
|
823
|
+
|
|
824
|
+
const currentState = 'loading';
|
|
825
|
+
|
|
826
|
+
renderState(currentState, {
|
|
827
|
+
idle: () => html`
|
|
828
|
+
<p>Ready</p>
|
|
829
|
+
`,
|
|
830
|
+
loading: () => html`
|
|
831
|
+
<p>Loading...</p>
|
|
832
|
+
`,
|
|
833
|
+
success: () => html`
|
|
834
|
+
<p>Success!</p>
|
|
835
|
+
`,
|
|
836
|
+
error: () => html`
|
|
837
|
+
<p>Error!</p>
|
|
838
|
+
`,
|
|
839
|
+
_default: 'idle', // Fallback
|
|
840
|
+
});
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
---
|
|
844
|
+
|
|
845
|
+
## 🆚 Why Choose Alwatr Flux?
|
|
846
|
+
|
|
847
|
+
| Feature | React + Redux | Solid.js | Svelte | **Alwatr Flux** 🌊 |
|
|
848
|
+
| ---------------------- | ------------------------ | --------------- | ------------------------ | ---------------------------------- |
|
|
849
|
+
| **Boot Time** | High (hydration) | Medium | Medium | **Near-zero** (global delegation) |
|
|
850
|
+
| **Re-renders** | Common (needs `useMemo`) | Rare | Rare | **Never** (fine-grained signals) |
|
|
851
|
+
| **Component Coupling** | Prop drilling / Context | Props / Context | Props / Stores | **Zero** (action bus) |
|
|
852
|
+
| **Bundle Size** | Large (~45KB) | Medium (~7KB) | Medium (~2KB + compiler) | **Small (~15KB)** |
|
|
853
|
+
| **Type Safety** | Partial | Good | Good | **Absolute** (declaration merging) |
|
|
854
|
+
| **Learning Curve** | Steep | Medium | Easy | **Easy** (familiar patterns) |
|
|
855
|
+
| **SSR/SSG Support** | Complex | Good | Good | **Excellent** (resumable) |
|
|
856
|
+
| **Dynamic Content** | Needs re-hydration | Works | Works | **Works instantly** |
|
|
857
|
+
|
|
858
|
+
---
|
|
859
|
+
|
|
860
|
+
## 🎓 Real-World Example: Todo App
|
|
861
|
+
|
|
862
|
+
```typescript
|
|
863
|
+
// actions.ts
|
|
864
|
+
declare module '@alwatr/flux' {
|
|
865
|
+
interface ActionRecord {
|
|
866
|
+
'add_todo': string;
|
|
867
|
+
'toggle_todo': number;
|
|
868
|
+
'remove_todo': number;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// state.ts
|
|
873
|
+
import {createStateSignal} from '@alwatr/flux';
|
|
874
|
+
|
|
875
|
+
interface Todo {
|
|
876
|
+
id: number;
|
|
877
|
+
text: string;
|
|
878
|
+
done: boolean;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
export const todosSignal = createStateSignal<Todo[]>({
|
|
882
|
+
name: 'todos',
|
|
883
|
+
initialValue: [],
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
// controllers.ts
|
|
887
|
+
import {onAction} from '@alwatr/flux';
|
|
888
|
+
import {todosSignal} from './state.js';
|
|
889
|
+
|
|
890
|
+
let nextId = 1;
|
|
891
|
+
|
|
892
|
+
onAction('add_todo', (text) => {
|
|
893
|
+
todosSignal.update((todos) => [
|
|
894
|
+
...todos,
|
|
895
|
+
{id: nextId++, text, done: false},
|
|
896
|
+
]);
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
onAction('toggle_todo', (id) => {
|
|
900
|
+
todosSignal.update((todos) =>
|
|
901
|
+
todos.map((todo) =>
|
|
902
|
+
todo.id === id ? {...todo, done: !todo.done} : todo
|
|
903
|
+
)
|
|
904
|
+
);
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
onAction('remove_todo', (id) => {
|
|
908
|
+
todosSignal.update((todos) => todos.filter((t) => t.id !== id));
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
// view.html
|
|
912
|
+
<div id="app">
|
|
913
|
+
<input id="new-todo" on-change="add_todo:$value" placeholder="What needs to be done?" />
|
|
914
|
+
<ul id="todo-list"></ul>
|
|
915
|
+
</div>
|
|
916
|
+
|
|
917
|
+
// main.ts
|
|
918
|
+
import {setupActionDelegation, html, render} from '@alwatr/flux';
|
|
919
|
+
import {todosSignal} from './state.js';
|
|
920
|
+
import './controllers.js';
|
|
921
|
+
|
|
922
|
+
setupActionDelegation();
|
|
923
|
+
|
|
924
|
+
todosSignal.subscribe((todos) => {
|
|
925
|
+
render(
|
|
926
|
+
html`
|
|
927
|
+
${todos.map((todo) => html`
|
|
928
|
+
<li>
|
|
929
|
+
<input
|
|
930
|
+
type="checkbox"
|
|
931
|
+
.checked=${todo.done}
|
|
932
|
+
on-change="toggle_todo:${todo.id}"
|
|
933
|
+
/>
|
|
934
|
+
<span style="${todo.done ? 'text-decoration: line-through' : ''}">${todo.text}</span>
|
|
935
|
+
<button on-click="remove_todo:${todo.id}">×</button>
|
|
936
|
+
</li>
|
|
937
|
+
`)}
|
|
938
|
+
`,
|
|
939
|
+
document.getElementById('todo-list')
|
|
940
|
+
);
|
|
941
|
+
});
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
---
|
|
945
|
+
|
|
946
|
+
## 🏛️ Part of the Alwatr Ecosystem
|
|
947
|
+
|
|
948
|
+
`@alwatr/flux` is the **UI layer** of the Alwatr Developer Kit — a complete monorepo of nano-packages for building production-grade TypeScript applications.
|
|
949
|
+
|
|
950
|
+
**Other packages in the ecosystem:**
|
|
951
|
+
|
|
952
|
+
- **[@alwatr/signal](https://github.com/Alwatr/alwatr/tree/next/pkg/nanolib/signal)** — Fine-grained reactive signals (part of Flux)
|
|
953
|
+
- **[@alwatr/action](https://github.com/Alwatr/alwatr/tree/next/pkg/nanolib/action)** — Global event delegation action bus (part of Flux)
|
|
954
|
+
- **[@alwatr/directive](https://github.com/Alwatr/alwatr/tree/next/pkg/nanolib/directive)** — Attribute-based DOM directives (part of Flux)
|
|
955
|
+
- **[@alwatr/fsm](https://github.com/Alwatr/alwatr/tree/next/pkg/fsm)** — Type-safe Finite State Machine
|
|
956
|
+
- **[@alwatr/nanotron](https://github.com/Alwatr/alwatr/tree/next/pkg/nanotron)** — Lightweight API server framework
|
|
957
|
+
- **[@alwatr/nitrobase](https://github.com/Alwatr/alwatr/tree/next/pkg/nitrobase)** — In-memory JSON database
|
|
958
|
+
- **[@alwatr/fetch](https://github.com/Alwatr/alwatr/tree/next/pkg/nanolib/fetch)** — Enhanced fetch with retry, cache, deduplication
|
|
959
|
+
- **[@alwatr/logger](https://github.com/Alwatr/alwatr/tree/next/pkg/nanolib/logger)** — Scoped, debug-strippable logger
|
|
960
|
+
|
|
961
|
+
All packages follow the **nano-package principle**: small, focused, zero-dependency, tree-shakeable.
|
|
962
|
+
|
|
963
|
+
---
|
|
964
|
+
|
|
965
|
+
## 🤝 Contributing
|
|
966
|
+
|
|
967
|
+
We welcome contributions! Please see our [Contributing Guide](https://github.com/Alwatr/alwatr/blob/next/CONTRIBUTING.md).
|
|
968
|
+
|
|
969
|
+
**Ways to contribute:**
|
|
970
|
+
|
|
971
|
+
- 🐛 Report bugs
|
|
972
|
+
- 💡 Suggest features
|
|
973
|
+
- 📖 Improve documentation
|
|
974
|
+
- 🔧 Submit pull requests
|
|
975
|
+
|
|
976
|
+
---
|
|
977
|
+
|
|
978
|
+
## 📄 License
|
|
979
|
+
|
|
980
|
+
[MPL-2.0](https://github.com/Alwatr/alwatr/blob/next/LICENSE) © [S. Ali Mihandoost](https://ali.mihandoost.com)
|
|
981
|
+
|
|
982
|
+
---
|
|
983
|
+
|
|
984
|
+
## 🔗 Links
|
|
985
|
+
|
|
986
|
+
- **GitHub:** [github.com/Alwatr/alwatr](https://github.com/Alwatr/alwatr)
|
|
987
|
+
- **npm:** [@alwatr/flux](https://www.npmjs.com/package/@alwatr/flux)
|
|
988
|
+
- **Documentation:** [github.com/Alwatr/alwatr/tree/next/pkg/flux](https://github.com/Alwatr/alwatr/tree/next/pkg/flux)
|
|
989
|
+
- **Issues:** [github.com/Alwatr/alwatr/issues](https://github.com/Alwatr/alwatr/issues)
|
|
990
|
+
|
|
991
|
+
---
|
|
992
|
+
|
|
993
|
+
<div align="center">
|
|
21
994
|
|
|
22
|
-
|
|
995
|
+
**Built with ❤️ by the Alwatr team**
|
|
23
996
|
|
|
24
|
-
|
|
997
|
+
_Making web development fast, simple, and enjoyable_
|
|
25
998
|
|
|
26
|
-
|
|
999
|
+
</div>
|
package/dist/main.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/* 📦 @alwatr/flux v9.
|
|
1
|
+
/* 📦 @alwatr/flux v9.16.0 */
|
|
2
2
|
export*from"@alwatr/signal";export*from"@alwatr/action";export*from"@alwatr/directive";export*from"@alwatr/render-state";export*from"@alwatr/local-storage";export*from"@alwatr/session-storage";export*from"@alwatr/page-ready";
|
|
3
3
|
|
|
4
|
-
//# debugId=
|
|
4
|
+
//# debugId=C06C2CEF2EC7ADAE64756E2164756E21
|
|
5
5
|
//# sourceMappingURL=main.js.map
|
package/dist/main.js.map
CHANGED
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
"// UI and reactive bundle — signals, actions, directives, and client-side storage.\n// This package aggregates all UI-layer nanolibs for convenient single-import usage.\n\nexport * from '@alwatr/signal';\nexport * from '@alwatr/action';\nexport * from '@alwatr/directive';\nexport * from '@alwatr/render-state';\nexport * from '@alwatr/local-storage';\nexport * from '@alwatr/session-storage';\nexport * from '@alwatr/page-ready';\nexport type * from '@alwatr/type-helper';\n"
|
|
6
6
|
],
|
|
7
7
|
"mappings": ";AAGA,4BACA,4BACA,+BACA,kCACA,mCACA,qCACA",
|
|
8
|
-
"debugId": "
|
|
8
|
+
"debugId": "C06C2CEF2EC7ADAE64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alwatr/flux",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.16.0",
|
|
4
4
|
"description": "UI and reactive library bundle for ECMAScript (JavaScript/TypeScript) projects — signals, actions, directives, and storage.",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"author": "S. Ali Mihandoost <ali.mihandoost@gmail.com> (https://ali.mihandoost.com)",
|
|
@@ -21,18 +21,18 @@
|
|
|
21
21
|
},
|
|
22
22
|
"sideEffects": false,
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@alwatr/action": "9.
|
|
25
|
-
"@alwatr/directive": "9.
|
|
26
|
-
"@alwatr/local-storage": "9.
|
|
27
|
-
"@alwatr/page-ready": "9.
|
|
28
|
-
"@alwatr/render-state": "9.
|
|
29
|
-
"@alwatr/session-storage": "9.
|
|
30
|
-
"@alwatr/signal": "9.
|
|
24
|
+
"@alwatr/action": "9.16.0",
|
|
25
|
+
"@alwatr/directive": "9.16.0",
|
|
26
|
+
"@alwatr/local-storage": "9.16.0",
|
|
27
|
+
"@alwatr/page-ready": "9.16.0",
|
|
28
|
+
"@alwatr/render-state": "9.16.0",
|
|
29
|
+
"@alwatr/session-storage": "9.16.0",
|
|
30
|
+
"@alwatr/signal": "9.16.0",
|
|
31
31
|
"@alwatr/type-helper": "9.14.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@alwatr/nano-build": "9.14.0",
|
|
35
|
-
"@alwatr/standard": "9.
|
|
35
|
+
"@alwatr/standard": "9.16.0",
|
|
36
36
|
"typescript": "^6.0.3"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
@@ -81,5 +81,5 @@
|
|
|
81
81
|
"ui",
|
|
82
82
|
"unidirectional-data-flow"
|
|
83
83
|
],
|
|
84
|
-
"gitHead": "
|
|
84
|
+
"gitHead": "c210044f6e8ab444ec2f9e600f095761cbd279bd"
|
|
85
85
|
}
|