oak-backend-base 4.1.28 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
2
2
  import { AppLoader as GeneralAppLoader, Trigger, EntityDict, Watcher, OpRecord, FreeTimer, OperationResult, BaseTimer } from "oak-domain/lib/types";
3
- import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
3
+ import { BackendRuntimeContext } from 'oak-domain/lib/context/BackendRuntimeContext';
4
4
  import { IncomingHttpHeaders, IncomingMessage } from 'http';
5
5
  import { Namespace } from 'socket.io';
6
6
  import DataSubscriber from './cluster/DataSubscriber';
@@ -8,6 +8,7 @@ import Synchronizer from './Synchronizer';
8
8
  import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
9
9
  import { InternalErrorHandler } from './types';
10
10
  import { AppDbStore } from './DbStore';
11
+ import { AppLoaderUpgradeOptions, AppLoaderUpgradeResult } from './upgrade';
11
12
  export declare class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> extends GeneralAppLoader<ED, Cxt> {
12
13
  protected dbStore: AppDbStore<ED, Cxt>;
13
14
  private aspectDict;
@@ -54,6 +55,7 @@ export declare class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt exten
54
55
  message?: string;
55
56
  }>;
56
57
  initialize(ifExists?: 'drop' | 'omit' | 'dropIfNotStatic'): Promise<void>;
58
+ upgrade(options?: AppLoaderUpgradeOptions): Promise<AppLoaderUpgradeResult>;
57
59
  getStore(): AppDbStore<ED, Cxt>;
58
60
  getEndpoints(prefix: string): [string, "post" | "get" | "put" | "delete", string, (params: Record<string, string>, headers: IncomingHttpHeaders, req: IncomingMessage, body?: any) => Promise<{
59
61
  headers?: Record<string, string | string[]>;
@@ -87,7 +89,9 @@ export declare class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt exten
87
89
  */
88
90
  private selectForWBWatcher;
89
91
  protected execWatcher(watcher: Watcher<ED, keyof ED, Cxt>): Promise<OperationResult<ED> | undefined>;
92
+ private lastCheckpointTs;
90
93
  protected getCheckpointTs(): number;
94
+ protected setCheckpointTs(ts: number): void;
91
95
  protected checkpoint(): Promise<number>;
92
96
  startWatchers(): void;
93
97
  protected execBaseTimer(timer: BaseTimer<ED, Cxt>, context: Cxt): Promise<OperationResult<ED>> | undefined;
package/lib/AppLoader.js CHANGED
@@ -15,10 +15,11 @@ const dependencyBuilder_1 = require("oak-domain/lib/compiler/dependencyBuilder")
15
15
  const DataSubscriber_1 = tslib_1.__importDefault(require("./cluster/DataSubscriber"));
16
16
  const env_1 = require("./cluster/env");
17
17
  const Synchronizer_1 = tslib_1.__importDefault(require("./Synchronizer"));
18
- const i18n_1 = tslib_1.__importDefault(require("oak-domain/lib/data/i18n"));
19
18
  const requirePrj_1 = tslib_1.__importDefault(require("./utils/requirePrj"));
20
19
  const dbPriority_1 = require("./utils/dbPriority");
21
20
  const DbStore_1 = require("./DbStore");
21
+ const upgrade_1 = require("./upgrade");
22
+ const initializeData_1 = require("./utils/initializeData");
22
23
  class AppLoader extends types_1.AppLoader {
23
24
  dbStore;
24
25
  aspectDict;
@@ -241,13 +242,11 @@ class AppLoader extends types_1.AppLoader {
241
242
  async initialize(ifExists) {
242
243
  await this.dbStore.initialize({ ifExists });
243
244
  const data = this.requireSth('lib/data/index');
244
- // oak-domain中只有i18n
245
- (0, assert_1.default)(data.i18n);
246
- data.i18n.push(...i18n_1.default);
247
245
  const context = this.contextBuilder(this.dbStore);
248
246
  context.openRootMode();
249
- for (const entity in data) {
250
- let rows = data[entity];
247
+ const entities = (0, initializeData_1.getInitializationOrder)(data, this.dbStore.getSchema());
248
+ for (const entity of entities) {
249
+ const rows = data[entity];
251
250
  if (rows.length > 0) {
252
251
  await context.begin();
253
252
  // 如果是static的对象,只要表中有数据就pass
@@ -267,23 +266,16 @@ class AppLoader extends types_1.AppLoader {
267
266
  }
268
267
  // 再插入所有的行
269
268
  try {
270
- const insertRows = async (idx) => {
271
- const rows2 = rows.slice(idx, 1000);
272
- if (rows2.length > 0) {
273
- await this.dbStore.operate(entity, {
274
- data: rows,
275
- action: 'create',
276
- }, context, {
277
- dontCollect: true,
278
- dontCreateOper: true,
279
- blockTrigger: true,
280
- });
281
- if (rows2.length === 1000) {
282
- await insertRows(idx + 1000);
283
- }
284
- }
285
- };
286
- await insertRows(0);
269
+ for (const rows2 of (0, initializeData_1.chunkRows)(rows)) {
270
+ await this.dbStore.operate(entity, {
271
+ data: rows2,
272
+ action: 'create',
273
+ }, context, {
274
+ dontCollect: true,
275
+ dontCreateOper: true,
276
+ blockTrigger: true,
277
+ });
278
+ }
287
279
  await context.commit();
288
280
  console.log(`data in ${entity} initialized, ${rows.length} rows inserted`);
289
281
  }
@@ -296,6 +288,30 @@ class AppLoader extends types_1.AppLoader {
296
288
  }
297
289
  // await this.dbStore.disconnect(); // 不需要马上断开连接,在initialize后可能还会有操作,unmount时会断开
298
290
  }
291
+ async upgrade(options = {}) {
292
+ const plan = await this.dbStore.makeUpgradePlan({
293
+ largeTableRowThreshold: options.largeTableRowThreshold,
294
+ });
295
+ let outputDir;
296
+ let files;
297
+ if (options.outputDir) {
298
+ outputDir = (0, path_1.resolve)(this.path, options.outputDir);
299
+ files = await (0, upgrade_1.writeUpgradeArtifacts)(plan, outputDir, this.dbStore.supportsTransactionalDdl());
300
+ }
301
+ const executed = await (0, upgrade_1.applyUpgradePlan)(this.dbStore, plan, options);
302
+ const remainingPlan = executed.total > 0
303
+ ? await this.dbStore.makeUpgradePlan({
304
+ largeTableRowThreshold: options.largeTableRowThreshold,
305
+ })
306
+ : undefined;
307
+ return {
308
+ plan,
309
+ remainingPlan,
310
+ outputDir,
311
+ files,
312
+ executed,
313
+ };
314
+ }
299
315
  getStore() {
300
316
  return this.dbStore;
301
317
  }
@@ -540,12 +556,18 @@ class AppLoader extends types_1.AppLoader {
540
556
  // 这里只需要清理被执行的行,因为被跳过的行本来就不是这一次执行被占用的。
541
557
  }
542
558
  }
559
+ lastCheckpointTs = 0;
543
560
  getCheckpointTs() {
544
- const now = Date.now();
545
- return process.env.NODE_ENV === 'development' ? now - 30 * 1000 : now - 120 * 1000;
561
+ return this.lastCheckpointTs;
546
562
  }
547
- checkpoint() {
548
- return this.dbStore.checkpoint(this.getCheckpointTs());
563
+ setCheckpointTs(ts) {
564
+ this.lastCheckpointTs = ts;
565
+ }
566
+ async checkpoint() {
567
+ const now = Date.now();
568
+ const count = await this.dbStore.checkpoint(this.getCheckpointTs());
569
+ this.setCheckpointTs(now);
570
+ return count;
549
571
  }
550
572
  startWatchers() {
551
573
  const watchers = this.requireSth('lib/watchers/index');
@@ -1,6 +1,6 @@
1
1
  import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
2
2
  import { EntityDict, OperationResult, Trigger, Watcher, FreeTimer, BaseTimer } from 'oak-domain/lib/types';
3
- import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
3
+ import { BackendRuntimeContext } from 'oak-domain/lib/context/BackendRuntimeContext';
4
4
  import { AppLoader } from './AppLoader';
5
5
  import { Namespace } from 'socket.io';
6
6
  import { Socket } from 'socket.io-client';
@@ -183,6 +183,7 @@ class ClusterAppLoader extends AppLoader_1.AppLoader {
183
183
  async checkpoint() {
184
184
  const { instanceCount, instanceId } = (0, env_1.getClusterInfo)();
185
185
  let count = 0;
186
+ const now = Date.now();
186
187
  for (const name in this.commitTriggers) {
187
188
  if (this.commitTriggers[name]) {
188
189
  count += await this.dbStore.independentCheckPoint(name, this.getCheckpointTs(), instanceCount, instanceId);
@@ -191,6 +192,7 @@ class ClusterAppLoader extends AppLoader_1.AppLoader {
191
192
  count += await this.dbStore.independentCheckPoint(name, this.getCheckpointTs());
192
193
  }
193
194
  }
195
+ this.setCheckpointTs(now);
194
196
  return count;
195
197
  }
196
198
  }
package/lib/DbStore.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { DbConfiguration } from 'oak-db/src/types/configuration';
2
2
  import { EntityDict, StorageSchema, Trigger, Checker, SelectFreeEntities, UpdateFreeDict, AuthDeduceRelationMap, VolatileTrigger, OperateOption } from 'oak-domain/lib/types';
3
3
  import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
4
- import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
4
+ import { BackendRuntimeContext } from 'oak-domain/lib/context/BackendRuntimeContext';
5
5
  import { DbTypeSymbol } from './utils/dbPriority';
6
6
  import { CascadeStore } from 'oak-domain/lib/store/CascadeStore';
7
7
  import { DbStore } from 'oak-db/lib/types/dbStore';
@@ -1,13 +1,22 @@
1
1
  import { EntityDict, StorageSchema, EndpointItem, SyncConfig } from 'oak-domain/lib/types';
2
2
  import { VolatileTrigger } from 'oak-domain/lib/types/Trigger';
3
3
  import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
4
- import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
4
+ import { BackendRuntimeContext } from 'oak-domain/lib/context/BackendRuntimeContext';
5
+ export type FetchFn = (url: string, options: {
6
+ method: string;
7
+ headers: Record<string, string>;
8
+ body: string;
9
+ }, timeout?: number) => Promise<{
10
+ status: number;
11
+ json: () => Promise<any>;
12
+ }>;
5
13
  export default class Synchronizer<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> {
6
14
  private config;
7
15
  private schema;
8
16
  private remotePullInfoMap;
9
17
  private channelDict;
10
18
  private contextBuilder;
19
+ private fetchFn;
11
20
  private pushAccessMap;
12
21
  private startChannel2;
13
22
  /**开始同步这些channel上的oper。注意,这时候即使某个channel上失败了,也不应影响本事务提交(其它的channel成功了) */
@@ -22,7 +31,7 @@ export default class Synchronizer<ED extends EntityDict & BaseEntityDict, Cxt ex
22
31
  */
23
32
  private trySynchronizeOpers;
24
33
  private makeCreateOperTrigger;
25
- constructor(config: SyncConfig<ED, Cxt>, schema: StorageSchema<ED>, contextBuilder: () => Cxt);
34
+ constructor(config: SyncConfig<ED, Cxt>, schema: StorageSchema<ED>, contextBuilder: () => Cxt, fetchFn?: FetchFn);
26
35
  /**
27
36
  * 根据sync的定义,生成对应的 commit triggers
28
37
  * @returns
@@ -5,11 +5,11 @@ const crypto_1 = require("crypto");
5
5
  const types_1 = require("oak-domain/lib/types");
6
6
  const relationPath_1 = require("oak-domain/lib/utils/relationPath");
7
7
  const assert_1 = tslib_1.__importDefault(require("assert"));
8
- const path_1 = require("path");
9
8
  const lodash_1 = require("oak-domain/lib/utils/lodash");
10
9
  const filter_1 = require("oak-domain/lib/store/filter");
11
10
  const uuid_1 = require("oak-domain/lib/utils/uuid");
12
11
  const lodash_2 = require("lodash");
12
+ const domain_1 = require("oak-domain/lib/utils/domain");
13
13
  const OAK_SYNC_HEADER_ENTITY = 'oak-sync-entity';
14
14
  const OAK_SYNC_HEADER_ENTITY_ID = 'oak-sync-entity-id';
15
15
  const OAK_SYNC_HEADER_TIMESTAMP = 'oak-sync-timestamp';
@@ -67,6 +67,7 @@ class Synchronizer {
67
67
  remotePullInfoMap = {};
68
68
  channelDict = {};
69
69
  contextBuilder;
70
+ fetchFn;
70
71
  pushAccessMap = {};
71
72
  async startChannel2(context, channel) {
72
73
  const { queue, api, selfEncryptInfo, entity, entityId, onFailed, timeout = 5000 } = channel;
@@ -78,12 +79,12 @@ class Synchronizer {
78
79
  seq: ele.$$seq$$,
79
80
  }))), 'txnId:', context.getCurrentTxnId());
80
81
  }
81
- const finalApi = (0, path_1.join)(api, selfEncryptInfo.id);
82
+ const finalApi = (0, domain_1.urlJoin)(api, selfEncryptInfo.id);
82
83
  channel.queue = [];
83
84
  try {
84
85
  const body = JSON.stringify(opers);
85
86
  const { ts, nonce, signature } = await sign(selfEncryptInfo.privateKey, body);
86
- const res = await fetchWithTimeout(finalApi, {
87
+ const res = await this.fetchFn(finalApi, {
87
88
  method: 'post',
88
89
  headers: {
89
90
  'Content-Type': 'application/json',
@@ -190,7 +191,7 @@ class Synchronizer {
190
191
  if (!this.channelDict[userId]) {
191
192
  // channel上缓存这些信息,暂不支持动态更新
192
193
  this.channelDict[userId] = {
193
- api: (0, path_1.join)(url, 'endpoint', endpoint),
194
+ api: (0, domain_1.urlJoin)(url, 'endpoint', endpoint),
194
195
  queue: [],
195
196
  entity: remoteEntity,
196
197
  entityId: remoteEntityId,
@@ -205,7 +206,7 @@ class Synchronizer {
205
206
  (0, assert_1.default)(this.channelDict[userId].onFailed === onFailed);
206
207
  }
207
208
  const channel = this.channelDict[userId];
208
- (0, assert_1.default)(channel.api === (0, path_1.join)(url, 'endpoint', endpoint));
209
+ (0, assert_1.default)(channel.api === (0, domain_1.urlJoin)(url, 'endpoint', endpoint));
209
210
  (0, assert_1.default)(channel.entity === remoteEntity);
210
211
  (0, assert_1.default)(channel.entityId === remoteEntityId);
211
212
  if (channel.queue.find(ele => ele.oper.id === oper.id)) {
@@ -343,8 +344,19 @@ class Synchronizer {
343
344
  }, { dontCollect: true, forUpdate: true });
344
345
  dirtyOpers = dirtyOpers.filter(ele => !!ele[types_1.TriggerUuidAttribute]);
345
346
  if (dirtyOpers.length > 0) {
347
+ // 检查所有 channel 的 queue 是否已清空
346
348
  for (const c in this.channelDict) {
347
- (0, assert_1.default)(this.channelDict[c].queue.length === 0);
349
+ // 在生产环境不使用assert,而是清空队列,以防止脏数据堆积影响后续同步
350
+ if (this.channelDict[c].queue.length > 0) {
351
+ if (process.env.NODE_ENV !== 'production') {
352
+ // 构建详细的错误信息
353
+ const queuedOperIds = this.channelDict[c].queue.map(q => q.oper.id).join(', ');
354
+ (0, assert_1.default)(false, `发现 channel ${c} 的 queue 未清空,包含 oper IDs: [${queuedOperIds}]`);
355
+ }
356
+ else {
357
+ this.channelDict[c].queue = [];
358
+ }
359
+ }
348
360
  }
349
361
  const pushedIds = [];
350
362
  const unPushedIds = [];
@@ -394,15 +406,30 @@ class Synchronizer {
394
406
  makeCreateOperTrigger() {
395
407
  const { config } = this;
396
408
  const { remotes, self } = config;
409
+ const checkActionsMap = {};
397
410
  // 根据remotes定义,建立从entity到需要同步的远端结点信息的Map
398
411
  remotes.forEach((remote) => {
399
412
  const { getPushInfo, pushEntities: pushEntityDefs, endpoint, pathToUser, relationName: rnRemote, onFailed, timeout } = remote;
400
413
  if (pushEntityDefs) {
401
414
  const pushEntities = [];
402
- const endpoint2 = (0, path_1.join)(endpoint || 'sync', self.entity);
415
+ const endpoint2 = (0, domain_1.urlJoin)(endpoint || 'sync', self.entity);
403
416
  for (const def of pushEntityDefs) {
404
417
  const { pathToRemoteEntity, pathToSelfEntity, relationName, recursive, entity, actions, onSynchronized } = def;
405
418
  pushEntities.push(entity);
419
+ (0, assert_1.default)(pathToRemoteEntity, 'pushEntityDef必须定义pathToRemoteEntity');
420
+ (0, assert_1.default)(pathToSelfEntity, 'pushEntityDef必须定义pathToSelfEntity');
421
+ (0, assert_1.default)(actions && actions.length > 0, 'pushEntityDef必须定义actions,并且长度必须大于0');
422
+ const checkKey = `${String(entity)}-${pathToRemoteEntity}-${pathToSelfEntity}`;
423
+ if (!checkActionsMap[checkKey]) {
424
+ checkActionsMap[checkKey] = new Set();
425
+ }
426
+ const checkActions = checkActionsMap[checkKey];
427
+ for (const action of actions) {
428
+ if (checkActions.has(action)) {
429
+ throw new Error(`pushEntityDef中发现重复的entity/pathToRemoteEntity/pathToSelfEntity/action组合,重复的action是「${action}」,entity是「${String(entity)}」,pathToRemoteEntity是「${pathToRemoteEntity}」,pathToSelfEntity是「${pathToSelfEntity}」`);
430
+ }
431
+ checkActions.add(action);
432
+ }
406
433
  const relationName2 = relationName || rnRemote;
407
434
  const path2 = pathToUser ? `${pathToRemoteEntity}.${pathToUser}` : pathToRemoteEntity;
408
435
  (0, assert_1.default)(!recursive);
@@ -507,10 +534,11 @@ class Synchronizer {
507
534
  };
508
535
  return createOperTrigger;
509
536
  }
510
- constructor(config, schema, contextBuilder) {
537
+ constructor(config, schema, contextBuilder, fetchFn) {
511
538
  this.config = config;
512
539
  this.schema = schema;
513
540
  this.contextBuilder = contextBuilder;
541
+ this.fetchFn = fetchFn || fetchWithTimeout;
514
542
  }
515
543
  /**
516
544
  * 根据sync的定义,生成对应的 commit triggers
@@ -1,6 +1,6 @@
1
1
  import { EntityDict, OpRecord } from 'oak-domain/lib/types';
2
2
  import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
3
- import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
3
+ import { BackendRuntimeContext } from 'oak-domain/lib/context/BackendRuntimeContext';
4
4
  import { Namespace } from 'socket.io';
5
5
  /**
6
6
  * 集群行为备忘:
package/lib/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { AppLoader } from './AppLoader';
2
2
  export { ClusterAppLoader } from './ClusterAppLoader';
3
3
  export * from './cluster/env';
4
+ export * from './upgrade';
package/lib/index.js CHANGED
@@ -7,3 +7,4 @@ Object.defineProperty(exports, "AppLoader", { enumerable: true, get: function ()
7
7
  var ClusterAppLoader_1 = require("./ClusterAppLoader");
8
8
  Object.defineProperty(exports, "ClusterAppLoader", { enumerable: true, get: function () { return ClusterAppLoader_1.ClusterAppLoader; } });
9
9
  tslib_1.__exportStar(require("./cluster/env"), exports);
10
+ tslib_1.__exportStar(require("./upgrade"), exports);
@@ -0,0 +1,65 @@
1
+ import { EntityDict as TypeEntityDict, StorageSchema } from 'oak-domain/lib/types';
2
+ import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
3
+ import BackendRuntimeContext from 'oak-domain/lib/context/BackendRuntimeContext';
4
+ import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
5
+ type EntityDict = TypeEntityDict & BaseEntityDict;
6
+ /**
7
+ * 反向引用描述
8
+ * 用于记录哪些实体引用了当前实体,便于在删除或更新时维护引用完整性
9
+ */
10
+ export type ReverseRefDesc<ED extends EntityDict> = {
11
+ type: 'ref' | 'refs';
12
+ attrName: string;
13
+ sourceEntity: keyof ED;
14
+ };
15
+ /**
16
+ * 更新计划配置
17
+ * 定义了数据同步的完整策略和生命周期钩子
18
+ */
19
+ export type UpdatePlan<ED extends EntityDict> = {
20
+ dontCreateOper?: boolean;
21
+ plan: {
22
+ [entityName in keyof ED]: {
23
+ /**
24
+ * 是否允许数据唯一性冲突时使用新数据覆盖旧数据(仅data中冲突)
25
+ * @deprecated ID必须相同但会导致ID检查不通过,弃用
26
+ */
27
+ allowDataUniqueReWrite?: boolean;
28
+ /**
29
+ * 在插入数据库遇到唯一性冲突时的处理方式(数据库和data冲突)
30
+ * skip表示跳过插入新数据,会将新数据的反指关系更新到已有数据上
31
+ * update表示使用新数据更新已有数据,会自动更新现有数据的所有反指关系
32
+ * error表示抛出错误
33
+ */
34
+ onUniqueViolation?: 'skip' | 'update' | 'error';
35
+ /**
36
+ * 处理数据库中存在但是数据文件中不存在的数据的方式
37
+ * skip表示跳过不处理
38
+ * delete表示做逻辑删除,可能导致插入新数据时id冲突
39
+ * 设置为physicalDelete可以保证最强的一致性
40
+ */
41
+ onOnlyExistingInDb?: 'skip' | 'delete' | 'physicalDelete';
42
+ };
43
+ };
44
+ beforeCheck?: (options: {
45
+ context: BackendRuntimeContext<ED>;
46
+ data: {
47
+ [entityName in keyof ED]?: Array<Partial<ED[entityName]['CreateOperationData']>>;
48
+ };
49
+ reverseRefs: {
50
+ [entityName in keyof ED]?: ReverseRefDesc<ED>[];
51
+ };
52
+ }) => Promise<void>;
53
+ afterUpdate?: (options: {
54
+ context: BackendRuntimeContext<ED>;
55
+ data: {
56
+ [entityName in keyof EntityDict]?: Array<Partial<EntityDict[entityName]['CreateOperationData']>>;
57
+ };
58
+ reverseRefs: {
59
+ [entityName in keyof EntityDict]?: ReverseRefDesc<ED>[];
60
+ };
61
+ }) => Promise<void>;
62
+ };
63
+ export declare const createUpdatePlan: <ED extends EntityDict>(options: UpdatePlan<ED>) => <Cxt extends AsyncContext<ED>>(context: Cxt) => Promise<void>;
64
+ export declare function checkPathValue(itemId: string, destEntity: keyof EntityDict, path: string, storageSchema: StorageSchema<EntityDict>): boolean;
65
+ export {};