ivue 2.3.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/ivue.mjs +62 -1
- package/dist/Reactive.d.ts +27 -6
- package/dist/extras.cjs +1 -1
- package/dist/extras.es.js +23 -23
- package/dist/index.cjs +1 -1
- package/dist/index.es.js +77 -71
- package/lib/Reactive.ts +54 -26
- package/lib/Static.ts +14 -0
- package/lib/__tests__/Reactive.vitest.spec.ts +83 -13
- package/lib/__tests__/Static.vitest.spec.ts +60 -1
- package/lib/__tests__/ivue.vitest.spec.ts +17 -5
- package/lib/ivue.ts +34 -1
- package/package.json +8 -3
- package/skills/ivue/SKILL.md +209 -9
- package/skills/ivue/constitution.test.ts +143 -0
- package/skills/ivue/ivue-generator-standard.ts +410 -0
- package/skills/ivue/ivue-house-gate.ts +130 -0
- package/skills/ivue/ivue-standards-check.ts +2464 -0
- package/skills/ivue/ivue-standards-skip.json +17 -0
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
propsWithDefaults,
|
|
18
18
|
Reactive,
|
|
19
19
|
type ReactiveInstance,
|
|
20
|
+
definePropTypes,
|
|
20
21
|
} from '../Reactive';
|
|
21
22
|
|
|
22
23
|
/**
|
|
@@ -418,18 +419,18 @@ describe('Reactive()', () => {
|
|
|
418
419
|
}
|
|
419
420
|
}
|
|
420
421
|
class Mid extends Base {
|
|
421
|
-
get summary() {
|
|
422
|
+
override get summary() {
|
|
422
423
|
return computed(() => `(Mid>${super.summary.value})`);
|
|
423
424
|
}
|
|
424
|
-
get chain() {
|
|
425
|
+
override get chain() {
|
|
425
426
|
return super.chain + '->Mid';
|
|
426
427
|
}
|
|
427
428
|
}
|
|
428
429
|
class Leaf extends Mid {
|
|
429
|
-
get summary() {
|
|
430
|
+
override get summary() {
|
|
430
431
|
return computed(() => `{Leaf>${super.summary.value}}`);
|
|
431
432
|
}
|
|
432
|
-
get chain() {
|
|
433
|
+
override get chain() {
|
|
433
434
|
return super.chain + '->Leaf';
|
|
434
435
|
}
|
|
435
436
|
}
|
|
@@ -446,7 +447,7 @@ describe('Reactive()', () => {
|
|
|
446
447
|
}
|
|
447
448
|
}
|
|
448
449
|
class Child extends Base {
|
|
449
|
-
get val() {
|
|
450
|
+
override get val() {
|
|
450
451
|
return computed(() => 10 + super.val.value);
|
|
451
452
|
}
|
|
452
453
|
}
|
|
@@ -491,13 +492,13 @@ describe('Reactive()', () => {
|
|
|
491
492
|
}
|
|
492
493
|
}
|
|
493
494
|
class L2 extends L1 {
|
|
494
|
-
get tag() {
|
|
495
|
+
override get tag() {
|
|
495
496
|
return computed(() => `L2(${super.tag.value})`);
|
|
496
497
|
}
|
|
497
|
-
get name() {
|
|
498
|
+
override get name() {
|
|
498
499
|
return super.name + '>L2';
|
|
499
500
|
}
|
|
500
|
-
greet() {
|
|
501
|
+
override greet() {
|
|
501
502
|
return super.greet() + '/L2';
|
|
502
503
|
}
|
|
503
504
|
}
|
|
@@ -505,18 +506,18 @@ describe('Reactive()', () => {
|
|
|
505
506
|
get extra() {
|
|
506
507
|
return ref(5);
|
|
507
508
|
}
|
|
508
|
-
get tag() {
|
|
509
|
+
override get tag() {
|
|
509
510
|
return computed(() => `L3[${super.tag.value}]`);
|
|
510
511
|
}
|
|
511
|
-
get name() {
|
|
512
|
+
override get name() {
|
|
512
513
|
return super.name + '>L3';
|
|
513
514
|
}
|
|
514
515
|
}
|
|
515
516
|
class L4 extends L3 {
|
|
516
|
-
get tag() {
|
|
517
|
+
override get tag() {
|
|
517
518
|
return computed(() => `L4{${super.tag.value}}`);
|
|
518
519
|
}
|
|
519
|
-
get name() {
|
|
520
|
+
override get name() {
|
|
520
521
|
return super.name + '>L4';
|
|
521
522
|
}
|
|
522
523
|
// computed in the child aggregating refs declared 3 and 1 levels up
|
|
@@ -525,7 +526,7 @@ describe('Reactive()', () => {
|
|
|
525
526
|
() => (this as any).base.value + (this as any).extra.value,
|
|
526
527
|
);
|
|
527
528
|
}
|
|
528
|
-
greet() {
|
|
529
|
+
override greet() {
|
|
529
530
|
return super.greet() + '/L4';
|
|
530
531
|
}
|
|
531
532
|
}
|
|
@@ -735,6 +736,64 @@ describe('Reactive()', () => {
|
|
|
735
736
|
expect(() => instance.$stopEffects()).not.toThrow();
|
|
736
737
|
});
|
|
737
738
|
|
|
739
|
+
it('{ reset: false } stops watchers but every cached cell survives', async () => {
|
|
740
|
+
class Session {
|
|
741
|
+
get counter() {
|
|
742
|
+
return ref(0);
|
|
743
|
+
}
|
|
744
|
+
get doubled() {
|
|
745
|
+
return computed(() => (this as any).counter.value * 2);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
const instance: any = new (Reactive(Session))();
|
|
749
|
+
let observed = 0;
|
|
750
|
+
instance.$watchEffect(() => {
|
|
751
|
+
observed = instance.doubled.value; // first-touches the computed IN the scope
|
|
752
|
+
});
|
|
753
|
+
const counterCell = instance.counter;
|
|
754
|
+
const doubledCell = instance.doubled;
|
|
755
|
+
instance.counter.value = 21;
|
|
756
|
+
await nextTick();
|
|
757
|
+
expect(observed).toBe(42);
|
|
758
|
+
|
|
759
|
+
instance.$stopEffects({ reset: false });
|
|
760
|
+
|
|
761
|
+
// watchers are dead…
|
|
762
|
+
instance.counter.value = 100;
|
|
763
|
+
await nextTick();
|
|
764
|
+
expect(observed).toBe(42);
|
|
765
|
+
// …but the cells survive with their CURRENT values (no re-init)
|
|
766
|
+
expect(instance.counter).toBe(counterCell);
|
|
767
|
+
expect(instance.doubled).toBe(doubledCell);
|
|
768
|
+
expect(instance.counter.value).toBe(100);
|
|
769
|
+
// the surviving computed still evaluates (pull-based past scope death)
|
|
770
|
+
expect(instance.doubled.value).toBe(200);
|
|
771
|
+
|
|
772
|
+
// and a SECOND life works: a fresh scope tracks the old cells
|
|
773
|
+
let observedAgain = 0;
|
|
774
|
+
instance.$watch(
|
|
775
|
+
() => instance.doubled.value,
|
|
776
|
+
(doubledValue: number) => {
|
|
777
|
+
observedAgain = doubledValue;
|
|
778
|
+
},
|
|
779
|
+
);
|
|
780
|
+
instance.counter.value = 7;
|
|
781
|
+
await nextTick();
|
|
782
|
+
expect(observedAgain).toBe(14);
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
it('default call still resets — cells re-initialize after teardown', () => {
|
|
786
|
+
class Session {
|
|
787
|
+
get counter() {
|
|
788
|
+
return ref(0);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
const instance: any = new (Reactive(Session))();
|
|
792
|
+
instance.counter.value = 41;
|
|
793
|
+
instance.$stopEffects();
|
|
794
|
+
expect(instance.counter.value).toBe(0);
|
|
795
|
+
});
|
|
796
|
+
|
|
738
797
|
it('$stopEffects is injected only once (idempotent re-Reactive)', () => {
|
|
739
798
|
class Store {
|
|
740
799
|
m() {
|
|
@@ -1054,3 +1113,14 @@ describe('$watchEffect (scoped watchEffect)', () => {
|
|
|
1054
1113
|
expect(watchRuns).toBe(1); // sibling watcher in the same scope still fires
|
|
1055
1114
|
});
|
|
1056
1115
|
});
|
|
1116
|
+
|
|
1117
|
+
describe('definePropTypes()', () => {
|
|
1118
|
+
it('returns the same types map (identity, literal-preserving)', () => {
|
|
1119
|
+
const types = definePropTypes({
|
|
1120
|
+
title: { type: String, required: true },
|
|
1121
|
+
size: { type: Number },
|
|
1122
|
+
});
|
|
1123
|
+
expect(types.title.required).toBe(true);
|
|
1124
|
+
expect(types.size.type).toBe(Number);
|
|
1125
|
+
});
|
|
1126
|
+
});
|
|
@@ -49,7 +49,7 @@ describe('Static', () => {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
class $Child extends $Base {
|
|
52
|
-
static describe() {
|
|
52
|
+
static override describe() {
|
|
53
53
|
return 'child';
|
|
54
54
|
}
|
|
55
55
|
}
|
|
@@ -426,6 +426,65 @@ describe('Static $-cached getters', () => {
|
|
|
426
426
|
expect(settle()).toBe(70); // inherited method binds to the child, detached
|
|
427
427
|
});
|
|
428
428
|
|
|
429
|
+
// invariant: A prototype level is transformed at most once (ivue.invariants.md)
|
|
430
|
+
it('re-wrapping a subclass never resurrects a parent-bound method through the bind cache', () => {
|
|
431
|
+
// The double-wrap pattern the manual prescribes: a Static parent, a raw
|
|
432
|
+
// subclass extending it, and Static() applied again to the subclass.
|
|
433
|
+
// The parent is READ FIRST, so its bind cache (an own symbol property)
|
|
434
|
+
// exists when the second wrap walks the chain — that cache must be
|
|
435
|
+
// invisible to the walk, or the child's methods run with the parent
|
|
436
|
+
// as receiver.
|
|
437
|
+
class $Fleet {
|
|
438
|
+
static get vessels(): string[] {
|
|
439
|
+
return ['tug'];
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
static roster() {
|
|
443
|
+
return this.vessels.join(',');
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const Fleet = Static($Fleet);
|
|
448
|
+
expect(Fleet.roster()).toBe('tug'); // parent reads (and caches its bound method) first
|
|
449
|
+
|
|
450
|
+
class $HarborFleet extends Fleet {
|
|
451
|
+
static override get vessels(): string[] {
|
|
452
|
+
return [...super.vessels, 'ferry'];
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
const HarborFleet = Static($HarborFleet);
|
|
456
|
+
|
|
457
|
+
expect(HarborFleet.roster()).toBe('tug,ferry'); // the child's override, not the parent's snapshot
|
|
458
|
+
const roster = HarborFleet.roster;
|
|
459
|
+
expect(roster()).toBe('tug,ferry'); // detached, still child-bound
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// invariant: A prototype level is transformed at most once (ivue.invariants.md)
|
|
463
|
+
it('re-wrapping skips the unregistered bind caches of symbol-keyed methods too', () => {
|
|
464
|
+
const describeKind = Symbol('describeKind');
|
|
465
|
+
class $Signal {
|
|
466
|
+
static get kind(): string {
|
|
467
|
+
return 'base';
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
static [describeKind]() {
|
|
471
|
+
return `kind:${this.kind}`;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const Signal = Static($Signal);
|
|
476
|
+
expect((Signal as any)[describeKind]()).toBe('kind:base'); // parent reads first — unregistered bind cache lands
|
|
477
|
+
|
|
478
|
+
class $AlertSignal extends Signal {
|
|
479
|
+
static override get kind(): string {
|
|
480
|
+
return 'alert';
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
const AlertSignal = Static($AlertSignal);
|
|
484
|
+
|
|
485
|
+
expect((AlertSignal as any)[describeKind]()).toBe('kind:alert'); // child receiver, not the parent snapshot
|
|
486
|
+
});
|
|
487
|
+
|
|
429
488
|
it('walks the raw inheritance chain — ancestor $-getters cache per receiver', () => {
|
|
430
489
|
class $Base {
|
|
431
490
|
static get scale() {
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
ivue,
|
|
14
14
|
propertiesAccessorsMaps,
|
|
15
15
|
propsWithDefaults,
|
|
16
|
+
definePropTypes,
|
|
16
17
|
} from '../ivue';
|
|
17
18
|
const {
|
|
18
19
|
copyOwnProps,
|
|
@@ -76,21 +77,21 @@ class ProductItem extends Item {
|
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
class StoreItem extends ProductItem {
|
|
79
|
-
_productType = iref('store');
|
|
80
|
-
get productType() {
|
|
80
|
+
override _productType = iref('store');
|
|
81
|
+
override get productType() {
|
|
81
82
|
const prefix = super.productFeel ?? '';
|
|
82
83
|
return (prefix ? prefix + ':' : '') + this._productType;
|
|
83
84
|
}
|
|
84
|
-
set productFeel(value: string) {
|
|
85
|
+
override set productFeel(value: string) {
|
|
85
86
|
this._productFeel = value;
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
class RetailStoreItem extends StoreItem {
|
|
90
|
-
_productType = iref('retail');
|
|
91
|
+
override _productType = iref('retail');
|
|
91
92
|
/** Do not overwrite productType getter here, on purpose. */
|
|
92
93
|
productHistory = iref([]);
|
|
93
|
-
get testProperty() {
|
|
94
|
+
override get testProperty() {
|
|
94
95
|
return this._testProperty;
|
|
95
96
|
}
|
|
96
97
|
calculateSize() {
|
|
@@ -1885,3 +1886,14 @@ describe('ivue coverage edge cases', () => {
|
|
|
1885
1886
|
expect(cloned.value).toBe(7);
|
|
1886
1887
|
});
|
|
1887
1888
|
});
|
|
1889
|
+
|
|
1890
|
+
describe('definePropTypes()', () => {
|
|
1891
|
+
it('returns the same types map (identity, literal-preserving)', () => {
|
|
1892
|
+
const types = definePropTypes({
|
|
1893
|
+
title: { type: String, required: true },
|
|
1894
|
+
size: { type: Number },
|
|
1895
|
+
});
|
|
1896
|
+
expect(types.title.required).toBe(true);
|
|
1897
|
+
expect(types.size.type).toBe(Number);
|
|
1898
|
+
});
|
|
1899
|
+
});
|
package/lib/ivue.ts
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* DEPRECATED — ivue v1 (the reactive-proxy engine)
|
|
4
|
+
* ============================================================
|
|
5
|
+
*
|
|
6
|
+
* This file is the ORIGINAL v1 implementation: `ivue(Class, ...args)`
|
|
7
|
+
* wraps instances in `reactive()` and converts accessors to computeds.
|
|
8
|
+
* It is kept in-tree for reference and migration only.
|
|
9
|
+
*
|
|
10
|
+
* v2 is `Reactive()` in ./Reactive.ts (the package's main entry) — a
|
|
11
|
+
* one-time prototype transform: instances stay plain objects, plain
|
|
12
|
+
* getters cost zero bytes, creation measures 55-253x faster. New code
|
|
13
|
+
* must never import from this file.
|
|
14
|
+
*
|
|
15
|
+
* Migration guide: https://ivue.dev/guide/standard
|
|
16
|
+
*/
|
|
17
|
+
|
|
1
18
|
import type { ComputedRef, ExtractPropTypes, Ref, ToRef } from 'vue';
|
|
2
19
|
import { computed, markRaw, reactive, ref, shallowRef, toRef } from 'vue';
|
|
3
20
|
import type { Ref as DemiRef } from 'vue-demi';
|
|
@@ -113,9 +130,15 @@ export type ExtractEmitTypes<T extends Record<string, any>> =
|
|
|
113
130
|
|
|
114
131
|
/**
|
|
115
132
|
* Extract properties as all assigned properties because they have defaults.
|
|
133
|
+
* Props declared `required: true` are filtered out (they can never carry a
|
|
134
|
+
* default); declare the types map with a literal-preserving generic call so
|
|
135
|
+
* the `required: true` literal survives `typeof`.
|
|
116
136
|
*/
|
|
117
137
|
export type ExtractPropDefaultTypes<O> = {
|
|
118
|
-
[K in keyof O]: ValueOf<
|
|
138
|
+
[K in keyof O as O[K] extends { required: true } ? never : K]: ValueOf<
|
|
139
|
+
ExtractPropTypes<O>,
|
|
140
|
+
K
|
|
141
|
+
>;
|
|
119
142
|
};
|
|
120
143
|
|
|
121
144
|
/**
|
|
@@ -399,6 +422,8 @@ const getClassPropertiesAccessorsMap = (obj: object): PropsMapValue => {
|
|
|
399
422
|
* get getter () { return 'hello world'; }
|
|
400
423
|
* }
|
|
401
424
|
*
|
|
425
|
+
* @deprecated v1 engine — use `Reactive()` from the main `ivue` entry
|
|
426
|
+
* instead; see https://ivue.dev/guide/standard for the migration.
|
|
402
427
|
* @param className Any Class
|
|
403
428
|
* @param args Class constructor arguments that you would pass to a `new AnyClass(args...)`
|
|
404
429
|
* @returns {IVue<T>}
|
|
@@ -760,6 +785,14 @@ export const isClass = (val: any): boolean => {
|
|
|
760
785
|
* @param typedProps Props declared in defineComponent() style with type and possibly required declared, but without default
|
|
761
786
|
* @returns Props declared in defineComponent() style with all properties having default property declared.
|
|
762
787
|
*/
|
|
788
|
+
/**
|
|
789
|
+
* Identity helper for a prop-TYPES map: generic inference preserves the
|
|
790
|
+
* `required: true` literal that a bare object const widens to `boolean`,
|
|
791
|
+
* which ExtractPropDefaultTypes' required-key filter depends on.
|
|
792
|
+
*/
|
|
793
|
+
export const definePropTypes = <T extends VuePropsObject>(types: T): T =>
|
|
794
|
+
types;
|
|
795
|
+
|
|
763
796
|
export const propsWithDefaults = <T extends VuePropsObject>(
|
|
764
797
|
defaults: Record<string, any>,
|
|
765
798
|
typedProps: T,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ivue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Infinite Vue – Class Based Architecture for Vue 3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -14,12 +14,16 @@
|
|
|
14
14
|
"import": "./dist/extras.es.js",
|
|
15
15
|
"require": "./dist/extras.cjs"
|
|
16
16
|
},
|
|
17
|
-
"./package.json": "./package.json"
|
|
17
|
+
"./package.json": "./package.json",
|
|
18
|
+
"./skills/*": "./skills/*"
|
|
18
19
|
},
|
|
19
20
|
"main": "./dist/index.cjs",
|
|
20
21
|
"module": "./dist/index.es.js",
|
|
21
22
|
"types": "./dist/index.d.ts",
|
|
22
23
|
"scripts": {
|
|
24
|
+
"gate": "vite-node skills/ivue/ivue-standards-check.ts --",
|
|
25
|
+
"gate:house": "vite-node skills/ivue/ivue-house-gate.ts --",
|
|
26
|
+
"gate:newsletter": "vite-node skills/ivue/ivue-generator-standard.ts -- --source-root newsletter --skip-list skills/ivue/ivue-standards-skip.json --test-glob 'newsletter/src/**/*.test.ts'",
|
|
23
27
|
"dev": "vite demo --host",
|
|
24
28
|
"dev:demo": "vite demo --host",
|
|
25
29
|
"dev:flyweight": "vite examples/playground --port 5181 --host",
|
|
@@ -39,7 +43,7 @@
|
|
|
39
43
|
"cypress:headed": "cypress run --component --browser chrome --headed --no-exit",
|
|
40
44
|
"build": "tsc --version;tsc --p ./tsconfig.json && vite build",
|
|
41
45
|
"build:demo": "tsc --p ./tsconfig.json && vite build demo",
|
|
42
|
-
"build:docs": "npm run sync:examples && npm run sync:releases && npm run sync:blog-index && npm --prefix docs_v2 run build && npm run check:links && node docs_v2/scripts/check-related-posts.mjs",
|
|
46
|
+
"build:docs": "npm run sync:examples && npm run sync:releases && npm run sync:blog-index && node docs_v2/scripts/rss-generator.mjs && npm --prefix docs_v2 run build && npm run check:links && node docs_v2/scripts/check-related-posts.mjs",
|
|
43
47
|
"sync:releases": "node docs_v2/scripts/releases-page-generator.mjs",
|
|
44
48
|
"sync:blog-index": "node docs_v2/scripts/blog-index-generator.mjs",
|
|
45
49
|
"check:links": "node docs_v2/scripts/check-links.mjs",
|
|
@@ -47,6 +51,7 @@
|
|
|
47
51
|
"render:form-header": "node docs_v2/scripts/brand-image-generator.mjs form-header",
|
|
48
52
|
"render:banner": "node docs_v2/scripts/brand-image-generator.mjs blog",
|
|
49
53
|
"render:diagram": "node docs_v2/scripts/brand-image-generator.mjs diagram",
|
|
54
|
+
"render:page-og": "node docs_v2/scripts/page-og-generator.mjs",
|
|
50
55
|
"render:embeds": "node docs_v2/scripts/blog-embed-shots.mjs",
|
|
51
56
|
"render:code-shots": "node docs_v2/scripts/blog-code-shots.mjs",
|
|
52
57
|
"sync:blog-dates": "node docs_v2/scripts/blog-dates-generator.mjs",
|