oak-backend-base 3.4.1 → 3.4.2

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.js CHANGED
@@ -160,7 +160,7 @@ class AppLoader extends types_1.AppLoader {
160
160
  if (!initialize) {
161
161
  const { syncConfig: syncConfig } = this.getConfiguration();
162
162
  if (syncConfig) {
163
- this.synchronizer = new Synchronizer_1.default(syncConfig, this.dbStore.getSchema());
163
+ this.synchronizer = new Synchronizer_1.default(syncConfig, this.dbStore.getSchema(), () => this.contextBuilder()(this.dbStore));
164
164
  }
165
165
  this.initTriggers();
166
166
  }
@@ -8,6 +8,7 @@ export default class Synchronizer<ED extends EntityDict & BaseEntityDict, Cxt ex
8
8
  private remotePullInfoMap;
9
9
  private pullMaxBornAtMap;
10
10
  private channelDict;
11
+ private contextBuilder;
11
12
  private pushAccessMap;
12
13
  /**
13
14
  * 向某一个远端对象push opers。根据幂等性,这里如果失败了必须反复推送
@@ -17,6 +18,7 @@ export default class Synchronizer<ED extends EntityDict & BaseEntityDict, Cxt ex
17
18
  private startChannel;
18
19
  private startAllChannel;
19
20
  private pushOperToChannel;
21
+ private refineOperData;
20
22
  private dispatchOperToChannels;
21
23
  /**
22
24
  * 为了保证推送的oper序,采用从database中顺序读取所有需要推送的oper来进行推送
@@ -25,7 +27,7 @@ export default class Synchronizer<ED extends EntityDict & BaseEntityDict, Cxt ex
25
27
  */
26
28
  private trySynchronizeOpers;
27
29
  private makeCreateOperTrigger;
28
- constructor(config: SyncConfig<ED, Cxt>, schema: StorageSchema<ED>);
30
+ constructor(config: SyncConfig<ED, Cxt>, schema: StorageSchema<ED>, contextBuilder: () => Promise<Cxt>);
29
31
  /**
30
32
  * 根据sync的定义,生成对应的 commit triggers
31
33
  * @returns
@@ -17,6 +17,7 @@ class Synchronizer {
17
17
  remotePullInfoMap = {};
18
18
  pullMaxBornAtMap = {};
19
19
  channelDict = {};
20
+ contextBuilder;
20
21
  pushAccessMap = {};
21
22
  /**
22
23
  * 向某一个远端对象push opers。根据幂等性,这里如果失败了必须反复推送
@@ -76,7 +77,7 @@ class Synchronizer {
76
77
  aliveOperIds.push(...this.channelDict[k].queue.map(ele => ele.oper.id));
77
78
  }
78
79
  }
79
- const overIds = (0, lodash_1.difference)(successIds, aliveOperIds);
80
+ const overIds = (0, lodash_1.difference)(successIds.concat(redundantIds), aliveOperIds);
80
81
  if (overIds.length > 0) {
81
82
  await context.operate('oper', {
82
83
  id: await (0, uuid_1.generateNewIdAsync)(),
@@ -154,6 +155,24 @@ class Synchronizer {
154
155
  onSynchronized,
155
156
  });
156
157
  }
158
+ refineOperData(oper, rowIds) {
159
+ const { action, id, targetEntity, data, $$seq$$, filter } = oper;
160
+ const data2 = (action === 'create' && data instanceof Array) ? data.filter(ele => rowIds.includes(ele.id)) : data;
161
+ // 过滤掉数据中的跨事务trigger信息
162
+ if (data2 instanceof Array) {
163
+ data2.forEach((d) => {
164
+ (0, lodash_2.unset)(d, types_1.TriggerDataAttribute);
165
+ (0, lodash_2.unset)(d, types_1.TriggerUuidAttribute);
166
+ });
167
+ }
168
+ else {
169
+ (0, lodash_2.unset)(data2, types_1.TriggerDataAttribute);
170
+ (0, lodash_2.unset)(data2, types_1.TriggerUuidAttribute);
171
+ }
172
+ return {
173
+ id, action, targetEntity, data: data2, $$seq$$, filter,
174
+ };
175
+ }
157
176
  async dispatchOperToChannels(oper, context) {
158
177
  const { operatorId, targetEntity, filter, action, data } = oper;
159
178
  const entityIds = (0, filter_1.getRelevantIds)(filter);
@@ -192,18 +211,7 @@ class Synchronizer {
192
211
  }
193
212
  const selfEncryptInfo = encryptInfoDict[selfEntityId];
194
213
  // 推送到远端结点的oper
195
- const oper2 = {
196
- id: oper.id,
197
- action: action,
198
- data: (action === 'create' && data instanceof Array) ? data.filter(ele => rowIds.includes(ele.id)) : data,
199
- filter: {
200
- id: rowIds.length === 1 ? rowIds[0] : {
201
- $in: rowIds,
202
- }
203
- },
204
- $$seq$$: oper.$$seq$$,
205
- targetEntity,
206
- };
214
+ const oper2 = this.refineOperData(oper, rowIds);
207
215
  const { url } = await getRemoteAccessInfo(context, {
208
216
  userId,
209
217
  remoteEntityId: entityId,
@@ -227,69 +235,79 @@ class Synchronizer {
227
235
  * 每个进程都保证把当前所有的oper顺序处理掉,就不会有乱序的问题,大家通过database上的锁来完成同步
228
236
  * @param context
229
237
  */
230
- async trySynchronizeOpers(context) {
231
- let dirtyOpers = await context.select('oper', {
232
- data: {
233
- id: 1,
234
- },
235
- filter: {
236
- [types_1.TriggerDataAttribute]: {
237
- $exists: true,
238
- },
239
- }
240
- }, { dontCollect: true });
241
- if (dirtyOpers.length > 0) {
242
- // 这一步是加锁,保证只有一个进程完成推送,推送者提交前会将$$triggerData$$清零
243
- const ids = dirtyOpers.map(ele => ele.id);
244
- dirtyOpers = await context.select('oper', {
238
+ async trySynchronizeOpers() {
239
+ const context = await this.contextBuilder();
240
+ await context.begin();
241
+ try {
242
+ let dirtyOpers = await context.select('oper', {
245
243
  data: {
246
244
  id: 1,
247
- action: 1,
248
- data: 1,
249
- targetEntity: 1,
250
- operatorId: 1,
251
- [types_1.TriggerDataAttribute]: 1,
252
- bornAt: 1,
253
- $$createAt$$: 1,
254
- $$seq$$: 1,
255
- filter: 1,
256
245
  },
257
246
  filter: {
258
- id: { $in: ids },
259
- },
260
- }, { dontCollect: true, forUpdate: true });
261
- dirtyOpers = dirtyOpers.filter(ele => !!ele[types_1.TriggerDataAttribute]);
247
+ [types_1.TriggerDataAttribute]: {
248
+ $exists: true,
249
+ },
250
+ }
251
+ }, { dontCollect: true });
262
252
  if (dirtyOpers.length > 0) {
263
- const pushedIds = [];
264
- const unpushedIds = [];
265
- await Promise.all(dirtyOpers.map(async (oper) => {
266
- const result = await this.dispatchOperToChannels(oper, context);
267
- if (result) {
268
- pushedIds.push(oper.id);
253
+ // 这一步是加锁,保证只有一个进程完成推送,推送者提交前会将$$triggerData$$清零
254
+ const ids = dirtyOpers.map(ele => ele.id);
255
+ dirtyOpers = await context.select('oper', {
256
+ data: {
257
+ id: 1,
258
+ action: 1,
259
+ data: 1,
260
+ targetEntity: 1,
261
+ operatorId: 1,
262
+ [types_1.TriggerDataAttribute]: 1,
263
+ bornAt: 1,
264
+ $$createAt$$: 1,
265
+ $$seq$$: 1,
266
+ filter: 1,
267
+ },
268
+ filter: {
269
+ id: { $in: ids },
270
+ },
271
+ }, { dontCollect: true, forUpdate: true });
272
+ dirtyOpers = dirtyOpers.filter(ele => !!ele[types_1.TriggerDataAttribute]);
273
+ if (dirtyOpers.length > 0) {
274
+ const pushedIds = [];
275
+ const unpushedIds = [];
276
+ await Promise.all(dirtyOpers.map(async (oper) => {
277
+ const result = await this.dispatchOperToChannels(oper, context);
278
+ if (result) {
279
+ pushedIds.push(oper.id);
280
+ }
281
+ else {
282
+ unpushedIds.push(oper.id);
283
+ }
284
+ }));
285
+ if (unpushedIds.length > 0) {
286
+ await context.operate('oper', {
287
+ id: await (0, uuid_1.generateNewIdAsync)(),
288
+ action: 'update',
289
+ data: {
290
+ [types_1.TriggerDataAttribute]: null,
291
+ [types_1.TriggerUuidAttribute]: null,
292
+ },
293
+ filter: {
294
+ id: {
295
+ $in: unpushedIds,
296
+ }
297
+ }
298
+ }, {});
269
299
  }
270
- else {
271
- unpushedIds.push(oper.id);
300
+ if (pushedIds.length > 0) {
301
+ await this.startAllChannel(context);
272
302
  }
273
- }));
274
- if (unpushedIds.length > 0) {
275
- await context.operate('oper', {
276
- id: await (0, uuid_1.generateNewIdAsync)(),
277
- action: 'update',
278
- data: {
279
- [types_1.TriggerDataAttribute]: null,
280
- [types_1.TriggerUuidAttribute]: null,
281
- },
282
- filter: {
283
- id: {
284
- $in: unpushedIds,
285
- }
286
- }
287
- }, {});
288
- }
289
- if (pushedIds.length > 0) {
290
- await this.startAllChannel(context);
291
303
  }
292
304
  }
305
+ await context.commit();
306
+ }
307
+ catch (err) {
308
+ await context.rollback();
309
+ console.error(err);
310
+ throw err;
293
311
  }
294
312
  }
295
313
  makeCreateOperTrigger() {
@@ -395,18 +413,19 @@ class Synchronizer {
395
413
  return pushEntities.includes(data.targetEntity)
396
414
  && !!this.pushAccessMap[targetEntity].find(({ actions }) => !actions || actions.includes(action));
397
415
  },
398
- fn: async ({ ids }, context) => {
416
+ fn: async ({ ids }) => {
399
417
  (0, assert_1.default)(ids.length === 1);
400
- this.trySynchronizeOpers(context);
418
+ this.trySynchronizeOpers();
401
419
  // 内部自主处理triggerData,因此不需要让triggerExecutor处理
402
420
  throw new types_1.OakMakeSureByMySelfException();
403
421
  }
404
422
  };
405
423
  return createOperTrigger;
406
424
  }
407
- constructor(config, schema) {
425
+ constructor(config, schema, contextBuilder) {
408
426
  this.config = config;
409
427
  this.schema = schema;
428
+ this.contextBuilder = contextBuilder;
410
429
  }
411
430
  /**
412
431
  * 根据sync的定义,生成对应的 commit triggers
@@ -418,8 +437,8 @@ class Synchronizer {
418
437
  getSyncRoutine() {
419
438
  return {
420
439
  name: 'checkpoint routine for sync',
421
- routine: async (context) => {
422
- this.trySynchronizeOpers(context);
440
+ routine: async () => {
441
+ this.trySynchronizeOpers();
423
442
  return {};
424
443
  },
425
444
  };
@@ -487,12 +506,12 @@ class Synchronizer {
487
506
  }
488
507
  let maxBornAt = this.pullMaxBornAtMap[entityId];
489
508
  const opers = body;
490
- const outdatedOpers = opers.filter(ele => ele.$$seq$$ <= maxBornAt);
509
+ const staleOpers = opers.filter(ele => ele.$$seq$$ <= maxBornAt);
491
510
  const freshOpers = opers.filter(ele => ele.$$seq$$ > maxBornAt);
492
511
  await Promise.all([
493
512
  // 无法严格保证推送按bornAt,所以一旦还有outdatedOpers,检查其已经被apply
494
513
  (async () => {
495
- const ids = outdatedOpers.map(ele => ele.id);
514
+ const ids = staleOpers.map(ele => ele.id);
496
515
  if (ids.length > 0) {
497
516
  const opersExisted = await context.select('oper', {
498
517
  data: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oak-backend-base",
3
- "version": "3.4.1",
3
+ "version": "3.4.2",
4
4
  "description": "oak-backend-base",
5
5
  "main": "lib/index",
6
6
  "author": {
@@ -22,8 +22,8 @@
22
22
  "node-schedule": "^2.1.0",
23
23
  "oak-common-aspect": "^2.3.0",
24
24
  "oak-db": "^3.2.0",
25
- "oak-domain": "^4.3.1",
26
- "oak-frontend-base": "^4.3.0",
25
+ "oak-domain": "^4.4.0",
26
+ "oak-frontend-base": "^4.4.0",
27
27
  "socket.io": "^4.7.2",
28
28
  "socket.io-client": "^4.7.2",
29
29
  "uuid": "^8.3.2"