• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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