ivue 2.2.0 → 2.2.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.
@@ -87,7 +87,9 @@ export declare type ReactiveInstance<T> = T & WritableGetters<T> & {
87
87
  /** Stop the instance's effect scope and drop cached cells. */
88
88
  $stopEffects: () => void;
89
89
  };
90
- export declare type ReactiveClass<C extends new (...args: any) => any> = new (...args: ConstructorParameters<C>) => ReactiveInstance<InstanceType<C>>;
90
+ export declare type ReactiveClass<C extends new (...args: any) => any> = {
91
+ [Key in keyof C]: C[Key];
92
+ } & (new (...args: ConstructorParameters<C>) => ReactiveInstance<InstanceType<C>>);
91
93
  /**
92
94
  * Component-authoring type utilities (types only — erased at build time).
93
95
  * These complement `propsWithDefaults` for the params/defaults component
package/dist/Static.d.ts CHANGED
@@ -8,15 +8,26 @@
8
8
  *
9
9
  * Get-only static accessors whose name starts with `$` become
10
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.
11
+ * through a given class, its result is stored under a symbol OWN property
12
+ * of that receiver, and later reads return the stored value. The
13
+ * `Object.hasOwn` guard never walks the prototype chain, so a parent's
14
+ * cache can never shadow a subclass — each class in a hierarchy derives
15
+ * through its own overrides on its own first read, in ANY read order.
16
+ * The `$` prefix IS the API: it promises STABLE IDENTITY per receiver,
17
+ * nothing more whether the cached value is then treated as immutable
18
+ * config or as a mutable memo table is the author's design. A static
19
+ * getter that must stay live (a knob for subclasses to pinch, a
20
+ * fresh-per-read value) must not use the prefix.
21
+ *
22
+ * Method binding uses the same per-receiver symbol discipline: the bound
23
+ * function is cached under a symbol own property, never under the method
24
+ * name — so a parent-first read can never install a parent-bound method
25
+ * where a subclass's chain lookup would find it.
26
+ *
27
+ * `$` semantics are GRANTED BY the transform: a raw class, a raw
28
+ * subclass, or a class only passed through `Reactive()` keeps native
29
+ * getter behavior. A class that needs instance reactivity AND static
30
+ * `$`-caches composes the transforms: `Static(Reactive($Class))`.
20
31
  *
21
32
  * This is the namespace pattern's backend adapter: canonical namespace +
22
33
  * mutable `Class` slot + late reads, for STATELESS capability classes (a
package/dist/extras.cjs CHANGED
@@ -1 +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};
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=Object.hasOwn;exports.Static=function(c){const r=class extends c{},s=new Set;for(let o=c;o!==Function.prototype;o=Object.getPrototypeOf(o))for(const e of Reflect.ownKeys(o)){if(s.has(e))continue;s.add(e);const t=Object.getOwnPropertyDescriptor(o,e);if(typeof t.value=="function"){const i=t.value,n=typeof e=="string"?Symbol.for(`ivue.staticBound.${e}`):Symbol("ivue.staticBound");Object.defineProperty(r,e,{configurable:!0,enumerable:t.enumerable,get(){return u(this,n)||Object.defineProperty(this,n,{configurable:!0,value:i.bind(this)}),this[n]}})}else if(t.get&&!t.set&&typeof e=="string"&&e.startsWith("$")){const i=t.get,n=Symbol.for(`ivue.staticCache.${e}`);Object.defineProperty(r,e,{configurable:!0,enumerable:t.enumerable,get(){return u(this,n)||Object.defineProperty(this,n,{configurable:!0,value:i.call(this)}),this[n]}})}}return r};
package/dist/extras.es.js CHANGED
@@ -1,27 +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))
1
+ const a = Object.hasOwn;
2
+ function u(s) {
3
+ const r = class extends s {
4
+ }, c = /* @__PURE__ */ new Set();
5
+ for (let n = s; n !== Function.prototype; n = Object.getPrototypeOf(n))
6
+ for (const e of Reflect.ownKeys(n)) {
7
+ if (c.has(e))
7
8
  continue;
8
- s.add(e);
9
- const t = Object.getOwnPropertyDescriptor(r, e);
9
+ c.add(e);
10
+ const t = Object.getOwnPropertyDescriptor(n, e);
10
11
  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;
12
+ const i = t.value, o = typeof e == "string" ? Symbol.for(`ivue.staticBound.${e}`) : Symbol("ivue.staticBound");
13
+ Object.defineProperty(r, e, { configurable: !0, enumerable: t.enumerable, get() {
14
+ return a(this, o) || Object.defineProperty(this, o, { configurable: !0, value: i.bind(this) }), this[o];
15
15
  } });
16
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];
17
+ const i = t.get, o = Symbol.for(`ivue.staticCache.${e}`);
18
+ Object.defineProperty(r, e, { configurable: !0, enumerable: t.enumerable, get() {
19
+ return a(this, o) || Object.defineProperty(this, o, { configurable: !0, value: i.call(this) }), this[o];
20
20
  } });
21
21
  }
22
22
  }
23
- return o;
23
+ return r;
24
24
  }
25
25
  export {
26
- a as Static
26
+ u as Static
27
27
  };
package/lib/Reactive.ts CHANGED
@@ -434,9 +434,9 @@ export type ReactiveInstance<T> = T &
434
434
  $stopEffects: () => void;
435
435
  };
436
436
 
437
- export type ReactiveClass<C extends new (...args: any) => any> = new (
438
- ...args: ConstructorParameters<C>
439
- ) => ReactiveInstance<InstanceType<C>>;
437
+ export type ReactiveClass<C extends new (...args: any) => any> = {
438
+ [Key in keyof C]: C[Key];
439
+ } & (new (...args: ConstructorParameters<C>) => ReactiveInstance<InstanceType<C>>);
440
440
 
441
441
  /**
442
442
  * Component-authoring type utilities (types only — erased at build time).
package/lib/Static.ts CHANGED
@@ -8,15 +8,26 @@
8
8
  *
9
9
  * Get-only static accessors whose name starts with `$` become
10
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.
11
+ * through a given class, its result is stored under a symbol OWN property
12
+ * of that receiver, and later reads return the stored value. The
13
+ * `Object.hasOwn` guard never walks the prototype chain, so a parent's
14
+ * cache can never shadow a subclass — each class in a hierarchy derives
15
+ * through its own overrides on its own first read, in ANY read order.
16
+ * The `$` prefix IS the API: it promises STABLE IDENTITY per receiver,
17
+ * nothing more whether the cached value is then treated as immutable
18
+ * config or as a mutable memo table is the author's design. A static
19
+ * getter that must stay live (a knob for subclasses to pinch, a
20
+ * fresh-per-read value) must not use the prefix.
21
+ *
22
+ * Method binding uses the same per-receiver symbol discipline: the bound
23
+ * function is cached under a symbol own property, never under the method
24
+ * name — so a parent-first read can never install a parent-bound method
25
+ * where a subclass's chain lookup would find it.
26
+ *
27
+ * `$` semantics are GRANTED BY the transform: a raw class, a raw
28
+ * subclass, or a class only passed through `Reactive()` keeps native
29
+ * getter behavior. A class that needs instance reactivity AND static
30
+ * `$`-caches composes the transforms: `Static(Reactive($Class))`.
20
31
  *
21
32
  * This is the namespace pattern's backend adapter: canonical namespace +
22
33
  * mutable `Class` slot + late reads, for STATELESS capability classes (a
@@ -29,6 +40,8 @@
29
40
  */
30
41
  export type ClassConstructor = new (...arguments_: any[]) => any;
31
42
 
43
+ const hasOwn = Object.hasOwn;
44
+
32
45
  export function Static<Class extends ClassConstructor>(targetClass: Class): Class {
33
46
  const SelectedClass = class extends targetClass {};
34
47
  const visitedKeys = new Set<PropertyKey>();
@@ -46,14 +59,22 @@ export function Static<Class extends ClassConstructor>(targetClass: Class): Clas
46
59
 
47
60
  if (typeof descriptor.value === 'function') {
48
61
  const method = descriptor.value;
62
+ const bindKey =
63
+ typeof key === 'string'
64
+ ? Symbol.for(`ivue.staticBound.${key}`)
65
+ : Symbol('ivue.staticBound');
49
66
 
50
67
  Object.defineProperty(SelectedClass, key, {
51
68
  configurable: true,
52
69
  enumerable: descriptor.enumerable,
53
- get(this: ClassConstructor) {
54
- const boundMethod = method.bind(this);
55
- Object.defineProperty(this, key, { ...descriptor, value: boundMethod });
56
- return boundMethod;
70
+ get(this: any) {
71
+ if (!hasOwn(this, bindKey)) {
72
+ Object.defineProperty(this, bindKey, {
73
+ configurable: true,
74
+ value: method.bind(this),
75
+ });
76
+ }
77
+ return this[bindKey];
57
78
  },
58
79
  });
59
80
  } else if (
@@ -69,10 +90,10 @@ export function Static<Class extends ClassConstructor>(targetClass: Class): Clas
69
90
  configurable: true,
70
91
  enumerable: descriptor.enumerable,
71
92
  get(this: any) {
72
- if (!Object.hasOwn(this, cacheKey)) {
93
+ if (!hasOwn(this, cacheKey)) {
73
94
  Object.defineProperty(this, cacheKey, {
74
95
  configurable: true,
75
- value: Object.freeze(getter.call(this)),
96
+ value: getter.call(this),
76
97
  });
77
98
  }
78
99
  return this[cacheKey];
@@ -1,4 +1,6 @@
1
+ import { ref } from 'vue';
1
2
  import { describe, expect, it } from 'vitest';
3
+ import { Reactive } from '../Reactive';
2
4
  import { Static } from '../Static';
3
5
 
4
6
  describe('Static', () => {
@@ -81,6 +83,23 @@ describe('Static', () => {
81
83
  expect(Config.resolve('/path')).toBe('https://example.test/path');
82
84
  });
83
85
 
86
+ it('binds symbol-keyed static methods with the same discipline', () => {
87
+ const describeKind = Symbol('describeKind');
88
+
89
+ class $Shape {
90
+ static kind = 'circle';
91
+ static [describeKind]() {
92
+ return `kind:${this.kind}`;
93
+ }
94
+ }
95
+
96
+ const Shape = Static($Shape);
97
+ const described = (Shape as any)[describeKind];
98
+
99
+ expect((Shape as any)[describeKind]).toBe(described); // identity-stable
100
+ expect(described()).toBe('kind:circle'); // bound through detachment
101
+ });
102
+
84
103
  it('binds `this` to the wrapped class, so statics compose', () => {
85
104
  class $Report {
86
105
  static header() {
@@ -166,21 +185,26 @@ describe('Static $-cached getters', () => {
166
185
  expect(computeRuns).toBe(2); // once per receiver, never shared
167
186
  });
168
187
 
169
- it('freezes cached valuesmutation throws instead of corrupting', () => {
170
- class $Config {
171
- static get $defaults() {
172
- return { width: 80 };
188
+ it('promises stable identity, not immutability memo tables mutate freely', () => {
189
+ class $Wrap {
190
+ static get $memo() {
191
+ return new Map<string, number>();
192
+ }
193
+ static get $state() {
194
+ return { frame: 0, quiescent: false };
173
195
  }
174
196
  }
175
197
 
176
- const Config = Static($Config);
177
- const defaults = Config.$defaults;
198
+ const Wrap = Static($Wrap);
178
199
 
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);
200
+ // a cached Map used as a per-class memo table — the dominant real pattern
201
+ Wrap.$memo.set('row', 42);
202
+ expect(Wrap.$memo.get('row')).toBe(42); // same table, mutation retained
203
+
204
+ // a cached plain object used as deliberate mutable state
205
+ Object.assign(Wrap.$state, { frame: 7, quiescent: true });
206
+ expect(Wrap.$state.frame).toBe(7);
207
+ expect(Wrap.$state).toBe(Wrap.$state); // identity stable throughout
184
208
  });
185
209
 
186
210
  it('caches primitive results too', () => {
@@ -257,6 +281,87 @@ describe('Static $-cached getters', () => {
257
281
  expect(Theme.$accent).toBe('blue');
258
282
  });
259
283
 
284
+ it('method binding is order-correct: parent read first, child dispatch intact', () => {
285
+ class $Render {
286
+ static get glyph() {
287
+ return '-'; // a knob subclasses pinch
288
+ }
289
+ static paint() {
290
+ return this.glyph.repeat(3);
291
+ }
292
+ }
293
+
294
+ const Render = Static($Render);
295
+ class DottedRender extends Render {
296
+ static override get glyph() {
297
+ return '.';
298
+ }
299
+ }
300
+
301
+ // PARENT reads first — the named-own-property shape poisoned this order
302
+ const parentPaint = Render.paint;
303
+ expect(parentPaint()).toBe('---');
304
+ expect(DottedRender.paint()).toBe('...'); // child binds itself, chain not shadowed
305
+ expect(DottedRender.paint).toBe(DottedRender.paint); // still identity-stable
306
+ expect(Render.paint).toBe(parentPaint);
307
+ });
308
+
309
+ it('method binding is order-correct: child read first, parent unaffected', () => {
310
+ class $Render {
311
+ static get glyph() {
312
+ return '-';
313
+ }
314
+ static paint() {
315
+ return this.glyph.repeat(2);
316
+ }
317
+ }
318
+
319
+ const Render = Static($Render);
320
+ class DottedRender extends Render {
321
+ static override get glyph() {
322
+ return '.';
323
+ }
324
+ }
325
+
326
+ expect(DottedRender.paint()).toBe('..');
327
+ expect(Render.paint()).toBe('--');
328
+ });
329
+
330
+ it('composes with Reactive(): Static(Reactive($Class)) grants both contracts', () => {
331
+ let derivations = 0;
332
+
333
+ class $Channel {
334
+ get status() {
335
+ return ref('idle');
336
+ }
337
+ announce() {
338
+ return `channel:${this.status.value}`;
339
+ }
340
+ static get $defaults() {
341
+ derivations++;
342
+ return { retries: 3 };
343
+ }
344
+ static open() {
345
+ return this.$defaults.retries;
346
+ }
347
+ }
348
+
349
+ const Channel = Static(Reactive($Channel));
350
+
351
+ // instance side: Reactive semantics intact through the Static subclass
352
+ const channel: any = new Channel();
353
+ const statusCell = channel.status;
354
+ expect(statusCell.value).toBe('idle');
355
+ expect(channel.status).toBe(statusCell); // cached ref cell
356
+ expect(channel.announce()).toBe('channel:idle');
357
+
358
+ // static side: $-cache + bound methods from Static
359
+ expect(Channel.$defaults).toBe(Channel.$defaults);
360
+ expect(derivations).toBe(1);
361
+ const open = Channel.open;
362
+ expect(open()).toBe(3); // detached, still bound
363
+ });
364
+
260
365
  it('walks the raw inheritance chain — ancestor $-getters cache per receiver', () => {
261
366
  class $Base {
262
367
  static get scale() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ivue",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "Infinite Vue – Class Based Architecture for Vue 3",
5
5
  "type": "module",
6
6
  "exports": {