ivue 1.5.8 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/ivue.ts CHANGED
@@ -1,35 +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
19
  export type IVue<T extends AnyClass> = InstanceType<T> & {
12
20
  toRefs: IVueToRefsFn<T>;
21
+ clone: IVueClone<T>;
13
22
  };
14
23
 
24
+ /**
25
+ * Type definition for `.clone()` method creates a deep clone of the ivue instance.
26
+ */
27
+ export type IVueClone<T extends AnyClass> = (
28
+ ...cloneArgs: CloneArgsFor<InstanceType<T>>
29
+ ) => IVue<T>;
30
+
15
31
  /**
16
32
  * Type definition for `.toRefs()` method converts reactive class properties to composable .value properties.
17
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.
18
34
  */
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
+ */
19
46
  interface IVueToRefsFn<T extends AnyClass> {
20
- <P extends keyof InstanceType<T>>(props: P[]): Pick<
47
+ <P extends keyof IVueRefs<InstanceType<T>>>(props: P[]): Pick<
21
48
  IVueRefs<InstanceType<T>>,
22
49
  P
23
50
  >;
24
- <P extends keyof InstanceType<T>>(props: P[], unwrap: false): Pick<
51
+
52
+ <P extends keyof IVueRefs<InstanceType<T>>>(props: P[], unwrap: false): Pick<
25
53
  IVueRefs<InstanceType<T>>,
26
54
  P
27
55
  >;
28
- <P extends keyof InstanceType<T>>(props: P[], unwrap: true): Pick<
29
- InstanceType<T>,
30
- P
31
- >;
32
- (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>>;
33
63
  (props: false): IVueRefs<InstanceType<T>>;
34
64
  (): IVueRefs<InstanceType<T>>;
35
65
  }
@@ -39,7 +69,7 @@ interface IVueToRefsFn<T extends AnyClass> {
39
69
  * also knows NOT to convert functions to .value Refs but to leave them as is.
40
70
  */
41
71
  export type IVueRefs<T> = {
42
- [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]>;
43
73
  };
44
74
 
45
75
  /**
@@ -72,7 +102,7 @@ export type Use<T = any> = T extends Ref | DemiRef
72
102
  : UnwrapComposableRefs<T extends AnyFn ? ReturnType<T> : T>;
73
103
 
74
104
  /**
75
- * 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.
76
106
  */
77
107
  export type ExtractEmitTypes<T extends Record<string, any>> =
78
108
  UnionToIntersection<
@@ -104,12 +134,12 @@ export type AnyFn = (...args: any[]) => any;
104
134
  /**
105
135
  * Any JavaScript class of any type.
106
136
  */
107
- export type AnyClass = abstract new (...args: any[]) => any;
137
+ export type AnyClass = new (...args: any[]) => any;
108
138
 
109
139
  /**
110
- * Descriptors map type.
140
+ * Accessors map type.
111
141
  */
112
- type Descriptors = Map<string, PropertyDescriptor>;
142
+ type Accessors = Map<string, PropertyDescriptor>;
113
143
 
114
144
  /**
115
145
  * Computeds hash map type.
@@ -130,23 +160,6 @@ export type PrefixKeys<T, P extends string | undefined = undefined> = {
130
160
  [K in Extract<keyof T, string> as P extends string ? `${P}${K}` : K]: T[K];
131
161
  };
132
162
 
133
- /**
134
- * Get Interface's property's function arguments Parmeters<F>
135
- */
136
- export type IFnParameters<
137
- T extends Record<any, any>,
138
- K extends string
139
- > = Parameters<Required<Pick<T, K>>[K]>;
140
-
141
- /**
142
- * Get Interface's [T] property's [P] function arguments Parmeters<F> parameter by key [K]
143
- */
144
- export type IFnParameter<
145
- T extends Record<any, any>,
146
- P extends keyof T,
147
- K extends number
148
- > = FnParameter<ValueOf<T, P>, K>;
149
-
150
163
  /**
151
164
  * Get function arguments Parmeters<F> parameter by key K
152
165
  */
@@ -155,80 +168,226 @@ export type FnParameter<F extends AnyFn, K extends number> = Parameters<F>[K];
155
168
  /**
156
169
  * Convert Union Type to Intersection Type.
157
170
  */
158
- export type UnionToIntersection<U> = (
159
- U extends any ? (k: U) => void : never
160
- ) extends (k: infer I) => void
171
+ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
172
+ k: infer I
173
+ ) => void
161
174
  ? I
162
175
  : never;
163
176
 
164
177
  /**
165
178
  * Convert Record to Union Type.
166
179
  */
167
- export type RecordToUnion<T extends Record<string, any>> = T[keyof T];
180
+ type RecordToUnion<T extends Record<string, any>> = T[keyof T];
168
181
 
169
182
  /**
170
183
  * Gets object T property by key [K].
171
184
  */
172
- 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
+ : [];
173
266
  /** Types End. */
174
267
 
268
+ export const IVUE_INSTANCE_SYMBOL = Symbol('IVUE_INSTANCE');
269
+
175
270
  /**
176
- * Store all descriptors for each class prototype processed by
177
- * @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.
178
274
  */
179
- export const descriptorsMap = new Map();
275
+ export let accessorsMethodsMaps = new WeakMap<object, AccessorsMethodsMap>();
180
276
 
181
277
  /**
182
- * 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.
183
279
  * Completely emulates JavaScript class inheritance chain for getters and setters.
184
280
  *
185
281
  * @param className
186
- * @return {Descriptors}
282
+ * @return {AccessorsMethodsMap}
187
283
  */
188
- export function getAllClassDescriptors(className: AnyClass): Descriptors {
189
- /* Retrieve descriptors from cache */
190
- if (descriptorsMap.has(className)) {
191
- return descriptorsMap.get(className);
284
+ const getClassAccessorsMethodsMap = (
285
+ className: AnyClass
286
+ ): AccessorsMethodsMap => {
287
+ if (accessorsMethodsMaps.has(className)) {
288
+ return accessorsMethodsMaps.get(className) as AccessorsMethodsMap;
192
289
  }
193
290
 
194
- const savedDescriptors: Descriptors = new Map();
291
+ const savedAccessors: Accessors = new Map();
292
+ const savedMethods: Set<string> = new Set();
195
293
 
196
294
  let prototype = className.prototype;
197
- while (prototype.constructor !== Object) {
198
- Object.entries(Object.getOwnPropertyDescriptors(prototype)).forEach(
199
- ([propertyName, currentDescriptor]) => {
200
- const savedDescriptor = savedDescriptors.get(propertyName);
201
- /** Emulate inheritance chain of getters and setters. */
202
- if (
203
- savedDescriptor !== currentDescriptor &&
204
- (currentDescriptor?.get || currentDescriptor?.set)
205
- ) {
206
- /** Overwrite descriptors if they were already set. */
207
- if (savedDescriptor?.get)
208
- currentDescriptor.get = savedDescriptor?.get;
209
- if (savedDescriptor?.set)
210
- currentDescriptor.set = savedDescriptor?.set;
211
-
212
- /** Store descriptors. */
213
- 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);
214
304
  }
305
+ } else if (typeof descriptor.value === 'function') {
306
+ savedMethods.add(propertyName);
215
307
  }
216
- );
217
- /** Walk up the prototype ancestors chain. */
218
- prototype = Object.getPrototypeOf(prototype);
308
+ }
309
+ prototype = getPrototypeOf(prototype);
219
310
  }
220
311
 
221
- /** Save descriptors in the descriptors map for each class. */
222
- descriptorsMap.set(className, savedDescriptors);
312
+ const propertyMap = {
313
+ accessors: savedAccessors,
314
+ methods: savedMethods,
315
+ };
316
+ accessorsMethodsMaps.set(className, propertyMap);
223
317
 
224
- 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,
225
327
  }
226
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
+
227
386
  /**
228
387
  * Infinite Vue (ivue) class reactive initializer.
229
388
  *
230
389
  * Converts class instance to a reactive object,
231
- * where descriptors are converted to computeds.
390
+ * where accessors are converted to computeds.
232
391
  *
233
392
  * You can turn off computed behaviour by adding static
234
393
  * ivue object and setting the getter props to false.
@@ -244,91 +403,209 @@ export function getAllClassDescriptors(className: AnyClass): Descriptors {
244
403
  * @param args Class constructor arguments that you would pass to a `new AnyClass(args...)`
245
404
  * @returns {IVue<T>}
246
405
  */
247
- export function ivue<T extends AnyClass>(
406
+ export const ivue = <T extends AnyClass>(
248
407
  className: T,
249
408
  ...args: InferredArgs<T>
250
- ): IVue<T> {
251
- const descriptors: Descriptors = getAllClassDescriptors(className);
252
- 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;
253
414
 
254
- // @ts-expect-error Abstract class initialization
415
+ /** Create a reactive instance of the class. */
255
416
  const vue = reactive(new className(...args));
256
417
 
257
- /** Setup descriptors as computeds. */
258
- for (const [prop, descriptor] of descriptors) {
259
- /* If prop exists on static getter className.ivue[prop]
260
- * We do not convert it to computed. Because sometimes
261
- * we want a normal getter. */
262
- if ((className as any)?.ivue?.[prop] === false) continue;
418
+ /** Setup accessors as computeds. */
419
+ for (const [prop, descriptor] of accessors) {
263
420
  /** Convert descriptor to computed. */
264
- Object.defineProperty(vue, prop, {
265
- get: descriptor.get
266
- ? () =>
267
- prop in computeds
268
- ? /** Get the existing computed, because we are in reactive scope, .value will auto unwrap itself. */
269
- computeds[prop]
270
- : /** Create the computed and return it, because we are in reactive scope, .value will auto unwrap itself. */
271
- (computeds[prop] = computed({
272
- get: descriptor.get?.bind(vue) as any,
273
- set: descriptor.set?.bind(vue),
274
- } as any))
275
- : undefined,
276
- set: descriptor.set?.bind(vue),
277
- 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,
278
430
  });
279
431
  }
280
432
 
281
- Object.defineProperty(vue, 'toRefs', {
282
- 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,
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,
283
485
  enumerable: false,
486
+ configurable: false,
487
+ writable: false,
284
488
  });
285
489
 
286
490
  /** Run ivue .init() initializer method, if it exists in the class. */
287
- if ('init' in vue) vue.init();
491
+ vue?.init?.(false);
288
492
 
289
493
  return vue;
290
- }
494
+ };
291
495
 
292
- /**
293
- * Store all props for each class prototype processed by:
294
- * @see {getAllClassProperties}
295
- */
296
- 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);
297
511
 
298
- /**
299
- * Get properties of an entire class prototype ancestors chain as a Map.
300
- */
301
- export function getAllClassProperties(obj: object): Set<string> {
302
- /* Retrieve props from cache */
303
- if (propsMap.has(obj.constructor)) {
304
- 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
+ }
305
526
  }
306
527
 
307
- 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
+ }
308
542
 
309
- const allProps: Set<string> = new Set();
310
- do {
311
- Object.getOwnPropertyNames(obj).forEach((prop) => {
312
- /* 'caller', 'callee', 'arguments', 'constructor' are
313
- * special object properties, so should be skipped. */
314
- if (!['caller', 'callee', 'arguments', 'constructor'].includes(prop)) {
315
- 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]);
316
557
  }
317
- });
318
- obj = Object.getPrototypeOf(obj);
319
- } while (obj.constructor !== Object);
558
+ }
559
+ }
320
560
 
321
- /** Save props in the props map. */
322
- 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
+ }
323
571
 
324
- return allProps;
325
- }
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
+ };
326
603
 
327
604
  /**
328
605
  * `iref()` is an alias for Vue ref() function but returns an unwrapped type without the .value
329
606
  * `iref()` does not alter the behavior of ref(), but simply transforms the type to an unwrapped raw value.
330
607
  * @param val T
331
- * @returns {T}
608
+ * @returns {T} Ref but with type unwrapped
332
609
  */
333
610
  export const iref = ref as <T = any>(value?: T) => T;
334
611
 
@@ -336,132 +613,87 @@ export const iref = ref as <T = any>(value?: T) => T;
336
613
  * `ishallowRef()` is an alias for Vue shallowRef() function but returns an unwrapped type without the .value
337
614
  * `ishallowRef()` does not alter the behavior of shallowRef(), but simply transforms the type to an unwrapped raw value.
338
615
  * @param val T
339
- * @returns {T}
616
+ * @returns {T} ShallowRef but with type unwrapped
340
617
  */
341
618
  export const ishallowRef = shallowRef as <T = any>(value?: T) => T;
342
619
 
343
620
  /**
344
- * Three modes of operation:
345
- * 1. `iuse(useComposable(arg, arg2, arg3, ...))` converts the return types of a Composable / Ref to pure raw unwrapped type definition.
346
- * Returns for all properties of an object an unwrapped raw type definition,
347
- * 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.
348
622
  *
349
- * 2. `iuse(useComposable, arg, arg2, arg3, ...)` for cleaner syntax for #1, it does exactly the same thing but
350
- * here the TypeScript inference works for composable function arguments to assist you with intellisence,
351
- * like they work for constructor arguments in the cause of `ivue()` core function,
352
- * 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.
353
624
  *
354
- * 3. `iuse(AnyClass, ...args)` If AnyClass is supplied and that class's arguments into `iuse(AnyClass, ...args)`,
355
- * it returns an 'ivue(AnyClass, ...args).toRefs()` object for all properties but casts
356
- * their types as raw (no-Ref) types to fit with reactive() structure of the ivue class context.
357
- */
358
- export function iuse<T extends AnyClass | AnyFn | Object | any>(
359
- classFunctionObject?: T,
360
- ...args: T extends AnyClass
361
- ? InferredArgs<T>
362
- : T extends AnyFn
363
- ? Parameters<T extends (...args: any[]) => any ? T : any>
364
- : any
365
- ): T extends AnyClass ? InstanceType<T> : Use<T> {
366
- return typeof classFunctionObject === 'function'
367
- ? isClass(classFunctionObject)
368
- ? /** 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. */
369
- ivue(
370
- classFunctionObject as T extends AnyClass ? T : any,
371
- ...(args as InferredArgs<T extends AnyClass ? T : any>)
372
- ).toRefs(true)
373
- : /** Run Vue 3 Standard Composable but also unwrap it to bare raw types. */
374
- (classFunctionObject as AnyFn)(
375
- ...(args as Parameters<T extends AnyFn ? AnyFn : any>)
376
- )
377
- : (classFunctionObject as unknown as Use<T>) /** Unwrap any other Object or any type down to its bare types. */;
378
- }
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. */
379
630
 
380
631
  /**
381
632
  * Convert reactive ivue class to Vue 3 refs.
382
633
  *
383
634
  * @param vue @see IVue
384
- * @param descriptors @see Descriptors
635
+ * @param accessors @see Accessors
385
636
  * @param computeds @see Computeds
386
637
  * @returns {ExtendWithToRefs<T>['toRefs']}
387
638
  */
388
- function ivueToRefs<T extends AnyClass>(
639
+ const ivueToRefs = <T extends AnyClass>(
389
640
  vue: IVue<T>,
390
- descriptors: Descriptors,
641
+ accessors: Accessors,
391
642
  computeds: Computeds
392
- ): IVueToRefsFn<T> {
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. */
393
650
  return function (
394
- props?: (keyof InstanceType<T>)[] | boolean,
651
+ props: (string & keyof InstanceType<T>)[] | boolean = false,
395
652
  unwrap?: boolean /** This property helps with TypeScript function definition overloading of return types and is not being used inside the function itself. */
396
653
  ): any /** @see {ReturnType<IVueToRefsFn<T>>} */ {
397
654
  /** Resulting refs store. */
398
- const result: Record<string | number | symbol, any> = {};
399
-
400
- /** Output specific props only, if props are specified. */
401
- if (Array.isArray(props)) {
402
- for (let i = 0; i < props.length; i++) {
403
- const prop = props[i] as any;
404
- /** Handle descriptors. */
405
- if (descriptors.has(prop)) {
406
- if (prop in computeds) {
407
- /** Return vue computed with .value from computeds store. */
408
- result[prop] = computeds[prop];
409
- } else {
410
- /** Initialize & store vue computed. */
411
- const descriptor = descriptors.get(prop);
412
- result[prop] = computeds[prop] = computed({
413
- get: descriptor?.get?.bind(vue) as any,
414
- set: descriptor?.set?.bind(vue),
415
- } as any);
416
- }
417
- } else {
418
- /** Handle methods. */
419
- if (typeof vue[prop] === 'function') {
420
- /** Bind method to vue, makes method destructuring point to right instance. */
421
- result[prop] = vue[prop].bind(vue);
422
- } else {
423
- /** Convert simple reactive prop to a Ref. */
424
- result[prop] = toRef(vue, prop);
425
- }
426
- }
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);
427
662
  }
428
663
  } else {
429
- /** Convert all props to refs and leave functions as is. */
430
- let allProps: null | Set<string> = new Set(getAllClassProperties(vue));
431
-
432
- /** Convert descriptors (non enumerable by default in JS). */
433
- descriptors.forEach((descriptor, prop) => {
434
- if (prop in computeds) {
435
- /** Return vue computed with .value from computeds store. */
436
- result[prop] = computeds[prop];
437
- } else {
438
- /** Initialize vue computed ref & store it in result. */
439
- result[prop] = computeds[prop] = computed({
440
- get: descriptor.get?.bind(vue) as any,
441
- set: descriptor.set?.bind(vue),
442
- } as any);
443
- }
444
- /** Delete descriptor from props as it was already processed. */
445
- allProps?.delete(prop as string);
446
- });
447
-
448
- allProps.forEach((prop) => {
449
- if (typeof vue[prop] === 'function') {
450
- /** Bind method to vue, makes method destructuring point to right instance. */
451
- result[prop] = vue[prop].bind(vue);
452
- } else {
453
- /** Convert simple reactive prop to a Ref. */
454
- result[prop] = toRef(vue, prop);
455
- }
456
- });
457
-
458
- /** Memory optimization. */
459
- allProps = null;
664
+ for (const [prop, kind] of allProps.entries()) {
665
+ resolveIvueToRefs(result, prop, kind, vue, getAccessor, computeds);
666
+ }
460
667
  }
461
668
 
462
- return result as any;
669
+ return result as any; /** @see {ReturnType<IVueToRefsFn<T>>} */
463
670
  };
464
- }
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
+ };
465
697
 
466
698
  /**
467
699
  * Vue props interface in defineComponent() style.
@@ -489,19 +721,19 @@ export type VuePropsWithDefaults<T extends VuePropsObject> = {
489
721
  * @param val Any value
490
722
  * @returns boolean If it's a JavaScript Class returns true
491
723
  */
492
- export function isClass(val: any): boolean {
724
+ export const isClass = (val: any): boolean => {
493
725
  if (typeof val !== 'function') return false; // Not a function, so not a class function either
494
726
 
495
727
  if (!val.prototype) return false; // Arrow function, so not a class
496
728
 
497
729
  // Finally -> distinguish between a normal function and a class function
498
- if (Object.getOwnPropertyDescriptor(val, 'prototype')?.writable) {
730
+ if (getOwnPropertyDescriptor(val, 'prototype')?.writable) {
499
731
  // Has writable prototype
500
732
  return false; // Normal function
501
733
  } else {
502
734
  return true; // Class -> Not a function
503
735
  }
504
- }
736
+ };
505
737
 
506
738
  /**
507
739
  * Creates props with defaults in defineComponent() style.
@@ -528,26 +760,214 @@ export function isClass(val: any): boolean {
528
760
  * @param typedProps Props declared in defineComponent() style with type and possibly required declared, but without default
529
761
  * @returns Props declared in defineComponent() style with all properties having default property declared.
530
762
  */
531
- export function propsWithDefaults<T extends VuePropsObject>(
763
+ export const propsWithDefaults = <T extends VuePropsObject>(
532
764
  defaults: Record<string, any>,
533
- typedProps: T
534
- ): VuePropsWithDefaults<T> {
765
+ typedProps: T,
766
+ deepCloneArgs: IVueDeepCloneArgsMap | undefined = undefined
767
+ ): VuePropsWithDefaults<T> => {
535
768
  for (const prop in typedProps) {
536
- 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) {
537
779
  /** Handle Arrays & Objects -> wrap them with an arrow function. */
538
- typedProps[prop].default = () => defaults?.[prop];
780
+ typedProps[prop].default = () => deepClone(def, deepCloneArgs);
539
781
  } else {
540
- if (isClass(defaults?.[prop])) {
782
+ if (isClass(def)) {
541
783
  /** Handle JavaScript Classes -> wrap them with an arrow function */
542
- typedProps[prop].default = () => defaults?.[prop];
784
+ typedProps[prop].default = () => def;
543
785
  } else {
544
786
  /** Handle JavaScript Function And All primitive properties -> output directly */
545
- typedProps[prop].default = defaults?.[prop];
787
+ typedProps[prop].default = def;
546
788
  }
547
789
  }
548
790
  }
549
791
  return typedProps as VuePropsWithDefaults<T>;
550
- }
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
+ };
551
971
 
552
972
  /** Necessary ivue.ts to be treated as a module. */
553
973
  export {};