@tramvai/module-common 2.7.0 → 2.11.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/index.js CHANGED
@@ -32,6 +32,7 @@ var toArray = require('@tinkoff/utils/array/toArray');
32
32
  var LRU = require('@tinkoff/lru-cache-nano');
33
33
  var tokensServer = require('@tramvai/tokens-server');
34
34
  var papi = require('@tramvai/papi');
35
+ var nodeAbortController = require('node-abort-controller');
35
36
  var jsxRuntime = require('react/jsx-runtime');
36
37
  var tokensRender = require('@tramvai/tokens-render');
37
38
  var tokensChildApp = require('@tramvai/tokens-child-app');
@@ -393,14 +394,17 @@ const resolveDi = (type, status, diContainer, providers) => {
393
394
  return di;
394
395
  };
395
396
  class CommandLineRunner {
396
- constructor({ lines, rootDi, logger, metrics, }) {
397
+ constructor({ lines, rootDi, logger, metrics, executionContextManager, }) {
398
+ this.executionContextByDi = new WeakMap();
397
399
  this.lines = lines;
398
400
  this.rootDi = rootDi;
399
401
  this.log = logger('command:command-line-runner');
400
402
  this.metrics = metrics;
403
+ this.executionContextManager = executionContextManager;
401
404
  }
402
405
  run(type, status, providers, customDi) {
403
406
  const di = customDi !== null && customDi !== void 0 ? customDi : resolveDi(type, status, this.rootDi, providers);
407
+ const rootExecutionContext = di.get({ token: tokensCommon.ROOT_EXECUTION_CONTEXT_TOKEN, optional: true });
404
408
  this.log.debug({
405
409
  event: 'command-run',
406
410
  type,
@@ -412,13 +416,25 @@ class CommandLineRunner {
412
416
  const doneMetric = this.createDurationMetric();
413
417
  // eslint-disable-next-line promise/no-nesting
414
418
  return Promise.resolve()
415
- .then(() => this.createLineChain(di, line))
419
+ .then(() => {
420
+ return this.executionContextManager.withContext(rootExecutionContext, `command-line:${line.toString()}`, async (executionContext) => {
421
+ this.executionContextByDi.set(di, executionContext);
422
+ await this.createLineChain(di, line);
423
+ });
424
+ })
416
425
  .finally(() => doneMetric({ line: line.toString() }));
417
426
  });
418
427
  }, Promise.resolve())
419
428
  // После завершения цепочки отдаем context выполнения
429
+ .finally(() => {
430
+ this.executionContextByDi.delete(di);
431
+ })
420
432
  .then(() => di));
421
433
  }
434
+ resolveExecutionContextFromDi(di) {
435
+ var _a;
436
+ return (_a = this.executionContextByDi.get(di)) !== null && _a !== void 0 ? _a : null;
437
+ }
422
438
  createLineChain(di, line) {
423
439
  let lineInstance;
424
440
  try {
@@ -529,7 +545,7 @@ exports.CommandModule = class CommandModule {
529
545
  exports.CommandModule = tslib.__decorate([
530
546
  core.Module({
531
547
  providers: [
532
- {
548
+ dippy.provide({
533
549
  // Раннер процессов
534
550
  provide: core.COMMAND_LINE_RUNNER_TOKEN,
535
551
  scope: dippy.Scope.SINGLETON,
@@ -542,14 +558,27 @@ exports.CommandModule = tslib.__decorate([
542
558
  token: tokensMetrics.METRICS_MODULE_TOKEN,
543
559
  optional: true,
544
560
  },
561
+ executionContextManager: tokensCommon.EXECUTION_CONTEXT_MANAGER_TOKEN,
545
562
  },
546
- },
547
- {
563
+ }),
564
+ dippy.provide({
565
+ provide: tokensCommon.COMMAND_LINE_EXECUTION_CONTEXT_TOKEN,
566
+ useFactory: ({ di, commandLineRunner, }) => {
567
+ return () => {
568
+ return commandLineRunner.resolveExecutionContextFromDi(di);
569
+ };
570
+ },
571
+ deps: {
572
+ di: core.DI_TOKEN,
573
+ commandLineRunner: core.COMMAND_LINE_RUNNER_TOKEN,
574
+ },
575
+ }),
576
+ dippy.provide({
548
577
  // Дефолтный список команл
549
578
  provide: core.COMMAND_LINES_TOKEN,
550
579
  scope: dippy.Scope.SINGLETON,
551
580
  useValue: lines$1,
552
- },
581
+ }),
553
582
  ],
554
583
  })
555
584
  ], exports.CommandModule);
@@ -601,14 +630,19 @@ const actionTramvaiReducer = state.createReducer('actionTramvai', initalState).o
601
630
  serverState: payload,
602
631
  }));
603
632
 
633
+ /**
634
+ * @deprecated only for compatibility with legacy createAction
635
+ */
604
636
  const actionType = {
605
637
  global: 'global',
606
638
  local: 'local',
607
639
  };
608
640
 
641
+ const DEFAULT_CONDITIONS = {};
609
642
  class ActionChecker {
610
643
  // eslint-disable-next-line max-params
611
644
  constructor(globalConditionals, payload, parameters, executionState, type) {
645
+ var _a;
612
646
  this.globalConditionals = globalConditionals;
613
647
  this.payload = payload;
614
648
  this.parameters = parameters;
@@ -622,7 +656,7 @@ class ActionChecker {
622
656
  // если экшен был уже выполнен, то считаем, что его не нужно больше выполнять
623
657
  this.status = executionState.status !== 'success';
624
658
  }
625
- this.conditions = parameters.conditions;
659
+ this.conditions = (_a = parameters.conditions) !== null && _a !== void 0 ? _a : DEFAULT_CONDITIONS;
626
660
  }
627
661
  check() {
628
662
  this.globalConditionals.forEach((filter) => {
@@ -652,12 +686,16 @@ class ActionChecker {
652
686
  }
653
687
  }
654
688
 
689
+ const EMPTY_DEPS = {};
690
+ const DEFAULT_PAYLOAD = {};
655
691
  const getParameters = (action) => action[core.ACTION_PARAMETERS];
656
692
  class ActionExecution {
657
- constructor({ actionConditionals, store, context, di, transformAction, }) {
693
+ constructor({ store, context, di, executionContextManager, actionConditionals, transformAction, }) {
658
694
  this.actionConditionals = flatten__default["default"](actionConditionals !== null && actionConditionals !== void 0 ? actionConditionals : []);
659
695
  this.context = context;
696
+ this.store = store;
660
697
  this.di = di;
698
+ this.executionContextManager = executionContextManager;
661
699
  this.execution = new Map();
662
700
  this.transformAction = transformAction || identity__default["default"];
663
701
  const initialState = store.getState(actionTramvaiReducer);
@@ -667,32 +705,63 @@ class ActionExecution {
667
705
  }, initialState.serverState);
668
706
  }
669
707
  }
670
- run(action, payload, type = actionType.local) {
671
- // TODO выпилить после перевода всех экшенов к виду Action
672
- this.transformAction(action);
673
- const parameters = getParameters(action);
674
- const executionState = this.getExecutionState(parameters);
708
+ async runInContext(executionContext, action, ...params) {
709
+ var _a;
710
+ let parameters;
711
+ const payload = (_a = params[0]) !== null && _a !== void 0 ? _a : DEFAULT_PAYLOAD;
712
+ // TODO: replace type with pure context usage
713
+ const type = (executionContext === null || executionContext === void 0 ? void 0 : executionContext.values.pageActions) === true ? actionType.global : actionType.local;
714
+ // TODO: remove else branch after migration to new declareAction
715
+ if (core.isTramvaiAction(action)) {
716
+ parameters = action;
717
+ }
718
+ else {
719
+ this.transformAction(action);
720
+ parameters = getParameters(action);
721
+ }
722
+ const executionState = this.getExecutionState(parameters.name);
675
723
  if (!this.canExecuteAction(payload, parameters, executionState, type)) {
676
- return Promise.resolve();
724
+ switch (parameters.conditionsFailResult) {
725
+ case 'reject':
726
+ // TODO: pass condition that has failed
727
+ return Promise.reject(new errors.ConditionFailError());
728
+ default:
729
+ return Promise.resolve();
730
+ }
677
731
  }
678
- const deps = parameters.deps ? this.di.getOfDeps(parameters.deps) : {};
679
732
  executionState.status = 'pending';
680
- return Promise.resolve()
681
- .then(() => action(this.context, payload, deps))
682
- .then((data) => {
683
- executionState.status = 'success';
684
- return data;
685
- })
686
- .catch((err) => {
687
- executionState.status = 'failed';
688
- throw err;
733
+ return this.executionContextManager.withContext(executionContext, {
734
+ name: parameters.name,
735
+ values: (executionContext === null || executionContext === void 0 ? void 0 : executionContext.values.pageActions) === true ? { pageActions: false } : undefined,
736
+ }, (executionActionContext) => {
737
+ const context = this.createActionContext(executionContext, executionActionContext, parameters);
738
+ return Promise.resolve()
739
+ .then(() => {
740
+ // TODO: do not execute action if context.abortSignal is aborted
741
+ if (core.isTramvaiAction(action)) {
742
+ return action.fn.apply(context, params);
743
+ }
744
+ return action(this.context, payload, context.deps);
745
+ })
746
+ .then((data) => {
747
+ executionState.status = 'success';
748
+ return data;
749
+ })
750
+ .catch((err) => {
751
+ executionState.status = 'failed';
752
+ throw err;
753
+ });
689
754
  });
690
755
  }
691
- getExecutionState(parameters) {
692
- let executionState = this.execution.get(parameters.name);
756
+ async run(action, ...params) {
757
+ return this.runInContext(null, action, ...params);
758
+ }
759
+ getExecutionState(name) {
760
+ let executionState = this.execution.get(name);
761
+ // TODO: probably do not need to create executionState on client as it is not used
693
762
  if (!executionState) {
694
763
  executionState = { status: 'pending', state: {} };
695
- this.execution.set(parameters.name, executionState);
764
+ this.execution.set(name, executionState);
696
765
  }
697
766
  return executionState;
698
767
  }
@@ -700,6 +769,16 @@ class ActionExecution {
700
769
  const actionChecker = new ActionChecker(this.actionConditionals, payload, parameters, executionState, type);
701
770
  return actionChecker.check();
702
771
  }
772
+ createActionContext(parentExecutionContext, actionExecutionContext, parameters) {
773
+ return {
774
+ abortSignal: actionExecutionContext === null || actionExecutionContext === void 0 ? void 0 : actionExecutionContext.abortSignal,
775
+ executeAction: this.runInContext.bind(this, actionExecutionContext),
776
+ deps: parameters.deps ? this.di.getOfDeps(parameters.deps) : EMPTY_DEPS,
777
+ actionType: (parentExecutionContext === null || parentExecutionContext === void 0 ? void 0 : parentExecutionContext.values.pageActions) ? 'page' : 'standalone',
778
+ dispatch: this.store.dispatch,
779
+ getState: this.store.getState,
780
+ };
781
+ }
703
782
  }
704
783
 
705
784
  const GLOBAL_PARAMETER = '@@global';
@@ -729,56 +808,59 @@ class ActionRegistry {
729
808
  }
730
809
  }
731
810
 
732
- const payload = {};
733
811
  class ActionPageRunner {
734
- constructor({ store, actionExecution, limitTime, logger }) {
735
- this.store = store;
736
- this.actionExecution = actionExecution;
737
- this.limitTime = limitTime;
738
- this.log = logger('action:action-page-runner');
812
+ constructor(deps) {
813
+ this.deps = deps;
814
+ this.log = deps.logger('action:action-page-runner');
739
815
  }
740
816
  // TODO stopRunAtError нужен только для редиректов на стороне сервера в экшенах. И нужно пересмотреть реализацию редиректов
741
817
  runActions(actions, stopRunAtError = () => false) {
742
- return new Promise((resolve, reject) => {
743
- const timeoutMarker = setTimeout(() => {
744
- this.log.warn(`page actions has exceeded timeout of ${this.limitTime}ms, ignore some results of execution`);
745
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
746
- endChecker();
747
- }, this.limitTime);
748
- const endChecker = () => {
749
- clearTimeout(timeoutMarker);
750
- const result = {};
751
- this.actionExecution.execution.forEach((value, key) => {
752
- // достаем значения экшенов, которые успешно выполнились, остальные выполнятся на клиенте
753
- if (value.status === 'success') {
754
- result[key] = value;
755
- }
756
- });
757
- this.syncStateActions(result);
758
- resolve();
759
- };
760
- const actionMapper = (action) => {
761
- return Promise.resolve()
762
- .then(() => this.actionExecution.run(action, payload, actionType.global))
763
- .catch((error) => {
764
- const parameters = action[core.ACTION_PARAMETERS];
765
- this.log.error({
766
- error,
767
- event: `action-execution-error`,
768
- message: `${parameters.name} execution error`,
818
+ return this.deps.executionContextManager.withContext(this.deps.commandLineExecutionContext(), { name: 'pageActions', values: { pageActions: true } }, (executionContext, abortController) => {
819
+ return new Promise((resolve, reject) => {
820
+ const timeoutMarker = setTimeout(() => {
821
+ this.log.warn(`page actions has exceeded timeout of ${this.deps.limitTime}ms, ignore some results of execution`);
822
+ abortController.abort();
823
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
824
+ endChecker();
825
+ }, this.deps.limitTime);
826
+ const endChecker = () => {
827
+ clearTimeout(timeoutMarker);
828
+ const result = {};
829
+ // TODO: dehydrate only actions on first level as inner actions are running on client despite their execution on server
830
+ this.deps.actionExecution.execution.forEach((value, key) => {
831
+ // достаем значения экшенов, которые успешно выполнились, остальные выполнятся на клиенте
832
+ if (value.status === 'success') {
833
+ result[key] = value;
834
+ }
769
835
  });
770
- if (stopRunAtError(error)) {
771
- clearTimeout(timeoutMarker);
772
- reject(error);
773
- }
774
- });
775
- };
776
- // eslint-disable-next-line promise/catch-or-return
777
- Promise.all(actions.map(actionMapper)).then(endChecker);
836
+ this.syncStateActions(result);
837
+ resolve();
838
+ };
839
+ const actionMapper = (action) => {
840
+ return Promise.resolve()
841
+ .then(() => this.deps.actionExecution.runInContext(executionContext, action))
842
+ .catch((error) => {
843
+ if (!errors.isSilentError(error)) {
844
+ const parameters = core.isTramvaiAction(action) ? action : action[core.ACTION_PARAMETERS];
845
+ this.log.error({
846
+ error,
847
+ event: `action-execution-error`,
848
+ message: `${parameters.name} execution error`,
849
+ });
850
+ }
851
+ if (stopRunAtError(error)) {
852
+ clearTimeout(timeoutMarker);
853
+ reject(error);
854
+ }
855
+ });
856
+ };
857
+ // eslint-disable-next-line promise/catch-or-return
858
+ Promise.all(actions.map(actionMapper)).then(endChecker);
859
+ });
778
860
  });
779
861
  }
780
862
  syncStateActions(success) {
781
- return this.store.dispatch(actionServerStateEvent(success));
863
+ return this.deps.store.dispatch(actionServerStateEvent(success));
782
864
  }
783
865
  }
784
866
 
@@ -856,23 +938,26 @@ ActionModule = tslib.__decorate([
856
938
  context: tokensCommon.CONTEXT_TOKEN,
857
939
  store: tokensCommon.STORE_TOKEN,
858
940
  di: core.DI_TOKEN,
941
+ executionContextManager: tokensCommon.EXECUTION_CONTEXT_MANAGER_TOKEN,
859
942
  transformAction: {
860
943
  token: 'actionTransformAction',
861
944
  optional: true,
862
945
  },
863
946
  },
864
947
  }),
865
- {
948
+ core.provide({
866
949
  provide: tokensCommon.ACTION_PAGE_RUNNER_TOKEN,
867
950
  scope: core.Scope.REQUEST,
868
951
  useClass: ActionPageRunner,
869
952
  deps: {
870
953
  actionExecution: tokensCommon.ACTION_EXECUTION_TOKEN,
954
+ executionContextManager: tokensCommon.EXECUTION_CONTEXT_MANAGER_TOKEN,
955
+ commandLineExecutionContext: tokensCommon.COMMAND_LINE_EXECUTION_CONTEXT_TOKEN,
871
956
  store: tokensCommon.STORE_TOKEN,
872
957
  limitTime: 'limitActionGlobalTimeRun',
873
958
  logger: tokensCommon.LOGGER_TOKEN,
874
959
  },
875
- },
960
+ }),
876
961
  {
877
962
  provide: 'limitActionGlobalTimeRun',
878
963
  useValue: 500,
@@ -1014,6 +1099,58 @@ CacheModule = tslib.__decorate([
1014
1099
  })
1015
1100
  ], CacheModule);
1016
1101
 
1102
+ const EMPTY_VALUES = {};
1103
+ const normalizeOptions = (nameOrOptions) => {
1104
+ return typeof nameOrOptions === 'string' ? { name: nameOrOptions } : nameOrOptions;
1105
+ };
1106
+ class ExecutionContextManager {
1107
+ async withContext(parentContext, nameOrOptions, cb) {
1108
+ const options = normalizeOptions(nameOrOptions);
1109
+ const { name, values: selfValues = EMPTY_VALUES } = options;
1110
+ const contextName = parentContext ? `${parentContext.name}.${name}` : name;
1111
+ if (parentContext === null || parentContext === void 0 ? void 0 : parentContext.abortSignal.aborted) {
1112
+ throw new errors.ExecutionAbortError({
1113
+ message: `Execution aborted in context "${contextName}"`,
1114
+ contextName,
1115
+ });
1116
+ }
1117
+ const abortController = new nodeAbortController.AbortController();
1118
+ let abortListener;
1119
+ let values = selfValues;
1120
+ if (parentContext) {
1121
+ values = {
1122
+ ...parentContext.values,
1123
+ ...selfValues,
1124
+ };
1125
+ abortListener = () => {
1126
+ abortController.abort();
1127
+ };
1128
+ // abort child context AbortController if parent AbortController was aborted
1129
+ parentContext.abortSignal.addEventListener('abort', abortListener);
1130
+ }
1131
+ const context = {
1132
+ name: contextName,
1133
+ abortSignal: abortController.signal,
1134
+ values,
1135
+ };
1136
+ try {
1137
+ const result = await cb(context, abortController);
1138
+ return result;
1139
+ }
1140
+ catch (error) {
1141
+ if (!error.executionContextName) {
1142
+ error.executionContextName = context.name;
1143
+ }
1144
+ throw error;
1145
+ }
1146
+ finally {
1147
+ if (abortListener) {
1148
+ parentContext.abortSignal.removeEventListener('abort', abortListener);
1149
+ }
1150
+ }
1151
+ }
1152
+ }
1153
+
1017
1154
  exports.CommonModule = class CommonModule {
1018
1155
  };
1019
1156
  exports.CommonModule = tslib.__decorate([
@@ -1084,6 +1221,10 @@ exports.CommonModule = tslib.__decorate([
1084
1221
  store: tokensCommon.STORE_TOKEN,
1085
1222
  },
1086
1223
  }),
1224
+ core.provide({
1225
+ provide: tokensCommon.EXECUTION_CONTEXT_MANAGER_TOKEN,
1226
+ useClass: ExecutionContextManager,
1227
+ }),
1087
1228
  ...providers$2,
1088
1229
  ],
1089
1230
  })
@@ -1174,6 +1315,7 @@ Object.defineProperty(exports, 'COOKIE_MANAGER_TOKEN', {
1174
1315
  get: function () { return moduleCookie.COOKIE_MANAGER_TOKEN; }
1175
1316
  });
1176
1317
  exports.ActionExecution = ActionExecution;
1318
+ exports.ExecutionContextManager = ExecutionContextManager;
1177
1319
  exports.alwaysCondition = alwaysCondition;
1178
1320
  exports.createConsumerContext = createConsumerContext;
1179
1321
  exports.onlyBrowser = onlyBrowser;
@@ -5,7 +5,7 @@ declare global {
5
5
  }
6
6
  }
7
7
  export declare const providers: ({
8
- provide: import("@tinkoff/dippy/lib/createToken/createToken").BaseTokenInterface<{
8
+ provide: import("@tinkoff/dippy").BaseTokenInterface<{
9
9
  stores: Record<string, any>;
10
10
  }>;
11
11
  useFactory: () => any;
@@ -13,12 +13,12 @@ export declare const providers: ({
13
13
  deps?: undefined;
14
14
  multi?: undefined;
15
15
  } | {
16
- provide: import("@tinkoff/dippy/lib/createToken/createToken").MultiTokenInterface<import("@tramvai/core").Command>;
16
+ provide: import("@tinkoff/dippy").MultiTokenInterface<import("@tramvai/core").Command>;
17
17
  useFactory: ({ commandLineRunner, }: {
18
18
  commandLineRunner: typeof COMMAND_LINE_RUNNER_TOKEN;
19
19
  }) => () => Promise<import("@tinkoff/dippy").Container>;
20
20
  deps: {
21
- commandLineRunner: import("@tinkoff/dippy/lib/createToken/createToken").BaseTokenInterface<import("@tramvai/tokens-core").CommandLine>;
21
+ commandLineRunner: import("@tinkoff/dippy").BaseTokenInterface<import("@tramvai/tokens-core").CommandLine>;
22
22
  };
23
23
  multi: boolean;
24
24
  scope?: undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tramvai/module-common",
3
- "version": "2.7.0",
3
+ "version": "2.11.0",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
@@ -28,30 +28,31 @@
28
28
  "build-for-publish": "true"
29
29
  },
30
30
  "dependencies": {
31
- "@tinkoff/errors": "0.2.22",
31
+ "@tinkoff/errors": "0.2.23",
32
32
  "@tinkoff/hook-runner": "0.3.23",
33
33
  "@tinkoff/lru-cache-nano": "^7.8.1",
34
34
  "@tinkoff/pubsub": "0.4.25",
35
35
  "@tinkoff/url": "0.7.39",
36
- "@tramvai/experiments": "2.7.0",
37
- "@tramvai/module-cookie": "2.7.0",
38
- "@tramvai/module-environment": "2.7.0",
39
- "@tramvai/module-log": "2.7.0",
40
- "@tramvai/tokens-child-app": "2.7.0",
41
- "@tramvai/tokens-common": "2.7.0",
42
- "@tramvai/tokens-render": "2.7.0",
43
- "hoist-non-react-statics": "^3.3.1"
36
+ "@tramvai/experiments": "2.11.0",
37
+ "@tramvai/module-cookie": "2.11.0",
38
+ "@tramvai/module-environment": "2.11.0",
39
+ "@tramvai/module-log": "2.11.0",
40
+ "@tramvai/tokens-child-app": "2.11.0",
41
+ "@tramvai/tokens-common": "2.11.0",
42
+ "@tramvai/tokens-render": "2.11.0",
43
+ "hoist-non-react-statics": "^3.3.1",
44
+ "node-abort-controller": "^3.0.1"
44
45
  },
45
46
  "peerDependencies": {
46
- "@tinkoff/dippy": "0.7.44",
47
+ "@tinkoff/dippy": "0.7.45",
47
48
  "@tinkoff/utils": "^2.1.2",
48
- "@tramvai/cli": "2.7.0",
49
- "@tramvai/core": "2.7.0",
50
- "@tramvai/papi": "2.7.0",
51
- "@tramvai/react": "2.7.0",
52
- "@tramvai/state": "2.7.0",
53
- "@tramvai/tokens-metrics": "2.7.0",
54
- "@tramvai/tokens-server": "2.7.0",
49
+ "@tramvai/cli": "2.11.0",
50
+ "@tramvai/core": "2.11.0",
51
+ "@tramvai/papi": "2.11.0",
52
+ "@tramvai/react": "2.11.0",
53
+ "@tramvai/state": "2.11.0",
54
+ "@tramvai/tokens-metrics": "2.11.0",
55
+ "@tramvai/tokens-server": "2.11.0",
55
56
  "react": ">=16.14.0",
56
57
  "tslib": "^2.0.3"
57
58
  },