ivue 2.0.0 → 2.2.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/dist/Static.d.ts +31 -0
- package/dist/extras.cjs +1 -0
- package/dist/extras.d.ts +9 -0
- package/dist/extras.es.js +27 -0
- package/dist/index.cjs +1 -0
- package/lib/Static.ts +86 -0
- package/lib/__tests__/Static.vitest.spec.ts +280 -0
- package/lib/__tests__/coverage-completion.vitest.spec.ts +7 -0
- package/lib/extras.ts +9 -0
- package/package.json +9 -3
- package/dist/index.umd.js +0 -1
package/dist/Static.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Static()` — the static-side sibling of `Reactive()`.
|
|
3
|
+
*
|
|
4
|
+
* Lazily binds every visible static method of a capability class without
|
|
5
|
+
* touching the raw class, so methods stay referentially stable and safe to
|
|
6
|
+
* retain as callbacks (routers, watchers, command handlers) while the
|
|
7
|
+
* selected `Class` slot remains replaceable by a kernel/plugin.
|
|
8
|
+
*
|
|
9
|
+
* Get-only static accessors whose name starts with `$` become
|
|
10
|
+
* compute-once-per-receiver caches: the getter body runs on first read
|
|
11
|
+
* through a given class, its (shallowly frozen) result is stored under a
|
|
12
|
+
* symbol OWN property of that receiver, and later reads return the stored
|
|
13
|
+
* value. The `Object.hasOwn` guard never walks the prototype chain, so a
|
|
14
|
+
* parent's cache can never shadow a subclass — each class in a hierarchy
|
|
15
|
+
* derives through its own overrides on its own first read, in ANY read
|
|
16
|
+
* order. The `$` prefix IS the API: a static getter that must stay live
|
|
17
|
+
* (a knob for subclasses to pinch, a fresh-per-read value) must not use
|
|
18
|
+
* it. Cached values are frozen shallowly — cache-and-freeze or
|
|
19
|
+
* return-fresh, never cache-mutable.
|
|
20
|
+
*
|
|
21
|
+
* This is the namespace pattern's backend adapter: canonical namespace +
|
|
22
|
+
* mutable `Class` slot + late reads, for STATELESS capability classes (a
|
|
23
|
+
* function bag). Never wrap stateful/reactive instance classes with it — use
|
|
24
|
+
* `Reactive()` for those; `Static()` operates on the single class object and
|
|
25
|
+
* has no instance dimension.
|
|
26
|
+
*
|
|
27
|
+
* Ships from `ivue/extras` (not the reactive core) so the primary `ivue`
|
|
28
|
+
* entry stays minimal.
|
|
29
|
+
*/
|
|
30
|
+
export declare type ClassConstructor = new (...arguments_: any[]) => any;
|
|
31
|
+
export declare function Static<Class extends ClassConstructor>(targetClass: Class): Class;
|
package/dist/extras.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"}),exports.Static=function(i){const o=class extends i{},s=new Set;for(let n=i;n!==Function.prototype;n=Object.getPrototypeOf(n))for(const e of Reflect.ownKeys(n)){if(s.has(e))continue;s.add(e);const t=Object.getOwnPropertyDescriptor(n,e);if(typeof t.value=="function"){const c=t.value;Object.defineProperty(o,e,{configurable:!0,enumerable:t.enumerable,get(){const r=c.bind(this);return Object.defineProperty(this,e,{...t,value:r}),r}})}else if(t.get&&!t.set&&typeof e=="string"&&e.startsWith("$")){const c=t.get,r=Symbol.for(`ivue.staticCache.${e}`);Object.defineProperty(o,e,{configurable:!0,enumerable:t.enumerable,get(){return Object.hasOwn(this,r)||Object.defineProperty(this,r,{configurable:!0,value:Object.freeze(c.call(this))}),this[r]}})}}return o};
|
package/dist/extras.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ivue/extras` — the ivue toolkit beyond the reactive core.
|
|
3
|
+
*
|
|
4
|
+
* Kept out of the primary `ivue` entry so that entry stays minimal (the
|
|
5
|
+
* 1.1kb reactive engine). Import these explicitly:
|
|
6
|
+
*
|
|
7
|
+
* import { Static } from 'ivue/extras';
|
|
8
|
+
*/
|
|
9
|
+
export { Static, type ClassConstructor } from './Static';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function a(i) {
|
|
2
|
+
const o = class extends i {
|
|
3
|
+
}, s = /* @__PURE__ */ new Set();
|
|
4
|
+
for (let r = i; r !== Function.prototype; r = Object.getPrototypeOf(r))
|
|
5
|
+
for (const e of Reflect.ownKeys(r)) {
|
|
6
|
+
if (s.has(e))
|
|
7
|
+
continue;
|
|
8
|
+
s.add(e);
|
|
9
|
+
const t = Object.getOwnPropertyDescriptor(r, e);
|
|
10
|
+
if (typeof t.value == "function") {
|
|
11
|
+
const c = t.value;
|
|
12
|
+
Object.defineProperty(o, e, { configurable: !0, enumerable: t.enumerable, get() {
|
|
13
|
+
const n = c.bind(this);
|
|
14
|
+
return Object.defineProperty(this, e, { ...t, value: n }), n;
|
|
15
|
+
} });
|
|
16
|
+
} else if (t.get && !t.set && typeof e == "string" && e.startsWith("$")) {
|
|
17
|
+
const c = t.get, n = Symbol.for(`ivue.staticCache.${e}`);
|
|
18
|
+
Object.defineProperty(o, e, { configurable: !0, enumerable: t.enumerable, get() {
|
|
19
|
+
return Object.hasOwn(this, n) || Object.defineProperty(this, n, { configurable: !0, value: Object.freeze(c.call(this)) }), this[n];
|
|
20
|
+
} });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return o;
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
a as Static
|
|
27
|
+
};
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("vue"),g=Object.hasOwn,y=Object.getPrototypeOf,w=Object.getOwnPropertyDescriptor,m=Object.getOwnPropertyNames,a=Object.defineProperty,d=Object.prototype,b=Symbol.for("ivue.raw"),p=Symbol.for("ivue.scope"),h=Symbol.for("ivue.processed");function s(e){const r=l.toRaw(e);if(r!==e)return r[b]??(r[b]=r);const n=e[b];return n?n===e?n:l.toRaw(n):e[b]=e}function O(e,r,n,o){a(e,r,{configurable:!0,enumerable:!1,get(){const t=s(this);return t[n]??(t[n]=o.bind(t))},set(t){s(this)[n]=t}})}function S(e,r,n,o,t){const f=r[0]==="$";a(e,r,{configurable:!0,enumerable:!1,get:function(){const c=s(this);if(n in c)return c[n];const i=o.call(c);return f?(c[n]=i,i):(l.isRef(i)?c[n]=i:a(e,r,{configurable:!0,enumerable:!1,get(){return o.call(s(this))},set:t?function(u){return t.call(s(this),u)}:void 0}),i)},set:t?function(c){return t.call(s(this),c)}:void 0})}const v=e=>{var r;return typeof e=="function"&&!!e.prototype&&!((r=w(e,"prototype"))!=null&&r.writable)};exports.Reactive=function(e){const r=[];let n=e.prototype;for(;n&&n!==d;)r.push(n),n=y(n);r.reverse();for(const o of r){if(g(o,h))continue;const t=m(o),f=[];for(const c of t){if(c==="constructor")continue;const i=w(o,c);if(typeof i.value=="function"){const u=Symbol(c);f.push(u),O(o,c,u,i.value)}else if(i.get){const u=Symbol(c);f.push(u),S(o,c,u,i.get,i.set)}}a(o,h,{value:f})}return g(e.prototype,"$stopEffects")||(a(e.prototype,"$watch",{enumerable:!1,configurable:!0,writable:!0,value:function(...o){const t=s(this);return(t[p]??(t[p]=l.effectScope(!0))).run(()=>l.watch(...o))}}),a(e.prototype,"$watchEffect",{enumerable:!1,configurable:!0,writable:!0,value:function(...o){const t=s(this);return(t[p]??(t[p]=l.effectScope(!0))).run(()=>l.watchEffect(...o))}}),a(e.prototype,"$stopEffects",{enumerable:!1,configurable:!0,writable:!0,value:function(){const o=s(this);try{const t=o[p];t&&t.stop()}finally{delete o[p];let t=y(o);for(;t&&t!==d;){const f=t[h];if(f)for(const c of f)delete o[c];t=y(t)}}}})),e},exports.isClass=v,exports.propsWithDefaults=(e,r,n)=>{const o={};for(const t in r){const f=e==null?void 0:e[t],c=r[t];o[t]={...c},c.required||f===void 0||(typeof f=="object"&&f!==null?o[t].default=()=>n?n(f):structuredClone(f):v(f)?o[t].default=()=>f:o[t].default=f)}return o};
|
package/lib/Static.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Static()` — the static-side sibling of `Reactive()`.
|
|
3
|
+
*
|
|
4
|
+
* Lazily binds every visible static method of a capability class without
|
|
5
|
+
* touching the raw class, so methods stay referentially stable and safe to
|
|
6
|
+
* retain as callbacks (routers, watchers, command handlers) while the
|
|
7
|
+
* selected `Class` slot remains replaceable by a kernel/plugin.
|
|
8
|
+
*
|
|
9
|
+
* Get-only static accessors whose name starts with `$` become
|
|
10
|
+
* compute-once-per-receiver caches: the getter body runs on first read
|
|
11
|
+
* through a given class, its (shallowly frozen) result is stored under a
|
|
12
|
+
* symbol OWN property of that receiver, and later reads return the stored
|
|
13
|
+
* value. The `Object.hasOwn` guard never walks the prototype chain, so a
|
|
14
|
+
* parent's cache can never shadow a subclass — each class in a hierarchy
|
|
15
|
+
* derives through its own overrides on its own first read, in ANY read
|
|
16
|
+
* order. The `$` prefix IS the API: a static getter that must stay live
|
|
17
|
+
* (a knob for subclasses to pinch, a fresh-per-read value) must not use
|
|
18
|
+
* it. Cached values are frozen shallowly — cache-and-freeze or
|
|
19
|
+
* return-fresh, never cache-mutable.
|
|
20
|
+
*
|
|
21
|
+
* This is the namespace pattern's backend adapter: canonical namespace +
|
|
22
|
+
* mutable `Class` slot + late reads, for STATELESS capability classes (a
|
|
23
|
+
* function bag). Never wrap stateful/reactive instance classes with it — use
|
|
24
|
+
* `Reactive()` for those; `Static()` operates on the single class object and
|
|
25
|
+
* has no instance dimension.
|
|
26
|
+
*
|
|
27
|
+
* Ships from `ivue/extras` (not the reactive core) so the primary `ivue`
|
|
28
|
+
* entry stays minimal.
|
|
29
|
+
*/
|
|
30
|
+
export type ClassConstructor = new (...arguments_: any[]) => any;
|
|
31
|
+
|
|
32
|
+
export function Static<Class extends ClassConstructor>(targetClass: Class): Class {
|
|
33
|
+
const SelectedClass = class extends targetClass {};
|
|
34
|
+
const visitedKeys = new Set<PropertyKey>();
|
|
35
|
+
|
|
36
|
+
for (
|
|
37
|
+
let currentClass: ClassConstructor = targetClass;
|
|
38
|
+
currentClass !== Function.prototype;
|
|
39
|
+
currentClass = Object.getPrototypeOf(currentClass)
|
|
40
|
+
) {
|
|
41
|
+
for (const key of Reflect.ownKeys(currentClass)) {
|
|
42
|
+
if (visitedKeys.has(key)) continue;
|
|
43
|
+
visitedKeys.add(key);
|
|
44
|
+
|
|
45
|
+
const descriptor = Object.getOwnPropertyDescriptor(currentClass, key)!;
|
|
46
|
+
|
|
47
|
+
if (typeof descriptor.value === 'function') {
|
|
48
|
+
const method = descriptor.value;
|
|
49
|
+
|
|
50
|
+
Object.defineProperty(SelectedClass, key, {
|
|
51
|
+
configurable: true,
|
|
52
|
+
enumerable: descriptor.enumerable,
|
|
53
|
+
get(this: ClassConstructor) {
|
|
54
|
+
const boundMethod = method.bind(this);
|
|
55
|
+
Object.defineProperty(this, key, { ...descriptor, value: boundMethod });
|
|
56
|
+
return boundMethod;
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
} else if (
|
|
60
|
+
descriptor.get &&
|
|
61
|
+
!descriptor.set &&
|
|
62
|
+
typeof key === 'string' &&
|
|
63
|
+
key.startsWith('$')
|
|
64
|
+
) {
|
|
65
|
+
const getter = descriptor.get;
|
|
66
|
+
const cacheKey = Symbol.for(`ivue.staticCache.${key}`);
|
|
67
|
+
|
|
68
|
+
Object.defineProperty(SelectedClass, key, {
|
|
69
|
+
configurable: true,
|
|
70
|
+
enumerable: descriptor.enumerable,
|
|
71
|
+
get(this: any) {
|
|
72
|
+
if (!Object.hasOwn(this, cacheKey)) {
|
|
73
|
+
Object.defineProperty(this, cacheKey, {
|
|
74
|
+
configurable: true,
|
|
75
|
+
value: Object.freeze(getter.call(this)),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return this[cacheKey];
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return SelectedClass as Class;
|
|
86
|
+
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { Static } from '../Static';
|
|
3
|
+
|
|
4
|
+
describe('Static', () => {
|
|
5
|
+
it('returns a subclass and leaves the raw class untouched', () => {
|
|
6
|
+
class $Mailer {
|
|
7
|
+
static send(message: string) {
|
|
8
|
+
return `sent:${message}`;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const Mailer = Static($Mailer);
|
|
13
|
+
|
|
14
|
+
expect(Mailer).not.toBe($Mailer);
|
|
15
|
+
expect(Object.getPrototypeOf(Mailer)).toBe($Mailer);
|
|
16
|
+
// the raw class keeps its plain data-property descriptor
|
|
17
|
+
const rawDescriptor = Object.getOwnPropertyDescriptor($Mailer, 'send')!;
|
|
18
|
+
expect(typeof rawDescriptor.value).toBe('function');
|
|
19
|
+
expect(rawDescriptor.get).toBeUndefined();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('binds methods lazily with stable identity, safe to detach', () => {
|
|
23
|
+
class $Counter {
|
|
24
|
+
static total = 0;
|
|
25
|
+
static bump() {
|
|
26
|
+
this.total++;
|
|
27
|
+
return this.total;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const Counter = Static($Counter);
|
|
32
|
+
const firstRead = Counter.bump;
|
|
33
|
+
|
|
34
|
+
expect(Counter.bump).toBe(firstRead); // cached after first access
|
|
35
|
+
const detached = Counter.bump;
|
|
36
|
+
expect(detached()).toBe(1); // `this` survives detachment
|
|
37
|
+
expect(Counter.total).toBe(1);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('walks the static inheritance chain; child overrides win', () => {
|
|
41
|
+
class $Base {
|
|
42
|
+
static describe() {
|
|
43
|
+
return 'base';
|
|
44
|
+
}
|
|
45
|
+
static inheritedOnly() {
|
|
46
|
+
return 'inherited';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
class $Child extends $Base {
|
|
50
|
+
static describe() {
|
|
51
|
+
return 'child';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const Child = Static($Child);
|
|
56
|
+
const describe = Child.describe;
|
|
57
|
+
const inheritedOnly = Child.inheritedOnly;
|
|
58
|
+
|
|
59
|
+
expect(describe()).toBe('child');
|
|
60
|
+
expect(inheritedOnly()).toBe('inherited');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('skips non-function statics and preserves enumerability', () => {
|
|
64
|
+
class $Config {
|
|
65
|
+
static baseUrl = 'https://example.test';
|
|
66
|
+
static resolve(path: string) {
|
|
67
|
+
return this.baseUrl + path;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const Config = Static($Config);
|
|
72
|
+
|
|
73
|
+
expect(Config.baseUrl).toBe('https://example.test');
|
|
74
|
+
// class static methods are non-enumerable by construction — the lazy
|
|
75
|
+
// accessor and the materialized bound method must both preserve that
|
|
76
|
+
const before = Object.getOwnPropertyDescriptor(Config, 'resolve')!;
|
|
77
|
+
expect(before.enumerable).toBe(false);
|
|
78
|
+
Config.resolve; // materialize the bound method
|
|
79
|
+
const after = Object.getOwnPropertyDescriptor(Config, 'resolve')!;
|
|
80
|
+
expect(after.enumerable).toBe(false);
|
|
81
|
+
expect(Config.resolve('/path')).toBe('https://example.test/path');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('binds `this` to the wrapped class, so statics compose', () => {
|
|
85
|
+
class $Report {
|
|
86
|
+
static header() {
|
|
87
|
+
return 'HEADER';
|
|
88
|
+
}
|
|
89
|
+
static render() {
|
|
90
|
+
return `${this.header()}|body`;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const Report = Static($Report);
|
|
95
|
+
const render = Report.render;
|
|
96
|
+
|
|
97
|
+
expect(render()).toBe('HEADER|body');
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('Static $-cached getters', () => {
|
|
102
|
+
it('computes a $-getter exactly once per receiver', () => {
|
|
103
|
+
let computeRuns = 0;
|
|
104
|
+
|
|
105
|
+
class $Palette {
|
|
106
|
+
static get $tokens() {
|
|
107
|
+
computeRuns++;
|
|
108
|
+
return { accent: 'cyan' };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const Palette = Static($Palette);
|
|
113
|
+
const firstRead = Palette.$tokens;
|
|
114
|
+
|
|
115
|
+
expect(Palette.$tokens).toBe(firstRead); // same object, cached
|
|
116
|
+
expect(Palette.$tokens.accent).toBe('cyan');
|
|
117
|
+
expect(computeRuns).toBe(1);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('is order-correct: parent read first, child override still wins', () => {
|
|
121
|
+
class $Momentum {
|
|
122
|
+
static get friction() {
|
|
123
|
+
return 2; // a live knob — subclasses pinch it
|
|
124
|
+
}
|
|
125
|
+
static get $atRest() {
|
|
126
|
+
return { velocity: 0, threshold: this.friction * 10 };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const Momentum = Static($Momentum);
|
|
131
|
+
class TunedMomentum extends Momentum {
|
|
132
|
+
static override get friction() {
|
|
133
|
+
return 7;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// PARENT reads first — the hand-rolled self-replacement idiom
|
|
138
|
+
// caches on the parent here and silently shadows the child forever
|
|
139
|
+
expect(Momentum.$atRest.threshold).toBe(20);
|
|
140
|
+
expect(TunedMomentum.$atRest.threshold).toBe(70); // child derives itself
|
|
141
|
+
expect(TunedMomentum.$atRest).not.toBe(Momentum.$atRest);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('is order-correct: child read first, parent unaffected', () => {
|
|
145
|
+
let computeRuns = 0;
|
|
146
|
+
|
|
147
|
+
class $Momentum {
|
|
148
|
+
static get friction() {
|
|
149
|
+
return 2;
|
|
150
|
+
}
|
|
151
|
+
static get $atRest() {
|
|
152
|
+
computeRuns++;
|
|
153
|
+
return { threshold: this.friction * 10 };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const Momentum = Static($Momentum);
|
|
158
|
+
class TunedMomentum extends Momentum {
|
|
159
|
+
static override get friction() {
|
|
160
|
+
return 7;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
expect(TunedMomentum.$atRest.threshold).toBe(70); // CHILD reads first
|
|
165
|
+
expect(Momentum.$atRest.threshold).toBe(20);
|
|
166
|
+
expect(computeRuns).toBe(2); // once per receiver, never shared
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('freezes cached values — mutation throws instead of corrupting', () => {
|
|
170
|
+
class $Config {
|
|
171
|
+
static get $defaults() {
|
|
172
|
+
return { width: 80 };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const Config = Static($Config);
|
|
177
|
+
const defaults = Config.$defaults;
|
|
178
|
+
|
|
179
|
+
expect(Object.isFrozen(defaults)).toBe(true);
|
|
180
|
+
expect(() => {
|
|
181
|
+
(defaults as any).width = 120;
|
|
182
|
+
}).toThrow(TypeError);
|
|
183
|
+
expect(Config.$defaults.width).toBe(80);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('caches primitive results too', () => {
|
|
187
|
+
let computeRuns = 0;
|
|
188
|
+
|
|
189
|
+
class $Layout {
|
|
190
|
+
static get $gutterWidth() {
|
|
191
|
+
computeRuns++;
|
|
192
|
+
return 4;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const Layout = Static($Layout);
|
|
197
|
+
|
|
198
|
+
expect(Layout.$gutterWidth).toBe(4);
|
|
199
|
+
expect(Layout.$gutterWidth).toBe(4);
|
|
200
|
+
expect(computeRuns).toBe(1);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('leaves non-$ getters live — knobs re-read every time', () => {
|
|
204
|
+
let reads = 0;
|
|
205
|
+
|
|
206
|
+
class $Driver {
|
|
207
|
+
static get retainedLimit() {
|
|
208
|
+
reads++;
|
|
209
|
+
return 96;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const Driver = Static($Driver);
|
|
214
|
+
Driver.retainedLimit;
|
|
215
|
+
Driver.retainedLimit;
|
|
216
|
+
|
|
217
|
+
expect(reads).toBe(2); // no caching without the $ prefix
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('leaves accessor pairs with a setter untouched', () => {
|
|
221
|
+
class $Tunable {
|
|
222
|
+
static backing = 1;
|
|
223
|
+
static get $level() {
|
|
224
|
+
return this.backing;
|
|
225
|
+
}
|
|
226
|
+
static set $level(value: number) {
|
|
227
|
+
this.backing = value;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const Tunable = Static($Tunable);
|
|
232
|
+
Tunable.$level = 5;
|
|
233
|
+
|
|
234
|
+
expect(Tunable.$level).toBe(5); // still the live pair, not a cache
|
|
235
|
+
Tunable.$level = 9;
|
|
236
|
+
expect(Tunable.$level).toBe(9);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('a subclass overriding the $-getter itself wins', () => {
|
|
240
|
+
class $Theme {
|
|
241
|
+
static get $accent() {
|
|
242
|
+
return 'blue';
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const Theme = Static($Theme);
|
|
247
|
+
expect(Theme.$accent).toBe('blue');
|
|
248
|
+
|
|
249
|
+
class DarkTheme extends Theme {
|
|
250
|
+
static override get $accent() {
|
|
251
|
+
return 'violet';
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// the subclass's own getter descriptor shadows the caching wrapper
|
|
256
|
+
expect(DarkTheme.$accent).toBe('violet');
|
|
257
|
+
expect(Theme.$accent).toBe('blue');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('walks the raw inheritance chain — ancestor $-getters cache per receiver', () => {
|
|
261
|
+
class $Base {
|
|
262
|
+
static get scale() {
|
|
263
|
+
return 1;
|
|
264
|
+
}
|
|
265
|
+
static get $metrics() {
|
|
266
|
+
return { unit: this.scale * 8 };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
class $Wide extends $Base {
|
|
270
|
+
static override get scale() {
|
|
271
|
+
return 3;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const Wide = Static($Wide);
|
|
276
|
+
|
|
277
|
+
expect(Wide.$metrics.unit).toBe(24); // derived through the override
|
|
278
|
+
expect(Wide.$metrics).toBe(Wide.$metrics); // and cached
|
|
279
|
+
});
|
|
280
|
+
});
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { describe, expect, it } from 'vitest';
|
|
5
5
|
import * as packageEntry from '../index';
|
|
6
|
+
import * as extrasEntry from '../extras';
|
|
6
7
|
import { Kernel, kernel } from '../kernel';
|
|
7
8
|
|
|
8
9
|
describe('package entry (index.ts)', () => {
|
|
@@ -11,6 +12,12 @@ describe('package entry (index.ts)', () => {
|
|
|
11
12
|
});
|
|
12
13
|
});
|
|
13
14
|
|
|
15
|
+
describe('extras entry (extras.ts)', () => {
|
|
16
|
+
it('re-exports the toolkit beyond the core', () => {
|
|
17
|
+
expect(typeof extrasEntry.Static).toBe('function');
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
14
21
|
describe('Kernel', () => {
|
|
15
22
|
it('sets, gets, falls back, clears', () => {
|
|
16
23
|
const container = new Kernel();
|
package/lib/extras.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ivue/extras` — the ivue toolkit beyond the reactive core.
|
|
3
|
+
*
|
|
4
|
+
* Kept out of the primary `ivue` entry so that entry stays minimal (the
|
|
5
|
+
* 1.1kb reactive engine). Import these explicitly:
|
|
6
|
+
*
|
|
7
|
+
* import { Static } from 'ivue/extras';
|
|
8
|
+
*/
|
|
9
|
+
export { Static, type ClassConstructor } from './Static';
|
package/package.json
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ivue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Infinite Vue – Class Based Architecture for Vue 3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"import": "./dist/index.es.js",
|
|
10
|
-
"require": "./dist/index.
|
|
10
|
+
"require": "./dist/index.cjs"
|
|
11
|
+
},
|
|
12
|
+
"./extras": {
|
|
13
|
+
"types": "./dist/extras.d.ts",
|
|
14
|
+
"import": "./dist/extras.es.js",
|
|
15
|
+
"require": "./dist/extras.cjs"
|
|
11
16
|
},
|
|
12
17
|
"./package.json": "./package.json"
|
|
13
18
|
},
|
|
14
|
-
"main": "./dist/index.
|
|
19
|
+
"main": "./dist/index.cjs",
|
|
15
20
|
"module": "./dist/index.es.js",
|
|
16
21
|
"types": "./dist/index.d.ts",
|
|
17
22
|
"scripts": {
|
|
@@ -35,6 +40,7 @@
|
|
|
35
40
|
"build:docs": "npm run sync:examples && npm --prefix docs_v2 run build",
|
|
36
41
|
"render:og": "node docs_v2/scripts/brand-image-generator.mjs og",
|
|
37
42
|
"render:form-header": "node docs_v2/scripts/brand-image-generator.mjs form-header",
|
|
43
|
+
"render:banner": "node docs_v2/scripts/brand-image-generator.mjs blog",
|
|
38
44
|
"preview:demo": "npm run build:demo && vite preview demo --host",
|
|
39
45
|
"preview:docs": "npm --prefix docs_v2 run preview",
|
|
40
46
|
"release": "npm run build && npm publish",
|
package/dist/index.umd.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(function(l,f){typeof exports=="object"&&typeof module<"u"?f(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],f):f((l=typeof globalThis<"u"?globalThis:l||self).ivue={},l.Vue)})(this,function(l,f){"use strict";const g=Object.hasOwn,d=Object.getPrototypeOf,v=Object.getOwnPropertyDescriptor,O=Object.getOwnPropertyNames,a=Object.defineProperty,m=Object.prototype,y=Symbol.for("ivue.raw"),b=Symbol.for("ivue.scope"),h=Symbol.for("ivue.processed");function s(e){const r=f.toRaw(e);if(r!==e)return r[y]??(r[y]=r);const n=e[y];return n?n===e?n:f.toRaw(n):e[y]=e}function j(e,r,n,o){a(e,r,{configurable:!0,enumerable:!1,get(){const t=s(this);return t[n]??(t[n]=o.bind(t))},set(t){s(this)[n]=t}})}function S(e,r,n,o,t){const u=r[0]==="$";a(e,r,{configurable:!0,enumerable:!1,get:function(){const c=s(this);if(n in c)return c[n];const i=o.call(c);return u?(c[n]=i,i):(f.isRef(i)?c[n]=i:a(e,r,{configurable:!0,enumerable:!1,get(){return o.call(s(this))},set:t?function(p){return t.call(s(this),p)}:void 0}),i)},set:t?function(c){return t.call(s(this),c)}:void 0})}const w=e=>{var r;return typeof e=="function"&&!!e.prototype&&!((r=v(e,"prototype"))!=null&&r.writable)};l.Reactive=function(e){const r=[];let n=e.prototype;for(;n&&n!==m;)r.push(n),n=d(n);r.reverse();for(const o of r){if(g(o,h))continue;const t=O(o),u=[];for(const c of t){if(c==="constructor")continue;const i=v(o,c);if(typeof i.value=="function"){const p=Symbol(c);u.push(p),j(o,c,p,i.value)}else if(i.get){const p=Symbol(c);u.push(p),S(o,c,p,i.get,i.set)}}a(o,h,{value:u})}return g(e.prototype,"$stopEffects")||(a(e.prototype,"$watch",{enumerable:!1,configurable:!0,writable:!0,value:function(...o){const t=s(this);return(t[b]??(t[b]=f.effectScope(!0))).run(()=>f.watch(...o))}}),a(e.prototype,"$watchEffect",{enumerable:!1,configurable:!0,writable:!0,value:function(...o){const t=s(this);return(t[b]??(t[b]=f.effectScope(!0))).run(()=>f.watchEffect(...o))}}),a(e.prototype,"$stopEffects",{enumerable:!1,configurable:!0,writable:!0,value:function(){const o=s(this);try{const t=o[b];t&&t.stop()}finally{delete o[b];let t=d(o);for(;t&&t!==m;){const u=t[h];if(u)for(const c of u)delete o[c];t=d(t)}}}})),e},l.isClass=w,l.propsWithDefaults=(e,r,n)=>{const o={};for(const t in r){const u=e==null?void 0:e[t],c=r[t];o[t]={...c},c.required||u===void 0||(typeof u=="object"&&u!==null?o[t].default=()=>n?n(u):structuredClone(u):w(u)?o[t].default=()=>u:o[t].default=u)}return o},Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})});
|