diff --git a/package-lock.json b/package-lock.json index 461a47b..1ea21d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3979,7 +3979,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, "license": "MIT" }, "node_modules/@storybook/addon-a11y": { @@ -7219,6 +7218,16 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/effect": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.21.0.tgz", + "integrity": "sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.332", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.332.tgz", @@ -8118,6 +8127,44 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fast-check/node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -15987,6 +16034,7 @@ "better-sqlite3": "^12.8.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.4", + "effect": "^3.21.0", "jsdom": "^29.0.2", "playwright": "^1.59.1", "reflect-metadata": "^0.2.2", diff --git a/server/package.json b/server/package.json index 9a2dbbc..2b3a616 100644 --- a/server/package.json +++ b/server/package.json @@ -32,6 +32,7 @@ "better-sqlite3": "^12.8.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.4", + "effect": "^3.21.0", "jsdom": "^29.0.2", "playwright": "^1.59.1", "reflect-metadata": "^0.2.2", diff --git a/server/src/scenario/scenario-scheduler.service.ts b/server/src/scenario/scenario-scheduler.service.ts index 4188c7f..e2e0513 100644 --- a/server/src/scenario/scenario-scheduler.service.ts +++ b/server/src/scenario/scenario-scheduler.service.ts @@ -2,6 +2,7 @@ import { Injectable, OnModuleInit } from "@nestjs/common"; import { Interval } from "@nestjs/schedule"; import { InjectRepository } from "@nestjs/typeorm"; import * as crypto from "crypto"; +import { Effect } from "effect"; import type { Browser, BrowserContext, Page } from "playwright"; import { chromium } from "playwright"; import { Repository } from "typeorm"; @@ -76,11 +77,29 @@ export class ScenarioSchedulerService implements OnModuleInit { this.logger.error( `Run #${run.id}: marked fail due to server restart (playwright session lost)`, ); - await this.failRun( - run.id, - "Run aborted: server restarted and playwright session could not be recovered", - ); } + + await Effect.runPromise( + Effect.forEach( + staleRuns, + (run) => + Effect.tryPromise(() => + this.failRun( + run.id, + "Run aborted: server restarted and playwright session could not be recovered", + ), + ).pipe( + Effect.catchAll((err) => + Effect.sync(() => + this.logger.error( + `Run #${run.id}: failRun threw unexpectedly — ${String(err)}`, + ), + ), + ), + ), + { concurrency: 10 }, + ), + ); } /**