1/* 2 * Copyright (c) 2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16/** 17 * 18 * This file includes definition of all decorators 19 * and supporting interfaces. 20 * used by V2 state mgmt 21 * 22 * part of SDK 23 * 24 */ 25 26/** 27 * @ObservedV2 class decorator, view model class 28 * 29 * Only @ObservedV2 classes can have functional @Trace attributes inside. 30 * and only changes of such decorated properties can be deep observed 31 * (decorated along the entire path from root object to property is required) 32 * 33 * part of SDK 34 * @from 12 35 * 36 */ 37type ConstructorV2 = { new(...args: any[]): any }; 38 39function ObservedV2<T extends ConstructorV2>(BaseClass: T): T { 40 ConfigureStateMgmt.instance.usingV2ObservedTrack(`@observed`, BaseClass?.name); 41 return observedV2Internal<T>(BaseClass); 42} 43 44/** 45 * @Trace class property decorator, property inside @ObservedV2 class 46 * 47 * turns given property into getter and setter functions 48 * adds property target[storeProp] as the backing store 49 * 50 * part of SDK 51 * @from 12 52 */ 53const Trace = (target: Object, propertyKey: string): void => { 54 ConfigureStateMgmt.instance.usingV2ObservedTrack(`@track`, propertyKey); 55 return trackInternal(target, propertyKey); 56}; 57 58/** 59 * @Local @ComponentV2/ViewV2 variable decorator 60 * 61 * allowed value: simple or object type value allowed. Objects must be instances of 62 * ObservedV2, Array, Map, Set, or Date for changes to be observed. No functions allowed 63 * local init required 64 * no init or update from parent @ComponentV2 65 * new value assignment allowed = has setter 66 * 67 * part of SDK 68 * @from 12 69 * 70 */ 71const Local = (target: Object, propertyKey: string): void => { 72 ObserveV2.addVariableDecoMeta(target, propertyKey, '@state'); 73 return trackInternal(target, propertyKey); 74}; 75 76/** 77 * @Param class property decorator 78 * 79 * allowed value: simple or object type value allowed. Objects must be instances of 80 * ObservedV2, Array, Map, Set, or Date for changes to be observed. No functions allowed 81 * local init optional 82 * init from parent @ComponentV2 is mandatory when no local init, otherwise optional 83 * updates from parent @ComponentV2 if initialized from parent @ComponentV2, 84 * no update when @Once is used. 85 * new value assignment not allowed = has no setter. 86 * 87 * part of SDK 88 * @from 12 89 * 90 */ 91const Param = (proto: Object, propertyKey: string): void => { 92 stateMgmtConsole.debug(`@param ${propertyKey}`); 93 ObserveV2.addParamVariableDecoMeta(proto, propertyKey, '@param', undefined); 94 95 let storeProp = ObserveV2.OB_PREFIX + propertyKey; 96 proto[storeProp] = proto[propertyKey]; 97 Reflect.defineProperty(proto, propertyKey, { 98 get() { 99 ObserveV2.getObserve().addRef(this, propertyKey); 100 return ObserveV2.autoProxyObject(this, ObserveV2.OB_PREFIX + propertyKey); 101 }, 102 set(val) { 103 const meta = proto[ObserveV2.V2_DECO_META]?.[propertyKey]; 104 if (meta && meta.deco2 !== '@once') { 105 stateMgmtConsole.applicationError(`@param ${propertyKey.toString()}: can not assign a new value, application error.`); 106 return; 107 } 108 if (val !== this[storeProp]) { 109 this[storeProp] = val; 110 if (this[ObserveV2.SYMBOL_REFS]) { // This condition can improve performance. 111 ObserveV2.getObserve().fireChange(this, propertyKey); 112 } 113 } 114 }, 115 // @param can not be assigned, no setter 116 enumerable: true 117 }); 118}; // Param 119 120/** 121 * @Once supplementary @ComponentV2 variable decorator to @Param decorator 122 * must use like this @Param @Once varName. Can not be used without @param. 123 * prevents @Param variable updates from parent component 124 * 125 * @param proto 126 * @param propertyKey 127 * 128 * part of SDK 129 * @from 12 130 * 131 */ 132const Once = (proto: Object, propertyKey: string): void => { 133 stateMgmtConsole.debug(`@once ${propertyKey}`); 134 ObserveV2.addParamVariableDecoMeta(proto, propertyKey, undefined, '@once'); 135}; 136 137/** 138 * @Event class variable decorator, class must be @ComponentV2 139 * 140 * Allowed value: Function, can have parameters and return a value. 141 * local init: optional for functions without return value, default is () => void 142 * Local init is mandatory for functions with return value. 143 * init from parent @Component: optional. 144 * update from parent @Component: never 145 * new value assignment not allowed 146 * 147 * part of SDK 148 * @from 12 149 * 150 */ 151 152const Event = (target, propertyKey): void => { 153 ObserveV2.addVariableDecoMeta(target, propertyKey, '@event'); 154 target[propertyKey] ??= (): void => { }; 155}; 156 157/** 158 * @Provider variable decorator of @ComponentV2 variable 159 * 160 * @Provider(alias? : string) varName : typeName = initValue 161 * 162 * @param alias defaults to varName 163 * 164 * allowed value: simple or object type value allowed. Objects must be instances of 165 * ObservedV2, Array, Map, Set, or Date for changes to be observed. No functions allowed 166 * local init required 167 * no init or update from parent @ComponentV2 168 * provides its value to any @Consumer counter part 169 * new value assignment allowed = has setter 170 * 171 * part of SDK 172 * @since 12 173 */ 174const Provider = (aliasName?: string) => { 175 return (proto: Object, varName: string): void => { 176 const providedUnderName: string = aliasName || varName; 177 ProviderConsumerUtilV2.addProvideConsumeVariableDecoMeta(proto, varName, providedUnderName, '@Provider'); 178 trackInternal(proto, varName); 179 }; 180}; // @Provider 181 182/** 183 * @Consumer variable decorator of @ComponentV2 variable 184 * 185 * @Consumer(alias? : string) varName : typeName = defaultValue 186* 187 * @param alias defaults to varName 188 * 189 * allowed value: simple or object type value allowed. Objects must be instances of 190 * ObservedV2, Array, Map, Set, or Date for changes to be observed. No functions allowed 191 * syncs two-way with the @Provider variable with same `alias` name in nearest ancestor @ComponentV2 192 * local init required, used only if no @Provider counter part is found. 193 * no init or update from parent @ComponentV2 via constructor allowed 194 * new value assignment allowed, changes sys back to @Provider of one exists, otherwise update local value. 195 * 196 * part of SDK 197 * @since 12 198 */ 199const Consumer = (aliasName?: string) => { 200 return (proto: object, varName: string): void => { 201 const searchForProvideWithName: string = aliasName || varName; 202 203 // redefining the property happens when owning ViewV2 gets constructed 204 // and @Consumer gets connected to @provide counterpart 205 ProviderConsumerUtilV2.addProvideConsumeVariableDecoMeta(proto, varName, searchForProvideWithName, '@Consumer'); 206 const providerName = (aliasName === undefined || aliasName === null || 207 (typeof aliasName === 'string' && aliasName.trim() === '') 208 ) ? varName : aliasName; 209 210 Reflect.defineProperty(proto, varName, { 211 get() { 212 // this get function should never be called, 213 // because transpiler will always assign it a value first. 214 stateMgmtConsole.warn('@Consumer outer "get" should never be called, internal error!') 215 return undefined; 216 }, 217 set(val) { 218 let providerInfo = ProviderConsumerUtilV2.findProvider(this, providerName); 219 if (providerInfo && providerInfo[0] && providerInfo[1]) { 220 ProviderConsumerUtilV2.connectConsumer2Provider(this, varName, providerInfo[0], providerInfo[1]); 221 } else { 222 ProviderConsumerUtilV2.defineConsumerWithoutProvider(this, varName, val); 223 } 224 }, 225 enumerable: true 226 }); 227 }; 228}; // @Consumer 229 230/** 231 * @Monitor class function decorator, inside either @ComponentV2 or @ObservedV2 class 232 * 233 * @Monitor(path: string, paths: string[]) functionName (m : IMonitor) : void 234 * 235 * @param path : string , path of monitored object properties (strictly objects, no arrays, maps etc) 236 * property names separated by '.'. 237 * @param paths : string[] , further, optional paths to monitor 238 * 239 * 240 * The decorated function must have one parameter of type IMonitor and no return value. 241 * 242 * Example: @Monitor('varName.obj', 'varName.obj.proA', 'varName2') onChange(m : IMonitor) : void { ... } 243 * monitors assignments to this.varName.obj, this.varName.obj.propA, and this.varName2 . 244 * 245 * part of SDK 246 * @since 12 247 */ 248const Monitor = function (key : string, ...keys: string[]): (target: any, _: any, descriptor: any) => void { 249 const pathsUniqueString = keys ? [key, ...keys].join(' ') : key; 250 return function (target, _, descriptor): void { 251 stateMgmtConsole.debug(`@monitor('${pathsUniqueString}')`); 252 let watchProp = Symbol.for(MonitorV2.WATCH_PREFIX + target.constructor.name); 253 const monitorFunc = descriptor.value; 254 target[watchProp] ? target[watchProp][pathsUniqueString] = monitorFunc : target[watchProp] = { [pathsUniqueString]: monitorFunc }; 255 }; 256}; 257 258/** 259* @Monitor decorated function parameter type IMonitor 260* and sub-type IMonitorValue<T> 261* 262* part of SDK 263* @from 12 264*/ 265interface IMonitor { 266 dirty: Array<string>; 267 value<T>(key?: string): IMonitorValue<T> | undefined; 268 } 269 270 interface IMonitorValue<T> { 271 before: T; 272 now: T; 273 path: string; 274 } 275 276 277 /** 278 * @Computed TS computed class member variable decorator, inside either @ComponentV2 or @ObservedV2 class 279 * 280 * must be a computed class property following TS syntax, e.g. @Computed get varName() { return this.state1 + this.state2 } 281 * value assignment / set not allowed = has no setter. 282 * The framework updates the value of the @Computed variable whenever its input changes 283 * Therefore, the getter function must only use variables whose changes can be observed. 284 * The getter function implementation must not mutate any state. 285 * Changes of the return value of the getter function must be observable to use for constructing UI. 286 * This means if the return value is an object, it must be @ObservedV2 class instance with @Trace 'ed properties, 287 * or of Array, Map, Set, or Date type. 288 * The app should not modify the return value because re-execution of the getter function would overwrite these changes. 289 * 290 * part of SDK 291 * @from 12 292 * 293 */ 294const Computed = (target: Object, propertyKey: string, descriptor: PropertyDescriptor): void => { 295 stateMgmtConsole.debug(`@Computed ${propertyKey}`); 296 let watchProp = Symbol.for(ComputedV2.COMPUTED_PREFIX + target.constructor.name); 297 const computeFunction = descriptor.get; 298 target[watchProp] ? target[watchProp][propertyKey] = computeFunction 299 : target[watchProp] = { [propertyKey]: computeFunction }; 300 301}; 302