ivue 1.5.7 → 1.6.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/lib/ivue.ts CHANGED
@@ -1,40 +1,65 @@
1
- /* eslint-disable @typescript-eslint/ban-types */
2
- /* eslint-disable @typescript-eslint/no-explicit-any */
3
1
  import type { ComputedRef, ExtractPropTypes, Ref, ToRef } from 'vue';
4
- import { computed, reactive, ref, shallowRef, toRef } from 'vue';
2
+ import { computed, markRaw, reactive, ref, shallowRef, toRef } from 'vue';
5
3
  import type { Ref as DemiRef } from 'vue-demi';
6
4
 
5
+ /** Cached global methods. */
6
+ const isArray = Array.isArray;
7
+ const createObject = Object.create;
8
+ const defineProperty = Object.defineProperty;
9
+ const getPrototypeOf = Object.getPrototypeOf;
10
+ const getOwnPropertyNames = Object.getOwnPropertyNames;
11
+ const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
12
+ const getOwnPropertyDescriptors = Object.getOwnPropertyDescriptors;
13
+
14
+
7
15
  /** Types */
8
16
  /**
9
17
  * IVue core reactive instance type with an extended .toRefs() method added.
10
18
  */
11
- export type IVue<T extends AnyClass> = InstanceType<T> & ExtendWithToRefs<T>;
19
+ export type IVue<T extends AnyClass> = InstanceType<T> & {
20
+ toRefs: IVueToRefsFn<T>;
21
+ clone: IVueClone<T>;
22
+ };
12
23
 
13
24
  /**
14
- * Extend with .toRefs() method, so that a reactive class can be converted to a composable definition.
25
+ * Type definition for `.clone()` method creates a deep clone of the ivue instance.
15
26
  */
16
- export interface ExtendWithToRefs<T extends AnyClass> {
17
- toRefs: IVueToRefsFn<T>;
18
- }
27
+ export type IVueClone<T extends AnyClass> = (
28
+ ...cloneArgs: CloneArgsFor<InstanceType<T>>
29
+ ) => IVue<T>;
19
30
 
20
31
  /**
21
32
  * Type definition for `.toRefs()` method converts reactive class properties to composable .value properties.
22
33
  * But if props = true OR unwrap = true is specified, the refs will be unwrapped refs to be able to be merged with the the root class properties without losing reactivity.
23
34
  */
24
- export interface IVueToRefsFn<T extends AnyClass> {
25
- <P extends keyof InstanceType<T>>(props: P[]): Pick<
35
+ /** Extract only non-method keys from a type */
36
+ type NonMethodKeys<T> = {
37
+ [K in keyof T]: T[K] extends AnyFn ? never : K;
38
+ }[keyof T];
39
+
40
+ /** Create a type with only data properties and accessors */
41
+ type DataProperties<T> = Pick<T, NonMethodKeys<T>>;
42
+
43
+ /**
44
+ * Simplified IVueToRefsFn that only works with data properties
45
+ */
46
+ interface IVueToRefsFn<T extends AnyClass> {
47
+ <P extends keyof IVueRefs<InstanceType<T>>>(props: P[]): Pick<
26
48
  IVueRefs<InstanceType<T>>,
27
49
  P
28
50
  >;
29
- <P extends keyof InstanceType<T>>(props: P[], unwrap: false): Pick<
51
+
52
+ <P extends keyof IVueRefs<InstanceType<T>>>(props: P[], unwrap: false): Pick<
30
53
  IVueRefs<InstanceType<T>>,
31
54
  P
32
55
  >;
33
- <P extends keyof InstanceType<T>>(props: P[], unwrap: true): Pick<
34
- InstanceType<T>,
35
- P
36
- >;
37
- (props: true): InstanceType<T>;
56
+
57
+ <P extends keyof DataProperties<InstanceType<T>>>(
58
+ props: P[],
59
+ unwrap: true
60
+ ): Pick<DataProperties<InstanceType<T>>, P>;
61
+
62
+ (props: true): DataProperties<InstanceType<T>>;
38
63
  (props: false): IVueRefs<InstanceType<T>>;
39
64
  (): IVueRefs<InstanceType<T>>;
40
65
  }
@@ -44,7 +69,7 @@ export interface IVueToRefsFn<T extends AnyClass> {
44
69
  * also knows NOT to convert functions to .value Refs but to leave them as is.
45
70
  */
46
71
  export type IVueRefs<T> = {
47
- [K in keyof T]: T[K] extends Function ? T[K] : ToRef<T[K]>;
72
+ [K in keyof T as T[K] extends AnyFn ? never : K]: ToRef<T[K]>;
48
73
  };
49
74
 
50
75
  /**
@@ -54,14 +79,14 @@ export type IVueRefs<T> = {
54
79
  * are difficult to fully resolve to bare values without this utility.
55
80
  * Also support Vue 3 Demi for vue-use library support.
56
81
  */
57
- export type UnwrapRefRecursively<T = any> = T extends Ref | DemiRef
82
+ type UnwrapRefRecursively<T = any> = T extends Ref | DemiRef
58
83
  ? UnwrapRefRecursively<T['value']>
59
84
  : T;
60
85
 
61
86
  /**
62
- * Helper type for Use type, unwraps any type of Vue 3 composable down to its bare types.
87
+ * Helper type for Use type, unwraps any type of Vue 3 composable return object down to its bare types.
63
88
  */
64
- export type Unwrap<T> = T extends Ref | DemiRef
89
+ type UnwrapComposableRefs<T> = T extends Ref | DemiRef
65
90
  ? UnwrapRefRecursively<T>
66
91
  : {
67
92
  [K in keyof T]: T[K] extends Ref | DemiRef
@@ -74,10 +99,10 @@ export type Unwrap<T> = T extends Ref | DemiRef
74
99
  */
75
100
  export type Use<T = any> = T extends Ref | DemiRef
76
101
  ? UnwrapRefRecursively<T>
77
- : Unwrap<T extends AnyFn ? ReturnType<T> : T>;
102
+ : UnwrapComposableRefs<T extends AnyFn ? ReturnType<T> : T>;
78
103
 
79
104
  /**
80
- * Extracts object defined emit types by converting them to a plain interface
105
+ * Extracts object defined emit types by converting them to a plain interface.
81
106
  */
82
107
  export type ExtractEmitTypes<T extends Record<string, any>> =
83
108
  UnionToIntersection<
@@ -109,17 +134,17 @@ export type AnyFn = (...args: any[]) => any;
109
134
  /**
110
135
  * Any JavaScript class of any type.
111
136
  */
112
- export type AnyClass = abstract new (...args: any[]) => any;
137
+ export type AnyClass = new (...args: any[]) => any;
113
138
 
114
139
  /**
115
- * Descriptors map type.
140
+ * Accessors map type.
116
141
  */
117
- export type Descriptors = Map<string, PropertyDescriptor>;
142
+ type Accessors = Map<string, PropertyDescriptor>;
118
143
 
119
144
  /**
120
145
  * Computeds hash map type.
121
146
  */
122
- export type Computeds = Record<string, ComputedRef>;
147
+ type Computeds = Record<string, ComputedRef>;
123
148
 
124
149
  /**
125
150
  * Infer paramaters of a constructor function of a Class.
@@ -135,23 +160,6 @@ export type PrefixKeys<T, P extends string | undefined = undefined> = {
135
160
  [K in Extract<keyof T, string> as P extends string ? `${P}${K}` : K]: T[K];
136
161
  };
137
162
 
138
- /**
139
- * Get Interface's property's function arguments Parmeters<F>
140
- */
141
- export type IFnParameters<
142
- T extends Record<any, any>,
143
- K extends string
144
- > = Parameters<Required<Pick<T, K>>[K]>;
145
-
146
- /**
147
- * Get Interface's [T] property's [P] function arguments Parmeters<F> parameter by key [K]
148
- */
149
- export type IFnParameter<
150
- T extends Record<any, any>,
151
- P extends keyof T,
152
- K extends number
153
- > = FnParameter<ValueOf<T, P>, K>;
154
-
155
163
  /**
156
164
  * Get function arguments Parmeters<F> parameter by key K
157
165
  */
@@ -160,80 +168,226 @@ export type FnParameter<F extends AnyFn, K extends number> = Parameters<F>[K];
160
168
  /**
161
169
  * Convert Union Type to Intersection Type.
162
170
  */
163
- export type UnionToIntersection<U> = (
164
- U extends any ? (k: U) => void : never
165
- ) extends (k: infer I) => void
171
+ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
172
+ k: infer I
173
+ ) => void
166
174
  ? I
167
175
  : never;
168
176
 
169
177
  /**
170
178
  * Convert Record to Union Type.
171
179
  */
172
- export type RecordToUnion<T extends Record<string, any>> = T[keyof T];
180
+ type RecordToUnion<T extends Record<string, any>> = T[keyof T];
173
181
 
174
182
  /**
175
183
  * Gets object T property by key [K].
176
184
  */
177
- export type ValueOf<T extends Record<any, any>, K> = T[K];
185
+ type ValueOf<T extends Record<any, any>, K> = T[K];
186
+
187
+ /**
188
+ * Props Map Value type.
189
+ */
190
+ type PropsMapValue = {
191
+ allProps: Map<string, PropKind>;
192
+ onlyProps: Set<string>;
193
+ };
194
+
195
+ /**
196
+ * Accessors and Methods map type.
197
+ */
198
+ type AccessorsMethodsMap = {
199
+ accessors: Accessors;
200
+ methods: Set<string>;
201
+ };
202
+
203
+ export type IVueInstanceBase = {
204
+ init?: (...args: any[]) => void;
205
+ };
206
+
207
+ /**
208
+ * IVue static configuration interface.
209
+ */
210
+ export interface IVueStaticConfig<T> {
211
+ ivueGlobalStore?: boolean;
212
+ ivueCloneByReference?: Set<keyof T>;
213
+ ivueDisableReactivity?: Set<keyof T>;
214
+ }
215
+
216
+ /**
217
+ * IVue constructor type.
218
+ */
219
+ export type IVueConstructor<T> = new (...args: any[]) => T;
220
+
221
+ /**
222
+ * IVue class type with static configuration.
223
+ */
224
+ export type IVueClass<T> = IVueConstructor<T> & IVueStaticConfig<T>;
225
+
226
+ /**
227
+ * Get IVue init() method arguments IVueInitArgs<T>
228
+ */
229
+ export type IVueInitArgs<T extends IVueInstanceBase> = T['init'] extends (
230
+ ...args: any[]
231
+ ) => any
232
+ ? Parameters<T['init']>
233
+ : never;
234
+
235
+ /**
236
+ * Get Interface's method keys MethodKeys<T>
237
+ */
238
+ type MethodKeys<T> = {
239
+ [P in keyof T]: T[P] extends (...args: any[]) => any ? P : never;
240
+ }[keyof T];
241
+
242
+ /**
243
+ * Get Interface's property's function arguments Parameters<F>
244
+ */
245
+ export type IFnParameters<
246
+ T extends Record<any, any> | InstanceType<AnyClass>,
247
+ K extends MethodKeys<T>
248
+ > = Parameters<Extract<Required<Pick<T, K>>[K], AnyFn>>;
249
+
250
+ /**
251
+ * Get Interface's [T] property's [P] function arguments Parmeters<F> parameter by key [K]
252
+ */
253
+ export type IFnParameter<
254
+ T extends Record<any, any>,
255
+ P extends keyof T,
256
+ K extends number
257
+ > = FnParameter<ValueOf<T, P>, K>;
258
+
259
+ export type IVueDeepCloneArgsMap = Map<AnyClass, any[]>;
260
+
261
+ type CloneArgsFor<T> = T extends {
262
+ init: (isClone: boolean, ...rest: infer A) => any;
263
+ }
264
+ ? A
265
+ : [];
178
266
  /** Types End. */
179
267
 
268
+ export const IVUE_INSTANCE_SYMBOL = Symbol('IVUE_INSTANCE');
269
+
180
270
  /**
181
- * Store all descriptors for each class prototype processed by
182
- * @see {getAllClassDescriptors}
271
+ * Stores accessors for each class prototype processed by
272
+ * @see {getClassAccessorsMethodsMap}. Uses WeakMap to allow garbage collection
273
+ * of unused class accessors, ensuring memory efficiency in long-running applications.
183
274
  */
184
- export const descriptorsMap = new Map();
275
+ export let accessorsMethodsMaps = new WeakMap<object, AccessorsMethodsMap>();
185
276
 
186
277
  /**
187
- * Get descriptors of an entire class prototype ancestors chain as a Map.
278
+ * Get accessors of an entire class prototype ancestors chain as a Map.
188
279
  * Completely emulates JavaScript class inheritance chain for getters and setters.
189
280
  *
190
281
  * @param className
191
- * @return {Descriptors}
282
+ * @return {AccessorsMethodsMap}
192
283
  */
193
- export function getAllClassDescriptors(className: AnyClass): Descriptors {
194
- /* Retrieve descriptors from cache */
195
- if (descriptorsMap.has(className)) {
196
- return descriptorsMap.get(className);
284
+ const getClassAccessorsMethodsMap = (
285
+ className: AnyClass
286
+ ): AccessorsMethodsMap => {
287
+ if (accessorsMethodsMaps.has(className)) {
288
+ return accessorsMethodsMaps.get(className) as AccessorsMethodsMap;
197
289
  }
198
290
 
199
- const savedDescriptors: Descriptors = new Map();
291
+ const savedAccessors: Accessors = new Map();
292
+ const savedMethods: Set<string> = new Set();
200
293
 
201
294
  let prototype = className.prototype;
202
- while (prototype.constructor !== Object) {
203
- Object.entries(Object.getOwnPropertyDescriptors(prototype)).forEach(
204
- ([propertyName, currentDescriptor]) => {
205
- const savedDescriptor = savedDescriptors.get(propertyName);
206
- /** Emulate inheritance chain of getters and setters. */
207
- if (
208
- savedDescriptor !== currentDescriptor &&
209
- (currentDescriptor?.get || currentDescriptor?.set)
210
- ) {
211
- /** Overwrite descriptors if they were already set. */
212
- if (savedDescriptor?.get)
213
- currentDescriptor.get = savedDescriptor?.get;
214
- if (savedDescriptor?.set)
215
- currentDescriptor.set = savedDescriptor?.set;
216
-
217
- /** Store descriptors. */
218
- savedDescriptors.set(propertyName, currentDescriptor);
295
+ while (prototype && prototype !== Object.prototype) {
296
+ const accessors = getOwnPropertyDescriptors(prototype);
297
+ for (const propertyName in accessors) {
298
+ if (propertyName === 'constructor') continue;
299
+ const descriptor = accessors[propertyName];
300
+ if (descriptor.get || descriptor.set) {
301
+ // Only add if it hasn't been defined yet (i.e. subclass overrides win)
302
+ if (!savedAccessors.has(propertyName)) {
303
+ savedAccessors.set(propertyName, descriptor);
219
304
  }
305
+ } else if (typeof descriptor.value === 'function') {
306
+ savedMethods.add(propertyName);
220
307
  }
221
- );
222
- /** Walk up the prototype ancestors chain. */
223
- prototype = Object.getPrototypeOf(prototype);
308
+ }
309
+ prototype = getPrototypeOf(prototype);
224
310
  }
225
311
 
226
- /** Save descriptors in the descriptors map for each class. */
227
- descriptorsMap.set(className, savedDescriptors);
312
+ const propertyMap = {
313
+ accessors: savedAccessors,
314
+ methods: savedMethods,
315
+ };
316
+ accessorsMethodsMaps.set(className, propertyMap);
228
317
 
229
- return savedDescriptors;
318
+ return propertyMap;
319
+ };
320
+
321
+ /**
322
+ * Property kind enum for distinguishing between data properties and accessors.
323
+ */
324
+ const enum PropKind {
325
+ Data = 0,
326
+ Accessor = 2,
230
327
  }
231
328
 
329
+ /**
330
+ * Stores properties for each class prototype processed by
331
+ * @see {getClassPropertiesAccessorsMap}. Uses WeakMap to allow garbage collection
332
+ * of unused class properties, ensuring memory efficiency in long-running applications.
333
+ */
334
+ export let propertiesAccessorsMaps: WeakMap<object, PropsMapValue> =
335
+ new WeakMap();
336
+
337
+ /**
338
+ * 'caller', 'callee', 'arguments', 'constructor' are
339
+ * special object properties, so should be skipped.
340
+ * Also skip 'toRefs' as it's added by ivue and not part of the class itself.
341
+ */
342
+ const skipProps = new Set(['caller', 'callee', 'arguments', 'constructor']);
343
+
344
+ /**
345
+ * Get properties of an entire class prototype ancestors chain as a Map.
346
+ */
347
+ const getClassPropertiesAccessorsMap = (obj: object): PropsMapValue => {
348
+ const constructor = obj.constructor;
349
+ /* Retrieve props from cache. */
350
+ if (propertiesAccessorsMaps.has(constructor)) {
351
+ return propertiesAccessorsMaps.get(constructor) as PropsMapValue;
352
+ }
353
+
354
+ const { accessors, methods } =
355
+ accessorsMethodsMaps.get(constructor) ??
356
+ getClassAccessorsMethodsMap(constructor as AnyClass);
357
+
358
+ const onlyProps = new Set<string>();
359
+ const allProps: Map<string, PropKind> = new Map();
360
+
361
+ do {
362
+ const propertyNames = getOwnPropertyNames(obj);
363
+ for (let i = 0, j = propertyNames.length; i < j; i++) {
364
+ const property = propertyNames[i];
365
+
366
+ if (skipProps.has(property)) continue; // Skip special properties
367
+ if (allProps.has(property)) continue; // Already defined in subclass, skip it
368
+
369
+ if (accessors.has(property)) {
370
+ allProps.set(property, PropKind.Accessor);
371
+ } else if (!methods.has(property)) {
372
+ onlyProps.add(property);
373
+ allProps.set(property, PropKind.Data);
374
+ }
375
+ }
376
+
377
+ obj = getPrototypeOf(obj);
378
+ } while (obj && obj.constructor !== Object);
379
+
380
+ const result = { allProps, onlyProps };
381
+ propertiesAccessorsMaps.set(constructor, result);
382
+
383
+ return result;
384
+ };
385
+
232
386
  /**
233
387
  * Infinite Vue (ivue) class reactive initializer.
234
388
  *
235
389
  * Converts class instance to a reactive object,
236
- * where descriptors are converted to computeds.
390
+ * where accessors are converted to computeds.
237
391
  *
238
392
  * You can turn off computed behaviour by adding static
239
393
  * ivue object and setting the getter props to false.
@@ -249,91 +403,209 @@ export function getAllClassDescriptors(className: AnyClass): Descriptors {
249
403
  * @param args Class constructor arguments that you would pass to a `new AnyClass(args...)`
250
404
  * @returns {IVue<T>}
251
405
  */
252
- export function ivue<T extends AnyClass>(
406
+ export const ivue = <T extends AnyClass>(
253
407
  className: T,
254
408
  ...args: InferredArgs<T>
255
- ): IVue<T> {
256
- const descriptors: Descriptors = getAllClassDescriptors(className);
257
- const computeds: Computeds | any = descriptors?.size ? {} : null;
409
+ ): IVue<T> => {
410
+ const { accessors, methods } = getClassAccessorsMethodsMap(className);
411
+ const computeds: Computeds | any = accessors?.size
412
+ ? createObject(null)
413
+ : null;
258
414
 
259
- // @ts-expect-error Abstract class initialization
415
+ /** Create a reactive instance of the class. */
260
416
  const vue = reactive(new className(...args));
261
417
 
262
- /** Setup descriptors as computeds. */
263
- for (const [prop, descriptor] of descriptors) {
264
- /* If prop exists on static getter className.ivue[prop]
265
- * We do not convert it to computed. Because sometimes
266
- * we want a normal getter. */
267
- if ((className as any)?.ivue?.[prop] === false) continue;
418
+ /** Setup accessors as computeds. */
419
+ for (const [prop, descriptor] of accessors) {
268
420
  /** Convert descriptor to computed. */
269
- Object.defineProperty(vue, prop, {
270
- get: descriptor.get
271
- ? () =>
272
- prop in computeds
273
- ? /** Get the existing computed, because we are in reactive scope, .value will auto unwrap itself. */
274
- computeds[prop]
275
- : /** Create the computed and return it, because we are in reactive scope, .value will auto unwrap itself. */
276
- (computeds[prop] = computed({
277
- get: descriptor.get?.bind(vue) as any,
278
- set: descriptor.set?.bind(vue),
279
- } as any))
280
- : undefined,
281
- set: descriptor.set?.bind(vue),
282
- enumerable: false,
421
+ defineProperty(vue, prop, {
422
+ get: () =>
423
+ computeds[prop] ??
424
+ (computeds[prop] = computed({
425
+ get: descriptor.get?.bind(vue) as unknown,
426
+ set: descriptor.set?.bind(vue),
427
+ } as any)) /** Create the computed and return it, because we are in reactive scope, .value will auto unwrap itself. */,
428
+ enumerable: descriptor.enumerable,
429
+ configurable: descriptor.configurable,
283
430
  });
284
431
  }
285
432
 
286
- Object.defineProperty(vue, 'toRefs', {
287
- get: () => ivueToRefs(vue, descriptors as Descriptors, computeds),
433
+ /** Disable reactivity for specified properties. */
434
+ const nonReactiveProps = (className as any)?.ivueDisableReactivity;
435
+ if (nonReactiveProps) {
436
+ for (const prop of nonReactiveProps) {
437
+ const descriptor = accessors.get(prop);
438
+ if (descriptor) {
439
+ defineProperty(vue, prop, {
440
+ get: descriptor.get?.bind(vue),
441
+ set: descriptor.set?.bind(vue),
442
+ enumerable: descriptor.enumerable,
443
+ configurable: descriptor.configurable,
444
+ });
445
+ } else {
446
+ console.log('des criptor', prop, descriptor);
447
+ vue[prop] = markRaw(vue[prop]);
448
+ }
449
+ }
450
+ }
451
+
452
+ // In ivue(), for method binding:
453
+ const methodDescriptor: PropertyDescriptor = {
454
+ writable: true,
455
+ configurable: true,
288
456
  enumerable: false,
457
+ };
458
+ /** Bind all methods to the vue instance. */
459
+ for (const methodName of methods) {
460
+ methodDescriptor.value = vue[methodName].bind(vue);
461
+ defineProperty(vue, methodName, methodDescriptor);
462
+ }
463
+
464
+ /** Define .toRefs() method on the vue instance. */
465
+ defineProperty(vue, 'toRefs', {
466
+ get: (() => {
467
+ /** Lazy loaded toRefs function cache. */
468
+ let toRefsFn: IVueToRefsFn<T> | null = null;
469
+ return () =>
470
+ toRefsFn ?? (toRefsFn = ivueToRefs(vue, accessors, computeds));
471
+ })(),
472
+ enumerable: false,
473
+ });
474
+
475
+ /** Define .clone() method on the vue instance. */
476
+ defineProperty(vue, 'clone', {
477
+ value: (...cloneArgs: CloneArgsFor<InstanceType<T>>) =>
478
+ ivueClone(className, args, vue, accessors, methods, cloneArgs),
479
+ enumerable: false,
480
+ });
481
+
482
+ /** Mark as ivue instance */
483
+ defineProperty(vue, IVUE_INSTANCE_SYMBOL, {
484
+ value: true,
485
+ enumerable: false,
486
+ configurable: false,
487
+ writable: false,
289
488
  });
290
489
 
291
490
  /** Run ivue .init() initializer method, if it exists in the class. */
292
- if ('init' in vue) vue.init();
491
+ vue?.init?.(false);
293
492
 
294
493
  return vue;
295
- }
494
+ };
296
495
 
297
- /**
298
- * Store all props for each class prototype processed by:
299
- * @see {getAllClassProperties}
300
- */
301
- export const propsMap: Map<object, Set<string> | any> = new Map();
496
+ const ivueClone = <T extends AnyClass>(
497
+ className: T,
498
+ args: InferredArgs<T>,
499
+ vueSrc: IVue<T>,
500
+ accessors: Accessors,
501
+ methods: Set<string>,
502
+ cloneArgs: CloneArgsFor<InstanceType<T>> = [] as CloneArgsFor<InstanceType<T>>
503
+ ): IVue<T> => {
504
+ const computeds: Computeds | any = accessors?.size
505
+ ? createObject(null)
506
+ : null;
507
+
508
+ /** Create a reactive instance of the class. */
509
+ const vue = reactive(new className(...args));
510
+ const { onlyProps } = getClassPropertiesAccessorsMap(vue);
302
511
 
303
- /**
304
- * Get properties of an entire class prototype ancestors chain as a Map.
305
- */
306
- export function getAllClassProperties(obj: object): Set<string> {
307
- /* Retrieve props from cache */
308
- if (propsMap.has(obj.constructor)) {
309
- return propsMap.get(obj.constructor);
512
+ /** Clone properties. */
513
+ const cloneByRef = (className as any).ivueCloneByReference;
514
+ const deepCloneArgs = activeDeepCloneArgsMap.get(className);
515
+
516
+ if (cloneByRef) {
517
+ for (const prop of onlyProps) {
518
+ vue[prop] = cloneByRef.has(prop)
519
+ ? vueSrc[prop]
520
+ : deepClone(vueSrc[prop], deepCloneArgs);
521
+ }
522
+ } else {
523
+ for (const prop of onlyProps) {
524
+ vue[prop] = deepClone(vueSrc[prop], deepCloneArgs);
525
+ }
310
526
  }
311
527
 
312
- const originalConstructor = obj.constructor;
528
+ /** Setup accessors as computeds. */
529
+ for (const [prop, descriptor] of accessors) {
530
+ /** Convert descriptor to computed. */
531
+ defineProperty(vue, prop, {
532
+ get: () =>
533
+ computeds[prop] ??
534
+ (computeds[prop] = computed({
535
+ get: descriptor.get?.bind(vue) as unknown,
536
+ set: descriptor.set?.bind(vue),
537
+ } as any)) /** Create the computed and return it, because we are in reactive scope, .value will auto unwrap itself. */,
538
+ enumerable: descriptor.enumerable,
539
+ configurable: descriptor.configurable,
540
+ });
541
+ }
313
542
 
314
- const allProps: Set<string> = new Set();
315
- do {
316
- Object.getOwnPropertyNames(obj).forEach((prop) => {
317
- /* 'caller', 'callee', 'arguments', 'constructor' are
318
- * special object properties, so should be skipped. */
319
- if (!['caller', 'callee', 'arguments', 'constructor'].includes(prop)) {
320
- allProps.add(prop);
543
+ /** Disable reactivity for specified properties. */
544
+ const nonReactiveProps = (className as any)?.ivueDisableReactivity;
545
+ if (nonReactiveProps) {
546
+ for (const prop of nonReactiveProps) {
547
+ const descriptor = accessors.get(prop);
548
+ if (descriptor) {
549
+ defineProperty(vue, prop, {
550
+ get: descriptor.get?.bind(vue),
551
+ set: descriptor.set?.bind(vue),
552
+ enumerable: descriptor.enumerable,
553
+ configurable: descriptor.configurable,
554
+ });
555
+ } else {
556
+ vue[prop] = markRaw(vue[prop]);
321
557
  }
322
- });
323
- obj = Object.getPrototypeOf(obj);
324
- } while (obj.constructor !== Object);
558
+ }
559
+ }
325
560
 
326
- /** Save props in the props map. */
327
- propsMap.set(originalConstructor, allProps);
561
+ /** Bind all methods to the vue instance. */
562
+ const methodDescriptor: PropertyDescriptor = {
563
+ writable: true,
564
+ configurable: true,
565
+ enumerable: false,
566
+ };
567
+ for (const methodName of methods) {
568
+ methodDescriptor.value = vue[methodName].bind(vue);
569
+ defineProperty(vue, methodName, methodDescriptor);
570
+ }
328
571
 
329
- return allProps;
330
- }
572
+ /** Define .toRefs() method on the vue instance. */
573
+ defineProperty(vue, 'toRefs', {
574
+ get: (() => {
575
+ /** Lazy loaded toRefs function cache. */
576
+ let toRefsFn: IVueToRefsFn<T> | null = null;
577
+ return () =>
578
+ toRefsFn ?? (toRefsFn = ivueToRefs(vue, accessors, computeds));
579
+ })(),
580
+ enumerable: false,
581
+ });
582
+
583
+ /** Define .clone() method on the vue instance. */
584
+ defineProperty(vue, 'clone', {
585
+ value: (...cloneArgs: CloneArgsFor<InstanceType<T>>) =>
586
+ ivueClone(className, args, vue, accessors, methods, cloneArgs),
587
+ enumerable: false,
588
+ });
589
+
590
+ /** Mark as ivue instance */
591
+ defineProperty(vue, IVUE_INSTANCE_SYMBOL, {
592
+ value: true,
593
+ enumerable: false,
594
+ configurable: false,
595
+ writable: false,
596
+ });
597
+
598
+ /** Run ivue .init() initializer method, if it exists in the class. */
599
+ vue?.init?.(true, ...cloneArgs);
600
+
601
+ return vue;
602
+ };
331
603
 
332
604
  /**
333
605
  * `iref()` is an alias for Vue ref() function but returns an unwrapped type without the .value
334
606
  * `iref()` does not alter the behavior of ref(), but simply transforms the type to an unwrapped raw value.
335
607
  * @param val T
336
- * @returns {T}
608
+ * @returns {T} Ref but with type unwrapped
337
609
  */
338
610
  export const iref = ref as <T = any>(value?: T) => T;
339
611
 
@@ -341,132 +613,87 @@ export const iref = ref as <T = any>(value?: T) => T;
341
613
  * `ishallowRef()` is an alias for Vue shallowRef() function but returns an unwrapped type without the .value
342
614
  * `ishallowRef()` does not alter the behavior of shallowRef(), but simply transforms the type to an unwrapped raw value.
343
615
  * @param val T
344
- * @returns {T}
616
+ * @returns {T} ShallowRef but with type unwrapped
345
617
  */
346
618
  export const ishallowRef = shallowRef as <T = any>(value?: T) => T;
347
619
 
348
620
  /**
349
- * Three modes of operation:
350
- * 1. `iuse(useComposable(arg, arg2, arg3, ...))` converts the return types of a Composable / Ref to pure raw unwrapped type definition.
351
- * Returns for all properties of an object an unwrapped raw type definition,
352
- * unwraps direct Refs & ComputedRefs as well down to their raw types.
621
+ * `iuse()` is a helper function that unwraps any Vue 3 composable return type down to its bare types.
353
622
  *
354
- * 2. `iuse(useComposable, arg, arg2, arg3, ...)` for cleaner syntax for #1, it does exactly the same thing but
355
- * here the TypeScript inference works for composable function arguments to assist you with intellisence,
356
- * like they work for constructor arguments in the cause of `ivue()` core function,
357
- * making the API cleaner to look at and make it compatible with how this function operates with classes, see #3.
623
+ * It does not alter the behavior of the composable, but simply transforms the type to an unwrapped raw value.
358
624
  *
359
- * 3. `iuse(AnyClass, ...args)` If AnyClass is supplied and that class's arguments into `iuse(AnyClass, ...args)`,
360
- * it returns an 'ivue(AnyClass, ...args).toRefs()` object for all properties but casts
361
- * their types as raw (no-Ref) types to fit with reactive() structure of the ivue class context.
362
- */
363
- export function iuse<T extends AnyClass | AnyFn | Object | any>(
364
- classFunctionObject?: T,
365
- ...args: T extends AnyClass
366
- ? InferredArgs<T>
367
- : T extends AnyFn
368
- ? Parameters<T extends (...args: any[]) => any ? T : any>
369
- : any
370
- ): T extends AnyClass ? InstanceType<T> : Use<T> {
371
- return typeof classFunctionObject === 'function'
372
- ? isClass(classFunctionObject)
373
- ? /** Run IVUE but return full Refs, yet property 'true' makes `.toRefs(true)` cast the type to the unwrapped raw type definition instead of a `.value` Ref. */
374
- ivue(
375
- classFunctionObject as T extends AnyClass ? T : any,
376
- ...(args as InferredArgs<T extends AnyClass ? T : any>)
377
- ).toRefs(true)
378
- : /** Run Vue 3 Standard Composable but also unwrap it to bare raw types. */
379
- (classFunctionObject as AnyFn)(
380
- ...(args as Parameters<T extends AnyFn ? AnyFn : any>)
381
- )
382
- : (classFunctionObject as unknown as Unwrap<T>) /** Unwrap any other Object or any type down to its bare types. */;
383
- }
625
+ * @param obj Any Vue 3 composable return type or any object or any type
626
+ * @returns {Use<T>} Composable with types unwrapped
627
+ */
628
+ export const iuse = <T extends object>(obj: T): Use<T> =>
629
+ obj as unknown as Use<T>; /** Unwrap any other Object or any type down to its bare types. */
384
630
 
385
631
  /**
386
632
  * Convert reactive ivue class to Vue 3 refs.
387
633
  *
388
634
  * @param vue @see IVue
389
- * @param descriptors @see Descriptors
635
+ * @param accessors @see Accessors
390
636
  * @param computeds @see Computeds
391
637
  * @returns {ExtendWithToRefs<T>['toRefs']}
392
638
  */
393
- function ivueToRefs<T extends AnyClass>(
639
+ const ivueToRefs = <T extends AnyClass>(
394
640
  vue: IVue<T>,
395
- descriptors: Descriptors,
641
+ accessors: Accessors,
396
642
  computeds: Computeds
397
- ): ExtendWithToRefs<T>['toRefs'] {
643
+ ): IVueToRefsFn<T> => {
644
+ /** Caches for toRefs function */
645
+ /** Cached accessor function */
646
+ const getAccessor = accessors.get.bind(accessors);
647
+ /** Get all class properties */
648
+ const { allProps } = getClassPropertiesAccessorsMap(vue);
649
+ /** The actual toRefs function returned. */
398
650
  return function (
399
- props?: (keyof InstanceType<T>)[] | boolean,
651
+ props: (string & keyof InstanceType<T>)[] | boolean = false,
400
652
  unwrap?: boolean /** This property helps with TypeScript function definition overloading of return types and is not being used inside the function itself. */
401
653
  ): any /** @see {ReturnType<IVueToRefsFn<T>>} */ {
402
654
  /** Resulting refs store. */
403
- const result: Record<string | number | symbol, any> = {};
404
-
405
- /** Output specific props only, if props are specified. */
406
- if (Array.isArray(props)) {
407
- for (let i = 0; i < props.length; i++) {
408
- const prop = props[i] as any;
409
- /** Handle descriptors. */
410
- if (descriptors.has(prop)) {
411
- if (prop in computeds) {
412
- /** Return vue computed with .value from computeds store. */
413
- result[prop] = computeds[prop];
414
- } else {
415
- /** Initialize & store vue computed. */
416
- const descriptor = descriptors.get(prop);
417
- result[prop] = computeds[prop] = computed({
418
- get: descriptor?.get?.bind(vue) as any,
419
- set: descriptor?.set?.bind(vue),
420
- } as any);
421
- }
422
- } else {
423
- /** Handle methods. */
424
- if (typeof vue[prop] === 'function') {
425
- /** Bind method to vue, makes method destructuring point to right instance. */
426
- result[prop] = vue[prop].bind(vue);
427
- } else {
428
- /** Convert simple reactive prop to a Ref. */
429
- result[prop] = toRef(vue, prop);
430
- }
431
- }
655
+ const result: Record<string, any> = {};
656
+
657
+ /** Convert all props to refs and leave functions as is. */
658
+ if (isArray(props)) {
659
+ for (const prop of props) {
660
+ const kind = allProps.get(prop);
661
+ resolveIvueToRefs(result, prop, kind, vue, getAccessor, computeds);
432
662
  }
433
663
  } else {
434
- /** Convert all props to refs and leave functions as is. */
435
- let allProps: null | Set<string> = new Set(getAllClassProperties(vue));
436
-
437
- /** Convert descriptors (non enumerable by default in JS). */
438
- descriptors.forEach((descriptor, prop) => {
439
- if (prop in computeds) {
440
- /** Return vue computed with .value from computeds store. */
441
- result[prop] = computeds[prop];
442
- } else {
443
- /** Initialize vue computed ref & store it in result. */
444
- result[prop] = computeds[prop] = computed({
445
- get: descriptor.get?.bind(vue) as any,
446
- set: descriptor.set?.bind(vue),
447
- } as any);
448
- }
449
- /** Delete descriptor from props as it was already processed. */
450
- allProps?.delete(prop as string);
451
- });
452
-
453
- allProps.forEach((prop) => {
454
- if (typeof vue[prop] === 'function') {
455
- /** Bind method to vue, makes method destructuring point to right instance. */
456
- result[prop] = vue[prop].bind(vue);
457
- } else {
458
- /** Convert simple reactive prop to a Ref. */
459
- result[prop] = toRef(vue, prop);
460
- }
461
- });
462
-
463
- /** Memory optimization. */
464
- allProps = null;
664
+ for (const [prop, kind] of allProps.entries()) {
665
+ resolveIvueToRefs(result, prop, kind, vue, getAccessor, computeds);
666
+ }
465
667
  }
466
668
 
467
- return result as any;
669
+ return result as any; /** @see {ReturnType<IVueToRefsFn<T>>} */
468
670
  };
469
- }
671
+ };
672
+
673
+ /**
674
+ * Resolve property to ref, method or computed and assign it to result.
675
+ * @private
676
+ */
677
+ const resolveIvueToRefs = (
678
+ result: Record<string, any>,
679
+ prop: string,
680
+ kind: PropKind | undefined,
681
+ vue: IVue<any>,
682
+ getAccessor: Accessors['get'],
683
+ computeds: Record<string, ComputedRef<any>>
684
+ ): void => {
685
+ if (kind === PropKind.Accessor) {
686
+ const descriptor = getAccessor(prop) as PropertyDescriptor;
687
+ result[prop] =
688
+ computeds[prop] ??
689
+ (computeds[prop] = computed({
690
+ get: descriptor.get?.bind(vue) as unknown,
691
+ set: descriptor.set?.bind(vue),
692
+ } as any));
693
+ } else if (kind === PropKind.Data) {
694
+ result[prop] = toRef(vue, prop);
695
+ }
696
+ };
470
697
 
471
698
  /**
472
699
  * Vue props interface in defineComponent() style.
@@ -494,19 +721,19 @@ export type VuePropsWithDefaults<T extends VuePropsObject> = {
494
721
  * @param val Any value
495
722
  * @returns boolean If it's a JavaScript Class returns true
496
723
  */
497
- export function isClass(val: any): boolean {
724
+ export const isClass = (val: any): boolean => {
498
725
  if (typeof val !== 'function') return false; // Not a function, so not a class function either
499
726
 
500
727
  if (!val.prototype) return false; // Arrow function, so not a class
501
728
 
502
729
  // Finally -> distinguish between a normal function and a class function
503
- if (Object.getOwnPropertyDescriptor(val, 'prototype')?.writable) {
730
+ if (getOwnPropertyDescriptor(val, 'prototype')?.writable) {
504
731
  // Has writable prototype
505
732
  return false; // Normal function
506
733
  } else {
507
734
  return true; // Class -> Not a function
508
735
  }
509
- }
736
+ };
510
737
 
511
738
  /**
512
739
  * Creates props with defaults in defineComponent() style.
@@ -533,26 +760,214 @@ export function isClass(val: any): boolean {
533
760
  * @param typedProps Props declared in defineComponent() style with type and possibly required declared, but without default
534
761
  * @returns Props declared in defineComponent() style with all properties having default property declared.
535
762
  */
536
- export function propsWithDefaults<T extends VuePropsObject>(
763
+ export const propsWithDefaults = <T extends VuePropsObject>(
537
764
  defaults: Record<string, any>,
538
- typedProps: T
539
- ): VuePropsWithDefaults<T> {
765
+ typedProps: T,
766
+ deepCloneArgs: IVueDeepCloneArgsMap | undefined = undefined
767
+ ): VuePropsWithDefaults<T> => {
540
768
  for (const prop in typedProps) {
541
- if (typeof defaults?.[prop] === 'object' && defaults?.[prop] !== null) {
769
+ const def = defaults?.[prop];
770
+ const typed = typedProps[prop];
771
+
772
+ /** Skip if prop is required — Vue will enforce it at runtime */
773
+ if (typed.required) continue;
774
+
775
+ /** Skip if default is undefined */
776
+ if (def === undefined) continue;
777
+
778
+ if (typeof def === 'object' && def !== null) {
542
779
  /** Handle Arrays & Objects -> wrap them with an arrow function. */
543
- typedProps[prop].default = () => defaults?.[prop];
780
+ typedProps[prop].default = () => deepClone(def, deepCloneArgs);
544
781
  } else {
545
- if (isClass(defaults?.[prop])) {
782
+ if (isClass(def)) {
546
783
  /** Handle JavaScript Classes -> wrap them with an arrow function */
547
- typedProps[prop].default = () => defaults?.[prop];
784
+ typedProps[prop].default = () => def;
548
785
  } else {
549
786
  /** Handle JavaScript Function And All primitive properties -> output directly */
550
- typedProps[prop].default = defaults?.[prop];
787
+ typedProps[prop].default = def;
551
788
  }
552
789
  }
553
790
  }
554
791
  return typedProps as VuePropsWithDefaults<T>;
555
- }
792
+ };
793
+
794
+ /**
795
+ * Copies own properties (including symbols and non-enumerable) from source to target.
796
+ * Recursively deep clones values, but preserves getters/setters as-is.
797
+ */
798
+ export const copyOwnProps = (
799
+ source: any,
800
+ target: any,
801
+ deepCloneArgs: IVueDeepCloneArgsMap | undefined,
802
+ seen: WeakMap<object, any>
803
+ ) => {
804
+ // 1. Get all descriptors (String keys AND Symbol keys)
805
+ const descriptors = Object.getOwnPropertyDescriptors(source);
806
+
807
+ // 2. Iterate over all keys returned by Reflect (safety for Proxies/Environments)
808
+ for (const key of Reflect.ownKeys(descriptors)) {
809
+ const desc = descriptors[key as any];
810
+
811
+ // 3. If it is a data descriptor (has a value), we must deep clone the value.
812
+ // We do NOT clone getters/setters (we copy the function reference).
813
+ if ('value' in desc) {
814
+ desc.value = deepClone(desc.value, deepCloneArgs, seen);
815
+ }
816
+
817
+ // 4. Define on target
818
+ Object.defineProperty(target, key, desc);
819
+ }
820
+ };
821
+
822
+ export let activeDeepCloneArgsMap = new WeakMap<
823
+ AnyClass,
824
+ IVueDeepCloneArgsMap
825
+ >();
826
+
827
+ export const deepClone = (
828
+ source: any,
829
+ deepCloneArgs?: IVueDeepCloneArgsMap,
830
+ seen: WeakMap<object, any> = new WeakMap()
831
+ ): any => {
832
+ // --- TIER 1: The "Every Call" Checks (Fastest) ---
833
+ if (source == null || typeof source !== 'object') return source;
834
+ if (seen.has(source)) return seen.get(source);
835
+
836
+ // --- TIER 2: The "Hottest" Collection (Arrays) ---
837
+ if (isArray(source)) {
838
+ const len = source.length;
839
+ const out = new Array(len);
840
+ seen.set(source, out);
841
+ for (let i = 0; i < len; i++) {
842
+ out[i] = deepClone(source[i], deepCloneArgs, seen);
843
+ }
844
+ return out;
845
+ }
846
+
847
+ // --- TIER 3: The "Hottest" Objects (Plain Objects) ---
848
+ const prototype = getPrototypeOf(source);
849
+ if (prototype === Object.prototype || prototype === null) {
850
+ const out = createObject(prototype);
851
+ seen.set(source, out);
852
+ copyOwnProps(source, out, deepCloneArgs, seen);
853
+ return out;
854
+ }
855
+
856
+ // --- TIER 4: IVue Special Logic (Your Framework) ---
857
+ // Checked here because it's specific to your domain
858
+ if (source[IVUE_INSTANCE_SYMBOL]) {
859
+ const srcClass = source.constructor as IVueClass<any>;
860
+ if (srcClass.ivueGlobalStore) {
861
+ seen.set(source, source);
862
+ return source;
863
+ }
864
+ let out;
865
+ if (deepCloneArgs?.has(srcClass)) {
866
+ activeDeepCloneArgsMap.set(srcClass, deepCloneArgs);
867
+ try {
868
+ out = source.clone(...(deepCloneArgs.get(srcClass) ?? []));
869
+ } finally {
870
+ activeDeepCloneArgsMap.delete(srcClass);
871
+ }
872
+ } else {
873
+ out = source.clone();
874
+ }
875
+ seen.set(source, out);
876
+ return out;
877
+ }
878
+
879
+ // --- TIER 5: Common Built-ins (Sorted by Likelihood) ---
880
+ // instanceof is fast, but order matters slightly for CPU branch prediction
881
+ if (source instanceof Date) {
882
+ return new Date(source.getTime());
883
+ }
884
+ if (source instanceof Map) {
885
+ const out = new (source.constructor as any)();
886
+ seen.set(source, out);
887
+ for (const [k, v] of source) {
888
+ out.set(
889
+ deepClone(k, deepCloneArgs, seen),
890
+ deepClone(v, deepCloneArgs, seen)
891
+ );
892
+ }
893
+ return out;
894
+ }
895
+ if (source instanceof Set) {
896
+ const out = new (source.constructor as any)();
897
+ seen.set(source, out);
898
+ for (const v of source) {
899
+ out.add(deepClone(v, deepCloneArgs, seen));
900
+ }
901
+ return out;
902
+ }
903
+
904
+ // --- TIER 6: Rare / Heavy Objects (The "Slow" Tail) ---
905
+ if (source instanceof RegExp) {
906
+ const out = new RegExp(source.source, source.flags);
907
+ out.lastIndex = source.lastIndex;
908
+ return out;
909
+ }
910
+ if (source instanceof Error) {
911
+ const out = new (source.constructor as any)(source.message);
912
+ seen.set(source, out);
913
+ copyOwnProps(source, out, deepCloneArgs, seen);
914
+ return out;
915
+ }
916
+ if (
917
+ source instanceof Promise ||
918
+ source instanceof WeakMap ||
919
+ source instanceof WeakSet
920
+ ) {
921
+ seen.set(source, source);
922
+ return source;
923
+ }
924
+ if (source instanceof DataView) {
925
+ // Clone the underlying buffer to ensure deep copy
926
+ const out = new DataView(
927
+ source.buffer.slice(0),
928
+ source.byteOffset,
929
+ source.byteLength
930
+ );
931
+ seen.set(source, out);
932
+ return out;
933
+ }
934
+ // Binary data (less common in UI state, but supported)
935
+ if (ArrayBuffer.isView(source) && !(source instanceof DataView)) {
936
+ const out = new (source.constructor as any)(source);
937
+ seen.set(source, out);
938
+ return out;
939
+ }
940
+ if (source instanceof ArrayBuffer) {
941
+ const out = source.slice(0);
942
+ seen.set(source, out);
943
+ return out;
944
+ }
945
+
946
+ // --- TIER 7: Final Fallback (Custom User Classes) ---
947
+ // If it's a class instance not handled above
948
+ const out = createObject(prototype);
949
+ seen.set(source, out);
950
+ copyOwnProps(source, out, deepCloneArgs, seen);
951
+ return out;
952
+ };
953
+
954
+ /**
955
+ * Clears the accessorsMethodsMaps, and propertiesAccessorsMaps WeakMaps.
956
+ * Useful for SSR scenarios where you want to reset the cache between requests.
957
+ * Also useful for testing purposes to ensure a clean state.
958
+ */
959
+ export const clearCache = () => {
960
+ accessorsMethodsMaps = new WeakMap();
961
+ propertiesAccessorsMaps = new WeakMap();
962
+ activeDeepCloneArgsMap = new WeakMap();
963
+ };
964
+
965
+ /** Exported for testing purposes. */
966
+ export const __test__ = {
967
+ copyOwnProps,
968
+ getClassAccessorsMethodsMap,
969
+ getClassPropertiesAccessorsMap,
970
+ };
556
971
 
557
972
  /** Necessary ivue.ts to be treated as a module. */
558
973
  export {};