• 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(`@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