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/README.md +170 -33
- package/bin/ivue.mjs +152 -0
- package/dist/Reactive.d.ts +131 -0
- package/dist/env.d.ts +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.es.js +90 -81
- package/dist/index.umd.js +1 -1
- package/lib/Reactive.ts +509 -0
- package/lib/__tests__/Reactive.vitest.spec.ts +1056 -0
- package/lib/__tests__/ReactiveAdversarial.vitest.spec.ts +182 -0
- package/lib/__tests__/coverage-completion.vitest.spec.ts +24 -0
- package/lib/__tests__/ivue.vitest.spec.ts +1460 -150
- package/lib/env.d.ts +1 -0
- package/lib/index.ts +1 -1
- package/lib/ivue.ts +661 -241
- package/lib/kernel.ts +18 -0
- package/package.json +51 -13
- package/skills/ivue/SKILL.md +726 -0
- package/dist/ivue.d.ts +0 -234
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
|
|
47
|
+
<P extends keyof IVueRefs<InstanceType<T>>>(props: P[]): Pick<
|
|
21
48
|
IVueRefs<InstanceType<T>>,
|
|
22
49
|
P
|
|
23
50
|
>;
|
|
24
|
-
|
|
51
|
+
|
|
52
|
+
<P extends keyof IVueRefs<InstanceType<T>>>(props: P[], unwrap: false): Pick<
|
|
25
53
|
IVueRefs<InstanceType<T>>,
|
|
26
54
|
P
|
|
27
55
|
>;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
P
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
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 =
|
|
137
|
+
export type AnyClass = new (...args: any[]) => any;
|
|
108
138
|
|
|
109
139
|
/**
|
|
110
|
-
*
|
|
140
|
+
* Accessors map type.
|
|
111
141
|
*/
|
|
112
|
-
type
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
177
|
-
* @see {
|
|
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
|
|
275
|
+
export let accessorsMethodsMaps = new WeakMap<object, AccessorsMethodsMap>();
|
|
180
276
|
|
|
181
277
|
/**
|
|
182
|
-
* Get
|
|
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 {
|
|
282
|
+
* @return {AccessorsMethodsMap}
|
|
187
283
|
*/
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
|
291
|
+
const savedAccessors: Accessors = new Map();
|
|
292
|
+
const savedMethods: Set<string> = new Set();
|
|
195
293
|
|
|
196
294
|
let prototype = className.prototype;
|
|
197
|
-
while (prototype
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
218
|
-
prototype = Object.getPrototypeOf(prototype);
|
|
308
|
+
}
|
|
309
|
+
prototype = getPrototypeOf(prototype);
|
|
219
310
|
}
|
|
220
311
|
|
|
221
|
-
|
|
222
|
-
|
|
312
|
+
const propertyMap = {
|
|
313
|
+
accessors: savedAccessors,
|
|
314
|
+
methods: savedMethods,
|
|
315
|
+
};
|
|
316
|
+
accessorsMethodsMaps.set(className, propertyMap);
|
|
223
317
|
|
|
224
|
-
return
|
|
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
|
|
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
|
|
406
|
+
export const ivue = <T extends AnyClass>(
|
|
248
407
|
className: T,
|
|
249
408
|
...args: InferredArgs<T>
|
|
250
|
-
): IVue<T> {
|
|
251
|
-
const
|
|
252
|
-
const computeds: Computeds | any =
|
|
409
|
+
): IVue<T> => {
|
|
410
|
+
const { accessors, methods } = getClassAccessorsMethodsMap(className);
|
|
411
|
+
const computeds: Computeds | any = accessors?.size
|
|
412
|
+
? createObject(null)
|
|
413
|
+
: null;
|
|
253
414
|
|
|
254
|
-
|
|
415
|
+
/** Create a reactive instance of the class. */
|
|
255
416
|
const vue = reactive(new className(...args));
|
|
256
417
|
|
|
257
|
-
/** Setup
|
|
258
|
-
for (const [prop, descriptor] of
|
|
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
|
-
|
|
265
|
-
get:
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
282
|
-
|
|
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
|
-
|
|
491
|
+
vue?.init?.(false);
|
|
288
492
|
|
|
289
493
|
return vue;
|
|
290
|
-
}
|
|
494
|
+
};
|
|
291
495
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
|
|
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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (
|
|
315
|
-
|
|
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
|
-
|
|
319
|
-
} while (obj.constructor !== Object);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
320
560
|
|
|
321
|
-
/**
|
|
322
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
355
|
-
*
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
|
635
|
+
* @param accessors @see Accessors
|
|
385
636
|
* @param computeds @see Computeds
|
|
386
637
|
* @returns {ExtendWithToRefs<T>['toRefs']}
|
|
387
638
|
*/
|
|
388
|
-
|
|
639
|
+
const ivueToRefs = <T extends AnyClass>(
|
|
389
640
|
vue: IVue<T>,
|
|
390
|
-
|
|
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
|
|
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
|
|
399
|
-
|
|
400
|
-
/**
|
|
401
|
-
if (
|
|
402
|
-
for (
|
|
403
|
-
const
|
|
404
|
-
|
|
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
|
-
|
|
430
|
-
|
|
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
|
|
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 (
|
|
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
|
|
763
|
+
export const propsWithDefaults = <T extends VuePropsObject>(
|
|
532
764
|
defaults: Record<string, any>,
|
|
533
|
-
typedProps: T
|
|
534
|
-
|
|
765
|
+
typedProps: T,
|
|
766
|
+
deepCloneArgs: IVueDeepCloneArgsMap | undefined = undefined
|
|
767
|
+
): VuePropsWithDefaults<T> => {
|
|
535
768
|
for (const prop in typedProps) {
|
|
536
|
-
|
|
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 = () =>
|
|
780
|
+
typedProps[prop].default = () => deepClone(def, deepCloneArgs);
|
|
539
781
|
} else {
|
|
540
|
-
if (isClass(
|
|
782
|
+
if (isClass(def)) {
|
|
541
783
|
/** Handle JavaScript Classes -> wrap them with an arrow function */
|
|
542
|
-
typedProps[prop].default = () =>
|
|
784
|
+
typedProps[prop].default = () => def;
|
|
543
785
|
} else {
|
|
544
786
|
/** Handle JavaScript Function And All primitive properties -> output directly */
|
|
545
|
-
typedProps[prop].default =
|
|
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 {};
|