fix: Gitea Traefik routing and connection pool optimization
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
This commit is contained in:
340
docs/modules/analytics.md
Normal file
340
docs/modules/analytics.md
Normal file
@@ -0,0 +1,340 @@
|
||||
# Analytics Module
|
||||
|
||||
**Unified Analytics System for Event Tracking and User Behavior**
|
||||
|
||||
The Analytics Module provides a comprehensive analytics system with GDPR compliance and multiple provider support.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **Event Tracking** - Track custom events
|
||||
- **Page View Tracking** - Automatic and manual page view tracking
|
||||
- **User Behavior Tracking** - Track user interactions
|
||||
- **Multiple Providers** - Support for Google Analytics, custom endpoints, and more
|
||||
- **GDPR Compliance** - Consent management and data anonymization
|
||||
- **Integration with LiveComponents** - Automatic tracking of LiveComponent events
|
||||
- **User Identification** - Identify users for user-level analytics
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```javascript
|
||||
import { Analytics } from './modules/analytics/index.js';
|
||||
|
||||
// Create analytics instance
|
||||
const analytics = Analytics.create({
|
||||
providers: ['google-analytics'],
|
||||
gdprCompliant: true,
|
||||
requireConsent: true
|
||||
});
|
||||
|
||||
// Give consent (GDPR)
|
||||
analytics.giveConsent();
|
||||
|
||||
// Track event
|
||||
await analytics.track('button_click', {
|
||||
button_id: 'submit',
|
||||
page: '/contact'
|
||||
});
|
||||
|
||||
// Track page view
|
||||
await analytics.trackPageView('/dashboard');
|
||||
```
|
||||
|
||||
### Module System Integration
|
||||
|
||||
```html
|
||||
<!-- Enable global analytics -->
|
||||
<script type="module">
|
||||
import { init } from './modules/analytics/index.js';
|
||||
|
||||
init({
|
||||
providers: [
|
||||
{
|
||||
type: 'google-analytics',
|
||||
measurementId: 'G-XXXXXXXXXX'
|
||||
},
|
||||
{
|
||||
type: 'custom',
|
||||
endpoint: '/api/analytics'
|
||||
}
|
||||
],
|
||||
gdprCompliant: true,
|
||||
requireConsent: true
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Access globally -->
|
||||
<script>
|
||||
// Give consent
|
||||
window.Analytics.giveConsent();
|
||||
|
||||
// Track event
|
||||
window.Analytics.track('purchase', {
|
||||
value: 99.99,
|
||||
currency: 'EUR'
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### Analytics.create(config)
|
||||
|
||||
Create a new Analytics instance.
|
||||
|
||||
**Parameters**:
|
||||
- `config.enabled` - Enable analytics (default: true)
|
||||
- `config.providers` - Array of provider configs
|
||||
- `config.gdprCompliant` - Enable GDPR compliance (default: true)
|
||||
- `config.requireConsent` - Require user consent (default: false)
|
||||
- `config.anonymizeIp` - Anonymize IP addresses (default: true)
|
||||
|
||||
### analytics.track(eventName, properties)
|
||||
|
||||
Track a custom event.
|
||||
|
||||
**Parameters**:
|
||||
- `eventName` - Event name
|
||||
- `properties` - Event properties
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
await analytics.track('purchase', {
|
||||
value: 99.99,
|
||||
currency: 'EUR',
|
||||
items: [{ id: 'product-1', quantity: 1 }]
|
||||
});
|
||||
```
|
||||
|
||||
### analytics.trackPageView(path, properties)
|
||||
|
||||
Track a page view.
|
||||
|
||||
**Parameters**:
|
||||
- `path` - Page path (optional, defaults to current path)
|
||||
- `properties` - Additional properties
|
||||
|
||||
### analytics.identify(userId, traits)
|
||||
|
||||
Identify a user.
|
||||
|
||||
**Parameters**:
|
||||
- `userId` - User ID
|
||||
- `traits` - User traits (name, email, etc.)
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
await analytics.identify('user-123', {
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com'
|
||||
});
|
||||
```
|
||||
|
||||
### analytics.trackBehavior(action, target, properties)
|
||||
|
||||
Track user behavior.
|
||||
|
||||
**Parameters**:
|
||||
- `action` - Action type (click, scroll, etc.)
|
||||
- `target` - Target element or identifier
|
||||
- `properties` - Additional properties
|
||||
|
||||
### analytics.giveConsent()
|
||||
|
||||
Give GDPR consent.
|
||||
|
||||
### analytics.revokeConsent()
|
||||
|
||||
Revoke GDPR consent.
|
||||
|
||||
---
|
||||
|
||||
## Providers
|
||||
|
||||
### Google Analytics
|
||||
|
||||
```javascript
|
||||
const analytics = Analytics.create({
|
||||
providers: [
|
||||
{
|
||||
type: 'google-analytics',
|
||||
measurementId: 'G-XXXXXXXXXX'
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
### Custom Provider
|
||||
|
||||
```javascript
|
||||
const analytics = Analytics.create({
|
||||
providers: [
|
||||
{
|
||||
type: 'custom',
|
||||
endpoint: '/api/analytics'
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
### Multiple Providers
|
||||
|
||||
```javascript
|
||||
const analytics = Analytics.create({
|
||||
providers: [
|
||||
'google-analytics',
|
||||
{
|
||||
type: 'custom',
|
||||
endpoint: '/api/analytics'
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GDPR Compliance
|
||||
|
||||
### Consent Management
|
||||
|
||||
```javascript
|
||||
const analytics = Analytics.create({
|
||||
requireConsent: true,
|
||||
gdprCompliant: true
|
||||
});
|
||||
|
||||
// Show consent banner
|
||||
showConsentBanner(() => {
|
||||
analytics.giveConsent();
|
||||
});
|
||||
```
|
||||
|
||||
### Data Anonymization
|
||||
|
||||
```javascript
|
||||
// IP addresses are automatically anonymized
|
||||
// PII fields are automatically removed
|
||||
await analytics.track('event', {
|
||||
email: 'user@example.com', // Will be removed
|
||||
ip: '192.168.1.1' // Will be anonymized to 192.168.1.0
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration with LiveComponents
|
||||
|
||||
```javascript
|
||||
import { Analytics } from './modules/analytics/index.js';
|
||||
import { LiveComponent } from './modules/livecomponent/index.js';
|
||||
|
||||
const analytics = Analytics.create();
|
||||
|
||||
// Track LiveComponent actions
|
||||
LiveComponent.on('action-executed', (componentId, actionName, params) => {
|
||||
analytics.track('livecomponent:action', {
|
||||
component_id: componentId,
|
||||
action: actionName,
|
||||
params
|
||||
});
|
||||
});
|
||||
|
||||
// Track component updates
|
||||
LiveComponent.on('component-updated', (componentId) => {
|
||||
analytics.track('livecomponent:updated', {
|
||||
component_id: componentId
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use Cases
|
||||
|
||||
### E-commerce Tracking
|
||||
|
||||
```javascript
|
||||
// Track purchase
|
||||
await analytics.track('purchase', {
|
||||
value: 99.99,
|
||||
currency: 'EUR',
|
||||
items: [
|
||||
{ id: 'product-1', name: 'Product 1', price: 49.99, quantity: 1 },
|
||||
{ id: 'product-2', name: 'Product 2', price: 50.00, quantity: 1 }
|
||||
]
|
||||
});
|
||||
|
||||
// Track add to cart
|
||||
await analytics.track('add_to_cart', {
|
||||
product_id: 'product-1',
|
||||
value: 49.99
|
||||
});
|
||||
```
|
||||
|
||||
### User Behavior Tracking
|
||||
|
||||
```javascript
|
||||
// Track button clicks
|
||||
document.addEventListener('click', (event) => {
|
||||
if (event.target.matches('[data-track]')) {
|
||||
analytics.trackBehavior('click', event.target.id, {
|
||||
text: event.target.textContent
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Track form submissions
|
||||
document.addEventListener('submit', (event) => {
|
||||
analytics.track('form_submit', {
|
||||
form_id: event.target.id,
|
||||
form_name: event.target.name
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Page View Tracking
|
||||
|
||||
```javascript
|
||||
// Automatic tracking on navigation
|
||||
// Or manual tracking
|
||||
await analytics.trackPageView('/dashboard', {
|
||||
section: 'admin',
|
||||
user_role: 'admin'
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Respect User Privacy** - Always get consent before tracking
|
||||
2. **Anonymize Data** - Remove PII and anonymize IPs
|
||||
3. **Track Meaningful Events** - Focus on business-critical events
|
||||
4. **Use Consistent Naming** - Use consistent event names
|
||||
5. **Monitor Performance** - Don't let analytics slow down the app
|
||||
|
||||
---
|
||||
|
||||
## Browser Support
|
||||
|
||||
- **Chrome/Edge**: 90+
|
||||
- **Firefox**: 88+
|
||||
- **Safari**: 14+
|
||||
- **Mobile**: iOS 14+, Android Chrome 90+
|
||||
|
||||
**Required Features**:
|
||||
- ES2020 JavaScript
|
||||
- Fetch API
|
||||
- CustomEvent support
|
||||
|
||||
---
|
||||
|
||||
**Next**: Continue with remaining modules →
|
||||
|
||||
180
docs/modules/animation-system-migration.md
Normal file
180
docs/modules/animation-system-migration.md
Normal file
@@ -0,0 +1,180 @@
|
||||
# Animation System Migration Guide
|
||||
|
||||
**Complete Migration Guide from Old Scroll Animation Modules to Unified Animation System**
|
||||
|
||||
This guide helps you migrate from the old scroll animation modules to the new unified Animation System.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The Animation System consolidates 8 separate modules into one unified system:
|
||||
|
||||
- `scrollfx` → `AnimationSystem` (fade-in, zoom-in)
|
||||
- `parallax` → `AnimationSystem` (parallax)
|
||||
- `scroll-timeline` → `AnimationSystem` (timeline)
|
||||
- `scroll-loop` → `AnimationSystem` (timeline with loop)
|
||||
- `scroll-dependent` → `AnimationSystem` (dependent animations)
|
||||
- `sticky-fade` → `AnimationSystem` (sticky-fade)
|
||||
- `sticky-steps` → `AnimationSystem` (sticky-steps)
|
||||
- `smooth-scroll` → Keep separate (different purpose)
|
||||
|
||||
---
|
||||
|
||||
## Migration Steps
|
||||
|
||||
### 1. Update Imports
|
||||
|
||||
**Before**:
|
||||
```javascript
|
||||
import { createTrigger } from './modules/scrollfx/index.js';
|
||||
import { init as initParallax } from './modules/parallax/index.js';
|
||||
```
|
||||
|
||||
**After**:
|
||||
```javascript
|
||||
import { AnimationSystem } from './modules/animation-system/index.js';
|
||||
```
|
||||
|
||||
### 2. Update Initialization
|
||||
|
||||
**Before**:
|
||||
```javascript
|
||||
createTrigger({ element: '.fade-in', offset: 0.85 });
|
||||
initParallax({ selector: '.parallax' });
|
||||
```
|
||||
|
||||
**After**:
|
||||
```javascript
|
||||
const system = AnimationSystem.create();
|
||||
// Auto-initializes based on HTML attributes
|
||||
// Or manually:
|
||||
document.querySelectorAll('.fade-in').forEach(el => {
|
||||
system.registerAnimation(el, { type: 'fade-in', offset: 0.85 });
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Update HTML Attributes
|
||||
|
||||
Most HTML attributes remain compatible, but you can use new unified attributes:
|
||||
|
||||
**Before**:
|
||||
```html
|
||||
<div class="fade-in-on-scroll" data-offset="0.85">Content</div>
|
||||
<div class="parallax" data-speed="0.5">Content</div>
|
||||
```
|
||||
|
||||
**After** (still works, or use new format):
|
||||
```html
|
||||
<div data-animate="fade-in" data-offset="0.85">Content</div>
|
||||
<div data-parallax data-speed="0.5">Content</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Module-Specific Migrations
|
||||
|
||||
### scrollfx → AnimationSystem
|
||||
|
||||
**Before**:
|
||||
```javascript
|
||||
import { createTrigger } from './modules/scrollfx/index.js';
|
||||
createTrigger({
|
||||
element: '.fade-in',
|
||||
offset: 0.85,
|
||||
baseDelay: 0.05,
|
||||
once: true
|
||||
});
|
||||
```
|
||||
|
||||
**After**:
|
||||
```javascript
|
||||
import { AnimationSystem } from './modules/animation-system/index.js';
|
||||
const system = AnimationSystem.create();
|
||||
document.querySelectorAll('.fade-in').forEach((el, index) => {
|
||||
system.registerAnimation(el, {
|
||||
type: 'fade-in',
|
||||
offset: 0.85,
|
||||
delay: index * 0.05,
|
||||
once: true
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### parallax → AnimationSystem
|
||||
|
||||
**Before**:
|
||||
```javascript
|
||||
import { init } from './modules/parallax/index.js';
|
||||
init({ selector: '.parallax', speed: 0.5 });
|
||||
```
|
||||
|
||||
**After**:
|
||||
```javascript
|
||||
import { AnimationSystem } from './modules/animation-system/index.js';
|
||||
const system = AnimationSystem.create();
|
||||
document.querySelectorAll('.parallax').forEach(el => {
|
||||
system.registerAnimation(el, {
|
||||
type: 'parallax',
|
||||
speed: 0.5
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### scroll-timeline → AnimationSystem
|
||||
|
||||
**Before**:
|
||||
```javascript
|
||||
import { init } from './modules/scroll-timeline/index.js';
|
||||
init({ attribute: 'data-scroll-step', triggerPoint: 0.4 });
|
||||
```
|
||||
|
||||
**After**:
|
||||
```javascript
|
||||
import { AnimationSystem } from './modules/animation-system/index.js';
|
||||
const system = AnimationSystem.create();
|
||||
document.querySelectorAll('[data-scroll-step]').forEach(el => {
|
||||
system.registerAnimation(el, {
|
||||
type: 'timeline',
|
||||
steps: parseInt(el.dataset.scrollSteps) || null,
|
||||
triggerPoint: 0.4
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
The Animation System maintains backward compatibility with existing HTML:
|
||||
|
||||
- Old CSS classes still work: `.fade-in-on-scroll`, `.zoom-in`, `.parallax`
|
||||
- Old data attributes still work: `data-parallax`, `data-scroll-step`, etc.
|
||||
- Old module initialization still works (but deprecated)
|
||||
|
||||
---
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
1. **Module Exports** - Old module exports are deprecated
|
||||
2. **JavaScript API** - Some APIs have changed (see migration examples)
|
||||
3. **Configuration** - Some config options have been renamed
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] All fade-in animations work
|
||||
- [ ] All parallax effects work
|
||||
- [ ] All timeline animations work
|
||||
- [ ] All sticky animations work
|
||||
- [ ] Performance is acceptable
|
||||
- [ ] No console errors
|
||||
- [ ] Backward compatibility maintained
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions, see the main [Animation System documentation](animation-system.md).
|
||||
|
||||
270
docs/modules/animation-system.md
Normal file
270
docs/modules/animation-system.md
Normal file
@@ -0,0 +1,270 @@
|
||||
# Animation System Module
|
||||
|
||||
**Unified Animation System for Scroll-Based Animations**
|
||||
|
||||
The Animation System Module consolidates all scroll animation modules into a single, unified system with backward compatibility.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **Fade-In Animations** - Elements fade in when scrolling into view
|
||||
- **Zoom-In Animations** - Elements zoom in when scrolling into view
|
||||
- **Parallax Effects** - Parallax scrolling effects
|
||||
- **Scroll Timeline** - Step-based scroll animations
|
||||
- **Sticky Fade** - Fade effects on sticky elements
|
||||
- **Sticky Steps** - Step-based animations on sticky elements
|
||||
- **IntersectionObserver Support** - Efficient scroll detection
|
||||
- **Backward Compatibility** - Works with existing HTML attributes
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```javascript
|
||||
import { AnimationSystem } from './modules/animation-system/index.js';
|
||||
|
||||
// Create animation system
|
||||
const system = AnimationSystem.create({
|
||||
enabled: true,
|
||||
useIntersectionObserver: true
|
||||
});
|
||||
|
||||
// Register animation
|
||||
system.registerAnimation(element, {
|
||||
type: 'fade-in',
|
||||
offset: 0.85,
|
||||
delay: 0.1,
|
||||
once: true
|
||||
});
|
||||
```
|
||||
|
||||
### Module System Integration
|
||||
|
||||
```html
|
||||
<!-- Enable global animation system -->
|
||||
<script type="module">
|
||||
import { init } from './modules/animation-system/index.js';
|
||||
|
||||
init({
|
||||
enabled: true,
|
||||
useIntersectionObserver: true
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Use data attributes (auto-initialized) -->
|
||||
<div class="fade-in-on-scroll" data-offset="0.85" data-delay="0.1">
|
||||
Content that fades in
|
||||
</div>
|
||||
|
||||
<div data-parallax data-speed="0.5">
|
||||
Parallax content
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### AnimationSystem.create(config)
|
||||
|
||||
Create a new AnimationSystem instance.
|
||||
|
||||
**Parameters**:
|
||||
- `config.enabled` - Enable animations (default: true)
|
||||
- `config.useIntersectionObserver` - Use IntersectionObserver (default: true)
|
||||
- `config.throttleDelay` - Throttle delay for scroll handler (default: 16ms)
|
||||
|
||||
### system.registerAnimation(element, config)
|
||||
|
||||
Register an animation for an element.
|
||||
|
||||
**Parameters**:
|
||||
- `element` - HTMLElement
|
||||
- `config.type` - Animation type
|
||||
- `config.offset` - Trigger offset (0-1)
|
||||
- `config.delay` - Animation delay
|
||||
- `config.once` - Trigger only once
|
||||
- `config.speed` - Parallax speed
|
||||
- `config.steps` - Number of steps
|
||||
- `config.triggerPoint` - Trigger point (0-1)
|
||||
|
||||
---
|
||||
|
||||
## Animation Types
|
||||
|
||||
### Fade-In
|
||||
|
||||
```javascript
|
||||
system.registerAnimation(element, {
|
||||
type: 'fade-in',
|
||||
offset: 0.85,
|
||||
delay: 0.1,
|
||||
once: true
|
||||
});
|
||||
```
|
||||
|
||||
### Zoom-In
|
||||
|
||||
```javascript
|
||||
system.registerAnimation(element, {
|
||||
type: 'zoom-in',
|
||||
offset: 0.85,
|
||||
delay: 0.1
|
||||
});
|
||||
```
|
||||
|
||||
### Parallax
|
||||
|
||||
```javascript
|
||||
system.registerAnimation(element, {
|
||||
type: 'parallax',
|
||||
speed: 0.5
|
||||
});
|
||||
```
|
||||
|
||||
### Timeline
|
||||
|
||||
```javascript
|
||||
system.registerAnimation(element, {
|
||||
type: 'timeline',
|
||||
steps: 5,
|
||||
triggerPoint: 0.4
|
||||
});
|
||||
```
|
||||
|
||||
### Sticky Fade
|
||||
|
||||
```javascript
|
||||
system.registerAnimation(element, {
|
||||
type: 'sticky-fade',
|
||||
fadeStart: 0,
|
||||
fadeEnd: 1
|
||||
});
|
||||
```
|
||||
|
||||
### Sticky Steps
|
||||
|
||||
```javascript
|
||||
system.registerAnimation(element, {
|
||||
type: 'sticky-steps',
|
||||
steps: 3
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## HTML Data Attributes
|
||||
|
||||
### Auto-Initialization
|
||||
|
||||
The system automatically initializes animations based on HTML attributes:
|
||||
|
||||
```html
|
||||
<!-- Fade in -->
|
||||
<div class="fade-in-on-scroll" data-offset="0.85" data-delay="0.1">
|
||||
Content
|
||||
</div>
|
||||
|
||||
<!-- Parallax -->
|
||||
<div data-parallax data-speed="0.5">
|
||||
Parallax content
|
||||
</div>
|
||||
|
||||
<!-- Timeline -->
|
||||
<div data-scroll-timeline data-scroll-steps="5">
|
||||
Timeline content
|
||||
</div>
|
||||
|
||||
<!-- Sticky fade -->
|
||||
<div data-sticky-fade data-fade-start="0" data-fade-end="1">
|
||||
Sticky content
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
The system maintains backward compatibility with old modules:
|
||||
|
||||
```html
|
||||
<!-- Old scrollfx classes still work -->
|
||||
<div class="fade-in-on-scroll">Content</div>
|
||||
<div class="zoom-in">Content</div>
|
||||
|
||||
<!-- Old parallax attributes still work -->
|
||||
<div class="parallax" data-speed="0.5">Content</div>
|
||||
|
||||
<!-- Old scroll-timeline attributes still work -->
|
||||
<div data-scroll-step="0">Content</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### From scrollfx
|
||||
|
||||
**Before**:
|
||||
```javascript
|
||||
import { createTrigger } from './modules/scrollfx/index.js';
|
||||
createTrigger({ element: '.fade-in', offset: 0.85 });
|
||||
```
|
||||
|
||||
**After**:
|
||||
```javascript
|
||||
import { AnimationSystem } from './modules/animation-system/index.js';
|
||||
const system = AnimationSystem.create();
|
||||
document.querySelectorAll('.fade-in').forEach(el => {
|
||||
system.registerAnimation(el, { type: 'fade-in', offset: 0.85 });
|
||||
});
|
||||
```
|
||||
|
||||
### From parallax
|
||||
|
||||
**Before**:
|
||||
```javascript
|
||||
import { init } from './modules/parallax/index.js';
|
||||
init({ selector: '.parallax' });
|
||||
```
|
||||
|
||||
**After**:
|
||||
```javascript
|
||||
import { AnimationSystem } from './modules/animation-system/index.js';
|
||||
const system = AnimationSystem.create();
|
||||
document.querySelectorAll('.parallax').forEach(el => {
|
||||
system.registerAnimation(el, { type: 'parallax', speed: 0.5 });
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use IntersectionObserver** - More efficient than scroll listeners
|
||||
2. **Set Appropriate Offsets** - Balance visibility with performance
|
||||
3. **Use Once for Performance** - Set `once: true` for elements that don't need to re-animate
|
||||
4. **Throttle Updates** - Use appropriate throttle delays
|
||||
5. **Clean Up** - Remove animations when elements are removed
|
||||
|
||||
---
|
||||
|
||||
## Browser Support
|
||||
|
||||
- **Chrome/Edge**: 90+
|
||||
- **Firefox**: 88+
|
||||
- **Safari**: 14+
|
||||
- **Mobile**: iOS 14+, Android Chrome 90+
|
||||
|
||||
**Required Features**:
|
||||
- ES2020 JavaScript
|
||||
- IntersectionObserver (for efficient detection)
|
||||
- requestAnimationFrame (for smooth animations)
|
||||
|
||||
---
|
||||
|
||||
**Next**: Continue with remaining modules →
|
||||
|
||||
382
docs/modules/cache-manager.md
Normal file
382
docs/modules/cache-manager.md
Normal file
@@ -0,0 +1,382 @@
|
||||
# Cache Manager Module
|
||||
|
||||
**Intelligent Caching for API Responses and Computed Values**
|
||||
|
||||
The Cache Manager Module provides a comprehensive caching system with multiple storage backends and caching strategies.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **Memory Cache** - Fast in-memory caching
|
||||
- **IndexedDB Cache** - Persistent browser storage
|
||||
- **Cache Strategies** - Multiple caching strategies (cache-first, network-first, stale-while-revalidate, etc.)
|
||||
- **Cache Invalidation** - Pattern-based cache invalidation
|
||||
- **Cache Warming** - Preload cache values
|
||||
- **Cache Analytics** - Track cache performance
|
||||
- **Integration with RequestDeduplicator** - Works with LiveComponent request deduplication
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```javascript
|
||||
import { CacheManager, CacheStrategy } from './modules/cache-manager/index.js';
|
||||
|
||||
// Create cache manager
|
||||
const cache = CacheManager.create({
|
||||
defaultStrategy: CacheStrategy.STALE_WHILE_REVALIDATE,
|
||||
defaultTTL: 3600000 // 1 hour
|
||||
});
|
||||
|
||||
// Get or set value
|
||||
const data = await cache.getOrSet('users', async () => {
|
||||
const response = await fetch('/api/users');
|
||||
return await response.json();
|
||||
});
|
||||
|
||||
// Get from cache
|
||||
const cached = await cache.get('users');
|
||||
|
||||
// Set in cache
|
||||
await cache.set('users', data, { ttl: 1800000 }); // 30 minutes
|
||||
```
|
||||
|
||||
### Module System Integration
|
||||
|
||||
```html
|
||||
<!-- Enable global cache manager -->
|
||||
<script type="module">
|
||||
import { init } from './modules/cache-manager/index.js';
|
||||
|
||||
init({
|
||||
defaultStrategy: 'stale-while-revalidate',
|
||||
enableIndexedDB: true
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Access globally -->
|
||||
<script>
|
||||
// Get or set
|
||||
const data = await window.CacheManager.getOrSet('key', async () => {
|
||||
return await fetch('/api/data').then(r => r.json());
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### CacheManager.create(config)
|
||||
|
||||
Create a new CacheManager instance.
|
||||
|
||||
**Parameters**:
|
||||
- `config.defaultStrategy` - Default caching strategy
|
||||
- `config.defaultTTL` - Default time-to-live in milliseconds
|
||||
- `config.maxMemorySize` - Maximum items in memory cache
|
||||
- `config.enableIndexedDB` - Enable IndexedDB persistence
|
||||
- `config.indexedDBName` - IndexedDB database name
|
||||
- `config.enableAnalytics` - Enable cache analytics
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
const cache = CacheManager.create({
|
||||
defaultStrategy: CacheStrategy.STALE_WHILE_REVALIDATE,
|
||||
defaultTTL: 3600000,
|
||||
maxMemorySize: 100,
|
||||
enableIndexedDB: true
|
||||
});
|
||||
```
|
||||
|
||||
### cache.get(key, options)
|
||||
|
||||
Get value from cache.
|
||||
|
||||
**Parameters**:
|
||||
- `key` - Cache key
|
||||
- `options.strategy` - Caching strategy override
|
||||
- `options.ttl` - Time-to-live override
|
||||
|
||||
**Returns**: `Promise<any | null>`
|
||||
|
||||
### cache.set(key, value, options)
|
||||
|
||||
Set value in cache.
|
||||
|
||||
**Parameters**:
|
||||
- `key` - Cache key
|
||||
- `value` - Value to cache
|
||||
- `options.strategy` - Caching strategy
|
||||
- `options.ttl` - Time-to-live
|
||||
|
||||
**Returns**: `Promise<void>`
|
||||
|
||||
### cache.getOrSet(key, computeFn, options)
|
||||
|
||||
Get value from cache or compute and cache it.
|
||||
|
||||
**Parameters**:
|
||||
- `key` - Cache key
|
||||
- `computeFn` - Function to compute value if not cached
|
||||
- `options` - Cache options
|
||||
|
||||
**Returns**: `Promise<any>`
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
const users = await cache.getOrSet('users', async () => {
|
||||
const response = await fetch('/api/users');
|
||||
return await response.json();
|
||||
}, { ttl: 1800000 });
|
||||
```
|
||||
|
||||
### cache.delete(key)
|
||||
|
||||
Delete value from cache.
|
||||
|
||||
**Parameters**:
|
||||
- `key` - Cache key
|
||||
|
||||
**Returns**: `Promise<void>`
|
||||
|
||||
### cache.clear()
|
||||
|
||||
Clear all cache.
|
||||
|
||||
**Returns**: `Promise<void>`
|
||||
|
||||
### cache.invalidate(pattern)
|
||||
|
||||
Invalidate cache entries matching a pattern.
|
||||
|
||||
**Parameters**:
|
||||
- `pattern` - String, RegExp, or function
|
||||
|
||||
**Returns**: `Promise<void>`
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
// Invalidate all user-related cache
|
||||
await cache.invalidate('user:');
|
||||
|
||||
// Invalidate with regex
|
||||
await cache.invalidate(/^user:\d+$/);
|
||||
|
||||
// Invalidate with function
|
||||
await cache.invalidate(key => key.startsWith('user:'));
|
||||
```
|
||||
|
||||
### cache.warm(keys, computeFn)
|
||||
|
||||
Warm cache by preloading values.
|
||||
|
||||
**Parameters**:
|
||||
- `keys` - Array of cache keys
|
||||
- `computeFn` - Function to compute value for each key
|
||||
|
||||
**Returns**: `Promise<void>`
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
await cache.warm(['user:1', 'user:2', 'user:3'], async (key) => {
|
||||
const userId = key.split(':')[1];
|
||||
const response = await fetch(`/api/users/${userId}`);
|
||||
return await response.json();
|
||||
});
|
||||
```
|
||||
|
||||
### cache.getAnalytics()
|
||||
|
||||
Get cache performance analytics.
|
||||
|
||||
**Returns**: `CacheAnalytics`
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
const analytics = cache.getAnalytics();
|
||||
console.log(`Hit rate: ${analytics.hitRate}%`);
|
||||
console.log(`Hits: ${analytics.hits}, Misses: ${analytics.misses}`);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cache Strategies
|
||||
|
||||
### Cache-First
|
||||
|
||||
Use cache if available, otherwise fetch.
|
||||
|
||||
```javascript
|
||||
const data = await cache.getOrSet('key', fetchData, {
|
||||
strategy: CacheStrategy.CACHE_FIRST
|
||||
});
|
||||
```
|
||||
|
||||
### Network-First
|
||||
|
||||
Try network first, fallback to cache.
|
||||
|
||||
```javascript
|
||||
const data = await cache.getOrSet('key', fetchData, {
|
||||
strategy: CacheStrategy.NETWORK_FIRST
|
||||
});
|
||||
```
|
||||
|
||||
### Stale-While-Revalidate
|
||||
|
||||
Return cache immediately, update in background.
|
||||
|
||||
```javascript
|
||||
const data = await cache.getOrSet('key', fetchData, {
|
||||
strategy: CacheStrategy.STALE_WHILE_REVALIDATE
|
||||
});
|
||||
```
|
||||
|
||||
### Network-Only
|
||||
|
||||
Always fetch from network, never use cache.
|
||||
|
||||
```javascript
|
||||
const data = await cache.getOrSet('key', fetchData, {
|
||||
strategy: CacheStrategy.NETWORK_ONLY
|
||||
});
|
||||
```
|
||||
|
||||
### Cache-Only
|
||||
|
||||
Only use cache, never fetch from network.
|
||||
|
||||
```javascript
|
||||
const data = await cache.get('key', {
|
||||
strategy: CacheStrategy.CACHE_ONLY
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration with RequestDeduplicator
|
||||
|
||||
```javascript
|
||||
import { CacheManager } from './modules/cache-manager/index.js';
|
||||
import { RequestDeduplicator } from './modules/livecomponent/RequestDeduplicator.js';
|
||||
|
||||
const cache = CacheManager.create();
|
||||
const deduplicator = new RequestDeduplicator();
|
||||
|
||||
// Use cache with request deduplication
|
||||
async function fetchWithCache(url) {
|
||||
return await cache.getOrSet(url, async () => {
|
||||
// Check for pending request
|
||||
const pending = deduplicator.getPendingRequest('api', 'GET', { url });
|
||||
if (pending) {
|
||||
return await pending;
|
||||
}
|
||||
|
||||
// Make request
|
||||
const promise = fetch(url).then(r => r.json());
|
||||
deduplicator.registerPendingRequest('api', 'GET', { url }, promise);
|
||||
|
||||
return await promise;
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use Cases
|
||||
|
||||
### API Response Caching
|
||||
|
||||
```javascript
|
||||
const cache = CacheManager.create({
|
||||
defaultStrategy: CacheStrategy.STALE_WHILE_REVALIDATE,
|
||||
defaultTTL: 300000 // 5 minutes
|
||||
});
|
||||
|
||||
async function getUsers() {
|
||||
return await cache.getOrSet('api:users', async () => {
|
||||
const response = await fetch('/api/users');
|
||||
return await response.json();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Computed Value Caching
|
||||
|
||||
```javascript
|
||||
const cache = CacheManager.create();
|
||||
|
||||
function expensiveComputation(input) {
|
||||
// Expensive operation
|
||||
return input * 2;
|
||||
}
|
||||
|
||||
async function getComputedValue(input) {
|
||||
const key = `computed:${input}`;
|
||||
return await cache.getOrSet(key, () => expensiveComputation(input), {
|
||||
ttl: 3600000 // Cache for 1 hour
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Cache Invalidation
|
||||
|
||||
```javascript
|
||||
// Invalidate all user-related cache when user updates
|
||||
async function updateUser(userId, data) {
|
||||
await fetch(`/api/users/${userId}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
// Invalidate related cache
|
||||
await cache.invalidate(`user:${userId}`);
|
||||
await cache.invalidate('users:list');
|
||||
}
|
||||
```
|
||||
|
||||
### Cache Warming
|
||||
|
||||
```javascript
|
||||
// Warm cache on page load
|
||||
async function warmCache() {
|
||||
await cache.warm(['user:1', 'user:2', 'user:3'], async (key) => {
|
||||
const userId = key.split(':')[1];
|
||||
const response = await fetch(`/api/users/${userId}`);
|
||||
return await response.json();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Choose Appropriate Strategy** - Use cache-first for static data, network-first for dynamic data
|
||||
2. **Set Appropriate TTL** - Balance freshness with performance
|
||||
3. **Invalidate on Updates** - Clear cache when data changes
|
||||
4. **Use Cache Warming** - Preload frequently accessed data
|
||||
5. **Monitor Analytics** - Track cache performance and adjust strategy
|
||||
|
||||
---
|
||||
|
||||
## Browser Support
|
||||
|
||||
- **Chrome/Edge**: 90+
|
||||
- **Firefox**: 88+
|
||||
- **Safari**: 14+
|
||||
- **Mobile**: iOS 14+, Android Chrome 90+
|
||||
|
||||
**Required Features**:
|
||||
- ES2020 JavaScript
|
||||
- IndexedDB (for persistent cache)
|
||||
- Promise support
|
||||
|
||||
---
|
||||
|
||||
**Next**: Continue with Phase 3 modules →
|
||||
|
||||
413
docs/modules/error-tracking.md
Normal file
413
docs/modules/error-tracking.md
Normal file
@@ -0,0 +1,413 @@
|
||||
# Error Tracking Module
|
||||
|
||||
**Centralized Error Tracking and Reporting**
|
||||
|
||||
The Error Tracking Module provides comprehensive error tracking, grouping, and reporting capabilities for production applications.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **Error Collection** - Automatically capture unhandled errors and promise rejections
|
||||
- **Error Grouping** - Group similar errors to reduce noise
|
||||
- **Error Reporting** - Send errors to backend for analysis
|
||||
- **Error Analytics** - Track error frequency and patterns
|
||||
- **Integration with ErrorBoundary** - Works with LiveComponent ErrorBoundary
|
||||
- **Source Map Support** - Map minified errors to source code
|
||||
- **Error Filtering** - Filter out known or irrelevant errors
|
||||
- **Sampling** - Control error reporting volume with sampling
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```javascript
|
||||
import { ErrorTracker } from './modules/error-tracking/index.js';
|
||||
|
||||
// Create error tracker
|
||||
const tracker = ErrorTracker.create({
|
||||
endpoint: '/api/errors',
|
||||
enabled: true,
|
||||
sampleRate: 1.0 // Report 100% of errors
|
||||
});
|
||||
|
||||
// Manually capture an error
|
||||
try {
|
||||
// Some code that might throw
|
||||
} catch (error) {
|
||||
tracker.captureException(error, {
|
||||
type: 'user-action',
|
||||
action: 'submit-form'
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Module System Integration
|
||||
|
||||
```html
|
||||
<!-- Enable global error tracking -->
|
||||
<script type="module">
|
||||
import { init } from './modules/error-tracking/index.js';
|
||||
|
||||
init({
|
||||
endpoint: '/api/errors',
|
||||
enabled: true,
|
||||
sampleRate: 0.1 // Report 10% of errors
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Access globally -->
|
||||
<script>
|
||||
// Errors are automatically captured
|
||||
// Or manually capture:
|
||||
window.ErrorTracker.captureException(new Error('Something went wrong'), {
|
||||
context: { userId: 123 }
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### ErrorTracker.create(config)
|
||||
|
||||
Create a new ErrorTracker instance.
|
||||
|
||||
**Parameters**:
|
||||
- `config.endpoint` - Backend endpoint for error reporting (default: '/api/errors')
|
||||
- `config.enabled` - Enable error tracking (default: true)
|
||||
- `config.sampleRate` - Sampling rate 0.0 to 1.0 (default: 1.0)
|
||||
- `config.maxErrors` - Maximum errors to keep in memory (default: 100)
|
||||
- `config.groupingWindow` - Time window for error grouping in ms (default: 60000)
|
||||
- `config.includeStack` - Include stack traces (default: true)
|
||||
- `config.includeContext` - Include context information (default: true)
|
||||
- `config.includeUserAgent` - Include user agent (default: true)
|
||||
- `config.includeUrl` - Include current URL (default: true)
|
||||
- `config.filters` - Array of filter functions or regex patterns
|
||||
- `config.beforeSend` - Hook to modify errors before sending
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
const tracker = ErrorTracker.create({
|
||||
endpoint: '/api/errors',
|
||||
enabled: true,
|
||||
sampleRate: 0.5, // Report 50% of errors
|
||||
filters: [
|
||||
// Filter out specific errors
|
||||
/Script error/i,
|
||||
(error, context) => {
|
||||
// Custom filter logic
|
||||
return error.message.includes('ResizeObserver');
|
||||
}
|
||||
],
|
||||
beforeSend: (errorData) => {
|
||||
// Add additional context
|
||||
errorData.userId = getCurrentUserId();
|
||||
errorData.sessionId = getSessionId();
|
||||
return errorData;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### tracker.captureException(error, context)
|
||||
|
||||
Manually capture an exception.
|
||||
|
||||
**Parameters**:
|
||||
- `error` - Error object or any value
|
||||
- `context` - Additional context information
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
try {
|
||||
// Some code
|
||||
} catch (error) {
|
||||
tracker.captureException(error, {
|
||||
type: 'api-call',
|
||||
endpoint: '/api/users',
|
||||
method: 'POST'
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### tracker.report()
|
||||
|
||||
Manually flush error reports to backend.
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
// Report errors immediately
|
||||
await tracker.report();
|
||||
```
|
||||
|
||||
### tracker.getErrorGroups()
|
||||
|
||||
Get grouped errors.
|
||||
|
||||
**Returns**: `Array<ErrorGroup>`
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
const groups = tracker.getErrorGroups();
|
||||
groups.forEach(group => {
|
||||
console.log(`${group.fingerprint}: ${group.count} occurrences`);
|
||||
});
|
||||
```
|
||||
|
||||
### tracker.getErrors()
|
||||
|
||||
Get all captured errors.
|
||||
|
||||
**Returns**: `Array<ErrorData>`
|
||||
|
||||
### tracker.clearErrors()
|
||||
|
||||
Clear all captured errors.
|
||||
|
||||
---
|
||||
|
||||
## Integration with ErrorBoundary
|
||||
|
||||
```javascript
|
||||
import { ErrorTracker } from './modules/error-tracking/index.js';
|
||||
import { ErrorBoundary } from './modules/livecomponent/ErrorBoundary.js';
|
||||
|
||||
const tracker = ErrorTracker.create({
|
||||
endpoint: '/api/errors'
|
||||
});
|
||||
|
||||
// ErrorBoundary automatically captures errors
|
||||
const errorBoundary = new ErrorBoundary(liveComponentManager);
|
||||
|
||||
// Listen for errors
|
||||
window.addEventListener('error-tracker:error', (event) => {
|
||||
const errorData = event.detail;
|
||||
console.error('Error captured:', errorData);
|
||||
|
||||
// Show user-friendly error message
|
||||
showErrorNotification(errorData.message);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Filtering
|
||||
|
||||
### Filter by Regex
|
||||
|
||||
```javascript
|
||||
const tracker = ErrorTracker.create({
|
||||
filters: [
|
||||
/Script error/i, // Filter out script errors
|
||||
/ResizeObserver/i // Filter out ResizeObserver errors
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
### Filter by Function
|
||||
|
||||
```javascript
|
||||
const tracker = ErrorTracker.create({
|
||||
filters: [
|
||||
(error, context) => {
|
||||
// Filter out errors from specific domains
|
||||
if (context.url && context.url.includes('localhost')) {
|
||||
return false; // Don't track localhost errors
|
||||
}
|
||||
return true; // Track other errors
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Sampling
|
||||
|
||||
Control error reporting volume with sampling:
|
||||
|
||||
```javascript
|
||||
const tracker = ErrorTracker.create({
|
||||
sampleRate: 0.1 // Report only 10% of errors
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## beforeSend Hook
|
||||
|
||||
Modify errors before sending to backend:
|
||||
|
||||
```javascript
|
||||
const tracker = ErrorTracker.create({
|
||||
beforeSend: (errorData) => {
|
||||
// Add user information
|
||||
errorData.user = {
|
||||
id: getCurrentUserId(),
|
||||
email: getCurrentUserEmail()
|
||||
};
|
||||
|
||||
// Add session information
|
||||
errorData.session = {
|
||||
id: getSessionId(),
|
||||
startTime: getSessionStartTime()
|
||||
};
|
||||
|
||||
// Remove sensitive data
|
||||
delete errorData.context.password;
|
||||
|
||||
// Return modified error data
|
||||
return errorData;
|
||||
|
||||
// Or return null/false to prevent sending
|
||||
// return null;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Grouping
|
||||
|
||||
Errors are automatically grouped by:
|
||||
- Error name
|
||||
- Error message
|
||||
- Stack trace (first 3 lines)
|
||||
|
||||
Similar errors are grouped together to reduce noise:
|
||||
|
||||
```javascript
|
||||
const groups = tracker.getErrorGroups();
|
||||
groups.forEach(group => {
|
||||
console.log(`Error: ${group.fingerprint}`);
|
||||
console.log(`Count: ${group.count}`);
|
||||
console.log(`First seen: ${new Date(group.firstSeen)}`);
|
||||
console.log(`Last seen: ${new Date(group.lastSeen)}`);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Backend Integration
|
||||
|
||||
The error tracker sends errors to the backend endpoint:
|
||||
|
||||
```javascript
|
||||
POST /api/errors
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"message": "Cannot read property 'x' of undefined",
|
||||
"name": "TypeError",
|
||||
"stack": "...",
|
||||
"timestamp": 1234567890,
|
||||
"type": "unhandled",
|
||||
"context": {
|
||||
"url": "https://example.com/page",
|
||||
"userAgent": "...",
|
||||
"viewport": { "width": 1920, "height": 1080 }
|
||||
}
|
||||
}
|
||||
],
|
||||
"errorGroups": [
|
||||
{
|
||||
"fingerprint": "TypeError:Cannot read property...",
|
||||
"count": 5,
|
||||
"firstSeen": 1234567890,
|
||||
"lastSeen": 1234567900
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Production Error Tracking
|
||||
|
||||
```javascript
|
||||
const tracker = ErrorTracker.create({
|
||||
endpoint: '/api/errors',
|
||||
enabled: true,
|
||||
sampleRate: 0.1, // Sample 10% in production
|
||||
filters: [
|
||||
// Filter out known issues
|
||||
/ResizeObserver/i,
|
||||
/Script error/i
|
||||
],
|
||||
beforeSend: (errorData) => {
|
||||
// Add user context
|
||||
errorData.userId = getCurrentUserId();
|
||||
return errorData;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Development Error Tracking
|
||||
|
||||
```javascript
|
||||
const tracker = ErrorTracker.create({
|
||||
endpoint: '/api/errors',
|
||||
enabled: true,
|
||||
sampleRate: 1.0, // Track all errors in development
|
||||
includeStack: true,
|
||||
includeContext: true
|
||||
});
|
||||
```
|
||||
|
||||
### API Error Tracking
|
||||
|
||||
```javascript
|
||||
async function apiCall(url, options) {
|
||||
try {
|
||||
const response = await fetch(url, options);
|
||||
if (!response.ok) {
|
||||
throw new Error(`API error: ${response.status}`);
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
tracker.captureException(error, {
|
||||
type: 'api-error',
|
||||
url,
|
||||
method: options.method || 'GET',
|
||||
status: error.status
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Sampling in Production** - Set sampleRate to 0.1 or lower to reduce backend load
|
||||
2. **Filter Known Issues** - Filter out errors you can't fix (e.g., browser extensions)
|
||||
3. **Add Context** - Use beforeSend to add user, session, or request context
|
||||
4. **Group Errors** - Let the tracker group similar errors automatically
|
||||
5. **Monitor Error Groups** - Track error frequency and patterns
|
||||
|
||||
---
|
||||
|
||||
## Browser Support
|
||||
|
||||
- **Chrome/Edge**: 90+
|
||||
- **Firefox**: 88+
|
||||
- **Safari**: 14+
|
||||
- **Mobile**: iOS 14+, Android Chrome 90+
|
||||
|
||||
**Required Features**:
|
||||
- ES2020 JavaScript
|
||||
- Fetch API
|
||||
- Promise support
|
||||
|
||||
---
|
||||
|
||||
**Next**: [Event Bus Module](event-bus.md) →
|
||||
|
||||
370
docs/modules/event-bus.md
Normal file
370
docs/modules/event-bus.md
Normal file
@@ -0,0 +1,370 @@
|
||||
# Event Bus Module
|
||||
|
||||
**Centralized Event System for Cross-Module Communication**
|
||||
|
||||
The Event Bus Module provides a pub/sub event system for decoupled communication between modules, components, and services.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **Pub/Sub Pattern** - Publish and subscribe to events
|
||||
- **Namespaced Events** - Organize events with namespaces (e.g., 'user:created')
|
||||
- **Event Filtering** - Filter events by data or conditions
|
||||
- **Event History** - Track event history for debugging
|
||||
- **Integration with LiveComponents** - Works seamlessly with LiveComponents
|
||||
- **Integration with SSE** - Integrate with Server-Sent Events
|
||||
- **Wildcard Support** - Subscribe to event patterns
|
||||
- **Middleware Support** - Transform or filter events with middleware
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```javascript
|
||||
import { EventBus } from './modules/event-bus/index.js';
|
||||
|
||||
// Create event bus
|
||||
const bus = EventBus.create();
|
||||
|
||||
// Subscribe to an event
|
||||
const unsubscribe = bus.on('user:created', (user) => {
|
||||
console.log('User created:', user);
|
||||
});
|
||||
|
||||
// Emit an event
|
||||
bus.emit('user:created', { id: 1, name: 'John' });
|
||||
|
||||
// Unsubscribe
|
||||
unsubscribe();
|
||||
```
|
||||
|
||||
### Module System Integration
|
||||
|
||||
```html
|
||||
<!-- Enable global event bus -->
|
||||
<script type="module">
|
||||
import { init } from './modules/event-bus/index.js';
|
||||
|
||||
init({
|
||||
enableHistory: true,
|
||||
maxHistorySize: 100
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Access globally -->
|
||||
<script>
|
||||
// Subscribe
|
||||
window.EventBus.on('user:created', (user) => {
|
||||
console.log('User created:', user);
|
||||
});
|
||||
|
||||
// Emit
|
||||
window.EventBus.emit('user:created', { id: 1, name: 'John' });
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### EventBus.create(config)
|
||||
|
||||
Create a new EventBus instance.
|
||||
|
||||
**Parameters**:
|
||||
- `config.enableHistory` - Enable event history (default: false)
|
||||
- `config.maxHistorySize` - Maximum history size (default: 100)
|
||||
- `config.enableWildcards` - Enable wildcard patterns (default: true)
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
const bus = EventBus.create({
|
||||
enableHistory: true,
|
||||
maxHistorySize: 50
|
||||
});
|
||||
```
|
||||
|
||||
### bus.on(eventName, callback, options)
|
||||
|
||||
Subscribe to an event.
|
||||
|
||||
**Parameters**:
|
||||
- `eventName` - Event name (supports wildcards: 'user:*', '*')
|
||||
- `callback` - Callback function: `(data, eventName, options) => void`
|
||||
- `options.once` - Subscribe only once (default: false)
|
||||
- `options.priority` - Subscription priority (default: 0)
|
||||
- `options.filter` - Filter function: `(data, options) => boolean`
|
||||
|
||||
**Returns**: Unsubscribe function
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
// Basic subscription
|
||||
const unsubscribe = bus.on('user:created', (user) => {
|
||||
console.log('User created:', user);
|
||||
});
|
||||
|
||||
// Once subscription
|
||||
bus.once('user:created', (user) => {
|
||||
console.log('User created (once):', user);
|
||||
});
|
||||
|
||||
// Priority subscription
|
||||
bus.on('user:created', (user) => {
|
||||
console.log('High priority handler');
|
||||
}, { priority: 10 });
|
||||
|
||||
// Filtered subscription
|
||||
bus.on('user:created', (user) => {
|
||||
console.log('Admin user created');
|
||||
}, {
|
||||
filter: (user) => user.role === 'admin'
|
||||
});
|
||||
```
|
||||
|
||||
### bus.once(eventName, callback, options)
|
||||
|
||||
Subscribe to an event once (auto-unsubscribe after first emission).
|
||||
|
||||
**Parameters**: Same as `on()`
|
||||
|
||||
**Returns**: Unsubscribe function
|
||||
|
||||
### bus.off(eventName, callback)
|
||||
|
||||
Unsubscribe from an event.
|
||||
|
||||
**Parameters**:
|
||||
- `eventName` - Event name
|
||||
- `callback` - Callback function to remove
|
||||
|
||||
### bus.emit(eventName, data, options)
|
||||
|
||||
Emit an event.
|
||||
|
||||
**Parameters**:
|
||||
- `eventName` - Event name
|
||||
- `data` - Event data
|
||||
- `options` - Event options
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
bus.emit('user:created', { id: 1, name: 'John' });
|
||||
bus.emit('user:updated', { id: 1, name: 'Jane' }, { source: 'api' });
|
||||
```
|
||||
|
||||
### bus.use(middleware)
|
||||
|
||||
Add middleware to transform or filter events.
|
||||
|
||||
**Parameters**:
|
||||
- `middleware` - Middleware function: `(eventName, data, options) => data | null | false`
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
// Logging middleware
|
||||
bus.use((eventName, data, options) => {
|
||||
console.log(`Event: ${eventName}`, data);
|
||||
return data; // Continue
|
||||
});
|
||||
|
||||
// Filtering middleware
|
||||
bus.use((eventName, data, options) => {
|
||||
if (eventName.startsWith('debug:')) {
|
||||
return null; // Block debug events in production
|
||||
}
|
||||
return data;
|
||||
});
|
||||
```
|
||||
|
||||
### bus.getHistory(filter)
|
||||
|
||||
Get event history.
|
||||
|
||||
**Parameters**:
|
||||
- `filter` - Optional filter (string for event name, or function)
|
||||
|
||||
**Returns**: `Array<EventHistoryItem>`
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
// Get all history
|
||||
const history = bus.getHistory();
|
||||
|
||||
// Get history for specific event
|
||||
const userHistory = bus.getHistory('user:created');
|
||||
|
||||
// Get history with custom filter
|
||||
const recentHistory = bus.getHistory((item) => {
|
||||
return Date.now() - item.timestamp < 60000; // Last minute
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Namespaced Events
|
||||
|
||||
Organize events with namespaces:
|
||||
|
||||
```javascript
|
||||
// Subscribe to namespace
|
||||
bus.on('user:*', (data, eventName) => {
|
||||
console.log(`User event: ${eventName}`, data);
|
||||
});
|
||||
|
||||
// Emit namespaced events
|
||||
bus.emit('user:created', { id: 1 });
|
||||
bus.emit('user:updated', { id: 1 });
|
||||
bus.emit('user:deleted', { id: 1 });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Wildcard Support
|
||||
|
||||
Subscribe to event patterns:
|
||||
|
||||
```javascript
|
||||
// Subscribe to all events
|
||||
bus.on('*', (data, eventName) => {
|
||||
console.log(`Event: ${eventName}`, data);
|
||||
});
|
||||
|
||||
// Subscribe to pattern
|
||||
bus.on('user:*', (data, eventName) => {
|
||||
console.log(`User event: ${eventName}`, data);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration with LiveComponents
|
||||
|
||||
```javascript
|
||||
import { EventBus } from './modules/event-bus/index.js';
|
||||
import { LiveComponentManager } from './modules/livecomponent/index.js';
|
||||
|
||||
const bus = EventBus.create();
|
||||
const lcManager = LiveComponentManager.getInstance();
|
||||
|
||||
// Listen for LiveComponent events
|
||||
bus.on('livecomponent:action-executed', (data) => {
|
||||
console.log('Action executed:', data);
|
||||
});
|
||||
|
||||
// Emit LiveComponent events
|
||||
lcManager.on('action-executed', (componentId, actionName, params) => {
|
||||
bus.emit('livecomponent:action-executed', {
|
||||
componentId,
|
||||
actionName,
|
||||
params
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration with SSE
|
||||
|
||||
```javascript
|
||||
import { EventBus } from './modules/event-bus/index.js';
|
||||
import { SseClient } from './modules/sse/index.js';
|
||||
|
||||
const bus = EventBus.create();
|
||||
const sse = new SseClient(['updates']);
|
||||
|
||||
// Forward SSE messages to event bus
|
||||
sse.on('message', (data) => {
|
||||
bus.emit('sse:message', data);
|
||||
});
|
||||
|
||||
// Listen for SSE events
|
||||
bus.on('sse:message', (data) => {
|
||||
console.log('SSE message:', data);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Module Communication
|
||||
|
||||
```javascript
|
||||
// Module A
|
||||
const bus = EventBus.create();
|
||||
bus.emit('module-a:data-ready', { data: [...] });
|
||||
|
||||
// Module B
|
||||
bus.on('module-a:data-ready', (data) => {
|
||||
processData(data);
|
||||
});
|
||||
```
|
||||
|
||||
### Component Communication
|
||||
|
||||
```javascript
|
||||
// Component A
|
||||
bus.emit('component:user-selected', { userId: 123 });
|
||||
|
||||
// Component B
|
||||
bus.on('component:user-selected', (data) => {
|
||||
loadUserDetails(data.userId);
|
||||
});
|
||||
```
|
||||
|
||||
### Global Notifications
|
||||
|
||||
```javascript
|
||||
// Emit notification
|
||||
bus.emit('notification:show', {
|
||||
type: 'success',
|
||||
message: 'Operation completed'
|
||||
});
|
||||
|
||||
// Listen for notifications
|
||||
bus.on('notification:show', (data) => {
|
||||
showNotification(data.type, data.message);
|
||||
});
|
||||
```
|
||||
|
||||
### Analytics Events
|
||||
|
||||
```javascript
|
||||
// Track events
|
||||
bus.on('*', (data, eventName) => {
|
||||
// Send to analytics
|
||||
analytics.track(eventName, data);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Namespaces** - Organize events with namespaces (e.g., 'user:created')
|
||||
2. **Document Events** - Document all events and their data structures
|
||||
3. **Use TypeScript** - Use TypeScript definitions for type safety
|
||||
4. **Clean Up Subscriptions** - Always unsubscribe when done
|
||||
5. **Use Middleware** - Use middleware for cross-cutting concerns (logging, analytics)
|
||||
|
||||
---
|
||||
|
||||
## Browser Support
|
||||
|
||||
- **Chrome/Edge**: 90+
|
||||
- **Firefox**: 88+
|
||||
- **Safari**: 14+
|
||||
- **Mobile**: iOS 14+, Android Chrome 90+
|
||||
|
||||
**Required Features**:
|
||||
- ES2020 JavaScript
|
||||
- Map and Set support
|
||||
|
||||
---
|
||||
|
||||
**Next**: Continue with Phase 2 modules →
|
||||
|
||||
364
docs/modules/router.md
Normal file
364
docs/modules/router.md
Normal file
@@ -0,0 +1,364 @@
|
||||
# Router Enhancement Module
|
||||
|
||||
**Enhanced Routing with Guards, Middleware, and Analytics**
|
||||
|
||||
The Router Enhancement Module provides advanced routing capabilities with access control, middleware, lazy loading, and analytics.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **Route Guards** - Access control for routes (auth, permissions, roles)
|
||||
- **Route Middleware** - Cross-cutting concerns (analytics, loading, etc.)
|
||||
- **Lazy Route Loading** - Load routes on demand
|
||||
- **Route Analytics** - Track navigation patterns
|
||||
- **Integration with LiveComponents** - Seamless integration with LiveComponent system
|
||||
- **History & Hash Modes** - Support for both history and hash routing
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```javascript
|
||||
import { Router, BuiltInGuards, BuiltInMiddleware } from './modules/router/index.js';
|
||||
|
||||
// Create router
|
||||
const router = Router.create({
|
||||
mode: 'history',
|
||||
base: '/',
|
||||
enableAnalytics: true
|
||||
});
|
||||
|
||||
// Register routes
|
||||
router.routes([
|
||||
{
|
||||
path: '/',
|
||||
component: () => '<div>Home</div>',
|
||||
name: 'home',
|
||||
title: 'Home'
|
||||
},
|
||||
{
|
||||
path: '/dashboard',
|
||||
component: () => '<div>Dashboard</div>',
|
||||
name: 'dashboard',
|
||||
title: 'Dashboard',
|
||||
guards: ['auth'],
|
||||
middleware: ['analytics', 'loading']
|
||||
},
|
||||
{
|
||||
path: '/admin',
|
||||
component: () => import('./components/Admin.js'),
|
||||
name: 'admin',
|
||||
title: 'Admin',
|
||||
guards: ['auth', 'role:admin'],
|
||||
lazy: true
|
||||
}
|
||||
]);
|
||||
|
||||
// Register guards
|
||||
router.guard('auth', async (to, from) => {
|
||||
const isAuthenticated = await checkAuth();
|
||||
if (!isAuthenticated) {
|
||||
return '/login'; // Redirect to login
|
||||
}
|
||||
return true; // Allow navigation
|
||||
});
|
||||
|
||||
// Add global middleware
|
||||
router.use(BuiltInMiddleware.analytics);
|
||||
router.use(BuiltInMiddleware.scrollToTop);
|
||||
|
||||
// Navigate
|
||||
await router.navigate('/dashboard');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### Router.create(config)
|
||||
|
||||
Create a new Router instance.
|
||||
|
||||
**Parameters**:
|
||||
- `config.mode` - Routing mode: 'history' or 'hash'
|
||||
- `config.base` - Base path
|
||||
- `config.enableAnalytics` - Enable route analytics
|
||||
|
||||
### router.route(path, config)
|
||||
|
||||
Register a single route.
|
||||
|
||||
**Parameters**:
|
||||
- `path` - Route path
|
||||
- `config.component` - Component (string, function, or HTMLElement)
|
||||
- `config.name` - Route name
|
||||
- `config.title` - Page title
|
||||
- `config.guards` - Array of guard names
|
||||
- `config.middleware` - Array of middleware
|
||||
- `config.lazy` - Lazy load component
|
||||
|
||||
### router.guard(name, guardFn)
|
||||
|
||||
Register a route guard.
|
||||
|
||||
**Parameters**:
|
||||
- `name` - Guard name
|
||||
- `guardFn` - Guard function: `(to, from, context) => boolean | string`
|
||||
|
||||
### router.use(middleware)
|
||||
|
||||
Add global middleware.
|
||||
|
||||
**Parameters**:
|
||||
- `middleware` - Middleware instance, function, or built-in name
|
||||
|
||||
### router.beforeEach(hook)
|
||||
|
||||
Add before navigation hook.
|
||||
|
||||
**Parameters**:
|
||||
- `hook` - Hook function: `(to, from) => boolean | string | void`
|
||||
|
||||
### router.afterEach(hook)
|
||||
|
||||
Add after navigation hook.
|
||||
|
||||
**Parameters**:
|
||||
- `hook` - Hook function: `(to, from) => void`
|
||||
|
||||
### router.navigate(path, options)
|
||||
|
||||
Navigate to a route.
|
||||
|
||||
**Parameters**:
|
||||
- `path` - Route path
|
||||
- `options.container` - Container element
|
||||
- `options.updateHistory` - Update browser history
|
||||
|
||||
**Returns**: `Promise<boolean>`
|
||||
|
||||
---
|
||||
|
||||
## Route Guards
|
||||
|
||||
### Authentication Guard
|
||||
|
||||
```javascript
|
||||
router.guard('auth', async (to, from) => {
|
||||
if (!isAuthenticated()) {
|
||||
return '/login';
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// Use in route
|
||||
router.route('/dashboard', {
|
||||
component: DashboardComponent,
|
||||
guards: ['auth']
|
||||
});
|
||||
```
|
||||
|
||||
### Role Guard
|
||||
|
||||
```javascript
|
||||
router.guard('admin', async (to, from) => {
|
||||
if (getUserRole() !== 'admin') {
|
||||
return '/unauthorized';
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// Use in route
|
||||
router.route('/admin', {
|
||||
component: AdminComponent,
|
||||
guards: ['auth', 'admin']
|
||||
});
|
||||
```
|
||||
|
||||
### Built-in Guards
|
||||
|
||||
```javascript
|
||||
// Use built-in guards
|
||||
router.route('/dashboard', {
|
||||
component: DashboardComponent,
|
||||
guards: ['auth'] // Uses BuiltInGuards.auth
|
||||
});
|
||||
|
||||
router.route('/login', {
|
||||
component: LoginComponent,
|
||||
guards: ['guest'] // Uses BuiltInGuards.guest
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Route Middleware
|
||||
|
||||
### Analytics Middleware
|
||||
|
||||
```javascript
|
||||
// Use built-in analytics middleware
|
||||
router.use('analytics');
|
||||
|
||||
// Or custom middleware
|
||||
router.use(RouteMiddleware.create('custom', async (to, from, next) => {
|
||||
// Track navigation
|
||||
analytics.track('page_view', { path: to.path });
|
||||
next();
|
||||
}));
|
||||
```
|
||||
|
||||
### Loading Middleware
|
||||
|
||||
```javascript
|
||||
router.use('loading'); // Shows loading indicator during navigation
|
||||
```
|
||||
|
||||
### Scroll to Top Middleware
|
||||
|
||||
```javascript
|
||||
router.use('scroll-to-top'); // Scrolls to top after navigation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Lazy Route Loading
|
||||
|
||||
```javascript
|
||||
router.route('/admin', {
|
||||
component: () => import('./components/Admin.js'),
|
||||
lazy: true
|
||||
});
|
||||
|
||||
// Or with dynamic import
|
||||
router.route('/user/:id', {
|
||||
component: async (route) => {
|
||||
const UserComponent = await import('./components/User.js');
|
||||
return UserComponent.default(route.params.id);
|
||||
},
|
||||
lazy: true
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration with LiveComponents
|
||||
|
||||
```javascript
|
||||
import { Router } from './modules/router/index.js';
|
||||
import { LiveComponent } from './modules/livecomponent/index.js';
|
||||
|
||||
const router = Router.create();
|
||||
|
||||
router.route('/dashboard', {
|
||||
component: async (route) => {
|
||||
// Initialize LiveComponents after navigation
|
||||
const container = document.querySelector('main');
|
||||
container.innerHTML = '<div data-live-component="dashboard"></div>';
|
||||
|
||||
// Initialize LiveComponent
|
||||
const component = container.querySelector('[data-live-component]');
|
||||
LiveComponent.init(component);
|
||||
|
||||
return container;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Route Analytics
|
||||
|
||||
```javascript
|
||||
const router = Router.create({ enableAnalytics: true });
|
||||
|
||||
// Get analytics
|
||||
const analytics = router.getAnalytics();
|
||||
console.log('Total navigations:', analytics.totalNavigations);
|
||||
console.log('Navigation history:', analytics.navigations);
|
||||
|
||||
// Listen for navigation events
|
||||
window.addEventListener('router:navigation', (event) => {
|
||||
const { to, from, timestamp } = event.detail;
|
||||
console.log(`Navigated from ${from} to ${to}`);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Protected Routes
|
||||
|
||||
```javascript
|
||||
router.routes([
|
||||
{
|
||||
path: '/',
|
||||
component: HomeComponent,
|
||||
name: 'home'
|
||||
},
|
||||
{
|
||||
path: '/dashboard',
|
||||
component: DashboardComponent,
|
||||
name: 'dashboard',
|
||||
guards: ['auth']
|
||||
},
|
||||
{
|
||||
path: '/admin',
|
||||
component: AdminComponent,
|
||||
name: 'admin',
|
||||
guards: ['auth', 'admin']
|
||||
}
|
||||
]);
|
||||
```
|
||||
|
||||
### Route with Middleware
|
||||
|
||||
```javascript
|
||||
router.route('/api-data', {
|
||||
component: ApiDataComponent,
|
||||
middleware: ['analytics', 'loading']
|
||||
});
|
||||
```
|
||||
|
||||
### Lazy Loading
|
||||
|
||||
```javascript
|
||||
router.route('/heavy-page', {
|
||||
component: () => import('./pages/HeavyPage.js'),
|
||||
lazy: true,
|
||||
guards: ['auth']
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Guards for Access Control** - Protect routes with guards
|
||||
2. **Use Middleware for Cross-Cutting Concerns** - Analytics, loading, etc.
|
||||
3. **Lazy Load Heavy Routes** - Improve initial load time
|
||||
4. **Track Navigation** - Use analytics to understand user behavior
|
||||
5. **Handle Errors** - Provide fallback routes for errors
|
||||
|
||||
---
|
||||
|
||||
## Browser Support
|
||||
|
||||
- **Chrome/Edge**: 90+
|
||||
- **Firefox**: 88+
|
||||
- **Safari**: 14+
|
||||
- **Mobile**: iOS 14+, Android Chrome 90+
|
||||
|
||||
**Required Features**:
|
||||
- ES2020 JavaScript
|
||||
- History API (for history mode)
|
||||
- Promise support
|
||||
|
||||
---
|
||||
|
||||
**Next**: [Analytics Module](analytics.md) →
|
||||
|
||||
382
docs/modules/security.md
Normal file
382
docs/modules/security.md
Normal file
@@ -0,0 +1,382 @@
|
||||
# Security Module
|
||||
|
||||
**Security Utilities: CSRF, XSS Protection, and CSP Helpers**
|
||||
|
||||
The Security Module provides comprehensive security utilities for client-side security management.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **CSRF Token Management** - Automatic token refresh and management
|
||||
- **XSS Protection** - HTML sanitization and input validation
|
||||
- **Content Security Policy** - CSP validation and helpers
|
||||
- **Security Headers Validation** - Client-side security header checks
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```javascript
|
||||
import { SecurityManager } from './modules/security/index.js';
|
||||
|
||||
// Create security manager
|
||||
const security = SecurityManager.create({
|
||||
csrf: {
|
||||
autoRefresh: true,
|
||||
refreshInterval: 30 * 60 * 1000 // 30 minutes
|
||||
},
|
||||
xss: {
|
||||
enabled: true,
|
||||
sanitizeOnInput: false
|
||||
}
|
||||
});
|
||||
|
||||
// Get CSRF token
|
||||
const token = security.getCsrfToken();
|
||||
|
||||
// Use in fetch request
|
||||
const response = await fetch('/api/endpoint', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...security.getCsrfTokenHeader(),
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
```
|
||||
|
||||
### Module System Integration
|
||||
|
||||
```html
|
||||
<!-- Enable global security manager -->
|
||||
<script type="module">
|
||||
import { init } from './modules/security/index.js';
|
||||
|
||||
init({
|
||||
csrf: {
|
||||
autoRefresh: true
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Access globally -->
|
||||
<script>
|
||||
// Get CSRF token
|
||||
const token = window.SecurityManager.getCsrfToken();
|
||||
|
||||
// Refresh token
|
||||
await window.SecurityManager.refreshCsrfToken();
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### SecurityManager.create(config)
|
||||
|
||||
Create a new SecurityManager instance.
|
||||
|
||||
**Parameters**:
|
||||
- `config.csrf` - CSRF manager configuration
|
||||
- `config.xss` - XSS protection configuration
|
||||
- `config.csp` - CSP configuration
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
const security = SecurityManager.create({
|
||||
csrf: {
|
||||
tokenName: '_token',
|
||||
headerName: 'X-CSRF-TOKEN',
|
||||
autoRefresh: true,
|
||||
refreshInterval: 30 * 60 * 1000
|
||||
},
|
||||
xss: {
|
||||
enabled: true,
|
||||
sanitizeOnInput: false
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### security.getCsrfToken()
|
||||
|
||||
Get current CSRF token.
|
||||
|
||||
**Returns**: `string | null`
|
||||
|
||||
### security.getCsrfTokenHeader()
|
||||
|
||||
Get CSRF token header object for use in fetch requests.
|
||||
|
||||
**Returns**: `Record<string, string>`
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
const headers = {
|
||||
...security.getCsrfTokenHeader(),
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
```
|
||||
|
||||
### security.refreshCsrfToken()
|
||||
|
||||
Manually refresh CSRF token.
|
||||
|
||||
**Returns**: `Promise<void>`
|
||||
|
||||
### security.escapeHtml(text)
|
||||
|
||||
Escape HTML to prevent XSS.
|
||||
|
||||
**Parameters**:
|
||||
- `text` - Text to escape
|
||||
|
||||
**Returns**: `string`
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
const safe = security.escapeHtml('<script>alert("xss")</script>');
|
||||
// Returns: <script>alert("xss")</script>
|
||||
```
|
||||
|
||||
### security.validateUrl(url)
|
||||
|
||||
Validate URL to prevent XSS.
|
||||
|
||||
**Parameters**:
|
||||
- `url` - URL to validate
|
||||
|
||||
**Returns**: `boolean`
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
if (security.validateUrl(userInput)) {
|
||||
// Safe to use
|
||||
} else {
|
||||
// Potentially dangerous
|
||||
}
|
||||
```
|
||||
|
||||
### security.validateSecurityHeaders()
|
||||
|
||||
Validate security headers.
|
||||
|
||||
**Returns**: `SecurityHeadersValidation`
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
const validation = security.validateSecurityHeaders();
|
||||
if (!validation.valid) {
|
||||
console.warn('Security issues:', validation.issues);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CSRF Token Management
|
||||
|
||||
### Automatic Token Refresh
|
||||
|
||||
```javascript
|
||||
const security = SecurityManager.create({
|
||||
csrf: {
|
||||
autoRefresh: true,
|
||||
refreshInterval: 30 * 60 * 1000 // Refresh every 30 minutes
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Manual Token Refresh
|
||||
|
||||
```javascript
|
||||
await security.refreshCsrfToken();
|
||||
```
|
||||
|
||||
### Using Token in Requests
|
||||
|
||||
```javascript
|
||||
// Get token header
|
||||
const headers = security.getCsrfTokenHeader();
|
||||
|
||||
// Use in fetch
|
||||
const response = await fetch('/api/endpoint', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...headers,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## XSS Protection
|
||||
|
||||
### HTML Sanitization
|
||||
|
||||
```javascript
|
||||
const security = SecurityManager.create({
|
||||
xss: {
|
||||
enabled: true,
|
||||
sanitizeOnInput: true // Auto-sanitize on input
|
||||
}
|
||||
});
|
||||
|
||||
// Manual sanitization
|
||||
const safe = security.sanitizeHtml(userInput);
|
||||
```
|
||||
|
||||
### HTML Escaping
|
||||
|
||||
```javascript
|
||||
const escaped = security.escapeHtml('<script>alert("xss")</script>');
|
||||
```
|
||||
|
||||
### URL Validation
|
||||
|
||||
```javascript
|
||||
if (security.validateUrl(userInput)) {
|
||||
// Safe URL
|
||||
window.location.href = userInput;
|
||||
} else {
|
||||
// Potentially dangerous
|
||||
console.error('Invalid URL');
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Content Security Policy
|
||||
|
||||
### CSP Validation
|
||||
|
||||
```javascript
|
||||
const security = SecurityManager.create({
|
||||
csp: {
|
||||
enabled: true
|
||||
}
|
||||
});
|
||||
|
||||
// CSP is automatically validated on init
|
||||
// Check validation results
|
||||
const validation = security.validateSecurityHeaders();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration with LiveComponents
|
||||
|
||||
```javascript
|
||||
import { SecurityManager } from './modules/security/index.js';
|
||||
import { LiveComponentManager } from './modules/livecomponent/index.js';
|
||||
|
||||
const security = SecurityManager.create();
|
||||
|
||||
// LiveComponents automatically use CSRF tokens
|
||||
// But you can also manually update tokens
|
||||
security.csrfManager.updateAllTokens();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration with Forms
|
||||
|
||||
```javascript
|
||||
import { SecurityManager } from './modules/security/index.js';
|
||||
|
||||
const security = SecurityManager.create();
|
||||
|
||||
// Forms automatically get updated CSRF tokens
|
||||
// Listen for token refresh events
|
||||
window.addEventListener('csrf:token-refreshed', (event) => {
|
||||
console.log('CSRF token refreshed:', event.detail.token);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use Cases
|
||||
|
||||
### API Requests with CSRF
|
||||
|
||||
```javascript
|
||||
const security = SecurityManager.create();
|
||||
|
||||
async function apiCall(url, data) {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...security.getCsrfTokenHeader(),
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
return response.json();
|
||||
}
|
||||
```
|
||||
|
||||
### User Input Sanitization
|
||||
|
||||
```javascript
|
||||
const security = SecurityManager.create();
|
||||
|
||||
function handleUserInput(input) {
|
||||
// Sanitize HTML
|
||||
const sanitized = security.sanitizeHtml(input);
|
||||
|
||||
// Escape for display
|
||||
const escaped = security.escapeHtml(input);
|
||||
|
||||
// Validate URL
|
||||
if (security.validateUrl(input)) {
|
||||
// Safe to use as URL
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Security Headers Check
|
||||
|
||||
```javascript
|
||||
const security = SecurityManager.create();
|
||||
|
||||
// Validate security headers
|
||||
const validation = security.validateSecurityHeaders();
|
||||
if (!validation.valid) {
|
||||
console.warn('Security issues detected:', validation.issues);
|
||||
// Report to backend or show warning
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always Use CSRF Tokens** - Include CSRF tokens in all state-changing requests
|
||||
2. **Sanitize User Input** - Sanitize HTML before displaying user content
|
||||
3. **Validate URLs** - Always validate URLs before using them
|
||||
4. **Enable Auto-Refresh** - Keep CSRF tokens fresh with auto-refresh
|
||||
5. **Check Security Headers** - Validate security headers in development
|
||||
|
||||
---
|
||||
|
||||
## Browser Support
|
||||
|
||||
- **Chrome/Edge**: 90+
|
||||
- **Firefox**: 88+
|
||||
- **Safari**: 14+
|
||||
- **Mobile**: iOS 14+, Android Chrome 90+
|
||||
|
||||
**Required Features**:
|
||||
- ES2020 JavaScript
|
||||
- Fetch API
|
||||
- CustomEvent support
|
||||
|
||||
---
|
||||
|
||||
**Next**: [Cache Manager Module](cache-manager.md) →
|
||||
|
||||
397
docs/modules/state-manager.md
Normal file
397
docs/modules/state-manager.md
Normal file
@@ -0,0 +1,397 @@
|
||||
# State Manager Module
|
||||
|
||||
**Centralized, Reactive State Management for Client-Side State**
|
||||
|
||||
The State Manager Module provides a centralized state management system similar to Redux or Vuex, with support for state persistence, cross-tab synchronization, and integration with LiveComponents.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **Reactive State Store** - Subscribe to state changes and react automatically
|
||||
- **State Persistence** - Automatically save and restore state from localStorage or sessionStorage
|
||||
- **Cross-Tab Synchronization** - Keep state synchronized across browser tabs
|
||||
- **Integration with LiveComponents** - Seamless integration with LiveComponent state
|
||||
- **Time-Travel Debugging** - Debug state changes with history and time-travel
|
||||
- **Middleware Support** - Extend functionality with custom middleware
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```javascript
|
||||
import { StateManager } from './modules/state-manager/index.js';
|
||||
|
||||
// Create a state manager
|
||||
const store = StateManager.create({
|
||||
initialState: {
|
||||
user: { name: '', email: '' },
|
||||
cart: { items: [], total: 0 },
|
||||
ui: { sidebarOpen: false }
|
||||
}
|
||||
});
|
||||
|
||||
// Set state
|
||||
store.set('user.name', 'John Doe');
|
||||
store.set('cart.items', [{ id: 1, name: 'Product' }]);
|
||||
|
||||
// Get state
|
||||
const userName = store.get('user.name');
|
||||
const cartItems = store.get('cart.items', []);
|
||||
|
||||
// Subscribe to changes
|
||||
const unsubscribe = store.subscribe('cart.items', (items) => {
|
||||
console.log('Cart items changed:', items);
|
||||
updateCartUI(items);
|
||||
});
|
||||
|
||||
// Unsubscribe
|
||||
unsubscribe();
|
||||
```
|
||||
|
||||
### Module System Integration
|
||||
|
||||
```html
|
||||
<!-- Enable global state manager -->
|
||||
<script type="module">
|
||||
import { init } from './modules/state-manager/index.js';
|
||||
|
||||
init({
|
||||
initialState: {
|
||||
user: {},
|
||||
cart: {}
|
||||
},
|
||||
persistence: {
|
||||
enabled: true,
|
||||
storage: 'localStorage',
|
||||
key: 'app-state'
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Access globally -->
|
||||
<script>
|
||||
window.StateManager.set('user.name', 'John');
|
||||
const name = window.StateManager.get('user.name');
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### StateManager.create(config)
|
||||
|
||||
Create a new StateManager instance.
|
||||
|
||||
**Parameters**:
|
||||
- `config.initialState` - Initial state object
|
||||
- `config.maxHistorySize` - Maximum history size (default: 50)
|
||||
- `config.enableHistory` - Enable history for time-travel (default: false)
|
||||
- `config.persistence.enabled` - Enable state persistence (default: false)
|
||||
- `config.persistence.storage` - Storage type: 'localStorage' or 'sessionStorage' (default: 'localStorage')
|
||||
- `config.persistence.key` - Storage key (default: 'app-state')
|
||||
- `config.persistence.paths` - Array of paths to persist (empty = all)
|
||||
- `config.sync.enabled` - Enable cross-tab synchronization (default: false)
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
const store = StateManager.create({
|
||||
initialState: { user: {}, cart: {} },
|
||||
persistence: {
|
||||
enabled: true,
|
||||
storage: 'localStorage',
|
||||
paths: ['user', 'cart'] // Only persist these paths
|
||||
},
|
||||
sync: {
|
||||
enabled: true // Sync across tabs
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### store.getState()
|
||||
|
||||
Get the entire state object.
|
||||
|
||||
**Returns**: `Record<string, any>`
|
||||
|
||||
### store.get(path, defaultValue)
|
||||
|
||||
Get state at a specific path.
|
||||
|
||||
**Parameters**:
|
||||
- `path` - Dot-separated path (e.g., 'user.name')
|
||||
- `defaultValue` - Default value if path doesn't exist
|
||||
|
||||
**Returns**: `any`
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
const userName = store.get('user.name', 'Guest');
|
||||
const cartTotal = store.get('cart.total', 0);
|
||||
```
|
||||
|
||||
### store.set(path, value)
|
||||
|
||||
Set state at a specific path.
|
||||
|
||||
**Parameters**:
|
||||
- `path` - Dot-separated path
|
||||
- `value` - Value to set
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
store.set('user.name', 'John Doe');
|
||||
store.set('cart.items', [{ id: 1, name: 'Product' }]);
|
||||
```
|
||||
|
||||
### store.dispatch(action)
|
||||
|
||||
Dispatch an action (Redux-style).
|
||||
|
||||
**Parameters**:
|
||||
- `action` - Action object with `type` property, or a thunk function
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
// Simple action
|
||||
store.dispatch({
|
||||
type: 'ADD_TO_CART',
|
||||
productId: 123,
|
||||
quantity: 1
|
||||
});
|
||||
|
||||
// Thunk (async action)
|
||||
store.dispatch(async (dispatch, getState) => {
|
||||
const response = await fetch('/api/products');
|
||||
const products = await response.json();
|
||||
dispatch({ type: 'SET_PRODUCTS', products });
|
||||
});
|
||||
```
|
||||
|
||||
### store.subscribe(path, callback)
|
||||
|
||||
Subscribe to state changes at a specific path.
|
||||
|
||||
**Parameters**:
|
||||
- `path` - Dot-separated path, or '*' for all changes
|
||||
- `callback` - Callback function: `(newValue, oldValue, path) => void`
|
||||
|
||||
**Returns**: Unsubscribe function
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
const unsubscribe = store.subscribe('cart.items', (items, oldItems, path) => {
|
||||
console.log(`Cart items changed at ${path}:`, items);
|
||||
updateCartUI(items);
|
||||
});
|
||||
|
||||
// Later...
|
||||
unsubscribe();
|
||||
```
|
||||
|
||||
### store.subscribeAll(callback)
|
||||
|
||||
Subscribe to all state changes.
|
||||
|
||||
**Parameters**:
|
||||
- `callback` - Callback function: `(state) => void`
|
||||
|
||||
**Returns**: Unsubscribe function
|
||||
|
||||
### store.use(middleware)
|
||||
|
||||
Add middleware to the state manager.
|
||||
|
||||
**Parameters**:
|
||||
- `middleware` - Middleware function: `(action, getState) => action | null`
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
// Logging middleware
|
||||
store.use((action, getState) => {
|
||||
console.log('Action:', action);
|
||||
console.log('Current state:', getState());
|
||||
return action; // Return action to continue, or null to block
|
||||
});
|
||||
|
||||
// Validation middleware
|
||||
store.use((action, getState) => {
|
||||
if (action.type === 'SET' && action.path === 'user.email') {
|
||||
if (!isValidEmail(action.value)) {
|
||||
console.error('Invalid email');
|
||||
return null; // Block the action
|
||||
}
|
||||
}
|
||||
return action;
|
||||
});
|
||||
```
|
||||
|
||||
### store.getHistory()
|
||||
|
||||
Get action history for time-travel debugging.
|
||||
|
||||
**Returns**: `Array<HistoryPoint>`
|
||||
|
||||
### store.timeTravel(index)
|
||||
|
||||
Time-travel to a specific history point.
|
||||
|
||||
**Parameters**:
|
||||
- `index` - History index
|
||||
|
||||
### store.reset()
|
||||
|
||||
Reset state to initial state.
|
||||
|
||||
### store.destroy()
|
||||
|
||||
Destroy the state manager and clean up resources.
|
||||
|
||||
---
|
||||
|
||||
## Integration with LiveComponents
|
||||
|
||||
```javascript
|
||||
import { StateManager } from './modules/state-manager/index.js';
|
||||
import { LiveComponentManager } from './modules/livecomponent/index.js';
|
||||
|
||||
const store = StateManager.create();
|
||||
const lcManager = LiveComponentManager.getInstance();
|
||||
|
||||
// Sync LiveComponent state with StateManager
|
||||
lcManager.on('component:state-updated', (componentId, state) => {
|
||||
store.set(`livecomponents.${componentId}`, state);
|
||||
});
|
||||
|
||||
// Update LiveComponent from StateManager
|
||||
store.subscribe('livecomponents', (state) => {
|
||||
Object.entries(state).forEach(([componentId, componentState]) => {
|
||||
lcManager.updateComponentState(componentId, componentState);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use Cases
|
||||
|
||||
### User Preferences
|
||||
|
||||
```javascript
|
||||
const store = StateManager.create({
|
||||
initialState: {
|
||||
preferences: {
|
||||
theme: 'light',
|
||||
language: 'en',
|
||||
notifications: true
|
||||
}
|
||||
},
|
||||
persistence: {
|
||||
enabled: true,
|
||||
storage: 'localStorage',
|
||||
paths: ['preferences']
|
||||
}
|
||||
});
|
||||
|
||||
// Save preference
|
||||
store.set('preferences.theme', 'dark');
|
||||
|
||||
// Load preference
|
||||
const theme = store.get('preferences.theme', 'light');
|
||||
```
|
||||
|
||||
### Shopping Cart
|
||||
|
||||
```javascript
|
||||
const store = StateManager.create({
|
||||
initialState: {
|
||||
cart: {
|
||||
items: [],
|
||||
total: 0
|
||||
}
|
||||
},
|
||||
persistence: {
|
||||
enabled: true,
|
||||
storage: 'sessionStorage',
|
||||
paths: ['cart']
|
||||
},
|
||||
sync: {
|
||||
enabled: true // Sync cart across tabs
|
||||
}
|
||||
});
|
||||
|
||||
// Add item
|
||||
store.set('cart.items', [
|
||||
...store.get('cart.items', []),
|
||||
{ id: 1, name: 'Product', price: 99.99 }
|
||||
]);
|
||||
|
||||
// Calculate total
|
||||
store.subscribe('cart.items', (items) => {
|
||||
const total = items.reduce((sum, item) => sum + item.price, 0);
|
||||
store.set('cart.total', total);
|
||||
});
|
||||
```
|
||||
|
||||
### UI State
|
||||
|
||||
```javascript
|
||||
const store = StateManager.create({
|
||||
initialState: {
|
||||
ui: {
|
||||
sidebarOpen: false,
|
||||
modalOpen: false,
|
||||
activeTab: 'home'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Toggle sidebar
|
||||
store.set('ui.sidebarOpen', !store.get('ui.sidebarOpen'));
|
||||
|
||||
// Subscribe to UI changes
|
||||
store.subscribe('ui', (uiState) => {
|
||||
updateUI(uiState);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Scoped State Managers** - Create separate state managers for different features
|
||||
2. **Persist Only Necessary Data** - Use `paths` to limit what gets persisted
|
||||
3. **Use Middleware for Cross-Cutting Concerns** - Logging, validation, etc.
|
||||
4. **Subscribe Selectively** - Only subscribe to paths you need
|
||||
5. **Clean Up Subscriptions** - Always call unsubscribe when done
|
||||
|
||||
---
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- State updates are synchronous and immediate
|
||||
- Subscriptions are called synchronously (be careful with expensive operations)
|
||||
- Persistence is debounced internally
|
||||
- Cross-tab sync uses BroadcastChannel (efficient)
|
||||
|
||||
---
|
||||
|
||||
## Browser Support
|
||||
|
||||
- **Chrome/Edge**: 38+
|
||||
- **Firefox**: 38+
|
||||
- **Safari**: 15.4+
|
||||
- **Mobile**: iOS 15.4+, Android Chrome 38+
|
||||
|
||||
**Required Features**:
|
||||
- ES2020 JavaScript
|
||||
- BroadcastChannel (for cross-tab sync)
|
||||
- localStorage/sessionStorage (for persistence)
|
||||
|
||||
---
|
||||
|
||||
**Next**: [Validation Module](validation.md) →
|
||||
|
||||
462
docs/modules/validation.md
Normal file
462
docs/modules/validation.md
Normal file
@@ -0,0 +1,462 @@
|
||||
# Validation Module
|
||||
|
||||
**Standalone Validation System for Fields, Forms, and Data**
|
||||
|
||||
The Validation Module provides a comprehensive validation system that can be used independently or integrated with form-handling and LiveComponents.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **Schema-Based Validation** - Define validation rules in a schema
|
||||
- **Field-Level Validation** - Validate individual fields
|
||||
- **Async Validation** - Support for asynchronous validation (e.g., API checks)
|
||||
- **Custom Validation Rules** - Register your own validation rules
|
||||
- **Integration with form-handling** - Works seamlessly with form-handling module
|
||||
- **Integration with LiveComponents** - Validate LiveComponent data
|
||||
- **HTML5 Attribute Support** - Automatically reads validation rules from HTML attributes
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```javascript
|
||||
import { Validator } from './modules/validation/index.js';
|
||||
|
||||
// Create validator with schema
|
||||
const validator = Validator.create({
|
||||
email: ['required', 'email'],
|
||||
age: [
|
||||
'required',
|
||||
'number',
|
||||
{ rule: 'min', options: { value: 18 } },
|
||||
{ rule: 'max', options: { value: 100 } }
|
||||
],
|
||||
password: [
|
||||
'required',
|
||||
{ rule: 'minLength', options: { value: 8 } }
|
||||
]
|
||||
});
|
||||
|
||||
// Validate data
|
||||
const result = await validator.validate({
|
||||
email: 'user@example.com',
|
||||
age: 25,
|
||||
password: 'secret123'
|
||||
});
|
||||
|
||||
if (result.valid) {
|
||||
console.log('Validation passed');
|
||||
} else {
|
||||
console.log('Validation errors:', result.errors);
|
||||
}
|
||||
```
|
||||
|
||||
### From HTML Form
|
||||
|
||||
```javascript
|
||||
import { Validator } from './modules/validation/index.js';
|
||||
|
||||
// Create validator from form element
|
||||
const form = document.querySelector('#my-form');
|
||||
const validator = Validator.fromForm(form);
|
||||
|
||||
// Validate form data
|
||||
const formData = new FormData(form);
|
||||
const data = Object.fromEntries(formData);
|
||||
const result = await validator.validate(data);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### Validator.create(schema)
|
||||
|
||||
Create a new Validator instance with a schema.
|
||||
|
||||
**Parameters**:
|
||||
- `schema` - Validation schema object
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
const validator = Validator.create({
|
||||
email: 'email',
|
||||
age: ['required', 'number', { rule: 'min', options: { value: 18 } }]
|
||||
});
|
||||
```
|
||||
|
||||
### Validator.fromForm(form)
|
||||
|
||||
Create a validator from an HTML form element, reading validation rules from HTML attributes.
|
||||
|
||||
**Parameters**:
|
||||
- `form` - HTMLFormElement
|
||||
|
||||
**Example**:
|
||||
```html
|
||||
<form id="my-form">
|
||||
<input type="email" name="email" required />
|
||||
<input type="number" name="age" min="18" max="100" required />
|
||||
<input type="password" name="password" minlength="8" required />
|
||||
</form>
|
||||
```
|
||||
|
||||
```javascript
|
||||
const form = document.querySelector('#my-form');
|
||||
const validator = Validator.fromForm(form);
|
||||
```
|
||||
|
||||
### validator.validate(data)
|
||||
|
||||
Validate entire data object against schema.
|
||||
|
||||
**Parameters**:
|
||||
- `data` - Data object to validate
|
||||
|
||||
**Returns**: `Promise<ValidationResults>`
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
const result = await validator.validate({
|
||||
email: 'user@example.com',
|
||||
age: 25
|
||||
});
|
||||
|
||||
console.log(result.valid); // true or false
|
||||
console.log(result.errors); // { email: ['...'], age: ['...'] }
|
||||
```
|
||||
|
||||
### validator.validateField(fieldName, value)
|
||||
|
||||
Validate a single field.
|
||||
|
||||
**Parameters**:
|
||||
- `fieldName` - Field name
|
||||
- `value` - Field value
|
||||
|
||||
**Returns**: `Promise<ValidationResult>`
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
const result = await validator.validateField('email', 'user@example.com');
|
||||
console.log(result.valid); // true or false
|
||||
console.log(result.errors); // ['error message']
|
||||
```
|
||||
|
||||
### validator.validateFields(data, fieldNames)
|
||||
|
||||
Validate specific fields.
|
||||
|
||||
**Parameters**:
|
||||
- `data` - Data object
|
||||
- `fieldNames` - Array of field names to validate
|
||||
|
||||
**Returns**: `Promise<ValidationResults>`
|
||||
|
||||
### validator.registerRule(name, rule)
|
||||
|
||||
Register a custom validation rule.
|
||||
|
||||
**Parameters**:
|
||||
- `name` - Rule name
|
||||
- `rule` - Validation function: `(value, options) => boolean | string`
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
validator.registerRule('customRule', (value, options) => {
|
||||
if (value === options.expected) {
|
||||
return true;
|
||||
}
|
||||
return options.message || 'Validation failed';
|
||||
});
|
||||
|
||||
// Use custom rule
|
||||
const validator = Validator.create({
|
||||
field: { rule: 'customRule', options: { expected: 'test', message: 'Must be "test"' } }
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Built-in Validation Rules
|
||||
|
||||
### required
|
||||
|
||||
Field must have a value.
|
||||
|
||||
```javascript
|
||||
email: 'required'
|
||||
// or
|
||||
email: { rule: 'required', options: { message: 'Email is required' } }
|
||||
```
|
||||
|
||||
### email
|
||||
|
||||
Valid email address format.
|
||||
|
||||
```javascript
|
||||
email: 'email'
|
||||
```
|
||||
|
||||
### url
|
||||
|
||||
Valid URL format.
|
||||
|
||||
```javascript
|
||||
website: 'url'
|
||||
```
|
||||
|
||||
### min / max
|
||||
|
||||
Minimum/maximum numeric value.
|
||||
|
||||
```javascript
|
||||
age: [
|
||||
{ rule: 'min', options: { value: 18 } },
|
||||
{ rule: 'max', options: { value: 100 } }
|
||||
]
|
||||
```
|
||||
|
||||
### minLength / maxLength
|
||||
|
||||
Minimum/maximum string length.
|
||||
|
||||
```javascript
|
||||
password: [
|
||||
{ rule: 'minLength', options: { value: 8 } },
|
||||
{ rule: 'maxLength', options: { value: 128 } }
|
||||
]
|
||||
```
|
||||
|
||||
### pattern
|
||||
|
||||
Regex pattern validation.
|
||||
|
||||
```javascript
|
||||
username: {
|
||||
rule: 'pattern',
|
||||
options: {
|
||||
value: '^[a-zA-Z0-9_]+$',
|
||||
message: 'Username can only contain letters, numbers, and underscores'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### number
|
||||
|
||||
Must be a valid number.
|
||||
|
||||
```javascript
|
||||
price: 'number'
|
||||
```
|
||||
|
||||
### integer
|
||||
|
||||
Must be an integer.
|
||||
|
||||
```javascript
|
||||
quantity: 'integer'
|
||||
```
|
||||
|
||||
### phone
|
||||
|
||||
Valid phone number format.
|
||||
|
||||
```javascript
|
||||
phone: 'phone'
|
||||
```
|
||||
|
||||
### postalCode
|
||||
|
||||
Valid postal code (supports DE, US, UK, FR).
|
||||
|
||||
```javascript
|
||||
postalCode: {
|
||||
rule: 'postalCode',
|
||||
options: { country: 'DE' }
|
||||
}
|
||||
```
|
||||
|
||||
### custom
|
||||
|
||||
Custom validation function.
|
||||
|
||||
```javascript
|
||||
field: {
|
||||
rule: 'custom',
|
||||
options: {
|
||||
validator: (value, options) => {
|
||||
// Custom validation logic
|
||||
return value === options.expected ? true : 'Value must be ' + options.expected;
|
||||
},
|
||||
expected: 'test'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Async Validation
|
||||
|
||||
```javascript
|
||||
const validator = Validator.create({
|
||||
email: {
|
||||
rule: 'custom',
|
||||
async: true,
|
||||
validator: async (value, options) => {
|
||||
// Check if email exists via API
|
||||
const response = await fetch(`/api/check-email?email=${value}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.exists) {
|
||||
return 'Email already exists';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const result = await validator.validate({ email: 'user@example.com' });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration with form-handling
|
||||
|
||||
```javascript
|
||||
import { Validator } from './modules/validation/index.js';
|
||||
import { FormHandler } from './modules/form-handling/index.js';
|
||||
|
||||
const form = document.querySelector('#my-form');
|
||||
const validator = Validator.fromForm(form);
|
||||
const formHandler = FormHandler.create(form);
|
||||
|
||||
// Use validator with form handler
|
||||
formHandler.validator = validator;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration with LiveComponents
|
||||
|
||||
```javascript
|
||||
import { Validator } from './modules/validation/index.js';
|
||||
import { LiveComponentManager } from './modules/livecomponent/index.js';
|
||||
|
||||
const validator = Validator.create({
|
||||
email: ['required', 'email'],
|
||||
name: ['required', { rule: 'minLength', options: { value: 2 } }]
|
||||
});
|
||||
|
||||
// Validate before action
|
||||
const lcManager = LiveComponentManager.getInstance();
|
||||
lcManager.on('action:before-execute', async (componentId, actionName, params) => {
|
||||
if (actionName === 'submitForm') {
|
||||
const result = await validator.validate(params);
|
||||
if (!result.valid) {
|
||||
// Handle validation errors
|
||||
return false; // Prevent action
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Form Validation
|
||||
|
||||
```javascript
|
||||
const validator = Validator.create({
|
||||
email: ['required', 'email'],
|
||||
password: [
|
||||
'required',
|
||||
{ rule: 'minLength', options: { value: 8 } }
|
||||
],
|
||||
confirmPassword: [
|
||||
'required',
|
||||
{
|
||||
rule: 'custom',
|
||||
validator: (value, options) => {
|
||||
const password = options.password;
|
||||
return value === password ? true : 'Passwords do not match';
|
||||
},
|
||||
options: { password: formData.password }
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
### API Response Validation
|
||||
|
||||
```javascript
|
||||
const validator = Validator.create({
|
||||
id: ['required', 'integer'],
|
||||
name: ['required', { rule: 'minLength', options: { value: 1 } }],
|
||||
email: ['required', 'email']
|
||||
});
|
||||
|
||||
// Validate API response
|
||||
const response = await fetch('/api/user');
|
||||
const data = await response.json();
|
||||
const result = await validator.validate(data);
|
||||
|
||||
if (!result.valid) {
|
||||
console.error('Invalid API response:', result.errors);
|
||||
}
|
||||
```
|
||||
|
||||
### User Input Validation
|
||||
|
||||
```javascript
|
||||
// Validate on input
|
||||
const validator = Validator.create({
|
||||
search: {
|
||||
rule: 'minLength',
|
||||
options: { value: 3, message: 'Search must be at least 3 characters' }
|
||||
}
|
||||
});
|
||||
|
||||
const input = document.querySelector('#search');
|
||||
input.addEventListener('input', async (e) => {
|
||||
const result = await validator.validateField('search', e.target.value);
|
||||
if (!result.valid) {
|
||||
showError(result.errors[0]);
|
||||
} else {
|
||||
hideError();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Schema-Based Validation** - Define validation rules in a schema for reusability
|
||||
2. **Register Custom Rules** - Create reusable custom validation rules
|
||||
3. **Validate Early** - Validate on blur or input for better UX
|
||||
4. **Show Clear Errors** - Display validation errors clearly to users
|
||||
5. **Use Async Validation Sparingly** - Only for necessary checks (e.g., email uniqueness)
|
||||
|
||||
---
|
||||
|
||||
## Browser Support
|
||||
|
||||
- **Chrome/Edge**: 90+
|
||||
- **Firefox**: 88+
|
||||
- **Safari**: 14+
|
||||
- **Mobile**: iOS 14+, Android Chrome 90+
|
||||
|
||||
**Required Features**:
|
||||
- ES2020 JavaScript
|
||||
- Promise support
|
||||
- Async/await support
|
||||
|
||||
---
|
||||
|
||||
**Next**: [Error Tracking Module](error-tracking.md) →
|
||||
|
||||
Reference in New Issue
Block a user