@contember/engine-actions 2.2.0-alpha.5 → 2.2.0-alpha.7

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.
Files changed (27) hide show
  1. package/dist/development/index.cjs +0 -1
  2. package/dist/development/index.cjs.map +1 -1
  3. package/dist/development/index.js +0 -1
  4. package/dist/development/index.js.map +1 -1
  5. package/dist/development/src/graphql/http/ActionsWebsocketControllerFactory.cjs +68 -28
  6. package/dist/development/src/graphql/http/ActionsWebsocketControllerFactory.cjs.map +1 -1
  7. package/dist/development/src/graphql/http/ActionsWebsocketControllerFactory.js +68 -28
  8. package/dist/development/src/graphql/http/ActionsWebsocketControllerFactory.js.map +1 -1
  9. package/dist/production/index.cjs +0 -1
  10. package/dist/production/index.cjs.map +1 -1
  11. package/dist/production/index.js +0 -1
  12. package/dist/production/index.js.map +1 -1
  13. package/dist/production/src/graphql/http/ActionsWebsocketControllerFactory.cjs +68 -28
  14. package/dist/production/src/graphql/http/ActionsWebsocketControllerFactory.cjs.map +1 -1
  15. package/dist/production/src/graphql/http/ActionsWebsocketControllerFactory.js +68 -28
  16. package/dist/production/src/graphql/http/ActionsWebsocketControllerFactory.js.map +1 -1
  17. package/dist/tests/cases/unit/actionsWebsocketController.test.d.ts +2 -0
  18. package/dist/tests/cases/unit/actionsWebsocketController.test.d.ts.map +1 -0
  19. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  20. package/dist/types/graphql/http/ActionsWebsocketControllerFactory.d.ts +7 -5
  21. package/dist/types/graphql/http/ActionsWebsocketControllerFactory.d.ts.map +1 -1
  22. package/dist/types/index.d.ts.map +1 -1
  23. package/dist/types/tsconfig.tsbuildinfo +1 -1
  24. package/package.json +16 -16
  25. package/src/graphql/http/ActionsWebsocketControllerFactory.ts +73 -30
  26. package/src/index.ts +0 -1
  27. package/tests/cases/unit/actionsWebsocketController.test.ts +270 -0
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../packages/engine-actions/src/index.ts"],"sourcesContent":["import { Plugin } from '@contember/engine-plugins'\nimport { migrationsGroup } from './migrations/index.js'\nimport { ActionsExecutionContainerHookFactory } from './ActionsExecutionContainerHookFactory.js'\nimport { ListenerStoreProvider } from './ListenerStoreProvider.js'\nimport { MigrationGroup } from '@contember/database-migrations'\nimport { MasterContainerBuilder, MasterContainerHook } from '@contember/engine-http'\nimport { ActionsApiMiddlewareFactory } from './graphql/http/ActionsApiMiddlewareFactory.js'\nimport { ActionsGraphQLHandlerFactory } from './graphql/http/ActionsGraphQLHandlerFactory.js'\nimport { ResolversFactory } from './graphql/resolvers/ResolversFactory.js'\nimport {\n\tEventsQueryResolver,\n\tProcessBatchMutationResolver,\n\tRetryEventMutationResolver,\n\tSetVariablesMutationResolver,\n\tStopEventMutationResolver,\n} from './graphql/resolvers/index.js'\nimport { EventDispatcher } from './dispatch/EventDispatcher.js'\nimport { EventsRepository } from './dispatch/EventsRepository.js'\nimport { TargetHandlerResolver } from './dispatch/TargetHandlerResolver.js'\nimport { WebhookTargetHandler } from './dispatch/WebhookTargetHandler.js'\nimport { ActionsContextResolver } from './graphql/http/ActionsContextResolver.js'\nimport { ActionsWebsocketControllerFactory } from './graphql/http/ActionsWebsocketControllerFactory.js'\nimport { DispatchWorkerSupervisorFactory } from './dispatch/DispatchWorkerSupervisor.js'\nimport { LazyDispatchWorker } from './dispatch/LazyDispatchWorker.js'\nimport { ProjectDispatcherFactory } from './dispatch/ProjectDispatcher.js'\nimport { VariablesQueryResolver } from './graphql/resolvers/query/VariablesQueryResolver.js'\nimport { VariablesManager } from './model/VariablesManager.js'\nimport { AccessEvaluator, Authorizator } from '@contember/authorization'\nimport { ActionsPermissionsFactory } from './authorization/index.js'\nimport { WebhookFetcherNative } from './dispatch/WebhookFetcher.js'\nimport { ActionsMetrics } from './ActionsMetrics.js'\n\nexport {\n\tTriggerHandler,\n\tTriggerIndirectChangesFetcher,\n\tTriggerListenersFactory,\n\tTriggerListenersStore,\n\tTriggerPayloadManager,\n} from './triggers/index.js'\nexport { ActionsExecutionContainerHookFactory } from './ActionsExecutionContainerHookFactory.js'\nexport { ListenerStoreProvider } from './ListenerStoreProvider.js'\n\nexport default class ActionsPlugin implements Plugin {\n\tname = 'contember/actions'\n\n\t/**\n\t * Metrics singleton, created in {@link getMasterContainerHook} (where the Prometheus registry is\n\t * available) and read back here in {@link getExecutionContainerHook}, which runs in the per-request\n\t * content container with no access to master-level services.\n\t */\n\tprivate actionsMetrics: ActionsMetrics | undefined\n\n\tgetSystemMigrations(): MigrationGroup {\n\t\treturn migrationsGroup\n\t}\n\n\tgetExecutionContainerHook() {\n\t\tconst hookFactory = new ActionsExecutionContainerHookFactory(new ListenerStoreProvider(), () => this.actionsMetrics)\n\t\treturn hookFactory.create()\n\t}\n\n\tgetMasterContainerHook() {\n\t\tconst hook: MasterContainerHook = (builder: MasterContainerBuilder) => {\n\t\t\treturn builder\n\t\t\t\t.addService('actions_metrics', ({ promRegistry }) => {\n\t\t\t\t\tconst metrics = new ActionsMetrics(promRegistry)\n\t\t\t\t\t// expose to getExecutionContainerHook (runs in a different container scope)\n\t\t\t\t\tthis.actionsMetrics = metrics\n\t\t\t\t\treturn metrics\n\t\t\t\t})\n\t\t\t\t.addService('actions_variableManager', ({ env }) => {\n\t\t\t\t\treturn new VariablesManager(env)\n\t\t\t\t})\n\t\t\t\t.addService('actions_eventRepository', () => {\n\t\t\t\t\treturn new EventsRepository()\n\t\t\t\t})\n\t\t\t\t.addService('actions_eventDispatcher', ({ actions_eventRepository, actions_variableManager }) => {\n\t\t\t\t\tconst webhookTargetHandler = new WebhookTargetHandler(new WebhookFetcherNative())\n\t\t\t\t\tconst targetHandlerResolver = new TargetHandlerResolver(webhookTargetHandler)\n\t\t\t\t\treturn new EventDispatcher(actions_eventRepository, actions_variableManager, targetHandlerResolver)\n\t\t\t\t})\n\t\t\t\t.addService('actions_dispatchWorkerSupervisorFactory', ({ actions_eventDispatcher, actions_metrics }) => {\n\t\t\t\t\tconst projectDispatcherFactory = new ProjectDispatcherFactory(actions_eventDispatcher, actions_metrics)\n\t\t\t\t\treturn new DispatchWorkerSupervisorFactory(projectDispatcherFactory)\n\t\t\t\t})\n\t\t\t\t.addService('actions_authorizator', () => {\n\t\t\t\t\treturn new Authorizator.Default(new AccessEvaluator.PermissionEvaluator(new ActionsPermissionsFactory().create()))\n\t\t\t\t})\n\t\t\t\t.setupService(\n\t\t\t\t\t'application',\n\t\t\t\t\t(\n\t\t\t\t\t\tit,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tprojectContextResolver,\n\t\t\t\t\t\t\tdebugMode,\n\t\t\t\t\t\t\tactions_eventRepository,\n\t\t\t\t\t\t\tactions_eventDispatcher,\n\t\t\t\t\t\t\tactions_dispatchWorkerSupervisorFactory,\n\t\t\t\t\t\t\tactions_variableManager,\n\t\t\t\t\t\t\tactions_authorizator,\n\t\t\t\t\t\t\tpanelApiMount,\n\t\t\t\t\t\t},\n\t\t\t\t\t) => {\n\t\t\t\t\t\tconst handlerFactory = new ActionsGraphQLHandlerFactory()\n\t\t\t\t\t\tconst eventsQueryResolver = new EventsQueryResolver()\n\t\t\t\t\t\tconst processBatchMutationResolver = new ProcessBatchMutationResolver(actions_eventDispatcher)\n\t\t\t\t\t\tconst variablesQueryResolver = new VariablesQueryResolver(actions_variableManager)\n\t\t\t\t\t\tconst setVariablesMutationResolver = new SetVariablesMutationResolver(actions_variableManager)\n\t\t\t\t\t\tconst resolversFactory = new ResolversFactory(\n\t\t\t\t\t\t\teventsQueryResolver,\n\t\t\t\t\t\t\tprocessBatchMutationResolver,\n\t\t\t\t\t\t\tvariablesQueryResolver,\n\t\t\t\t\t\t\tsetVariablesMutationResolver,\n\t\t\t\t\t\t\tnew RetryEventMutationResolver(actions_eventRepository),\n\t\t\t\t\t\t\tnew StopEventMutationResolver(actions_eventRepository),\n\t\t\t\t\t\t)\n\n\t\t\t\t\t\tconst actionsContextResolver = new ActionsContextResolver(debugMode, projectContextResolver)\n\t\t\t\t\t\tconst actionsMiddlewareFactory = new ActionsApiMiddlewareFactory(\n\t\t\t\t\t\t\tactionsContextResolver,\n\t\t\t\t\t\t\thandlerFactory.create(resolversFactory),\n\t\t\t\t\t\t\tactions_authorizator,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tconst actionsWebsocketMiddlewareFactory = new ActionsWebsocketControllerFactory(\n\t\t\t\t\t\t\tdebugMode,\n\t\t\t\t\t\t\tactionsContextResolver,\n\t\t\t\t\t\t\tactions_dispatchWorkerSupervisorFactory,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tit.addRoute('actions', '/actions/:projectSlug', actionsMiddlewareFactory.create())\n\t\t\t\t\t\tit.addWebsocketRoute('actions', '/actions/_worker', actionsWebsocketMiddlewareFactory.create())\n\t\t\t\t\t\t// Does nothing unless the panel is served; when it is, the panel's access gate applies\n\t\t\t\t\t\t// and the UI learns the actions module has an API to talk to.\n\t\t\t\t\t\tpanelApiMount.mount(it, {\n\t\t\t\t\t\t\tname: 'actions',\n\t\t\t\t\t\t\tpath: 'actions/:projectSlug',\n\t\t\t\t\t\t\tcontroller: actionsMiddlewareFactory.create(),\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\t.setupService(\n\t\t\t\t\t'applicationWorkers',\n\t\t\t\t\t(it, { actions_dispatchWorkerSupervisorFactory, projectGroupContainer, projectGroupContainerResolver, serverConfig }) => {\n\t\t\t\t\t\tif (!(serverConfig as any).projectGroup) {\n\t\t\t\t\t\t\tit.registerWorker(this.name, actions_dispatchWorkerSupervisorFactory.create(projectGroupContainer))\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tit.registerWorker(this.name, new LazyDispatchWorker(projectGroupContainerResolver, actions_dispatchWorkerSupervisorFactory))\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t) as unknown as MasterContainerBuilder\n\t\t}\n\t\treturn hook\n\t}\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../../packages/engine-actions/src/index.ts"],"sourcesContent":["import { Plugin } from '@contember/engine-plugins'\nimport { migrationsGroup } from './migrations/index.js'\nimport { ActionsExecutionContainerHookFactory } from './ActionsExecutionContainerHookFactory.js'\nimport { ListenerStoreProvider } from './ListenerStoreProvider.js'\nimport { MigrationGroup } from '@contember/database-migrations'\nimport { MasterContainerBuilder, MasterContainerHook } from '@contember/engine-http'\nimport { ActionsApiMiddlewareFactory } from './graphql/http/ActionsApiMiddlewareFactory.js'\nimport { ActionsGraphQLHandlerFactory } from './graphql/http/ActionsGraphQLHandlerFactory.js'\nimport { ResolversFactory } from './graphql/resolvers/ResolversFactory.js'\nimport {\n\tEventsQueryResolver,\n\tProcessBatchMutationResolver,\n\tRetryEventMutationResolver,\n\tSetVariablesMutationResolver,\n\tStopEventMutationResolver,\n} from './graphql/resolvers/index.js'\nimport { EventDispatcher } from './dispatch/EventDispatcher.js'\nimport { EventsRepository } from './dispatch/EventsRepository.js'\nimport { TargetHandlerResolver } from './dispatch/TargetHandlerResolver.js'\nimport { WebhookTargetHandler } from './dispatch/WebhookTargetHandler.js'\nimport { ActionsContextResolver } from './graphql/http/ActionsContextResolver.js'\nimport { ActionsWebsocketControllerFactory } from './graphql/http/ActionsWebsocketControllerFactory.js'\nimport { DispatchWorkerSupervisorFactory } from './dispatch/DispatchWorkerSupervisor.js'\nimport { LazyDispatchWorker } from './dispatch/LazyDispatchWorker.js'\nimport { ProjectDispatcherFactory } from './dispatch/ProjectDispatcher.js'\nimport { VariablesQueryResolver } from './graphql/resolvers/query/VariablesQueryResolver.js'\nimport { VariablesManager } from './model/VariablesManager.js'\nimport { AccessEvaluator, Authorizator } from '@contember/authorization'\nimport { ActionsPermissionsFactory } from './authorization/index.js'\nimport { WebhookFetcherNative } from './dispatch/WebhookFetcher.js'\nimport { ActionsMetrics } from './ActionsMetrics.js'\n\nexport {\n\tTriggerHandler,\n\tTriggerIndirectChangesFetcher,\n\tTriggerListenersFactory,\n\tTriggerListenersStore,\n\tTriggerPayloadManager,\n} from './triggers/index.js'\nexport { ActionsExecutionContainerHookFactory } from './ActionsExecutionContainerHookFactory.js'\nexport { ListenerStoreProvider } from './ListenerStoreProvider.js'\n\nexport default class ActionsPlugin implements Plugin {\n\tname = 'contember/actions'\n\n\t/**\n\t * Metrics singleton, created in {@link getMasterContainerHook} (where the Prometheus registry is\n\t * available) and read back here in {@link getExecutionContainerHook}, which runs in the per-request\n\t * content container with no access to master-level services.\n\t */\n\tprivate actionsMetrics: ActionsMetrics | undefined\n\n\tgetSystemMigrations(): MigrationGroup {\n\t\treturn migrationsGroup\n\t}\n\n\tgetExecutionContainerHook() {\n\t\tconst hookFactory = new ActionsExecutionContainerHookFactory(new ListenerStoreProvider(), () => this.actionsMetrics)\n\t\treturn hookFactory.create()\n\t}\n\n\tgetMasterContainerHook() {\n\t\tconst hook: MasterContainerHook = (builder: MasterContainerBuilder) => {\n\t\t\treturn builder\n\t\t\t\t.addService('actions_metrics', ({ promRegistry }) => {\n\t\t\t\t\tconst metrics = new ActionsMetrics(promRegistry)\n\t\t\t\t\t// expose to getExecutionContainerHook (runs in a different container scope)\n\t\t\t\t\tthis.actionsMetrics = metrics\n\t\t\t\t\treturn metrics\n\t\t\t\t})\n\t\t\t\t.addService('actions_variableManager', ({ env }) => {\n\t\t\t\t\treturn new VariablesManager(env)\n\t\t\t\t})\n\t\t\t\t.addService('actions_eventRepository', () => {\n\t\t\t\t\treturn new EventsRepository()\n\t\t\t\t})\n\t\t\t\t.addService('actions_eventDispatcher', ({ actions_eventRepository, actions_variableManager }) => {\n\t\t\t\t\tconst webhookTargetHandler = new WebhookTargetHandler(new WebhookFetcherNative())\n\t\t\t\t\tconst targetHandlerResolver = new TargetHandlerResolver(webhookTargetHandler)\n\t\t\t\t\treturn new EventDispatcher(actions_eventRepository, actions_variableManager, targetHandlerResolver)\n\t\t\t\t})\n\t\t\t\t.addService('actions_dispatchWorkerSupervisorFactory', ({ actions_eventDispatcher, actions_metrics }) => {\n\t\t\t\t\tconst projectDispatcherFactory = new ProjectDispatcherFactory(actions_eventDispatcher, actions_metrics)\n\t\t\t\t\treturn new DispatchWorkerSupervisorFactory(projectDispatcherFactory)\n\t\t\t\t})\n\t\t\t\t.addService('actions_authorizator', () => {\n\t\t\t\t\treturn new Authorizator.Default(new AccessEvaluator.PermissionEvaluator(new ActionsPermissionsFactory().create()))\n\t\t\t\t})\n\t\t\t\t.setupService(\n\t\t\t\t\t'application',\n\t\t\t\t\t(\n\t\t\t\t\t\tit,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tprojectContextResolver,\n\t\t\t\t\t\t\tdebugMode,\n\t\t\t\t\t\t\tactions_eventRepository,\n\t\t\t\t\t\t\tactions_eventDispatcher,\n\t\t\t\t\t\t\tactions_dispatchWorkerSupervisorFactory,\n\t\t\t\t\t\t\tactions_variableManager,\n\t\t\t\t\t\t\tactions_authorizator,\n\t\t\t\t\t\t\tpanelApiMount,\n\t\t\t\t\t\t},\n\t\t\t\t\t) => {\n\t\t\t\t\t\tconst handlerFactory = new ActionsGraphQLHandlerFactory()\n\t\t\t\t\t\tconst eventsQueryResolver = new EventsQueryResolver()\n\t\t\t\t\t\tconst processBatchMutationResolver = new ProcessBatchMutationResolver(actions_eventDispatcher)\n\t\t\t\t\t\tconst variablesQueryResolver = new VariablesQueryResolver(actions_variableManager)\n\t\t\t\t\t\tconst setVariablesMutationResolver = new SetVariablesMutationResolver(actions_variableManager)\n\t\t\t\t\t\tconst resolversFactory = new ResolversFactory(\n\t\t\t\t\t\t\teventsQueryResolver,\n\t\t\t\t\t\t\tprocessBatchMutationResolver,\n\t\t\t\t\t\t\tvariablesQueryResolver,\n\t\t\t\t\t\t\tsetVariablesMutationResolver,\n\t\t\t\t\t\t\tnew RetryEventMutationResolver(actions_eventRepository),\n\t\t\t\t\t\t\tnew StopEventMutationResolver(actions_eventRepository),\n\t\t\t\t\t\t)\n\n\t\t\t\t\t\tconst actionsContextResolver = new ActionsContextResolver(debugMode, projectContextResolver)\n\t\t\t\t\t\tconst actionsMiddlewareFactory = new ActionsApiMiddlewareFactory(\n\t\t\t\t\t\t\tactionsContextResolver,\n\t\t\t\t\t\t\thandlerFactory.create(resolversFactory),\n\t\t\t\t\t\t\tactions_authorizator,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tconst actionsWebsocketMiddlewareFactory = new ActionsWebsocketControllerFactory(\n\t\t\t\t\t\t\tdebugMode,\n\t\t\t\t\t\t\tactions_dispatchWorkerSupervisorFactory,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tit.addRoute('actions', '/actions/:projectSlug', actionsMiddlewareFactory.create())\n\t\t\t\t\t\tit.addWebsocketRoute('actions', '/actions/_worker', actionsWebsocketMiddlewareFactory.create())\n\t\t\t\t\t\t// Does nothing unless the panel is served; when it is, the panel's access gate applies\n\t\t\t\t\t\t// and the UI learns the actions module has an API to talk to.\n\t\t\t\t\t\tpanelApiMount.mount(it, {\n\t\t\t\t\t\t\tname: 'actions',\n\t\t\t\t\t\t\tpath: 'actions/:projectSlug',\n\t\t\t\t\t\t\tcontroller: actionsMiddlewareFactory.create(),\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\t.setupService(\n\t\t\t\t\t'applicationWorkers',\n\t\t\t\t\t(it, { actions_dispatchWorkerSupervisorFactory, projectGroupContainer, projectGroupContainerResolver, serverConfig }) => {\n\t\t\t\t\t\tif (!(serverConfig as any).projectGroup) {\n\t\t\t\t\t\t\tit.registerWorker(this.name, actions_dispatchWorkerSupervisorFactory.create(projectGroupContainer))\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tit.registerWorker(this.name, new LazyDispatchWorker(projectGroupContainerResolver, actions_dispatchWorkerSupervisorFactory))\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t) as unknown as MasterContainerBuilder\n\t\t}\n\t\treturn hook\n\t}\n}\n"],"names":[],"mappings":"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"}
@@ -52,9 +52,8 @@ const OutgoingMessage = Typesafe__namespace.discriminatedUnion(
52
52
  }
53
53
  );
54
54
  class ActionsWebsocketControllerFactory {
55
- constructor(debug, actionsContextResolver, dispatchWorkerSupervisorFactory) {
55
+ constructor(debug, dispatchWorkerSupervisorFactory) {
56
56
  this.debug = debug;
57
- this.actionsContextResolver = actionsContextResolver;
58
57
  this.dispatchWorkerSupervisorFactory = dispatchWorkerSupervisorFactory;
59
58
  }
60
59
  create() {
@@ -73,23 +72,42 @@ class ActionsWebsocketControllerFactory {
73
72
  ws.send(JSON.stringify(message));
74
73
  };
75
74
  let workers = [];
75
+ let stopAllPromise;
76
+ let connectionClosing = false;
76
77
  const abortListener = async () => {
77
- send({ type: "message", message: "shutting down" });
78
- await stopAll();
79
- send({ type: "message", message: "closing connection" });
80
- ws.close(1012);
78
+ connectionClosing = true;
79
+ try {
80
+ send({ type: "message", message: "shutting down" });
81
+ await stopAll();
82
+ } catch (error) {
83
+ logger.error(error, { message: "Websocket worker cleanup failed" });
84
+ } finally {
85
+ try {
86
+ send({ type: "message", message: "closing connection" });
87
+ } finally {
88
+ ws.close(1012);
89
+ }
90
+ }
81
91
  };
82
92
  ctx.abortSignal.addEventListener("abort", abortListener);
83
- const stopAll = async () => {
84
- const currentWorkers = workers.map(async (it) => {
85
- await (await it.running).end();
86
- send({
87
- type: "workedStopped",
88
- workerId: it.id
89
- });
90
- });
91
- workers = [];
92
- await Promise.all(currentWorkers);
93
+ const stopAll = () => {
94
+ stopAllPromise ?? (stopAllPromise = (async () => {
95
+ const results = await Promise.allSettled(workers.map(async (it) => {
96
+ await (await it.running).end();
97
+ workers = workers.filter((worker) => worker !== it);
98
+ send({
99
+ type: "workedStopped",
100
+ workerId: it.id
101
+ });
102
+ }));
103
+ const errors = results.flatMap((it) => it.status === "rejected" ? [it.reason] : []);
104
+ if (errors.length > 0) {
105
+ throw new AggregateError(errors, "Worker cleanup failed");
106
+ }
107
+ })().finally(() => {
108
+ stopAllPromise = void 0;
109
+ }));
110
+ return stopAllPromise;
93
111
  };
94
112
  send({ type: "ready" });
95
113
  ws.addEventListener("message", async (message) => {
@@ -113,38 +131,60 @@ class ActionsWebsocketControllerFactory {
113
131
  }
114
132
  switch (data.type) {
115
133
  case "startWorker":
134
+ if (connectionClosing || stopAllPromise !== void 0) {
135
+ send({ type: "error", message: "Workers are stopping" });
136
+ break;
137
+ }
116
138
  const workerId = Math.random().toString().substring(2);
117
139
  const dispatchSupervisor = this.dispatchWorkerSupervisorFactory.create(projectGroup);
118
- try {
119
- const running = dispatchSupervisor.run({
140
+ const running = Promise.resolve().then(
141
+ () => dispatchSupervisor.run({
120
142
  logger,
121
143
  onError: () => {
122
144
  send({ type: "workerCrashed", workerId });
123
145
  }
124
- });
125
- workers.push({ id: workerId, running });
146
+ })
147
+ );
148
+ const worker = { id: workerId, running };
149
+ workers.push(worker);
150
+ try {
126
151
  await running;
127
- send({ type: "workerStarted", workerId });
128
152
  } catch (e) {
153
+ workers = workers.filter((it) => it !== worker);
129
154
  logger.error(e, { message: "Worker failed to start" });
130
155
  send({ type: "workerFailedToStart", workerId });
156
+ break;
131
157
  }
158
+ send({ type: "workerStarted", workerId });
132
159
  break;
133
160
  case "stopAllWorkers":
134
161
  send({ type: "message", message: "stopping" });
135
- await stopAll();
136
- send({ type: "message", message: "all stopped" });
162
+ try {
163
+ await stopAll();
164
+ send({ type: "message", message: "all stopped" });
165
+ } catch (error) {
166
+ logger.error(error, { message: "Websocket worker cleanup failed" });
167
+ send({ type: "error", message: "Worker cleanup failed" });
168
+ }
137
169
  break;
138
170
  }
139
171
  });
140
172
  const pingHandle = setInterval(() => {
141
173
  ws.ping();
142
174
  }, 5e3);
143
- ws.addEventListener("close", async () => {
144
- ctx.abortSignal.removeEventListener("abort", abortListener);
145
- clearInterval(pingHandle);
146
- await stopAll();
147
- });
175
+ ctx.waitUntil(
176
+ new Promise((resolve) => {
177
+ ws.addEventListener("close", () => {
178
+ connectionClosing = true;
179
+ ctx.abortSignal.removeEventListener("abort", abortListener);
180
+ clearInterval(pingHandle);
181
+ void stopAll().then(resolve, (error) => {
182
+ logger.error(error, { message: "Websocket worker cleanup failed" });
183
+ resolve();
184
+ });
185
+ });
186
+ })
187
+ );
148
188
  };
149
189
  }
150
190
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ActionsWebsocketControllerFactory.cjs","sources":["../../../../../../packages/engine-actions/src/graphql/http/ActionsWebsocketControllerFactory.ts"],"sourcesContent":["import { HttpErrorResponse, WebSocketController } from '@contember/engine-http'\nimport { ActionsContextResolver } from './ActionsContextResolver.js'\nimport { DispatchWorkerSupervisorFactory } from '../../dispatch/DispatchWorkerSupervisor.js'\nimport * as Typesafe from '@contember/typesafe'\nimport { TenantRole } from '@contember/engine-tenant-api'\nimport { Running } from '@contember/engine-common'\n\nconst IncomingMessage = Typesafe.discriminatedUnion(\n\t'type',\n\t{\n\t\tstartWorker: Typesafe.object({}),\n\t\tstopAllWorkers: Typesafe.object({}),\n\t},\n)\ntype IncomingMessage = ReturnType<typeof IncomingMessage>\nconst OutgoingMessage = Typesafe.discriminatedUnion(\n\t'type',\n\t{\n\t\terror: Typesafe.object({\n\t\t\tmessage: Typesafe.string,\n\t\t}),\n\t\tmessage: Typesafe.object({\n\t\t\tmessage: Typesafe.string,\n\t\t}),\n\t\tready: Typesafe.object({}),\n\n\t\tworkerStarted: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkedStopped: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkerFailedToStart: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkerCrashed: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t},\n)\n\ntype OutgoingMessage = ReturnType<typeof OutgoingMessage>\n\nexport class ActionsWebsocketControllerFactory {\n\tconstructor(\n\t\tprivate readonly debug: boolean,\n\t\tprivate readonly actionsContextResolver: ActionsContextResolver,\n\t\tprivate readonly dispatchWorkerSupervisorFactory: DispatchWorkerSupervisorFactory,\n\t) {\n\t}\n\n\tcreate(): WebSocketController {\n\t\treturn async ctx => {\n\t\t\tconst { ws, logger, authResult, projectGroup } = ctx\n\t\t\tif (!authResult) {\n\t\t\t\tthrow new HttpErrorResponse(401, 'Authentication required')\n\t\t\t}\n\t\t\tif (!authResult.roles.some(it => it === TenantRole.SUPER_ADMIN || it === TenantRole.PROJECT_ADMIN)) {\n\t\t\t\tthrow new HttpErrorResponse(403, 'Not allowed to run actions worker')\n\t\t\t}\n\n\t\t\tconst send = (message: OutgoingMessage) => {\n\t\t\t\tif (this.debug) {\n\t\t\t\t\tOutgoingMessage(message)\n\t\t\t\t}\n\t\t\t\tws.send(JSON.stringify(message))\n\t\t\t}\n\n\t\t\tlet workers: { id: string; running: Promise<Running> }[] = []\n\t\t\tconst abortListener = async () => {\n\t\t\t\tsend({ type: 'message', message: 'shutting down' })\n\t\t\t\tawait stopAll()\n\t\t\t\tsend({ type: 'message', message: 'closing connection' })\n\t\t\t\tws.close(1012) // Service Restart\n\t\t\t}\n\t\t\tctx.abortSignal.addEventListener('abort', abortListener)\n\n\t\t\tconst stopAll = async () => {\n\t\t\t\tconst currentWorkers = workers.map(async it => {\n\t\t\t\t\tawait (await it.running).end()\n\t\t\t\t\tsend({\n\t\t\t\t\t\ttype: 'workedStopped',\n\t\t\t\t\t\tworkerId: it.id,\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\tworkers = []\n\t\t\t\tawait Promise.all(currentWorkers)\n\t\t\t}\n\t\t\tsend({ type: 'ready' })\n\n\t\t\tws.addEventListener('message', async message => {\n\t\t\t\tlet json\n\t\t\t\ttry {\n\t\t\t\t\tjson = JSON.parse(message.data.toString())\n\t\t\t\t} catch {\n\t\t\t\t\tsend({ type: 'error', message: 'Invalid JSON' })\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tlet data: IncomingMessage\n\t\t\t\ttry {\n\t\t\t\t\tdata = IncomingMessage(json)\n\t\t\t\t} catch (e) {\n\t\t\t\t\tif (e instanceof Typesafe.ParseError) {\n\t\t\t\t\t\tsend({ type: 'error', message: e.message })\n\t\t\t\t\t\treturn\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthrow e\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tswitch (data.type) {\n\t\t\t\t\tcase 'startWorker':\n\t\t\t\t\t\tconst workerId = Math.random().toString().substring(2)\n\t\t\t\t\t\tconst dispatchSupervisor = this.dispatchWorkerSupervisorFactory.create(projectGroup)\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst running = dispatchSupervisor.run({\n\t\t\t\t\t\t\t\tlogger,\n\t\t\t\t\t\t\t\tonError: () => {\n\t\t\t\t\t\t\t\t\tsend({ type: 'workerCrashed', workerId })\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tworkers.push({ id: workerId, running })\n\t\t\t\t\t\t\tawait running\n\t\t\t\t\t\t\tsend({ type: 'workerStarted', workerId })\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\tlogger.error(e, { message: 'Worker failed to start' })\n\t\t\t\t\t\t\tsend({ type: 'workerFailedToStart', workerId })\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'stopAllWorkers':\n\t\t\t\t\t\tsend({ type: 'message', message: 'stopping' })\n\t\t\t\t\t\tawait stopAll()\n\t\t\t\t\t\tsend({ type: 'message', message: 'all stopped' })\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tconst pingHandle = setInterval(() => {\n\t\t\t\tws.ping()\n\t\t\t}, 5000)\n\n\t\t\tws.addEventListener('close', async () => {\n\t\t\t\tctx.abortSignal.removeEventListener('abort', abortListener)\n\t\t\t\tclearInterval(pingHandle)\n\t\t\t\tawait stopAll()\n\t\t\t})\n\t\t}\n\t}\n}\n"],"names":["Typesafe","HttpErrorResponse","TenantRole"],"mappings":"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"}
1
+ {"version":3,"file":"ActionsWebsocketControllerFactory.cjs","sources":["../../../../../../packages/engine-actions/src/graphql/http/ActionsWebsocketControllerFactory.ts"],"sourcesContent":["import { HttpErrorResponse, ProjectGroupContainer, WebSocketController } from '@contember/engine-http'\nimport * as Typesafe from '@contember/typesafe'\nimport { TenantRole } from '@contember/engine-tenant-api'\nimport { Runnable, Running } from '@contember/engine-common'\n\nconst IncomingMessage = Typesafe.discriminatedUnion(\n\t'type',\n\t{\n\t\tstartWorker: Typesafe.object({}),\n\t\tstopAllWorkers: Typesafe.object({}),\n\t},\n)\ntype IncomingMessage = ReturnType<typeof IncomingMessage>\nconst OutgoingMessage = Typesafe.discriminatedUnion(\n\t'type',\n\t{\n\t\terror: Typesafe.object({\n\t\t\tmessage: Typesafe.string,\n\t\t}),\n\t\tmessage: Typesafe.object({\n\t\t\tmessage: Typesafe.string,\n\t\t}),\n\t\tready: Typesafe.object({}),\n\n\t\tworkerStarted: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkedStopped: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkerFailedToStart: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkerCrashed: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t},\n)\n\ntype OutgoingMessage = ReturnType<typeof OutgoingMessage>\n\ntype DispatchWorkerFactory = {\n\tcreate(projectGroup: ProjectGroupContainer): Runnable\n}\n\nexport class ActionsWebsocketControllerFactory {\n\tconstructor(\n\t\tprivate readonly debug: boolean,\n\t\tprivate readonly dispatchWorkerSupervisorFactory: DispatchWorkerFactory,\n\t) {\n\t}\n\n\tcreate(): WebSocketController {\n\t\treturn async ctx => {\n\t\t\tconst { ws, logger, authResult, projectGroup } = ctx\n\t\t\tif (!authResult) {\n\t\t\t\tthrow new HttpErrorResponse(401, 'Authentication required')\n\t\t\t}\n\t\t\tif (!authResult.roles.some(it => it === TenantRole.SUPER_ADMIN || it === TenantRole.PROJECT_ADMIN)) {\n\t\t\t\tthrow new HttpErrorResponse(403, 'Not allowed to run actions worker')\n\t\t\t}\n\n\t\t\tconst send = (message: OutgoingMessage) => {\n\t\t\t\tif (this.debug) {\n\t\t\t\t\tOutgoingMessage(message)\n\t\t\t\t}\n\t\t\t\tws.send(JSON.stringify(message))\n\t\t\t}\n\n\t\t\tlet workers: { id: string; running: Promise<Running> }[] = []\n\t\t\tlet stopAllPromise: Promise<void> | undefined\n\t\t\tlet connectionClosing = false\n\t\t\tconst abortListener = async () => {\n\t\t\t\tconnectionClosing = true\n\t\t\t\ttry {\n\t\t\t\t\tsend({ type: 'message', message: 'shutting down' })\n\t\t\t\t\tawait stopAll()\n\t\t\t\t} catch (error) {\n\t\t\t\t\tlogger.error(error, { message: 'Websocket worker cleanup failed' })\n\t\t\t\t} finally {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tsend({ type: 'message', message: 'closing connection' })\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tws.close(1012) // Service Restart\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tctx.abortSignal.addEventListener('abort', abortListener)\n\n\t\t\tconst stopAll = (): Promise<void> => {\n\t\t\t\tstopAllPromise ??= (async () => {\n\t\t\t\t\t// a worker stays tracked until its own end() resolves, so a failed cleanup is not forgotten\n\t\t\t\t\tconst results = await Promise.allSettled(workers.map(async it => {\n\t\t\t\t\t\tawait (await it.running).end()\n\t\t\t\t\t\tworkers = workers.filter(worker => worker !== it)\n\t\t\t\t\t\tsend({\n\t\t\t\t\t\t\ttype: 'workedStopped',\n\t\t\t\t\t\t\tworkerId: it.id,\n\t\t\t\t\t\t})\n\t\t\t\t\t}))\n\t\t\t\t\tconst errors = results.flatMap(it => it.status === 'rejected' ? [it.reason] : [])\n\t\t\t\t\tif (errors.length > 0) {\n\t\t\t\t\t\tthrow new AggregateError(errors, 'Worker cleanup failed')\n\t\t\t\t\t}\n\t\t\t\t})().finally(() => {\n\t\t\t\t\tstopAllPromise = undefined\n\t\t\t\t})\n\t\t\t\treturn stopAllPromise\n\t\t\t}\n\t\t\tsend({ type: 'ready' })\n\n\t\t\tws.addEventListener('message', async message => {\n\t\t\t\tlet json\n\t\t\t\ttry {\n\t\t\t\t\tjson = JSON.parse(message.data.toString())\n\t\t\t\t} catch {\n\t\t\t\t\tsend({ type: 'error', message: 'Invalid JSON' })\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tlet data: IncomingMessage\n\t\t\t\ttry {\n\t\t\t\t\tdata = IncomingMessage(json)\n\t\t\t\t} catch (e) {\n\t\t\t\t\tif (e instanceof Typesafe.ParseError) {\n\t\t\t\t\t\tsend({ type: 'error', message: e.message })\n\t\t\t\t\t\treturn\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthrow e\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tswitch (data.type) {\n\t\t\t\t\tcase 'startWorker':\n\t\t\t\t\t\tif (connectionClosing || stopAllPromise !== undefined) {\n\t\t\t\t\t\t\tsend({ type: 'error', message: 'Workers are stopping' })\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst workerId = Math.random().toString().substring(2)\n\t\t\t\t\t\tconst dispatchSupervisor = this.dispatchWorkerSupervisorFactory.create(projectGroup)\n\t\t\t\t\t\tconst running = Promise.resolve().then(() =>\n\t\t\t\t\t\t\tdispatchSupervisor.run({\n\t\t\t\t\t\t\t\tlogger,\n\t\t\t\t\t\t\t\tonError: () => {\n\t\t\t\t\t\t\t\t\tsend({ type: 'workerCrashed', workerId })\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t)\n\t\t\t\t\t\tconst worker = { id: workerId, running }\n\t\t\t\t\t\tworkers.push(worker)\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait running\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\tworkers = workers.filter(it => it !== worker)\n\t\t\t\t\t\t\tlogger.error(e, { message: 'Worker failed to start' })\n\t\t\t\t\t\t\tsend({ type: 'workerFailedToStart', workerId })\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsend({ type: 'workerStarted', workerId })\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'stopAllWorkers':\n\t\t\t\t\t\tsend({ type: 'message', message: 'stopping' })\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait stopAll()\n\t\t\t\t\t\t\tsend({ type: 'message', message: 'all stopped' })\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tlogger.error(error, { message: 'Websocket worker cleanup failed' })\n\t\t\t\t\t\t\tsend({ type: 'error', message: 'Worker cleanup failed' })\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tconst pingHandle = setInterval(() => {\n\t\t\t\tws.ping()\n\t\t\t}, 5000)\n\n\t\t\tctx.waitUntil(\n\t\t\t\tnew Promise<void>(resolve => {\n\t\t\t\t\tws.addEventListener('close', () => {\n\t\t\t\t\t\tconnectionClosing = true\n\t\t\t\t\t\tctx.abortSignal.removeEventListener('abort', abortListener)\n\t\t\t\t\t\tclearInterval(pingHandle)\n\t\t\t\t\t\tvoid stopAll().then(resolve, error => {\n\t\t\t\t\t\t\tlogger.error(error, { message: 'Websocket worker cleanup failed' })\n\t\t\t\t\t\t\tresolve()\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\t}\n}\n"],"names":["Typesafe","HttpErrorResponse","TenantRole"],"mappings":"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"}
@@ -33,9 +33,8 @@ const OutgoingMessage = Typesafe.discriminatedUnion(
33
33
  }
34
34
  );
35
35
  class ActionsWebsocketControllerFactory {
36
- constructor(debug, actionsContextResolver, dispatchWorkerSupervisorFactory) {
36
+ constructor(debug, dispatchWorkerSupervisorFactory) {
37
37
  this.debug = debug;
38
- this.actionsContextResolver = actionsContextResolver;
39
38
  this.dispatchWorkerSupervisorFactory = dispatchWorkerSupervisorFactory;
40
39
  }
41
40
  create() {
@@ -54,23 +53,42 @@ class ActionsWebsocketControllerFactory {
54
53
  ws.send(JSON.stringify(message));
55
54
  };
56
55
  let workers = [];
56
+ let stopAllPromise;
57
+ let connectionClosing = false;
57
58
  const abortListener = async () => {
58
- send({ type: "message", message: "shutting down" });
59
- await stopAll();
60
- send({ type: "message", message: "closing connection" });
61
- ws.close(1012);
59
+ connectionClosing = true;
60
+ try {
61
+ send({ type: "message", message: "shutting down" });
62
+ await stopAll();
63
+ } catch (error) {
64
+ logger.error(error, { message: "Websocket worker cleanup failed" });
65
+ } finally {
66
+ try {
67
+ send({ type: "message", message: "closing connection" });
68
+ } finally {
69
+ ws.close(1012);
70
+ }
71
+ }
62
72
  };
63
73
  ctx.abortSignal.addEventListener("abort", abortListener);
64
- const stopAll = async () => {
65
- const currentWorkers = workers.map(async (it) => {
66
- await (await it.running).end();
67
- send({
68
- type: "workedStopped",
69
- workerId: it.id
70
- });
71
- });
72
- workers = [];
73
- await Promise.all(currentWorkers);
74
+ const stopAll = () => {
75
+ stopAllPromise ?? (stopAllPromise = (async () => {
76
+ const results = await Promise.allSettled(workers.map(async (it) => {
77
+ await (await it.running).end();
78
+ workers = workers.filter((worker) => worker !== it);
79
+ send({
80
+ type: "workedStopped",
81
+ workerId: it.id
82
+ });
83
+ }));
84
+ const errors = results.flatMap((it) => it.status === "rejected" ? [it.reason] : []);
85
+ if (errors.length > 0) {
86
+ throw new AggregateError(errors, "Worker cleanup failed");
87
+ }
88
+ })().finally(() => {
89
+ stopAllPromise = void 0;
90
+ }));
91
+ return stopAllPromise;
74
92
  };
75
93
  send({ type: "ready" });
76
94
  ws.addEventListener("message", async (message) => {
@@ -94,38 +112,60 @@ class ActionsWebsocketControllerFactory {
94
112
  }
95
113
  switch (data.type) {
96
114
  case "startWorker":
115
+ if (connectionClosing || stopAllPromise !== void 0) {
116
+ send({ type: "error", message: "Workers are stopping" });
117
+ break;
118
+ }
97
119
  const workerId = Math.random().toString().substring(2);
98
120
  const dispatchSupervisor = this.dispatchWorkerSupervisorFactory.create(projectGroup);
99
- try {
100
- const running = dispatchSupervisor.run({
121
+ const running = Promise.resolve().then(
122
+ () => dispatchSupervisor.run({
101
123
  logger,
102
124
  onError: () => {
103
125
  send({ type: "workerCrashed", workerId });
104
126
  }
105
- });
106
- workers.push({ id: workerId, running });
127
+ })
128
+ );
129
+ const worker = { id: workerId, running };
130
+ workers.push(worker);
131
+ try {
107
132
  await running;
108
- send({ type: "workerStarted", workerId });
109
133
  } catch (e) {
134
+ workers = workers.filter((it) => it !== worker);
110
135
  logger.error(e, { message: "Worker failed to start" });
111
136
  send({ type: "workerFailedToStart", workerId });
137
+ break;
112
138
  }
139
+ send({ type: "workerStarted", workerId });
113
140
  break;
114
141
  case "stopAllWorkers":
115
142
  send({ type: "message", message: "stopping" });
116
- await stopAll();
117
- send({ type: "message", message: "all stopped" });
143
+ try {
144
+ await stopAll();
145
+ send({ type: "message", message: "all stopped" });
146
+ } catch (error) {
147
+ logger.error(error, { message: "Websocket worker cleanup failed" });
148
+ send({ type: "error", message: "Worker cleanup failed" });
149
+ }
118
150
  break;
119
151
  }
120
152
  });
121
153
  const pingHandle = setInterval(() => {
122
154
  ws.ping();
123
155
  }, 5e3);
124
- ws.addEventListener("close", async () => {
125
- ctx.abortSignal.removeEventListener("abort", abortListener);
126
- clearInterval(pingHandle);
127
- await stopAll();
128
- });
156
+ ctx.waitUntil(
157
+ new Promise((resolve) => {
158
+ ws.addEventListener("close", () => {
159
+ connectionClosing = true;
160
+ ctx.abortSignal.removeEventListener("abort", abortListener);
161
+ clearInterval(pingHandle);
162
+ void stopAll().then(resolve, (error) => {
163
+ logger.error(error, { message: "Websocket worker cleanup failed" });
164
+ resolve();
165
+ });
166
+ });
167
+ })
168
+ );
129
169
  };
130
170
  }
131
171
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ActionsWebsocketControllerFactory.js","sources":["../../../../../../packages/engine-actions/src/graphql/http/ActionsWebsocketControllerFactory.ts"],"sourcesContent":["import { HttpErrorResponse, WebSocketController } from '@contember/engine-http'\nimport { ActionsContextResolver } from './ActionsContextResolver.js'\nimport { DispatchWorkerSupervisorFactory } from '../../dispatch/DispatchWorkerSupervisor.js'\nimport * as Typesafe from '@contember/typesafe'\nimport { TenantRole } from '@contember/engine-tenant-api'\nimport { Running } from '@contember/engine-common'\n\nconst IncomingMessage = Typesafe.discriminatedUnion(\n\t'type',\n\t{\n\t\tstartWorker: Typesafe.object({}),\n\t\tstopAllWorkers: Typesafe.object({}),\n\t},\n)\ntype IncomingMessage = ReturnType<typeof IncomingMessage>\nconst OutgoingMessage = Typesafe.discriminatedUnion(\n\t'type',\n\t{\n\t\terror: Typesafe.object({\n\t\t\tmessage: Typesafe.string,\n\t\t}),\n\t\tmessage: Typesafe.object({\n\t\t\tmessage: Typesafe.string,\n\t\t}),\n\t\tready: Typesafe.object({}),\n\n\t\tworkerStarted: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkedStopped: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkerFailedToStart: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkerCrashed: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t},\n)\n\ntype OutgoingMessage = ReturnType<typeof OutgoingMessage>\n\nexport class ActionsWebsocketControllerFactory {\n\tconstructor(\n\t\tprivate readonly debug: boolean,\n\t\tprivate readonly actionsContextResolver: ActionsContextResolver,\n\t\tprivate readonly dispatchWorkerSupervisorFactory: DispatchWorkerSupervisorFactory,\n\t) {\n\t}\n\n\tcreate(): WebSocketController {\n\t\treturn async ctx => {\n\t\t\tconst { ws, logger, authResult, projectGroup } = ctx\n\t\t\tif (!authResult) {\n\t\t\t\tthrow new HttpErrorResponse(401, 'Authentication required')\n\t\t\t}\n\t\t\tif (!authResult.roles.some(it => it === TenantRole.SUPER_ADMIN || it === TenantRole.PROJECT_ADMIN)) {\n\t\t\t\tthrow new HttpErrorResponse(403, 'Not allowed to run actions worker')\n\t\t\t}\n\n\t\t\tconst send = (message: OutgoingMessage) => {\n\t\t\t\tif (this.debug) {\n\t\t\t\t\tOutgoingMessage(message)\n\t\t\t\t}\n\t\t\t\tws.send(JSON.stringify(message))\n\t\t\t}\n\n\t\t\tlet workers: { id: string; running: Promise<Running> }[] = []\n\t\t\tconst abortListener = async () => {\n\t\t\t\tsend({ type: 'message', message: 'shutting down' })\n\t\t\t\tawait stopAll()\n\t\t\t\tsend({ type: 'message', message: 'closing connection' })\n\t\t\t\tws.close(1012) // Service Restart\n\t\t\t}\n\t\t\tctx.abortSignal.addEventListener('abort', abortListener)\n\n\t\t\tconst stopAll = async () => {\n\t\t\t\tconst currentWorkers = workers.map(async it => {\n\t\t\t\t\tawait (await it.running).end()\n\t\t\t\t\tsend({\n\t\t\t\t\t\ttype: 'workedStopped',\n\t\t\t\t\t\tworkerId: it.id,\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\tworkers = []\n\t\t\t\tawait Promise.all(currentWorkers)\n\t\t\t}\n\t\t\tsend({ type: 'ready' })\n\n\t\t\tws.addEventListener('message', async message => {\n\t\t\t\tlet json\n\t\t\t\ttry {\n\t\t\t\t\tjson = JSON.parse(message.data.toString())\n\t\t\t\t} catch {\n\t\t\t\t\tsend({ type: 'error', message: 'Invalid JSON' })\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tlet data: IncomingMessage\n\t\t\t\ttry {\n\t\t\t\t\tdata = IncomingMessage(json)\n\t\t\t\t} catch (e) {\n\t\t\t\t\tif (e instanceof Typesafe.ParseError) {\n\t\t\t\t\t\tsend({ type: 'error', message: e.message })\n\t\t\t\t\t\treturn\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthrow e\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tswitch (data.type) {\n\t\t\t\t\tcase 'startWorker':\n\t\t\t\t\t\tconst workerId = Math.random().toString().substring(2)\n\t\t\t\t\t\tconst dispatchSupervisor = this.dispatchWorkerSupervisorFactory.create(projectGroup)\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst running = dispatchSupervisor.run({\n\t\t\t\t\t\t\t\tlogger,\n\t\t\t\t\t\t\t\tonError: () => {\n\t\t\t\t\t\t\t\t\tsend({ type: 'workerCrashed', workerId })\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tworkers.push({ id: workerId, running })\n\t\t\t\t\t\t\tawait running\n\t\t\t\t\t\t\tsend({ type: 'workerStarted', workerId })\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\tlogger.error(e, { message: 'Worker failed to start' })\n\t\t\t\t\t\t\tsend({ type: 'workerFailedToStart', workerId })\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'stopAllWorkers':\n\t\t\t\t\t\tsend({ type: 'message', message: 'stopping' })\n\t\t\t\t\t\tawait stopAll()\n\t\t\t\t\t\tsend({ type: 'message', message: 'all stopped' })\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tconst pingHandle = setInterval(() => {\n\t\t\t\tws.ping()\n\t\t\t}, 5000)\n\n\t\t\tws.addEventListener('close', async () => {\n\t\t\t\tctx.abortSignal.removeEventListener('abort', abortListener)\n\t\t\t\tclearInterval(pingHandle)\n\t\t\t\tawait stopAll()\n\t\t\t})\n\t\t}\n\t}\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ActionsWebsocketControllerFactory.js","sources":["../../../../../../packages/engine-actions/src/graphql/http/ActionsWebsocketControllerFactory.ts"],"sourcesContent":["import { HttpErrorResponse, ProjectGroupContainer, WebSocketController } from '@contember/engine-http'\nimport * as Typesafe from '@contember/typesafe'\nimport { TenantRole } from '@contember/engine-tenant-api'\nimport { Runnable, Running } from '@contember/engine-common'\n\nconst IncomingMessage = Typesafe.discriminatedUnion(\n\t'type',\n\t{\n\t\tstartWorker: Typesafe.object({}),\n\t\tstopAllWorkers: Typesafe.object({}),\n\t},\n)\ntype IncomingMessage = ReturnType<typeof IncomingMessage>\nconst OutgoingMessage = Typesafe.discriminatedUnion(\n\t'type',\n\t{\n\t\terror: Typesafe.object({\n\t\t\tmessage: Typesafe.string,\n\t\t}),\n\t\tmessage: Typesafe.object({\n\t\t\tmessage: Typesafe.string,\n\t\t}),\n\t\tready: Typesafe.object({}),\n\n\t\tworkerStarted: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkedStopped: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkerFailedToStart: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkerCrashed: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t},\n)\n\ntype OutgoingMessage = ReturnType<typeof OutgoingMessage>\n\ntype DispatchWorkerFactory = {\n\tcreate(projectGroup: ProjectGroupContainer): Runnable\n}\n\nexport class ActionsWebsocketControllerFactory {\n\tconstructor(\n\t\tprivate readonly debug: boolean,\n\t\tprivate readonly dispatchWorkerSupervisorFactory: DispatchWorkerFactory,\n\t) {\n\t}\n\n\tcreate(): WebSocketController {\n\t\treturn async ctx => {\n\t\t\tconst { ws, logger, authResult, projectGroup } = ctx\n\t\t\tif (!authResult) {\n\t\t\t\tthrow new HttpErrorResponse(401, 'Authentication required')\n\t\t\t}\n\t\t\tif (!authResult.roles.some(it => it === TenantRole.SUPER_ADMIN || it === TenantRole.PROJECT_ADMIN)) {\n\t\t\t\tthrow new HttpErrorResponse(403, 'Not allowed to run actions worker')\n\t\t\t}\n\n\t\t\tconst send = (message: OutgoingMessage) => {\n\t\t\t\tif (this.debug) {\n\t\t\t\t\tOutgoingMessage(message)\n\t\t\t\t}\n\t\t\t\tws.send(JSON.stringify(message))\n\t\t\t}\n\n\t\t\tlet workers: { id: string; running: Promise<Running> }[] = []\n\t\t\tlet stopAllPromise: Promise<void> | undefined\n\t\t\tlet connectionClosing = false\n\t\t\tconst abortListener = async () => {\n\t\t\t\tconnectionClosing = true\n\t\t\t\ttry {\n\t\t\t\t\tsend({ type: 'message', message: 'shutting down' })\n\t\t\t\t\tawait stopAll()\n\t\t\t\t} catch (error) {\n\t\t\t\t\tlogger.error(error, { message: 'Websocket worker cleanup failed' })\n\t\t\t\t} finally {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tsend({ type: 'message', message: 'closing connection' })\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tws.close(1012) // Service Restart\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tctx.abortSignal.addEventListener('abort', abortListener)\n\n\t\t\tconst stopAll = (): Promise<void> => {\n\t\t\t\tstopAllPromise ??= (async () => {\n\t\t\t\t\t// a worker stays tracked until its own end() resolves, so a failed cleanup is not forgotten\n\t\t\t\t\tconst results = await Promise.allSettled(workers.map(async it => {\n\t\t\t\t\t\tawait (await it.running).end()\n\t\t\t\t\t\tworkers = workers.filter(worker => worker !== it)\n\t\t\t\t\t\tsend({\n\t\t\t\t\t\t\ttype: 'workedStopped',\n\t\t\t\t\t\t\tworkerId: it.id,\n\t\t\t\t\t\t})\n\t\t\t\t\t}))\n\t\t\t\t\tconst errors = results.flatMap(it => it.status === 'rejected' ? [it.reason] : [])\n\t\t\t\t\tif (errors.length > 0) {\n\t\t\t\t\t\tthrow new AggregateError(errors, 'Worker cleanup failed')\n\t\t\t\t\t}\n\t\t\t\t})().finally(() => {\n\t\t\t\t\tstopAllPromise = undefined\n\t\t\t\t})\n\t\t\t\treturn stopAllPromise\n\t\t\t}\n\t\t\tsend({ type: 'ready' })\n\n\t\t\tws.addEventListener('message', async message => {\n\t\t\t\tlet json\n\t\t\t\ttry {\n\t\t\t\t\tjson = JSON.parse(message.data.toString())\n\t\t\t\t} catch {\n\t\t\t\t\tsend({ type: 'error', message: 'Invalid JSON' })\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tlet data: IncomingMessage\n\t\t\t\ttry {\n\t\t\t\t\tdata = IncomingMessage(json)\n\t\t\t\t} catch (e) {\n\t\t\t\t\tif (e instanceof Typesafe.ParseError) {\n\t\t\t\t\t\tsend({ type: 'error', message: e.message })\n\t\t\t\t\t\treturn\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthrow e\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tswitch (data.type) {\n\t\t\t\t\tcase 'startWorker':\n\t\t\t\t\t\tif (connectionClosing || stopAllPromise !== undefined) {\n\t\t\t\t\t\t\tsend({ type: 'error', message: 'Workers are stopping' })\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst workerId = Math.random().toString().substring(2)\n\t\t\t\t\t\tconst dispatchSupervisor = this.dispatchWorkerSupervisorFactory.create(projectGroup)\n\t\t\t\t\t\tconst running = Promise.resolve().then(() =>\n\t\t\t\t\t\t\tdispatchSupervisor.run({\n\t\t\t\t\t\t\t\tlogger,\n\t\t\t\t\t\t\t\tonError: () => {\n\t\t\t\t\t\t\t\t\tsend({ type: 'workerCrashed', workerId })\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t)\n\t\t\t\t\t\tconst worker = { id: workerId, running }\n\t\t\t\t\t\tworkers.push(worker)\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait running\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\tworkers = workers.filter(it => it !== worker)\n\t\t\t\t\t\t\tlogger.error(e, { message: 'Worker failed to start' })\n\t\t\t\t\t\t\tsend({ type: 'workerFailedToStart', workerId })\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsend({ type: 'workerStarted', workerId })\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'stopAllWorkers':\n\t\t\t\t\t\tsend({ type: 'message', message: 'stopping' })\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait stopAll()\n\t\t\t\t\t\t\tsend({ type: 'message', message: 'all stopped' })\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tlogger.error(error, { message: 'Websocket worker cleanup failed' })\n\t\t\t\t\t\t\tsend({ type: 'error', message: 'Worker cleanup failed' })\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tconst pingHandle = setInterval(() => {\n\t\t\t\tws.ping()\n\t\t\t}, 5000)\n\n\t\t\tctx.waitUntil(\n\t\t\t\tnew Promise<void>(resolve => {\n\t\t\t\t\tws.addEventListener('close', () => {\n\t\t\t\t\t\tconnectionClosing = true\n\t\t\t\t\t\tctx.abortSignal.removeEventListener('abort', abortListener)\n\t\t\t\t\t\tclearInterval(pingHandle)\n\t\t\t\t\t\tvoid stopAll().then(resolve, error => {\n\t\t\t\t\t\t\tlogger.error(error, { message: 'Websocket worker cleanup failed' })\n\t\t\t\t\t\t\tresolve()\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\t}\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=actionsWebsocketController.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actionsWebsocketController.test.d.ts","sourceRoot":"","sources":["../../../../tests/cases/unit/actionsWebsocketController.test.ts"],"names":[],"mappings":""}