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.
- package/lib/AppLoader.d.ts +5 -1
- package/lib/AppLoader.js +49 -27
- package/lib/ClusterAppLoader.d.ts +1 -1
- package/lib/ClusterAppLoader.js +2 -0
- package/lib/DbStore.d.ts +1 -1
- package/lib/Synchronizer.d.ts +11 -2
- package/lib/Synchronizer.js +36 -8
- package/lib/cluster/DataSubscriber.d.ts +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/routines/update.d.ts +65 -0
- package/lib/routines/update.js +784 -0
- package/lib/types/Sync.d.ts +16 -16
- package/lib/upgrade.d.ts +50 -0
- package/lib/upgrade.js +262 -0
- package/lib/utils/dbPriority.d.ts +1 -1
- package/lib/utils/dbPriority.js +1 -1
- package/lib/utils/initializeData.d.ts +5 -0
- package/lib/utils/initializeData.js +61 -0
- package/package.json +14 -8
package/lib/AppLoader.d.ts
CHANGED
|
@@ -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-
|
|
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
|
-
|
|
250
|
-
|
|
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
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
545
|
-
return process.env.NODE_ENV === 'development' ? now - 30 * 1000 : now - 120 * 1000;
|
|
561
|
+
return this.lastCheckpointTs;
|
|
546
562
|
}
|
|
547
|
-
|
|
548
|
-
|
|
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-
|
|
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';
|
package/lib/ClusterAppLoader.js
CHANGED
|
@@ -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-
|
|
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';
|
package/lib/Synchronizer.d.ts
CHANGED
|
@@ -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-
|
|
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
|
package/lib/Synchronizer.js
CHANGED
|
@@ -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,
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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-
|
|
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
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 {};
|