@je-es/client 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,770 @@
1
+ <!-- ╔══════════════════════════════ BEG ══════════════════════════════╗ -->
2
+
3
+ <br>
4
+ <div align="center">
5
+ <p>
6
+ <img src="./assets/img/logo.png" alt="logo" style="" height="80" />
7
+ </p>
8
+ </div>
9
+
10
+ <div align="center">
11
+ <img src="https://img.shields.io/badge/v-0.0.1-black"/>
12
+ <img src="https://img.shields.io/badge/🔥-@je--es-black"/>
13
+ <br>
14
+ <img src="https://github.com/je-es/client/actions/workflows/ci.yml/badge.svg" alt="CI" />
15
+ <img src="https://img.shields.io/badge/coverage-90%25-brightgreen" alt="Test Coverage" />
16
+ <img src="https://img.shields.io/github/issues/je-es/client?style=flat" alt="Github Repo Issues" />
17
+ <img src="https://img.shields.io/github/stars/je-es/client?style=social" alt="GitHub Repo stars" />
18
+ </div>
19
+ <br>
20
+
21
+ <!-- ╚═════════════════════════════════════════════════════════════════╝ -->
22
+
23
+
24
+
25
+
26
+ <!-- ╔══════════════════════════════ DOC ══════════════════════════════╗ -->
27
+
28
+ - ## Quick Start 🔥
29
+
30
+ > _**The simplest, fastest and most organized way to manage the front-end of web applications.**_
31
+
32
+ > _**This lib must run with [@je-es/server](https://github.com/je-es/server)**_.
33
+
34
+ > _We prefer to use [`space`](https://github.com//solution-lib/space) with [`@solution-dist/web`](https://github.com/solution-dist/web) for a better experience._
35
+
36
+ - ### Setup
37
+
38
+ > install [`space`](https://github.com/solution-lib/space) first.
39
+
40
+ - #### Create
41
+
42
+ ```bash
43
+ > space init <name> -t web # This will clone a ready-to-use repo and make some changes to suit your app.
44
+ > cd <name> # Go to the project directory
45
+ > space install # Install the dependencies
46
+ ```
47
+
48
+ - #### Manage
49
+
50
+ ```bash
51
+ > space lint
52
+ > space build
53
+ > space test
54
+ > space start
55
+ ```
56
+
57
+ - #### Usage
58
+
59
+ ```typescript
60
+ import { Component, html, state, router } from '@je-es/client';
61
+
62
+ class MyComponent extends Component {
63
+ @state count = 0;
64
+
65
+ render() {
66
+ return html`
67
+ <div>
68
+ <h1>Count: ${this.count}</h1>
69
+ <button onclick=${() => this.count++}>
70
+ Increment
71
+ </button>
72
+ </div>
73
+ `;
74
+ }
75
+ }
76
+ ```
77
+
78
+ ```bash
79
+ > space start
80
+
81
+ Building @je-es/client application...
82
+ Build completed successfully!
83
+ Output: ./src/frontend/static/js/client.js
84
+
85
+ Server started at http://localhost:3000
86
+ ```
87
+
88
+ <div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
89
+
90
+ - ## Examples
91
+
92
+ - ### Component System
93
+
94
+ ```typescript
95
+ import { Component, html, css, state, computed } from '@je-es/client';
96
+
97
+ class TodoComponent extends Component {
98
+ @state todos = [];
99
+ @state filter = 'all';
100
+
101
+ @computed
102
+ get filteredTodos() {
103
+ return this.filter === 'all'
104
+ ? this.todos
105
+ : this.todos.filter(t => t.status === this.filter);
106
+ }
107
+
108
+ render() {
109
+ return html`
110
+ <div class="todo-app">
111
+ <h1>My Todos</h1>
112
+ ${this.filteredTodos.map(todo => html`
113
+ <div class="todo-item">
114
+ ${todo.title}
115
+ </div>
116
+ `)}
117
+ </div>
118
+ `;
119
+ }
120
+
121
+ styles() {
122
+ return css`
123
+ .todo-app {
124
+ padding: 2rem;
125
+ max-width: 600px;
126
+ margin: 0 auto;
127
+ }
128
+ .todo-item {
129
+ padding: 1rem;
130
+ border-bottom: 1px solid #eee;
131
+ }
132
+ `;
133
+ }
134
+ }
135
+ ```
136
+
137
+ <div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
138
+
139
+ - ### Router Configuration
140
+
141
+ ```typescript
142
+ import { client, router } from '@je-es/client';
143
+
144
+ const clientApp = client({
145
+ app: {
146
+ root: '#app',
147
+ routes: [
148
+ {
149
+ path: '/',
150
+ component: () => import('./pages/HomePage'),
151
+ meta: { title: 'Home' }
152
+ },
153
+ {
154
+ path: '/users/:id',
155
+ component: () => import('./pages/UserPage'),
156
+ meta: { title: 'User Profile' },
157
+ beforeEnter: (to, from, next) => {
158
+ // Route guard
159
+ if (isAuthenticated()) {
160
+ next();
161
+ } else {
162
+ next('/login');
163
+ }
164
+ }
165
+ }
166
+ ]
167
+ },
168
+ router: {
169
+ mode: 'history',
170
+ base: '/',
171
+ scrollBehavior: 'smooth'
172
+ }
173
+ });
174
+
175
+ // Navigate programmatically
176
+ router.push('/users/123');
177
+ router.replace('/home');
178
+ router.back();
179
+ ```
180
+
181
+ <div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
182
+
183
+ - ### State Management (Store)
184
+
185
+ ```typescript
186
+ import { createStore, connect } from '@je-es/client';
187
+
188
+ // Create global store
189
+ const userStore = createStore({
190
+ state: {
191
+ user: null,
192
+ isAuthenticated: false,
193
+ preferences: {}
194
+ },
195
+ persist: true,
196
+ storage: 'localStorage',
197
+ storageKey: 'app-user-store'
198
+ });
199
+
200
+ // Subscribe to changes
201
+ userStore.subscribe((state) => {
202
+ console.log('User state changed:', state);
203
+ });
204
+
205
+ // Update state
206
+ userStore.setState({
207
+ user: { name: 'John', email: 'john@example.com' },
208
+ isAuthenticated: true
209
+ });
210
+
211
+ // Connect to component
212
+ class UserProfile extends Component {
213
+ render() {
214
+ return html`<div>User: ${userStore.state.user?.name}</div>`;
215
+ }
216
+ }
217
+
218
+ connect(userStore, component, (state) => ({
219
+ user: state.user
220
+ }));
221
+ ```
222
+
223
+ <div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
224
+
225
+ - ### React-Like Hooks
226
+
227
+ ```typescript
228
+ import {
229
+ createFunctionalComponent,
230
+ useState,
231
+ useEffect,
232
+ useMemo
233
+ } from '@je-es/client';
234
+
235
+ const Counter = createFunctionalComponent((props) => {
236
+ const [count, setCount] = useState(0);
237
+ const [multiplier, setMultiplier] = useState(2);
238
+
239
+ // Computed value
240
+ const result = useMemo(() => {
241
+ return count * multiplier;
242
+ }, [count, multiplier]);
243
+
244
+ // Side effect
245
+ useEffect(() => {
246
+ document.title = `Count: ${count}`;
247
+
248
+ return () => {
249
+ // Cleanup
250
+ document.title = 'App';
251
+ };
252
+ }, [count]);
253
+
254
+ return html`
255
+ <div>
256
+ <h2>Result: ${result}</h2>
257
+ <button onclick=${() => setCount(count + 1)}>
258
+ Increment
259
+ </button>
260
+ <button onclick=${() => setMultiplier(multiplier + 1)}>
261
+ Increase Multiplier
262
+ </button>
263
+ </div>
264
+ `;
265
+ }, 'Counter');
266
+
267
+ const component = new Counter({ initial: 5 });
268
+ await component.mount(container);
269
+ ```
270
+
271
+ <div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
272
+
273
+ - ### Smart Forms
274
+
275
+ ```typescript
276
+ import { SmartFormComponent } from '@je-es/client';
277
+
278
+ const loginForm = new SmartFormComponent({
279
+ fields: [
280
+ {
281
+ name: 'email',
282
+ type: 'email',
283
+ label: 'Email Address',
284
+ placeholder: 'Enter your email',
285
+ validation: {
286
+ required: true,
287
+ email: true,
288
+ message: 'Please enter a valid email'
289
+ }
290
+ },
291
+ {
292
+ name: 'password',
293
+ type: 'password',
294
+ label: 'Password',
295
+ validation: {
296
+ required: true,
297
+ minLength: 8,
298
+ message: 'Password must be at least 8 characters'
299
+ }
300
+ },
301
+ {
302
+ name: 'remember',
303
+ type: 'checkbox',
304
+ label: 'Remember me'
305
+ }
306
+ ],
307
+ endpoint: '/api/auth/login',
308
+ method: 'POST',
309
+ autoValidate: true,
310
+ submitButton: {
311
+ label: 'Sign In',
312
+ loadingLabel: 'Signing in...',
313
+ className: 'btn-primary'
314
+ },
315
+ onSuccess: (data) => {
316
+ localStorage.setItem('token', data.token);
317
+ router.push('/dashboard');
318
+ },
319
+ onError: (error) => {
320
+ console.error('Login failed:', error);
321
+ }
322
+ });
323
+
324
+ await loginForm.mount(container);
325
+ ```
326
+
327
+ <div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
328
+
329
+ - ### Context API
330
+
331
+ ```typescript
332
+ import { createContext, Provider, useContext } from '@je-es/client';
333
+
334
+ // Create contexts
335
+ const ThemeContext = createContext({ theme: 'light' });
336
+ const UserContext = createContext({ user: null });
337
+
338
+ // Provider component
339
+ class App extends Component {
340
+ @state theme = 'dark';
341
+ @state user = { name: 'John' };
342
+
343
+ render() {
344
+ return html`
345
+ <${Provider}
346
+ context=${ThemeContext}
347
+ value=${{ theme: this.theme }}
348
+ >
349
+ <${Provider}
350
+ context=${UserContext}
351
+ value=${{ user: this.user }}
352
+ >
353
+ <${ConsumerComponent} />
354
+ </${Provider}>
355
+ </${Provider}>
356
+ `;
357
+ }
358
+ }
359
+
360
+ // Consumer component
361
+ class ConsumerComponent extends Component {
362
+ render() {
363
+ const theme = useContext(ThemeContext, this);
364
+ const user = useContext(UserContext, this);
365
+
366
+ return html`
367
+ <div class="${theme.theme}">
368
+ Welcome, ${user.user?.name}!
369
+ </div>
370
+ `;
371
+ }
372
+ }
373
+ ```
374
+
375
+ <div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
376
+
377
+ - ### API Integration
378
+
379
+ ```typescript
380
+ import { api, http, configureApi } from '@je-es/client';
381
+
382
+ // Global configuration
383
+ configureApi({
384
+ baseURL: 'https://api.example.com',
385
+ timeout: 30000,
386
+ headers: {
387
+ 'Content-Type': 'application/json'
388
+ },
389
+ interceptors: {
390
+ request: (config) => {
391
+ const token = localStorage.getItem('token');
392
+ if (token) {
393
+ config.headers['Authorization'] = `Bearer ${token}`;
394
+ }
395
+ return config;
396
+ },
397
+ response: (response) => {
398
+ console.log('Response:', response);
399
+ return response;
400
+ },
401
+ error: (error) => {
402
+ if (error.status === 401) {
403
+ router.push('/login');
404
+ }
405
+ throw error;
406
+ }
407
+ }
408
+ });
409
+
410
+ // Make requests
411
+ const users = await http.get('/users');
412
+ const user = await http.get('/users/123');
413
+ const created = await http.post('/users', { name: 'Jane' });
414
+ const updated = await http.put('/users/123', { name: 'Jane Doe' });
415
+ await http.delete('/users/123');
416
+
417
+ // Advanced usage
418
+ const response = await api({
419
+ method: 'POST',
420
+ url: '/upload',
421
+ data: formData,
422
+ params: { folder: 'avatars' },
423
+ timeout: 60000
424
+ });
425
+ ```
426
+
427
+ <br>
428
+
429
+ - ## API
430
+
431
+ - ### Component Lifecycle
432
+
433
+ ```typescript
434
+ class MyComponent extends Component {
435
+ // Called before component is mounted to DOM
436
+ async onBeforeMount(): void {
437
+ // Initialize data, fetch resources
438
+ }
439
+
440
+ // Called after component is mounted to DOM
441
+ async onMount(): void {
442
+ // Setup event listeners, start timers
443
+ }
444
+
445
+ // Called before component updates
446
+ async onBeforeUpdate(prevProps, prevState): void {
447
+ // Prepare for update
448
+ }
449
+
450
+ // Called after component updates
451
+ onUpdate(prevProps, prevState): void {
452
+ // React to changes
453
+ }
454
+
455
+ // Called before component unmounts
456
+ onBeforeUnmount(): void {
457
+ // Cleanup preparation
458
+ }
459
+
460
+ // Called after component unmounts
461
+ onUnmount(): void {
462
+ // Remove listeners, clear timers
463
+ }
464
+
465
+ // Called when error occurs
466
+ onError(error: Error, errorInfo): void {
467
+ // Handle errors
468
+ }
469
+
470
+ // Control whether component should update
471
+ shouldUpdate(prevProps, prevState): boolean {
472
+ return true; // or custom logic
473
+ }
474
+ }
475
+ ```
476
+
477
+ <div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
478
+
479
+ - ### Decorators
480
+
481
+ ```typescript
482
+ import { state, computed, watch } from '@je-es/client';
483
+
484
+ class ReactiveComponent extends Component {
485
+ // Reactive state - triggers re-render on change
486
+ @state count = 0;
487
+ @state items = [];
488
+ @state user = { name: 'John' };
489
+
490
+ // Computed property - cached until dependencies change
491
+ @computed
492
+ get doubleCount() {
493
+ return this.count * 2;
494
+ }
495
+
496
+ @computed
497
+ get itemCount() {
498
+ return this.items.length;
499
+ }
500
+
501
+ // Watch for property changes
502
+ @watch('count')
503
+ onCountChange(newValue, oldValue) {
504
+ console.log(`Count changed from ${oldValue} to ${newValue}`);
505
+ }
506
+
507
+ @watch('user')
508
+ onUserChange(newUser, oldUser) {
509
+ console.log('User updated:', newUser);
510
+ }
511
+
512
+ render() {
513
+ return html`
514
+ <div>
515
+ Count: ${this.count}
516
+ Double: ${this.doubleCount}
517
+ </div>
518
+ `;
519
+ }
520
+ }
521
+ ```
522
+
523
+ <div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
524
+
525
+ - ### React-Like Hooks
526
+
527
+ ```typescript
528
+ import {
529
+ useState,
530
+ useEffect,
531
+ useMemo,
532
+ useCallback,
533
+ useRef,
534
+ useReducer,
535
+ useLocalStorage,
536
+ useDebounce,
537
+ usePrevious,
538
+ useToggle,
539
+ useInterval,
540
+ useFetch,
541
+ useWindowSize,
542
+ useEventListener
543
+ } from '@je-es/client';
544
+
545
+ // State management
546
+ const [count, setCount] = useState(0);
547
+ const [user, setUser] = useState({ name: 'John' });
548
+
549
+ // Side effects
550
+ useEffect(() => {
551
+ console.log('Component mounted');
552
+ return () => console.log('Component unmounted');
553
+ }, []);
554
+
555
+ // Memoization
556
+ const expensiveValue = useMemo(() => {
557
+ return count * 2;
558
+ }, [count]);
559
+
560
+ // Callback memoization
561
+ const handleClick = useCallback(() => {
562
+ setCount(count + 1);
563
+ }, [count]);
564
+
565
+ // Persistent reference
566
+ const inputRef = useRef(null);
567
+
568
+ // Complex state
569
+ const [state, dispatch] = useReducer(reducer, initialState);
570
+
571
+ // LocalStorage sync
572
+ const [value, setValue] = useLocalStorage('key', defaultValue);
573
+
574
+ // Debounced value
575
+ const debouncedSearch = useDebounce(searchTerm, 500);
576
+
577
+ // Previous value
578
+ const prevCount = usePrevious(count);
579
+
580
+ // Boolean toggle
581
+ const [isOn, toggle] = useToggle(false);
582
+
583
+ // Interval
584
+ useInterval(() => {
585
+ console.log('Tick');
586
+ }, 1000);
587
+
588
+ // Data fetching
589
+ const { data, loading, error, refetch } = useFetch('/api/users');
590
+
591
+ // Window size
592
+ const { width, height } = useWindowSize();
593
+
594
+ // Event listener
595
+ useEventListener('click', handleClick, element);
596
+ ```
597
+
598
+ <div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
599
+
600
+ - ### Router API
601
+
602
+ ```typescript
603
+ import { router, Router } from '@je-es/client';
604
+
605
+ // Navigation
606
+ router.push('/users/123');
607
+ router.push('/search?q=test&page=2');
608
+ router.replace('/home');
609
+ router.back();
610
+ router.forward();
611
+ router.go(-2);
612
+
613
+ // Named routes
614
+ router.pushNamed('user', { id: '123' });
615
+
616
+ // Route guards
617
+ router.beforeEach((to, from, next) => {
618
+ if (to.meta.requiresAuth && !isAuthenticated()) {
619
+ next('/login');
620
+ } else {
621
+ next();
622
+ }
623
+ });
624
+
625
+ router.afterEach((to, from) => {
626
+ console.log(`Navigated from ${from.path} to ${to.path}`);
627
+ });
628
+
629
+ // Route info
630
+ const current = router.getCurrentRoute();
631
+ const isActive = router.isActive('/users');
632
+ const route = router.resolve('/users/123');
633
+
634
+ // Route outlet (in component)
635
+ render() {
636
+ return html`
637
+ <div>
638
+ <nav>...</nav>
639
+ ${router.outlet()}
640
+ </div>
641
+ `;
642
+ }
643
+ ```
644
+
645
+ <div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
646
+
647
+ - ### Store API
648
+
649
+ ```typescript
650
+ import { Store, createStore, createComputedStore, connect } from '@je-es/client';
651
+
652
+ // Create store
653
+ const store = createStore({
654
+ state: { count: 0, user: null },
655
+ persist: true,
656
+ storage: 'localStorage',
657
+ storageKey: 'my-store',
658
+ middleware: [
659
+ (state, action) => {
660
+ console.log('State changed:', action, state);
661
+ }
662
+ ]
663
+ });
664
+
665
+ // Get state
666
+ const state = store.state;
667
+ const count = store.get('count');
668
+
669
+ // Update state
670
+ store.setState({ count: 5 });
671
+ store.set('count', 10);
672
+ store.setState(prev => ({ count: prev.count + 1 }));
673
+
674
+ // Subscribe
675
+ const unsubscribe = store.subscribe((state) => {
676
+ console.log('State:', state);
677
+ });
678
+
679
+ store.subscribeToKey('count', (value) => {
680
+ console.log('Count:', value);
681
+ });
682
+
683
+ // Batch updates
684
+ store.batch(() => {
685
+ store.set('count', 1);
686
+ store.set('user', { name: 'Jane' });
687
+ });
688
+
689
+ // Computed store
690
+ const doubleStore = createComputedStore(
691
+ [store],
692
+ (state) => state.count * 2
693
+ );
694
+
695
+ // Clear/Reset
696
+ store.clear();
697
+ store.reset({ count: 0, user: null });
698
+
699
+ // Destroy
700
+ store.destroy();
701
+ ```
702
+
703
+ <div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
704
+
705
+ - ### Utility Functions
706
+
707
+ ```typescript
708
+ import {
709
+ debounce,
710
+ throttle,
711
+ classNames,
712
+ formatDate,
713
+ deepClone,
714
+ deepMerge,
715
+ uniqueId,
716
+ sleep,
717
+ isEmpty,
718
+ capitalize,
719
+ kebabCase,
720
+ camelCase,
721
+ pascalCase,
722
+ truncate,
723
+ parseQuery,
724
+ stringifyQuery,
725
+ clamp
726
+ } from '@je-es/client';
727
+
728
+ // Function utilities
729
+ const debouncedFn = debounce(() => console.log('Called'), 300);
730
+ const throttledFn = throttle(() => console.log('Called'), 1000);
731
+
732
+ // String utilities
733
+ const classes = classNames('btn', { active: true, disabled: false });
734
+ const date = formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss');
735
+ const cap = capitalize('hello'); // 'Hello'
736
+ const kebab = kebabCase('helloWorld'); // 'hello-world'
737
+ const camel = camelCase('hello-world'); // 'helloWorld'
738
+ const pascal = pascalCase('hello-world'); // 'HelloWorld'
739
+ const short = truncate('Long text here', 10); // 'Long te...'
740
+
741
+ // Object utilities
742
+ const cloned = deepClone(obj);
743
+ const merged = deepMerge(obj1, obj2, obj3);
744
+
745
+ // Other utilities
746
+ const id = uniqueId('prefix');
747
+ await sleep(1000);
748
+ const empty = isEmpty(value);
749
+ const clamped = clamp(150, 0, 100); // 100
750
+
751
+ // Query string
752
+ const params = parseQuery('?page=1&limit=10');
753
+ const query = stringifyQuery({ page: 1, limit: 10 });
754
+ ```
755
+
756
+ <!-- ╚═════════════════════════════════════════════════════════════════╝ -->
757
+
758
+
759
+
760
+ <!-- ╔══════════════════════════════ END ══════════════════════════════╗ -->
761
+
762
+ <br>
763
+
764
+ ---
765
+
766
+ <div align="center">
767
+ <a href="https://github.com/solution-lib/space"><img src="https://img.shields.io/badge/by-Space-black"/></a>
768
+ </div>
769
+
770
+ <!-- ╚═════════════════════════════════════════════════════════════════╝ -->