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(`@ObservedV2`, 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(`@Trace`, propertyKey); 55 ObserveV2.addVariableDecoMeta(target, propertyKey, '@Trace'); 56 return trackInternal(target, propertyKey); 57}; 58 59/** 60 * @Local @ComponentV2/ViewV2 variable decorator 61 * 62 * allowed value: simple or object type value allowed. Objects must be instances of 63 * ObservedV2, Array, Map, Set, or Date for changes to be observed. No functions allowed 64 * local init required 65 * no init or update from parent @ComponentV2 66 * new value assignment allowed = has setter 67 * 68 * part of SDK 69 * @from 12 70 * 71 */ 72const Local = (target: Object, propertyKey: string): void => { 73 ObserveV2.addVariableDecoMeta(target, propertyKey, '@Local'); 74 return trackInternal(target, propertyKey); 75}; 76 77/** 78 * @Param class property decorator 79 * 80 * allowed value: simple or object type value allowed. Objects must be instances of 81 * ObservedV2, Array, Map, Set, or Date for changes to be observed. No functions allowed 82 * local init optional 83 * init from parent @ComponentV2 is mandatory when no local init, otherwise optional 84 * updates from parent @ComponentV2 if initialized from parent @ComponentV2, 85 * no update when @Once is used. 86 * new value assignment not allowed = has no setter. 87 * 88 * part of SDK 89 * @from 12 90 * 91 */ 92const Param = (proto: Object, propertyKey: string): void => { 93 stateMgmtConsole.debug(`@Param ${propertyKey}`); 94 ObserveV2.addParamVariableDecoMeta(proto, propertyKey, '@Param', undefined); 95 96 let storeProp = ObserveV2.OB_PREFIX + propertyKey; 97 proto[storeProp] = proto[propertyKey]; 98 Reflect.defineProperty(proto, propertyKey, { 99 get() { 100 ObserveV2.getObserve().addRef(this, propertyKey); 101 return ObserveV2.autoProxyObject(this, ObserveV2.OB_PREFIX + propertyKey); 102 }, 103 set(val) { 104 const meta = proto[ObserveV2.V2_DECO_META]?.[propertyKey]; 105 if (meta && meta.deco2 !== '@Once') { 106 stateMgmtConsole.applicationError(`@Param ${propertyKey.toString()}: can not assign a new value, application error.`); 107 return; 108 } 109 if (val !== this[storeProp]) { 110 this[storeProp] = val; 111 // the bindings <*, target, propertyKey> might not have been recorded yet (!) 112 // fireChange will run idleTasks to record pending bindings, if any 113 ObserveV2.getObserve().fireChange(this, propertyKey); 114 } 115 }, 116 // @param can not be assigned, no setter 117 enumerable: true 118 }); 119}; // Param 120 121/** 122 * @Once supplementary @ComponentV2 variable decorator to @Param decorator 123 * must use like this @Param @Once varName. Can not be used without @param. 124 * prevents @Param variable updates from parent component 125 * 126 * @param proto 127 * @param propertyKey 128 * 129 * part of SDK 130 * @from 12 131 * 132 */ 133const Once = (proto: Object, propertyKey: string): void => { 134 stateMgmtConsole.debug(`@Once ${propertyKey}`); 135 ObserveV2.addParamVariableDecoMeta(proto, propertyKey, undefined, '@Once'); 136}; 137 138/** 139 * @Event class variable decorator, class must be @ComponentV2 140 * 141 * Allowed value: Function, can have parameters and return a value. 142 * local init: optional for functions without return value, default is () => void 143 * Local init is mandatory for functions with return value. 144 * init from parent @Component: optional. 145 * update from parent @Component: never 146 * new value assignment not allowed 147 * 148 * part of SDK 149 * @from 12 150 * 151 */ 152 153const Event = (target, propertyKey): void => { 154 ObserveV2.addVariableDecoMeta(target, propertyKey, '@Event'); 155 target[propertyKey] ??= (): void => { }; 156}; 157 158/** 159 * @Provider variable decorator of @ComponentV2 variable 160 * 161 * @Provider(alias? : string) varName : typeName = initValue 162 * 163 * @param alias defaults to varName 164 * 165 * allowed value: simple or object type value allowed. Objects must be instances of 166 * ObservedV2, Array, Map, Set, or Date for changes to be observed. No functions allowed 167 * local init required 168 * no init or update from parent @ComponentV2 169 * provides its value to any @Consumer counter part 170 * new value assignment allowed = has setter 171 * 172 * part of SDK 173 * @since 12 174 */ 175const Provider = (aliasName?: string) => { 176 return (proto: Object, varName: string): void => { 177 const providedUnderName: string = aliasName || varName; 178 ProviderConsumerUtilV2.addProvideConsumeVariableDecoMeta(proto, varName, providedUnderName, '@Provider'); 179 trackInternal(proto, varName); 180 }; 181}; // @Provider 182 183/** 184 * @Consumer variable decorator of @ComponentV2 variable 185 * 186 * @Consumer(alias? : string) varName : typeName = defaultValue 187* 188 * @param alias defaults to varName 189 * 190 * allowed value: simple or object type value allowed. Objects must be instances of 191 * ObservedV2, Array, Map, Set, or Date for changes to be observed. No functions allowed 192 * syncs two-way with the @Provider variable with same `alias` name in nearest ancestor @ComponentV2 193 * local init required, used only if no @Provider counter part is found. 194 * no init or update from parent @ComponentV2 via constructor allowed 195 * new value assignment allowed, changes sys back to @Provider of one exists, otherwise update local value. 196 * 197 * part of SDK 198 * @since 12 199 */ 200const Consumer = (aliasName?: string) => { 201 return (proto: object, varName: string): void => { 202 const searchForProvideWithName: string = aliasName || varName; 203 204 // redefining the property happens when owning ViewV2 gets constructed 205 // and @Consumer gets connected to @provide counterpart 206 ProviderConsumerUtilV2.addProvideConsumeVariableDecoMeta(proto, varName, searchForProvideWithName, '@Consumer'); 207 const providerName = (aliasName === undefined || aliasName === null || 208 (typeof aliasName === 'string' && aliasName.trim() === '') 209 ) ? varName : aliasName; 210 211 Reflect.defineProperty(proto, varName, { 212 get() { 213 // this get function should never be called, 214 // because transpiler will always assign it a value first. 215 stateMgmtConsole.warn('@Consumer outer "get" should never be called, internal error!'); 216 return undefined; 217 }, 218 set(val) { 219 let providerInfo = ProviderConsumerUtilV2.findProvider(this, providerName); 220 if (providerInfo && providerInfo[0] && providerInfo[1]) { 221 ProviderConsumerUtilV2.connectConsumer2Provider(this, varName, providerInfo[0], providerInfo[1]); 222 } else { 223 ProviderConsumerUtilV2.defineConsumerWithoutProvider(this, varName, val); 224 } 225 }, 226 enumerable: true 227 }); 228 }; 229}; // @Consumer 230 231/** 232 * @Monitor class function decorator, inside either @ComponentV2 or @ObservedV2 class 233 * 234 * @Monitor(path: string, paths: string[]) functionName (m : IMonitor) : void 235 * 236 * @param path : string , path of monitored object properties (strictly objects, no arrays, maps etc) 237 * property names separated by '.'. 238 * @param paths : string[] , further, optional paths to monitor 239 * 240 * 241 * The decorated function must have one parameter of type IMonitor and no return value. 242 * 243 * Example: @Monitor('varName.obj', 'varName.obj.proA', 'varName2') onChange(m : IMonitor) : void { ... } 244 * monitors assignments to this.varName.obj, this.varName.obj.propA, and this.varName2 . 245 * 246 * part of SDK 247 * @since 12 248 */ 249const Monitor = function (key : string, ...keys: string[]): (target: any, _: any, descriptor: any) => void { 250 const pathsUniqueString = keys ? [key, ...keys].join(' ') : key; 251 return function (target, _, descriptor): void { 252 ObserveV2.addMethodDecoMeta(target, descriptor.value.name, '@Monitor'); 253 stateMgmtConsole.debug(`@Monitor('${pathsUniqueString}')`); 254 let watchProp = Symbol.for(MonitorV2.WATCH_PREFIX + target.constructor.name); 255 const monitorFunc = descriptor.value; 256 target[watchProp] ? target[watchProp][pathsUniqueString] = monitorFunc : target[watchProp] = { [pathsUniqueString]: monitorFunc }; 257 }; 258}; 259 260/** 261* @Monitor decorated function parameter type IMonitor 262* and sub-type IMonitorValue<T> 263* 264* part of SDK 265* @from 12 266*/ 267interface IMonitor { 268 dirty: Array<string>; 269 value<T>(key?: string): IMonitorValue<T> | undefined; 270 } 271 272 interface IMonitorValue<T> { 273 before: T; 274 now: T; 275 path: string; 276 } 277 278 279 /** 280 * @Computed TS computed class member variable decorator, inside either @ComponentV2 or @ObservedV2 class 281 * 282 * must be a computed class property following TS syntax, e.g. @Computed get varName() { return this.state1 + this.state2 } 283 * value assignment / set not allowed = has no setter. 284 * The framework updates the value of the @Computed variable whenever its input changes 285 * Therefore, the getter function must only use variables whose changes can be observed. 286 * The getter function implementation must not mutate any state. 287 * Changes of the return value of the getter function must be observable to use for constructing UI. 288 * This means if the return value is an object, it must be @ObservedV2 class instance with @Trace 'ed properties, 289 * or of Array, Map, Set, or Date type. 290 * The app should not modify the return value because re-execution of the getter function would overwrite these changes. 291 * 292 * part of SDK 293 * @from 12 294 * 295 */ 296const Computed = (target: Object, propertyKey: string, descriptor: PropertyDescriptor): void => { 297 stateMgmtConsole.debug(`@Computed ${propertyKey}`); 298 ObserveV2.addMethodDecoMeta(target, propertyKey, '@Computed'); 299 let watchProp = Symbol.for(ComputedV2.COMPUTED_PREFIX + target.constructor.name); 300 const computeFunction = descriptor.get; 301 target[watchProp] ? target[watchProp][propertyKey] = computeFunction 302 : target[watchProp] = { [propertyKey]: computeFunction }; 303 304}; 305