zero-query 0.9.8 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +55 -31
  2. package/cli/args.js +1 -1
  3. package/cli/commands/build.js +2 -2
  4. package/cli/commands/bundle.js +15 -15
  5. package/cli/commands/create.js +41 -7
  6. package/cli/commands/dev/devtools/index.js +1 -1
  7. package/cli/commands/dev/devtools/js/core.js +14 -14
  8. package/cli/commands/dev/devtools/js/elements.js +4 -4
  9. package/cli/commands/dev/devtools/js/stats.js +1 -1
  10. package/cli/commands/dev/devtools/styles.css +2 -2
  11. package/cli/commands/dev/index.js +2 -2
  12. package/cli/commands/dev/logger.js +1 -1
  13. package/cli/commands/dev/overlay.js +21 -14
  14. package/cli/commands/dev/server.js +5 -5
  15. package/cli/commands/dev/validator.js +7 -7
  16. package/cli/commands/dev/watcher.js +6 -6
  17. package/cli/help.js +4 -2
  18. package/cli/index.js +2 -2
  19. package/cli/scaffold/default/app/app.js +17 -18
  20. package/cli/scaffold/default/app/components/about.js +9 -9
  21. package/cli/scaffold/default/app/components/api-demo.js +6 -6
  22. package/cli/scaffold/default/app/components/contact-card.js +4 -4
  23. package/cli/scaffold/default/app/components/contacts/contacts.css +2 -2
  24. package/cli/scaffold/default/app/components/contacts/contacts.html +3 -3
  25. package/cli/scaffold/default/app/components/contacts/contacts.js +11 -11
  26. package/cli/scaffold/default/app/components/counter.js +8 -8
  27. package/cli/scaffold/default/app/components/home.js +13 -13
  28. package/cli/scaffold/default/app/components/not-found.js +1 -1
  29. package/cli/scaffold/default/app/components/playground/playground.css +1 -1
  30. package/cli/scaffold/default/app/components/playground/playground.html +11 -11
  31. package/cli/scaffold/default/app/components/playground/playground.js +11 -11
  32. package/cli/scaffold/default/app/components/todos.js +8 -8
  33. package/cli/scaffold/default/app/components/toolkit/toolkit.css +1 -1
  34. package/cli/scaffold/default/app/components/toolkit/toolkit.html +4 -4
  35. package/cli/scaffold/default/app/components/toolkit/toolkit.js +7 -7
  36. package/cli/scaffold/default/app/routes.js +1 -1
  37. package/cli/scaffold/default/app/store.js +1 -1
  38. package/cli/scaffold/default/global.css +2 -2
  39. package/cli/scaffold/default/index.html +2 -2
  40. package/cli/scaffold/minimal/app/app.js +6 -7
  41. package/cli/scaffold/minimal/app/components/about.js +5 -5
  42. package/cli/scaffold/minimal/app/components/counter.js +6 -6
  43. package/cli/scaffold/minimal/app/components/home.js +8 -8
  44. package/cli/scaffold/minimal/app/components/not-found.js +1 -1
  45. package/cli/scaffold/minimal/app/routes.js +1 -1
  46. package/cli/scaffold/minimal/app/store.js +1 -1
  47. package/cli/scaffold/minimal/global.css +2 -2
  48. package/cli/scaffold/minimal/index.html +1 -1
  49. package/cli/scaffold/ssr/app/app.js +29 -0
  50. package/cli/scaffold/ssr/app/components/about.js +28 -0
  51. package/cli/scaffold/ssr/app/components/home.js +37 -0
  52. package/cli/scaffold/ssr/app/components/not-found.js +15 -0
  53. package/cli/scaffold/ssr/app/routes.js +6 -0
  54. package/cli/scaffold/ssr/global.css +113 -0
  55. package/cli/scaffold/ssr/index.html +31 -0
  56. package/cli/scaffold/ssr/package.json +8 -0
  57. package/cli/scaffold/ssr/server/index.js +118 -0
  58. package/cli/utils.js +6 -6
  59. package/dist/zquery.dist.zip +0 -0
  60. package/dist/zquery.js +565 -228
  61. package/dist/zquery.min.js +2 -2
  62. package/index.d.ts +25 -12
  63. package/index.js +11 -7
  64. package/package.json +9 -3
  65. package/src/component.js +64 -63
  66. package/src/core.js +15 -15
  67. package/src/diff.js +38 -38
  68. package/src/errors.js +72 -18
  69. package/src/expression.js +15 -17
  70. package/src/http.js +4 -4
  71. package/src/package.json +1 -0
  72. package/src/reactive.js +75 -9
  73. package/src/router.js +104 -24
  74. package/src/ssr.js +133 -39
  75. package/src/store.js +103 -21
  76. package/src/utils.js +64 -12
  77. package/tests/audit.test.js +143 -15
  78. package/tests/cli.test.js +20 -20
  79. package/tests/component.test.js +121 -121
  80. package/tests/core.test.js +56 -56
  81. package/tests/diff.test.js +42 -42
  82. package/tests/errors.test.js +425 -147
  83. package/tests/expression.test.js +58 -53
  84. package/tests/http.test.js +20 -20
  85. package/tests/reactive.test.js +185 -24
  86. package/tests/router.test.js +501 -74
  87. package/tests/ssr.test.js +444 -10
  88. package/tests/store.test.js +264 -23
  89. package/tests/utils.test.js +163 -26
  90. package/types/collection.d.ts +2 -2
  91. package/types/component.d.ts +5 -5
  92. package/types/errors.d.ts +36 -4
  93. package/types/http.d.ts +3 -3
  94. package/types/misc.d.ts +9 -9
  95. package/types/reactive.d.ts +25 -3
  96. package/types/router.d.ts +10 -6
  97. package/types/ssr.d.ts +22 -2
  98. package/types/store.d.ts +40 -5
  99. package/types/utils.d.ts +1 -1
@@ -6,7 +6,7 @@ import { createStore, getStore } from '../src/store.js';
6
6
  // Store creation
7
7
  // ---------------------------------------------------------------------------
8
8
 
9
- describe('Store creation', () => {
9
+ describe('Store - creation', () => {
10
10
  it('creates a store with initial state', () => {
11
11
  const store = createStore('test-create', {
12
12
  state: { count: 0, name: 'Tony' },
@@ -42,7 +42,7 @@ describe('Store — creation', () => {
42
42
  // Dispatch & actions
43
43
  // ---------------------------------------------------------------------------
44
44
 
45
- describe('Store dispatch', () => {
45
+ describe('Store - dispatch', () => {
46
46
  it('dispatches a named action', () => {
47
47
  const store = createStore('dispatch-1', {
48
48
  state: { count: 0 },
@@ -101,7 +101,7 @@ describe('Store — dispatch', () => {
101
101
  // Subscriptions
102
102
  // ---------------------------------------------------------------------------
103
103
 
104
- describe('Store subscribe', () => {
104
+ describe('Store - subscribe', () => {
105
105
  it('notifies key-specific subscribers', () => {
106
106
  const store = createStore('sub-1', {
107
107
  state: { count: 0 },
@@ -110,7 +110,7 @@ describe('Store — subscribe', () => {
110
110
  const fn = vi.fn();
111
111
  store.subscribe('count', fn);
112
112
  store.dispatch('inc');
113
- expect(fn).toHaveBeenCalledWith(1, 0, 'count');
113
+ expect(fn).toHaveBeenCalledWith('count', 1, 0);
114
114
  });
115
115
 
116
116
  it('wildcard subscriber gets all changes', () => {
@@ -157,7 +157,7 @@ describe('Store — subscribe', () => {
157
157
  // Getters
158
158
  // ---------------------------------------------------------------------------
159
159
 
160
- describe('Store getters', () => {
160
+ describe('Store - getters', () => {
161
161
  it('computes values from state', () => {
162
162
  const store = createStore('getters-1', {
163
163
  state: { count: 5 },
@@ -184,7 +184,7 @@ describe('Store — getters', () => {
184
184
  // Middleware
185
185
  // ---------------------------------------------------------------------------
186
186
 
187
- describe('Store middleware', () => {
187
+ describe('Store - middleware', () => {
188
188
  it('calls middleware before action', () => {
189
189
  const log = vi.fn();
190
190
  const store = createStore('mw-1', {
@@ -223,7 +223,7 @@ describe('Store — middleware', () => {
223
223
  // Snapshot & replaceState
224
224
  // ---------------------------------------------------------------------------
225
225
 
226
- describe('Store snapshot & replaceState', () => {
226
+ describe('Store - snapshot & replaceState', () => {
227
227
  it('snapshot returns plain object copy', () => {
228
228
  const store = createStore('snap-1', { state: { a: 1, b: { c: 2 } } });
229
229
  const snap = store.snapshot();
@@ -257,7 +257,7 @@ describe('Store — snapshot & replaceState', () => {
257
257
  // Multiple middleware
258
258
  // ---------------------------------------------------------------------------
259
259
 
260
- describe('Store multiple middleware', () => {
260
+ describe('Store - multiple middleware', () => {
261
261
  it('runs middleware in order', () => {
262
262
  const order = [];
263
263
  const store = createStore('mw-multi', {
@@ -287,7 +287,7 @@ describe('Store — multiple middleware', () => {
287
287
  // Async actions
288
288
  // ---------------------------------------------------------------------------
289
289
 
290
- describe('Store async actions', () => {
290
+ describe('Store - async actions', () => {
291
291
  it('supports async action returning promise', async () => {
292
292
  const store = createStore('async-1', {
293
293
  state: { data: null },
@@ -307,7 +307,7 @@ describe('Store — async actions', () => {
307
307
  // Subscriber deduplication
308
308
  // ---------------------------------------------------------------------------
309
309
 
310
- describe('Store subscriber edge cases', () => {
310
+ describe('Store - subscriber edge cases', () => {
311
311
  it('same function subscribed twice fires twice', () => {
312
312
  const store = createStore('sub-dedup', {
313
313
  state: { x: 0 },
@@ -340,7 +340,7 @@ describe('Store — subscriber edge cases', () => {
340
340
  // Action return value
341
341
  // ---------------------------------------------------------------------------
342
342
 
343
- describe('Store action return value', () => {
343
+ describe('Store - action return value', () => {
344
344
  it('dispatch returns action result', () => {
345
345
  const store = createStore('ret-1', {
346
346
  state: { x: 0 },
@@ -355,7 +355,7 @@ describe('Store — action return value', () => {
355
355
  // Getters with multiple state keys
356
356
  // ---------------------------------------------------------------------------
357
357
 
358
- describe('Store complex getters', () => {
358
+ describe('Store - complex getters', () => {
359
359
  it('getter uses multiple state keys', () => {
360
360
  const store = createStore('getter-multi', {
361
361
  state: { firstName: 'Tony', lastName: 'W' },
@@ -383,7 +383,7 @@ describe('Store — complex getters', () => {
383
383
  // PERF: history trim uses splice (in-place) instead of slice (copy)
384
384
  // ---------------------------------------------------------------------------
385
385
 
386
- describe('Store history trim in-place', () => {
386
+ describe('Store - history trim in-place', () => {
387
387
  it('trims history to maxHistory without exceeding', () => {
388
388
  const store = createStore('hist-trim', {
389
389
  state: { n: 0 },
@@ -412,10 +412,10 @@ describe('Store — history trim in-place', () => {
412
412
 
413
413
 
414
414
  // ===========================================================================
415
- // use() middleware chaining
415
+ // use() - middleware chaining
416
416
  // ===========================================================================
417
417
 
418
- describe('Store use() chaining', () => {
418
+ describe('Store - use() chaining', () => {
419
419
  it('returns the store for chaining', () => {
420
420
  const store = createStore({
421
421
  state: { x: 0 },
@@ -453,7 +453,7 @@ describe('Store — use() chaining', () => {
453
453
  // debug mode
454
454
  // ===========================================================================
455
455
 
456
- describe('Store debug mode', () => {
456
+ describe('Store - debug mode', () => {
457
457
  it('logs when debug is true', () => {
458
458
  const spy = vi.spyOn(console, 'log').mockImplementation(() => {});
459
459
  const store = createStore({
@@ -474,7 +474,7 @@ describe('Store — debug mode', () => {
474
474
  // replaceState
475
475
  // ===========================================================================
476
476
 
477
- describe('Store replaceState', () => {
477
+ describe('Store - replaceState', () => {
478
478
  it('replaces all keys', () => {
479
479
  const store = createStore({
480
480
  state: { a: 1, b: 2 }
@@ -492,7 +492,7 @@ describe('Store — replaceState', () => {
492
492
  // wildcard subscription
493
493
  // ===========================================================================
494
494
 
495
- describe('Store wildcard subscription', () => {
495
+ describe('Store - wildcard subscription', () => {
496
496
  it('fires on any state change', () => {
497
497
  const store = createStore({
498
498
  state: { a: 1, b: 2 },
@@ -528,7 +528,7 @@ describe('Store — wildcard subscription', () => {
528
528
  // state as factory function
529
529
  // ===========================================================================
530
530
 
531
- describe('Store state factory', () => {
531
+ describe('Store - state factory', () => {
532
532
  it('calls state function for initial state', () => {
533
533
  const store = createStore({
534
534
  state: () => ({ count: 0 })
@@ -539,10 +539,10 @@ describe('Store — state factory', () => {
539
539
 
540
540
 
541
541
  // ===========================================================================
542
- // createStore named stores
542
+ // createStore - named stores
543
543
  // ===========================================================================
544
544
 
545
- describe('createStore named stores', () => {
545
+ describe('createStore - named stores', () => {
546
546
  it('creates default store when no name given', () => {
547
547
  const store = createStore({ state: { x: 1 } });
548
548
  expect(store.state.x).toBe(1);
@@ -559,7 +559,7 @@ describe('createStore — named stores', () => {
559
559
  // reset
560
560
  // ===========================================================================
561
561
 
562
- describe('Store reset', () => {
562
+ describe('Store - reset', () => {
563
563
  it('resets state and clears history', () => {
564
564
  const store = createStore({
565
565
  state: { x: 0 },
@@ -580,10 +580,251 @@ describe('Store — reset', () => {
580
580
  // empty config
581
581
  // ===========================================================================
582
582
 
583
- describe('Store empty config', () => {
583
+ describe('Store - empty config', () => {
584
584
  it('creates store with no config', () => {
585
585
  const store = createStore({});
586
586
  expect(store.snapshot()).toEqual({});
587
587
  expect(store.history).toEqual([]);
588
588
  });
589
589
  });
590
+
591
+
592
+ // ===========================================================================
593
+ // Store - batch
594
+ // ===========================================================================
595
+
596
+ describe('Store - batch', () => {
597
+ it('fires subscribers once per key, not per mutation', () => {
598
+ const store = createStore({
599
+ state: { x: 0, y: 0 },
600
+ actions: {
601
+ setX(state, v) { state.x = v; },
602
+ setY(state, v) { state.y = v; },
603
+ }
604
+ });
605
+ const fn = vi.fn();
606
+ store.subscribe('x', fn);
607
+
608
+ store.batch(state => {
609
+ state.x = 1;
610
+ state.x = 2;
611
+ state.x = 3;
612
+ });
613
+
614
+ // Should fire once with the final value
615
+ expect(fn).toHaveBeenCalledTimes(1);
616
+ expect(store.state.x).toBe(3);
617
+ });
618
+
619
+ it('batches changes across multiple keys', () => {
620
+ const store = createStore({
621
+ state: { a: 0, b: 0 }
622
+ });
623
+ const fnA = vi.fn();
624
+ const fnB = vi.fn();
625
+ store.subscribe('a', fnA);
626
+ store.subscribe('b', fnB);
627
+
628
+ store.batch(state => {
629
+ state.a = 10;
630
+ state.b = 20;
631
+ });
632
+
633
+ expect(fnA).toHaveBeenCalledTimes(1);
634
+ expect(fnB).toHaveBeenCalledTimes(1);
635
+ expect(store.state.a).toBe(10);
636
+ expect(store.state.b).toBe(20);
637
+ });
638
+
639
+ it('does not fire subscribers during the batch', () => {
640
+ const store = createStore({ state: { x: 0 } });
641
+ const calls = [];
642
+ store.subscribe('x', (val) => calls.push(val));
643
+
644
+ store.batch(state => {
645
+ state.x = 1;
646
+ // Subscriber should not have been called yet
647
+ expect(calls.length).toBe(0);
648
+ state.x = 2;
649
+ expect(calls.length).toBe(0);
650
+ });
651
+
652
+ // Now it fires
653
+ expect(calls.length).toBe(1);
654
+ });
655
+ });
656
+
657
+
658
+ // ===========================================================================
659
+ // Store - checkpoint / undo / redo
660
+ // ===========================================================================
661
+
662
+ describe('Store - checkpoint / undo / redo', () => {
663
+ it('undo restores to checkpointed state', () => {
664
+ const store = createStore({
665
+ state: { count: 0 },
666
+ actions: { inc(state) { state.count++; } }
667
+ });
668
+
669
+ store.checkpoint();
670
+ store.dispatch('inc');
671
+ store.dispatch('inc');
672
+ expect(store.state.count).toBe(2);
673
+
674
+ const didUndo = store.undo();
675
+ expect(didUndo).toBe(true);
676
+ expect(store.state.count).toBe(0);
677
+ });
678
+
679
+ it('redo restores the undone state', () => {
680
+ const store = createStore({
681
+ state: { count: 0 },
682
+ actions: { inc(state) { state.count++; } }
683
+ });
684
+
685
+ store.checkpoint();
686
+ store.dispatch('inc');
687
+ store.dispatch('inc');
688
+ store.undo();
689
+ expect(store.state.count).toBe(0);
690
+
691
+ const didRedo = store.redo();
692
+ expect(didRedo).toBe(true);
693
+ expect(store.state.count).toBe(2);
694
+ });
695
+
696
+ it('undo returns false when no checkpoints', () => {
697
+ const store = createStore({ state: { x: 1 } });
698
+ expect(store.undo()).toBe(false);
699
+ expect(store.state.x).toBe(1);
700
+ });
701
+
702
+ it('redo returns false when nothing to redo', () => {
703
+ const store = createStore({ state: { x: 1 } });
704
+ expect(store.redo()).toBe(false);
705
+ });
706
+
707
+ it('canUndo and canRedo reflect stack state', () => {
708
+ const store = createStore({
709
+ state: { v: 'a' },
710
+ actions: { set(state, v) { state.v = v; } }
711
+ });
712
+
713
+ expect(store.canUndo).toBe(false);
714
+ expect(store.canRedo).toBe(false);
715
+
716
+ store.checkpoint();
717
+ expect(store.canUndo).toBe(true);
718
+
719
+ store.dispatch('set', 'b');
720
+ store.undo();
721
+ expect(store.canRedo).toBe(true);
722
+
723
+ store.redo();
724
+ expect(store.canRedo).toBe(false);
725
+ });
726
+
727
+ it('new checkpoint clears redo stack', () => {
728
+ const store = createStore({
729
+ state: { x: 0 },
730
+ actions: { set(state, v) { state.x = v; } }
731
+ });
732
+
733
+ store.checkpoint();
734
+ store.dispatch('set', 1);
735
+ store.undo();
736
+ expect(store.canRedo).toBe(true);
737
+
738
+ // New checkpoint clears redo
739
+ store.checkpoint();
740
+ expect(store.canRedo).toBe(false);
741
+ });
742
+
743
+ it('respects maxUndo limit', () => {
744
+ const store = createStore({
745
+ state: { x: 0 },
746
+ maxUndo: 3,
747
+ actions: { set(state, v) { state.x = v; } }
748
+ });
749
+
750
+ store.checkpoint(); // save x=0
751
+ store.dispatch('set', 1);
752
+ store.checkpoint(); // save x=1
753
+ store.dispatch('set', 2);
754
+ store.checkpoint(); // save x=2
755
+ store.dispatch('set', 3);
756
+ store.checkpoint(); // save x=3 -> should trim oldest (x=0)
757
+ store.dispatch('set', 4);
758
+
759
+ // Should have at most 3 entries
760
+ store.undo(); // -> x=3
761
+ store.undo(); // -> x=2
762
+ store.undo(); // -> x=1
763
+ expect(store.undo()).toBe(false); // oldest was trimmed
764
+ });
765
+
766
+ it('multiple undo/redo cycles', () => {
767
+ const store = createStore({
768
+ state: { n: 0 },
769
+ actions: { set(state, v) { state.n = v; } }
770
+ });
771
+
772
+ store.checkpoint();
773
+ store.dispatch('set', 1);
774
+ store.checkpoint();
775
+ store.dispatch('set', 2);
776
+ store.checkpoint();
777
+ store.dispatch('set', 3);
778
+
779
+ store.undo(); // -> 2
780
+ expect(store.state.n).toBe(2);
781
+ store.undo(); // -> 1
782
+ expect(store.state.n).toBe(1);
783
+ store.redo(); // -> 2
784
+ expect(store.state.n).toBe(2);
785
+ store.redo(); // -> 3
786
+ expect(store.state.n).toBe(3);
787
+ });
788
+ });
789
+
790
+
791
+ // ===========================================================================
792
+ // Store - reset with no args
793
+ // ===========================================================================
794
+
795
+ describe('Store - reset defaults to initial state', () => {
796
+ it('resets to the original initial state when called with no arguments', () => {
797
+ const store = createStore({
798
+ state: { count: 0, name: 'test' },
799
+ actions: {
800
+ inc(state) { state.count++; },
801
+ rename(state, n) { state.name = n; }
802
+ }
803
+ });
804
+
805
+ store.dispatch('inc');
806
+ store.dispatch('inc');
807
+ store.dispatch('rename', 'changed');
808
+ expect(store.state.count).toBe(2);
809
+ expect(store.state.name).toBe('changed');
810
+
811
+ store.reset();
812
+ expect(store.state.count).toBe(0);
813
+ expect(store.state.name).toBe('test');
814
+ });
815
+
816
+ it('clears undo/redo stacks on reset', () => {
817
+ const store = createStore({
818
+ state: { x: 0 },
819
+ actions: { set(state, v) { state.x = v; } }
820
+ });
821
+
822
+ store.checkpoint();
823
+ store.dispatch('set', 5);
824
+ expect(store.canUndo).toBe(true);
825
+
826
+ store.reset();
827
+ expect(store.canUndo).toBe(false);
828
+ expect(store.canRedo).toBe(false);
829
+ });
830
+ });