• 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 only framework internal classes and functions
19 * non are part of SDK. Do not access from app.
20 *
21 *
22 * Helper class for handling V2 decorated variables
23 */
24class VariableUtilV3 {
25  /**
26     * setReadOnlyAttr - helper function used to update @param
27     * from parent @Component. Not allowed for @param @once .
28     * @param target  - the object, usually the ViewV2
29     * @param attrName - @param variable name
30     * @param newValue - update to new value
31     */
32  public static initParam<Z>(target: object, attrName: string, newValue: Z): void {
33    const meta = target[ObserveV2.V2_DECO_META]?.[attrName];
34    if (!meta || meta.deco !== '@param') {
35      const error = `Use initParam(${attrName}) only to init @param. Internal error!`;
36      stateMgmtConsole.error(error);
37      throw new Error(error);
38    }
39    // prevent update for @param @once
40    const storeProp = ObserveV2.OB_PREFIX + attrName;
41    stateMgmtConsole.propertyAccess(`initParam '@param ${attrName}' - setting backing store`);
42    target[storeProp] = newValue;
43    ObserveV2.getObserve().addRef(target, attrName);
44  }
45
46  /**
47   * setReadOnlyAttr - helper function used to update @param
48   * from parent @Component. Not allowed for @param @once .
49   * @param target  - the object, usually the ViewV2
50   * @param attrName - @param variable name
51   * @param newValue - update to new value
52   */
53  public static updateParam<Z>(target: object, attrName: string, newValue: Z): void {
54    // prevent update for @param @once
55    const meta = target[ObserveV2.V2_DECO_META]?.[attrName];
56    if (!meta || meta.deco !== '@param') {
57      const error = `Use updateParm(${attrName}) only to update @param. Internal error!`;
58      stateMgmtConsole.error(error);
59      throw new Error(error);
60    }
61
62    const storeProp = ObserveV2.OB_PREFIX + attrName;
63    // @observed class and @track attrName
64    if (newValue === target[storeProp]) {
65      stateMgmtConsole.propertyAccess(`updateParm '@param ${attrName}' unchanged. Doing nothing.`);
66      return;
67    }
68    if (meta.deco2 === '@once') {
69      // @param @once - init but no update
70      stateMgmtConsole.log(`updateParm: '@param @once ${attrName}' - Skip updating.`);
71    } else {
72      stateMgmtConsole.propertyAccess(`updateParm '@param ${attrName}' - updating backing store and fireChange.`);
73      target[storeProp] = newValue;
74      ObserveV2.getObserve().fireChange(target, attrName);
75    }
76  }
77}
78
79class ProviderConsumerUtilV2 {
80  private static readonly ALIAS_PREFIX = '___pc_alias_';
81
82  /**
83   *  meta added to the ViewV2
84   *  varName: { deco: '@Provider' | '@Consumer', aliasName: ..... }
85   *  prefix_@Provider_aliasName: {'varName': ..., 'aliasName': ...., 'deco': '@Provider' | '@Consumer'
86   */
87  private static metaAliasKey(aliasName: string, deco: '@Provider' | '@Consumer') : string {
88    return `${ProviderConsumerUtilV2.ALIAS_PREFIX}_${deco}_${aliasName}`;
89  }
90
91  /**
92   * Helper function to add meta data about @Provider and @Consumer decorators to ViewV2
93   * similar to @see addVariableDecoMeta, but adds the alias to allow search from @Consumer for @Provider counterpart
94   * @param proto prototype object of application class derived from ViewV2
95   * @param varName decorated variable
96   * @param deco '@state', '@event', etc (note '@model' gets transpiled in '@param' and '@event')
97   */
98  public static addProvideConsumeVariableDecoMeta(proto: Object, varName: string, aliasName: string, deco: '@Provider' | '@Consumer'): void {
99    // add decorator meta data to prototype
100    const meta = proto[ObserveV2.V2_DECO_META] ??= {};
101    // note: aliasName is the actual alias not the prefixed version
102    meta[varName] = { 'deco': deco, 'aliasName': aliasName };
103
104    // prefix to avoid name collisions with variable of same name as the alias!
105    const aliasProp = ProviderConsumerUtilV2.metaAliasKey(aliasName, deco);
106    meta[aliasProp] = { 'varName': varName, 'aliasName': aliasName, 'deco': deco };
107  }
108
109  /**
110  * find a @Provider'ed variable from its nearest ancestor ViewV2.
111  * @param searchingAliasName The key name to search for.
112  * @returns A tuple containing the ViewPU instance where the provider is found
113  * and the provider name
114  * If root @Component reached without finding, returns undefined.
115  */
116  public static findProvider(view: ViewV2, aliasName: string): [ViewV2, string] | undefined {
117    let checkView : IView | undefined = view?.getParent();
118    const searchingPrefixedAliasName = ProviderConsumerUtilV2.metaAliasKey(aliasName, '@Provider');
119    stateMgmtConsole.debug(`findProvider: Try to connect ${view.debugInfo__()} '@Consumer ${aliasName}' to @Provider counterpart....`);
120    while (checkView) {
121      const meta = checkView.constructor?.prototype[ObserveV2.V2_DECO_META];
122      if (checkView instanceof ViewV2 && meta && meta[searchingPrefixedAliasName]) {
123        const aliasMeta = meta[searchingPrefixedAliasName];
124        const providedVarName: string | undefined = (aliasMeta && (aliasMeta.deco === '@Provider') ? aliasMeta.varName : undefined);
125
126        if (providedVarName) {
127          stateMgmtConsole.debug(`findProvider: success: ${checkView.debugInfo__()} has matching @Provider('${aliasName}') ${providedVarName}`);
128          return [checkView, providedVarName];
129        }
130      }
131      checkView = checkView.getParent();
132    }; // while
133    stateMgmtConsole.warn(`findProvider: ${view.debugInfo__()} @Consumer('${aliasName}'), no matching @Provider found amongst ancestor @ComponentV2's!`);
134    return undefined;
135  }
136
137  /**
138  * Connects a consumer property of a view (`consumeView`) to a provider property of another view (`provideView`).
139  * This function establishes a link between the consumer and provider, allowing the consumer to access and update
140  * the provider's value directly. If the provider view is garbage collected, attempts to access the provider
141  * property will throw an error.
142  *
143  * @param consumeView - The view object that consumes data from the provider.
144  * @param consumeVarName - The name of the property in the consumer view that will be linked to the provider.
145  * @param provideView - The view object that provides the data to the consumer.
146  * @param provideVarName - The name of the property in the provider view that the consumer will access.
147  *
148  */
149  public static connectConsumer2Provider(consumeView: ViewV2, consumeVarName: string, provideView: ViewV2, provideVarName: string): T {
150    const weakView = new WeakRef<ViewV2>(provideView);
151    const provideViewName = provideView.constructor?.name;
152    Reflect.defineProperty(consumeView, consumeVarName, {
153      get() {
154        let view = weakView.deref();
155        stateMgmtConsole.propertyAccess(`@Consumer ${consumeVarName} get`);
156        ObserveV2.getObserve().addRef(this, consumeVarName);
157        if (!view) {
158          const error = `${this.debugInfo__()}: get() on @Consumer ${consumeVarName}: providing @ComponentV2 with @Provider ${provideViewName} no longer exists. Application error.`;
159          stateMgmtConsole.error(error);
160          throw new Error(error);
161        }
162        return view[provideVarName];
163      },
164      set(val) {
165        let view = weakView.deref();
166        // If the object has not been observed, you can directly assign a value to it. This improves performance.
167        stateMgmtConsole.propertyAccess(`@Consumer ${consumeVarName} set`);
168        if (!view) {
169          const error = `${this.debugInfo__()}: set() on @Consumer ${consumeVarName}: providing @ComponentV2 with @Provider ${provideViewName} no longer exists. Application error.`;
170          stateMgmtConsole.error(error);
171          throw new Error(error);
172        }
173
174        if (val !== view[provideVarName]) {
175          stateMgmtConsole.propertyAccess(`@Consumer ${consumeVarName} valueChanged`);
176          view[provideVarName] = val;
177          if (this[ObserveV2.SYMBOL_REFS]) { // This condition can improve performance.
178            ObserveV2.getObserve().fireChange(this, consumeVarName);
179          }
180        }
181      },
182      enumerable: true
183    });
184    return provideView[provideVarName];
185  }
186
187  public static defineConsumerWithoutProvider(consumeView: ViewV2, consumeVarName: string, consumerLocalVal: T): T {
188    stateMgmtConsole.debug(`defineConsumerWithoutProvider: ${consumeView.debugInfo__()} @Consumer ${consumeVarName} does not have @Provider counter part, will use local init value`);
189
190    const storeProp = ObserveV2.OB_PREFIX + consumeVarName;
191    consumeView[storeProp] = consumerLocalVal; // use local init value, also as backing store
192    Reflect.defineProperty(consumeView, consumeVarName, {
193      get() {
194        ObserveV2.getObserve().addRef(this, consumeVarName);
195        return ObserveV2.autoProxyObject(this, ObserveV2.OB_PREFIX + consumeVarName);
196      },
197      set(val) {
198        if (val !== this[storeProp]) {
199          this[storeProp] = val;
200          if (this[ObserveV2.SYMBOL_REFS]) { // This condition can improve performance.
201            ObserveV2.getObserve().fireChange(this, consumeVarName);
202          }
203        }
204      },
205      enumerable: true
206    });
207    return consumeView[storeProp];
208  }
209}
210
211/*
212  Internal decorator for @Trace without usingV2ObservedTrack call.
213  Real @Trace decorator function is in v2_decorators.ts
214*/
215const Trace_Internal = (target: Object, propertyKey: string): void => {
216  return trackInternal(target, propertyKey);
217};
218
219/*
220  Internal decorator for @ObservedV2 without usingV2ObservedTrack call.
221  Real @ObservedV2 decorator function is in v2_decorators.ts
222*/
223function ObservedV2_Internal<T extends ConstructorV2>(BaseClass: T): T {
224  return observedV2Internal<T>(BaseClass);
225}
226
227/*
228  @ObservedV2 decorator function uses this in v2_decorators.ts
229*/
230function observedV2Internal<T extends ConstructorV2>(BaseClass: T): T {
231
232  // prevent @Track inside @observed class
233  if (BaseClass.prototype && Reflect.has(BaseClass.prototype, TrackedObject.___IS_TRACKED_OPTIMISED)) {
234    const error = `'@observed class ${BaseClass?.name}': invalid use of V2 @Track decorator inside V3 @observed class. Need to fix class definition to use @track.`;
235    stateMgmtConsole.applicationError(error);
236    throw new Error(error);
237  }
238
239  if (BaseClass.prototype && !Reflect.has(BaseClass.prototype, ObserveV2.V2_DECO_META)) {
240    // not an error, suspicious of developer oversight
241    stateMgmtConsole.warn(`'@observed class ${BaseClass?.name}': no @track property inside. Is this intended? Check our application.`);
242  }
243
244  // Use ID_REFS only if number of observed attrs is significant
245  const attrList = Object.getOwnPropertyNames(BaseClass.prototype);
246  const count = attrList.filter(attr => attr.startsWith(ObserveV2.OB_PREFIX)).length;
247  if (count > 5) {
248    stateMgmtConsole.log(`'@observed class ${BaseClass?.name}' configured to use ID_REFS optimization`);
249    BaseClass.prototype[ObserveV2.ID_REFS] = {};
250  }
251  const observedClass = class extends BaseClass {
252    constructor(...args) {
253      super(...args);
254      AsyncAddComputedV2.addComputed(this, BaseClass.name);
255      AsyncAddMonitorV2.addMonitor(this, BaseClass.name);
256    }
257  };
258  Object.defineProperty(observedClass, 'name', { value: BaseClass.name });
259  return observedClass;
260}
261