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/README.md +5 -5
- package/dist/env.d.ts +1 -0
- package/dist/index.es.js +212 -86
- package/dist/index.umd.js +1 -1
- package/dist/ivue.d.ts +134 -71
- package/lib/Reactive.ts +338 -0
- package/lib/__tests__/ivue.vitest.spec.ts +1403 -150
- package/lib/env.d.ts +1 -0
- package/lib/index.ts +1 -0
- package/lib/ivue.ts +667 -252
- package/lib/kernel.ts +18 -0
- package/package.json +16 -7
package/dist/ivue.d.ts
CHANGED
|
@@ -1,25 +1,35 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ExtractPropTypes, Ref, ToRef } from 'vue';
|
|
2
2
|
import type { Ref as DemiRef } from 'vue-demi';
|
|
3
3
|
/** Types */
|
|
4
4
|
/**
|
|
5
5
|
* IVue core reactive instance type with an extended .toRefs() method added.
|
|
6
6
|
*/
|
|
7
|
-
export declare type IVue<T extends AnyClass> = InstanceType<T> &
|
|
7
|
+
export declare type IVue<T extends AnyClass> = InstanceType<T> & {
|
|
8
|
+
toRefs: IVueToRefsFn<T>;
|
|
9
|
+
clone: IVueClone<T>;
|
|
10
|
+
};
|
|
8
11
|
/**
|
|
9
|
-
*
|
|
12
|
+
* Type definition for `.clone()` method creates a deep clone of the ivue instance.
|
|
10
13
|
*/
|
|
11
|
-
export
|
|
12
|
-
toRefs: IVueToRefsFn<T>;
|
|
13
|
-
}
|
|
14
|
+
export declare type IVueClone<T extends AnyClass> = (...cloneArgs: CloneArgsFor<InstanceType<T>>) => IVue<T>;
|
|
14
15
|
/**
|
|
15
16
|
* Type definition for `.toRefs()` method converts reactive class properties to composable .value properties.
|
|
16
17
|
* 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.
|
|
17
18
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
/** Extract only non-method keys from a type */
|
|
20
|
+
declare type NonMethodKeys<T> = {
|
|
21
|
+
[K in keyof T]: T[K] extends AnyFn ? never : K;
|
|
22
|
+
}[keyof T];
|
|
23
|
+
/** Create a type with only data properties and accessors */
|
|
24
|
+
declare type DataProperties<T> = Pick<T, NonMethodKeys<T>>;
|
|
25
|
+
/**
|
|
26
|
+
* Simplified IVueToRefsFn that only works with data properties
|
|
27
|
+
*/
|
|
28
|
+
interface IVueToRefsFn<T extends AnyClass> {
|
|
29
|
+
<P extends keyof IVueRefs<InstanceType<T>>>(props: P[]): Pick<IVueRefs<InstanceType<T>>, P>;
|
|
30
|
+
<P extends keyof IVueRefs<InstanceType<T>>>(props: P[], unwrap: false): Pick<IVueRefs<InstanceType<T>>, P>;
|
|
31
|
+
<P extends keyof DataProperties<InstanceType<T>>>(props: P[], unwrap: true): Pick<DataProperties<InstanceType<T>>, P>;
|
|
32
|
+
(props: true): DataProperties<InstanceType<T>>;
|
|
23
33
|
(props: false): IVueRefs<InstanceType<T>>;
|
|
24
34
|
(): IVueRefs<InstanceType<T>>;
|
|
25
35
|
}
|
|
@@ -28,7 +38,7 @@ export interface IVueToRefsFn<T extends AnyClass> {
|
|
|
28
38
|
* also knows NOT to convert functions to .value Refs but to leave them as is.
|
|
29
39
|
*/
|
|
30
40
|
export declare type IVueRefs<T> = {
|
|
31
|
-
[K in keyof T
|
|
41
|
+
[K in keyof T as T[K] extends AnyFn ? never : K]: ToRef<T[K]>;
|
|
32
42
|
};
|
|
33
43
|
/**
|
|
34
44
|
* Unwraps Ref Recursively, helps resolve fully the bare value types of any type T
|
|
@@ -37,19 +47,19 @@ export declare type IVueRefs<T> = {
|
|
|
37
47
|
* are difficult to fully resolve to bare values without this utility.
|
|
38
48
|
* Also support Vue 3 Demi for vue-use library support.
|
|
39
49
|
*/
|
|
40
|
-
|
|
50
|
+
declare type UnwrapRefRecursively<T = any> = T extends Ref | DemiRef ? UnwrapRefRecursively<T['value']> : T;
|
|
41
51
|
/**
|
|
42
|
-
* Helper type for Use type, unwraps any type of Vue 3 composable down to its bare types.
|
|
52
|
+
* Helper type for Use type, unwraps any type of Vue 3 composable return object down to its bare types.
|
|
43
53
|
*/
|
|
44
|
-
|
|
54
|
+
declare type UnwrapComposableRefs<T> = T extends Ref | DemiRef ? UnwrapRefRecursively<T> : {
|
|
45
55
|
[K in keyof T]: T[K] extends Ref | DemiRef ? UnwrapRefRecursively<T[K]> : T[K];
|
|
46
56
|
};
|
|
47
57
|
/**
|
|
48
58
|
* Fully unwraps to bare value types any Vue 3 composable return definition type.
|
|
49
59
|
*/
|
|
50
|
-
export declare type Use<T = any> = T extends Ref | DemiRef ? UnwrapRefRecursively<T> :
|
|
60
|
+
export declare type Use<T = any> = T extends Ref | DemiRef ? UnwrapRefRecursively<T> : UnwrapComposableRefs<T extends AnyFn ? ReturnType<T> : T>;
|
|
51
61
|
/**
|
|
52
|
-
* Extracts object defined emit types by converting them to a plain interface
|
|
62
|
+
* Extracts object defined emit types by converting them to a plain interface.
|
|
53
63
|
*/
|
|
54
64
|
export declare type ExtractEmitTypes<T extends Record<string, any>> = UnionToIntersection<RecordToUnion<{
|
|
55
65
|
[K in keyof T]: (evt: K, ...args: Parameters<T[K]>) => void;
|
|
@@ -72,15 +82,11 @@ export declare type AnyFn = (...args: any[]) => any;
|
|
|
72
82
|
/**
|
|
73
83
|
* Any JavaScript class of any type.
|
|
74
84
|
*/
|
|
75
|
-
export declare type AnyClass =
|
|
85
|
+
export declare type AnyClass = new (...args: any[]) => any;
|
|
76
86
|
/**
|
|
77
|
-
*
|
|
87
|
+
* Accessors map type.
|
|
78
88
|
*/
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Computeds hash map type.
|
|
82
|
-
*/
|
|
83
|
-
export declare type Computeds = Record<string, ComputedRef>;
|
|
89
|
+
declare type Accessors = Map<string, PropertyDescriptor>;
|
|
84
90
|
/**
|
|
85
91
|
* Infer paramaters of a constructor function of a Class.
|
|
86
92
|
*/
|
|
@@ -93,14 +99,6 @@ export declare type InferredArgs<T> = T extends {
|
|
|
93
99
|
export declare type PrefixKeys<T, P extends string | undefined = undefined> = {
|
|
94
100
|
[K in Extract<keyof T, string> as P extends string ? `${P}${K}` : K]: T[K];
|
|
95
101
|
};
|
|
96
|
-
/**
|
|
97
|
-
* Get Interface's property's function arguments Parmeters<F>
|
|
98
|
-
*/
|
|
99
|
-
export declare type IFnParameters<T extends Record<any, any>, K extends string> = Parameters<Required<Pick<T, K>>[K]>;
|
|
100
|
-
/**
|
|
101
|
-
* Get Interface's [T] property's [P] function arguments Parmeters<F> parameter by key [K]
|
|
102
|
-
*/
|
|
103
|
-
export declare type IFnParameter<T extends Record<any, any>, P extends keyof T, K extends number> = FnParameter<ValueOf<T, P>, K>;
|
|
104
102
|
/**
|
|
105
103
|
* Get function arguments Parmeters<F> parameter by key K
|
|
106
104
|
*/
|
|
@@ -108,34 +106,96 @@ export declare type FnParameter<F extends AnyFn, K extends number> = Parameters<
|
|
|
108
106
|
/**
|
|
109
107
|
* Convert Union Type to Intersection Type.
|
|
110
108
|
*/
|
|
111
|
-
|
|
109
|
+
declare type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
112
110
|
/**
|
|
113
111
|
* Convert Record to Union Type.
|
|
114
112
|
*/
|
|
115
|
-
|
|
113
|
+
declare type RecordToUnion<T extends Record<string, any>> = T[keyof T];
|
|
116
114
|
/**
|
|
117
115
|
* Gets object T property by key [K].
|
|
118
116
|
*/
|
|
119
|
-
|
|
117
|
+
declare type ValueOf<T extends Record<any, any>, K> = T[K];
|
|
118
|
+
/**
|
|
119
|
+
* Props Map Value type.
|
|
120
|
+
*/
|
|
121
|
+
declare type PropsMapValue = {
|
|
122
|
+
allProps: Map<string, PropKind>;
|
|
123
|
+
onlyProps: Set<string>;
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Accessors and Methods map type.
|
|
127
|
+
*/
|
|
128
|
+
declare type AccessorsMethodsMap = {
|
|
129
|
+
accessors: Accessors;
|
|
130
|
+
methods: Set<string>;
|
|
131
|
+
};
|
|
132
|
+
export declare type IVueInstanceBase = {
|
|
133
|
+
init?: (...args: any[]) => void;
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* IVue static configuration interface.
|
|
137
|
+
*/
|
|
138
|
+
export interface IVueStaticConfig<T> {
|
|
139
|
+
ivueGlobalStore?: boolean;
|
|
140
|
+
ivueCloneByReference?: Set<keyof T>;
|
|
141
|
+
ivueDisableReactivity?: Set<keyof T>;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* IVue constructor type.
|
|
145
|
+
*/
|
|
146
|
+
export declare type IVueConstructor<T> = new (...args: any[]) => T;
|
|
147
|
+
/**
|
|
148
|
+
* IVue class type with static configuration.
|
|
149
|
+
*/
|
|
150
|
+
export declare type IVueClass<T> = IVueConstructor<T> & IVueStaticConfig<T>;
|
|
151
|
+
/**
|
|
152
|
+
* Get IVue init() method arguments IVueInitArgs<T>
|
|
153
|
+
*/
|
|
154
|
+
export declare type IVueInitArgs<T extends IVueInstanceBase> = T['init'] extends (...args: any[]) => any ? Parameters<T['init']> : never;
|
|
155
|
+
/**
|
|
156
|
+
* Get Interface's method keys MethodKeys<T>
|
|
157
|
+
*/
|
|
158
|
+
declare type MethodKeys<T> = {
|
|
159
|
+
[P in keyof T]: T[P] extends (...args: any[]) => any ? P : never;
|
|
160
|
+
}[keyof T];
|
|
161
|
+
/**
|
|
162
|
+
* Get Interface's property's function arguments Parameters<F>
|
|
163
|
+
*/
|
|
164
|
+
export declare type IFnParameters<T extends Record<any, any> | InstanceType<AnyClass>, K extends MethodKeys<T>> = Parameters<Extract<Required<Pick<T, K>>[K], AnyFn>>;
|
|
165
|
+
/**
|
|
166
|
+
* Get Interface's [T] property's [P] function arguments Parmeters<F> parameter by key [K]
|
|
167
|
+
*/
|
|
168
|
+
export declare type IFnParameter<T extends Record<any, any>, P extends keyof T, K extends number> = FnParameter<ValueOf<T, P>, K>;
|
|
169
|
+
export declare type IVueDeepCloneArgsMap = Map<AnyClass, any[]>;
|
|
170
|
+
declare type CloneArgsFor<T> = T extends {
|
|
171
|
+
init: (isClone: boolean, ...rest: infer A) => any;
|
|
172
|
+
} ? A : [];
|
|
120
173
|
/** Types End. */
|
|
174
|
+
export declare const IVUE_INSTANCE_SYMBOL: unique symbol;
|
|
121
175
|
/**
|
|
122
|
-
*
|
|
123
|
-
* @see {
|
|
176
|
+
* Stores accessors for each class prototype processed by
|
|
177
|
+
* @see {getClassAccessorsMethodsMap}. Uses WeakMap to allow garbage collection
|
|
178
|
+
* of unused class accessors, ensuring memory efficiency in long-running applications.
|
|
124
179
|
*/
|
|
125
|
-
export declare
|
|
180
|
+
export declare let accessorsMethodsMaps: WeakMap<object, AccessorsMethodsMap>;
|
|
126
181
|
/**
|
|
127
|
-
*
|
|
128
|
-
* Completely emulates JavaScript class inheritance chain for getters and setters.
|
|
129
|
-
*
|
|
130
|
-
* @param className
|
|
131
|
-
* @return {Descriptors}
|
|
182
|
+
* Property kind enum for distinguishing between data properties and accessors.
|
|
132
183
|
*/
|
|
133
|
-
|
|
184
|
+
declare const enum PropKind {
|
|
185
|
+
Data = 0,
|
|
186
|
+
Accessor = 2
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Stores properties for each class prototype processed by
|
|
190
|
+
* @see {getClassPropertiesAccessorsMap}. Uses WeakMap to allow garbage collection
|
|
191
|
+
* of unused class properties, ensuring memory efficiency in long-running applications.
|
|
192
|
+
*/
|
|
193
|
+
export declare let propertiesAccessorsMaps: WeakMap<object, PropsMapValue>;
|
|
134
194
|
/**
|
|
135
195
|
* Infinite Vue (ivue) class reactive initializer.
|
|
136
196
|
*
|
|
137
197
|
* Converts class instance to a reactive object,
|
|
138
|
-
* where
|
|
198
|
+
* where accessors are converted to computeds.
|
|
139
199
|
*
|
|
140
200
|
* You can turn off computed behaviour by adding static
|
|
141
201
|
* ivue object and setting the getter props to false.
|
|
@@ -151,46 +211,30 @@ export declare function getAllClassDescriptors(className: AnyClass): Descriptors
|
|
|
151
211
|
* @param args Class constructor arguments that you would pass to a `new AnyClass(args...)`
|
|
152
212
|
* @returns {IVue<T>}
|
|
153
213
|
*/
|
|
154
|
-
export declare
|
|
155
|
-
/**
|
|
156
|
-
* Store all props for each class prototype processed by:
|
|
157
|
-
* @see {getAllClassProperties}
|
|
158
|
-
*/
|
|
159
|
-
export declare const propsMap: Map<object, Set<string> | any>;
|
|
160
|
-
/**
|
|
161
|
-
* Get properties of an entire class prototype ancestors chain as a Map.
|
|
162
|
-
*/
|
|
163
|
-
export declare function getAllClassProperties(obj: object): Set<string>;
|
|
214
|
+
export declare const ivue: <T extends AnyClass>(className: T, ...args: InferredArgs<T>) => IVue<T>;
|
|
164
215
|
/**
|
|
165
216
|
* `iref()` is an alias for Vue ref() function but returns an unwrapped type without the .value
|
|
166
217
|
* `iref()` does not alter the behavior of ref(), but simply transforms the type to an unwrapped raw value.
|
|
167
218
|
* @param val T
|
|
168
|
-
* @returns {T}
|
|
219
|
+
* @returns {T} Ref but with type unwrapped
|
|
169
220
|
*/
|
|
170
221
|
export declare const iref: <T = any>(value?: T | undefined) => T;
|
|
171
222
|
/**
|
|
172
223
|
* `ishallowRef()` is an alias for Vue shallowRef() function but returns an unwrapped type without the .value
|
|
173
224
|
* `ishallowRef()` does not alter the behavior of shallowRef(), but simply transforms the type to an unwrapped raw value.
|
|
174
225
|
* @param val T
|
|
175
|
-
* @returns {T}
|
|
226
|
+
* @returns {T} ShallowRef but with type unwrapped
|
|
176
227
|
*/
|
|
177
228
|
export declare const ishallowRef: <T = any>(value?: T | undefined) => T;
|
|
178
229
|
/**
|
|
179
|
-
*
|
|
180
|
-
* 1. `iuse(useComposable(arg, arg2, arg3, ...))` converts the return types of a Composable / Ref to pure raw unwrapped type definition.
|
|
181
|
-
* Returns for all properties of an object an unwrapped raw type definition,
|
|
182
|
-
* unwraps direct Refs & ComputedRefs as well down to their raw types.
|
|
230
|
+
* `iuse()` is a helper function that unwraps any Vue 3 composable return type down to its bare types.
|
|
183
231
|
*
|
|
184
|
-
*
|
|
185
|
-
* here the TypeScript inference works for composable function arguments to assist you with intellisence,
|
|
186
|
-
* like they work for constructor arguments in the cause of `ivue()` core function,
|
|
187
|
-
* making the API cleaner to look at and make it compatible with how this function operates with classes, see #3.
|
|
232
|
+
* It does not alter the behavior of the composable, but simply transforms the type to an unwrapped raw value.
|
|
188
233
|
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
* their types as raw (no-Ref) types to fit with reactive() structure of the ivue class context.
|
|
234
|
+
* @param obj Any Vue 3 composable return type or any object or any type
|
|
235
|
+
* @returns {Use<T>} Composable with types unwrapped
|
|
192
236
|
*/
|
|
193
|
-
export declare
|
|
237
|
+
export declare const iuse: <T extends object>(obj: T) => Use<T>; /** Unwrap any other Object or any type down to its bare types. */
|
|
194
238
|
/**
|
|
195
239
|
* Vue props interface in defineComponent() style.
|
|
196
240
|
*/
|
|
@@ -216,7 +260,7 @@ export declare type VuePropsWithDefaults<T extends VuePropsObject> = {
|
|
|
216
260
|
* @param val Any value
|
|
217
261
|
* @returns boolean If it's a JavaScript Class returns true
|
|
218
262
|
*/
|
|
219
|
-
export declare
|
|
263
|
+
export declare const isClass: (val: any) => boolean;
|
|
220
264
|
/**
|
|
221
265
|
* Creates props with defaults in defineComponent() style.
|
|
222
266
|
*
|
|
@@ -242,6 +286,25 @@ export declare function isClass(val: any): boolean;
|
|
|
242
286
|
* @param typedProps Props declared in defineComponent() style with type and possibly required declared, but without default
|
|
243
287
|
* @returns Props declared in defineComponent() style with all properties having default property declared.
|
|
244
288
|
*/
|
|
245
|
-
export declare
|
|
289
|
+
export declare const propsWithDefaults: <T extends VuePropsObject>(defaults: Record<string, any>, typedProps: T, deepCloneArgs?: IVueDeepCloneArgsMap | undefined) => VuePropsWithDefaults<T>;
|
|
290
|
+
/**
|
|
291
|
+
* Copies own properties (including symbols and non-enumerable) from source to target.
|
|
292
|
+
* Recursively deep clones values, but preserves getters/setters as-is.
|
|
293
|
+
*/
|
|
294
|
+
export declare const copyOwnProps: (source: any, target: any, deepCloneArgs: IVueDeepCloneArgsMap | undefined, seen: WeakMap<object, any>) => void;
|
|
295
|
+
export declare let activeDeepCloneArgsMap: WeakMap<AnyClass, IVueDeepCloneArgsMap>;
|
|
296
|
+
export declare const deepClone: (source: any, deepCloneArgs?: IVueDeepCloneArgsMap, seen?: WeakMap<object, any>) => any;
|
|
297
|
+
/**
|
|
298
|
+
* Clears the accessorsMethodsMaps, and propertiesAccessorsMaps WeakMaps.
|
|
299
|
+
* Useful for SSR scenarios where you want to reset the cache between requests.
|
|
300
|
+
* Also useful for testing purposes to ensure a clean state.
|
|
301
|
+
*/
|
|
302
|
+
export declare const clearCache: () => void;
|
|
303
|
+
/** Exported for testing purposes. */
|
|
304
|
+
export declare const __test__: {
|
|
305
|
+
copyOwnProps: (source: any, target: any, deepCloneArgs: IVueDeepCloneArgsMap | undefined, seen: WeakMap<object, any>) => void;
|
|
306
|
+
getClassAccessorsMethodsMap: (className: AnyClass) => AccessorsMethodsMap;
|
|
307
|
+
getClassPropertiesAccessorsMap: (obj: object) => PropsMapValue;
|
|
308
|
+
};
|
|
246
309
|
/** Necessary ivue.ts to be treated as a module. */
|
|
247
310
|
export {};
|
package/lib/Reactive.ts
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { toRaw, isRef, type Ref } from 'vue';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Constants & Helpers
|
|
5
|
+
*/
|
|
6
|
+
const prototypeHasOwnProperty = Object.prototype.hasOwnProperty.call.bind(
|
|
7
|
+
Object.prototype.hasOwnProperty
|
|
8
|
+
);
|
|
9
|
+
const getPrototypeOf = Object.getPrototypeOf;
|
|
10
|
+
const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
11
|
+
const defineProperty = Object.defineProperty;
|
|
12
|
+
const getOwnPropertyNames = Object.getOwnPropertyNames;
|
|
13
|
+
const objectPrototype = Object.prototype;
|
|
14
|
+
const getOwnPropertySymbols = Object.getOwnPropertySymbols;
|
|
15
|
+
|
|
16
|
+
const $stopEffects = '$stopEffects';
|
|
17
|
+
const fn = 'function';
|
|
18
|
+
const RAW = Symbol('ivue_raw');
|
|
19
|
+
const SK_MAP = '__ivue_sk'; // Store for cache keys
|
|
20
|
+
const PROCESSED = Symbol('ivue_processed'); // Flag to mark prototype as "Reactified"
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get unique symbol key for super method/property.
|
|
24
|
+
*/
|
|
25
|
+
function getSuperKey(proto: any, key: string): symbol {
|
|
26
|
+
// Ensure the map exists on this specific prototype (shadowing parents)
|
|
27
|
+
if (!prototypeHasOwnProperty(proto, SK_MAP)) {
|
|
28
|
+
defineProperty(proto, SK_MAP, {
|
|
29
|
+
value: {},
|
|
30
|
+
configurable: true,
|
|
31
|
+
enumerable: false,
|
|
32
|
+
writable: true,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const map = proto[SK_MAP];
|
|
36
|
+
return map[key] || (map[key] = Symbol(key));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Convert method to a lazy-bound prototype method.
|
|
41
|
+
*/
|
|
42
|
+
function convertToLazyBoundMethod(proto: any, key: string, superKey: symbol) {
|
|
43
|
+
const desc = getOwnPropertyDescriptor(proto, key);
|
|
44
|
+
if (!desc || typeof desc.value !== fn) return;
|
|
45
|
+
|
|
46
|
+
const originalFn = desc.value;
|
|
47
|
+
|
|
48
|
+
defineProperty(proto, key, {
|
|
49
|
+
configurable: true,
|
|
50
|
+
enumerable: false,
|
|
51
|
+
get(this: any) {
|
|
52
|
+
const raw = this[RAW] ?? (this[RAW] = toRaw(this));
|
|
53
|
+
return raw[superKey] ?? (raw[superKey] = originalFn.bind(raw));
|
|
54
|
+
},
|
|
55
|
+
set(this: any, newFn: any) {
|
|
56
|
+
toRaw(this)[superKey] = newFn;
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Convert a property to a lazy-computed property.
|
|
63
|
+
*/
|
|
64
|
+
function convertToLazyComputed<T extends object>(
|
|
65
|
+
proto: T,
|
|
66
|
+
key: string,
|
|
67
|
+
superKey: symbol
|
|
68
|
+
) {
|
|
69
|
+
const desc = getOwnPropertyDescriptor(proto, key);
|
|
70
|
+
const originalGetter = desc?.get;
|
|
71
|
+
const originalSetter = desc?.set;
|
|
72
|
+
|
|
73
|
+
// Optimization: Properties starting with $ are assumed singletons
|
|
74
|
+
const cacheWhole = key[0] === '$';
|
|
75
|
+
|
|
76
|
+
// DEV WARNING: Setter/Getter mismatch
|
|
77
|
+
if (import.meta.env.DEV && originalGetter && originalSetter) {
|
|
78
|
+
try {
|
|
79
|
+
const testVal = originalGetter.call({});
|
|
80
|
+
if (isRef(testVal)) {
|
|
81
|
+
console.warn(
|
|
82
|
+
`[ivue] API conflict on "${key}": Getter returns Ref but Setter is standard.`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const newGetter = function (this: any) {
|
|
89
|
+
const raw = toRaw(this);
|
|
90
|
+
|
|
91
|
+
// 1. Check Cache
|
|
92
|
+
if (superKey in raw) return raw[superKey];
|
|
93
|
+
|
|
94
|
+
// 2. Execute Original
|
|
95
|
+
const result = originalGetter!.call(raw);
|
|
96
|
+
|
|
97
|
+
// 3. Handle Result
|
|
98
|
+
if (cacheWhole) {
|
|
99
|
+
// Cache result forever (Singleton pattern)
|
|
100
|
+
raw[superKey] = result;
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (isRef(result)) {
|
|
105
|
+
// Cache Ref instance (Reactivity pattern)
|
|
106
|
+
raw[superKey] = result;
|
|
107
|
+
} else {
|
|
108
|
+
// DE-OPTIMIZATION: It's just a value. Restore original getter on prototype.
|
|
109
|
+
// This removes overhead for all future calls on this prototype.
|
|
110
|
+
defineProperty(proto, key, {
|
|
111
|
+
configurable: true,
|
|
112
|
+
enumerable: false,
|
|
113
|
+
get: originalGetter
|
|
114
|
+
? function (this: any) {
|
|
115
|
+
return originalGetter.call(toRaw(this));
|
|
116
|
+
}
|
|
117
|
+
: undefined,
|
|
118
|
+
set: originalSetter
|
|
119
|
+
? function (this: any, v: any) {
|
|
120
|
+
return originalSetter.call(toRaw(this), v);
|
|
121
|
+
}
|
|
122
|
+
: undefined,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return result;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
defineProperty(proto, key, {
|
|
130
|
+
configurable: true,
|
|
131
|
+
enumerable: false,
|
|
132
|
+
get: originalGetter ? newGetter : undefined,
|
|
133
|
+
set: originalSetter
|
|
134
|
+
? function (this: any, v: any) {
|
|
135
|
+
return originalSetter.call(toRaw(this), v);
|
|
136
|
+
}
|
|
137
|
+
: undefined,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Create a reactive class.
|
|
143
|
+
* @param targetClass The class to make reactive.
|
|
144
|
+
* @returns A reactive version of the class.
|
|
145
|
+
*/
|
|
146
|
+
export function Reactive<C extends new (...args: any) => any>(
|
|
147
|
+
targetClass: C
|
|
148
|
+
): ReactiveClass<C> & { Instance: ReactiveInstance<InstanceType<C>> } {
|
|
149
|
+
const chain: any[] = [];
|
|
150
|
+
|
|
151
|
+
let targetPrototype = targetClass.prototype;
|
|
152
|
+
while (targetPrototype && targetPrototype !== objectPrototype) {
|
|
153
|
+
chain.push(targetPrototype);
|
|
154
|
+
targetPrototype = getPrototypeOf(targetPrototype);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Process Base -> Child
|
|
158
|
+
chain.reverse();
|
|
159
|
+
|
|
160
|
+
for (const prototype of chain) {
|
|
161
|
+
// OPTIMIZATION: Skip if this prototype layer is already "Reactified"
|
|
162
|
+
// This handles diamond inheritance and multiple Reactive children safely.
|
|
163
|
+
if (prototypeHasOwnProperty(prototype, PROCESSED)) continue;
|
|
164
|
+
|
|
165
|
+
const names = getOwnPropertyNames(prototype);
|
|
166
|
+
|
|
167
|
+
for (const key of names) {
|
|
168
|
+
if (key === 'constructor') continue;
|
|
169
|
+
const desc = getOwnPropertyDescriptor(prototype, key);
|
|
170
|
+
if (!desc) continue;
|
|
171
|
+
|
|
172
|
+
const superKey = getSuperKey(prototype, key);
|
|
173
|
+
|
|
174
|
+
if (typeof desc.value === fn) {
|
|
175
|
+
convertToLazyBoundMethod(prototype, key, superKey);
|
|
176
|
+
} else if (desc.get) {
|
|
177
|
+
convertToLazyComputed(prototype, key, superKey);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Mark this specific prototype level as processed
|
|
182
|
+
defineProperty(prototype, PROCESSED, {
|
|
183
|
+
configurable: false,
|
|
184
|
+
enumerable: false,
|
|
185
|
+
value: true,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Inject cleanup
|
|
190
|
+
if (!prototypeHasOwnProperty(targetClass.prototype, $stopEffects)) {
|
|
191
|
+
defineProperty(targetClass.prototype, $stopEffects, {
|
|
192
|
+
enumerable: false,
|
|
193
|
+
configurable: true,
|
|
194
|
+
writable: true,
|
|
195
|
+
value: function (this: any) {
|
|
196
|
+
const raw = toRaw(this);
|
|
197
|
+
if (typeof raw.stopEffects === fn) raw.stopEffects();
|
|
198
|
+
|
|
199
|
+
const symbols = getOwnPropertySymbols(raw);
|
|
200
|
+
for (const symbol of symbols) {
|
|
201
|
+
if (symbol === RAW) continue;
|
|
202
|
+
const cached = raw[symbol];
|
|
203
|
+
// Stop computed/effect if present
|
|
204
|
+
if (cached && cached.effect && typeof cached.effect.stop === fn) {
|
|
205
|
+
cached.effect.stop();
|
|
206
|
+
}
|
|
207
|
+
delete raw[symbol];
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return targetClass as any;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Vue props interface in defineComponent() style.
|
|
218
|
+
*/
|
|
219
|
+
export type VuePropsObject = Record<
|
|
220
|
+
string,
|
|
221
|
+
{ type: any; default?: any; required?: boolean }
|
|
222
|
+
>;
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Vue Props with default properties declared as existing and having values.
|
|
226
|
+
*/
|
|
227
|
+
export type VuePropsWithDefaults<T extends VuePropsObject> = {
|
|
228
|
+
[K in keyof T]: {
|
|
229
|
+
type: T[K]['type'];
|
|
230
|
+
default: T[K]['default'];
|
|
231
|
+
required?: boolean;
|
|
232
|
+
};
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Determines if the value is a JavaScript Class.
|
|
237
|
+
* Note that class is a class function in JavaScript.
|
|
238
|
+
*
|
|
239
|
+
* @param val Any value
|
|
240
|
+
* @returns boolean If it's a JavaScript Class returns true
|
|
241
|
+
*/
|
|
242
|
+
export const isClass = (val: any): boolean => {
|
|
243
|
+
if (typeof val !== 'function') return false; // Not a function, so not a class function either
|
|
244
|
+
|
|
245
|
+
if (!val.prototype) return false; // Arrow function, so not a class
|
|
246
|
+
|
|
247
|
+
// Finally -> distinguish between a normal function and a class function
|
|
248
|
+
if (getOwnPropertyDescriptor(val, 'prototype')?.writable) {
|
|
249
|
+
// Has writable prototype
|
|
250
|
+
return false; // Normal function
|
|
251
|
+
} else {
|
|
252
|
+
return true; // Class -> Not a function
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
/**
|
|
256
|
+
* Creates props with defaults in defineComponent() style.
|
|
257
|
+
*
|
|
258
|
+
* Merge defaults regular object with Vue types object
|
|
259
|
+
* declared in defineComponent() style.
|
|
260
|
+
*
|
|
261
|
+
* This is made so that the defaults can be declared "as they are"
|
|
262
|
+
* without requiring objects to be function callbacks returning an object.
|
|
263
|
+
*
|
|
264
|
+
* // You don't need to wrap objects in () => ({ nest: { nest :{} } })
|
|
265
|
+
* // You can just delcare them normally.
|
|
266
|
+
* const defaults = {
|
|
267
|
+
* nest: {
|
|
268
|
+
* nest
|
|
269
|
+
* }
|
|
270
|
+
* }
|
|
271
|
+
*
|
|
272
|
+
* This function will create the Vue expected callbacks for Objects, Arrays & Classes
|
|
273
|
+
* but leave primitive properties and functions intact so that
|
|
274
|
+
* the final object is fully defineComponent() style compatible.
|
|
275
|
+
*
|
|
276
|
+
* @param defaults Regular object of default key -> values
|
|
277
|
+
* @param typedProps Props declared in defineComponent() style with type and possibly required declared, but without default
|
|
278
|
+
* @returns Props declared in defineComponent() style with all properties having default property declared.
|
|
279
|
+
*/
|
|
280
|
+
export const propsWithDefaults = <T extends VuePropsObject>(
|
|
281
|
+
defaults: Record<string, any>,
|
|
282
|
+
typedProps: T,
|
|
283
|
+
// Optional: Allows user to pass a custom cloner if structuredClone isn't enough
|
|
284
|
+
customCloner?: (val: any) => any
|
|
285
|
+
): VuePropsWithDefaults<T> => {
|
|
286
|
+
for (const prop in typedProps) {
|
|
287
|
+
const def = defaults?.[prop];
|
|
288
|
+
const typed = typedProps[prop];
|
|
289
|
+
|
|
290
|
+
if (typed.required || def === undefined) continue;
|
|
291
|
+
|
|
292
|
+
if (typeof def === 'object' && def !== null) {
|
|
293
|
+
typedProps[prop].default = () =>
|
|
294
|
+
customCloner ? customCloner(def) : structuredClone(def);
|
|
295
|
+
} else {
|
|
296
|
+
if (isClass(def)) {
|
|
297
|
+
typedProps[prop].default = () => def;
|
|
298
|
+
} else {
|
|
299
|
+
typedProps[prop].default = def;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return typedProps as VuePropsWithDefaults<T>;
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Type Utilities
|
|
308
|
+
*/
|
|
309
|
+
type GetterKeys<T> = {
|
|
310
|
+
[K in keyof T]: T[K] extends (...args: any[]) => any
|
|
311
|
+
? never
|
|
312
|
+
: T[K] extends undefined
|
|
313
|
+
? never
|
|
314
|
+
: K;
|
|
315
|
+
}[keyof T];
|
|
316
|
+
|
|
317
|
+
type GetterReturn<T, K extends keyof T> = T[K] extends (...args: any[]) => any
|
|
318
|
+
? never
|
|
319
|
+
: T[K];
|
|
320
|
+
type WritableComputedLike = Ref<any> & { set: (...args: any[]) => any };
|
|
321
|
+
type IsWritableGetter<R> = R extends Ref<any>
|
|
322
|
+
? true
|
|
323
|
+
: R extends WritableComputedLike
|
|
324
|
+
? true
|
|
325
|
+
: false;
|
|
326
|
+
|
|
327
|
+
type WritableGetters<T> = {
|
|
328
|
+
[K in GetterKeys<T> as IsWritableGetter<GetterReturn<T, K>> extends true
|
|
329
|
+
? K
|
|
330
|
+
: never]-?: T[K];
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
export type ReactiveInstance<T> = T &
|
|
334
|
+
WritableGetters<T> & { $stopEffects: () => void };
|
|
335
|
+
|
|
336
|
+
export type ReactiveClass<C extends new (...args: any) => any> = new (
|
|
337
|
+
...args: ConstructorParameters<C>
|
|
338
|
+
) => ReactiveInstance<InstanceType<C>>;
|